> ## 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.

# Wallet authentication

> Sign up or sign in to QFEX programmatically with an Ethereum wallet and use the resulting session.

QFEX supports Sign-In with Ethereum (SIWE) using the EIP-4361 standard. A user proves ownership of an EVM address by signing a human-readable message. QFEX verifies the signature and returns a user session containing an access token and refresh token.

Wallet authentication is both sign-up and sign-in:

* The first valid signature from an address creates a QFEX user.
* Later signatures from the same address resume that user.
* Signing the message does not submit a blockchain transaction, require gas, or grant QFEX permission to transfer assets.

<Info>
  QFEX currently supports Ethereum-compatible wallets for authentication. Solana
  wallet authentication is not enabled. The wallet may be connected to any EVM
  chain because the signature proves address ownership rather than executing a
  transaction.
</Info>

## Before you start

Install the SIWE utilities used by the QFEX web terminal:

```sh theme={null}
npm install viem@2.55.10
```

You also need:

* The QFEX Auth URL: `https://verify.qfex.com`
* A QFEX publishable Auth client key
* A sign-in URL registered in the QFEX Auth redirect allowlist
* CORS approval for any QFEX REST service called directly from that browser origin
* A Cloudflare Turnstile token when CAPTCHA protection is enabled
* An EIP-1193 wallet provider, such as a provider discovered through EIP-6963, wagmi, MetaMask, or Phantom

Contact [support@qfex.com](mailto:support@qfex.com) for the current publishable Auth client key and to register an integration URL. The publishable key may be included in browser code. Never expose a QFEX secret or privileged server key.

SIWE validates the message domain and URI against the registered redirect URLs. Use the sign-in page's real URL when signing. Do not claim a QFEX or third-party domain that does not host the sign-in page.

## Complete the SIWE flow

The following example requests the wallet address and chain, constructs the EIP-4361 message, asks the wallet to sign it, and exchanges the signed message for a QFEX session:

```javascript theme={null}
import { getAddress, stringToHex } from "viem";
import { createSiweMessage, generateSiweNonce } from "viem/siwe";

const QFEX_AUTH_URL = "https://verify.qfex.com";
const QFEX_AUTH_CLIENT_KEY = "YOUR_QFEX_AUTH_CLIENT_KEY";

export async function signInToQfex({
  provider,
  signInUrl,
  captchaToken,
}) {
  const accounts = await provider.request({ method: "eth_requestAccounts" });
  if (!Array.isArray(accounts) || !accounts[0]) {
    throw new Error("The wallet did not return an account");
  }

  const address = getAddress(accounts[0]);
  const chainIdHex = await provider.request({ method: "eth_chainId" });
  const chainId = Number.parseInt(chainIdHex, 16);
  const pageUrl = new URL(signInUrl);

  const message = createSiweMessage({
    address,
    chainId,
    domain: pageUrl.host,
    issuedAt: new Date(),
    nonce: generateSiweNonce(),
    statement: "Sign in to QFEX",
    uri: pageUrl.href,
    version: "1",
  });

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

  const response = await fetch(
    `${QFEX_AUTH_URL}/auth/v1/token?grant_type=web3`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        apikey: QFEX_AUTH_CLIENT_KEY,
        Authorization: `Bearer ${QFEX_AUTH_CLIENT_KEY}`,
      },
      body: JSON.stringify({
        chain: "ethereum",
        message,
        signature,
        ...(captchaToken
          ? { gotrue_meta_security: { captcha_token: captchaToken } }
          : {}),
      }),
    },
  );

  const result = await response.json();
  if (!response.ok) {
    throw new Error(result.msg ?? result.message ?? "Wallet authentication failed");
  }

  return result;
}
```

In a browser, call the function with the current page URL:

```javascript theme={null}
const session = await signInToQfex({
  provider: selectedWalletProvider,
  signInUrl: window.location.href,
  captchaToken,
});
```

The provider must implement the EIP-1193 `request` method. The complete sequence is:

1. Call `eth_requestAccounts` to request an address.
2. Call `eth_chainId` to read the current chain.
3. Build an EIP-4361 message with the address, chain ID, registered URL, current time, fresh nonce, and the exact statement `Sign in to QFEX`.
4. Call `personal_sign` in the wallet.
5. Send the message and signature to `POST /auth/v1/token?grant_type=web3`.
6. Receive the existing user or create a new one, then receive a QFEX session.

<Warning>
  Display the wallet confirmation to the user and let the wallet perform the
  signature. Do not ask for, transmit, or store the wallet's private key or seed
  phrase.
</Warning>

## Choose the correct injected provider

`window.ethereum` is acceptable when only one wallet extension is installed. If several wallets are installed, discover them with EIP-6963 or use a wallet library and pass the provider selected by the user.

Do not silently choose the first wallet. The user should see which address and domain they are authorizing before signing.

## SIWE message requirements

The signed message must contain:

* The checksummed EVM address
* The real host of the registered sign-in page
* The complete registered sign-in URI
* Version `1`
* The wallet's current numeric chain ID
* A fresh alphanumeric nonce
* A current `Issued At` timestamp
* The exact statement `Sign in to QFEX`

