# Set up non-custodial wallet approvals

Authorize Bridge to pull funds from a non-custodial wallet for stablecoin-backed card payments.

Bridge pulls funds onchain from a linked non-custodial wallet at authorization, so cardholders don’t need to fund a separate card balance. The amount held can include reserves and fees in addition to the purchase amount. Before Bridge can pull funds, the wallet owner must submit an onchain approval that authorizes the Stripe card contract to transfer stablecoins from the wallet.

This guide explains how to submit an approval on each supported chain. To link a card to the wallet, [create a card](https://docs.stripe.com/issuing/bridge-stablecoin-cards.md#create-a-card) with `crypto_wallet[type]` set to `standard`.

## How approvals work

The approval is a standard token allowance: an ERC-20 `approve` or a [Permit2](https://docs.uniswap.org/contracts/permit2/overview) approval on EVM chains, or an SPL token delegation on Solana. An ERC-20 approval can be for a fixed or unlimited amount. A Permit2 approval can also be for a fixed or unlimited amount, and expires after a period that you set. The approval sets the maximum amount that Bridge can pull from the wallet. At authorization, Stripe declines the purchase if:

- The approval isn’t active.
- The remaining approved amount is lower than the amount to hold.
- The wallet doesn’t have enough funds, after subtracting pending authorizations.

Stripe checks your [spending controls](https://docs.stripe.com/issuing/controls/spending-controls.md) before checking the approval. You can manage spending limits in either of these ways:

- **High approval with spending controls (recommended)**: Set a high approval amount once, and enforce specific limits with spending controls on the card or cardholder. You can change spending controls at any time with the Stripe API, without an onchain transaction.
- **Frequent approval top-ups**: Keep the approval amount low and top it up as the cardholder needs more spending power. Each change requires a new onchain transaction from the wallet owner.

We recommend creating the card before you submit the approval, so that Bridge ties the wallet address to your customer before any approval exists onchain.

## Stripe card contracts 

Stripe deploys a card contract on each supported mainnet chain, as listed in the following table. On EVM chains, including Tempo, each program also has a dedicated issuer contract that interacts with the card contract. Submit approvals to your program’s issuer contract address, which you receive during onboarding.

| Chain | Contract or program address |
| --- | --- |
| Base | `0x65bf8b55EEDef53C094E40003a03390De744DF33` |
| Linea | `0x930fa762919fDE945fD2d2c1dE25084daD2f8bBd` |
| Solana | `cardWArqhdV5jeRXXjUti7cHAa4mj41Nj3Apc6RPZH2` |
| Tempo | `0x661AA387dF0A94e81c06f5C00e9706665B7be686` |
| World Chain | `0x6B0D105999491a48d5793FB6Cb54f5cE079E0da9` |

For testnet addresses, see [test stablecoin-backed cards](https://docs.stripe.com/issuing/stablecoin-cards/testing.md#test-networks). To learn how the card contracts keep funds secure, see [card contract architecture and security](https://docs.stripe.com/issuing/bridge-stablecoin-cards/contract-security.md).

## Submit the approval

The approval process differs between EVM chains and Solana.

#### EVM chains

On EVM chains, including Tempo, submit the approval to your program’s issuer contract.

This example uses [ethers v6](https://docs.ethers.org/v6/) to approve your issuer contract to spend up to 100 USDC from the wallet:

```javascript
import { ethers } from "ethers";

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

// The ERC-20 address of the stablecoin that the card spends
const TOKEN_ADDRESS = "TOKEN_ADDRESS";
// The issuer contract address that Bridge provides for your program
const ISSUER_ADDRESS = "ISSUER_ADDRESS";

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

async function approve() {
  const token = new ethers.Contract(TOKEN_ADDRESS, ERC20_ABI, signer);
  // USDC uses 6 decimals
  const amount = ethers.parseUnits("100", 6);
  const tx = await token.approve(ISSUER_ADDRESS, amount);
  await tx.wait();
  console.log("Approval transaction:", tx.hash);
}

approve();
```

If you use Privy wallets, you can submit the same transaction with Privy and sponsor the network fees for your users. See the Privy [cards integration guide](https://docs.privy.io/financial-flows/cards/integration-guide).

#### Solana

On Solana, the approval delegates the wallet’s token account to a program-derived address (PDA) of the Stripe card program. The PDA depends on three values:

- **Merchant ID**: An ID assigned to your program during onboarding.
- **Mint**: The token mint of the stablecoin that the card spends, such as USDC (`EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`).
- **Token account**: The wallet’s associated token account for that mint.

This example uses [@solana/web3.js](https://www.npmjs.com/package/@solana/web3.js) and [@solana/spl-token](https://www.npmjs.com/package/@solana/spl-token) to approve the delegate to spend up to 100 USDC. Bridge provides the `findUserDelegatePDA` helper in its [sample approval script](https://gist.github.com/lvn/dd41a7233f63f1c45b4ec25c220ecff6), which also creates the associated token account if it doesn’t exist.

```typescript
const PROGRAM_ID = new PublicKey("cardWArqhdV5jeRXXjUti7cHAa4mj41Nj3Apc6RPZH2");
const MINT_PUBKEY = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
const MINT_DECIMALS = 6;
const APPROVAL_AMOUNT = BigInt(100 * 10 ** MINT_DECIMALS);

const userAta = getAssociatedTokenAddressSync(
  MINT_PUBKEY,
  userKeypair.publicKey,
  false,
  TOKEN_PROGRAM_ID // Some stablecoins use TOKEN_2022_PROGRAM_ID
);

const bridgeSdk = new BridgeSDK(PROGRAM_ID);
const [delegatePda] = bridgeSdk.findUserDelegatePDA(
  MERCHANT_ID,
  MINT_PUBKEY,
  userAta
);

const transaction = new Transaction().add(
  createApproveInstruction(
    userAta,
    delegatePda,
    userKeypair.publicKey,
    APPROVAL_AMOUNT,
    [],
    TOKEN_PROGRAM_ID
  )
);
// You can pay network fees on behalf of your users by setting a different fee payer.
transaction.feePayer = userKeypair.publicKey;

const signature = await sendAndConfirmTransaction(connection, transaction, [
  userKeypair,
]);
console.log("Approval transaction:", signature);
```

When you create the card, pass the wallet’s owner address in `crypto_wallet[address]`, not the associated token account address.

## Verify the approval

After the approval transaction confirms, test a purchase in a sandbox. Each authorization’s `crypto_transactions` field lists the onchain transactions that Bridge submits for it. Learn more about [authorizations and webhooks](https://docs.stripe.com/issuing/stablecoin-cards/authorizations.md).

For other chains or custom approval flows, contact your Stripe account manager.
