@blockula/sdk 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,8 +11,8 @@ npm install @blockula/sdk
11
11
  ```ts
12
12
  import { Client, Wallet, encodeKeyGrant } from "@blockula/sdk";
13
13
 
14
- const wallet = Wallet.generate(Client.testnet()); // the public testnet, every pin set: tboo1… addresses
15
- await wallet.faucet(); // test chains: 100 test FANG
14
+ const wallet = Wallet.generate(Client.local()); // `blockula dev` on this machine
15
+ await wallet.faucet(); // dev chains: 100 FANG
16
16
  await wallet.registerName("vlad"); // vlad.boo
17
17
  await wallet.send("alice.boo", "1.5", { memo: "trick or treat" });
18
18
 
@@ -22,19 +22,17 @@ const agentGrantCode = encodeKeyGrant(await agent.keyGrant(wallet.account));
22
22
  await wallet.addSessionKey(agentGrantCode, "claude", { limit: "10", periodBlocks: 86_400, expiresInBlocks: 30 * 86_400 });
23
23
  ```
24
24
 
25
- On a `blockula dev` chain, use `Client.local()` (or `new Client("http://localhost:7070")`).
25
+ For a chain you launched (`blockula summon`), pass its node: `new Client("https://<your node>", { chainId, genesisHash })`.
26
26
 
27
27
  ## Which network?
28
28
 
29
29
  ```ts
30
- Client.testnet(); // the public testnet
31
- new Client({ network: "testnet" }); // the same
32
- new Client("http://localhost:7171", { network: "testnet" }); // your own node, held to the testnet's pins
33
30
  Client.local(); // blockula dev on this machine
31
+ new Client("http://localhost:7171", { network: "testnet" }); // a blockula-testnet-2 node you run, held to its pins
34
32
  new Client("http://<your node>:7070", { chainId: "mychain", genesisHash: "…" }); // a chain of your own
35
33
  ```
36
34
 
37
- `NETWORKS` lists what each preset pins: the chain id, the genesis hash, the address prefix and the genesis clock. A client made for a network checks them on every status it reads, including the one each signature is made against, and refuses a node on another chain (`ChainMismatchError`). The prefix is known up front, so `wallet.address` is `tboo1…` on the testnet before the first request. Pins you pass as well must agree with the network's.
35
+ The public testnet (`blockula-testnet-2`) was retired in November 2026 and has no public node, so the `testnet` preset has no RPC: `Client.testnet()` and `new Client({ network: "testnet" })` throw unless you pass a node's URL (`Client.testnet({ rpc })`). `NETWORKS` lists what each preset pins: the chain id, the genesis hash, the address prefix and the genesis clock. A client made for a network checks them on every status it reads, including the one each signature is made against, and refuses a node on another chain (`ChainMismatchError`). The prefix is known up front, so `wallet.address` is `tboo1…` on the testnet before the first request. Pins you pass as well must agree with the network's.
38
36
 
39
37
  A wallet signs nothing for a node that says it's on `blockula-1` unless its client pins that chain (`chainId: "blockula-1"`): it throws `MainnetNotAllowedError` instead.
40
38
 
