Deploy Wallet - Integration Guide

This guide explains how to deploy a wallet for your users, covering signup, signin, refresh token, and wallet recovery.

📊 View Flow Diagram

1. Sign Up

Sign up allows a new user to create an account. The v1.2 API supports three different signup scenarios:

Scenario 1: Simple ExternalUserId Creation

Endpoint : GET /v1.2/auth/sign-up?passkeys=FALSE

Query Parameters :

Request Example :

GET /v1.2/auth/sign-up?passkeys=FALSE

Response (200 OK) :

{
  "userId": "<userId>",
  "externalUserId": "<externalUserId>",
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "issuer": "foo.domain",
  "audience": "foo.domain",
  "subject": "<externalUserId>",
  "roles": ["USER"],
  "hasPasskey": false,
  "emailValidationRequired": false
}
💡 Note : This creates an account immediately without requiring biometric authentication. The user can add a passkey later by calling POST /v1.2/auth/sign-up with the externalUserId and a credential.

Scenario 2: Email Validation

Endpoint : GET /v1.2/auth/sign-up?passkeys=FALSE&email=user@example.com

Query Parameters :

Request Example :

GET /v1.2/auth/sign-up?passkeys=FALSE&email=user@example.com

Response (200 OK) :

{
  "userId": "<userId>",
  "externalUserId": "<externalUserId>",
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "emailValidationRequired": true
}

Complete Email Validation (POST) :

POST /v1.2/auth/sign-up
Content-Type: application/json

{
  "emailCode": "123456",
  "externalUserId": "<externalUserId>"
}

Scenario 3: Passkey Creation (Default)

Endpoint : GET /v1.2/auth/sign-up or GET /v1.2/auth/sign-up?passkeys=TRUE

Optional Parameters (query string) :

Request Example :

GET /v1.2/auth/sign-up?user.name=jane.doe@foo.domain&user.displayname=Jane%20Doe&email=user@example.com

Response (200 OK) :

{
  "credentialRequestOptions": {
    "rp": { "id": "foo.domain", "name": "foo.domain" },
    "user": { 
      "id": "<base64url>", 
      "name": "foo.domain <shortId>", 
      "displayName": "foo.domain <shortId>" 
    },
    "challenge": "<base64url>",
    "pubKeyCredParams": [{ "alg": -7, "type": "public-key" }],
    "authenticatorSelection": { 
      "residentKey": "preferred", 
      "userVerification": "preferred" 
    },
    "attestation": "none",
    "timeout": 60000
  },
  "emailValidationRequired": true
}
⚠️ Important : Keep the complete credentialRequestOptions object. It will be needed for the next step. If emailValidationRequired is true, you can optionally validate the email in the POST request.

Step 2: Complete Sign Up with Passkey

Endpoint : POST /v1.2/auth/sign-up

Body (JSON) :

Request Example (Passkey only) :

POST /v1.2/auth/sign-up
Content-Type: application/json

{
  "credential": {
    "id": "AVZs0qRCBSmfThZWu37g...",
    "rawId": "AVZs0qRCBSmfThZWu37g...",
    "type": "public-key",
    "response": {
      "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVh...",
      "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoi..."
    }
  },
  "chainIds": [421614],
  "keyName": "my-passkey",
  "keyDisplayName": "My Passkey"
}

Request Example (Passkey + Email validation) :

POST /v1.2/auth/sign-up
Content-Type: application/json

{
  "credential": {
    "id": "AVZs0qRCBSmfThZWu37g...",
    "rawId": "AVZs0qRCBSmfThZWu37g...",
    "type": "public-key",
    "response": {
      "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVh...",
      "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoi..."
    }
  },
  "emailCode": "123456",
  "chainIds": [421614]
}

Request Example (Deferred passkey - add passkey to existing account) :

POST /v1.2/auth/sign-up
Content-Type: application/json

{
  "externalUserId": "<externalUserId-from-previous-signup>",
  "credential": {
    "id": "AVZs0qRCBSmfThZWu37g...",
    "rawId": "AVZs0qRCBSmfThZWu37g...",
    "type": "public-key",
    "response": {
      "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVh...",
      "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoi..."
    }
  },
  "chainIds": [421614]
}

