---
name: IBEx Wallet API
description: Advanced self-custody wallet API. Build gasless, passwordless dApps using FIDO2 passkeys + Safe Global wallet or EOA with account abstraction EIP-7702.
---

# IBEx Wallet API

## When to Use This Skill

Use this skill when you need to:
- Create or authenticate blockchain wallet users via passkeys (FIDO2)
- Deploy on-chain Safe Global wallets
- Query balances, transactions, or pool positions
- Execute token transfers or swaps
- Manage wallet recovery
- Store/retrieve private user data (GDPR-compliant)

Do NOT use this skill for:
- Direct smart contract interactions (use the deployed wallet instead)
- Non-EVM blockchains (currently EVM-only via Safe Global)

## Machine-Readable Resources

| Resource | URL | Description |
|----------|-----|-------------|
| OpenAPI Spec | `/openapi.json` | Full OpenAPI 3.0 specification (JSON) |
| LLM Index | `/docs/llms.txt` | Markdown index of all endpoints for LLM consumption |
| Full Docs | `/docs/llms-full.txt` | Complete API docs in a single Markdown file |
| MCP Server | `npx -y @chr33s/mcpdoc <base>/docs/llms.txt` | For Cursor, Windsurf, Claude |

## Structural Overview

### Architecture

```
Client (dApp)
  └── IBEx.Fi API (REST + WebSocket)
        ├── Authentication (FIDO2 passkeys → JWT)
        ├── Wallet Operations (Safe Global, ERC-4337)
        ├── User Data (balances, transactions, pools, signers, IBANs)
        ├── DeFi (CoW Swap, 1inch, AAVE lending)
        ├── KYC (iframe integration)
        └── Admin (chain info, tokens, user lookup)
```

### Core Objects

| Object | Description |
|--------|-------------|
| **User** | Identified by passkey. Has a JWT session. Scoped to an rpId (tenant). |
| **Wallet** | Safe Global smart contract wallet. Deployed on-chain. Controlled by user's passkey-derived key. |
| **rpId** | Tenant identifier. Passed via query param (`?rpId=...`), header (`X-RpId`), or Origin header. Must be registered with IBEx. |
| **Builder** | Tenant operator. Manages IBX token balance for service fees. |
| **Session** | JWT token issued on sign-up/sign-in. Required for all authenticated endpoints. |

### Authentication

All authenticated requests require a JWT Bearer token:
```
Authorization: Bearer <jwt>
```

The JWT is obtained via the sign-up or sign-in flow. Refresh with `POST /v1.2/auth/refresh`.

Tenant identification uses an API key:
```
x-api-key: <api-key>
```

Chain selection (optional, defaults to config):
```
X-Blockchain-Id: <chain-id>
```

### Base URL & rpId

The rpId identifies your tenant. It must be registered with IBEx (contact@ibex.fi).
Pass it to every request via one of:
- Query parameter: `?rpId=yourdomain.com`
- Header: `X-RpId: yourdomain.com`
- Origin header (automatic in browser requests)

If the rpId is missing or unregistered, auth endpoints return `400 Unknown domain/rpId`.
See `/docs/guides/integration/rpid` for details.

**Local development exception:** On development environments, `localhost`, `127.0.0.1`, and `[::1]` are automatically recognized as valid origins with `rpId: "localhost"`. No registration or DNS setup is needed — you can test directly from `http://localhost:3000` (or any port). Passkeys created on localhost are scoped to development and will not carry over to production.

## Core Workflows

### 1. Sign Up a New User

```
GET  /v1.2/auth/sign-up
     → Returns FIDO2 challenge (publicKey options)

POST /v1.2/auth/sign-up
     Body: { credential: <attestation response from WebAuthn> }
     → Returns { access_token, refresh_token, token_type: "Bearer", expires_in, safeAddress, ... }
```

### 2. Sign In an Existing User

```
GET  /v1.2/auth/sign-in
     → Returns FIDO2 assertion challenge (credentialRequestOptions)

POST /v1.2/auth/sign-in
     Body: { credential: <assertion response from WebAuthn> }
     → Returns { access_token, refresh_token, token_type: "Bearer", expires_in, safeAddress, ... }
```

**Important — PRF extension (sign-in passkeys mode):**
The `extensions.prf.eval.first` value in `GET /sign-in` response is a **base64url-encoded string**.
Before passing to `navigator.credentials.get()`, decode it to `ArrayBuffer`:

```js
import { base64URLStringToBuffer } from '@simplewebauthn/browser';
const opts = response.credentialRequestOptions;
if (opts.extensions?.prf?.eval?.first) {
    opts.extensions.prf.eval.first = base64URLStringToBuffer(opts.extensions.prf.eval.first);
    if (opts.extensions.prf.eval.second)
        opts.extensions.prf.eval.second = base64URLStringToBuffer(opts.extensions.prf.eval.second);
}
```

Without this conversion, the browser throws: `The provided value is not of type '(ArrayBuffer or ArrayBufferView)'`.

