Skip to content

TAO Intents: Contract Integration

Audience: Partner integrators connecting their application to the TAO Intents system at the smart-contract layer.

Scope: This guide covers contract-layer integration: opening a cross-chain intent by calling the open function on the FullSettler contract. The REST Service API is companion support for quotes, optional notify-after-broadcast, mirrored status reads, and refund verification. It does not open, sign, relay, or submit transactions.

Version: 1.0


Overview

The TAO Intents system lets a user on one chain (the origin chain) request a specific token amount on another chain (the destination chain). The user signs an on-chain transaction that escrows their input funds. Solvers observe the intent, deliver the requested output to the user on the destination chain, and are reimbursed later from the escrow. When the input token is not native, the user also signs a permit so the escrow contract can pull the tokens (covered in Caller setup below).

%%{init: {'theme':'base','themeCSS':'[id$=-sequencenumber]{fill:#8c80e5 !important;stroke:#8c80e5 !important;}.sequenceNumber{fill:#ffffff !important;font-weight:600;font-size:13px;}','themeVariables':{'primaryColor':'#1a1a1a','primaryTextColor':'#f5f5f5','primaryBorderColor':'#8c80e5','lineColor':'#8a8a8a','secondaryColor':'#8c80e5','tertiaryColor':'#0a0a0a','noteBkgColor':'#1f1d2e','noteTextColor':'#f5f5f5','noteBorderColor':'#8c80e5','actorBkg':'#1a1a1a','actorBorder':'#8c80e5','actorTextColor':'#f5f5f5','actorLineColor':'#8a8a8a','signalColor':'#c7c7c7','signalTextColor':'#f5f5f5','sequenceNumberColor':'#ffffff'}}}%%
sequenceDiagram
    autonumber
    participant User
    participant Origin as FullSettler<br/>(origin)
    participant Solver
    participant Dest as FullSettler<br/>(destination)

    User->>Origin: open(order)
    Note over User,Origin: escrows the input token
    Origin-->>Solver: Open(orderId) event
    Solver->>Dest: fill(order)
    Dest->>User: outputToken delivered

You only need to integrate the origin-side open call. Filling, bridging, root-bundle proposing, dispute handling, and refunds are managed by TAO Intents off-chain services and on-chain roles.

Contract execution vs Service API

Partners and users call FullSettler.open(...) directly on the origin chain. The Service API is a companion and mirror for quotes, optional post-broadcast notification, status reads, and refund verification. Chain events and contract state remain the source of truth.

Companion references

This contract-level guide pairs with the Service API Reference (rendered from openapi.yaml) for quotes, mirrored order status, optional notify-after-broadcast, and refund verification. Deployment and route metadata come from the current TAO Intents deployment source for the target environment. See the Architecture overview for the broader protocol picture.


Standard compliance

FullSettler implements the ERC-7683 cross-chain intents standard:

  • open(OnchainCrossChainOrder): used by integrators. Supported.
  • openFor(GaslessCrossChainOrder, signature, fillerData): gasless flow. Not supported in this deployment (reverts with FullSettler__NotSupported).
  • resolve(OnchainCrossChainOrder): view function. Returns the ResolvedCrossChainOrder (useful for pre-flight simulation).

Warning

resolve() evaluates the order with nonce = 0 for predictability. The orderId it returns will not match the orderId produced by a subsequent open call on the same parameters. open substitutes the actual nonce at execution time. Treat resolve output as a structural preview only; do not assume orderId equality between resolve and open.

If you already speak ERC-7683, you can wire open directly into existing flows.


Prerequisites

Before calling open, you must obtain the following from TAO Intents:

Item Source Notes
FullSettler address per supported origin chain TAO Intents deployment source One contract per supported origin chain.
USDC address on the origin chain TAO Intents deployment source USDC is the canonical input token.
USDC address on the settlement chain TAO Intents deployment source Used for accounting only.
Output token address on the destination chain Service API quote endpoint or TAO Intents deployment source The token the user receives.
Quote (input → output amount) Service API quote endpoint Provides outputAmount for the order.
Supported chain IDs TAO Intents deployment source Both origin and destination must be supported.
Contract ABIs FullSettler artifact, supplied by the TAO Intents team Used for encoding the order.