Response (200 OK) :

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "issuer": "foo.domain",
  "audience": "foo.domain",
  "subject": "<externalUserId>",
  "roles": ["USER"],
  "safeAddress": { 
    "421614": "0xd676c6188195372EC269E9C2cAf815C56436A679"
  },
  "chainId": 421614,
  "keyName": "my-passkey",
  "keyDisplayName": "My Passkey"
}
💾 To Store :

2. Sign In

Sign in allows an existing user to authenticate. The v1.2 API supports three different sign-in scenarios:

Scenario 1: Passkey Authentication (Default)

Endpoint : GET /v1.2/auth/sign-in

Optional Parameters (query string) :

Request Example :

GET /v1.2/auth/sign-in?chainId=421614

Response (200 OK) :

{
  "credentialRequestOptions": {
    "challenge": "<base64url>",
    "rpId": "foo.domain",
    "userVerification": "required",
    "timeout": 60000
  }
}

Scenario 2: Email Authentication

Endpoint : GET /v1.2/auth/sign-in?email=user@example.com&externalUserId=<externalUserId>

Query Parameters :

Request Example :

GET /v1.2/auth/sign-in?email=user@example.com&externalUserId=abc-123-def

Response (200 OK) :

{
  "emailCodeSent": true,
  "email": "user@example.com",
  "message": "A validation code has been sent to your email"
}

Scenario 3: ExternalUserId Direct Sign-In

Endpoint : GET /v1.2/auth/sign-in?externalUserId=<externalUserId>

Query Parameters :

Request Example :

GET /v1.2/auth/sign-in?externalUserId=abc-123-def

Response (200 OK) :

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "issuer": "foo.domain",
  "audience": "foo.domain",
  "subject": "<externalUserId>",
  "roles": ["USER"],
  "authMethod": "EXTERNAL_USER_ID",
  "hasPasskey": false
}

Step 2: Complete Sign In

Endpoint : POST /v1.2/auth/sign-in

Body (JSON) :

Request Example (Passkey) :

POST /v1.2/auth/sign-in
Content-Type: application/json

{
  "credential": {
    "id": "AVZs0qRCBSmfThZWu37g...",
    "rawId": "AVZs0qRCBSmfThZWu37g...",
    "type": "public-key",
    "response": {
      "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2MFAAAAAQ...",
      "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoi...",
      "signature": "MEUCIQC..."
    }
  },
  "chainId": 421614,
  "includeBalance": true,
  "includeUserdata": true
}

Request Example (Email) :

POST /v1.2/auth/sign-in
Content-Type: application/json

{
  "emailCode": "123456",
  "email": "user@example.com",
  "externalUserId": "abc-123-def"
}

Response (200 OK) :

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "issuer": "foo.domain",
  "audience": "foo.domain",
  "subject": "<externalUserId>",
  "roles": ["USER"],
  "authMethod": "PASSKEY",
  "hasPasskey": true,
  "safeAddress": { 
    "421614": "0xd676c6188195372EC269E9C2cAf815C56436A679"
  },
  "chainId": 421614,
  "keyName": "my-passkey",
  "keyDisplayName": "My Passkey",
  "balance": { /* BCReader balances si includeBalance=true */ },
  "transactions": { /* Historique si includeTransactions=true */ },
  "userdata": { /* IBEX Safe userdata si includeUserdata=true */ }
}
💾 To Store : Same as signup, keep access_token, refresh_token, and subject.

3. Refresh Token

The refresh token allows you to obtain a new access_token before expiration (duration: ~1h).

Endpoint : POST /v1.2/auth/refresh

💡 Note : The refresh endpoint is the same for all API versions. You can use /v1.2/auth/refresh, /v1.1/auth/refresh, or /v1.2/auth/refresh.

Body (JSON) :

Request Example :

POST /v1.2/auth/refresh
Content-Type: application/json

{
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Response (200 OK) :

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "issuer": "foo.domain",
  "audience": "foo.domain",
  "subject": "<externalUserId>",
  "roles": ["USER"]
}
💡 Note : The refresh token issues a new access_token AND a new refresh_token. Replace both in your local storage.

4. Recovery

Recovery allows a user to configure a wallet recovery mechanism in case of access loss.

💡 Note : Recovery operations use the Safe operations endpoints. These are available in all API versions (v1, v1.1, v1.2).

Enable Recovery

Endpoint : POST /v1.2/safes/operations

