Documentation>AVAX Quick Start

Avalanche Quick Start (ERC-4337)

Add gasless UserOperation flows on Avalanche through SmoothSend gateway.

The 1-Line Overhaul
Already using wagmi? Take your entire dApp gasless in two simple steps.

1Global Configuration

App.tsx
TypeScript (React)
import { SmoothSendAvaxProvider } from '@smoothsend/sdk/avax';
 
<SmoothSendAvaxProvider
apiKey="pk_nogas_..."
network="testnet" // Optional: defaults to testnet
mode="developer-sponsored" // Optional: defaults to developer-sponsored
>
<YourApp />
</SmoothSendAvaxProvider>

Add this provider in your app root (for example main.tsx, WalletProvider.tsx, or your top-level layout/providers file).

Default mode is developer-sponsored, so users pay 0 native gas unless you switch to user-pays-erc20.

2The Hook Overhaul

Standard Wagmi

TypeScript (React)
import { useWriteContract } from 'wagmi';

SmoothSend Gasless

TypeScript (React)
import { useSmoothSendWrite as useWriteContract } from '@smoothsend/sdk/avax';

* Every writeContract() call in your file is now automatically sponsored by SmoothSend.

In wagmi apps, this is the only component-level change needed: import { useSmoothSendWrite as useWriteContract } from '@smoothsend/sdk/avax'.

Modes
Both modes use the same bundler + paymaster path; only billing behavior changes.
ModeGas payerUser pays
developer-sponsoredSmoothSend paymasterNothing
user-pays-erc20SmoothSend paymasterERC20 (for launch: USDC)
Owner/admin caveat: sponsored writes execute from the smart account (msg.sender = smart account), not the EOA. If your contract uses owner checks tied to an EOA, keep those admin actions on direct wagmi writes (EOA path) or set ownership/admin roles to the smart account.
Install
Use the AVAX-specific install guide and subpath imports.
TypeScript
import { createSmoothSendAvaxClient } from '@smoothsend/sdk/avax';

Dependency setup lives in AVAX Installation.

Backend (server / agent) — simplest path
High-level client that handles estimation, paymaster sign, and send.
submit-call.ts
TypeScript
1import { createSmoothSendAvaxClient } from '@smoothsend/sdk/avax';
2 
3const avax = createSmoothSendAvaxClient({
4 apiKey: process.env.SMOOTHSEND_API_KEY!, // sk_nogas_* on backend
5 network: 'testnet', // fuji
6 publicClient, // viem public client
7 walletClient, // viem wallet client
8});
9 
10const result = await avax.submitCall({
11 call: {
12 to: '0xYourTargetContract',
13 data: '0xYourEncodedCalldata',
14 value: 0n,
15 },
16 mode: 'developer-sponsored', // or 'user-pays-erc20'
17 // optional: override smart account if not provided in constructor
18 // smartAccountAddress: '0xYourSmartAccount',
19 // optional: override factory or provide owner for counterfactuals
20 // accountFactory: '0xYourFactoryAddress',
21 waitForReceipt: false,
22});
23 
24console.log(result.userOpHash);
Fee preflight (user-pays-erc20)
Quote before submit to show what token fee will be charged.
estimate-fee.ts
TypeScript
1const quote = await avax.estimateUserPaysFee({
2 calls: [{
3 to: '0xYourTargetContract',
4 data: '0xYourEncodedCalldata',
5 value: 0n,
6 }],
7 paymaster: { token: '0xUSDC' },
8 // optional: specify which smart account will pay the fee
9 // smartAccountAddress: '0xYourSmartAccount',
10});
11 
12console.log(quote.feePreview); // token + amount + usd + policy fields
Hash semantics
UserOperation hash and on-chain transaction hash are different values.

userOpHash: returned immediately by eth_sendUserOperation.

transactionHash: available after inclusion from eth_getUserOperationReceipt. Use this for explorer links.

React (wagmi) — The Easy Path ✨
Drop-in replacement for wagmi's useWriteContract. Handles encoding and sponsoring in one line.
GaslessButton.tsx
TypeScript (React)
1import { useSmoothSendWrite } from '@smoothsend/sdk/avax';
2 
3// Your existing ABI and Contract Address
4const YOUR_ABI = [...] as const;
5const YOUR_CONTRACT_ADDRESS = '0x...';
6 
7function GaslessButton() {
8 // 1. Swap wagmi's useWriteContract for this!
9 // data here is a UserOperation hash (not an EVM tx hash)
10 const { writeContract, data: userOpHash, isPending: isWriting } = useSmoothSendWrite();
11 const isConfirming = false; // optional: track via eth_getUserOperationReceipt
12 
13 return (
14 <button
15 disabled={isWriting || isConfirming}
16 onClick={() =>
17 writeContract({
18 address: YOUR_CONTRACT_ADDRESS,
19 abi: YOUR_ABI,
20 functionName: 'mint', // Your function name
21 args: [1], // Your arguments
22 mode: 'developer-sponsored',
23 })
24 }
25 >
26 {isWriting ? 'Sponsoring...' : isConfirming ? 'Confirming...' : 'Submit Gasless'}
27 </button>
28 );
29}

Best Practices: The 3 UI States

Idle: Button is active. User is ready to start.

Writing (Sponsoring):The SDK is generating the UserOperation and requesting sponsorship. Disable the button here to prevent duplicate submissions.