Generate the message immediately before signing. QFEX rejects invalid signatures, reused or malformed messages, and messages whose domain or URI is not allowed. A message more than 10 minutes old is considered expired.

In a non-browser integration, pass the registered sign-in URL explicitly and use a wallet or custody provider that exposes an EIP-1193-compatible signing interface. Never load a raw private key merely to automate this flow.

## Auth HTTP request

The signed-credential exchange uses this request:

```http theme={null}
POST /auth/v1/token?grant_type=web3
Host: verify.qfex.com
Content-Type: application/json
apikey: <QFEX_AUTH_CLIENT_KEY>
Authorization: Bearer <QFEX_AUTH_CLIENT_KEY>

{
  "chain": "ethereum",
  "message": "<EIP-4361 message>",
  "signature": "<0x-prefixed signature>"
}
```

When CAPTCHA protection is active, also include the Turnstile result:

```json theme={null}
{
  "gotrue_meta_security": {
    "captcha_token": "<TURNSTILE_TOKEN>"
  }
}
```

A successful response includes:

```json theme={null}
{
  "access_token": "<JWT>",
  "token_type": "bearer",
  "expires_in": 3600,
  "expires_at": 1790000000,
  "refresh_token": "<REFRESH_TOKEN>",
  "user": {
    "id": "11111111-1111-4111-8111-111111111111"
  }
}
```

Treat both tokens as credentials. Do not put them in URLs, logs, analytics events, or source control.

## Use the access token

### REST API

Send the session access token as a bearer token:

```javascript theme={null}
const response = await fetch("https://api.qfex.com/user/positions", {
  headers: {
    Authorization: `Bearer ${session.access_token}`,
  },
});
```

The same bearer token works with the [programmatic funding API](/api-reference/programmatic-funding):

```javascript theme={null}
const response = await fetch(
  "https://banker.qfex.com/address?network=ARBITRUM_ONE",
  {
    headers: {
      Authorization: `Bearer ${session.access_token}`,
    },
  },
);
```

Direct browser requests require the page's origin to be allowed by the target QFEX service. A backend can use the user's bearer token without browser CORS restrictions.

### Trade WebSocket

Pass the access token in the connection URL and the authentication message:

```javascript theme={null}
const token = session.access_token;
const ws = new WebSocket(
  `wss://trade.qfex.com/?jwt=${encodeURIComponent(token)}`,
);

ws.addEventListener("open", () => {
  ws.send(
    JSON.stringify({
      type: "auth",
      params: { jwt: token },
    }),
  );
});
```

See [Trade WebSocket authentication](/websocket/channels/trade/authenticate) for account selection and builder attribution.

## Refresh the session

Access tokens expire. Exchange the refresh token for a new session before continuing to call QFEX services:

```javascript theme={null}
export async function refreshQfexSession(refreshToken) {
  const response = await fetch(
    `${QFEX_AUTH_URL}/auth/v1/token?grant_type=refresh_token`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        apikey: QFEX_AUTH_CLIENT_KEY,
        Authorization: `Bearer ${QFEX_AUTH_CLIENT_KEY}`,
      },
      body: JSON.stringify({ refresh_token: refreshToken }),
    },
  );

  const result = await response.json();
  if (!response.ok) {
    throw new Error(result.msg ?? result.message ?? "Session refresh failed");
  }
  return result;
}
```

Refresh tokens rotate. Store the newest refresh token returned by each successful refresh and discard the previous value. Browser applications should use secure storage appropriate to their threat model. Server applications should use an encrypted credential store.

## Account behavior and limitations

* A new wallet address creates a new QFEX account and must complete QFEX onboarding before it can trade or move funds.
* Wallet authentication does not bypass geographic restrictions, identity verification, terms acceptance, or 2FA.
* If the user has enrolled a second factor, complete the existing QFEX MFA flow after wallet sign-in.
* An address already registered with QFEX always resumes the same user.
* QFEX does not currently let an email-, Google-, or Apple-first account attach a wallet identity. Starting wallet authentication with a different address may create a separate account.
* A wallet-first user can add supported email or social sign-in methods from QFEX Security settings.
* A wallet identity cannot currently be unlinked or replaced.

## Common errors

| Error                                     | Resolution                                                                                                          |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| No compatible provider                    | Install or unlock an EVM wallet, or pass the selected EIP-1193 provider explicitly.                                 |
| User rejected the request                 | Stop the flow and let the user initiate it again. Do not retry the signature prompt automatically.                  |
| Invalid SIWE message                      | Rebuild the message with version `1`, a current timestamp, a fresh alphanumeric nonce, and the checksummed address. |
| Domain or URI rejected                    | Use the real sign-in page URL and ask QFEX support to register it.                                                  |
| CAPTCHA required or invalid               | Complete Turnstile and include the fresh token in `gotrue_meta_security.captcha_token`.                             |
| Rate limited                              | Apply backoff and wait before allowing another attempt.                                                             |
| Session succeeds but API access is denied | Complete QFEX onboarding, required terms, identity checks, and any enrolled MFA challenge.                          |
