Sign locally with the SDKs
Send BTC, ETH and USDT on TRON with the JavaScript, Python, PHP or shell SDK in one transfer() call, keys never leaving your machine.
# Sign locally with the SDKs
The SDKs turn the whole [send flow](https://docs.rarebl.com/guides/send-a-transaction) into one `transfer()` call. They ask the API for UTXOs, nonce and fees, then build, verify and sign **on your machine**, and ask the API to broadcast. Your private key is never sent over the network. You will download an SDK, load a key, and send BTC, ETH or USDT on TRON.
## Before you start
- An address with a small balance on the chain you want to try, and its private key (hex or WIF) in an environment variable or a file.
- For ETH and TRON, a little ETH or TRX for fees, even when you send a token.
- JavaScript (Node 18+ or a browser), Python 3.9+, PHP 8.1+ with `ext-gmp`, or bash with `curl`, `jq` and Python 3.9+.
- Run signing on a machine you control. The pure-language crypto is correct but not constant-time, so avoid shared hosting.
## Step 1 — Download the SDK
Download the single file and check its SHA-256 against `https://docs.rarebl.com/sdk/SHA256SUMS`. Each SDK page lists the checksum: [JavaScript](https://docs.rarebl.com/sdks/js), [Python](https://docs.rarebl.com/sdks/python), [PHP](https://docs.rarebl.com/sdks/php), [shell](https://docs.rarebl.com/sdks/shell).
```bash JavaScript
curl -fsSLO https://docs.rarebl.com/sdk/js/rarebl.mjs
curl -s https://docs.rarebl.com/sdk/SHA256SUMS | grep " js/rarebl.mjs"; sha256sum rarebl.mjs
```
```bash Python
curl -fsSLO https://docs.rarebl.com/sdk/python/rarebl.py
curl -s https://docs.rarebl.com/sdk/SHA256SUMS | grep " python/rarebl.py"; sha256sum rarebl.py
```
```bash PHP
curl -fsSLO https://docs.rarebl.com/sdk/php/Rarebl.php
curl -s https://docs.rarebl.com/sdk/SHA256SUMS | grep " php/Rarebl.php"; sha256sum Rarebl.php
```
```bash Shell
curl -fsSLO https://docs.rarebl.com/sdk/shell/rarebl && chmod +x rarebl
curl -s https://docs.rarebl.com/sdk/SHA256SUMS | grep " shell/rarebl"; sha256sum rarebl
```
**You should see** the two hashes match. Put the file next to your script.
## Step 2 — Create or load a key
Generate a key locally, or load one you already have. Keys never leave this process.
```js JavaScript
import { Rarebl, keys } from './rarebl.mjs';
const me = keys.generate('btc', 'p2wpkh'); // or keys.fromPrivate('btc', process.env.BTC_WIF, 'p2wpkh')
console.log(me.address); // fund this address, keep me.privateKey secret
const rarebl = new Rarebl(); // https://api.rarebl.com
```
```python Python
import os, rarebl
me = rarebl.keys.generate("btc", "p2wpkh") # or rarebl.keys.fromPrivate("btc", key)
print(me["address"]) # fund this address, keep me["privateKey"] secret
c = rarebl.Rarebl() # https://api.rarebl.com
```
```php PHP
<?php
require 'Rarebl.php';
$me = Rarebl\Keys::generate('btc', 'p2wpkh'); // or Rarebl\Keys::fromPrivate('btc', $key)
echo $me['address'], "\n"; // fund this address, keep $me['privateKey'] secret
$c = new Rarebl\Rarebl(); // https://api.rarebl.com
```
```bash Shell
rarebl keygen --chain btc --type p2wpkh > btc.json # prints the new address and key: keep this file private
jq -r .privateKey btc.json > btc.key; chmod 600 btc.key
jq -r .address btc.json
```
**You should see** a new address. Send it a small amount and wait for a confirmation before you continue. Keep keys out of source control and out of command lines.
## Step 3 — Send BTC
On BTC, LTC and BCH the sender is watched first so the API can list its UTXOs. The JavaScript and shell SDKs let you start that explicitly; Python and PHP do it automatically the first time. See [Watch addresses](https://docs.rarebl.com/guides/watch-addresses).
```js JavaScript
await rarebl.watch('btc', me.address); // waits until the watch is active
const txid = await rarebl.transfer({
chain: 'btc', from: me.address, to: 'bc1qanfrvw5lunlemvm09dthnxmwknal0fw0q7jfnm',
amount: '0.0015', key: me.privateKey, priority: 'standard', // or feeRate: 12 (sat/vB)
});
console.log(txid);
```
```python Python
txid = c.transfer(chain="btc", from_=me["address"], to="bc1qanfrvw5lunlemvm09dthnxmwknal0fw0q7jfnm",
amount="0.0015", key=me["privateKey"], priority="standard") # or fee_rate=12 (sat/vB)
print(txid)
```
```php PHP
$txid = $c->transfer([
'chain' => 'btc', 'from' => $me['address'], 'to' => 'bc1qanfrvw5lunlemvm09dthnxmwknal0fw0q7jfnm',
'amount' => '0.0015', 'key' => $me['privateKey'], 'priority' => 'standard',
]);
echo $txid, "\n";
```
```bash Shell
rarebl watch --chain btc "$(jq -r .address btc.json)"
rarebl transfer --chain btc --from "$(jq -r .address btc.json)" --to bc1qanfrvw5lunlemvm09dthnxmwknal0fw0q7jfnm \
--amount 0.0015 --key-file btc.key --priority standard # prints the txid
```
**You should see** the transaction id (a 64-character hex string). Behind that one call, the SDK built the transaction, recomputed every digest, checked that it pays exactly what you asked (recipient, amount, change back to you, a fee cap), signed, assembled and broadcast. If anything differs it raises `VerifyError` before signing.
## Step 4 — Send ETH or a token on Ethereum
Ethereum needs a nonce and gas. The API supplies the nonce and fee suggestions; you can pin them with `maxFee` (a cap in wei). For an ERC-20 token, `token` is the contract address and `amount` is in whole token units.
```js JavaScript
const txid = await rarebl.transfer({
chain: 'eth', from: '0xYourAddress', to: '0xRecipient', amount: '25',
token: '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT; leave out `token` to send ETH
key: process.env.ETH_KEY,
});
```
```python Python
txid = c.transfer(chain="eth", from_="0xYourAddress", to="0xRecipient", amount="25",
token="0xdac17f958d2ee523a2206206994597c13d831ec7", key=os.environ["ETH_KEY"],
max_fee=21000 * 40 * 10**9) # cap in wei
```
```php PHP
$txid = $c->transfer([
'chain' => 'eth', 'from' => '0xYourAddress', 'to' => '0xRecipient', 'amount' => '25',
'token' => '0xdac17f958d2ee523a2206206994597c13d831ec7', 'key' => getenv('ETH_KEY'),
]);
```
```bash Shell
rarebl transfer --chain eth --from 0xYourAddress --to 0xRecipient --amount 25 \
--token 0xdac17f958d2ee523a2206206994597c13d831ec7 --key-env ETH_KEY
```
**You should see** the transaction hash. The sender needs ETH for gas; a deposit address that holds only USDT cannot send until it has some.
## Step 5 — Send USDT on TRON
`token` is the TRC-20 contract. A TRON transfer uses energy and bandwidth; what the account lacks is paid by burning TRX, capped by `feeLimit` (in sun, 1 TRX = 1,000,000 sun). Check what the account has with [Address resources](https://docs.rarebl.com/api/address/resources).
```js JavaScript
const txid = await rarebl.transfer({
chain: 'tron', from: 'TYourAddress', to: 'TRecipient', amount: '25',
token: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', // USDT-TRC20
key: process.env.TRON_KEY, // 64 hex characters
feeLimit: 15_000_000,
});
```
```python Python
txid = c.transfer(chain="tron", from_="TYourAddress", to="TRecipient", amount="25",
token="TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", key=os.environ["TRON_KEY"],
max_fee=100_000_000) # feeLimit cap in sun
```
```php PHP
$txid = $c->transfer([
'chain' => 'tron', 'from' => 'TYourAddress', 'to' => 'TRecipient', 'amount' => '25',
'token' => 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', 'key' => getenv('TRON_KEY'),
'maxFee' => 100000000, // feeLimit cap in sun
]);
```
```bash Shell
rarebl transfer --chain tron --from TYourAddress --to TRecipient --amount 25 \
--token TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t --key-env TRON_KEY --max-fee 100000000
```
**You should see** the transaction id. TRON transactions expire (see `expiresAt` in the build reply), so sign and broadcast promptly; the SDK does both in one call.
## Step 6 — Check the result
```bash cURL
curl "https://api.rarebl.com/open/tx/status?chain=btc&id=<txid>"
```
```python Python
print(c.status("btc", txid))
```
**You should see** `status: "pending"` with 0 confirmations, then `"success"`. For a push instead of polling use a `tx.confirmations` [webhook](https://docs.rarebl.com/guides/webhooks).
## Good to know
| Topic | What to do |
| --- | --- |
| Token decimals | USDT and USDC are built in. For any other token pass `decimals` explicitly: the SDK never takes decimals from the server. |
| Fee caps | `max_fee` / `maxFee` caps the fee in base units (sats, wei or sun). The SDK refuses a build that exceeds it. Without one, SDK 0.2.0 applies defaults: 1000 sat/vB on BTC and LTC, 50 sat/byte on BCH and DASH, ETH `maxFeePerGas` 500 gwei and 0.05 ETH in total, TRON `feeLimit` 150 TRX. |
| Pinned recipient | `transfer()` always checks the build against the recipient, amount and token you passed. Signing a build yourself (`signBuild`, `sign_build`, `Signer::signBuild`, `rarebl sign`) needs an `expect` with the recipient too: [Send a transaction](https://docs.rarebl.com/guides/send-a-transaction). |
| Legacy inputs | On BTC, LTC and DASH p2pkh inputs the SDK reads the input amount from the `prevTx` that `tx/build` delivers, after checking that it hashes to the input's txid. |
| Reproducible signatures | ECDSA is always deterministic. Schnorr (p2tr) adds random auxiliary data; pass `deterministic` (`--deterministic` in the shell) for byte-identical output. |
| Not broadcasting yet | Pass `broadcast=False` (`--no-broadcast` in the shell) to get the signed transaction, inspect it with [Decode](https://docs.rarebl.com/api/tx/decode), then [Broadcast](https://docs.rarebl.com/api/tx/broadcast). |
| Token contracts | USDT on `eth`: `0xdac17f958d2ee523a2206206994597c13d831ec7`. USDT on `tron`: `TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t`. Map others with [Assets](https://docs.rarebl.com/api/market/assets). |
## Common errors
The SDKs raise typed errors that carry `code`, `reason` and `detail`.
| Error | What it means | What to do |
| --- | --- | --- |
| `VerifyError` | The API's reply did not match what you asked for, a digest could not be recomputed, the fee is above the cap, or a legacy p2pkh input lacks its `prevTx`. Raised before any signature. | Do not sign. Retry, and report it with the request id (`meta.requestid` or `X-Request-Id`). |
| `InputError` | Signing a build yourself without `expect` (or without `expect.to`). `transfer()` does not raise it. | Pass the recipient and amount in `expect`, as in [Send a transaction](https://docs.rarebl.com/guides/send-a-transaction). |
| `API409` `not_watched` | BTC, LTC or BCH sender not active yet. | Wait for the watch, then retry. |
| `API422` `insufficient_funds` | Balance cannot cover the amount plus the fee (or gas). | Fund the address, or lower the amount. |
| `API422` `nonce_too_low`, `tx_expired` | ETH nonce behind, or a TRON transaction expired. | Run `transfer()` again. |
| `API422` `node_rejected` | The node refused it; read `detail`. | Often a fee below the relay minimum: raise `priority` or `feeRate`. |
| `API429` | Write limit: 6 per minute, burst 3. | Wait `Retry-After`; queue your payouts. |
More: [Responses & codes](https://docs.rarebl.com/responses-and-codes).
## Next steps
- See what `transfer()` does under the hood, or sign with an HSM: [Send a transaction](https://docs.rarebl.com/guides/send-a-transaction).
- Sweep many deposits: [Watch addresses, get UTXOs and balances](https://docs.rarebl.com/guides/watch-addresses).
- Follow confirmations: [Webhooks](https://docs.rarebl.com/guides/webhooks).
- Fix a payment that does not confirm: [Stuck transactions](https://docs.rarebl.com/guides/stuck-transactions).