@totemsdk/omnia-vtxo 0.2.1 → 0.3.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 +87 -0
  2. package/dist/anchor.js +107 -0
  3. package/dist/cjs/anchor.d.ts +87 -0
  4. package/dist/cjs/anchor.js +114 -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 +55 -0
  14. package/dist/cjs/exit-tx.js +81 -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 +19 -0
  29. package/dist/cjs/pool-script.js +41 -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 +55 -0
  55. package/dist/exit-tx.js +74 -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 +19 -0
  67. package/dist/pool-script.js +36 -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,19 @@
1
+ /**
2
+ * VTXO pool on-chain script (RFC-038 V1).
3
+ *
4
+ * The pool is an on-chain state output holding `totalCapacity`; an epoch's batch
5
+ * root is committed as STATE(100) with the epoch number in STATE(101), and the
6
+ * update requires a strictly-greater epoch (`ASSERT EPOCH GT PREVSTATE(101)`) so a
7
+ * old root cannot be re-anchored (RFC-038 §5 "stale/rolled-back root").
8
+ *
9
+ * CUSTODY NOTE: this script is currently **operator-only** (`SIGNEDBY(operator)`),
10
+ * so until V2 lands the owner-unilateral timelock branch the pool output is
11
+ * custodied by the operator on-chain. This is surfaced in RFC-038 §3.3/§5.
12
+ */
13
+ /** Operator-controlled, epoch-monotone state-carrying pool script. */
14
+ export declare function buildPoolScript(operatorPublicKeyDigest: string): string;
15
+ export declare function poolScriptAddress(script: string): string;
16
+ export declare function buildAndHashPoolScript(operatorPublicKeyDigest: string): {
17
+ script: string;
18
+ address: string;
19
+ };
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ /**
3
+ * VTXO pool on-chain script (RFC-038 V1).
4
+ *
5
+ * The pool is an on-chain state output holding `totalCapacity`; an epoch's batch
6
+ * root is committed as STATE(100) with the epoch number in STATE(101), and the
7
+ * update requires a strictly-greater epoch (`ASSERT EPOCH GT PREVSTATE(101)`) so a
8
+ * old root cannot be re-anchored (RFC-038 §5 "stale/rolled-back root").
9
+ *
10
+ * CUSTODY NOTE: this script is currently **operator-only** (`SIGNEDBY(operator)`),
11
+ * so until V2 lands the owner-unilateral timelock branch the pool output is
12
+ * custodied by the operator on-chain. This is surfaced in RFC-038 §3.3/§5.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.buildPoolScript = buildPoolScript;
16
+ exports.poolScriptAddress = poolScriptAddress;
17
+ exports.buildAndHashPoolScript = buildAndHashPoolScript;
18
+ const core_1 = require("@totemsdk/core");
19
+ function operatorPkHex(operatorPublicKeyDigest) {
20
+ const raw = operatorPublicKeyDigest.replace(/^0x/i, '');
21
+ if (!/^[0-9a-fA-F]{64}$/.test(raw)) {
22
+ throw new Error(`operator public key digest must be 64 hex chars, got ${JSON.stringify(operatorPublicKeyDigest)}`);
23
+ }
24
+ return '0X' + raw.toUpperCase();
25
+ }
26
+ /** Operator-controlled, epoch-monotone state-carrying pool script. */
27
+ function buildPoolScript(operatorPublicKeyDigest) {
28
+ return [
29
+ 'LET ROOT=STATE(100)',
30
+ 'LET EPOCH=STATE(101)',
31
+ 'ASSERT EPOCH GT PREVSTATE(101)',
32
+ `RETURN SIGNEDBY(${operatorPkHex(operatorPublicKeyDigest)})`,
33
+ ].join('\n');
34
+ }
35
+ function poolScriptAddress(script) {
36
+ return (0, core_1.computeScriptAddress)(script);
37
+ }
38
+ function buildAndHashPoolScript(operatorPublicKeyDigest) {
39
+ const script = buildPoolScript(operatorPublicKeyDigest);
40
+ return { script, address: poolScriptAddress(script) };
41
+ }
@@ -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
+ };