TypeScript SDK
The TypeScript SDK holds, pays and receives notes from Node or a browser.
The privacy rules and all signing run in the same Rust core as the Rust SDK,
worgl-sdk, compiled to WebAssembly. The package moves its JSON between the
holder, the server and a store.
Amounts are bigint, and requests, payments and transfers travel as JSON
text, so no amount loses precision.
Build it
Section titled “Build it”The package is not published on npm. Build it from sdk/typescript in the
repository:
git clone https://github.com/zhangheqi/worglcd worglrustup target add wasm32-unknown-unknowncargo install wasm-bindgen-cli --version 0.2.129 --locked # the version Cargo.lock pinscd sdk/typescriptnpm cinpm run build # compiles the Rust core to wasm/ and the package to dist/Then point your project at the built folder, for instance with
npm install ../worgl/sdk/typescript. The package is named worgl, with
worgl/node for what needs Node.
It needs Node 22.18 or later, or a browser with top-level await.
Importing the package loads the WebAssembly module: from disk under Node,
through fetch relative to the module in a browser or bundler.
A payment
Section titled “A payment”import { Client, Purse } from 'worgl';import { FileStore } from 'worgl/node';
const client = new Client('https://mint.example.com');const alice = await Purse.open(client, { store: new FileStore('alice.json') });const bob = await Purse.open(client, { store: new FileStore('bob.json') });
const request = await bob.request(mint, 5n); // Bob sends request.json to Aliceconst payment = await alice.pay(request); // Alice sends payment.json to Bobconst received = await bob.receive(request, payment); // checks, submits at its times, keepsawait alice.accept(received.theirs); // Bob sends Alice her change, she keeps itThe other side reads what it is sent with PaymentRequest.from(json) and
Payment.from(json). received.theirs holds the notes of the mint’s answers
that are not Bob’s: Alice’s change. Keeping it with accept also settles
Alice’s transfer. Without it, alice.settle() sends her transfers again, and
the mint answers with the same notes while it keeps its answers (see
Error codes).
For a fuller worked example, the SDK’s tests in
sdk/typescript/test/purse.test.ts
run purses against a real server.
Pursereads mint info itself, submits each transfer at its time, retries what fails on the way (a resent transfer gets the same answer) and saves after every change, before handing out anything that depends on it. If a save fails, the change is undone and the call rejects. After abusyanswer it waits as long as the mint’sRetry-Afterasks, up to 30 seconds, before the next try.purse.refresh(mint)pays the notes about to expire back to the purse, each at a random moment before the next rotation, and resends the outgoing transfers that must land before it.planRefreshandsubmitsplit it for applications with their own scheduler. The moments are counted by the server’s clock, from theDateof its info answer (client.infoTextAndTime), so this machine’s clock, slow or fast, moves none past the rotation. A browser reads thatDatefrom a mint on another origin only if the answer carriesAccess-Control-Expose-Headers: Date, so the proxy that adds the CORS headers must send it too; without it, the browser’s clock counts the moments.purse.prune(mint)drops the notes whose layer the mint’s info says it has dropped, never by this machine’s clock.- When the payer submits instead (
purse.submit(payment.transfers)), it handssubmitted.theirs, the payee’s notes, to the payee, who checks their signatures against the mint’s keys and keeps them withpurse.accept(notes). The payer’s purse keeps them for the payee too, untilpurse.forgetOwed(submitted.theirs)once they are handed over. - A transfer refused as
spentorexpiredthat the purse has something in carriesatStakeinsubmitted.refused: it may have been applied, with an answer the mint no longer keeps, and that much may be lost unless the other side hands the notes over. - Each refused transfer carries
outcome, what the purse did with it. One it made that was refused asspentorexpiredwith status 409 is dropped, since its note is gone. One refused aslayer_fullwith status 503, which the mint never applied and never will, is cancelled and its note taken back, and so is one refused aspolicythat the policy the mint publishes refuses too. All keep the keys of their change, soacceptstill takes in change handed over later. Any other refusal leaves it outgoing.purse.cancel(transfer)andpurse.forget(transfer)do the same by hand, for a transfer frompurse.outgoing(), which lists those not yet answered. purse.payrefuses, before signing, a request for a note the mint does not make, or to a key the purse has paid already, such as one request twice.purse.payments()lists the payments it made and keeps, each with the payment to send again, less any transfer cancelled or forgotten, andowed, the payee’s notes from every answer the purse got itself by settling, refreshing or submitting;purse.forgetOwed(notes)stops keeping them once they are handed over.purse.takeFromFaucet(new Client(faucetUrl))asks a playground faucet for a note, to a fresh key like any other, and keeps it.
Client and errors
Section titled “Client and errors”Client makes one call per route and rejects with a MintError carrying the
mint’s code (spent, frozen, paused…). isTransient(error) says whether
trying again may work, and retryAfterMs how long a busy mint asked to
wait first. isTransient counts layer_full out: a transfer refused so is
refused every time, though a mint request may pass after the next rotation.
The codes are listed in Error codes.
Stores
Section titled “Stores”MemoryStore, and FileStore from worgl/node, which replaces its file
atomically, readable by its owner only. The JSON holds every note’s secret
key, so keep it private and encrypted at rest. Implement Store to keep it
elsewhere.
Testing it yourself
Section titled “Testing it yourself”The tests run against a real server on a local port, so build the server first:
cargo build -p worgl-server -p worgl-faucet # from the repository rootcd sdk/typescriptnpm run check # type-checks the package and its testsnpm testv0.1Not yet independently security reviewed. Don't use it to hold anything you can't afford to lose.MIT or Apache-2.0