Skip to content
    0x Swap v2 Headless Example@0xproject
    generated-icon.png
    index.ts
    README.md
    tsconfig.json
    Packager files
    bun.lockb
    package-lock.json
    package.json
    Config files
    .replit
    import { config as dotenv } from "dotenv";
    import {
    createWalletClient,
    http,
    getContract,
    erc20Abi,
    parseUnits,
    maxUint256,
    publicActions,
    concat,
    numberToHex,
    size,
    } from "viem";
    import type { Hex } from "viem";
    import { privateKeyToAccount } from "viem/accounts";
    import { base } from "viem/chains";
    import { wethAbi } from "./abi/weth-abi";

    const qs = require("qs");

    // load env vars
    dotenv();
    const { PRIVATE_KEY, ZERO_EX_API_KEY, ALCHEMY_HTTP_TRANSPORT_URL } =
    process.env;

    // validate requirements
    if (!PRIVATE_KEY) throw new Error("missing PRIVATE_KEY.");
    if (!ZERO_EX_API_KEY) throw new Error("missing ZERO_EX_API_KEY.");
    if (!ALCHEMY_HTTP_TRANSPORT_URL)
    throw new Error("missing ALCHEMY_HTTP_TRANSPORT_URL.");

    // fetch headers
    const headers = new Headers({
    "Content-Type": "application/json",
    "0x-api-key": ZERO_EX_API_KEY,
    "0x-version": "v2",
    });

    // setup wallet client
    const client = createWalletClient({
    account: privateKeyToAccount(`0x${PRIVATE_KEY}` as `0x${string}`),
    chain: base,
    transport: http(ALCHEMY_HTTP_TRANSPORT_URL),
    }).extend(publicActions); // extend wallet client with publicActions for public client

    const [address] = await client.getAddresses();

    // set up contracts
    const usdc = getContract({
    address: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
    abi: erc20Abi,
    client,
    });
    const weth = getContract({
    address: "0x4200000000000000000000000000000000000006",
    abi: wethAbi,
    client,
    });

    const main = async () => {
    // specify sell amount
    const sellAmount = parseUnits("0.1", await usdc.read.decimals());

    // 1. fetch price
    const priceParams = new URLSearchParams({
    chainId: client.chain.id.toString(),
    sellToken: usdc.address,
    buyToken: weth.address,
    sellAmount: sellAmount.toString(),
    taker: client.account.address,
    });

    const priceResponse = await fetch(
    "https://api.0x.org/swap/permit2/price?" + priceParams.toString(),
    {
    headers,
    }
    );

    const price = await priceResponse.json();
    console.log("Fetching price to swap 0.1 USDC for WETH");
    console.log(
    `https://api.0x.org/swap/permit2/price?${priceParams.toString()}`
    );
    console.log("priceResponse: ", price);

    // 2. check if taker needs to set an allowance for Permit2

    if (price.issues.allowance !== null) {
    try {
    const { request } = await usdc.simulate.approve([
    price.issues.allowance.spender,
    maxUint256,
    ]);
    console.log("Approving Permit2 to spend USDC...", request);
    // set approval
    const hash = await usdc.write.approve(request.args);
    console.log(
    "Approved Permit2 to spend USDC.",
    await client.waitForTransactionReceipt({ hash })
    );
    } catch (error) {
    console.log("Error approving Permit2:", error);
    }
    } else {
    console.log("USDC already approved for Permit2");
    }

    // 3. fetch quote
    const quoteParams = new URLSearchParams();
    for (const [key, value] of priceParams.entries()) {
    quoteParams.append(key, value);
    }

    const quoteResponse = await fetch(
    "https://api.0x.org/swap/permit2/quote?" + quoteParams.toString(),
    {
    headers,
    }
    );

    const quote = await quoteResponse.json();
    console.log("Fetching quote to swap 0.1 USDC for WETH");
    console.log("quoteResponse: ", quote);

    // 4. sign permit2.eip712 returned from quote
    let signature: Hex | undefined;
    if (quote.permit2?.eip712) {
    try {
    signature = await client.signTypedData(quote.permit2.eip712);
    console.log("Signed permit2 message from quote response");
    } catch (error) {
    console.error("Error signing permit2 coupon:", error);
    }

    // 5. append sig length and sig data to transaction.data
    if (signature && quote?.transaction?.data) {
    const signatureLengthInHex = numberToHex(size(signature), {
    signed: false,
    size: 32,
    });

    const transactionData = quote.transaction.data as Hex;
    const sigLengthHex = signatureLengthInHex as Hex;
    const sig = signature as Hex;

    quote.transaction.data = concat([transactionData, sigLengthHex, sig]);
    } else {
    throw new Error("Failed to obtain signature or transaction data");
    }
    }
    // 6. submit txn with permit2 signature
    if (signature && quote.transaction.data) {
    const nonce = await client.getTransactionCount({
    address: client.account.address,
    });

    const signedTransaction = await client.signTransaction({
    account: client.account,
    chain: client.chain,
    gas: !!quote?.transaction.gas
    ? BigInt(quote?.transaction.gas)
    : undefined,
    to: quote?.transaction.to,
    data: quote.transaction.data,
    value: quote?.transaction.value
    ? BigInt(quote.transaction.value)
    : undefined, // value is used for native tokens
    gasPrice: !!quote?.transaction.gasPrice
    ? BigInt(quote?.transaction.gasPrice)
    : undefined,
    nonce: nonce,
    });
    const hash = await client.sendRawTransaction({
    serializedTransaction: signedTransaction,
    });

    console.log("Transaction hash:", hash);

    console.log(`See tx details at https://basescan.org/tx/${hash}`);
    } else {
    console.error("Failed to obtain a signature, transaction not sent.");
    }
    };
    main();