### 3. Refresh Session

```
POST /v1.2/auth/refresh
     Authorization: Bearer <jwt>
     → Returns { access_token, refresh_token, token_type: "Bearer", expires_in }
```

### 4. Deploy a Wallet

Wallet deployment happens automatically during sign-up. The Safe Global wallet is deployed on-chain using ERC-4337 (gasless).

### 5. Get User Profile

```
GET /v1.2/users/me
    Authorization: Bearer <jwt>
    → Response (200):
    {
      "id": "c76302cb-f845-40f4-9c56-29710323afba",
      "ky": "2",
      "signers": [
        {
          "id": "AVZs0qRCBSmfThZWu37g...",
          "safes": [
            {
              "address": "0xd676c6188195372EC269E9C2cAf815C56436A679",
              "threshold": 1,
              "iban": { "chainId": 421614 }
            }
          ]
        }
      ]
    }
```

### 6. Check Balances

```
GET /v1.2/users/me/balances
    Authorization: Bearer <jwt>
    X-Blockchain-Id: <chain-id>  (optional)
    → Response (200):
    [
      {
        "token": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
        "symbol": "ETH",
        "balance": "0.042",
        "decimals": 18,
        "blockchainId": 421614
      },
      {
        "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
        "symbol": "USDC",
        "balance": "150.25",
        "decimals": 6,
        "blockchainId": 421614
      }
    ]
```

### 7. Get Wallet Address

```
GET /v1.2/users/me/address
    Authorization: Bearer <jwt>
    → Response (200):
    {
      "address": "0xd676c6188195372EC269E9C2cAf815C56436A679",
      "blockchainId": 421614
    }
```

### 8. Transfer Tokens

```
POST /v1.2/safes/operations
     Authorization: Bearer <jwt>
     Body: {
       "type": "TRANSFER_TOKEN",
       "to": "0x...",
       "token": "0x...",
       "amount": "1.5",
       "blockchainId": 100
     }
     → Returns operation status
```

### 9. Swap Tokens

```
GET /v1.2/safes/swap/quote
    Authorization: Bearer <jwt>
    ?srcToken=0x...&dstToken=0x...&amount=100&blockchainId=100
    → Returns swap quote from COWSWAP, 1INCH or both
```

### 10. Get Transaction History

```
GET /v1.2/users/me/transactions
    Authorization: Bearer <jwt>
    X-Blockchain-Id: <chain-id>  (optional)
    → Returns array of past transactions
```

## Reference

### API Version

All public endpoints use the `/v1.2/` prefix. Legacy `/v1/` and `/v1.1/` paths are retained for backward compatibility but are not documented.

### Endpoint Categories

| Category | Endpoints | Auth |
|----------|-----------|------|
| Authentication | `GET/POST /v1.2/auth/sign-up`, `GET/POST /v1.2/auth/sign-in`, `POST /v1.2/auth/refresh`, `POST /v1.2/auth/email/recover` | None (sign-up/in), JWT (refresh) |
| User Data | `GET /v1.2/users/me`, `/me/address`, `/me/balances`, `/me/transactions`, `/me/pools`, `/me/lending`, `/me/ibans`, `/me/signers`, `/me/chainid` | JWT |
| Blockchain Ops | `POST /v1.2/safes/operations`, `GET /v1.2/safes/swap/quote`, `PUT /v1.2/safes/{addr}/automation-module/config` | JWT |
| Bitcoin | `POST /v1.2/safes/bitcoin/send/prepare`, `POST /v1.2/safes/bitcoin/tx/broadcast`, `GET /v1.2/safes/bitcoin/fees`, `GET /v1.2/safes/bitcoin/utxos`, `GET /v1.2/safes/bitcoin/info` | JWT |
| KYC | `POST /v1.2/auth/iframe`, `GET /v1.2/users/kyc/status` | JWT |
| Recovery | `GET /v1.2/recovery/status/{safeAddress}` | JWT |
| Admin | `GET /v1.2/users/{id}`, `GET /v1.2/chain/tokens`, `GET /v1.2/chain/id` | API Key |
| Health | `GET /health` | None |

### Error Handling

| Status | Meaning |
|--------|---------|
| 400 | Validation error (bad request body/params) |
| 401 | Unauthorized (missing/invalid JWT) |
| 403 | Forbidden (wrong rpId or insufficient permissions) |
| 404 | Not found |
| 429 | Rate limited |
| 503 | Service temporarily unavailable (maintenance mode) |

### OpenAPI Spec

The full OpenAPI 3.0 specification is available at:
```
GET /openapi.json      # v1.2 endpoints
```

### Supported Chains

The API supports multiple EVM chains. Query available chains via `GET /v1.2/chain/id`. Common chain IDs:
- `1` — Ethereum Mainnet
- `100` — Gnosis
- `137` — Polygon
- `8453` — Base
- `42161` — Arbitrum One
- `421614` — Arbitrum Sepolia (testnet)
