AI agent swaps on Uniswap
You can find a working code example to run locally in our example repository (opens in a new tab).
Requirements
- A deployed Safe Smart Account
- The AI agent is a signer on the Safe
- This example assumes, that the threshold of the Safe Smart Account is one, so the AI agent can sign autonomously. If you require more signatures, you have to collect those signatures programmatically of with the Safe Wallet (opens in a new tab).
- This guide assumes the Safe owns WETH. The example repository shows how to swap ETH to WETH.
Swap on Uniswap
Here is a quick guide to get you up and running:
Setup the Safe Smart Account
Your Safe Smart Account should be deployed. Now, initialize an instance with the Safe Protocol Kit:
_10import Safe from "@safe-global/protocol-kit";_10_10const preExistingSafe = await Safe.init({_10 provider: RPC_URL,_10 signer: AGENT_PRIVATE_KEY,_10 safeAddress: SAFE_ADDRESS,_10});
Fetch Uniswap pool data
First, you have to fetch the pool data from Uniswap. This data provides information about the liquidity at the current and at other prices. Uniswap has a unique Pricing Math (opens in a new tab).
_26import {_26 Address,_26 createPublicClient,_26 createWalletClient,_26 defineChain,_26 encodeFunctionData,_26 http,_26 PublicClient,_26} from "viem";_26_26// Fetch slot0 data (current price, tick, etc.)_26const slot0 = (await publicClient.readContract({_26 address: poolAddress,_26 abi: POOL_ABI,_26 functionName: "slot0",_26})) as any;_26_26// Fetch liquidity_26const liquidity = (await publicClient.readContract({_26 address: poolAddress,_26 abi: POOL_ABI,_26 functionName: "liquidity",_26})) as any;_26_26const sqrtPriceX96 = BigInt(slot0[0]);_26const tick = slot0[1];
Execute Swap
Now, you can setup your Safe Smart Account and send a swap transaction to Uniswap:
_104import { _104 FeeAmount,_104 Pool,_104 Route,_104 SwapRouter,_104 CurrencyAmount,_104 TradeType,_104 Percent_104} from "@uniswap/v3-sdk";_104import { Token, SwapOptions } from "@uniswap/sdk-core";_104import JSBI from "jsbi";_104import { OperationType, MetaTransactionData } from "@safe-global/types-kit";_104_104// Set up viem clients and accounts_104const account = privateKeyToAccount(AGENT_PRIVATE_KEY as `0x${string}`);_104_104const publicClient = createPublicClient({_104 transport: http(RPC_URL!)_104});_104const walletClient = createWalletClient({_104 transport: http(RPC_URL!)_104});_104_104const chainId = (await publicClient.getChainId());_104_104// Example Values for WETH/USDC Uniswap Pool on Sepolia:_104const WETH_ADDRESS = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";_104const USDC_ADDRESS = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";_104const USDC_ETH_POOL_ADDRESS = "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640";_104const SWAP_ROUTER_ADDRESS = "0xE592427A0AEce92De3Edee1F18E0157C05861564"; // Uniswap V3 Router_104const INPUT_AMOUNT = "100000000000"; // Amount of ETH to swap to USDC_104const OUTOUT_AMOUNT = "0"; // 0 USDC_104_104// Define token details_104const USDC = new Token(chainId, USDC_ADDRESS, 6, "USDC", "USD Coin");_104const WETH = new Token(chainId, WETH_ADDRESS, 18, "WETH", "Wrapped Ether");_104_104const callDataApprove = encodeFunctionData({_104 abi: WETH_ABI,_104 functionName: "approve",_104 args: [SWAP_ROUTER_ADDRESS, INPUT_AMOUNT],_104});_104_104const safeApproveTx: MetaTransactionData = {_104 to: WETH_ADDRESS,_104 value: "0",_104 data: callDataApprove,_104 operation: OperationType.Call,_104};_104_104const options: SwapOptions = {_104 slippageTolerance: new Percent(50, 10_000), // 50 bips, or 0.50%_104 deadline: Math.floor(Date.now() / 1000) + 60 * 20, // 20 minutes from the current Unix time_104 recipient: SAFE_ADDRESS,_104};_104_104const poolInfo = await fetchPoolData(publicClient, USDC_ETH_POOL_ADDRESS);_104_104// Create the pool object_104const pool = new Pool(_104 WETH,_104 USDC,_104 FeeAmount.MEDIUM,_104 JSBI.BigInt(poolInfo.sqrtPriceX96.toString()),_104 JSBI.BigInt(poolInfo.liquidity.toString()),_104 poolInfo.tick_104);_104_104const swapRoute = new Route([pool], WETH, USDC);_104_104const uncheckedTrade = Trade.createUncheckedTrade({_104 tradeType: TradeType.EXACT_INPUT,_104 route: swapRoute,_104 inputAmount: CurrencyAmount.fromRawAmount(WETH, _104 INPUT_AMOUNT_104 ),_104 outputAmount: CurrencyAmount.fromRawAmount(USDC, OUTOUT_AMOUNT),_104});_104_104const methodParameters = SwapRouter.swapCallParameters(_104 [uncheckedTrade],_104 options_104);_104_104const safeSwapTx: MetaTransactionData = {_104 to: SWAP_ROUTER_ADDRESS,_104 value: methodParameters.value,_104 data: methodParameters.calldata,_104 operation: OperationType.Call,_104};_104_104const safeTx = await preExistingSafe.createTransaction({_104 transactions: [safeApproveTx, safeSwapTx],_104 onlyCalls: true,_104});_104_104// You might need to collect more signatures here, depending on the threshold_104_104const txResponse = await preExistingSafe.executeTransaction(safeTx);_104 await publicClient.waitForTransactionReceipt({_104 hash: txResponse.hash as `0x${string}`,_104});_104_104console.log(`Deposit and approve transaction: [${txResponse.hash}]`);
Now your AI agent executed a swap on Uniswap.
Next steps
You can find more information about Swaps on Uniswap in their docs about swaps (opens in a new tab).
If you have a technical question about Safe Smart Accounts, feel free to reach out on Stack Exchange (opens in a new tab) with the safe-core tag.