Відстеження транзакцій у React за допомогою Pulsar
Цей посібник крок за кроком демонструє, як налаштувати автономне відстеження життєвого циклу транзакцій у React за допомогою ядра Pulsar. Ви отримаєте стійкість до перезавантаження вкладки, перемикання між сторінками та автоматичне виявлення заміни транзакцій (Speed-Up).
Перед початком
- React 19.2 або новіше (з підтримкою Next.js App Router).
- Підключення гаманця через Satellite Connect або Nova Connect.
- Конфігурація мережі: wagmiConfig для EVM або solanaRPCUrls для Solana.
Крок 1: Встановлення пакетів
pnpm add @tuwaio/pulsar-core @tuwaio/pulsar-evm @tuwaio/pulsar-react \ @tuwaio/orbit-core @tuwaio/orbit-evm @wagmi/core viem zustand immer dayjsКрок 2: Опис контракту чи програми
Вам потрібні адреса та ABI контракту. У цьому посібнику використовується контракт Counter у мережі Sepolia:
// A Counter contract deployed on Sepolia.export const COUNTER_ADDRESS = '0xAe7f46914De82028eCB7E2bF97Feb3D3dDCc2BAB'; export const CounterAbi = [ { inputs: [], name: 'decrement', outputs: [], stateMutability: 'nonpayable', type: 'function', }, { inputs: [], name: 'getCurrentNumber', outputs: [ { internalType: 'uint256', name: '', type: 'uint256', }, ], stateMutability: 'view', type: 'function', }, { inputs: [], name: 'increment', outputs: [], stateMutability: 'nonpayable', type: 'function', },] as const;Крок 3: Створення дії
Дія — це функція, яка запитує підпис гаманця і повертає ключ транзакції. Pulsar викликає її всередині executeTxAction:
import { type Config, writeContract } from '@wagmi/core';import { sepolia } from 'viem/chains'; import { COUNTER_ADDRESS, CounterAbi } from '@/abis/CounterAbi'; // Returns the transaction hash, which Pulsar uses as the txKey.export function increment({ wagmiConfig }: { wagmiConfig: Config }) { return writeContract(wagmiConfig, { abi: CounterAbi, address: COUNTER_ADDRESS, functionName: 'increment', chainId: sepolia.id, });}Опціонально: виклик через смарт-акаунт ERC-4337. За допомогою @tuwaio/orbit-evm можна відправити дію як UserOperation:
import { createPimlicoSmartAccountClient } from '@tuwaio/orbit-evm';import type { Config } from '@wagmi/core';import { encodeFunctionData } from 'viem';import { sepolia } from 'viem/chains'; import { COUNTER_ADDRESS, CounterAbi } from '@/abis/CounterAbi'; // Returns the userOpHash, which Pulsar uses as the txKey.export async function incrementWithSmartAccount({ wagmiConfig, pimlicoApiKey,}: { wagmiConfig: Config; pimlicoApiKey?: string;}) { // A Solady smart account owned by the connected wallet, sent through the Pimlico bundler. // With an API key, gas is sponsored by the Pimlico paymaster by default. const { account, bundlerClient } = await createPimlicoSmartAccountClient({ chain: sepolia, wagmiConfig, apiKey: pimlicoApiKey, }); return bundlerClient.sendUserOperation({ account, calls: [ { to: COUNTER_ADDRESS, data: encodeFunctionData({ abi: CounterAbi, functionName: 'increment' }), }, ], });}Крок 4: Створення сховища транзакцій
'use client'; import { createBoundedUseStore, createPulsarStore, type EvmTransaction } from '@tuwaio/pulsar-core';import { pulsarEvmAdapter } from '@tuwaio/pulsar-evm'; import { appChains, wagmiConfig } from '@/configs/wagmiConfig'; // Typed transactions of your apptype IncrementTx = EvmTransaction & { type: 'increment'; payload: { value: number; };}; export type TransactionUnion = IncrementTx; export const pulsarStore = createPulsarStore<TransactionUnion>({ name: 'transactions-tracking-storage', // localStorage key adapter: pulsarEvmAdapter(wagmiConfig, appChains), beforeTxProcess: () => { if (!navigator.onLine) throw new Error('You are offline.'); },}); export const usePulsarStore = createBoundedUseStore(pulsarStore);Крок 5: Відновлення відстеження після перезавантаження
Компонент PulsarInitializer запускає фонові трекери для всіх нефіналізованих транзакцій після релоаду:
'use client'; import { useInitializeTransactionsPool } from '@tuwaio/pulsar-react'; import { usePulsarStore } from '@/hooks/txTrackingHooks'; export function PulsarInitializer() { const initializeTransactionsPool = usePulsarStore((state) => state.initializeTransactionsPool); // Restarts the trackers of transactions that were pending before a page reload. useInitializeTransactionsPool({ initializeTransactionsPool }); return null;}Крок 6: Відправка та відстеження транзакції
Виклик executeTxAction валідує метадані, запускає дію та бере транзакцію під керування стейт-машини:
'use client'; import { ConnectButton } from '@tuwaio/nova-connect/components';import { OrbitAdapter } from '@tuwaio/orbit-core';import { TransactionTracker } from '@tuwaio/pulsar-core';import { sepolia } from 'viem/chains'; import { wagmiConfig } from '@/configs/wagmiConfig';import { usePulsarStore } from '@/hooks/txTrackingHooks';import { increment } from '@/transactions/actions/increment';import { incrementWithSmartAccount } from '@/transactions/actions/incrementWithSmartAccount'; const pimlicoApiKey = process.env.NEXT_PUBLIC_PIMLICO_API_KEY; export const Increment = () => { const executeTxAction = usePulsarStore((state) => state.executeTxAction); const pendingCount = usePulsarStore( (state) => Object.values(state.transactionsPool).filter((tx) => tx.pending).length, ); const handleIncrement = async () => { try { await executeTxAction({ actionFunction: () => increment({ wagmiConfig }), params: { type: 'increment', adapter: OrbitAdapter.EVM, desiredChainID: sepolia.id, // the wallet is asked to switch to Sepolia if needed title: ['Incrementing', 'Incremented', 'Increment failed', 'Increment replaced'], description: 'Increment the counter by 1.', payload: { value: 1 }, withTrackedModal: true, // opens the tracking modal of Nova Transactions }, onSuccess: (tx) => console.log('Incremented in', tx.hash), }); } catch (error) { // Rejected signature, declined network switch, failed preflight... Also saved in `initialTx.error`. console.error(error); } }; const handleIncrementWithSmartAccount = async () => { try { await executeTxAction({ actionFunction: () => incrementWithSmartAccount({ wagmiConfig, pimlicoApiKey }), params: { type: 'increment', adapter: OrbitAdapter.EVM, desiredChainID: sepolia.id, title: 'Increment with a smart account', payload: { value: 1 }, tracker: TransactionTracker.ERC4337, // required: UserOperations are not detected automatically pimlicoApiKey, // saved with the transaction to resume tracking after a reload }, }); } catch (error) { console.error(error); } }; return ( <div className="flex flex-col items-start gap-4"> <ConnectButton /> <button type="button" onClick={handleIncrement}> Increment </button> <button type="button" onClick={handleIncrementWithSmartAccount}> Increment with a smart account </button> {pendingCount > 0 && <p>{pendingCount} pending transaction(s)</p>} </div> );};Крок 7: Реакція на завершення транзакцій
Хук useTxFinished гарантує виконання оновлення UI навіть якщо транзакція завершилася під час релоаду:
'use client'; import { TransactionStatus } from '@tuwaio/pulsar-core';import { useTxFinished } from '@tuwaio/pulsar-react'; import { usePulsarStore } from '@/hooks/txTrackingHooks'; export function CounterValue({ value, refetch }: { value: number | undefined; refetch: () => void }) { const subscribeTxFinished = usePulsarStore((state) => state.subscribeTxFinished); useTxFinished({ subscribeTxFinished, onFinished: (tx) => { if (tx.type === 'increment' && tx.status === TransactionStatus.Success) refetch(); }, }); return <p>Counter: {value ?? '…'}</p>;}📚 Наступні кроки
- Full-Stack React застосунок: інтеграція з SIWX та провайдерами Nova.
- Синхронізація з Quasar: міжпристроєва персистентність.