Wallet SDK
Use the StableOps wallet SDK to select payment instructions, connect injected or WalletConnect wallets, send EVM, Solana, and TRON transfers, and handle errors.
@stableops/wallet-sdk is a browser-only front-end library: it hands the
paymentInstructions returned when your backend creates an order to the user's
wallet and sends an on-chain transfer to that order's deposit address.
Idempotency, address allocation, chain scanning, confirmations, and webhooks all stay on the StableOps backend. The wallet SDK does exactly one thing: get the user's funds to the right address.
STABLEOPS_API_KEY in the browser. Orders must be created on your backend; the frontend only receives the order id, amount, and paymentInstructions.Install
pnpm add @stableops/wallet-sdkRequires a browser environment with fetch and EIP-1193 / TronLink / Solana
wallet adapters. WalletConnect is optional; install its dependency only when you
need it (see below).
Want a runnable end-to-end example?
Quick start (injected wallets)
getInjectedWalletProviders() collects the wallets injected into the page
(MetaMask, TronLink, Phantom, etc). sendOrderWalletPayment() picks a candidate
chain from the order that has an available wallet and sends the transfer.
import { getInjectedWalletProviders, sendOrderWalletPayment } from '@stableops/wallet-sdk'
const sent = await sendOrderWalletPayment({
order, // { amount, paymentInstructions }, from your backend
providers: getInjectedWalletProviders(),
})
console.log(sent.txHash)Use preferredChains to control which chains are tried first:
await sendOrderWalletPayment({
order,
providers: getInjectedWalletProviders(),
preferredChains: ['base', 'arbitrum'],
})Selecting an instruction yourself
When you want to control which chain is used, select the instruction with
selectWalletPaymentInstruction(), then call the lower-level
sendWalletPayment():
import {
getInjectedWalletProviders,
selectWalletPaymentInstruction,
sendWalletPayment,
} from '@stableops/wallet-sdk'
const { instruction, provider } = selectWalletPaymentInstruction(
order.paymentInstructions,
getInjectedWalletProviders(),
['base'], // optional: preferred chains
)
const sent = await sendWalletPayment({
provider,
instruction,
amount: order.amount,
})
console.log(sent.txHash)WalletConnect (mobile / custom UI)
For mobile browsers or pages without an injected EVM provider, wire up the optional WalletConnect runtime. The SDK ships no UI and maintains no wallet list: you pass the wallet options in, and render the picker, QR code, and loading / error states yourself by subscribing to the controller state.
pnpm add @walletconnect/universal-providerimport { createWalletConnectController, sendOrderWalletPayment } from '@stableops/wallet-sdk'
const wc = await createWalletConnectController({
projectId: 'YOUR_REOWN_PROJECT_ID',
metadata: {
name: 'Your App',
description: 'StableOps checkout',
url: window.location.origin,
icons: [`${window.location.origin}/icon.png`],
},
// Enabled namespaces (pick what you need)
chains: ['base', 'arbitrum'], // EVM
solanaChains: ['solana'], // Solana
tronChains: ['tron'], // TRON
wallets: [
{
id: 'metamask',
name: 'MetaMask',
links: { native: 'metamask://', universal: 'https://metamask.app.link' },
},
],
})
const unsubscribe = wc.subscribe((state) => {
// Render based on state.status:
// 'idle' | 'connecting' | 'uri_ready' | 'connected' | 'failed' | 'disconnected'
// When state.status === 'uri_ready', render a QR code from state.uri.
})
await wc.connect({ walletId: 'metamask' })
const sent = await sendOrderWalletPayment({
order,
providers: wc.providers, // pass the controller's providers straight in
})
unsubscribe()
console.log(sent.txHash)All three namespaces work over WalletConnect:
- EVM: behaves the same as injected wallets.
- Solana: depends on wallet support for
solana_signTransaction/solana_signAndSendTransaction; custom RPC / devnet flows requiresolana_signTransaction. - TRON: the wallet only does
tron_signTransaction; the SDK builds and broadcasts the transaction with tronweb (default trongrid public node, overridable viatronRpcUrl).
Return value: SentWalletPayment
Both sendWalletPayment and sendOrderWalletPayment return:
{
txHash: string
chain: ChainId
asset: 'USDC' | 'USDT'
fromAddress: string
toAddress: string
tokenContract: string
amount: string // human-readable amount, e.g. '49.00'
amountUnits: string // smallest-unit integer string
confirmation: Promise<void>
}confirmation is a best-effort hint and does not block the function from
returning (it runs in the background after broadcast):
- resolves when the transaction is mined and the contract call succeeded (or after a best-effort timeout of ~90s).
- rejects (
code: 'wallet_tx_reverted') when the call reverted on-chain and no tokens moved (e.g. insufficient balance). The goal is to surface an immediate revert early instead of leaving the user waiting.
sent.confirmation.catch((err) => {
// err.code === 'wallet_tx_reverted'
})detected / confirmed, defer to the server and ignore a confirmation rejection.Supported chains
| Chain | How it works |
|---|---|
ethereum base arbitrum polygon optimism bsc (plus testnets *-sepolia / polygon-amoy / bsc-testnet) | Calls EIP-1193 wallets, switches / adds networks when needed, sends an ERC-20 transfer. Override RPC / explorer via chainConfigs. |
tron tron-nile | Calls TronLink / TronWeb (or WalletConnect TRON) to build, sign, and broadcast a TRC-20 transfer. Pass tronRpcUrl for testnet or a self-hosted node. |
solana solana-devnet | Calls Solana wallet adapters, idempotently creates the recipient associated token account, sends an SPL Token TransferChecked. For devnet pass solanaRpcUrl: 'https://api.devnet.solana.com' (or an equivalent node). |
Error handling
Every failure throws StableOpsWalletError with .code, .message, and an
optional .details.
import { StableOpsWalletError } from '@stableops/wallet-sdk'
try {
await sendOrderWalletPayment({ order, providers: getInjectedWalletProviders() })
} catch (err) {
if (err instanceof StableOpsWalletError && err.code === 'wallet_user_rejected') {
// User cancelled in the wallet
}
throw err
}Common code values:
| code | Meaning |
|---|---|
payment_instruction_not_found | The order has no payable on-chain instruction |
wallet_provider_not_found | No wallet provider for any candidate chain |
wallet_user_rejected | The user rejected the request in their wallet |
wallet_tx_reverted | On-chain revert (usually thrown via confirmation) |
unsupported_chain | Chain not supported by the wallet helper |
chain_config_not_found | Missing EVM chain config (supply chainConfigs) |
invalid_amount / invalid_evm_address / invalid_tron_address / invalid_solana_address | Invalid input |
tron_dependency_missing / solana_dependency_missing | Missing TRON / Solana runtime dependency |
Debugging
Turn on module-level debug logging (prefixed with [wallet-sdk]):
import { setWalletSdkDebug } from '@stableops/wallet-sdk'
setWalletSdkDebug(true)You can also enable it without code changes: set
globalThis.STABLEOPS_WALLET_DEBUG = true in the browser console, or inject the
WALLET_SDK_DEBUG=1 environment variable at build time.
How is this guide?
Last updated
Python API SDK
Install the StableOps Python SDK to create payment orders and checkout sessions, manage Webhook endpoints, verify signatures, and handle API errors and replay.
Webhook verifier
Verify StableOps Webhook signatures using the raw body, handle secret rotation and timestamp tolerance, and choose verification methods for your runtime.