Docs
v1

Guide

Accept and send payments

Integrate TRAI deposits and withdrawals on a server with the wallet daemon. Addresses per customer, crediting, withdrawals that never pay twice.

On this page

Exchanges, shops and other services that hold TRAI run the wallet as a daemon. tenrai-wallet daemon keeps the keys, watches the wallet's addresses, and offers JSON-RPC over HTTP and events over WebSocket.

If you would rather sign in your own process, the SDK wallet does the same in JavaScript.

Start the daemon

Shell
tenrai-wallet init --generate-password
tenrai-wallet daemon --confirmations 3

init creates the wallet of this machine and prints its 24 recovery words once; --generate-password keeps a random password beside the wallet file, so the daemon opens it unattended. The daemon listens on 127.0.0.1:28703 and writes a bearer token beside the wallet file on first start: ~/.tenrai/wallet/mainnet/wallet.vault.token on Linux. --file, --password-file and --token-file name a wallet, a password and a token in other places.

It uses the node on this machine unless --service names another, and waits for it instead of switching to the public API; a service with many addresses needs its own node.

Shell
curl http://127.0.0.1:28703/v1/rpc \
  -H "Authorization: Bearer $(cat ~/.tenrai/wallet/mainnet/wallet.vault.token)" \
  -H "content-type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"new_address","params":{"label":"customer-17"}}'

Amounts are strings in TRAI with up to 8 decimals, such as "1.25". Keep them as decimals or integer atoms on your side; a float cannot hold 0.1 TRAI exactly.

Deposits

WayHowGood for
An address per customernew_address with a label, stored with the customerExchanges
One shared addressThe customer puts a reference into the payment's payloadShops, invoices

For an invoice, hand out a payment request that carries the amount and the reference.

Credit a deposit

Subscribe to the daemon's events on ws://127.0.0.1:28703/v1/ws:

JSON
{"jsonrpc":"2.0","id":1,"method":"subscribe","params":{"events":["unconfirmed","removed","finalized","confirmations"]}}
Wallet daemon events for an incoming payment. unconfirmed: the payment is in the mempool; show it, but do not credit it. finalized: a cut executed it; credit it. confirmations: it reached the number of cuts set with --confirmations, for policies that want more. removed: it left the mempool without being finalized; drop it. PENDING FINAL unconfirmed in the mempool: show it, do not credit finalized a cut executed it: credit it, once confirmations N cuts later, for a house policy that asks for more removed: never finalized, drop it
The events of one incoming payment. Credit on finalized.
EventDo
unconfirmedShow the payment as incoming. Do not credit
removedIt left the mempool without being finalized. Drop it
finalizedThe payment is final. Credit it
confirmationsIt reached --confirmations cuts. Credit here instead if your policy asks for more than one

One finalized cut is already final on Tenrai. A higher --confirmations is house policy, not a safety requirement.

A finalized event lists its payments with transaction, output, amount, payload_text and senders. Key your ledger by transaction and output, so a payment can never be credited twice. A chat message from the messenger is a payment of one atom with messenger set to true and no payload fields.

Catch up after a restart

Events are prompts, not a ledger. Store the indexed_height of your last read and ask for everything after it whenever you start, reconnect or receive resync_required:

JSON
{"jsonrpc":"2.0","id":2,"method":"received","params":{"since_height":8812}}

Withdrawals

Store a request ID of your own before you send, then pass it along:

JSON
{"jsonrpc":"2.0","id":3,"method":"send","params":{"to":"trai1…","amount":"1.25","request_id":"withdrawal-8812"}}
JSON
{"jsonrpc":"2.0","id":3,"result":{"kind":"payment","to":"trai1…","amount":"1.25000000","fee":"0.00012340","fee_per_mass":"2","transaction":"…","submitted":true}}

Repeating a request_id returns the first result with "duplicate": true instead of paying again. That makes the call safe to repeat after a timeout.

Error codeMeaningDo
no answerDaemon not reachableRepeat with the same request_id
-32001The node is down, busy or not synchronizedRepeat later
-32002The outcome of the submission is unknown. The daemon keeps sending the transactionRepeat only with the same request_id
-32003Another operation holds the walletRepeat in a moment
-32000Refused, for example not enough fundsDo not repeat as is
-32004The node refused the transactionDo not repeat as is
-32602Invalid address, amount or fieldFix the call

The settled event, or the transaction call after a restart, tells you when a withdrawal is final. send_many pays up to seven recipients in one transaction, and "subtract_fee": true takes the fee out of the amount.

Validate a customer's withdrawal address first with validate_address. It also returns the address's verification words to show for comparison.

Many small outputs

A transaction spends at most 8 outputs. A wallet that takes many small deposits needs to merge them:

OptionEffect
--auto-compound NMerges every address that holds more than N outputs
"compound_first": true on sendMerges what this payment needs, then pays
--auto-sweep-to ADDRESSForwards every finalized payment to a cold wallet

Address ownership

To confirm that a customer controls a withdrawal address, send a challenge and check the signed answer:

JSON
{"jsonrpc":"2.0","id":4,"method":"proof_request","params":{"address":"trai1…"}}
{"jsonrpc":"2.0","id":5,"method":"verify_address_proof","params":{"proof":{…},"request":{…}}}

The customer answers the request in the Tenrai Wallet under Settings, Ownership or with tenrai-wallet prove-address. Multisig addresses have no single key and cannot be proven this way. TCC-0004 specifies the two documents and the checks.

Security

TopicRule
Token<wallet file>.token allows every call, payments included. Only the service account may read it
NetworkKeep the daemon on loopback. --allow-remote needs TLS in front
Payloadspayload_text comes from the sender. Escape it in HTML, pass it to SQL only as a parameter
Hot walletKeep only what withdrawals need; sweep the rest to a wallet that is offline

Methods

GroupMethods
Walletstatus (with node_synced and node_behind_by), balance, pending, rescan
Addressesnew_address, receive, addresses, set_label, validate_address
Incomingreceived, incoming, outputs, history, transaction
Outgoingsend, send_many, sweep, burn, compound
Stakingvalidators, positions, stake, unstake, withdraw
Ownershipproof_request, prove_address, verify_address_proof
Eventssubscribe, unsubscribe

params is an object; unknown fields are refused. GET /health needs no token.

Type to search guides, concepts and reference pages.