STREAM on Dassie
STREAM is how money and data travel over Interledger: it splits a payment into small packets, encrypts what rides in them, and has the receiver say what arrived. Dassie speaks it the standard way, so the Interledger tools you know can pay a Dassie node and be paid by one.
On this page
Compatibility
Dassie implements STREAM as
RFC 29
describes it, in one Rust crate,
dassie-stream.
Everything in Dassie that speaks STREAM runs that one crate: a node's
receivers, which take payments into its ledgers; its payers, which
send them; the quotes that measure a route's rate; and the
STREAM Toy on this site.
On the wire it matches ilp-protocol-stream, the
reference implementation most Interledger software runs. Three kinds
of test keep it that way:
-
Interop tests run
dassie-streamagainstilp-protocol-streamin both directions, as a native build and as the WebAssembly build browsers load. -
End-to-end tests pay real Dassie nodes with
ilp-protocol-streamoverilp-plugin-btp, across one node and across several. - Golden vectors pin the bytes of STREAM packets and their encryption.
How Dassie is tested against the reference implementations
Paying a Dassie node
You get STREAM credentials from a node through
Open Payments: an incoming
payment lists a method of type ilp, with an
ilpAddress and a sharedSecret in base64url.
Hand both to your STREAM sender.
- One set of credentials, one payment. Each time you read an incoming payment, the node hands out fresh credentials, and each set is a STREAM connection of its own. The receiver tells each connection what it delivered, so two payments never take each other's money for their own. Do not pay twice with the same credentials: read the incoming payment again.
- Money from the first packet. The receiver accepts money on a new stream right away. The incoming payment, not STREAM's stream limit, bounds what the connection may deliver.
- Paid within a tolerance. Hops round down, so no sender can hit an amount to the unit. An incoming payment counts as paid once what it received is within a tiny tolerance of its amount, under or over.
- Too much is refused and explained. A packet over what the receiver told its streams, by more than the tolerance, is refused with F99, and the reply says how much it still takes. Send that instead.
-
Full or stopped, said plainly. When nothing is
left, the receiver closes the connection: with
NoErrorwhen the incoming payment got all it asked for, and withApplicationErrorwhen it was completed or closed before it got that. - No receipts. Dassie issues no STREAM receipts (RFC 39 is deprecated in favor of Open Payments). It still reads them, so a Dassie payer can pay a receiver that sends them.
From your own code, the shortest path is a BTP connection to a node
and ilp-protocol-stream on top of it. With a BTP token
from the node's owner (see
Connect an ILP client over BTP) and an
incoming payment's credentials, after
npm install ilp-plugin-btp ilp-protocol-stream:
import BtpPlugin from "ilp-plugin-btp";
import { createConnection } from "ilp-protocol-stream";
// The btp+wss:// URL of your token, and the incoming payment's ilp method.
const plugin = new BtpPlugin({ server: process.env.BTP_URL });
await plugin.connect();
const connection = await createConnection({
plugin,
destinationAccount: process.env.ILP_ADDRESS,
sharedSecret: Buffer.from(process.env.SHARED_SECRET, "base64url"),
});
const stream = connection.createStream();
await stream.sendTotal(1_000_000_000); // in the ledger's smallest units
await connection.end();
await plugin.disconnect();
The script may stay alive for about 35 seconds after the payment.
ilp-protocol-stream sends a last packet or two as it
closes, and ilp-plugin-btp waits for answers that cannot
come once it is disconnected. Exit with process.exit()
once the payment is done, or answer those calls yourself, as
tests/e2e/btp.ts
does.
On a local dev network you can do this without touching a public network.
Where it differs
Dassie keeps to the wire format exactly. Where RFC 29 leaves room, or
where ilp-protocol-stream does something that works less
well, Dassie chooses on its own. None of it changes a byte another
implementation reads.
| Dassie | Why it helps |
|---|---|
| Sends several packets at once, and adjusts how many by how they fare. | ilp-protocol-stream sends one at a time. Several are faster on a slow path. |
| Every response, Fulfill or Reject, carries every open stream's limits and totals. | The sender always knows where it stands, whatever happened to its packet. |
| When one stream in a packet is full, its share goes to the others. | A packet fails only when all its streams together cannot take it. |
| Small leftover amounts wait only while other money is in flight. | A payment below the usual threshold still completes. |
| After a too-low rate or a final error from the path, sends the money again, and stops after a long run of failures with nothing delivered. | Each packet may take a different path, so one poor node does not end a payment. |
| Takes a receive maximum that went down, from the receiver's latest reply. | A refused packet costs one round trip, and the next one fits. |
| Measures a rate only when asked, with exact integer amounts throughout. | No rounding surprises, and no probes nobody needs. |
One rule shows when a sender that is not Dassie meets a Dassie
receiver. RFC 29 says the most a stream is told it may receive never
goes down, and a Dassie receiver keeps to that. So when two payments
race for the last part of one incoming payment, the receiver does not
tell the slower one less. It closes that connection with
NoError, which every sender understands, and its reply
still says what arrived. The incoming payment may then stay a little
short, and open for the rest.
Every difference, and the reasons for each
The codes a node or its receivers send, and what a sender does about each, are on Error codes.
The STREAM Toy
The STREAM Toy lets you watch a
STREAM payment cross a network, packet by packet, in your browser.
The sender and the receiver are dassie-stream's real
state machines, compiled to WebAssembly, the same build the tests
load. So what you see is what Dassie does, not a picture of it.
Between them sits a simulated connector. You choose:
- The network: the largest packet the connector takes (larger ones get F08), the median round trip, how much round trips vary, and how many packets may be in flight (more get T03).
- The payment: how much the sender sends.
- The simulation: how fast it plays, and a seed. The same settings and seed always play the same run.
The log decrypts the STREAM packets inside each ILP packet with the connection's shared secret, so you can read every frame: the money, the limits each side announces, and the close at the end. Try a small largest packet to watch the sender learn it from F08, or few packets in flight to watch it back off.
Everything runs on the page. Nothing leaves your browser, and no money moves, test or otherwise.