Skip to content

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=.

API ConfigurationNot configured

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.

Create Wallet (EVM)
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"}'
Response200
{ "address": "0xe3f53fe54f8e1b5e8c86f070e4c9c2c7fa641d0d", "chain": "celo", "existing": true, "message": "wallet works on all EVM chains (Celo, Base, Arbitrum, etc.)" }
Create Wallet (Stellar)
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"}'
Response200
{ "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:

FieldTypeRequiredDescription
externalUserIdstringYesYour own user ID, as a string
chainstringNoChain 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 ....

  1. 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
  2. Fund it. On testnet, Stellar's own Friendbot faucet does this for free:
    bash
    curl "https://friendbot.stellar.org/?addr=<YOUR_G_ADDRESS>"
    On mainnet there's no faucet — send it real XLM (the account needs at least the 1 XLM base reserve, plus enough to cover startingBalanceXLM — 3 XLM per new user wallet it funds, by default).
  3. Set the seed in the engine's environment:
    env
    STELLAR_RELAYER_SEED=S...
  4. Restart the engine. GET /health should now list "stellar" in chains.
  5. 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

Get Wallet (EVM)
curl -X GET "http://localhost:8100/api/v1/wallets?externalUserId=123" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_TOKEN_HERE"
Response200
{ "address": "0xe3f53fe54f8e1b5e8c86f070e4c9c2c7fa641d0d", "chain": "celo" }
Get Wallet (Stellar)
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"
Response200
{ "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.

Wallet Balance
curl -X GET "http://localhost:8100/api/v1/wallets/balance?externalUserId=123" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_TOKEN_HERE"
Response200
{ "address": "0xe3f53fe54f8e1b5e8c86f070e4c9c2c7fa641d0d", "nativeWei": "500000000000000000", "usdtBaseUnits": "10000000" }

Key Management (Security)

  1. Generated as a keypair per (tenant, user, chain family) — secp256k1 for EVM, ed25519 for Stellar.
  2. Encrypted at rest with AES-256-GCM using a user-scoped key derived from the master WALLET_ENCRYPTION_KEY + tenant + user. Stored as encrypted_key in MongoDB.
  3. Shamir split — the sealed key is split into 5 Shamir shares (3-of-5 threshold). Server stores shares[0] as server_shard for recovery.
  4. Decrypted in memory only during transaction signing; zeroed immediately after.
  5. 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:

ChainChain IDNative Token
Celo11142220 (Sepolia) / 42220 (Mainnet)CELO
Base8453ETH
Arbitrum42161ETH

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.

Released under the MIT License.