Skip to main content

Wallets

Overview

The API provides multi-chain wallet management for EVM-compatible blockchains. Each user can hold multiple wallets across different chains, with two distinct wallet types:
  • EOA (Externally Owned Account) — user-controlled wallets added by your application
  • Safe (Smart Wallet) — multisig smart contract wallets created automatically by the system
Every user has a primary EOA wallet that serves as their main identity on-chain. When a primary EOA is set, the system automatically deploys a Safe smart wallet on the same chain. This Safe creation is asynchronous — the API returns 202 Accepted and you poll for completion.

Quick Start

1. Add a wallet

Register an EOA wallet for the authenticated user:
curl -X POST /v0/wallets \
  -H "x-juno-jwt: <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD78",
    "chain_id": 8453,
    "is_eoa": true,
    "nickname": "Trading Wallet"
  }'
Response: 201 Created
{
  "id": 42,
  "user_id": 7,
  "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD78",
  "chain_id": 8453,
  "is_primary": false,
  "is_eoa": true,
  "nickname": "Trading Wallet",
  "logo_uri": "https://img.logo.dev/base.org?token=pk_TEMpNn5BSh20ps7GrOLkyQ",
  "created_at": 1704067200000,
  "_links": {
    "self": { "href": "/v0/wallets/42" },
    "user": { "href": "/v0/user/me" },
    "set-primary": { "href": "/v0/wallets/42", "method": "PATCH" },
    "delete": { "href": "/v0/wallets/42", "method": "DELETE" }
  }
}

2. Set as primary

Promote the wallet to primary status. This may trigger asynchronous Safe creation:
curl -X PATCH /v0/wallets/42 \
  -H "x-juno-jwt: <token>" \
  -H "Content-Type: application/json" \
  -d '{"is_primary": true}'
If a Safe already exists on that chain, you get 200 OK. If a new Safe needs to be deployed, you get 202 Accepted with a safe_creation_event_id:
{
  "id": 42,
  "user_id": 7,
  "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD78",
  "chain_id": 8453,
  "is_primary": true,
  "is_eoa": true,
  "safe_creation_event_id": "evt_abc123def456",
  "created_at": 1704067200000,
  "_links": {
    "self": { "href": "/v0/wallets/42" },
    "user": { "href": "/v0/user/me" },
    "safe-status": { "href": "/v0/user/me" }
  }
}

3. Poll for Safe creation

When you receive a 202, poll the user profile to check Safe creation progress:
curl /v0/user/me \
  -H "x-juno-jwt: <token>"
The safe_creation_status field on the user tracks progress. Once completed, the primary_smart_wallet_address field contains the deployed Safe address.

4. List wallets

curl /v0/wallets \
  -H "x-juno-jwt: <token>"
Response: 200 OK
{
  "wallets": [
    {
      "id": 42,
      "user_id": 7,
      "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD78",
      "chain_id": 8453,
      "is_primary": true,
      "is_eoa": true,
      "nickname": "Trading Wallet",
      "created_at": 1704067200000
    },
    {
      "id": 43,
      "user_id": 7,
      "address": "0xSafeContractAddress1234567890abcdef12345678",
      "chain_id": 8453,
      "is_primary": true,
      "is_eoa": false,
      "nickname": null,
      "created_at": 1704067260000
    }
  ],
  "_links": {
    "self": { "href": "/v0/wallets" },
    "user": { "href": "/v0/user/me" }
  }
}

Wallet Types

Typeis_eoaCreated ByCan Be PrimaryCan Be Deleted
EOAtrueYour application via POST /v0/walletsYesYes (if not primary)
SafefalseSystem (automatically when primary EOA is set)Yes (auto-set on creation)No
EOA wallets are standard Ethereum accounts controlled by a private key. Your application registers them so the API can track balances and transactions. Safe wallets are smart contract wallets deployed by the system. They provide multisig capabilities and are paired with the user’s primary EOA on the same chain. You cannot create or delete Safe wallets directly — they are managed entirely by the system.

Multi-Chain Support

The API supports wallets across all major EVM-compatible chains:
ChainChain IDNative Token
Ethereum1ETH
Optimism10ETH
Polygon137MATIC
Arbitrum One42161ETH
Base8453ETH
Avalanche43114AVAX
BNB Smart Chain56BNB
Each wallet is scoped to a single chain. A user can have wallets on multiple chains, but the primary EOA and its corresponding Safe must be on the same chain.

Safe Creation Flow

When you set a primary EOA on a chain where no Safe exists, the system deploys one asynchronously.

Safe Creation Statuses

StatusMeaning
pendingInitial state before Safe creation is requested
processingSafe deployment is in progress on-chain
completedSafe deployed successfully — address available on user profile
failedDeployment failed — contact support
Safe creation requires an active agent signer on the target chain. If signer setup is still in progress, setting a primary wallet returns 424 Failed Dependency. Wait for onboarding to complete before setting a primary.

Endpoints

