Quickstart
This guide will show you how to send transaction requests to Peaze via our API using ethers.js.
This example uses the following:
- Source chain: Polygon, chain ID 137
- Destination chain: Optimism, chain ID 10
- Target contract: 0xFd4885148054b43518980e8007417d8fB7348EF6
- USDC contract on Optimism: 0x7F5c764cBc14f9669B88837ca1490cCa17c31607
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);
});