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 withFullSettler__NotSupported).resolve(OnchainCrossChainOrder): view function. Returns theResolvedCrossChainOrder(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 callingopen. - 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 asmsg.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:
- Parse the
Open(bytes32 orderId, ResolvedCrossChainOrder)event from the transaction receipt. StoreorderId. - 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).
- Track destination fill via the
IntentFilled(address solver, OrderData order)event on the destinationFullSettler. TheorderIdis recomputed from the emittedOrderDataand matches the originorderId. - 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
EnforcedPauserevert path gracefully. For example, surface a user-facing "system temporarily paused, please retry shortly" message and offer a retry button rather than failing silently:- Quote freshness: For swap paths, quote on the same block (or within a 30–60 second window) as the transaction submission. Stale quotes causetry 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)) } }FullSettler__SwapAmountTooLow. - Protocol fee: ReadprotocolFeeBps()at quote time. The fee is deducted frominputAmounton the origin side; the resultingoutputAmountis what the user actually receives. - Allowance hygiene: Approve exactlyinputAmount(orinputAmountToSwapFromfor 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.