{
  "name": "Solana Price Alert — get notified when a token crosses a threshold",
  "nodes": [
    {
      "parameters": {
        "rule": { "interval": [{ "field": "minutes", "minutesInterval": 5 }] }
      },
      "id": "trigger-5m",
      "name": "Every 5 minutes",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [0, 0]
    },
    {
      "parameters": {
        "url": "=https://price.jup.ag/v6/price?ids={{ $env.TOKEN_MINT || 'So11111111111111111111111111111111111111112' }}",
        "options": { "timeout": 15000 }
      },
      "id": "fetch-price",
      "name": "Current price (Jupiter)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [220, 0]
    },
    {
      "parameters": {
        "jsCode": "// Fires only on a STATE CHANGE (not-past -> past threshold), not every run: no cooldown to configure.\nconst mint = $env.TOKEN_MINT || 'So11111111111111111111111111111111111111112';\nconst body = $input.first().json;\nconst entry = (body.data && body.data[mint]) || null;\nif (!entry) return [{ json: { fire: false, error: 'price not found for ' + mint } }];\nconst price = Number(entry.price);\nconst threshold = Number($env.PRICE_THRESHOLD || 0);\nconst direction = ($env.DIRECTION || 'above').toLowerCase();\nconst past = direction === 'below' ? price <= threshold : price >= threshold;\nconst state = $getWorkflowStaticData('global');\nconst wasPast = !!state.past;\nconst fire = past && !wasPast;\nstate.past = past;\nreturn [{ json: { fire, price, threshold, direction, mint } }];"
      },
      "id": "valuta-soglia",
      "name": "Evaluate threshold change",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [440, 0]
    },
    {
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" },
          "conditions": [
            { "leftValue": "={{ $json.fire }}", "rightValue": true, "operator": { "type": "boolean", "operation": "true" } }
          ],
          "combinator": "and"
        }
      },
      "id": "if-scatta",
      "name": "Should alert?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [660, 0]
    },
    {
      "parameters": {
        "chatId": "={{ $env.TELEGRAM_CHAT_ID || 'SET_CHAT_ID' }}",
        "text": "=📈 Token {{ $json.mint }} price: {{ $json.price }} USD — crossed the {{ $json.direction }} threshold {{ $json.threshold }}.",
        "additionalFields": {}
      },
      "id": "notify-telegram",
      "name": "Notify on Telegram",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [880, 0],
      "credentials": {
        "telegramApi": { "id": "CONFIGURE_IN_N8N", "name": "Telegram bot (create the credential in n8n)" }
      }
    }
  ],
  "connections": {
    "Every 5 minutes": { "main": [[{ "node": "Current price (Jupiter)", "type": "main", "index": 0 }]] },
    "Current price (Jupiter)": { "main": [[{ "node": "Evaluate threshold change", "type": "main", "index": 0 }]] },
    "Evaluate threshold change": { "main": [[{ "node": "Should alert?", "type": "main", "index": 0 }]] },
    "Should alert?": { "main": [[{ "node": "Notify on Telegram", "type": "main", "index": 0 }]] }
  },
  "settings": { "executionOrder": "v1" },
  "meta": {
    "note": "Setup (5 min): 1) TOKEN_MINT = the SPL mint address to watch; 2) PRICE_THRESHOLD = the USD price that triggers the alert; 3) DIRECTION = 'above' (default) or 'below'; 4) create a Telegram bot with @BotFather and attach the credential to the 'Notify on Telegram' node; 5) TELEGRAM_CHAT_ID = your chat id; 6) activate. Fires only on a state change (not-past -> past threshold), does not repeat every run while still past threshold: no cooldown to configure. Uses the public Jupiter API, no API key required."
  }
}
