Platform

Overview

How It Works

Beneficiary Identity

Policy Corridors

Deterministic Finality

Architecture

Security Model

Governance

Integration

Solutions

Corridors Overview

Institutional Overview

Pricing

All Scenarios

Humanitarian Impact Fund

Assurance

Technical Assurance

Verify Receipt

Receipt Example

Developers

Documentation

APIs & Bridges

Architecture Docs

Glossary

BID API

Company

About

Team

Partners

Roadmap

Investors

Contact

Blog

All Documentation

Schedule Consultation
← Back to Documentation
Wallet API v1.0 All Documentation →

Wallet API Specification

Complete API reference for MPC wallet operations: passkey authentication, balance queries, two-phase transaction signing, @handle resolution, social recovery ceremonies, and cryptographic transaction proofs. Non-custodial, WebAuthn-secured, institutional-grade.

API Reference JIL Sovereign February 2026 Port 8002

Overview

The JIL Sovereign Wallet API provides programmatic access to non-custodial MPC wallet infrastructure. Users hold their own keys via MPC 2-of-3 threshold signing with WebAuthn passkeys. All transactions are signed client-side and verified on the JIL L1 ledger with 14-of-20 validator consensus.

Base URLs

EnvironmentBase URL
Productionhttps://wallet.jilsovereign.com
Devnethttps://devnet-wallet.jilsovereign.com
Local (Docker)http://wallet-api:8002

Protocol

PropertyValue
TransportHTTPS (TLS 1.3 required in production)
Content Typeapplication/json
Character EncodingUTF-8
Date FormatISO 8601 (2026-02-09T14:30:00.000Z)
Amount PrecisionString-encoded decimals (e.g. "1500.00")
Auth ModelJWT Bearer token + WebAuthn passkey signing
Devnet Access: Contact developers@jilsovereign.com to provision your API key and sandbox credentials before integrating.

Authentication

The Wallet API uses a two-layer authentication model. All requests require a JWT Bearer token. Transaction signing additionally requires a WebAuthn passkey challenge-response.

Bearer Token

Include the JWT token in the Authorization header with every request.

GET /wallet/balances HTTP/1.1 Host: wallet.jilsovereign.com Authorization: Bearer eyJhbGciOiJFZERTQSIs... Content-Type: application/json

Two-Phase Transaction Signing

Sending funds uses a two-phase flow: (1) request a WebAuthn challenge via /send/options, (2) sign with the user's passkey and submit via /send/submit. This ensures the user's private key never leaves their device.

Token Expiry: JWT tokens expire after 7 days. Refresh tokens are not supported. The user must re-authenticate to obtain a new token.

Passkey Management

POST /auth/passkey/register/options

Get WebAuthn registration options for creating a new passkey. Returns the challenge and public key creation parameters for the authenticator.

Response (200 OK)

{ "publicKey": { "challenge": "base64url-encoded-challenge", "rp": { "name": "JIL Sovereign", "id": "jilsovereign.com" }, "user": { "id": "base64url-user-id", "name": "user@example.com", "displayName": "Alice" }, "pubKeyCredParams": [ { "type": "public-key", "alg": -7 }, { "type": "public-key", "alg": -257 } ], "authenticatorSelection": { "userVerification": "required", "residentKey": "preferred" }, "timeout": 60000 } }

POST /auth/passkey/register/verify

Verify and complete passkey registration with the attestation response from the authenticator.

Request Body

FieldTypeRequiredDescription
idstringrequiredCredential ID from the authenticator
rawIdstringrequiredRaw credential ID (base64url-encoded)
response.clientDataJSONstringrequiredClient data JSON from authenticator (base64url)
response.attestationObjectstringrequiredAttestation object from authenticator (base64url)

Response (200 OK)

{ "verified": true, "credentialId": "cred_a1b2c3d4e5f6", "publicKey": "base64url-encoded-public-key" }

POST /auth/passkey/authenticate/options

Get WebAuthn authentication options for signing in with an existing passkey.

Response (200 OK)

