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/CHANGELOG.md +337 -0
- package/LICENSE +21 -0
- package/README.md +718 -3
- package/dist/deployment.d.ts +21 -0
- package/dist/deployment.js +92 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +9 -0
- package/dist/lock.d.ts +70 -0
- package/dist/lock.js +106 -0
- package/dist/onchain.d.ts +451 -0
- package/dist/onchain.js +1150 -0
- package/dist/paywall.d.ts +60 -0
- package/dist/paywall.js +175 -0
- package/dist/place.d.ts +113 -0
- package/dist/place.js +155 -0
- package/dist/session.d.ts +71 -0
- package/dist/session.js +57 -0
- package/dist/store.d.ts +27 -0
- package/dist/store.js +192 -0
- package/dist/subscription.d.ts +317 -0
- package/dist/subscription.js +959 -0
- package/dist/transfer.d.ts +19 -0
- package/dist/transfer.js +33 -0
- package/package.json +80 -6
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ccc } from "@ckb-ccc/core";
|
|
2
|
+
import { type SessionLockDeployment } from "./lock.js";
|
|
3
|
+
/**
|
|
4
|
+
* Throws a `SessionPolicyError` unless new cells may go under `deployment` on
|
|
5
|
+
* `client`'s network:
|
|
6
|
+
*
|
|
7
|
+
* - it is not one of `TESTNET_LEGACY_DEPLOYMENTS` (older code with fewer rules;
|
|
8
|
+
* the 13 Oct one cannot even spend a token session's 128/160-byte args);
|
|
9
|
+
* - its `hashType` is not `data`: under CKB-VM version 0 the lock faults on
|
|
10
|
+
* load, owner mode included, so such cells could never be spent;
|
|
11
|
+
* - its cell dep is live on this network and holds the code: the code cell's
|
|
12
|
+
* data hashes to `codeHash` (`data1`, `data2`) or its type script hashes to
|
|
13
|
+
* it (`type`). A `depGroup` cell dep is resolved and any member may match.
|
|
14
|
+
* This catches a testnet deployment on a mainnet client, a typo in
|
|
15
|
+
* `codeHash` and a wrong out point, any of which would leave funds under a
|
|
16
|
+
* lock no code can run.
|
|
17
|
+
*
|
|
18
|
+
* A passing check is remembered for that client and deployment; a failing one
|
|
19
|
+
* is not, so the next call looks again.
|
|
20
|
+
*/
|
|
21
|
+
export declare function assertCurrentDeployment(client: ccc.Client, deployment: SessionLockDeployment): Promise<void>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { ccc } from "@ckb-ccc/core";
|
|
2
|
+
import { TESTNET_LEGACY_DEPLOYMENTS } from "./lock.js";
|
|
3
|
+
import { SessionPolicyError } from "./session.js";
|
|
4
|
+
/**
|
|
5
|
+
* Checks run before any new cell goes under the session lock (audit round 2, 31 Oct).
|
|
6
|
+
*
|
|
7
|
+
* Only paths that CREATE cells under a deployment call this: `openSession`,
|
|
8
|
+
* `openSubscription`. Spending, closing, recovering, charging and cancelling
|
|
9
|
+
* still accept any deployment, legacy ones included, so funds already under an
|
|
10
|
+
* old lock can always come back.
|
|
11
|
+
*/
|
|
12
|
+
/** Successful checks, per client and deployment: one lookup per process, not per call. */
|
|
13
|
+
const verified = new WeakMap();
|
|
14
|
+
const keyOf = (d) => {
|
|
15
|
+
const op = ccc.OutPoint.from(d.cellDep.outPoint);
|
|
16
|
+
return [ccc.hexFrom(d.codeHash), ccc.hashTypeFrom(d.hashType), op.txHash, op.index, ccc.depTypeFrom(d.cellDep.depType)].join(":");
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Throws a `SessionPolicyError` unless new cells may go under `deployment` on
|
|
20
|
+
* `client`'s network:
|
|
21
|
+
*
|
|
22
|
+
* - it is not one of `TESTNET_LEGACY_DEPLOYMENTS` (older code with fewer rules;
|
|
23
|
+
* the 13 Oct one cannot even spend a token session's 128/160-byte args);
|
|
24
|
+
* - its `hashType` is not `data`: under CKB-VM version 0 the lock faults on
|
|
25
|
+
* load, owner mode included, so such cells could never be spent;
|
|
26
|
+
* - its cell dep is live on this network and holds the code: the code cell's
|
|
27
|
+
* data hashes to `codeHash` (`data1`, `data2`) or its type script hashes to
|
|
28
|
+
* it (`type`). A `depGroup` cell dep is resolved and any member may match.
|
|
29
|
+
* This catches a testnet deployment on a mainnet client, a typo in
|
|
30
|
+
* `codeHash` and a wrong out point, any of which would leave funds under a
|
|
31
|
+
* lock no code can run.
|
|
32
|
+
*
|
|
33
|
+
* A passing check is remembered for that client and deployment; a failing one
|
|
34
|
+
* is not, so the next call looks again.
|
|
35
|
+
*/
|
|
36
|
+
export async function assertCurrentDeployment(client, deployment) {
|
|
37
|
+
const codeHash = ccc.hexFrom(deployment.codeHash);
|
|
38
|
+
if (TESTNET_LEGACY_DEPLOYMENTS.some((d) => ccc.hexFrom(d.codeHash) === codeHash)) {
|
|
39
|
+
throw new SessionPolicyError("this is an old deployment of the session lock: new sessions and subscriptions go under the current one " +
|
|
40
|
+
"(old deployments are still accepted to spend, close, recover and cancel)");
|
|
41
|
+
}
|
|
42
|
+
if (ccc.hashTypeFrom(deployment.hashType) === "data") {
|
|
43
|
+
throw new SessionPolicyError("the session lock cannot run under hash_type data (CKB-VM version 0): use data1 or data2 (or type)");
|
|
44
|
+
}
|
|
45
|
+
let byKey = verified.get(client);
|
|
46
|
+
if (!byKey)
|
|
47
|
+
verified.set(client, (byKey = new Map()));
|
|
48
|
+
const key = keyOf(deployment);
|
|
49
|
+
let check = byKey.get(key);
|
|
50
|
+
if (!check) {
|
|
51
|
+
check = checkCode(client, deployment);
|
|
52
|
+
byKey.set(key, check);
|
|
53
|
+
check.catch(() => byKey.delete(key));
|
|
54
|
+
}
|
|
55
|
+
await check;
|
|
56
|
+
}
|
|
57
|
+
async function checkCode(client, d) {
|
|
58
|
+
const codeHash = ccc.hexFrom(d.codeHash);
|
|
59
|
+
const byType = ccc.hashTypeFrom(d.hashType) === "type";
|
|
60
|
+
const outPoint = ccc.OutPoint.from(d.cellDep.outPoint);
|
|
61
|
+
const where = `${outPoint.txHash}:${Number(outPoint.index)}`;
|
|
62
|
+
const dep = await client.getCellLive(outPoint, true);
|
|
63
|
+
if (!dep) {
|
|
64
|
+
throw new SessionPolicyError(`the session lock's cell dep ${where} is not a live cell on this network: a deployment for another ` +
|
|
65
|
+
"network (TESTNET_DEPLOYMENT on mainnet?) or a wrong out point; no funds go under a lock with no code");
|
|
66
|
+
}
|
|
67
|
+
let candidates = [dep];
|
|
68
|
+
if (ccc.depTypeFrom(d.cellDep.depType) === "depGroup") {
|
|
69
|
+
const members = depGroupMembers(dep.outputData);
|
|
70
|
+
if (!members)
|
|
71
|
+
throw new SessionPolicyError(`the session lock's dep group ${where} does not hold a list of out points`);
|
|
72
|
+
candidates = (await Promise.all(members.map((m) => client.getCellLive(m, true)))).filter((c) => !!c);
|
|
73
|
+
}
|
|
74
|
+
const holds = (c) => (byType ? c.cellOutput.type?.hash() === codeHash : ccc.hashCkb(c.outputData) === codeHash);
|
|
75
|
+
if (!candidates.some(holds)) {
|
|
76
|
+
throw new SessionPolicyError(`the session lock's cell dep ${where} does not hold code whose ${byType ? "type hash" : "data hash"} is ` +
|
|
77
|
+
`${codeHash}: a typo in codeHash or a wrong out point; no funds go under a lock with no code`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** A dep group cell's data: a molecule OutPointVec (u32 LE count, then 36-byte out points). */
|
|
81
|
+
function depGroupMembers(data) {
|
|
82
|
+
const b = ccc.bytesFrom(data);
|
|
83
|
+
if (b.length < 4)
|
|
84
|
+
return undefined;
|
|
85
|
+
const n = Number(ccc.numLeFromBytes(b.slice(0, 4)));
|
|
86
|
+
if (b.length !== 4 + 36 * n)
|
|
87
|
+
return undefined;
|
|
88
|
+
return Array.from({ length: n }, (_, i) => {
|
|
89
|
+
const at = 4 + 36 * i;
|
|
90
|
+
return ccc.OutPoint.from({ txHash: b.slice(at, at + 32), index: ccc.numLeFromBytes(b.slice(at + 32, at + 36)) });
|
|
91
|
+
});
|
|
92
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { checkRequest, createSession, isActive, SessionPolicyError } from "./session.js";
|
|
2
|
+
export type { CreateOptions, ScopeCheck, Session, SessionPolicy, SessionPolicyReason, SessionScope } from "./session.js";
|
|
3
|
+
export { deserializeSession, indexedDbStore, memoryStore, serializeSession, SessionStoreConflict } from "./store.js";
|
|
4
|
+
export type { SessionStore } from "./store.js";
|
|
5
|
+
export { ScopeError, sendInScope, sessionAddress, sessionSigner } from "./transfer.js";
|
|
6
|
+
export { relativeBlocksSince, SESSION_LOCK_ERRORS, sessionLockArgs, sessionLockCellDep, sessionLockErrorFrom, sessionLockScript, TESTNET_DEPLOYMENT, TESTNET_LEGACY_DEPLOYMENTS, } from "./lock.js";
|
|
7
|
+
export type { SessionLockDeployment, SessionLockParams } from "./lock.js";
|
|
8
|
+
export { assertCurrentDeployment } from "./deployment.js";
|
|
9
|
+
export { assertPlainXudt, assertSigningLock, closeSession, createPayoutCells, DEFAULT_RETRY, findOwnedSessionCells, findSessionCells, keyLockOf, matureCells, MAX_PAYOUT_CELLS, MAX_POOL_CHECKS, onchainRecipient, openSession, pickPayoutCell, poolConflict, recoverSessions, SendOutcomeUnknownError, SessionBalanceError, sessionLockOf, spendInSession, tokenAmount, } from "./onchain.js";
|
|
10
|
+
export type { OnChainBinding, RetryOptions, SessionBalanceReason, SessionCells, SessionWarning, SigningLockOptions, } from "./onchain.js";
|
|
11
|
+
export { blocksForSeconds, cancelSubscription, chargeSubscription, clearPlantedCells, findSubscriptionCells, MAX_SUBSCRIPTION_CELLS, openSubscription, subscriptionLock, subscriptionMemo, subscriptionStatus, verifySubscription, } from "./subscription.js";
|
|
12
|
+
export type { Subscription, SubscriptionCheck, SubscriptionStatus } from "./subscription.js";
|
|
13
|
+
export { paymentMemo, signAccess, verifyAccess } from "./paywall.js";
|
|
14
|
+
export type { AccessCheck, AccessProof } from "./paywall.js";
|
|
15
|
+
export { applyPlacements, checkPlacement, decodePlacement, encodePlacement, PLACE_BOARD, placementMemo, placementPrice, PlaceMemoError, } from "./place.js";
|
|
16
|
+
export type { BoardState, Pixel, PlaceBoard, PlacedPixel, Placement, PlacementCheck } from "./place.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { checkRequest, createSession, isActive, SessionPolicyError } from "./session.js";
|
|
2
|
+
export { deserializeSession, indexedDbStore, memoryStore, serializeSession, SessionStoreConflict } from "./store.js";
|
|
3
|
+
export { ScopeError, sendInScope, sessionAddress, sessionSigner } from "./transfer.js";
|
|
4
|
+
export { relativeBlocksSince, SESSION_LOCK_ERRORS, sessionLockArgs, sessionLockCellDep, sessionLockErrorFrom, sessionLockScript, TESTNET_DEPLOYMENT, TESTNET_LEGACY_DEPLOYMENTS, } from "./lock.js";
|
|
5
|
+
export { assertCurrentDeployment } from "./deployment.js";
|
|
6
|
+
export { assertPlainXudt, assertSigningLock, closeSession, createPayoutCells, DEFAULT_RETRY, findOwnedSessionCells, findSessionCells, keyLockOf, matureCells, MAX_PAYOUT_CELLS, MAX_POOL_CHECKS, onchainRecipient, openSession, pickPayoutCell, poolConflict, recoverSessions, SendOutcomeUnknownError, SessionBalanceError, sessionLockOf, spendInSession, tokenAmount, } from "./onchain.js";
|
|
7
|
+
export { blocksForSeconds, cancelSubscription, chargeSubscription, clearPlantedCells, findSubscriptionCells, MAX_SUBSCRIPTION_CELLS, openSubscription, subscriptionLock, subscriptionMemo, subscriptionStatus, verifySubscription, } from "./subscription.js";
|
|
8
|
+
export { paymentMemo, signAccess, verifyAccess } from "./paywall.js";
|
|
9
|
+
export { applyPlacements, checkPlacement, decodePlacement, encodePlacement, PLACE_BOARD, placementMemo, placementPrice, PlaceMemoError, } from "./place.js";
|
package/dist/lock.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { ccc } from "@ckb-ccc/core";
|
|
2
|
+
/**
|
|
3
|
+
* Client side of the on-chain session lock (contracts/session-lock).
|
|
4
|
+
*
|
|
5
|
+
* Args layout, 80 or 112 bytes:
|
|
6
|
+
* owner_lock_hash 32 | session_lock_hash 32 | max_per_tx u64 LE | min_interval u64 LE | [recipient_lock_hash 32]
|
|
7
|
+
*
|
|
8
|
+
* A token session (128 or 160 bytes) puts the token before the recipient:
|
|
9
|
+
* ... | min_interval | token_type_hash 32 | max_per_tx_token u128 LE | [recipient_lock_hash 32]
|
|
10
|
+
*/
|
|
11
|
+
export interface SessionLockParams {
|
|
12
|
+
/** Lock hash whose presence in a transaction's inputs grants owner mode. */
|
|
13
|
+
ownerLockHash: ccc.HexLike;
|
|
14
|
+
/** Lock hash of the session key's own lock (its key cell); grants session mode. */
|
|
15
|
+
sessionLockHash: ccc.HexLike;
|
|
16
|
+
/** Most shannons one session-mode transaction may move out, fees included. */
|
|
17
|
+
maxPerTx: bigint;
|
|
18
|
+
/** Blocks each session-locked input must have aged (relative since); 0 = no rate limit. */
|
|
19
|
+
minInterval?: bigint;
|
|
20
|
+
/** If set, the only lock session-mode spends may pay (besides change, key cell, owner). */
|
|
21
|
+
recipientLockHash?: ccc.HexLike;
|
|
22
|
+
/** A token session: the one xUDT type its cells may hold, and the most units one spend may move. */
|
|
23
|
+
token?: {
|
|
24
|
+
typeHash: ccc.HexLike;
|
|
25
|
+
maxPerTx: bigint;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** Where the session lock code lives on a network. */
|
|
29
|
+
export interface SessionLockDeployment {
|
|
30
|
+
codeHash: ccc.HexLike;
|
|
31
|
+
hashType: ccc.HashTypeLike;
|
|
32
|
+
cellDep: {
|
|
33
|
+
outPoint: {
|
|
34
|
+
txHash: ccc.HexLike;
|
|
35
|
+
index: ccc.NumLike;
|
|
36
|
+
};
|
|
37
|
+
depType: ccc.DepTypeLike;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The session lock deployed on CKB public testnet. Kept identical to
|
|
42
|
+
* deployment/testnet.json by a test.
|
|
43
|
+
*/
|
|
44
|
+
export declare const TESTNET_DEPLOYMENT: SessionLockDeployment;
|
|
45
|
+
/**
|
|
46
|
+
* Earlier testnet deployments, newest first. Their code is immutable, so sessions
|
|
47
|
+
* opened under them keep their old rules; pass these to `recoverSessions` too so
|
|
48
|
+
* those sessions can still be swept. 13 Oct 2026: before token sessions (errors
|
|
49
|
+
* 19, 20) and untyped-only payments. 8 Oct 2026: before errors 16, 17 and 18.
|
|
50
|
+
*/
|
|
51
|
+
export declare const TESTNET_LEGACY_DEPLOYMENTS: SessionLockDeployment[];
|
|
52
|
+
/** Error codes returned by the session lock, for reading node rejections. */
|
|
53
|
+
export declare const SESSION_LOCK_ERRORS: Record<number, string>;
|
|
54
|
+
export declare function sessionLockArgs(p: SessionLockParams): ccc.Hex;
|
|
55
|
+
export declare function sessionLockScript(d: SessionLockDeployment, p: SessionLockParams): ccc.Script;
|
|
56
|
+
export declare function sessionLockCellDep(d: SessionLockDeployment): ccc.CellDep;
|
|
57
|
+
/** A relative `since` of `blocks` block numbers (RFC 0017), as the rate limit requires. */
|
|
58
|
+
export declare function relativeBlocksSince(blocks: bigint): bigint;
|
|
59
|
+
/**
|
|
60
|
+
* Pulls a session-lock error code out of a node's rejection message, if there is
|
|
61
|
+
* one. The session lock only ever runs as an input's lock: a code from a type
|
|
62
|
+
* script (xUDT on a token payment, say) is not one, and gives undefined. An
|
|
63
|
+
* input lock can still be another lock in the same transaction (the recipient's
|
|
64
|
+
* anyone-can-pay cell on a top-up); pass `sessionInputs`, the indexes of the
|
|
65
|
+
* session cells, to rule that out too.
|
|
66
|
+
*/
|
|
67
|
+
export declare function sessionLockErrorFrom(message: string, sessionInputs?: number[]): {
|
|
68
|
+
code: number;
|
|
69
|
+
meaning: string;
|
|
70
|
+
} | undefined;
|
package/dist/lock.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { ccc } from "@ckb-ccc/core";
|
|
2
|
+
/**
|
|
3
|
+
* The session lock deployed on CKB public testnet. Kept identical to
|
|
4
|
+
* deployment/testnet.json by a test.
|
|
5
|
+
*/
|
|
6
|
+
export const TESTNET_DEPLOYMENT = {
|
|
7
|
+
codeHash: "0xab419466529ea479590d4be7b23580ce2ddc640ba902735c9ad9a22dc64c08ce",
|
|
8
|
+
hashType: "data2",
|
|
9
|
+
cellDep: {
|
|
10
|
+
outPoint: { txHash: "0xf3098bdb9a7bb1c500990be0f1159b7e66f507fef6b7a4f904046024284877f2", index: "0x0" },
|
|
11
|
+
depType: "code",
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Earlier testnet deployments, newest first. Their code is immutable, so sessions
|
|
16
|
+
* opened under them keep their old rules; pass these to `recoverSessions` too so
|
|
17
|
+
* those sessions can still be swept. 13 Oct 2026: before token sessions (errors
|
|
18
|
+
* 19, 20) and untyped-only payments. 8 Oct 2026: before errors 16, 17 and 18.
|
|
19
|
+
*/
|
|
20
|
+
export const TESTNET_LEGACY_DEPLOYMENTS = [
|
|
21
|
+
{
|
|
22
|
+
codeHash: "0x1cb7724269e2e86359b5b47cad6b1860ccd6da254c25c651056c8431d11590cd",
|
|
23
|
+
hashType: "data2",
|
|
24
|
+
cellDep: {
|
|
25
|
+
outPoint: { txHash: "0x37cfe04f64fd4dbec3803a972a51b7080f9ca1a3042535e97b7a756d0e03b071", index: "0x0" },
|
|
26
|
+
depType: "code",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
codeHash: "0x1ae8b8f71ed1846e2823ecccd8c3f80527be2895689f5dae54a53bd42526bc20",
|
|
31
|
+
hashType: "data2",
|
|
32
|
+
cellDep: {
|
|
33
|
+
outPoint: { txHash: "0xab2e69b8b97c15bddeb80954edd6d33befcd2fe9e4243377a46f14a3e1c343d2", index: "0x0" },
|
|
34
|
+
depType: "code",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
/** Error codes returned by the session lock, for reading node rejections. */
|
|
39
|
+
export const SESSION_LOCK_ERRORS = {
|
|
40
|
+
10: "bad args",
|
|
41
|
+
11: "not authorised: neither owner nor session key present",
|
|
42
|
+
12: "outflow exceeds max_per_tx",
|
|
43
|
+
13: "recipient not allowed",
|
|
44
|
+
14: "rate limited: input too young",
|
|
45
|
+
15: "overflow (capacity or token amounts)",
|
|
46
|
+
16: "too many change cells: a session spend may not split the session cell",
|
|
47
|
+
17: "the key cell may not grow in a session spend",
|
|
48
|
+
18: "session cells and their change may not carry a type script (in a token session: none but the token)",
|
|
49
|
+
19: "malformed token data: under 16 bytes, or token change not exactly 16",
|
|
50
|
+
20: "token outflow exceeds max_per_tx_token",
|
|
51
|
+
};
|
|
52
|
+
const U64_MAX = (1n << 64n) - 1n;
|
|
53
|
+
function u64le(v) {
|
|
54
|
+
if (v < 0n || v > U64_MAX)
|
|
55
|
+
throw new RangeError(`not a u64: ${v}`);
|
|
56
|
+
const b = new Uint8Array(8);
|
|
57
|
+
new DataView(b.buffer).setBigUint64(0, v, true);
|
|
58
|
+
return b;
|
|
59
|
+
}
|
|
60
|
+
const U128_MAX = (1n << 128n) - 1n;
|
|
61
|
+
function u128le(v) {
|
|
62
|
+
if (v < 0n || v > U128_MAX)
|
|
63
|
+
throw new RangeError(`not a u128: ${v}`);
|
|
64
|
+
return ccc.numLeToBytes(v, 16);
|
|
65
|
+
}
|
|
66
|
+
function hash32(h, name) {
|
|
67
|
+
const b = ccc.bytesFrom(h);
|
|
68
|
+
if (b.length !== 32)
|
|
69
|
+
throw new RangeError(`${name} must be 32 bytes`);
|
|
70
|
+
return b;
|
|
71
|
+
}
|
|
72
|
+
export function sessionLockArgs(p) {
|
|
73
|
+
return ccc.hexFrom(ccc.bytesConcat(hash32(p.ownerLockHash, "ownerLockHash"), hash32(p.sessionLockHash, "sessionLockHash"), u64le(p.maxPerTx), u64le(p.minInterval ?? 0n), ...(p.token === undefined ? [] : [hash32(p.token.typeHash, "token.typeHash"), u128le(p.token.maxPerTx)]), ...(p.recipientLockHash === undefined ? [] : [hash32(p.recipientLockHash, "recipientLockHash")])));
|
|
74
|
+
}
|
|
75
|
+
export function sessionLockScript(d, p) {
|
|
76
|
+
return ccc.Script.from({ codeHash: d.codeHash, hashType: d.hashType, args: sessionLockArgs(p) });
|
|
77
|
+
}
|
|
78
|
+
export function sessionLockCellDep(d) {
|
|
79
|
+
return ccc.CellDep.from(d.cellDep);
|
|
80
|
+
}
|
|
81
|
+
/** A relative `since` of `blocks` block numbers (RFC 0017), as the rate limit requires. */
|
|
82
|
+
export function relativeBlocksSince(blocks) {
|
|
83
|
+
if (blocks < 0n || blocks >= 1n << 56n)
|
|
84
|
+
throw new RangeError(`blocks out of range: ${blocks}`);
|
|
85
|
+
return (1n << 63n) | blocks;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Pulls a session-lock error code out of a node's rejection message, if there is
|
|
89
|
+
* one. The session lock only ever runs as an input's lock: a code from a type
|
|
90
|
+
* script (xUDT on a token payment, say) is not one, and gives undefined. An
|
|
91
|
+
* input lock can still be another lock in the same transaction (the recipient's
|
|
92
|
+
* anyone-can-pay cell on a top-up); pass `sessionInputs`, the indexes of the
|
|
93
|
+
* session cells, to rule that out too.
|
|
94
|
+
*/
|
|
95
|
+
export function sessionLockErrorFrom(message, sessionInputs) {
|
|
96
|
+
const m = message.match(/error code (-?\d+)/i) ?? message.match(/ValidationFailure\D+(-?\d+)/);
|
|
97
|
+
if (!m)
|
|
98
|
+
return undefined;
|
|
99
|
+
const source = message.match(/source: (Inputs|Outputs)\[(\d+)\]\.(Lock|Type)/);
|
|
100
|
+
if (source && (source[1] !== "Inputs" || source[3] !== "Lock"))
|
|
101
|
+
return undefined;
|
|
102
|
+
if (source && sessionInputs && !sessionInputs.includes(Number(source[2])))
|
|
103
|
+
return undefined;
|
|
104
|
+
const code = Number(m[1]);
|
|
105
|
+
return { code, meaning: SESSION_LOCK_ERRORS[code] ?? "unknown" };
|
|
106
|
+
}
|