Skip to content

Instantly share code, notes, and snippets.

@gaupoit
Created November 21, 2025 07:36
Show Gist options
  • Select an option

  • Save gaupoit/4bbe6967597527e8fb9552024788a195 to your computer and use it in GitHub Desktop.

Select an option

Save gaupoit/4bbe6967597527e8fb9552024788a195 to your computer and use it in GitHub Desktop.
MachineX Swap
import {
createPublicClient,
createWalletClient,
http,
parseUnits,
formatUnits,
zeroAddress
} from "viem";
import { peaq } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
// --- 1. CONFIGURATION ---
const PRIVATE_KEY = ""; // Replace with your Private Key
// The Pool Address you want to use (e.g., WPEAQ/TOPS 1%)
const POOL_ADDRESS = "0xD5669Ada8FE453A68Aa441C38613d45fe0ae2003";
// Standard Uniswap V3 SwapRouter02 Address
const ROUTER_ADDRESS = "0x723eA41937d7C1F5a4C5ccFa5bfF82210de17C71";
// Amount to swap (in human readable units, e.g., "1.5")
// The script will automatically detect decimals.
const AMOUNT_TO_SWAP = "0.001";
// --- 2. ABIS ---
// Minimal Pool ABI to get details
const POOL_ABI = [
{ inputs: [], name: "token0", outputs: [{ type: "address" }], stateMutability: "view", type: "function" },
{ inputs: [], name: "token1", outputs: [{ type: "address" }], stateMutability: "view", type: "function" },
{ inputs: [], name: "fee", outputs: [{ type: "uint24" }], stateMutability: "view", type: "function" },
{ inputs: [], name: "liquidity", outputs: [{ type: "uint128" }], stateMutability: "view", type: "function" }
];
// Minimal ERC20 ABI
const ERC20_ABI = [
{ inputs: [{ name: "account", type: "address" }], name: "balanceOf", outputs: [{ type: "uint256" }], stateMutability: "view", type: "function" },
{ inputs: [{ name: "spender", type: "address" }, { name: "amount", type: "uint256" }], name: "approve", outputs: [{ type: "bool" }], stateMutability: "nonpayable", type: "function" },
{ inputs: [], name: "decimals", outputs: [{ type: "uint8" }], stateMutability: "view", type: "function" },
{ inputs: [], name: "symbol", outputs: [{ type: "string" }], stateMutability: "view", type: "function" }
];
// Router ABI for exactInputSingle
const ROUTER_ABI = [
{
inputs: [
{
components: [
{ name: "tokenIn", type: "address" },
{ name: "tokenOut", type: "address" },
{ name: "fee", type: "uint24" },
{ name: "recipient", type: "address" },
{ name: "amountIn", type: "uint256" },
{ name: "amountOutMinimum", type: "uint256" },
{ name: "sqrtPriceLimitX96", type: "uint160" },
],
name: "params",
type: "tuple",
},
],
name: "exactInputSingle",
outputs: [{ name: "amountOut", type: "uint256" }],
stateMutability: "payable",
type: "function",
},
];
// --- 3. MAIN SCRIPT ---
async function main() {
// Setup Clients
const publicClient = createPublicClient({ chain: peaq, transport: http() });
const account = privateKeyToAccount(PRIVATE_KEY);
const walletClient = createWalletClient({ account, chain: peaq, transport: http() });
console.log(`--- Uniswap V3 Pool Swap ---`);
console.log(`Wallet: ${account.address}`);
console.log(`Target Pool: ${POOL_ADDRESS}`);
try {
// 1. Fetch Pool Details
// We use multicall to fetch token0, token1, and fee in one go
const [token0Result, token1Result, feeResult, liquidityResult] = await publicClient.multicall({
contracts: [
{ address: POOL_ADDRESS, abi: POOL_ABI, functionName: "token0" },
{ address: POOL_ADDRESS, abi: POOL_ABI, functionName: "token1" },
{ address: POOL_ADDRESS, abi: POOL_ABI, functionName: "fee" },
{ address: POOL_ADDRESS, abi: POOL_ABI, functionName: "liquidity" }
],
allowFailure: false
});
const token0 = token0Result;
const token1 = token1Result;
const fee = feeResult;
const liquidity = liquidityResult;
console.log(`\nPool Data Loaded:`);
console.log(`Token 0: ${token0}`);
console.log(`Token 1: ${token1}`);
console.log(`Fee Tier: ${fee} (${fee / 10000}%)`);
console.log(`Liquidity: ${liquidity}`);
if (liquidity === 0n) {
throw new Error("Pool has no liquidity!");
}
// 2. Determine Swap Direction (What does the user have?)
// Fetch user balances and decimals for both tokens
const [
bal0, bal1,
dec0, dec1,
sym0, sym1
] = await publicClient.multicall({
contracts: [
{ address: token0, abi: ERC20_ABI, functionName: "balanceOf", args: [account.address] },
{ address: token1, abi: ERC20_ABI, functionName: "balanceOf", args: [account.address] },
{ address: token0, abi: ERC20_ABI, functionName: "decimals" },
{ address: token1, abi: ERC20_ABI, functionName: "decimals" },
{ address: token0, abi: ERC20_ABI, functionName: "symbol" },
{ address: token1, abi: ERC20_ABI, functionName: "symbol" }
],
allowFailure: false
});
console.log(`\nYour Balances:`);
console.log(`- ${sym0}: ${formatUnits(bal0, dec0)}`);
console.log(`- ${sym1}: ${formatUnits(bal1, dec1)}`);
// Logic: We try to swap the token specified in configuration (AMOUNT_TO_SWAP).
// But we need to know which one to treat as input.
// For this example, we'll assume we swap Token 0 -> Token 1.
// You can change this logic to swap based on which balance is higher.
let tokenIn, tokenOut, amountInRaw, decimalsIn, symbolIn;
// Simple decision logic: Check if we have enough Token 0. If yes, swap 0->1. Else check Token 1.
// We parse the target amount based on decimals.
const amountIn0 = parseUnits(AMOUNT_TO_SWAP, dec0);
const amountIn1 = parseUnits(AMOUNT_TO_SWAP, dec1);
if (bal0 >= amountIn0) {
console.log(`\nConfig: Swapping ${AMOUNT_TO_SWAP} ${sym0} -> ${sym1}`);
tokenIn = token0;
tokenOut = token1;
amountInRaw = amountIn0;
decimalsIn = dec0;
symbolIn = sym0;
} else if (bal1 >= amountIn1) {
console.log(`\nConfig: Swapping ${AMOUNT_TO_SWAP} ${sym1} -> ${sym0}`);
tokenIn = token1;
tokenOut = token0;
amountInRaw = amountIn1;
decimalsIn = dec1;
symbolIn = sym1;
} else {
throw new Error(`Insufficient balance in either token to swap ${AMOUNT_TO_SWAP}`);
}
// 3. Approve Router
console.log(`\nStep 1: Approving Router to spend ${symbolIn}...`);
const approveHash = await walletClient.writeContract({
address: tokenIn,
abi: ERC20_ABI,
functionName: "approve",
args: [ROUTER_ADDRESS, amountInRaw]
});
console.log(`Approval sent: ${approveHash}`);
await publicClient.waitForTransactionReceipt({ hash: approveHash });
console.log("Approval Confirmed.");
// 4. Execute Swap (exactInputSingle)
console.log(`\nStep 2: Executing Swap...`);
const params = {
tokenIn: tokenIn,
tokenOut: tokenOut,
fee: fee, // Use the fee we fetched from the pool!
recipient: account.address,
amountIn: amountInRaw,
amountOutMinimum: 0n, // Be careful in production! Calculate slippage.
sqrtPriceLimitX96: 0n, // No limit
};
const swapHash = await walletClient.writeContract({
address: ROUTER_ADDRESS,
abi: ROUTER_ABI,
functionName: "exactInputSingle",
args: [params]
});
console.log(`Swap transaction sent: ${swapHash}`);
const receipt = await publicClient.waitForTransactionReceipt({ hash: swapHash });
console.log(`Swap Mined! Status: ${receipt.status}`);
console.log(`Transaction: https://etherscan.io/tx/${swapHash}`);
} catch (e) {
console.error("Error:", e);
}
}
main();
@gaupoit
Copy link
Author

