Don’t have an API key? Get started here.

This guide will show you how to send transaction requests to Peaze via our API using ethers.js.

This example uses the following:

The target contract on Optimism is a simple wrapped USDC contract that contains a mint function, which, when called, takes the specified amount of USDC from the caller and sends them the equivalent amount of a wrapped USDC token. Don’t worry, we can always unwrap it back into regular USDC!

Step 1. Prepare data

import axios from "axios";
import { Interface, Wallet, parseUnits } from "ethers";
import { config } from "dotenv";
config();

const wallet = new Wallet(process.env.WALLET_PRIVATE_KEY!); // set it in .env
const SRC_CHAIN_ID = 137;
const DST_CHAIN_ID = 10;
const TARGET_ADDRESS = "0xFd4885148054b43518980e8007417d8fB7348EF6";
const DST_USDC_ADDRESS = "0x7F5c764cBc14f9669B88837ca1490cCa17c31607";
const AMOUNT_TO_SEND = "1.0"; // 1 USDC

const axiosClient = axios.create({
  baseURL: "https://api.peaze.com/api",
  headers: {
    "Content-Type": "application/json",
    "X-Api-Key": process.env.PEAZE_API_KEY, // set PEAZE_API_KEY in .env
  },
});

Step 2: Estimate transaction

Call the Estimate Transaction endpoint:

async function estimateTransaction() {
  const usdcTokenInterface = new Interface(["function approve(address,uint256)"]);
  const wUsdcTokenInterface = new Interface(["function mint(uint256)"]);

  const tokenAmount = parseUnits(AMOUNT_TO_SEND, 6); // 6 = decimals for USDC on the dst chain

  // Encode a tx to approve the WUSDC contract to transfer USDC from the caller
  const approvalData = usdcTokenInterface.encodeFunctionData("approve", [
    TARGET_ADDRESS,
    tokenAmount,
  ]);

  // Encode a tx to mint WUSDC tokens
  const mintData = wUsdcTokenInterface.encodeFunctionData("mint", [
    tokenAmount,
  ]);

  // Send the request to the estimate endpoint
  const response = await axiosClient.post("/v1/cross-chain/estimate", {
    transactions: [
      {
        to: DST_USDC_ADDRESS,
        data: approvalData,
      },
      {
        to: TARGET_ADDRESS,
        data: mintData,
      },
    ],
    userAddress: wallet.address,
    tokenAmount: tokenAmount.toString(),
    sourceChain: SRC_CHAIN_ID,
    destinationChain: DST_CHAIN_ID,
  });

  return response.data;
}

const estimateData = await estimateTransaction();

You will receive a response that looks like this:

{
    "costSummary": {
        "tokenAddress": "0x...",
        "tokenSymbol": "USDC",
        "totalAmount": 1.53,
        "baseAmount": 1.0,
        "gasCost": 0.43,
        "peazeFee": 0.1,
        "gasCostInWei": "...",
        "gasUsedOnDst": "..."
    },
    "typedData": {
        "td712": {...},
        "td2612": {...}
    }
}

Here, tokenAddress is the USDC contract address on the source chain, i.e. the currency that the user will pay for the cross-chain transaction in.

For more details, please check out the reference here.

Step 3: Sign typed data

const estimateData = await estimateTransaction();

// Extract the necessary data from the estimate
const {
  typedData: { td712, td2612 },
  costSummary: { gasCostInWei, gasUsedOnDst },
} = 10estimateData;

// Sign the typed data with the user's wallet
const sig712 = await wallet.signTypedData(
  td712.domain,
  td712.types,
  td712.message
);
const sig2612 = await wallet.signTypedData(
  td2612.domain,
  td2612.types,
  td2612.message
);

Step 4: Execute Transaction

Call the Cross-chain Execute Transaction endpoint, passing in the signatures from step 3.

// Form the request payload
const executeRequestData = {
  sourceChain: SRC_CHAIN_ID,
  destinationChain: DST_CHAIN_ID,
  gasUsed: gasUsedOnDst,
  txValue: gasCostInWei, // this will be paid (by Peaze) to the Stargate bridge
  signatures: {
    sig712,
    sig2612,
  },
  message: td712.message,
  domain: td712.domain,
};

// Send the request to the execute endpoint
const { data } = await axiosClient.post(
  "/v1/cross-chain/execute",
  executeRequestData
);

You will receive a response that looks like this:

{
  "transactionId": "<transactionId>",
  "transactionHash": "0x...",
  "blockExplorerUrl": "https://..."
}

That’s it!

You can now now check the status of the transaction on the source chain using the returned block explorer URL. You can also check its status on the destination chain by using the Layer 0 Explorer, where you simply need to paste in the returned transaction hash.


