Skip to main content

Overview

Hooks are callback functions that the SDK triggers at critical points during operations, allowing you to build interactive user experiences. They enable users to review and approve actions before execution.

Available Hooks

The Avail Nexus SDK provides three types of hooks:

Intent Hook

Review cross-chain intents before execution

Allowance Hook

Approve token spending allowances

Swap Intent Hook

Review swap operations before execution

Intent Hook

Called when the SDK needs user approval for a bridge or transfer intent.

Setting the Hook

import { NexusSDK } from '@avail-project/nexus-core';

const sdk = new NexusSDK({ network: 'mainnet' });

sdk.setOnIntentHook(async ({ intent, allow, deny, refresh }) => {
  // Your approval logic here
});

Hook Data Structure

type OnIntentHookData = {
  allow: () => void;
  deny: () => void;
  intent: ReadableIntent;
  refresh: (selectedSources?: number[]) => Promise<ReadableIntent>;
};
Parameters:
allow
() => void
Call this function to approve the intent and proceed with execution.
deny
() => void
Call this function to reject the intent. Throws a USER_DENIED_INTENT error.
intent
ReadableIntent
The intent object containing sources, destination, fees, and token information.
refresh
(selectedSources?: number[]) => Promise<ReadableIntent>
Refresh the intent with updated quotes or different source chains.

Example: Basic Intent Review

sdk.setOnIntentHook(async ({ intent, allow, deny }) => {
  console.log('=== Intent Review ===');
  
  // Display sources
  console.log('Sources:');
  intent.sources.forEach(source => {
    console.log(`  - ${source.amount} ${source.token.symbol} from ${source.chain.name}`);
  });
  
  // Display destination
  console.log(`Destination: ${intent.destination.amount} on ${intent.destination.chainName}`);
  
  // Display fees
  console.log('Fees:');
  console.log(`  Protocol: ${intent.fees.protocol}`);
  console.log(`  Solver: ${intent.fees.solver}`);
  console.log(`  Gas: ${intent.fees.caGas}`);
  console.log(`  Total: ${intent.fees.total}`);
  
  // Get user approval
  const approved = confirm('Approve this intent?');
  
  if (approved) {
    allow();
  } else {
    deny();
  }
});

Example: Intent with Refresh

sdk.setOnIntentHook(async ({ intent, allow, deny, refresh }) => {
  // Show initial intent
  displayIntent(intent);
  
  // User wants to use different chains
  const wantsCustomSources = await askUserForCustomization();
  
  if (wantsCustomSources) {
    // Show available sources
    const availableChains = intent.allSources.map(s => s.chain.id);
    const selectedChains = await userSelectChains(availableChains);
    
    // Refresh with selected chains
    const refreshedIntent = await refresh(selectedChains);
    displayIntent(refreshedIntent);
  }
  
  // Final approval
  const approved = await confirmIntent();
  if (approved) {
    allow();
  } else {
    deny();
  }
});
Refreshing an intent may change fees and amounts due to price movements or liquidity changes.

Allowance Hook

Called when token approval is needed before a transaction can proceed.

Setting the Hook

sdk.setOnAllowanceHook(({ sources, allow, deny }) => {
  // Your approval logic here
});

Hook Data Structure

type OnAllowanceHookData = {
  allow: (amounts: Array<'max' | 'min' | bigint | string>) => void;
  deny: () => void;
  sources: AllowanceHookSources;
};

type AllowanceHookSources = Array<{
  allowance: {
    current: string;      // Current allowance (human-readable)
    currentRaw: bigint;   // Current allowance (raw)
    minimum: string;      // Minimum required (human-readable)
    minimumRaw: bigint;   // Minimum required (raw)
  };
  chain: {
    id: number;
    logo: string;
    name: string;
  };
  token: {
    contractAddress: Hex;
    decimals: number;
    logo: string;
    name: string;
    symbol: string;
  };
}>;
Parameters:
allow
(amounts: Array<'max' | 'min' | bigint | string>) => void
Approve allowances with one value per source:
  • 'min' - Approve exact minimum needed
  • 'max' - Approve unlimited (type(uint256).max)
  • bigint - Approve specific amount
