Skip to main content

Overview

The Intent Hook is called when the SDK needs user approval for a bridge or transfer intent. This hook allows users to review source chains, destination details, fees, and amounts before approving or denying the operation.
The Intent Hook is required for all bridge, transfer, and bridge-and-execute operations. Without setting this hook, these operations will fail.

Hook Signature

type OnIntentHook = (data: OnIntentHookData) => void;

type OnIntentHookData = {
  allow: () => void;
  deny: () => void;
  intent: ReadableIntent;
  refresh: (selectedSources?: number[]) => Promise<ReadableIntent>;
};

Setting the Hook

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

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

sdk.setOnIntentHook(async ({ intent, allow, deny, refresh }) => {
  // Display intent details to user
  console.log('Source chains:', intent.sources);
  console.log('Destination:', intent.destination);
  console.log('Fees:', intent.fees);
  console.log('Total from sources:', intent.sourcesTotal);

  // User approves
  if (userApproves) {
    allow();
  } else {
    deny(); // Throws USER_DENIED_INTENT error
  }
});

Parameters

allow()

Approve the intent and proceed with the operation.
allow
() => void
required
Callback function to approve the intent. Call this when the user confirms they want to proceed.

deny()

Reject the intent and cancel the operation.
deny
() => void
required
Callback function to reject the intent. Throws a USER_DENIED_INTENT error when called.

intent

The intent details for user review.
intent
ReadableIntent
required
Complete intent information including sources, destination, fees, and token details.

refresh()

Refresh the intent with different source chains or updated quotes.
refresh
(selectedSources?: number[]) => Promise<ReadableIntent>
required
Optional function to refresh the intent. Pass an array of chain IDs to use specific source chains.

ReadableIntent Structure

sources
array
required
Array of source chains from which funds will be pulled.
allSources
array
required
All available source chains before selection (same structure as sources).
destination
object
required
Destination chain details.
fees
object
required
Complete fee breakdown.
token
object
required
Token being bridged.
sourcesTotal
string
required
Total amount from all sources combined (human-readable).

Examples

Basic Usage

sdk.setOnIntentHook(({ intent, allow, deny }) => {
  // Show confirmation dialog
  const confirmed = window.confirm(
    `Bridge ${intent.sourcesTotal} ${intent.token.symbol} to ${intent.destination.chainName}?\n` +
    `Total fees: ${intent.fees.total} ${intent.token.symbol}`
  );

  if (confirmed) {
    allow();
  } else {
    deny();
  }
});

With UI Framework (React)

import { useState } from 'react';
import { NexusSDK, type ReadableIntent } from '@avail-project/nexus-core';

function BridgeComponent() {
  const [intent, setIntent] = useState<ReadableIntent | null>(null);
  const [callbacks, setCallbacks] = useState<{ allow: () => void; deny: () => void } | null>(null);

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

  sdk.setOnIntentHook(({ intent, allow, deny }) => {
    setIntent(intent);
    setCallbacks({ allow, deny });
  });

  return (
    <div>
      {intent && (
        <div className="intent-review">
          <h3>Review Bridge Intent</h3>
          
          <div className="sources">
            <h4>From:</h4>
            {intent.sources.map((source, i) => (
              <div key={i}>
                {source.amount} {source.token.symbol} on {source.chain.name}
              </div>
            ))}
            <p>Total: {intent.sourcesTotal} {intent.token.symbol}</p>
          </div>

          <div className="destination">
            <h4>To:</h4>
            <p>{intent.destination.amount} {intent.token.symbol} on {intent.destination.chainName}</p>
          </div>

          <div className="fees">
            <h4>Fees:</h4>
            <p>Protocol: {intent.fees.protocol}</p>
            <p>Solver: {intent.fees.solver}</p>
            <p>Gas: {intent.fees.caGas}</p>
            <p><strong>Total: {intent.fees.total} {intent.token.symbol}</strong></p>
          </div>

          <button onClick={() => callbacks?.allow()}>Approve</button>
          <button onClick={() => callbacks?.deny()}>Reject</button>
        </div>
      )}
    </div>
  );
}

Refreshing with Different Source Chains

sdk.setOnIntentHook(async ({ intent, allow, deny, refresh }) => {
  console.log('Initial sources:', intent.sources);

  // User wants to use different chains
  if (userWantsDifferentChains) {
    const refreshedIntent = await refresh([8453, 42161]); // Base and Arbitrum only
    console.log('Refreshed sources:', refreshedIntent.sources);
    console.log('New fees:', refreshedIntent.fees);
  }

  allow();
});

Displaying All Available Sources

sdk.setOnIntentHook(({ intent, allow, deny }) => {
  console.log('Selected sources:', intent.sources.length);
  console.log('All available sources:', intent.allSources.length);

  // Show which chains have funds
  intent.allSources.forEach(source => {
    const isSelected = intent.sources.some(s => s.chain.id === source.chain.id);
    console.log(
      `${source.chain.name}: ${source.amount} ${source.token.symbol} ${isSelected ? '(selected)' : ''}`
    );
  });

  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.bridge(params);
} catch (error) {
  if (error instanceof NexusError && error.code === ERROR_CODES.USER_DENIED_INTENT) {
    // User cancelled - not an error to display
    console.log('User cancelled the bridge operation');
  } else {
    // Actual error
    console.error('Bridge failed:', error);
  }
}

Best Practices

  1. Always display fees: Users should know the total cost before approving.
  2. Show source breakdown: When bridging from multiple chains, clearly show which chains are being used.
  3. Highlight totals: Display sourcesTotal and fees.total prominently.
  4. Use refresh judiciously: Only call refresh() when the user explicitly wants to change source chains.
  5. Handle denials gracefully: Don’t show error messages when users cancel via deny().
  6. Async operations: The hook can be async if you need to perform asynchronous UI operations before calling allow() or deny().

Build docs developers (and LLMs) love