JavaScript SDK
Download the single-file JavaScript SDK (ESM, Node 18+ and browsers): local keys, signing, transfer(), webhook verification.
# JavaScript SDK
The JavaScript SDK is an ES module for **Node 18 or later and browsers**, shipped as a single file, `rarebl.mjs`. It builds, verifies and signs transactions **on your machine**; your private key never leaves the process. Version 0.2.0. It is the reference implementation: the Python and PHP SDKs must produce byte-identical ECDSA signatures (and Schnorr signatures when `deterministic` is set).
Crypto comes from the pinned libraries `@noble/curves`, `@noble/hashes` and `@scure/base`, bundled into the single file, so you do not install anything.
## Download
| File | Size | SHA-256 |
| --- | --- | --- |
| [rarebl-sdk-0.2.0.tgz](https://docs.rarebl.com/sdk/js/rarebl-sdk-0.2.0.tgz) | 103.7 KB | `78c11fb5a1c378f55e240556c472b837ed0fbef35b940c63d167ed846e43f5fa` |
| [rarebl.mjs](https://docs.rarebl.com/sdk/js/rarebl.mjs) | 197.7 KB | `7c5bd8fc5e412ae8596cedab97634915fd94d91bf2fb8b863c1656e36b070aea` |
Requires: Node 18 or later, or a browser (ESM). All checksums are in [SHA256SUMS](https://docs.rarebl.com/sdk/SHA256SUMS). Check the file before you use it:
~~~bash
curl -O https://docs.rarebl.com/sdk/js/rarebl.mjs
sha256sum rarebl.mjs
~~~
## Install
Download the file, check it against the SHA-256 above, and import it:
~~~js
import { Rarebl, keys } from './rarebl.mjs';
~~~
A tarball with the source and tests (`rarebl-sdk-0.2.0.tgz`) is next to it. The npm package `@rarebl/sdk` will follow. In a browser use `<script type="module">`. On Node 18 add `globalThis.crypto ??= (await import('node:crypto')).webcrypto;` first (Node 20 and later have it).
## Ten lines: send BTC
~~~js
import { Rarebl, keys } from './rarebl.mjs';
const me = keys.fromPrivate('btc', process.env.BTC_WIF, 'p2wpkh'); // or keys.generate('btc')
const rarebl = new Rarebl(); // https://api.rarebl.com
await rarebl.watch('btc', me.address); // BTC, LTC, BCH: start watching, wait until active
const txid = await rarebl.transfer({
chain: 'btc', from: me.address, to: 'bc1qanfrvw5lunlemvm09dthnxmwknal0fw0q7jfnm', amount: '0.0015',
key: process.env.BTC_WIF, priority: 'standard', // or feeRate: 12 (sat/vB)
});
console.log(txid);
~~~
## Send USDT on TRON
~~~js
const txid = await rarebl.transfer({
chain: 'tron', from: 'T...sender', to: 'T...recipient', amount: '25',
token: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', // USDT-TRC20; decimals are known to the SDK, other tokens need decimals: n
key: process.env.TRON_KEY, // 64 hex characters
});
~~~
ETH works the same (`chain: 'eth'`, `token: '0xdac17f958d2ee523a2206206994597c13d831ec7'` for USDT). The sender needs TRX or ETH for fees.
## `transfer()`
`transfer` builds the transaction on the API, **recomputes every digest and checks that it pays exactly what you asked** (recipient, amount, change back to you, fee cap), signs on your machine, then assembles and broadcasts. It throws `VerifyError` before signing if anything differs. Amounts are decimal strings in whole units: do not pass numbers for money.
## Lower level: build, describe, sign, assemble
~~~js
import { describeBuild, signBuild } from './rarebl.mjs';
const reply = await rarebl.build({ chain: 'btc', from: [addr], to: [{ address, amount: '0.001' }], priority: 'fast' });
const summary = describeBuild(reply, { expect: { to: [{ address, amount: '0.001' }] } }); // what is really being signed
const signatures = signBuild(reply, [key], { expect: { to: [{ address, amount: '0.001' }] } }); // expect is required; verify is on by default
const { broadcast } = await rarebl.assemble(reply.unsigned, signatures, { broadcast: true });
~~~
| Function | What it does |
| --- | --- |
| `keys.generate`, `keys.fromPrivate` | Local key generation (CSPRNG) and import (hex or WIF). |
| `address.derive`, `address.validate` | Derive and validate addresses. |
| `signDigest` | One signature: DER + sighash byte, BIP340 Schnorr, or recoverable `r‖s‖v`. |
| `verifyBuild` | Recompute every digest and check outputs, amounts, change and fee. |
| `describeBuild` | A readable summary of what a build reply would sign. |
| `signBuild(reply, keys, { expect, unsafeNoExpect?, verify?, deterministic?, prevTxs?, now? })` | Verify, then sign every digest. `expect` is required and must contain `to`; `verify` is on by default. |
| `Rarebl` client | `build`, `assemble`, `broadcast`, `decode`, `estimate`, `status`, `watch`, `utxos`, `nonce`, `resources`, `marketPrice`, `transfer`. |
| `envelope.encrypt(signerKey, { privateKeys, op, chain, digests?, bind?, exp?, jti? })` | Builds an envelope v2 for [hosted signing](https://docs.rarebl.com/guides/hosted-signing); not needed for local signing. `signerKey` is the whole reply of `rarebl.signerKey()`. |
| `envelope.bindOf`, `envelope.seal`, `envelope.decrypt` | `bindOf(chain, to, { token?, decimals? })` converts recipients to the base-unit `bind` of an `op: 'send'` envelope; `decrypt` opens the `encryptedPrivateKey` of an [address/new](https://docs.rarebl.com/api/address/new) reply. |
| `webhook.verify` | Check `X-Rarebl-Signature`. |
`signBuild` refuses to run without `expect` (it must contain `to`): without it nothing pins who is paid. Pass `unsafeNoExpect: true` to sign without pinning the recipients; the default fee caps still apply, and `verify: false` also needs it. `transfer()` always pins what you passed. The `expect` fields are `to`, `change`, `token`, `decimals`, `opReturn`, `from`, `chain` and the fee caps below.
TypeScript types are in the tarball (`src/index.d.ts`).
## Fee caps and defaults
When `expect` gives no cap, the SDK applies one. Override it through `expect`:
| Chain | Default | Override |
| --- | --- | --- |
| BTC, LTC | 1000 sat/vB | `maxFeeRate`, or an absolute `maxFee` (whole units) or `maxFeeSats` |
| BCH, DASH | 50 sat/byte | the same |
| ETH | `maxFeePerGas` at most 500 gwei, total fee at most 0.05 ETH, tip at most max(5 gwei, `maxFeePerGas` / 2) | `maxFeePerGas`, `maxPriorityFeePerGas` (wei), `maxFee` |
| TRON | `feeLimit` at most 150 TRX | `maxFeeLimit` (sun) |
A UTXO transaction with a version other than 1 or 2, a non-zero locktime (`allowLocktime: true` allows it), a repeated outpoint or a sighash type other than SIGHASH_ALL is refused (`0x41`, with FORKID, is accepted on BCH only).
## Hosted signing
For [hosted signing](https://docs.rarebl.com/guides/hosted-signing) the SDK builds an envelope v2 that names the chain, an expiry, a one-time id and what may be signed. The request needs the header `X-Rarebl-Accept-Risk: hosted-signing`.
~~~js
import { envelope } from './rarebl.mjs';
const signer = await rarebl.signerKey(); // {kid, publicKey, expiresAt}
const digests = reply.toSign.map((t) => t.digest); // after describeBuild passed
const forSign = await envelope.encrypt(signer, { privateKeys: [key], op: 'sign', chain: 'btc', digests });
const bind = envelope.bindOf('btc', [{ address: to, amount: '0.0015' }]); // recipients, as base units
const forSend = await envelope.encrypt(signer, { privateKeys: [key], op: 'send', chain: 'btc', bind });
// POST /open/tx/sign or /open/tx/send with { ..., keys: { encrypted } } and the header above
~~~
An envelope is valid for at most 300 seconds and opens once. The signer refuses digests or outputs that are not the ones it names.
## Verify webhooks
~~~js
import { webhook } from './rarebl.mjs';
app.post('/hook', express.raw({ type: '*/*' }), (req, res) => { // the RAW body, not parsed JSON
if (!webhook.verify(process.env.RAREBL_SIGNING_SECRET, req.body, req.get('X-Rarebl-Signature'))) return res.sendStatus(400);
const event = JSON.parse(req.body); // deduplicate on X-Rarebl-Event-Id
res.sendStatus(200);
});
~~~
The check is `HMAC-SHA256(secret, t + "." + body)` in constant time, with a 5 minute tolerance by default. See [Webhooks](https://docs.rarebl.com/guides/webhooks).
## Errors
API errors are typed (`ValidationError`, `ConflictError`, `UnprocessableError`, `RateLimitError` and others) and carry `code`, `reason` and `detail`, for example `API422` with reason `node_rejected`. A verification failure is a `VerifyError`, thrown before any signature is made.
## Security notes
- **Why local signing:** a private key that is never sent cannot be leaked, logged or subpoenaed from a server. The SDK never sends a key by itself; the only code that puts key material on the wire is `envelope.encrypt`, which you must call explicitly. See [Send a transaction](https://docs.rarebl.com/guides/send-a-transaction).
- **What `verify` protects against:** `signBuild` and `transfer` rebuild every digest (legacy, BIP143, BIP341 with SIGHASH_DEFAULT, BCH FORKID, EIP-1559 RLP and keccak, TRON `sha256(raw_data_hex)` plus a decode of TRX and TRC-20 transfers) from the unsigned transaction, and check outputs, amounts, change and fee against what *you* asked for. A compromised or buggy server therefore cannot make you sign a different payment. `expect` is mandatory in `signBuild` and `transfer` builds it from its arguments. `unsafeNoExpect: true` checks only internal consistency, and `verify: false` signs whatever digests the server sends: do not use either.
- **Legacy inputs:** the signature of a legacy p2pkh input (BTC, LTC, DASH) does not commit to its amount. The SDK therefore requires `unsigned.inputs[i].prevTx` (delivered by `tx/build`) or `prevTxs`, checks that it hashes to the outpoint and reads the amount from it; a reply without it, or with a different amount, is refused.
- **Known limits:** token decimals are never taken from the server: unknown tokens need an explicit `decimals`. TRON contracts other than TRX and TRC-20 `transfer` are refused.
- ECDSA signatures use RFC 6979 deterministic nonces and low-S, and are verified before they are returned. BIP340 (p2tr) signatures use fresh random auxiliary data; pass `deterministic: true` for reproducible output.
- **In a browser** the key lives in page memory: never load third-party scripts on a page that handles keys, and prefer a server you control for custody.
- **Hosted signing.** `envelope.encrypt` encrypts keys to the signer's X25519 key for `tx/sign` and `tx/send`; the key then leaves your machine, which local signing avoids: [Hosted signing](https://docs.rarebl.com/guides/hosted-signing).
- Keep keys in environment variables or a secret store, not in source. Test with small amounts first.
MIT licensed.
## Next
[Sign locally with the SDK](https://docs.rarebl.com/guides/sign-locally), [Send a transaction](https://docs.rarebl.com/guides/send-a-transaction), [Watch addresses, get UTXOs and balances](https://docs.rarebl.com/guides/watch-addresses).