From e79b8a8961bc7ddd0eafea295e8fbacba2c19615 Mon Sep 17 00:00:00 2001 From: Jakub Cech <3016965+jakubcech@users.noreply.github.com> Date: Thu, 13 Nov 2025 13:54:43 +0100 Subject: [PATCH 1/8] intro addition --- .../nexus-core/bridge-and-execute/page.mdx | 60 ++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/page.mdx b/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/page.mdx index 2546b2a68..2f1147163 100644 --- a/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/page.mdx +++ b/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/page.mdx @@ -13,7 +13,46 @@ import { Callout, Steps, Tabs } from 'nextra/components' 2. We also [created a tutorial](/nexus/nexus-examples/nexus-initialization-basic) to make it easier to understand how devs need to initialize the Nexus SDK in their project. -Use the `bridgeAndExecute()` function to bridge a token and execute a contract function on the recipient chain in a single flow. +The `bridgeAndExecute` method enables **atomic cross-chain operations** - allowing you to bridge assets from one chain to another and immediately execute a smart contract call in a single transaction. This eliminates the need for multiple steps and reduces the risk of MEV attacks, failed transactions, or price slippage that can occur when bridging and executing separately. + +### Key Benefits + +- **Atomic Execution**: Both the bridge and subsequent operation succeed or fail together +- **Reduced Complexity**: Single transaction instead of multiple steps +- **Improved User Experience**: Users get their desired outcome in one interaction +- **Cost Efficiency**: Potentially lower overall gas costs +- **MEV Protection**: Minimizes front-running opportunities + +### Common Use Cases + +- Bridging tokens and immediately swapping to another token +- Cross-chain yield farming deposits +- Bridging NFTs and listing them on a marketplace +- Cross-chain liquidity provision +- Bridging and staking in one transaction + +## How It Works + +The `bridgeAndExecute` process follows this sequence: + +### Step 1: Initiation +Your dApp calls the `bridgeAndExecute` method with both the bridging parameters and the execution payload for the destination chain. + +### Step 2: Source Chain Processing +- Tokens are locked or burned on the source chain +- Bridge message is emitted with both the asset transfer details and execution instructions + +### Step 3: Cross-Chain Messaging +The bridge protocol relays the message to the destination chain, ensuring the execution payload is included. + +### Step 4: Atomic Execution on Destination +On the destination chain: +1. Tokens are minted or released to the user +2. The specified contract is immediately called with the provided calldata +3. Both operations are atomic - if either fails, the entire transaction reverts + +### Step 5: Confirmation +Your application receives a response with transaction details to track the operation. ## Method signature @@ -28,6 +67,7 @@ async simulateBridgeAndExecute(params: BridgeAndExecuteParams): Promise Date: Wed, 19 Nov 2025 10:27:36 +0100 Subject: [PATCH 2/8] response handling examples --- .../nexus-core/bridge-and-execute/page.mdx | 79 +++++++++++++++++-- 1 file changed, 73 insertions(+), 6 deletions(-) diff --git a/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/page.mdx b/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/page.mdx index 2f1147163..04f0ae0d6 100644 --- a/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/page.mdx +++ b/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/page.mdx @@ -84,23 +84,90 @@ export interface BridgeAndExecuteParams { ``` ## Handling responses -The `bridgeAndExecute` method handles responses differently depending on the bridge protocol and chain configurations. Here's how to properly handle both scenarios. -### Synchronous response +The `bridgeAndExecute` call returns a `BridgeAndExecuteResult` object. You can use its fields to show status to users or to poll block explorers for progress. + +Note that the `bridgeAndExecute` method handles responses differently depending on the bridge protocol and chain configurations. Below are minimal examples on how you can work with the responses. + +### Immediate confirmation bridges Some bridges (like those with instant finality or optimistic execution) provide immediate confirmation: -```typescript -try { +Use the hashes and explorer URLs returned by the bridge call to surface immediate confirmations. The bridge transaction hash is available right away, so you can link to it and begin polling while the destination execution continues. +```typescript +import { NexusSDK } from '@avail-project/nexus-core'; +import type { BridgeAndExecuteResult } from '@avail-project/nexus-core'; + +const sdk = new NexusSDK(/* ... */); + +const result: BridgeAndExecuteResult = await sdk.bridgeAndExecute(params); + +if (result.success) { + // Show immediate bridge confirmation + if (result.bridgeTransactionHash && result.bridgeExplorerUrl) { + console.log(`Bridge submitted: ${result.bridgeExplorerUrl}`); + } + + // Track follow-up approval or execution hashes when present + if (result.approvalTransactionHash) { + console.log(`Approval tx: ${result.approvalTransactionHash}`); + } + if (result.executeTransactionHash && result.executeExplorerUrl) { + console.log(`Execute tx: ${result.executeExplorerUrl}`); + } +} else { + console.error(`Bridge and execute failed: ${result.error}`); +} ``` -### Asynchronous response -Most cross-chain operations require waiting for destination chain finality: +**What to do with the response in your user experience** +- Use the bridge hash/URL to show an immediate “bridge submitted” banner. +- Start polling the bridge transaction for completion on the source chain. +- Surface execution details (hash/URL) when available to help users follow the destination action. + +### Destination chain finality bridges + +For bridges that only finalize after the destination chain confirms the execution, rely on the destination transaction hash to track finality. The response flags whether the bridge step was skipped and always includes the target chain ID so you can pick the correct explorer. ```typescript +import { NexusSDK } from '@avail-project/nexus-core'; +import type { BridgeAndExecuteResult } from '@avail-project/nexus-core'; + +const sdk = new NexusSDK(/* ... */); + +const result: BridgeAndExecuteResult = await sdk.bridgeAndExecute({ + ...params, + waitForReceipt: true, // wait for receipt on the destination chain + requiredConfirmations: 3, // enforce destination finality +}); + +if (result.success) { + // Bridge may be skipped if funds already exist on the destination + if (result.bridgeSkipped) { + console.log('Bridge skipped; executed directly on destination chain'); + } + + // Wait for the destination execution to reach finality + if (result.executeTransactionHash) { + await pollForConfirmations({ + txHash: result.executeTransactionHash, + chainId: result.toChainId, + minConfirmations: 3, + }); + console.log('Destination execution finalized'); + } +} else { + console.error(`Bridge and execute failed: ${result.error}`); +} ``` +**What to do with the response in your user experience** +- Respect `bridgeSkipped` to avoid showing bridge progress when the SDK executes directly. +- Use `toChainId` and the execution hash to poll the destination chain until the required confirmations are met. +- Present finality status updates (e.g., “Waiting for 3 confirmations on chain X”). + + ## Example ```typescript showLineNumbers filename="Typescript" From b7844315b07a70aa6b542b1828e3439442eeed2b Mon Sep 17 00:00:00 2001 From: Jakub Cech <3016965+jakubcech@users.noreply.github.com> Date: Wed, 19 Nov 2025 12:05:18 +0100 Subject: [PATCH 3/8] lido sEth example --- .../nexus-core/bridge-and-execute/page.mdx | 54 ++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/page.mdx b/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/page.mdx index 04f0ae0d6..6599243dc 100644 --- a/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/page.mdx +++ b/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/page.mdx @@ -85,7 +85,7 @@ export interface BridgeAndExecuteParams { ## Handling responses -The `bridgeAndExecute` call returns a `BridgeAndExecuteResult` object. You can use its fields to show status to users or to poll block explorers for progress. +The `bridgeAndExecute` call returns a `BridgeAndExecuteResult` [object](). You can use its fields to show status to users or to poll block explorers for progress. Note that the `bridgeAndExecute` method handles responses differently depending on the bridge protocol and chain configurations. Below are minimal examples on how you can work with the responses. @@ -167,8 +167,11 @@ if (result.success) { - Use `toChainId` and the execution hash to poll the destination chain until the required confirmations are met. - Present finality status updates (e.g., “Waiting for 3 confirmations on chain X”). +## Examples -## Example +### Example 1 + +The following examples showcases the use of the `bridgeAndExecute` call. The example bridges USDC from Base to Ethereum and then deposits the USDC to the `Yearn USDC Vault`. ```typescript showLineNumbers filename="Typescript" import type { @@ -278,6 +281,53 @@ console.log('Approval required:', simulation.metadata?.approvalRequired); console.log('Bridge receive amount:', simulation.metadata?.bridgeReceiveAmount); ``` +### Example 2 + +The following examples showcases the use of the `bridgeAndExecute` call. The example bridges native ETH from Arbitrum and stakes it in the `Lido stETH` vault on Ethereum. + +Only the `bridgeAndExecute` call part of the first example has changed. Note that native ETH deposit does not require setting `tokenApproval`. + +```typescript +... + +// Bridge native ETH from Arbitrum and stake into Lido stETH on Ethereum +const lidoStakeResult: BridgeAndExecuteResult = await sdk.bridgeAndExecute({ + token: 'ETH', + amount: '0.5', // Bridge 0.5 ETH + toChainId: 1, // Stake on Ethereum mainnet + sourceChains: [42161], // Bridge from Arbitrum + execute: { + contractAddress: '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84', // Lido stETH + contractAbi: [ + { + inputs: [{ internalType: 'address', name: '_referral', type: 'address' }], + name: 'submit', + outputs: [{ internalType: 'uint256', name: 'stETH', type: 'uint256' }], + stateMutability: 'payable', + type: 'function', + }, + ], + functionName: 'submit', + buildFunctionParams: ( + token: SUPPORTED_TOKENS, + amount: string, + chainId: SUPPORTED_CHAINS_IDS, + userAddress: `0x${string}`, + ) => { + const amountWei = parseUnits(amount, 18); + return { + // Stake without referral and send the bridged ETH as msg.value + functionParams: ['0x0000000000000000000000000000000000000000'], + value: amountWei.toString(), + }; + }, + }, + waitForReceipt: true, +} as BridgeAndExecuteParams); +... + +``` + ## Return Value The return value is a `BridgeAndExecuteResult` object. From c2849fb33453871256c5d275b31de8ce78f53244 Mon Sep 17 00:00:00 2001 From: Jakub Cech <3016965+jakubcech@users.noreply.github.com> Date: Wed, 19 Nov 2025 12:14:31 +0100 Subject: [PATCH 4/8] description fix --- .../avail-nexus-sdk/nexus-core/bridge-and-execute/page.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/page.mdx b/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/page.mdx index 6599243dc..abdfe77e1 100644 --- a/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/page.mdx +++ b/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/page.mdx @@ -91,7 +91,7 @@ Note that the `bridgeAndExecute` method handles responses differently depending ### Immediate confirmation bridges -Some bridges (like those with instant finality or optimistic execution) provide immediate confirmation: +Some bridges (like those with instant finality or optimistic execution) provide immediate confirmation. Use the hashes and explorer URLs returned by the bridge call to surface immediate confirmations. The bridge transaction hash is available right away, so you can link to it and begin polling while the destination execution continues. ```typescript From 5b5ee189bd0eae3b5f0752b51f4739e2680bdbef Mon Sep 17 00:00:00 2001 From: Jakub Cech <3016965+jakubcech@users.noreply.github.com> Date: Wed, 19 Nov 2025 12:19:31 +0100 Subject: [PATCH 5/8] aave tutorial --- .../bridge-and-execute-aave-tutorial.md | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/bridge-and-execute-aave-tutorial.md diff --git a/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/bridge-and-execute-aave-tutorial.md b/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/bridge-and-execute-aave-tutorial.md new file mode 100644 index 000000000..49670bf6d --- /dev/null +++ b/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/bridge-and-execute-aave-tutorial.md @@ -0,0 +1,155 @@ +### Tutorial: Bridge from Base to Ethereum and Stake USDC in Aave + +The snippets above are great for quick reference, but the following walkthrough shows how to wire up a complete bridge → stake experience that: + +1. Bridges 5,000,000 USDC (5,000 USDC with 6 decimals) from **Base (chainId 8453)**. +2. Stakes the bridged USDC inside **Aave v3 on Ethereum mainnet (chainId 1)** by calling `Pool.supply` at `0x87870Bca3F3fD6335C3F4ce8392D69350B4FA4E2`. +3. Requires a token approval on Ethereum because Aave pulls USDC via `transferFrom` (non-native deposit). +4. Streams progress, handles transaction responses, and surfaces explorer links in your UI. + +#### 1. Subscribe to bridge-and-execute progress for UI updates + +```tsx +import { useEffect, useState } from 'react'; +import { + NexusSDK, + NEXUS_EVENTS, + type BridgeAndExecuteResult, + type ProgressStep, +} from '@avail-project/nexus-core'; + +const sdk = new NexusSDK({ network: 'mainnet' }); + +export function useBridgeAndStakeProgress() { + const [expectedSteps, setExpectedSteps] = useState([]); + const [completedStep, setCompletedStep] = useState(null); + + useEffect(() => { + const unsubExpected = sdk.nexusEvents.on( + NEXUS_EVENTS.BRIDGE_EXECUTE_EXPECTED_STEPS, + (steps) => setExpectedSteps(steps), + ); + const unsubCompleted = sdk.nexusEvents.on( + NEXUS_EVENTS.BRIDGE_EXECUTE_COMPLETED_STEPS, + (step) => setCompletedStep(step), + ); + return () => { + unsubExpected(); + unsubCompleted(); + }; + }, []); + + return { expectedSteps, completedStep }; +} +``` + +Render `expectedSteps` to show the checklist (intent signed → bridge submitted → execute submitted) and `completedStep` to highlight the active stage or link out to the explorer when a step exposes `step.data.explorerURL`. + +#### 2. Simulate the flow and prompt for approvals + +```typescript +import { + type BridgeAndExecuteParams, + type BridgeAndExecuteSimulationResult, + TOKEN_CONTRACT_ADDRESSES, + TOKEN_METADATA, +} from '@avail-project/nexus-core'; +import { parseUnits } from 'viem'; + +const params: BridgeAndExecuteParams = { + token: 'USDC', + amount: '5000000', // 5,000 USDC with 6 decimals + toChainId: 1, + sourceChains: [8453], + enableTransactionPolling: true, // auto-poll if the provider does not emit receipts + transactionTimeout: 180_000, // 3 minutes + waitForReceipt: true, + requiredConfirmations: 2, + execute: { + contractAddress: '0x87870Bca3F3fD6335C3F4ce8392D69350B4FA4E2', // Aave v3 Pool + contractAbi: [ + { + inputs: [ + { internalType: 'address', name: 'asset', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + { internalType: 'address', name: 'onBehalfOf', type: 'address' }, + { internalType: 'uint16', name: 'referralCode', type: 'uint16' }, + ], + name: 'supply', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + ], + functionName: 'supply', + buildFunctionParams: (token, amount, chainId, userAddress) => { + const decimals = TOKEN_METADATA[token].decimals; + const amountWei = parseUnits(amount, decimals); + const tokenAddress = TOKEN_CONTRACT_ADDRESSES[token][chainId]; + return { + functionParams: [tokenAddress, amountWei, userAddress, 0], + }; + }, + tokenApproval: { + token: 'USDC', + amount: '5000000', + }, + }, +}; + +const preview: BridgeAndExecuteSimulationResult = await sdk.simulateBridgeAndExecute(params); + +if (!preview.success) { + throw new Error(preview.error || 'Simulation failed'); +} + +if (preview.metadata?.approvalRequired) { + // Trigger your allowance modal – show preview.metadata.minApproval to the user. +} +``` + +Simulations also return the full `steps` array so you can pre-render the UI timeline while the user decides whether to proceed. + +#### 3. Submit the bridge and staking call + +```typescript +const result: BridgeAndExecuteResult = await sdk.bridgeAndExecute(params); + +if (!result.success) { + console.error('Bridge + stake failed:', result.error); + return; +} + +// Update the UI immediately after each phase +if (result.approvalTransactionHash) { + setApprovalLink(result.approvalTransactionHash); +} + +if (result.bridgeTransactionHash) { + setBridgeLink(result.bridgeExplorerUrl ?? result.bridgeTransactionHash); +} + +if (result.executeTransactionHash) { + setStakeLink(result.executeExplorerUrl ?? result.executeTransactionHash); +} +``` + +- With `waitForReceipt: true`, the promise resolves only after the execute transaction reaches 2 confirmations (configurable via `requiredConfirmations`). Surface `result.executeExplorerUrl` as “Funds staked” in the UI. +- Without waiting for receipts you can still poll your own subgraph or rely on `enableTransactionPolling` so the SDK polls for hashes on RPCs that delay responses. + +#### 4. Keep the UI in sync post-submission + +Even after the promise resolves, you may want to periodically refresh balances or intent status: + +```typescript +// Example poller to refresh balances + confirmations every 15 seconds +const interval = window.setInterval(async () => { + const balances = await sdk.getUnifiedBalances(); + updatePortfolio(balances); +}, 15_000); + +// When you no longer need updates +window.clearInterval(interval); +``` + +Pairing periodic refreshes with the progress events from Step 1 guarantees that users always see the latest state: intent accepted, bridge mined, Aave supply confirmed, and the resulting aUSDC balance increase. From 915c310a2f07c74890e8ac595634750134d5df63 Mon Sep 17 00:00:00 2001 From: Jakub Cech <3016965+jakubcech@users.noreply.github.com> Date: Wed, 19 Nov 2025 12:21:45 +0100 Subject: [PATCH 6/8] description for tutorial --- .../bridge-and-execute/bridge-and-execute-aave-tutorial.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/bridge-and-execute-aave-tutorial.md b/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/bridge-and-execute-aave-tutorial.md index 49670bf6d..b214aac41 100644 --- a/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/bridge-and-execute-aave-tutorial.md +++ b/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/bridge-and-execute-aave-tutorial.md @@ -1,6 +1,6 @@ ### Tutorial: Bridge from Base to Ethereum and Stake USDC in Aave -The snippets above are great for quick reference, but the following walkthrough shows how to wire up a complete bridge → stake experience that: +The following example shows how to wire up a complete bridge → stake experience that: 1. Bridges 5,000,000 USDC (5,000 USDC with 6 decimals) from **Base (chainId 8453)**. 2. Stakes the bridged USDC inside **Aave v3 on Ethereum mainnet (chainId 1)** by calling `Pool.supply` at `0x87870Bca3F3fD6335C3F4ce8392D69350B4FA4E2`. From 2d67f5f09ddb800394062cfdcd0cb54b89aaab78 Mon Sep 17 00:00:00 2001 From: Jakub Cech <3016965+jakubcech@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:01:50 +0100 Subject: [PATCH 7/8] unified balance tutorial --- .../fetch-unified-balances-tutorial.md | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 app/nexus/avail-nexus-sdk/nexus-core/fetch-unified-balances/fetch-unified-balances-tutorial.md diff --git a/app/nexus/avail-nexus-sdk/nexus-core/fetch-unified-balances/fetch-unified-balances-tutorial.md b/app/nexus/avail-nexus-sdk/nexus-core/fetch-unified-balances/fetch-unified-balances-tutorial.md new file mode 100644 index 000000000..e9745aaad --- /dev/null +++ b/app/nexus/avail-nexus-sdk/nexus-core/fetch-unified-balances/fetch-unified-balances-tutorial.md @@ -0,0 +1,191 @@ +# `getUnifiedBalances` End-to-End Guide + +The `getUnifiedBalances` method collects a user's on-chain portfolio across every chain that Nexus knows about. The call is a great way to have a single source of truth for every balance-aware widget in your application, from asset pickers to transaction reviews and more. Handling and setting up the the experience well in your front-end requires a bit more work as you depend on a larger number of concurrent calls with different properties and guarantees. This document provides some best practices for working with the call in your UI to achieve the best results. + +This document walks through: + +1. Preparing the SDK +2. Calling `getUnifiedBalances` +3. Understanding the response shape +4. Rendering balances in a production-ready UI with resilient UX patterns + +--- + +## 1. Prerequisites + +- Install the core SDK and peers: `npm install @avail-project/nexus-core viem` +- Make sure you have a connected wallet provider (e.g., `window.ethereum`) +- Initialize the SDK once during app bootstrap, then reuse the instance everywhere + +```ts +import { NexusSDK } from '@avail-project/nexus-core'; + +const sdk = new NexusSDK({ network: 'mainnet' }); +await sdk.initialize(window.ethereum); +``` + +> **Tip:** Run `initialize` after the wallet is connected. This lets Nexus read the current account and chain before any balance calls go out. + +--- + +## 2. Fetching unified balances + +```ts +const balances = await sdk.getUnifiedBalances(); +``` + +Calling `getUnifiedBalances` automatically: + +- Fetches balances for every supported chain +- Normalizes token decimals +- Calculates fiat equivalents (USD) for each entry +- Groups the raw sources in a `breakdown` array so UIs can explain where funds sit + +Because `getUnifiedBalances` makes multiple RPC/intent requests under the hood, prefer memoized calls (React Query, SWR, Zustand store) instead of calling it inside render loops. + +--- + +## 3. Response shape + +Each entry in the returned array is a `UserAsset`: + +```ts +export type UserAsset = { + symbol: string; // e.g., 'USDC' + decimals: number; // 6 for USDC, 18 for ETH, etc. + balance: string; // human-readable balance (already decimal adjusted) + balanceInFiat: number; // USD value of the total balance + icon?: string; // CDN URL for token icon (optional) + abstracted?: boolean; // True if the asset is available via chain abstraction + breakdown: Array<{ + balance: string; // chain-specific portion of the balance + balanceInFiat: number; // USD value for that portion + chain: { + id: number; // EVM chain ID + name: string; // Human label, e.g., 'Base' + logo: string; // Logo URL for the chain + }; + decimals: number; + contractAddress: `0x${string}`; + isNative?: boolean; // Native ETH/MATIC/etc. + universe: 'evm' | 'fuel' | 'cosmos'; + }>; +}; +``` + +### Common UI operations + +- Convert `balance` to `number` or `bigint` for calculations via `parseFloat` / `parseUnits` +- Aggregate totals per chain by summing `breakdown` +- Flag abstracted balances (e.g., "Available through Chain Abstraction") + +--- + +## 4. UI integration best practices + +### 4.1 React hook using TanStack Query (recommended) + +```tsx +import { useQuery } from '@tanstack/react-query'; +import { sdk } from './nexus-sdk-instance'; + +export function useUnifiedBalances() { + return useQuery({ + queryKey: ['nexus', 'unified-balances'], + queryFn: () => sdk.getUnifiedBalances(), + staleTime: 30_000, // cache for 30s to avoid spam + refetchInterval: 60_000, // background refresh + retry: 2, // auto-retry transient RPC issues + }); +} +``` + +Why TanStack Query? + +- Deduplicates concurrent requests when multiple components need balances +- Survives re-renders and route changes +- Gives you `isLoading`, `isError`, and `refetch` helpers for UI states + +### 4.2 Rendering component with skeleton, error, empty states + +```tsx +import { formatUSD } from './currency'; +import { useUnifiedBalances } from './useUnifiedBalances'; + +export function PortfolioPanel() { + const { data, isLoading, isError, refetch } = useUnifiedBalances(); + + if (isLoading) return ; + if (isError) + return ( + + ); + + if (!data?.length) return ; + + return ( +
+ {data.map((asset) => ( +
+
+ +
+ {asset.symbol} + {asset.balance} +
+
+
{formatUSD(asset.balanceInFiat)}
+ +
+ ))} +
+ ); +} +``` + +### 4.3 Showing the chain breakdown (modal or drawer) + +```tsx +function openBreakdown(asset: UserAsset) { + showModal({ + title: `${asset.symbol} sources`, + body: ( +
    + {asset.breakdown.map((entry) => ( +
  • + + {entry.balance} + {formatUSD(entry.balanceInFiat)} + {entry.isNative && Native} + {asset.abstracted && Abstracted} +
  • + ))} +
+ ), + }); +} +``` + +### 4.4 Additional UX recommendations + +- **Keep totals consistent**: Derive UI totals from `balanceInFiat` rather than recalculating on the client, to avoid rounding drift with large decimal tokens. +- **Sort deterministically**: Sort by `balanceInFiat` (desc) or alphabetically so that rows do not jump between renders. +- **Respect chain context**: When a user switches wallet chains, call `sdk.initialize` again or trigger a refetch so balances stay accurate. +- **Highlight actionable entries**: Show "Bridge" or "Send" buttons inline when `asset.balanceInFiat` exceeds a threshold. +- **Avoid blocking UI**: Never gate the page behind the balance call; render skeletons or cached data immediately and refresh in the background. +- **Accessibility**: Use semantic lists/tables and ensure fiat + token amounts have `aria-label`s (e.g., "15.23 USDC on Base"). + +--- + +## 5. Testing strategies + +- **Unit test parsing**: Mock `sdk.getUnifiedBalances` to return fixture data and ensure your selectors (totals, breakdown) behave as expected. +- **Integration test flows**: In Playwright/Cypress, stub the SDK call to simulate loading, error, and success states. +- **Performance checks**: Measure render time with 20+ assets to ensure the UI remains responsive; memoize derived data if necessary. + +With these patterns, `getUnifiedBalances` becomes a single source of truth for every balance-aware widget in your application, from asset pickers to transaction reviews. From 3225e95903b1809f6ac64c0a040ab8be82cce3ed Mon Sep 17 00:00:00 2001 From: Jakub Cech <3016965+jakubcech@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:59:42 +0100 Subject: [PATCH 8/8] bridgeandexecute larger tutorial --- .../bridge-and-execute-tutorial.md | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/bridge-and-execute-tutorial.md diff --git a/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/bridge-and-execute-tutorial.md b/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/bridge-and-execute-tutorial.md new file mode 100644 index 000000000..e807483ba --- /dev/null +++ b/app/nexus/avail-nexus-sdk/nexus-core/bridge-and-execute/bridge-and-execute-tutorial.md @@ -0,0 +1,210 @@ +# End-to-End `bridgeAndExecute` Tutorial + +A guided walkthrough for full-stack developers who want to move tokens **and** call a contract in one flow using `bridgeAndExecute`. If you already read the `getUnifiedBalances` tutorial, this picks up where it left off and focuses on the full bridge + execution journey with clear explanations, configuration tips, and response-handling best practices. + +## What you will build + +A minimal Node/TypeScript script that: + +1. Initializes the Nexus SDK with your wallet provider +2. Simulates a bridge + execute flow to preview costs and approvals +3. Bridges USDC from Base to Ethereum **and** deposits it into a Yearn USDC vault +4. Streams progress updates (bridge + execution stages) +5. Waits for receipts and confirmations following production-ready practices + +> Feel free to swap the contract, ABI, or destination chain—this tutorial highlights the pieces you need to change later on. + +## Prerequisites + +- Node 18+ and pnpm/npm/yarn +- An EIP-1193 provider (e.g., injected wallet like MetaMask, or a viem `walletClient` wrapped as a provider) +- USDC on Base for the example flow (or adjust `token`/`sourceChains` as needed) +- Basic TypeScript familiarity (no web3 expertise required) + +## Install dependencies + +```bash +npm install @avail-project/nexus-core viem dotenv +``` + +- `@avail-project/nexus-core` – headless SDK +- `viem` – ergonomic provider utilities (works great with Nexus) +- `dotenv` – keep keys out of source control + +## Configure an EIP-1193 provider + +Nexus only needs a standard EIP-1193 provider. The snippet below shows how to adapt a viem wallet client into that shape for Node scripts. In a browser app you can simply pass `window.ethereum`. + +Note that the below example shows a simplified way of handling the private key (and secrets in general). For production, a better validation flow and secret handling is adviseable. + +```ts +// src/provider.ts +import { createWalletClient, http } from 'viem'; +import { base, mainnet } from 'viem/chains'; +import { privateKeyToAccount } from 'viem/accounts'; + +const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); + +// Use Base as the default chain; Nexus will route bridge legs for you +const walletClient = createWalletClient({ + account, + chain: base, + transport: http(process.env.RPC_URL || 'https://mainnet.base.org'), +}); + +// viem exposes an EIP-1193-compatible provider on the client +export const provider = walletClient.transport as unknown as any; // satisfies EthereumProvider +``` + +> **Security tip:** keep `PRIVATE_KEY` and `RPC_URL` in `.env` and never commit them. + +## Understand the `bridgeAndExecute` inputs + +`bridgeAndExecute` combines a bridge with a contract call on the destination chain. Key properties: + +- **token** – symbol from `SUPPORTED_TOKENS` (e.g., `"USDC"`). +- **amount** – string amount in token units (e.g., `"100"` for 100 USDC). The SDK normalizes to wei internally. +- **toChainId** – numeric chain ID where the contract call runs (Ethereum in the example). +- **sourceChains** – optional array restricting which chains to pull liquidity from. Use it to avoid slow/expensive sources. +- **execute** – describes the destination contract call: + - `contractAddress`, `contractAbi`, `functionName` – standard ABI call details. + - `buildFunctionParams(token, amount, chainId, userAddress)` – returns `{ functionParams, value? }` with properly formatted arguments. Use this to convert amounts to wei and insert token addresses for the target chain. + - `tokenApproval` – optional approval to request before execution (token + amount in wei). Omit if not needed. + - `value` – optional native value to forward (e.g., when calling a payable function). +- **waitForReceipt** – when `true`, SDK waits for the destination execution receipt. +- **requiredConfirmations** – number of block confirmations to wait for after the receipt (useful for production safety). + +### Best practices before calling + +1. **Always simulate** with `simulateBridgeAndExecute` to get gas/fee estimates, approval requirements, and the planned route. +2. **Validate balances** with `getUnifiedBalances` if you want to short-circuit when the user already has funds on the target chain. +3. **Cap approvals** – set `tokenApproval.amount` to the exact amount you need instead of `MAX_UINT`. +4. **Timeouts and polling** – keep `waitForReceipt` + `requiredConfirmations` for production, but surface a loading UI that respects the bridge duration. +5. **Log hashes** – persist `bridgeTransactionHash`, `approvalTransactionHash`, and `executeTransactionHash` so you can rehydrate UI state after a refresh. + +## Full example + +```ts +// src/bridge-and-execute.ts +import 'dotenv/config'; +import { parseUnits } from 'viem'; +import { + NexusSDK, + TOKEN_METADATA, + type SUPPORTED_TOKENS, + type SUPPORTED_CHAINS_IDS, + NEXUS_EVENTS, +} from '@avail-project/nexus-core'; +import { provider } from './provider'; + +async function main() { + const sdk = new NexusSDK({ network: 'mainnet' }); + await sdk.initialize(provider); + + // Optional: listen for granular bridge + execute step updates + sdk.nexusEvents.on(NEXUS_EVENTS.BRIDGE_EXECUTE_COMPLETED_STEPS, (step) => { + console.log(`[step] ${step.type}:`, step.data); + }); + + const params = { + token: 'USDC', + amount: '100', // in token units + toChainId: 1, // Ethereum + sourceChains: [8453], // Prefer pulling USDC from Base + execute: { + contractAddress: '0xa354F35829Ae975e850e23e9615b11Da1B3dC4DE', // Yearn USDC vault + contractAbi: [ + { + inputs: [ + { internalType: 'uint256', name: 'assets', type: 'uint256' }, + { internalType: 'address', name: 'receiver', type: 'address' }, + ], + name: 'deposit', + outputs: [{ internalType: 'uint256', name: 'shares', type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function', + }, + ], + functionName: 'deposit', + buildFunctionParams: + (token: SUPPORTED_TOKENS, amount: string, chainId: SUPPORTED_CHAINS_IDS, user: `0x${string}`) => { + const decimals = TOKEN_METADATA[token].decimals; + const amountWei = parseUnits(amount, decimals); + return { + functionParams: [amountWei, user], + }; + }, + tokenApproval: { + token: 'USDC', + amount: parseUnits('100', TOKEN_METADATA.USDC.decimals).toString(), + }, + }, + waitForReceipt: true, + requiredConfirmations: 3, + } as const; + + // 1) Simulate to surface costs, approvals, and routing + const simulation = await sdk.simulateBridgeAndExecute(params); + if (!simulation.success) { + console.error('Simulation failed:', simulation.error); + return; + } + + console.log('Route overview:', simulation.steps); + console.log('Estimated total cost:', simulation.totalEstimatedCost); + if (simulation.metadata?.approvalRequired) { + console.log('Approval required:', simulation.metadata.approvalRequired); + } + + // 2) Run the real transaction + const result = await sdk.bridgeAndExecute(params); + + if (!result.success) { + console.error('Bridge + execute failed:', result.error); + return; + } + + // 3) Production-friendly response handling + console.log('Bridge tx hash:', result.bridgeTransactionHash); + console.log('Execute tx hash:', result.executeTransactionHash); + console.log('Explorer links:', { + bridge: result.bridgeExplorerUrl, + execute: result.executeExplorerUrl, + }); + + if (result.bridgeSkipped) { + console.log('Bridge skipped: funds already available on destination chain.'); + } + + // Clean up your listener when done + sdk.nexusEvents.removeAllListeners(NEXUS_EVENTS.BRIDGE_EXECUTE_COMPLETED_STEPS); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); +``` + +### Notes on the example +Some of the practices used in the example for better usage in production user interface: + +- **Simulation first** catches allowance issues and previews the exact steps (including chain abstraction routes). +- **`waitForReceipt` + confirmations** ensure the contract call is finalized, which is critical for downstream business logic. +- **Step events** let you stream progress to your UI (or logs) during longer bridge legs. +- **Explicit approvals** scope token allowances to the amount you actually need. +- **Route constraints** via `sourceChains` help you avoid illiquid or slow chains. + +## Handling responses safely + +- **Persist hashes**: store the transaction hashes and explorer URLs so you can rebuild UI state after refresh or process restarts. +- **Map stages to UX**: use `simulation.steps` and runtime step events to display stages like "bridging", "waiting for settlement", "executing", and "confirming". +- **Timeout strategy**: if you set client-side timers, keep them generous—bridges may take minutes. Prefer showing the live step stream over hard timeouts. +- **Error surfaces**: the SDK returns structured errors (`result.error`) and also emits a final `operation.failed` step; display both in UI for clarity. +- **Reconciliation**: after success, call `getUnifiedBalances` again to refresh balances on the destination chain and verify the vault receipt/shares. + +## Next steps + +- Swap in your own ABI + params builder to target different protocols (staking, lending, DEX interactions, etc.). +- Gate flows with `simulateBridgeAndExecute` + `getUnifiedBalances` checks to avoid redundant bridges when funds already exist on the destination chain. +- Combine with the widgets package if you want pre-built UI components once the headless flow is working. \ No newline at end of file