Examples
Real-world code examples for common use cases
Wallet Adapter — Provider Setup
One-time setup: wrap your app with the gasless submitter. After this, every
signAndSubmitTransaction call is automatically routed through SmoothSend.providers.tsx
TypeScript
1import { SmoothSendTransactionSubmitter } from '@smoothsend/sdk';2import { AptosWalletAdapterProvider } from '@aptos-labs/wallet-adapter-react';3import { Network } from '@aptos-labs/ts-sdk';4 5const submitter = new SmoothSendTransactionSubmitter({6 apiKey: process.env.NEXT_PUBLIC_SMOOTHSEND_API_KEY!,7 network: 'mainnet', // or 'testnet'8});9 10export function Providers({ children }: { children: React.ReactNode }) {11 return (12 <AptosWalletAdapterProvider13 dappConfig={{14 network: Network.MAINNET,15 transactionSubmitter: submitter,16 }}17 >18 {children}19 </AptosWalletAdapterProvider>20 );21}Wallet Adapter — Sending Any Transaction
After the provider is set up, your existing wallet code works unchanged. The user pays no gas.
TransferButton.tsx
TypeScript
1import { useWallet } from '@aptos-labs/wallet-adapter-react';2 3function TransferAPT() {4 const { signAndSubmitTransaction, account } = useWallet();5 6 const handleTransfer = async () => {7 // Works for APT transfers, contract calls, NFT mints — anything8 const result = await signAndSubmitTransaction({9 data: {10 function: '0x1::coin::transfer',11 typeArguments: ['0x1::aptos_coin::AptosCoin'],12 functionArguments: [13 '0xRecipientAddress',14 100_000_000, // 1 APT (8 decimals)15 ],16 },17 });18 console.log('Tx hash:', result.hash);19 };20 21 return <button onClick={handleTransfer}>Send 1 APT (gasless)</button>;22}useSmoothSend — Per-Function Gasless Routing
Some functions sponsored, some not — the hook routes automatically based on your Sponsorship Rules. No need to put
transactionSubmitter in the wallet provider.TodoList.tsx
TypeScript
1import { useSmoothSend, SmoothSendTransactionSubmitter } from '@smoothsend/sdk';2import { useWallet } from '@aptos-labs/wallet-adapter-react';3 4const MODULE = '0xYourModuleAddress';5const SMOOTHSEND_KEY = process.env.NEXT_PUBLIC_SMOOTHSEND_API_KEY!;6 7// Create once outside the component — avoids re-creating on every render8const submitter = new SmoothSendTransactionSubmitter({9 apiKey: SMOOTHSEND_KEY,10 network: 'mainnet',11});12 13function TodoList({ todos }: { todos: Array<{ id: number; content: string }> }) {14 const { account } = useWallet();15 // Drop-in for useWallet().signAndSubmitTransaction16 const { signAndSubmitTransaction } = useSmoothSend(submitter);17 18 // 'create_todo' is NOT in Sponsorship Rules → user pays gas (~0.001 APT)19 const handleCreate = async (content: string) => {20 const result = await signAndSubmitTransaction({21 data: {22 function: `${MODULE}::todolist::create_todo`,23 functionArguments: [content],24 },25 });26 console.log('Created:', result.hash);27 };28 29 // 'delete_todo' IS in Sponsorship Rules → gasless, user pays 0 APT30 const handleDelete = async (id: number) => {31 const result = await signAndSubmitTransaction({32 data: {33 function: `${MODULE}::todolist::delete_todo`,34 functionArguments: [id],35 },36 });37 console.log('Deleted:', result.hash);38 };39 40 return (41 <div>42 <button onClick={() => handleCreate('New task')}>Create (pays gas)</button>43 {todos.map((t) => (44 <button key={t.id} onClick={() => handleDelete(t.id)}>45 Delete "{t.content}" (gasless)46 </button>47 ))}48 </div>49 );50}Script Composer — USDC Transfer (Fee-in-Token)
Transfer USDC on mainnet — the relayer fee (~$0.01) is deducted from the USDC being sent. No APT or credits needed.
USDCTransfer.tsx
TypeScript
1import { ScriptComposerClient } from '@smoothsend/sdk';2import { Deserializer, SimpleTransaction } from '@aptos-labs/ts-sdk';3import { useWallet } from '@aptos-labs/wallet-adapter-react';4 5const client = new ScriptComposerClient({6 apiKey: process.env.NEXT_PUBLIC_SMOOTHSEND_API_KEY!,7 network: 'mainnet',8});9 10// USDC mainnet asset address on Aptos11const USDC = '0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b';12 13function USDCTransfer() {14 const { account, signTransaction } = useWallet();15 16 const transfer = async (toAddress: string, amountUsdc: number) => {17 // Step 1 — build (fee shown before signing)18 const build = await client.buildTransfer({19 sender: account!.address,20 recipient: toAddress,21 amount: String(amountUsdc * 1_000_000), // USDC has 6 decimals22 assetType: USDC,23 decimals: 6,24 symbol: 'USDC',25 });26 console.log('Fee:', build.feeBreakdown.formatted.fee); // e.g. "0.010000 USDC"27 28 // Step 2 — sign with the connected wallet29 const txBytes = new Uint8Array(build.transactionBytes);30 const tx = SimpleTransaction.deserialize(new Deserializer(txBytes));31 const signed = await signTransaction({ transactionOrPayload: tx });32 33 // Step 3 — submit34 const { txHash } = await client.submitSignedTransaction({35 transactionBytes: Array.from(txBytes),36 authenticatorBytes: Array.from(signed.authenticator.bcsToBytes()),37 });38 console.log('Tx hash:', txHash);39 };40 41 return (42 <button onClick={() => transfer('0xRecipientAddress', 10)}>43 Send 10 USDC (fee-in-token)44 </button>45 );46}TrueGaslessClient — Backend Gasless (Node.js)
For server-side applications that need to execute any Move payload with full gas sponsorship. Your backend signs the transaction; SmoothSend's relayer pays the APT gas. Use a
sk_nogas_* secret key — never expose this in frontend code.gasless-server.ts
TypeScript
1import { TrueGaslessClient, SmoothSendError } from '@smoothsend/sdk';2import { Account, Ed25519PrivateKey } from '@aptos-labs/ts-sdk';3 4// Initialize once — reuse for all requests (don't recreate per request)5const backendWallet = Account.fromPrivateKey({6 privateKey: new Ed25519PrivateKey(process.env.APTOS_PRIVATE_KEY!),7});8 9const client = new TrueGaslessClient({10 apiKey: process.env.SMOOTHSEND_SECRET_KEY!, // sk_nogas_* only11 network: 'mainnet',12});13 14// Execute any Move payload — works for transfers, NFT mints, DeFi, custom contracts15async function mintNFT(recipientAddress: string, tokenId: string) {16 const result = await client.execute({17 senderAccount: backendWallet,18 payload: {19 function: '0x12b...::nft_collection::mint_to',20 functionArguments: [recipientAddress, tokenId],21 },22 });23 24 console.log('NFT minted successfully!');25 console.log('Tx hash:', result.txHash);26 console.log('Gas used:', result.gasUsed); // Paid by SmoothSend relayer27 console.log('VM status:', result.vmStatus);28 return result;29}30 31// Error handling32async function safeExecute() {33 try {34 await mintNFT('0xRecipientAddress', 'token-001');35 } catch (error) {36 if (error instanceof SmoothSendError) {37 switch (error.statusCode) {38 case 401: console.error('Invalid API key — check SMOOTHSEND_SECRET_KEY'); break;39 case 402: console.error('Insufficient credits — top up your dashboard'); break;40 case 429: console.error('Rate limit exceeded — implement retry with backoff'); break;41 default: console.error('Relayer error:', error.message);42 }43 } else {44 console.error('Unexpected error:', error);45 }46 }47}Script Composer — Show Fee Before Transfer
Estimate the relayer fee and display it to users before they sign.
FeePreview.tsx
TypeScript
1import { ScriptComposerClient } from '@smoothsend/sdk';2import { useState } from 'react';3 4async function getUSDCFee(client: ScriptComposerClient, amount: string) {5 const estimate = await client.estimateFee({6 amount,7 assetType: '0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b',8 decimals: 6,9 symbol: 'USDC',10 });11 return estimate.formatted.fee; // e.g. "0.010000 USDC"12}13 14function TransferForm() {15 const [fee, setFee] = useState<string | null>(null);16 17 const handleAmountChange = async (amount: string) => {18 if (!amount) return;19 const feeDisplay = await getUSDCFee(client, String(Number(amount) * 1_000_000));20 setFee(feeDisplay);21 };22 23 return (24 <div>25 <input26 type="number"27 placeholder="Amount USDC"28 onChange={(e) => handleAmountChange(e.target.value)}29 />30 {fee && <p>Relayer fee: {fee}</p>}31 </div>32 );33}Error Handling
Handle SmoothSend-specific errors and show meaningful messages to users.
handleTx.ts
TypeScript
1import { SmoothSendError } from '@smoothsend/sdk';2 3async function handleTransaction(tx: () => Promise<{ hash: string }>) {4 try {5 const result = await tx();6 toast.success(`Submitted: ${result.hash}`);7 } catch (error) {8 if (error instanceof SmoothSendError) {9 const messages: Record<number, string> = {10 401: 'Invalid API key — check your configuration.',11 402: 'Insufficient credits — top up your dashboard.',12 429: 'Rate limit exceeded — please wait a moment.',13 };14 toast.error(messages[error.statusCode] ?? `Error: ${error.message}`);15 } else {16 toast.error('Wallet or network error. Please try again.');17 }18 }19}