Overview
The Swap Intent Hook is called when user approval is needed for a swap operation. This hook allows users to review source and destination tokens, amounts, chains, and gas before approving or denying the swap.
The Swap Intent Hook is required for all swap operations (swapWithExactIn, swapWithExactOut). Without setting this hook, swap operations will fail.
Hook Signature
type OnSwapIntentHook = ( data : OnSwapIntentHookData ) => unknown ;
type OnSwapIntentHookData = {
allow : () => void ;
deny : () => void ;
intent : SwapIntent ;
refresh : () => Promise < SwapIntent >;
};
Setting the Hook
import { NexusSDK } from '@avail-project/nexus-core' ;
const sdk = new NexusSDK ({ network: 'mainnet' });
sdk . setOnSwapIntentHook ( async ({ intent , allow , deny , refresh }) => {
// Display swap details to user
console . log ( 'Swap from:' , intent . sources );
console . log ( 'Swap to:' , intent . destination );
console . log ( 'Gas:' , intent . destination . gas );
if ( userApproves ) {
allow ();
} else {
deny ();
}
});
Parameters
allow()
Approve the swap intent and proceed with the operation.
Callback function to approve the swap. Call this when the user confirms they want to proceed.
deny()
Reject the swap intent and cancel the operation.
Callback function to reject the swap. Throws a USER_DENIED_INTENT error when called.
intent
The swap intent details for user review.
Complete swap information including sources, destination, and gas details.
refresh()
Refresh the swap intent to get updated quotes and rates.
refresh
() => Promise<SwapIntent>
required
Function to refresh the swap quote. Returns updated SwapIntent with current market rates.
SwapIntent Structure
Destination details for the swap. Show Destination properties
Output amount (human-readable)
Destination chain information. Destination token information. Gas details for destination chain. Gas amount (human-readable)
Native gas token information. Show Gas token properties
Array of source tokens being swapped. Input amount (human-readable)
Source chain information. Source token information.
Examples
Basic Usage
sdk . setOnSwapIntentHook (({ intent , allow , deny }) => {
// Calculate total input
const totalInput = intent . sources . reduce (( sum , source ) => {
return sum + parseFloat ( source . amount );
}, 0 );
const confirmed = window . confirm (
`Swap ${ totalInput } from ${ intent . sources . length } chain(s) \n ` +
`to ${ intent . destination . amount } ${ intent . destination . token . symbol } on ${ intent . destination . chain . name } ?`
);
if ( confirmed ) {
allow ();
} else {
deny ();
}
});
With UI Framework (React)
import { useState } from 'react' ;
import { type SwapIntent } from '@avail-project/nexus-core' ;
function SwapComponent () {
const [ intent , setIntent ] = useState < SwapIntent | null >( null );
const [ callbacks , setCallbacks ] = useState <{
allow : () => void ;
deny : () => void ;
refresh : () => Promise < SwapIntent >;
} | null >( null );
const sdk = new NexusSDK ({ network: 'mainnet' });
sdk . setOnSwapIntentHook (({ intent , allow , deny , refresh }) => {
setIntent ( intent );
setCallbacks ({ allow , deny , refresh });
});
const handleRefresh = async () => {
if ( callbacks ) {
const refreshedIntent = await callbacks . refresh ();
setIntent ( refreshedIntent );
}
};
return (
< div >
{ intent && (
< div className = "swap-review" >
< h3 > Review Swap </ h3 >
< div className = "swap-sources" >
< h4 > From :</ h4 >
{ intent . sources . map (( source , i ) => (
< div key = { i } className = "source-item" >
< img src = {source.token. contractAddress } alt = {source.token. symbol } />
< span >
{ source . amount } { source . token . symbol } on { source . chain . name }
</ span >
</ div >
))}
</ div >
< div className = "swap-arrow" > ↓ </ div >
< div className = "swap-destination" >
< h4 > To :</ h4 >
< div className = "destination-item" >
< img src = {intent.destination.token. contractAddress } alt = {intent.destination.token. symbol } />
< span >
{ intent . destination . amount } { intent . destination . token . symbol } on { intent . destination . chain . name }
</ span >
</ div >
</ div >
< div className = "gas-info" >
< p > Gas : { intent . destination . gas . amount } { intent . destination . gas . token . symbol }</ p >
</ div >
< div className = "actions" >
< button onClick = { handleRefresh } > Refresh Quote </ button >
< button onClick = {() => callbacks?.allow()} > Approve </ button >
< button onClick = {() => callbacks?.deny()} > Reject </ button >
</ div >
</ div >
)}
</ div >
);
}
Refreshing Quotes
sdk . setOnSwapIntentHook ( async ({ intent , allow , deny , refresh }) => {
console . log ( 'Initial quote:' , intent . destination . amount );
// Wait 5 seconds and refresh
await new Promise ( resolve => setTimeout ( resolve , 5000 ));
const refreshedIntent = await refresh ();
console . log ( 'Updated quote:' , refreshedIntent . destination . amount );
// Check if quote improved
const oldAmount = parseFloat ( intent . destination . amount );
const newAmount = parseFloat ( refreshedIntent . destination . amount );
if ( newAmount > oldAmount ) {
console . log ( 'Quote improved!' );
} else {
console . log ( 'Quote stayed the same or got worse' );
}
allow ();
});
Cross-Chain Swap Display
sdk . setOnSwapIntentHook (({ intent , allow , deny }) => {
const isCrossChain = intent . sources . some (
source => source . chain . id !== intent . destination . chain . id
);
console . log ( ` ${ isCrossChain ? 'Cross-chain' : 'Same-chain' } swap` );
if ( isCrossChain ) {
console . log ( 'Source chains:' );
intent . sources . forEach ( source => {
console . log ( ` - ${ source . chain . name } (ID: ${ source . chain . id } )` );
});
console . log ( `Destination: ${ intent . destination . chain . name } (ID: ${ intent . destination . chain . id } )` );
}
allow ();
});
Calculating Exchange Rate
sdk . setOnSwapIntentHook (({ intent , allow }) => {
// Calculate total input value
const totalInput = intent . sources . reduce (( sum , source ) => {
return sum + parseFloat ( source . amount );
}, 0 );
const outputAmount = parseFloat ( intent . destination . amount );
const exchangeRate = outputAmount / totalInput ;
console . log ( `Exchange rate: 1 ${ intent . sources [ 0 ]. token . symbol } = ${ exchangeRate } ${ intent . destination . token . symbol } ` );
allow ();
});
Price Impact Warning
sdk . setOnSwapIntentHook ( async ({ intent , allow , deny , refresh }) => {
// Get fresh quote
const freshIntent = await refresh ();
const expectedOutput = parseFloat ( intent . destination . amount );
const actualOutput = parseFloat ( freshIntent . destination . amount );
const priceImpact = (( expectedOutput - actualOutput ) / expectedOutput ) * 100 ;
if ( priceImpact > 5 ) {
const confirmed = window . confirm (
`WARNING: Price has moved ${ priceImpact . toFixed ( 2 ) } % against you. Continue?`
);
if ( ! confirmed ) {
deny ();
return ;
}
}
allow ();
});
Error Handling
Calling deny() throws a NexusError with code USER_DENIED_INTENT. This is expected behavior and should not be treated as an error in your UI.
import { NexusError , ERROR_CODES } from '@avail-project/nexus-core' ;
try {
await sdk . swapWithExactIn ( params );
} catch ( error ) {
if ( error instanceof NexusError ) {
if ( error . code === ERROR_CODES . USER_DENIED_INTENT ) {
console . log ( 'User cancelled the swap' );
} else if ( error . code === ERROR_CODES . SWAP_FAILED ) {
console . error ( 'Swap failed:' , error . message );
} else if ( error . code === ERROR_CODES . RATES_CHANGED_BEYOND_TOLERANCE ) {
console . error ( 'Price moved too much, please retry' );
}
}
}
Best Practices
Show exchange rates : Calculate and display the rate between input and output tokens.
Display gas costs : Show the gas amount that will be supplied to the destination chain.
Refresh quotes : Use refresh() to get updated prices, especially for large swaps or after delays.
Cross-chain indicators : Clearly show when swapping between different chains.
Price impact warnings : Alert users to significant price movements.
Source breakdown : When swapping from multiple chains, show each source clearly.
Handle denials gracefully : Don’t show error messages when users cancel.
Async operations : The hook can be async for UI operations before calling allow() or deny().