Skip to content

Withdraw

A Withdraw (also called on-ramp — moving from fiat to crypto) lets a user spend EUR from their Holyheld card balance and receive tokens at a wallet address. Unlike a Deposit, the user does not sign a blockchain transaction in your application — they confirm the request inside the Holyheld mobile app, and BRRR submits the on-chain transfer on their behalf.

How a Withdraw works

  1. Your application requests a Withdraw: destination wallet, target token and network, EUR amount to spend.
  2. BRRR validates the destination address, locks a quote, and creates a request identified by a requestUid.
  3. The user receives a push notification from the Holyheld app and has 3 minutes to approve.
  4. On approval, BRRR debits the user's card balance, converts EUR to tokens at the quoted rate, and submits the transfer on-chain.
  5. Your application polls the request until it reaches a terminal state — approved with transaction hash, declined, or expired.

If the 3-minute window expires, the requestUid becomes invalid. To retry, your application must create a new request from scratch.

Choose your integration surface

SurfaceWhen to useAuth
SDKA user-facing app where the end user holds a Holyheld card and confirms in the mobile appSDK API key
Card APIA platform building its own Withdraw UX on top of the same primitivesX-Api-Key

The Multi-tenant Settlement and OTC APIs are Deposit-shaped surfaces; for Withdraws, the SDK and Card API are the integration surfaces today.

Withdraw with the SDK

The requestOnRamp method (which initiates a Withdraw) returns a requestUid immediately. Your application then waits for user confirmation via watchRequestId. EVM example:

typescript
import HolyheldSDK, { Network } from '@holyheld/sdk';

const sdk = new HolyheldSDK({ apiKey: process.env.HOLYHELD_SDK_API_KEY });
await sdk.init();

const settings = await sdk.getServerSettings();
if (!settings.external.isOnRampEnabled) throw new Error('Withdraws unavailable');

const wallet = await sdk.validateAddress('0x...');
if (!wallet.isOnRampAllowed) throw new Error('Address not eligible');

const request = await sdk.evm.onRamp.requestOnRamp({
  walletAddress: '0x...',
  tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum
  tokenNetwork: Network.ethereum,
  EURAmount: '50',
});

// Prompt the user to open the Holyheld app and approve within 3 minutes.

const outcome = await sdk.evm.onRamp.watchRequestId(request.requestUid, {
  timeout: 180_000,
  waitForTransactionHash: true,
});

if (outcome.success) {
  console.log('Tokens en route:', outcome.hash);
} else {
  // Declined or window expired — start over from requestOnRamp.
}

See the requestOnRamp and watchRequestId references for parameters and the full outcome shape. getOnRampEstimation lets you preview fees and the expected token output before submitting.

Withdraws are EVM-only at present. Solana support is on the SDK roadmap; check the Supported Networks page for the current matrix.

The 3-minute confirmation window

The mobile-app confirmation is intentional — it's how BRRR enforces that the cardholder authorizes the spend, even when the request originates from a third-party application. Three implications for your UX:

  • After requestOnRamp resolves, surface a clear "open your Holyheld app" prompt with a visible countdown.
  • Don't pre-emptively retry. A second requestOnRamp while the first is pending will create a competing request.
  • If watchRequestId resolves with success: false, the cause is either a user decline or window expiry. Both require a new request to retry.

Funding source and limits

Withdraws debit the user's Holyheld card EUR balance. If the balance is insufficient, requestOnRamp throws FailedOnRampRequest with a descriptive message — show it to the user and prompt them to top up their card before retrying. The current minimum and maximum per-request amounts are returned by getServerSettings() under minOnRampAmountInEUR and maxOnRampAmountInEUR.

Errors

Common Withdraw codes thrown via HolyheldSDKError include FailedOnRampRequest (the request couldn't be created — usually balance or eligibility) and FailedWatchOnRampRequestTimeout (network error during polling, distinct from a user decline). The full list lives in SDK error handling.

Where to next