deny
() => void
Reject the allowance request. Throws a USER_DENIED_ALLOWANCE error.
sources
AllowanceHookSources
Array of sources requiring allowance approval.

Example: Basic Allowance Approval

sdk.setOnAllowanceHook(({ sources, allow, deny }) => {
  console.log('=== Allowance Required ===');
  
  sources.forEach((source, index) => {
    console.log(`Source ${index + 1}:`);
    console.log(`  Chain: ${source.chain.name}`);
    console.log(`  Token: ${source.token.symbol}`);
    console.log(`  Current allowance: ${source.allowance.current}`);
    console.log(`  Required minimum: ${source.allowance.minimum}`);
  });
  
  // Approve minimum for all sources
  allow(sources.map(() => 'min'));
});

Allowance Approval Strategies

Example: User-Confirmed Allowances

sdk.setOnAllowanceHook(async ({ sources, allow, deny }) => {
  // Display allowance details to user
  const modal = createAllowanceModal({
    sources: sources.map(s => ({
      chain: s.chain.name,
      token: s.token.symbol,
      current: s.allowance.current,
      required: s.allowance.minimum,
    })),
  });
  
  const userChoice = await modal.show();
  
  if (userChoice === 'approve-min') {
    allow(sources.map(() => 'min'));
  } else if (userChoice === 'approve-max') {
    allow(sources.map(() => 'max'));
  } else {
    deny();
  }
});

Swap Intent Hook

Called when user approval is needed for a swap operation.

Setting the Hook

sdk.setOnSwapIntentHook(async ({ intent, allow, deny, refresh }) => {
  // Your approval logic here
});

Hook Data Structure

type OnSwapIntentHookData = {
  allow: () => void;
  deny: () => void;
  intent: SwapIntent;
  refresh: () => Promise<SwapIntent>;
};

type SwapIntent = {
  destination: {
    amount: string;
    chain: { id: number; logo: string; name: string };
    token: { contractAddress: Hex; decimals: number; symbol: string };
    gas: {
      amount: string;
      token: { contractAddress: Hex; decimals: number; symbol: string };
    };
  };
  sources: Array<{
    amount: string;
    chain: { id: number; logo: string; name: string };
    token: { contractAddress: Hex; decimals: number; symbol: string };
  }>;
};

Example: Basic Swap Review

sdk.setOnSwapIntentHook(async ({ intent, allow, deny, refresh }) => {
  console.log('=== Swap Intent Review ===');
  
  // Display sources
  console.log('Swapping from:');
  intent.sources.forEach(source => {
    console.log(
      `  - ${source.amount} ${source.token.symbol} on ${source.chain.name}`
    );
  });
  
  // Display destination
  console.log('Receiving:');
  console.log(
    `  ${intent.destination.amount} ${intent.destination.token.symbol} on ${intent.destination.chain.name}`
  );
  
  // Display gas
  console.log('Gas:');
  console.log(
    `  ${intent.destination.gas.amount} ${intent.destination.gas.token.symbol}`
  );
  
  // Get approval
  const approved = confirm('Approve this swap?');
  
  if (approved) {
    allow();
  } else {
    deny();
  }
});

Example: Swap with Refresh

sdk.setOnSwapIntentHook(async ({ intent, allow, deny, refresh }) => {
  // Show initial quote
  displaySwapQuote(intent);
  
  // Allow user to refresh for updated quote
  const wantsRefresh = await askToRefreshQuote();
  
  if (wantsRefresh) {
    const updatedIntent = await refresh();
    displaySwapQuote(updatedIntent);
  }
  
  // Final approval
  const approved = await confirmSwap();
  if (approved) {
    allow();
  } else {
    deny();
  }
});
Swap rates can change between quote and execution. Consider setting slippage tolerance and refreshing quotes if too much time has passed.

Default Hook Behavior

If you don’t set hooks, the SDK uses these defaults:
// Default intent hook - auto-approves all intents
default: (data) => data.allow()

// Default allowance hook - approves minimum for all sources
default: (data) => data.allow(data.sources.map(() => 'min'))

