Across is now live on Lens Chain!
Bridge Now
Across Documentation
V3 Developer Docs
V3 Developer Docs
  • 👋Introduction
    • Welcome to Across
    • What is Across?
    • Technical FAQ
    • Migration Guides
      • Migration from V2 to V3
      • Migration to CCTP
        • Migration Guide for Relayers
        • Migration Guide for API Users
      • Migration Guide for Non-EVM and Prefills
        • Breaking Changes for Indexers
        • Breaking Changes for API Users
        • Breaking Changes for Relayers
        • Testnet Environment for Migration
      • Solana Migration Guide
      • BNB Smart Chain Migration Guide
  • 🔗Use Cases
    • Instant Bridging in your Application
      • Bridge Integration Guide
      • Multichain Bridge UI Guide
      • Single Chain Bridge UI Guide
    • Embedded Crosschain Actions
      • Crosschain Actions Integration Guide
        • Using the Generic Multicaller Handler Contract
        • Using a Custom Handler Contract
      • Crosschain Actions UI Guide
    • Settle Crosschain Intents
    • ERC-7683 in Production
  • 🧠Concepts
    • What are Crosschain Intents?
    • Intents Architecture in Across
    • Intent Lifecycle in Across
    • Canonical Asset Maximalism
  • 🛠️Reference
    • API Reference
    • App SDK Reference
    • Contracts
      • Aleph Zero
      • Arbitrum
      • Base
      • Blast
      • Ethereum
      • Linea
      • Ink
      • Lens
      • Lisk
      • Mode
      • Optimism
      • Polygon
      • Redstone
      • Scroll
      • Soneium
      • Unichain
      • World Chain
      • zkSync
      • Zora
    • Selected Contract Functions
    • Supported Chains
    • Fees in the System
    • Actors in the System
    • Security Model and Verification
      • Disputing Root Bundles
      • Validating Root Bundles
    • Tracking Events
  • 🔁Relayers
    • Running a Relayer
    • Relayer Nomination
  • 📚Resources
    • Release Notes
    • Developer Support
    • Bug Bounty
    • Audits
Powered by GitBook
LogoLogo

Products

  • Across Bridge
  • Across+
  • Across Settlement

Socials

  • Discord
  • Twitter
  • Medium
  • Forum

Resources

  • Blog
  • Across Brand Assets
  • Github

Routes

  • Bridge to Unichain
  • Bridge to Arbitrum
  • Bridge to Optimism
  • Bridge to Linea
  • Bridge to Polygon
  • Bridge to Base
  • Bridge to World Chain
  • Bridge to zkSync
On this page
  • Introduction
  • Production Contracts
  • Building with Crosschain Intents
  • Manually Executing Crosschain Intents
  1. Use Cases

ERC-7683 in Production

PreviousSettle Crosschain IntentsNextWhat are Crosschain Intents?

Last updated 1 day ago

Introduction

ERC-7683 reimagines how users interact with crosschain applications.

Instead of users having to understand and specify complex execution paths, they simply declare their desired outcome. This intent-based approach shifts the complexity of crosschain execution from users to specialized actors called relayers, who compete to find the most efficient path to fulfill the user's desired outcome.

AcrossOriginSettler Contract and Its Impact

The AcrossOriginSettler contract is an extremely important part of this setup. It implements the IOriginSettler ERC-7683 interface. Its core features include:

  1. Validating an intent

  2. Processing an intent by creating a deposit on the SpokePool contract (either depositV3() or unsafeDeposit())

This contract demonstrates how intents can be modular. This allows different protocols to build their own implementations while maintaining compatibility with the core ERC-7683 standard.

Users no longer need to understand the intricacies of different L2s or bridges, they simply express what they want to achieve and the protocol does it in a cheap and fast way.

Each SpokePool contract on destination chains (i.e. Base and Arbitrum) implements the IDestinationSettler interface to allow for seamless crosschain activity. You can learn more about these contracts .

As specified in the , we will be focusing on fillDeadline, orderDataType and orderData to build the intent:

  1. fillDeadline: timestamp indicating when the crosschain intent expires if not completed.

  2. orderDataType: an EIP-712 typehash that specifies the format/structure of the orderData.

  3. orderData: The encoded parameters (tokens, amounts, chains, recipient, etc.) that define the desired outcome.

Let's dive in!


Production Contracts

Currently, AcrossOriginSettler contract is deployed on the following chains:

Chain
Contract Address

Base

Arbitrum

Optimism


Building with Crosschain Intents

Now, let's walk through crosschain intent execution and learn how using the AcrossOriginSettler contract, we can bridge USDC from Base to Arbitrum:

1

Gathering Details for the Intent

To get started, we need the following information from the user:

  1. Input Amount

  2. Recipient Wallet Address

