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
| Type | is_eoa | Created By | Can Be Primary | Can Be Deleted |
|---|
| EOA | true | Your application via POST /v0/wallets | Yes | Yes (if not primary) |
| Safe | false | System (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:
| Chain | Chain ID | Native Token |
|---|
| Ethereum | 1 | ETH |
| Optimism | 10 | ETH |
| Polygon | 137 | MATIC |
| Arbitrum One | 42161 | ETH |
| Base | 8453 | ETH |
| Avalanche | 43114 | AVAX |
| BNB Smart Chain | 56 | BNB |
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
| Status | Meaning |
|---|
pending | Initial state before Safe creation is requested |
processing | Safe deployment is in progress on-chain |
completed | Safe deployed successfully — address available on user profile |
failed | Deployment 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
Returns all wallets for the authenticated user. Supports filtering and expansion.
Query parameters:
| Parameter | Type | Description |
|---|
chain_id | integer | Filter by blockchain chain ID |
is_eoa | boolean | true for EOA wallets, false for Safe wallets |
expand | string | Expand 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:
| Parameter | Type | Description |
|---|
expand | string | Expand related resources. Options: cards |
Response: 200 OK
Create Wallet
Adds an EOA wallet to the authenticated user’s account.
Request body:
| Field | Type | Required | Description |
|---|
address | string | Yes | Valid Ethereum address (0x-prefixed, 40 hex characters) |
chain_id | integer | Yes | Blockchain chain ID (e.g. 8453 for Base) |
is_eoa | boolean | No | Defaults to true. Must be true for user-created wallets |
nickname | string | No | Friendly 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:
| Field | Type | Description |
|---|
is_primary | boolean | Set true to make this the primary EOA |
nickname | string | Update 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:
| Parameter | Type | Default | Description |
|---|
offset | integer | 0 | Pagination offset |
limit | integer | 50 | Results per page (max 100) |
from | integer | — | Filter transactions after this timestamp (epoch ms) |
to | integer | — | Filter 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)
Following HAL Links
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
| Code | Status | Description | Recovery |
|---|
WAL-400-001 | 400 | Invalid Ethereum address format | Provide a valid 0x-prefixed, checksummed address |
WAL-400-002 | 400 | Chain ID does not match primary chain | Use the same chain as the primary wallet |
WAL-403-001 | 403 | Cannot delete primary wallet | Set another wallet as primary first |
WAL-403-002 | 403 | Wallet belongs to another user | Verify the wallet ID |
WAL-404-001 | 404 | Wallet not found | Check the wallet ID; list wallets via GET /v0/wallets |
WAL-409-001 | 409 | Wallet already exists for this address and chain | Use the existing wallet |
USR-424-001 | 424 | No active agent signer (precondition failed) | Wait for signer setup to complete |
Asset Error Codes
| Code | Status | Description | Recovery |
|---|
AST-404-001 | 404 | Asset not found in wallet | Verify the asset ID for this wallet |
Reference Tables
Wallet Response Fields
| Field | Type | Description |
|---|
id | integer | Internal wallet ID |
user_id | integer | Owning user’s ID |
address | string | Wallet address (EOA or Safe contract) |
chain_id | integer | Blockchain chain ID |
is_primary | boolean | Whether this is the user’s primary wallet of its type |
is_eoa | boolean | true for EOA, false for Safe |
nickname | string or null | User-defined label |
logo_uri | string or null | Chain logo image URI |
created_at | integer | Creation timestamp (epoch milliseconds) |
deleted_at | integer or null | Soft-deletion timestamp if removed |
safe_creation_event_id | string or null | Present only in 202 responses when Safe creation is triggered |
cards | array or null | Linked payment cards (only with expand=cards) |
Wallet Asset Fields
| Field | Type | Description |
|---|
asset_id | integer | Asset identifier |
symbol | string | Token symbol (e.g. ETH, USDC) |
name | string | Full token name |
chain_id | integer or null | Chain where the asset exists |
balance | string | Token balance as a decimal string |
balance_usd | string or null | USD valuation (null if price unavailable) |
balance_updated_at | integer | Last balance refresh timestamp (epoch milliseconds) |
Asset Transaction Fields
| Field | Type | Description |
|---|
id | integer | Transaction ID |
external_id | string or null | External reference ID |
type | string | Transaction type (e.g. transfer) |
status | string | Transaction status (e.g. confirmed, pending) |
direction | string | incoming or outgoing |
amount | string | Transaction amount as a decimal string |
created_at | integer | Transaction timestamp (epoch milliseconds) |
tx_hash | string or null | On-chain transaction hash |
merchant_name | string or null | Merchant name for card-funded transactions |
Next Steps