Executing a transaction

To demonstrate MagicSpend++, we'll walk through a simple, real-world EVM transaction: transferring 1.0 USDC on Polygon. This compact example shows the full flow end-to-end—constructing ERC‑20 transfer calldata from the USDC ABI, requesting a canonical userOp hash from the backend, signing that hash with a Privy-backed wallet, producing EIP‑7702 authorization(s), and finally submitting everything to a submit endpoint. By following this sequence, you'll see how MagicSpend++ enables chain abstracted execution with minimal friction while keeping the signer experience familiar.

Step 1 — Build ERC‑20 Transfer Transaction Calldata

import { erc20Abi, encodeFunctionData, parseUnits } from 'viem';

const chainId = 137;
const usdc = '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174';
const recipient = '0x1111111111111111111111111111111111111111';

// 1. Convert 1.0 USDC to base units (6 decimals)
const amount = parseUnits('1.0', 6);

// 2. Encode transfer(to, amount) calldata
const encodedData = encodeFunctionData({
  abi: erc20Abi,
  functionName: 'transfer',
  args: [recipient, amount],
});

Step 2 — Build the chain abstracted transaction (with quote type + token addresses)

import axios from 'axios';

const chainId = 137; // Polygon

// Token the user settles in
const settlementToken = 'USDC'; 

// Token the user spends
const spendingTokenAddress = '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174';

// For a transfer, spending and settlement tokens are both USDC.
// Use 'AMOUNT_OUT' to send an exact recipient amount; 'AMOUNT_IN' to spend an exact input amount.
const quoteType = 'AMOUNT_OUT'; // or 'AMOUNT_IN'
const amount = '1000000'; // Formatted amount (USDC uses 6 decimals)

const { data: transactionData } = await axios.post('https://api.enclave.money/magicspend/build-transaction', {
  chainId,
  transactions: [{
    to: usdc
    
    // encodedData comes from Step 1 (ERC-20 transfer calldata built via viem/ethers)
    data: encodedData
  }],
  quoteType, // 'AMOUNT_OUT' | 'AMOUNT_IN'
  amount, // '1.0' => 1 USDC
  spendingTokenAddress,   // token user spends
  settlementToken // token user settle in (ex. 'USDC' or 'SOL')
}, {
  headers: {
    'Content-Type': 'application/json',
    'Authorization': process.env.ENCLAVE_API_KEY,
  }
});

const { 
  userOpHash, 
  authorizations, 
  settlementPlan, 
  expiryTimestamp, 
  transactionId 
} = transactionData;

Supported Settlement Tokens

Token
Identifier
Supported Networks

USDC

USDC

Ethereum Mainnet, Arbitrum, Base, Optimism, Polygon, Avalanche, Unichain, Binance Smart Chain, World Chain, Solana

SOL

SOL

Solana

ETH

ETH

Ethereum Mainnet, Arbitrum, Base, Optimism, Polygon, Unichain, World Chain

Step 3 — Initialize Privy and sign the user operation and authorizations

import { PrivyClient } from '@privy-io/node';

// Initialize Privy client (equivalent to TurnkeyServerSDK)
const privyClient = new PrivyClient(
  process.env.NEXT_PUBLIC_PRIVY_APP_ID!,
  process.env.NEXT_PUBLIC_PRIVY_APP_SECRET!
);

// User's wallet ID from Privy (equivalent to Turnkey's organizationId + address)
const walletId = 'USER_WALLET_ID'; // Get this from the user's Privy embedded wallet

// Sign the userOpHash (equivalent to walletClient.signMessage)
const { signature: userOpSignature, encoding } = await privyClient
  .wallets()
  .ethereum()
  .signMessage(walletId, {
    params: {
      message: userOpHash
    }
  });

Step 4 — Sign EIP‑7702 authorization(s)

// Sign each authorization
const authorizationList = await Promise.all(
  authorizations.map((authorization: { contractAddress: string, chainId: number, nonce: bigint }) =>
    privyClient
      .wallets()
      .ethereum()
      .sign7702Authorization(walletId, {
        params: {
          contract: authorization.contractAddress,
          chainId: authorization.chainId,
          nonce: Number(authorization.nonce)
        }
      })
  )
);

Step 5 - Submitting the transaction

// Submitting transaction response
const { data: submitResponse } = await axios.post('https://api.enclave.money/magicspend/submit', {
  transactionId,
  userOpSignature,
  authorizationList,
}, {
  headers: {
    'Content-Type': 'application/json',
    'Authorization': process.env.ENCLAVE_API_KEY,
  }
});

const { txHash, transactionId, status } = submitResponse

Step 6 - Checking the status of the transaction

const { 
    status, 
    failureReason, 
    executedAt, 
    settledAt, 
    targetTransaction, 
    settlementTransactions  
} = await axios.get('https://api.enclave.money/magicspend/tx-status', 
    { params: { transactionId } },
    {
      headers: {
        'Content-Type': 'application/json',
        'Authorization': process.env.ENCLAVE_API_KEY,
    }
});
// Example: { status: 'PENDING' | 'SUBMITTED' | 'TARGET_EXECUTED' | 'SETTLING | 'SETTLEMENT_EXECUTED' | 'EXPIRED' | 'FAILED' }
console.log('Transaction Status:', status);

Status Codes

Status Code
Description

PENDING

Transaction is built but signatures for the transaction have not been submitted yet

SUBMITTED

Transaction has been submitted and the user's desired action will be executed on the target chain

TARGET_EXECUTED

Target chain transaction has been succeefully executed. Settlement of the transaction is now pending.

SETTLING

Settlement transactions on each of the settlement chains have been submitted and pending execution

SETTLEMENT_EXECUTED

Settlement transactions on all settlement chains have been executed

EXPIRED

Transaction was built but was not submitted on time for valid execution

FAILED

Transaction execution failed. This may be due to a reverted transaction on the target or settlement chains.

Last updated