Confirming:The UserOperation is sent. Poll eth_getUserOperationReceipt (or SDK receipt helpers) and keep the button disabled until confirmation.

Pro Tip: If you need to target a different network for a specific action, you can pass network directly to the hook or the writeContract call.

React (wagmi) — Single call
Provider + hook API for a sponsored contract call from a frontend wallet.
App.tsx
TypeScript (React)
1import { SmoothSendAvaxProvider, useSmoothSendAvax } from '@smoothsend/sdk/avax';
2import { usePublicClient, useWalletClient } from 'wagmi';
3 
4function TransferButton({ to, data }: { to: `0x${string}`; data: `0x${string}` }) {
5 const publicClient = usePublicClient();
6 const { data: walletClient } = useWalletClient();
7 const { submitCall } = useSmoothSendAvax({
8 publicClient,
9 walletClient: walletClient ?? undefined,
10 });
11 
12 return (
13 <button
14 onClick={() =>
15 submitCall({
16 to,
17 data,
18 mode: 'developer-sponsored',
19 })
20 }
21 >
22 Submit gasless call
23 </button>
24 );
25}
26 
27export function Root({ children }: { children: React.ReactNode }) {
28 return (
29 <SmoothSendAvaxProvider
30 apiKey={process.env.NEXT_PUBLIC_SMOOTHSEND_API_KEY!} // pk_nogas_* in browser
31 network="testnet"
32 smartAccountAddress={'0xYourSmartAccount' as `0x${string}`}
33 >
34 {children}
35 </SmoothSendAvaxProvider>
36 );
37}
Privy signer adapter (one-line style)
Use Privy for auth/signing and SmoothSend for AVAX sponsorship.
RootProviders.tsx
TypeScript (React)
import { PrivyProvider } from '@privy-io/react-auth';
import { avalancheFuji } from 'viem/chains';
import { SmoothSendAvaxProvider } from '@smoothsend/sdk/avax';
 
<PrivyProvider
appId={process.env.NEXT_PUBLIC_PRIVY_APP_ID!}
config={{
embeddedWallets: { ethereum: { createOnLogin: 'users-without-wallets' } },
defaultChain: avalancheFuji,
supportedChains: [avalancheFuji],
}}
>
<SmoothSendAvaxProvider
apiKey={process.env.NEXT_PUBLIC_SMOOTHSEND_API_KEY!}
network="testnet"
>
<YourApp />
</SmoothSendAvaxProvider>
</PrivyProvider>

Full minimal integration (with one hook call) lives in AVAX Privy Integration.

React (wagmi) — Atomic multi-call batch
Use submitSponsoredUserOp with a calls array to execute multiple operations in a single UserOperation — one signature, one on-chain transaction. In user-pays-erc20 mode, batch the ERC20 approval and the transfer together so no separate approval step is required.
BatchTransfer.tsx
TypeScript (React)
1import { useSmoothSendAvax, fetchAvaxAaPublicDefaults } from '@smoothsend/sdk/avax';
2import { encodeFunctionData, parseUnits } from 'viem';
3import { usePublicClient, useWalletClient } from 'wagmi';
4 
5const ERC20_ABI = [
6 {
7 name: 'approve', type: 'function', stateMutability: 'nonpayable',
8 inputs: [{ name: 'spender', type: 'address' }, { name: 'amount', type: 'uint256' }],
9 outputs: [{ type: 'bool' }],
10 },
11 {
12 name: 'transfer', type: 'function', stateMutability: 'nonpayable',
13 inputs: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }],
14 outputs: [{ type: 'bool' }],
15 },
16] as const;
17 
18const USDC = '0x5425890298aed601595a70AB815c96711a31Bc65'; // Fuji testnet USDC
19 
20function USDCTransferButton({ recipient }: { recipient: `0x${string}` }) {
21 const publicClient = usePublicClient();
22 const { data: walletClient } = useWalletClient();
23 const { submitSponsoredUserOp } = useSmoothSendAvax({
24 publicClient,
25 walletClient: walletClient ?? undefined,
26 });
27 
28 const handleTransfer = async () => {
29 // 1. Fetch paymaster address dynamically from SmoothSend
30 const { paymasterFuji } = await fetchAvaxAaPublicDefaults();
31 if (!paymasterFuji) throw new Error('Paymaster not found');
32 
33 const amount = parseUnits('1', 6); // 1 USDC (6 decimals)
34 
35 // [approve paymaster, transfer to recipient] — one signature, atomic on-chain
36 await submitSponsoredUserOp({
37 calls: [
38 {
39 to: USDC,
40 data: encodeFunctionData({
41 abi: ERC20_ABI,
42 functionName: 'approve',
43 args: [paymasterFuji, parseUnits('1000', 6)],
44 }),
45 value: 0n,
46 },
47 {
48 to: USDC,
49 data: encodeFunctionData({
50 abi: ERC20_ABI,
51 functionName: 'transfer',
52 args: [recipient, amount],
53 }),
54 value: 0n,
55 },
56 ],
57 sponsorshipMode: 'user-pays-erc20',
58 paymaster: { token: USDC },
59 });
60 };
61 
62 return <button onClick={handleTransfer}>Send 1 USDC (gasless)</button>;
63}

The SDK automatically encodes the batch via the smart account's executeBatch(dest[], value[], func[]) function. By using fetchAvaxAaPublicDefaults(), you ensure your app always uses the correct Paymaster address for approvals without hardcoding it.