Embed Stablecoin Payments
Embed onchain stablecoin payments directly into your telecom, neobank, or fintech application. No wallet UI, no seed phrases - just passkey authentication and instant USDC transfers.
Why Stablecoin Payments?
Traditional cross-border payments are slow and expensive. When you send $1,000 to a contractor in Brazil using traditional rails, the costs add up quickly: payment processor fees ($10–$40), intermediary bank cuts (0.1%–4.0% FX spread), and 3-5 days of settlement time. By the time the payment clears, the recipient has lost $80 to fees.
Stablecoins change this entirely:| Traditional Rails | Stablecoin Rails |
|---|---|
| 3-5 business days | Seconds |
| $40-80 in fees per $1,000 | ~$0.01-0.10 |
| Bank hours only | 24/7/365 |
| Currency volatility risk | Dollar-denominated stability |
| Opaque reconciliation | On-chain transparency |
With JAW, you can embed these benefits directly into your application. Users authenticate with passkeys (no seed phrases), pay gas fees in USDC (no ETH needed), and send payments that settle in seconds.
Prerequisites
Install the core SDK:
npm install @jaw.id/coreGet your API key at dashboard.jaw.id.
Configuration
Set up your account configuration with the ERC-20 paymaster for USDC gas payments:
import { Account, JAW_PAYMASTER_URL } from '@jaw.id/core';
const USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; // USDC on Base
const config = {
chainId: 8453, // Base
apiKey: 'YOUR_API_KEY',
};
const paymasterUrl = `${JAW_PAYMASTER_URL}?chainId=8453&api-key=YOUR_API_KEY`;Account Management
Since you're using the Account class directly (headless mode), no JAW UI is rendered. The only prompt users see is the native browser/OS passkey dialog.
// New user: create account with passkey
const account = await Account.create(config, {
username: 'user@yourapp.com' // Your app's user identifier
});
// Store the credentialId for future sign-ins
const { credentialId } = account.getMetadata();
// Save credentialId in your own backend/database (not an SDK function)
await storeCredentialId('user@yourapp.com', credentialId);Send a USDC Payment
Send USDC with gas fees paid in USDC - users don't need ETH:
import { encodeFunctionData, erc20Abi, parseUnits } from 'viem';
// Send 10 USDC to a recipient
const { id, chainId } = await account.sendCalls(
[{
to: USDC_ADDRESS,
data: encodeFunctionData({
abi: erc20Abi,
functionName: 'transfer',
args: [
'0xRecipientAddress...', // Recipient address
parseUnits('10', 6) // 10 USDC (6 decimals)
]
})
}],
undefined, // options
paymasterUrl, // ERC-20 paymaster
{ token: USDC_ADDRESS } // Pay gas in USDC
);
console.log('Transaction submitted:', id);The SDK automatically handles the USDC approval for gas payment - no extra step required.
Check Transaction Status
Poll for transaction confirmation:
const status = account.getCallStatus(id);
if (status) {
switch (status.status) {
case 100:
console.log('Pending...');
break;
case 200:
console.log('Completed!', status.receipts?.[0]?.transactionHash);
break;
case 400:
console.log('Failed (off-chain)');
break;
case 500:
console.log('Reverted (on-chain)');
break;
}
}Batch Payments
Send multiple payments in a single transaction for payroll or mass payouts:
const recipients = [
{ address: '0xAlice...', amount: '500' },
{ address: '0xBob...', amount: '750' },
{ address: '0xCharlie...', amount: '300' },
];
const calls = recipients.map(({ address, amount }) => ({
to: USDC_ADDRESS,
data: encodeFunctionData({
abi: erc20Abi,
functionName: 'transfer',
args: [address, parseUnits(amount, 6)]
})
}));
const { id } = await account.sendCalls(
calls,
undefined,
paymasterUrl,
{ token: USDC_ADDRESS }
);All transfers execute atomically - either all succeed or all fail together.
Supported Networks
See Supported Networks for the full list.
Related
- Account.create() - Create accounts with passkeys
- Account.get() - Sign in to existing accounts
- account.sendCalls() - Send batched transactions
- Gas Sponsoring - Fully sponsor gas for users
- Subscription Payments - Recurring payments with permissions