--- url: 'https://docs.brrr.network/api/settlement/onboarding/add-address-to-customer.md' description: Add an additional EVM wallet address to an existing customer. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/api/v4/partner/customer/add-address \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "customerId": "cust_a1b2c3d4", "addressEVM": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/api/v4/partner/customer/add-address', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ customerId: 'cust_a1b2c3d4', addressEVM: '0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B', }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Failed to add address: ${error.errorCode}`); } const data = await response.json(); console.log('Address added:', data); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/api/v4/partner/customer/add-address', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ 'customerId': 'cust_a1b2c3d4', 'addressEVM': '0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B', }, ) response.raise_for_status() data = response.json() print('Address added:', data) ``` ::: --- --- url: 'https://docs.brrr.network/api/settlement/iban/add-iban-to-customer.md' description: Register a SEPA IBAN for an existing partner customer. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/api/v4/partner/customer/add-iban \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "customerId": "cust_a1b2c3d4", "iban": "DE89370400440532013000", "beneficiaryName": "John Doe" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/api/v4/partner/customer/add-iban', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ customerId: 'cust_a1b2c3d4', iban: 'DE89370400440532013000', beneficiaryName: 'John Doe', }), }); const data = await response.json(); console.log('IBAN registered:', data.payload.ibanId); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/api/v4/partner/customer/add-iban', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ 'customerId': 'cust_a1b2c3d4', 'iban': 'DE89370400440532013000', 'beneficiaryName': 'John Doe', }, ) response.raise_for_status() print('IBAN registered:', response.json()['payload']['ibanId']) ``` ::: --- --- url: 'https://docs.brrr.network/api/card-api/config-and-info/card-address-check.md' description: >- Check whether a wallet address is eligible for Card API onramp and offramp flows. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/v5/config/address-check \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "address": "0x45FB5699C0BFFC5e7D7F0c4947417833cc0623aa" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v5/config/address-check', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ 'address': '0x45FB5699C0BFFC5e7D7F0c4947417833cc0623aa' }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Request failed: ${error.errorCode}`); } const data = await response.json(); console.log(data.payload); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/v5/config/address-check', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ "address": "0x45FB5699C0BFFC5e7D7F0c4947417833cc0623aa" }, ) response.raise_for_status() print(response.json()['payload']) ``` ::: --- --- url: 'https://docs.brrr.network/docs/agentic/agent-patterns.md' description: >- Best practices and common patterns for building reliable AI agents with the Payments for AI Agents API — threshold top-up, safe polling, error escalation, and anti-patterns to avoid. --- # Agent Patterns This page documents patterns for building reliable agents that use the Payments for AI Agents API. Each pattern addresses a common scenario and includes implementation guidance and the reasoning behind each decision. ## Pattern 1 — Balance-aware spending **Problem:** An agent is about to take an action that costs money (book a flight, pay for an API call). It should not assume the card is funded. **Solution:** Always check the balance before spending, and top up proactively if the available balance is less than the expected cost plus a small buffer. ```typescript async function ensureFunds(expectedCost: number, token: string): Promise { const BUFFER_EUR = 5; // small buffer to cover rounding and fx differences const result = await getBalance(token); if (!result.ok) { throw new Error(`Cannot check balance: ${result.errorCode}`); } const available = parseFloat(result.balance); const required = expectedCost + BUFFER_EUR; if (available < required) { const topupAmount = (required - available).toFixed(2); await topUpAndConfirm(topupAmount, token); // see Pattern 3 } } ``` **Why a buffer?** EUR exchange rates and network fees mean the actual charge can exceed the quoted cost by a small margin. A €5 buffer prevents a failed payment after a successful booking. ## Pattern 2 — Threshold-based auto top-up **Problem:** An agent runs on a schedule and needs to maintain a minimum balance. The user has instructed: *"Keep my card above €50 at all times."* **Solution:** Check the balance on each run. If below the threshold, top up the difference to a target amount. ```typescript const THRESHOLD_EUR = 50; const TARGET_EUR = 100; async function maintainBalance(token: string): Promise { const result = await getBalance(token); if (!result.ok) { return `Balance check failed: ${result.errorCode} — ${result.error}`; } const balance = parseFloat(result.balance); if (balance >= THRESHOLD_EUR) { return `Balance is €${result.balance} — no top-up needed.`; } const topupAmount = (TARGET_EUR - balance).toFixed(2); return await topUpAndConfirm(topupAmount, token); } ``` **Why top up to a target, not just to the threshold?** Topping up to exactly the threshold means the next run will immediately trigger another top-up. A target above the threshold reduces the number of top-up operations and keeps the card funded for longer. ## Pattern 3 — Safe polling after top-up **Problem:** The `POST /topup-request` response confirms the request was accepted, but balance updates can take up to 5 minutes. Reporting success before the balance updates misleads the user. **Solution:** Record the balance before the top-up, then poll until it increases or the timeout is reached. ```typescript const POLL_INTERVAL_MS = 30_000; // 30 seconds — do not poll more frequently const POLL_TIMEOUT_MS = 5 * 60_000; // 5 minutes async function topUpAndConfirm(amount: string, token: string): Promise { // 1. Snapshot balance before the top-up const before = await getBalance(token); if (!before.ok) throw new Error(`Pre-top-up balance check failed: ${before.errorCode}`); const balanceBefore = parseFloat(before.balance); // 2. Submit the top-up request const topupRes = await fetch(`${BASE_URL}/topup-request`, { method: 'POST', headers: authHeaders(token), body: JSON.stringify({ amount }), }); if (!topupRes.ok) { const err = await topupRes.json() as any; handleTopupError(err); // see Pattern 4 and Pattern 5 } // 3. Poll until balance increases const deadline = Date.now() + POLL_TIMEOUT_MS; while (Date.now() < deadline) { await sleep(POLL_INTERVAL_MS); const current = await getBalance(token); if (!current.ok) continue; // transient error — keep polling const balanceNow = parseFloat(current.balance); if (balanceNow > balanceBefore) { const added = (balanceNow - balanceBefore).toFixed(2); return `Top-up complete. Added €${added}. Balance is now €${current.balance}.`; } } // 4. Timeout — top-up was accepted but not yet reflected return ( `Top-up of €${amount} was accepted and is being processed. ` + 'The balance has not updated within 5 minutes — check again shortly.' ); } function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } ``` **Why 30-second intervals?** Polling more frequently does not speed up settlement and adds unnecessary load. The balance typically updates within 1–3 minutes; 30-second intervals mean you will detect it within the same minute it lands. **Why not fail on timeout?** The top-up was accepted by the API. A timeout means the settlement is taking longer than usual, not that it failed. Telling the user it failed when it will succeed in a few minutes is worse than telling them to check back shortly. ## Pattern 4 — Spending limit escalation **Problem:** The agent hits `AI_TOPUP_LIMIT_EXCEEDED`. The user configured a cumulative spending limit, and the agent has reached it. The agent cannot reset this limit. **Solution:** Surface the issue immediately to the user and suspend autonomous top-up until the user resets the limit. Do not retry. ```typescript function handleTopupError(err: { errorCode: string; error: string }): never { if (err.errorCode === 'AI_TOPUP_LIMIT_EXCEEDED') { // This is a user-action-required error. Do not retry. throw new UserActionRequiredError( 'Your agent spending limit has been reached. ' + 'To continue, please reset the limit in your Holyheld dashboard under Settings → Agentic access. ' + 'Autonomous top-ups are paused until you do.' ); } if (err.errorCode === 'AI_TOPUP_INSUFFICIENT_BALANCE') { // The Holyheld main account does not have enough available balance. throw new UserActionRequiredError( 'Your Holyheld account does not have enough available balance to top up the card. ' + 'Please add funds to your Holyheld account and try again.' ); } // All other errors throw new Error(`Top-up failed: ${err.errorCode} — ${err.error}`); } class UserActionRequiredError extends Error { constructor(message: string) { super(message); this.name = 'UserActionRequiredError'; } } ``` **Catching and routing in the agent loop:** ```typescript try { await maintainBalance(token); } catch (err) { if (err instanceof UserActionRequiredError) { await notifyUser(err.message); // send notification; pause the loop await pauseAutonomousRuns(); // stop until user acknowledges } else { await logError(err); // log for ops; may retry on next run } } ``` **Why distinguish `UserActionRequiredError`?** Most errors are transient and can be retried. `AI_TOPUP_LIMIT_EXCEEDED` and `AI_TOPUP_INSUFFICIENT_BALANCE` are permanent until the user takes action. Retrying them wastes time and can alarm the user with repeated failure notifications. ## Pattern 5 — Insufficient funds escalation **Problem:** The agent tries to top up but the Holyheld main account does not have enough available balance (`AI_TOPUP_INSUFFICIENT_BALANCE`). **Solution:** Notify the user clearly and stop retrying. The agent cannot resolve this — only the user can add funds to their Holyheld account. This is handled by `handleTopupError` in Pattern 4. The key guidance: * Tell the user **exactly what they need to do** (add funds to Holyheld account), not just that there was an error. * Do **not** retry. Each retry will return the same error. * Do **not** attempt to top up a smaller amount automatically. ## Amount formatting The `amount` field in `POST /topup-request` must be a **string** matching `^\d+(\.\d{1,2})?$`. When you calculate an amount programmatically, always format it before sending: ```typescript // ✅ Correct — always convert to a formatted string const topupAmount = (TARGET_EUR - currentBalance).toFixed(2); // e.g. 47.5 → "47.50" // ❌ Wrong — sending a raw number const topupAmount = TARGET_EUR - currentBalance; // e.g. 47.5 — will be rejected (must be a string) // ❌ Wrong — more than 2 decimal places const topupAmount = (1 / 3).toString(); // "0.3333333..." — will be rejected ``` **Never include a currency symbol:** ```typescript // ❌ Wrong const amount = '€50.00'; // ✅ Correct const amount = '50.00'; ``` ## Card data handling `GET /card-data` returns full payment card details. Treat it as short-lived secret material: * Fetch card data only when the agent is actively completing a checkout flow. * Never log `cardNumber`, `CVV`, or the raw response body. * Do not cache card data between runs unless your security model explicitly requires it. * Keep the tool local when exposing it through MCP so the card data does not leave the user's machine. ```typescript async function getCheckoutCardData(token: string) { const res = await fetch(`${BASE_URL}/card-data`, { headers: authHeaders(token), }); const data = await res.json(); if (!res.ok) throw new Error(`${data.errorCode}: ${data.error}`); return data.payload; } ``` ## Idempotency — do not retry top-ups blindly `POST /topup-request` is **not idempotent**. Each call creates a new top-up request on the blockchain. If you receive a network error and cannot determine whether the request was accepted, do **not** immediately retry. **Safe retry sequence:** ```typescript async function safeTopup(amount: string, token: string): Promise { // 1. Record balance before attempting the top-up const before = await getBalance(token); if (!before.ok) throw new Error('Cannot check balance'); const balanceBefore = parseFloat(before.balance); // 2. Attempt the top-up let topupAccepted = false; try { const res = await fetch(`${BASE_URL}/topup-request`, { method: 'POST', headers: authHeaders(token), body: JSON.stringify({ amount }), }); topupAccepted = res.ok; } catch { // Network error — we don't know if the request was accepted } if (!topupAccepted) { // 3. Check whether the balance has already moved before deciding to retry await sleep(5_000); const check = await getBalance(token); if (check.ok && parseFloat(check.balance) > balanceBefore) { // Balance already moved — do not retry return; } // Balance unchanged — safe to retry once await topUpAndConfirm(amount, token); } } ``` ## Anti-patterns ### ❌ Polling immediately after top-up ```typescript // Wrong — 200ms polling ignores the async nature of settlement while (true) { await sleep(200); const balance = await getBalance(token); ... } ``` Use 30-second intervals. The balance will not update in milliseconds, and rapid polling puts unnecessary load on the API. ### ❌ Assuming 200 means the balance is already updated ```typescript // Wrong — treating the accepted response as a settled balance const res = await fetch(`${BASE_URL}/topup-request`, { ... }); if (res.ok) { console.log('Balance topped up successfully'); // ❌ not yet } ``` A `200` from `POST /topup-request` means the request was **accepted**, not that the balance has been updated. Always poll to confirm. ### ❌ Retrying on AI\_TOPUP\_LIMIT\_EXCEEDED or AI\_TOPUP\_INSUFFICIENT\_BALANCE ```typescript // Wrong — both of these require user action; retrying wastes time for (let attempt = 0; attempt < 3; attempt++) { const res = await topup(amount, token); if (res.errorCode === 'AI_TOPUP_LIMIT_EXCEEDED') continue; // ❌ } ``` These errors will not resolve on their own. Notify the user and stop. ### ❌ Sending the amount as a number ```typescript // Wrong — the API requires a string body: JSON.stringify({ amount: 50.00 }) // ❌ sends 50 (number) // Correct body: JSON.stringify({ amount: '50.00' }) // ✅ sends "50.00" (string) ``` ### ❌ Topping up without recording the pre-top-up balance ```typescript // Wrong — no baseline to detect when the balance has changed await fetch(`${BASE_URL}/topup-request`, { ... }); await sleep(POLL_INTERVAL_MS); const balance = await getBalance(token); // how do you know it changed? ``` Always record the balance before the top-up. Without a baseline you cannot reliably detect the update, especially if the user has multiple agents or concurrent activity on the account. ::: tip Related resources * [Top Up Card](/docs/agentic/topup) — full API reference including all error codes * [Get Card Data](/docs/agentic/card-data) — endpoint reference for retrieving card details safely * [Error Reference](/docs/agentic/errors) — recovery guidance for every error code * [Build an MCP Server](/docs/agentic/mcp-guide) — wrap these patterns as Claude tools ::: --- --- url: 'https://docs.brrr.network/docs/use-cases/ai-platforms.md' description: >- How agent frameworks (Claude MCP, LangChain, custom) let an AI act on a user's Holyheld card autonomously — checking balance, retrieving card details for checkout, and topping up within a budget. --- # AI Platforms **Who it's for.** Builders of AI agent platforms, MCP server authors, LangChain tool authors, and anyone giving an LLM the ability to **transact on a user's behalf**. The agent is the actor; the user holds the card; BRRR is the boundary that enforces the budget. This vertical is intentionally **isolated** from the rest of the API surface. It uses a different base URL, a different auth scheme (Bearer token, not `X-Api-Key`), a different scope (one user's own card, not a multi-tenant integration), and a deliberately minimal endpoint set. That isolation is the safety property — an Agentic credential cannot accidentally invoke a settlement or a card-data call outside its narrow surface. ## Typical scenarios * **Threshold-based top-up.** *"Keep my Holyheld card above €50 at all times."* The agent polls balance on a schedule and triggers a Deposit (top-up from the user's main account) whenever the threshold is breached. * **Pre-spend balance check.** Before booking a service for the user, the agent verifies the card has enough EUR — and tops up if not — without surfacing a "low balance" error mid-checkout. * **One-time card-data retrieval for checkout.** The agent fetches card number / CVV / expiry only at the exact moment a payment form needs them, never logs them, and never persists them past the session. * **Budgeted autonomous spending.** A user grants the agent a cumulative spending limit in the Holyheld dashboard. The agent operates within that limit; any attempt beyond it is rejected with `AI_TOPUP_LIMIT_EXCEEDED` and the agent must surface the rejection back to the user. ## Recommended stack | Need | Reach for | |---|---| | Read current EUR balance on the card | [`GET /balance`](/docs/agentic/get-balance) | | Retrieve card credentials for an active checkout | [`GET /card-data`](/docs/agentic/card-data) | | Move EUR from the user's Holyheld main account to the card | [`POST /topup-request`](/docs/agentic/topup) | | Expose these three calls as tools to Claude | [MCP server guide](/docs/agentic/mcp-guide) | | Patterns for retries, polling, error escalation | [Agent patterns](/docs/agentic/agent-patterns) | That's the entire surface. Three endpoints. A complete agent integration is small by design — the smaller the surface, the smaller the blast radius if a prompt-injection or jailbreak tries to push the agent past its boundary. ## Architecture at a glance ``` ┌──────────────────────────────────┐ │ AI Agent │ │ (Claude MCP server, LangChain │ │ tool, OpenAI function call) │ └──────────────┬───────────────────┘ │ Authorization: Bearer ▼ ┌──────────────────────────────────┐ │ Payments for AI Agents API │ │ apicore.holyheld.com │ │ /v4/ai-agents │ │ │ │ GET /balance ← read │ │ GET /card-data ← read │ │ POST /topup-request ← write │ └──────────────┬───────────────────┘ │ enforces cumulative │ spending-limit policy ▼ ┌──────────────────────────────────┐ │ User's Holyheld card balance │ │ (funded from main account) │ └──────────────────────────────────┘ ``` The Bearer token comes from the **agent instructions for a Holyheld card**, not through a partner-onboarding flow. Each card has its own agent scope, and a user may have multiple cards. There is no multi-tenant fan-out — if you operate an agent platform serving many users, each user provides the agent instructions for the card they want the agent to manage, and the agent stores that token for that user-card pair. ## Where to start 1. [Payments for AI Agents — Introduction](/docs/agentic/introduction) 2. [Authentication](/docs/agentic/authentication) — copy card agent instructions and use the Bearer token 3. [MCP Guide](/docs/agentic/mcp-guide) — wrap the three endpoints as Claude tools in minutes ## Where to next * [`GET /balance`](/docs/agentic/get-balance), [`GET /card-data`](/docs/agentic/card-data), [`POST /topup-request`](/docs/agentic/topup) * [Agent patterns](/docs/agentic/agent-patterns) — threshold polling, retry budgets, error escalation * [Errors](/docs/agentic/errors) — `AI_TOPUP_LIMIT_EXCEEDED`, `AI_TOPUP_INSUFFICIENT_BALANCE`, and how an agent should react --- --- url: 'https://docs.brrr.network/docs/api/authentication.md' description: How to authenticate requests using your API key. --- # API Authentication All API requests must be authenticated using an API key passed as a request header. There is no OAuth flow or token exchange — the key is sent directly with every request. ## Authentication header Include this header in every request: ``` X-Api-Key: ``` ## Complete request example ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/api/v4/partner/customer/register \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"customerId": "cust_a1b2c3", "addressEVM": "0xAbCd...1234"}' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/api/v4/partner/customer/register', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ customerId: 'cust_a1b2c3', addressEVM: '0xAbCd...1234', }), }); const data = await response.json(); ``` ```python [Python] import httpx, os response = httpx.post( 'https://api.brrr.network/api/v4/partner/customer/register', headers={'X-Api-Key': os.environ['BRRR_API_KEY']}, json={'customerId': 'cust_a1b2c3', 'addressEVM': '0xAbCd...1234'}, ) data = response.json() ``` ::: ## Security best practices * **Server-side only** — never include your API key in client-side JavaScript, mobile apps, or public repositories. All requests must originate from your backend. * **Environment variables** — store the key in an environment variable (`BRRR_API_KEY`), not hardcoded in source files. * **Rotate on compromise** — if you suspect your key has been exposed, contact immediately. Do not wait — a leaked key allows anyone to make requests on your behalf. ## Rate limits The APIs enforce rate limits per API key. Requests that exceed the limit receive a `429 Too Many Requests` response. **Recommended handling:** ```javascript async function fetchWithRetry(url, options, maxRetries = 3) { for (let attempt = 0; attempt < maxRetries; attempt++) { const response = await fetch(url, options); if (response.status !== 429) return response; const retryAfter = parseInt(response.headers.get('Retry-After') ?? '1', 10); await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000 * Math.pow(2, attempt))); } throw new Error('Max retries exceeded'); } ``` Contact your Holyheld point of contact for information about rate limit thresholds for your integration tier. ## Authentication errors A missing or invalid key returns `403 Forbidden`: ```json { "status": "error", "error": "Access denied", "errorCode": "ACCESS_DENIED" } ``` Verify that: 1. The `X-Api-Key` header is present in every request 2. The key value is correct and has not expired or been revoked 3. You are not sending the key as a query parameter — it must be a header --- --- url: 'https://docs.brrr.network/docs/api/sections.md' description: Overview of the four BRRR API products with flow diagrams and endpoint tables. --- # API Sections BRRR's APIs are organised into four independent products. Each section below shows what it does, the integration flow (where applicable), and a list of its endpoints. ## Web3 API BRRR provides a number of blockchain-related API methods for partners to create better user experiences and rich interfaces: with tokens metadata, balances, price charts, etc. These endpoints are read-only and do not modify any BRRR or on-chain state. They are intended for client-side display and UX calculations. ### Endpoints | Endpoint | Purpose | |----------|---------| | [`POST /v4/web3/tag-info`](/api/web3/get-tag-info) | Resolve a holytag to its public display info (avatar, name). | | [`GET /v4/web3/token-info/{network}/{address}`](/api/web3/get-token-info) | Token metadata + current price for a single token. | | [`POST /v4/web3/token-metadata`](/api/web3/get-token-metadata) | Extended market data for a token (ranges, supply, market cap). | | [`POST /v4/web3/token-chart`](/api/web3/get-token-chart) | Historical price chart data for a token. | | [`POST /v4/web3/evm-balances`](/api/web3/get-evm-balances) | All EVM token balances for a wallet across supported networks. | | [`POST /v4/web3/solana-balances`](/api/web3/get-solana-balances) | All Solana token balances for a wallet. | | [`GET /v4/web3/solana-priority-fee`](/api/web3/get-solana-priority-fee) | Recommended Solana priority fee for transactions. | ## OTC API This API provides a simple crypto to fiat and fiat to crypto settlement for a BRRR partner. OTC orders settle directly between a registered IBAN and a chosen crypto wallet — unlike the [Card API](#card-api) flows, which settle into a Holyheld customer's card balance. ### Who can use OTC OTC access is granted through direct conversations with Holyheld. Approved OTC customers receive: * An API key scoped to the OTC API * Access to the Holyheld OTC web application * One or more registered IBANs on file No self-serve onboarding is available for OTC — contact Holyheld to discuss your use case. ### Sell Crypto → EUR on IBAN ``` POST /api/v4/otc/sell-crypto/quote { network, token, amount, amountType } │ ▼ Quote returned (quoteId, rate, fiatAmount, confirmDeadline) │ ▼ (before confirmDeadline) POST /api/v4/otc/sell-crypto/execute { quoteId, ibanId, reference? } │ ▼ Order created (orderId, receiveAddress) → Customer sends tokenAmount to receiveAddress → BRRR sends fiatAmount to the IBAN via SEPA ``` ### Buy EUR on IBAN → Crypto ``` POST /api/v4/otc/buy-eur/quote { network, token, amount, amountType } │ ▼ Quote returned │ ▼ (before confirmDeadline) POST /api/v4/otc/buy-eur/execute { quoteId, ibanId, reference? } │ ▼ Order created (orderId, BRRR collection IBAN returned) → Customer sends fiatAmount via SEPA from the registered IBAN → BRRR delivers tokenAmount to the customer's wallet ``` ### Endpoints | Endpoint | Description | |----------|-------------| | [`POST /api/v4/otc/sell-crypto/quote`](/api/otc/otc-sell-crypto-quote) | Quote for selling crypto into EUR | | [`POST /api/v4/otc/buy-eur/quote`](/api/otc/otc-buy-eur-quote) | Quote for buying crypto with EUR | | [`POST /api/v4/otc/sell-crypto/execute`](/api/otc/otc-sell-crypto-execute) | Confirm a sell-crypto quote | | [`POST /api/v4/otc/buy-eur/execute`](/api/otc/otc-buy-eur-execute) | Confirm a buy-EUR quote | | [`GET /api/v4/otc/order/status/{orderId}`](/api/otc/otc-get-order-status) | Poll order status | Quotes are valid until their `confirmDeadline` (typically 10–15 minutes). Attempting to execute an expired quote returns an error — fetch a fresh quote and retry. **Base URL:** `https://api.brrr.network`   ·   **Auth:** `X-Api-Key` header ## Multi-tenant Settlement API This API provides capability to BRRR partners to settle crypto to EUR bank account of BRRR partners and their customers. Risk scanning and processing is integrated into a seamless flow, supporting all networks that are supported by BRRR. ### Integration flow overview The complete integration lifecycle follows this sequence: ``` ┌──────────────────────────────────┐ │ PARTNER SYSTEM │ └─────────────────┬────────────────┘ │ ┌─────────────────▼────────────────┐ │ 1. Register customer │ POST /partner/customer/register │ + link EVM wallet address │ POST /partner/customer/add-address │ + (optional) register IBAN │ POST /partner/customer/add-iban └────────────────┬─────────────────┘ │ ┌────────────────▼─────────────────┐ │ 2. BRRR monitors the address │ (automatic — no API call needed) │ for on-chain activity │ └────────────────┬─────────────────┘ │ on-chain activity detected ┌────────────────▼─────────────────┐ │ 3. Retrieve risk assessment │ RISK_ASSESSMENT webhook → Partner │ Receive webhook or poll │ GET /partner/risk/by-customer/{customerId} │ current risk level │ GET /partner/risk/by-address/{addressEVM} └────────────────┬─────────────────┘ │ risk is LOW or MEDIUM ┌────────────────▼─────────────────┐ │ 4. Create settlement quote │ POST /partner/settlement/quote │ (valid for 15 minutes) │ └────────────────┬─────────────────┘ │ ┌────────────────▼─────────────────┐ │ 5. Execute settlement │ POST /partner/settlement/execute │ (optional: ibanId, reference) │ → Partner sends tokens on-chain │ Partner sends token transfer │ │ to receiveAddress │ └────────────────┬─────────────────┘ │ ┌────────────────▼─────────────────┐ │ 6. Poll status / receive webhook │ GET /partner/settlement/status/{quoteId} │ CREATED → CONFIRMED → │ SETTLEMENT_STATUS_CHANGE webhook │ PROCESSING → SENT → FINISHED │ └──────────────────────────────────┘ ``` ### Functional areas * **Customer onboarding** — register customers, link blockchain addresses, attach IBANs * **Customer info** — retrieve a customer's registered addresses and IBANs * **Customer offboarding** — remove customers, addresses, or IBANs * **Risk assessment** — retrieve risk scores for customer addresses * **Monitoring** — track blockchain activity for registered addresses * **Settlement quote** — request a settlement quote (valid 15 minutes) * **Settlement execution** — confirm, execute, and poll settlement status * **Rates** — retrieve exchange rates for supported tokens and networks ### Prerequisites Customers must complete **KYC verification through SumSub** with a `GREEN` status before they can be registered. BRRR will not begin monitoring or risk-assessing addresses until this status is confirmed. **Base URL:** `https://api.brrr.network`   ·   **Auth:** `X-Api-Key` header ## Card API This API provides integration with a Holyheld customers, allowing to process crypto to fiat and fiat to crypto payments using customers cards and accounts. ::: warning 2FA confirmation required Providing operations on behalf of a Holyheld customer requires 2FA confirmation by the customer from the Holyheld App. Your integration must surface the Holyheld-App prompt to the user and wait for confirmation before polling status. ::: ### Deposit flow (crypto → card / SEPA) ``` ┌──────────────────────────────────┐ │ INTEGRATOR │ └─────────────────┬────────────────┘ │ ┌─────────────────▼────────────────┐ │ 1. Resolve destination │ │ (A) Crypto to card: │ POST /v5/offramp/crypto-to-card/tag-hash │ fetch one-time tagHash │ │ (B) Crypto to SEPA: │ POST /v5/offramp/sepa/estimate-fee │ estimate fee, then │ POST /v5/offramp/sepa/create-transfer │ create transfer → tagHash │ └────────────────┬─────────────────┘ │ ┌────────────────▼─────────────────┐ │ 2. Estimate amounts │ POST /v5/offramp/crypto-to-card/evm-* │ (choose EVM or Solana) │ POST /v5/offramp/crypto-to-card/solana-* └────────────────┬─────────────────┘ │ ┌────────────────▼─────────────────┐ │ 3. Execute │ │ (A) Calldata for wallet: │ POST /v5/offramp/crypto-to-card/transaction-data │ (B) Gasless on customer's │ POST /v5/offramp/crypto-to-card/execute-gasless │ behalf → HHTXID │ └────────────────┬─────────────────┘ │ ┌────────────────▼─────────────────┐ │ 4. Poll status │ POST /v5/offramp/status │ (by tagHash, tx hash, or │ OFFRAMP_STATUS_CHANGE webhook │ HHTXID) │ └──────────────────────────────────┘ ``` ### Withdraw flow (EUR card balance → crypto) ``` ┌──────────────────────────────────┐ │ INTEGRATOR │ └─────────────────┬────────────────┘ │ ┌─────────────────▼────────────────┐ │ 1. List available tokens │ POST /v5/onramp/tokens │ for the target network │ └────────────────┬─────────────────┘ │ ┌────────────────▼─────────────────┐ │ 2. Estimate amounts │ POST /v5/onramp/eur-amount │ (token→EUR or EUR→token) │ POST /v5/onramp/token-amount └────────────────┬─────────────────┘ │ ┌────────────────▼─────────────────┐ │ 3. Simulate gas + feasibility │ POST /v5/onramp/estimate └────────────────┬─────────────────┘ │ ┌────────────────▼─────────────────┐ │ 4. Execute │ POST /v5/onramp/execute │ (customer confirms in app) │ → returns HHTXID └────────────────┬─────────────────┘ │ ┌────────────────▼─────────────────┐ │ 5. Poll status │ POST /v5/onramp/status │ (by HHTXID) │ ONRAMP_STATUS_CHANGE webhook └──────────────────────────────────┘ ``` ### Sub-sections | Sub-section | Contents | |-------------|----------| | [Config & Info](/api/card-api/config-and-info/card-get-configuration) | Configuration bundle, address validity, FX rates | | [Offramp — Crypto to Card](/api/card-api/offramp-crypto-to-card/card-get-tag-hash) | Tag Hash, conversion estimates, Transaction Data, Gasless Execution | | [Offramp — SEPA Transfers](/api/card-api/offramp-sepa-transfers/card-estimate-sepa-fee) | SEPA fee estimation, transfer creation | | [Offramp — Status](/api/card-api/offramp-status/card-get-offramp-status) | Unified offramp status polling | | [Buy Crypto (Onramp)](/api/card-api/buy-crypto-onramp/card-get-tokens-list) | Token listing, amount estimation, execution, status | --- --- url: 'https://docs.brrr.network/docs/api/introduction.md' description: >- Overview of the BRRR API surface: products, environments, and request conventions. --- # APIs Introduction BRRR's APIs are organised into four independent products. Each one is summarised on the [API Sections](/docs/api/sections) page, which also includes flow diagrams and endpoint tables. | Section | What it does | |---------|--------------| | [Web3 API](/docs/api/sections#web3-api) | Blockchain-related read helpers: tokens, balances, price charts, holytag info. Useful for building rich UI around crypto. | | [OTC API](/docs/api/sections#otc-api) | Simple crypto ↔ EUR OTC settlement for BRRR partners with registered IBANs. | | [Multi-tenant Settlement API](/docs/api/sections#multi-tenant-settlement-api) | Partners settle crypto to EUR bank accounts of their own customers, with risk scanning integrated into the flow. | | [Card API](/docs/api/sections#card-api) | Integration with individual Holyheld customers: offramp to card or SEPA, onramp to buy crypto. 2FA confirmation required from the customer's Holyheld App. | ## API endpoint All API requests must be sent to the following HTTPS endpoint: `https://api.brrr.network` This endpoint is shared across all integrations. Authentication and access control are handled using API keys — see [Authentication](/docs/api/authentication). ## Request format All requests must use HTTPS. Request bodies must be JSON with the `Content-Type: application/json` header. Responses are always JSON. Timestamps in request and response bodies are Unix timestamps in **seconds** unless otherwise noted. ## Rate limits The APIs enforce rate limits per API key. Requests that exceed the limit receive a `429 Too Many Requests` response with a `Retry-After` header indicating when to retry. Use exponential backoff when handling 429 responses: ```javascript async function fetchWithRetry(url, options, maxRetries = 3) { for (let attempt = 0; attempt < maxRetries; attempt++) { const response = await fetch(url, options); if (response.status !== 429) return response; const retryAfter = parseInt(response.headers.get('Retry-After') ?? '1', 10); await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000 * Math.pow(2, attempt))); } throw new Error('Max retries exceeded'); } ``` Contact your Holyheld point of contact for information about rate limit thresholds for your integration tier. ## Idempotency Understanding which API operations are safe to retry is important for building reliable integrations. **Idempotent by design (safe to retry):** | Operation | Behaviour on repeat call | |-----------|--------------------------| | `GET` requests (risk scores, settlement status, rates) | Always safe — read-only, no side effects. | | Register a customer (`POST /partner/customer/register`) | Returns `CUSTOMER_EXISTS` (400) if the `customerId` is already registered. Your system can treat this as a success if you were retrying a registration. | | Add address to a customer (`POST /partner/customer/add-address`) | Returns `ADDRESS_EXISTS` (400) if the address is already registered. | **Not idempotent — do not retry without checking:** | Operation | Risk | |-----------|------| | Create Settlement Quote (`POST /partner/settlement/quote`) | Each call creates a new quote with a new `quoteId` and a new 15-minute expiry window. | | Execute Settlement (`POST /partner/settlement/execute`) | Submitting the same `quoteId` twice returns an error (quote already executed). Always check settlement status before retrying. | ::: tip Check before retrying settlements If a network error prevents you from receiving the Execute Settlement response, call [Get Settlement Status](/api/settlement/settlement-execution/get-settlement-status) using the `quoteId` before retrying. The settlement may have already been accepted. ::: ::: tip Next steps Start by authenticating your requests — see [Authentication](/docs/api/authentication). Then open [API Sections](/docs/api/sections) to find the section, flow diagram, and endpoint list that match your integration. ::: --- --- url: 'https://docs.brrr.network/docs/agentic/authentication.md' description: >- How to authenticate AI agent requests using the Bearer token included in Holyheld agent instructions. --- # Authentication The Payments for AI Agents API uses **Bearer token authentication**. Every request must include an `Authorization` header containing the token from the agent instructions for a Holyheld card. ::: warning This is not the same as X-Api-Key The APIs use `X-Api-Key` for partner authentication. The Payments for AI Agents API uses a separate Bearer token tied to a single user's Holyheld card. The two keys are obtained differently, work at different endpoints, and cannot be used interchangeably. ::: ## Authentication header Include this header in every request: ``` Authorization: Bearer ``` **Example:** ``` Authorization: Bearer hh_agent_a1b2c3d4e5f6... ``` ## How to obtain agent instructions The user does not create standalone agent tokens. The user creates a Holyheld card, then copies the agent instructions for that card. Those instructions include the Bearer token required for API access. 1. Log in to your Holyheld account. 2. Create a card in the app. 3. After the card is created, click the button to copy the link to the agent instructions. The instructions include the agent token required for API access. If the card has already been created, you can retrieve the instructions again at any time. ## Complete request example ::: code-group ```bash [curl] curl https://apicore.holyheld.com/v4/ai-agents/balance \ -H "Authorization: Bearer $HOLYHELD_AGENT_TOKEN" ``` ```javascript [JavaScript] const response = await fetch( 'https://apicore.holyheld.com/v4/ai-agents/balance', { headers: { Authorization: `Bearer ${process.env.HOLYHELD_AGENT_TOKEN}`, }, } ); const data = await response.json(); ``` ```python [Python] import httpx, os response = httpx.get( 'https://apicore.holyheld.com/v4/ai-agents/balance', headers={'Authorization': f"Bearer {os.environ['HOLYHELD_AGENT_TOKEN']}"}, ) data = response.json() ``` ::: ## Security best practices **Store the token as an environment variable.** Never hardcode it in source files, commit it to version control, or include it in client-side code. ```bash # In your shell profile or .env file HOLYHELD_AGENT_TOKEN=hh_agent_a1b2c3... ``` **Keep the server local when using MCP.** If you are running an MCP server that wraps this API, keep it running locally on the user's machine. The Bearer token never leaves the local environment. **Replace the agent instructions on compromise.** If you suspect the token from an agent instruction has been exposed, get a new agent instruction for the card and update the agent configuration immediately. There is no grace period — an exposed token gives access to the card balance, card details, and top-up capability up to the configured spending limit. **Use one card per agent when isolation matters.** Because the agent token is tied to a card, create separate cards for separate agents or integrations when you want independent spending limits and replacement paths. ## Spending limits Each agent-enabled card can be configured with a **cumulative spending limit** — the maximum total EUR amount the agent is allowed to top up across all requests. This limit is set in the Holyheld dashboard for the card's agent access. When the limit is reached, any further top-up request returns `AI_TOPUP_LIMIT_EXCEEDED`. The agent cannot reset this limit — only the user can, from the dashboard. See [Error Reference](/docs/agentic/errors) for handling this and other authentication errors. ## Authentication errors | HTTP status | Error code | Cause | Fix | |-------------|------------|-------|-----| | `401` | `AI_AUTHORIZATION_INVALID` | `Authorization` header missing entirely | Add the `Authorization: Bearer ` header | | `403` | `AI_AUTHORIZATION_INVALID` | Token is invalid, revoked, or agent access is disabled | Check the token value; get a new agent instruction for the card if needed | Both responses follow the standard error shape: ```json { "status": "error", "errorCode": "AI_AUTHORIZATION_INVALID", "error": "Authorization header missing" } ``` ::: tip Next steps With authentication configured, make your first request: [Get Balance](/docs/agentic/get-balance). ::: --- --- url: 'https://docs.brrr.network/docs/use-cases/baas.md' description: >- How regulated banking-as-a-service platforms, treasury products, and OTC desks settle crypto to EUR for their own KYC'd customers — server-to-server, with risk scanning and SEPA delivery built in. --- # BaaS **Who it's for.** Regulated banking-as-a-service platforms, treasury products, OTC desks, and B2B fintech infrastructure providers whose **own KYC'd customers** need crypto-to-fiat or fiat-to-crypto rails. You operate the customer relationship; BRRR provides the settlement engine and the AML/risk layer. The defining characteristic of this vertical: **server-to-server, no end-user signing.** Your customer's funds either arrive on-chain at a BRRR address (Deposit) or originate from a registered IBAN (OTC). Your application never holds private keys, never surfaces a wallet-connect prompt, and never asks for 2FA — those concerns live with you and your customers separately. ## Typical scenarios * **Crypto cash-out as a feature.** A regulated payments platform offers its KYC'd customers the ability to convert held crypto into EUR delivered to their own bank account. (Multi-tenant Settlement.) * **OTC desk routing.** An OTC desk takes a customer order — *"sell 10 ETH for EUR to IBAN X"* — and routes the leg through BRRR. (OTC API.) * **Treasury product cash management.** A B2B treasury tool periodically converts client crypto holdings to EUR and delivers via SEPA, on a programmed cadence. (Multi-tenant Settlement, scheduled by your system.) * **Compliance-driven Withdraw.** A regulated platform funds customer wallets from registered IBANs for crypto-related products, with KYC tied to your own SumSub flow. (OTC API buy-EUR direction.) ## Recommended stack | Need | Reach for | |---|---| | Settle crypto into customer bank accounts (your customers, not yours) | [Multi-tenant Settlement API](/docs/settlement/onboarding) | | Pre-settlement risk score on a customer wallet | [Risk assessment](/docs/settlement/risk-assessment) — `RISK_ASSESSMENT` webhook + on-demand polling | | Direct crypto ↔ EUR settlement against a single registered IBAN | [OTC API](/docs/api/sections#otc-api) | | Server-side notifications of state changes | [Webhooks](/docs/api/webhooks) — `RISK_ASSESSMENT`, `SETTLEMENT_STATUS_CHANGE` | | Indicative rates for UI display ahead of a quote | [Rates](/docs/settlement/rates) | The Multi-tenant Settlement and OTC APIs are deliberately low-level: they expose the quote → execute → status loop directly, with no opinionated UX layer in between. That's the right shape for a BaaS — your product surfaces the quote in your own UX, and you control retry, expiry, and reconciliation. ## Architecture at a glance ``` ┌────────────────────────────────────┐ │ Your BaaS platform │ │ (your KYC, your customer DB, │ │ your treasury / orchestration) │ └────────────────┬───────────────────┘ │ X-Api-Key ▼ ┌────────────────────────────────────┐ │ BRRR APIs │ │ api.brrr.network │ │ │ │ • Multi-tenant Settlement │ │ register → monitor → quote │ │ → execute → status │ │ │ │ • OTC API │ │ sell-crypto / buy-EUR │ │ against a registered IBAN │ │ │ │ • Risk + monitoring + webhooks │ └─────┬─────────────────────────┬────┘ │ │ ▼ ▼ ┌──────────────┐ ┌──────────────┐ │ Customer │ │ Customer │ │ IBAN │ │ wallet │ │ (SEPA-out) │ │ (token-in) │ └──────────────┘ └──────────────┘ ``` The customer relationship — KYC, contracts, T\&Cs, support — lives entirely on your side. BRRR's contribution is the settlement rail and the AML/risk overlay that sits between an on-chain transfer and a fiat payout. ## Prerequisites * A signed agreement with the partner program (no self-serve onboarding for Multi-tenant Settlement or OTC). * Your own customers must complete **KYC verification through SumSub** with a `GREEN` status before they can be registered. BRRR does not begin monitoring or risk-assessing addresses until that status is confirmed. See [Onboarding](/docs/settlement/onboarding) for the integration sequence. * A webhook endpoint capable of validating signatures and handling out-of-order delivery. ## Where to start 1. [API Authentication](/docs/api/authentication) — `X-Api-Key` and security best practices 2. [Onboarding](/docs/settlement/onboarding) — register a customer and their first EVM address 3. [Risk assessment](/docs/settlement/risk-assessment) — act on `LOW` / `MEDIUM` / `HIGH` / `VERY HIGH` outcomes 4. [Settlement estimation](/docs/settlement/settlement-estimation) and [execution](/docs/settlement/settlement-execution) — the quote → execute → status loop ## Where to next * [Multi-tenant Settlement section](/docs/settlement/onboarding) — full server-to-server lifecycle * [OTC API](/docs/api/sections#otc-api) — IBAN-anchored direct settlement * [Webhooks](/docs/api/webhooks) — signatures, retries, event ordering * [Go-Live Checklist](/docs/go-live) — production readiness --- --- url: 'https://docs.brrr.network/docs/brrr-details.md' description: Onchain contract details. --- # BRRR Details BRRR token is available to use immediately from day one across 12 networks: Arbitrum, Avalanche, Base, Berachain, BNB Smart Chain, Ethereum, Gnosis, Ink, Mode, Optimism, Polygon and Solana. BRRR is a native utility token of Blockchain Reconciliation and Remittance Record protocol, which consists in allowing traditional and existing payment rails and onchain agents to interact with each other seamlessly. BRRR token details: * Ticker: BRRR * Token standard: ERC20, SPL * Token supply: 1,000,000,000 BRRR is natively deployed across Blockchain Reconciliation and Remittance Record supported networks. As the protocol expands, so may the support of BRRR token. BRRR token contracts: * Arbitrum: 0x000008759C99325443735d0D1c4a871E80911111 * Avalanche: 0x000008759C99325443735d0D1c4a871E80911111 * Base: 0x000008759C99325443735d0D1c4a871E80911111 * Berachain: 0x000008759C99325443735d0D1c4a871E80911111 * BNB Smart Chain: 0x5a1B25b0341A9DE674467857BB12f56F2c0aCA3D * Ethereum: 0x000008759C99325443735d0D1c4a871E80911111 * Gnosis: 0x000008759C99325443735d0D1c4a871E80911111 * Hyperliquid: COMING SOON * Ink: 0x000008759C99325443735d0D1c4a871E80911111 * Manta Pacific: COMING SOON * Mode: 0x000008759C99325443735d0D1c4a871E80911111 * Optimism: 0x000008759C99325443735d0D1c4a871E80911111 * Plasma: COMING SOON * Plume: COMING SOON * Polygon: 0x000008759C99325443735d0D1c4a871E80911111 * Solana: BRRRxmRMs3WFvcfbxumhVnLoUVLhC75EAc6WhZfgBRRR * Starknet: COMING SOON --- --- url: 'https://docs.brrr.network/docs/agentic/mcp-guide.md' description: >- Step-by-step guide to building a Claude MCP server that exposes Holyheld balance, card data, and top-up as native AI tools. --- # Build an MCP Server [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is an open standard that lets AI assistants call external tools through a local server. This guide walks you through building an MCP server that exposes your Holyheld card balance, card data, and top-up capability as tools that Claude can call natively. Once installed, you can ask Claude things like: * *"What is my Holyheld card balance?"* * *"Get my Holyheld card details for checkout."* * *"Top up my Holyheld card with €50."* * *"Check my balance. If it's below €20, top it up to €50."* Claude will handle the reasoning; the MCP server handles the API calls. ## What you will build A Node.js MCP server that registers three tools: | Tool | Wraps | Description | |------|-------|-------------| | `get_holyheld_balance` | `GET /balance` | Returns the current EUR balance on the card | | `get_holyheld_card_data` | `GET /card-data` | Returns card details for active checkout flows | | `topup_holyheld_card` | `POST /topup-request` | Tops up the card and polls until the balance updates | ## Prerequisites * **Node.js 18+** — `fetch` is built-in from Node 18 (no extra HTTP library needed) * **npm** or **pnpm** * **Claude Desktop** installed ([download](https://claude.ai/download)) * **Agent instructions** for a Holyheld card; they include the Bearer token — see [Authentication](/docs/agentic/authentication) ## Step 1 — Set up the project ```bash mkdir holyheld-mcp && cd holyheld-mcp npm init -y npm install @modelcontextprotocol/sdk npm install -D typescript @types/node ``` Create `tsconfig.json`: ```json { "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "dist", "rootDir": "src", "strict": true, "esModuleInterop": true }, "include": ["src/**/*.ts"] } ``` Add build scripts to `package.json`: ```json { "type": "module", "scripts": { "build": "tsc", "start": "node dist/index.js" } } ``` Create the source directory: ```bash mkdir src ``` ## Step 2 — Implement the server Create `src/index.ts` with the complete implementation below. Read the inline comments — they explain every decision. ```typescript import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; // ── Configuration ────────────────────────────────────────────────────────────── const BASE_URL = 'https://apicore.holyheld.com/v4/ai-agents'; const POLL_INTERVAL_MS = 30_000; // 30 seconds between balance polls const POLL_TIMEOUT_MS = 5 * 60_000; // 5 minutes maximum wait after top-up const token = process.env.HOLYHELD_AGENT_TOKEN; if (!token) { console.error('Error: HOLYHELD_AGENT_TOKEN environment variable is not set.'); process.exit(1); } const authHeaders = { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }; // ── Helpers ──────────────────────────────────────────────────────────────────── async function getBalance(): Promise<{ ok: true; balance: string } | { ok: false; errorCode: string; error: string }> { const res = await fetch(`${BASE_URL}/balance`, { headers: authHeaders }); const data = await res.json() as any; if (data.status === 'error') return { ok: false, errorCode: data.errorCode, error: data.error }; return { ok: true, balance: data.payload.balance as string }; } async function getCardData(): Promise< | { ok: true; payload: { cardNumber: string; expirationDate: string; cardholderName: string; CVV: string; billingAddress: string; }; } | { ok: false; errorCode: string; error: string } > { const res = await fetch(`${BASE_URL}/card-data`, { headers: authHeaders }); const data = await res.json() as any; if (data.status === 'error') return { ok: false, errorCode: data.errorCode, error: data.error }; return { ok: true, payload: data.payload }; } function isValidAmount(amount: string): boolean { return /^\d+(\.\d{1,2})?$/.test(amount); } function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } // ── MCP Server ───────────────────────────────────────────────────────────────── const server = new Server( { name: 'holyheld-payments', version: '1.0.0' }, { capabilities: { tools: {} } } ); // ── Tool definitions ─────────────────────────────────────────────────────────── server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'get_holyheld_balance', description: 'Get the current EUR balance available on the Holyheld card. ' + 'Use this before deciding whether to top up, and after a top-up to confirm the balance has updated.', inputSchema: { type: 'object', properties: {}, required: [], }, }, { name: 'get_holyheld_card_data', description: 'Get Holyheld card details for an active checkout flow. ' + 'This returns sensitive data, so use it only when the user is about to pay.', inputSchema: { type: 'object', properties: {}, required: [], }, }, { name: 'topup_holyheld_card', description: 'Top up the Holyheld card by transferring funds from the Holyheld main account to the card. ' + 'The top-up may take up to 5 minutes to be reflected in the balance. ' + 'This tool waits and polls until the balance updates or the 5-minute window expires.', inputSchema: { type: 'object', properties: { amount: { type: 'string', description: 'Amount in EUR to top up. Must be a positive decimal string with up to 2 decimal places. ' + 'Examples: "50", "50.00", "12.50". Do not include a currency symbol.', }, }, required: ['amount'], }, }, ], })); // ── Tool handlers ────────────────────────────────────────────────────────────── server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; // ── get_holyheld_balance ── if (name === 'get_holyheld_balance') { const result = await getBalance(); if (!result.ok) { return { content: [{ type: 'text', text: `Failed to retrieve balance: ${result.errorCode} — ${result.error}` }], isError: true, }; } return { content: [{ type: 'text', text: `Your Holyheld card balance is €${result.balance}.` }], }; } // ── get_holyheld_card_data ── if (name === 'get_holyheld_card_data') { const result = await getCardData(); if (!result.ok) { return { content: [{ type: 'text', text: `Failed to retrieve card data: ${result.errorCode} — ${result.error}` }], isError: true, }; } return { content: [{ type: 'text', text: `Card number: ${result.payload.cardNumber}\n` + `Expiration date: ${result.payload.expirationDate}\n` + `Cardholder name: ${result.payload.cardholderName}\n` + `CVV: ${result.payload.CVV}\n` + `Billing address: ${result.payload.billingAddress}`, }], }; } // ── topup_holyheld_card ── if (name === 'topup_holyheld_card') { const { amount } = args as { amount: string }; // Validate amount format before hitting the API if (!isValidAmount(amount)) { return { content: [{ type: 'text', text: `Invalid amount: "${amount}". ` + 'Please provide a positive number with up to 2 decimal places (e.g. "50.00").', }], isError: true, }; } // Record balance before top-up so we can detect the change const before = await getBalance(); if (!before.ok) { return { content: [{ type: 'text', text: `Could not check balance before top-up: ${before.errorCode} — ${before.error}` }], isError: true, }; } const balanceBefore = parseFloat(before.balance); // Request the top-up const topupRes = await fetch(`${BASE_URL}/topup-request`, { method: 'POST', headers: authHeaders, body: JSON.stringify({ amount }), }); if (!topupRes.ok) { const err = await topupRes.json() as any; if (err.errorCode === 'AI_TOPUP_INSUFFICIENT_BALANCE') { return { content: [{ type: 'text', text: 'Top-up failed: your Holyheld account does not have enough available balance. ' + 'Please add funds to your Holyheld account and try again.', }], isError: true, }; } if (err.errorCode === 'AI_TOPUP_LIMIT_EXCEEDED') { return { content: [{ type: 'text', text: 'Top-up failed: your agent spending limit has been reached. ' + 'To continue, please reset the limit in your Holyheld dashboard under Settings → Agentic access.', }], isError: true, }; } return { content: [{ type: 'text', text: `Top-up failed: ${err.errorCode} — ${err.error}` }], isError: true, }; } // Top-up accepted — poll until balance increases or timeout const deadline = Date.now() + POLL_TIMEOUT_MS; while (Date.now() < deadline) { await sleep(POLL_INTERVAL_MS); const current = await getBalance(); if (!current.ok) continue; // transient error — keep polling const balanceNow = parseFloat(current.balance); if (balanceNow > balanceBefore) { const added = (balanceNow - balanceBefore).toFixed(2); return { content: [{ type: 'text', text: `Top-up complete. Added €${added} to your Holyheld card. ` + `Your balance is now €${current.balance} (was €${before.balance}).`, }], }; } } // Timeout — top-up was accepted but balance has not yet updated return { content: [{ type: 'text', text: `Your top-up of €${amount} was accepted and is being processed. ` + 'The balance has not updated within 5 minutes, which can happen during high network load. ' + 'Please check your Holyheld card balance in a few minutes.', }], }; } return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true, }; }); // ── Entrypoint ───────────────────────────────────────────────────────────────── async function main() { const transport = new StdioServerTransport(); await server.connect(transport); } main().catch((error) => { console.error('Server error:', error); process.exit(1); }); ``` ## Step 3 — Build the server ```bash npm run build ``` This compiles TypeScript to `dist/index.js`. Confirm the build succeeded: ```bash node dist/index.js # Should start silently — MCP servers communicate via stdin/stdout, not console output # Press Ctrl+C to exit ``` Note the **absolute path** to `dist/index.js` — you'll need it in the next step: ```bash pwd # e.g. /Users/yourname/holyheld-mcp # Full path: /Users/yourname/holyheld-mcp/dist/index.js ``` ## Step 4 — Configure Claude Desktop Open the Claude Desktop configuration file in a text editor: ::: code-group ```bash [macOS] open -e "$HOME/Library/Application Support/Claude/claude_desktop_config.json" ``` ```powershell [Windows] notepad "$env:APPDATA\Claude\claude_desktop_config.json" ``` ```bash [Linux] nano "$HOME/.config/Claude/claude_desktop_config.json" ``` ::: Add the `holyheld-payments` server entry. If the file is empty, create it from scratch: ```json { "mcpServers": { "holyheld-payments": { "command": "node", "args": ["/absolute/path/to/holyheld-mcp/dist/index.js"], "env": { "HOLYHELD_AGENT_TOKEN": "your-bearer-token-here" } } } } ``` Replace: * `/absolute/path/to/holyheld-mcp/dist/index.js` → the actual absolute path from Step 3 * `your-bearer-token-here` → the Bearer token from the card's agent instructions ::: warning Use the absolute path Claude Desktop launches the MCP server as a subprocess. Relative paths will not work — always use the full absolute path to `dist/index.js`. ::: **Restart Claude Desktop** after saving the configuration file. The MCP tools are loaded at startup. ## Step 5 — Verify the connection In Claude Desktop, look for the tools icon (🔧) in the input bar. Click it to see connected tools. You should see: * `get_holyheld_balance` * `get_holyheld_card_data` * `topup_holyheld_card` If the tools do not appear, check: 1. The JSON in `claude_desktop_config.json` is valid (no trailing commas) 2. The path to `dist/index.js` is correct and absolute 3. `HOLYHELD_AGENT_TOKEN` is set to a valid token value 4. Claude Desktop was fully restarted (quit and reopen — not just the window) ## Step 6 — Test with Claude Try these prompts in Claude Desktop: **Check balance:** > *What is my Holyheld card balance?* Claude calls `get_holyheld_balance` and responds with something like: > *Your Holyheld card balance is €42.02.* **Get card details:** > *Get my Holyheld card details for checkout.* Claude calls `get_holyheld_card_data` and responds with the card number, expiry, CVV, cardholder name, and billing address. Because this is sensitive data, your MCP server should stay local and you should avoid logging tool output. **Top up a fixed amount:** > *Top up my Holyheld card with €30.* Claude calls `topup_holyheld_card` with `amount: "30.00"` and waits up to 5 minutes. When confirmed: > *Top-up complete. Added €30.00 to your Holyheld card. Your balance is now €72.02 (was €42.02).* **Conditional top-up (multi-step reasoning):** > *Check my Holyheld balance. If it's below €20, top it up to €50.* Claude will call `get_holyheld_balance`, reason about the result, and — if needed — call `topup_holyheld_card` with the right amount automatically. **Spending limit hit:** > *Top up my Holyheld card with €200.* If the spending limit is exceeded: > *Top-up failed: your agent spending limit has been reached. To continue, please reset the limit in your Holyheld dashboard under Settings → Agentic access.* ## Security checklist * \[x] Bearer token stored in `claude_desktop_config.json` `env` field — not hardcoded in source * \[x] `dist/index.js` runs locally on your machine — the token never leaves your device * \[x] Full card data from `GET /card-data` is only fetched for active checkout flows and is never logged * \[x] Source code does not contain the token (safe to commit `src/index.ts`) * \[x] `claude_desktop_config.json` should not be committed to version control ::: tip Next steps See [Agent Patterns](/docs/agentic/agent-patterns) for best practices on structuring agent logic around this API — including threshold-based auto top-up, safe polling, and how to handle limit-exceeded scenarios correctly. ::: --- --- url: 'https://docs.brrr.network/docs/changelog.md' description: >- Release notes and recent changes across the APIs, SDK, and Payments for AI Agents API. --- # Changelog Release notes for all BRRR products: APIs, Holyheld SDK, and Payments for AI Agents API. ## April 2026 ### v4.1.5 #### Bug Fixes * Fixed SDK build and export compatibility issues ### v4.1.4 #### Features * Added new network: hyperEVM ## March 2026 ### APIs #### Added * Settlement status enum now includes all 7 documented states: `CREATED`, `CONFIRMED`, `EXPIRED`, `PROCESSING`, `SENT`, `FINISHED`, `ERROR` * `RISK_ASSESSMENT` webhook payload now includes `reviewData` object for `TOPUP` events, containing `fromAddress`, `amount`, and `totalAmount` fields * `SETTLEMENT_STATUS_CHANGE` webhook now fires for all status transitions including terminal states (`FINISHED`, `EXPIRED`, `ERROR`) #### Documentation * Settlement execution overview updated with full state machine diagram and timing guide * Webhook delivery and retry schedule documented (4 retries over ~26 hours) * Idempotency behaviour documented for all endpoints * Multi-language code samples (curl, JavaScript, Python) added across the API Reference ### v4.1.3 #### Features * Updated EVM return values for `offRamp.topup` and `offRamp.topupSelf`: both methods now return the transaction hash ### v0.0.1 — Initial Release #### Added * **Initial release of the Payments for AI Agents API** (`v0.0.1`) — a purpose-built HTTP API for AI agents acting on behalf of a single Holyheld user account. * `GET /balance` endpoint — returns the current EUR card balance as a decimal string. * `GET /card-data` endpoint — returns card number, expiration date, cardholder name, CVV, and billing address for checkout flows. * `POST /topup-request` endpoint — initiates an asynchronous top-up by transferring funds from the Holyheld main account to the Holyheld card. * Bearer token authentication (`Authorization: Bearer `) issued from the Holyheld dashboard under **Settings → Agentic access**. * Configurable per-token cumulative spending limit — enforced server-side; returns `AI_TOPUP_LIMIT_EXCEEDED` when exceeded. #### Error codes introduced | Code | HTTP | Description | |------|------|-------------| | `AI_AUTHORIZATION_INVALID` | 401 / 403 | Missing, invalid, or revoked bearer token | | `AI_TOPUP_INSUFFICIENT_BALANCE` | 500 | Not enough available balance on the user's Holyheld main account | | `AI_TOPUP_LIMIT_EXCEEDED` | 500 | Agent cumulative spending limit reached | | `WRONG_REQUEST` | 400 | Malformed request body (e.g. invalid amount format) | | `INTERNAL_SERVER_ERROR` | 500 | Transient server-side error; retry with exponential backoff | #### Documentation * [Introduction](/docs/agentic/introduction) — API overview, use cases, architecture diagram, and comparison with the APIs. * [Authentication](/docs/agentic/authentication) — Bearer token setup, security best practices, and spending limit configuration. * [Get Balance](/docs/agentic/get-balance) — `GET /balance` endpoint reference with multi-language examples. * [Get Card Data](/docs/agentic/card-data) — `GET /card-data` endpoint reference and guidance for handling sensitive card details. * [Top Up Card](/docs/agentic/topup) — `POST /topup-request` reference, amount format rules, polling pattern, and full error decision tree. * [Error Reference](/docs/agentic/errors) — Recovery guidance for every error code, with TypeScript implementation using `UserActionRequiredError`. * [Build an MCP Server](/docs/agentic/mcp-guide) — Step-by-step guide to wrapping the API as Claude tools using `@modelcontextprotocol/sdk`. * [Agent Patterns](/docs/agentic/agent-patterns) — Best practices: balance-aware spending, threshold-based auto top-up, safe polling, limit escalation, and anti-patterns. ## November 2025 ### APIs #### Added * `quoteType` field added to `QuoteBeneficiary` request body: `fiat_to_token` or `token_to_fiat` * Settlement quote response now includes per-beneficiary `amountGBPReference` for GBP cross-reference reporting #### Changed * `confirmDeadline` window extended from 10 minutes to 15 minutes for settlement quotes ### v4.1.2 #### Features * Added new network: plasma ## October 2025 ### v4.1.1 #### Features * Updated dependencies: viem and internal tools ## September 2025 ### v4.1.0 #### Features * Added `topupSelf` method for off-ramping to the connected user address without tag argument ## June 2025 ### APIs #### Added * `GET /api/v1/rates/{currency}/{network}/{token}` endpoint introduced for real-time exchange rate lookups * `externalTransactionId` field added to `QuoteBeneficiary` to support partner-side reconciliation #### Fixed * `GET /api/v1/customer/risk/{customerId}` now returns all registered addresses for a customer, including those with no activity yet (previously returned an empty array until the first `TOPUP` event) ### v4.0.5 #### Bug Fixes * Fixed return values in internal methods ## May 2025 ### v4.0.4 #### Bug Fixes * Fixed return values in internal methods ### v4.0.3 #### Bug Fixes * Fixed return values in internal methods ### v4.0.2 #### Bug Fixes * Renamed method `evm.offRamp.getAvailableEVMNetworks` to `evm.offRamp.getAvailableNetworks` ### v4.0.1 #### Features * Added support for the Solana network (currently available for off-ramp only) #### BREAKING CHANGES * SDK structure has changed: network-specific functionality is now accessed via sdk.evm and sdk.solana namespaces (e.g. sdk.evm.offRamp, sdk.solana.offRamp). Each level in the hierarchy (e.g. sdk, sdk.evm) may expose shared methods relevant to all submodules * One method was renamed: `offRamp.getTagInfoForTopUp` → `getTagInfo` * Some methods now accept a single configuration object instead of multiple positional arguments: `offRamp.convertTokenToEUR`, `offRamp.convertEURToToken`, `offRamp.getTopUpEstimation`, `offRamp.topup`, `onRamp.convertTokenToEUR`, `onRamp.convertEURToToken`, `onRamp.getOnRampEstimation`, `onRamp.requestOnRamp` * The return structure of the method `evm.getWalletBalances` has changed * Some TypeScript types were renamed for consistency with the new SDK structure: `RequestOnRampResult` → `RequestOnRampEVMResult`, `WalletBalances` → `WalletBalancesEVM`, `WalletToken` → `WalletTokenEVM`, `ConvertTopUpData` → `ConvertTopUpDataEVM`, `TransferData` → `TransferDataEVM`, `NetworkInfo` → `NetworkInfoEVM` * The SDK now requires a Node.js Buffer polyfill See the updated [SDK documentation](/sdk/introduction) for details ## March 2025 ### APIs — Initial Release #### Added * Initial public release of the APIs * Customer registration: `POST /api/v1/customer/register`, `POST /api/v1/customer/register/address` * Risk assessment: `GET /api/v1/customer/risk/{customerId}`, `GET /api/v1/customer/risk/address/{addressEVM}` * Settlement estimation: `POST /api/v1/settlement/quote` * Settlement execution: `POST /api/v1/settlement/execute`, `GET /api/v1/settlement/status/{quoteId}` * Offboarding: `POST /api/v1/customer/remove`, `POST /api/v1/customer/remove/address` * Webhooks: `RISK_ASSESSMENT`, `SETTLEMENT_STATUS_CHANGE` ### v3.2.5 #### Features * Corrected HTTP headers in requests ### v3.2.4 #### Features * Updated dependencies: viem and internal tools ### v3.2.3 #### Features * Added networks (sonic, hyperliquid, berachain, plume) * Updated viem to ^2.22.23 ## January 2025 ### v3.2.2 #### Features * Updated dependencies: viem and internal tools ## December 2024 ### v3.2.1 #### Bug Fixes * Fixed the method `onRamp.watchRequestId` ### v3.2.0 #### Features * Added method `onRamp.getOnRampEstimation` #### BREAKING CHANGES * Changed the arguments and the return value of the method `onRamp.watchRequestId`. See [Withdraw](/docs/on-ramp) * Changed the arguments of the method `offRamp.getTopUpEstimation`. See [Deposit](/docs/off-ramp) * The `offRamp.getTopUpEstimation` method now supports estimation only for native tokens used for gas fees. ## November 2024 ### v3.1.0 #### Features * Fixed conversion methods `onRamp.convertTokenToEUR` and `onRamp.convertEURToToken` * Added properties `minOnRampAmountInEUR` and `maxOnRampAmountInEUR` in the method `getServerSettings` #### BREAKING CHANGES * Method `onRamp.requestOnRamp` no longer requires `WalletClient` to be passed. Changed the order and number of arguments. See [Withdraw](/docs/on-ramp) * Changed the arguments in the methods `onRamp.convertTokenToEUR` and `onRamp.convertEURToToken`. See [Withdraw](/docs/on-ramp) ### v3.0.1 #### Bug Fixes * Fixed the method `onRamp.getAvailableNetworks` ### v3.0.0 #### Features * Added on-ramp flow #### BREAKING CHANGES * The methods `getTagInfoForTopUp`, `convertTokenToEUR`, `convertEURToToken`, and `topup` are now invoked via the `offRamp` object for the off-ramp flow. For example: `holyheldSDK.offRamp.getTagInfoForTopUp('SDKTEST')` ## September 2024 ### v2.1.2 #### Features * Changed the maximum limit for the test tag (1 EUR) ## August 2024 ### v2.1.1 #### Features * Added networks (bsc, manta, alephzero) ### v2.1.0 #### Features * Added method `validateAddress` ## March 2024 ### v2.0.0 #### Features * Added methods `getAvailableNetworks` and `getTopUpEstimation` * Updated viem to ^2.7.8 #### BREAKING CHANGES * Added `init` asynchronous method that must be called after instantiating an SDK object * Changed the way to call utility methods `getNetwork`, `getNetworkByChainId`, `getNetworkChainId`: after SDK initialization they are available as SDK object methods * Changed the return value in utility methods `getNetwork` and `getNetworkByChainId`, see `NetworkInfo` type in README ## February 2024 ### v1.2.4 #### Bug Fixes * Fixed maximum amount calculation ## December 2023 ### v1.2.3 #### Bug Fixes * Fixed getting an avatar in the method `getTagInfoForTopUp` ## November 2023 ### v1.2.2 #### Bug Fixes * Fixed error handling in methods: `getServerSettings`, `getTagInfoForTopUp`, `convertTokenToEUR`, `convertEURToToken` ### v1.2.1 #### Bug Fixes * Updated `files` field in package.json ### v1.2.0 #### Features * Updated USDC to Circle USDC for Polygon and Optimism --- --- url: 'https://docs.brrr.network/sdk/references/off-ramp/convert-eur-to-token.md' description: >- Get the token amount required to reach a target EUR value — step 3 of the off-ramp flow. --- # `convertEURToToken` (Off-Ramp) Get EUR to token quote. `convertEURToToken` method returns a calculated token amount to match provided (expected) EUR amount. This method also returns [`transferData`](/sdk/references/off-ramp/convert-eur-to-token#transferdata-optional), a structured object that can contain token-specific transfer, unwrap, or swap data and is passed unchanged into the off-ramp transaction. ## Usage ::: code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.offRamp.convertEURToToken({ walletAddress: '0x...', tokenAddress: '0x...', tokenDecimals: 6, amount: '9.99', network: Network.ethereum, }); ``` ```typescript [Solana] import { SolanaNetwork } from '@holyheld/sdk'; const data = await holyheldSDK.solana.offRamp.convertEURToToken({ walletAddress: '...', tokenAddress: '...', tokenDecimals: 6, amount: '9.99', network: SolanaNetwork.Mainnet, }); ``` ::: ## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `walletAddress` | `string` | Yes | The wallet address initiating the transaction (EVM or Solana). | | `tokenAddress` | `string` | Yes | Contract address of the token to receive. | | `tokenDecimals` | `number` | Yes | Number of decimal places for the token (e.g. `6` for USDC, `18` for ETH). | | `amount` | `string` | Yes | Target EUR amount, as a decimal string (e.g. `"9.99"` for 9.99 EUR). | | `network` | `Network` | `SolanaNetwork` | Yes | The chain the token lives on. Must match `tokenAddress`. | ### walletAddress Wallet address * **Type:** `string` :::code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.offRamp.convertEURToToken({ walletAddress: '0x...', // [!code highlight] tokenAddress: '0x...', tokenDecimals: 6, amount: '9.99', network: Network.ethereum, }); ``` ```typescript [Solana] import { SolanaNetwork } from '@holyheld/sdk'; const data = await holyheldSDK.solana.offRamp.convertEURToToken({ walletAddress: '...', // [!code highlight] tokenAddress: '...', tokenDecimals: 6, amount: '9.99', network: SolanaNetwork.Mainnet, }); ``` ::: ### tokenAddress Token address * **Type:** `string` :::code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.offRamp.convertEURToToken({ walletAddress: '0x...', tokenAddress: '0x...', // [!code highlight] tokenDecimals: 6, amount: '9.99', network: Network.ethereum, }); ``` ```typescript [Solana] import { SolanaNetwork } from '@holyheld/sdk'; const data = await holyheldSDK.solana.offRamp.convertEURToToken({ walletAddress: '...', tokenAddress: '...', // [!code highlight] tokenDecimals: 6, amount: '9.99', network: SolanaNetwork.Mainnet, }); ``` ::: ### tokenDecimals Token decimals * **Type:** `Number` :::code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.offRamp.convertEURToToken({ walletAddress: '0x...', tokenAddress: '0x...', tokenDecimals: 6, // [!code highlight] amount: '9.99', network: Network.ethereum, }); ``` ```typescript [Solana] import { SolanaNetwork } from '@holyheld/sdk'; const data = await holyheldSDK.solana.offRamp.convertEURToToken({ walletAddress: '...', tokenAddress: '...', tokenDecimals: 6, // [!code highlight] amount: '9.99', network: SolanaNetwork.Mainnet, }); ``` ::: ### amount EUR amount * **Type:** `String` :::code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.offRamp.convertEURToToken({ walletAddress: '0x...', tokenAddress: '0x...', tokenDecimals: 6, amount: '9.99', // [!code highlight] network: Network.ethereum, }); ``` ```typescript [Solana] import { SolanaNetwork } from '@holyheld/sdk'; const data = await holyheldSDK.solana.offRamp.convertEURToToken({ walletAddress: '...', tokenAddress: '...', tokenDecimals: 6, amount: '9.99', // [!code highlight] network: SolanaNetwork.Mainnet, }); ``` ::: ### network Token network * **Type:** `Network` | `SolanaNetwork` :::code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.offRamp.convertEURToToken({ walletAddress: '0x...', tokenAddress: '0x...', tokenDecimals: 6, amount: '9.99', network: Network.ethereum, // [!code highlight] }); ``` ```typescript [Solana] import { SolanaNetwork } from '@holyheld/sdk'; const data = await holyheldSDK.solana.offRamp.convertEURToToken({ walletAddress: '...', tokenAddress: '...', tokenDecimals: 6, amount: '9.99', network: SolanaNetwork.Mainnet, // [!code highlight] }); ``` ::: ## Returns Returns a `Promise` with the required token amount and routing data for the transaction. :::code-group ```typescript [EVM] import type { TransferDataEVM } from '@holyheld/sdk'; type ConvertTopUpDataEVM = { tokenAmount: string; EURAmount: string; transferData?: TransferDataEVM; } ``` ```typescript [Solana] import type { TransferDataSolana } from '@holyheld/sdk'; type ConvertTopUpDataSolana = { tokenAmount: string; EURAmount: string; transferData?: TransferDataSolana; } ``` ::: ### tokenAmount Calculated token amount equivalent to the requested EUR amount * **Type:** `String` * **Example:** `1.99` :::code-group ```typescript [EVM] import type { TransferDataEVM } from '@holyheld/sdk'; type ConvertTopUpDataEVM = { tokenAmount: string; // [!code highlight] EURAmount: string; transferData?: TransferDataEVM; } ``` ```typescript [Solana] import type { TransferDataSolana } from '@holyheld/sdk'; type ConvertTopUpDataSolana = { tokenAmount: string; // [!code highlight] EURAmount: string; transferData?: TransferDataSolana; } ``` ::: ### EURAmount EUR valuation of the token amount provided * **Type:** `String` * **Example:** `314.25` :::code-group ```typescript [EVM] import type { TransferDataEVM } from '@holyheld/sdk'; type ConvertTopUpDataEVM = { tokenAmount: string; EURAmount: string; // [!code highlight] transferData?: TransferDataEVM; } ``` ```typescript [Solana] import type { TransferDataSolana } from '@holyheld/sdk'; type ConvertTopUpDataSolana = { tokenAmount: string; EURAmount: string; // [!code highlight] transferData?: TransferDataSolana; } ``` ::: ### transferData (optional) Data to be passed in sending transaction for this specific token (and amount) * **Type:** `TransferDataEVM` | `TransferDataSolana` :::code-group ```typescript [EVM] import type { TransferDataEVM } from '@holyheld/sdk'; type ConvertTopUpDataEVM = { tokenAmount: string; EURAmount: string; transferData?: TransferDataEVM; // [!code highlight] } ``` ```typescript [Solana] import type { TransferDataSolana } from '@holyheld/sdk'; type ConvertTopUpDataSolana = { tokenAmount: string; EURAmount: string; transferData?: TransferDataSolana; // [!code highlight] } ``` ::: --- --- url: 'https://docs.brrr.network/sdk/references/on-ramp/convert-eur-to-token.md' description: >- Calculate the token amount equivalent to a given EUR input — optional step in the on-ramp flow. --- # `convertEURToToken` (On-Ramp) This method returns a calculated token amount to match requested EUR amount. ## Usage ::: code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.onRamp.convertEURToToken({ tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', tokenNetwork: Network.ethereum, amount: '11.11', }); ``` ::: ## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `tokenAddress` | `string` | Yes | Contract address of the token to receive. | | `tokenNetwork` | `Network` | Yes | The chain the token will arrive on. | | `amount` | `string` | Yes | EUR amount to convert, as a decimal string. | ### Token Address Address of the token * **Type:** `string` ::: code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.onRamp.convertEURToToken({ tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // [!code highlight] tokenNetwork: Network.ethereum, amount: '11.11', }); ``` ::: ### Token Network Token's network * **Type:** `Network` ::: code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.onRamp.convertEURToToken({ tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', tokenNetwork: Network.ethereum, // [!code highlight] amount: '11.11', }); ``` ::: ### Amount EUR amount * **Type:** `String` ::: code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.onRamp.convertEURToToken({ tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', tokenNetwork: Network.ethereum, amount: '11.11' // [!code highlight] }); ``` ::: ## Returns * **Type:** `String` Token amount equivalent to the given EUR input, as a decimal string (e.g. `"10.47"`). --- --- url: 'https://docs.brrr.network/sdk/references/off-ramp/convert-token-to-eur.md' description: Get a EUR quote for a given token amount — step 3 of the off-ramp flow. --- # `convertTokenToEUR` (Off-Ramp) Get token to EUR quote. This method estimates a token's EUR value before initiating an off-ramp. `convertTokenToEUR` can also be used in scenarios where the token to be sent is preset and not user-selectable. This method also returns [`transferData`](/sdk/references/off-ramp/convert-token-to-eur#transferdata-optional) — a structured object that can contain token-specific transfer, unwrap, or swap data and is passed unchanged into the off-ramp transaction. ## Usage :::code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.offRamp.convertTokenToEUR({ walletAddress: '0x...', tokenAddress: '0x...', tokenDecimals: 6, amount: '9.99', network: Network.ethereum, }); ``` ```typescript [Solana] import { SolanaNetwork } from '@holyheld/sdk'; const data = await holyheldSDK.solana.offRamp.convertTokenToEUR({ walletAddress: '...', tokenAddress: '...', tokenDecimals: 6, amount: '9.99', network: SolanaNetwork.Mainnet, }); ``` ::: ## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `walletAddress` | `string` | Yes | The wallet address initiating the transaction (EVM or Solana). | | `tokenAddress` | `string` | Yes | Contract address of the token to convert. | | `tokenDecimals` | `number` | Yes | Number of decimal places for the token (e.g. `6` for USDC, `18` for ETH). | | `amount` | `string` | Yes | Token amount to convert, as a decimal string (e.g. `"9.99"` for 9.99 USDC). | | `network` | `Network` | `SolanaNetwork` | Yes | The chain the token lives on. Must match `tokenAddress`. | ### walletAddress Wallet address * **Type:** `string` :::code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.offRamp.convertTokenToEUR({ walletAddress: '0x...', // [!code highlight] tokenAddress: '0x...', tokenDecimals: 6, amount: '9.99', network: Network.ethereum, }); ``` ```typescript [Solana] import { SolanaNetwork } from '@holyheld/sdk'; const data = await holyheldSDK.solana.offRamp.convertTokenToEUR({ walletAddress: '...', // [!code highlight] tokenAddress: '...', tokenDecimals: 6, amount: '9.99', network: SolanaNetwork.Mainnet, }); ``` ::: ### tokenAddress Token address * **Type:** `string` :::code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.offRamp.convertTokenToEUR({ walletAddress: '0x...', tokenAddress: '0x...', // [!code highlight] tokenDecimals: 6, amount: '9.99', network: Network.ethereum, }); ``` ```typescript [Solana] import { SolanaNetwork } from '@holyheld/sdk'; const data = await holyheldSDK.solana.offRamp.convertTokenToEUR({ walletAddress: '...', tokenAddress: '...', // [!code highlight] tokenDecimals: 6, amount: '9.99', network: SolanaNetwork.Mainnet, }); ``` ::: ### tokenDecimals Token decimals * **Type:** `Number` :::code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.offRamp.convertTokenToEUR({ walletAddress: '0x...', tokenAddress: '0x...', tokenDecimals: 6, // [!code highlight] amount: '9.99', network: Network.ethereum, }); ``` ```typescript [Solana] import { SolanaNetwork } from '@holyheld/sdk'; const data = await holyheldSDK.solana.offRamp.convertTokenToEUR({ walletAddress: '...', tokenAddress: '...', tokenDecimals: 6, // [!code highlight] amount: '9.99', network: SolanaNetwork.Mainnet, }); ``` ::: ### amount Token amount * **Type:** `String` :::code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.offRamp.convertTokenToEUR({ walletAddress: '0x...', tokenAddress: '0x...', tokenDecimals: 6, amount: '9.99', // [!code highlight] network: Network.ethereum, }); ``` ```typescript [Solana] import { SolanaNetwork } from '@holyheld/sdk'; const data = await holyheldSDK.solana.offRamp.convertTokenToEUR({ walletAddress: '...', tokenAddress: '...', tokenDecimals: 6, amount: '9.99', // [!code highlight] network: SolanaNetwork.Mainnet, }); ``` ::: ### network Token network * **Type:** `Network` | `SolanaNetwork` :::code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.offRamp.convertTokenToEUR({ walletAddress: '0x...', tokenAddress: '0x...', tokenDecimals: 6, amount: '9.99', network: Network.ethereum, // [!code highlight] }); ``` ```typescript [Solana] import { SolanaNetwork } from '@holyheld/sdk'; const data = await holyheldSDK.solana.offRamp.convertTokenToEUR({ walletAddress: '...', tokenAddress: '...', tokenDecimals: 6, amount: '9.99', network: SolanaNetwork.Mainnet, // [!code highlight] }); ``` ::: ## Returns Returns a `Promise` with the EUR equivalent and routing data for the transaction. :::code-group ```typescript [EVM] import type { TransferDataEVM } from '@holyheld/sdk'; type ConvertTopUpDataEVM = { tokenAmount: string; EURAmount: string; transferData?: TransferDataEVM; } ``` ```typescript [Solana] import type { TransferDataSolana } from '@holyheld/sdk'; type ConvertTopUpDataSolana = { tokenAmount: string; EURAmount: string; transferData?: TransferDataSolana; } ``` ::: ### tokenAmount Amount of token that was passed to query * **Type:** `String` * **Example:** `1.99` :::code-group ```typescript [EVM] import type { TransferDataEVM } from '@holyheld/sdk'; type ConvertTopUpDataEVM = { tokenAmount: string; // [!code highlight] EURAmount: string; transferData?: TransferDataEVM; } ``` ```typescript [Solana] import type { TransferDataSolana } from '@holyheld/sdk'; type ConvertTopUpDataSolana = { tokenAmount: string; // [!code highlight] EURAmount: string; transferData?: TransferDataSolana; } ``` ::: ### EURAmount EUR valuation of the token amount provided * **Type:** `String` * **Example:** `314.25` :::code-group ```typescript [EVM] import type { TransferDataEVM } from '@holyheld/sdk'; type ConvertTopUpDataEVM = { tokenAmount: string; EURAmount: string; // [!code highlight] transferData?: TransferDataEVM; } ``` ```typescript [Solana] import type { TransferDataSolana } from '@holyheld/sdk'; type ConvertTopUpDataSolana = { tokenAmount: string; EURAmount: string; // [!code highlight] transferData?: TransferDataSolana; } ``` ::: ### transferData (optional) Data to be passed in sending transaction for this specific token (and amount) * **Type:** `TransferDataEVM` | `TransferDataSolana` :::code-group ```typescript [EVM] import type { TransferDataEVM } from '@holyheld/sdk'; type ConvertTopUpDataEVM = { tokenAmount: string; EURAmount: string; transferData?: TransferDataEVM; // [!code highlight] } ``` ```typescript [Solana] import type { TransferDataSolana } from '@holyheld/sdk'; type ConvertTopUpDataSolana = { tokenAmount: string; EURAmount: string; transferData?: TransferDataSolana; // [!code highlight] } ``` ::: --- --- url: 'https://docs.brrr.network/sdk/references/on-ramp/convert-token-to-eur.md' description: >- Estimate the EUR value of a token amount before initiating an on-ramp — optional step in the on-ramp flow. --- # `convertTokenToEUR` (On-Ramp) This method estimates a token's value in EUR before initiating an on-ramp. `convertTokenToEUR` can also be used in scenarios where the token to be sent is preset and not user-selectable. ## Usage ::: code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.onRamp.convertTokenToEUR({ tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', tokenNetwork: Network.ethereum, amount: '11.11', }); ``` ::: ## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `tokenAddress` | `string` | Yes | Contract address of the token to convert. | | `tokenNetwork` | `Network` | Yes | The chain the token belongs to. | | `amount` | `string` | Yes | Token amount to convert, as a decimal string. | ### Token Address Address of the token * **Type:** `string` ::: code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.onRamp.convertTokenToEUR({ tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // [!code highlight] tokenNetwork: Network.ethereum, amount: '11.11', }); ``` ::: ### Token Network Token's network * **Type:** `Network` ::: code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.onRamp.convertTokenToEUR({ tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', tokenNetwork: Network.ethereum, // [!code highlight] amount: '11.11', }); ``` ::: ### Amount Native token amount in `Units` * **Type:** `String` ::: code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.onRamp.convertTokenToEUR({ tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', tokenNetwork: Network.ethereum, amount: '11.11' // [!code highlight] }); ``` ::: ## Returns * **Type:** `String` EUR equivalent of the given token amount, as a decimal string (e.g. `"11.23"`). --- --- url: >- https://docs.brrr.network/api/settlement/settlement-estimation/create-settlement-quote.md description: Create a settlement quote required for settlement execution. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/api/v4/partner/settlement/quote \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "currency": "EUR", "network": "ethereum", "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "beneficiaries": [ { "externalTransactionId": "txn_001", "customerId": "cust_a1b2c3d4", "customerAddress": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", "quoteType": "fiat_to_token", "amount": "100.00" } ] }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/api/v4/partner/settlement/quote', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ currency: 'EUR', network: 'ethereum', token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum beneficiaries: [ { externalTransactionId: 'txn_001', customerId: 'cust_a1b2c3d4', customerAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', quoteType: 'fiat_to_token', amount: '100.00', // EUR amount to settle }, ], }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Quote creation failed: ${error.errorCode}`); } const data = await response.json(); const quote = data.payload; console.log('Quote ID:', quote.quoteId); console.log('Confirm before:', new Date(quote.confirmDeadline * 1000).toISOString()); console.log('Total token amount to send:', quote.totalAmountIn); // Store quote — pass it as the body to execute-settlement ``` ```python [Python] import os import httpx from datetime import datetime response = httpx.post( 'https://api.brrr.network/api/v4/partner/settlement/quote', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ 'currency': 'EUR', 'network': 'ethereum', 'token': '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', # USDC on Ethereum 'beneficiaries': [ { 'externalTransactionId': 'txn_001', 'customerId': 'cust_a1b2c3d4', 'customerAddress': '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', 'quoteType': 'fiat_to_token', 'amount': '100.00', # EUR amount to settle } ], }, ) response.raise_for_status() data = response.json() quote = data['payload'] print(f"Quote ID: {quote['quoteId']}") print(f"Confirm before: {datetime.fromtimestamp(quote['confirmDeadline']).isoformat()}") print(f"Total token amount to send: {quote['totalAmountIn']}") # Store quote — pass it as the body to execute-settlement ``` ::: ::: warning Quote expiry Each quote has a 15-minute validity window. The `confirmDeadline` field in the response is a Unix timestamp indicating the latest time you can call [Execute Settlement](/api/settlement/settlement-execution/execute-settlement). After that, the quote is expired and a new one must be created. ::: --- --- url: >- https://docs.brrr.network/api/card-api/offramp-sepa-transfers/card-create-sepa-transfer.md description: Create a one-time SEPA transfer destination for an offramp. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/v5/offramp/sepa/create-transfer \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "iban": "DE89370400440532013000", "beneficiaryName": "John Doe", "eurAmount": "500.00", "feeAmount": "0.35", "reference": "Invoice #2024-0042" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v5/offramp/sepa/create-transfer', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ 'iban': 'DE89370400440532013000', 'beneficiaryName': 'John Doe', 'eurAmount': '500.00', 'feeAmount': '0.35', 'reference': 'Invoice #2024-0042' }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Request failed: ${error.errorCode}`); } const data = await response.json(); console.log(data.payload); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/v5/offramp/sepa/create-transfer', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ "iban": "DE89370400440532013000", "beneficiaryName": "John Doe", "eurAmount": "500.00", "feeAmount": "0.35", "reference": "Invoice #2024-0042" }, ) response.raise_for_status() print(response.json()['payload']) ``` ::: --- --- url: 'https://docs.brrr.network/docs/off-ramp.md' description: >- Move tokens from a user's wallet into EUR on a Holyheld card. The SDK handles user-facing flows; the APIs handle server-side settlement. --- # Deposit A **Deposit** (also called off-ramp — moving from crypto to fiat) lets a user send tokens from their connected wallet and receive EUR on a Holyheld card. The user signs one blockchain transaction; BRRR handles conversion, routing across chains, and crediting the recipient's card. Deposits are the most common flow on the platform. Reach for them whenever your application needs to turn on-chain assets into spendable fiat — payouts to creators, customer cash-out, agent operating budgets, or simply letting a user move funds out of a wallet. ## How a Deposit works 1. Your application proposes a Deposit: source wallet, token, amount, and recipient (a `$holytag` for sending to anyone, or the wallet's own card). 2. BRRR returns a live conversion quote — token amount in, EUR amount out — together with the routing data that describes how the transaction will execute on-chain. 3. The user signs in their wallet. If the token needs an allowance approval or supports an EIP-2612 permit, the SDK handles that transparently in the same flow. 4. BRRR receives the tokens, converts at the quoted rate, and credits EUR to the recipient's Holyheld card. The user's private key never leaves their wallet. BRRR is non-custodial through the entire signing flow. ## Choose your integration surface | Surface | When to use | Auth | |---|---|---| | [SDK](/sdk/introduction) | A user-facing app where the end user signs the transaction in their wallet | SDK API key | | [Card API](/docs/api/sections) | You already operate a card, custody, or payments product and want low-level building blocks | `X-Api-Key` | | [OTC API](/docs/api/sections) | Server-to-server quote and execution against a known recipient | `X-Api-Key` | | [Multi-tenant Settlement API](/docs/settlement/settlement-execution) | A regulated partner running settlement for its own customers | `X-Api-Key` | If your end user controls the wallet and signs in the browser, choose the SDK. If you control the wallet and operate from a server, choose the API surface that matches your role. ## Deposit with the SDK The `topup` method (which executes a Deposit) is the primary entry point. A minimal end-to-end 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.isTopupEnabled) { throw new Error('Deposits unavailable'); } const tag = await sdk.getTagInfo('RECIPIENT_HOLYTAG'); if (!tag.found) { throw new Error('Recipient not found'); } const quote = await sdk.evm.offRamp.convertTokenToEUR({ walletAddress: '0x...', tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum tokenDecimals: 6, amount: '100', network: Network.ethereum, }); await sdk.evm.offRamp.topup({ publicClient, walletClient, walletAddress: '0x...', tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', tokenNetwork: Network.ethereum, tokenAmount: quote.tokenAmount, transferData: quote.transferData, holytag: 'RECIPIENT_HOLYTAG', supportsSignTypedDataV4: true, }); ``` Solana follows the same shape via `sdk.solana.offRamp.topup` with a `Connection` instead of a viem `publicClient`. See the [`topup` reference](/sdk/references/off-ramp/topup) for parameters and callbacks, and [`topupSelf`](/sdk/references/off-ramp/topup-self) when the destination is the connected wallet's own card. ## Deposit through the APIs Server-side Deposits skip the wallet-signing flow and instead rely on: * A registered partner (Multi-tenant Settlement) or your own platform credentials (Card API, OTC API). * An API-issued quote that locks the conversion rate. * An execution call that posts the on-chain transfer from your custody system. Endpoint inventories live in the [API Sections](/docs/api/sections) page; the [Multi-tenant Settlement section](/docs/settlement/settlement-estimation) walks through the partner-specific quote → execute → status loop. ## Recipient, currency, and assets * **Recipient.** Any Holyheld user identified by `$holytag`, or the connected wallet's own card via `topupSelf`. The `$` prefix appears in code; in prose, refer to the value as a holytag. * **Settlement currency.** EUR. All Deposits credit a Holyheld card balance. * **Supported assets and networks.** USDC, USDT, native gas tokens, and a curated set of EVM and Solana tokens. The canonical list is on the [Supported Networks](/docs/supported-networks) page — that page is the source of truth, not this hub. ## Errors The SDK throws typed errors via `HolyheldSDKError` with codes from `HolyheldSDKErrorCode`. Common Deposit codes include `UnexpectedWalletNetwork`, `UserRejectedTransaction`, `UserRejectedSignature`, and `FailedTopUp`. The full list — and how to catch them — lives in [SDK error handling](/sdk/error-handling). ## Where to next * [`topup`](/sdk/references/off-ramp/topup) and [`topupSelf`](/sdk/references/off-ramp/topup-self) reference * [`convertTokenToEUR`](/sdk/references/off-ramp/convert-token-to-eur) and [`convertEURToToken`](/sdk/references/off-ramp/convert-eur-to-token) * [Orchestrate](/docs/orchestration) — the routing layer that makes cross-chain Deposits work * [Webhooks](/docs/api/webhooks) — for server-side notification when a Deposit settles --- --- url: 'https://docs.brrr.network/docs/develop-with-ai.md' description: >- Use AI coding assistants to build BRRR integrations faster with llms.txt and ready-made system prompts. --- # Develop with AI BRRR documentation is optimised for AI-assisted development. You can load the full docs into any AI coding assistant — Claude, ChatGPT, Cursor, GitHub Copilot, or any LLM that accepts context — and get accurate, working code for your integration without manually reading every page. ## llms.txt BRRR publishes machine-readable versions of its documentation following the [llms.txt](https://llmstxt.org) standard: | File | Contents | Best for | |------|----------|----------| | [`/llms.txt`](https://docs.brrr.network/llms.txt) | Index of all documentation pages with short descriptions | Lightweight context loading | | [`/llms-full.txt`](https://docs.brrr.network/llms-full.txt) | Full text of every documentation page concatenated | Complete context for complex integrations | ## Loading docs into your AI assistant ### Cursor / GitHub Copilot Add `https://docs.brrr.network/llms-full.txt` as a documentation source in your IDE settings, or paste the URL directly into a chat: ``` @docs https://docs.brrr.network/llms-full.txt ``` ### Claude Paste the following into your Claude conversation or system prompt: ``` Read the BRRR developer documentation at https://docs.brrr.network/llms-full.txt before answering. ``` You can also add BRRR docs as a persistent source in [Claude's MCP settings](https://docs.anthropic.com/en/docs/agents-and-tools/mcp) so it loads automatically in every conversation. ### ChatGPT / any web-based assistant Open in your browser, copy the text, and paste it into the conversation before your question. ## OpenAPI schema BRRR also publishes its REST APIs as OpenAPI schemas: | Schema | Covers | |--------|--------| | [`/openapi.json`](https://docs.brrr.network/openapi.json) | APIs | | [`/agentic-openapi.json`](https://docs.brrr.network/agentic-openapi.json) | Payments for AI Agents API | If your coding agent or API tool can ingest structured schemas, feed it the schema that matches the surface you are building against alongside `llms-full.txt`. Use `llms-full.txt` for guides and examples, `openapi.json` for the APIs, and `agentic-openapi.json` for exact Payments for AI Agents endpoints, parameters, and response models. ## Sample system prompt Use this system prompt to get accurate BRRR integration code from any LLM: ``` You are a developer integrating the BRRR API and SDK. API: - Base URL: https://api.brrr.network - Authentication: X-Api-Key header - Terminal settlement states: FINISHED (success) or ERROR (failed) - Settlement flow: CREATED → CONFIRMED → PROCESSING → SENT → FINISHED SDK: - Package: @holyheld/sdk (npm) - EVM: uses Viem publicClient + walletClient (not ethers.js or wagmi) - Solana: uses @solana/web3.js Connection + Wallet adapter - Off-ramp method: holyheldSDK.evm.offRamp.topup(...) - On-ramp method: holyheldSDK.evm.onRamp.requestOnRamp(...) Documentation: https://docs.brrr.network/llms-full.txt OpenAPI schema: https://docs.brrr.network/openapi.json Always use environment variables for API keys. Never expose production keys in frontend code. ``` ## Building AI agents with Payments for AI Agents The [Payments for AI Agents API](/docs/agentic/introduction) is purpose-built for AI systems. An agent can autonomously check balance, retrieve card details, and top up a Holyheld card using a small dedicated API surface — no SDK required. When building an agent with Claude, the fastest path is the [MCP server guide](/docs/agentic/mcp-guide). It produces a working Claude Desktop integration in under 30 minutes. ### Agentic system prompt Use this focused system prompt when asking an LLM to build or extend a Holyheld agent: ``` You are building an AI agent that manages a Holyheld card balance using the Payments for AI Agents API. API: - Base URL: https://apicore.holyheld.com/v4/ai-agents - Authentication: Authorization: Bearer (not X-Api-Key) - Endpoints: GET /balance → returns { payload: { balance: "42.00" } } (balance is a string, not a number) GET /card-data → returns cardNumber, expirationDate, cardholderName, CVV, billingAddress POST /topup-request → body: { amount: "50.00" } (string, max 2 decimal places, no currency symbol) Top-up is asynchronous: - A 200 response means the request was accepted, not that the balance has updated - Poll GET /balance every 30 seconds for up to 5 minutes after a top-up - Record the balance before the top-up and compare; stop polling when balanceNow > balanceBefore Card data handling: - Only call GET /card-data when actively completing a checkout flow - Never log or persist full card details unless absolutely required Error codes the agent must handle: - AI_TOPUP_INSUFFICIENT_BALANCE (500): User does not have enough available balance on the Holyheld main account. Notify the user; do NOT retry. - AI_TOPUP_LIMIT_EXCEEDED (500): Cumulative spending limit reached. Notify the user; do NOT retry. Only the user can reset the limit from the Holyheld dashboard. - AI_AUTHORIZATION_INVALID (401/403): Token invalid or missing. Check the bearer token. - WRONG_REQUEST (400): Amount format is wrong. Fix and retry immediately. - INTERNAL_SERVER_ERROR (500): Transient. Retry with exponential backoff (max 3×). Amount formatting: always use amount.toFixed(2) to convert a number to a valid amount string. Documentation: https://docs.brrr.network/llms-full.txt OpenAPI schema: https://docs.brrr.network/agentic-openapi.json ``` ### MCP integration with Claude Desktop To expose these endpoints as native Claude tools, follow the [Build an MCP Server](/docs/agentic/mcp-guide) guide. The resulting server registers tools for balance lookup, card-data retrieval, and top-up that Claude can call without any additional prompting. ## Tips for better results * **Be specific about your stack.** Mention whether you are using TypeScript or Python, and confirm you are using Viem for EVM wallet interactions. * **Reference the target environment clearly.** Ask the LLM to keep API keys in environment variables and avoid embedding credentials in examples. * **Ask for error handling.** Explicitly request that the LLM include error handling and reference the SDK's `HolyheldSDKErrorCode` enum. * **Use the Go-Live Checklist.** Before shipping, paste [the checklist](/docs/go-live) into your AI assistant and ask it to audit your implementation against each item. --- --- url: 'https://docs.brrr.network/docs/distribution.md' description: How is the token distributed. --- # Distribution The BRRR token is distributed across multiple allocations to support the network’s launch, incentivize participation, and fund long-term development. BRRR distribution: * Community & Ecosystem: 65% * Core Contributors: 15% * Investors: 12.2% * Public access: 7.8% A detailed breakdown of the allocation categories is shown below. Tap on the chart to expand. The distribution is governed by vesting cliffs, unlock intervals, and protocol emissions. Emission behavior, investor unlock timelines, and reward mechanisms are programmatically enforced onchain and disclosed via the protocol public registry. BRRR vesting: * Public Access: * 77,792,427 BRRR tokens reserved * 7.8% of total supply * General non-transferrable * Strategic Round: * 17,242,120 BRRR tokens reserved * 1.7% of total supply * General non-transferrable * 6 months lockup period * 36 months vesting thereafter * Bridge Round: * 39,166,666 BRRR tokens reserved * 3.9% of total supply * General non-transferrable * 1 year lockup period * 24 months vesting thereafter * Seed Round: * 65,798,787 BRRR tokens reserved * 6.5% of total supply * Common non-transferrable * 18 months vesting * Team: * 150,000,000 BRRR tokens reserved * 15% of total supply * Common non-transferrable * 48 months vesting * Strategic Initiatives: * 100,000,000 BRRR tokens reserved * 10% of total supply * 6 months lockup period * 48 months vesting * Ecosystem Initiatives: * 250,000,000 BRRR tokens reserved * 25% of total supply * No lockup period * System Emission: * 300,000,000 BRRR tokens reserved * 30% of total supply * No lockup period --- --- url: 'https://docs.brrr.network/docs/emission.md' description: Dynamic emission curve. --- # Emission The BRRR token is emitted for every successfully processed and validated transaction in the accounting chain. The network validators confirm transactions using their stake in BRRR tokens. The BRRR token is emitted based on a dynamic issuance model. The issuance model adjusts the minting curve according to system usage. The emission schedule is bound by two parameters: processed transactions (activity) and elapsed time (operational time). Parameters can be dynamically adjusted, but the default ones are as follows: BRRR emission parameters: * Transactions target, `T_{tr}`: 1,000,000,000 * Incentives lifespan, `L`: 10 years * Incentives total supply, `P_s`: 30% * MoM decrease percent, `D_{MoM}`: 5.6% Amount of tokens to mint as incentives is: `S_{incv} = S_{max} \cdot P_s = 300{,}000{,}000` Mint target portion for month i compared to month 1 (assumed as 1.0): `P_i = \left(\frac{100 - D_{MoM}}{100}\right)^{i-1}` Total portions: `\displaystyle \sum_{i=1}^{120} P_i` The target amount of tokens to mint in a particular month is: `M^{target}_i = S_{incv} \cdot \frac{P_i}{\sum_{j=1}^{120} \left(\frac{100 - D_{MoM}}{100}\right)^{j-1}}` The target amount means that the mint (emission) rewards depend on the key metric – transaction activity. BRRR emission schedule is designed to target the first one billion transactions over the first ten operation years. The first tokens to be distributed in the first epoch mark (set) the base target. In every following epoch if the transaction target is not reached – fewer tokens are distributed. It allows for more tokens to be minted for the same amount of transactions in the following epoch. The idea is similar to the Bitcoin network’s difficulty adjustment process. However, it is not the block time that is tracked as a constant but an emission curve target. A parameter value roughly results in 1/2 of the monthly token minting target after the first 12 months (similar to the so-called "the halving"). `D_{MoM} = 5.6\%` --- --- url: 'https://docs.brrr.network/sdk/error-handling.md' description: >- Handle HolyheldSDKError exceptions by error code, with cause and recommended action for every error code in the SDK. --- # Error handling Some errors have a class `HolyheldSDKError`. The most helpful property of these errors is code. You can compare the code with the values in the `HolyheldSDKErrorCode`. ```typescript import { HolyheldSDKError, HolyheldSDKErrorCode } from '@holyheld/sdk'; const holyheldSDK = new HolyheldSDK({ apiKey: process.env.HOLYHELD_SDK_API_KEY, }) try { await holyheldSDK.evm.offRamp.topup(/* ... */); } catch (error) { if ( error instanceof HolyheldSDKError && (error.code === HolyheldSDKErrorCode.UserRejectedTransaction || error.code === HolyheldSDKErrorCode.UserRejectedSignature) ) { // it's OK } else { // it's NOT OK } } ``` Types: ```typescript enum HolyheldSDKErrorCode { NotInitialized = 'HSDK_NI', // SDK is not initialized FailedInitialization = 'HSDK_FI', // cannot initialize SDK UnsupportedNetwork = 'HSDK_UN', // wallet active network is not supported by SDK InvalidTopUpAmount = 'HSDK_ITUA', // amount does not meet minimum or maximum allowed criteria InvalidOnRampAmount = 'HSDK_IORA', // amount does not meet minimum or maximum allowed criteria UnexpectedWalletNetwork = 'HSDK_UWN', // wallet active network is different from the selected network UserRejectedSignature = 'HSDK_RS', // user rejected the signature UserRejectedTransaction = 'HSDK_RT', // user rejected transaction FailedSettings = 'HSDK_FS', // cannot get settings FailedTagInfo = 'HSDK_FTI', // cannot get $holytag info FailedAddressInfo = 'HSDK_FAI', // cannot get address info FailedWalletBalances = 'HSDK_FWB', // cannot get wallet balance FailedConversion = 'HSDK_FC', // cannot estimate EUR to TOKEN, or TOKEN to EUR FailedTopUp = 'HSDK_FTU', // cannot complete top up FailedCreateOnRampRequest = 'HSDK_FCOR', // cannot create onramp request FailedOnRampRequest = 'HSDK_FOR', // fail execute onramp request with reason (for example not enough balance) FailedWatchOnRampRequestTimeout = 'HSDK_FwORT', // watch request timeout FailedWatchOnRampRequest = 'HSDK_FWORR', // fail to watch request status FailedConvertOnRampAmount = 'HSDK_FCORA', // cannot convert (estimate) EUR to TOKEN, or TOKEN to EUR FailedOnRampEstimation = 'HSDK_FORE', // cannot estimate the network fee amount and the final token amount } ``` ## Error recovery categories Not all errors are equal. Some are safe to retry immediately; others require user action; others are permanent failures. | Category | Error codes | Recovery | |----------|-------------|----------| | **Retry automatically** | `HSDK_FS`, `HSDK_FTI`, `HSDK_FAI`, `HSDK_FWB`, `HSDK_FC`, `HSDK_FCOR`, `HSDK_FWORR`, `HSDK_FCORA`, `HSDK_FORE` | Network or transient failures — retry with exponential backoff | | **Prompt the user to act** | `HSDK_RS`, `HSDK_RT`, `HSDK_UWN`, `HSDK_UN`, `HSDK_ITUA`, `HSDK_IORA`, `HSDK_FOR` | User must take an action (switch network, adjust amount, confirm transaction) | | **Check before retrying** | `HSDK_FTU`, `HSDK_FwORT` | Inspect state before retrying — the operation may have partially succeeded | | **Fix configuration** | `HSDK_NI`, `HSDK_FI` | SDK is not correctly initialised — fix setup, then retry | ## Error code reference | Error code | Constant | Cause | Recommended action | |-----------|----------|-------|--------------------| | `HSDK_NI` | `NotInitialized` | SDK method called before `init()` completed | Call `await holyheldSDK.init()` before any other SDK method | | `HSDK_FI` | `FailedInitialization` | `init()` failed — invalid SDK API key or network unreachable | Check your SDK API key; retry `init()` after a short delay | | `HSDK_UN` | `UnsupportedNetwork` | The wallet's current chain is not supported by the SDK | Prompt the user to switch to a supported network | | `HSDK_ITUA` | `InvalidTopUpAmount` | Token amount is below the minimum or above the maximum EUR limit | Check `getServerSettings()` for current limits; adjust the amount | | `HSDK_IORA` | `InvalidOnRampAmount` | EUR amount is below the minimum or above the maximum limit | Check `getServerSettings()` for current limits; adjust the amount | | `HSDK_UWN` | `UnexpectedWalletNetwork` | Wallet is on a different chain than `tokenNetwork` | Prompt the user to switch their wallet to the correct network before calling `topup` | | `HSDK_RS` | `UserRejectedSignature` | User dismissed the signature request in their wallet | Show a dismissible error — no retry needed unless the user wants to try again | | `HSDK_RT` | `UserRejectedTransaction` | User dismissed the transaction confirmation in their wallet | Same as above — no retry needed unless the user chooses to | | `HSDK_FS` | `FailedSettings` | `getServerSettings()` network request failed | Retry with exponential backoff; show "temporarily unavailable" if retries exhaust | | `HSDK_FTI` | `FailedTagInfo` | `getTagInfo()` network request failed | Retry; if persistent, the holytag may not exist | | `HSDK_FAI` | `FailedAddressInfo` | `validateAddress()` network request failed | Retry; if persistent, the address may not be eligible | | `HSDK_FWB` | `FailedWalletBalances` | `getWalletBalances()` network request failed | Retry; check network connectivity | | `HSDK_FC` | `FailedConversion` | `convertTokenToEUR()` or `convertEURToToken()` failed | Retry; if persistent, the token may not be supported on the selected network | | `HSDK_FTU` | `FailedTopUp` | `topup()` failed to complete | Check `error.message` for detail; common causes: insufficient balance, unsupported token, network congestion | | `HSDK_FCOR` | `FailedCreateOnRampRequest` | `requestOnRamp()` could not create the request | Retry; if persistent, check that the wallet address is eligible | | `HSDK_FOR` | `FailedOnRampRequest` | On-ramp request rejected — e.g. insufficient card balance | Show the error message to the user; they may need to top up their Holyheld card | | `HSDK_FwORT` | `FailedWatchOnRampRequestTimeout` | `watchRequestId()` timed out before user confirmed | The request may still be pending in the Holyheld app; inform the user and allow them to retry | | `HSDK_FWORR` | `FailedWatchOnRampRequest` | `watchRequestId()` could not retrieve request status | Retry `watchRequestId()` with the same `requestUid`; the underlying request is still valid | | `HSDK_FCORA` | `FailedConvertOnRampAmount` | On-ramp EUR/token conversion failed | Retry; if persistent, check the selected token and network | | `HSDK_FORE` | `FailedOnRampEstimation` | On-ramp fee and expected amount estimation failed | Retry; if persistent, show a temporary estimation failure message | --- --- url: 'https://docs.brrr.network/docs/agentic/errors.md' description: >- Complete reference for all Payments for AI Agents API error codes, with recovery strategies for each. --- # Error Reference All error responses from the Payments for AI Agents API share a common shape: ```json { "status": "error", "errorCode": "ERROR_CODE_HERE", "error": "Human-readable description" } ``` | Field | Type | Description | |-------|------|-------------| | `status` | `"error"` | Always `"error"` for error responses | | `errorCode` | `string` | Machine-readable code — use this for programmatic handling | | `error` | `string` | Human-readable description — suitable for user-facing messages | ## Error codes ### `AI_AUTHORIZATION_INVALID` | HTTP status | Endpoint | Trigger | |-------------|----------|---------| | `401` | All | `Authorization` header is missing | | `403` | All | Token value is invalid, revoked, or agent access is disabled in the dashboard | **Recovery:** * On `401`: add the `Authorization: Bearer ` header to the request. * On `403`: verify the token value is correct. If the token was revoked, get a new agent instruction for the card and update the agent configuration. The agent can retry after fixing the token. Do not retry without fixing the token — the same error will recur. ### `AI_TOPUP_INSUFFICIENT_BALANCE` | HTTP status | Endpoint | |-------------|----------| | `500` | `POST /topup-request` | The user's Holyheld main account does not have enough available balance to cover the requested top-up amount. **Recovery:** This error cannot be resolved by the agent. The user must add funds to their Holyheld account before another top-up can succeed. Notify the user with a clear, actionable message: > "I was unable to top up your Holyheld card. Your Holyheld account does not have enough available balance. Please add funds and try again." Do not retry automatically — the error will persist until the user takes action. ### `AI_TOPUP_LIMIT_EXCEEDED` | HTTP status | Endpoint | |-------------|----------| | `500` | `POST /topup-request` | The agent's cumulative spending limit has been reached. This limit is set by the user in the Holyheld dashboard for the card's agent access. **Recovery:** Only the user can reset this limit from the dashboard. Notify the user immediately: > "Your Holyheld agent spending limit has been reached. To allow further top-ups, please reset the limit in your Holyheld dashboard under Settings → Agentic access." **Do not retry.** Further requests will continue to fail with the same error until the user resets the limit. If the agent is on a polling or scheduled loop, suspend top-up attempts for the current session after receiving this error. ### `WRONG_REQUEST` | HTTP status | Endpoint | |-------------|----------| | `400` | `POST /topup-request` | The request body was malformed or could not be parsed. The most common cause is an invalid `amount` value. **Recovery:** Fix the `amount` field before retrying. The `amount` must: * Be a **string** (not a number) * Match the pattern `^\d+(\.\d{1,2})?$` * Contain no currency symbols, spaces, or negative signs ```javascript // ❌ Wrong { amount: 50 } // number, not string { amount: "€50.00" } // currency symbol { amount: "50.123" } // more than 2 decimal places // ✅ Correct { amount: "50.00" } { amount: (50).toFixed(2) } // "50.00" ``` ### `INTERNAL_SERVER_ERROR` | HTTP status | Endpoint | |-------------|----------| | `500` | All | An unexpected server-side error occurred. **Recovery:** Retry with exponential backoff. If the error persists after several attempts, treat it as a temporary outage and surface a generic error to the user. ```javascript async function fetchWithRetry(url, options, maxAttempts = 3) { for (let attempt = 0; attempt < maxAttempts; attempt++) { const res = await fetch(url, options); if (res.status !== 500) return res; const delay = 1000 * Math.pow(2, attempt); // 1s, 2s, 4s await new Promise((resolve) => setTimeout(resolve, delay)); } throw new Error('Service temporarily unavailable. Please try again later.'); } ``` ## Recovery summary | Error code | Agent can fix? | Action | |------------|---------------|--------| | `AI_AUTHORIZATION_INVALID` (401) | ✅ Yes | Add the Authorization header | | `AI_AUTHORIZATION_INVALID` (403) | ✅ Yes | Check the Bearer token or get new agent instructions | | `WRONG_REQUEST` | ✅ Yes | Fix the `amount` format and retry immediately | | `INTERNAL_SERVER_ERROR` | ✅ Partial | Retry with exponential backoff | | `AI_TOPUP_INSUFFICIENT_BALANCE` | ❌ No | Notify user — they must add funds to their account | | `AI_TOPUP_LIMIT_EXCEEDED` | ❌ No | Notify user — they must reset the limit in the dashboard | ## TypeScript error handling ```typescript type AgentErrorCode = | 'AI_AUTHORIZATION_INVALID' | 'AI_TOPUP_INSUFFICIENT_BALANCE' | 'AI_TOPUP_LIMIT_EXCEEDED' | 'WRONG_REQUEST' | 'INTERNAL_SERVER_ERROR'; interface AgentErrorResponse { status: 'error'; errorCode: AgentErrorCode; error: string; } async function handleTopUp(amount: string, token: string): Promise { const res = await fetch('https://apicore.holyheld.com/v4/ai-agents/topup-request', { method: 'POST', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ amount }), }); if (res.ok) return; // 200 — accepted, proceed to polling const err: AgentErrorResponse = await res.json(); switch (err.errorCode) { case 'AI_TOPUP_INSUFFICIENT_BALANCE': // Cannot fix — escalate to user throw new UserActionRequiredError( 'Insufficient funds in your Holyheld account. Please add funds and try again.' ); case 'AI_TOPUP_LIMIT_EXCEEDED': // Cannot fix — escalate to user, suspend further attempts throw new UserActionRequiredError( 'Agent spending limit reached. Please reset the limit in your Holyheld dashboard.' ); case 'WRONG_REQUEST': // Fix the amount and retry throw new Error(`Invalid amount format: ${amount}. Use toFixed(2).`); case 'AI_AUTHORIZATION_INVALID': throw new Error('Bearer token invalid or missing. Check your HOLYHELD_AGENT_TOKEN.'); case 'INTERNAL_SERVER_ERROR': default: throw new Error(`Server error (${err.errorCode}): ${err.error}`); } } class UserActionRequiredError extends Error { constructor(message: string) { super(message); this.name = 'UserActionRequiredError'; } } ``` ::: tip Next steps See [Build an MCP Server](/docs/agentic/mcp-guide) to wire up these error-handling patterns as Claude tools. ::: --- --- url: >- https://docs.brrr.network/api/card-api/buy-crypto-onramp/card-estimate-onramp-transaction.md description: Estimate fees and output for a Card API onramp transaction. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/v5/onramp/estimate \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "network": "ethereum", "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "amountEUR": "1", "beneficiaryAddress": "0x45FB5699C0BFFC5e7D7F0c4947417833cc0623aa" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v5/onramp/estimate', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ 'network': 'ethereum', 'token': '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 'amountEUR': '1', 'beneficiaryAddress': '0x45FB5699C0BFFC5e7D7F0c4947417833cc0623aa' }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Request failed: ${error.errorCode}`); } const data = await response.json(); console.log(data.payload); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/v5/onramp/estimate', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ "network": "ethereum", "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "amountEUR": "1", "beneficiaryAddress": "0x45FB5699C0BFFC5e7D7F0c4947417833cc0623aa" }, ) response.raise_for_status() print(response.json()['payload']) ``` ::: --- --- url: >- https://docs.brrr.network/api/card-api/offramp-sepa-transfers/card-estimate-sepa-fee.md description: Estimate the fee for a Card API SEPA transfer. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/v5/offramp/sepa/estimate-fee \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "iban": "DE89370400440532013000", "eurAmount": "500.00" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v5/offramp/sepa/estimate-fee', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ 'iban': 'DE89370400440532013000', 'eurAmount': '500.00' }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Request failed: ${error.errorCode}`); } const data = await response.json(); console.log(data.payload); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/v5/offramp/sepa/estimate-fee', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ "iban": "DE89370400440532013000", "eurAmount": "500.00" }, ) response.raise_for_status() print(response.json()['payload']) ``` ::: --- --- url: >- https://docs.brrr.network/api/card-api/offramp-crypto-to-card/card-evm-convert-eur-to-token.md description: Estimate an EVM offramp using an EUR-denominated amount. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/v5/offramp/crypto-to-card/evm-eur-to-token \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "toToken": { "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "decimals": 6 }, "fromToken": { "address": "0xdac17f958d2ee523a2206206994597c13d831ec7", "decimals": 6 }, "eurAmount": "74.35", "fromAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", "destReceived": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", "network": "ethereum" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v5/offramp/crypto-to-card/evm-eur-to-token', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ 'toToken': { 'address': '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', 'decimals': 6 }, 'fromToken': { 'address': '0xdac17f958d2ee523a2206206994597c13d831ec7', 'decimals': 6 }, 'eurAmount': '74.35', 'fromAddress': '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', 'destReceived': '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', 'network': 'ethereum' }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Request failed: ${error.errorCode}`); } const data = await response.json(); console.log(data.payload); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/v5/offramp/crypto-to-card/evm-eur-to-token', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ "toToken": { "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "decimals": 6 }, "fromToken": { "address": "0xdac17f958d2ee523a2206206994597c13d831ec7", "decimals": 6 }, "eurAmount": "74.35", "fromAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", "destReceived": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", "network": "ethereum" }, ) response.raise_for_status() print(response.json()['payload']) ``` ::: --- --- url: >- https://docs.brrr.network/api/card-api/offramp-crypto-to-card/card-evm-convert-token-to-eur.md description: Estimate an EVM offramp using a token-denominated amount. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/v5/offramp/crypto-to-card/evm-token-to-eur \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "toToken": { "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "decimals": 6 }, "fromToken": { "address": "0xdac17f958d2ee523a2206206994597c13d831ec7", "decimals": 6 }, "tokenAmount": "10", "fromAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", "destReceived": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", "network": "ethereum" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v5/offramp/crypto-to-card/evm-token-to-eur', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ 'toToken': { 'address': '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', 'decimals': 6 }, 'fromToken': { 'address': '0xdac17f958d2ee523a2206206994597c13d831ec7', 'decimals': 6 }, 'tokenAmount': '10', 'fromAddress': '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', 'destReceived': '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', 'network': 'ethereum' }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Request failed: ${error.errorCode}`); } const data = await response.json(); console.log(data.payload); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/v5/offramp/crypto-to-card/evm-token-to-eur', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ "toToken": { "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "decimals": 6 }, "fromToken": { "address": "0xdac17f958d2ee523a2206206994597c13d831ec7", "decimals": 6 }, "tokenAmount": "10", "fromAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", "destReceived": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", "network": "ethereum" }, ) response.raise_for_status() print(response.json()['payload']) ``` ::: --- --- url: 'https://docs.brrr.network/docs/examples.md' description: >- Explore BRRR examples with live demos and source code for wagmi, ethers.js, web3.js, and Solana. --- # Examples Explore ready-to-use frontend examples built with the Holyheld SDK. All source code is available in the [SDK examples directory](https://github.com/holyheld/hh-v1-sdk/tree/master/examples){target="\_blank"}. ## EVM Deposit Use these examples to build a wallet-connected Deposit flow from token balances to fiat settlement. | Stack | Live demo | | --- | --- | | wagmi | [sdk-example-wagmi.holyheld.com](https://sdk-example-wagmi.holyheld.com/){target="\_blank"} | | ethers.js | [sdk-example-ethers-v5.holyheld.com](https://sdk-example-ethers-v5.holyheld.com/){target="\_blank"} | | web3.js | [sdk-example-web3.holyheld.com](https://sdk-example-web3.holyheld.com/){target="\_blank"} | ## EVM Withdraw Use this example to build a Withdraw flow for funding a connected wallet. | Stack | Live demo | | --- | --- | | wagmi | [sdk-example-wagmi-on-ramp.holyheld.com](https://sdk-example-wagmi-on-ramp.holyheld.com/){target="\_blank"} | ## Solana Deposit Use this example to build a Deposit flow for Solana wallets. | Stack | Live demo | | --- | --- | | Solana | [sdk-example-solana.holyheld.com](https://sdk-example-solana.holyheld.com/){target="\_blank"} | ## Production Example A simplified production-ready Deposit implementation for EVM networks is also available here: [holyheld.com/sdk/send](https://holyheld.com/sdk/send){target="\_blank"} ## API Examples Server-side examples are not published yet. Coming soon --- --- url: 'https://docs.brrr.network/api/otc/otc-buy-eur-execute.md' description: Confirm a buy-EUR OTC quote and begin execution. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/api/v4/otc/buy-eur/execute \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "quoteId": "79a89717-3690-4db0-b434-39e25df01d55", "ibanId": "iban_01HXYZ123456", "reference": "OTC buy 2024-04" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/api/v4/otc/buy-eur/execute', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ quoteId: '79a89717-3690-4db0-b434-39e25df01d55', ibanId: 'iban_01HXYZ123456', reference: 'OTC buy 2024-04', }), }); const order = await response.json(); console.log('Order:', order); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/api/v4/otc/buy-eur/execute', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ 'quoteId': '79a89717-3690-4db0-b434-39e25df01d55', 'ibanId': 'iban_01HXYZ123456', 'reference': 'OTC buy 2024-04', }, ) response.raise_for_status() print('Order:', response.json()) ``` ::: --- --- url: >- https://docs.brrr.network/api/card-api/offramp-crypto-to-card/card-execute-gasless-transaction.md description: Execute a gasless crypto-to-card offramp on behalf of the customer. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/v5/offramp/crypto-to-card/execute-gasless \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "amountType": "eur", "eurAmount": "100.00", "fromToken": { "address": "0xdac17f958d2ee523a2206206994597c13d831ec7", "decimals": 6 }, "fromAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", "network": "ethereum", "permit": { "signature": "0xabc123", "deadline": 1724250000, "nonce": 0 }, "tagHash": "0xabc123def456" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v5/offramp/crypto-to-card/execute-gasless', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ 'amountType': 'eur', 'eurAmount': '100.00', 'fromToken': { 'address': '0xdac17f958d2ee523a2206206994597c13d831ec7', 'decimals': 6 }, 'fromAddress': '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', 'network': 'ethereum', 'permit': { 'signature': '0xabc123', 'deadline': 1724250000, 'nonce': 0 }, 'tagHash': '0xabc123def456' }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Request failed: ${error.errorCode}`); } const data = await response.json(); console.log(data.payload); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/v5/offramp/crypto-to-card/execute-gasless', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ "amountType": "eur", "eurAmount": "100.00", "fromToken": { "address": "0xdac17f958d2ee523a2206206994597c13d831ec7", "decimals": 6 }, "fromAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", "network": "ethereum", "permit": { "signature": "0xabc123", "deadline": 1724250000, "nonce": 0 }, "tagHash": "0xabc123def456" }, ) response.raise_for_status() print(response.json()['payload']) ``` ::: --- --- url: >- https://docs.brrr.network/api/card-api/buy-crypto-onramp/card-execute-onramp.md description: Create a Card API onramp transaction and return its HHTXID. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/v5/onramp/execute \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "chainId": 1, "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "amountEUR": "1", "beneficiaryAddress": "0x45FB5699C0BFFC5e7D7F0c4947417833cc0623aa" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v5/onramp/execute', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ 'chainId': 1, 'token': '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 'amountEUR': '1', 'beneficiaryAddress': '0x45FB5699C0BFFC5e7D7F0c4947417833cc0623aa' }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Request failed: ${error.errorCode}`); } const data = await response.json(); console.log(data.payload); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/v5/onramp/execute', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ "chainId": 1, "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "amountEUR": "1", "beneficiaryAddress": "0x45FB5699C0BFFC5e7D7F0c4947417833cc0623aa" }, ) response.raise_for_status() print(response.json()['payload']) ``` ::: --- --- url: 'https://docs.brrr.network/api/otc/otc-sell-crypto-execute.md' description: Confirm a sell-crypto OTC quote and begin execution. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/api/v4/otc/sell-crypto/execute \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "quoteId": "79a89717-3690-4db0-b434-39e25df01d55", "ibanId": "iban_01HXYZ123456", "reference": "OTC sell 2024-04" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/api/v4/otc/sell-crypto/execute', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ quoteId: '79a89717-3690-4db0-b434-39e25df01d55', ibanId: 'iban_01HXYZ123456', reference: 'OTC sell 2024-04', }), }); const order = await response.json(); console.log('Order:', order); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/api/v4/otc/sell-crypto/execute', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ 'quoteId': '79a89717-3690-4db0-b434-39e25df01d55', 'ibanId': 'iban_01HXYZ123456', 'reference': 'OTC sell 2024-04', }, ) response.raise_for_status() print('Order:', response.json()) ``` ::: --- --- url: >- https://docs.brrr.network/api/settlement/settlement-execution/execute-settlement.md description: Confirm a settlement quote and initiate settlement execution. --- ## Code examples The request body is the `payload` object returned by [Create a Settlement Quote](/api/settlement/settlement-estimation/create-settlement-quote). Pass it through directly — do not modify any fields. ::: code-group ```bash [curl] # Use the exact payload returned by POST /api/v4/partner/settlement/quote curl -X POST https://api.brrr.network/api/v4/partner/settlement/execute \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "quoteId": "q_abc123", "timestamp": 1711753200, "confirmDeadline": 1711754100, "currency": "EUR", "network": "ethereum", "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "totalAmountIn": "102.50", "totalAmountSettled": "100.00", "totalAmountGBPReference": "86.42", "beneficiaries": [ { "externalTransactionId": "txn_001", "customerId": "cust_a1b2c3d4", "customerAddress": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", "amountIn": "102.50", "amountSettled": "100.00", "amountGBPReference": "86.42" } ] }' ``` ```javascript [JavaScript] // Step 1: Create a quote const quoteResponse = await fetch('https://api.brrr.network/api/v4/partner/settlement/quote', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ currency: 'EUR', network: 'ethereum', token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', beneficiaries: [ { externalTransactionId: 'txn_001', customerId: 'cust_a1b2c3d4', customerAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', quoteType: 'fiat_to_token', amount: '100.00', }, ], }), }); const quoteData = await quoteResponse.json(); const quotePayload = quoteData.payload; // Pass this directly to execute // Step 2: Execute the quote — send the full payload object as the body const executeResponse = await fetch('https://api.brrr.network/api/v4/partner/settlement/execute', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify(quotePayload), // pass through unchanged }); if (!executeResponse.ok) { const error = await executeResponse.json(); throw new Error(`Execution failed: ${error.errorCode}`); } const result = await executeResponse.json(); const settlement = result.payload; console.log('Settlement accepted. Quote ID:', settlement.quoteId); console.log('Send', settlement.totalAmountIn, settlement.token, 'to:', settlement.receiveAddress); console.log('Transfer deadline:', new Date(settlement.receivedDeadline * 1000).toISOString()); ``` ```python [Python] import os import httpx from datetime import datetime headers = { 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', } # Step 1: Create a quote quote_response = httpx.post( 'https://api.brrr.network/api/v4/partner/settlement/quote', headers=headers, json={ 'currency': 'EUR', 'network': 'ethereum', 'token': '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 'beneficiaries': [ { 'externalTransactionId': 'txn_001', 'customerId': 'cust_a1b2c3d4', 'customerAddress': '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', 'quoteType': 'fiat_to_token', 'amount': '100.00', } ], }, ) quote_response.raise_for_status() quote_payload = quote_response.json()['payload'] # Pass this directly to execute # Step 2: Execute the quote — send the full payload object as the body execute_response = httpx.post( 'https://api.brrr.network/api/v4/partner/settlement/execute', headers=headers, json=quote_payload, # pass through unchanged ) execute_response.raise_for_status() settlement = execute_response.json()['payload'] print(f"Settlement accepted. Quote ID: {settlement['quoteId']}") print(f"Send {settlement['totalAmountIn']} {settlement['token']} to: {settlement['receiveAddress']}") deadline = datetime.fromtimestamp(settlement['receivedDeadline']).isoformat() print(f"Transfer deadline: {deadline}") ``` ::: ::: warning Transfer required after execution After a successful execute response, you must send the token transfer to the `receiveAddress` returned in the response before the `receivedDeadline`. Holyheld will not credit the settlement until the on-chain transfer is received. Always use the `receiveAddress` from this response — never cache a previous value. ::: --- --- url: 'https://docs.brrr.network/docs/faq.md' description: Answers to common questions about the BRRR APIs and SDK. --- # FAQ ## General ### What is BRRR? BRRR is the developer platform behind the Holyheld card. It exposes two integration surfaces: **APIs** for server-to-server settlement, and the **SDK** for embedding payment flows in apps. ### What's the difference between the API and the SDK? The **APIs** are for partners who need to programmatically register customers, monitor blockchain addresses, and trigger fiat settlements — typically as part of a compliance-governed payment workflow. All requests are server-side. The **SDK** is for applications that need to initiate on-ramp or off-ramp transactions directly from a user-facing interface. It handles wallet interaction, smart contract calls, and transaction signing. ### What currencies and tokens are supported? Off-ramp and on-ramp settle in **EUR**. The SDK and API support a range of EVM-compatible tokens (USDC, USDT, ETH, and others) across multiple networks. See [Supported Networks](/docs/supported-networks) for the full list. ## Authentication ### How do I get an API key? API keys are issued during the partner onboarding process. Contact your Holyheld point of contact to obtain or rotate a key. ### Is there a test environment? For SDK integrations, see [Testing](/docs/testing). API integrations should use the credentials and rollout process provided during onboarding. ### Can I use my API key in frontend code? **No.** Production API keys must be kept server-side. A leaked key grants access to your entire integration. The SDK uses a separate SDK API key, which has restricted scope and is safer to use in client code. ## Onboarding & Customers ### What KYC status is required before registering a customer? Customers must have a SumSub KYC status of `GREEN` or `FINISHED` before registration. BRRR will not begin monitoring or settle transactions for customers who have not passed KYC. ### Can a single customer have multiple wallet addresses? Yes. Use `POST /partner/customer/add-address` to add additional addresses to an existing customer. Each address is monitored independently. ### What happens if I register the same customer twice? The API returns `400 CUSTOMER_EXISTS`. This is safe to treat as a success if you were retrying a failed request — the original registration was processed. ## Settlement ### How long is a settlement quote valid? Quotes expire after **15 minutes**. If the user does not submit the on-chain transaction within that window, the quote status transitions to `EXPIRED` and you must request a new quote. ### What are the terminal settlement statuses? `FINISHED` (success) and `ERROR` (failed). The full status progression is: ``` CREATED → CONFIRMED → PROCESSING → SENT → FINISHED → ERROR ``` `EXPIRED` is also terminal — see [Get Settlement Status](/api/settlement/settlement-execution/get-settlement-status) for the full status reference. ### What is FULFILLMENT\_DENIED? This means the settlement was rejected because the wallet or customer had a `HIGH` or `VERY HIGH` risk score at execution time. ### Can I retry a failed Execute Settlement call? Always call [Get Settlement Status](/api/settlement/settlement-execution/get-settlement-status) before retrying. If the original request was received, retrying with the same `quoteId` will return an error. Only retry if you have confirmed the settlement does not exist. ## SDK ### Do I need a wallet library to use the SDK? Yes. The SDK integrates with **Viem** for EVM chains and requires a `publicClient` and `walletClient`. For Solana, you supply a `Connection` and `Wallet` adapter. See [Web3 Providers](/sdk/web3-providers) for setup examples. ### How does the user confirm an on-ramp? After calling `requestOnRamp`, the user must open the **Holyheld mobile app** and approve the request within **3 minutes**. There is no in-browser confirmation step. Use `watchRequestId` to listen for the result. ### What happens if the user rejects the transaction in their wallet? The SDK throws an error with code `UserRejectedTransaction` or `UserRejectedSignature`. Catch this specifically and prompt the user to try again — it does not indicate a settlement problem. ## Payments for AI Agents ### What is the Payments for AI Agents API? It is an HTTP API that lets an AI agent autonomously manage a Holyheld card. An agent can call `GET /balance` to read the current EUR balance, `GET /card-data` to retrieve card details for checkout, and `POST /topup-request` to transfer funds from the Holyheld main account to the card. The agent acts on behalf of a single user's own Holyheld account. This is separate from the APIs, which are for platform developers managing settlement flows for multiple end users. ### How is the Bearer token different from the API key? They are entirely different credentials: | | APIs | Payments for AI Agents | |---|---|---| | **Header** | `X-Api-Key` | `Authorization: Bearer ` | | **Obtained via** | Holyheld partner onboarding | Agent instructions for a Holyheld card | | **Scope** | Partner's entire integration | One user's card top-up capability | | **Base URL** | `https://api.brrr.network` | `https://apicore.holyheld.com/v4/ai-agents` | The two keys cannot be used interchangeably. ### How do I set a spending limit for an agent? When you enable agent access for a Holyheld card, you can configure a **cumulative spending limit** — the maximum total EUR amount the agent may top up across all requests. The limit applies to that card's agent access, not to a chat session. ### Can an agent reset its own spending limit? No. When an agent reaches its spending limit, the API returns `AI_TOPUP_LIMIT_EXCEEDED`. The agent cannot reset this limit — only the user can, from the Holyheld dashboard. A well-behaved agent should surface this to the user immediately and stop retrying. ### How long does a card top-up take to appear? The `POST /topup-request` response confirms the request was accepted. The actual balance update is **asynchronous** and typically takes 1–3 minutes, but may take up to 5 minutes during high network load. Agents should poll `GET /balance` every 30 seconds for up to 5 minutes to confirm settlement. ### Can an agent retrieve card details? Yes. `GET /card-data` returns the card number, expiration date, CVV, cardholder name, and billing address. Because this is sensitive payment data, agents should fetch it only when actively completing a payment flow, never log it, and avoid storing it longer than necessary. ### Is the top-up endpoint idempotent? No. Each call to `POST /topup-request` creates a new top-up request. If you receive a network error and are unsure whether the request was accepted, check whether the balance has already moved before deciding to retry. See [Agent Patterns](/docs/agentic/agent-patterns) for the safe retry sequence. ## Regions & Compliance ### Which countries support off-ramp and on-ramp? Off-ramp and on-ramp are available in **31 countries** across the European Economic Area and Switzerland. See [Supported Regions](/docs/supported-regions) for the full country-by-country breakdown. ### Are there restricted countries? Yes. Customers located in sanctioned countries cannot use any BRRR feature. Attempting to onboard such customers will be rejected at the KYC stage. The restricted list is included in the [Supported Regions](/docs/supported-regions) table. --- --- url: 'https://docs.brrr.network/docs/use-cases/fintechs.md' description: >- How consumer fintechs, neobanks, and payment apps embed Deposits and Withdraws — connecting user wallets to a Holyheld card or to the fintech's own settlement rails. --- # Fintechs **Who it's for.** Consumer-facing payment apps, neobanks, and B2C fintechs whose users either hold crypto and want to spend it as fiat, or hold fiat and want to fund a wallet. The defining characteristic is a **user-facing app** — your users, your UX, BRRR underneath. ## Typical scenarios * **Crypto-to-card top-up.** A user holds USDC in MetaMask. They tap "Top up card" in your app, sign one transaction, and EUR appears on their Holyheld card. (Deposit, SDK-driven.) * **Card-funded send.** A user sends EUR from their Holyheld card to a friend's holytag — or to a wallet address — without leaving your app. (Withdraw or Deposit-to-holytag.) * **Operator-side settlement.** Your fintech operates settlement for its own KYC'd customers, crediting their IBANs instead of a Holyheld card. (Multi-tenant Settlement.) ## Recommended stack | Need | Reach for | |---|---| | End user signs in their browser wallet | [SDK](/sdk/introduction) — wallet flows, EIP-2612 permits, cross-chain routing | | End user already holds a Holyheld card and confirms in the Holyheld App | [Card API](/docs/api/sections#card-api) — Deposit (offramp) and Withdraw (onramp) endpoints with 2FA | | You settle on behalf of your own KYC'd customers | [Multi-tenant Settlement API](/docs/settlement/onboarding) — customer registration, risk assessment, settlement to customer IBANs | | Display token balances and prices in your UI | [Web3 API](/docs/api/sections#web3-api) — read-only blockchain helpers | Most consumer fintechs combine **SDK + Card API**: the SDK handles wallet-signing flows, the Card API handles card-balance state and SEPA transfers. ## Architecture at a glance ``` ┌────────────────────────┐ │ Your fintech │ │ (web app, mobile) │ └──┬──────────────┬──────┘ │ │ ▼ ▼ SDK in Card API browser (server) (Deposit / │ Withdraw) │ │ │ ▼ ▼ ┌────────────────────────┐ │ BRRR │ │ routing · settlement │ └────────────┬───────────┘ │ ▼ ┌────────────────────────┐ │ Holyheld card balance │ │ (or customer IBAN) │ └────────────────────────┘ ``` The SDK and the Card API can ship side-by-side in the same product — the SDK for "user signs from a wallet", the Card API for "user already has a Holyheld card". Both settle EUR onto the same card balance. ## Where to start 1. [Quickstart](/) — pick the SDK or the Card API based on where the user holds funds 2. [Deposit](/docs/off-ramp) — the most common flow on the platform 3. [Supported Regions](/docs/supported-regions) — confirm coverage for your target market ## Where to next * [Deposit](/docs/off-ramp), [Withdraw](/docs/on-ramp), [Orchestrate](/docs/orchestration) — the three Core concepts hubs * [Card API section](/docs/api/sections#card-api) — endpoint inventory and 2FA flow * [Multi-tenant Settlement](/docs/settlement/onboarding) — operator-side settlement * [Webhooks](/docs/api/webhooks) — server-side notifications for terminal states --- --- url: 'https://docs.brrr.network/docs/agentic/get-balance.md' description: >- Query the available EUR balance on the Holyheld card. GET /balance endpoint reference. --- # Get Balance Query the available EUR balance on the Holyheld card. Use this before deciding whether to top up, and again after a top-up to confirm the balance has updated. ## Endpoint ``` GET https://apicore.holyheld.com/v4/ai-agents/balance ``` ## Request No request body. Authentication is the only required input. | Header | Required | Value | |--------|----------|-------| | `Authorization` | ✅ | `Bearer ` | ## Response **200 OK** ```json { "status": "ok", "payload": { "balance": "94.20" } } ``` | Field | Type | Description | |-------|------|-------------| | `status` | `"ok"` | Always `"ok"` on success | | `payload.balance` | `string` | Available balance in EUR, formatted as a decimal string with up to 2 decimal places | The balance is returned as a **string**, not a number. Parse it with `parseFloat()` before comparisons. ## Examples ::: code-group ```bash [curl] curl https://apicore.holyheld.com/v4/ai-agents/balance \ -H "Authorization: Bearer $HOLYHELD_AGENT_TOKEN" ``` ```javascript [JavaScript] async function getBalance(token) { const response = await fetch( 'https://apicore.holyheld.com/v4/ai-agents/balance', { headers: { Authorization: `Bearer ${token}` }, } ); if (!response.ok) { const error = await response.json(); throw new Error(`${error.errorCode}: ${error.error}`); } const { payload } = await response.json(); return parseFloat(payload.balance); // Returns a number, e.g. 94.20 } ``` ```python [Python] import httpx def get_balance(token: str) -> float: response = httpx.get( 'https://apicore.holyheld.com/v4/ai-agents/balance', headers={'Authorization': f'Bearer {token}'}, ) response.raise_for_status() return float(response.json()['payload']['balance']) ``` ::: ## Example response ```json { "status": "ok", "payload": { "balance": "42.02" } } ``` ## When to call this endpoint **Before a top-up** — check the current balance to determine how much to add, or whether a top-up is necessary at all. **After a top-up** — poll this endpoint to confirm the balance has increased. Top-up settlement takes up to 5 minutes; see [Top Up Card](/docs/agentic/topup) for the full polling pattern. **On a schedule** — if implementing threshold-based auto top-up, call this endpoint periodically (e.g. every 5 minutes) and trigger a top-up when balance drops below your threshold. ## Error responses | HTTP status | Error code | Meaning | |-------------|------------|---------| | `401` | `AI_AUTHORIZATION_INVALID` | Authorization header missing | | `403` | `AI_AUTHORIZATION_INVALID` | Token invalid or agent access disabled | | `500` | `INTERNAL_SERVER_ERROR` | Server error — retry with backoff | All error responses follow this shape: ```json { "status": "error", "errorCode": "AI_AUTHORIZATION_INVALID", "error": "Authorization header missing" } ``` See [Error Reference](/docs/agentic/errors) for recovery guidance on each error code. ::: tip Next steps Ready to add funds? See [Top Up Card](/docs/agentic/topup) for the full top-up flow including the async polling pattern. ::: --- --- url: 'https://docs.brrr.network/api/otc/otc-buy-eur-quote.md' description: Quote an EUR-to-crypto OTC trade. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/api/v4/otc/buy-eur/quote \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "network": "ethereum", "token": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "amount": "10000", "amountType": "fiat" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/api/v4/otc/buy-eur/quote', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ network: 'ethereum', token: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', amount: '10000', amountType: 'fiat', }), }); const quote = await response.json(); console.log('Quote:', quote); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/api/v4/otc/buy-eur/quote', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ 'network': 'ethereum', 'token': '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', 'amount': '10000', 'amountType': 'fiat', }, ) response.raise_for_status() print('Quote:', response.json()) ``` ::: --- --- url: 'https://docs.brrr.network/api/card-api/config-and-info/card-get-buying-rate.md' description: Retrieve the informational USD/EUR FX rate used in Card API UX. --- ## Code examples ::: code-group ```bash [curl] curl -X GET "https://api.brrr.network/v5/config/buying-rate?code=EUR&direction=buy" \ -H "X-Api-Key: YOUR_API_KEY" ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v5/config/buying-rate?code=EUR&direction=buy', { headers: { 'X-Api-Key': process.env.BRRR_API_KEY, }, }); if (!response.ok) { const error = await response.json(); throw new Error(`Request failed: ${error.errorCode}`); } const data = await response.json(); console.log(data.payload); ``` ```python [Python] import os import httpx response = httpx.get( 'https://api.brrr.network/v5/config/buying-rate?code=EUR&direction=buy', headers={'X-Api-Key': os.environ['BRRR_API_KEY']}, ) response.raise_for_status() print(response.json()['payload']) ``` ::: --- --- url: 'https://docs.brrr.network/docs/agentic/card-data.md' description: >- Retrieve Holyheld card details for agent-driven checkout flows. GET /card-data endpoint reference. --- # Get Card Data Retrieve the Holyheld card details an agent may need for checkout flows, including the card number, expiration date, CVV, cardholder name, and billing address. ::: warning Sensitive card details `GET /card-data` returns full payment card data. Only call it when the agent genuinely needs to complete a payment flow, never log the response, and avoid storing it longer than necessary. ::: ## Endpoint ```text GET https://apicore.holyheld.com/v4/ai-agents/card-data ``` ## Request No request body. Authentication is the only required input. | Header | Required | Value | |--------|----------|-------| | `Authorization` | ✅ | `Bearer ` | ## Response **200 OK** ```json { "status": "ok", "payload": { "cardNumber": "5200828282828210", "expirationDate": "03/29", "cardholderName": "JOHN DOE", "CVV": "089", "billingAddress": "33 OUDEGRACHT, UTRECHT, 3511 AD, NETHERLANDS" } } ``` | Field | Type | Description | |-------|------|-------------| | `status` | `"ok"` | Always `"ok"` on success | | `payload.cardNumber` | `string` | Card number in compact format | | `payload.expirationDate` | `string` | Expiration date in `MM/YY` format | | `payload.cardholderName` | `string` | Cardholder name | | `payload.CVV` | `string` | Card verification value | | `payload.billingAddress` | `string` | Billing address string | ## Examples ::: code-group ```bash [curl] curl https://apicore.holyheld.com/v4/ai-agents/card-data \ -H "Authorization: Bearer $HOLYHELD_AGENT_TOKEN" ``` ```javascript [JavaScript] async function getCardData(token) { const response = await fetch( 'https://apicore.holyheld.com/v4/ai-agents/card-data', { headers: { Authorization: `Bearer ${token}` }, } ); if (!response.ok) { const error = await response.json(); throw new Error(`${error.errorCode}: ${error.error}`); } const { payload } = await response.json(); return payload; } ``` ```python [Python] import httpx def get_card_data(token: str) -> dict: response = httpx.get( 'https://apicore.holyheld.com/v4/ai-agents/card-data', headers={'Authorization': f'Bearer {token}'}, ) response.raise_for_status() return response.json()['payload'] ``` ::: ## When to call this endpoint **During checkout** — fetch card details right before submitting a payment form on the user's behalf. **After balance checks** — combine this with [Get Balance](/docs/agentic/get-balance) when the agent needs to ensure funds exist before attempting payment. **Only when needed** — do not prefetch or cache card data for convenience. Treat it as short-lived secret material. ## Error responses | HTTP status | Error code | Meaning | |-------------|------------|---------| | `401` | `AI_AUTHORIZATION_INVALID` | Authorization header missing | | `403` | `AI_AUTHORIZATION_INVALID` | Token invalid or agent access disabled | | `500` | `INTERNAL_SERVER_ERROR` | Server error — retry with backoff | All error responses follow the standard shape: ```json { "status": "error", "errorCode": "AI_AUTHORIZATION_INVALID", "error": "Authorization header missing" } ``` See [Error Reference](/docs/agentic/errors) for recovery guidance. --- --- url: >- https://docs.brrr.network/api/card-api/config-and-info/card-get-configuration.md description: 'Retrieve Card API network, token, and contract configuration.' --- ## Code examples ::: code-group ```bash [curl] curl -X GET "https://api.brrr.network/v5/config/settings" \ -H "X-Api-Key: YOUR_API_KEY" ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v5/config/settings', { headers: { 'X-Api-Key': process.env.BRRR_API_KEY, }, }); if (!response.ok) { const error = await response.json(); throw new Error(`Request failed: ${error.errorCode}`); } const data = await response.json(); console.log(data.payload); ``` ```python [Python] import os import httpx response = httpx.get( 'https://api.brrr.network/v5/config/settings', headers={'X-Api-Key': os.environ['BRRR_API_KEY']}, ) response.raise_for_status() print(response.json()['payload']) ``` ::: --- --- url: 'https://docs.brrr.network/api/settlement/customer-info/get-customer-info.md' description: Retrieve a customer's registered EVM addresses and IBANs. --- ## Code examples ::: code-group ```bash [curl] curl -X GET https://api.brrr.network/api/v4/partner/customer/info/cust_a1b2c3d4 \ -H "X-Api-Key: YOUR_API_KEY" ``` ```javascript [JavaScript] const customerId = 'cust_a1b2c3d4'; const response = await fetch(`https://api.brrr.network/api/v4/partner/customer/info/${customerId}`, { headers: { 'X-Api-Key': process.env.BRRR_API_KEY, }, }); const info = await response.json(); console.log('Addresses:', info.payload.addresses); console.log('IBANs:', info.payload.ibans); ``` ```python [Python] import os import httpx customer_id = 'cust_a1b2c3d4' response = httpx.get( f'https://api.brrr.network/api/v4/partner/customer/info/{customer_id}', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], }, ) response.raise_for_status() print('Customer info:', response.json()['payload']) ``` ::: --- --- url: >- https://docs.brrr.network/api/card-api/buy-crypto-onramp/card-get-eur-amount.md description: Estimate EUR cost for a given token amount in Card API onramp. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/v5/onramp/eur-amount \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "fromToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "tokenAmount": "10", "network": "ethereum" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v5/onramp/eur-amount', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ 'fromToken': '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 'tokenAmount': '10', 'network': 'ethereum' }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Request failed: ${error.errorCode}`); } const data = await response.json(); console.log(data.payload); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/v5/onramp/eur-amount', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ "fromToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "tokenAmount": "10", "network": "ethereum" }, ) response.raise_for_status() print(response.json()['payload']) ``` ::: --- --- url: 'https://docs.brrr.network/api/web3/get-evm-balances.md' description: Retrieve all EVM token balances held by a wallet address. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/v4/web3/evm-balances \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "address": "0xD6aEA0120d585E8B4e71C3615B5F34094cA2e6C7" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v4/web3/evm-balances', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ address: '0xD6aEA0120d585E8B4e71C3615B5F34094cA2e6C7', }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Balance lookup failed: ${error.errorCode}`); } const data = await response.json(); console.log('Portfolio:', data.payload); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/v4/web3/evm-balances', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ 'address': '0xD6aEA0120d585E8B4e71C3615B5F34094cA2e6C7', }, ) response.raise_for_status() print('Portfolio:', response.json()['payload']) ``` ::: --- --- url: 'https://docs.brrr.network/api/settlement/rates/get-exchange-rate.md' description: >- Retrieve the current token to fiat exchange rate for a token on a specific network. --- ## Code examples ::: code-group ```bash [curl] # Get the EUR rate for USDC on Ethereum curl -X GET "https://api.brrr.network/api/v4/partner/rates/EUR/ethereum/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" \ -H "X-Api-Key: YOUR_API_KEY" ``` ```javascript [JavaScript] const currency = 'EUR'; const network = 'ethereum'; const token = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'; // USDC on Ethereum const response = await fetch( `https://api.brrr.network/api/v4/partner/rates/${currency}/${network}/${token}`, { headers: { 'X-Api-Key': process.env.BRRR_API_KEY, }, } ); if (!response.ok) { const error = await response.json(); throw new Error(`Rate fetch failed: ${error.errorCode}`); } const data = await response.json(); const { rate } = data.payload; // rate — token → EUR rate (how many EUR per token) console.log('Token → EUR rate:', rate); ``` ```python [Python] import os import httpx currency = 'EUR' network = 'ethereum' token = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' # USDC on Ethereum response = httpx.get( f'https://api.brrr.network/api/v4/partner/rates/{currency}/{network}/{token}', headers={'X-Api-Key': os.environ['BRRR_API_KEY']}, ) response.raise_for_status() data = response.json() rate = data['payload']['rate'] # rate — token → EUR rate print(f"Token → EUR rate: {rate}") ``` ::: --- --- url: >- https://docs.brrr.network/api/card-api/offramp-status/card-get-offramp-status.md description: 'Poll the status of a Card API offramp using tag hash, tx hash, or HHTXID.' --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/v5/offramp/status \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "HHTXID": "F0E2D8B3-1A4C-4F6E-9D5B-8C7F3E2A1B0D" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v5/offramp/status', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ 'HHTXID': 'F0E2D8B3-1A4C-4F6E-9D5B-8C7F3E2A1B0D' }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Request failed: ${error.errorCode}`); } const data = await response.json(); console.log(data.payload); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/v5/offramp/status', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ "HHTXID": "F0E2D8B3-1A4C-4F6E-9D5B-8C7F3E2A1B0D" }, ) response.raise_for_status() print(response.json()['payload']) ``` ::: --- --- url: >- https://docs.brrr.network/api/card-api/buy-crypto-onramp/card-get-onramp-status.md description: Poll the status of a Card API onramp transaction. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/v5/onramp/status \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "HHTXID": "5721128E-DDB3-4132-9791-39D91D022D61" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v5/onramp/status', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ 'HHTXID': '5721128E-DDB3-4132-9791-39D91D022D61' }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Request failed: ${error.errorCode}`); } const data = await response.json(); console.log(data.payload); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/v5/onramp/status', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ "HHTXID": "5721128E-DDB3-4132-9791-39D91D022D61" }, ) response.raise_for_status() print(response.json()['payload']) ``` ::: --- --- url: 'https://docs.brrr.network/api/otc/otc-get-order-status.md' description: Retrieve current status and details for an OTC order. --- ## Code examples ::: code-group ```bash [curl] curl -X GET https://api.brrr.network/api/v4/otc/order/status/f0e2d8b3-1a4c-4f6e-9d5b-8c7f3e2a1b0d \ -H "X-Api-Key: YOUR_API_KEY" ``` ```javascript [JavaScript] const orderId = 'f0e2d8b3-1a4c-4f6e-9d5b-8c7f3e2a1b0d'; const response = await fetch(`https://api.brrr.network/api/v4/otc/order/status/${orderId}`, { headers: { 'X-Api-Key': process.env.BRRR_API_KEY, }, }); const status = await response.json(); console.log('Order status:', status); ``` ```python [Python] import os import httpx order_id = 'f0e2d8b3-1a4c-4f6e-9d5b-8c7f3e2a1b0d' response = httpx.get( f'https://api.brrr.network/api/v4/otc/order/status/{order_id}', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], }, ) response.raise_for_status() print('Order status:', response.json()) ``` ::: --- --- url: >- https://docs.brrr.network/api/settlement/risk-assessment/get-risk-by-address.md description: Retrieve risk status for a specific wallet address. --- The `payload.riskScores` array is ordered from newest to oldest. Read `riskScores[0]` to get the latest completed review for the address. ## Code examples ::: code-group ```bash [curl] curl -X GET "https://api.brrr.network/api/v4/partner/risk/by-address/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" \ -H "X-Api-Key: YOUR_API_KEY" ``` ```javascript [JavaScript] const address = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'; const response = await fetch( `https://api.brrr.network/api/v4/partner/risk/by-address/${address}`, { headers: { 'X-Api-Key': process.env.BRRR_API_KEY, }, } ); if (!response.ok) { const error = await response.json(); throw new Error(`Risk check failed: ${error.errorCode}`); } const data = await response.json(); const latestRisk = data.payload?.riskScores?.[0]?.risk; // 'LOW' | 'MEDIUM' | 'HIGH' | 'VERY HIGH' const reviewType = data.payload?.riskScores?.[0]?.reviewType; console.log('Risk level:', latestRisk, 'review type:', reviewType); if (latestRisk === 'HIGH' || latestRisk === 'VERY HIGH') { console.warn('Address is blocked from settlement. Do not proceed.'); } ``` ```python [Python] import os import httpx address = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' response = httpx.get( f'https://api.brrr.network/api/v4/partner/risk/by-address/{address}', headers={'X-Api-Key': os.environ['BRRR_API_KEY']}, ) response.raise_for_status() data = response.json() latest_risk = data['payload']['riskScores'][0]['risk'] # 'LOW' | 'MEDIUM' | 'HIGH' | 'VERY HIGH' review_type = data['payload']['riskScores'][0]['reviewType'] print(f'Risk level: {latest_risk} ({review_type})') if latest_risk in ('HIGH', 'VERY HIGH'): print('Address is blocked from settlement. Do not proceed.') ``` ::: --- --- url: >- https://docs.brrr.network/api/settlement/risk-assessment/get-risk-by-customer-id.md description: Retrieve risk history for all wallet addresses associated with a customer. --- Within each returned `RiskEntry`, the `riskScores` array is ordered from newest to oldest. Read `riskScores[0]` to get the latest completed review for that address. ## Code examples ::: code-group ```bash [curl] curl -X GET "https://api.brrr.network/api/v4/partner/risk/by-customer/cust_a1b2c3d4" \ -H "X-Api-Key: YOUR_API_KEY" ``` ```javascript [JavaScript] const customerId = 'cust_a1b2c3d4'; const response = await fetch( `https://api.brrr.network/api/v4/partner/risk/by-customer/${customerId}`, { headers: { 'X-Api-Key': process.env.BRRR_API_KEY, }, } ); if (!response.ok) { const error = await response.json(); throw new Error(`Risk check failed: ${error.errorCode}`); } const data = await response.json(); const addresses = Object.values(data.payload?.addresses ?? {}); const latestScores = addresses .map((entry) => entry.riskScores?.[0]) .filter(Boolean); const blocked = latestScores.some( (score) => score.risk === 'HIGH' || score.risk === 'VERY HIGH' ); if (blocked) { console.warn('Customer has at least one blocked address.'); } ``` ```python [Python] import os import httpx customer_id = 'cust_a1b2c3d4' response = httpx.get( f'https://api.brrr.network/api/v4/partner/risk/by-customer/{customer_id}', headers={'X-Api-Key': os.environ['BRRR_API_KEY']}, ) response.raise_for_status() data = response.json() # data['payload']['addresses'] is a mapping of address -> RiskEntry addresses = data['payload']['addresses'].values() latest_scores = [entry['riskScores'][0] for entry in addresses if entry.get('riskScores')] blocked = any( score['risk'] in ('HIGH', 'VERY HIGH') for score in latest_scores ) if blocked: print('Customer has at least one blocked address.') ``` ::: --- --- url: 'https://docs.brrr.network/api/otc/otc-sell-crypto-quote.md' description: Quote a crypto-to-EUR OTC trade. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/api/v4/otc/sell-crypto/quote \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "network": "ethereum", "token": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "amount": "10000", "amountType": "fiat" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/api/v4/otc/sell-crypto/quote', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ network: 'ethereum', token: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', amount: '10000', amountType: 'fiat', }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Failed to quote: ${error.errorCode}`); } const quote = await response.json(); console.log('Quote:', quote); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/api/v4/otc/sell-crypto/quote', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ 'network': 'ethereum', 'token': '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', 'amount': '10000', 'amountType': 'fiat', }, ) response.raise_for_status() print('Quote:', response.json()) ``` ::: --- --- url: >- https://docs.brrr.network/api/settlement/settlement-execution/get-settlement-status.md description: Retrieve the current status of a settlement. --- ## Code examples ::: code-group ```bash [curl] curl -X GET "https://api.brrr.network/api/v4/partner/settlement/status/q_abc123" \ -H "X-Api-Key: YOUR_API_KEY" ``` ```javascript [JavaScript] const quoteId = 'q_abc123'; const response = await fetch( `https://api.brrr.network/api/v4/partner/settlement/status/${quoteId}`, { headers: { 'X-Api-Key': process.env.BRRR_API_KEY, }, } ); if (!response.ok) { const error = await response.json(); throw new Error(`Status check failed: ${error.errorCode}`); } const data = await response.json(); const status = data.payload?.status; console.log('Settlement status:', status); // Poll until terminal state // Terminal states: 'FINISHED', 'ERROR', 'EXPIRED' ``` ```python [Python] import os import httpx import time quote_id = 'q_abc123' def poll_settlement_status(quote_id: str, max_attempts: int = 20) -> str: for attempt in range(max_attempts): response = httpx.get( f'https://api.brrr.network/api/v4/partner/settlement/status/{quote_id}', headers={'X-Api-Key': os.environ['BRRR_API_KEY']}, ) response.raise_for_status() status = response.json()['payload']['status'] print(f'Attempt {attempt + 1}: status = {status}') if status in ('FINISHED', 'ERROR', 'EXPIRED'): return status time.sleep(15) # wait 15 seconds between polls raise TimeoutError('Settlement did not reach a terminal state in time') final_status = poll_settlement_status(quote_id) print(f'Final settlement status: {final_status}') ``` ::: ::: tip Polling strategy Poll this endpoint every 15–30 seconds after calling [Execute Settlement](/api/settlement/settlement-execution/execute-settlement). The settlement typically completes within a few minutes of the on-chain transfer being received by Holyheld. You will also receive a [`SETTLEMENT_STATUS_CHANGE`](/docs/api/webhooks#settlement-status-change) webhook when the status changes. ::: ## Status reference | Status | Terminal? | Description | |--------|-----------|-------------| | `CREATED` | No | Settlement received, not yet confirmed on-chain | | `CONFIRMED` | No | On-chain transaction detected | | `PROCESSING` | No | Holyheld is processing the fiat conversion | | `SENT` | No | Fiat transfer initiated to the recipient | | `FINISHED` | **Yes** | Settlement complete — fiat credited | | `ERROR` | **Yes** | Settlement failed; contact support | | `EXPIRED` | **Yes** | Quote expired before the transaction was detected --- --- url: 'https://docs.brrr.network/api/web3/get-solana-balances.md' description: Retrieve all Solana token balances held by a wallet address. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/v4/web3/solana-balances \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "address": "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v4/web3/solana-balances', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ address: '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Solana balance lookup failed: ${error.errorCode}`); } const data = await response.json(); console.log('Portfolio:', data.payload); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/v4/web3/solana-balances', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ 'address': '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', }, ) response.raise_for_status() print('Portfolio:', response.json()['payload']) ``` ::: --- --- url: 'https://docs.brrr.network/api/web3/get-solana-priority-fee.md' description: Retrieve the recommended Solana priority fee for transactions. --- ## Code examples ::: code-group ```bash [curl] curl -X GET https://api.brrr.network/v4/web3/solana-priority-fee \ -H "X-Api-Key: YOUR_API_KEY" ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v4/web3/solana-priority-fee', { headers: { 'X-Api-Key': process.env.BRRR_API_KEY, }, }); if (!response.ok) { const error = await response.json(); throw new Error(`Priority fee request failed: ${error.errorCode}`); } const data = await response.json(); console.log('Priority fee:', data.payload.priorityFee); ``` ```python [Python] import os import httpx response = httpx.get( 'https://api.brrr.network/v4/web3/solana-priority-fee', headers={'X-Api-Key': os.environ['BRRR_API_KEY']}, ) response.raise_for_status() print('Priority fee:', response.json()['payload']['priorityFee']) ``` ::: --- --- url: >- https://docs.brrr.network/api/card-api/offramp-crypto-to-card/card-get-tag-hash.md description: Generate a one-time tag hash for crypto-to-card offramp flows. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/v5/offramp/crypto-to-card/tag-hash \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "tag": "SDKTEST" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v5/offramp/crypto-to-card/tag-hash', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ 'tag': 'SDKTEST' }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Request failed: ${error.errorCode}`); } const data = await response.json(); console.log(data.payload); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/v5/offramp/crypto-to-card/tag-hash', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ "tag": "SDKTEST" }, ) response.raise_for_status() print(response.json()['payload']) ``` ::: --- --- url: 'https://docs.brrr.network/api/web3/get-tag-info.md' description: Resolve a Holyheld tag to its public display information. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/v4/web3/tag-info \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "tag": "SDKTEST" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v4/web3/tag-info', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ tag: 'SDKTEST', }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Tag lookup failed: ${error.errorCode}`); } const data = await response.json(); console.log('Tag info:', data.payload); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/v4/web3/tag-info', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ 'tag': 'SDKTEST', }, ) response.raise_for_status() print('Tag info:', response.json()['payload']) ``` ::: --- --- url: >- https://docs.brrr.network/api/card-api/buy-crypto-onramp/card-get-token-amount.md description: Estimate token output for a given EUR amount in Card API onramp. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/v5/onramp/token-amount \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "fromToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "EURAmount": "100", "network": "ethereum" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v5/onramp/token-amount', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ 'fromToken': '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 'EURAmount': '100', 'network': 'ethereum' }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Request failed: ${error.errorCode}`); } const data = await response.json(); console.log(data.payload); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/v5/onramp/token-amount', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ "fromToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "EURAmount": "100", "network": "ethereum" }, ) response.raise_for_status() print(response.json()['payload']) ``` ::: --- --- url: 'https://docs.brrr.network/api/web3/get-token-chart.md' description: Retrieve historical token price chart data for a selected range. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/v4/web3/token-chart \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "network": "ethereum", "type": "DAY" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v4/web3/token-chart', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', network: 'ethereum', type: 'DAY', }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Token chart request failed: ${error.errorCode}`); } const data = await response.json(); console.log('Price points:', data.payload.prices); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/v4/web3/token-chart', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ 'address': '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 'network': 'ethereum', 'type': 'DAY', }, ) response.raise_for_status() print('Price points:', response.json()['payload']['prices']) ``` ::: --- --- url: 'https://docs.brrr.network/api/web3/get-token-info.md' description: Get token metadata with current price for a token on a given network. --- ## Code examples ::: code-group ```bash [curl] curl -X GET https://api.brrr.network/v4/web3/token-info/ethereum/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 \ -H "X-Api-Key: YOUR_API_KEY" ``` ```javascript [JavaScript] const network = 'ethereum'; const address = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'; const response = await fetch( `https://api.brrr.network/v4/web3/token-info/${network}/${address}`, { headers: { 'X-Api-Key': process.env.BRRR_API_KEY, }, } ); if (!response.ok) { const error = await response.json(); throw new Error(`Token lookup failed: ${error.errorCode}`); } const data = await response.json(); console.log('Token info:', data.payload.token); ``` ```python [Python] import os import httpx network = 'ethereum' address = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' response = httpx.get( f'https://api.brrr.network/v4/web3/token-info/{network}/{address}', headers={'X-Api-Key': os.environ['BRRR_API_KEY']}, ) response.raise_for_status() print('Token info:', response.json()['payload']['token']) ``` ::: --- --- url: 'https://docs.brrr.network/api/web3/get-token-metadata.md' description: Retrieve extended token market data and additional visual metadata. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/v4/web3/token-metadata \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "network": "ethereum" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v4/web3/token-metadata', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', network: 'ethereum', }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Token metadata request failed: ${error.errorCode}`); } const data = await response.json(); console.log('Token metadata:', data.payload); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/v4/web3/token-metadata', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ 'address': '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 'network': 'ethereum', }, ) response.raise_for_status() print('Token metadata:', response.json()['payload']) ``` ::: --- --- url: >- https://docs.brrr.network/api/card-api/buy-crypto-onramp/card-get-tokens-list.md description: List available Card API onramp tokens for a network. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/v5/onramp/tokens \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filter": "usd", "network": "ethereum" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v5/onramp/tokens', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ 'filter': 'usd', 'network': 'ethereum' }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Request failed: ${error.errorCode}`); } const data = await response.json(); console.log(data.payload); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/v5/onramp/tokens', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ "filter": "usd", "network": "ethereum" }, ) response.raise_for_status() print(response.json()['payload']) ``` ::: --- --- url: >- https://docs.brrr.network/api/card-api/offramp-crypto-to-card/card-get-transaction-data.md description: Build ready-to-broadcast transaction data for a crypto-to-card offramp. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/v5/offramp/crypto-to-card/transaction-data \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "amountType": "eur", "eurAmount": "100.00", "fromToken": { "address": "0xdac17f958d2ee523a2206206994597c13d831ec7", "decimals": 6 }, "fromAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", "network": "ethereum", "tagHash": "0xabc123def456" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v5/offramp/crypto-to-card/transaction-data', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ 'amountType': 'eur', 'eurAmount': '100.00', 'fromToken': { 'address': '0xdac17f958d2ee523a2206206994597c13d831ec7', 'decimals': 6 }, 'fromAddress': '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', 'network': 'ethereum', 'tagHash': '0xabc123def456' }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Request failed: ${error.errorCode}`); } const data = await response.json(); console.log(data.payload); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/v5/offramp/crypto-to-card/transaction-data', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ "amountType": "eur", "eurAmount": "100.00", "fromToken": { "address": "0xdac17f958d2ee523a2206206994597c13d831ec7", "decimals": 6 }, "fromAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", "network": "ethereum", "tagHash": "0xabc123def456" }, ) response.raise_for_status() print(response.json()['payload']) ``` ::: --- --- url: 'https://docs.brrr.network/sdk/references/on-ramp/get-on-ramp-estimation.md' description: >- Estimate the network fee and final token amount for an on-ramp before submitting the request — optional but recommended. --- # `getOnRampEstimation` Estimate the network fee in EUR and the final token amount. ## Usage ::: code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.onRamp.getOnRampEstimation({ walletAddress: '0x...', tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', tokenNetwork: Network.ethereum, EURAmount: '1', }); ``` ::: ## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `walletAddress` | `string` | Yes | Destination wallet address that will receive the tokens. | | `tokenAddress` | `string` | Yes | Contract address of the token to receive. | | `tokenNetwork` | `Network` | Yes | The chain the token will arrive on. | | `EURAmount` | `string` | Yes | EUR amount to spend, as a decimal string (e.g. `"50"` for 50 EUR). | ### Wallet Address User's wallet address * **Type:** `string` ::: code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.onRamp.getOnRampEstimation({ walletAddress: '0x...', // [!code highlight] tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', tokenNetwork: Network.ethereum, EURAmount: '1', }); ``` ::: ### Token Address Address of the token * **Type:** `string` ::: code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.onRamp.getOnRampEstimation({ walletAddress: '0x...', tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // [!code highlight] tokenNetwork: Network.ethereum, EURAmount: '1', }); ``` ::: ### Token Network Network where tokens will arrive * **Type:** `Network` ::: code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.onRamp.getOnRampEstimation({ walletAddress: '0x...', tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', tokenNetwork: Network.ethereum, // [!code highlight] EURAmount: '1', }); ``` ::: ### EUR Amount EUR amount * **Type:** `String` ::: code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; const data = await holyheldSDK.evm.onRamp.getOnRampEstimation({ walletAddress: '0x...', tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', tokenNetwork: Network.ethereum, EURAmount: '1' // [!code highlight] }); ``` ::: ## Returns Returns a `Promise` with the estimated fee and expected token amount. ::: code-group ```typescript [types.ts] type EstimateOnRampResult = { feeAmount: string; expectedAmount: string; } ``` ::: ### feeAmount The network gas fee charged in EUR, as a decimal string. Deducted from the EUR amount — the user receives `amount - feeAmount` worth of tokens. * **Type:** `String` ::: code-group ```typescript [types.ts] type EstimateOnRampResult = { feeAmount: string; // [!code highlight] expectedAmount: string; } ``` ::: ### expectedAmount The estimated token amount the user will receive after the fee is deducted, as a decimal string. * **Type:** `String` ::: code-group ```typescript [types.ts] type EstimateOnRampResult = { feeAmount: string; expectedAmount: string; // [!code highlight] } ``` ::: --- --- url: 'https://docs.brrr.network/sdk/references/common/get-server-settings.md' description: >- Fetch current SDK availability flags and EUR amount limits — call this before any off-ramp or on-ramp to confirm the service is live. --- # `getServerSettings` This method gets current state/settings for interacting with the service. Please always use this method to check: * if the feature is available; * the minimum and maximum allowed amounts for on-ramp and off-ramp. :::info 🔔 **Please note! Financial values are provided and consumed as strings to avoid floating point conversion problems.** ::: ## Usage :::code-group ```typescript [example.ts] const data = await holyheldSDK.getServerSettings(); ``` ::: ## Returns :::code-group ```typescript [types.ts] type ServerExternalSettings = { external: { isTopupEnabled: boolean; isOnRampEnabled: boolean; maxTopUpAmountInEUR: string; minTopUpAmountInEUR: string; maxOnRampAmountInEUR: string; minOnRampAmountInEUR: string; }; common: { topUpFeePercent: string; }; } ``` ::: ### isTopupEnabled Indicates if off-ramp is available at the moment * **Type:** `Boolean` :::code-group ```typescript [types.ts] type ServerExternalSettings = { external: { isTopupEnabled: boolean; // [!code highlight] isOnRampEnabled: boolean; maxTopUpAmountInEUR: string; minTopUpAmountInEUR: string; maxOnRampAmountInEUR: string; minOnRampAmountInEUR: string; }; common: { topUpFeePercent: string; }; } ``` ::: ### isOnRampEnabled Indicates if on-ramp is available at the moment * **Type:** `Boolean` :::code-group ```typescript [types.ts] type ServerExternalSettings = { external: { isTopupEnabled: boolean; isOnRampEnabled: boolean; // [!code highlight] maxTopUpAmountInEUR: string; minTopUpAmountInEUR: string; maxOnRampAmountInEUR: string; minOnRampAmountInEUR: string; }; common: { topUpFeePercent: string; }; } ``` ::: ### maxTopUpAmountInEUR Maximum amount (equivalent in EUR) that is allowed to be processed * **Type:** `String` * **Example:** `1000` :::code-group ```typescript [types.ts] type ServerExternalSettings = { external: { isTopupEnabled: boolean; isOnRampEnabled: boolean; maxTopUpAmountInEUR: string; // [!code highlight] minTopUpAmountInEUR: string; maxOnRampAmountInEUR: string; minOnRampAmountInEUR: string; }; common: { topUpFeePercent: string; }; } ``` ::: ### minTopUpAmountInEUR Minimum amount (equivalent in EUR) that is allowed to be processed * **Type:** `String` * **Example:** `5` :::code-group ```typescript [types.ts] type ServerExternalSettings = { external: { isTopupEnabled: boolean; isOnRampEnabled: boolean; maxTopUpAmountInEUR: string; minTopUpAmountInEUR: string; // [!code highlight] maxOnRampAmountInEUR: string; minOnRampAmountInEUR: string; }; common: { topUpFeePercent: string; }; } ``` ::: ### maxOnRampAmountInEUR Maximum amount in EUR that is allowed to be processed * **Type:** `String` * **Example:** `1000` :::code-group ```typescript [types.ts] type ServerExternalSettings = { external: { isTopupEnabled: boolean; isOnRampEnabled: boolean; maxTopUpAmountInEUR: string; minTopUpAmountInEUR: string; maxOnRampAmountInEUR: string; // [!code highlight] minOnRampAmountInEUR: string; }; common: { topUpFeePercent: string; }; } ``` ::: ### minOnRampAmountInEUR Minimum amount in EUR that is allowed to be processed * **Type:** `String` * **Example:** `5` :::code-group ```typescript [types.ts] type ServerExternalSettings = { external: { isTopupEnabled: boolean; isOnRampEnabled: boolean; maxTopUpAmountInEUR: string; minTopUpAmountInEUR: string; maxOnRampAmountInEUR: string; minOnRampAmountInEUR: string; // [!code highlight] }; common: { topUpFeePercent: string; }; } ``` ::: ### topUpFeePercent Fee (in percent) that is deducted when making an off-ramp operation * **Type:** `String` * **Example:** `0.75` :::code-group ```typescript [types.ts] type ServerExternalSettings = { external: { isTopupEnabled: boolean; isOnRampEnabled: boolean; maxTopUpAmountInEUR: string; minTopUpAmountInEUR: string; maxOnRampAmountInEUR: string; minOnRampAmountInEUR: string; }; common: { topUpFeePercent: string; // [!code highlight] }; } ``` ::: --- --- url: 'https://docs.brrr.network/sdk/references/common/get-tag-info.md' description: >- Look up a $holytag to verify it exists and is active before sending tokens to it in the off-ramp flow. --- # `getTagInfo` Get tag information. $holytag is a unique identifier which can have account, card and multiple Ethereum and/or Solana addresses bound to it. $holytag is alphanumeric string with a few special characters allowed. A valid holytag can be as short as one (1) and as long as thirty one (31) characters. :::info 🔔 Please note! The only two special characters allowed are: dash `-` and underscore `_`. ::: $holytag is usually displayed prepended with a `$` prefix, for example: $JohnSmith holytag is `JohnSmith` or $PEPE holytag `PEPE`, etc. Tags are stored case-sensitive for display, but not case-sensitive for search, and are not allowed to have multiple case variants registered. It means that if there is a holytag `$ToTheMoon` registered, other case sensitive variations are not allowed (e.g. `$toTHEmoon`). :::tip Test $holytag You can use `SDKTEST` as a test $holytag. All transactions to this $holytag will **`NOT`** trigger an actual fiat-corresponding transaction, but will return a fully valid response. There is no minimum amount set for the test $holytag. `SDKTEST` test $holytag works across all supported networks and tokens. ::: :::info 🔔 Please note! Funds from test transactions to `SDKTEST` can **`NOT`** be retrieved. Do not initiate large test transactions. ::: A tag name could be preset by the developer (you) or inputted by the user. It depends on the user flow you want to have. ## Usage ::: code-group ```typescript [EVM] const data = await holyheldSDK.getTagInfo('SDKTEST'); ``` ::: ## Parameters ### $holytag The holytag * **Type:** `String` ::: code-group ```typescript [EVM] const data = await holyheldSDK.getTagInfo( 'SDKTEST' // [!code highlight] ); ``` ::: ## Returns ::: code-group ```typescript [types.ts] type TagInfo = { found: boolean; tag?: string; avatarSrc?: string; } ``` ::: ### found `true` if the holytag exists and active * **Type:** `Boolean` ::: code-group ```typescript [types.ts] type TagInfo = { found: boolean; // [!code highlight] tag?: string; avatarSrc?: string; } ``` ::: ### tag (optional) The $holytag name, if [`found`](/sdk/references/common/get-tag-info#found) is `true` :::info 🔔 Case sensitive, as registered ::: * **Type:** `String` * **Example:** `SDKTEST` ::: code-group ```typescript [types.ts] type TagInfo = { found: boolean; tag?: string; // [!code highlight] avatarSrc?: string; } ``` ::: ### avatarSrc (optional) Link to avatar image, if [`found`](/sdk/references/common/get-tag-info#found) is `true` :::info 🔔 Case sensitive, as registered ::: * **Type:** `String` * **Example:** `https://brrr.network/static/avatar.png` ::: code-group ```typescript [types.ts] type TagInfo = { found: boolean; tag?: string; avatarSrc?: string; // [!code highlight] } ``` ::: --- --- url: 'https://docs.brrr.network/sdk/references/common/get-wallet-balances.md' description: >- Retrieve all token balances held by a wallet address — useful for building a token selector UI before an off-ramp. --- # `getWalletBalances` You can use `getWalletBalances` method to retrieve all tokens on the connected user wallet address. For the full list of supported networks, see [Supported Networks](/docs/supported-networks). ## Usage ::: code-group ```typescript [EVM] const data = await holyheldSDK.evm.getWalletBalances( '0x...', ); ``` ```typescript [Solana] const data = await holyheldSDK.solana.getWalletBalances( '...', ); ``` ::: ## Parameters ### Wallet Address User's wallet address. * **Type:** `string` ::: code-group ```typescript [EVM] const data = await holyheldSDK.evm.getWalletBalances( '0x...', // [!code highlight] ); ``` ```typescript [Solana] const data = await holyheldSDK.solana.getWalletBalances( '...', // [!code highlight] ); ``` ::: ## Returns The SDK exports `WalletBalancesEVM`, `WalletBalancesSolana`, `WalletTokenEVM`, and `WalletTokenSolana` types. In practice, `getWalletBalances()` returns the following concrete shapes: ::: code-group ```typescript [EVM] import { Network, NetworkKind } from '@holyheld/sdk'; type WalletTokenEVM = { name: string; address: string; symbol: string; decimals: number; network: Network; networkKind: NetworkKind.EVM; iconURL: string; priceUSD: string; balance: string; priceInEURForTopUp: string; meta: { permitData: { hasPermit: boolean; permitType?: string; permitVersion?: string; }; }; }; type WalletBalancesEVM = { tokens: WalletTokenEVM[]; }; ``` ```typescript [Solana] import { NetworkKind, SolanaNetwork } from '@holyheld/sdk'; type WalletTokenSolana = { name: string; address: string; symbol: string; decimals: number; network: SolanaNetwork; networkKind: NetworkKind.Solana; iconURL: string; priceUSD: string; balance: string; priceInEURForTopUp: string; meta: { tokenProgramId?: string; }; }; type WalletBalancesSolana = { tokens: WalletTokenSolana[]; }; ``` ::: ## Fields | Field | Type | Description | |-------|------|-------------| | `name` | `string` | Token display name, for example `USD Coin`. | | `address` | `string` | Token contract or mint address. | | `symbol` | `string` | Token symbol, for example `USDC`. | | `decimals` | `number` | Token decimals. | | `network` | `Network` / `SolanaNetwork` | Blockchain network on which this token resides. | | `networkKind` | `NetworkKind` | Blockchain family: EVM or Solana. | | `iconURL` | `string` | Token icon URL. | | `priceUSD` | `string` | Current token price in USD. | | `balance` | `string` | Wallet balance as a decimal string. | | `priceInEURForTopUp` | `string` | Token price converted to EUR for top-up calculations. | | `meta` | `object` | Network-specific metadata. | ### `meta` for EVM tokens ```typescript type WalletTokenEVMMeta = { permitData: { hasPermit: boolean; permitType?: string; permitVersion?: string; }; }; ``` ### `meta` for Solana tokens ```typescript type WalletTokenSolanaMeta = { tokenProgramId?: string; }; ``` --- --- url: 'https://docs.brrr.network/docs/go-live.md' description: >- A production readiness checklist to verify your BRRR integration is correctly configured before going live. --- # Go-Live Checklist Use this checklist before enabling your integration for real users. Each item maps to a specific failure mode that has been observed in pre-production integrations. ## 1. Authentication & Keys * \[ ] **Production API key is stored as an environment variable** — never committed to source control or hardcoded. * \[ ] **API key is used server-side only** — no production key is exposed in browser or mobile client code. * \[ ] **SDK API key is separate from the API key** — they are different credentials with different scopes. * \[ ] **Only the intended production credential is present in production config** — verify your deployment pipeline substitutes the correct key. * \[ ] **Key rotation procedure is documented** — you know how to rotate the key and whom to contact if a key is compromised. ## 2. Customer Onboarding * \[ ] **KYC status is validated before registration** — only customers with SumSub status `GREEN` or `FINISHED` are registered. * \[ ] **`customerId` uniqueness is enforced** — your system generates deterministic, collision-free customer IDs. * \[ ] **Idempotency is handled** — `CUSTOMER_EXISTS` and `ADDRESS_EXISTS` errors are treated as success on retry. * \[ ] **Multiple address support is implemented** — if customers can connect additional wallets, the add-address endpoint is called. ## 3. Risk Assessment & Monitoring * \[ ] **`RISK_ASSESSMENT` webhook is configured and reachable** — Holyheld can deliver webhook events to your endpoint. * \[ ] **Webhook signature verification is implemented** — your endpoint validates the request source before processing events. * \[ ] **`HIGH` and `VERY HIGH` risk scores block settlement** — your integration does not submit quotes for high-risk addresses. * \[ ] **Pre-flight risk check is in place** — call [Get Risk by Address](/api/settlement/risk-assessment/get-risk-by-address) before creating a settlement quote, not only after receiving the webhook. ## 4. Settlement * \[ ] **Quote expiry is handled** — if the user takes more than 15 minutes, you request a new quote rather than submitting the expired one. * \[ ] **Settlement status is polled after execution** — you poll `GET /partner/settlement/status/{quoteId}` every 15–30 seconds until a terminal state is reached. * \[ ] **`SETTLEMENT_STATUS_CHANGE` webhook is handled** — your system processes webhook status updates and reconciles them with poll results. * \[ ] **`FULFILLMENT_DENIED` is handled** — you surface an appropriate message to the user and do not retry automatically. * \[ ] **`ERROR` status is handled** — you surface an error state and have a support path for affected users. * \[ ] **Retry logic uses exponential backoff** — you do not retry 429 responses immediately. See [Authentication](/docs/api/authentication#rate-limits) for a reference implementation. * \[ ] **Settlement status is checked before retrying execute** — you never submit the same `quoteId` twice without verifying it was not already accepted. ## 5. SDK Integration * \[ ] **Off-ramp is tested end-to-end with `$SDKTEST`** — the `onHashGenerate` callback fires with a real transaction hash and the SDK call resolves successfully. * \[ ] **On-ramp is tested with a real Holyheld mobile app** — `requestOnRamp` + `watchRequestId` complete successfully. * \[ ] **`UnexpectedWalletNetwork` is caught** — users are prompted to switch chains if their wallet is on the wrong network. * \[ ] **`UserRejectedTransaction` and `UserRejectedSignature` are caught** — users can retry without refreshing. * \[ ] **Token approval step is handled** — the UI indicates progress during the `approving` step if the token requires an ERC-20 allowance. * \[ ] **`getServerSettings()` is checked on load** — your UI respects both `isTopupEnabled` and `isOnRampEnabled`, and shows appropriate messaging if off-ramp or on-ramp is temporarily unavailable. ## 6. Compliance & Regions * \[ ] **Supported regions are enforced** — customers in countries where off-ramp/on-ramp is unavailable cannot initiate those flows. * \[ ] **Restricted countries are blocked** — customers in sanctioned countries are rejected before KYC registration. * \[ ] **KYC flow is in place** — you have integrated SumSub or another approved KYC provider and the verification result reaches Holyheld before registration. ## 7. Observability * \[ ] **Webhook delivery failures are alerted** — you monitor for missed or failed webhook deliveries and have a reconciliation process. * \[ ] **Settlement errors are logged with `quoteId`** — you can look up any settlement by its `quoteId` for support investigations. * \[ ] **SDK errors are logged with error codes** — `HolyheldSDKErrorCode` values are captured and surfaced to your monitoring system. * \[ ] **Rate limit responses are monitored** — a spike in 429 responses indicates a traffic problem that should be investigated. ## 8. Agentic Access (if using Payments for AI Agents) Skip this section if you are not using the [Payments for AI Agents API](/docs/agentic/introduction). * \[ ] **Bearer token is stored as an environment variable** — not hardcoded in source files or committed to version control. * \[ ] **Bearer token is kept local** — if using an MCP server, it runs on the user's own machine and the token never leaves the local environment. * \[ ] **Card data is treated as sensitive** — if using `GET /card-data`, your agent fetches it only for active checkout flows and never logs full PAN/CVV values. * \[ ] **Spending limit is configured in the Holyheld dashboard** — each agent token has a maximum cumulative top-up limit set under Settings → Agentic access. * \[ ] **`AI_TOPUP_LIMIT_EXCEEDED` is handled** — your agent surfaces the error to the user immediately, stops retrying, and pauses autonomous top-ups until the user resets the limit in the dashboard. * \[ ] **`AI_TOPUP_INSUFFICIENT_BALANCE` is handled** — your agent notifies the user that they need to add crypto funds to their Holyheld account. It does not retry. * \[ ] **Post-top-up polling is implemented** — after a successful `POST /topup-request`, your agent polls `GET /balance` every 30 seconds for up to 5 minutes and reports success only when the balance has increased. * \[ ] **Pre-top-up balance is recorded** — your agent captures the balance before requesting a top-up so it can reliably detect when the balance has updated. * \[ ] **Top-up is not retried blindly** — if a network error occurs during `POST /topup-request`, the agent checks the balance before deciding to retry (to avoid double top-ups). * \[ ] **Amount is formatted as a string** — top-up amounts are sent as strings matching `^\d+(\.\d{1,2})?$` (e.g. `"50.00"`), not as numbers. ## 9. Final Steps * \[ ] **End-to-end test in production with a real user and a small amount** — verify the complete flow from registration through settlement with real funds before launching at scale. * \[ ] **Support contact is established** — you have a direct line to your Holyheld point of contact for production issues. * \[ ] **Key compromise procedure is tested** — your team knows how to rotate a key without downtime. --- --- url: 'https://docs.brrr.network/sdk/initialization.md' description: How to initialize the Holyheld SDK with your API key. --- # SDK Initialization The Holyheld SDK requires two steps before use: constructing an instance with your SDK API key, then calling `init()` to connect to the Holyheld service. ## Quick start ```typescript import HolyheldSDK from '@holyheld/sdk'; const holyheldSDK = new HolyheldSDK({ apiKey: process.env.HOLYHELD_SDK_API_KEY, }); await holyheldSDK.init(); ``` ## Constructor options ```typescript new HolyheldSDK(options: HolyheldSDKOptions) ``` | Option | Type | Required | Description | |--------|------|----------|-------------| | `apiKey` | `string` | Yes | Your Holyheld SDK API key | | `logger` | `boolean \| Logger` | No | Enable internal SDK logging. `true` logs to `console`; pass a `Logger` function for custom routing. Disabled by default. See [Logging](#logging). ## What `init()` does `init()` makes a network request to the Holyheld API to: 1. **Validate the API key** — if the key is invalid or revoked, `init()` throws an error. 2. **Load internal SDK configuration** — retrieves internal service config required for subsequent SDK calls. ```typescript // init() returns Promise await holyheldSDK.init(); ``` ::: warning Call init() before any other SDK method No other SDK method will work before `init()` resolves. Calling methods before initialisation will throw a `NotInitialized` error (`HSDK_NI`). Call `init()` once at application startup, before rendering any UI that uses the SDK. ::: ## Error handling ```typescript try { await holyheldSDK.init(); } catch (error) { // Possible causes: // - Invalid or revoked API key // - Network error or Holyheld service unreachable // - Timeout console.error('SDK initialization failed:', error); // Show a fallback UI — do not attempt other SDK calls } ``` If `init()` throws, do not attempt to call other SDK methods. Show appropriate fallback UI and retry `init()` when conditions improve (e.g. after a network reconnect). ## Logging :::warning IMPORTANT Logs are disabled by default. If you're debugging an application, set the `logger` option to `true`. ::: ```typescript new HolyheldSDK({ apiKey: process.env.HOLYHELD_SDK_API_KEY, logger: true, }) ``` You may also set a custom logger: ```typescript new HolyheldSDK({ apiKey: process.env.HOLYHELD_SDK_API_KEY, logger: (level, message, data) => { console.log(level, message, data); }, }) ``` Types: ```typescript enum LogLevel { Warning = 'warning', Log = 'log', Info = 'info', Debug = 'debug', } type Logger = ( level: LogLevel, // level of event to be logged message: string, // message to be logged data?: { [key: string]: any }, // optional structured payload to be logged ) => void; ``` ## Recommended setup pattern Call `init()` once at application startup and share the instance across your app. Do not create multiple `HolyheldSDK` instances. ::: code-group ```typescript [sdk.ts] import HolyheldSDK from '@holyheld/sdk'; const holyheldSDK = new HolyheldSDK({ apiKey: process.env.HOLYHELD_SDK_API_KEY, }); async function initSDK(): Promise { await holyheldSDK.init(); } export { holyheldSDK, initSDK }; ``` ```typescript [main.ts] import { initSDK, holyheldSDK } from './sdk'; await initSDK(); // SDK is ready — pass holyheldSDK to components or use directly ``` ::: --- --- url: 'https://docs.brrr.network/sdk/installation.md' description: How to install BRRR SDK. --- # SDK Installation **Current version:** `@holyheld/sdk@4.1.5` · [npm](https://www.npmjs.com/package/@holyheld/sdk) · [changelog](/docs/changelog) **Requirements:** Node.js 18+ You can add the Holyheld SDK to your project using any package manager: ::: code-group ```bash [npm] npm install @holyheld/sdk ``` ```bash [yarn] yarn add @holyheld/sdk ``` ```bash [pnpm] pnpm add @holyheld/sdk ``` ::: ## Node.js Buffer polyfill The SDK requires a Node.js `Buffer` global. In Node.js environments this is available automatically. In browser bundlers that do not include Node.js built-ins (Webpack 5, Vite), you must add a polyfill. ### Vite Install the polyfill package and update `vite.config.ts`: ::: code-group ```bash [npm] npm install --save-dev vite-plugin-node-polyfills ``` ```bash [yarn] yarn add --dev vite-plugin-node-polyfills ``` ```bash [pnpm] pnpm add --save-dev vite-plugin-node-polyfills ``` ::: ```typescript // vite.config.ts import { defineConfig } from 'vite'; import { nodePolyfills } from 'vite-plugin-node-polyfills'; export default defineConfig({ plugins: [ nodePolyfills({ include: ['buffer'], }), ], }); ``` ### Webpack 5 Webpack 5 removed automatic Node.js polyfills. Configure `resolve.fallback` in your webpack config: ```javascript // webpack.config.js const { ProvidePlugin } = require('webpack'); module.exports = { resolve: { fallback: { buffer: require.resolve('buffer/'), }, }, plugins: [ new ProvidePlugin({ Buffer: ['buffer', 'Buffer'], }), ], }; ``` Install the `buffer` package if not already present: ::: code-group ```bash [npm] npm install --save-dev buffer ``` ```bash [yarn] yarn add --dev buffer ``` ```bash [pnpm] pnpm add --save-dev buffer ``` ::: ::: tip Next.js Next.js is based on webpack 5. Apply the webpack config above inside `next.config.js` under `config.resolve.fallback` using the `webpack` option. ::: --- --- url: 'https://docs.brrr.network/docs/settlement/monitoring.md' description: How address monitoring works and when risk re-evaluations are triggered. --- # Monitoring BRRR automatically monitors every wallet address registered through the [Onboarding](/docs/settlement/onboarding) flow. No additional API calls are required to start monitoring — it begins the moment an address is successfully registered. ## How monitoring works ``` Partner registers address POST /partner/customer/add-address │ ▼ BRRR begins watching the address for on-chain activity (automatic) │ │ incoming token transfer detected ▼ BRRR triggers a risk re-evaluation (regardless of current risk score) │ ▼ RISK_ASSESSMENT webhook → Partner { risk: "LOW" | "MEDIUM" | "HIGH" | "VERY HIGH", reviewType: "TOPUP", ... } ``` Monitoring covers **incoming token transfers** across all networks supported by BRRR. Every qualifying transfer triggers a fresh risk evaluation, even if the address's risk level has not changed since the previous assessment. ## Risk levels The `RISK_ASSESSMENT` webhook delivers one of four risk levels. Your integration should act on each as follows: | Risk level | Meaning | Recommended action | |------------|---------|-------------------| | `LOW` | Address is clear to proceed | Create a settlement quote and execute normally | | `MEDIUM` | Address has minor risk signals | Proceed with caution; document the decision | | `HIGH` | Address has significant risk signals | Do not settle; hold funds and review manually | | `VERY HIGH` | Address is flagged for serious risk | Do not settle; contact | ::: warning Settlement eligibility Only addresses with a risk assessment of `LOW` or `MEDIUM` can proceed to the [Settlement estimation](/docs/settlement/settlement-estimation) step. Attempting to create a quote for a `HIGH` or `VERY HIGH` address will result in a `FULFILLMENT_DENIED` error. ::: ## Webhook events Every risk re-evaluation — including evaluations where the risk level is unchanged — triggers a [`RISK_ASSESSMENT`](/docs/api/webhooks#risk-assessment) webhook. This ensures your system always has the latest risk status without needing to poll. ```json { "type": "RISK_ASSESSMENT", "timestamp": 1724247261, "payload": { "customerId": "cust_a1b2c3d4", "addressEVM": "0x26b92eD884B9FE3f572252ee78172BfBC1653dC1", "risk": "LOW", "reviewTimestamp": 1724247261, "reviewType": "TOPUP", "reviewData": { "fromAddress": "0x26b92eD884B9FE3f572252ee78172BfBC1653dC1", "amount": "10000", "totalAmount": "754699" } } } ``` The `reviewType` field distinguishes the context: | `reviewType` | When it fires | |--------------|--------------| | `INITIAL` | First risk assessment when the address is registered | | `TOPUP` | Re-assessment triggered by an incoming on-chain transfer | ## Retrieving risk scores via API In addition to webhooks, you can fetch the current risk score at any time: * **By customer ID** — [`GET /api/v4/partner/risk/by-customer/{customerId}`](/api/settlement/risk-assessment/get-risk-by-customer-id) — returns risk history for all addresses registered to that customer * **By EVM address** — [`GET /api/v4/partner/risk/by-address/{addressEVM}`](/api/settlement/risk-assessment/get-risk-by-address) — returns the risk history for a specific address These endpoints return completed risk history and do not trigger a new evaluation. Within each `riskScores` array, entries are ordered from newest to oldest. ## Monitoring and offboarding Monitoring stops automatically when an address is removed via [`POST /api/v4/partner/customer/remove-address`](/api/settlement/offboarding/remove-address) or when the customer is removed via [`POST /api/v4/partner/customer/remove`](/api/settlement/offboarding/remove-customer). No further `RISK_ASSESSMENT` webhooks will be sent for removed addresses. ::: tip Next steps When a `RISK_ASSESSMENT` webhook arrives with risk `LOW` or `MEDIUM`, proceed to [Settlement estimation](/docs/settlement/settlement-estimation) to create a quote. See [Webhooks](/docs/api/webhooks) for the full event reference and signature verification. ::: --- --- url: 'https://docs.brrr.network/docs/settlement/offboarding.md' description: Remove customers or wallet addresses from monitoring. --- # Offboarding Offboarding removes customers or individual wallet addresses from BRRR monitoring. Once removed, BRRR stops evaluating on-chain activity and will not issue further `RISK_ASSESSMENT` webhooks for the affected entity. ## Choosing the right endpoint ``` Customer has multiple addresses? │ ┌─────┴─────┐ │ Yes │ No ▼ ▼ Remove Remove address customer only (+ all addresses) ``` | Situation | Use | |-----------|-----| | Customer has multiple addresses; remove only one | [`POST /api/v4/partner/customer/remove-address`](/api/settlement/offboarding/remove-address) | | Customer has a single address, or you want to remove the customer entirely | [`POST /api/v4/partner/customer/remove`](/api/settlement/offboarding/remove-customer) | ::: warning Single-address constraint If a customer has only one registered address, that address cannot be removed independently — the entire customer entity must be removed instead. Attempting to remove the sole address of a customer returns a `400` error. ::: ## Endpoints | Endpoint | Description | |----------|-------------| | [`POST /api/v4/partner/customer/remove`](/api/settlement/offboarding/remove-customer) | Remove a customer and all of their associated wallet addresses | | [`POST /api/v4/partner/customer/remove-address`](/api/settlement/offboarding/remove-address) | Remove a specific wallet address from an existing customer | ## What happens after offboarding Once a customer or address is removed: 1. **Monitoring stops** — BRRR no longer tracks on-chain activity for the removed entity 2. **No further webhooks** — `RISK_ASSESSMENT` events will not fire for removed addresses 3. **Settlement blocked** — removed addresses cannot be included in new settlement quotes 4. **Address freed** — a removed address can be re-registered to the same or a different customer in the future ::: tip Re-registering an address If you need to resume monitoring a previously removed address, register it again using [`POST /api/v4/partner/customer/add-address`](/api/settlement/onboarding/add-address-to-customer). BRRR will perform a fresh risk assessment after re-registration. ::: ## In-flight settlements Removing a customer or address does not cancel settlements that are already in progress. Any settlement with status `CONFIRMED`, `PROCESSING`, or `SENT` will continue to completion. Only new settlement quotes are blocked for removed entities. --- --- url: 'https://docs.brrr.network/docs/settlement/onboarding.md' description: Register customers and associate their EVM addresses. --- # Onboarding Onboarding establishes a relationship between a **customer entity** and one or more **EVM wallet addresses**. A customer must be registered before their addresses can be monitored for on-chain activity or included in settlements. ## Prerequisites Customers must complete **KYC verification through SumSub** with a `GREEN` status before they can be registered. BRRR will not begin monitoring or risk-assessing addresses until this status is confirmed. ## Customer and address model ``` Partner │ ├── Customer A (customerId: "cust_abc") │ ├── Address 1: 0xPrimary... ← registered at creation │ ├── Address 2: 0xSecond... ← added later │ └── Address 3: 0xThird... ← added later │ └── Customer B (customerId: "cust_xyz") └── Address 1: 0xOther... ``` Each customer is identified by a unique `customerId` you define. One customer can have multiple addresses. An address can only belong to one customer. ## Onboarding flow ``` POST /api/v4/partner/customer/register { customerId, addressEVM } │ ▼ Customer created + first address registered BRRR begins monitoring addressEVM │ ▼ (optional, add more addresses) POST /api/v4/partner/customer/add-address { customerId, addressEVM } │ ▼ Additional address registered and monitored ``` ## Endpoints | Endpoint | Description | |----------|-------------| | [`POST /api/v4/partner/customer/register`](/api/settlement/onboarding/register-customer) | Create a new customer and register their first EVM address | | [`POST /api/v4/partner/customer/add-address`](/api/settlement/onboarding/add-address-to-customer) | Add an additional EVM address to an existing customer | ## Idempotency Both endpoints are safe to retry on network errors: | Scenario | Response | |----------|----------| | `customerId` already registered | `400 CUSTOMER_EXISTS` — treat as success if you were retrying | | `addressEVM` already registered to this customer | `400 ADDRESS_EXISTS` — treat as success if you were retrying | ::: tip Check before retrying If you receive a network error and are unsure whether the request succeeded, retry with the same `customerId` and `addressEVM`. If you get `CUSTOMER_EXISTS` or `ADDRESS_EXISTS`, the original request already succeeded. ::: ## `customerId` requirements | Constraint | Detail | |------------|--------| | Format | Alphanumeric, hyphens, and underscores only (`^[a-zA-Z0-9_-]+$`) | | Maximum length | 64 characters | | Case sensitivity | Case-sensitive — `Cust_1` and `cust_1` are different customers | | Uniqueness | Must be unique within your integration | ## After onboarding Once a customer and address are registered, BRRR automatically: 1. Begins monitoring the address for on-chain activity 2. Performs an initial risk assessment 3. Sends a [`RISK_ASSESSMENT`](/docs/api/webhooks#risk-assessment) webhook with the result See [Monitoring](/docs/settlement/monitoring) and [Risk assessment](/docs/settlement/risk-assessment) for what happens next. ## Offboarding To remove an address or customer from monitoring, see [Offboarding](/docs/settlement/offboarding). ::: tip Next steps After onboarding, BRRR will send a [`RISK_ASSESSMENT`](/docs/api/webhooks#risk-assessment) webhook with the initial risk score. Set up your webhook endpoint and read [Risk assessment](/docs/settlement/risk-assessment) to understand how to act on each risk level. ::: --- --- url: 'https://docs.brrr.network/docs/orchestration.md' description: >- The supporting layer that makes Deposits and Withdraws work — cross-chain routing, address validation, balance lookup, and monitoring. --- # Orchestrate **Orchestrate** (the supporting layer — cross-chain routing, validation, balances, monitoring) is everything your application needs around a Deposit or a Withdraw, but isn't the on-chain action itself. It is how BRRR turns "the user holds tokens somewhere on some chain" into a single signed transaction, and how your application gets enough information to render a credible UI before that transaction is signed. There's no single `orchestrate()` method. Orchestrate surfaces through four concrete capabilities: cross-chain routing, recipient and address validation, wallet balance discovery, and post-flow monitoring. ## Cross-chain routing A user might hold USDC on Polygon while connecting to your app from Ethereum. They might hold a long-tail token that needs a swap before BRRR can accept it. They might be on a chain that requires a bridge hop. None of that is your application's concern. When you call `convertTokenToEUR` or `convertEURToToken`, BRRR computes the optimal route — bridges, swaps, gas estimation — and returns it as an opaque `transferData` object. You pass that object unchanged to `topup` and the SDK executes the route in one signing flow. If you skip the explicit quote step, `topup` fetches current routing data internally before submitting. The tradeoff: skipping the quote means you can't show the user a rate preview, but it removes one round trip when previewing isn't needed. ## Recipient and address validation Two helpers replace the question "is this destination valid?" with a structured answer: * [`getTagInfo(holytag)`](/sdk/references/common/get-tag-info) — looks up a Holyheld user by holytag and returns whether they exist and accept Deposits. Use before calling `topup`. * [`validateAddress(address)`](/sdk/references/common/validate-address) — returns whether a wallet address is registered with Holyheld and which flows it's eligible for (`isTopupAllowed`, `isOnRampAllowed`). Use before calling `topupSelf` or `requestOnRamp`. Both methods catch the common failure modes — typos, ineligible addresses, risk-flagged wallets — before the user signs anything. ## Wallet balances across networks [`getWalletBalances(address)`](/sdk/references/common/get-wallet-balances) returns every supported token the wallet holds, across every supported network, in one call. Each entry includes the network, contract address, decimals, symbol, USD value, and whether the token supports an EIP-2612 permit (`hasPermit: true`) — useful for picking the cheapest signing path in your UI. Combined with cross-chain routing, this is what makes a "pick any token, on any chain, send anywhere" experience possible without your application implementing per-chain RPC logic. ## Network helpers The SDK ships small synchronous utilities for working with the supported network catalog. Useful when building dropdowns, validating user input, or matching wallet state to expected chains. ::: code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; sdk.evm.offRamp.getAvailableNetworks(); // ['ethereum', 'polygon', ...] sdk.evm.onRamp.getAvailableNetworks(); // ['ethereum', 'polygon', ...] sdk.evm.isEVMNetwork(Network.ethereum); // true sdk.evm.getNetwork(Network.ethereum); // NetworkInfoEVM sdk.evm.getNetworkChainId(Network.ethereum); // 1 sdk.evm.getNetworkByChainId(1); // NetworkInfoEVM | undefined ``` ```typescript [Solana] import { SolanaNetwork } from '@holyheld/sdk'; sdk.solana.offRamp.getAvailableNetworks(); // ['solana-mainnet'] sdk.solana.isSolanaNetwork(SolanaNetwork.Mainnet); // true sdk.solana.getNetwork(SolanaNetwork.Mainnet); // NetworkInfoSolana ``` ::: `NetworkInfoEVM` and `NetworkInfoSolana` carry the displayable name, chain ID, RPC URLs, block explorer details, native gas token, and an icon URL. Use them to render network pickers without hard-coding chain metadata. For server-side gas estimation on EVM Deposits, `getTopUpEstimation` returns the expected native-token cost (in WEI) for the underlying transaction — useful when the application needs to display "you'll spend ~X ETH in gas" alongside the conversion quote. ## Monitoring Once a Deposit or Withdraw is in flight, [Webhooks](/docs/api/webhooks) deliver server-side notifications for terminal states: settled, failed, refunded. Prefer webhooks over polling whenever your architecture allows — they are the canonical channel for letting downstream systems react to flow outcomes. For client-side state, the SDK's `onStepChange` and `onHashGenerate` callbacks (Deposit) and `watchRequestId` (Withdraw) are the equivalent surfaces. ## Where to next * [`getWalletBalances`](/sdk/references/common/get-wallet-balances), [`validateAddress`](/sdk/references/common/validate-address), [`getTagInfo`](/sdk/references/common/get-tag-info) * [Deposit](/docs/off-ramp) and [Withdraw](/docs/on-ramp) — the user-facing flows that Orchestrate supports * [Webhooks](/docs/api/webhooks) — server-side flow notifications * A live multi-network reference implementation: [holyheld.com/sdk/send](https://holyheld.com/sdk/send) --- --- url: 'https://docs.brrr.network/docs/agentic/introduction.md' description: >- Give your AI agent a spending card. Let agents check balance, retrieve card details, and top up a Holyheld card autonomously with a simple HTTP API. --- # Payments for AI Agents The Payments for AI Agents API lets an AI agent autonomously check and replenish a Holyheld card balance, and retrieve card details when needed for checkout. It is a purpose-built HTTP API designed to be called directly by AI systems — Claude, GPT, LangChain agents, or any orchestration layer that can make HTTP requests. ::: info Early access This API is at version `0.0.1`. Endpoints and schemas are stable for the use cases documented here, but the surface may expand as new capabilities are added. ::: ## What an agent can do | Action | Endpoint | Description | |--------|----------|-------------| | Check balance | `GET /balance` | Query the current EUR balance available on the Holyheld card | | Get card data | `GET /card-data` | Retrieve card number, expiration date, CVV, cardholder name, and billing address | | Top up | `POST /topup-request` | Transfer funds from the user's Holyheld main account to the card | That is the complete API surface. An agent that can call these three endpoints can manage card balance and execute card-funded checkout flows within the limits the user configures. ## Use cases **Autonomous expense management** — An agent monitors a card balance and tops it up before it runs low, ensuring uninterrupted spending without user intervention. **Threshold-based top-up** — An agent is instructed: *"Keep my card above €50 at all times."* It checks balance on a schedule, and triggers a top-up whenever the threshold is breached. **Pre-spend balance check** — Before booking a service on behalf of a user, an agent checks that sufficient funds are available and tops up if needed. **Checkout automation** — An agent verifies balance, retrieves the card details only when required, and completes a payment flow on the user's behalf without exposing the card data beyond the active session. **Budgeted agent spending** — Users grant an agent a spending limit (configured in the Holyheld dashboard). The agent operates within that limit; any attempt beyond the limit is rejected with `AI_TOPUP_LIMIT_EXCEEDED`. ## How it works ``` ┌─────────────────────────────────────────────────────────┐ │ Your AI Agent │ │ (Claude MCP server, GPT function, LangChain tool, …) │ └────────────────────────┬────────────────────────────────┘ │ Bearer token ▼ ┌───────────────────────────────┐ │ Payments for AI Agents API │ │ apicore.holyheld.com/v4/ai-agents │ │ │ │ GET /balance ──────────┼──► EUR balance on card │ GET /card-data ──────────┼──► Card details for checkout │ POST /topup-request ─────────┼──► Top up from main account balance └───────────────────────────────┘ │ │ (settlement, up to 5 min) ▼ ┌───────────────────────────────┐ │ Holyheld card │ │ Balance updated in EUR │ └───────────────────────────────┘ ``` ## How it differs from the APIs The APIs (`api.brrr.network`) are for **platform developers** building settlement flows for their end users. The Payments for AI Agents API is for **AI agents** acting on behalf of a single user's own Holyheld account. | | APIs | Payments for AI Agents API | |---|---|---| | **Who uses it** | Platform backends (B2B) | AI agents (personal) | | **Base URL** | `https://api.brrr.network` | `https://apicore.holyheld.com/v4/ai-agents` | | **Authentication** | `X-Api-Key` header | `Authorization: Bearer ` | | **Endpoints** | Full product surfaces | 3 (balance + card data + top-up) | | **Manages** | Multiple end-user accounts | One user's own card | | **Key obtained via** | Holyheld partner onboarding | Agent instructions for a Holyheld card | ## Environment | Environment | Base URL | |-------------|----------| | **Production** | `https://apicore.holyheld.com/v4/ai-agents` | ## Prerequisites Before making your first request you need: 1. **A Holyheld account** with an active card and available balance on the main Holyheld account 2. **Agent instructions** copied for that card; the instructions include the Bearer token 3. **A spending limit** configured for the card's agent access (the dashboard lets you set a maximum cumulative top-up amount) ## Next steps ::: tip Get started 1. [Configure authentication](/docs/agentic/authentication) — copy the card's agent instructions and learn how to use them 2. [Check balance](/docs/agentic/get-balance) — make your first API call 3. [Get card data](/docs/agentic/card-data) — retrieve card details safely for checkout flows 4. [Top up the card](/docs/agentic/topup) — trigger a top-up and handle the asynchronous confirmation 5. [Build an MCP server](/docs/agentic/mcp-guide) — expose these endpoints as Claude tools in minutes ::: --- --- url: 'https://docs.brrr.network/docs/settlement/rates.md' description: Retrieve the current token to fiat exchange rate. --- # Rates The rates endpoint returns the current **token-to-fiat exchange rate** for a specific token on a given network. Use it to display an indicative fiat value in your UI before a settlement quote is requested. ::: warning Rates are indicative only The rate returned by this endpoint is for display purposes. Actual settlement amounts are determined at quote time by [`POST /partner/settlement/quote`](/api/settlement/settlement-estimation/create-settlement-quote), which locks in a guaranteed rate for 15 minutes. Do not use the rates endpoint to calculate the token amount to transfer on-chain. ::: ## Endpoint | Endpoint | Description | |----------|-------------| | [`GET /api/v4/partner/rates/{currency}/{network}/{token}`](/api/settlement/rates/get-exchange-rate) | Get the current exchange rate for a token and settlement currency | ## URL parameters | Parameter | Description | Example | |-----------|-------------|---------| | `currency` | Settlement currency | `EUR`, `GBP` | | `network` | Blockchain network identifier | `ethereum`, `polygon` | | `token` | Token contract address on the specified network | `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48` | ## Response fields The response payload contains one top-level field: | Field | Description | |-------|-------------| | `rate` | Token-to-fiat rate — how many units of the settlement currency are received per 1 token | ### Example response ```json { "status": "ok", "payload": { "rate": "0.9175" } } ``` ## Usage guidance | Goal | Use | |------|-----| | Show an indicative fiat value for a token amount in the UI | `rate` × token amount | | Calculate the exact token amount to send for a settlement | Use [`POST /partner/settlement/quote`](/api/settlement/settlement-estimation/create-settlement-quote) instead | ## Supported currencies Currently `EUR` and `GBP` are supported as settlement currencies. Pass the currency as the first path segment in the URL. --- --- url: 'https://docs.brrr.network/sdk/references.md' description: Complete reference for all BRRR SDK functions. --- # SDK References This section provides a complete reference for all functions available in the BRRR SDK. Functions are grouped into three categories. For step-by-step integration guidance, see the [Deposit](/docs/off-ramp) and [Withdraw](/docs/on-ramp) hubs. ## Off-Ramp Functions These functions facilitate **crypto-to-fiat** transactions, converting tokens held in a user's wallet into EUR credited to their Holyheld card. | Function | Description | |----------|-------------| | [`convertTokenToEUR`](/sdk/references/off-ramp/convert-token-to-eur) | Convert a token amount to its EUR equivalent at the current rate | | [`convertEURToToken`](/sdk/references/off-ramp/convert-eur-to-token) | Convert a EUR amount to the corresponding token amount at the current rate | | [`topup`](/sdk/references/off-ramp/topup) | Execute an off-ramp transaction, sending tokens to a specified Holyheld `$holytag` | | [`topupSelf`](/sdk/references/off-ramp/topup-self) | Execute an off-ramp to the Holyheld card of the connected wallet — no $holytag required | ## On-Ramp Functions These functions facilitate **fiat-to-crypto** transactions, converting EUR from the user's Holyheld card into tokens delivered to a wallet address. | Function | Description | |----------|-------------| | [`convertTokenToEUR`](/sdk/references/on-ramp/convert-token-to-eur) | Estimate the EUR value of a token amount for on-ramp display | | [`convertEURToToken`](/sdk/references/on-ramp/convert-eur-to-token) | Estimate the token amount for a given EUR input | | [`getOnRampEstimation`](/sdk/references/on-ramp/get-on-ramp-estimation) | Get a full on-ramp estimate including fees and expected token output | | [`requestOnRamp`](/sdk/references/on-ramp/request-on-ramp) | Initiate an on-ramp request, triggering the fiat-to-crypto conversion | | [`watchRequestId`](/sdk/references/on-ramp/watch-request-id) | Poll an on-ramp request until it reaches a terminal status | ## Common Functions These functions are not specific to either flow but are required for general SDK operation, address validation, and balance retrieval. | Function | Description | |----------|-------------| | [`getServerSettings`](/sdk/references/common/get-server-settings) | Fetch current service status and min/max EUR limits for on-ramp and off-ramp | | [`getTagInfo`](/sdk/references/common/get-tag-info) | Look up a Holyheld `$holytag` to verify it exists and retrieve recipient details | | [`validateAddress`](/sdk/references/common/validate-address) | Validate a wallet address and check whether it is eligible for off-ramp and on-ramp | | [`getWalletBalances`](/sdk/references/common/get-wallet-balances) | Retrieve all supported token balances for a wallet address | --- --- url: 'https://docs.brrr.network/api/settlement/onboarding/register-customer.md' description: Register a customer and associate their EVM wallet address. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/api/v4/partner/customer/register \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "customerId": "cust_a1b2c3d4", "addressEVM": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/api/v4/partner/customer/register', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ customerId: 'cust_a1b2c3d4', addressEVM: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Registration failed: ${error.errorCode}`); } const data = await response.json(); console.log('Customer registered:', data); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/api/v4/partner/customer/register', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ 'customerId': 'cust_a1b2c3d4', 'addressEVM': '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', }, ) response.raise_for_status() data = response.json() print('Customer registered:', data) ``` ::: --- --- url: 'https://docs.brrr.network/api/settlement/offboarding/remove-customer.md' description: Remove a customer and all associated wallet addresses from monitoring. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/api/v4/partner/customer/remove \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "customerId": "cust_a1b2c3d4" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/api/v4/partner/customer/remove', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ customerId: 'cust_a1b2c3d4', }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Failed to remove customer: ${error.errorCode}`); } const data = await response.json(); console.log('Customer removed:', data); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/api/v4/partner/customer/remove', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ 'customerId': 'cust_a1b2c3d4', }, ) response.raise_for_status() data = response.json() print('Customer removed:', data) ``` ::: --- --- url: 'https://docs.brrr.network/api/settlement/offboarding/remove-address.md' description: Remove a specific wallet address from monitoring. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/api/v4/partner/customer/remove-address \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "customerId": "cust_a1b2c3d4", "addressEVM": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/api/v4/partner/customer/remove-address', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ customerId: 'cust_a1b2c3d4', addressEVM: '0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B', }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Failed to remove address: ${error.errorCode}`); } const data = await response.json(); console.log('Address removed:', data); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/api/v4/partner/customer/remove-address', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ 'customerId': 'cust_a1b2c3d4', 'addressEVM': '0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B', }, ) response.raise_for_status() data = response.json() print('Address removed:', data) ``` ::: --- --- url: 'https://docs.brrr.network/api/settlement/iban/remove-iban-from-customer.md' description: Remove a previously registered IBAN from a partner customer. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/api/v4/partner/customer/remove-iban \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "customerId": "cust_a1b2c3d4", "ibanId": "iban_01HXYZ123456" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/api/v4/partner/customer/remove-iban', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ customerId: 'cust_a1b2c3d4', ibanId: 'iban_01HXYZ123456', }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Failed to remove IBAN: ${error.errorCode}`); } ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/api/v4/partner/customer/remove-iban', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ 'customerId': 'cust_a1b2c3d4', 'ibanId': 'iban_01HXYZ123456', }, ) response.raise_for_status() ``` ::: --- --- url: 'https://docs.brrr.network/sdk/references/on-ramp/request-on-ramp.md' description: >- Submit an on-ramp request that the user then confirms in their Holyheld app — the primary execution step of the on-ramp flow. --- # `requestOnRamp` This is the primary method for submitting an on-ramp request — initiating a fiat spend that results in tokens delivered to the specified wallet address. :::info 🔔 After creating the on-ramp request, the user will need to confirm it in their Holyheld app. ::: :::warning 🚨 As per security requirements, the user **MUST** approve the on-ramp request in their Holyheld mobile app within 3 minutes. If the user declines or lets the confirmation expire, the transaction will fail and will not be executed. ::: ## Usage ::: code-group ```typescript [EVM] const data = await holyheldSDK.evm.onRamp.requestOnRamp({ walletAddress: '0x...', tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', tokenNetwork: Network.ethereum, EURAmount: '1', }); ``` ::: ## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `walletAddress` | `string` | Yes | Destination wallet address that will receive the tokens. Must be a registered, eligible address. | | `tokenAddress` | `string` | Yes | Contract address of the token to receive. | | `tokenNetwork` | `Network` | Yes | The chain the token will arrive on. | | `EURAmount` | `string` | Yes | EUR amount to spend, as a decimal string (e.g. `"50"` for 50 EUR). | ### Wallet Address User's wallet address * **Type:** `string` ::: code-group ```typescript [EVM] const data = await holyheldSDK.evm.onRamp.requestOnRamp({ walletAddress: '0x...', // [!code highlight] tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', tokenNetwork: Network.ethereum, EURAmount: '1', }); ``` ::: ### Token Address Address of the token * **Type:** `string` ::: code-group ```typescript [EVM] const data = await holyheldSDK.evm.onRamp.requestOnRamp({ walletAddress: '0x...', tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // [!code highlight] tokenNetwork: Network.ethereum, EURAmount: '1', }); ``` ::: ### Token Network Network where tokens will arrive * **Type:** `Network` ::: code-group ```typescript [EVM] const data = await holyheldSDK.evm.onRamp.requestOnRamp({ walletAddress: '0x...', tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', tokenNetwork: Network.ethereum, // [!code highlight] EURAmount: '1', }); ``` ::: ### EUR Amount EUR amount * **Type:** `String` ::: code-group ```typescript [EVM] const data = await holyheldSDK.evm.onRamp.requestOnRamp({ walletAddress: '0x...', tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', tokenNetwork: Network.ethereum, EURAmount: '1' // [!code highlight] }); ``` ::: ## Returns ::: code-group ```typescript [types.ts] type RequestOnRampEVMResult = { requestUid: string; chainId: number; token: TokenEVM; amountEUR: string; amountToken: string; feeEUR: string; beneficiaryAddress: EVMAddress; } ``` ::: ### requestUid ID of the on-ramp request created * **Type:** `String` ::: code-group ```typescript [types.ts] type RequestOnRampEVMResult = { requestUid: string; // [!code highlight] chainId: number; token: TokenEVM; amountEUR: string; amountToken: string; feeEUR: string; beneficiaryAddress: EVMAddress; } ``` ::: ### chainId ID of the network where tokens will arrive * **Type:** `Number` ::: code-group ```typescript [types.ts] type RequestOnRampEVMResult = { requestUid: string; chainId: number; // [!code highlight] token: TokenEVM; amountEUR: string; amountToken: string; feeEUR: string; beneficiaryAddress: EVMAddress; } ``` ::: ### token Token object that will be delivered on success * **Type:** `TokenEVM` ::: code-group ```typescript [types.ts] type RequestOnRampEVMResult = { requestUid: string; chainId: number; token: TokenEVM; // [!code highlight] amountEUR: string; amountToken: string; feeEUR: string; beneficiaryAddress: EVMAddress; } ``` ::: ### amountEUR Amount of EUR charged from the user * **Type:** `String` ::: code-group ```typescript [types.ts] type RequestOnRampEVMResult = { requestUid: string; chainId: number; token: TokenEVM; amountEUR: string; // [!code highlight] amountToken: string; feeEUR: string; beneficiaryAddress: EVMAddress; } ``` ::: ### amountToken Native amount of tokens received * **Type:** `String` ::: code-group ```typescript [types.ts] type RequestOnRampEVMResult = { requestUid: string; chainId: number; token: TokenEVM; amountEUR: string; amountToken: string; // [!code highlight] feeEUR: string; beneficiaryAddress: EVMAddress; } ``` ::: ### feeEUR Network gas fee charged from the total transaction amount * **Type:** `String` ::: code-group ```typescript [types.ts] type RequestOnRampEVMResult = { requestUid: string; chainId: number; token: TokenEVM; amountEUR: string; amountToken: string; feeEUR: string; // [!code highlight] beneficiaryAddress: EVMAddress; } ``` ::: ### beneficiaryAddress User wallet address where tokens will arrive * **Type:** `string` ::: code-group ```typescript [types.ts] type RequestOnRampEVMResult = { requestUid: string; chainId: number; token: TokenEVM; amountEUR: string; amountToken: string; feeEUR: string; beneficiaryAddress: EVMAddress; // [!code highlight] } ``` ::: --- --- url: 'https://docs.brrr.network/docs/settlement/risk-assessment.md' description: Risk assessment process and webhook notifications. --- # Risk assessment Every registered wallet address is continuously monitored for on-chain activity. Whenever activity is detected, BRRR performs a risk evaluation and delivers the result via webhook. Your integration must act on the risk level before proceeding to settlement. ## How risk assessment works ``` Customer passes KYC (SumSub GREEN) │ ▼ Partner registers address POST /partner/customer/add-address │ ▼ BRRR performs initial risk assessment (reviewType: "INITIAL") │ ▼ RISK_ASSESSMENT webhook → Partner │ ┌────────┴─────────────────────────────┐ │ │ ▼ ▼ LOW / MEDIUM HIGH / VERY HIGH Proceed to settlement Do not settle — hold and review manually ``` After the initial assessment, BRRR continues monitoring. Every incoming transfer to the address triggers a new evaluation (`reviewType: "TOPUP"`) and another `RISK_ASSESSMENT` webhook. See [Monitoring](/docs/settlement/monitoring) for details. ## Risk levels | Level | Meaning | Settlement allowed? | Recommended action | |-------|---------|--------------------|--------------------| | `LOW` | Address is clear, no risk signals detected | ✅ Yes | Proceed to [Create a Settlement Quote](/api/settlement/settlement-estimation/create-settlement-quote) | | `MEDIUM` | Minor risk signals present | ✅ Yes | Proceed with caution; document the decision for your records | | `HIGH` | Significant risk signals; escalated to Holyheld's AML team | ❌ No | Hold funds; flag for manual review; do not settle | | `VERY HIGH` | Address flagged for serious risk; escalated to Holyheld's AML team | ❌ No | Do not settle; contact | ::: warning HIGH and VERY HIGH block settlement Attempting to create a settlement quote for an address with `HIGH` or `VERY HIGH` risk returns a `FULFILLMENT_DENIED` error. Always check the risk level before calling [Create a Settlement Quote](/api/settlement/settlement-estimation/create-settlement-quote). ::: ## Receiving risk results ### Webhook notifications (recommended) Subscribe to [`RISK_ASSESSMENT`](/docs/api/webhooks#risk-assessment) webhooks to receive updates in real time. A webhook fires for: * The **initial assessment** (`reviewType: "INITIAL"`) — typically within seconds of address registration * **Every incoming transfer** (`reviewType: "TOPUP"`) — even if the risk level has not changed Webhooks may arrive out of order. Always use the `timestamp` field to determine recency. See [Webhooks — Event ordering](/docs/api/webhooks#event-ordering). ### API polling Query the current risk score at any time without waiting for a webhook: * [`GET /api/v4/partner/risk/by-customer/{customerId}`](/api/settlement/risk-assessment/get-risk-by-customer-id) — all addresses for a customer * [`GET /api/v4/partner/risk/by-address/{addressEVM}`](/api/settlement/risk-assessment/get-risk-by-address) — a specific address These endpoints return the most recent completed assessment and do not trigger a new evaluation. For each address, the `riskScores` array is ordered from newest to oldest, so `riskScores[0]` is always the latest completed review. ## Risk assessment during onboarding Risk assessment begins automatically after both conditions are met: 1. The customer has passed KYC — SumSub status is `GREEN` or `FINISHED` 2. The address has been registered via [`POST /api/v4/partner/customer/add-address`](/api/settlement/onboarding/add-address-to-customer) If the KYC result is not yet available when the address is registered, BRRR stores the registration and waits for the SumSub `GREEN` webhook before starting the evaluation. ::: tip KYC status requirements The `externalCustomerID` in the SumSub webhook must match the `customerId` used during [Onboarding](/docs/settlement/onboarding). Risk assessment only begins after `GREEN` or `FINISHED` SumSub status. The following statuses do **not** trigger risk assessment: `RETRY`, `RESUBMIT`, `FAILED`, `FINAL`. The statuses `EDD` and `ManualApproval` are escalated to Holyheld's AML team for manual review. ::: ## When risk level cannot be determined If BRRR cannot automatically determine the risk level (for example, due to insufficient on-chain history or an AML flag), an internal review is initiated. The `RISK_ASSESSMENT` webhook is sent once the manual review is complete. Design your system to handle the webhook whenever it arrives — do not time out waiting for it. ::: tip Next steps Once you receive a `LOW` or `MEDIUM` risk score, you can proceed to [Settlement estimation](/docs/settlement/settlement-estimation) to create a quote. For `HIGH` or `VERY HIGH` addresses, hold funds and do not submit a quote until the risk is resolved. ::: --- --- url: 'https://docs.brrr.network/docs/risk-disclosures.md' description: Learn about risks involved. --- # Risk Disclosures ## Offer-Related Risks * Market Volatility: The value of crypto assets may be highly volatile and subject to unpredictable fluctuations due to speculative activity, general market trends, regulatory developments, or macroeconomic factors. * Liquidity Risk: The absence of an active secondary market or the inability to access trading platforms may render it difficult or impossible for holders to sell or otherwise dispose of their crypto assets at prevailing market prices or at all. * Regulatory Risk: Changes in applicable laws, regulations, or enforcement policies in the European Union or other jurisdictions may adversely affect the legality, viability, or value of the crypto assets or limit the ability of the issuer to conduct the offering or continue operations. * Offering Failure: There is a risk that the offer may not reach its funding objectives or attract sufficient interest, thus affecting the implementation or continuity of the project. ## Issuer-Related Risks * Operational Risk: The issuer may face operational challenges, including management inefficiencies, inadequate internal controls, or dependence on key personnel, which may affect its ability to meet its obligations. * Financial Risk: The issuer may be subject to financial distress, including insolvency or lack of sufficient working capital, which may impair the value or functionality of the crypto assets. * Jurisdictional Risk: The issuer may be located in or subject to laws of a jurisdiction that provides limited recourse or legal protection for holders of the crypto assets. ## Crypto Assets-Related Risks * Functional Risk: The crypto assets may not perform as intended or may be susceptible to software bugs, unintended code behavior, or protocol design flaws. * Cybersecurity Risk: The crypto assets may be vulnerable to cyberattacks, including unauthorized access, theft, loss of private keys, or other forms of digital compromise. * Lack of Intrinsic Value: The crypto assets may not be backed by tangible assets or cash flows and may derive their value solely from supply and demand dynamics or network usage. ## Project Implementation-Related Risks * Development Risk: The project may face delays, technical difficulties, or changes in strategic direction that could adversely affect its timeline, functionality, or scope. * Third-Party Dependency: The project may rely on external service providers, partners, or infrastructure whose performance or failure may impact the success of the project. * Resource Risk: The project may lack sufficient human, technical, or financial resources to complete its development or operate sustainably. The issuer has implemented various technical and organizational safeguards, including code audits, network monitoring, and multi-signature wallets, aimed at reducing exposure to technological and operational risks. However, such measures cannot eliminate all the risks inherent in the use of blockchain or distributed ledger technology. --- --- url: 'https://docs.brrr.network/sdk/introduction.md' description: >- BRRR SDK provides methods to on-ramp and off-ramp crypto in TypeScript/JavaScript environments. The process takes only a few steps and allows you to customize the customer flow and UI. --- # SDK Introduction The Holyheld SDK enables your application to facilitate crypto-to-fiat conversions (off-ramp) and fiat-to-crypto purchases (on-ramp) directly from a user's connected wallet. Off-ramping converts tokens from a user's wallet to EUR on their Holyheld card. On-ramping does the reverse — initiating a fiat spend that results in tokens delivered to a specified wallet address. To use Holyheld SDK, you will require an **SDK API key**. To obtain one, please reach out to your Holyheld point of contact. ## SDK flow overview ::: code-group ```text [Off-Ramp (token → EUR)] 1. Check availability holyheldSDK.getServerSettings() └── confirms isTopupEnabled, min/max EUR limits 2. Validate recipient holyheldSDK.getTagInfo(holytag) ← sending to a $holytag holyheldSDK.validateAddress(address) ← OR sending to own account 3. Get a conversion quote holyheldSDK.evm.offRamp.convertTokenToEUR({ tokenAddress, amount, ... }) holyheldSDK.evm.offRamp.convertEURToToken({ tokenAddress, amount, ... }) └── returns { tokenAmount, EURAmount, transferData } 4. Execute the off-ramp holyheldSDK.evm.offRamp.topup({ transferData, holytag, eventConfig, ... }) holyheldSDK.evm.offRamp.topupSelf({ transferData, walletAddress, eventConfig, ... }) │ ├── onStepChange('confirming') ← user confirms in wallet ├── onStepChange('approving') ← token approval or permit (if needed) ├── onStepChange('sending') ← final send transaction └── onHashGenerate(txHash) ← transaction hash available ✓ ``` ```text [On-Ramp (EUR → token)] 1. Check availability holyheldSDK.getServerSettings() └── confirms isOnRampEnabled, min/max EUR limits 2. Validate recipient wallet holyheldSDK.validateAddress(walletAddress) 3. Get an estimation (optional) holyheldSDK.evm.onRamp.getOnRampEstimation({ tokenAddress, EURAmount, ... }) └── returns estimated token amount to be delivered 4. Request on-ramp holyheldSDK.evm.onRamp.requestOnRamp({ tokenAddress, EURAmount, walletAddress, ... }) └── returns { requestUid, amountEUR, amountToken, beneficiaryAddress, ... } 5. Wait for user confirmation holyheldSDK.evm.onRamp.watchRequestId(requestUid, { timeout: 180_000 }) │ ├── success: true → on-ramp approved, tokens en route │ hash (if waitForTransactionHash: true) → on-chain tx hash └── success: false → declined or timed out ``` ::: ## Web3 Provider The SDK supports both EVM compatible and Solana networks. Please see [examples](/sdk/web3-providers) for more details. ### EVM Networks A [Viem](https://github.com/wagmi-dev/viem){target="\_blank"} provider is used for EVM-compatible JSON-RPC interactions, [Web3.js](https://github.com/web3/web3.js){target="\_blank"} and [ethers.js](https://github.com/ethers-io/ethers.js){target="\_blank"} are also supported. ### Solana Solana is supported via [@solana/web3.js](https://github.com/solana-labs/solana-web3.js){target="\_blank"}. ::: warning Node.js polyfill required Some dependencies in SDK rely on Node.js's Buffer class, which is not available by default in modern bundlers. To ensure compatibility, you must polyfill `Buffer` using one of the following: * [buffer](https://github.com/feross/buffer){target="\_blank"} - for manual importing * [vite-plugin-node-polyfills](https://github.com/davidmyersdev/vite-plugin-node-polyfills){target="\_blank"} - for `Vite` * [node-polyfill-webpack-plugin](https://github.com/Richienb/node-polyfill-webpack-plugin){target="\_blank"} - for `Webpack` * [rollup-plugin-node-polyfills](https://github.com/ionic-team/rollup-plugin-node-polyfills){target="\_blank"} - for `Rollup` Please refer to your bundler's documentation for setup instructions. ::: ## Getting help | Resource | Link | |----------|------| | **Bug reports & questions** | | | **Changelog** | [SDK Changelog](/docs/changelog) | | **npm package** | [@holyheld/sdk](https://www.npmjs.com/package/@holyheld/sdk) | If you believe you have found a security issue, contact directly rather than filing a public issue. --- --- url: 'https://docs.brrr.network/docs/settlement/settlement-estimation.md' description: Request settlement quotes before executing settlement. --- # Settlement estimation Before executing a settlement, you must request a **quote**. The quote locks in conversion rates for a set of beneficiaries and returns a `quoteId` required for [Settlement execution](/docs/settlement/settlement-execution). ## Where estimation fits in the flow ``` Risk assessment: LOW or MEDIUM │ ▼ POST /partner/settlement/quote ← you are here • specify currency, network, token • list one or more beneficiaries • choose quoteType per beneficiary │ ▼ Quote returned: { quoteId, totalAmountIn, confirmDeadline, beneficiaries[] } │ │ valid for 15 minutes ▼ POST /partner/settlement/execute → send tokens on-chain to receiveAddress ``` ## Quote types Each beneficiary in the request specifies a `quoteType`: | `quoteType` | When to use | `amount` field means | |-------------|------------|----------------------| | `fiat_to_token` | You know the fiat amount to settle | Amount in the requested fiat currency (2 decimal places) | | `token_to_fiat` | You know the token amount to send | Token amount in the token's native precision | All beneficiaries in a single quote must share the same `currency`, `network`, and `token`. Mixed currencies or networks require separate quotes. ## What a quote contains A successful response includes: | Field | Description | |-------|-------------| | `quoteId` | Unique identifier | | `confirmDeadline` | Unix timestamp after which the quote expires (typically 15 minutes from creation) | | `totalAmountIn` | Exact token amount to transfer on-chain when executing | | `totalAmountSettled` | Total fiat amount that will be distributed to all beneficiaries | | `beneficiaries[]` | Per-beneficiary breakdown with individual `amountIn` and `amountSettled` | ::: tip Where does `receiveAddress` come from? `receiveAddress` — the BRRR wallet address you send tokens to — is returned by [Execute Settlement](/api/settlement/settlement-execution/execute-settlement), **not** by the quote endpoint. Always use the address from the execute response; never cache or reuse an address from a previous settlement. ::: ## Fulfillment validation Each beneficiary is validated when the quote is created. A beneficiary is **denied** (and excluded from the quote) if: | Reason | `reason` value | |--------|----------------| | Customer or address not found | `NOT_FOUND` | | Risk assessment not yet available | `RISK_UNAVAILABLE` | | Risk level is `HIGH` or `VERY HIGH` | `RISK_LEVEL` | If **any** beneficiary is denied, the entire request returns a `400 FULFILLMENT_DENIED` error with a `payload` array listing each denied beneficiary and their reason. No partial quote is returned — fix the denied beneficiaries and re-submit. ::: tip Handling partial failures If only some beneficiaries are denied, remove them from the request and re-submit with the remaining eligible beneficiaries. Contact for guidance on denied beneficiaries. ::: ## Quote expiry Quotes are valid for **15 minutes** from creation (`confirmDeadline`). If you do not call [Execute Settlement](/api/settlement/settlement-execution/execute-settlement) before the deadline: 1. The quote expires — the `quoteId` cannot be used 2. Create a new quote before executing ::: warning Do not cache quotes Exchange rates change. Always use the `quoteId` from the most recent quote request. Never reuse a `quoteId` from a previous session or a cached response. ::: ## Re-estimation If market conditions change between quote creation and execution, or if you need to adjust the beneficiary list, simply create a new quote. Each call to `POST /partner/settlement/quote` generates a fresh `quoteId` with updated rates and a new 15-minute window. ::: tip Next steps Pass the full quote `payload` from the quote response to [Settlement execution](/docs/settlement/settlement-execution). The execute response provides the `receiveAddress` to send tokens to on-chain. ::: --- --- url: 'https://docs.brrr.network/docs/settlement/settlement-execution.md' description: Execute settlements using a previously generated quote. --- # Settlement execution Settlement execution confirms a previously generated settlement quote and initiates the settlement process. Before executing, a valid quote must be obtained through the [Settlement estimation](/docs/settlement/settlement-estimation) step. ## Settlement lifecycle A settlement moves through a fixed sequence of statuses. Each status change triggers a [`SETTLEMENT_STATUS_CHANGE`](/docs/api/webhooks#settlement-status-change) webhook. ``` ┌─────────────────────────────┐ │ CREATED │ │ Quote generated, awaiting │ │ partner confirmation │ └──────────┬──────────────────┘ │ ┌────────────────┼───────────────────┐ │ confirmed │ not confirmed │ within 15 min │ within 15 min ▼ ▼ ┌────────────────┐ ┌─────────────────┐ │ CONFIRMED │ │ EXPIRED │ │ Awaiting token │ │ Quote no longer │ │ transfer on- │ │ valid — create │ │ chain │ │ a new quote │ └───────┬────────┘ └─────────────────┘ │ │ tokens received ▼ ┌────────────────┐ │ PROCESSING │ │ Settlement in │ │ progress │ └───────┬────────┘ │ ▼ ┌────────────────┐ │ SENT │ │ Fiat sent to │ │ bank account │ └───────┬────────┘ │ ▼ ┌────────────────┐ │ FINISHED │ │ Settlement │ ← terminal state ✓ │ complete │ └────────────────┘ ┌────────────────┐ │ ERROR │ ← requires manual handling by BRRR support └────────────────┘ ``` ## Status reference | Status | Description | Terminal? | Action required | |--------|-------------|-----------|-----------------| | `CREATED` | Quote generated, awaiting partner execution | No | Call [Execute Settlement](/api/settlement/settlement-execution/execute-settlement) | | `CONFIRMED` | Quote confirmed, awaiting on-chain token transfer | No | Send tokens to `receiveAddress` before `receivedDeadline` | | `EXPIRED` | Quote not confirmed within 15 minutes | Yes | Create a new quote — expired quotes cannot be reused | | `PROCESSING` | Tokens received, fiat settlement in progress | No | No action needed — wait for `SENT` | | `SENT` | Fiat transferred to the designated bank account | No | No action needed — wait for `FINISHED` | | `FINISHED` | Settlement fully complete | Yes | No further action required | | `ERROR` | Settlement failed and requires manual review | Yes | Contact with the `quoteId` | ## Timing | Step | Typical duration | |------|-----------------| | `CREATED` → `CONFIRMED` | Immediate (partner-controlled, 15-minute window) | | `CONFIRMED` → `PROCESSING` | Seconds to minutes after on-chain transfer is detected | | `PROCESSING` → `SENT` | Minutes to hours depending on banking rails | | `SENT` → `FINISHED` | Typically immediate after bank confirmation | ::: warning Token transfer deadline After executing a settlement (`CONFIRMED` state), you must send the token transfer to the `receiveAddress` before the `receivedDeadline` timestamp. If the deadline passes without a transfer being received, the settlement will not proceed. A new quote must be created. ::: ## Handling ERROR settlements If a settlement reaches `ERROR` status: 1. Do **not** retry the same `quoteId` — it cannot be re-executed 2. Note the `quoteId` for your records 3. Contact with the `quoteId` to initiate manual review 4. Create a new quote only after confirming with BRRR support that the original transaction did not partially settle ## Safe retry algorithm If a network error prevents you from receiving the Execute Settlement response, never retry blindly. Use this sequence: ``` 1. Call GET /partner/settlement/status/{quoteId} 2. Check the status: ┌──────────────────┬─────────────────────────────────────────────┐ │ Status │ Action │ ├──────────────────┼─────────────────────────────────────────────┤ │ CREATED │ Execute was not received — safe to retry │ │ EXPIRED │ Quote expired — create a new quote │ │ CONFIRMED │ Execute was accepted — do NOT retry execute │ │ PROCESSING │ Settlement in progress — do NOT retry │ │ SENT │ Fiat sent — do NOT retry │ │ FINISHED │ Settlement complete — do NOT retry │ │ ERROR │ Contact support — do NOT retry with same ID │ └──────────────────┴─────────────────────────────────────────────┘ 3. Only call Execute Settlement again if step 2 returns CREATED. For any other non-error status, the settlement has been accepted. ``` ```javascript async function safeExecuteSettlement(quoteId, executePayload) { try { return await fetch('https://api.brrr.network/api/v4/partner/settlement/execute', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify(executePayload), }); } catch (networkError) { // Network error — check whether execute was received before retrying const statusRes = await fetch( `https://api.brrr.network/api/v4/partner/settlement/status/${quoteId}`, { headers: { 'X-Api-Key': process.env.BRRR_API_KEY } } ); if (statusRes.status === 404) { // Quote unknown — safe to create a new quote and retry throw new Error('Quote not found — create a new quote'); } const { payload } = await statusRes.json(); if (payload.status === 'CREATED') { // Execute was not received — safe to retry return safeExecuteSettlement(quoteId, executePayload); } if (payload.status === 'EXPIRED') { throw new Error('Quote expired — create a new quote'); } // Any other status means execute was accepted return { alreadyAccepted: true, status: payload.status }; } } ``` ## Confirming settlement After calling [Execute Settlement](/api/settlement/settlement-execution/execute-settlement), the response contains: * `receiveAddress` — the BRRR wallet address you must send tokens to. **Always use the address from this response** — never cache a value from a previous settlement. * `totalAmountIn` — the exact token amount to transfer on-chain * `receivedDeadline` — Unix timestamp after which the settlement expires if no transfer is received Once the required token amount is received on-chain, BRRR automatically advances the settlement through `PROCESSING` → `SENT` → `FINISHED`. ## Monitoring status Poll [Get Settlement Status](/api/settlement/settlement-execution/get-settlement-status) every 15–30 seconds, or rely on [`SETTLEMENT_STATUS_CHANGE`](/docs/api/webhooks#settlement-status-change) webhooks. Both mechanisms reflect the same state. ::: tip Webhook ordering Settlement status webhooks may arrive out of order. Always use the `timestamp` field to determine whether an incoming event is more recent than your stored state. See [Webhooks — Event ordering](/docs/api/webhooks#event-ordering) for a handler example. ::: ::: tip Next steps Once settlements are working end-to-end, review the [Go-Live Checklist](/docs/go-live) before enabling real users. When you need to remove a customer or address from monitoring, see [Offboarding](/docs/settlement/offboarding). ::: --- --- url: >- https://docs.brrr.network/api/card-api/offramp-crypto-to-card/card-solana-convert-eur-to-token.md description: Estimate a Solana offramp using an EUR-denominated amount. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/v5/offramp/crypto-to-card/solana-eur-to-token \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "toToken": { "address": "So11111111111111111111111111111111111111112", "decimals": 9 }, "fromToken": { "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "decimals": 6 }, "eurAmount": "100.00", "fromAddress": "7xKXtg2CWmS8cznVn9Yx8uN4m1s1P9HhQw", "network": "solana-mainnet" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v5/offramp/crypto-to-card/solana-eur-to-token', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ 'toToken': { 'address': 'So11111111111111111111111111111111111111112', 'decimals': 9 }, 'fromToken': { 'address': 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', 'decimals': 6 }, 'eurAmount': '100.00', 'fromAddress': '7xKXtg2CWmS8cznVn9Yx8uN4m1s1P9HhQw', 'network': 'solana-mainnet' }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Request failed: ${error.errorCode}`); } const data = await response.json(); console.log(data.payload); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/v5/offramp/crypto-to-card/solana-eur-to-token', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ "toToken": { "address": "So11111111111111111111111111111111111111112", "decimals": 9 }, "fromToken": { "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "decimals": 6 }, "eurAmount": "100.00", "fromAddress": "7xKXtg2CWmS8cznVn9Yx8uN4m1s1P9HhQw", "network": "solana-mainnet" }, ) response.raise_for_status() print(response.json()['payload']) ``` ::: --- --- url: >- https://docs.brrr.network/api/card-api/offramp-crypto-to-card/card-solana-convert-token-to-eur.md description: Estimate a Solana offramp using a token-denominated amount. --- ## Code examples ::: code-group ```bash [curl] curl -X POST https://api.brrr.network/v5/offramp/crypto-to-card/solana-token-to-eur \ -H "X-Api-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "toToken": { "address": "So11111111111111111111111111111111111111112", "decimals": 9 }, "fromToken": { "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "decimals": 6 }, "tokenAmount": "1", "fromAddress": "7xKXtg2CWmS8cznVn9Yx8uN4m1s1P9HhQw", "network": "solana-mainnet" }' ``` ```javascript [JavaScript] const response = await fetch('https://api.brrr.network/v5/offramp/crypto-to-card/solana-token-to-eur', { method: 'POST', headers: { 'X-Api-Key': process.env.BRRR_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ 'toToken': { 'address': 'So11111111111111111111111111111111111111112', 'decimals': 9 }, 'fromToken': { 'address': 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', 'decimals': 6 }, 'tokenAmount': '1', 'fromAddress': '7xKXtg2CWmS8cznVn9Yx8uN4m1s1P9HhQw', 'network': 'solana-mainnet' }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Request failed: ${error.errorCode}`); } const data = await response.json(); console.log(data.payload); ``` ```python [Python] import os import httpx response = httpx.post( 'https://api.brrr.network/v5/offramp/crypto-to-card/solana-token-to-eur', headers={ 'X-Api-Key': os.environ['BRRR_API_KEY'], 'Content-Type': 'application/json', }, json={ "toToken": { "address": "So11111111111111111111111111111111111111112", "decimals": 9 }, "fromToken": { "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "decimals": 6 }, "tokenAmount": "1", "fromAddress": "7xKXtg2CWmS8cznVn9Yx8uN4m1s1P9HhQw", "network": "solana-mainnet" }, ) response.raise_for_status() print(response.json()['payload']) ``` ::: --- --- url: 'https://docs.brrr.network/docs/supported-networks.md' description: >- This page provides an overview of the blockchain networks we currently support. --- # Supported Networks This page provides an overview of the blockchain networks we currently support. ::: details What is a network? A **network** (or blockchain) is the infrastructure on which wallets, addresses, tokens, and smart contracts live. Each network has its own gas token and set of supported assets. The same token (e.g. USDC) can exist on many networks simultaneously. BRRR abstracts cross-network routing — you specify the token and network, and BRRR handles the rest. ::: ::: warning Please note. Please note. On networks marked USDC only DEX liquidity routing is not available. ::: * **✅** Supported * USDC only No token swapping * **❌** Not available ## EVM Networks | Network | Offramp | Onramp | Orchestration | x402 | Gasless Transactions | | --- | --- | --- | --- | --- | --- | | Ethereum | ✅ | ✅ | ✅ | ✅ | ✅ | | Arbitrum | ✅ | ✅ | ✅ | ✅ | ✅ | | Polygon | ✅ | ✅ | ✅ | ✅ | ✅ | | Base | ✅ | ✅ | ✅ | ✅ | ✅ | | Avalanche | ✅ | ✅ | ✅ | ✅ | ✅ | | Optimism | ✅ | ✅ | ✅ | ✅ | ✅ | | Gnosis | ✅ | ✅ | ✅ | ✅ | ✅ | | Polygon zkEVM | ✅ | ❌ | ✅ | ❌ | ❌ | | BNB Smart Chain | ✅ | ❌ | ✅ | ❌ | ❌ | | Berachain | ✅ | ❌ | ✅ | ❌ | ❌ | | Sonic | ✅ | ❌ | ✅ | ❌ | ❌ | | zkSync | ✅ | ❌ | ✅ | ❌ | ❌ | | Plasma | ✅ | ❌ | ✅ | ❌ | ❌ | | HyperEVM | ✅ | ❌ | ✅ | ❌ | ❌ | | Aleph Zero | USDC only | USDC only | ✅ | ❌ | ❌ | | Ink | USDC only | ❌ | ✅ | ❌ | ❌ | | Blast | USDC only | ❌ | ✅ | ❌ | ❌ | | Mode | USDC only | ❌ | ✅ | ❌ | ❌ | | Manta | USDC only | ❌ | ✅ | ❌ | ❌ | | Plume | USDC only | ❌ | ✅ | ❌ | ❌ | ## Non-EVM Networks | Network | Offramp | Onramp | Orchestration | x402 | Gasless Transactions | | --- | --- | --- | --- | --- | --- | | Solana | ✅ | ❌ | ✅ | ❌ | ❌ | | Starknet | ✅ | ❌ | ✅ | ❌ | ❌ | ## Choosing a Network **Low fees and fast settlement.** For most integrations, Arbitrum, Polygon, Base, or Avalanche offer the best balance of speed and cost. These L2s settle transactions in seconds and keep gas fees low — especially important when off-ramping smaller amounts where Ethereum mainnet fees would be disproportionate. **Maximum token liquidity.** Ethereum and Polygon have the deepest DEX liquidity pools, which means BRRR can route a wider range of input tokens to USDC before settlement. If your users hold less common ERC-20 tokens, these networks offer the best swap success rate. **USDC-native flows.** If your users already hold USDC, any supported network works — including those marked USDC only. On those networks the swap step is skipped entirely, reducing gas cost and transaction time. --- --- url: 'https://docs.brrr.network/docs/supported-regions.md' description: >- This page provides an overview of the countries where each BRRR feature is available. --- # Supported Regions This page provides an overview of the countries where each BRRR feature is available. ::: warning Off-ramp and on-ramp require customers to complete KYC through Holyheld before use. ::: * **✅** Available * **❌** Not available **A** | Country | Offramp | Onramp | Orchestration | x402 | Gas Sponsorship | AI Agent Banking | | --- | --- | --- | --- | --- | --- | --- | | 🇦🇫 Afghanistan | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | 🇦🇱 Albania | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇩🇿 Algeria | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇦🇩 Andorra | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇦🇴 Angola | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇦🇬 Antigua and Barbuda | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇦🇷 Argentina | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇦🇲 Armenia | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇦🇺 Australia | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇦🇹 Austria | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇦🇿 Azerbaijan | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | **B** | Country | Offramp | Onramp | Orchestration | x402 | Gas Sponsorship | AI Agent Banking | | --- | --- | --- | --- | --- | --- | --- | | 🇧🇸 Bahamas | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇧🇭 Bahrain | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇧🇩 Bangladesh | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇧🇧 Barbados | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇧🇾 Belarus | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | 🇧🇪 Belgium | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇧🇿 Belize | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇧🇯 Benin | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇧🇹 Bhutan | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇧🇴 Bolivia | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇧🇦 Bosnia and Herzegovina | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇧🇼 Botswana | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇧🇷 Brazil | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇧🇳 Brunei | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇧🇬 Bulgaria | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇧🇫 Burkina Faso | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇧🇮 Burundi | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **C** | Country | Offramp | Onramp | Orchestration | x402 | Gas Sponsorship | AI Agent Banking | | --- | --- | --- | --- | --- | --- | --- | | 🇰🇭 Cambodia | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇨🇲 Cameroon | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇨🇦 Canada | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇨🇻 Cape Verde | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇨🇫 Central African Republic | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | 🇹🇩 Chad | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇨🇱 Chile | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇨🇳 China | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇨🇴 Colombia | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇰🇲 Comoros | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇨🇷 Costa Rica | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇭🇷 Croatia | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇨🇺 Cuba | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇨🇾 Cyprus | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇨🇿 Czech Republic | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇨🇮 Côte d'Ivoire | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | **D** | Country | Offramp | Onramp | Orchestration | x402 | Gas Sponsorship | AI Agent Banking | | --- | --- | --- | --- | --- | --- | --- | | 🇨🇩 Dem. Rep. of the Congo | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | 🇩🇰 Denmark | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇩🇯 Djibouti | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇩🇲 Dominica | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇩🇴 Dominican Republic | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | **E** | Country | Offramp | Onramp | Orchestration | x402 | Gas Sponsorship | AI Agent Banking | | --- | --- | --- | --- | --- | --- | --- | | 🇪🇨 Ecuador | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇪🇬 Egypt | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇸🇻 El Salvador | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇬🇶 Equatorial Guinea | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇪🇷 Eritrea | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇪🇪 Estonia | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇸🇿 Eswatini | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇪🇹 Ethiopia | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | **F** | Country | Offramp | Onramp | Orchestration | x402 | Gas Sponsorship | AI Agent Banking | | --- | --- | --- | --- | --- | --- | --- | | 🇫🇯 Fiji | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇫🇮 Finland | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇫🇷 France | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | **G** | Country | Offramp | Onramp | Orchestration | x402 | Gas Sponsorship | AI Agent Banking | | --- | --- | --- | --- | --- | --- | --- | | 🇬🇦 Gabon | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇬🇲 Gambia | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇬🇪 Georgia | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇩🇪 Germany | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇬🇭 Ghana | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇬🇷 Greece | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇬🇩 Grenada | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇬🇹 Guatemala | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇬🇳 Guinea | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | 🇬🇼 Guinea-Bissau | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇬🇾 Guyana | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | **H** | Country | Offramp | Onramp | Orchestration | x402 | Gas Sponsorship | AI Agent Banking | | --- | --- | --- | --- | --- | --- | --- | | 🇭🇹 Haiti | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇭🇳 Honduras | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇭🇺 Hungary | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | **I** | Country | Offramp | Onramp | Orchestration | x402 | Gas Sponsorship | AI Agent Banking | | --- | --- | --- | --- | --- | --- | --- | | 🇮🇸 Iceland | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇮🇳 India | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇮🇩 Indonesia | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇮🇷 Iran | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | 🇮🇶 Iraq | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | 🇮🇪 Ireland | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇮🇱 Israel | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇮🇹 Italy | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | **J** | Country | Offramp | Onramp | Orchestration | x402 | Gas Sponsorship | AI Agent Banking | | --- | --- | --- | --- | --- | --- | --- | | 🇯🇲 Jamaica | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇯🇵 Japan | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇯🇴 Jordan | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | **K** | Country | Offramp | Onramp | Orchestration | x402 | Gas Sponsorship | AI Agent Banking | | --- | --- | --- | --- | --- | --- | --- | | 🇰🇿 Kazakhstan | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇰🇪 Kenya | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇰🇮 Kiribati | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇽🇰 Kosovo | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇰🇼 Kuwait | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇰🇬 Kyrgyzstan | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | **L** | Country | Offramp | Onramp | Orchestration | x402 | Gas Sponsorship | AI Agent Banking | | --- | --- | --- | --- | --- | --- | --- | | 🇱🇦 Laos | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇱🇻 Latvia | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇱🇧 Lebanon | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | 🇱🇸 Lesotho | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇱🇷 Liberia | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇱🇾 Libya | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | 🇱🇮 Liechtenstein | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇱🇹 Lithuania | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇱🇺 Luxembourg | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | **M** | Country | Offramp | Onramp | Orchestration | x402 | Gas Sponsorship | AI Agent Banking | | --- | --- | --- | --- | --- | --- | --- | | 🇲🇬 Madagascar | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇲🇼 Malawi | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇲🇾 Malaysia | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇲🇻 Maldives | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇲🇱 Mali | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | 🇲🇹 Malta | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇲🇭 Marshall Islands | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇲🇷 Mauritania | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇲🇺 Mauritius | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇲🇽 Mexico | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇫🇲 Micronesia | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇲🇩 Moldova | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | 🇲🇨 Monaco | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇲🇳 Mongolia | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇲🇪 Montenegro | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇲🇦 Morocco | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇲🇿 Mozambique | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇲🇲 Myanmar | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **N** | Country | Offramp | Onramp | Orchestration | x402 | Gas Sponsorship | AI Agent Banking | | --- | --- | --- | --- | --- | --- | --- | | 🇳🇦 Namibia | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇳🇷 Nauru | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇳🇵 Nepal | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇳🇱 Netherlands | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇳🇿 New Zealand | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇳🇮 Nicaragua | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | 🇳🇪 Niger | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇳🇬 Nigeria | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇰🇵 North Korea | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | 🇲🇰 North Macedonia | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇳🇴 Norway | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | **O** | Country | Offramp | Onramp | Orchestration | x402 | Gas Sponsorship | AI Agent Banking | | --- | --- | --- | --- | --- | --- | --- | | 🇴🇲 Oman | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | **P** | Country | Offramp | Onramp | Orchestration | x402 | Gas Sponsorship | AI Agent Banking | | --- | --- | --- | --- | --- | --- | --- | | 🇵🇰 Pakistan | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇵🇼 Palau | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇵🇸 Palestine | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇵🇦 Panama | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇵🇬 Papua New Guinea | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇵🇾 Paraguay | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇵🇪 Peru | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇵🇭 Philippines | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇵🇱 Poland | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇵🇹 Portugal | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | **Q** | Country | Offramp | Onramp | Orchestration | x402 | Gas Sponsorship | AI Agent Banking | | --- | --- | --- | --- | --- | --- | --- | | 🇶🇦 Qatar | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | **R** | Country | Offramp | Onramp | Orchestration | x402 | Gas Sponsorship | AI Agent Banking | | --- | --- | --- | --- | --- | --- | --- | | 🇨🇬 Republic of the Congo | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | 🇷🇴 Romania | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇷🇺 Russia | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | 🇷🇼 Rwanda | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | **S** | Country | Offramp | Onramp | Orchestration | x402 | Gas Sponsorship | AI Agent Banking | | --- | --- | --- | --- | --- | --- | --- | | 🇰🇳 Saint Kitts and Nevis | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇱🇨 Saint Lucia | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇻🇨 Saint Vincent and the Grenadines | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇼🇸 Samoa | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇸🇲 San Marino | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇸🇹 Sao Tome and Principe | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇸🇦 Saudi Arabia | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇸🇳 Senegal | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇷🇸 Serbia | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇸🇨 Seychelles | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇸🇱 Sierra Leone | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇸🇬 Singapore | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇸🇰 Slovakia | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇸🇮 Slovenia | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇸🇧 Solomon Islands | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇸🇴 Somalia | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | 🇿🇦 South Africa | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇸🇸 South Sudan | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | 🇪🇸 Spain | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇱🇰 Sri Lanka | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇸🇩 Sudan | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇸🇷 Suriname | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇸🇪 Sweden | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇨🇭 Switzerland | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 🇸🇾 Syria | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **T** | Country | Offramp | Onramp | Orchestration | x402 | Gas Sponsorship | AI Agent Banking | | --- | --- | --- | --- | --- | --- | --- | | 🇹🇼 Taiwan | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇹🇯 Tajikistan | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇹🇿 Tanzania | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇹🇭 Thailand | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇹🇱 Timor-Leste | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇹🇬 Togo | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇹🇴 Tonga | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇹🇹 Trinidad and Tobago | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇹🇳 Tunisia | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇹🇷 Turkey | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇹🇲 Turkmenistan | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇹🇻 Tuvalu | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | **U** | Country | Offramp | Onramp | Orchestration | x402 | Gas Sponsorship | AI Agent Banking | | --- | --- | --- | --- | --- | --- | --- | | 🇺🇬 Uganda | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇺🇦 Ukraine | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇦🇪 United Arab Emirates | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇬🇧 United Kingdom | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇺🇸 United States | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇺🇾 Uruguay | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇺🇿 Uzbekistan | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | **V** | Country | Offramp | Onramp | Orchestration | x402 | Gas Sponsorship | AI Agent Banking | | --- | --- | --- | --- | --- | --- | --- | | 🇻🇺 Vanuatu | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇻🇪 Venezuela | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | 🇻🇳 Vietnam | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | **Y** | Country | Offramp | Onramp | Orchestration | x402 | Gas Sponsorship | AI Agent Banking | | --- | --- | --- | --- | --- | --- | --- | | 🇾🇪 Yemen | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | **Z** | Country | Offramp | Onramp | Orchestration | x402 | Gas Sponsorship | AI Agent Banking | | --- | --- | --- | --- | --- | --- | --- | | 🇿🇲 Zambia | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | | 🇿🇼 Zimbabwe | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ## Choosing a Region **Off-ramp and on-ramp availability.** These features are currently available in 31 countries across the European Economic Area and Switzerland. Customers must complete KYC through Holyheld before they can use these features. **Orchestration, x402, Gas Sponsorship, and AI Agent Banking** are available globally in all supported countries. These features have no KYC requirement and can be used immediately after integration. **Restricted countries.** All features are unavailable in countries subject to international sanctions and compliance obligations. Attempts to onboard customers from restricted countries will be rejected at the KYC stage. --- --- url: 'https://docs.brrr.network/docs/testing.md' description: Test your BRRR SDK integration safely with the $SDKTEST holytag. --- # Testing ## SDK Testing The SDK provides a dedicated test $holytag, `$SDKTEST`, that lets you run the full off-ramp flow — including wallet signing, smart contract interactions, and on-chain transactions — without any real fiat movement. ::: warning Funds from test transactions are not retrievable Tokens sent to `$SDKTEST` cannot be recovered. Use small amounts (under $0.10) on low-fee networks. Do not send significant value to this tag. ::: ### The $SDKTEST holytag `$SDKTEST` is a special off-ramp recipient with the following properties: | Property | Behavior | |----------|----------| | Minimum amount | No minimum (unlike regular $holytags) | | Fiat movement | **None** — no EUR is credited anywhere | | On-chain transaction | Executed normally on the selected network | | Smart contract interaction | Same as production | | `onHashGenerate` callback | Fires as normal with the real transaction hash | Any call to `getTagInfo('SDKTEST')` returns `{ found: true }`, so your validation code works identically to production. ### Recommended test setup For the lowest gas cost, use one of these networks with USDC (6 decimals): | Network | Reason | |---------|--------| | Arbitrum | Low fees, fast confirmation, widely supported | | Polygon | Very low fees | | Avalanche | Low fees | | Solana | Near-zero fees | Avoid Ethereum mainnet for testing — gas costs are disproportionate for the tiny amounts involved. ### Step-by-step test checklist Follow these steps to validate your off-ramp integration end-to-end: **1. Initialize the SDK** ```typescript import HolyheldSDK from '@holyheld/sdk'; const holyheldSDK = new HolyheldSDK({ apiKey: process.env.HOLYHELD_SDK_API_KEY, // your real SDK API key logger: true, // enable logging during testing }); await holyheldSDK.init(); ``` **2. Confirm off-ramp is enabled** ```typescript const settings = await holyheldSDK.getServerSettings(); console.assert(settings.external.isTopupEnabled, 'Off-ramp must be enabled'); console.log('Min EUR:', settings.external.minTopUpAmountInEUR); console.log('Max EUR:', settings.external.maxTopUpAmountInEUR); ``` **3. Validate the test $holytag** ```typescript const tagInfo = await holyheldSDK.getTagInfo('SDKTEST'); console.assert(tagInfo.found === true, 'SDKTEST tag must be found'); ``` **4. Get a conversion quote** Use a small amount — less than $0.10 worth of the token: ```typescript import { Network } from '@holyheld/sdk'; const quote = await holyheldSDK.evm.offRamp.convertTokenToEUR({ walletAddress: '0xYOUR_WALLET_ADDRESS', tokenAddress: '0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC2', // USDC on Arbitrum tokenDecimals: 6, amount: '0.05', // 0.05 USDC ≈ ~$0.05 network: Network.arbitrum, }); console.log('Quote:', quote.EURAmount, 'EUR for', quote.tokenAmount, 'tokens'); ``` **5. Execute the off-ramp to $SDKTEST** ```typescript await holyheldSDK.evm.offRamp.topup({ publicClient, walletClient, walletAddress: '0xYOUR_WALLET_ADDRESS', tokenAddress: '0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC2', tokenNetwork: Network.arbitrum, tokenAmount: quote.tokenAmount, transferData: quote.transferData, holytag: 'SDKTEST', supportsSignTypedDataV4: true, eventConfig: { onStepChange: (step) => console.log('Step:', step), onHashGenerate: (hash) => console.log('✓ Transaction hash available:', hash), }, }); ``` **6. Verify `onHashGenerate` fires** The test is successful when `onHashGenerate` fires with a real transaction hash and the `topup` call resolves successfully. ### Testing on-ramp On-ramp testing requires a real Holyheld mobile app connected to your wallet. The test flow is the same as production — `requestOnRamp` creates a request, and the user confirms it in the app. There is no simulated confirmation available; you must use a real device. Use the SDK API key from your development environment and small EUR amounts for on-ramp testing. --- --- url: 'https://docs.brrr.network/docs/agentic/topup.md' description: >- Request a card top-up by transferring funds from the Holyheld main account to the card. POST /topup-request endpoint reference with async polling pattern. --- # Top Up Card Create a top-up request that transfers funds from the user's Holyheld main account to the Holyheld card. The request is accepted synchronously, but the balance update is **asynchronous** — it may take up to 5 minutes to appear on the card. ## Endpoint ``` POST https://apicore.holyheld.com/v4/ai-agents/topup-request ``` ## Request | Header | Required | Value | |--------|----------|-------| | `Authorization` | ✅ | `Bearer ` | | `Content-Type` | ✅ | `application/json` | **Request body:** ```json { "amount": "50.00" } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `amount` | `string` | ✅ | Amount in EUR to top up. Must be a decimal string matching `^\d+(\.\d{1,2})?$` | ### Amount format rules The `amount` field must be a **string** (not a number) and must match the pattern `^\d+(\.\d{1,2})?$`: | Value | Valid? | Note | |-------|--------|------| | `"50"` | ✅ | Integer amounts are valid | | `"50.00"` | ✅ | Two decimal places | | `"50.5"` | ✅ | One decimal place | | `"50.123"` | ❌ | More than 2 decimal places | | `50.00` | ❌ | Number type — must be a string | | `"€50"` | ❌ | No currency symbol | | `"-10"` | ❌ | No negative values | Use `amount.toFixed(2)` in JavaScript to format a number as a valid amount string. ## Response **200 OK — top-up request accepted** ```json { "status": "ok" } ``` A `200` response means the request was accepted and the top-up process has begun. It does **not** mean the balance has already updated. Poll `GET /balance` to confirm the settlement. ::: warning Balance updates are asynchronous After receiving a `200`, the card balance may take **up to 5 minutes** to reflect the top-up. Do not assume the top-up is reflected immediately. Always poll to confirm before reporting success to the user. ::: ## The polling pattern After a successful `POST /topup-request`, poll `GET /balance` until the balance increases or a timeout is reached. ```javascript const BASE_URL = 'https://apicore.holyheld.com/v4/ai-agents'; const POLL_INTERVAL_MS = 30_000; // 30 seconds const TIMEOUT_MS = 5 * 60_000; // 5 minutes async function topUpAndConfirm(amount, token) { const headers = { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }; // 1. Record balance before top-up const beforeRes = await fetch(`${BASE_URL}/balance`, { headers }); const before = await beforeRes.json(); const balanceBefore = parseFloat(before.payload.balance); // 2. Request the top-up const topupRes = await fetch(`${BASE_URL}/topup-request`, { method: 'POST', headers, body: JSON.stringify({ amount: parseFloat(amount).toFixed(2) }), }); if (!topupRes.ok) { const err = await topupRes.json(); throw new Error(`Top-up failed: ${err.errorCode} — ${err.error}`); } // 3. Poll until balance increases or timeout const deadline = Date.now() + TIMEOUT_MS; while (Date.now() < deadline) { await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS)); const currentRes = await fetch(`${BASE_URL}/balance`, { headers }); const current = await currentRes.json(); const balanceNow = parseFloat(current.payload.balance); if (balanceNow > balanceBefore) { return { success: true, previousBalance: balanceBefore, newBalance: balanceNow, added: balanceNow - balanceBefore, }; } } // Timeout — top-up may still complete; advise the user to check later throw new Error( 'Top-up accepted but balance has not updated within 5 minutes. ' + 'Check your Holyheld card balance in a few minutes.' ); } ``` ::: code-group ```bash [curl] curl -X POST https://apicore.holyheld.com/v4/ai-agents/topup-request \ -H "Authorization: Bearer $HOLYHELD_AGENT_TOKEN" \ -H "Content-Type: application/json" \ -d '{"amount": "50.00"}' ``` ```python [Python] import httpx, time, os BASE_URL = 'https://apicore.holyheld.com/v4/ai-agents' def topup_and_confirm(amount: float, token: str) -> dict: headers = { 'Authorization': f'Bearer {token}', 'Content-Type': 'application/json', } # 1. Record balance before before = httpx.get(f'{BASE_URL}/balance', headers=headers) balance_before = float(before.json()['payload']['balance']) # 2. Request top-up res = httpx.post( f'{BASE_URL}/topup-request', headers=headers, json={'amount': f'{amount:.2f}'}, ) if res.status_code != 200: err = res.json() raise RuntimeError(f"{err['errorCode']}: {err['error']}") # 3. Poll deadline = time.time() + 300 # 5 minutes while time.time() < deadline: time.sleep(30) current = httpx.get(f'{BASE_URL}/balance', headers=headers) balance_now = float(current.json()['payload']['balance']) if balance_now > balance_before: return {'new_balance': balance_now, 'added': balance_now - balance_before} raise TimeoutError('Top-up accepted but balance not updated within 5 minutes.') ``` ::: ## Error responses **400 Bad Request — malformed amount** ```json { "status": "error", "errorCode": "WRONG_REQUEST", "error": "Failed to parse request body" } ``` Check the `amount` format. See [Amount format rules](#amount-format-rules) above. **500 — Insufficient balance** ```json { "status": "error", "errorCode": "AI_TOPUP_INSUFFICIENT_BALANCE", "error": "Insufficient balance for top up" } ``` The user does not have enough available balance on their Holyheld main account. The agent cannot resolve this autonomously — notify the user that they need to add funds to their Holyheld account. **500 — Spending limit exceeded** ```json { "status": "error", "errorCode": "AI_TOPUP_LIMIT_EXCEEDED", "error": "Limit exceeded" } ``` The agent's cumulative spending limit has been reached. **Only the user can reset this limit** from the Holyheld dashboard. Do not retry — further attempts will continue to fail until the user resets the limit. Notify the user immediately. **500 / default — Internal server error** ```json { "status": "error", "errorCode": "INTERNAL_SERVER_ERROR", "error": "Internal Server Error" } ``` An unexpected server-side error occurred. Retry with exponential backoff (1s → 2s → 4s). If the error persists after 3 attempts, surface a generic failure message to the user. **Error handling decision tree:** ``` POST /topup-request │ ├── 200 OK ──────────────────► Poll GET /balance (up to 5 min) │ │ │ balance increased? │ ├── Yes ──► Report success │ └── No ──► Report timeout; advise manual check │ ├── 400 WRONG_REQUEST ────────► Fix amount format and retry immediately │ ├── 401/403 AI_AUTHORIZATION_INVALID ──► Check Bearer token or get new agent instructions │ ├── 500 AI_TOPUP_INSUFFICIENT_BALANCE ──► Notify user: add funds to Holyheld account │ Do NOT retry — user action required │ ├── 500 AI_TOPUP_LIMIT_EXCEEDED ────────► Notify user: reset limit in dashboard │ Do NOT retry — user action required │ └── 500 INTERNAL_SERVER_ERROR ──────────► Retry with exponential backoff (max 3×) ``` See [Error Reference](/docs/agentic/errors) for full recovery guidance. ::: tip Next steps Read the [Error Reference](/docs/agentic/errors) for handling every error code. Then see [Build an MCP Server](/docs/agentic/mcp-guide) to wrap these endpoints as Claude tools. ::: --- --- url: 'https://docs.brrr.network/sdk/references/off-ramp/topup.md' description: >- Execute an off-ramp by sending tokens from a user's wallet to a $holytag — the final step of the off-ramp flow. --- # topup This is the primary method for executing an off-ramp to the user's Holyheld card. If you already requested a quote, reuse its `transferData` unchanged. If you did not request a quote, the SDK can fetch the current conversion and routing data internally. :::warning 🚨 Please note! Some wallets like Metamask are single-network handled. It means that while Holyheld can return/accept transaction on any supported network, user **MUST** switch to the correct network in the wallet for the transaction to be processed. ::: You can call `topup` with or without a pre-fetched quote. If you already obtained a quote from `convertTokenToEUR` or `convertEURToToken`, reuse its `transferData` unchanged. ## Usage ::: code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; import * as chains from 'viem/chains'; import { createPublicClient, createWalletClient, custom, http } from 'viem'; const provider; // current provider in your app (see examples below) const transferData; // transfer data from conversion methods or undefined const eventConfig; // callbacks const chainId; // token chain id const chain = Object.values(chains).find((item) => item.id === chainId); // get chain entity from viem const publicClient = createPublicClient({ // create viem public client - https://viem.sh/docs/clients/public.html chain, transport: http(), }); const walletClient = createWalletClient({ // wrap your provider in viem wallet client - https://viem.sh/docs/clients/wallet.html chain, transport: custom(provider), // current provider in your app (see examples below) account: '0x...', // wallet address }); // off-ramp to user's Holyheld card by $holytag await holyheldSDK.evm.offRamp.topup({ publicClient: publicClient, walletClient: walletClient, walletAddress: '0x...', tokenAddress: '0x...', tokenNetwork: Network.ethereum, tokenAmount: '5.25', transferData: transferData, // if was provided by 'convertTokenToEUR' and/or 'convertEURToToken' holytag: 'SDKTEST', // funds recipient tag supportsSignTypedDataV4: true, // true if connected wallet supports eth_signTypedData_v4 (default: false) supportsRawTransactionsSigning: true, // true if connected wallet supports raw transactions signing via "eth_sign" or "eth_signTransaction" (default: false) eventConfig: eventConfig, // callbacks (see below) }); ``` ```typescript [Solana] import { Connection, clusterApiUrl } from '@solana/web3.js'; import { PhantomWalletAdapter } from '@solana/wallet-adapter-phantom'; import { SolanaNetwork, createSolanaWalletClientFromAdapter } from '@holyheld/sdk'; const transferData; // transfer data from conversion methods or undefined const eventConfig; // callbacks const networkInfo = holyheldSDK.solana.getNetwork(SolanaNetwork.Mainnet); const connection = new Connection(networkInfo.httpRpcURL, { commitment: 'confirmed', wsEndpoint: networkInfo.wsRpcURL ?? clusterApiUrl(networkInfo.cluster, true) }); const walletAdapter = new PhantomWalletAdapter(); const walletClient = createSolanaWalletClientFromAdapter(walletAdapter, connection); // off-ramp to user's Holyheld card by $holytag await holyheldSDK.solana.offRamp.topup({ connection: connection, walletClient: walletClient, walletAddress: '...', tokenAddress: '...', tokenNetwork: SolanaNetwork.Mainnet, tokenAmount: '5.25', transferData: transferData, // if was provided by 'convertTokenToEUR' and/or 'convertEURToToken' holytag: 'SDKTEST', // funds recipient tag eventConfig: {}, // callbacks (see below) }); ``` ::: ## Parameters ::: code-group ```typescript [EVM] holyheldSDK.evm.offRamp.topup({ publicClient, // PublicClient walletClient, // WalletClient walletAddress, // string tokenAddress, // string tokenNetwork, // Network tokenAmount, // string transferData, // TransferDataEVM | undefined holytag, // string supportsSignTypedDataV4, // boolean (default: false) supportsRawTransactionsSigning, // boolean (default: false) eventConfig, // TopUpCallbackConfig | undefined }); ``` ```typescript [Solana] holyheldSDK.solana.offRamp.topup({ connection, // Connection walletClient, // SolanaWalletClient walletAddress, // string tokenAddress, // string tokenNetwork, // SolanaNetwork tokenAmount, // string transferData, // TransferDataSolana | undefined holytag, // string eventConfig, // TopUpCallbackConfig | undefined }); ``` ::: | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `publicClient` | `PublicClient` | Yes (EVM only) | Viem public client configured for the token's chain. Used to read on-chain state. | | `walletClient` | `WalletClient` / `SolanaWalletClient` | Yes | Viem wallet client (EVM) or Solana wallet client wrapping the user's provider. Used to request signatures and send transactions. | | `connection` | `Connection` | Yes (Solana only) | Solana `Connection` instance for the target cluster. | | `walletAddress` | `string` | Yes | The EVM or Solana wallet address initiating the transaction. Must match the account in `walletClient`. | | `tokenAddress` | `string` | Yes | Contract address of the token to send. | | `tokenNetwork` | `Network` | `SolanaNetwork` | Yes | The network enum value for the token's chain. Must match the network of `tokenAddress`. | | `tokenAmount` | `string` | Yes | Amount of tokens to send, as a decimal string (e.g. `"5.25"` for 5.25 USDC). Use the value returned by `convertTokenToEUR` or `convertEURToToken`. | | `transferData` | `TransferDataEVM` | `TransferDataSolana` | `undefined` | No | Routing data from `convertTokenToEUR` or `convertEURToToken`. Pass `undefined` to use the current market rate without a pre-fetched quote. | | `holytag` | `string` | Yes | The recipient's Holyheld tag (without the `$` prefix). Funds are credited to this account. | | `supportsSignTypedDataV4` | `boolean` | No | Set `true` if the connected wallet supports `eth_signTypedData_v4`. Enables permit-based token approvals, avoiding a separate approval transaction. Default: `false`. | | `supportsRawTransactionsSigning` | `boolean` | No | Set `true` if the wallet supports raw transaction signing via `eth_sign` or `eth_signTransaction`. Default: `false`. | | `eventConfig` | `TopUpCallbackConfig` | No | Callback hooks for tracking transaction progress. See [Types](#types) below. | ## Returns ```typescript Promise ``` `topup` waits until the transaction has been included in a block before resolving. For both EVM and Solana, the method returns the transaction hash. The `onHashGenerate` callback also emits the hash as soon as it becomes available, so your UI can display it immediately. ## Types ::: code-group ```typescript enum TopUpStep { Confirming = 'confirming', // user is confirming action in wallet Approving = 'approving', // wallet is signing an approval or permit Sending = 'sending', // wallet is signing the final send transaction } interface TopUpCallbackConfig { /** Called once the transaction hash is available. */ onHashGenerate?: (hash: string) => void; /** Called each time the flow advances to a new step. */ onStepChange?: (step: TopUpStep) => void; } ``` ::: `TopUpStep` is a single shared enum exported by the SDK. Some flows may not use every step at runtime. --- --- url: 'https://docs.brrr.network/sdk/references/off-ramp/topup-self.md' description: >- Execute an off-ramp to the connected wallet's own Holyheld card — no $holytag required. --- # topupSelf This is the primary method for executing an off-ramp to the user's Holyheld card without specifying a $holytag — the Holyheld card is identified automatically from the connected wallet address. If you already requested a quote, reuse its `transferData` unchanged. If you did not request a quote, the SDK can fetch the current conversion and routing data internally. :::warning 🚨 Please note! Some wallets like Metamask are single-network handled. It means that while Holyheld can return/accept transaction on any supported network, user **MUST** switch to the correct network in the wallet for the transaction to be processed. ::: Use `topupSelf` instead of `topup` when the user is off-ramping to their own Holyheld card and you do not need to specify a $holytag. If you already obtained a conversion quote, reuse its `transferData` unchanged. ## Usage ::: code-group ```typescript [EVM] import { Network } from '@holyheld/sdk'; import * as chains from 'viem/chains'; import { createPublicClient, createWalletClient, custom, http } from 'viem'; const provider; // current provider in your app (see examples below) const transferData; // transfer data from conversion methods or undefined const eventConfig; // callbacks const chainId; // token chain id const chain = Object.values(chains).find((item) => item.id === chainId); // get chain entity from viem const publicClient = createPublicClient({ // create viem public client - https://viem.sh/docs/clients/public.html chain, transport: http(), }); const walletClient = createWalletClient({ // wrap your provider in viem wallet client - https://viem.sh/docs/clients/wallet.html chain, transport: custom(provider), // current provider in your app (see examples below) account: '0x...', // wallet address }); // off-ramp to the Holyheld card of the connected wallet await holyheldSDK.evm.offRamp.topupSelf({ publicClient: publicClient, walletClient: walletClient, walletAddress: '0x...', tokenAddress: '0x...', tokenNetwork: Network.ethereum, tokenAmount: '5.25', transferData: transferData, // if was provided by 'convertTokenToEUR' and/or 'convertEURToToken' supportsSignTypedDataV4: true, // true if connected wallet supports eth_signTypedData_v4 (default: false) supportsRawTransactionsSigning: true, // true if connected wallet supports raw transactions signing via "eth_sign" or "eth_signTransaction" (default: false) eventConfig: eventConfig, // callbacks (see below) }); ``` ```typescript [Solana] import { Connection, clusterApiUrl } from '@solana/web3.js'; import { PhantomWalletAdapter } from '@solana/wallet-adapter-phantom'; import { SolanaNetwork, createSolanaWalletClientFromAdapter } from '@holyheld/sdk'; const transferData; // transfer data from conversion methods or undefined const eventConfig; // callbacks const networkInfo = holyheldSDK.solana.getNetwork(SolanaNetwork.Mainnet); const connection = new Connection(networkInfo.httpRpcURL, { commitment: 'confirmed', wsEndpoint: networkInfo.wsRpcURL ?? clusterApiUrl(networkInfo.cluster, true) }); const walletAdapter = new PhantomWalletAdapter(); const walletClient = createSolanaWalletClientFromAdapter(walletAdapter, connection); // off-ramp to the Holyheld card of the connected wallet await holyheldSDK.solana.offRamp.topupSelf({ connection: connection, walletClient: walletClient, walletAddress: '...', tokenAddress: '...', tokenNetwork: SolanaNetwork.Mainnet, tokenAmount: '5.25', transferData: transferData, // if was provided by 'convertTokenToEUR' and/or 'convertEURToToken' eventConfig: {}, // callbacks (see below) }); ``` ::: ## Parameters `topupSelf` accepts the same parameters as [`topup`](/sdk/references/off-ramp/topup), except `holytag` is omitted — funds are credited to the Holyheld card associated with the connected wallet address. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `publicClient` | `PublicClient` | Yes (EVM only) | Viem public client configured for the token's chain. | | `walletClient` | `WalletClient` / `SolanaWalletClient` | Yes | Wallet client wrapping the user's provider. | | `connection` | `Connection` | Yes (Solana only) | Solana `Connection` instance for the target cluster. | | `walletAddress` | `string` | Yes | The wallet address initiating the transaction. The Holyheld card linked to this address receives the funds. | | `tokenAddress` | `string` | Yes | Contract address of the token to send. | | `tokenNetwork` | `Network` | `SolanaNetwork` | Yes | The network enum value for the token's chain. | | `tokenAmount` | `string` | Yes | Amount of tokens to send as a decimal string (e.g. `"5.25"`). | | `transferData` | `TransferDataEVM` | `TransferDataSolana` | `undefined` | No | Routing data from `convertTokenToEUR` or `convertEURToToken`. | | `supportsSignTypedDataV4` | `boolean` | No | Set `true` if the wallet supports `eth_signTypedData_v4`. Enables permit-based approvals. Default: `false`. | | `supportsRawTransactionsSigning` | `boolean` | No | Set `true` if the wallet supports raw transaction signing. Default: `false`. | | `eventConfig` | `TopUpCallbackConfig` | No | Callback hooks for tracking transaction progress. | ## Returns ```typescript Promise ``` `topupSelf` waits until the transaction has been included in a block before resolving. For both EVM and Solana, the method returns the transaction hash. The `onHashGenerate` callback also emits the hash as soon as it becomes available, so your UI can display it immediately. ## Types ::: code-group ```typescript enum TopUpStep { Confirming = 'confirming', // user is confirming action in wallet Approving = 'approving', // wallet is signing an approval or permit Sending = 'sending', // wallet is signing the final send transaction } interface TopUpCallbackConfig { /** Called once the transaction hash is available. */ onHashGenerate?: (hash: string) => void; /** Called each time the flow advances to a new step. */ onStepChange?: (step: TopUpStep) => void; } ``` ::: `TopUpStep` is a single shared enum exported by the SDK. Some flows may not use every step at runtime. --- --- url: 'https://docs.brrr.network/sdk/references/types.md' description: >- Complete reference for all exported TypeScript types, interfaces, and enums in the Holyheld SDK. --- # TypeScript Types Key exported types, interfaces, and enums from `@holyheld/sdk`. Import them directly: ```typescript import { Network, SolanaNetwork, HolyheldSDKErrorCode, TopUpStep, } from '@holyheld/sdk'; import type { TopUpCallbackConfig, ServerExternalSettings, } from '@holyheld/sdk'; ``` ## Enums ### `Network` EVM-compatible blockchain networks supported by the SDK. ```typescript enum Network { ethereum = 'ethereum', polygon = 'polygon', avalanche = 'avalanche', arbitrum = 'arbitrum', optimism = 'optimism', gnosis = 'gnosis', zkevm = 'zkevm', base = 'base', zksyncera = 'zksyncera', blast = 'blast', mode = 'mode', bsc = 'bsc', manta = 'manta', alephzero = 'alephzero', ink = 'ink', sonic = 'sonic', hyperEVM = 'hyperevm', berachain = 'berachain', plume = 'plume', plasma = 'plasma' } ``` Use `Network` values for the `tokenNetwork` parameter in all EVM off-ramp and on-ramp methods. ### `SolanaNetwork` Solana networks supported by the SDK. ```typescript enum SolanaNetwork { Mainnet = 'solana-mainnet' } ``` ### `HolyheldSDKErrorCode` Error codes thrown by SDK methods. Catch and inspect `error.code` to handle specific cases. ```typescript enum HolyheldSDKErrorCode { NotInitialized = 'HSDK_NI', FailedInitialization = 'HSDK_FI', UnsupportedNetwork = 'HSDK_UN', InvalidTopUpAmount = 'HSDK_ITUA', InvalidOnRampAmount = 'HSDK_IORA', UnexpectedWalletNetwork = 'HSDK_UWN', UserRejectedSignature = 'HSDK_RS', UserRejectedTransaction = 'HSDK_RT', FailedSettings = 'HSDK_FS', FailedTagInfo = 'HSDK_FTI', FailedAddressInfo = 'HSDK_FAI', FailedWalletBalances = 'HSDK_FWB', FailedEstimation = 'HSDK_FE', FailedConversion = 'HSDK_FC', FailedTopUp = 'HSDK_FTU', FailedCreateOnRampRequest = 'HSDK_FCOR', FailedOnRampRequest = 'HSDK_FOR', FailedWatchOnRampRequestTimeout = 'HSDK_FwORT', FailedWatchOnRampRequest = 'HSDK_FWORR', FailedConvertOnRampAmount = 'HSDK_FCORA', FailedOnRampEstimation = 'HSDK_FORE', } ``` ### `TopUpStep` Steps emitted via `onStepChange` during a `topup` or `topupSelf` call. ```typescript enum TopUpStep { Confirming = 'confirming', // Waiting for the user to confirm in wallet Approving = 'approving', // Waiting for token approval or permit signature Sending = 'sending', // Waiting for the final send transaction signature } ``` `TopUpStep` is a single shared enum exported by the SDK. Some flows may not use every step at runtime. ## Interfaces ### `TopUpCallbackConfig` Callback hooks passed to `topup` and `topupSelf` via the `eventConfig` parameter. ```typescript interface TopUpCallbackConfig { /** * Called once when the transaction hash becomes available. * Use this to display or store the transaction hash. */ onHashGenerate?: (hash: string) => void; /** * Called each time the flow advances to a new TopUpStep. * Use this to update your UI (e.g. spinner text, progress indicator). */ onStepChange?: (step: TopUpStep) => void; } ``` ### `ServerExternalSettings` Return type of [`getServerSettings`](/sdk/references/common/get-server-settings). ```typescript type ServerExternalSettings = { external: { /** Whether off-ramp (topup) is currently available. */ isTopupEnabled: boolean; /** Whether on-ramp is currently available. */ isOnRampEnabled: boolean; /** Maximum EUR equivalent allowed per off-ramp transaction (as string). */ maxTopUpAmountInEUR: string; /** Minimum EUR equivalent allowed per off-ramp transaction (as string). */ minTopUpAmountInEUR: string; /** Maximum EUR amount allowed per on-ramp transaction (as string). */ maxOnRampAmountInEUR: string; /** Minimum EUR amount allowed per on-ramp transaction (as string). */ minOnRampAmountInEUR: string; }; common: { /** Off-ramp fee as a percentage string (e.g. "0.75" means 0.75%). */ topUpFeePercent: string; }; }; ``` ### `ConvertTopUpData` Return type of [`convertTokenToEUR`](/sdk/references/off-ramp/convert-token-to-eur) and [`convertEURToToken`](/sdk/references/off-ramp/convert-eur-to-token). ::: code-group ```typescript [EVM] type ConvertTopUpDataEVM = { /** Token amount involved in the conversion, as a decimal string. */ tokenAmount: string; /** EUR equivalent of the token amount, as a decimal string. */ EURAmount: string; /** * Token-specific routing data for the off-ramp transaction. * Pass this directly to topup/topupSelf as the transferData parameter. * Do not modify or cache across sessions. */ transferData?: TransferDataEVM; }; ``` ```typescript [Solana] type ConvertTopUpDataSolana = { tokenAmount: string; EURAmount: string; transferData?: TransferDataSolana; }; ``` ::: ### `RequestOnRampEVMResult` Return type of [`requestOnRamp`](/sdk/references/on-ramp/request-on-ramp). ```typescript type RequestOnRampEVMResult = { /** Unique ID of the on-ramp request. Pass to watchRequestId. */ requestUid: string; /** Chain ID of the network where tokens will arrive. */ chainId: number; /** Token metadata for the token being purchased. */ token: TokenEVM; /** EUR amount charged from the user's Holyheld balance, as a decimal string. */ amountEUR: string; /** Expected token amount to be delivered to the wallet, as a decimal string. */ amountToken: string; /** Network gas fee deducted from the transaction, denominated in EUR. */ feeEUR: string; /** Destination wallet address where tokens will arrive. */ beneficiaryAddress: EVMAddress; }; ``` ### `WatchOnRampResult` Return type of [`watchRequestId`](/sdk/references/on-ramp/watch-request-id). ```typescript type WatchOnRampResult = { /** true if the user approved and the transaction was submitted; false if declined or expired. */ success: boolean; /** * On-chain transaction hash. Present only when success is true * and waitForTransactionHash was set to true in the options. */ hash?: string; }; ``` ### `WatchOnRampRequestIdOptions` Options passed to [`watchRequestId`](/sdk/references/on-ramp/watch-request-id). ```typescript type WatchOnRampRequestIdOptions = { /** * Polling timeout in milliseconds. If the request is not resolved * within this window, watchRequestId throws a FailedWatchOnRampRequestTimeout error. * Default: SDK internal default (recommended: 180000 for a 3-minute window). */ timeout?: number; /** * If true, watchRequestId continues polling until the on-chain transaction * hash is available, not just until user confirmation. * Default: false. */ waitForTransactionHash?: boolean; }; ``` --- --- url: 'https://docs.brrr.network/sdk/references/common/validate-address.md' description: >- Check whether a wallet address is eligible for off-ramp (topupSelf) or on-ramp — call this before submitting a transaction. --- # `validateAddress` User wallet address is a unique identifier which can have account, card and a $holytag bound to it. It is alphanumeric string. Wallet address must be a valid EVM (0x..., 42 chars) or Solana (Base58, ~32–44 chars) address. :::info 🔔 **Please note! Ethereum Name Service (ENS) and Solana Name Service (SNS) domains are not supported.** ::: ## Usage ::: code-group ```typescript [EVM] const evmAddressData = await holyheldSDK.validateAddress('0x000000000000000000000000000000000000dEaD'); ``` ```typescript [Solana] const solanaAddressData = await holyheldSDK.validateAddress('14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjBRRR'); ``` ::: ## Parameters ### Address Wallet address * **Type:** `string` ::: code-group ```typescript [EVM] const evmAddressData = await holyheldSDK.validateAddress( '0x000000000000000000000000000000000000dEaD' // [!code highlight] ); ``` ```typescript [Solana] const solanaAddressData = await holyheldSDK.validateAddress( '14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjBRRR' // [!code highlight] ); ``` ::: ## Returns ::: code-group ```typescript [types.ts] type ValidateAddressResult = { isTopupAllowed: boolean; isOnRampAllowed: boolean; } ``` ::: ### isTopupAllowed Indicates if off-ramp is available at the moment for `Address` * **Type:** `Boolean` ::: code-group ```typescript [types.ts] type ValidateAddressResult = { isTopupAllowed: boolean; // [!code highlight] isOnRampAllowed: boolean; } ``` ::: ### isOnRampAllowed Indicates if on-ramp is available at the moment for `Address` * **Type:** `Boolean` ::: code-group ```typescript [types.ts] type ValidateAddressResult = { isTopupAllowed: boolean; isOnRampAllowed: boolean; // [!code highlight] } ``` ::: --- --- url: 'https://docs.brrr.network/docs/validation.md' description: What is it? And how to participate. --- # Validation The BRRR protocol adopts a decentralized validator model, wherein any holder of the native BRRR token may elect to participate in system operations as a Validator. This validator eligibility is inherently permissionless, facilitating an open ecosystem that incentivizes participation through reward driven mechanisms and enforces reliability through cryptoeconomic penalties. To assume the role of a validator, a BRRR token holder must engage in staking, which entails the locking of a predetermined quantity of BRRR tokens into a designated Security Deposit (SD) smart contract. This process binds the validator’s operational commitments to a tangible economic stake, aligning incentives with the integrity of the protocol. ## Omnibus Staking Availability Staking is supported across all BRRR-enabled networks, allowing validators to commit collateral on the network most aligned with their infrastructure. This is achieved via the protocol’s chain-agnostic architecture and reconciled on the Accounting Chain, maintaining a unified ledger state for validator eligibility. ## Unstaking and Withdrawal Delay To mitigate the risk of malicious behavior and support protocol-level finality guarantees, the protocol enforces a 7-day unbonding period following any unstaking request. This slashing window allows pending obligations to be verified before funds are released. BRRR unstaking parameters: * Unstaking time for validator, `i`: `U_i` * Withdrawal delay in days: `\tau_s = 7` Then a temporal security measure or a withdrawal becomes available at: `W_i = U_i + \tau_s` ## Emission Rewards for Validators Validators are incentivized through a dynamic emission schedule. Each processed transaction earns rewards according to system activity and individual contribution. BRRR validator rewards: * Cumulative transactions by month, `t`: `T(t)` * Target transactions: `T_{tr}` * Emission lifespan: `L` * Maximum BRRR token supply: `S_{max}` * Allocated portion of `S_{max}`: `P_s` * Monthly decay rate: `D` * BRRR balance held by a validator: `B_i` * Monthly rewards to participant, `i`: `R_i(t)` * Total monthly emission rewards: `R_{total}(t)` To estimate monthly emission target: `R_{\text{total}}(t) = S_{\text{max}} \cdot P_s \cdot (0.944)^{t-1}` Adjusting for actual transaction activity: `f(t) = \frac{T(t)}{T_{tr} \cdot \frac{t}{L}}` `R_{\text{actual}}(t) = R_{\text{total}}(t) \cdot \min(1, f(t))` Estimating share of the emission reward per validator: `s_i(t) = \frac{B_i}{B_{\text{total}}(t)}` `R_i(t) = s_i(t) \cdot R_{\text{actual}}(t)` APR calculations: `\text{APR}_i(t) = 12 \cdot \frac{R_i(t)}{B_i} \cdot 100\%` With monthly compounding: `\text{APR}_i(t) = \left( \left(1 + \frac{R_i(t)}{B_i} \right)^{12} - 1 \right) \cdot 100\%` Estimation of the emissions rewards for the validators: `\text{APR}_i(t) = \left( \left(1 + \frac{s_i(t) \cdot R_{\text{total}}(t) \cdot \min(1, f(t))}{B_i} \right)^{12} - 1 \right) \cdot 100\%` ## Slashing and Penalty Enforcement Validators may be penalized via slashing for unfulfilled obligations, such as delayed reconciliation or failed guarantees. BRRR slashing parameters: * Staked deposit: `SD_i` * Slashing severity coefficient: `\sigma \in [0, 1]` These penalties are enforced through automated or dispute-driven mechanisms as outlined in the protocol documentation. `S_i = \sigma \cdot SD_i` --- --- url: 'https://docs.brrr.network/sdk/references/on-ramp/watch-request-id.md' description: >- Poll for the outcome of a pending on-ramp request — resolves when the user approves, declines, or the timeout expires. --- # `watchRequestId` Watch the on-ramp request by ID. This method is used to await for the request outcome based on the user confirmation or rejection in the Holyheld app. There are only three possible outcomes of any request: 1. `{ success: true }` or `{ success: true, hash: '0x...' }` if the request has been confirmed by the user and processed 2. `{ success: false }` if the request has been declined by the user 3. An error if request was not processed, timed out, or HTTP request was not returned as `OK` Call `watchRequestId` immediately after `requestOnRamp` returns. Prompt the user to open the Holyheld app; they have 3 minutes to confirm. If the promise rejects with a timeout error, the request may still be pending — retain the `requestUid` and consider retrying `watchRequestId`. ## Usage ::: code-group ```typescript [EVM] const data = await holyheldSDK.evm.onRamp.watchRequestId( requestUid, options ); ``` ::: ## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `requestUid` | `string` | Yes | The request ID returned by [`requestOnRamp`](/sdk/references/on-ramp/request-on-ramp). | | `options.timeout` | `number` | No | Maximum time in milliseconds to wait for user confirmation. Defaults to the SDK's internal timeout. Recommended: `180_000` (3 minutes). | | `options.waitForTransactionHash` | `boolean` | No | If `true`, the promise waits until the on-chain transaction hash is available before resolving. Default: `false`. | ### requestUid ID of the on-ramp request created by [`requestOnRamp`](/sdk/references/on-ramp/request-on-ramp) * **Type:** `String` ::: code-group ```typescript [EVM] const data = await holyheldSDK.evm.onRamp.watchRequestId( requestUid, // [!code highlight] options // optional ); ``` ::: ### options (optional) * **Type:** `WatchOnRampRequestIdOptions` ::: code-group ```typescript [EVM] const data = await holyheldSDK.evm.onRamp.watchRequestId( requestUid, options // [!code highlight] ); ``` ```typescript [types.ts] type WatchOnRampRequestIdOptions = { timeout?: number; waitForTransactionHash?: boolean; }; ``` ::: ## Returns ::: code-group ```typescript [types.ts] type WatchOnRampResult = { success: boolean; hash?: string; } ``` ::: ### success Successful or not * **Type:** `Boolean` ::: code-group ```typescript [types.ts] type WatchOnRampResult = { success: boolean; // [!code highlight] hash?: string; } ``` ::: ### hash (optional) Transaction hash. Present if [`waitForTransactionHash`](/sdk/references/on-ramp/watch-request-id#options-optional) is `true` in request `options` and `success` is `true` * **Type:** `string` ::: code-group ```typescript [types.ts] type WatchOnRampResult = { success: boolean; hash?: string; // [!code highlight] } ``` ::: --- --- url: 'https://docs.brrr.network/docs/use-cases/web3.md' description: >- How dApps, wallet apps, and on-chain protocols add fiat rails — letting users cash out tokens to a Holyheld card, or fund a wallet from a card balance, without ever touching the user's keys. --- # Web3 **Who it's for.** dApps, wallet apps, DEX frontends, on-chain protocols, NFT marketplaces — anywhere the **end user holds tokens in self-custody** and the connected wallet is the source of truth. The user signs; you orchestrate; BRRR settles. The defining characteristic of this vertical: **non-custodial through the entire signing flow.** The private key never leaves the wallet, and your application never holds user funds in transit. ## Typical scenarios * **"Cash out" button next to a token balance.** A wallet app surfaces a Deposit (off-ramp — moving from crypto to fiat) action wherever a user holds a supported token. One signature, EUR on a Holyheld card. * **Fiat-payout for swap or trade settlement.** A DEX, perp protocol, or yield product routes the realized output token to a Deposit instead of returning it to the wallet — the user receives EUR directly. * **Creator / contributor payouts.** A marketplace or DAO tool pays creators by Deposit-to-holytag, so the recipient receives spendable EUR rather than a stablecoin position. * **Top-up from card.** A user funds a wallet from their Holyheld card balance via a Withdraw — useful for gas, for protocol participation, or for "send crypto" UX inside a non-custodial app. ## Recommended stack | Need | Reach for | |---|---| | User signs from wagmi / viem / ethers / web3.js / Solana wallet adapter | [SDK](/sdk/introduction) — handles signing flow, allowance, EIP-2612 permits | | Cross-chain routing without per-chain RPC code | [Orchestrate](/docs/orchestration) — bridges and swaps are computed by `convertTokenToEUR`/`convertEURToToken` | | Display all of a wallet's supported tokens across networks | [`getWalletBalances`](/sdk/references/common/get-wallet-balances) | | Resolve a recipient holytag before sending | [`getTagInfo`](/sdk/references/common/get-tag-info) | | Read-only token metadata, prices, charts for richer UI | [Web3 API](/docs/api/sections#web3-api) | Almost every Web3 integration is **SDK-first**. The Web3 API is a complement, not a substitute — it's read-only display data that lives outside the user's signing flow. ## Architecture at a glance ``` ┌────────────────────────┐ │ Your dApp / wallet │ │ (browser, RN, etc.) │ └────────────┬───────────┘ │ ▼ ┌───────────────┐ │ @holyheld │ │ /sdk │ ← runs in the user's browser │ │ │ signs with │ │ user's wallet│ └──────┬────────┘ │ ▼ ┌───────────────┐ │ BRRR │ ← cross-chain routing, │ Orchestrate │ conversion, settlement └──────┬────────┘ │ ▼ ┌────────────────────────┐ │ Holyheld card (EUR) │ │ or recipient wallet │ └────────────────────────┘ ``` You ship the SDK in the same bundle as your wallet-connect logic. There is no server component required for a typical dApp integration — the SDK API key is intentionally client-safe. ## Where to start 1. [Quickstart](/) — pick the SDK 2. [SDK Installation](/sdk/installation) and [Initialization](/sdk/initialization) 3. [Web3 Providers](/sdk/web3-providers) — wagmi, ethers.js, web3.js, Solana wallet adapter 4. [Examples](/docs/examples) — live multi-network reference apps ## Where to next * [Deposit](/docs/off-ramp), [Withdraw](/docs/on-ramp), [Orchestrate](/docs/orchestration) — the three Core concepts hubs * [`topup`](/sdk/references/off-ramp/topup), [`requestOnRamp`](/sdk/references/on-ramp/request-on-ramp) — the methods that execute each flow * [Supported Networks](/docs/supported-networks) — current EVM and Solana coverage * A live multi-network reference: [holyheld.com/sdk/send](https://holyheld.com/sdk/send) --- --- url: 'https://docs.brrr.network/sdk/web3-providers.md' description: How to work with different Web3 providers for various blockchain networks. --- # Working with different Web3 providers ## EVM Networks By default, a [viem](https://github.com/wagmi-dev/viem){target="\_blank"} provider is used for EVM-compatible JSON-RPC interaction. However, you can use Holyheld SDK with other providers like Ethers.js and Web3.js. ::: code-group ```typescript [Wagmi] import { getPublicClient, getWalletClient } from '@wagmi/core'; const chainId; // token chain id const publicClient = getPublicClient({ chainId }); const walletClient = await getWalletClient({ chainId }); ``` ```typescript [Ethers.js] import { providers } from 'ethers'; import { createPublicClient, createWalletClient, custom, http } from 'viem'; const chain; // chain entity from viem const provider = new providers.Web3Provider(window.ethereum); const publicClient = createPublicClient({ chain, transport: http(), }); const walletClient = createWalletClient({ chain, transport: custom(provider.provider), account: '0x...', // wallet address }); ``` ```typescript [Web3.js] import Web3 from 'web3'; import { createPublicClient, createWalletClient, custom, http } from 'viem'; const chain; // chain entity from viem const provider = new Web3(window.ethereum).currentProvider; const publicClient = createPublicClient({ chain, transport: http(), }); const walletClient = createWalletClient({ chain, transport: custom(provider), account: '0x...', // wallet address }); ``` ::: ## Solana Network To interact with the Solana network, Holyheld SDK requires two things: a connection to the network and a wallet client. ### Connection The `Connection` object is part of the [@solana/web3.js](https://github.com/solana-labs/solana-web3.js){target="\_blank"} library and is responsible for all communication with the Solana network. ```typescript import HolyheldSDK, { SolanaNetwork } from '@holyheld/sdk'; import { Connection, clusterApiUrl } from '@solana/web3.js'; const sdk = new HolyheldSDK({ apiKey: process.env.HOLYHELD_SDK_API_KEY! }); await sdk.init(); const networkInfo = sdk.solana.getNetwork(SolanaNetwork.Mainnet); const connection = new Connection(networkInfo.httpRpcURL, { commitment: 'confirmed', wsEndpoint: networkInfo.wsRpcURL ?? clusterApiUrl(networkInfo.cluster, true) }); ``` ### Wallet client The wallet client must implement the following interface expected by the SDK: ```typescript import { type SendOptions, VersionedTransaction } from '@solana/web3.js'; interface WalletClientSolana { signMessage(message: string): Promise; signTransaction(transaction: VersionedTransaction): Promise; signAllTransactions?(transactions: Array): Promise>; signAndSendTransaction(transaction: VersionedTransaction, sendOptions?: SendOptions): Promise; } ``` You can implement this interface manually, or use one of the [wallet adapters](https://github.com/anza-xyz/wallet-adapter){target="\_blank"}. The SDK provides a helper function to wrap such adapters into the required format. For example, `PhantomWalletAdapter`: ```typescript import { Connection } from '@solana/web3.js'; import { PhantomWalletAdapter } from '@solana/wallet-adapter-phantom'; import { createSolanaWalletClientFromAdapter } from '@holyheld/sdk'; const walletAdapter = new PhantomWalletAdapter(); const connection = new Connection(/* ... */); const walletClient = createSolanaWalletClientFromAdapter(walletAdapter, connection); ``` --- --- url: 'https://docs.brrr.network/docs/api/webhooks.md' description: Receive event notifications from Holyheld. --- # Webhooks Holyheld uses webhooks to notify integrations about important events such as risk assessment updates and settlement status changes. During integration setup you must provide an HTTPS endpoint that will receive webhook requests. ## Verifying webhook requests ::: warning Always verify incoming webhooks Never process a webhook payload without first confirming it came from Holyheld. Without verification, an attacker could send forged events to your endpoint and trigger unintended actions in your system. ::: Holyheld signs every webhook request with your API key value. The `X-Api-Key` header is included in every delivery: ``` X-Api-Key: ``` Before processing any event, confirm that the header value matches your configured API key: ```javascript // Node.js / Express example app.post('/webhook', (req, res) => { const receivedKey = req.headers['X-Api-Key']; if (!receivedKey || receivedKey !== process.env.BRRR_API_KEY) { return res.status(401).json({ error: 'Unauthorized' }); } // Safe to process the event const event = req.body; console.log('Received event:', event.type); res.status(200).send('OK'); }); ``` ```python # Python / Flask example from flask import Flask, request, abort import os app = Flask(__name__) @app.route('/webhook', methods=['POST']) def webhook(): received_key = request.headers.get('X-Api-Key') if received_key != os.environ['BRRR_API_KEY']: abort(401) event = request.json print('Received event:', event['type']) return 'OK', 200 ``` Return HTTP `200` to acknowledge successful receipt. Any other status code (or a network timeout) will cause BRRR to retry the delivery. ## Delivery and retries BRRR expects your endpoint to return an HTTP `2xx` status within **10 seconds**. If your endpoint returns any other status code, returns an error, or does not respond in time, the delivery is considered failed and BRRR will retry. **Retry schedule:** | Attempt | Delay after previous failure | |---------|------------------------------| | 1st retry | 5 minutes | | 2nd retry | 30 minutes | | 3rd retry | 2 hours | | 4th retry | 24 hours | After the 4th retry (approximately 26 hours after the initial delivery attempt), the event is dropped. Contact if you need a missed event replayed. ::: tip Make your handler idempotent Because retries can cause the same event to be delivered more than once, always check whether you have already processed an event before taking action. Use `quoteId` (for settlement events) or a combination of `type` + `timestamp` + payload identifier as your deduplication key. ::: ## Event ordering Webhook events are **not guaranteed to arrive in strict chronological order**. Network conditions, retry schedules, and concurrent processing mean that a later event may be delivered before an earlier one. For example, you may receive `SETTLEMENT_STATUS_CHANGE` with `newStatus: "FINISHED"` before you receive the earlier `newStatus: "CONFIRMED"` event. **Recommended approach — use `timestamp` as ground truth:** Always compare the `timestamp` field in the incoming event against the last known timestamp you have stored for that resource. If the incoming event is older than what you have already processed, discard it. ```javascript // Example: idempotent status handler async function handleSettlementStatusChange(event) { const { quoteId, newStatus } = event.payload; const eventTimestamp = event.timestamp; const stored = await db.getSettlement(quoteId); // Discard out-of-order events if (stored && stored.lastEventTimestamp >= eventTimestamp) { console.log('Ignoring stale event for', quoteId); return; } await db.updateSettlement(quoteId, { status: newStatus, lastEventTimestamp: eventTimestamp, }); } ``` ::: tip Never rely on event order for state transitions Design your handler to accept any status transition, not just the expected sequence. Always fetch the current status via [Get Settlement Status](/api/settlement/settlement-execution/get-settlement-status) if your system state is uncertain. ::: ## Event format All webhook events follow the same structure: ```json { "type": "EVENT_TYPE", "timestamp": 1724247261, "payload": {} } ``` ## Event catalogue | Event type | Emitted by | Triggered when | |------------------------------|--------------------|------------------------------------------------------------------------------| | `RISK_ASSESSMENT` | Partner Settlement | Risk evaluation completes for a monitored wallet address | | `SETTLEMENT_STATUS_CHANGE` | Partner Settlement | Settlement moves through its lifecycle (`CREATED` → `CONFIRMED` → `FINISHED`) | | `IBAN_REGISTERED` | Partner Settlement | A partner customer's IBAN is registered via Add IBAN to a Customer | | `IBAN_REMOVED` | Partner Settlement | A partner customer's IBAN is removed | | `OTC_ORDER_STATUS_CHANGE` | OTC API | An OTC order changes state (`CONFIRMED`, `PROCESSING`, `SETTLED`, etc.) | | `OFFRAMP_STATUS_CHANGE` | Card API | Offramp (crypto-to-card or crypto-to-SEPA) transitions state | | `ONRAMP_STATUS_CHANGE` | Card API | Onramp (buy crypto) transitions state | | `SEPA_TRANSFER_STATUS_CHANGE`| Card API | A SEPA transfer leg moves from queued → sent → settled | | `GASLESS_TX_BROADCAST` | Card API | A gasless offramp transaction has been broadcast on-chain | | `CARD_TOPUP_RECEIVED` | Card API | Funds from an offramp reached the customer's card balance | | `TAG_HASH_EXPIRED` | Card API | A one-time Tag Hash expired without being consumed | ## Event types ### RISK\_ASSESSMENT Sent when a risk evaluation is completed for a monitored wallet address. ```json { "type": "RISK_ASSESSMENT", "timestamp": 1724247261, "payload": { "customerId": "", "addressEVM": "", "risk": "LOW", "reviewTimestamp": 1724247261, "reviewType": "TOPUP", // "INITIAL" - for initial wallet registration, "TOPUP" - for activity "reviewComment": "comment", // optional "reviewData": { // optional "fromAddress": "0x26b92eD884B9FE3f572252ee78172BfBC1653dC1", "amount": "10000", "totalAmount": "754699" } } } ``` ### SETTLEMENT\_STATUS\_CHANGE Sent when the status of a settlement changes. ```json { "type": "SETTLEMENT_STATUS_CHANGE", "timestamp": 1724247261, "payload": { "quoteId": "", "oldStatus": "CREATED", "newStatus": "CONFIRMED" } } ``` ### IBAN\_REGISTERED Sent when a partner customer's IBAN is successfully registered via Add IBAN to a Customer. ```json { "type": "IBAN_REGISTERED", "timestamp": 1724247261, "payload": { "customerId": "cust_a1b2c3d4", "ibanId": "iban_01HXYZ123456", "iban": "DE89370400440532013000", "beneficiaryName": "John Doe", "bankName": "Commerzbank", "bankCountry": "DE" } } ``` ### IBAN\_REMOVED Sent when a partner customer's IBAN is removed. ```json { "type": "IBAN_REMOVED", "timestamp": 1724247261, "payload": { "customerId": "cust_a1b2c3d4", "ibanId": "iban_01HXYZ123456" } } ``` ### OTC\_ORDER\_STATUS\_CHANGE Sent when an OTC order moves between states. ```json { "type": "OTC_ORDER_STATUS_CHANGE", "timestamp": 1724247261, "payload": { "orderId": "F0E2D8B3-1A4C-4F6E-9D5B-8C7F3E2A1B0D", "orderType": "SELL_CRYPTO", // "SELL_CRYPTO" | "BUY_EUR" "oldStatus": "CONFIRMED", "newStatus": "PROCESSING", "network": "ethereum", "token": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "tokenAmount": "10245.182341", "fiatAmount": "9500.00", "chainTxHash": "0xabcd..." // present once crypto leg is broadcast } } ``` ### OFFRAMP\_STATUS\_CHANGE Sent as a Card API offramp (crypto to card or crypto to SEPA) moves between lifecycle states — the same states returned by Get Offramp Status. ```json { "type": "OFFRAMP_STATUS_CHANGE", "timestamp": 1724247261, "payload": { "HHTXID": "F0E2D8B3-1A4C-4F6E-9D5B-8C7F3E2A1B0D", "oldState": "QUEUED", "newState": "PENDING", // WAITFORTX | QUEUED | PENDING | EXECUTING | SUCCESS | CANCELLED | FAILED "destination": { "type": "SEPA", // "CARD" | "SEPA" | "TAG" "iban": "DE89370400440532013000" }, "tokenAmount": "10", "EURAmount": "18032.06", "chainId": 1, "chainTxHash": "0x9c8b7a..." // present once on-chain tx is observed } } ``` ### ONRAMP\_STATUS\_CHANGE Sent as a Card API onramp (buy crypto) moves between states — the same states returned by Get Onramp Status. ```json { "type": "ONRAMP_STATUS_CHANGE", "timestamp": 1724247261, "payload": { "HHTXID": "5721128E-DDB3-4132-9791-39D91D022D61", "oldStatus": "approved", "newStatus": "success", // not_approved | approved | success | failed | declined "chainId": 1, "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "amountEUR": "100.00", "txHash": "0x1234abcd...", // present when newStatus = "success" "reason": "unknown" // present when newStatus = "failed" } } ``` ### SEPA\_TRANSFER\_STATUS\_CHANGE Sent when a SEPA transfer associated with a Card API offramp moves state. ```json { "type": "SEPA_TRANSFER_STATUS_CHANGE", "timestamp": 1724247261, "payload": { "HHTXID": "F0E2D8B3-1A4C-4F6E-9D5B-8C7F3E2A1B0D", "iban": "DE89370400440532013000", "beneficiaryName": "John Doe", "eurAmount": "499.65", "feeAmount": "0.35", "oldStatus": "PENDING", "newStatus": "EXECUTING" // PENDING | EXECUTING | SUCCESS | FAILED } } ``` ### GASLESS\_TX\_BROADCAST Sent when a gasless offramp (Execute Gasless Transaction) has been broadcast on-chain on the customer's behalf. ```json { "type": "GASLESS_TX_BROADCAST", "timestamp": 1724247261, "payload": { "HHTXID": "F0E2D8B3-1A4C-4F6E-9D5B-8C7F3E2A1B0D", "chainId": 1, "chainTxHash": "0x9c8b7a...", "fromAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0Ad", "tokenAmount": "10", "EURAmount": "18032.06" } } ``` ### CARD\_TOPUP\_RECEIVED Sent when funds from an offramp top-up have been credited to the customer's card balance. ```json { "type": "CARD_TOPUP_RECEIVED", "timestamp": 1724247261, "payload": { "HHTXID": "F0E2D8B3-1A4C-4F6E-9D5B-8C7F3E2A1B0D", "tagName": "SDKTEST", "EURAmount": "18032.06", "source": { "type": "CRYPTO", // "CRYPTO" | "SEPA" "chainId": 1, "chainTxHash": "0x9c8b7a..." } } } ``` ### TAG\_HASH\_EXPIRED Sent when a one-time Tag Hash (issued by Get Tag Hash or Create SEPA Transfer) expired before being consumed by an offramp transaction. ```json { "type": "TAG_HASH_EXPIRED", "timestamp": 1724247261, "payload": { "tagHash": "0xabc...def", "issuedAt": 1724246661, "expiredAt": 1724247261 } } ``` --- --- url: 'https://docs.brrr.network/docs/what-is-brrr.md' description: What is BRRR token. --- # What is BRRR BRRR is a native utility token of the Blockchain Reconciliation and Remittance Record (BRRR) protocol. The BRRR protocol enables seamless interaction between traditional payment systems (fiat rails) and onchain infrastructure, serving as a technological bridge without BRRR itself ever functioning as a means of payment, store of value, or investment. BRRR qualifies as a pure utility token. It provides access to and interaction with the features of the BRRR protocol. Specifically, BRRR is required to activate, configure, or operate protocol functions that enable real-time financial messaging, programmable money flows, and interoperability between fiat and crypto systems. BRRR serves the following functional purposes: * Enabling protocol-level access and operations for modular financial logic and reconciliation tools across fiat and blockchain networks. * Allowing smart contract automation to be extended to fiat rails and legacy infrastructure without BRRR being used as currency or a payment token itself. * Unlocking specific protocol services such as real-time FX settlements, AI agent coordination, cross-border remittances, and tokenized asset integrations. * Supporting developers and financial institutions in building applications on top of the BRRR protocol, by authorizing configuration changes, permissions, or modular service layers. BRRR DOES NOT GRANT ANY FINANCIAL OR GOVERNANCE RIGHTS, NOR IS IT USED FOR SPECULATIVE INVESTMENT PURPOSES. IT DOES NOT REPRESENT OWNERSHIP, DEBT, OR REVENUE SHARE AND IS NOT INTENDED TO APPRECIATE IN VALUE. IT IS STRICTLY REQUIRED TO ACCESS AND USE SPECIFIC TECHNICAL FEATURES OF THE PROTOCOL. The token is currently used in production within the Holyheld ecosystem, demonstrating its functionality as a tool for activating and executing protocol features in real-world financial use cases. By abstracting away blockchain complexity—including gas, network-specific logic, and token bridges—BRRR empowers institutions to innovate within a compliant, programmable, and modular financial framework. --- --- url: 'https://docs.brrr.network/docs/on-ramp.md' description: >- Spend EUR from a Holyheld card and deliver tokens to a wallet. The user confirms in the Holyheld mobile app within 3 minutes. --- # 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 | Surface | When to use | Auth | |---|---|---| | [SDK](/sdk/introduction) | A user-facing app where the end user holds a Holyheld card and confirms in the mobile app | SDK API key | | [Card API](/docs/api/sections) | A platform building its own Withdraw UX on top of the same primitives | `X-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`](/sdk/references/on-ramp/request-on-ramp) and [`watchRequestId`](/sdk/references/on-ramp/watch-request-id) references for parameters and the full outcome shape. [`getOnRampEstimation`](/sdk/references/on-ramp/get-on-ramp-estimation) 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](/docs/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](/sdk/error-handling). ## Where to next * [`requestOnRamp`](/sdk/references/on-ramp/request-on-ramp) and [`watchRequestId`](/sdk/references/on-ramp/watch-request-id) reference * [`getOnRampEstimation`](/sdk/references/on-ramp/get-on-ramp-estimation) * [`validateAddress`](/sdk/references/common/validate-address) — eligibility checks before submitting * [Webhooks](/docs/api/webhooks) — for server-side delivery of Withdraw outcomes