Skip to main content
This guide demonstrates how to bridge tokens to a destination chain and execute a smart contract call in one atomic operation. This is ideal for interacting with DeFi protocols, NFT minting, and other on-chain operations.

Overview

Bridge and Execute allows you to:
  • Fund and interact with contracts on any chain
  • Execute DeFi operations (deposits, swaps, staking)
  • Mint NFTs or interact with protocols
  • Skip bridging if you already have sufficient balance
  • Handle token approvals automatically

Use Cases

DeFi Deposits

Bridge USDC and deposit into lending protocols like Aave or Compound

NFT Minting

Bridge ETH and mint NFTs on any supported chain

Staking

Bridge tokens and stake in validator or yield protocols

DEX Trading

Bridge and execute swaps on decentralized exchanges

How It Works

The SDK intelligently handles the operation:
  1. Balance Check: Checks if you already have sufficient funds on the destination chain
  2. Smart Bridging: Bridges only if needed, or skips entirely if balance is sufficient
  3. Token Approval: Handles ERC-20 approvals automatically if required
  4. Contract Execution: Executes your smart contract call
  5. Progress Tracking: Provides real-time events for each step
If you already have enough balance on the destination chain, the bridge is skipped and only the execution happens. This saves time and gas!

Browser Example

1

Initialize the SDK

Set up the SDK with your wallet:
import { NexusSDK, type EthereumProvider } from '@avail-project/nexus-core';

const provider = (window as any).ethereum;
const sdk = new NexusSDK({ network: 'testnet' });
await sdk.initialize(provider);

// Set up hooks
sdk.setOnIntentHook(({ allow }) => allow());
sdk.setOnAllowanceHook(({ allow }) => allow(['min']));
2

Prepare contract interaction

Encode your contract call using ethers or viem:
import { ethers } from 'ethers';

// Example: Deposit into a DeFi protocol
const protocolAddress = '0xYourProtocolAddress';

// ABI for the deposit function
const abi = ['function deposit(uint256 amount)'];
const iface = new ethers.Interface(abi);

// Encode the function call
const depositAmount = 100_000_000n; // 100 USDC
const encodedData = iface.encodeFunctionData('deposit', [depositAmount]);

console.log('Encoded data:', encodedData);
You can also use viem for encoding:
import { encodeFunctionData } from 'viem';

const data = encodeFunctionData({
  abi: [{ name: 'deposit', type: 'function', inputs: [{ type: 'uint256' }] }],
  functionName: 'deposit',
  args: [100_000_000n],
});
3

Execute bridge and contract call

Execute the combined operation:
import { NEXUS_EVENTS, type BridgeAndExecuteParams } from '@avail-project/nexus-core';

const params: BridgeAndExecuteParams = {
  // Bridge parameters
  token: 'USDC',
  amount: 100_000_000n, // 100 USDC
  toChainId: 1, // Ethereum mainnet
  sourceChains: [8453], // Optional: Bridge from Base

  // Execution parameters
  execute: {
    to: protocolAddress,
    data: encodedData,

    // Token approval for the contract
    tokenApproval: {
      token: 'USDC',
      amount: 100_000_000n,
      spender: protocolAddress,
    },

    // Optional execution settings
    gasPrice: 'medium',
    waitForReceipt: true,
    receiptTimeout: 60000,
    requiredConfirmations: 1,
  },
};

try {
  const result = await sdk.bridgeAndExecute(params, {
    onEvent: (event) => {
      if (event.name === NEXUS_EVENTS.STEP_COMPLETE) {
        console.log('Step completed:', event.args.type);
      }
    },
  });

  console.log('Operation complete!');
  console.log('Execute TX:', result.executeExplorerUrl);

  if (result.bridgeSkipped) {
    console.log('Bridge was skipped - used existing balance');
  } else {
    console.log('Bridge TX:', result.bridgeExplorerUrl);
  }

  if (result.approvalTransactionHash) {
    console.log('Approval TX:', result.approvalTransactionHash);
  }
} catch (error) {
  console.error('Operation failed:', error);
}

Node.js Example

For backend applications:
import {
  BridgeAndExecuteParams,
  NEXUS_EVENTS,
  NexusSDK,
} from '@avail-project/nexus-core';
import { ethers } from 'ethers';

async function bridgeAndExecute(
  params: BridgeAndExecuteParams,
  sdk: NexusSDK
): Promise<boolean> {
  try {
    const result = await sdk.bridgeAndExecute(params, {
      onEvent: (event) => {
        if (event.name === NEXUS_EVENTS.STEP_COMPLETE) {
          console.log(`[${new Date().toISOString()}] ${event.args.type}`);
        }
      },
    });

    console.log('Bridge and Execute successful!');
    console.log('Execute transaction:', result.executeExplorerUrl);

    if (!result.bridgeSkipped) {
      console.log('Bridge transaction:', result.bridgeExplorerUrl);
    }

    return true;
  } catch (error) {
    console.error('Bridge and Execute failed:', error);
    return false;
  }
}

// Initialize SDK
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!);
const sdk = new NexusSDK({ network: 'testnet' });
await sdk.initialize(wallet);