gaupoit commented Nov 21, 2025

Error when running this code base

Error: ContractFunctionExecutionError: The contract function "exactInputSingle" reverted with the following reason:
VM Exception while processing transaction: revert

Contract Call:
  address:   0x723eA41937d7C1F5a4C5ccFa5bfF82210de17C71
  function:  exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96))
  args:                      ({"tokenIn":"0x3cD66d2e1fac1751B0A20BeBF6cA4c9699Bb12d7","tokenOut":"0x6C1cA31A9F3A57Bb680f82A8FE97Fc00Ac4aAd21","fee":10000,"recipient":"0x75AcA149492fc739Cbc5C117948a8028f1c7Dc44","amountIn":"1000000000000000","amountOutMinimum":"0","sqrtPriceLimitX96":"0"})
  sender:    0x75AcA149492fc739Cbc5C117948a8028f1c7Dc44

Docs: https://viem.sh/docs/contract/writeContract
Version: [email protected]
    at getContractError (file:///Users/vietdht/WebstormProjects/swap-sample/node_modules/viem/_esm/utils/errors/getContractError.js:33:12)
    at writeContract.internal (file:///Users/vietdht/WebstormProjects/swap-sample/node_modules/viem/_esm/actions/wallet/writeContract.js:82:19)
    at process.processTicksAndRejections (node:internal/process/task_queues:103:5)
    at async main (file:///Users/vietdht/WebstormProjects/swap-sample/index.js:187:22) {
  cause: ContractFunctionRevertedError: The contract function "exactInputSingle" reverted with the following reason:
  VM Exception while processing transaction: revert
  
  Version: [email protected]
      at file:///Users/vietdht/WebstormProjects/swap-sample/node_modules/viem/_esm/utils/errors/getContractError.js:22:20
      at getContractError (file:///Users/vietdht/WebstormProjects/swap-sample/node_modules/viem/_esm/utils/errors/getContractError.js:32:7)
      at writeContract.internal (file:///Users/vietdht/WebstormProjects/swap-sample/node_modules/viem/_esm/actions/wallet/writeContract.js:82:19)
      at process.processTicksAndRejections (node:internal/process/task_queues:103:5)
      at async main (file:///Users/vietdht/WebstormProjects/swap-sample/index.js:187:22) {
    details: undefined,
    docsPath: undefined,
    metaMessages: undefined,
    shortMessage: 'The contract function "exactInputSingle" reverted with the following reason:\n' +
      'VM Exception while processing transaction: revert',
    version: '2.39.3',
    data: undefined,
    raw: '0x',
    reason: 'VM Exception while processing transaction: revert',
    signature: undefined
  },
  details: undefined,
  docsPath: '/docs/contract/writeContract',
  metaMessages: [
    'Contract Call:',
    '  address:   0x723eA41937d7C1F5a4C5ccFa5bfF82210de17C71\n' +
      '  function:  exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96))\n' +
      '  args:                      ({"tokenIn":"0x3cD66d2e1fac1751B0A20BeBF6cA4c9699Bb12d7","tokenOut":"0x6C1cA31A9F3A57Bb680f82A8FE97Fc00Ac4aAd21","fee":10000,"recipient":"0x75AcA149492fc739Cbc5C117948a8028f1c7Dc44","amountIn":"1000000000000000","amountOutMinimum":"0","sqrtPriceLimitX96":"0"})\n' +
      '  sender:    0x75AcA149492fc739Cbc5C117948a8028f1c7Dc44'
  ],
  shortMessage: 'The contract function "exactInputSingle" reverted with the following reason:\n' +
    'VM Exception while processing transaction: revert',
  version: '2.39.3',
  abi: [
    {
      inputs: [Array],
      name: 'exactInputSingle',
      outputs: [Array],
      stateMutability: 'payable',
      type: 'function'
    }
  ],
  args: [
    {
      tokenIn: '0x3cD66d2e1fac1751B0A20BeBF6cA4c9699Bb12d7',
      tokenOut: '0x6C1cA31A9F3A57Bb680f82A8FE97Fc00Ac4aAd21',
      fee: 10000,
      recipient: '0x75AcA149492fc739Cbc5C117948a8028f1c7Dc44',
      amountIn: 1000000000000000n,
      amountOutMinimum: 0n,
      sqrtPriceLimitX96: 0n
    }
  ],
  contractAddress: '0x723eA41937d7C1F5a4C5ccFa5bfF82210de17C71',
  formattedArgs: undefined,
  functionName: 'exactInputSingle',
  sender: '0x75AcA149492fc739Cbc5C117948a8028f1c7Dc44'
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment