> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qfex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Builder wallet registration

> Use a wallet signature to create a main-account trading key for a builder's users.

Builders can onboard an Ethereum wallet user and receive a trading-only QFEX API key with two endpoints:

| Endpoint | Authentication | Result |
| - | - | - |
| `GET /builder/web3/message` | Public | The wallet message to sign |
| `POST /builder/web3/api-key` | Builder API key with `register_user` | The user's trading key and account identifiers |

The examples use the pre-production REST API at `https://api.qfex.io` and Trade WebSocket at `wss://trade.qfex.io`. Use the environment URLs and credentials supplied by QFEX for your integration.

This flow returns API keys. [Wallet session authentication](/api-reference/wallet-authentication) returns access and refresh tokens; [OAuth](/api-reference/builder-integration) uses an application authorization grant.

## Before you start

Your builder needs:

* A QFEX account with an active [builder code](/api-reference/builder-codes).
* A username on that account, used in the generated key's label.
* An existing API public key and secret owned by the builder-code account.
* The `register_user` permission enabled on that builder API key. Contact [support@qfex.com](mailto:support@qfex.com) to enable it; `subaccount_ops` does not grant registration access.

Keep the builder secret on your backend. The browser needs the builder code and the selected wallet provider, such as Rabby discovered through EIP-6963. It sends the signed message to your backend, which authenticates the registration request to QFEX.

## 1. Fetch the wallet message

Pass the user's Ethereum address and your builder-code UUID:

```sh theme={null}
curl --get 'https://api.qfex.io/builder/web3/message' \
  --data-urlencode 'address=0x0000000000000000000000000000000000000001' \
  --data-urlencode 'builder_code=aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa'
```

The `200 OK` response contains a `message` string. For example:

```text theme={null}
www.qfex.com wants you to sign in with your Ethereum account:
0x0000000000000000000000000000000000000001

Authorize QFEX to create a trading-only API key for builder aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa on my main account.

URI: https://www.qfex.com
Version: 1
Chain ID: 1
Nonce: 27c41a80ab6a61cf914075b1caffe3d8
Issued At: 2026-10-01T12:00:00Z
Expiration Time: 2026-10-01T12:10:00Z
```

This example is illustrative. Sign the actual `message` returned by the endpoint, unchanged. The domain and URI are configured by QFEX for the environment; do not replace them with your application's URL.

The endpoint generates a fresh nonce and a ten-minute validity window. Fetch a new message if signing or submission takes too long. Returning a message does not establish that a builder exists or is active; those checks happen during registration.

## 2. Ask the wallet to sign

Use the EIP-1193 provider selected by the user. Rabby supports this interface. When multiple wallets are installed, use EIP-6963 discovery rather than assuming `window.ethereum` is the selected wallet.

```javascript theme={null}
export async function signBuilderMessage({ provider, builderCode }) {
  const accounts = await provider.request({ method: "eth_requestAccounts" });
  const address = accounts?.[0];
  if (!address) throw new Error("The wallet did not return an address");

  const chainId = await provider.request({ method: "eth_chainId" });
  if (Number.parseInt(chainId, 16) !== 1) {
    await provider.request({
      method: "wallet_switchEthereumChain",
      params: [{ chainId: "0x1" }],
    });
  }

  const url = new URL("https://api.qfex.io/builder/web3/message");
  url.searchParams.set("address", address);
  url.searchParams.set("builder_code", builderCode);

  const response = await fetch(url, { cache: "no-store", credentials: "omit" });
  const result = await response.json();
  if (!response.ok) {
    throw new Error(result.detail ?? "Could not fetch the wallet message");
  }

  const message = result.message;
  const hexMessage = "0x" + Array.from(
    new TextEncoder().encode(message),
    (byte) => byte.toString(16).padStart(2, "0"),
  ).join("");

  const signature = await provider.request({
    method: "personal_sign",
    params: [hexMessage, address],
  });

  return { message, signature };
}
```

Show the returned message to the user before they authorize it. Signing proves wallet ownership and authorizes trading-key creation; it does not submit an on-chain transaction or require gas.

Send the returned `{ message, signature }` to your backend. Do not add the words `Wallet signature` or the signature itself to the message string.

## 3. Register the user and create the trading key

Your backend calls `POST /builder/web3/api-key` using the builder's credentials and four HMAC headers:

