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

# Proof of Ownership

> Understand when and how wallet ownership verification works for onramp transactions

When onramping to non-Crossmint wallets, the onramp may require the user to prove they own the recipient wallet by signing a message. This is a legal requirement under anti-money laundering (AML) and counter-terrorism financing (CTF) regulations, which mandate verification of the beneficiary wallet owner's identity above certain transaction thresholds. This page explains when proof of ownership is required and how it works.

## When Is Proof of Ownership Required?

Proof of ownership is triggered based on transaction volume thresholds:

| Condition                                                         | Verification Required? |
| :---------------------------------------------------------------- | :--------------------- |
| Transaction below **\$1,000** AND 30-day volume below **\$1,000** | No                     |
| Transaction above **\$1,000** OR 30-day volume above **\$1,000**  | Yes                    |

<Note>
  Proof of ownership only applies to **external (non-Crossmint) wallets**. A Crossmint-managed wallet does not require ownership verification **as long as it was created with its `owner` set to the user**. A Crossmint wallet created without an `owner` (or owned by someone else) is treated as external and must be linked and verified like any other external wallet.
</Note>

<Note>
  Wallet-ownership proof applies only to users verified outside the United States. Buyers whose KYC country of residence is the United States are not asked to prove wallet ownership, regardless of transaction amount.
</Note>

## How It Works

<Steps>
  <Step title="Link External Wallet">
    Before creating an onramp order to an external wallet, link the wallet to a Crossmint user (created automatically if one does not yet exist for the locator) using the Link External Wallet API:

    ```bash theme={null}
    curl --request PUT \
        --url https://staging.crossmint.com/api/2025-06-09/users/YOUR_USER_LOCATOR/linked-wallets/YOUR_WALLET_ADDRESS \
        --header 'X-API-KEY: YOUR_API_KEY' \
        --header 'Content-Type: application/json' \
        --data '{
            "chain": "base-sepolia"
        }'
    ```

    The API responds with a verification challenge following the <a href="https://chainagnostic.org/CAIPs/caip-122" target="_blank">CAIP-122</a> standard:

    ```json theme={null}
    {
        "address": "0x1234...5678",
        "chain": "base-sepolia",
        "type": "external-wallet",
        "ownership": {
            "verified": false,
            "verificationChallenge": "crossmint.com wants you to sign in with your blockchain account:\n0x1234...5678\n\n..."
        }
    }
    ```
  </Step>

  <Step title="Create Order">
    Create the onramp order as usual. If verification is required, the order response will have status `requires-recipient-verification`:

    ```json theme={null}
    {
        "order": {
            "payment": {
                "status": "requires-recipient-verification",
                "preparation": {
                    "message": "crossmint.com wants you to sign in with your blockchain account:\n0x1234...5678\n\n..."
                }
            }
        }
    }
    ```
  </Step>

  <Step title="Sign the Message">
    Have the user sign the `preparation.message` using their wallet's private key:

    <CodeGroup>
      ```js EVM (ethers.js) theme={null}
      import { Wallet } from "ethers";

      const wallet = new Wallet("YOUR_PRIVATE_KEY");
      const message = order.payment.preparation.message;
      const signature = await wallet.signMessage(message);
      ```

      ```js Solana (tweetnacl) theme={null}
      import nacl from "tweetnacl";
      import { decodeUTF8 } from "tweetnacl-util";
      import { encode as base64Encode } from "@stablelib/base64";

      const YOUR_SECRET_KEY_BYTES = new Uint8Array(64); // Replace with your actual secret key bytes
      const keypair = nacl.sign.keyPair.fromSecretKey(YOUR_SECRET_KEY_BYTES);

      const message = order.payment.preparation.message;
      const messageBytes = decodeUTF8(message);
      const signatureBytes = nacl.sign.detached(messageBytes, keypair.secretKey);
      const signature = base64Encode(signatureBytes);
      ```
    </CodeGroup>
  </Step>

  <Step title="Submit Signature">
    Submit the signature as proof of ownership using the same Link External Wallet API:

    ```bash theme={null}
    curl --request PUT \
        --url https://staging.crossmint.com/api/2025-06-09/users/YOUR_USER_LOCATOR/linked-wallets/YOUR_WALLET_ADDRESS \
        --header 'X-API-KEY: YOUR_API_KEY' \
        --header 'Content-Type: application/json' \
        --data '{
            "chain": "base-sepolia",
            "proof": "YOUR_SIGNATURE"
        }'
    ```

    Once verified, the response confirms ownership:

    ```json theme={null}
    {
        "address": "0x1234...5678",
        "chain": "base-sepolia",
        "type": "external-wallet",
        "ownership": {
            "verified": true
        }
    }
    ```
  </Step>

  <Step title="Complete Order">
    After ownership is verified, fetch the order using the [Get Order API](/api-reference/headless/get-order). The status will advance to:

    * **`requires-kyc`** — the user has not completed KYC yet.
    * **`awaiting-payment`** — the user can proceed to payment.
  </Step>
</Steps>

## Pre-Verifying Ownership

You can verify wallet ownership at the time of linking (before any order is created) by including the `proof` field in the initial Link External Wallet call. This avoids requiring verification during the checkout flow.

```bash theme={null}
curl --request PUT \
    --url https://staging.crossmint.com/api/2025-06-09/users/YOUR_USER_LOCATOR/linked-wallets/YOUR_WALLET_ADDRESS \
    --header 'X-API-KEY: YOUR_API_KEY' \
    --header 'Content-Type: application/json' \
    --data '{
        "chain": "base-sepolia",
        "proof": "YOUR_SIGNATURE"
    }'
```

## Related Resources

* [Onramp to Non-Crossmint Wallets](/onramp/guides/onramp-to-external-wallets) — full guide for non-Crossmint wallet onramp integration
* [React Quickstart (External Wallets tab)](/onramp/quickstarts/react) — quickstart with external wallet support
