{
  "id": "tgUy8qbiSc3uSHuX",
  "name": "\ud83e\udd16 AgentGatePay - Buyer Agent MCP [TEMPLATE]",
  "tags": [],
  "nodes": [
    {
      "id": "92e66d1d-f0f5-4d1e-96cd-aec1f1916e7d",
      "name": "\u25b6\ufe0f START",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        608,
        336
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "5d3971b4-3854-40d0-b967-51c245a8dec1",
      "name": "1\ufe0f\u20e3 Load Config",
      "type": "n8n-nodes-base.code",
      "position": [
        816,
        336
      ],
      "parameters": {
        "jsCode": "const CONFIG = {\n  buyer: {\n    name: \"Research Assistant AI\",\n    company: \"Your Company\",\n    email: \"user@example.com\",  // \u26a0\ufe0f REPLACE: Your buyer agent email\n    task: \"Analyze top 5 competitors in SaaS market\",\n    api_key: \"YOUR_AGENTGATEPAY_API_KEY\",  // \u26a0\ufe0f REPLACE: From AgentGatePay signup\n    budget_usd: 100,\n    mandate_ttl_days: 7,\n    mandate_scope: \"resource.read payment.execute\"\n  },\n  seller: {\n    name: \"DataBot Pro\",\n    company: \"MarketInsights AI Ltd.\",\n    email: \"user@example.com\",\n    service: \"Premium Market Research Reports\",\n    api_url: \"https://YOUR_N8N.app.n8n.cloud/webhook/seller-resource-api-test\",  // \u26a0\ufe0f REPLACE: Seller webhook URL\n    selected_resource_id: \"saas-competitors-2025\"\n  },\n  render: {\n    service_url: \"https://YOUR_RENDER_APP.onrender.com\"\n  },\n  blockchain: {\n    chain: \"base\",\n    token: \"USDC\",\n    rpc_url: \"https://mainnet.base.org\"\n  },\n  agentgatepay: {\n    api_url: \"https://api.agentgatepay.com\",\n    mcp_endpoint: \"https://mcp.agentgatepay.com\"\n  }\n};\n\nreturn [{json: {config: CONFIG, session: {id: `session_${Date.now()}`, started_at: new Date().toISOString(), agent: CONFIG.buyer.email}}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "f9512e04-334e-4d81-9e99-90c3d89c9a3f",
      "name": "2\ufe0f\u20e3 \ud83d\udcca Get Mandate Token",
      "type": "n8n-nodes-base.dataTable",
      "notes": "\ud83d\udd0d CHECK: Does mandate exist?\n\n\u26a0\ufe0f CONFIGURE: You MUST select 'AgentPay_Mandates' from dropdown!\n\nClick this node \u2192 Data table dropdown \u2192 Select AgentPay_Mandates \u2192 Save",
      "position": [
        1024,
        336
      ],
      "parameters": {
        "operation": "get",
        "returnAll": true,
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "0RVPG9hWFRYnyUw7",
          "cachedResultUrl": "/projects/GLZPTknscCLLXTgj/datatables/0RVPG9hWFRYnyUw7",
          "cachedResultName": "AgentPay_Mandates"
        }
      },
      "typeVersion": 1,
      "continueOnFail": true,
      "alwaysOutputData": true
    },
    {
      "id": "64fe4d58-91f7-41fd-a5a0-87ec24b44e6d",
      "name": "2B\ufe0f\u20e3 Normalize Result",
      "type": "n8n-nodes-base.code",
      "notes": "\ud83d\udd04 NORMALIZE: Handle empty table\n\nIf table empty \u2192 Pass { mandate_token: null }\nIf table has row \u2192 Pass the row data\n\nThis ensures Node 3 always receives data!",
      "position": [
        1232,
        336
      ],
      "parameters": {
        "jsCode": "// Normalize Data Table output\n// When table is empty, create empty object\n// When table has row, pass it through\n\nconst config = $('1\ufe0f\u20e3 Load Config').first().json.config;\n\nif ($input.all().length === 0) {\n  // Table is empty - return EMPTY STRING (not null)\n  console.log('\ud83d\udcca Data Table is empty - returning empty string');\n  return [{ json: { mandate_token: '', config: config } }];\n}\n\n// Table has data - pass it through with config\nconst allRows = $input.all();\nconsole.log(`\ud83d\udcca Data Table returned ${allRows.length} row(s)`);\n\nif (allRows.length > 1) {\n  console.warn('\u26a0\ufe0f WARNING: Multiple rows found! Using first row only.');\n}\n\nconst tableData = $input.first().json;\nconsole.log(`   mandate_token: ${tableData.mandate_token ? tableData.mandate_token.substring(0, 30) + '...' : 'EMPTY/NULL'}`);\n\nreturn [{ json: { ...tableData, config: config } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "2b833bcc-d396-4680-b313-0a1d4941cb74",
      "name": "3\ufe0f\u20e3 Has Token?",
      "type": "n8n-nodes-base.if",
      "notes": "\ud83d\udd00 ROUTER:\n\nTRUE \u2192 Token exists \u2192 Verify it\nFALSE \u2192 No token \u2192 Create new mandate",
      "position": [
        1440,
        336
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "has-token",
              "operator": {
                "type": "string",
                "operation": "notEmpty"
              },
              "leftValue": "={{ $json.mandate_token }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "09934b2c-4f43-4d94-9952-93f22fd20353",
      "name": "4\ufe0f\u20e3 \u2705 Verify Existing Token",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "\ud83d\udd12 ASK GATEWAY: Is this token still valid?\n\nGateway checks:\n\u2705 Signature valid?\n\u2705 Not expired?\n\u2705 Budget remaining?\n\u2705 Scope correct?\n\nGateway = Source of Truth!",
      "position": [
        1696,
        192
      ],
      "parameters": {
        "url": "={{ $('1\ufe0f\u20e3 Load Config').first().json.config.agentgatepay.mcp_endpoint }}",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"jsonrpc\": \"2.0\",\n  \"id\": 3,\n  \"method\": \"tools/call\",\n  \"params\": {\n    \"name\": \"agentpay_verify_mandate\",\n    \"arguments\": {\n      \"mandate_token\": \"{{ $json.mandate_token }}\"\n    }\n  }\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "x-api-key",
              "value": "={{ $('1\ufe0f\u20e3 Load Config').first().json.config.buyer.api_key }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "9e9e0ede-81f8-472b-9364-5069045cfbf3",
      "name": "4B\ufe0f\u20e3 Check Verification",
      "type": "n8n-nodes-base.code",
      "notes": "\ud83d\udd0d PARSE GATEWAY RESPONSE:\n\n\u2705 Valid \u2192 Continue with existing mandate\n\u274c Invalid \u2192 ERROR with clear renewal instructions\n\nNEVER auto-renews! Security by design!",
      "position": [
        1904,
        192
      ],
      "parameters": {
        "jsCode": "const verify_response = $input.first().json;\nconst config = $('1\ufe0f\u20e3 Load Config').first().json.config;\nconst stored_token = $('2\ufe0f\u20e3 \ud83d\udcca Get Mandate Token').first().json.mandate_token;\n\n// Parse MCP response\nif (!verify_response.result || !verify_response.result.content) {\n  throw new Error('\u274c Gateway verification failed - invalid response');\n}\n\nconst text = verify_response.result.content[0].text;\nconst verification = JSON.parse(text);\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// \ud83d\udea8 MANDATE EXPIRED OR INVALID - SHOW CLEAR INSTRUCTIONS\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nif (!verification.valid) {\n  const errorMsg = `\n\ud83d\udea8 MANDATE EXPIRED OR INVALID!\n\n${verification.error || 'Mandate is no longer valid'}\n\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\ud83d\udccb TO CREATE NEW MANDATE (2 STEPS):\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\n1\ufe0f\u20e3 Delete the token from storage:\n   \u2022 Go to: Data \u2192 AgentPay_Mandates\n   \u2022 Click the row\n   \u2022 Click Delete (trash icon)\n   \u2022 Click Confirm\n\n2\ufe0f\u20e3 Run this workflow again:\n   \u2022 Will automatically create fresh mandate\n   \u2022 New $${config.buyer.budget_usd} budget allocated\n   \u2022 7-day validity period\n\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\n\ud83d\udca1 WHY THIS HAPPENS:\nMandates have limited budget/time by design.\nThis is a SECURITY FEATURE to prevent unlimited spending.\n\nWhen budget depleted or time expired, you must\nmanually approve a new mandate.\n\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n  `;\n  \n  throw new Error(errorMsg);\n}\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// \u2705 MANDATE VALID - CONTINUE WITH EXISTING TOKEN\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nconsole.log('\u267b\ufe0f REUSING EXISTING MANDATE!');\nconsole.log(`   Budget Remaining: $${verification.budget_remaining}`);\nconsole.log(`   TTL Remaining: ${verification.ttl_remaining_hours} hours`);\nconsole.log(`   Status: ${verification.status}`);\n\nconst mandate = {\n  token: stored_token,\n  id: verification.mandate_id || 'unknown',\n  budget_remaining: verification.budget_remaining,\n  ttl_hours: verification.ttl_remaining_hours,\n  status: verification.status\n};\n\nreturn [{\n  json: {\n    config,\n    mandate,\n    verification,\n    was_reused: true,\n    source: 'existing_token',\n    message: `\u267b\ufe0f REUSING MANDATE!\\n\\nBudget: $${verification.budget_remaining}\\nTTL: ${verification.ttl_remaining_hours} hours\\nStatus: ${verification.status}`\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "075f1847-da02-4a80-907a-0c978ea1d619",
      "name": "5\ufe0f\u20e3 \ud83c\udd95 Create New Mandate",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "\ud83c\udd95 CREATE: New mandate via Gateway API\n\nOnly runs when:\n- First time (no token in table)\n- OR user deleted token (manual renewal)\n\nNEVER auto-creates on expiry!",
      "position": [
        1696,
        480
      ],
      "parameters": {
        "url": "={{ $('1\ufe0f\u20e3 Load Config').first().json.config.agentgatepay.mcp_endpoint }}",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"method\": \"tools/call\",\n  \"params\": {\n    \"name\": \"agentpay_issue_mandate\",\n    \"arguments\": {\n      \"subject\": \"{{ $('1\ufe0f\u20e3 Load Config').first().json.config.buyer.email }}\",\n      \"budget_usd\": {{ $('1\ufe0f\u20e3 Load Config').first().json.config.buyer.budget_usd }},\n      \"scope\": \"{{ $('1\ufe0f\u20e3 Load Config').first().json.config.buyer.mandate_scope }}\",\n      \"ttl_minutes\": {{ $('1\ufe0f\u20e3 Load Config').first().json.config.buyer.mandate_ttl_days * 1440 }}\n    }\n  }\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "x-api-key",
              "value": "={{ $('1\ufe0f\u20e3 Load Config').first().json.config.buyer.api_key }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "8ae0aef5-0581-45f9-8aed-ee291f01fe6c",
      "name": "5B\ufe0f\u20e3 Parse New Mandate",
      "type": "n8n-nodes-base.code",
      "notes": "\ud83d\udce6 EXTRACT: Token from API response\n\nNext: VERIFY this new token works before saving!",
      "position": [
        1904,
        480
      ],
      "parameters": {
        "jsCode": "const api_response = $input.first().json;\nconst config = $('1\ufe0f\u20e3 Load Config').first().json.config;\n\nif (!api_response.result || !api_response.result.content) {\n  throw new Error('\u274c Failed to create mandate - invalid API response');\n}\n\nconst text = api_response.result.content[0].text;\nconst result = JSON.parse(text);\n\nif (!result.mandate_token) {\n  throw new Error('\u274c API did not return mandate_token');\n}\n\nconsole.log('\ud83c\udd95 NEW MANDATE CREATED!');\nconsole.log(`   Mandate ID: ${result.mandate_id}`);\nconsole.log(`   Token: ${result.mandate_token.substring(0, 20)}...`);\nconsole.log(`   Budget: $${result.budget_remaining || config.buyer.budget_usd}`);\nconsole.log(`   Expires: ${result.expires_at}`);\nconsole.log('   \u2705 Will be saved to table');\n\nconst mandate = {\n  token: result.mandate_token,\n  id: result.mandate_id,\n  budget_remaining: parseFloat(result.budget_remaining || config.buyer.budget_usd),\n  expires_at: result.expires_at\n};\n\nreturn [{\n  json: {\n    config,\n    mandate,\n    verification: {\n      valid: true,\n      budget_remaining: mandate.budget_remaining,\n      status: 'active'\n    },\n    was_reused: false,\n    source: 'new_mandate',\n    message: `\ud83c\udd95 NEW MANDATE!\\n\\nID: ${mandate.id}\\nBudget: $${mandate.budget_remaining}\\nExpires: ${mandate.expires_at}`\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "c698bfa3-31df-4d15-b853-fbae8b6dc95f",
      "name": "6\ufe0f\u20e3 \u2705 Verify New Mandate",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "\ud83d\udd12 VERIFY: New mandate works!\n\nDon't save broken tokens to table.\nGateway confirms it's valid before we store it.",
      "position": [
        2112,
        480
      ],
      "parameters": {
        "url": "={{ $('1\ufe0f\u20e3 Load Config').first().json.config.agentgatepay.mcp_endpoint }}",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"jsonrpc\": \"2.0\",\n  \"id\": 4,\n  \"method\": \"tools/call\",\n  \"params\": {\n    \"name\": \"agentpay_verify_mandate\",\n    \"arguments\": {\n      \"mandate_token\": \"{{ $json.mandate.token }}\"\n    }\n  }\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "x-api-key",
              "value": "={{ $('1\ufe0f\u20e3 Load Config').first().json.config.buyer.api_key }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "e6648b7b-da08-4471-a8c9-d3022cb6dae5",
      "name": "6B\ufe0f\u20e3 Check New Mandate",
      "type": "n8n-nodes-base.code",
      "notes": "\u2705 CHECK: New mandate is valid\n\nIf invalid \u2192 ERROR (don't save)\nIf valid \u2192 Prepare token for saving",
      "position": [
        2320,
        480
      ],
      "parameters": {
        "jsCode": "const verify_response = $input.first().json;\nconst parsed_data = $('5B\ufe0f\u20e3 Parse New Mandate').first().json;\nconst config = $('1\ufe0f\u20e3 Load Config').first().json.config;\n\n// Parse MCP response\nif (!verify_response.result || !verify_response.result.content) {\n  throw new Error('\u274c Gateway verification failed - invalid response');\n}\n\nconst text = verify_response.result.content[0].text;\nconst verification = JSON.parse(text);\n\nif (!verification.valid) {\n  throw new Error(`\u274c New mandate is INVALID: ${verification.error || 'Unknown error'}`);\n}\n\nconst token = parsed_data.mandate.token;\n\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// \ud83d\udee1\ufe0f STRICT VALIDATION - PREVENT NULL/INVALID TOKENS\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n// Check 1: Exists and is a string\nif (!token || typeof token !== 'string') {\n  throw new Error('\u274c BLOCKED: Token is missing or not a string!');\n}\n\n// Check 2: Not literal 'null' or 'undefined' strings\nif (token === 'null' || token === 'undefined' || token === '') {\n  throw new Error('\u274c BLOCKED: Token is null, undefined, or empty string!');\n}\n\n// Check 3: Minimum length (JWT tokens are 100+ chars)\nif (token.length < 50) {\n  throw new Error(`\u274c BLOCKED: Token too short (${token.length} chars) - likely invalid!`);\n}\n\n// Check 4: Must contain periods (JWT format: header.payload.signature)\nif (!token.includes('.')) {\n  throw new Error('\u274c BLOCKED: Token is not in JWT format (missing periods)!');\n}\n\nconsole.log('\u2705 New mandate verified successfully!');\nconsole.log(`   Token: ${token.substring(0, 30)}...`);\nconsole.log(`   Token length: ${token.length} chars`);\nconsole.log(`   Budget: $${verification.budget_remaining}`);\nconsole.log(`   Status: ${verification.status}`);\nconsole.log('   \ud83d\udee1\ufe0f Validation: ALL CHECKS PASSED');\n\n// Prepare for saving to table\nreturn [{\n  json: {\n    mandate_token: token,\n    _original_data: parsed_data  // Keep for Node 7B\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "18397ef6-45a5-49ba-9f8a-af2af960f664",
      "name": "7\ufe0f\u20e3 \ud83d\udcbe Insert Token",
      "type": "n8n-nodes-base.dataTable",
      "notes": "\ud83d\udcbe INSERT: Verified token to table\n\nReceives: { mandate_token: \"...\" }\nAuto-maps to table column\n\nOnly saves tokens that passed verification!\n\n\u26a0\ufe0f IMPORTANT: RE-SELECT 'AgentPay_Mandates' from dropdown!",
      "position": [
        2528,
        480
      ],
      "parameters": {
        "columns": {
          "value": {
            "mandate_token": "={{ $json.mandate_token }}"
          },
          "schema": [
            {
              "id": "mandate_token",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "mandate_token",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "0RVPG9hWFRYnyUw7",
          "cachedResultUrl": "/projects/GLZPTknscCLLXTgj/datatables/0RVPG9hWFRYnyUw7",
          "cachedResultName": "AgentPay_Mandates"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "ce95fef9-8a7c-44d4-a56f-80d2b8a2054c",
      "name": "7B\ufe0f\u20e3 Restore Data",
      "type": "n8n-nodes-base.code",
      "notes": "\ud83d\udd04 RESTORE: Original data structure\n\nNode 7 returns table response\nNode 8 needs original mandate data\n\nThis bridges the gap.",
      "position": [
        2720,
        480
      ],
      "parameters": {
        "jsCode": "// Restore original data for merge node\nconst table_response = $input.first().json;\nconst original_data = $('6B\ufe0f\u20e3 Check New Mandate').first().json._original_data;\n\nconsole.log('\u2705 Token saved to table successfully!');\n\n// Return original mandate data for Node 8\nreturn [{ json: original_data }];"
      },
      "typeVersion": 2
    },
    {
      "id": "98ce0832-7986-4700-9731-eebdf91742bd",
      "name": "8\ufe0f\u20e3 \ud83d\udd00 Merge Paths",
      "type": "n8n-nodes-base.code",
      "notes": "\ud83d\udd00 MERGE: Both paths converge here\n\nPath 1: Verified existing token \u267b\ufe0f\nPath 2: Created new token \ud83c\udd95\n\nBoth continue with same flow",
      "position": [
        2112,
        192
      ],
      "parameters": {
        "jsCode": "// Both paths merge here (existing token or new token)\nconst data = $input.first().json;\n\nconst config = data.config;\nconst session = $('1\ufe0f\u20e3 Load Config').first().json.session;\nconst mandate = data.mandate;\nconst verification = data.verification;\n\nconst merchant = config.seller;\n\nconst selected_resource = {\n  id: merchant.selected_resource_id,\n  title: \"SaaS Competitor Analysis 2025\",\n  description: \"Top 5 competitors analysis\"\n};\n\nconst resource_request = {\n  from: config.buyer.email,\n  to: merchant.email,\n  resource_id: selected_resource.id,\n  resource_title: selected_resource.title,\n  mandate_token: mandate.token,\n  timestamp: new Date().toISOString()\n};\n\nreturn [{\n  json: {\n    config,\n    session,\n    mandate,\n    verification,\n    was_reused: data.was_reused,\n    merchant: merchant,\n    selected_resource: selected_resource,\n    resource_request: resource_request,\n    message: `\u2705 Mandate Ready!\\n\\nSource: ${data.was_reused ? 'Reused existing \u267b\ufe0f' : 'Created new \ud83c\udd95'}\\nBudget: $${verification.budget_remaining}\\n\\nMerchant: ${merchant.name}\\nResource: ${selected_resource.title}`\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "8d0a44b0-efe9-48ed-9e92-3c08f8741e35",
      "name": "9\ufe0f\u20e3 Request Resource (x402)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2320,
        192
      ],
      "parameters": {
        "url": "={{ $json.config.seller.api_url }}/resource/{{ $json.config.seller.selected_resource_id }}",
        "options": {
          "response": {
            "response": {
              "neverError": true,
              "fullResponse": true
            }
          }
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-agent-id",
              "value": "={{ $json.config.buyer.email }}"
            },
            {
              "name": "x-mandate",
              "value": "={{ $json.mandate.token }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "187810bc-9ee3-4fdb-8577-338dd8e45d01",
      "name": "9B\ufe0f\u20e3 Parse 402 Response",
      "type": "n8n-nodes-base.code",
      "position": [
        2528,
        192
      ],
      "parameters": {
        "jsCode": "const response = $input.first().json;\nconst body = response.body || response;\n\nif (response.statusCode !== 402 && body.statusCode !== 402) {\n  throw new Error(`Expected 402 Payment Required, got ${response.statusCode || body.statusCode}`);\n}\n\nconst payment_required = body;\nconst data = $('8\ufe0f\u20e3 \ud83d\udd00 Merge Paths').first().json;\n\nreturn [{\n  json: {\n    ...data,\n    payment_required: payment_required,\n    message: `\ud83d\udcb0 402 Received!\\n\\nWallet: ${payment_required.payTo}\\nAmount: $${payment_required.priceUsd}\\nToken: ${payment_required.token}\\nChain: ${payment_required.chain}`\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "d75a575f-995e-4dca-82b3-c48e7b095257",
      "name": "\ud83d\udd1f \ud83d\udd12 Sign Payment (Render)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2720,
        192
      ],
      "parameters": {
        "url": "={{ $('8\ufe0f\u20e3 \ud83d\udd00 Merge Paths').item.json.config.render.service_url }}/sign-payment",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"merchant_address\": \"{{ $json.payment_required.payTo }}\",\n  \"total_amount\": \"{{ $json.payment_required.amount }}\",\n  \"token\": \"{{ $json.payment_required.token }}\",\n  \"chain\": \"{{ $json.payment_required.chain }}\"\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "x-api-key",
              "value": "={{ $('8\ufe0f\u20e3 \ud83d\udd00 Merge Paths').item.json.config.buyer.api_key }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "2eab8b07-a05e-4391-9ec1-2b5c3a1c9697",
      "name": "1\ufe0f\u20e31\ufe0f\u20e3 Extract TX Hashes",
      "type": "n8n-nodes-base.code",
      "position": [
        2928,
        192
      ],
      "parameters": {
        "jsCode": "const render_response = $input.first().json;\nconst data = $('9B\ufe0f\u20e3 Parse 402 Response').first().json;\n\nif (!render_response.success) {\n  throw new Error(`Render signing failed: ${render_response.error || 'Unknown error'}`);\n}\n\nif (!render_response.tx_hash || !render_response.tx_hash_commission) {\n  throw new Error('Missing transaction hashes from Render');\n}\n\nreturn [{\n  json: {\n    ...data,\n    payment_proof: {\n      tx_hash: render_response.tx_hash,\n      tx_hash_commission: render_response.tx_hash_commission,\n      status: 'confirmed',\n      from: render_response.from,\n      merchant: data.payment_required.payTo,\n      commission_address: render_response.commission_address,\n      total_usd: render_response.total_usd,\n      merchant_usd: render_response.merchant_usd,\n      commission_usd: render_response.commission_usd,\n      token: data.payment_required.token,\n      chain: data.payment_required.chain,\n      merchant_url: `https://basescan.org/tx/${render_response.tx_hash}`,\n      commission_url: `https://basescan.org/tx/${render_response.tx_hash_commission}`\n    }\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "1aad75be-ca5b-447a-8b5f-6907b369ecde",
      "name": "1\ufe0f\u20e32\ufe0f\u20e3 Submit Payment (MCP)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3136,
        192
      ],
      "parameters": {
        "url": "={{ $json.config.agentgatepay.mcp_endpoint }}",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"jsonrpc\": \"2.0\",\n  \"id\": 2,\n  \"method\": \"tools/call\",\n  \"params\": {\n    \"name\": \"agentpay_submit_payment\",\n    \"arguments\": {\n      \"mandate_token\": \"{{ $json.mandate.token }}\",\n      \"tx_hash\": \"{{ $json.payment_proof.tx_hash }}\",\n      \"tx_hash_commission\": \"{{ $json.payment_proof.tx_hash_commission }}\",\n      \"chain\": \"{{ $json.payment_required.chain }}\",\n      \"token\": \"{{ $json.payment_required.token }}\",\n      \"price_usd\": \"{{ $json.payment_required.priceUsd }}\"\n    }\n  }\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "x-api-key",
              "value": "={{ $('8\ufe0f\u20e3 \ud83d\udd00 Merge Paths').first().json.config.buyer.api_key }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "7a2ff1f9-bf4a-425a-ba93-125ec6c95ed3",
      "name": "1\ufe0f\u20e33\ufe0f\u20e3 Receive Resource",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3344,
        192
      ],
      "parameters": {
        "url": "={{ $('9B\ufe0f\u20e3 Parse 402 Response').first().json.config.seller.api_url }}/resource/{{ $('9B\ufe0f\u20e3 Parse 402 Response').first().json.config.seller.selected_resource_id }}",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "responseFormat": "json"
            }
          }
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-payment",
              "value": "={{ $('1\ufe0f\u20e31\ufe0f\u20e3 Extract TX Hashes').first().json.payment_proof.tx_hash }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "cec5d7b2-90e4-4d84-81cc-cf4d6f33a308",
      "name": "1\ufe0f\u20e34\ufe0f\u20e3 Complete Task",
      "type": "n8n-nodes-base.code",
      "position": [
        3552,
        192
      ],
      "parameters": {
        "jsCode": "const resource_http_response = $input.first().json;\nconst payment_data = $('1\ufe0f\u20e31\ufe0f\u20e3 Extract TX Hashes').first().json;\nconst merge_data = $('8\ufe0f\u20e3 \ud83d\udd00 Merge Paths').first().json;\n\nconst resource_body = resource_http_response.body || resource_http_response;\nlet resource_data = typeof resource_body === 'object' ? resource_body : { raw: resource_body };\n\nconst spent = parseFloat(payment_data.payment_required.priceUsd);\nconst budget_before = merge_data.verification.budget_remaining;\nconst budget_after = budget_before - spent;\n\nreturn [{\n  json: {\n    config: merge_data.config,\n    session: merge_data.session,\n    resource_data: resource_data,\n    resource_http_status: resource_http_response.statusCode,\n    payment_proof: payment_data.payment_proof,\n    summary: {\n      task: merge_data.config.buyer.task,\n      status: \"COMPLETED\",\n      mandate_source: merge_data.was_reused ? \"Reused (\u267b\ufe0f)\" : \"Created New (\ud83c\udd95)\",\n      mandate_storage: \"Data Tables (1 column!)\",\n      budget_before_payment: budget_before,\n      budget_spent: spent,\n      budget_remaining: budget_after,\n      merchant_tx: payment_data.payment_proof.tx_hash,\n      commission_tx: payment_data.payment_proof.tx_hash_commission,\n      completed_at: new Date().toISOString()\n    },\n    message: `\ud83c\udf89 TASK COMPLETED !\\n\\n\u2705 Task: \"${merge_data.config.buyer.task}\"\\n\u2705 Resource Received (HTTP ${resource_http_response.statusCode})\\n\\n\ud83d\udd12 Mandate: ${merge_data.was_reused ? 'REUSED \u267b\ufe0f' : 'CREATED NEW \ud83c\udd95'}\\n\ud83d\udcca Storage: Data Tables (just 1 column!)\\n\\n\ud83d\udcb0 Budget:\\n   - Before: $${budget_before}\\n   - Spent: $${spent}\\n   - After: $${budget_after}\\n\\n\ud83d\udd17 TX: ${payment_data.payment_proof.tx_hash}\\n\\n\u2705 v3.7 = ULTRA SIMPLE!\\n   - 1 column table (not 9!)\\n   - Gateway is source of truth\\n   - Clear error messages\\n   - No auto-renewal (security!)\\n   - Easy manual renewal (delete row)`\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "77711363-6bea-4837-8ee9-aa49bbe04633",
      "name": "START HERE",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        0
      ],
      "parameters": {
        "color": 4,
        "width": 520,
        "height": 600,
        "content": "# Buyer Agent Workflow\n\n**What it does:** AI agent that buys resources from sellers using AgentGatePay. Automatically reuses mandate tokens to track spending.\n\n**Quick setup (2 min):**\n1. Create Data Table called `AgentPay_Mandates` with one column: `mandate_token` (type: String).\n2. Choose blockchain: coin and network\n3. Click Node 2 and Node 7, re-select the table from dropdowns\n4. Edit Node 1 with your email, AgentGatePay API Key,seller URL, budget ($100 default) and Render URL or other external TX service for signing transactions.\n\n**How it works:**\n- First run creates a mandate token and saves it to the table\n- Next runs reuse the same token\n- Budget decreases with each payment\n- When budget's gone, delete the table row and run again for a fresh mandate\n\n**Cost:** $0.01 per resource \u00b7 Budget tracked automatically \u00b7 No surprises\n\nFor more info:\nhttps://github.com/AgentGatePay/agentgatepay-examples/tree/main/n8n"
      },
      "typeVersion": 1
    },
    {
      "id": "49fe3a6c-4c97-4c7a-b5a4-764da49d2866",
      "name": "Sticky Note 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        560,
        224
      ],
      "parameters": {
        "color": 7,
        "width": 880,
        "height": 280,
        "content": "## Mandate Management\n\nLoads your config, checks if you have an existing mandate token in the Data Table. If found, verify it. If not, create a new one."
      },
      "typeVersion": 1
    },
    {
      "id": "3c1b7cc0-e7ab-466e-abf8-71b752011527",
      "name": "Sticky Note 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1632,
        64
      ],
      "parameters": {
        "color": 7,
        "width": 418,
        "height": 280,
        "content": "## Token Verification\n\nAsks AgentGatePay if your existing token is still valid. Checks signature, expiry, and remaining budget. If expired, you'll get clear instructions to renew."
      },
      "typeVersion": 1
    },
    {
      "id": "f42cea23-beb7-4fd2-8a63-b90ed0e7adb6",
      "name": "Sticky Note 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1632,
        384
      ],
      "parameters": {
        "color": 7,
        "width": 1228,
        "height": 280,
        "content": "## New Mandate Creation\n\nCreates fresh mandate via Gateway API, verifies it works, validates the token format, then saves to Data Table. Only runs when you don't have a valid token."
      },
      "typeVersion": 1
    },
    {
      "id": "368ec38d-61d3-4a32-aab6-f1fe07d7aa2e",
      "name": "Sticky Note 4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2080,
        64
      ],
      "parameters": {
        "color": 7,
        "width": 952,
        "height": 280,
        "content": "## Payment Flow\n\nMerges both paths (reused or new token), requests the resource, receives 402 payment details, signs the payment via Render service, extracts transaction hashes."
      },
      "typeVersion": 1
    },
    {
      "id": "d0ca8152-ff18-4414-b214-72c47dad9d19",
      "name": "Sticky Note 5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3072,
        64
      ],
      "parameters": {
        "color": 7,
        "width": 648,
        "height": 280,
        "content": "## Resource Delivery\n\nSubmits payment proof to Gateway, receives the paid resource from seller, calculates updated budget, and completes the task with full summary."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "62f2c498-0f5e-4d72-a9b5-1ec08961a0d5",
  "connections": {
    "\u25b6\ufe0f START": {
      "main": [
        [
          {
            "node": "1\ufe0f\u20e3 Load Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3\ufe0f\u20e3 Has Token?": {
      "main": [
        [
          {
            "node": "4\ufe0f\u20e3 \u2705 Verify Existing Token",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "5\ufe0f\u20e3 \ud83c\udd95 Create New Mandate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1\ufe0f\u20e3 Load Config": {
      "main": [
        [
          {
            "node": "2\ufe0f\u20e3 \ud83d\udcca Get Mandate Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "7B\ufe0f\u20e3 Restore Data": {
      "main": [
        [
          {
            "node": "8\ufe0f\u20e3 \ud83d\udd00 Merge Paths",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "8\ufe0f\u20e3 \ud83d\udd00 Merge Paths": {
      "main": [
        [
          {
            "node": "9\ufe0f\u20e3 Request Resource (x402)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2B\ufe0f\u20e3 Normalize Result": {
      "main": [
        [
          {
            "node": "3\ufe0f\u20e3 Has Token?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "7\ufe0f\u20e3 \ud83d\udcbe Insert Token": {
      "main": [
        [
          {
            "node": "7B\ufe0f\u20e3 Restore Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5B\ufe0f\u20e3 Parse New Mandate": {
      "main": [
        [
          {
            "node": "6\ufe0f\u20e3 \u2705 Verify New Mandate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6B\ufe0f\u20e3 Check New Mandate": {
      "main": [
        [
          {
            "node": "7\ufe0f\u20e3 \ud83d\udcbe Insert Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4B\ufe0f\u20e3 Check Verification": {
      "main": [
        [
          {
            "node": "8\ufe0f\u20e3 \ud83d\udd00 Merge Paths",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "9B\ufe0f\u20e3 Parse 402 Response": {
      "main": [
        [
          {
            "node": "\ud83d\udd1f \ud83d\udd12 Sign Payment (Render)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2\ufe0f\u20e3 \ud83d\udcca Get Mandate Token": {
      "main": [
        [
          {
            "node": "2B\ufe0f\u20e3 Normalize Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6\ufe0f\u20e3 \u2705 Verify New Mandate": {
      "main": [
        [
          {
            "node": "6B\ufe0f\u20e3 Check New Mandate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1\ufe0f\u20e33\ufe0f\u20e3 Receive Resource": {
      "main": [
        [
          {
            "node": "1\ufe0f\u20e34\ufe0f\u20e3 Complete Task",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5\ufe0f\u20e3 \ud83c\udd95 Create New Mandate": {
      "main": [
        [
          {
            "node": "5B\ufe0f\u20e3 Parse New Mandate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "9\ufe0f\u20e3 Request Resource (x402)": {
      "main": [
        [
          {
            "node": "9B\ufe0f\u20e3 Parse 402 Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udd1f \ud83d\udd12 Sign Payment (Render)": {
      "main": [
        [
          {
            "node": "1\ufe0f\u20e31\ufe0f\u20e3 Extract TX Hashes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1\ufe0f\u20e31\ufe0f\u20e3 Extract TX Hashes": {
      "main": [
        [
          {
            "node": "1\ufe0f\u20e32\ufe0f\u20e3 Submit Payment (MCP)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4\ufe0f\u20e3 \u2705 Verify Existing Token": {
      "main": [
        [
          {
            "node": "4B\ufe0f\u20e3 Check Verification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1\ufe0f\u20e32\ufe0f\u20e3 Submit Payment (MCP)": {
      "main": [
        [
          {
            "node": "1\ufe0f\u20e33\ufe0f\u20e3 Receive Resource",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}