Sign in with a crypto wallet

Let a user sign in with a crypto wallet they already own — MetaMask, Rainbow, Ledger — as their identity here. One endpoint covers both first time and every time after. 1. `GET /auth/challenge` — get a one-off string to sign. 2. Ask their wallet to sign it with `personal_sign`. 3. Send the signature here. We recover the address from the signature, so nothing but the signature itself proves who they are. On a first connection we create their Dual wallet for them; afterwards we recognise the address and start a new session. Either way the response carries their wallet and a session. No sign-in needed — this is the sign-in.

POST
/wallets/connect/eoa

Request Body

application/json

EOA connect payload — challenge, signature, and optional metadata.

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

curl -X POST "https://api.dual.network/wallets/connect/eoa" \  -H "Content-Type: application/json" \  -d '{    "auth": {      "challenge": "<challenge returned by GET /auth/challenge>",      "signature": "<0x-prefixed 65-byte signature>"    },    "organization_id": "665f1c2d4b1a2c3d4e5f6aa0",    "nickname": "alexf",    "email": "alex@example.com",    "language": "en"  }'
{
  "wallet": {
    "id": "665f1c2d4b1a2c3d4e5f6a01",
    "fqdn": "dual.network",
    "email": "alex@example.com",
    "nickname": "alexf",
    "language": "en",
    "activated": true,
    "disabled": false,
    "account": {
      "address": "0x1234567890abcdef1234567890abcdef12345678",
      "type": "SMART_WALLET",
      "controller": {
        "address": "0x9876543210fedcba9876543210fedcba98765432",
        "type": "SECP256K1",
        "custody": "self-custodial"
      },
      "smart_account": {
        "chain_id": 42161,
        "factory": "0x7777777777777777777777777777777777777777",
        "implementation": "0x8888888888888888888888888888888888888888",
        "index": 0,
        "validator": "0x9999999999999999999999999999999999999999",
        "validator_type": "ECDSA",
        "version": "0.3.1"
      }
    },
    "when_created": "2026-02-01T09:00:00Z",
    "when_modified": "2026-02-01T09:00:00Z"
  },
  "access_token": "<access token>",
  "refresh_token": "<refresh token>"
}
{
  "code": 3,
  "message": "Key: 'limit' Error:Field validation for 'limit' failed on the 'lte' tag"
}
{
  "code": 3,
  "message": "limit must be 25 or less",
  "details": {}
}
{
  "code": 13,
  "message": "internal error"
}