Wallets
The GullyPay Wallet is a real, self-owned embedded wallet. Creating one generates a fresh secp256k1 EOA on-chain; its private key is encrypted at rest and GullyPay signs real on-chain transfers on your behalf. Read live balances, send tokens, swap between assets, and view history. Wallets support USDC and USDT on Base, Ethereum, and BSC.
Each wallet's private key is encrypted with AES-256-GCM (random 96-bit IV per encryption,
authenticated tag verified on every decrypt) under a 32-byte master key held only in the server
environment (WALLET_ENCRYPTION_KEY). The plaintext key is never logged, never returned by any
endpoint, and is decrypted transiently in-memory only to sign a send. If the master key is not
configured, wallet creation and sends fail-closed — GullyPay never stores or signs with an
unencrypted or absent key.
All wallet endpoints require a valid JWT or API key. Wallets are owned by the authenticated user.
Endpoints
/walletsCreate a wallet/wallets/meList wallets for the authenticated user/wallets/:idGet wallet details/wallets/:id/balanceRead real on-chain balances/wallets/:id/gaslessGasless (ERC-4337) status + the smart-account address to fund/wallets/:id/sendSend tokens to an address/wallets/:id/transactionsGet wallet transaction history/wallets/:id/swapSwap between supported tokensCreate a Wallet
/walletsCreate a wallet| Field | Type | Required | Description |
|---|---|---|---|
chain | string | No | Target chain: base, ethereum, bsc, or gully-chain-testnet (chain id 485590, only while the API runs with GULLY_CHAIN_ENABLED=true). Defaults to base. |
Gully Chain Testnet wallets read and send over GULLY_CHAIN_RPC_URL (the public testnet RPC
https://testnet-rpc.gullychain.xyz when unset). No stablecoin is built in there: USDC and glUSD are available
once their contracts are set with USDC_TOKEN_GULLY_CHAIN_TESTNET / GLUSD_TOKEN_GULLY_CHAIN_TESTNET
(decimals via USDC_DECIMALS_GULLY_CHAIN_TESTNET / GLUSD_DECIMALS_GULLY_CHAIN_TESTNET, default 6). Balances
also list WETH and native ETH. Gasless sends and swaps are not available on Gully Chain. Testnet funds have no value.
The wallet is created for the authenticated user as a user-type owner. A fresh secp256k1 EOA is
generated and its private key encrypted at rest before the row is written. The response contains the
public wallet record (id, chain, address, balances) — never the private key.
List My Wallets
/wallets/meList wallets for the authenticated userReturns all wallets owned by the authenticated user (private keys excluded).
Get a Wallet
/wallets/:idGet wallet detailsReturns the wallet record (id, chain, address, status). The encrypted private key is never included.
Read Balances
/wallets/:id/balanceRead real on-chain balancesReturns live on-chain balances read directly from the wallet's chain via balanceOf — every
supported stablecoin plus the native gas token. No cached or fabricated values.
Send
/wallets/:id/sendSend tokens to an address| Field | Type | Required | Description |
|---|---|---|---|
toAddress | string | Yes | Destination EVM address (0x…, 40 hex chars) |
amount | number | Yes | Token amount (decimal, not cents) |
currency | string | Yes | USDC, USDT, or glUSD |
A send performs a real, signed ERC-20 transfer: GullyPay loads the wallet, decrypts its key
transiently in-memory, submits the transfer on the wallet's chain, and records a ledger entry with
the actual transaction hash. The send automatically routes over one of two rails — gasless
(ERC-4337) when a bundler is configured for the chain, or the direct EOA transfer otherwise —
and the chosen rail is returned as path.
| Field | Type | Description |
|---|---|---|
txHash | string | Real on-chain transaction hash the transfer settled in (never fabricated) |
path | string | Rail used: gasless (ERC-4337 UserOperation) or eoa (direct EOA transfer) |
userOpHash | string | (gasless only) ERC-4337 UserOperation hash returned by the bundler |
smartAccountAddress | string | (gasless only) Smart-account address the tokens moved from |
sponsored | boolean | (gasless only) true when a paymaster sponsored gas |
Sends are settled on-chain and are irreversible once confirmed. The returned txHash is the real
broadcast hash — GullyPay never fabricates one. Validate the destination address before submitting.
Gasless sends (ERC-4337) — Phase 2
GullyPay supports gasless, bundler-sponsored stablecoin sends via
ERC-4337 account abstraction (opens in a new tab), built on viem's first-party
account-abstraction module. When gasless is configured for a chain, POST /wallets/:id/send
transparently routes over the gasless rail instead of the EOA path — same request body, no API change.
How it works. GullyPay derives a Coinbase Smart Account (opens in a new tab)
(EntryPoint v0.6) controlled by your wallet's owner key, encodes the ERC-20 transfer(to, amount) as a
UserOperation, submits it through the configured bundler, and — when a paymaster is configured —
has the paymaster sponsor gas so the transfer costs the user no native token. GullyPay waits for the
UserOperation receipt and returns the real settled transaction hash; if the operation reverts,
the send fails and no ledger entry is written (no fabricated success).
The gasless rail moves tokens from the ERC-4337 smart account (smartAccountAddress in the
response), which is a different address from the wallet's EOA (address). That smart account —
not the EOA — must hold the tokens being sent. The two addresses are both controlled by the same
encrypted owner key.
GET /wallets/:id/gasless returns both the smart-account address to fund and its real on-chain
balances, so you can confirm it is funded before sending. When gasless is not configured for the
chain, smartAccountAddress and balances are null.
Configuration (env-gated, provider-swap). Gasless is enabled per chain by setting a bundler URL; a paymaster URL is optional (without it the smart account must hold native gas, with it gas is sponsored). When no bundler is configured for a chain, sends fall back to the EOA rail automatically.
| Env var | Required for gasless | Purpose |
|---|---|---|
BASE_BUNDLER_URL / ETHEREUM_BUNDLER_URL / BSC_BUNDLER_URL | Yes (per chain) | ERC-4337 bundler RPC endpoint |
BASE_PAYMASTER_URL / ETHEREUM_PAYMASTER_URL / BSC_PAYMASTER_URL | Optional | Paymaster RPC for gas sponsorship |
BASE_RPC_URL / ETHEREUM_RPC_URL / BSC_RPC_URL | Yes | Execution-layer RPC (also used by the EOA rail) |
Gasless is available on the same EVM chains as the EOA wallet — Base, Ethereum, and BSC — wherever a bundler URL is configured. Solana is settled through the dedicated Solana path, not ERC-4337. The EntryPoint (v0.6) and account factory are provided by the Coinbase Smart Account implementation; no entrypoint/factory address needs to be supplied.
Swap
/wallets/:id/swapSwap between supported tokens (real, 0x-backed EVM swap)| Field | Type | Required | Description |
|---|---|---|---|
fromToken | string | Yes | Source token symbol (must be a supported token on the wallet's chain) |
toToken | string | Yes | Destination token symbol (must differ from fromToken) |
amount | number | Yes | Amount of fromToken to swap (decimal, not base units) |
A swap performs a real, on-chain EVM token swap via the 0x Swap API (opens in a new tab). GullyPay resolves both token contracts from the shared registry, fetches a firm quote for the taker (the wallet EOA), submits an ERC-20 allowance to the 0x AllowanceHolder spender when one is needed, then signs and broadcasts the swap transaction 0x returns, waits for the receipt, and records a ledger entry with the actual transaction hash and the real bought/sold amounts and quoted price. No rate is invented and no hash is fabricated.
Swaps require a ZEROX_API_KEY (0x Swap API) configured on the server, and are only supported on
the EVM chains 0x covers — Base, Ethereum, and BSC. If the key is not configured, or the wallet's
chain is not supported by 0x, POST /wallets/:id/swap fails-closed with 503 Service Unavailable
("Token swap is not configured") and no ledger entry is written — it never returns a fabricated
rate. Solana swaps are not yet supported (a documented follow-up, via Jupiter) and return a clear
"not yet supported on Solana" error. glUSD is not on 0x, so it is not a valid swap token today.
Transaction History
/wallets/:id/transactionsGet wallet transaction historyReturns the wallet's transaction history (sends, receives, and swaps), most recent first.
The wallets described here are blockchain wallets. The Financial OS exposes a separate set of
ledger-backed treasury wallets (POST /wallets, GET /wallets/me, GET /wallets/:id/ledger) for
fiat + crypto double-entry accounting. See the Financial OS page.