For this example:

  1. Base is the origin chain (Chain ID: 8453)

  2. Arbitrum is the destination chain (Chain ID: 42161)

  3. Input and Output Tokens are both USDC

https://app.across.to/api/suggested-fees?inputToken=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913&outputToken=0xaf88d065e77c8cC2239327C5EDb3A432268e5831&originChainId=8453&destinationChainId=42161&amount=10000000

Now, in the response of this API, you will notice outputAmount and we will be using that to build our intent. The outputAmount ensures that the intent includes a sufficient fee to cover gas, and relayer costs, making it more likely to be filled quickly and reliably on the destination chain.

2

Building the Intent

We now have all the necessary information needed to build the intent. Let's look at how fillDeadline, orderDataType and orderData can be formed using this information:

fillDeadline

FillDeadline is a timestamp that indicates the deadline after which a deposit can no longer be filled. Once the destination chain’s block timestamp exceeds the fillDeadline, the deposit is considered expired and will eventually be refunded (typically approximately 90 minutes after the fillDeadline as part of the next root bundle).

Here is how you can calculate the fillDeadline for your intent to be 30 mins from the time of submission:

const currentTimestamp = Math.floor(Date.now() / 1000);
const fillDeadline = currentTimestamp + (30 * 60);

orderDataType

OrderDataType is an EIP-712 typehash composed off of the fields in the order data:

  • inputToken: The token the user deposits on the origin chain.

  • inputAmount: The amount of inputToken the user is depositing.

  • outputToken: The token the user wants to receive on the destination chain.

  • outputAmount: The amount the user expects to receive on the destination chain.

  • destinationChainId: The chain ID where the user wants to receive the outputToken.

  • recipient: The address that will receive the outputToken on the destination chain.

  • exclusiveRelayer: An optional address allowed to fill the intent during the exclusivity period. For this example, we will simply use 0x0000000000000000000000000000000000000000.

  • depositNonce: A unique identifier for the deposit, used to prevent replay or duplicate fills. For this example, we will simply use 0

  • exclusivityPeriod: The duration (in seconds) after submission during which only the exclusiveRelayer can fill the intent. For this example, we will simply use 0

  • message: Optional calldata to be executed with the fill on the destination chain (e.g., contract interaction). For this example, we will simply use 0

For our crosschain intent, you can simply use the following orderDataType for all your crosschain intents:

const orderDataType = "0x9df4b782e7bbc178b3b93bfe8aafb909e84e39484d7f3c59f400f1b4691f85e2";

orderData

For the orderData of the crosschain intent, we simply take all the fields we have talked about above and encode them in the following way:

import { encodeAbiParameters, parseUnits, pad } from 'viem';

// Helper function to pad addresses to bytes32 (for Across's recipient field)
const padAddress = (address) => {
  return pad(address, { size: 32 });
};

// Define the ABI tuple structure for orderData
const abiFragment = [
  {
    type: 'tuple',
    components: [
      { name: 'inputToken', type: 'address' },          // Token being sent from source chain
      { name: 'inputAmount', type: 'uint256' },         // Amount to bridge
      { name: 'outputToken', type: 'address' },         // Expected token on destination chain
      { name: 'outputAmount', type: 'uint256' },        // Expected amount on destination chain
      { name: 'destinationChainId', type: 'uint256' },  // Destination chain ID
      { name: 'recipient', type: 'bytes32' },           // Padded recipient address
      { name: 'exclusiveRelayer', type: 'address' },    // Optional relayer address
      { name: 'depositNonce', type: 'uint256' },        // Nonce for replay protection
      { name: 'exclusivityPeriod', type: 'uint32' },    // Period where only `exclusiveRelayer` can fill
      { name: 'message', type: 'bytes' },               // Optional encoded message
    ]
  }
];

// Inputs
const inputToken = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; // USDC on source
const outputToken = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831'; // USDC on dest
const destinationChainId = 42161;
const recipientAddress = '0x1234...'; // Replace with actual user address
const amount = '10'; // human-readable value (e.g., 10 USDC)
const decimals = 6;

const paddedDepositor = padAddress(recipientAddress);
const inputAmount = parseUnits(amount, decimals); // Safe precision scaling
const outputAmount = inputAmount; // Can modify for fees/slippage

// Encode the parameters using viem
const orderData = encodeAbiParameters(abiFragment, [[
  inputToken,
  inputAmount,
  outputToken,
  outputAmount,
  destinationChainId,
  paddedDepositor,
  '0x0000000000000000000000000000000000000000', // no exclusive relayer
  0, // nonce
  0, // exclusivity period
  '0x' // empty message
]]);

console.log("Encoded orderData:", orderData);
3

Executing the Intent

Finally, it's time to execute the intent and receive funds on the destination chain. To do this, you can use wallet SDK of your choice. Please ensure that you check your code and keep your private keys secure.

