ckb-session-kit 0.0.0-stage → 2.0.0

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/dist/store.js ADDED
@@ -0,0 +1,192 @@
1
+ export class SessionStoreConflict extends Error {
2
+ constructor() {
3
+ super("another session is already stored here (opened in another tab?); reload the page");
4
+ this.name = "SessionStoreConflict";
5
+ }
6
+ }
7
+ const UNREADABLE = "the stored session is unreadable; it may still hold a funded key, so it is never overwritten";
8
+ /** The stored value's public key, without parsing the rest. */
9
+ function storedKey(raw) {
10
+ if (raw === undefined)
11
+ return undefined;
12
+ let key;
13
+ try {
14
+ key = JSON.parse(raw).publicKey;
15
+ }
16
+ catch {
17
+ throw new Error(UNREADABLE);
18
+ }
19
+ if (typeof key !== "string")
20
+ throw new Error(UNREADABLE);
21
+ return key;
22
+ }
23
+ /**
24
+ * Throws SessionStoreConflict unless `session` may replace the stored value: a
25
+ * different key may hold funds, and a save of the same key without the on-chain
26
+ * binding (a stale tab that never saw the funding) would lose what finds them
27
+ * (review, 28 Oct).
28
+ */
29
+ function assertReplaceable(raw, session) {
30
+ const held = storedKey(raw);
31
+ if (held === undefined)
32
+ return;
33
+ if (held !== session.publicKey)
34
+ throw new SessionStoreConflict();
35
+ if (!session.onchain && JSON.parse(raw).onchain)
36
+ throw new SessionStoreConflict();
37
+ }
38
+ export function serializeSession(s) {
39
+ const stored = {
40
+ v: 1,
41
+ privateKey: s.privateKey,
42
+ publicKey: s.publicKey,
43
+ expiresAt: s.policy.expiresAt.toISOString(),
44
+ maxPerTx: s.policy.scope.maxPerTx.toString(),
45
+ ...(s.policy.scope.recipients && { recipients: s.policy.scope.recipients }),
46
+ ...(s.onchain && {
47
+ onchain: {
48
+ ownerLock: s.onchain.ownerLock,
49
+ minInterval: s.onchain.minInterval.toString(),
50
+ ...(s.onchain.recipientLockHash && { recipientLockHash: s.onchain.recipientLockHash }),
51
+ ...(s.onchain.token && {
52
+ token: { type: s.onchain.token.type, maxPerTx: s.onchain.token.maxPerTx.toString() },
53
+ }),
54
+ ...(s.onchain.openTx && { openTx: s.onchain.openTx }),
55
+ },
56
+ }),
57
+ };
58
+ return JSON.stringify(stored);
59
+ }
60
+ export function deserializeSession(json) {
61
+ let d;
62
+ try {
63
+ d = JSON.parse(json);
64
+ }
65
+ catch {
66
+ throw new Error(UNREADABLE);
67
+ }
68
+ if (d.v !== 1)
69
+ throw new Error(`unsupported stored session version: ${String(d.v)}`);
70
+ return {
71
+ privateKey: d.privateKey,
72
+ publicKey: d.publicKey,
73
+ policy: {
74
+ expiresAt: new Date(d.expiresAt),
75
+ scope: {
76
+ maxPerTx: BigInt(d.maxPerTx),
77
+ ...(d.recipients && { recipients: d.recipients }),
78
+ },
79
+ },
80
+ ...(d.onchain && {
81
+ onchain: {
82
+ ownerLock: {
83
+ codeHash: d.onchain.ownerLock.codeHash,
84
+ hashType: d.onchain.ownerLock.hashType,
85
+ args: d.onchain.ownerLock.args,
86
+ },
87
+ minInterval: BigInt(d.onchain.minInterval),
88
+ ...(d.onchain.recipientLockHash && { recipientLockHash: d.onchain.recipientLockHash }),
89
+ ...(d.onchain.token && {
90
+ token: {
91
+ type: {
92
+ codeHash: d.onchain.token.type.codeHash,
93
+ hashType: d.onchain.token.type.hashType,
94
+ args: d.onchain.token.type.args,
95
+ },
96
+ maxPerTx: BigInt(d.onchain.token.maxPerTx),
97
+ },
98
+ }),
99
+ ...(d.onchain.openTx && { openTx: d.onchain.openTx }),
100
+ },
101
+ }),
102
+ };
103
+ }
104
+ export function memoryStore() {
105
+ let value;
106
+ return {
107
+ load: async () => (value === undefined ? undefined : deserializeSession(value)),
108
+ save: async (s) => {
109
+ assertReplaceable(value, s);
110
+ value = serializeSession(s);
111
+ },
112
+ clear: async (s) => {
113
+ if (storedKey(value) === s.publicKey)
114
+ value = undefined;
115
+ },
116
+ };
117
+ }
118
+ const DB = "ckb-session-kit";
119
+ const STORE = "sessions";
120
+ const KEY = "current";
121
+ function request(r) {
122
+ return new Promise((resolve, reject) => {
123
+ r.onsuccess = () => resolve(r.result);
124
+ r.onerror = () => reject(r.error);
125
+ });
126
+ }
127
+ async function open(idb) {
128
+ const r = idb.open(DB, 1);
129
+ r.onupgradeneeded = () => r.result.createObjectStore(STORE);
130
+ return request(r);
131
+ }
132
+ /**
133
+ * One session per `key`. Pages on the same origin that each run their own
134
+ * session (a wallet page and a reader page, say) pass different keys.
135
+ */
136
+ export function indexedDbStore(idb = globalThis.indexedDB, key = KEY) {
137
+ // Read and write in one readwrite transaction, so two tabs cannot interleave.
138
+ // It resolves only once the transaction has committed, durably: a save is what
139
+ // makes it safe to send funds to the key.
140
+ const update = async (decide, value) => {
141
+ const db = await open(idb);
142
+ try {
143
+ const tx = db.transaction(STORE, "readwrite", { durability: "strict" });
144
+ const done = new Promise((resolve, reject) => {
145
+ tx.oncomplete = () => resolve();
146
+ tx.onabort = () => reject(tx.error ?? new Error("session store transaction aborted"));
147
+ tx.onerror = () => reject(tx.error ?? new Error("session store transaction failed"));
148
+ });
149
+ done.catch(() => { }); // awaited below; a decide() error aborts it first
150
+ const store = tx.objectStore(STORE);
151
+ const raw = await request(store.get(key));
152
+ let action;
153
+ try {
154
+ action = decide(raw);
155
+ }
156
+ catch (e) {
157
+ tx.abort();
158
+ throw e;
159
+ }
160
+ if (action === "put")
161
+ await request(store.put(value, key));
162
+ if (action === "delete")
163
+ await request(store.delete(key));
164
+ await done;
165
+ }
166
+ finally {
167
+ db.close();
168
+ }
169
+ };
170
+ return {
171
+ load: async () => {
172
+ const db = await open(idb);
173
+ try {
174
+ const raw = await request(db.transaction(STORE, "readonly").objectStore(STORE).get(key));
175
+ return raw === undefined ? undefined : deserializeSession(raw);
176
+ }
177
+ finally {
178
+ db.close();
179
+ }
180
+ },
181
+ save: async (session) => {
182
+ const value = serializeSession(session);
183
+ await update((raw) => {
184
+ assertReplaceable(raw, session);
185
+ return "put";
186
+ }, value);
187
+ },
188
+ clear: async (session) => {
189
+ await update((raw) => (storedKey(raw) === session.publicKey ? "delete" : "keep"));
190
+ },
191
+ };
192
+ }
@@ -0,0 +1,317 @@
1
+ import { ccc } from "@ckb-ccc/core";
2
+ import { type SessionLockDeployment } from "./lock.js";
3
+ import { type SigningLockOptions, type RetryOptions } from "./onchain.js";
4
+ /**
5
+ * Subscriptions (docs/design/subscriptions.md): the same session lock, with the
6
+ * roles moved. The subscriber is the owner; the creator's charger key is the
7
+ * session key; `max_per_tx` is the price, `min_interval` the period, and the
8
+ * recipient the creator's payout address. So the network lets the charger take
9
+ * at most the price, at most once per period, only to the payout address, and
10
+ * the subscriber can sweep everything back alone at any time.
11
+ *
12
+ * - `openSubscription` — the subscriber funds one cell. One signature.
13
+ * - `chargeSubscription` — the creator's charger key takes the price, paying the
14
+ * fee from its own cell. No subscriber involved.
15
+ * - `cancelSubscription` — the subscriber sweeps the cell back. One signature.
16
+ * - `verifySubscription` — the creator checks a subscription it was told about.
17
+ *
18
+ * A `Subscription` holds no private key: both sides can store it as it is.
19
+ *
20
+ * A token subscription (`token` set) is paid in an xUDT token instead: the
21
+ * 160-byte token layout with `max_per_tx` 0, so no CKB ever leaves the cell, and
22
+ * the price as `max_per_tx_token`. Charges top up the payout address's
23
+ * anyone-can-pay cell of that token (docs/design/subscriptions.md, "Token subscriptions").
24
+ */
25
+ export interface Subscription {
26
+ subscriberLockHash: ccc.Hex;
27
+ chargerLockHash: ccc.Hex;
28
+ payToLockHash: ccc.Hex;
29
+ /** Per charge, in the subscription's asset: shannons, or token units in a token subscription. */
30
+ price: bigint;
31
+ /** Blocks between two charges of the same cell. */
32
+ periodBlocks: bigint;
33
+ /** A token subscription: the plain xUDT type script it is paid in. */
34
+ token?: {
35
+ type: {
36
+ codeHash: ccc.Hex;
37
+ hashType: ccc.HashType;
38
+ args: ccc.Hex;
39
+ };
40
+ };
41
+ /** The funding transaction, when this side opened it. */
42
+ openTx?: ccc.Hex;
43
+ }
44
+ export declare function subscriptionLock(deployment: SessionLockDeployment, sub: Subscription): ccc.Script;
45
+ /**
46
+ * The memo an opening commits to, binding the subscription to one account at the
47
+ * creator: a hash, so the account id never appears on-chain and can double as a
48
+ * secret the subscriber's browser presents to the creator.
49
+ */
50
+ export declare function subscriptionMemo(account: string): ccc.Hex;
51
+ /**
52
+ * The most cells under one subscription's lock that a status or a charge reads
53
+ * (security audit, 30 Oct). Anyone can create cells under any lock, and each
54
+ * cell read costs RPC calls (its creating transaction, and that transaction's
55
+ * inputs), so without a bound one request could be made to cost thousands. A
56
+ * subscription in use holds one cell, or a few after top-ups; 16 is far above that.
57
+ *
58
+ * Beyond it, a status or a charge is refused with a `SessionPolicyError` of
59
+ * reason "too-many-cells", never answered from part of the cells: the cells
60
+ * left unread could hold the newest charge (so access would be denied as
61
+ * unpaid, with a wrong balance) or a mature cell (so a charge would leave it free
62
+ * to pay a second price in the same period). Refusing grants nothing and charges
63
+ * nothing. Only cells of the subscription's own kind count (plain CKB, or its
64
+ * token). The subscriber clears cells planted among them with
65
+ * `clearPlantedCells`, which leaves the subscription's own cells as they are,
66
+ * or with `cancelSubscription`.
67
+ */
68
+ export declare const MAX_SUBSCRIPTION_CELLS = 16;
69
+ /**
70
+ * The most transactions that only created cells under the lock that `lastCharge`
71
+ * pages past in the recent history (review W6 M2). Anyone can make those, but
72
+ * they are skipped without a fetch, so only the indexer's pages cost anything
73
+ * (`HISTORY_PAGE` entries each). Past it, a status or a charge is refused
74
+ * rather than stopping short of the last charge: blocking one period this way
75
+ * takes over 1024 transactions within it, each locking up at least a cell's
76
+ * size (153 CKB) under the subscription's lock.
77
+ */
78
+ export declare const MAX_CREATED_HISTORY: number;
79
+ /**
80
+ * The memo `clearPlantedCells` puts in the witness (`input_type`) of the
81
+ * subscriber's own input, under the subscriber's signature: a status tells its
82
+ * cleanup from a cancel by it, so a cleanup does not end a paid period (review
83
+ * W6 L2). Only the subscriber can sign it; a subscriber who marks a cancel
84
+ * with it keeps access only through a period already paid for.
85
+ */
86
+ export declare const CLEAR_PLANTED_MEMO: `0x${string}`;
87
+ /**
88
+ * The subscriber opens a subscription, in one transaction and one signature: the
89
+ * first period's price goes to `payTo` at once (a new cell cannot be charged for a
90
+ * whole period, so otherwise the first period would be free; review H1), and one
91
+ * cell holding `budget` shannons goes under the session lock. No key cell: the
92
+ * creator already owns cells under its charger key. `budget` of the cell's own
93
+ * size (153 CKB) plus n prices pays n more periods; what is left comes back on
94
+ * cancel. With a price of 153 CKB or more, the last 153 can be charged too.
95
+ *
96
+ * `account` (the subscriber's id at the creator) is committed in the witness of
97
+ * the subscriber's own input, which the subscriber's signature covers; the
98
+ * creator checks it with `verifySubscription`, so nobody else can present this
99
+ * subscription as theirs (review H3).
100
+ *
101
+ * `payTo` should be an anyone-can-pay address with a live cell, so a charge of
102
+ * any size tops it up; otherwise each charge creates a new cell and the price must
103
+ * be at least that cell's size (61 CKB for a plain address). Keep a plain cell in
104
+ * the wallet: cancelling needs one to prove ownership.
105
+ *
106
+ * With `token`, the subscription is paid in that plain xUDT and `price` is in
107
+ * token units: the cell holds `token.amount` units plus its own size in CKB
108
+ * (282 CKB; `budget` may add more, which stays until cancel), and the first price
109
+ * tops up `payTo`'s cell of the token. `payTo` must then be anyone-can-pay and
110
+ * already hold a cell of the token: no CKB may leave a token subscription, so a
111
+ * charge cannot pay for a new token cell.
112
+ */
113
+ export declare function openSubscription(subscriber: ccc.Signer, deployment: SessionLockDeployment, opts: {
114
+ charger: string;
115
+ payTo: string;
116
+ price: bigint;
117
+ periodBlocks: bigint;
118
+ /** Shannons in the subscription cell. Required without `token`; with one, defaults to the cell's own size. */
119
+ budget?: bigint;
120
+ /** A token subscription: the plain xUDT it is paid in, and the units put in the cell (beyond the first price). */
121
+ token?: {
122
+ type: ccc.ScriptLike;
123
+ amount: bigint;
124
+ };
125
+ account?: string;
126
+ beforeSend?: (sub: Subscription) => Promise<void>;
127
+ /** Let an unreviewed charger lock through, with a warning (`allowUnreviewedOwnerLock` covers the subscriber's own lock only). */
128
+ allowUnreviewedChargerLock?: boolean;
129
+ } & SigningLockOptions): Promise<{
130
+ txHash: ccc.Hex;
131
+ subscription: Subscription;
132
+ }>;
133
+ /**
134
+ * The live cells of a subscription, their balance (shannons) and token balance
135
+ * (units; 0 without a token), and how many blocks until one can be charged: once
136
+ * a cell is a period old and the last charge (the newest transaction that spent
137
+ * a cell under the lock) is too. Throws a `SessionPolicyError` ("too-many-cells")
138
+ * with over `MAX_SUBSCRIPTION_CELLS` cells under the lock.
139
+ */
140
+ export declare function findSubscriptionCells(client: ccc.Client, deployment: SessionLockDeployment, sub: Subscription): Promise<{
141
+ lock: ccc.Script;
142
+ cells: ccc.Cell[];
143
+ balance: bigint;
144
+ tokenBalance: bigint;
145
+ nextChargeIn: bigint | undefined;
146
+ }>;
147
+ /**
148
+ * The creator's charger key takes one price from the subscription and pays it to
149
+ * `payTo` (topping up its anyone-can-pay cell with `topUp`). The fee comes from
150
+ * one of the charger's own plain cells, which goes back to it smaller. Throws a
151
+ * `SessionBalanceError` while no cell is a full period old or the last charge is
152
+ * under one, or once what is left cannot cover a price and still leave a cell behind.
153
+ *
154
+ * In a token subscription the price is in token units and always tops up
155
+ * `payTo`'s cell of the token (`topUp` is implied). The change keeps every
156
+ * shannon that came in (no CKB may leave) and the tokens less one price, in
157
+ * exactly 16 data bytes.
158
+ *
159
+ * With `topUp` (or a token), `payTo`'s cell is a confirmed one that no
160
+ * transaction in the node's pool spends, where the node can tell
161
+ * (`pickPayoutCell`). A charge the node rejects for a spent or unknown input is
162
+ * built again after clearing the client's cache, with another payout cell,
163
+ * after a short wait: up to `retry.attempts` in all (default 3), waiting
164
+ * `retry.delayMs` (default 1000) doubling each time (`rebuildOnDeadInputs`).
165
+ * `beforeSend` runs again with each new hash. A send whose outcome is unknown
166
+ * is never retried.
167
+ */
168
+ export declare function chargeSubscription(charger: ccc.Signer, deployment: SessionLockDeployment, sub: Subscription, opts: ChargeOptions): Promise<ccc.Hex>;
169
+ type ChargeOptions = {
170
+ payTo: string;
171
+ topUp?: boolean;
172
+ beforeSend?: (txHash: ccc.Hex) => Promise<void>;
173
+ retry?: RetryOptions;
174
+ };
175
+ /**
176
+ * The subscriber takes everything left back, in owner mode: no creator involved
177
+ * and no wait. `nothingFound` means no cell is left (already cancelled, or run dry).
178
+ * In a token subscription the tokens come back as one token cell (`refundedTokens`).
179
+ * Subscriptions are also found and swept by `recoverSessions`, since their args
180
+ * begin with the subscriber's lock hash like any session's.
181
+ */
182
+ export declare function cancelSubscription(subscriber: ccc.Signer, deployment: SessionLockDeployment, sub: Subscription): Promise<{
183
+ txHash?: ccc.Hex;
184
+ refunded: bigint;
185
+ refundedTokens?: bigint;
186
+ nothingFound?: true;
187
+ }>;
188
+ /**
189
+ * The subscriber clears cells a third party created under the subscription's
190
+ * lock, without cancelling (mentor review, 2 Nov). Anyone can create cells under
191
+ * any lock; past `MAX_SUBSCRIPTION_CELLS` of the subscription's kind, a status or
192
+ * a charge is refused ("too-many-cells").
193
+ *
194
+ * A cell is the subscription's own when its creating transaction is the
195
+ * opening (`sub.openTx`), or had an input under the subscriber's or the
196
+ * charger's lock: an opening, a charge, a top-up by the subscriber. Nobody else
197
+ * can sign for those. Every other cell of the subscription's kind is planted:
198
+ * one owner-mode transaction (one signature) sweeps them to the subscriber,
199
+ * who keeps their CKB and tokens. The subscription's own cells are not touched,
200
+ * so their age and the payments that made them still count: access and the
201
+ * charge schedule go on as before.
202
+ *
203
+ * In a token subscription, look-alikes of the token (cells typed with the
204
+ * token's code and longer args that start with the token's, which xUDT accepts
205
+ * and anyone can create with 0 units) are planted too, and swept the same way
206
+ * when an owner sweep can spend them (plain xUDT, no flags; review W6 M1).
207
+ *
208
+ * Bounded: one call keeps at most `4 * MAX_SUBSCRIPTION_CELLS` cells of the
209
+ * kind, reads at most twice as many listed cells for each of the two scans,
210
+ * and looks each creating transaction up once. `more` says cells were left
211
+ * unread: call again once this sweep commits. Refused when none of the cells read is the
212
+ * subscription's own: cancel instead. `nothingFound` when nothing is planted.
213
+ */
214
+ export declare function clearPlantedCells(subscriber: ccc.Signer, deployment: SessionLockDeployment, sub: Subscription): Promise<{
215
+ txHash?: ccc.Hex;
216
+ swept: number;
217
+ refunded: bigint;
218
+ refundedTokens?: bigint;
219
+ more: boolean;
220
+ nothingFound?: true;
221
+ }>;
222
+ export type SubscriptionCheck = {
223
+ ok: true;
224
+ subscription: Subscription;
225
+ /** Shannons in the cell. */
226
+ balance: bigint;
227
+ /** Token units in the cell (token subscriptions only). */
228
+ tokenBalance?: bigint;
229
+ /** Charges the cell can still pay. */
230
+ chargesLeft: bigint;
231
+ /** The block until which the subscriber has paid: the block that created this cell, plus one period. */
232
+ paidThroughBlock: bigint;
233
+ } | {
234
+ ok: false;
235
+ reason: string;
236
+ };
237
+ /**
238
+ * The creator checks a subscription from the chain alone. Nothing in the request
239
+ * is trusted but the out point. The cell at `outPoint` must be live, under this
240
+ * deployment of the session lock (never an older one), untyped and without data,
241
+ * with 112-byte args holding exactly these terms, and at least `minBalance`.
242
+ *
243
+ * And the transaction that created the cell must have paid `payTo` at least one
244
+ * price: the opening pays the first period, every charge pays one. A cell the
245
+ * subscriber re-created in owner mode to reset its age paid nothing, and is
246
+ * refused (review H2). So access is granted up to `paidThroughBlock`; check the
247
+ * current cell again (`findSubscriptionCells`) once that block has passed.
248
+ *
249
+ * With `account`, the creating transaction must also carry `subscriptionMemo(account)`
250
+ * in the witness of an input under the subscriber's lock, which only the
251
+ * subscriber's signature covers: someone else's subscription cannot be claimed
252
+ * (review H3). The memo is in the opening only, so check `account` once, when a
253
+ * subscription is registered, then keep the subscription by its lock.
254
+ *
255
+ * With `token` (a token subscription), the cell must instead be typed with exactly
256
+ * that token and hold its amount, with 160-byte args (`max_per_tx` 0, the token's
257
+ * type hash, `price` in token units as `max_per_tx_token`); `minBalance` is in token
258
+ * units, and the payment counted is token units into `payTo`'s cells of the token.
259
+ * Without `token`, a 160-byte cell is refused, and the reverse.
260
+ */
261
+ export declare function verifySubscription(client: ccc.Client, deployment: SessionLockDeployment, outPoint: ccc.OutPointLike, expected: {
262
+ charger: string;
263
+ payTo: string;
264
+ price: bigint;
265
+ periodBlocks: bigint;
266
+ minBalance?: bigint;
267
+ account?: string;
268
+ token?: ccc.ScriptLike;
269
+ }): Promise<SubscriptionCheck>;
270
+ export interface SubscriptionStatus {
271
+ /**
272
+ * The block until which the subscriber has paid, from the newest paying cell or
273
+ * a charge that took the whole balance; undefined when neither is left.
274
+ */
275
+ paidThroughBlock: bigint | undefined;
276
+ tip: bigint;
277
+ /** Whether the tip is within the paid period. */
278
+ active: boolean;
279
+ /** Shannons in the subscription's cells. */
280
+ balance: bigint;
281
+ /** Token units in the subscription's cells (token subscriptions only). */
282
+ tokenBalance?: bigint;
283
+ chargesLeft: bigint;
284
+ /** Blocks until the creator may charge again (0 = now). */
285
+ nextChargeIn: bigint | undefined;
286
+ }
287
+ /**
288
+ * Where a subscription stands, for a creator's server that keeps no database:
289
+ * the subscriber sends the terms' subscriber lock hash, the opening's hash and
290
+ * its account id on every request. The opening must have paid the first price
291
+ * into a cell under exactly this subscription's lock and committed to `account`
292
+ * under the subscriber's signature; then the live cells give the period paid for.
293
+ * Each must have been created by a payment, so a cell re-created by the
294
+ * subscriber to reset its age counts for nothing. Cancelling ends access at once.
295
+ * A token subscription (`sub.token`) counts payments in its token, as `verifySubscription` does.
296
+ *
297
+ * Throws a `SessionPolicyError` of reason "too-many-cells" when over
298
+ * `MAX_SUBSCRIPTION_CELLS` cells (or recent transactions) sit under the lock:
299
+ * not a verdict on the subscription, so not an `ok: false`. A server should
300
+ * answer it as a conflict the subscriber can clear by cancelling and opening again.
301
+ */
302
+ export declare function subscriptionStatus(client: ccc.Client, deployment: SessionLockDeployment, sub: Subscription, proof: {
303
+ openTx: ccc.HexLike;
304
+ account: string;
305
+ }): Promise<{
306
+ ok: true;
307
+ status: SubscriptionStatus;
308
+ } | {
309
+ ok: false;
310
+ reason: string;
311
+ }>;
312
+ /**
313
+ * About how many blocks `seconds` is, from the average block time over the last
314
+ * `sample` blocks. Block time varies; round up when a period means "no more often than".
315
+ */
316
+ export declare function blocksForSeconds(client: ccc.Client, seconds: number, sample?: bigint): Promise<bigint>;
317
+ export {};