@totemsdk/omnia-vtxo 0.2.1 → 0.4.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.
Files changed (83) hide show
  1. package/dist/anchor.d.ts +90 -0
  2. package/dist/anchor.js +112 -0
  3. package/dist/cjs/anchor.d.ts +90 -0
  4. package/dist/cjs/anchor.js +119 -0
  5. package/dist/cjs/commitment.d.ts +41 -0
  6. package/dist/cjs/commitment.js +173 -0
  7. package/dist/cjs/constants.d.ts +10 -0
  8. package/dist/cjs/constants.js +13 -0
  9. package/dist/cjs/durable-store.d.ts +33 -0
  10. package/dist/cjs/durable-store.js +93 -0
  11. package/dist/cjs/errors.d.ts +33 -0
  12. package/dist/cjs/errors.js +77 -0
  13. package/dist/cjs/exit-tx.d.ts +57 -0
  14. package/dist/cjs/exit-tx.js +83 -0
  15. package/dist/cjs/exit.d.ts +29 -0
  16. package/dist/cjs/exit.js +115 -0
  17. package/dist/cjs/index.d.ts +21 -0
  18. package/dist/cjs/index.js +37 -0
  19. package/dist/cjs/memory-store.d.ts +17 -0
  20. package/dist/cjs/memory-store.js +51 -0
  21. package/dist/cjs/merge.d.ts +8 -0
  22. package/dist/cjs/merge.js +106 -0
  23. package/dist/cjs/operator.d.ts +29 -0
  24. package/dist/cjs/operator.js +65 -0
  25. package/dist/cjs/package.json +1 -0
  26. package/dist/cjs/policy.d.ts +22 -0
  27. package/dist/cjs/policy.js +27 -0
  28. package/dist/cjs/pool-script.d.ts +36 -0
  29. package/dist/cjs/pool-script.js +60 -0
  30. package/dist/cjs/pool.d.ts +5 -0
  31. package/dist/cjs/pool.js +60 -0
  32. package/dist/cjs/receipt.d.ts +31 -0
  33. package/dist/cjs/receipt.js +52 -0
  34. package/dist/cjs/refresh.d.ts +8 -0
  35. package/dist/cjs/refresh.js +84 -0
  36. package/dist/cjs/serialization.d.ts +5 -0
  37. package/dist/cjs/serialization.js +31 -0
  38. package/dist/cjs/split.d.ts +8 -0
  39. package/dist/cjs/split.js +96 -0
  40. package/dist/cjs/transfer.d.ts +8 -0
  41. package/dist/cjs/transfer.js +152 -0
  42. package/dist/cjs/types.d.ts +166 -0
  43. package/dist/cjs/types.js +2 -0
  44. package/dist/cjs/verify.d.ts +23 -0
  45. package/dist/cjs/verify.js +121 -0
  46. package/dist/cjs/vtxo.d.ts +14 -0
  47. package/dist/cjs/vtxo.js +96 -0
  48. package/dist/commitment.d.ts +8 -0
  49. package/dist/commitment.js +38 -36
  50. package/dist/constants.d.ts +0 -2
  51. package/dist/constants.js +4 -9
  52. package/dist/durable-store.js +8 -11
  53. package/dist/errors.js +10 -23
  54. package/dist/exit-tx.d.ts +57 -0
  55. package/dist/exit-tx.js +76 -0
  56. package/dist/exit.d.ts +4 -1
  57. package/dist/exit.js +20 -26
  58. package/dist/index.d.ts +5 -0
  59. package/dist/index.js +21 -32
  60. package/dist/memory-store.js +3 -7
  61. package/dist/merge.js +15 -19
  62. package/dist/operator.d.ts +29 -0
  63. package/dist/operator.js +61 -0
  64. package/dist/policy.d.ts +1 -1
  65. package/dist/policy.js +3 -7
  66. package/dist/pool-script.d.ts +36 -0
  67. package/dist/pool-script.js +55 -0
  68. package/dist/pool.js +21 -27
  69. package/dist/receipt.d.ts +31 -0
  70. package/dist/receipt.js +48 -0
  71. package/dist/refresh.js +10 -14
  72. package/dist/serialization.js +4 -10
  73. package/dist/split.js +13 -17
  74. package/dist/transfer.js +13 -17
  75. package/dist/types.d.ts +3 -1
  76. package/dist/types.js +1 -2
  77. package/dist/verify.js +7 -13
  78. package/dist/vtxo.js +13 -19
  79. package/package.json +18 -8
  80. package/rust/pkg/omnia_vtxo_wasm_bg.wasm +0 -0
  81. package/rust/pkg/package.json +1 -1
  82. package/rust/pkg-node/omnia_vtxo_wasm_bg.wasm +0 -0
  83. package/rust/pkg-node/package.json +1 -1
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mergeVtxos = mergeVtxos;
4
+ const errors_js_1 = require("./errors.js");
5
+ const commitment_js_1 = require("./commitment.js");
6
+ /**
7
+ * Merges multiple VTXOs into one.
8
+ * @param now - Timestamp in ms. Defaults to Date.now() if omitted — pass explicitly for determinism.
9
+ */
10
+ function mergeVtxos(vtxos, params, now) {
11
+ const ts = now !== undefined ? now : Date.now();
12
+ if (vtxos.length < 2) {
13
+ throw new errors_js_1.VtxoMergeError('Merge requires at least 2 VTXOs');
14
+ }
15
+ const ids = vtxos.map(v => v.vtxoId);
16
+ const uniqueIds = new Set(ids);
17
+ if (uniqueIds.size !== ids.length) {
18
+ throw new errors_js_1.VtxoMergeError('Duplicate VTXO IDs detected in merge — double-counting prevented');
19
+ }
20
+ for (const vtxo of vtxos) {
21
+ if (vtxo.status !== 'active') {
22
+ throw new errors_js_1.VtxoStatusError(`Cannot merge VTXO ${vtxo.vtxoId}: status is '${vtxo.status}'`, vtxo.status);
23
+ }
24
+ }
25
+ const poolId = vtxos[0].poolId;
26
+ const tokenId = vtxos[0].tokenId;
27
+ const inputOwner = vtxos[0].owner;
28
+ for (const vtxo of vtxos) {
29
+ if (vtxo.poolId !== poolId) {
30
+ throw new errors_js_1.VtxoMergeError(`Cannot merge VTXOs from different pools: ${vtxo.poolId} vs ${poolId}`);
31
+ }
32
+ if (vtxo.tokenId !== tokenId) {
33
+ throw new errors_js_1.VtxoMergeError(`Cannot merge VTXOs with different tokenIds: ${vtxo.tokenId} vs ${tokenId}`);
34
+ }
35
+ if (vtxo.owner !== inputOwner) {
36
+ throw new errors_js_1.VtxoOwnershipError(`Cannot merge VTXOs with different owners: ${vtxo.owner} vs ${inputOwner}`);
37
+ }
38
+ }
39
+ if (params.owner !== inputOwner) {
40
+ throw new errors_js_1.VtxoOwnershipError(`Merge output owner '${params.owner}' must match input owner '${inputOwner}'`);
41
+ }
42
+ const totalAmount = vtxos.reduce((sum, v) => sum + v.amount, BigInt(0));
43
+ const outputId = (0, commitment_js_1.computeVtxoId)({
44
+ poolId,
45
+ owner: params.owner,
46
+ amount: totalAmount,
47
+ tokenId,
48
+ nonce: params.nonce,
49
+ });
50
+ const inputVtxos = vtxos.map(vtxo => ({
51
+ ...vtxo,
52
+ status: 'merged',
53
+ updatedAt: ts,
54
+ history: [
55
+ ...vtxo.history,
56
+ {
57
+ op: 'merge',
58
+ at: ts,
59
+ relatedIds: [outputId],
60
+ },
61
+ ],
62
+ }));
63
+ const rawOutput = {
64
+ vtxoId: outputId,
65
+ poolId,
66
+ owner: params.owner,
67
+ amount: totalAmount,
68
+ tokenId,
69
+ status: 'active',
70
+ epoch: vtxos[0].epoch,
71
+ proof: {
72
+ leaf: '',
73
+ root: '',
74
+ siblings: [],
75
+ positions: [],
76
+ epoch: vtxos[0].epoch,
77
+ batchId: '',
78
+ },
79
+ history: [
80
+ {
81
+ op: 'merge',
82
+ at: ts,
83
+ relatedIds: ids,
84
+ },
85
+ ],
86
+ createdAt: ts,
87
+ updatedAt: ts,
88
+ };
89
+ const { proofs } = (0, commitment_js_1.buildVtxoProofSet)([rawOutput]);
90
+ const output = { ...rawOutput, proof: proofs[outputId] };
91
+ const receiptId = (0, commitment_js_1.computeReceiptId)(poolId, 'merge', ids, [outputId], ts);
92
+ return {
93
+ inputs: inputVtxos,
94
+ output,
95
+ receipt: {
96
+ receiptId,
97
+ poolId,
98
+ op: 'merge',
99
+ inputIds: ids,
100
+ outputIds: [outputId],
101
+ epoch: vtxos[0].epoch,
102
+ at: ts,
103
+ signature: '',
104
+ },
105
+ };
106
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * VTXO operator co-signature (RFC-038 V0).
3
+ *
4
+ * The operator signs the canonical batch commitment with a unified WOTS
5
+ * `TreeKey` (RFC-037), allocating one leaf per signature under a WOTS lease
6
+ * (RFC-013). This retires the mock operator-signature placeholder: a receipt's
7
+ * `signature` is now a real serialized `TreeSignature` over the commitment, and
8
+ * `verify` checks it against the operator's tree-root public key.
9
+ *
10
+ * The operator's identity is the TreeKey root public key, so a holder can verify
11
+ * a receipt without trusting transport identity.
12
+ */
13
+ import { type TreeKey } from '@totemsdk/core';
14
+ import type { WotsLeaseProvider } from '@totemsdk/wots-lease';
15
+ import type { OmniaVtxoOperator } from './types.js';
16
+ /** Lease treeId for a pool's operator signatures. */
17
+ export declare function vtxoOperatorTreeId(poolId: string): string;
18
+ export interface VtxoOperatorOptions {
19
+ poolId: string;
20
+ /** The operator's unified WOTS TreeKey (RFC-037). */
21
+ treeKey: TreeKey;
22
+ /** Lease provider for one-time leaf allocation (RFC-013). */
23
+ leaseProvider: WotsLeaseProvider;
24
+ }
25
+ export interface VtxoOperatorWithIdentity extends OmniaVtxoOperator {
26
+ /** The operator's tree-root public key (its identity). */
27
+ readonly publicKey: string;
28
+ }
29
+ export declare function createVtxoOperator(options: VtxoOperatorOptions): VtxoOperatorWithIdentity;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ /**
3
+ * VTXO operator co-signature (RFC-038 V0).
4
+ *
5
+ * The operator signs the canonical batch commitment with a unified WOTS
6
+ * `TreeKey` (RFC-037), allocating one leaf per signature under a WOTS lease
7
+ * (RFC-013). This retires the mock operator-signature placeholder: a receipt's
8
+ * `signature` is now a real serialized `TreeSignature` over the commitment, and
9
+ * `verify` checks it against the operator's tree-root public key.
10
+ *
11
+ * The operator's identity is the TreeKey root public key, so a holder can verify
12
+ * a receipt without trusting transport identity.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.vtxoOperatorTreeId = vtxoOperatorTreeId;
16
+ exports.createVtxoOperator = createVtxoOperator;
17
+ const core_1 = require("@totemsdk/core");
18
+ /** Keys per tree level — the flat leaf index is `l1 * KEYS_PER_LEVEL + l2`. */
19
+ const KEYS_PER_LEVEL = 64;
20
+ /** Lease treeId for a pool's operator signatures. */
21
+ function vtxoOperatorTreeId(poolId) {
22
+ return `omnia-vtxo:${poolId}`;
23
+ }
24
+ function createVtxoOperator(options) {
25
+ const { poolId, treeKey, leaseProvider } = options;
26
+ const rootPubkey = treeKey.getPublicKey();
27
+ // The TreeKey carries mutable state (setUses + sign); serialize signing so
28
+ // concurrent co-signs cannot race on a shared key.
29
+ let signLock = Promise.resolve();
30
+ const doSign = async (data) => {
31
+ const reservation = await leaseProvider.reserveKeyUse({
32
+ treeId: vtxoOperatorTreeId(poolId),
33
+ purpose: 'vtxo-operator',
34
+ payloadHash: (0, core_1.bytesToHex)((0, core_1.sha3_256)(data)),
35
+ });
36
+ const flat = reservation.indices.l1 * KEYS_PER_LEVEL + reservation.indices.l2;
37
+ try {
38
+ treeKey.setUses(flat);
39
+ const signature = (0, core_1.serializeTreeSignature)(treeKey.sign(data));
40
+ await leaseProvider.commitKeyUse(reservation.reservationId, 'vtxo-operator');
41
+ return '0x' + (0, core_1.bytesToHex)(signature);
42
+ }
43
+ catch (err) {
44
+ await leaseProvider.burnReservation(reservation.reservationId, 'sign-failed').catch(() => { });
45
+ throw err;
46
+ }
47
+ };
48
+ return {
49
+ poolId,
50
+ publicKey: '0x' + (0, core_1.bytesToHex)(rootPubkey).toLowerCase(),
51
+ sign(data) {
52
+ const run = signLock.then(() => doSign(data), () => doSign(data));
53
+ signLock = run.then(() => undefined, () => undefined);
54
+ return run;
55
+ },
56
+ async verify(data, sig) {
57
+ try {
58
+ return (0, core_1.verifyTreeSignature)(rootPubkey, data, (0, core_1.deserializeTreeSignature)((0, core_1.hexToBytes)(sig)));
59
+ }
60
+ catch {
61
+ return false;
62
+ }
63
+ },
64
+ };
65
+ }
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
@@ -0,0 +1,22 @@
1
+ import { OmniaVtxo, VtxoId } from './types.js';
2
+ export interface VtxoTransferIntent {
3
+ type: 'vtxo-transfer';
4
+ poolId: string;
5
+ inputId: VtxoId;
6
+ recipient: string;
7
+ amount: bigint;
8
+ tokenId: string;
9
+ nonce: string;
10
+ changeNonce?: string;
11
+ }
12
+ export interface VtxoExitIntent {
13
+ type: 'vtxo-exit';
14
+ poolId: string;
15
+ vtxoId: VtxoId;
16
+ owner: string;
17
+ amount: bigint;
18
+ tokenId: string;
19
+ draftType: 'vtxo-exit';
20
+ }
21
+ export declare function buildVtxoTransferIntent(vtxo: OmniaVtxo, recipient: string, amount: bigint, nonce: string, changeNonce?: string): VtxoTransferIntent;
22
+ export declare function buildVtxoExitIntent(vtxo: OmniaVtxo): VtxoExitIntent;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildVtxoTransferIntent = buildVtxoTransferIntent;
4
+ exports.buildVtxoExitIntent = buildVtxoExitIntent;
5
+ function buildVtxoTransferIntent(vtxo, recipient, amount, nonce, changeNonce) {
6
+ return {
7
+ type: 'vtxo-transfer',
8
+ poolId: vtxo.poolId,
9
+ inputId: vtxo.vtxoId,
10
+ recipient,
11
+ amount,
12
+ tokenId: vtxo.tokenId,
13
+ nonce,
14
+ changeNonce,
15
+ };
16
+ }
17
+ function buildVtxoExitIntent(vtxo) {
18
+ return {
19
+ type: 'vtxo-exit',
20
+ poolId: vtxo.poolId,
21
+ vtxoId: vtxo.vtxoId,
22
+ owner: vtxo.owner,
23
+ amount: vtxo.amount,
24
+ tokenId: vtxo.tokenId,
25
+ draftType: 'vtxo-exit',
26
+ };
27
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * VTXO pool on-chain script (RFC-038 V2 — non-custodial).
3
+ *
4
+ * The pool is an on-chain state output holding `totalCapacity`. An epoch's batch
5
+ * root is committed as STATE(100), the epoch number in STATE(101), and the
6
+ * owner's absolute exit-eligibility block in STATE(102).
7
+ *
8
+ * Custody (RFC-044 / CUSTODY-POLICY §2):
9
+ * - **Unilateral owner exit.** After `@BLOCK GT EXITBLOCK` the owner alone may
10
+ * spend (`RETURN SIGNEDBY(OWNER)`) — no operator signature or liveness. This
11
+ * is exercised by the pre-signed exit tx (`exit-tx.ts`).
12
+ * - **Cooperative update.** Before the exit window the pool may only move by
13
+ * `MULTISIG(2 OPERATOR OWNER)`, epoch-monotone (`ASSERT EPOCH GT
14
+ * PREVSTATE(101)`), with the exit block immutable (`ASSERT SAMESTATE(102
15
+ * 102)`) so the operator can never push the owner's escape clock, and the
16
+ * value confined to the same script address (`ASSERT VERIFYOUT(@INPUT
17
+ * @ADDRESS @AMOUNT @TOKENID TRUE)`) so the operator can never pay itself.
18
+ *
19
+ * The operator holds no unilateral path: it cannot move funds, cannot advance
20
+ * the exit block, and cannot stop the owner's exit. It is a liveness-only peer.
21
+ */
22
+ export interface PoolScriptParams {
23
+ /** Operator WOTS public-key digest (64 hex). */
24
+ operatorPublicKeyDigest: string;
25
+ /** Pool owner WOTS public-key digest (64 hex). */
26
+ ownerPublicKeyDigest: string;
27
+ /** Absolute block height after which the owner may exit unilaterally. */
28
+ exitBlock: number;
29
+ }
30
+ /** Non-custodial, epoch-monotone, owner-escapable state-carrying pool script. */
31
+ export declare function buildPoolScript(params: PoolScriptParams): string;
32
+ export declare function poolScriptAddress(script: string): string;
33
+ export declare function buildAndHashPoolScript(params: PoolScriptParams): {
34
+ script: string;
35
+ address: string;
36
+ };
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ /**
3
+ * VTXO pool on-chain script (RFC-038 V2 — non-custodial).
4
+ *
5
+ * The pool is an on-chain state output holding `totalCapacity`. An epoch's batch
6
+ * root is committed as STATE(100), the epoch number in STATE(101), and the
7
+ * owner's absolute exit-eligibility block in STATE(102).
8
+ *
9
+ * Custody (RFC-044 / CUSTODY-POLICY §2):
10
+ * - **Unilateral owner exit.** After `@BLOCK GT EXITBLOCK` the owner alone may
11
+ * spend (`RETURN SIGNEDBY(OWNER)`) — no operator signature or liveness. This
12
+ * is exercised by the pre-signed exit tx (`exit-tx.ts`).
13
+ * - **Cooperative update.** Before the exit window the pool may only move by
14
+ * `MULTISIG(2 OPERATOR OWNER)`, epoch-monotone (`ASSERT EPOCH GT
15
+ * PREVSTATE(101)`), with the exit block immutable (`ASSERT SAMESTATE(102
16
+ * 102)`) so the operator can never push the owner's escape clock, and the
17
+ * value confined to the same script address (`ASSERT VERIFYOUT(@INPUT
18
+ * @ADDRESS @AMOUNT @TOKENID TRUE)`) so the operator can never pay itself.
19
+ *
20
+ * The operator holds no unilateral path: it cannot move funds, cannot advance
21
+ * the exit block, and cannot stop the owner's exit. It is a liveness-only peer.
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.buildPoolScript = buildPoolScript;
25
+ exports.poolScriptAddress = poolScriptAddress;
26
+ exports.buildAndHashPoolScript = buildAndHashPoolScript;
27
+ const core_1 = require("@totemsdk/core");
28
+ function pkHex(digest, label) {
29
+ const raw = digest.replace(/^0x/i, '');
30
+ if (!/^[0-9a-fA-F]{64}$/.test(raw)) {
31
+ throw new Error(`${label} public key digest must be 64 hex chars, got ${JSON.stringify(digest)}`);
32
+ }
33
+ return '0X' + raw.toUpperCase();
34
+ }
35
+ /** Non-custodial, epoch-monotone, owner-escapable state-carrying pool script. */
36
+ function buildPoolScript(params) {
37
+ const owner = pkHex(params.ownerPublicKeyDigest, 'owner');
38
+ const operator = pkHex(params.operatorPublicKeyDigest, 'operator');
39
+ if (!Number.isInteger(params.exitBlock) || params.exitBlock < 0) {
40
+ throw new Error(`exitBlock must be a non-negative integer, got ${JSON.stringify(params.exitBlock)}`);
41
+ }
42
+ return [
43
+ 'LET EPOCH=STATE(101)',
44
+ 'LET EXITBLOCK=STATE(102)',
45
+ 'IF @BLOCK GT EXITBLOCK THEN',
46
+ ` RETURN SIGNEDBY(${owner})`,
47
+ 'ENDIF',
48
+ 'ASSERT EPOCH GT PREVSTATE(101)',
49
+ 'ASSERT SAMESTATE(102 102)',
50
+ 'ASSERT VERIFYOUT(@INPUT @ADDRESS @AMOUNT @TOKENID TRUE)',
51
+ `RETURN MULTISIG(2 ${operator} ${owner})`,
52
+ ].join('\n');
53
+ }
54
+ function poolScriptAddress(script) {
55
+ return (0, core_1.computeScriptAddress)(script);
56
+ }
57
+ function buildAndHashPoolScript(params) {
58
+ const script = buildPoolScript(params);
59
+ return { script, address: poolScriptAddress(script) };
60
+ }
@@ -0,0 +1,5 @@
1
+ import { OmniaVtxoPool, CreatePoolParams } from './types.js';
2
+ export declare function createPool(params: CreatePoolParams, now: number): OmniaVtxoPool;
3
+ export declare function assertPoolCanMint(pool: OmniaVtxoPool, amount: bigint): void;
4
+ export declare function updatePoolRoot(pool: OmniaVtxoPool, root: string): OmniaVtxoPool;
5
+ export declare function advancePoolEpoch(pool: OmniaVtxoPool, newEpoch: number): OmniaVtxoPool;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createPool = createPool;
4
+ exports.assertPoolCanMint = assertPoolCanMint;
5
+ exports.updatePoolRoot = updatePoolRoot;
6
+ exports.advancePoolEpoch = advancePoolEpoch;
7
+ const errors_js_1 = require("./errors.js");
8
+ const constants_js_1 = require("./constants.js");
9
+ const commitment_js_1 = require("./commitment.js");
10
+ function createPool(params, now) {
11
+ const policy = {
12
+ minAmount: params.policy?.minAmount ?? constants_js_1.DEFAULT_POLICY.minAmount,
13
+ maxAmount: params.policy?.maxAmount ?? constants_js_1.DEFAULT_POLICY.maxAmount,
14
+ maxMergeInputs: params.policy?.maxMergeInputs ?? constants_js_1.DEFAULT_POLICY.maxMergeInputs,
15
+ maxSplitOutputs: params.policy?.maxSplitOutputs ?? constants_js_1.DEFAULT_POLICY.maxSplitOutputs,
16
+ exitTimelockSeconds: params.policy?.exitTimelockSeconds ?? constants_js_1.DEFAULT_POLICY.exitTimelockSeconds,
17
+ };
18
+ if (params.totalCapacity <= BigInt(0)) {
19
+ throw new errors_js_1.VtxoPolicyError('Pool totalCapacity must be > 0');
20
+ }
21
+ const poolId = (0, commitment_js_1.computePoolId)({
22
+ operator: params.operator,
23
+ tokenId: params.tokenId,
24
+ nonce: params.nonce,
25
+ });
26
+ return {
27
+ poolId,
28
+ operator: params.operator,
29
+ tokenId: params.tokenId,
30
+ totalCapacity: params.totalCapacity,
31
+ availableCapacity: params.totalCapacity,
32
+ epoch: constants_js_1.EPOCH_ZERO,
33
+ commitmentRoot: constants_js_1.EMPTY_TREE_ROOT,
34
+ createdAt: now,
35
+ policy,
36
+ };
37
+ }
38
+ function assertPoolCanMint(pool, amount) {
39
+ if (amount <= BigInt(0)) {
40
+ throw new errors_js_1.VtxoPolicyError(`Mint amount must be > 0, got ${amount}`);
41
+ }
42
+ if (amount < pool.policy.minAmount) {
43
+ throw new errors_js_1.VtxoPolicyError(`Mint amount ${amount} is below pool minimum ${pool.policy.minAmount}`);
44
+ }
45
+ if (amount > pool.policy.maxAmount) {
46
+ throw new errors_js_1.VtxoPolicyError(`Mint amount ${amount} exceeds pool maximum ${pool.policy.maxAmount}`);
47
+ }
48
+ if (amount > pool.availableCapacity) {
49
+ throw new errors_js_1.VtxoPoolCapacityError(`Insufficient pool capacity: requested ${amount}, available ${pool.availableCapacity}`, amount, pool.availableCapacity);
50
+ }
51
+ }
52
+ function updatePoolRoot(pool, root) {
53
+ return { ...pool, commitmentRoot: root };
54
+ }
55
+ function advancePoolEpoch(pool, newEpoch) {
56
+ if (newEpoch <= pool.epoch) {
57
+ throw new errors_js_1.VtxoPolicyError(`New epoch ${newEpoch} must be greater than current epoch ${pool.epoch}`);
58
+ }
59
+ return { ...pool, epoch: newEpoch };
60
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * VTXO receipt co-signing (RFC-038 V0, verify-before-sign per §3.5).
3
+ *
4
+ * A mutation emits its `VtxoOperatorReceipt` **unsigned**; the operator co-signs
5
+ * it only after **recomputing** the batch root from the batch's leaves and
6
+ * checking epoch monotonicity, output membership, and (when supplied) amount
7
+ * conservation. This prevents a caller from getting the operator to co-sign an
8
+ * arbitrary `(root, epoch, ids)` tuple whose root does not commit the outputs.
9
+ */
10
+ import type { OmniaVtxo, OmniaVtxoOperator, VtxoOperatorReceipt } from './types.js';
11
+ /** The batch the receipt belongs to — the operator's source of truth. */
12
+ export interface VtxoReceiptBatch {
13
+ /** The VTXOs committed in this epoch's batch. */
14
+ outputs: OmniaVtxo[];
15
+ /** The batch root the receipt claims (the operator recomputes and compares). */
16
+ root: string;
17
+ /** The batch epoch. */
18
+ epoch: number;
19
+ /** The pool's current epoch, for monotonicity (must be strictly less). */
20
+ previousEpoch?: number;
21
+ /** Optional: total input amount, for a conservation check. */
22
+ inputsAmount?: bigint;
23
+ }
24
+ /**
25
+ * Co-sign a receipt with the operator, after verifying the batch.
26
+ * @throws if the root does not commit the leaves, the epoch is stale, an output
27
+ * is not in the batch, or conservation fails.
28
+ */
29
+ export declare function signVtxoReceipt(operator: OmniaVtxoOperator, receipt: VtxoOperatorReceipt, batch: VtxoReceiptBatch): Promise<VtxoOperatorReceipt>;
30
+ /** Verify a co-signed receipt against the operator and the batch root. */
31
+ export declare function verifyVtxoReceipt(operator: OmniaVtxoOperator, receipt: VtxoOperatorReceipt, root: string): Promise<boolean>;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ /**
3
+ * VTXO receipt co-signing (RFC-038 V0, verify-before-sign per §3.5).
4
+ *
5
+ * A mutation emits its `VtxoOperatorReceipt` **unsigned**; the operator co-signs
6
+ * it only after **recomputing** the batch root from the batch's leaves and
7
+ * checking epoch monotonicity, output membership, and (when supplied) amount
8
+ * conservation. This prevents a caller from getting the operator to co-sign an
9
+ * arbitrary `(root, epoch, ids)` tuple whose root does not commit the outputs.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.signVtxoReceipt = signVtxoReceipt;
13
+ exports.verifyVtxoReceipt = verifyVtxoReceipt;
14
+ const commitment_js_1 = require("./commitment.js");
15
+ /**
16
+ * Co-sign a receipt with the operator, after verifying the batch.
17
+ * @throws if the root does not commit the leaves, the epoch is stale, an output
18
+ * is not in the batch, or conservation fails.
19
+ */
20
+ async function signVtxoReceipt(operator, receipt, batch) {
21
+ if (receipt.epoch !== batch.epoch) {
22
+ throw new Error(`receipt epoch ${receipt.epoch} does not match batch epoch ${batch.epoch}`);
23
+ }
24
+ if (batch.previousEpoch !== undefined && batch.epoch <= batch.previousEpoch) {
25
+ throw new Error(`batch epoch ${batch.epoch} is not greater than the pool epoch ${batch.previousEpoch}`);
26
+ }
27
+ const idSet = new Set(batch.outputs.map((v) => v.vtxoId));
28
+ for (const id of receipt.outputIds) {
29
+ if (!idSet.has(id))
30
+ throw new Error(`receipt output '${id}' is not a VTXO in the batch`);
31
+ }
32
+ const recomputedRoot = (0, commitment_js_1.computeCommitmentRoot)(batch.outputs.map(commitment_js_1.computeVtxoLeaf));
33
+ if (recomputedRoot !== batch.root) {
34
+ throw new Error('batch root does not commit the batch leaves');
35
+ }
36
+ if (batch.inputsAmount !== undefined) {
37
+ const outputSum = batch.outputs.reduce((sum, v) => sum + v.amount, 0n);
38
+ if (outputSum > batch.inputsAmount) {
39
+ throw new Error(`conservation violated: outputs ${outputSum} exceed inputs ${batch.inputsAmount}`);
40
+ }
41
+ }
42
+ const data = (0, commitment_js_1.computeBatchCommitmentBytes)(receipt.poolId, receipt.epoch, batch.root, receipt.outputIds);
43
+ const signature = await operator.sign(data);
44
+ return { ...receipt, signature };
45
+ }
46
+ /** Verify a co-signed receipt against the operator and the batch root. */
47
+ async function verifyVtxoReceipt(operator, receipt, root) {
48
+ if (!receipt.signature)
49
+ return false;
50
+ const data = (0, commitment_js_1.computeBatchCommitmentBytes)(receipt.poolId, receipt.epoch, root, receipt.outputIds);
51
+ return operator.verify(data, receipt.signature);
52
+ }
@@ -0,0 +1,8 @@
1
+ import { OmniaVtxo, RefreshVtxoParams, RefreshResult, VtxoOperatorReceipt } from './types.js';
2
+ /**
3
+ * Refreshes a VTXO to a new epoch.
4
+ * @param now - Timestamp in ms. Defaults to Date.now() if omitted — pass explicitly for determinism.
5
+ */
6
+ export declare function refreshVtxo(vtxo: OmniaVtxo, params: RefreshVtxoParams, now?: number): RefreshResult & {
7
+ receipt: VtxoOperatorReceipt;
8
+ };
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.refreshVtxo = refreshVtxo;
4
+ const errors_js_1 = require("./errors.js");
5
+ const commitment_js_1 = require("./commitment.js");
6
+ /**
7
+ * Refreshes a VTXO to a new epoch.
8
+ * @param now - Timestamp in ms. Defaults to Date.now() if omitted — pass explicitly for determinism.
9
+ */
10
+ function refreshVtxo(vtxo, params, now) {
11
+ const ts = now !== undefined ? now : Date.now();
12
+ if (vtxo.status !== 'active') {
13
+ throw new errors_js_1.VtxoStatusError(`Cannot refresh VTXO ${vtxo.vtxoId}: status is '${vtxo.status}'`, vtxo.status);
14
+ }
15
+ if (params.newEpoch <= vtxo.epoch) {
16
+ throw new errors_js_1.VtxoPolicyError(`Refresh epoch ${params.newEpoch} must be strictly greater than current epoch ${vtxo.epoch}`);
17
+ }
18
+ const refreshedId = (0, commitment_js_1.computeVtxoId)({
19
+ poolId: vtxo.poolId,
20
+ owner: vtxo.owner,
21
+ amount: vtxo.amount,
22
+ tokenId: vtxo.tokenId,
23
+ nonce: params.nonce,
24
+ });
25
+ const oldVtxo = {
26
+ ...vtxo,
27
+ status: 'refreshed',
28
+ updatedAt: ts,
29
+ history: [
30
+ ...vtxo.history,
31
+ {
32
+ op: 'refresh',
33
+ at: ts,
34
+ relatedIds: [refreshedId],
35
+ meta: { newEpoch: params.newEpoch },
36
+ },
37
+ ],
38
+ };
39
+ const rawRefreshed = {
40
+ vtxoId: refreshedId,
41
+ poolId: vtxo.poolId,
42
+ owner: vtxo.owner,
43
+ amount: vtxo.amount,
44
+ tokenId: vtxo.tokenId,
45
+ status: 'active',
46
+ epoch: params.newEpoch,
47
+ proof: {
48
+ leaf: '',
49
+ root: '',
50
+ siblings: [],
51
+ positions: [],
52
+ epoch: params.newEpoch,
53
+ batchId: '',
54
+ },
55
+ history: [
56
+ {
57
+ op: 'refresh',
58
+ at: ts,
59
+ relatedIds: [vtxo.vtxoId],
60
+ meta: { prevEpoch: vtxo.epoch, newEpoch: params.newEpoch },
61
+ },
62
+ ],
63
+ createdAt: ts,
64
+ updatedAt: ts,
65
+ expiresAt: vtxo.expiresAt,
66
+ };
67
+ const { proofs } = (0, commitment_js_1.buildVtxoProofSet)([rawRefreshed]);
68
+ const refreshedVtxo = { ...rawRefreshed, proof: proofs[refreshedId] };
69
+ const receiptId = (0, commitment_js_1.computeReceiptId)(vtxo.poolId, 'refresh', [vtxo.vtxoId], [refreshedId], ts);
70
+ return {
71
+ old: oldVtxo,
72
+ refreshed: refreshedVtxo,
73
+ receipt: {
74
+ receiptId,
75
+ poolId: vtxo.poolId,
76
+ op: 'refresh',
77
+ inputIds: [vtxo.vtxoId],
78
+ outputIds: [refreshedId],
79
+ epoch: params.newEpoch,
80
+ at: ts,
81
+ signature: '',
82
+ },
83
+ };
84
+ }
@@ -0,0 +1,5 @@
1
+ import { OmniaVtxo, OmniaVtxoPool } from './types.js';
2
+ export declare function serializeVtxo(vtxo: OmniaVtxo): string;
3
+ export declare function deserializeVtxo(json: string): OmniaVtxo;
4
+ export declare function serializePool(pool: OmniaVtxoPool): string;
5
+ export declare function deserializePool(json: string): OmniaVtxoPool;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.serializeVtxo = serializeVtxo;
4
+ exports.deserializeVtxo = deserializeVtxo;
5
+ exports.serializePool = serializePool;
6
+ exports.deserializePool = deserializePool;
7
+ const BIGINT_PREFIX = '__bigint__:';
8
+ function replacer(_key, value) {
9
+ if (typeof value === 'bigint') {
10
+ return `${BIGINT_PREFIX}${value.toString()}`;
11
+ }
12
+ return value;
13
+ }
14
+ function reviver(_key, value) {
15
+ if (typeof value === 'string' && value.startsWith(BIGINT_PREFIX)) {
16
+ return BigInt(value.slice(BIGINT_PREFIX.length));
17
+ }
18
+ return value;
19
+ }
20
+ function serializeVtxo(vtxo) {
21
+ return JSON.stringify(vtxo, replacer);
22
+ }
23
+ function deserializeVtxo(json) {
24
+ return JSON.parse(json, reviver);
25
+ }
26
+ function serializePool(pool) {
27
+ return JSON.stringify(pool, replacer);
28
+ }
29
+ function deserializePool(json) {
30
+ return JSON.parse(json, reviver);
31
+ }
@@ -0,0 +1,8 @@
1
+ import { OmniaVtxo, SplitVtxoParams, SplitResult, VtxoOperatorReceipt } from './types.js';
2
+ /**
3
+ * Splits a VTXO into multiple outputs.
4
+ * @param now - Timestamp in ms. Defaults to Date.now() if omitted — pass explicitly for determinism.
5
+ */
6
+ export declare function splitVtxo(vtxo: OmniaVtxo, params: SplitVtxoParams, now?: number): SplitResult & {
7
+ receipt: VtxoOperatorReceipt;
8
+ };