Contract surface

open function

function open(OnchainCrossChainOrder calldata order) external payable;
Parameter Type Description
order OnchainCrossChainOrder ERC-7683 order envelope. Contains the fill deadline, type hash, and ABI-encoded InputOrderData.

The function is payable to allow native-token input paths (ETH or chain-native gas token).

The ERC-7683 envelope is intentionally generic; TAO-specific fields live inside orderData, which we ABI-encode as the InputOrderData struct shown below.

OnchainCrossChainOrder envelope (ERC-7683)

struct OnchainCrossChainOrder {
    uint32  fillDeadline;     // Latest unix time the order may be filled on the destination
    bytes32 orderDataType;    // MUST equal FullSettler.ONCHAIN_ORDER_DATA_TYPEHASH()
    bytes   orderData;        // abi.encode(InputOrderData)
}

InputOrderData (decoded from orderData)

struct InputOrderData {
    // === Identity ===
    address user;                      // Order owner — MUST equal msg.sender

    // === Pre-swap inputs ===
    address inputTokenToSwapFrom;      // Token user provides at open time (USDC, ERC-20, or native sentinel)
    uint24  poolFee;                   // Uniswap V3 pool fee tier (0 when no swap)
    uint256 inputAmountToSwapFrom;     // Amount of inputTokenToSwapFrom the user provides
    uint32  swapDeadline;              // Latest unix time the origin-side swap is valid

    // === Escrowed input (post-swap) ===
    address inputToken;                // Escrow token after swap (always USDC on this deployment)
    uint256 inputAmount;               // Escrow amount after swap (this is what solvers must be paid)

    // === Destination delivery ===
    address outputToken;               // Token the user receives on the destination chain
    uint256 outputAmount;              // Amount the user receives on the destination chain
    uint256 destinationChainId;        // Destination chain ID
    bytes32 recipient;                 // Destination recipient (left-padded address)

    // === Protocol fee ===
    uint16  maxProtocolFeeBps;         // Max protocol fee user accepts (use type(uint16).max for unlimited)

    // === Metadata ===
    bytes   alphaStakeData;            // ABI-encoded AlphaStakeData; empty bytes for non-stake orders (see below)
}

AlphaStakeData (encoded in alphaStakeData)

For Bittensor subnet stake flows, alphaStakeData carries the ABI-encoded AlphaStakeData struct described below. For ordinary cross-chain transfers (no staking), leave alphaStakeData as empty bytes ("" / 0x).

/// @notice Alpha stake metadata used to encode Bittensor subnet-specific data
///         in `alphaStakeData` for both origin (stake-as-input) and destination
///         (stake-as-output) flows.
struct AlphaStakeData {
    bool    isStake;
    uint32  netuid;
    bytes32 delegatorHotkey;
    bytes32 recipientColdkey; // Used to transfer stake from contract to recipient
    uint256 amount;
}

Field semantics:

Field Meaning
isStake true: the order represents a Bittensor subnet stake flow. false: order is treated as a normal non-stake order; the other fields are ignored.
netuid Bittensor subnet identifier for which stake is added or removed.
delegatorHotkey Hotkey used as the delegator in Bittensor staking operations (addStake, transferStake, removeStake).
recipientColdkey Coldkey that ultimately receives the stake on the subnet.
amount For stake-as-input (Alpha → TAO → USDC): subnet token amount to remove as stake via transferStakeFrom / removeStake before converting to TAO. For stake-as-output (TAO → Alpha): minimum amount of Alpha tokens to receive on staking.

Note

Set isStake = false (or pass empty alphaStakeData) for any order that does not interact with a Bittensor subnet. When isStake = false, the other fields are not read.