| Header | Value |
| - | - |
| `x-qfex-public-key` | Builder's existing API public key |
| `x-qfex-nonce` | Fresh cryptographically random hex nonce for this HTTP request |
| `x-qfex-timestamp` | Current Unix time in seconds |
| `x-qfex-hmac-signature` | Hex-encoded HMAC-SHA256 of `nonce:timestamp`, using the builder's secret |

The HTTP nonce is separate from the nonce inside the wallet message. Generate new HMAC headers for each request or retry.

Example for a Node.js backend:

```javascript theme={null}
import crypto from "node:crypto";

export async function registerWalletUser({ message, signature }) {
  const publicKey = process.env.QFEX_BUILDER_PUBLIC_KEY;
  const secretKey = process.env.QFEX_BUILDER_SECRET_KEY;
  if (!publicKey || !secretKey) {
    throw new Error("Builder credentials are not configured");
  }

  const nonce = crypto.randomBytes(16).toString("hex");
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const hmacSignature = crypto.createHmac("sha256", secretKey)
    .update(`${nonce}:${timestamp}`)
    .digest("hex");

  const response = await fetch("https://api.qfex.io/builder/web3/api-key", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-qfex-public-key": publicKey,
      "x-qfex-nonce": nonce,
      "x-qfex-timestamp": timestamp,
      "x-qfex-hmac-signature": hmacSignature,
    },
    body: JSON.stringify({ message, signature }),
    cache: "no-store",
  });

  const result = await response.json();
  if (!response.ok) {
    throw new Error(result.detail ?? "Could not create the trading key");
  }
  return result;
}
```

The body contains only `message` and `signature`. QFEX resolves the builder code from the authenticated builder account and requires the signed message to authorize that exact code. Use the builder's own API key for this request, rather than an end user's trading key.

The wallet signature must match the address in the message. The message must have been issued within ten minutes, must not be expired or issued in the future, and must use a domain and URI accepted by QFEX Auth.

A previously unseen wallet creates a QFEX user and main account through wallet signup. An existing wallet resumes its existing user. Account creation must complete before the key can be issued.

## Response and key permissions

Successful creation returns `201 Created` with `Cache-Control: no-store`:

```json theme={null}
{
  "user_id": "11111111-1111-4111-8111-111111111111",
  "account_id": "11111111-1111-4111-8111-111111111111",
  "public_key": "qfex_pub_EXAMPLE",
  "secret_key": "qfex_secret_EXAMPLE"
}
```

| Property | Meaning |
| - | - |
| `user_id` | QFEX user associated with the signing wallet |
| `account_id` | That user's main account; equal to `user_id` |
| `public_key` | The end user's new API public key |
| `secret_key` | The end user's new API secret, returned at creation |

The key grants order execution and read access to orders, positions, and balances on the main account. Withdrawals are disabled. Subaccounts are outside its scope. Access and refresh tokens are not returned by this endpoint.

Store the response credentials securely and avoid logging or caching them. They authenticate subsequent trading requests as the wallet user; they do not replace the builder's registration credentials.

## Replacement and trading attribution

The key is labelled `builder-` followed by the builder account's username, for example `builder-alice`. Issuing another key for the same user and builder label deletes the previous key and its stored secret. A failed key replacement rolls back and preserves the previous key. Keys for other users and other builder labels remain unaffected.

There is no automatic revocation immediately after use. A successful replacement invalidates the previous key for subsequent authentication. Keys whose labels start with `builder-` or `managed` are excluded from the user's normal API-key listing.

Use the returned user key with [HMAC Trade WebSocket authentication](/websocket/channels/trade/authenticate), and include your builder-code UUID in `params.builder_code`. The key label alone does not attribute orders to the builder. Attach the code again whenever you reconnect.

## Errors

| Status | Meaning and next step |
| - | - |
| `400` | Invalid address, builder UUID, or wallet message. Fetch a fresh message for the correct wallet and authenticated builder code. |
| `401` | Builder authentication or wallet authentication failed. Check HMAC credentials, the wallet signature, and message validity. The generic wallet error can also hide a signup failure; contact QFEX support if a fresh signed message still fails. |
| `403` | The builder key lacks `register_user`, or its account has no active builder code. |
| `422` | The request does not match the required query parameters or JSON schema. |
| `502` | QFEX could not load the builder or issue the key. The builder username and user's main account must exist; contact QFEX support if the error persists. |

For direct browser calls, the API must allow your origin and request headers through CORS. The backend handles the underlying Auth exchange; this builder flow does not require your frontend to call `/auth/v1/token?grant_type=web3` or hold an Auth client key.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.