Wallets API
Custodial wallet management, scoped per chain family. One EVM wallet address works on ALL configured EVM chains — same address, same key, different chains. A separate address family exists for non-EVM chains (currently Stellar) via the same endpoints, selected with ?chain=.
How It Works
Comet Engine provides server-custodial embedded wallets: the engine generates and safeguards each user's key, and the client never handles raw key material.
- User authenticates → wallet is created idempotently, keyed by
externalUserId(your own user ID as a string — not an engine-assigned ID) - One secp256k1 keypair per (tenant, user, EVM) — same address on every EVM chain
- A separate keypair per (tenant, user, Stellar) when the Stellar chain is configured — a distinct address from the EVM wallet, not an alias of it
- Private key encrypted with AES-256-GCM, split via Shamir's Secret Sharing
- Platform relayer pays gas — users never need native tokens
externalUserId, not userId
Wallet endpoints identify the user with externalUserId (a string — your own user ID from your own system), not a numeric userId assigned by the engine. Pass it in the request body for Create Wallet, or as ?externalUserId= / the X-External-User-ID header for Get Wallet and Wallet Balance.
Create Wallet
Creates (idempotently) a custodial wallet for a user on the given chain.
curl -X POST "http://localhost:8100/api/v1/wallets/create" \
-H "Content-Type: application/json" \
-H "X-Service-Token: YOUR_TOKEN_HERE" \
-d '{"externalUserId": "123"}'{ "address": "0xe3f53fe54f8e1b5e8c86f070e4c9c2c7fa641d0d", "chain": "celo", "existing": true, "message": "wallet works on all EVM chains (Celo, Base, Arbitrum, etc.)" }curl -X POST "http://localhost:8100/api/v1/wallets/create" \
-H "Content-Type: application/json" \
-H "X-Service-Token: YOUR_TOKEN_HERE" \
-d '{"externalUserId": "123", "chain": "stellar"}'{ "address": "GBSKYH53LKZVZRWGPSGCC7RDWPBBCCQAUM4JFBL6HGHLZKD3VJWPLNXE", "chain": "stellar", "existing": true, "message": "wallet is scoped to Stellar — a separate address from any EVM wallet" }Verified
The response above is a real, live call against a STELLAR_RELAYER_SEED-configured engine — that address exists on Stellar testnet, funded with the 3 XLM starting balance described below. Not a hypothetical example.
Request Fields:
| Field | Type | Required | Description |
|---|---|---|---|
externalUserId | string | Yes | Your own user ID, as a string |
chain | string | No | Chain family to provision. Defaults to celo. Pass stellar (or ?chain=stellar) for a Stellar-family address instead. |
TIP
This endpoint is idempotent — calling it twice for the same (tenant, user, chain family) returns the existing wallet.
Chain must be configured
chain: "stellar" returns 400 chain "stellar" not configured unless STELLAR_RELAYER_SEED is set on the engine. See Setting Up Stellar → below for the exact steps. EVM chains are configured per-chain the same way.
Setting Up Stellar
Stellar wallets need the relayer account funded on-chain before it can fund anything else — an unfunded relayer fails every wallet creation with 500 stellar: relayer account ... is not funded on ....
- Generate a relayer keypair. Any ed25519 Stellar keypair works — for local dev, a quick way is a throwaway Go script using the same SDK the engine does:go
kp, _ := keypair.Random() // github.com/stellar/go-stellar-sdk/keypair fmt.Println(kp.Address()) // G... — public, safe to share fmt.Println(kp.Seed()) // S... — secret, treat like a private key - Fund it. On testnet, Stellar's own Friendbot faucet does this for free:bashOn mainnet there's no faucet — send it real XLM (the account needs at least the 1 XLM base reserve, plus enough to cover
curl "https://friendbot.stellar.org/?addr=<YOUR_G_ADDRESS>"startingBalanceXLM— 3 XLM per new user wallet it funds, by default). - Set the seed in the engine's environment:env
STELLAR_RELAYER_SEED=S... - Restart the engine.
GET /healthshould now list"stellar"inchains. - Verify with a real Create Wallet call (above) — confirm the returned address exists on-chain, e.g. via
https://horizon-testnet.stellar.org/accounts/<address>.
Every subsequent user wallet is funded automatically from this same relayer, the same way EVM's relayer tops up gas for new wallets — no per-user manual funding step.
Get Wallet
curl -X GET "http://localhost:8100/api/v1/wallets?externalUserId=123" \
-H "Content-Type: application/json" \
-H "X-Service-Token: YOUR_TOKEN_HERE"{ "address": "0xe3f53fe54f8e1b5e8c86f070e4c9c2c7fa641d0d", "chain": "celo" }curl -X GET "http://localhost:8100/api/v1/wallets?externalUserId=123&chain=stellar" \
-H "Content-Type: application/json" \
-H "X-Service-Token: YOUR_TOKEN_HERE"{ "address": "GATTV27NPBOP6DPNL5UGZG3IHESM7DIYYVLXHNJGCUZ5T4TIWDPY6ADE", "chain": "stellar" }externalUserId may also be sent as the X-External-User-ID header instead of a query param.
Wallet Balance
Returns native balance and configured token balances for the given chain.
curl -X GET "http://localhost:8100/api/v1/wallets/balance?externalUserId=123" \
-H "Content-Type: application/json" \
-H "X-Service-Token: YOUR_TOKEN_HERE"{ "address": "0xe3f53fe54f8e1b5e8c86f070e4c9c2c7fa641d0d", "nativeWei": "500000000000000000", "usdtBaseUnits": "10000000" }Key Management (Security)
- Generated as a keypair per (tenant, user, chain family) — secp256k1 for EVM, ed25519 for Stellar.
- Encrypted at rest with AES-256-GCM using a user-scoped key derived from the master
WALLET_ENCRYPTION_KEY+ tenant + user. Stored asencrypted_keyin MongoDB. - Shamir split — the sealed key is split into 5 Shamir shares (3-of-5 threshold). Server stores
shares[0]asserver_shardfor recovery. - Decrypted in memory only during transaction signing; zeroed immediately after.
- Signed server-side — the engine constructs and broadcasts transactions without the client ever holding the private key.
Custodial model
The server holds the encrypted private key and decrypts it for signing. This is an embedded-wallet trust model. For non-custodial signing, build a separate flow.
Cross-Chain Support
The same EVM wallet address works on all configured EVM chains:
| Chain | Chain ID | Native Token |
|---|---|---|
| Celo | 11142220 (Sepolia) / 42220 (Mainnet) | CELO |
| Base | 8453 | ETH |
| Arbitrum | 42161 | ETH |
To operate on a specific EVM chain, pass ?chain=celo (or base, arbitrum, ...) to asset endpoints.
Stellar is a separate address family, not another EVM chain — ?chain=stellar returns a different address than any ?chain=celo/base/arbitrum call for the same user, and that address is not usable on any EVM chain or vice versa.
Gas Relaying
All wallet operations have gas paid by the platform relayer:
ensureGas()checks the wallet's native balance- If below threshold, tops up from the relayer wallet
- Transaction signed with user's key, sent from relayer's nonce
Stellar transactions are relayed the same way, funded from the relayer account configured via STELLAR_RELAYER_SEED.