// Default swap intent hook - auto-approves all swaps
default: (data) => data.allow()
Default hooks auto-approve operations without user interaction. Always set custom hooks in production to ensure users can review operations.

Error Handling

Handle hook-related errors appropriately:
import { NexusError, ERROR_CODES } from '@avail-project/nexus-core';

try {
  await sdk.bridge(params);
} catch (error) {
  if (error instanceof NexusError) {
    switch (error.code) {
      case ERROR_CODES.USER_DENIED_INTENT:
        console.log('User rejected the intent');
        break;
      case ERROR_CODES.USER_DENIED_ALLOWANCE:
        console.log('User rejected the allowance');
        break;
      case ERROR_CODES.INVALID_VALUES_ALLOWANCE_HOOK:
        console.error('Invalid allowance values provided');
        break;
      default:
        console.error('Operation failed:', error.message);
    }
  }
}

Best Practices

Show users:
  • Source chains and amounts
  • Destination chain and expected amount
  • All fees broken down
  • Token symbols and logos for clarity
When using custom allowance amounts:
sdk.setOnAllowanceHook(({ sources, allow, deny }) => {
  const amounts = sources.map(source => {
    // Ensure amount is at least minimum
    const userAmount = getUserInputAmount();
    if (userAmount < source.allowance.minimumRaw) {
      throw new Error('Amount below minimum required');
    }
    return userAmount;
  });
  allow(amounts);
});
Hooks can be async for showing modals or fetching data:
sdk.setOnIntentHook(async ({ intent, allow, deny }) => {
  // Async modal display
  const result = await showModalAsync(intent);
  
  if (result.approved) {
    allow();
  } else {
    deny();
  }
});
Make it obvious what users are approving:
// Good: Specific action
<Button onClick={allow}>
  Approve Bridge of 100 USDC to Polygon
</Button>

// Bad: Vague action
<Button onClick={allow}>
  Continue
</Button>
Prevent stuck operations:
sdk.setOnIntentHook(async ({ intent, allow, deny }) => {
  const timeout = setTimeout(() => {
    console.log('User took too long, cancelling');
    deny();
  }, 5 * 60 * 1000); // 5 minutes
  
  const approved = await getUserApproval(intent);
  clearTimeout(timeout);
  
  if (approved) allow();
  else deny();
});

Complete Example: Production-Ready Hooks

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

const sdk = new NexusSDK({ network: 'mainnet' });

// Intent Hook
sdk.setOnIntentHook(async ({ intent, allow, deny, refresh }) => {
  try {
    const modal = createIntentModal({
      title: 'Review Bridge',
      sources: intent.sources,
      destination: intent.destination,
      fees: intent.fees,
      onApprove: allow,
      onReject: deny,
      onRefresh: async (chains) => {
        const updated = await refresh(chains);
        modal.updateIntent(updated);
      },
    });
    
    await modal.show();
  } catch (error) {
    console.error('Error in intent hook:', error);
    deny();
  }
});

// Allowance Hook
sdk.setOnAllowanceHook(async ({ sources, allow, deny }) => {
  try {
    const modal = createAllowanceModal({
      title: 'Token Approval Required',
      sources: sources,
      strategies: ['min', 'max'],
      onApprove: (strategy) => {
        allow(sources.map(() => strategy));
      },
      onReject: deny,
    });
    
    await modal.show();
  } catch (error) {
    console.error('Error in allowance hook:', error);
    deny();
  }
});

// Swap Intent Hook
sdk.setOnSwapIntentHook(async ({ intent, allow, deny, refresh }) => {
  try {
    const modal = createSwapModal({
      title: 'Review Swap',
      sources: intent.sources,
      destination: intent.destination,
      onApprove: allow,
      onReject: deny,
      onRefresh: async () => {
        const updated = await refresh();
        modal.updateIntent(updated);
      },
    });
    
    await modal.show();
  } catch (error) {
    console.error('Error in swap intent hook:', error);
    deny();
  }
});

Next Steps

  • Events - Track operation progress with events
  • Intents - Deep dive into the intent structure

Build docs developers (and LLMs) love