In order to operate an RGB transfer it is necessary to exchange special files, called consignments, that represent the offline data used to move asset rights. Moreover, with NFTs and collectibles there could also be the need to exchange media files.
While this can be achieved using the Storm protocol, which is a powerful protocol for distributed storage, it is preferable not to rely on a single file exchange medium, in order for the RGB ecosystem to be as resilient as possible.
To provide a simple alternative, we defined this protocol. It can be easily implemented in almost any programming language and can use any storage system.
The protocol uses a JSON-RPC 2.0 interface over HTTP(s), with one call per request: JSON-RPC batches are not supported (see Protocol methods). In order to be compliant with the protocol, an implementation must support all the protocol methods.
The protocol is supported as a consignment endpoint type in the Invoice
provided by rgb-wallet.
Protocol version is 1.0.
Version 1.0 is not compatible with 0.2: clients and proxies must both be
updated, no 0.2 data is migrated, and transfers still in flight must be
completed or abandoned before the upgrade.
The rationale behind the protocol design, its limitations and the changes with
respect to version 0.2 are described in the v1.0 changes specification.
A transfer involves two parties, each using a proxy server of its choice:
- the recipient, which reserves an invoice on its proxy (the recipient proxy), where the sender uploads the consignment and media files
- the sender, which registers the signed TX on its proxy (the sender proxy), where the recipient posts its signed acknowledgement (ACK) and, in exchange, obtains the signed TX
The recipient proxy URLs are included in the invoice as transport endpoints, while the sender proxy URL is communicated to the recipient when the consignment is uploaded.
The signed TX is always encrypted end-to-end between the two parties, while consignment and media are encrypted according to the recipient preference and may therefore be in plaintext (see Encryption and Invoice). In both cases the proxies store and return opaque bytes, without inspecting them.
An invoice can receive up to a quota of transfers (1 for a single-use invoice), so the same invoice (and thus the same address or blinded UTXO) can be used for multiple transfers.
Recipient Sender
| |
|-- choose keys |
| |
|-- reserve invoice on all |
| recipient proxies |
| |
|-- create invoice including --->|
| selected pubkey |
| |
| choose keys --|
| |
| check invoice can accept --|
| transfers on recipient proxy |
| |
| compute proof of work --|
| |
| post transfer data --|
| (inc. signed TXs) |
| to sender proxy |
| |
| post signed consignment, --|
| related data and proof of |
| work to recipient proxy |
| |
|-- list invoice transfers on |
| recipient proxy |
| |
|-- get consignment and related |
| data from recipient proxy |
| |
|-- post signed ACK to sender |
| proxy (and get signed TX) |
| |
|-- broadcast TX |
| |
| get signed ACKs from sender --|
| proxy and store them |
| |
| broadcast TX (idempotent) --|
| |
|-- wait for confirmations --|
Donations are the exception: the signed TX is included in the consignment and broadcast by the sender right after uploading it, so neither the sender proxy nor the ACK are involved.
- invoice: the recipient's request to receive (see Invoice),
reserved on the recipient proxy via
invoice.prepare - invoice pubkey: the recipient pubkey included in the invoice; a fresh one is used for each invoice and it is the recipient pubkey for all the transfers to the invoice
- quota: the maximum number of transfers an invoice can receive
- transfer: a single transfer to an invoice, created on the recipient proxy by
a successful
consignment.postcall - beneficiary string: the canonical string form of the invoice beneficiary,
i.e. the chain network prefix (e.g.
bc,tb4,bcrt), a:, then eitherutxob:followed by the Baid64-encoded concealed seal (blind receives) orwvout:followed by the Baid64-encoded pay-to-vout payload (witness receives), in both cases including the-chunk separators, followed, for a witness beneficiary that includes an internal key, by+and the hex-encoded internal pubkey; the parsed beneficiary must be re-encoded in this form regardless of how it appears in the received invoice (e.g. without separators), and the plain bitcoin address must never be used - invoice ID: hex-encoded SHA-256 of the concatenation of the literal string
rgb-proxy-server/invoice/v1, the beneficiary string and the invoice pubkey - transfer ID: hex-encoded SHA-256 of the concatenation of the literal string
rgb-proxy-server/transfer/v1, the invoice ID and the sender pubkey - operation: the set of transfers sent with a single bitcoin TX, registered on
the sender proxy via
ack.prepare
Concatenations are made of the fields encoded as UTF-8 bytes, with no separators.
Example IDs, with invoice pubkey
5c298bf4b0dcf967871778ca682b4de838eb7a201853845c49adc15b10681360 and sender
pubkey e11a179a775712d6763f01de1e10a6141c08f098816aa89a0da333974bf8bc2b:
| receive | beneficiary string | invoice ID | transfer ID |
|---|---|---|---|
| blind | bc:utxob:4vm1CX2Z-K8hMo59-e7dgGBS-Jka7mYn-Xe~yP85-yUiHHxr-aVlYa |
6ffae5a533bad53bb7ff50152f8a282564fb87429a53384f1b20e88895e7faad |
285530722532e2fc802c0ad10bd381d3cca23f01af7d89fb03ec55f734a2a3e7 |
| witness | bc:wvout:A8cJ7Ww3-NIzADo3-Tzp_5aD-7CTBWmA-AAAAAAA-AAAAAAA-ALSQkcw |
4b4ef334f725afa5b09959a62518a09da375b0708c14f760745c241339b8d54f |
7ac01c41928a6a114e3320aa4bcbdedb9dd7dd6915fcf993ecd5544fdce1c00f |
- IDs, hashes and pubkeys are lowercase hex strings of 64 characters
- signatures are lowercase hex strings of 128 characters
- numbers (timestamps, sizes, quotas, difficulties) are carried as decimal
strings, except the
uptimeofserver.info, a JSON number:server.infokeeps the shape of its pre-v1.0 fields so that any client can parse it and detect the protocol version - timestamps are unix absolute timestamps, in seconds, UTC
- the signed TX and the returned files are standard base64 strings (with padding)
- hashes of files are SHA-256 hashes of their plaintext content
Methods uploading a file (consignment.post,
media.post and ack.prepare) use a multipart
form (see Protocol methods).
Each party holds an Ed25519 key pair, made of a secret key and the corresponding public key (pubkey):
- the recipient generates a fresh key pair for each invoice
- the sender generates a fresh key pair for each operation
Secret keys are never transmitted, so every pk field (and the pk invoice
query parameter) always refers to a pubkey.
A proxy can optionally be configured with a list of authentication pubkeys,
belonging to its operator(s) or authorized users, to restrict calls to
invoice.prepare and ack.prepare.
Every signature follows the same scheme:
- the message is the concatenation, encoded as UTF-8 bytes, of the fields defined for the method, with no separators
- the signed value is the 32-byte SHA-256 digest of the message
- the digest is signed with Ed25519 using the signer's secret key
| method | signer | message |
|---|---|---|
invoice.prepare (optional) |
authentication key | invoice_id, expires_at, recipient_pk, quota (1 if omitted) |
consignment.post |
sender key | transfer_id, consignment_hash, blob_hash, literal consignment |
media.post |
sender key | transfer_id, media_id, blob_hash, literal media |
transfer.list |
invoice key | invoice_id, timestamp |
transfer.retrieved |
invoice key | transfer_id, literal retrieved |
transfer.delete |
invoice key | transfer_id, literal delete |
ack.prepare (optional) |
authentication key | sender_pk, expires_at, signed_tx_hash, then for each transfer, sorted lexicographically by transfer ID: transfer_id, recipient_pk, consignment_hash |
ack.post |
invoice key | transfer_id, literal true or false (the ACK value), consignment_hash |
ack.get |
sender key | all the lexicographically sorted transfer_ids, timestamp |
ack.clean |
sender key | all the lexicographically sorted transfer_ids |
blob_hash is the hex-encoded SHA-256 of the uploaded (possibly encrypted)
file, which the proxy computes itself and is never transmitted.
The timestamp of transfer.list and ack.get
must be within 300 seconds of the proxy clock (see current_time in
server.info), otherwise the call is refused as invalid
(-200, with timestamp among the fields), unlike an
out-of-window pow_timestamp, which gets
-114.
Invoices (via invoice.prepare) and operations (via
ack.prepare) are reserved with an expiration (expires_at),
which can't be further in the future than the proxy max_expiry_secs (see
server.info).
After the expiration:
- no new transfers are accepted for an expired invoice and no signed TX is released for an expired operation
- the recipient proxy keeps the data of pending transfers (neither retrieved
nor deleted by the recipient) for
retention_grace_secs, along with the invoice reservation and its transfer records, needed to authenticate the recipient calls; once no pending transfer is left, all the invoice data can be deleted - the sender proxy deletes the signed TXs of operations not ACKed by all
recipients, while it keeps the operation data (ACK values, signatures and
released signed TXs) for
retention_grace_secs - after the retention grace, all the data can be deleted
- an invoice reservation is never deleted before its expiration
Consignment, media and signed TX are encrypted end-to-end between sender and recipient. Consignment and media encryption follows the recipient preference (see Invoice), while the signed TX is always encrypted.
Both parties derive the same shared secret via X25519 ECDH between their own secret key and the counterpart pubkey, converted from Ed25519 to X25519 with the standard Curve25519 birational map (RFC 7748):
- pubkey: the X25519 pubkey is the Montgomery
ucoordinate,u = (1 + y) / (1 - y) mod p, withp = 2^255 - 19, encoded as a 32-byte little-endian value - secret key: the Ed25519 secret key is the 32-byte seed (the RFC 8032 private
key, not libsodium's 64-byte secret key); the X25519 secret scalar is
clamp(SHA-512(seed)[0..32]), with standard Curve25519 clamping
Implementations should use a vetted conversion routine (e.g. libsodium's
crypto_sign_ed25519_pk_to_curve25519 / crypto_sign_ed25519_sk_to_curve25519,
@noble/curves edwardsToMontgomery* or curve25519-dalek to_montgomery).
The symmetric key is derived with HKDF-SHA-256 (RFC 5869):
PRK = HKDF-Extract(salt = <empty>, IKM = shared_secret)
key = HKDF-Expand(PRK, info = "rgb-proxy-server/enc/v1", L = 32)
The same key is used for consignment, media and signed TX.
Data is encrypted with XChaCha20-Poly1305 (one-shot AEAD, caller-supplied
nonce), in chunks of 65536 plaintext bytes. Each chunk is encrypted
independently and followed by its 16-byte tag. A plaintext of n bytes is
split into max(1, ceil(n / 65536)) chunks:
- every chunk except the last is exactly 65536 bytes
- the last (final) chunk holds the remaining bytes, from 1 to 65536 (a full
65536-byte final chunk when
nis a multiple of 65536, with no additional empty chunk) - an empty plaintext is encrypted as a single empty final chunk (just its 16-byte tag)
Each 24-byte chunk nonce is the concatenation of:
- a 19-byte base: the last 19 bytes of the raw (hex-decoded) SHA-256 hash of
the plaintext (
consignment_hash,signed_tx_hashor the media digest) - a 4-byte big-endian chunk counter, starting at 0
- a 1-byte final-chunk flag:
01for the last chunk,00otherwise
The blob is prefixed with a single-byte encryption-format version, currently
0x01, which selects the HKDF info label (rgb-proxy-server/enc/v1).
Example, for a 204800-byte plaintext:
| chunk | plaintext range (bytes) | plaintext size | counter | final flag | ciphertext size (incl. tag) |
|---|---|---|---|---|---|
| 0 | 0 - 65536 | 65536 | 00 00 00 00 |
00 |
65552 |
| 1 | 65536 - 131072 | 65536 | 00 00 00 01 |
00 |
65552 |
| 2 | 131072 - 196608 | 65536 | 00 00 00 02 |
00 |
65552 |
| 3 | 196608 - 204800 | 8192 | 00 00 00 03 |
01 |
8208 |
[0x01] 1 byte version prefix
[chunk 0 || tag] 65552 bytes
[chunk 1 || tag] 65552 bytes
[chunk 2 || tag] 65552 bytes
[chunk 3 || tag] 8208 bytes (final chunk, nonce final flag = 0x01)
To decrypt, the recipient reads the version prefix, derives the same key and
nonce base, then decrypts the 65552-byte ciphertext chunks in order, verifying
each tag. The final chunk, decrypted with the final flag 01, is the one that
ends the blob, identified by its position rather than by its size, since it can
also be a full 65552-byte chunk.
Media attachments are identified by a media_id, derived from the shared
secret rather than being the plaintext digest:
Kmid = HKDF-Expand(PRK, info = "rgb-proxy-server/enc/v1/media-id", L = 32)
media_id = hex(HMAC-SHA256(Kmid, media_digest))
where media_digest is the hex-encoded SHA-256 of the plaintext media, known
to both parties from the asset metadata and never sent to the proxy.
media_id is used for all media, both encrypted and cleartext.
For witness receives, the blinding factor of the seal assigning assets to the recipient output is derived from the shared secret instead of being chosen by the sender:
Kbld = HKDF-Expand(PRK, info = "rgb-proxy-server/enc/v1/seal-blinding", L = 8)
blinding = u64_le(Kbld)
where u64_le interprets the 8 bytes as an unsigned 64-bit little-endian
integer.
The sender uses blinding for the recipient witness seal, while the recipient
uses it, together with the script and the vout of the output, to identify the
output paying the invoice. For blind receives, the blinding factor is chosen by
the recipient and embedded in the invoice concealed seal.
The invoice (defined by the RGB invoice standard) carries, as query parameters:
pk: the invoice pubkey (mandatory)enc(optional): the recipient consignment and media encryption preference:- missing: encryption is mandatory, cleartext files are refused
0: encryption must not be used, encrypted files are refused1: encryption may be used, both are accepted (senders should default to encryption)
The invoice expiration is mandatory.
When creating an invoice, the recipient:
- generates a fresh key pair for the invoice and stores it (the seed)
- sets the invoice expiration, which must be the same as the
expires_atused to reserve the invoice on the recipient proxies, hence not further in the future than the lowestmax_expiry_secsamong them (minus some margin for clock skew) - reserves the invoice on all the transport endpoints it lists, via
invoice.prepare, with the same expiration, invoice pubkey and quota, before sharing the invoice; if a proxy refuses the reservation, it either lowers the expiration so that all proxies accept it or removes that transport endpoint from the invoice
When parsing an invoice, the sender:
- considers it invalid if
pkor the expiration is missing - encrypts the consignment and media according to
enc, or refuses the invoice if the requested mode is not acceptable - computes the invoice ID and calls
invoice.checkto learn whether the invoice can accept a transfer, since nothing in the invoice signals its quota - uses the pubkey and expiration from the invoice, never the data registered on the recipient proxy
Each proxy enforces the quota independently, so an invoice listing more than one transport endpoint can receive up to the sum of their quotas.
The recipient proxy can require a proof of work on
consignment.post, with a difficulty d that is 0 during
normal operation and rises while an invoice or the proxy is under attack.
The current difficulty for an invoice is returned by
invoice.check and the maximum one is advertised by
server.info (0 means proof of work is disabled).
The sender must find a pow_nonce (1 to 64 printable ASCII characters) such
that:
SHA-256(transfer_id || pow_timestamp || pow_nonce)
has at least d leading zero bits, where the input is the concatenation of the
three fields encoded as UTF-8 bytes, with no separators, and leading zero bits
are counted from the most significant bit of the first byte of the digest.
pow_timestamp must be within 300 seconds of the proxy clock.
pow_timestamp and pow_nonce are always required: when the difficulty is 0
any value is accepted. If the proof of work does not satisfy the current
difficulty, or pow_timestamp is outside the allowed window, the proxy returns
an invalid proof of work error including the
current difficulty, and the sender computes a new proof of work (with a fresh
pow_timestamp) and retries, with no need to rebuild the operation.
The recommended max_pow_difficulty is 24, which should take roughly 1 second
with native code on a mid-range phone and 3 to 8 seconds in a mobile browser.
Clients can refuse to compute proofs of work above their own limits.
| method | caller | proxy |
|---|---|---|
server.info |
both | both |
invoice.prepare |
recipient | recipient |
invoice.check |
sender | recipient |
consignment.post |
sender | recipient |
consignment.get |
recipient | recipient |
media.post |
sender | recipient |
media.get |
recipient | recipient |
transfer.list |
recipient | recipient |
transfer.retrieved |
recipient | recipient |
transfer.delete |
recipient | recipient |
ack.prepare |
sender | sender |
ack.post |
recipient | sender |
ack.get |
sender | sender |
ack.clean |
sender | sender |
All methods are JSON-RPC 2.0 requests sent via HTTP POST to the proxy
endpoint (e.g. https://proxy.example.com/json-rpc), with an
application/json body, except the methods uploading a file
(consignment.post, media.post and
ack.prepare), which use a multipart/form-data body: the file
is attached under the file name, while the JSON-RPC fields are sent as form
fields, with params as a JSON-encoded string, which must precede the file.
Proxies may also accept the parameters as one params[<name>] form field each,
but only the JSON-encoded params field is guaranteed, and it is the only
option for ack.prepare, whose transfer_data parameter is a nested object.
Proxies bound the number and size of the form fields of a request.
Each request carries a single call: a JSON-RPC batch (an array of calls) is
refused (-32600), because it would multiply the cost of
a request beyond what the limits of each call allow. For the same reason, a
JSON body larger than a call listing max_transfers IDs (see
server.info) is refused (-32600).
Clients must therefore send their calls one per request and disable any
automatic batching of their JSON-RPC library. Note that this has nothing to do
with the batch parameter, which flags an operation with more than one
transfer (multiple recipients) and is fully supported.
Upload checks, applied while receiving the request and right after the parameter validation:
- a file part arriving before
paramsis refused (-200, withfileamong the fields) - a declared
file_sizeabove the maximum is refused (-108):max_file_size(seeserver.info) forconsignment.postandmedia.post, theack.preparemaximum (seeack.prepare) forack.prepare - a missing file is refused (
-301) - the received file must be exactly
file_sizebytes, otherwise it's refused (-200, withfile_sizeamong the fields); a larger file is aborted as soon as it exceeds the declared size
When more than one validation error is possible, missing parameters are
reported first (-300) and invalid ones
(-200) only once all parameters are present. Both errors
list all the failing fields.
HTTP POST, application/json body.
{ "id": 0, "method": "server.info" }{
"id": 0,
"result": {
"protocol_version": "1.0",
"version": "1.0.0",
"uptime": 42069,
"current_time": "1791367200",
"max_expiry_secs": "2592000",
"max_quota": "100",
"max_pow_difficulty": "24",
"retention_grace_secs": "2592000",
"max_file_size": "104857600",
"max_signed_tx_size": "153600",
"max_transfers": "1000",
"max_media": "100"
}
}Result:
protocol_version: the supported protocol versionversion: the server versionuptime: the server uptime, in secondscurrent_time: the proxy clock, which clients can use to compute the clock skewmax_expiry_secs: the maximum number of seconds in the future anexpires_atcan be set tomax_quota: the maximum quota an invoice can be reserved withmax_pow_difficulty: the maximum proof of work difficulty the proxy can require (0if proof of work is disabled)retention_grace_secs: how long, after the expiration, the proxy guarantees to keep data awaiting retrieval (0if no grace is granted)max_file_size: the maximum size, in bytes, of a file uploaded viaconsignment.postormedia.post; it does not apply to the file ofack.prepare, which is bounded bymax_transfersandmax_signed_tx_sizeinstead (seeack.prepare)max_signed_tx_size: the maximum size of each signed TX inack.prepare, measured as the length of its base64 stringmax_transfers: the maximum number of transfers anack.preparecall can include, which also bounds the number of IDs ininvoice.check,ack.getandack.cleancallsmax_media: the maximum number of media attachments a transfer can have
Reserve an invoice, before sharing it, so that it can receive transfers.
HTTP POST, application/json body.
{
"id": 1,
"method": "invoice.prepare",
"params": {
"invoice_id": "a39abbd52b82bace937079982fa585941ce084a045d92bdb443643bc98235b3f",
"expires_at": "1791972000",
"recipient_pk": "5c298bf4b0dcf967871778ca682b4de838eb7a201853845c49adc15b10681360",
"quota": "1"
}
}Params:
invoice_id: the invoice IDexpires_at: the invoice expiration, which must be in the future and not exceedmax_expiry_secsrecipient_pk: the invoice pubkey, which authenticates the recipient ontransfer.list,transfer.retrievedandtransfer.deletequota: (optional) the maximum number of transfers (default1), between 1 andmax_quotasignature: (optional) required only if the proxy is configured with authentication pubkeys (see Keys and signatures)
When the invoice is newly reserved:
{ "id": 1, "result": true }When the invoice was already reserved with the same data:
{ "id": 1, "result": false }Behavior:
- an invoice ID can be reserved only once and the reservation can't be changed nor removed before its expiration
- the proxy cannot verify that
recipient_pkmatches the invoice ID: whoever reserves the invoice ID first owns the reservation
Errors:
Check whether one or more invoices can accept transfers and get the proof of work difficulty currently required.
HTTP POST, application/json body.
{
"id": 2,
"method": "invoice.check",
"params": {
"invoice_ids": [
"a39abbd52b82bace937079982fa585941ce084a045d92bdb443643bc98235b3f"
]
}
}Params:
invoice_ids: the invoice IDs to check
{
"id": 2,
"result": {
"a39abbd52b82bace937079982fa585941ce084a045d92bdb443643bc98235b3f": {
"status": "available",
"pow_difficulty": "0"
}
}
}Result: a map from invoice ID to:
status:unknown: the invoice is not known to the proxy (not reserved, or its data has been deleted)available: the invoice is reserved and its quota is not fullfull: the invoice is reserved and its quota is full
pow_difficulty: the proof of work difficulty currently required to create a transfer for the invoice
Behavior:
- the check is read-only: an
availablestatus doesn't guarantee that a laterconsignment.postwill succeed - the status doesn't consider the invoice expiration, which the sender knows from the invoice
- a request with more than
max_transfersinvoice IDs (seeserver.info) is refused (-109)
Senders should check each invoice on all the transport endpoints it lists,
typically stopping at the first one that returns available.
Errors:
Upload a consignment, creating a transfer to an invoice.
HTTP POST, multipart/form-data body.
In addition to the parameters, the (possibly encrypted) consignment file needs
to be attached under the file name via multipart form.
{
"id": 3,
"method": "consignment.post",
"params": {
"invoice_id": "a39abbd52b82bace937079982fa585941ce084a045d92bdb443643bc98235b3f",
"transfer_id": "6313972fcf4dfede897ed8cd932c579af0eb83c57ff8a8f6def81412ccd4ae0e",
"sender_transport_endpoint": "rpc://proxy.example.com/json-rpc",
"sender_pk": "e11a179a775712d6763f01de1e10a6141c08f098816aa89a0da333974bf8bc2b",
"consignment_hash": "0949f665e0563c0a769aee4dbea0f71f958b183985ab892e7745d3c67a0560a0",
"batch": false,
"file_size": "48213",
"signature": "e7e8be3d72de57e64156e95f2631a30a9eaa0e1a98863c2f1512affa5056f7dc9ca86f117e5621a817e6c8aebf1121f584c68bd5e5666aab2e85662632ef8fb6",
"pow_timestamp": "1791367210",
"pow_nonce": "0"
}
}Params:
invoice_id: the invoice ID of the transfertransfer_id: the transfer ID, derived frominvoice_idandsender_pksender_transport_endpoint: the sender proxy, in theRgbTransportformat (e.g.rpc://...), where the recipient will post its ACKsender_pk: the sender pubkey of the operationconsignment_hash: the hex-encoded SHA-256 of the plaintext consignmentbatch: whether the operation includes more than one transferfile_size: the size of the uploaded file, in bytessignature: the sender signature (see Keys and signatures)pow_timestamp: the proof of work timestamp (see Proof of work)pow_nonce: the proof of work nonce
For donations, the signed TX is included in the consignment.
When the transfer is newly created:
{ "id": 3, "result": true }When the identical transfer was already created:
{ "id": 3, "result": false }Behavior, in order:
- missing, then invalid parameters, then the upload checks
- an identical re-post returns
false, even aftertransfer.retrieved, without checking the proof of work - the invoice must be reserved (
-101) and not expired (-111),transfer_idmust match the one derived frominvoice_idandsender_pk(-200), the signature must be valid (-502) and the transfer ID must not have been deleted (-113) - if the transfer ID already exists with different data, the call is rejected
(
-100, listing the differing fields) - the invoice quota must not be full (
-112) - only if the current difficulty is greater than 0, the proof of work must be
valid (
-114)
Errors:
Get a consignment and the related data.
HTTP POST, application/json body.
{
"id": 4,
"method": "consignment.get",
"params": {
"transfer_id": "6313972fcf4dfede897ed8cd932c579af0eb83c57ff8a8f6def81412ccd4ae0e"
}
}Params:
transfer_id: the transfer ID
When a consignment is available, it is returned in base64-encoded form:
{
"id": 4,
"result": {
"consignment": "AQ8AHQEE/////wEA8gUqAQAAAENBBJa1OOhTUZxyapEeYewRYACuE5CBOmJ8ZvuL55R75jxS2nWJN5UV1OCmBPgUF4HiKUchFmv2Ih5zqCy/I0LIWO6sAAAAAA==",
"sender_transport_endpoint": "rpc://proxy.example.com/json-rpc",
"sender_pk": "e11a179a775712d6763f01de1e10a6141c08f098816aa89a0da333974bf8bc2b",
"consignment_hash": "0949f665e0563c0a769aee4dbea0f71f958b183985ab892e7745d3c67a0560a0",
"batch": false
}
}Result:
consignment: the (possibly encrypted) consignment filesender_transport_endpoint: the sender proxy, where to post the ACKsender_pk: the sender pubkey, needed to decrypt and to verify the transfer IDconsignment_hash: the hex-encoded SHA-256 of the plaintext consignmentbatch: whether the operation includes more than one transfer
Errors:
Upload a media attachment for a transfer.
HTTP POST, multipart/form-data body.
In addition to the parameters, the (possibly encrypted) media file needs to be
attached under the file name via multipart form.
{
"id": 5,
"method": "media.post",
"params": {
"transfer_id": "6313972fcf4dfede897ed8cd932c579af0eb83c57ff8a8f6def81412ccd4ae0e",
"media_id": "317da33ac74b113d3b315789a0ac5a7bcdc65d1b1b2e828a9b18aba185370756",
"file_size": "102400",
"signature": "3835b11be35eba3ac2f18624e2667be619d3b65170cb440a53b64ef15e2b864c48768de9fcd7c8fae7c90c0dc3d0a14b3f0f700316e842c31fd288a4a2fea68c"
}
}Params:
transfer_id: the transfer ID, which must have been created viaconsignment.postmedia_id: the media ID (see Media ID)file_size: the size of the uploaded file, in bytessignature: the sender signature, made with the key of thesender_pkof the transfer (see Keys and signatures)
On first successful upload:
{ "id": 5, "result": true }On successive successful uploads (of the same media file):
{ "id": 5, "result": false }Behavior:
- parameters are validated first, then the upload checks are applied
- media can be posted while the transfer exists, also after the invoice
expiration (e.g. for a transfer created right before it), as long as its
data has not been retrieved (
-403) - an identical re-post returns
false, also aftertransfer.retrieved, and doesn't count towardsmax_media - a new media is refused (
-116) when the transfer already hasmax_mediamedia (seeserver.info)
Errors:
Get a media attachment.
HTTP POST, application/json body.
{
"id": 6,
"method": "media.get",
"params": {
"transfer_id": "6313972fcf4dfede897ed8cd932c579af0eb83c57ff8a8f6def81412ccd4ae0e",
"media_id": "317da33ac74b113d3b315789a0ac5a7bcdc65d1b1b2e828a9b18aba185370756"
}
}Params:
transfer_id: the transfer IDmedia_id: the media ID
When a media is available, the (possibly encrypted) file is returned in base64-encoded form:
{
"id": 6,
"result": "AQsAHdEE+P9/AQDyBSoBAAAAQ0EElrU46FNRnHJqkR5h7BFgAK4TkIE6YnxmPx+NBVpdTyaovx4fsKR2AXCzvlRvXd9cVZpKAQ=="
}Errors:
List the transfers of an invoice.
HTTP POST, application/json body.
{
"id": 7,
"method": "transfer.list",
"params": {
"invoice_id": "a39abbd52b82bace937079982fa585941ce084a045d92bdb443643bc98235b3f",
"timestamp": "1791367500",
"signature": "82763abbe89e96738d25c643c22e2cddc8381b6cddfa8ce61d59bfc3798eb15db642285928605af3cbcb8b887780d72f9124a6003ba006a0811782d55dc39ef7"
}
}Params:
invoice_id: the invoice IDtimestamp: the current time, within 300 seconds of the proxy clocksignature: the recipient signature, made with the invoice key (see Keys and signatures)
{
"id": 7,
"result": {
"6313972fcf4dfede897ed8cd932c579af0eb83c57ff8a8f6def81412ccd4ae0e": "pending",
"ccde6c4ddc6ebc8add7a88f1f917d85f01881a1cd855a9cdaf87d6fa1b3b38c8": "retrieved"
}
}Result: a map from transfer ID to its state:
pending: the transfer data (consignment and media) is availableretrieved: the transfer data has been deleted viatransfer.retrieved, while the transfer still counts towards the invoice quota
Deleted transfers are not listed.
Errors:
The -101 error is returned before verifying the
signature, allowing the recipient to distinguish a reservation lost in a proxy
data wipe from one taken by someone else. After the invoice expiration, it can
also mean that the proxy deleted the invoice data because no transfer was
pending.
Signal that the data of a transfer has been retrieved and saved, so the proxy deletes its consignment and media, while the transfer keeps counting towards the invoice quota.
HTTP POST, application/json body.
{
"id": 8,
"method": "transfer.retrieved",
"params": {
"transfer_id": "6313972fcf4dfede897ed8cd932c579af0eb83c57ff8a8f6def81412ccd4ae0e",
"signature": "82763abbe89e96738d25c643c22e2cddc8381b6cddfa8ce61d59bfc3798eb15db642285928605af3cbcb8b887780d72f9124a6003ba006a0811782d55dc39ef7"
}
}Params:
transfer_id: the transfer IDsignature: the recipient signature, made with the invoice key (see Keys and signatures)
When the transfer has been marked as retrieved:
{ "id": 8, "result": true }When the transfer had already been marked as retrieved or deleted:
{ "id": 8, "result": false }Behavior:
- after this call
consignment.getandmedia.getreturn-403, while identical re-posts are still recognized - a retrieved transfer can still be deleted via
transfer.delete
Errors:
The -402 error is returned before verifying the
signature, since the pubkey to verify it with is only known for an existing
transfer.
Delete a transfer (e.g. a settled, refused or fake one), removing its consignment and media and freeing one slot of the invoice quota.
HTTP POST, application/json body.
{
"id": 9,
"method": "transfer.delete",
"params": {
"transfer_id": "6313972fcf4dfede897ed8cd932c579af0eb83c57ff8a8f6def81412ccd4ae0e",
"signature": "82763abbe89e96738d25c643c22e2cddc8381b6cddfa8ce61d59bfc3798eb15db642285928605af3cbcb8b887780d72f9124a6003ba006a0811782d55dc39ef7"
}
}Params:
transfer_id: the transfer IDsignature: the recipient signature, made with the invoice key (see Keys and signatures)
When the transfer has been deleted:
{ "id": 9, "result": true }When the transfer had already been deleted:
{ "id": 9, "result": false }Behavior:
- a transfer can be deleted both while pending and after being retrieved
- a deleted transfer ID can't be used again (see
-113) - the invoice reservation is never deleted by this call
Errors:
The -402 error is returned before verifying the
signature, since the pubkey to verify it with is only known for an existing
transfer.
Register an operation on the sender proxy, with the data needed to verify the recipients' ACKs and the signed TX to release once all of them have ACKed. Not needed for donations.
HTTP POST, multipart/form-data body.
In addition to the parameters, the signed TXs need to be attached under the
file name via multipart form. The file is a JSON object mapping each transfer
ID to its signed TX, encrypted for the recipient and base64-encoded, with
exactly the transfer IDs of transfer_data:
{
"6313972fcf4dfede897ed8cd932c579af0eb83c57ff8a8f6def81412ccd4ae0e": "AQHrCMd+mQ6ZeYBnUo2Jf7d8pY1tL4bX2hKzQ0vV3wQ5E6iRgS1u8aPcN7yTjW+0fA=="
}The parameters are:
{
"id": 10,
"method": "ack.prepare",
"params": {
"transfer_data": {
"6313972fcf4dfede897ed8cd932c579af0eb83c57ff8a8f6def81412ccd4ae0e": {
"recipient_pk": "5c298bf4b0dcf967871778ca682b4de838eb7a201853845c49adc15b10681360",
"consignment_hash": "0949f665e0563c0a769aee4dbea0f71f958b183985ab892e7745d3c67a0560a0"
}
},
"expires_at": "1791710000",
"signed_tx_hash": "cbcc7a7e01884c0493372da517f21cf52d90bf9715868086a1bdbaea5ddde5f6",
"sender_pk": "e11a179a775712d6763f01de1e10a6141c08f098816aa89a0da333974bf8bc2b",
"file_size": "139"
}
}Params:
transfer_data: a map from transfer ID to:recipient_pk: the invoice pubkey of the transferconsignment_hash: the hex-encoded SHA-256 of the plaintext consignment
expires_at: the operation expiration, which must be in the future and not exceedmax_expiry_secssigned_tx_hash: the hex-encoded SHA-256 of the plaintext signed TX (the same for all transfers)sender_pk: the sender pubkey of the operation, which authenticates the sender onack.getandack.cleansignature: (optional) required only if the proxy is configured with authentication pubkeys (see Keys and signatures)file_size: the size of the file, in bytes
When the operation is newly registered:
{ "id": 10, "result": true }When the identical operation was already registered:
{ "id": 10, "result": false }Behavior:
- a transfer ID can belong to a single operation: a request including a
transfer ID already registered to a different operation, or only a subset of
the transfer IDs of an operation, is refused
(
-102) - a request with the same transfer IDs as an existing operation but a
differing field is refused (
-100, listing the differing fields) - the proxy refuses requests with more than
max_transferstransfers (-109) or with a signed TX larger thanmax_signed_tx_size(-110, seeserver.info) max_file_sizedoes not apply to the file: its maximum size ismax_transferstimes (max_signed_tx_sizeplus 70 bytes, the transfer ID, its JSON punctuation and the separator of each entry), and a proxy accepts at least that (it can allow a little more, e.g. for whitespace); a larger declaredfile_sizeis refused (-108)- the file must be a JSON object of base64 strings with exactly the transfer
IDs of
transfer_data: a file that isn't (-200, withfileamong the fields), a transfer without a signed TX (-300, withsigned_tx) or a signed TX that isn't valid base64 or for another transfer (-200, withsigned_tx) are refused - the checks run in this order: the request (a single call, with a JSON-RPC
envelope within the size limit), the parameters (missing ones first, then
invalid ones),
max_transfers, the upload checks (see Protocol methods), thesignature(if the proxy requires it), the content of the file (includingmax_signed_tx_size) and lastly the registration of the operation; the signature does not cover the signed TXs, so it is verified before the file is read, and a caller that is not allowed doesn't make the proxy read it - an identical re-post returns
falseonly until the expiration and while the signed TXs have not been removed (byack.cleanor after a NACK); after that it's refused (-200or-100)
Errors:
Post the signed ACK (accept) or NACK (reject) for a transfer and, on ACK, get the signed TX.
HTTP POST, application/json body.
To accept a transfer:
{
"id": 11,
"method": "ack.post",
"params": {
"transfer_id": "6313972fcf4dfede897ed8cd932c579af0eb83c57ff8a8f6def81412ccd4ae0e",
"ack": true,
"signature": "e7e8be3d72de57e64156e95f2631a30a9eaa0e1a98863c2f1512affa5056f7dc9ca86f117e5621a817e6c8aebf1121f584c68bd5e5666aab2e85662632ef8fb6"
}
}To reject a transfer:
{
"id": 11,
"method": "ack.post",
"params": {
"transfer_id": "6313972fcf4dfede897ed8cd932c579af0eb83c57ff8a8f6def81412ccd4ae0e",
"ack": false,
"signature": "3835b11be35eba3ac2f18624e2667be619d3b65170cb440a53b64ef15e2b864c48768de9fcd7c8fae7c90c0dc3d0a14b3f0f700316e842c31fd288a4a2fea68c"
}
}Params:
transfer_id: the transfer IDack: whether the transfer is accepted (true) or rejected (false)signature: the recipient signature, made with the invoice key (see Keys and signatures)
On ACK, when the signed TX is released:
{
"id": 11,
"result": {
"result": true,
"sender_pk": "e11a179a775712d6763f01de1e10a6141c08f098816aa89a0da333974bf8bc2b",
"signed_tx_hash": "cbcc7a7e01884c0493372da517f21cf52d90bf9715868086a1bdbaea5ddde5f6",
"signed_tx": "AQHrCMd+mQ6ZeYBnUo2Jf7d8pY1tL4bX2hKzQ0vV3wQ5E6iRgS1u8aPcN7yTjW+0fA==",
"expires_at": "1791710000"
}
}On ACK, when the signed TX is not (yet) released:
{
"id": 11,
"result": {
"result": true,
"sender_pk": "e11a179a775712d6763f01de1e10a6141c08f098816aa89a0da333974bf8bc2b",
"signed_tx_hash": "cbcc7a7e01884c0493372da517f21cf52d90bf9715868086a1bdbaea5ddde5f6",
"expires_at": "1791710000"
}
}On NACK:
{
"id": 11,
"result": {
"result": true,
"expires_at": "1791710000"
}
}Result:
result:trueon the first successful post,falseon successive posts of the same ACK value and signaturesender_pk: (only on ACK) the sender pubkey registered viaack.prepare, to be used to decrypt the signed TXsigned_tx_hash: (only on ACK) the hex-encoded SHA-256 of the plaintext signed TX, needed to decrypt and verify the signed TXsigned_tx: (optional) the signed TX, encrypted for the recipient, base64-encodedexpires_at: the operation expiration, which, together with the sender proxy'sretention_grace_secs, sets the broadcast deadline (see Recipient)
Behavior:
- the proxy verifies the signature against the
recipient_pkand theconsignment_hashregistered for the transfer - the signed TX is released only once all the transfers of the operation have been ACKed (the last recipient to ACK gets it right away, the others by posting the same ACK again) and only before the operation expiration
- after a NACK, the signed TX is never released
- a new ACK or NACK (not an identical re-post) for an expired operation is
refused (
-105) - successive posts with the same data return the same data, also after the
expiration and until the end of the retention grace, including a released
signed TX (an ACK that would still be waiting for the signed TX instead
returns
-105once the operation has expired)
Errors:
Get the ACKs and their signatures for an operation.
HTTP POST, application/json body.
{
"id": 12,
"method": "ack.get",
"params": {
"transfer_ids": [
"6313972fcf4dfede897ed8cd932c579af0eb83c57ff8a8f6def81412ccd4ae0e",
"ccde6c4ddc6ebc8add7a88f1f917d85f01881a1cd855a9cdaf87d6fa1b3b38c8"
],
"timestamp": "1791367800",
"signature": "e7e8be3d72de57e64156e95f2631a30a9eaa0e1a98863c2f1512affa5056f7dc9ca86f117e5621a817e6c8aebf1121f584c68bd5e5666aab2e85662632ef8fb6"
}
}Params:
transfer_ids: the transfer IDs, all belonging to the same operationtimestamp: the current time, within 300 seconds of the proxy clocksignature: the sender signature, made with the key of thesender_pkregistered viaack.prepare(see Keys and signatures)
{
"id": 12,
"result": {
"acks": {
"6313972fcf4dfede897ed8cd932c579af0eb83c57ff8a8f6def81412ccd4ae0e": true
},
"signatures": {
"6313972fcf4dfede897ed8cd932c579af0eb83c57ff8a8f6def81412ccd4ae0e": "e7e8be3d72de57e64156e95f2631a30a9eaa0e1a98863c2f1512affa5056f7dc9ca86f117e5621a817e6c8aebf1121f584c68bd5e5666aab2e85662632ef8fb6"
}
}
}Result:
acks: a map from transfer ID to its ACK value (truefor ACK,falsefor NACK)signatures: a map from transfer ID to the recipient signature
Transfers that have not been responded to yet are not included.
Behavior:
- a request with more than
max_transferstransfer IDs (seeserver.info) is refused (-109) - a request with an unknown transfer ID, or with transfer IDs belonging to more
than one operation, is refused (
-104) - once the operation has expired, the set of responses is final
- the operation data is kept until the retention grace after the expiration has passed
Errors:
Remove the signed TXs of an operation whose signed TX has not been released yet (not all the transfers have been ACKed), so that it can't be released anymore.
HTTP POST, application/json body.
{
"id": 13,
"method": "ack.clean",
"params": {
"transfer_ids": [
"6313972fcf4dfede897ed8cd932c579af0eb83c57ff8a8f6def81412ccd4ae0e",
"ccde6c4ddc6ebc8add7a88f1f917d85f01881a1cd855a9cdaf87d6fa1b3b38c8"
],
"signature": "3835b11be35eba3ac2f18624e2667be619d3b65170cb440a53b64ef15e2b864c48768de9fcd7c8fae7c90c0dc3d0a14b3f0f700316e842c31fd288a4a2fea68c"
}
}Params:
transfer_ids: all the transfer IDs of the operationsignature: the sender signature, made with the key of thesender_pkregistered viaack.prepare(see Keys and signatures)
When the signed TXs have been removed:
{ "id": 13, "result": true }When there was no signed TX left:
{ "id": 13, "result": false }Behavior:
- the call is refused once all the transfers have been ACKed (the signed TX has been released); it's allowed after partial responses, while after a NACK there is no signed TX left to remove
ack.cleanandack.postcalls for the same operation are processed atomically: either the signed TXs are removed before the last ACK is registered, so no TX is released, or the ACK is registered first andack.cleanis refused- a request with more than
max_transferstransfer IDs (seeserver.info) is refused (-109) - a request whose transfer IDs aren't exactly all those of a single operation
is refused (
-104) - the operation record is kept, so its transfer IDs can't be reused and later
ack.postcalls don't release any TX
Errors:
The following rules are not enforced by the proxies but are required for clients to interoperate correctly.
The sender:
- uses a fresh key pair for each operation and includes at most one transfer per invoice in an operation
- calls
invoice.checkbefore building the operation - derives the witness seal blinding factor (see Witness seal blinding)
- sets the
ack.prepareexpiration sufficiently before the earliest invoice expiration among the operation recipients, leaving some margin to detect the ACKs and broadcast the TX - calls
ack.preparebeforeconsignment.post(except for donations) - doesn't broadcast the TX, including for donations, until the calls to
consignment.postandmedia.posthave succeeded - doesn't create a transfer after the invoice expiration
- if a transfer cannot be created on a transport endpoint (e.g.
-111,-112,-113,-115), tries the other transport endpoints listed in the invoice; only if none accepts it, fails the operation, callingack.cleanfirst for non-donation operations; for a donation whose consignment has already been posted for another transfer of the operation, it can only fail the operation after spending one of the TX inputs, otherwise it completes it and hands the missing consignment over by other means - verifies the ACK signatures returned by
ack.getand saves them, together with the invoices, as proof of acceptance - keeps the consignment, so it can be handed over by other means if needed
Failing an operation:
- before its expiration, an operation can only be failed after a successful
ack.clean - after its expiration (plus some margin for clock skew), the sender must call
ack.get, within the retention grace:- if at least one recipient has not ACKed, the TX was never released and the operation can be failed
- if every recipient has ACKed, the TX was released: before the invoice expiration of every recipient, the sender progresses the operation, broadcasting the TX; otherwise it doesn't broadcast it and either progresses the operation if the TX is found on-chain or fails it after spending one of the TX inputs; since the TX was certainly released (unlike when the operation is lost or an ACK signature is invalid), failing it without invalidating the TX is not allowed, and a sender that can't spend one of its inputs keeps the operation pending until the TX confirms or becomes invalid
When the sender proxy no longer knows an operation
(-104 for a registered operation), the sender
waits until the expiration plus the sender proxy's retention_grace_secs
has passed (plus some margin for clock skew), so that recipients that obtained
the TX (or still can) have time to broadcast it, then progresses the operation
if the TX is found on-chain (mempool included) or fails it, either accepting
the risk that a recipient that obtained the TX broadcasts it later or after
spending one of the TX inputs.
When an ACK signature returned by ack.get is invalid, the sender
doesn't trust the ACK nor broadcasts the TX, skips ack.clean and
handles the operation as when the sender proxy no longer knows it, as the
signed TX may have already been released.
Definition:
- the broadcast deadline (
D) is the operationexpires_atreturned byack.postplus the sender proxy'sretention_grace_secs - the confirmation margin is an additional waiting time after
D, chosen by the client (e.g. one day), that gives a TX broadcast shortly beforeDtime to be confirmed and also covers clock differences
The recipient:
- uses a fresh key pair for each invoice and reserves the invoice on all the transport endpoints listed in it before sharing it (see Invoice)
- discovers the transfers via
transfer.listand retrieves them before the end of the retention grace following the invoice expiration - fails a transfer whose data the proxy deleted before it was downloaded or, for a donation, whose TX has already been broadcast, obtains the consignment from the sender by other means
- verifies that the transfer ID matches the one derived from the invoice ID and
the
sender_pkreturned byconsignment.get - keeps, for each invoice, the sender pubkeys of the accepted transfers and refuses a transfer with an already seen sender pubkey unless it has the same consignment hash (i.e. it's a retry of the same transfer)
- refuses a transfer whose allocation paying the invoice (operation ID and assignment index) has already been credited for a different transfer ID
- for witness invoices, identifies the output paying the invoice by its script, vout and derived blinding factor, refusing the transfer if no such output exists
- for blind invoices, identifies the allocation paying the invoice by the
invoice concealed seal and keeps the blinded UTXO unspent until the invoice
expiration has passed (plus some margin for clock skew) and, on each
transport endpoint, either a
transfer.listmade after that point shows all transfers retrieved and processed (a-101at that point can't distinguish a deletion with nothing pending from a data wipe, so the recipient chooses whether to treat it as nothing pending or to wait for that proxy's retention grace) or that proxy's retention grace has passed, and all the accepted transfers have been settled or failed; donations count as settled once their TX confirms (the recipient broadcasts it itself after downloading the transfer) and as failed only once their TX can no longer confirm; a non-donation transfer downloaded after the blinded UTXO has been spent must be refused (NACK), while a donation's assets are lost if its TX confirms - once the data of a transfer has been durably saved, calls
transfer.retrieved, ortransfer.deleteto also free its quota slot (e.g. for refused or fake transfers) - before posting an ACK, reads
retention_grace_secsfrom the sender proxy'sserver.infoand posts a NACK instead if it's below its acceptable minimum - posts a signed ACK or NACK via
ack.postto the sender transport endpoint (except for donations) and, as soon as the signed TX is returned, decrypts, verifies and broadcasts it, saving it for later re-broadcasts - rebroadcasts the signed TX, if needed, only until the broadcast deadline
D: then, if the TX gets confirmed byDplus the confirmation margin, it progresses the transfer, otherwise it fails it - periodically verifies, until the invoice expiration, that its reservations are still in place on all the transport endpoints, reserving the invoice again after a proxy data wipe
- takes into account that, when the invoice lists more than one transport endpoint, the total number of transfers can exceed the quota, since each proxy enforces it independently; extra non-donation transfers can be refused, while a strict global limit requires listing a single transport endpoint
After a successful ACK without the signed TX (e.g. a batch operation not yet ACKed by every recipient, or a non-compliant proxy), the transfer waits for the broadcast and the recipient posts the same ACK again until:
- the signed TX is returned: it broadcasts it and progresses the transfer
-105is returned: the TX has never been released and never will be, so it fails the transfer-104is returned, or no definite answer is obtained byD: it waits untilDplus the confirmation margin, then progresses the transfer if the TX has been confirmed, otherwise it fails it
A recipient may fail a non-donation transfer before posting its ACK, typically by posting a NACK.
Before failing a transfer, both parties check whether its TX is on-chain and, if so, progress it instead; once failed, a transfer is final.
Errors follow JSON-RPC 2.0 rules and their codes are divided in 5 categories: State, Invalid, Missing, Not found and Security, plus the standard JSON-RPC errors for requests that aren't valid calls.
When present, error.data contains the list of failing fields (fields) or,
for -114, the current difficulty
(pow_difficulty).
The fields are parameter names: for values nested in a parameter (entries of
transfer_data, items of transfer_ids) the inner name is used (e.g.
recipient_pk, transfer_id), while file refers to the uploaded file (e.g.
a different file in -100).
{
"id": 1,
"error": {
"code": -100,
"message": "Cannot change",
"data": {
"fields": ["expires_at"]
}
}
}{ "id": 1, "error": { "code": -101, "message": "Invoice not reserved" } }{ "id": 1, "error": { "code": -102, "message": "Transfer ID in use" } }{ "id": 1, "error": { "code": -104, "message": "Operation not found" } }{ "id": 1, "error": { "code": -105, "message": "Operation expired" } }{ "id": 1, "error": { "code": -107, "message": "Already acked" } }The declared file_size exceeds the maximum: max_file_size for
consignment.post and media.post, the
ack.prepare maximum for ack.prepare (see
server.info).
{ "id": 1, "error": { "code": -108, "message": "File too large" } }The request includes more than max_transfers transfers (ack.prepare) or IDs
(invoice.check, ack.get, ack.clean), see server.info.
{ "id": 1, "error": { "code": -109, "message": "Too many items" } }A signed TX in ack.prepare exceeds max_signed_tx_size (see
server.info).
{ "id": 1, "error": { "code": -110, "message": "Signed TX too large" } }{ "id": 1, "error": { "code": -111, "message": "Invoice expired" } }{ "id": 1, "error": { "code": -112, "message": "Invoice quota exceeded" } }{ "id": 1, "error": { "code": -113, "message": "Transfer ID deleted" } }{
"id": 1,
"error": {
"code": -114,
"message": "Invalid proof of work",
"data": {
"pow_difficulty": "18"
}
}
}The proxy doesn't accept new transfers, other calls keep working.
{ "id": 1, "error": { "code": -115, "message": "Server draining" } }The transfer already has max_media media (see server.info).
{ "id": 1, "error": { "code": -116, "message": "Too many media" } }{
"id": 1,
"error": {
"code": -200,
"message": "Invalid",
"data": {
"fields": ["invoice_id", "timestamp"]
}
}
}{
"id": 1,
"error": {
"code": -300,
"message": "Missing",
"data": {
"fields": ["signature"]
}
}
}{ "id": 1, "error": { "code": -301, "message": "Missing file" } }{ "id": 1, "error": { "code": -400, "message": "Consignment not found" } }{ "id": 1, "error": { "code": -401, "message": "Media not found" } }{ "id": 1, "error": { "code": -402, "message": "Transfer not found" } }{ "id": 1, "error": { "code": -403, "message": "Transfer data retrieved" } }{
"id": 1,
"error": {
"code": -501,
"message": "Invalid proxy auth signature"
}
}{
"id": 1,
"error": {
"code": -502,
"message": "Invalid endpoint auth signature"
}
}{ "id": 1, "error": { "code": -503, "message": "Invalid content signature" } }These errors come from the request itself, not from a method, so their id is
null.
The request isn't a single, valid JSON-RPC call: it is a JSON-RPC batch (an
array of calls), or not an object, or its JSON body is larger than a call
listing max_transfers IDs can be (see Protocol methods).
{ "id": null, "error": { "code": -32600, "message": "Invalid Request" } }