List Wallets

GET /v0/wallets
Returns all wallets for the authenticated user. Supports filtering and expansion. Query parameters:
ParameterTypeDescription
chain_idintegerFilter by blockchain chain ID
is_eoabooleantrue for EOA wallets, false for Safe wallets
expandstringExpand related resources. Options: cards
Response: 200 OK
# All wallets on Base
curl "/v0/wallets?chain_id=8453" \
  -H "x-juno-jwt: <token>"

# Only Safe wallets
curl "/v0/wallets?is_eoa=false" \
  -H "x-juno-jwt: <token>"

# Include linked payment cards
curl "/v0/wallets?expand=cards" \
  -H "x-juno-jwt: <token>"

Get Wallet

GET /v0/wallets/{wallet_id}
Returns full details for a specific wallet. Query parameters:
ParameterTypeDescription
expandstringExpand related resources. Options: cards
Response: 200 OK

Create Wallet

POST /v0/wallets
Adds an EOA wallet to the authenticated user’s account. Request body:
FieldTypeRequiredDescription
addressstringYesValid Ethereum address (0x-prefixed, 40 hex characters)
chain_idintegerYesBlockchain chain ID (e.g. 8453 for Base)
is_eoabooleanNoDefaults to true. Must be true for user-created wallets
nicknamestringNoFriendly label, max 100 characters
Response: 201 Created Returns the created wallet with _links including set-primary and delete actions.

Update Wallet

PATCH /v0/wallets/{wallet_id}
Updates wallet properties or promotes to primary status. Request body:
FieldTypeDescription
is_primarybooleanSet true to make this the primary EOA
nicknamestringUpdate the friendly label
Response: 200 OK when the update is synchronous, 202 Accepted when Safe creation is triggered. When the response is 202, it includes:
  • safe_creation_event_id — event ID for tracking
  • _links.safe-status — link to poll for Safe deployment completion

Delete Wallet

DELETE /v0/wallets/{wallet_id}
Soft-deletes a wallet. The record is retained for audit purposes but no longer appears in lists. Response: 204 No Content Primary wallets cannot be deleted. Set another wallet as primary first, then delete the old one.

List Wallet Assets

GET /v0/wallets/{wallet_id}/assets
Returns all token balances held in a specific wallet, with USD valuations. Response: 200 OK
{
  "wallet_id": 42,
  "assets": [
    {
      "asset_id": 1,
      "symbol": "ETH",
      "name": "Ethereum",
      "chain_id": 8453,
      "balance": "1.5",
      "balance_usd": "4875.00",
      "balance_updated_at": 1704067200000,
      "transactions_href": "/v0/wallets/42/assets/1/transactions"
    },
    {
      "asset_id": 5,
      "symbol": "USDC",
      "name": "USD Coin",
      "chain_id": 8453,
      "balance": "2500",
      "balance_usd": "2500.00",
      "balance_updated_at": 1704067200000,
      "transactions_href": "/v0/wallets/42/assets/5/transactions"
    }
  ],
  "total_assets": 2,
  "_links": {
    "self": { "href": "/v0/wallets/42/assets" },
    "wallet": { "href": "/v0/wallets/42" },
    "balances": { "href": "/v0/wallets/42/balances" },
    "transactions": { "href": "/v0/transactions?wallet_id=42" },
    "user": { "href": "/v0/user/me" }
  }
}

List Asset Transactions

GET /v0/wallets/{wallet_id}/assets/{asset_id}/transactions
Returns paginated transactions for a specific asset in a wallet. Query parameters:
ParameterTypeDefaultDescription
offsetinteger0Pagination offset
limitinteger50Results per page (max 100)
fromintegerFilter transactions after this timestamp (epoch ms)
tointegerFilter transactions before this timestamp (epoch ms)
Response: 200 OK
{
  "wallet_id": 42,
  "asset_id": 1,
  "asset_symbol": "ETH",
  "transactions": [
    {
      "id": 101,
      "external_id": "0xabc123",
      "type": "transfer",
      "status": "confirmed",
      "direction": "incoming",
      "amount": "0.5",
      "created_at": 1704067200000,
      "tx_hash": "0x9f8e7d6c5b4a3210fedcba9876543210abcdef0123456789abcdef0123456789",
      "merchant_name": null
    }
  ],
  "total": 1,
  "offset": 0,
  "limit": 50,
  "_links": {
    "self": { "href": "/v0/wallets/42/assets/1/transactions?offset=0&limit=50" },
    "wallet": { "href": "/v0/wallets/42" },
    "asset": { "href": "/v0/assets/1" },
    "wallet_assets": { "href": "/v0/wallets/42/assets" },
    "user": { "href": "/v0/user/me" }
  }
}
Pagination links (next, prev) are included in _links when applicable.

Get Balance Summary

