Usage
const { syncTransfers, syncTransfersAsync, data, isLoading, isError, error, isSuccess, reset } =
useSyncOnChainTransfers();
Parameters
syncTransfers / syncTransfersAsync accept an object with:
| Parameter | Type | Required | Description |
|---|
walletAddress | string | Yes | Wallet public key to sync transfers for |
bearerToken | string | Yes | Bearer token for authentication |
Return Value
| Property | Type | Description |
|---|
syncTransfers | (input) => void | Trigger sync (fire-and-forget) |
syncTransfersAsync | (input) => Promise<SyncOnChainTransfersResponse> | Promise-based sync |
data | SyncOnChainTransfersResponse | undefined | Sync result |
isLoading | boolean | Whether sync is in progress |
isError | boolean | Whether an error occurred |
error | Error | null | Error from last sync attempt |
isSuccess | boolean | Whether last sync succeeded |
reset | () => void | Reset mutation state |
SyncOnChainTransfersResponse
| Field | Type | Description |
|---|
synced | number | New transactions discovered and saved to DB |
total | number | Total transfers found on-chain for this wallet |
Example Implementation
import { useState } from "react";
import { useAuth } from "@clerk/nextjs";
import {
useSyncOnChainTransfers,
useGetTransactionList,
} from "@chipi-stack/nextjs";
export function TransactionHistory({ wallet }: { wallet: { publicKey: string } }) {
const { getToken } = useAuth();
const { syncTransfersAsync, isLoading: isSyncing } = useSyncOnChainTransfers();
const { data: txList, refetch } = useGetTransactionList({
query: { walletAddress: wallet.publicKey },
getBearerToken: getToken,
});
const handleSync = async () => {
const token = await getToken();
if (!token) return;
// Discover external transfers (e.g., USDC received from Argent X)
const result = await syncTransfersAsync({
walletAddress: wallet.publicKey,
bearerToken: token,
});
console.log(`Found ${result.synced} new transfers`);
// Transaction list cache is automatically invalidated — refetch happens
};
return (
<div>
<button onClick={handleSync} disabled={isSyncing}>
{isSyncing ? "Syncing..." : "Sync On-Chain Transfers"}
</button>
{txList?.data.map((tx) => (
<div key={tx.id}>
<span>{tx.subType === "receive" ? "Received" : "Sent"}</span>
<span>{tx.amount} {tx.token}</span>
<span>{tx.transactionHash.slice(0, 12)}...</span>
</div>
))}
</div>
);
}
How It Works
- Calls
POST /transactions/sync-on-chain on the backend
- Backend queries Voyager API for all token transfers for the wallet
- Compares with existing DB records by transaction hash
- Saves new transfers to the
Transaction table (read-through cache)
- Returns
{ synced, total }
- Invalidates
useGetTransactionList cache so new transfers appear immediately
After sync, useGetTransactionList returns both SDK-initiated transfers AND external receives.
When to Use
- On wallet load: Sync once when user opens their wallet to discover any external receives
- Pull-to-refresh: Let users manually trigger sync
- After receiving funds: If user expects incoming funds from an external wallet
Synced transfers are cached in the database. Subsequent useGetTransactionList calls return them
without re-querying Voyager.
Limitations
- Only discovers token transfers (ERC-20). Contract interactions without transfers are not synced.
- Voyager API rate limits apply. Avoid calling sync on every render.
- First sync for a wallet with many transfers may take a few seconds (paginated, max 500 transfers per sync).
Related