Contract constants and getters

Source Use
FullSettler.ONCHAIN_ORDER_DATA_TYPEHASH() Set order.orderDataType to this value.
FullSettler.NATIVE_TOKEN() (0xEeee...EEeE) Sentinel for native-token input paths.
FullSettler.protocolFeeBps() Current protocol fee in basis points. Quote should reflect this.
FullSettler.router() Uniswap V3 router used for swaps (informational).
FullSettler.chainToDestinationSettler(chainId) Returns bytes32(0) if no destination settler is registered yet. Destination settlers are registered by a protocol-authorized role.
FullSettler.orderStatus(orderId) None / Deposited / Claimed / Refunded.
FullSettler.resolve(order) View call to preview the resolved order.

Event emitted

event Open(bytes32 indexed orderId, ResolvedCrossChainOrder resolvedOrder);

The orderId is the canonical identifier across the entire system. Track it for status, fills, and (if applicable) refunds.


Input paths

The open function supports three input modes. Choose the mode based on what the user is paying with.

Field USDC-in ERC-20-in Native-token-in
inputTokenToSwapFrom USDC address ERC-20 address NATIVE_TOKEN sentinel
poolFee 0 Uniswap V3 tier Uniswap V3 tier
swapDeadline 0 block.timestamp + 60 block.timestamp + 60
msg.value 0 0 inputAmountToSwapFrom
Allowance to grant inputAmount on USDC inputAmountToSwapFrom on ERC-20 none

USDC-in (no swap)

The user already holds USDC on the origin chain. No Uniswap interaction.

Set the following fields in InputOrderData:

Field Value
inputTokenToSwapFrom USDC address
poolFee 0
inputAmountToSwapFrom 0
swapDeadline 0
inputToken USDC address
inputAmount USDC amount to escrow

Caller setup

  • Required allowance: the user must approve(FullSettler, inputAmount) on the USDC contract before calling open.
  • Transaction value: 0.

ERC-20-in (swap to USDC)

The user holds an ERC-20 other than USDC (for example, WETH). FullSettler swaps the input to USDC via the configured Uniswap V3 router before escrowing.

Field Value
inputTokenToSwapFrom The user's ERC-20
poolFee Uniswap V3 fee tier (e.g., 500, 3000, or 10000)
inputAmountToSwapFrom Token amount to swap
swapDeadline block.timestamp + 60 (or similar short window)
inputToken USDC address
inputAmount Expected USDC received from the swap (use a quote with slippage applied)

Caller setup

  • Required allowance: the user must approve(FullSettler, inputAmountToSwapFrom) on the ERC-20 contract.
  • Transaction value: 0.

Sizing the swap

Use the Service API quote endpoint (or your own Uniswap V3 quoter) to derive inputAmountToSwapFrom such that the swap produces at least inputAmount USDC after slippage.

Native-token-in (wrap + swap)

The user pays in the chain's native token (ETH on mainnet/Sepolia, TAO on Subtensor EVM). FullSettler wraps the value to WETH/WTAO and either uses it directly or routes it through Uniswap.

Field Value
inputTokenToSwapFrom NATIVE_TOKEN sentinel (0xEeee...EEeE)
poolFee Uniswap V3 fee tier
inputAmountToSwapFrom Native amount in wei
swapDeadline block.timestamp + 60
inputToken USDC address
inputAmount Expected USDC after wrap + swap

Caller setup

  • Required allowance: none.
  • Transaction value: inputAmountToSwapFrom (must be sent as msg.value).

Field encoding rules