Full code

We include a couple of additional useful features in the full version of the code below, namely:

  • A MAX_TOTAL_COST variable that allows you to abort the transaction if the total cost is too high
  • Logging of the transaction estimate and execution data
  • Error handling, which shows how to extract the error message and details from the API response
import axios from "axios";
import { Interface, Wallet, parseUnits } from "ethers";
import { config } from "dotenv";
config();

const wallet = new Wallet(process.env.WALLET_PRIVATE_KEY!); // set it in .env
const SRC_CHAIN_ID = 137;
const DST_CHAIN_ID = 10;
const TARGET_ADDRESS = "0xFd4885148054b43518980e8007417d8fB7348EF6";
const DST_USDC_ADDRESS = "0x7F5c764cBc14f9669B88837ca1490cCa17c31607";
const AMOUNT_TO_SEND = "1.0"; // 1 USDC
const MAX_TOTAL_COST = "2.5"; // abort if the USDC cost is higher than this

const axiosClient = axios.create({
  baseURL: "https://api.peaze.com/api",
  headers: {
    "Content-Type": "application/json",
    "X-Api-Key": process.env.PEAZE_API_KEY, // set PEAZE_API_KEY in .env
  },
});

async function estimateTransaction() {
  const usdcTokenInterface = new Interface([
    "function approve(address,uint256)",
  ]);
  const wUsdcTokenInterface = new Interface(["function mint(uint256)"]);

  const tokenAmount = parseUnits(AMOUNT_TO_SEND, 6); // 6 = decimals for USDC on the target chain

  // Encode a tx to approve the WUSDC contract to transfer USDC from the caller
  const approvalData = usdcTokenInterface.encodeFunctionData("approve", [
    TARGET_ADDRESS,
    tokenAmount,
  ]);

  // Encode a tx to mint WUSDC tokens
  const mintData = wUsdcTokenInterface.encodeFunctionData("mint", [
    tokenAmount,
  ]);

  // Send the request to the estimate endpoint
  const response = await axiosClient.post("/v1/cross-chain/estimate", {
    transactions: [
      {
        to: DST_USDC_ADDRESS,
        data: approvalData,
      },
      {
        to: TARGET_ADDRESS,
        data: mintData,
      },
    ],
    userAddress: wallet.address,
    tokenAmount: tokenAmount.toString(),
    sourceChain: SRC_CHAIN_ID,
    destinationChain: DST_CHAIN_ID,
  });

  return response.data;
}

async function main() {
  console.log("Getting tx estimate...");
  const estimateData = await estimateTransaction();
  console.log(`Estimate data:\n${JSON.stringify(estimateData, null, 2)}\n`);

  // Check total cost before proceeding
  const totalCost: number = estimateData.costSummary.totalAmount;
  console.log(`Total cost (costSummary.totalAmount): ${totalCost} USDC\n`);

  if (totalCost > parseFloat(MAX_TOTAL_COST)) {
    console.log(`Cost is higher than ${MAX_TOTAL_COST}. Aborting.`);
    return;
  }

  // Extract the necessary data from the estimate
  const {
    typedData: { td712, td2612 },
    costSummary: { gasCostInWei, gasUsedOnDst },
  } = estimateData;

  // Sign the typed data with the user's wallet
  const sig712 = await wallet.signTypedData(
    td712.domain,
    td712.types,
    td712.message
  );
  const sig2612 = await wallet.signTypedData(
    td2612.domain,
    td2612.types,
    td2612.message
  );

  // Form the request payload
  const executeRequestData = {
    sourceChain: SRC_CHAIN_ID,
    destinationChain: DST_CHAIN_ID,
    gasUsed: gasUsedOnDst,
    txValue: gasCostInWei, // this will be paid (by Peaze) for bridging the funds
    signatures: {
      sig712,
      sig2612,
    },
    message: td712.message,
    domain: td712.domain,
  };

  // Send the request to the execute endpoint
  console.log("Executing transaction...");
  const { data } = await axiosClient.post(
    "/v1/cross-chain/execute",
    executeRequestData
  );
  console.log(`Transaction submitted:\n${JSON.stringify(data, null, 2)}\n`);
  console.log(
    "Monitor the cross-chain tx status on https://layerzeroscan.com.",
    "Simply search for the tx hash above."
  );
}

main().catch((e) => {
  const errorMsg = e.response?.data?.message ?? `${e}`;
  const errorDetails = JSON.stringify(e.response?.data?.data, null, 2);

  console.log("Oops... we got an error :(");
  console.log(errorMsg);
  if (errorDetails) console.log(`Error details:\n${errorDetails}`);
  process.exit(1);
});