GET /v0/wallets/{wallet_id}/balances
Returns an aggregated balance summary for a wallet, including all assets with USD valuations. Response: 200 OK
{
  "summary": {
    "wallet_id": 42,
    "total_assets": 2,
    "last_updated": 1704067200000,
    "assets": [
      {
        "asset_id": 1,
        "symbol": "ETH",
        "name": "Ethereum",
        "chain_id": 8453,
        "balance": "1.5",
        "balance_usd": "4875.00",
        "balance_updated_at": 1704067200000,
        "transactions_href": "/v0/wallets/42/assets/1/transactions"
      }
    ]
  },
  "_links": {
    "self": { "href": "/v0/wallets/42/balances" },
    "wallet": { "href": "/v0/wallets/42" },
    "assets": { "href": "/v0/wallets/42/assets" },
    "transactions": { "href": "/v0/transactions?wallet_id=42" },
    "user": { "href": "/v0/user/me" }
  }
}

Integration Patterns

Handling Async Safe Creation

When PATCH /v0/wallets/{id} returns 202, implement a polling loop:
response = PATCH /v0/wallets/42 { "is_primary": true }

if response.status == 202:
    while true:
        user = GET /v0/user/me
        if user.safe_creation_status == "completed":
            safe_address = user.primary_smart_wallet_address
            break
        if user.safe_creation_status == "failed":
            handle_error()
            break
        wait(3 seconds)
The typical Safe deployment takes 10-30 seconds depending on chain congestion.

Building a Wallet Selector

Fetch all wallets and group by type:
# Fetch all wallets
curl /v0/wallets \
  -H "x-juno-jwt: <token>"
wallets = GET /v0/wallets
eoa_wallets = [w for w in wallets where w.is_eoa == true]
safe_wallets = [w for w in wallets where w.is_eoa == false]
primary_eoa = first(w for w in eoa_wallets where w.is_primary == true)
Responses include _links for discoverable navigation. Non-primary wallets include set-primary and delete action links. Primary wallets omit these since the actions are not permitted.
wallet = GET /v0/wallets/42

if "set-primary" in wallet._links:
    # Wallet can be promoted
    PATCH wallet._links["set-primary"].href { "is_primary": true }

if "delete" in wallet._links:
    # Wallet can be removed
    DELETE wallet._links["delete"].href

Error Handling

All error responses follow RFC 7807 Problem Details:
{
  "type": "https://api.sumvin.com/errors/wal-400-001",
  "title": "Invalid Wallet Address",
  "status": 400,
  "detail": "Invalid wallet address: 0xinvalid",
  "instance": "/v0/wallets",
  "error_code": "WAL-400-001",
  "trace_id": "abc-123-def"
}

Wallet Error Codes

CodeStatusDescriptionRecovery
WAL-400-001400Invalid Ethereum address formatProvide a valid 0x-prefixed, checksummed address
WAL-400-002400Chain ID does not match primary chainUse the same chain as the primary wallet
WAL-403-001403Cannot delete primary walletSet another wallet as primary first
WAL-403-002403Wallet belongs to another userVerify the wallet ID
WAL-404-001404Wallet not foundCheck the wallet ID; list wallets via GET /v0/wallets
WAL-409-001409Wallet already exists for this address and chainUse the existing wallet
USR-424-001424No active agent signer (precondition failed)Wait for signer setup to complete

Asset Error Codes

CodeStatusDescriptionRecovery
AST-404-001404Asset not found in walletVerify the asset ID for this wallet

Reference Tables

Wallet Response Fields

FieldTypeDescription
idintegerInternal wallet ID
user_idintegerOwning user’s ID
addressstringWallet address (EOA or Safe contract)
chain_idintegerBlockchain chain ID
is_primarybooleanWhether this is the user’s primary wallet of its type
is_eoabooleantrue for EOA, false for Safe
nicknamestring or nullUser-defined label
logo_uristring or nullChain logo image URI
created_atintegerCreation timestamp (epoch milliseconds)
deleted_atinteger or nullSoft-deletion timestamp if removed
safe_creation_event_idstring or nullPresent only in 202 responses when Safe creation is triggered
cardsarray or nullLinked payment cards (only with expand=cards)

Wallet Asset Fields

FieldTypeDescription
asset_idintegerAsset identifier
symbolstringToken symbol (e.g. ETH, USDC)
namestringFull token name
chain_idinteger or nullChain where the asset exists
balancestringToken balance as a decimal string
balance_usdstring or nullUSD valuation (null if price unavailable)
balance_updated_atintegerLast balance refresh timestamp (epoch milliseconds)

Asset Transaction Fields

FieldTypeDescription
idintegerTransaction ID
external_idstring or nullExternal reference ID
typestringTransaction type (e.g. transfer)
statusstringTransaction status (e.g. confirmed, pending)
directionstringincoming or outgoing
amountstringTransaction amount as a decimal string
created_atintegerTransaction timestamp (epoch milliseconds)
tx_hashstring or nullOn-chain transaction hash
merchant_namestring or nullMerchant name for card-funded transactions

Next Steps