{ "publicKey": { "challenge": "base64url-encoded-challenge", "rpId": "jilsovereign.com", "allowCredentials": [ { "type": "public-key", "id": "base64url-cred-id" } ], "userVerification": "required", "timeout": 60000 } }

POST /auth/passkey/authenticate/verify

Verify the passkey assertion and issue a JWT Bearer token.

Response (200 OK)

{ "token": "eyJhbGciOiJFZERTQSIs...", "expiresAt": "2026-02-16T14:30:00.000Z", "accountId": "acc_abc123" }

Wallet Operations

GET /wallet/balances

Get all asset balances for the authenticated user's wallet. Returns each asset with its balance and USD value.

Response (200 OK)

{ "accountId": "acc_abc123", "zone": "protected", "assets": [ { "symbol": "JIL", "balance": "1000.00", "usdValue": "2500.00" }, { "symbol": "USDC", "balance": "500.00", "usdValue": "500.00" }, { "symbol": "jBTC", "balance": "0.25000000", "usdValue": "24500.00" } ], "protectionTier": "premium", "protectionCoverage": "250000.00" }

POST /wallet/send/options

Get transaction intent and WebAuthn challenge for signing a payment. This is the first step of the two-phase send flow. The challenge must be signed by the user's passkey before submitting.

Request Body

FieldTypeRequiredDescription
tostringrequiredRecipient address or @handle (e.g., "@alice.jil")
assetstringrequiredAsset symbol (e.g. JIL, USDC, jBTC)
amountstringrequiredAmount to send as decimal string (e.g. "100.00")
memostringoptionalOptional memo (max 256 characters)

Response (200 OK)

{ "txIntent": { "from": "acc_abc123", "to": "jil1xyz...", "asset": "JIL", "amount": "100.00", "nonce": 42 }, "challenge": { "publicKey": { "challenge": "base64url-challenge", "rpId": "jilsovereign.com", "userVerification": "required" } }, "estimatedFee": "0.01" }

POST /wallet/send/submit

Submit a signed payment transaction. Requires the transaction intent from /send/options and the signed WebAuthn credential from the user's passkey.

Request Body

FieldTypeRequiredDescription
txIntentobjectrequiredTransaction intent object from /send/options
credentialobjectrequiredWebAuthn assertion response from passkey signing

Response (200 OK)

{ "txId": "tx_7f3a9b2c4e1d", "status": "confirmed", "receipt": { "blockHeight": 1000042, "blockHash": "0x4e7f1a...c3d8b2", "timestamp": "2026-02-09T14:30:02.345Z", "gasUsed": 21000 }, "from": "acc_abc123", "to": "jil1xyz...", "asset": "JIL", "amount": "100.00" }

GET /wallet/transactions

Get transaction history for the authenticated wallet. Supports cursor-based pagination and filtering.

Query Parameters

ParameterTypeRequiredDescription
cursorstringoptionalCursor for pagination. Omit for first page.
limitintegeroptionalResults per page, 1-100. Default: 25.
assetstringoptionalFilter by asset symbol.

Response (200 OK)

{ "transactions": [ { "txId": "tx_7f3a9b2c4e1d", "type": "send", "asset": "JIL", "amount": "-100.00", "to": "@alice.jil", "status": "confirmed", "timestamp": "2026-02-09T14:30:02.345Z" } ], "pagination": { "cursor": "eyJpZCI6InR4XzdmM2E5YjJjNGUxZCJ9", "has_more": true } }

GET /wallet/wrapped-assets

List available wrapper tokens (jBTC, jETH, jUSDC) with deposit and unwrap information.

Response (200 OK)