@@ -98,16 +96,17 @@ await watchPayments(client, shopAddress, {
98
96
 
99
97
  ## Your own RPC node
100
98
 
101
- Any node serves the same API, and one on your machine isn't rate limited. To run one against the testnet:
99
+ Any node serves the same API, and one on your machine isn't rate limited. To run one of a chain you know a node of:
102
100
 
103
101
  ```bash
104
- blockula join --genesis https://blockula-testnet.fly.dev --home ~/blockula-node --port 7171
102
+ blockula join --genesis https://<a node of the chain> --home ~/blockula-node --port 7171
105
103
  ```
106
104
 
107
105
  ```ts
106
+ const pins = { chainId: "mychain", genesisHash: "…" }; // a node on another chain is refused
108
107
  const client = new Client("http://localhost:7171", {
109
- network: "testnet", // its pins: a node on another chain is refused
110
- witnesses: [new Client("https://blockula-testnet.fly.dev")], // names proven against a second node
108
+ ...pins,
109
+ witnesses: [new Client("https://<another node>", pins)], // names proven against a second node
111
110
  });
112
111
  ```
113
112
 
@@ -160,7 +159,7 @@ wallet.validForBlocks = 600;
160
159
  Addresses are bech32m with the chain's prefix: `boo1…` by default, `tboo1…` on public testnets. `isAddress(s)` accepts any known prefix and `isAddress(s, "tboo")` exactly one; `addressFromPublicKey(pk, prefix)` renders one. A `Client` learns its chain's prefix from `/api/status` (`await client.addressPrefix()`), and wallets using that client sign with it (`wallet.address`, `wallet.account`). Pin it with `new Client(rpc, { prefix: "boo" })` to refuse a node on another network (see "Which chain?" below). Nodes reject addresses of another network's prefix.
161
160
 
162
161
  - Names in a signature (audit gap-7-1). Where the chain resolves a name itself (`send`, `awardHunt`, `vouch`/`unvouch`, `grantSoul`, `flagSybil`, `clawback`, `transferName`, proposal targets), a client with `witnesses` (below) signs the name's proven owner, and one without signs the **name itself** (`alice.boo`): the chain pays whoever owns it when the tx runs, so a lying node can't point it at another account now. It could still hold the signed tx and release it later in its validity window, which counts from the height that node reported, by when the name may have lapsed and been registered again: pay a name that's about to expire by address. `Wallet.build` refuses a height more than `maxHeadLeadBlocks` (30) ahead of the lowest head the witnesses report (every witness must answer, so one honest witness is enough), or past what a `genesisClock: { createdAtMs, blockTimeMs }` pin allows by this machine's clock (`HeightAheadError`, nothing signed); without either the height is the node's word. `client.recipient(name)` says which (`signed`, `byChain`, and `how` to show before signing); the address it reports for an unproven name is the node's word, for display. Where an address is needed (`delegate`, `undelegate`, `redelegate`, `unjail`, `actAs`, `keyGrant`), a name is resolved (`client.resolution`: `proven` and `how`), and on `blockula-1` nothing is signed with one only a single node vouched for: it throws `UnprovenNameError` (checked against the very status each tx is signed for). Pass the address once its owner has confirmed it, or give the client witnesses.
163
- - `wallet.address` uses the chain's prefix once the client knows it. A client made for a network (`Client.testnet()`) or pinned with `prefix` knows it up front. Otherwise it shows the `boo` form until the first request, so `await client.addressPrefix()` first if you display it.
162
+ - `wallet.address` uses the chain's prefix once the client knows it. A client made for a network (`Client.testnet({ rpc })`) or pinned with `prefix` knows it up front. Otherwise it shows the `boo` form until the first request, so `await client.addressPrefix()` first if you display it.
164
163
  - Node errors throw `ApiError` (`.status`: 429 means rate limited, and `.retryAfterMs` says how long the node asked you to wait). A transaction the node reports as failed throws `TxFailedError` from `waitForTx` (`dropped` says whether it ran: see "Timeouts and pending transactions").
165
164
  - Pass `rpId` to `createPasskeyWallet`/`unlockPasskeyWallet` to pin wallets to your production domain. Passkeys are bound to that hostname for good.
166
165
 
@@ -250,8 +249,6 @@ Every request has a timeout (`new Client(rpc, { timeoutMs })`, default 15 s, at
250
249
 
251
250
  `verifyReleaseSignature(sig, checksumsFile, trustedKeyId)` checks a release's offline ML-DSA-65 signature (docs/OPERATIONS.md "Releases").
252
251
 
253
- The public testnet is `https://blockula-testnet.fly.dev`, and the wallet is https://testnet.blockula.app.
254
-
255
252
  Tests: `npm run test:vectors` (offline: passkey derivation, state-proof and tx-encoding vectors from the node, every protocol v1 helper against a fake node, pinning, proof freshness, timeouts, release signatures, and `test/integrators.ts`: network presets, the `blockula-1` pin, balances, the nonce queue, `sendMany`, `quote`, history, `watchPayments` and `onSigned`; `test/programs.ts` (programs: every answer check, the fuel and deposit arithmetic, receipts and balance reconciliation, `provenProgram`'s refusals one by one, and the Wallet's helpers against a fake node serving answers recorded from a dev chain), and `test/examples.ts`, which runs `examples/pay-from-a-script.mjs` against a fake node, so build first), `npm run test:kat` (ML-DSA known answers), `npm test` (end to end against `blockula dev`: `test/e2e.ts` and `test/e2e-integrators.ts`, on a chain with programs or without), `npm run test:programs` (`test/e2e-programs.ts`, which needs `blockula dev --programs`: on a chain without programs it says so and passes, and `BLOCKULA_E2E_PROGRAMS=1` makes that a failure, as CI sets it). The `examples/` directory at the repository's root has two runnable scripts: receiving payments, and paying from a script.
256
253
 
257
254
  ## Changes in 0.6
package/dist/index.d.ts CHANGED
@@ -1258,7 +1258,7 @@ export declare class Client {
1258
1258
  readonly network?: NetworkName;
1259
1259
  /** See `ClientOptions.wireTxs`. */
1260
1260
  readonly wireTxs: boolean;
1261
- /** The public testnet (`NETWORKS.testnet`), every pin set: `Client.testnet({ witnesses })`, or pass your own node's URL as `rpc`. */
1261
+ /** A node of blockula-testnet-2 (`NETWORKS.testnet`), every pin set: `Client.testnet({ rpc })`. The public testnet was retired and has no public node, so `rpc` is required. */
1262
1262
  static testnet(opts?: Omit<ClientOptions, "network"> & {
1263
1263
  rpc?: string;
1264
1264
  }): Client;
@@ -1267,7 +1267,7 @@ export declare class Client {
1267
1267
  rpc?: string;
1268
1268
  }): Client;
1269
1269
  /**
1270
- * `new Client(rpc, opts)`, `new Client({ network: "testnet" })` (the network's public RPC), or
1270
+ * `new Client(rpc, opts)`, `new Client({ network: "local" })` (the network's built-in RPC), or
1271
1271
  * `new Client(rpc, { network })` (your own node, held to that network's pins). The pins
1272
1272
  * (`opts`, and the network's) are checked on every status: a node reporting another prefix,
1273
1273
  * chain id or genesis hash is refused with `ChainMismatchError`.
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { DEFAULT_TIP_CAP_MULTIPLE, KEY_POP_CONTEXT, PUBLIC_KEY_LEN, SIGNATURE_LE
6
6
  import { NAMESPACES, checkProofResponse, decodeAccount, decodeName, decodeProgram } from "./proof.js";
7
7
  import { ANY_PROGRAM_VERSION, MAX_PROGRAM_BINDINGS, MAX_PROGRAM_CALLS, MAX_PROGRAM_CODE_BYTES, MAX_PROGRAM_INTERFACE_BYTES, ProgramAnswerError, ProgramsOffError, defaultFuelLimit, depositWithMargin, plainText, positiveFuel, programAddressArg, programBytesArg, programHashArg, programInputArg, programMethodArg, checkCode, checkFuel, checkInterface, checkParams, checkProgram, checkProgramsStatus, checkReceipt, checkView, outcomeWords, priceFuel, programCost, programRecordDeposit, programUploadDeposit, reserveFuel, uploadFuel } from "./programs.js";
8
8
  import { describeKind, reviewAction } from "./describe.js";
9
- import { NETWORKS, isNetworkName } from "./networks.js";
9
+ import { NETWORKS, TESTNET_RETIRED, isNetworkName } from "./networks.js";
10
10
  export * from "./tx.js";
11
11
  export * from "./proof.js";
12
12
  export * from "./distribution.js";
@@ -1987,7 +1987,7 @@ export class Client {
1987
1987
  network;
1988
1988
  /** See `ClientOptions.wireTxs`. */
1989
1989
  wireTxs;
1990
- /** The public testnet (`NETWORKS.testnet`), every pin set: `Client.testnet({ witnesses })`, or pass your own node's URL as `rpc`. */
1990
+ /** A node of blockula-testnet-2 (`NETWORKS.testnet`), every pin set: `Client.testnet({ rpc })`. The public testnet was retired and has no public node, so `rpc` is required. */
1991
1991
  static testnet(opts = {}) {
1992
1992
  const { rpc, ...rest } = opts;
1993
1993
  return new Client(rpc, { ...rest, network: "testnet" });
@@ -1998,7 +1998,7 @@ export class Client {
1998
1998
  return new Client(rpc, { ...rest, network: "local" });
1999
1999
  }
2000
2000
  /**
2001
- * `new Client(rpc, opts)`, `new Client({ network: "testnet" })` (the network's public RPC), or
2001
+ * `new Client(rpc, opts)`, `new Client({ network: "local" })` (the network's built-in RPC), or
2002
2002
  * `new Client(rpc, { network })` (your own node, held to that network's pins). The pins
2003
2003
  * (`opts`, and the network's) are checked on every status: a node reporting another prefix,
2004
2004
  * chain id or genesis hash is refused with `ChainMismatchError`.
@@ -2017,7 +2017,7 @@ export class Client {
2017
2017
  if (preset) {
2018
2018
  url ??= preset.rpc;
2019
2019
  if (url === undefined)
2020
- throw new Error(`no public ${preset.name} RPC is built into this SDK yet: pass the URL of a ${preset.name} node you trust, new Client(url, { network: "${preset.name}" })`);
2020
+ throw new Error(preset.name === "testnet" ? TESTNET_RETIRED : `no public ${preset.name} RPC is built into this SDK: pass the URL of a ${preset.name} node you trust, new Client(url, { network: "${preset.name}" })`);
2021
2021
  opts = withPreset(opts, preset);
2022
2022
  }
2023
2023
  this.rpc = url ?? DEFAULT_RPC;
@@ -19,6 +19,8 @@ export interface NetworkPreset {
19
19
  /** The genesis clock of `genesisHash`'s genesis: it bounds the height a tx is signed against. */
20
20
  genesisClock?: NetworkClock;
21
21
  }
22
+ /** Why `{ network: "testnet" }` needs a node URL: the public testnet has none any more. */
23
+ export declare const TESTNET_RETIRED = "the public testnet (blockula-testnet-2) was retired in November 2026 and has no public node: run your own chain (`blockula dev`, `blockula summon`) and pass its URL, or pass the URL of a blockula-testnet-2 node you run, new Client(url, { network: \"testnet\" })";
22
24
  /** The networks this SDK knows: `new Client({ network })` pins what they list. */
23
25
  export declare const NETWORKS: Readonly<Record<NetworkName, Readonly<NetworkPreset>>>;
24
26
  /** Whether `s` names a network this SDK knows. */
package/dist/networks.js CHANGED
@@ -1,21 +1,24 @@
1
1
  // The networks this SDK knows by name, and what a client for each pins (DEV-1).
2
2
  //
3
- // `new Client({ network: "testnet" })` (or `Client.testnet()`) talks to the public testnet and
4
- // pins its chain id, genesis hash, address prefix and genesis clock up front, so the first address
5
- // it renders is already `tboo1…` and a node on any other chain is refused before anything is
6
- // signed. `new Client("http://localhost:7171", { network: "testnet" })` does the same through your
7
- // own node. For a chain of your own, pin it yourself: `new Client(rpc, { chainId, genesisHash })`.
3
+ // `new Client("http://localhost:7171", { network: "testnet" })` (or `Client.testnet({ rpc })`)
4
+ // talks to your own node of blockula-testnet-2 and pins its chain id, genesis hash, address prefix
5
+ // and genesis clock up front, so the first address it renders is already `tboo1…` and a node on
6
+ // any other chain is refused before anything is signed. The public testnet was retired in November
7
+ // 2026 and has no public node, so no RPC is built in for it: pass your node's URL. For a chain of
8
+ // your own, pin it yourself: `new Client(rpc, { chainId, genesisHash })`.
8
9
  //
9
- // The testnet values match mcp/src/network.ts (`TESTNET_RPC`, `KNOWN_GENESIS`, `KNOWN_CLOCKS`) and
10
+ // The testnet values match mcp/src/network.ts (`KNOWN_GENESIS`, `KNOWN_CLOCKS`) and
10
11
  // web/lib/config.ts; test/integrators.ts checks the MCP's.
12
+ /** blockula-testnet-2, the retired public testnet: its pins, and no public RPC. */
11
13
  const TESTNET = {
12
14
  name: "testnet",
13
- rpc: "https://blockula-testnet.fly.dev",
14
15
  chainId: "blockula-testnet-2",
15
16
  genesisHash: "b004e2f91b63429fc009088b16ec02b6d5388a03642699ae3dc6e325939f19b2",
16
17
  prefix: "tboo",
17
18
  genesisClock: { createdAtMs: 1792328803000, blockTimeMs: 1000 },
18
19
  };
20
+ /** Why `{ network: "testnet" }` needs a node URL: the public testnet has none any more. */
21
+ export const TESTNET_RETIRED = 'the public testnet (blockula-testnet-2) was retired in November 2026 and has no public node: run your own chain (`blockula dev`, `blockula summon`) and pass its URL, or pass the URL of a blockula-testnet-2 node you run, new Client(url, { network: "testnet" })';
19
22
  /** A `blockula dev` chain on this machine. Nothing is pinned: `--reset` makes a new chain each time. */
20
23
  const LOCAL = { name: "local", rpc: "http://localhost:7070" };
21
24
  const frozen = (p) => Object.freeze({ ...p, genesisClock: p.genesisClock ? Object.freeze({ ...p.genesisClock }) : undefined });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockula/sdk",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "\ud83e\udddb TypeScript SDK for Blockula: wallets with post-quantum signatures (ML-DSA-65), .boo names and transfers. Experimental software, provided as is under MIT.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",