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}Script Composer — Backend / Server-Side (Node.js)
For backend products (bots, WhatsApp flows, Keyless). Your server builds the transaction, your signing service produces an authenticator, then the server submits it. Use a
sk_nogas_* key on the backend.script-composer-backend.ts
TypeScript
1import { ScriptComposerClient } from '@smoothsend/sdk';2 3const client = new ScriptComposerClient({4 apiKey: process.env.SMOOTHSEND_API_KEY!, // use sk_nogas_* on backend5 network: 'mainnet',6});7 8// USDC mainnet asset address on Aptos9const USDC = '0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b';10 11async function sendUSDCViaBackend(sender: string, recipient: string, amountUsdc: number) {12 // Step 1 — build on the server13 const build = await client.buildTransfer({14 sender,15 recipient,16 amount: String(amountUsdc * 1_000_000), // USDC has 6 decimals17 assetType: USDC,18 decimals: 6,19 symbol: 'USDC',20 });21 22 // Step 2 — sign using your own signer (Aptos Keyless / social login / custodial key store)23 // This MUST return authenticatorBytes compatible with the built transactionBytes.24 const signed = await yourSigningService.sign({25 transactionBytes: build.transactionBytes,26 sender,27 });28 29 // Step 3 — submit from the server30 const { txHash } = await client.submitSignedTransaction({31 transactionBytes: build.transactionBytes,32 authenticatorBytes: signed.authenticatorBytes,33 });34 35 return txHash;36}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}