{ "assets": [ { "id": "jbtc-eth-wbtc", "symbol": "jBTC", "originalChain": "eth", "originalToken": "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", "decimals": 8 }, { "id": "jeth-eth-native", "symbol": "jETH", "originalChain": "eth", "originalToken": "0x0000000000000000000000000000000000000000", "decimals": 18 }, { "id": "jusdc-eth-usdc", "symbol": "jUSDC", "originalChain": "eth", "originalToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "decimals": 6 } ] }
Wrapper Tokens: jBTC, jETH, and jUSDC are 1:1 backed wrapper tokens. Deposits on the external chain are confirmed and auto-minted on the JIL L1. Unwraps burn the wrapper and release the original asset on the external chain.

Handle Resolution

GET /handle/resolve/{handle}

Resolve a human-readable @handle to its on-chain address. No authentication required.

Path Parameters

ParameterTypeRequiredDescription
handlestringrequiredHandle without @ prefix (e.g., "alice.jil")

Response (200 OK)

{ "handle": "alice.jil", "address": "jil1abc123def456...", "profile": { "displayName": "Alice", "avatar": "https://cdn.jilsovereign.com/avatars/alice.jpg" } }

POST /handle/register

Register a new @handle for the authenticated user's account.

Request Body

FieldTypeRequiredDescription
handlestringrequiredHandle (3-20 chars, lowercase alphanumeric + underscore)
displayNamestringoptionalDisplay name (max 50 characters)

Response (201 Created)

{ "handle": "alice.jil", "address": "jil1abc123def456...", "registered_at": "2026-02-09T14:30:00.000Z" }
Handle Rules: Handles must be 3-20 characters, lowercase alphanumeric with underscores allowed. Each account can register one handle. Handles are globally unique and cannot be transferred.

Account Recovery

Social recovery enables account access restoration through guardian approvals with a 24-hour timelock. This protects against unauthorized recovery attempts while ensuring legitimate users can always regain access.

POST /wallet/recovery/start

Initiate an account recovery ceremony. This begins the social recovery process with a 24-hour timelock. No authentication required (the user has lost access).

Request Body

FieldTypeRequiredDescription
accountIdstringrequiredAccount ID to recover
newCredentialCommitmentstringrequiredSHA-256 hash of the new passkey public key

Response (201 Created)

{ "ceremonyId": "rec_xyz789", "requiredApprovals": 2, "currentApprovals": 0, "guardians": [ { "id": "grd_001", "name": "Bob", "approved": false }, { "id": "grd_002", "name": "Carol", "approved": false }, { "id": "grd_003", "name": "Dave", "approved": false } ], "timelockEndsAt": "2026-02-10T14:30:00.000Z", "expiresAt": "2026-02-16T14:30:00.000Z" }
24-Hour Timelock: Even after all guardian approvals are collected, the recovery cannot be finalized until the 24-hour timelock expires. This gives the legitimate account holder time to cancel a fraudulent recovery attempt.

POST /wallet/recovery/approve

Submit a guardian approval for an ongoing recovery ceremony. Each guardian signs with their Ed25519 key.

Request Body

FieldTypeRequiredDescription
ceremonyIdstringrequiredRecovery ceremony ID
guardianIdstringrequiredGuardian's identifier
guardianSignaturestringrequiredEd25519 signature over the ceremony ID + new credential commitment

Response (200 OK)

{ "ceremonyId": "rec_xyz789", "approved": true, "currentApprovals": 1, "requiredApprovals": 2 }

POST /wallet/recovery/finalize

Finalize recovery after the timelock expires and required approvals are met. Binds the new credential to the account and revokes all previous passkeys.

Request Body

FieldTypeRequiredDescription
ceremonyIdstringrequiredRecovery ceremony ID
newCredentialobjectrequiredWebAuthn attestation response for the new passkey

Response (200 OK)

{ "ceremonyId": "rec_xyz789", "status": "completed", "accountId": "acc_abc123", "newCredentialId": "cred_new_x1y2z3", "revokedCredentials": 1, "completedAt": "2026-02-10T14:30:05.000Z" }

Transaction Proofs

GET /api/proof/tx/{txId}

Get a cryptographic proof receipt for a transaction. Includes the Merkle inclusion proof and 14-of-20 validator quorum signatures. No authentication required.

Path Parameters

ParameterTypeRequiredDescription
txIdstringrequiredTransaction ID (e.g. tx_7f3a9b2c4e1d)

Response (200 OK)

{ "txId": "tx_7f3a9b2c4e1d", "receipt": { "blockHeight": 1000042, "blockHash": "0x4e7f1a...c3d8b2", "stateRoot": "0x3c1d8a...f2e7b9", "merkleProof": ["0xa1...", "0xb2...", "0xc3..."], "quorumSignatures": { "threshold": "14-of-20", "signers": 14, "signatures": [ { "validator": "val_de_01", "jurisdiction": "DE", "signature": "0xed25519..." } ] } }, "timestamp": "2026-02-09T14:30:02.345Z" }
Proof Verification: Transaction proofs can be independently verified by any party using the validator public keys published in the genesis block. The Merkle proof confirms transaction inclusion, and the quorum signatures confirm validator consensus.

Error Codes

All error responses include a JSON body with error containing code, message, and optional details fields.

Error Response Format

{ "error": { "code": "INSUFFICIENT_BALANCE", "message": "Account has insufficient funds for this transaction", "details": { "available": "50.00", "required": "100.00", "asset": "JIL" } } }

HTTP Status Codes

StatusError CodeDescription
400VALIDATION_ERRORRequest body failed Zod schema validation. Check details for field-level errors.
401UNAUTHORIZEDMissing, invalid, or expired JWT Bearer token.
401PASSKEY_VERIFICATION_FAILEDWebAuthn assertion verification failed.
404ACCOUNT_NOT_FOUNDAccount ID does not exist.
404HANDLE_NOT_FOUNDHandle does not exist in the registry.
404TRANSACTION_NOT_FOUNDTransaction ID does not exist.
409HANDLE_ALREADY_TAKENThe requested handle is already registered.
422INSUFFICIENT_BALANCEAccount does not have enough funds for the requested transfer.
423TIMELOCK_NOT_EXPIREDRecovery finalization attempted before 24-hour timelock expired.
429RATE_LIMIT_EXCEEDEDToo many requests. Includes retry_after_ms field.

Code Examples

Complete JavaScript/TypeScript examples for common wallet operations.

1. Authenticate with Passkey

// Step 1: Get authentication options const optionsRes = await fetch('https://wallet.jilsovereign.com/auth/passkey/authenticate/options', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'alice@example.com' }), }); const { publicKey } = await optionsRes.json(); // Step 2: Sign with passkey (browser API) const credential = await navigator.credentials.get({ publicKey }); // Step 3: Verify and get JWT const verifyRes = await fetch('https://wallet.jilsovereign.com/auth/passkey/authenticate/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credential), }); const { token } = await verifyRes.json();

2. Send Funds (Two-Phase)

// Phase 1: Get challenge const optionsRes = await fetch('/wallet/send/options', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ to: '@alice.jil', asset: 'JIL', amount: '100.00', memo: 'Coffee money', }), }); const { txIntent, challenge } = await optionsRes.json(); // Phase 2: Sign with passkey and submit const credential = await navigator.credentials.get({ publicKey: challenge.publicKey }); const submitRes = await fetch('/wallet/send/submit', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ txIntent, credential }), }); const result = await submitRes.json(); console.log('TX confirmed:', result.txId);

3. Check Balances

const res = await fetch('/wallet/balances', { headers: { 'Authorization': `Bearer ${token}` }, }); const { assets, protectionTier, protectionCoverage } = await res.json(); assets.forEach(a => { console.log(`${a.symbol}: ${a.balance} ($${a.usdValue})`); }); console.log(`Protection: ${protectionTier} ($${protectionCoverage} coverage)`);

4. Resolve an @Handle

const res = await fetch('/handle/resolve/alice.jil'); const { address, profile } = await res.json(); console.log(`@alice.jil resolves to ${address}`); console.log(`Display name: ${profile.displayName}`);

5. Start Social Recovery

// Initiate recovery ceremony const res = await fetch('/wallet/recovery/start', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ accountId: 'acc_abc123', newCredentialCommitment: 'sha256-hash-of-new-pubkey', }), }); const ceremony = await res.json(); console.log(`Ceremony: ${ceremony.ceremonyId}`); console.log(`Need ${ceremony.requiredApprovals} of ${ceremony.guardians.length} guardians`); console.log(`Timelock ends: ${ceremony.timelockEndsAt}`);