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).

Back to the Rarebl AI documentation index