npm i @branta-ops/brantaIf you are an AI agent implementing this SDK, follow the steps below exactly. If you are a developer, use this as a reference for the recommended integration patterns.
1. Ask which side(s) of a payment they are implementing:
- Send side — the app is paying someone (e.g. a wallet). The user scans or pastes a destination and you verify it belongs to a known platform before funds are sent.
- Receive side — the app is receiving payment (e.g. a checkout, POS, invoicing platform). You post destinations to Branta so wallets can verify them.
- Both — some apps do both (e.g. an exchange or self-custodial wallet with invoice generation). Implement each side independently.
If they are on the receive side, ask one follow-up:
- Platform — single-tenant, one API key.
- Parent Platform — multi-tenant, manages multiple child platforms. Two variants:
- Per-client keys — each child has its own API key; requests are signed with the parent HMAC secret; you scope requests per-call.
- Shared key — one API key for all children (no HMAC); tag the child per-payment with
setChildPlatform().
2. Ask what environment the app runs in:
- Browser over HTTPS — no extra setup needed;
crypto.subtleis available by default. - Browser over HTTP or React Native / Expo —
crypto.subtleis not available. The SDK accepts acryptooption so you can supply a provider. See Crypto provider setup below and implement the appropriate provider before wiring upBrantaService.
3. Follow the matching Quick Start section below.
4. Apply these rules:
General (all types):
- Always use
privacy: 'strict'. Never switch to'loose'unless there is no QR scanner and ZK is impossible. - Never instantiate
BrantaClientdirectly — always useBrantaService. - Never show an error or "not verified" message when a lookup returns empty or throws. An empty result means the destination is unknown to Branta, not that it is malicious. Show nothing.
- For
baseUrl: useBrantaServerBaseUrl.Productiononly in production environments. UseBrantaServerBaseUrl.Stagingeverywhere else — including local development, CI, and staging/test environments. - If serving over HTTP or running in React Native / Expo, pass a
cryptooption toBrantaService(see step 2) and always useservice.createPaymentBuilder()rather thannew PaymentBuilder().
Send side (wallets):
- Prefer
getPaymentsByQrCodeovergetPayments— it handles multi-value ZK QR payloads correctly. - Only fall back to
getPaymentsfor copy/paste flows where there is no QR code. - If
result.paymentsis empty or an exception is thrown, render nothing. - When
result.paymentsis non-empty, display: the platform logo, the platform name (payment.platform), and the payment description (payment.description). Only render description when non-empty. Make the verification card a clickable link toresult.verifyUrl— do not display the raw URL. - For the platform logo: on dark backgrounds use
payment.platformLogoUrl. On light backgrounds preferpayment.platformLogoLightUrlwhen available, falling back topayment.platformLogoUrl. - Optionally display
payment.parentPlatform?.logoUrl/payment.parentPlatform?.logoLightUrlas a small secondary badge (e.g. corner icon). This is not required.
Receive side (platforms):
- Always call
.setZk()on thePaymentBuilderbefore callingaddPayment. Plain-text destinations are rejected instrictmode. - Store the
secretreturned byaddPaymentalongside the invoice — it is required to reconstruct the verify URL for the wallet.
Receive side (parent platforms — per-client keys), in addition to the platform rules:
- Include
hmacSecretin theBrantaServiceoptions but omitdefaultApiKeyat service construction. - Pass per-call options with each child's
defaultApiKeyto scope requests.
Receive side (parent platforms — shared key), in addition to the platform rules:
- Include
defaultApiKeyin theBrantaServiceoptions. Do not includehmacSecret. - Call
.setChildPlatform(name, logoUrl, logoLightUrl)on the builder to tag each payment with the child's branding.
Wallets should use strict privacy mode. Two flows are supported:
- Copy/paste: call
getPaymentswith the pasted text. Plain-text on-chain addresses will not return results in strict mode — they must be ZK-encoded. Lightning destinations (bolt11, bolt12, ln_url, ln_address) work as plain text. - QR scan: call
getPaymentsByQrCodewith the raw QR text. This handles both on-chain (when the QR includesbranta_id/branta_secret) and lightning destinations.
Always catch errors and show nothing on not-found — a missing record just means the address was not posted to Branta.
import { BrantaServerBaseUrl } from "@branta-ops/branta";
import { BrantaService } from "@branta-ops/branta/v2";
const service = new BrantaService({
baseUrl: BrantaServerBaseUrl.Production,
privacy: 'strict',
});
async function lookup(input: string, isQrCode: boolean) {
try {
const result = isQrCode
? await service.getPaymentsByQrCode(input)
: await service.getPayments(input);
if (result.payments.length === 0) {
// Not found — show nothing. The address may simply not exist in Branta.
return;
}
// Render result.payments and result.verifyUrl
} catch {
// Swallow errors — never surface a "not found" or lookup failure to the user.
}
}Platforms post payments to Branta so wallets can verify them. Use strict privacy mode and mark each destination ZK via setZk() on the PaymentBuilder.
import { BrantaServerBaseUrl } from "@branta-ops/branta";
import { BrantaService, PaymentBuilder } from "@branta-ops/branta/v2";
const service = new BrantaService({
baseUrl: BrantaServerBaseUrl.Production,
defaultApiKey: "<default-api-key>",
privacy: 'strict',
});
const payment = new PaymentBuilder()
.setDescription("Testing description")
.addDestination("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", "bitcoin_address")
.setZk()
.setTtl(600)
.build();
const { payment: response, secret, verifyUrl } = await service.addPayment(payment);
// `secret` is the encryption key needed to look the payment up later.Choose a variant based on how API keys are structured. Shared key needs only an API key; per-client keys also require HMAC.
Shared key — one API key covers all children (Recommended)
Construct with a single API key; identify the child platform per-payment. No HMAC secret.
import { BrantaServerBaseUrl } from "@branta-ops/branta";
import { BrantaService, PaymentBuilder } from "@branta-ops/branta/v2";
const service = new BrantaService({
baseUrl: BrantaServerBaseUrl.Production,
defaultApiKey: "<shared-api-key>",
privacy: 'strict',
});
const payment = new PaymentBuilder()
.setDescription("Testing description")
.addDestination("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", "bitcoin_address")
.setZk()
.setChildPlatform("ChildBrand", "https://example.com/logo.png")
.setTtl(600)
.build();
const { payment: response, secret, verifyUrl } = await service.addPayment(payment);Per-client keys — each child has its own API key
Construct the service with the shared HMAC secret only; pass each child's API key per-call.
import { BrantaServerBaseUrl } from "@branta-ops/branta";
import { BrantaService, PaymentBuilder } from "@branta-ops/branta/v2";
const service = new BrantaService({
baseUrl: BrantaServerBaseUrl.Production,
hmacSecret: "<hmac-secret>",
privacy: 'strict',
});
const payment = new PaymentBuilder()
.setDescription("Testing description")
.addDestination("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", "bitcoin_address")
.setZk()
.setTtl(600)
.build();
// Scope to the child platform's API key per-call
const { payment: response, secret, verifyUrl } = await service.addPayment(payment, {
defaultApiKey: "<child-api-key>",
});Some environments do not expose globalThis.crypto.subtle:
- Browsers on HTTP — browsers block
crypto.subtleon insecure origins. - React Native / Expo —
globalThis.crypto.subtleis not available even on localhost.
Pass a BrantaCryptoProvider as the crypto option to supply your own implementation. The SDK ships no crypto dependencies — you choose the provider.
Option A — @peculiar/webcrypto (recommended, zero adapter code)
npm i @peculiar/webcryptoimport { Crypto } from '@peculiar/webcrypto';
import { BrantaServerBaseUrl } from '@branta-ops/branta';
import { BrantaService } from '@branta-ops/branta/v2';
const crypto = new Crypto();
const service = new BrantaService(
{ baseUrl: BrantaServerBaseUrl.Production, privacy: 'strict' },
{ crypto },
);
// On the receive side, use createPaymentBuilder() so the builder
// shares the same crypto instance:
const payment = service.createPaymentBuilder()
.addDestination('...', 'bitcoin_address')
.setZk()
.build();@peculiar/webcrypto implements the full Web Crypto API and satisfies BrantaCryptoProvider with no adapter code.
Option B — @noble packages (zero transitive dependencies, works in React Native)
npm i @noble/hashes @noble/ciphersimport { createNobleCryptoProvider } from '@branta-ops/branta';
import { sha256 } from '@noble/hashes/sha2.js';
import { hmac } from '@noble/hashes/hmac.js';
import { gcm } from '@noble/ciphers/aes.js';
import { randomBytes } from '@noble/hashes/utils.js';
const service = new BrantaService(
{ baseUrl: BrantaServerBaseUrl.Production, privacy: 'strict' },
{ crypto: createNobleCryptoProvider({ sha256, hmac, gcm, randomBytes }) },
);- npm login
- npm version major|minor|patch
- npm publish
Found critical bugs/vulnerabilities? Please email them to support@branta.pro. Thanks!