import { createWalletClient, http, createPublicClient } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { base } from 'viem/chains';

// Initialize account from private key, please use a testing account and donot push this key to production
const account = privateKeyToAccount('your-private-key');

// Create public client for reading blockchain state
const publicClient = createPublicClient({
    chain: base,
    transport: http("you-rpc-url"),
});

// Create wallet client for sending transactions
const walletClient = createWalletClient({
    account,
    chain: base,
    transport: http("your-rpc-url"),
});

// Contract address for the AcrossOriginSettler on Base
const contractAddress = "0x514496264fa0B4Ee0522bC7Db644F78B02AEb1ae";

// Contract ABI - only including the function we need
const abi = [
    {
        type: 'function',
        name: 'open',
        stateMutability: 'nonpayable',
        inputs: [
            {
                components: [
                  { internalType: "uint32", name: "fillDeadline", type: "uint32" },
                  { internalType: "bytes32", name: "orderDataType", type: "bytes32" },
                  { internalType: "bytes", name: "orderData", type: "bytes" },
                ],
                internalType: "struct OnchainCrossChainOrder",
                name: "order",
                type: "tuple",
              },
        ],
        outputs: [],
    }
];

const fillDeadline = 17...; // Use the value obtained in the step above
const orderDataType = '0x9df4b782e7bbc178b3b93bfe8aafb909e84e39484d7f3c59f400f1b4691f85e2'
const orderData = '0x00...' // Use the value obtained in the step above

async function main() {
    try {
        // Simulate the contract call first to catch any potential errors
        const { request } = await publicClient.simulateContract({
            address: contractAddress,
            abi,
            functionName: 'open',
            args: [{
                fillDeadline,
                orderDataType,
                orderData
            }],
            account: account,
        });

        // If simulation succeeds, send the actual transaction
        const txHash = await walletClient.writeContract(request);
        console.log("Transaction submitted:", txHash);
    } catch (error) {
        console.error('Error:', error.message);
    }
}

main().catch(console.error);

Manually Executing Crosschain Intents

    1. 1000000000 as the value (i.e. maximum allowance amount)

    2. Click on Write and sign the transaction.

  1. After connecting your wallet, expand the open function and input the details we had obtained from our script in the previous section:

Congratulations! You are now an adopter of ERC-7683 and we thank you for your support.

AcrossOriginSettler contract processes an external order type and translates it into an AcrossV3Deposit that it sends to the SpokePool Contract. This allows crosschain intents to work seamlessly throughout multiple chains. You can learn more about the Across protocol .

However, to build the intent successfully we would still need to calculate the output amount that the recipient will receive on the destination once the bridge fee is deducted from the input amount. To do this, we will be using the endpoint:

You can learn more about the .

: You can also checkout the here to quickly understand how intents are built.

Congratulations! You have now completely built the crosschain intent and it is now time to execute it using the open() function on the AcrossOriginSettler Contract on .

Here is how you can call the open() function on the AcrossOriginSettler Contract on :

With this, you have now built your own crosschain intent executor! if you check , you will notice the funds bridged to your wallet on Arbitrum. You can now expand this to any app, wallet, or protocol you are building.

You can read about .

This implementation is aimed at teaching how crosschain intents can be built and executed in a basic environment. As you build complex applications, please adhere to security guidelines and audits for the safety of your users and protocol. For help, please reach out to us .

If you are not comfortable with developing the above flow and want to experience ERC-7683 in production anyway, You can checkout the here to quickly understand how intents are built.

With the fillDeadline, orderDataType and orderData ready, it's now time that we head over to and execute the crosschain intent where we bridge 10 USDC from Base to Arbitrum:

Head over to the and call the approve function with the following details:

AcrossOriginSettler Contract address on Base () as the spender.

: Please ensure that you connect your wallet by clicking on the "Connect to Web3" button before interacting with the contract.

Now, head over to the and click on Contract and then Write Contract section and click on Connect to Web3 to connect your wallet:

Now, simply click on Write and sign the transaction on your wallet. Finally check . You will notice 10 USDC in your wallet on Arbitrum. This ensures that your crosschain intent was executed successfully.

In case you need any help, please feel free to contact us .

🔗
💡
here
standard
here
💡
crosschain intent generator website
Base
Base
Arbiscan
ERC-7683 standard in-depth here
here
crosschain intent generator website
basescan.org
USDC contract on Basescan
0x514496264fa0B4Ee0522bC7Db644F78B02AEb1ae
AcrossOriginSettler contract on basescan
arbiscan.io
here
0x514496264fa0B4Ee0522bC7Db644F78B02AEb1ae
0xDE5cFBDE966bF8a187a332EC9c5081A2c8a537c5
0x5cC9dde9Fdc4fE3A910006709bFa7A39155ef93f
/suggested-fees
/suggested-fees API here