// Set hooks
sdk.setOnIntentHook(({ allow }) => allow());
sdk.setOnAllowanceHook(({ allow }) => allow(['min']));

// Prepare contract call
const iface = new ethers.Interface(['function deposit(uint256)']);
const data = iface.encodeFunctionData('deposit', [100_000_000n]);

// Execute
await bridgeAndExecute(
  {
    token: 'USDC',
    amount: 100_000_000n,
    toChainId: 1,
    execute: {
      to: '0xProtocolAddress',
      data: data,
      tokenApproval: {
        token: 'USDC',
        amount: 100_000_000n,
        spender: '0xProtocolAddress',
      },
    },
  },
  sdk
);

Execute Parameters

All available execution options:
ParameterTypeRequiredDescription
toHexYesContract address to call
dataHexNoEncoded function call data
valuebigintNoNative token value to send (in wei)
gasbigintNoGas limit for the transaction
gasPrice'low' | 'medium' | 'high'NoGas price strategy
tokenApproval{ token, amount, spender }NoToken approval before execution
waitForReceiptbooleanNoWait for transaction receipt
receiptTimeoutnumberNoReceipt wait timeout (ms)
requiredConfirmationsnumberNoRequired block confirmations

Token Approvals

When interacting with ERC-20 tokens, you need to approve the contract:
const params: BridgeAndExecuteParams = {
  token: 'USDC',
  amount: 100_000_000n,
  toChainId: 1,
  execute: {
    to: protocolAddress,
    data: encodedData,

    // Approve the protocol to spend USDC
    tokenApproval: {
      token: 'USDC',
      amount: 100_000_000n, // Amount to approve
      spender: protocolAddress, // Who can spend it
    },
  },
};
The tokenApproval.spender is typically the contract you’re calling (to address), but some protocols use separate router contracts.

Sending Native Tokens

To send ETH (or other native tokens) with your contract call:
const params: BridgeAndExecuteParams = {
  token: 'ETH',
  amount: 1_000_000_000_000_000_000n, // 1 ETH to bridge
  toChainId: 1,
  execute: {
    to: nftContractAddress,
    data: encodedMintData,
    value: 100_000_000_000_000_000n, // 0.1 ETH mint price
  },
};

Simulation

Test your operation before executing:
const simulation = await sdk.simulateBridgeAndExecute(params);

// Check bridge simulation (null if not needed)
if (simulation.bridgeSimulation) {
  console.log('Bridge fees:', simulation.bridgeSimulation.intent.fees.total);
  console.log('Source chains:', simulation.bridgeSimulation.intent.sources);
}

// Check execution simulation
console.log('Gas estimate:', simulation.executeSimulation.gasUsed);
console.log('Gas fee:', simulation.executeSimulation.gasFee);

// Calculate total cost
const totalGasCost = simulation.executeSimulation.gasFee;
if (simulation.bridgeSimulation) {
  console.log('Bridge fees:', simulation.bridgeSimulation.intent.fees.total);
}

Result Structure

type BridgeAndExecuteResult = {
  // Execution result
  executeTransactionHash: string;
  executeExplorerUrl: string;
  toChainId: number;

  // Optional: approval transaction
  approvalTransactionHash?: string;

  // Optional: bridge transaction (undefined if skipped)
  bridgeExplorerUrl?: string;
  intent?: ReadableIntent;

  // Bridge optimization flag
  bridgeSkipped: boolean; // true if balance was sufficient
};

Common Patterns

// Deposit USDC into Aave
const aavePool = '0x794a61358D6845594F94dc1DB02A252b5b4814aD';
const iface = new ethers.Interface([
  'function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode)',
]);

const data = iface.encodeFunctionData('supply', [
  '0xUSDCAddress',
  100_000_000n, // 100 USDC
  userAddress,
  0, // referral code
]);

await sdk.bridgeAndExecute({
  token: 'USDC',
  amount: 100_000_000n,
  toChainId: 1,
  execute: {
    to: aavePool,
    data: data,
    tokenApproval: {
      token: 'USDC',
      amount: 100_000_000n,
      spender: aavePool,
    },
  },
});

Error Handling

import { NexusError, ERROR_CODES } from '@avail-project/nexus-core';

try {
  await sdk.bridgeAndExecute(params);
} catch (error) {
  if (error instanceof NexusError) {
    switch (error.code) {
      case ERROR_CODES.INSUFFICIENT_BALANCE:
        console.error('Not enough tokens for operation');
        break;

      case ERROR_CODES.TRANSACTION_REVERTED:
        console.error('Contract execution reverted:', error.data?.details);
        break;

      case ERROR_CODES.SIMULATION_FAILED:
        console.error('Pre-execution simulation failed');
        break;

      case ERROR_CODES.TRANSACTION_TIMEOUT:
        console.error('Transaction timed out');
        break;

      default:
        console.error('Operation failed:', error.message);
    }
  }
}

Next Steps

Basic Bridge

Learn basic bridge operations

Progress UI

Build progress tracking interfaces

Execute API

View execute API reference

Error Handling

Learn error handling best practices

Build docs developers (and LLMs) love