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

# Noncustodial wallets

> Issue cards that spend just-in-time from a customer's own stablecoin wallet

Bridge supports issuing cards that spend directly from a noncustodial wallet controlled by you or your customer, without requiring a separate Bridge Wallet. At the time of each card authorization, Bridge pulls the exact spend amount onchain from the linked wallet—no pre-funding required.

To use a noncustodial wallet, specify `crypto_wallet[type]=standard` when creating the card via the Stripe API (see [Funding strategies](/platform/cards/overview/funding-strategies)). You must also set up an onchain approval so Bridge's smart contract can pull funds at authorization time.

This page covers how to set up that approval for each supported chain. Noncustodial direct debit cards are supported on Tempo, Solana, and other EVM chains. The main contract address for each chain is:

| Chain       | Address                                                                                                                                            |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| Tempo       | [**0x661AA387dF0A94e81c06f5C00e9706665B7be686**](https://explore.tempo.xyz/address/0x661AA387dF0A94e81c06f5C00e9706665B7be686?tab=contract)        |
| Solana      | [cardWArqhdV5jeRXXjUti7cHAa4mj41Nj3Apc6RPZH2](https://solscan.io/account/cardWArqhdV5jeRXXjUti7cHAa4mj41Nj3Apc6RPZH2?instruction=92e77d98f81fb8b1) |
| Base        | **0x65bf8b55EEDef53C094E40003a03390De744DF33**                                                                                                     |
| World Chain | [0x6B0D105999491a48d5793FB6Cb54f5cE079E0da9](https://worldscan.org/address/0x6b0d105999491a48d5793fb6cb54f5ce079e0da9)                             |
| Linea       | **0x930fa762919fDE945fD2d2c1dE25084daD2f8bBd**                                                                                                     |

# Setting Up A Noncustodial Card

## Step 1: Provision a card account

In the request to the endpoint to [provision a card account](https://apidocs.bridge.xyz/api-reference/cards/provision-a-card-account#body-crypto-account-address), specify the wallet and currency to use. For the `crypto_account` type, specify `standard` to indicate that Bridge should pull from the non-custodial wallet.

Note that a wallet can only be tied to one card account. Bridge does not allow issuing multiple cards that spend from the same wallet.

<Tabs>
  <Tab title="Tempo and EVM">
    ```json theme={null}
    {
        "chain": "tempo",
        "currency": "usdc",
        "crypto_account": {
            "type": "standard",
            "address": "0xC37e5d75F3D212D22e943D8DD93849a17F79dc79"
        }
    }
    ```
  </Tab>

  <Tab title="Solana">
    <Warning>
      If you are using Solana, do not specify the [associated token account](https://solana.com/docs/tokens/basics/create-token-account) address in this request. Bridge will automatically derive it from the address and currency specified. In this example, the account's owner address is `BDkZQv1DqS7RJG5MZjVEP8FbN9Xvpf5b67kpi3765rQb` but the USDC token account is `GXaRe925ejuzX3KZdtLPJ8fpnudzhdk9eXDxNSEdro49`.
    </Warning>

    ```json theme={null}
    {
        "chain": "solana",
        "currency": "usdc",
        "crypto_account": {
            "type": "standard",
            "address": "BDkZQv1DqS7RJG5MZjVEP8FbN9Xvpf5b67kpi3765rQb"
        }
    }
    ```
  </Tab>
</Tabs>

When the card account is created, Bridge will submit an onchain transaction to register the program delegate address associated with the specified `crypto_account` address. [Here](https://solscan.io/tx/4JwD5Sypv19r3M8TWKLu9c8B18twyKDN8DW5nnsvtnS36cr1puDHZF6YZArHYiTnntKEQA382hJQKQf96LR6Txf9) is a sample transaction doing so.

For safety reasons, we recommend provisioning the card account before setting up the delegate approval, so that Bridge ties this address to the customer before any approvals are submitted onchain.

## Step 2: Token Approval

For both SPL (Solana-based) and ERC-20 (EVM-based) tokens, end users will need to approve the cards smart contract to pull funds from their wallet.

<Tabs>
  <Tab title="Tempo and EVM">
    This example uses the [ethers v6 JavaScript SDK](https://docs.ethers.org/v6/) to call the ERC-20 `approve` function. It approves a cards issuer contract to spend up to \$100 from its balance.

    <Warning>
      The card issuer contract in the example blow is **not** the same contract as the one listed in the table above. The Bridge team will provide you an EVM cards contract unique to your program which interfaces with the global cards contract.
    </Warning>

    ```javascript theme={null}
    // Simple ERC-20 approve example (ethers v6)
    // npm install ethers
    import { ethers } from "ethers";

    const provider = new ethers.JsonRpcProvider("RPC_URL");
    const signer = new ethers.Wallet("PRIVATE_KEY", provider);

    const TOKEN_ADDRESS = "TOKEN_ADDRESS"; // the erc20 address of the stablecoin you are using
    const ISSUER_ADDRESS = "ISSUER_ADDRESS"; // developer-specific contract address provided by Bridge

    const ERC20_ABI = [
      "function approve(address spender, uint256 amount) returns (bool)",
    ];

    async function approve() {
      const token = new ethers.Contract(TOKEN_ADDRESS, ERC20_ABI, signer);

      // Approve $100 tokens (assumes 6 decimal currency such as USDC)
      const amount = ethers.parseUnits("100", 6);

      const tx = await token.approve(ISSUER_ADDRESS, amount);
      console.log("txid:", tx.hash);
      await tx.wait();
    }

    approve();
    ```

    The result will be a transaction that resembles [this transaction](https://explore.tempo.xyz/tx/0xc5c67375af9ceecac1e35faa5ed93f18fd5528eeb61ebc625063b7bbdcd6b845).
  </Tab>

  <Tab title="Solana">
    For this example, we'll be using [Solana's Javascript SDK](https://www.npmjs.com/package/@solana/web3.js) to set up an approval to the Bridge card program's associated [Program-Derived Address](https://solana.com/docs/core/pda).

    We've provided a consolidated version of this delegate approval logic in a [GitHub Gist](https://gist.github.com/lvn/dd41a7233f63f1c45b4ec25c220ecff6), but this section will explain each step in detail.

    ### Step 2a: Setup connection and transaction

    First, we set up a new transaction to contain our instructions. We also ensure that the account has a non-zero amount of SOL so that we can submit transactions onchain.

    ```typescript theme={null}
    const connection = new Connection(clusterApiUrl("mainnet-beta"), "confirmed");
    const transaction = new Transaction();

    const userKeypair = loadKeypairFromJson(...);
    console.log('Public key:', userKeypair.publicKey.toString());

    // Check SOL balance
    const balance = await connection.getBalance(userKeypair.publicKey);
    console.log('SOL balance:', balance / LAMPORTS_PER_SOL, 'SOL');

    if (balance === 0) {
      console.log('⚠️  No SOL balance. You need SOL for transaction fees.');
      return;
    }
    ```

    ### Step 2b: Setup ATA if necessary

    If necessary, first set up the [associated token account](https://solana.com/docs/tokens/basics/create-token-account) for your wallet to hold a balance in the token you want to spend. This section checks for the existence of the associated token account, and adds an instruction into the transaction to create the ATA if it doesn't already exist.

    <CodeGroup>
      ```typescript Typescript theme={null}
      // Get the user's token account for the given currency.
      const MINT_PUBKEY = new PublicKey(
        "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"  // https://solscan.io/token/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
      );  
      const userAta = getAssociatedTokenAddressSync(
        MINT_PUBKEY,
        userKeypair.publicKey,
        false,
        TOKEN_PROGRAM_ID  // note: depending on the currency, might need to be TOKEN_2022_PROGRAM_ID
      );
      console.log('Associated Token Account:', userAta.toString());

      // Check if the ATA exists
      let ataExists = false;
      try {
        await getAccount(connection, userAta, undefined, TOKEN_PROGRAM_ID);
        ataExists = true;
        console.log('✅ Associated Token Account exists');
      } catch (error) {
        console.log('❌ Associated Token Account does not exist, will create it');
      	
        const createAtaInstruction = createAssociatedTokenAccountInstruction(
          userKeypair.publicKey, // payer
          userAta, // ata
          userKeypair.publicKey, // owner
          MINT_PUBKEY, // mint
          TOKEN_PROGRAM_ID
        );
        transaction.add(createAtaInstruction);
        console.log('Added instruction to create ATA');
      }
      ```
    </CodeGroup>

    ### Step 2c: Set up program delegate approval

    Next, we'll add an instruction to the transaction to approve Bridge's card program to spend from the specified ATA.

    Note that the `MERCHANT_ID` to use when computing the delegate is specific to your developer. You will be assigned a specific `MERCHANT_ID` value by Bridge.

    ```typescript theme={null}

    const PROGRAM_ID = new PublicKey("cardWArqhdV5jeRXXjUti7cHAa4mj41Nj3Apc6RPZH2");
    const MINT_DECIMALS = 6;
    const APPROVAL_AMOUNT_UI = 100;
    const APPROVAL_AMOUNT = BigInt(APPROVAL_AMOUNT_UI * 10 ** MINT_DECIMALS);
    const bridgeSdk = new BridgeSDK(PROGRAM_ID);
    const [delegatePda] = bridgeSdk.findUserDelegatePDA(
      MERCHANT_ID,
      MINT_PUBKEY,
      userAta
    );

    console.log('Delegate PDA:', delegatePda.toString());

    // Approve the BridgeCard contract to manage the user's token account.
    const approveInstruction = createApproveInstruction(
      userAta,
      delegatePda,
      userKeypair.publicKey,
      APPROVAL_AMOUNT,
      [],
      TOKEN_PROGRAM_ID
    );

    transaction.add(approveInstruction);
    console.log('Added approve instruction');

    // You could optionally pay these fees on behalf of your users.
    transaction.feePayer = userKeypair.publicKey;
    ```

    ### Step 2d: Submit approval transaction

    Finally, you will submit the transaction onchain:

    ```typescript theme={null}
    try {
      console.log('Sending transaction...');
      const signature = await sendAndConfirmTransaction(connection, transaction, [
        userKeypair,
      ]);

      console.log('✅ Transaction successful!');
      console.log('Signature:', signature);
      console.log(`View on Solana Explorer: https://explorer.solana.com/tx/${signature}`);
    } catch (error: any) {
      console.error('❌ Error sending and confirming transaction:', error);
      
      // Enhanced error logging
      if (error.logs) {
        console.log('Transaction logs:', error.logs);
      }
      throw error;
    }
    ```

    The result will be a delegate approval that resembles [this transaction](https://solscan.io/tx/5o1aAuCFpmB9sEt1djX9PDENGmkNEitUYoqg93a9JLdvQ1pZbFX5ChkVFULq38aArBCg9sQTREQt9fTFr1puPJ1H).
  </Tab>
</Tabs>

## Step 3: Consume webhook events for card transactions

The card is now ready to use. When the card is used for a purchase at a merchant, you will receive webhook events informing you about it. This webhook event will contain information about both the card network authorization and the onchain transaction.

Transactions will be submitted onchain at the time of card authorization, but complete asynchronously. This will cause two webhook events to be published. When the card authorization initially occurs, you will receive a webhook like the following, without the crypto transaction details:

```json theme={null}
{
  "api_version": "v0",
  "event_id": "wh_tuvB3rbeBKREPEgZ2jmnB4k",
  "event_developer_id": "d76d99b4-f4e8-4799-9907-10c965e0e9fc",
  "event_sequence": 13426,
  "event_category": "card_transaction",
  "event_type": "card_transaction.created",
  "event_object_id": "86d30f38-5ea0-402d-ad48-48003d3b3f29",
  "event_object_status": "approved",
  "event_object": {
    "id": "86d30f38-5ea0-402d-ad48-48003d3b3f29",
    "amount": "-10.0",
    "status": "approved",
    "category": "purchase",
    "currency": "usd",
    "created_at": "2025-12-11T17:47:32.152Z",
    "updated_at": "2025-12-11T17:47:45.153Z",
    "customer_id": "2c21ddbb-cf34-4170-b9b2-076a3ed55de9",
    "authorized_at": "2025-12-11T17:47:32.167Z",
    "merchant_name": "BRIDGE CAFE, SAN FRANCISCO, CA",
    "status_reason": "approved",
    "billing_amount": "-10.0",
    "card_account_id": "3d698985-fbd4-4889-999a-4fa7bd578452",
    "original_amount": "-10.0",
    "merchant_location": "+18886772541, CAUS",
    "authorization_infos": [
      {
        "amount": "-10.0",
        "wallet": "other",
        "account": {
          "last_4": "2960"
        },
        "network": "visa",
        "currency": "usd",
        "merchant": {
          "state": "CA",
          "country": "USA",
          "description": "BRIDGE CAFE, SAN FRANCISCO, CA",
          "postal_code": "94306",
          "category_code": "6012"
        },
        "auth_type": "auth",
        "recurring": false,
        "created_at": "2025-12-11T17:47:31.000Z",
        "fee_amount": "0.00",
        "customer_id": "2c21ddbb-cf34-4170-b9b2-076a3ed55de9",
        "card_present": false,
        "entry_method": "card_not_present",
        "international": false,
        "status_reason": "approved",
        "billing_amount": "-10.0",
        "transaction_id": "86d30f38-5ea0-402d-ad48-48003d3b3f29",
        "approval_status": "approved",
        "card_account_id": "3d698985-fbd4-4889-999a-4fa7bd578452",
        "cashback_amount": "0.00",
        "authorization_id": "3aa53334-29c5-4db2-b5fc-bf0a330a7c4c",
        "partial_supported": false,
        "verification_data": {
          "cvv_check": "not_provided",
          "pin_check": "no_pin_passed",
          "address_check": "mismatch",
          "three_d_secure_check": "authenticated",
          "address_postal_code_check": "mismatch"
        },
        "local_transaction_details": {
          "amount": "-10.0",
          "currency": "usd",
          "exchange_rate": "1.0"
        }
      }
    ],
    "merchant_category_code": "6012",
    "transaction_description": "BRIDGE CAFE, SAN FRANCISCO, CA"
  },
  "event_object_changes": {},
  "event_created_at": "2025-12-11T17:47:45.900Z"
}
```

Subsequently (on the order of seconds) when the crypto transaction is confirmed onchain, another webhook event will be published with the details:

```json theme={null}
{
  "api_version": "v0",
  "event_id": "wh_tdcF5jcwFPT1cg5mvmeKMG1",
  "event_developer_id": "d76d99b4-f4e8-4799-9907-10c965e0e9fc",
  "event_sequence": 13428,
  "event_category": "card_transaction",
  "event_type": "card_transaction.updated",
  "event_object_id": "86d30f38-5ea0-402d-ad48-48003d3b3f29",
  "event_object_status": "approved",
  "event_object": {
    "id": "86d30f38-5ea0-402d-ad48-48003d3b3f29",
    "amount": "-10.0",
    "status": "approved",
    "category": "purchase",
    "currency": "usd",
    "created_at": "2025-12-11T17:47:32.152Z",
    "updated_at": "2025-12-11T17:47:51.785Z",
    "customer_id": "2c21ddbb-cf34-4170-b9b2-076a3ed55de9",
    "authorized_at": "2025-12-11T17:47:32.167Z",
    "merchant_name": "BRIDGE CAFE, SAN FRANCISCO, CA",
    "status_reason": "approved",
    "billing_amount": "-10.0",
    "card_account_id": "3d698985-fbd4-4889-999a-4fa7bd578452",
    "original_amount": "-10.0",
    "merchant_location": "+18886772541, CAUS",
    "authorization_infos": [
      {
        "amount": "-10.0",
        "wallet": "other",
        "account": {
          "last_4": "2960"
        },
        "network": "visa",
        "currency": "usd",
        "merchant": {
          "state": "CA",
          "country": "USA",
          "description": "BRIDGE CAFE, SAN FRANCISCO, CA",
          "postal_code": "94306",
          "category_code": "6012"
        },
        "auth_type": "auth",
        "recurring": false,
        "created_at": "2025-12-11T17:47:31.000Z",
        "fee_amount": "0.00",
        "customer_id": "2c21ddbb-cf34-4170-b9b2-076a3ed55de9",
        "card_present": false,
        "entry_method": "card_not_present",
        "international": false,
        "status_reason": "approved",
        "billing_amount": "-10.0",
        "crypto_details": {
          "chain": "solana",
          "amount": "10.0",
          "tx_hash": "618p4RWZ4UPe6uCeFo0BaRb2H4gSQJjLayDihFD7fERAnfyoxyMJQZc4WmKkPkLu7QHnXgpWp6pPUMqc2HzGqH3",
          "currency": "usdc",
          "tx_reference": "d0b935bf-300e-431b-82bd-311ef7115f86"
        },
        "transaction_id": "86d30f38-5ea0-402d-ad48-48003d3b3f29",
        "approval_status": "approved",
        "card_account_id": "3d698985-fbd4-4889-999a-4fa7bd578452",
        "cashback_amount": "0.00",
        "authorization_id": "c9537b55-094f-4bd5-b8f5-4a27c37ce83d",
        "partial_supported": false,
        "verification_data": {
          "cvv_check": "not_provided",
          "pin_check": "no_pin_passed",
          "address_check": "mismatch",
          "three_d_secure_check": "authenticated",
          "address_postal_code_check": "mismatch"
        },
        "local_transaction_details": {
          "amount": "-10.0",
          "currency": "usd",
          "exchange_rate": "1.0"
        }
      }
    ],
    "merchant_category_code": "6012",
    "transaction_description": "BRIDGE CAFE, SAN FRANCISCO, CA"
  },
  "event_object_changes": {
    ...
  },
  "event_created_at": "2025-12-11T17:47:52.380Z"
}
```

You can see more card transaction scenarios in the [Card Transaction Webhooks](https://apidocs.bridge.xyz/platform/cards/overview/webhooks#card-transaction-webhooks) section of the documentation. Incremental authorizations will cause additional transactions to be submitted onchain to cover the additional charge.