Field Rule
user MUST equal msg.sender. Otherwise reverts with FullSettler__InvalidOrderUser.
recipient A bytes32. For an EVM address, left-pad: bytes32(uint256(uint160(recipientAddress))).
fillDeadline (envelope) Unix seconds. Use block.timestamp + 1 days for a generous window. After this, the order can be refunded.
swapDeadline Origin-side only. A short window (60–120 seconds) is recommended to avoid stale quotes.
maxProtocolFeeBps Set to type(uint16).max (65_535) for "no preference". Otherwise the order reverts if the live protocol fee exceeds this value.
destinationChainId MUST differ from the origin chain. MUST be registered via chainToDestinationSettler.
outputToken The ERC-20 contract address on the destination chain (NOT a bytes32).
alphaStakeData ABI-encoded AlphaStakeData struct for Bittensor subnet stake flows; pass empty bytes (0x) for ordinary (non-stake) orders. See AlphaStakeData below.

Reference flow (Solidity / TypeScript)

import {IFullSettler} from "tao-intents/interfaces/IFullSettler.sol";
import {OnchainCrossChainOrder} from "tao-intents/interfaces/external/ERC7683.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

function openUsdcIntent(
    address fullSettler,
    address usdc,
    uint256 amount,
    address outputToken,
    uint256 outputAmount,
    uint256 destinationChainId,
    address recipient
) external {
    IERC20(usdc).approve(fullSettler, amount);

    IFullSettler.InputOrderData memory input = IFullSettler.InputOrderData({
        user: msg.sender,
        inputTokenToSwapFrom: usdc,
        poolFee: 0,
        inputAmountToSwapFrom: 0,
        swapDeadline: 0,
        inputToken: usdc,
        inputAmount: amount,
        outputToken: outputToken,
        outputAmount: outputAmount,
        destinationChainId: destinationChainId,
        recipient: bytes32(uint256(uint160(recipient))),
        maxProtocolFeeBps: type(uint16).max,
        alphaStakeData: ""
    });

    OnchainCrossChainOrder memory order = OnchainCrossChainOrder({
        fillDeadline: uint32(block.timestamp + 1 days),
        orderDataType: IFullSettler(fullSettler).ONCHAIN_ORDER_DATA_TYPEHASH(),
        orderData: abi.encode(input)
    });

    IFullSettler(fullSettler).open(order);
}
import { encodeAbiParameters, parseAbiParameters, pad } from "viem";

async function openUsdcIntent({
  user, usdc, inputAmount, outputToken, outputAmount,
  destinationChainId, recipient, fullSettler,
}: {
  user: `0x${string}`;
  usdc: `0x${string}`;
  inputAmount: bigint;
  outputToken: `0x${string}`;
  outputAmount: bigint;
  destinationChainId: number;
  recipient: `0x${string}`;
  fullSettler: `0x${string}`;
}) {
  const inputOrderData = encodeAbiParameters(
    parseAbiParameters(
      "(address,address,uint24,uint256,uint32,address,uint256,address,uint256,uint256,bytes32,uint16,bytes)"
    ),
    [
      user,                                  // user
      usdc,                                  // inputTokenToSwapFrom
      0,                                     // poolFee
      0n,                                    // inputAmountToSwapFrom
      0,                                     // swapDeadline
      usdc,                                  // inputToken
      inputAmount,                           // inputAmount
      outputToken,                           // outputToken
      outputAmount,                          // outputAmount
      BigInt(destinationChainId),            // destinationChainId
      pad(recipient, { size: 32 }),          // recipient (bytes32)
      65535,                                 // maxProtocolFeeBps
      "0x",                                  // alphaStakeData
    ]
  );

  const order = {
    fillDeadline: Math.floor(Date.now() / 1000) + 86_400,
    orderDataType: await fullSettler.read.ONCHAIN_ORDER_DATA_TYPEHASH(),
    orderData: inputOrderData,
  };

  await fullSettler.write.open([order]);
}

Observe the order

After a successful open call:

  1. Parse the Open(bytes32 orderId, ResolvedCrossChainOrder) event from the transaction receipt. Store orderId.
  2. Query order status at any time via FullSettler.orderStatus(orderId):
    • Deposited: funds escrowed, awaiting fill or refund window.
    • Claimed: order was filled and solver was reimbursed (terminal).
    • Refunded: user funds were returned (terminal).
  3. Track destination fill via the IntentFilled(address solver, OrderData order) event on the destination FullSettler. The orderId is recomputed from the emitted OrderData and matches the origin orderId.
  4. Query the Service API status endpoint to aggregate origin and destination state: GET /intents/v1/intents/{orderId} in the Service API Reference.

