@blockula/sdk 1.0.0 → 1.0.3

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 made (`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
@@ -1233,6 +1233,54 @@ export interface HistoryPage {
1233
1233
  */
1234
1234
  paged: boolean;
1235
1235
  }
1236
+ /** An account as the node labels it: its primary `.boo` name when it has one (chain text). */
1237
+ export interface AccountLabel {
1238
+ address: string;
1239
+ name: string | null;
1240
+ short: string;
1241
+ }
1242
+ /** One row of `/api/accounts`. */
1243
+ export interface DirectoryAccount {
1244
+ account: AccountLabel;
1245
+ /** Its active names, primary first. */
1246
+ names: string[];
1247
+ balance: string;
1248
+ balance_display: string;
1249
+ transferable: string;
1250
+ transferable_display: string;
1251
+ /** The height of its newest finalized tx this node holds, or null. */
1252
+ last_active: number | null;
1253
+ validator: boolean;
1254
+ program: boolean;
1255
+ /** A system account's role (treasury, community, …), or null. */
1256
+ system: string | null;
1257
+ }
1258
+ /** A page of `/api/accounts`. */
1259
+ export interface AccountsPage {
1260
+ sort: "balance" | "recent" | "names";
1261
+ accounts: DirectoryAccount[];
1262
+ /** The next page's `after`, or null at the end. */
1263
+ next: string | null;
1264
+ /** Rows in this listing (named accounts only for `names`). */
1265
+ total: number;
1266
+ accounts_total: number;
1267
+ }
1268
+ /** One row of `/api/names`. */
1269
+ export interface DirectoryName {
1270
+ name: string;
1271
+ owner: AccountLabel;
1272
+ registered_at: number;
1273
+ expires_at: number;
1274
+ grace_until: number;
1275
+ status: "active" | "grace";
1276
+ }
1277
+ /** A page of `/api/names`. */
1278
+ export interface NamesPage {
1279
+ sort: "recent" | "alpha";
1280
+ names: DirectoryName[];
1281
+ next: string | null;
1282
+ total: number;
1283
+ }
1236
1284
  export declare class Client {
1237
1285
  /** The chain's address prefix: given, learned from `/api/status`, or `boo` until then. */
1238
1286
  prefix: string;
@@ -1258,7 +1306,7 @@ export declare class Client {
1258
1306
  readonly network?: NetworkName;
1259
1307
  /** See `ClientOptions.wireTxs`. */
1260
1308
  readonly wireTxs: boolean;
1261
- /** The public testnet (`NETWORKS.testnet`), every pin set: `Client.testnet({ witnesses })`, or pass your own node's URL as `rpc`. */
1309
+ /** 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
1310
  static testnet(opts?: Omit<ClientOptions, "network"> & {
1263
1311
  rpc?: string;
1264
1312
  }): Client;
@@ -1267,7 +1315,7 @@ export declare class Client {
1267
1315
  rpc?: string;
1268
1316
  }): Client;
1269
1317
  /**
1270
- * `new Client(rpc, opts)`, `new Client({ network: "testnet" })` (the network's public RPC), or
1318
+ * `new Client(rpc, opts)`, `new Client({ network: "local" })` (the network's built-in RPC), or
1271
1319
  * `new Client(rpc, { network })` (your own node, held to that network's pins). The pins
1272
1320
  * (`opts`, and the network's) are checked on every status: a node reporting another prefix,
1273
1321
  * chain id or genesis hash is refused with `ChainMismatchError`.
@@ -1358,6 +1406,22 @@ export declare class Client {
1358
1406
  limit?: number;
1359
1407
  }): Promise<any>;
1360
1408
  search(q: string): Promise<any>;
1409
+ /**
1410
+ * The people directory: accounts richest first (`balance`), by their newest tx (`recent`; not
1411
+ * offered on very large chains) or by name (`names`: named accounts only), a page at a time
1412
+ * (pass the previous page's `next` as `after`; at most 100). Names are chain text: show them as text.
1413
+ */
1414
+ accounts(opts?: {
1415
+ sort?: "balance" | "recent" | "names";
1416
+ limit?: number;
1417
+ after?: string;
1418
+ }): Promise<AccountsPage>;
1419
+ /** Registered names (active, or in their grace), newest registration first (`recent`) or alphabetically (`alpha`), a page at a time. */
1420
+ names(opts?: {
1421
+ sort?: "recent" | "alpha";
1422
+ limit?: number;
1423
+ after?: string;
1424
+ }): Promise<NamesPage>;
1361
1425
  /** The validator set: `active` this epoch, `next_epoch` (with `joining`/`leaving`), `candidates`, `jailed`, `tombstoned` and `removed`, each with stake, self-bond, commission, jail and liveness; epoch timing; the churn limit and slashing parameters. */
1362
1426
  validators(): Promise<any>;
1363
1427
  /** Hunts (bounties on GitHub issues), biggest first. */
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";
@@ -1965,6 +1965,17 @@ function canonicalNodeUrl(u) {
1965
1965
  return u;
1966
1966
  }
1967
1967
  }
1968
+ /** `?sort=&limit=&after=` from the options that are set. */
1969
+ function pageQuery(opts) {
1970
+ const q = new URLSearchParams();
1971
+ if (opts.sort)
1972
+ q.set("sort", opts.sort);
1973
+ if (opts.limit !== undefined)
1974
+ q.set("limit", String(opts.limit));
1975
+ if (opts.after)
1976
+ q.set("after", opts.after);
1977
+ return q.size ? `?${q}` : "";
1978
+ }
1968
1979
  export class Client {
1969
1980
  /** The chain's address prefix: given, learned from `/api/status`, or `boo` until then. */
1970
1981
  prefix;
@@ -1987,7 +1998,7 @@ export class Client {
1987
1998
  network;
1988
1999
  /** See `ClientOptions.wireTxs`. */
1989
2000
  wireTxs;
1990
- /** The public testnet (`NETWORKS.testnet`), every pin set: `Client.testnet({ witnesses })`, or pass your own node's URL as `rpc`. */
2001
+ /** 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
2002
  static testnet(opts = {}) {
1992
2003
  const { rpc, ...rest } = opts;
1993
2004
  return new Client(rpc, { ...rest, network: "testnet" });
@@ -1998,7 +2009,7 @@ export class Client {
1998
2009
  return new Client(rpc, { ...rest, network: "local" });
1999
2010
  }
2000
2011
  /**
2001
- * `new Client(rpc, opts)`, `new Client({ network: "testnet" })` (the network's public RPC), or
2012
+ * `new Client(rpc, opts)`, `new Client({ network: "local" })` (the network's built-in RPC), or
2002
2013
  * `new Client(rpc, { network })` (your own node, held to that network's pins). The pins
2003
2014
  * (`opts`, and the network's) are checked on every status: a node reporting another prefix,
2004
2015
  * chain id or genesis hash is refused with `ChainMismatchError`.
@@ -2017,7 +2028,7 @@ export class Client {
2017
2028
  if (preset) {
2018
2029
  url ??= preset.rpc;
2019
2030
  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}" })`);
