Overview
Swaps can be done through a website or programmatically through a standard Uniswap V3 API interface.
Below are the requirements
Nodejsethersdotenv
Below is the full source code:
import { ethers } from “ethers”;
import “dotenv/config”;
const QUOTER_CONTRACT_ADDRESS = “0x0880484412B5bAa18B3fa2904a500c08f0fE79D2”;
// prettier-ignore
const QUOTER_ABI = [ { “inputs”: [ { “internalType”: “address”, “name”: “_factory”, “type”: “address” }, { “internalType”: “address”, “name”: “_WETH9”, “type”: “address” } ], “stateMutability”: “nonpayable”, “type”: “constructor” }, { “inputs”: [], “name”: “WETH9”, “outputs”: [{ “internalType”: “address”, “name”: “”, “type”: “address” }], “stateMutability”: “view”, “type”: “function” }, { “inputs”: [], “name”: “factory”, “outputs”: [{ “internalType”: “address”, “name”: “”, “type”: “address” }], “stateMutability”: “view”, “type”: “function” }, { “inputs”: [ { “internalType”: “bytes”, “name”: “path”, “type”: “bytes” }, { “internalType”: “uint256”, “name”: “amountIn”, “type”: “uint256” } ], “name”: “quoteExactInput”, “outputs”: [ { “internalType”: “uint256”, “name”: “amountOut”, “type”: “uint256” }, { “internalType”: “uint160[]”, “name”: “sqrtPriceX96AfterList”, “type”: “uint160[]” }, { “internalType”: “uint32[]”, “name”: “initializedTicksCrossedList”, “type”: “uint32[]” }, { “internalType”: “uint256”, “name”: “gasEstimate”, “type”: “uint256” } ], “stateMutability”: “nonpayable”, “type”: “function” }, { “inputs”: [ { “components”: [ { “internalType”: “address”, “name”: “tokenIn”, “type”: “address” }, { “internalType”: “address”, “name”: “tokenOut”, “type”: “address” }, { “internalType”: “uint256”, “name”: “amountIn”, “type”: “uint256” }, { “internalType”: “uint24”, “name”: “fee”, “type”: “uint24” }, { “internalType”: “uint160”, “name”: “sqrtPriceLimitX96”, “type”: “uint160” } ], “internalType”: “struct IQuoterV2.QuoteExactInputSingleParams”, “name”: “params”, “type”: “tuple” } ], “name”: “quoteExactInputSingle”, “outputs”: [ { “internalType”: “uint256”, “name”: “amountOut”, “type”: “uint256” }, { “internalType”: “uint160”, “name”: “sqrtPriceX96After”, “type”: “uint160” }, { “internalType”: “uint32”, “name”: “initializedTicksCrossed”, “type”: “uint32” }, { “internalType”: “uint256”, “name”: “gasEstimate”, “type”: “uint256” } ], “stateMutability”: “nonpayable”, “type”: “function” }, { “inputs”: [ { “internalType”: “bytes”, “name”: “path”, “type”: “bytes” }, { “internalType”: “uint256”, “name”: “amountOut”, “type”: “uint256” } ], “name”: “quoteExactOutput”, “outputs”: [ { “internalType”: “uint256”, “name”: “amountIn”, “type”: “uint256” }, { “internalType”: “uint160[]”, “name”: “sqrtPriceX96AfterList”, “type”: “uint160[]” }, { “internalType”: “uint32[]”, “name”: “initializedTicksCrossedList”, “type”: “uint32[]” }, { “internalType”: “uint256”, “name”: “gasEstimate”, “type”: “uint256” } ], “stateMutability”: “nonpayable”, “type”: “function” }, { “inputs”: [ { “components”: [ { “internalType”: “address”, “name”: “tokenIn”, “type”: “address” }, { “internalType”: “address”, “name”: “tokenOut”, “type”: “address” }, { “internalType”: “uint256”, “name”: “amount”, “type”: “uint256” }, { “internalType”: “uint24”, “name”: “fee”, “type”: “uint24” }, { “internalType”: “uint160”, “name”: “sqrtPriceLimitX96”, “type”: “uint160” } ], “internalType”: “struct IQuoterV2.QuoteExactOutputSingleParams”, “name”: “params”, “type”: “tuple” } ], “name”: “quoteExactOutputSingle”, “outputs”: [ { “internalType”: “uint256”, “name”: “amountIn”, “type”: “uint256” }, { “internalType”: “uint160”, “name”: “sqrtPriceX96After”, “type”: “uint160” }, { “internalType”: “uint32”, “name”: “initializedTicksCrossed”, “type”: “uint32” }, { “internalType”: “uint256”, “name”: “gasEstimate”, “type”: “uint256” } ], “stateMutability”: “nonpayable”, “type”: “function” }, { “inputs”: [ { “internalType”: “int256”, “name”: “amount0Delta”, “type”: “int256” }, { “internalType”: “int256”, “name”: “amount1Delta”, “type”: “int256” }, { “internalType”: “bytes”, “name”: “path”, “type”: “bytes” } ], “name”: “uniswapV3SwapCallback”, “outputs”: [], “stateMutability”: “view”, “type”: “function” } ]
const provider = new ethers.JsonRpcProvider(“https://rpc.o.xyz/”);
const quoterContract = new ethers.Contract(
QUOTER_CONTRACT_ADDRESS,
QUOTER_ABI,
provider
);
const signer = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
const params = {
tokenIn: “0xc7C3cff325c6976965edfEB05c024584e4e4BBDD”,
tokenOut: “0x91d70c3adde2f0f0c4fb0b2de69f37a7c7e326df”,
fee: 3000,
recipient: signer.address,
deadline: Math.floor(new Date().getTime() / 1000 + 60 * 10),
amountIn: ethers.parseUnits(“0.01”),
sqrtPriceLimitX96: 0,
};
main();
async function main() {
const transaction =
await quoterContract.quoteExactInputSingle.populateTransaction(params);
console.log({ transaction });
const receipt = await signer.sendTransaction(transaction);
console.log({ receipt });
}
Define Variables
const QUOTER_CONTRACT_ADDRESS = “0x0880484412B5bAa18B3fa2904a500c08f0fE79D2”;
// prettier-ignore
const QUOTER_ABI = [ { “inputs”: [ { “internalType”: “address”, “name”: “_factory”, “type”: “address” }, { “internalType”: “address”, “name”: “_WETH9”, “type”: “address” } ], “stateMutability”: “nonpayable”, “type”: “constructor” }, { “inputs”: [], “name”: “WETH9”, “outputs”: [{ “internalType”: “address”, “name”: “”, “type”: “address” }], “stateMutability”: “view”, “type”: “function” }, { “inputs”: [], “name”: “factory”, “outputs”: [{ “internalType”: “address”, “name”: “”, “type”: “address” }], “stateMutability”: “view”, “type”: “function” }, { “inputs”: [ { “internalType”: “bytes”, “name”: “path”, “type”: “bytes” }, { “internalType”: “uint256”, “name”: “amountIn”, “type”: “uint256” } ], “name”: “quoteExactInput”, “outputs”: [ { “internalType”: “uint256”, “name”: “amountOut”, “type”: “uint256” }, { “internalType”: “uint160[]”, “name”: “sqrtPriceX96AfterList”, “type”: “uint160[]” }, { “internalType”: “uint32[]”, “name”: “initializedTicksCrossedList”, “type”: “uint32[]” }, { “internalType”: “uint256”, “name”: “gasEstimate”, “type”: “uint256” } ], “stateMutability”: “nonpayable”, “type”: “function” }, { “inputs”: [ { “components”: [ { “internalType”: “address”, “name”: “tokenIn”, “type”: “address” }, { “internalType”: “address”, “name”: “tokenOut”, “type”: “address” }, { “internalType”: “uint256”, “name”: “amountIn”, “type”: “uint256” }, { “internalType”: “uint24”, “name”: “fee”, “type”: “uint24” }, { “internalType”: “uint160”, “name”: “sqrtPriceLimitX96”, “type”: “uint160” } ], “internalType”: “struct IQuoterV2.QuoteExactInputSingleParams”, “name”: “params”, “type”: “tuple” } ], “name”: “quoteExactInputSingle”, “outputs”: [ { “internalType”: “uint256”, “name”: “amountOut”, “type”: “uint256” }, { “internalType”: “uint160”, “name”: “sqrtPriceX96After”, “type”: “uint160” }, { “internalType”: “uint32”, “name”: “initializedTicksCrossed”, “type”: “uint32” }, { “internalType”: “uint256”, “name”: “gasEstimate”, “type”: “uint256” } ], “stateMutability”: “nonpayable”, “type”: “function” }, { “inputs”: [ { “internalType”: “bytes”, “name”: “path”, “type”: “bytes” }, { “internalType”: “uint256”, “name”: “amountOut”, “type”: “uint256” } ], “name”: “quoteExactOutput”, “outputs”: [ { “internalType”: “uint256”, “name”: “amountIn”, “type”: “uint256” }, { “internalType”: “uint160[]”, “name”: “sqrtPriceX96AfterList”, “type”: “uint160[]” }, { “internalType”: “uint32[]”, “name”: “initializedTicksCrossedList”, “type”: “uint32[]” }, { “internalType”: “uint256”, “name”: “gasEstimate”, “type”: “uint256” } ], “stateMutability”: “nonpayable”, “type”: “function” }, { “inputs”: [ { “components”: [ { “internalType”: “address”, “name”: “tokenIn”, “type”: “address” }, { “internalType”: “address”, “name”: “tokenOut”, “type”: “address” }, { “internalType”: “uint256”, “name”: “amount”, “type”: “uint256” }, { “internalType”: “uint24”, “name”: “fee”, “type”: “uint24” }, { “internalType”: “uint160”, “name”: “sqrtPriceLimitX96”, “type”: “uint160” } ], “internalType”: “struct IQuoterV2.QuoteExactOutputSingleParams”, “name”: “params”, “type”: “tuple” } ], “name”: “quoteExactOutputSingle”, “outputs”: [ { “internalType”: “uint256”, “name”: “amountIn”, “type”: “uint256” }, { “internalType”: “uint160”, “name”: “sqrtPriceX96After”, “type”: “uint160” }, { “internalType”: “uint32”, “name”: “initializedTicksCrossed”, “type”: “uint32” }, { “internalType”: “uint256”, “name”: “gasEstimate”, “type”: “uint256” } ], “stateMutability”: “nonpayable”, “type”: “function” }, { “inputs”: [ { “internalType”: “int256”, “name”: “amount0Delta”, “type”: “int256” }, { “internalType”: “int256”, “name”: “amount1Delta”, “type”: “int256” }, { “internalType”: “bytes”, “name”: “path”, “type”: “bytes” } ], “name”: “uniswapV3SwapCallback”, “outputs”: [], “stateMutability”: “view”, “type”: “function” } ]
const provider = new ethers.JsonRpcProvider(“https://rpc.o.xyz/”);
const quoterContract = new ethers.Contract(
QUOTER_CONTRACT_ADDRESS,
QUOTER_ABI,
provider
);
const signer = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);QUOTER_CONTRACT_ADDRESS: Is part of the V3 deployment contractsQUOTER_ABI: Is the abi of the contractprovider: Connect to the RPCquoterContract: Connect to the quoter contractsigner: Connect to your wallet
Params
const params = {
tokenIn: “0xc7C3cff325c6976965edfEB05c024584e4e4BBDD”,
tokenOut: “0x91d70c3adde2f0f0c4fb0b2de69f37a7c7e326df”,
fee: 3000,
recipient: signer.address,
deadline: Math.floor(new Date().getTime() / 1000 + 60 * 10),
amountIn: ethers.parseUnits(“0.01”),
sqrtPriceLimitX96: 0,
};tokenIn: ERC20 contract address you’re spendingtokenOut: ERC20 contract address you’re receivingfee: Are the fee tiers 1%, 0.3%, 0.05%, and 0.01%recipient: The receiving addressdeadline: How long the transaction is valid foramountIn: Amount (smallest unit) you’re spendingsqrtPriceLimitX96: No limit
Raw Transction
const transaction = await quoterContract.quoteExactInputSingle.populateTransaction(params);
Creates a raw transaction to sign.
Output
{
to: “0x0880484412B5bAa18B3fa2904a500c08f0fE79D2”,
data: “0xc6a5026a000000000000000000000000c7c3cff325c6976965edfeb05c024584e4e4bbdd00000000000000000000000091d70c3adde2f0f0c4fb0b2de69f37a7c7e326df000000000000000000000000000000000000000000000000002386f26fc100000000000000000000000000000000000000000000000000000000000000000bb80000000000000000000000000000000000000000000000000000000000000000”
}
Send Transaction
const receipt = await signer.sendTransaction(transaction);
Signs and broadcasts the transaction
Output
{
receipt: TransactionResponse {
provider: JsonRpcProvider {},
blockNumber: null,
blockHash: null,
index: undefined,
hash: “0x58f4da9449ae4a1eca01a05765e1d54c292d0bcf4935b206cf4747073f2bf3b7”,
type: 2,
to: “0x0880484412B5bAa18B3fa2904a500c08f0fE79D2”,
from: “0xa427305648aD5227098812EdA6FF82D980b2DbBD”,
nonce: 51,
gasLimit: 117236n,
gasPrice: undefined,
maxPriorityFeePerGas: 1000000n,
maxFeePerGas: 1000504n,
maxFeePerBlobGas: null,
data: “0xc6a5026a000000000000000000000000c7c3cff325c6976965edfeb05c024584e4e4bbdd00000000000000000000000091d70c3adde2f0f0c4fb0b2de69f37a7c7e326df000000000000000000000000000000000000000000000000002386f26fc100000000000000000000000000000000000000000000000000000000000000000bb80000000000000000000000000000000000000000000000000000000000000000”,
value: 0n,
chainId: 84841n,
signature: Signature { r: “0xe485ba3893b0543a77a1bb14f0e2661dc7af3f6b2d0b3b917c8fbf4eaa6103c3”, s: “0x15e605c7e1fd6a64f5ca5d3f519c085367ce1250bf7647a6450bf9aef549c98c”, yParity: 0, networkV: null },
accessList: [],
blobVersionedHashes: null
}
}
Approve Spending (prerequisite)
const SWAP_ROUTER_CONTRACT_ADDRESS = ‘0x660C85710a1cd10e8345BB30C80A49f661486007’
const approveTransaction = await tokenContract.approve.populateTransaction(
SWAP_ROUTER_CONTRACT_ADDRESS,
ethers.parseEther(amount.toString())
);
Make sure the wallet has approved spending.
How to Programmatically Swap a V3 DEX was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.