Refunds

If an order is not filled before fillDeadline, the user's funds remain escrowed until an account holding REFUNDER_ROLE on the origin FullSettler calls refund. In V1, this role is held or controlled by a Tensora-operated refund service. The refund transfers the original input amount plus the previously deducted protocol fee back to the user; the role can trigger refunds after expiry but cannot redirect funds or change refund amounts. See the Architecture trust model for the bounded role model.

Your action: none on-chain. You can query orderStatus(orderId) == Refunded to detect completion, or rely on the Service API status endpoint.


Reverts and error handling

open may revert with the following selectors (defined in IFullSettler):

Selector Cause
FullSettler__InvalidOrderUser inputOrderData.user != msg.sender
FullSettler__InvalidOrderDataType order.orderDataType does not match ONCHAIN_ORDER_DATA_TYPEHASH
FullSettler__InvalidInputToken inputToken is not the configured USDC
FullSettler__WrongChain(givenChainId) destinationChainId == block.chainid
FullSettler__NoDestination destinationChainId == 0
FullSettler__DestinationSettlerNotSet No destination settler registered for destinationChainId
FullSettler__InvalidAmount Zero amounts on input or output
FullSettler__InvalidRecipient recipient decodes to address(0)
FullSettler__NonCanonicalRecipient recipient has dirty upper bytes (not a left-padded address)
FullSettler__ProtocolFeeExceedsMax Live protocolFeeBps > maxProtocolFeeBps
FullSettler__SwapAmountTooLow Swap path produced less USDC than inputAmount
FullSettler__SwapRouterNotSet Swap requested but router not configured
FullSettler__TimestampPassed swapDeadline or fillDeadline already passed
EnforcedPause (from Pausable) Origin settler is paused by a protocol-authorized role

Custom errors only

All errors are custom errors (no string revert reasons). Decode them via the contract ABI.


Operational notes

  • Pause state: The settler can be paused by a protocol-authorized role in emergencies. Always handle the EnforcedPause revert path gracefully. For example, surface a user-facing "system temporarily paused, please retry shortly" message and offer a retry button rather than failing silently:

    try fullSettler.open(order) {
        // success path
    } catch (bytes memory reason) {
        // Compare against the EnforcedPause selector: bytes4(keccak256("EnforcedPause()"))
        if (reason.length >= 4 && bytes4(reason) == bytes4(keccak256("EnforcedPause()"))) {
            // surface a "paused, retry shortly" UI state
            revert SystemTemporarilyPaused();
        }
        // rethrow other reverts
        assembly { revert(add(reason, 0x20), mload(reason)) }
    }
    
    - Quote freshness: For swap paths, quote on the same block (or within a 30–60 second window) as the transaction submission. Stale quotes cause FullSettler__SwapAmountTooLow. - Protocol fee: Read protocolFeeBps() at quote time. The fee is deducted from inputAmount on the origin side; the resulting outputAmount is what the user actually receives. - Allowance hygiene: Approve exactly inputAmount (or inputAmountToSwapFrom for swap paths). Avoid infinite allowances to limit blast radius. - Token support: The set of accepted input tokens is constrained by Uniswap V3 pool availability on the origin chain and the current TAO Intents deployment source for the target environment.


Support

For integration support, contract addresses, or to request the OpenAPI specification, contact the TAO Intents team. Report contract issues via the standard security disclosure process.

See also

  • Service API Reference: REST endpoints for quotes, optional notify-after-broadcast, mirrored order status, and refund verification.
  • Architecture: Full protocol architecture, role model, and component overview.
  • ERC-7683: Cross-chain intents standard implemented by FullSettler.