AutomationFlowsAI & RAG › Automate Token Purchases with Dollar Cost Averaging on Uniswap V3 & 1shot API

Automate Token Purchases with Dollar Cost Averaging on Uniswap V3 & 1shot API

By1Shot API @oneshotapi on n8n.io

This workflow contains community nodes that are only compatible with the self-hosted version of n8n.

Cron / scheduled trigger★★★★☆ complexity16 nodesN8N Nodes 1ShotTelegram
AI & RAG Trigger: Cron / scheduled Nodes: 16 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #8044 — we link there as the canonical source.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "maYy0i7nYbJwepaQ",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "DCA w/ Uniswap V3",
  "tags": [],
  "nodes": [
    {
      "id": "f43b592f-1203-47b8-9dac-61e8a9d3c9d3",
      "name": "Calculate TWAP",
      "type": "n8n-nodes-base.code",
      "position": [
        432,
        160
      ],
      "parameters": {
        "jsCode": "// Constants\nconst MaxUint256 = (1n << 256n) - 1n;\nconst Q32 = 1n << 32n;\nconst ZERO = 0n;\nconst ONE = 1n;\n\nfunction mulShift(val, mulBy) {\n  return (val * BigInt(mulBy)) >> 128n;\n}\n\n/**\n * Returns the sqrt ratio as a Q64.96 for the given tick.\n * The sqrt ratio is computed as sqrt(1.0001)^tick\n * @param {number} tick the tick for which to compute the sqrt ratio\n */\nfunction getSqrtRatioAtTick(tick) {\n  if (!Number.isInteger(tick)) throw new Error(\"Tick must be integer\");\n  const MIN_TICK = -887272;\n  const MAX_TICK = 887272;\n\n  if (tick < MIN_TICK || tick > MAX_TICK) throw new Error(\"Tick out of bounds\");\n\n  const absTick = tick < 0 ? -tick : tick;\n\n  let ratio =\n    (absTick & 0x1) != 0\n      ? 0xfffcb933bd6fad37aa2d162d1a594001n\n      : 0x1+1234567890+1234567890n;\n  if ((absTick & 0x2) != 0) ratio = mulShift(ratio, 0xfff97272373d413259a46990580e213an);\n  if ((absTick & 0x4) != 0) ratio = mulShift(ratio, 0xfff2e50f5f656932ef12357cf3c7fdccn);\n  if ((absTick & 0x8) != 0) ratio = mulShift(ratio, 0xffe5caca7e10e4e61c3624eaa0941cd0n);\n  if ((absTick & 0x10) != 0) ratio = mulShift(ratio, 0xffcb9843d60f6159c9db58835c926644n);\n  if ((absTick & 0x20) != 0) ratio = mulShift(ratio, 0xff973b41fa98c081472e6896dfb254c0n);\n  if ((absTick & 0x40) != 0) ratio = mulShift(ratio, 0xff2ea16466c96a3843ec78b326b52861n);\n  if ((absTick & 0x80) != 0) ratio = mulShift(ratio, 0xfe5dee046a99a2a811c461f1969c3053n);\n  if ((absTick & 0x100) != 0) ratio = mulShift(ratio, 0xfcbe86c7900a88aedcffc83b479aa3a4n);\n  if ((absTick & 0x200) != 0) ratio = mulShift(ratio, 0xf987a7253ac413176f2b074cf7815e54n);\n  if ((absTick & 0x400) != 0) ratio = mulShift(ratio, 0xf3392b0822b70005940c7a398e4b70f3n);\n  if ((absTick & 0x800) != 0) ratio = mulShift(ratio, 0xe7159475a2c29b7443b29c7fa6e889d9n);\n  if ((absTick & 0x1000) != 0) ratio = mulShift(ratio, 0xd097f3bdfd2022b8845ad8f792aa5825n);\n  if ((absTick & 0x2000) != 0) ratio = mulShift(ratio, 0xa9f746462d870fdf8a65dc1f90e061e5n);\n  if ((absTick & 0x4000) != 0) ratio = mulShift(ratio, 0x70d869a156d2a1b890bb3df62baf32f7n);\n  if ((absTick & 0x8000) != 0) ratio = mulShift(ratio, 0x31be135f97d08fd981231505542fcfa6n);\n  if ((absTick & 0x10000) != 0) ratio = mulShift(ratio, 0x9aa508b5b7a84e1c677de54f3e99bc9n);\n  if ((absTick & 0x20000) != 0) ratio = mulShift(ratio, 0x5d6af8dedb81196699c329225ee604n);\n  if ((absTick & 0x40000) != 0) ratio = mulShift(ratio, 0x2216e584f5fa1ea926041bedfe98n);\n  if ((absTick & 0x80000) != 0) ratio = mulShift(ratio, 0x48a170391f7dc42444e8fa2n);\n\n  if (tick > 0) {\n    ratio = MaxUint256 / ratio;\n  }\n\n  // back to Q96\n  return ratio % Q32 > 0n ? ratio / Q32 + ONE : ratio / Q32;\n}\n\nfunction getPriceFromSqrtPriceX96(sqrtPriceX96, decimals0, decimals1) {\n  // Ensure input is BigInt\n  const sqrtPrice = BigInt(sqrtPriceX96.toString());\n\n  // (sqrtPriceX96 ^ 2)\n  const numerator = sqrtPrice * sqrtPrice;\n\n  // Denominator = 2^192\n  const denominator = 1n << 192n;\n\n  // Raw price ratio (tokenOut per tokenIn, no decimals adjusted)\n  let ratio = Number(numerator * 10n**18n / denominator) / 1e18; \n  // (we scale by 1e18 to stay precise when converting to Number)\n\n  // Adjust for token decimals\n  const decimalFactor = 10 ** (decimals0 - decimals1);\n  const price = ratio * decimalFactor;\n\n  return price;\n}\n\nconst amountOut = $input.first().json.result.decodedData[0];\nconst diffTickCumulative = parseInt($('Fetch Pool TWA Observations').first().json.response[0][0]) - parseInt($('Fetch Pool TWA Observations').first().json.response[0][1]);\nconst diffSecondsPerLIquidityX128 = parseInt($('Fetch Pool TWA Observations').first().json.response[1][0]) - parseInt($('Fetch Pool TWA Observations').first().json.response[1][1]);\nconst secondsBetween = parseInt($('Swap Configs').first().json.secondsAgo);\nconst secondsBetweenX128 = BigInt(secondsBetween) << BigInt(128);\nconst averageTick = parseInt(diffTickCumulative/secondsBetween);\n\nconst sqrtPriceX96After = $input.first().json.result.decodedData[1];\nconst priceAfter = getPriceFromSqrtPriceX96(sqrtPriceX96After, $('Swap Configs').first().json.tokenInDecimals, $('Swap Configs').first().json.tokenOutDecimals);\n\nconst sqrtTWAPricex96 = getSqrtRatioAtTick(averageTick);\nconst TWAP = getPriceFromSqrtPriceX96(sqrtTWAPricex96, $('Swap Configs').first().json.tokenInDecimals, $('Swap Configs').first().json.tokenOutDecimals);\nconst TWAL = secondsBetweenX128 / BigInt(diffSecondsPerLIquidityX128);\n\n// The \"price\" reported by a pool is how much of `token0` \nconsole.log(\"TWAP\", 1/TWAP);\nconsole.log(\"Price After: \", 1/priceAfter);\nconsole.log(\"TWAL\", TWAL);\n\n$input.first().json.twap = TWAP; \n$input.first().json.sqrtTWAPriceX96 = sqrtTWAPricex96; \n$input.first().json.twal = TWAL; \n$input.first().json.quotePriceAfter = priceAfter; \n$input.first().json.amountOut = amountOut;\n\nreturn $input.all()\n"
      },
      "typeVersion": 2
    },
    {
      "id": "bd180960-416b-48f8-b524-9841428a34e9",
      "name": "Fetch Pool TWA Observations",
      "type": "n8n-nodes-1shot.oneShot",
      "position": [
        -16,
        160
      ],
      "parameters": {
        "params": "={\n  \"secondsAgos\": [\"0\",\"{{ $json.secondsAgo }}\"]\n} ",
        "operation": "read",
        "contractMethodId": "10cb761e-caae-400e-9c31-bde85f45f4cd"
      },
      "credentials": {
        "oneShotOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "46184b6b-263e-4e14-9518-2515e2d311a3",
      "name": "Get Swap Qoute",
      "type": "n8n-nodes-1shot.oneShot",
      "position": [
        208,
        160
      ],
      "parameters": {
        "params": "={\n  \"params\": \n    {\n  \"tokenIn\": \"{{ $('Swap Configs').item.json.tokenIn }}\",\n  \"tokenOut\": \"{{ $('Swap Configs').item.json.tokenOut }}\",\n  \"amountIn\": \"{{ $('Swap Configs').item.json.amountDCA }}\",\n  \"fee\": \"{{ $('Swap Configs').item.json.fee }}\",\n  \"sqrtPriceLimitX96\": \"0\"\n    }\n} ",
        "operation": "simulate",
        "contractMethodId": "3bc18080-46f6-4ba0-99f0-c1f6af57e7b3"
      },
      "credentials": {
        "oneShotOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "6b4d7900-7fae-4c1d-80cf-2900dfb4e299",
      "name": "Swap Tokens",
      "type": "n8n-nodes-1shot.oneShotSynch",
      "onError": "continueRegularOutput",
      "position": [
        880,
        80
      ],
      "parameters": {
        "params": "={\n\"params\": {\n\"tokenIn\": \"{{ $('Swap Configs').item.json.tokenIn }}\",\n\"tokenOut\": \"{{ $('Swap Configs').item.json.tokenOut }}\",\n\"fee\": \"{{ $('Swap Configs').item.json.fee }}\",\n\"recipient\": \"{{ $('Swap Configs').item.json.delegator }}\",\n\"amountIn\": \"{{ $('Swap Configs').item.json.amountDCA }}\",\n\"amountOutMinimum\": \"{{ $('Calculate TWAP').item.json.amountOut }}\",\n\"sqrtPriceLimitX96\": \"0\"\n}\n}",
        "operation": "executeAsDelegator",
        "additionalFields": {
          "memo": "=DCA Swap for {{ $('Swap Configs').item.json.amountDCA }}, TWAP: {{ $('Calculate TWAP').item.json.twap }}"
        },
        "contractMethodId": "971b6978-79bf-42ac-8c99-52289dc94fac",
        "delegatorWalletAddress": "={{ $('Swap Configs').item.json.delegator }}"
      },
      "credentials": {
        "oneShotOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "a11c7481-97bf-4cee-8a3f-e604cd09236d",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -464,
        160
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 15
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "8c435f3f-72dc-4102-99bd-13cda1ea27c8",
      "name": "Failure Details",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1104,
        256
      ],
      "parameters": {
        "text": "=\u274c Swap Failed",
        "chatId": "123456789",
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "d1512fe2-112f-48c0-8a98-cbbb08ab8f65",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -944,
        160
      ],
      "parameters": {
        "width": 320,
        "height": 224,
        "content": "## Set Your DCA Schedule\n\nn8n makes it really easy to set a recurring schedule for your DCA purchases. Click on the Schedule Trigger node and set your frequency. "
      },
      "typeVersion": 1
    },
    {
      "id": "4f6661d0-978c-4554-97eb-f91519dd8c85",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -528,
        -160
      ],
      "parameters": {
        "width": 720,
        "height": 304,
        "content": "## DCA configs\n\nSince we are using the Uniswap protocol directly, you'll need to set a few parameters in the Swap Configs node. \n\n1. Decide on the amount you want to spend on each DCA buy. \n2. Put your wallet address as the `delegator` so that the workflow can execute DCA buys on your behalf. \n3. Set the correct address for the Uniswap [SwapRouter](https://docs.uniswap.org/contracts/v3/reference/deployments/) contract\n4. Depending on the pool your are trading against, set the correct addresses for `tokenIn` and `tokenOut`. Also set the number of decimals used by `tokenIn` and `tokenOut`.\n5. Next, set the correct fee for the pool you are trading against (for most pools its just `500`). "
      },
      "typeVersion": 1
    },
    {
      "id": "4b46e786-07d6-4184-8a90-ff264686c075",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        608
      ],
      "parameters": {
        "width": 720,
        "height": 384,
        "content": "## Connect to Your 1Shot API Account\n\nCreate an API key and secret in your 1Shot API account and connect your n8n instance by creating a credential. \n\n1. The `Fetch Pool TWA Observactions` should point to the `observe` method on your target trading pool (like this [one](https://app.uniswap.org/explore/pools/base/0xfBB6Eed8e7aa03B138556eeDaF5D271A5E1e43ef)). \n2. The `Get Swap Quote` node should point to the `quoteExactInputSingle` on the QuoterV2 contract.\n3. The `Give Approval to Router` should call `approve` on the token you are DCA'ing out of (like USDC). \n4. The `Swap Tokens` node should point at the `exactInputSingle` function on the Uniswap SwapRouterV2 contract. \n5. In order to get look up your remaining balance of funds that can be used for DCA purchases, point the `Get Remaining DCA Funds` at the `balanceOf` functions of the `token0` contract."
      },
      "typeVersion": 1
    },
    {
      "id": "bb585dd2-901b-4bb3-b822-5ddf3ad80be7",
      "name": "Give Approval to Router",
      "type": "n8n-nodes-1shot.oneShotSynch",
      "onError": "continueRegularOutput",
      "position": [
        656,
        160
      ],
      "parameters": {
        "params": "={\n  \"spender\": \"{{ $('Swap Configs').item.json.router }}\", \n  \"value\": \"{{ $('Swap Configs').item.json.amountDCA }}\"\n} ",
        "operation": "executeAsDelegator",
        "additionalFields": {
          "memo": "=DCA Approve for {{ $('Swap Configs').item.json.amountDCA }}"
        },
        "contractMethodId": "a8abf8ae-f524-444a-a9db-76a8a4711599",
        "delegatorWalletAddress": "={{ $('Swap Configs').item.json.delegator }}"
      },
      "credentials": {
        "oneShotOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "792928fb-59e5-44ba-a6b9-9f388c74f02a",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1344,
        224
      ],
      "parameters": {
        "width": 544,
        "height": 176,
        "content": "## Telegram Notifications\n\nIf you want to get notified on each DCA purchase, connect a Telegram bot. "
      },
      "typeVersion": 1
    },
    {
      "id": "2edc9432-92f2-4feb-a44a-f6d6f5608db1",
      "name": "Success Details",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1328,
        32
      ],
      "parameters": {
        "text": "=\u2705 Swapped `{{ $('Swap Configs').item.json.amountDCA }}` `{{ $('Swap Configs').item.json.token0 }}` for {{ $('Get Swap Qoute').item.json.result.decodedData[0] }} {{ $('Swap Configs').item.json.token1 }}. \n\nThe `{{ $('Swap Configs').item.json.secondsAgo }}` second TWAP was `{{ $('Calculate TWAP').item.json.twap }}`. Your tx hash is `{{ $('Swap Tokens').item.json.transactionHash }}`.\n\nYou have {{ $json.response }} of token `{{ $('Swap Configs').item.json.token0 }}` left. ",
        "chatId": "123456789",
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "3147ded9-fc81-4d00-b230-c313c0c3501a",
      "name": "Get Remaining DCA Funds Balance",
      "type": "n8n-nodes-1shot.oneShot",
      "position": [
        1104,
        32
      ],
      "parameters": {
        "params": "={\n  \"account\": \"{{ $('Swap Configs').item.json.delegator }}\"\n}",
        "operation": "read",
        "contractMethodId": "9ad15620-d9bd-4c45-a0b2-4f9b647e80a7"
      },
      "credentials": {
        "oneShotOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "4d7da856-78f7-4553-a9f7-2a372688712b",
      "name": "Swap Configs",
      "type": "n8n-nodes-base.code",
      "position": [
        -240,
        160
      ],
      "parameters": {
        "jsCode": "const amountDCA = 250000; // amount to swap each time\nconst delegator = \"0x9fead8b19c044c2f404dac38b925ea16adaa2954\"; // your delegated wallet address.\nconst router = \"0x2626664c2603336E57B271c5C0b26F421741e481\"; // the uniswap SwapRouterV2\nconst tokenIn = \"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\"; // token0 of your target pool, this one is USDC\nconst tokenOut = \"0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf\"; // token1 of your target pool, this one is cbBTC\nconst tokenInDecimals = 6; // USDC has 6 decimals\nconst tokenOutDecimals = 8; // cbBTC has 8 decimals\nconst fee = 500; // the fee of your target pool, most pools have a fee of 500\nconst secondsAgo = 120; // the size of your TWAP window\n\n$input.first().json.amountDCA = amountDCA;\n$input.first().json.delegator = delegator;\n$input.first().json.router = router;\n$input.first().json.tokenIn = tokenIn;\n$input.first().json.tokenInDecimals = tokenInDecimals;\n$input.first().json.tokenOut = tokenOut;\n$input.first().json.tokenOutDecimals = tokenOutDecimals;\n$input.first().json.fee = fee;\n$input.first().json.secondsAgo = secondsAgo;\n\nreturn $input.all();\n"
      },
      "typeVersion": 2
    },
    {
      "id": "326628a6-6b36-4f27-9af1-6fc5bc13d430",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        64,
        368
      ],
      "parameters": {
        "width": 576,
        "height": 192,
        "content": "## Contracts to Import to Your 1Shot API account:\n\n1. [Uniswap SwapRouterV2](https://app.1shotapi.com/1shot-prompts/ce849711-a23a-4d6b-8a85-f5787011893e)\n2. [Uniswap Quoter02](https://app.1shotapi.com/1shot-prompts/4025d2d0-d7be-4251-b118-3470e9412d77)\n3. [Uniswap USDC/cbBTC Pool V3](https://app.1shotapi.com/1shot-prompts/3d95715b-ac09-4f7f-b2e6-918f6bd11875)\n4. [USDC](https://app.1shotapi.com/1shot-prompts/e087662d-154a-4810-bebf-327a950e2414)"
      },
      "typeVersion": 1
    },
    {
      "id": "be85f1ff-4ebf-4a5d-a3f2-aa62c6dc706e",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -944,
        432
      ],
      "parameters": {
        "width": 768,
        "height": 560,
        "content": "## YouTube Tutorial\n\n@[youtube](JiyLR5NtU7I)"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "1b7fd32c-564f-477e-a38e-cc97299735c0",
  "connections": {
    "Swap Tokens": {
      "main": [
        [
          {
            "node": "Get Remaining DCA Funds Balance",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Failure Details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Swap Configs": {
      "main": [
        [
          {
            "node": "Fetch Pool TWA Observations",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate TWAP": {
      "main": [
        [
          {
            "node": "Give Approval to Router",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Swap Qoute": {
      "main": [
        [
          {
            "node": "Calculate TWAP",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Swap Configs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Give Approval to Router": {
      "main": [
        [
          {
            "node": "Swap Tokens",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Failure Details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Pool TWA Observations": {
      "main": [
        [
          {
            "node": "Get Swap Qoute",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Remaining DCA Funds Balance": {
      "main": [
        [
          {
            "node": "Success Details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow contains community nodes that are only compatible with the self-hosted version of n8n.

Source: https://n8n.io/workflows/8044/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

Free Support: Setting up and getting the workflow tailord to your needs. One small free adjustment included.

HTTP Request, Google Cloud Storage, YouTube +2
AI & RAG

AI Institutional Stock Valuation Engine with Risk Scoring & Scenario Targets

Google Sheets, XML, HTTP Request +3
AI & RAG

Overview This is a production-grade, fully automated stock analysis system built entirely in n8n. It combines institutional-level financial analysis, dual AI model consensus, and a self-improving back

Google Sheets, XML, HTTP Request +3
AI & RAG

This workflow enables the automatic and regular tracking of competitors' Instagram Reels, providing rich insights for each video (summary, topic, hook, angles, tags, etc) through ChatGPT, and storing

Google Sheets, Telegram, @Apify/N8N Nodes Apify +1
AI & RAG

A professional AI equity analysis automation built on n8n that transforms structured financial data and real-time news into disciplined, risk-adjusted price targets and actionable BUY/HOLD/SELL signal

Google Sheets, OpenAI, XML +3