In this guide, we’ll walk through the process of depositing USDC into Apex Omni, a perpetual DEX on Arbitrum, using the Aarc SDK. The complete implementation can be found in our example repository.

Prerequisites

  • Node.js and npm installed
  • An Ethereum wallet with private key
  • API key from Aarc
  • USDC tokens on Arbitrum
  • Basic understanding of TypeScript and Ethereum

Setup

1

Install SDK

Install the Aarc SDK and required dependencies:

npm install @aarc-xyz/core-viem ethers dotenv
2

Configure Environment

Create a .env file with your credentials:

API_KEY=your_aarc_api_key
PRIVATE_KEY=your_wallet_private_key
RPC_URL=your_arbitrum_rpc_url
3

Initialize SDK

Set up the SDK and wallet configuration:

import { AarcCore } from "@aarc-xyz/core-viem";
import { ethers } from "ethers";

const API_KEY = process.env.API_KEY!;
const PRIVATE_KEY = process.env.PRIVATE_KEY!;
const RPC_URL = process.env.RPC_URL!;

export const aarcCoreSDK = new AarcCore(API_KEY);
export const walletProvider = ethers.getDefaultProvider(RPC_URL);
export const wallet = new ethers.Wallet(PRIVATE_KEY, walletProvider);

Implementation

1

Define Constants

Set up the necessary contract addresses and token details:

const APEX_OMNI_ADDRESS = "0x3169844a120C0f517B4eB4A750c08d8518C8466a";
const USDC = {
  symbol: "USDC",
  address: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
  decimals: 6,
};

const DESTINATION_TOKEN = {
  decimals: USDC.decimals,
  chainId: 42161, // Arbitrum chain ID
  address: USDC.address,
};
2

Create Deposit Call Data Generator

Implement the function to generate the deposit call data:

function generateDepositCallData(
  tokenAddress: string,
  amount: string,
  userAddress: string
): string {
  const apexOmniInterface = new ethers.Interface([
    "function depositERC20(address _token, uint104 _amount, bytes32 _zkLinkAddress, uint8 _subAccountId, bool _mapping) external",
  ]);

  // Format the zkLink address
  const zkLinkAddress = `0x000000000000000000000000${userAddress.slice(2)}`;

  return apexOmniInterface.encodeFunctionData("depositERC20", [
    tokenAddress,
    amount,
    zkLinkAddress,
    0, // subAccountId
    false,
  ]);
}
3

Check Token Balance

Verify USDC balance before proceeding:

const balance = await getMultichainBalance(wallet.address, {
  tokenAddress: DESTINATION_TOKEN.address,
  tokenChainId: DESTINATION_TOKEN.chainId,
  tokenAmount: amount,
});

const fromToken = balance[DESTINATION_TOKEN.chainId.toString()]?.balances?.find(
  (b) => b.symbol === "USDC"
);

if (!fromToken) {
  throw new Error("No USDC balance available");
}
4

Get Deposit Address

Fetch the deposit address from Aarc:

const depositData = await getDepositAddress({
  contractPayload: callData,
  fromToken: {
    decimals: fromToken.decimals,
    chainId: DESTINATION_TOKEN.chainId,
    address: fromToken.token_address,
  },
  fromTokenAmount: amount,
  destinationToken: DESTINATION_TOKEN,
  destinationWalletAddress: APEX_OMNI_ADDRESS,
  requestedAmount: amount,
});
5

Execute Transaction

Send the transaction and notify Aarc:

const trxHash = await executeTransaction(depositData);
console.log("Transaction completed with hash:", trxHash);

Usage Example

async function main() {
  try {
    // Amount in smallest unit (6 decimals for USDC)
    const amount = "1000000"; // 1 USDC
    await depositIntoPerp(amount);
  } catch (error) {
    console.error("Error in main execution:", error);
  }
}

Understanding the Flow

  1. Balance Check: Verifies if you have sufficient USDC on Arbitrum
  2. Call Data Generation: Creates the encoded function call for Apex Omni deposit
  3. Deposit Address: Gets a unique deposit address from Aarc
  4. Transaction Execution: Sends the transaction and notifies Aarc of completion

For the complete implementation with error handling and utility functions, check out our example repository.