Headers :

Body (JSON) :

ENABLE_RECOVERY Operation :

Request Example :

POST /v1.2/safes/operations
Content-Type: application/json
Authorization: Bearer <access_token>

{
  "safeAddress": "0xd676c6188195372EC269E9C2cAf815C56436A679",
  "operations": [
    {
      "type": "ENABLE_RECOVERY",
      "newOwners": ["0x303f215950f7B07Ae45FD98590d503E0242E7de3"],
      "threshold": 1,
      "firstName": "Jane",
      "lastName": "Doe",
      "birthDate": "1990-01-01",
      "birthCity": "Paris",
      "birthCountry": "France"
    }
  ]
}

Response (200 OK) :

{
  "credentialRequestOptions": {
    "challenge": "<base64url_encoded_userOpHash>",
    "rpId": "foo.domain",
    "userVerification": "required"
  }
}
⚠️ Important : After receiving credentialRequestOptions, you must:
  1. Use navigator.credentials.get() with these options to obtain a credential
  2. Send a PUT /v1.2/safes/operations request with the credential to finalize activation

Finalize Activation (PUT)

Endpoint : PUT /v1.2/safes/operations

Body (JSON) :

Request Example :

PUT /v1.2/safes/operations
Content-Type: application/json
Authorization: Bearer <access_token>

{
  "credential": {
    "id": "AVZs0qRCBSmfThZWu37g...",
    "rawId": "AVZs0qRCBSmfThZWu37g...",
    "type": "public-key",
    "response": {
      "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2MFAAAAAQ...",
      "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoi...",
      "signature": "MEUCIQC..."
    }
  }
}

Cancel Recovery

To cancel an ongoing recovery, use the CANCEL_RECOVERY operation:

Request Example :

POST /v1.2/safes/operations
Content-Type: application/json
Authorization: Bearer <access_token>

{
  "safeAddress": "0xd676c6188195372EC269E9C2cAf815C56436A679",
  "operations": [
    {
      "type": "CANCEL_RECOVERY"
    }
  ]
}
💡 Note : Recovery requires a delay (default 1 day = 86400 seconds) before it can be executed. Final execution is performed by the IBEX team via an administrative endpoint after the delay expires.

Summary of Data to Store

Technical Deep Dive - For AI Integration

This section provides detailed technical information for AI systems integrating the IBEX API, including architecture patterns, data models, and implementation details.

Architecture Overview

The IBEX API uses a multi-layered architecture:

Database Schema Relationships

The authentication and wallet system uses the following entity relationships:

Domain (rpId)
  └── ExternalUser (id, rpId, userId)
       └── Signer (id, externalUserId, type: PASSKEY)
            └── Safe (address, blockchainId, signerId, threshold)
                 ├── SafeOperation (userOpHash, status, safeAddress, blockchainId)
                 └── Iban (safeAddress, blockchainId, status)

User (id, ky, lock)
  └── ExternalUser (id, rpId, userId)
       └── Login (id, userId, signerId, safeAddress, type, event)

Key Relationships:

Sign Up Flow - Technical Details

Step 1: GET /v1.2/auth/sign-up

Step 2: POST /v1.2/auth/sign-up

Sign In Flow - Technical Details

Step 1: GET /v1.2/auth/sign-in

Step 2: POST /v1.2/auth/sign-in

Refresh Token Flow - Technical Details

POST /v1.2/auth/refresh

Recovery Flow - Technical Details

ENABLE_RECOVERY Operation

CANCEL_RECOVERY Operation

JWT Token Structure

Access Token Payload:

{
  "iss": "foo.domain",        // rpId (issuer)
  "aud": "foo.domain",        // rpId (audience)
  "sub": "<externalUserId>", // ExternalUser.id
  "roles": ["USER"],          // User role
  "iat": 1234567890,         // Issued at
  "exp": 1234571490          // Expires at (iat + 3600s)
}

Refresh Token Payload:

{
  "challenge": "<base64url>", // Challenge stored in Redis
  "iat": 1234567890,
  "exp": 1234571490
}

Safe Wallet Deployment

Address Computation:

Multi-Chain Support:

State Management

SafeOperation Status Flow:

Status Transitions:

WebSocket Integration

Connection Flow:

Error Handling Patterns

Key Implementation Files

← Back to Main Guide