2031
+ 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
2032
  opts = withPreset(opts, preset);
2022
2033
  }
2023
2034
  this.rpc = url ?? DEFAULT_RPC;
@@ -2220,6 +2231,18 @@ export class Client {
2220
2231
  return this.call(`/key/${keyIdHex}${q.size ? `?${q}` : ""}`);
2221
2232
  }
2222
2233
  search(q) { return this.call(`/search/${encodeURIComponent(q)}`); }
2234
+ /**
2235
+ * The people directory: accounts richest first (`balance`), by their newest tx (`recent`; not
2236
+ * offered on very large chains) or by name (`names`: named accounts only), a page at a time
2237
+ * (pass the previous page's `next` as `after`; at most 100). Names are chain text: show them as text.
2238
+ */
2239
+ accounts(opts = {}) {
2240
+ return this.call(`/accounts${pageQuery(opts)}`);
2241
+ }
2242
+ /** Registered names (active, or in their grace), newest registration first (`recent`) or alphabetically (`alpha`), a page at a time. */
2243
+ names(opts = {}) {
2244
+ return this.call(`/names${pageQuery(opts)}`);
2245
+ }
2223
2246
  /** The validator set: `active` this epoch, `next_epoch` (with `joining`/`leaving`), `candidates`, `jailed`, `tombstoned` and `removed`, each with stake, self-bond, commission, jail and liveness; epoch timing; the churn limit and slashing parameters. */
2224
2247
  validators() { return this.call("/validators"); }
2225
2248
  /** Hunts (bounties on GitHub issues), biggest first. */
@@ -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.3",
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",