@totemsdk/omnia-vtxo 0.2.0 → 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 (84) 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 +13 -0
  49. package/dist/commitment.js +39 -30
  50. package/dist/constants.d.ts +0 -2
  51. package/dist/constants.js +4 -9
  52. package/dist/durable-store.d.ts +33 -0
  53. package/dist/durable-store.js +90 -0
  54. package/dist/errors.js +10 -23
  55. package/dist/exit-tx.d.ts +55 -0
  56. package/dist/exit-tx.js +74 -0
  57. package/dist/exit.d.ts +12 -2
  58. package/dist/exit.js +39 -23
  59. package/dist/index.d.ts +6 -0
  60. package/dist/index.js +21 -31
  61. package/dist/memory-store.js +3 -7
  62. package/dist/merge.js +15 -19
  63. package/dist/operator.d.ts +29 -0
  64. package/dist/operator.js +61 -0
  65. package/dist/policy.d.ts +1 -1
  66. package/dist/policy.js +3 -7
  67. package/dist/pool-script.d.ts +19 -0
  68. package/dist/pool-script.js +36 -0
  69. package/dist/pool.js +21 -27
  70. package/dist/receipt.d.ts +31 -0
  71. package/dist/receipt.js +48 -0
  72. package/dist/refresh.js +10 -14
  73. package/dist/serialization.js +4 -10
  74. package/dist/split.js +13 -17
  75. package/dist/transfer.js +13 -17
  76. package/dist/types.d.ts +12 -1
  77. package/dist/types.js +1 -2
  78. package/dist/verify.js +7 -13
  79. package/dist/vtxo.js +14 -19
  80. package/package.json +19 -8
  81. package/rust/pkg/omnia_vtxo_wasm_bg.wasm +0 -0
  82. package/rust/pkg/package.json +1 -1
  83. package/rust/pkg-node/omnia_vtxo_wasm_bg.wasm +0 -0
  84. package/rust/pkg-node/package.json +1 -1
@@ -1,4 +1,17 @@
1
1
  import { OmniaVtxo, VtxoId, VtxoProof } from './types.js';
2
+ /** Domain-separated hash prefixes (#33) — a hash on one record cannot replay against another. */
3
+ export declare const VTXO_ID_DOMAIN = "totemsdk/omnia-vtxo/vtxo/v1";
4
+ export declare const VTXO_POOL_ID_DOMAIN = "totemsdk/omnia-vtxo/pool/v1";
5
+ export declare const VTXO_LEAF_DOMAIN = "totemsdk/omnia-vtxo/leaf/v1";
6
+ export declare const VTXO_RECEIPT_DOMAIN = "totemsdk/omnia-vtxo/receipt/v1";
7
+ export declare const VTXO_BATCH_DOMAIN = "totemsdk/omnia-vtxo/batch/v1";
8
+ /**
9
+ * Canonical batch commitment (RFC-038 §3.2): the operator signs this over the
10
+ * pool, epoch, Merkle root, and the ordered VTXO ids in the batch.
11
+ */
12
+ export declare function computeBatchCommitment(poolId: string, epoch: number, root: string, orderedIds: readonly string[]): string;
13
+ /** The 32-byte digest of a batch commitment, for signing. */
14
+ export declare function computeBatchCommitmentBytes(poolId: string, epoch: number, root: string, orderedIds: readonly string[]): Uint8Array;
2
15
  export declare function computeVtxoLeaf(vtxo: OmniaVtxo): string;
3
16
  export interface ComputeVtxoIdParams {
4
17
  poolId: string;
@@ -1,26 +1,35 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.computeVtxoLeaf = computeVtxoLeaf;
4
- exports.computeVtxoId = computeVtxoId;
5
- exports.computePoolId = computePoolId;
6
- exports.computeCommitmentRoot = computeCommitmentRoot;
7
- exports.verifyMerkleProof = verifyMerkleProof;
8
- exports.buildVtxoProofSet = buildVtxoProofSet;
9
- exports.computeReceiptId = computeReceiptId;
10
- const core_1 = require("@totemsdk/core");
11
- const constants_js_1 = require("./constants.js");
1
+ import { sha3_256, bytesToHex, hexToBytes } from '@totemsdk/core';
2
+ import { EMPTY_LEAF } from './constants.js';
3
+ /** Domain-separated hash prefixes (#33) — a hash on one record cannot replay against another. */
4
+ export const VTXO_ID_DOMAIN = 'totemsdk/omnia-vtxo/vtxo/v1';
5
+ export const VTXO_POOL_ID_DOMAIN = 'totemsdk/omnia-vtxo/pool/v1';
6
+ export const VTXO_LEAF_DOMAIN = 'totemsdk/omnia-vtxo/leaf/v1';
7
+ export const VTXO_RECEIPT_DOMAIN = 'totemsdk/omnia-vtxo/receipt/v1';
8
+ export const VTXO_BATCH_DOMAIN = 'totemsdk/omnia-vtxo/batch/v1';
9
+ /**
10
+ * Canonical batch commitment (RFC-038 §3.2): the operator signs this over the
11
+ * pool, epoch, Merkle root, and the ordered VTXO ids in the batch.
12
+ */
13
+ export function computeBatchCommitment(poolId, epoch, root, orderedIds) {
14
+ return sha3hex(`${VTXO_BATCH_DOMAIN}|${poolId}|${epoch}|${root}|${[...orderedIds].join(',')}`);
15
+ }
16
+ /** The 32-byte digest of a batch commitment, for signing. */
17
+ export function computeBatchCommitmentBytes(poolId, epoch, root, orderedIds) {
18
+ return hexToBytes(computeBatchCommitment(poolId, epoch, root, orderedIds));
19
+ }
12
20
  function sha3hex(input) {
13
21
  const bytes = new TextEncoder().encode(input);
14
- return (0, core_1.bytesToHex)((0, core_1.sha3_256)(bytes));
22
+ return bytesToHex(sha3_256(bytes));
15
23
  }
16
24
  function hashPair(a, b) {
17
25
  const [left, right] = a <= b ? [a, b] : [b, a];
18
26
  const combined = left + right;
19
- const bytes = (0, core_1.hexToBytes)(combined);
20
- return (0, core_1.bytesToHex)((0, core_1.sha3_256)(bytes));
27
+ const bytes = hexToBytes(combined);
28
+ return bytesToHex(sha3_256(bytes));
21
29
  }
22
- function computeVtxoLeaf(vtxo) {
30
+ export function computeVtxoLeaf(vtxo) {
23
31
  const input = [
32
+ VTXO_LEAF_DOMAIN,
24
33
  vtxo.vtxoId,
25
34
  vtxo.owner,
26
35
  vtxo.amount.toString(),
@@ -30,9 +39,9 @@ function computeVtxoLeaf(vtxo) {
30
39
  ].join(':');
31
40
  return sha3hex(input);
32
41
  }
33
- function computeVtxoId(params) {
42
+ export function computeVtxoId(params) {
34
43
  const input = [
35
- 'vtxo',
44
+ VTXO_ID_DOMAIN,
36
45
  params.poolId,
37
46
  params.owner,
38
47
  params.amount.toString(),
@@ -41,9 +50,9 @@ function computeVtxoId(params) {
41
50
  ].join(':');
42
51
  return sha3hex(input);
43
52
  }
44
- function computePoolId(params) {
53
+ export function computePoolId(params) {
45
54
  const input = [
46
- 'pool',
55
+ VTXO_POOL_ID_DOMAIN,
47
56
  params.operator,
48
57
  params.tokenId,
49
58
  params.nonce,
@@ -58,11 +67,11 @@ function nextPowerOfTwo(n) {
58
67
  p <<= 1;
59
68
  return p;
60
69
  }
61
- function computeCommitmentRoot(leaves) {
70
+ export function computeCommitmentRoot(leaves) {
62
71
  if (leaves.length === 0)
63
- return constants_js_1.EMPTY_LEAF;
72
+ return EMPTY_LEAF;
64
73
  const size = nextPowerOfTwo(leaves.length);
65
- const level = new Array(size).fill(constants_js_1.EMPTY_LEAF);
74
+ const level = new Array(size).fill(EMPTY_LEAF);
66
75
  for (let i = 0; i < leaves.length; i++) {
67
76
  level[i] = leaves[i];
68
77
  }
@@ -76,7 +85,7 @@ function computeCommitmentRoot(leaves) {
76
85
  }
77
86
  return current[0];
78
87
  }
79
- function verifyMerkleProof(leaf, proof, root) {
88
+ export function verifyMerkleProof(leaf, proof, root) {
80
89
  let current = leaf;
81
90
  for (const node of proof) {
82
91
  if (node.position === 'left') {
@@ -88,9 +97,9 @@ function verifyMerkleProof(leaf, proof, root) {
88
97
  }
89
98
  return current === root;
90
99
  }
91
- function buildVtxoProofSet(vtxos) {
100
+ export function buildVtxoProofSet(vtxos) {
92
101
  if (vtxos.length === 0) {
93
- return { root: constants_js_1.EMPTY_LEAF, proofs: {} };
102
+ return { root: EMPTY_LEAF, proofs: {} };
94
103
  }
95
104
  const sorted = [...vtxos].sort((a, b) => a.vtxoId < b.vtxoId ? -1 : a.vtxoId > b.vtxoId ? 1 : 0);
96
105
  const leafMap = new Map();
@@ -102,7 +111,7 @@ function buildVtxoProofSet(vtxos) {
102
111
  const size = nextPowerOfTwo(leaves.length);
103
112
  const paddedLeaves = [...leaves];
104
113
  while (paddedLeaves.length < size)
105
- paddedLeaves.push(constants_js_1.EMPTY_LEAF);
114
+ paddedLeaves.push(EMPTY_LEAF);
106
115
  const levels = [paddedLeaves];
107
116
  let current = paddedLeaves;
108
117
  while (current.length > 1) {
@@ -122,7 +131,7 @@ function buildVtxoProofSet(vtxos) {
122
131
  for (let lvl = 0; lvl < levels.length - 1; lvl++) {
123
132
  const isLeft = pos % 2 === 0;
124
133
  const siblingIdx = isLeft ? pos + 1 : pos - 1;
125
- const sibling = levels[lvl][siblingIdx] ?? constants_js_1.EMPTY_LEAF;
134
+ const sibling = levels[lvl][siblingIdx] ?? EMPTY_LEAF;
126
135
  siblings.push(sibling);
127
136
  positions.push(isLeft ? 'right' : 'left');
128
137
  pos = Math.floor(pos / 2);
@@ -134,14 +143,14 @@ function buildVtxoProofSet(vtxos) {
134
143
  siblings,
135
144
  positions,
136
145
  epoch: vtxo.epoch,
137
- batchId: constants_js_1.MOCK_BATCH_ID,
146
+ batchId: root,
138
147
  };
139
148
  });
140
149
  return { root, proofs };
141
150
  }
142
- function computeReceiptId(poolId, op, inputIds, outputIds, at) {
151
+ export function computeReceiptId(poolId, op, inputIds, outputIds, at) {
143
152
  const input = [
144
- 'receipt',
153
+ VTXO_RECEIPT_DOMAIN,
145
154
  poolId,
146
155
  op,
147
156
  inputIds.sort().join(','),
@@ -8,5 +8,3 @@ export declare const DEFAULT_POLICY: {
8
8
  readonly maxSplitOutputs: 8;
9
9
  readonly exitTimelockSeconds: 86400;
10
10
  };
11
- export declare const MOCK_OPERATOR_SIGNATURE = "mock-operator-sig-v0";
12
- export declare const MOCK_BATCH_ID = "batch-local-mvp";
package/dist/constants.js CHANGED
@@ -1,15 +1,10 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MOCK_BATCH_ID = exports.MOCK_OPERATOR_SIGNATURE = exports.DEFAULT_POLICY = exports.EPOCH_ZERO = exports.EMPTY_TREE_ROOT = exports.EMPTY_LEAF = void 0;
4
- exports.EMPTY_LEAF = '0000000000000000000000000000000000000000000000000000000000000000';
5
- exports.EMPTY_TREE_ROOT = '0000000000000000000000000000000000000000000000000000000000000000';
6
- exports.EPOCH_ZERO = 0;
7
- exports.DEFAULT_POLICY = {
1
+ export const EMPTY_LEAF = '0000000000000000000000000000000000000000000000000000000000000000';
2
+ export const EMPTY_TREE_ROOT = '0000000000000000000000000000000000000000000000000000000000000000';
3
+ export const EPOCH_ZERO = 0;
4
+ export const DEFAULT_POLICY = {
8
5
  minAmount: BigInt(1),
9
6
  maxAmount: BigInt(1000000000000000),
10
7
  maxMergeInputs: 8,
11
8
  maxSplitOutputs: 8,
12
9
  exitTimelockSeconds: 86400,
13
10
  };
14
- exports.MOCK_OPERATOR_SIGNATURE = 'mock-operator-sig-v0';
15
- exports.MOCK_BATCH_ID = 'batch-local-mvp';
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Durable VTXO/pool snapshot store (RFC-007 G6).
3
+ *
4
+ * Drop-in replacement for `MemoryOmniaVtxoStore` — same `OmniaVtxoStore` port
5
+ * — backed by a storage adapter instead of heap maps: the whole pool/VTXO
6
+ * registry is a single revision-CAS snapshot record, so concurrent pool/VTXO
7
+ * transitions never lose an update, reopen restores every VTXO (incl. spent
8
+ * status and history), and corruption is surfaced — never treated as absence.
9
+ */
10
+ import type { StorageAdapterWithCapabilities, CasStore, WriteAckMode } from '@totemsdk/storage/types';
11
+ import type { OmniaVtxo, OmniaVtxoPool, OmniaVtxoStore } from './types.js';
12
+ export interface OmniaVtxoRegistryState {
13
+ pools: Record<string, OmniaVtxoPool>;
14
+ vtxos: Record<string, OmniaVtxo>;
15
+ }
16
+ export interface DurableOmniaVtxoStoreOptions {
17
+ /** Key namespace prefix; default `totem_omnia_vtxo:v1:`. */
18
+ readonly namespace?: string;
19
+ /**
20
+ * Required write acknowledgment; default `durably-acknowledged`. Pass
21
+ * `volatile` only for tests/scratch adapters (e.g. `MemoryStore`).
22
+ */
23
+ readonly requireAckMode?: WriteAckMode;
24
+ }
25
+ export interface DurableOmniaVtxoStore extends OmniaVtxoStore {
26
+ /** Current registry transition counter (0 before the first write). */
27
+ getRevision(): Promise<number>;
28
+ /** True once any snapshot record has been persisted. */
29
+ hasState(): Promise<boolean>;
30
+ /** Current persisted snapshot state (pool + vtxo maps). */
31
+ getSnapshot(): Promise<OmniaVtxoRegistryState>;
32
+ }
33
+ export declare function createDurableOmniaVtxoStore(adapter: StorageAdapterWithCapabilities & CasStore, options?: DurableOmniaVtxoStoreOptions): DurableOmniaVtxoStore;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Durable VTXO/pool snapshot store (RFC-007 G6).
3
+ *
4
+ * Drop-in replacement for `MemoryOmniaVtxoStore` — same `OmniaVtxoStore` port
5
+ * — backed by a storage adapter instead of heap maps: the whole pool/VTXO
6
+ * registry is a single revision-CAS snapshot record, so concurrent pool/VTXO
7
+ * transitions never lose an update, reopen restores every VTXO (incl. spent
8
+ * status and history), and corruption is surfaced — never treated as absence.
9
+ */
10
+ import { createRevisionedSnapshotStore, } from '@totemsdk/storage/snapshot';
11
+ import { StorageError } from '@totemsdk/storage/errors';
12
+ import { VtxoStatusError } from './errors.js';
13
+ const DEFAULT_NAMESPACE = 'totem_omnia_vtxo:v1:';
14
+ function assertRegistryState(state) {
15
+ if (typeof state !== 'object' || state === null || Array.isArray(state)) {
16
+ throw new StorageError('omnia-vtxo registry state is not an object', 'corrupt');
17
+ }
18
+ for (const section of ['pools', 'vtxos']) {
19
+ const value = state[section];
20
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
21
+ throw new StorageError(`omnia-vtxo registry state is missing section "${section}"`, 'corrupt');
22
+ }
23
+ }
24
+ }
25
+ export function createDurableOmniaVtxoStore(adapter, options = {}) {
26
+ const snapshots = createRevisionedSnapshotStore(adapter, {
27
+ namespace: options.namespace ?? DEFAULT_NAMESPACE,
28
+ requireAckMode: options.requireAckMode,
29
+ empty: () => ({ pools: {}, vtxos: {} }),
30
+ validate: assertRegistryState,
31
+ });
32
+ async function mutate(fn) {
33
+ await snapshots.mutate(fn);
34
+ }
35
+ return {
36
+ async savePool(pool) {
37
+ await mutate((state) => {
38
+ const pools = { ...state.pools, [pool.poolId]: pool };
39
+ return { ...state, pools };
40
+ });
41
+ },
42
+ async getPool(poolId) {
43
+ return (await snapshots.load()).pools[poolId];
44
+ },
45
+ async saveVtxo(vtxo) {
46
+ await mutate((state) => {
47
+ const vtxos = { ...state.vtxos, [vtxo.vtxoId]: vtxo };
48
+ return { ...state, vtxos };
49
+ });
50
+ },
51
+ async getVtxo(vtxoId) {
52
+ return (await snapshots.load()).vtxos[vtxoId];
53
+ },
54
+ async listVtxos(poolId) {
55
+ const state = await snapshots.load();
56
+ const all = Object.values(state.vtxos);
57
+ if (poolId === undefined)
58
+ return all;
59
+ return all.filter((v) => v.poolId === poolId);
60
+ },
61
+ async markVtxoSpent(vtxoId, now) {
62
+ await mutate((state) => {
63
+ const existing = state.vtxos[vtxoId];
64
+ if (!existing) {
65
+ // Preserve MemoryOmniaVtxoStore error behaviour exactly.
66
+ throw new Error(`VTXO ${vtxoId} not found`);
67
+ }
68
+ if (existing.status !== 'active') {
69
+ throw new VtxoStatusError(`Cannot mark VTXO ${vtxoId} as spent: status is '${existing.status}'`, existing.status);
70
+ }
71
+ const ts = now !== undefined ? now : Date.now();
72
+ const vtxos = {
73
+ ...state.vtxos,
74
+ [vtxoId]: {
75
+ ...existing,
76
+ status: 'spent',
77
+ updatedAt: ts,
78
+ history: [...existing.history, { op: 'spent', at: ts }],
79
+ },
80
+ };
81
+ return { ...state, vtxos };
82
+ });
83
+ },
84
+ getRevision: () => snapshots.getRevision(),
85
+ hasState: () => snapshots.hasState(),
86
+ async getSnapshot() {
87
+ return snapshots.load();
88
+ },
89
+ };
90
+ }
package/dist/errors.js CHANGED
@@ -1,44 +1,36 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.VtxoExitError = exports.VtxoSplitError = exports.VtxoMergeError = exports.VtxoPolicyError = exports.VtxoPoolCapacityError = exports.VtxoProofError = exports.VtxoOwnershipError = exports.VtxoStatusError = exports.VtxoAmountError = exports.OmniaVtxoError = void 0;
4
- class OmniaVtxoError extends Error {
1
+ export class OmniaVtxoError extends Error {
5
2
  constructor(message) {
6
3
  super(message);
7
4
  this.name = 'OmniaVtxoError';
8
5
  Object.setPrototypeOf(this, new.target.prototype);
9
6
  }
10
7
  }
11
- exports.OmniaVtxoError = OmniaVtxoError;
12
- class VtxoAmountError extends OmniaVtxoError {
8
+ export class VtxoAmountError extends OmniaVtxoError {
13
9
  constructor(message) {
14
10
  super(message);
15
11
  this.name = 'VtxoAmountError';
16
12
  }
17
13
  }
18
- exports.VtxoAmountError = VtxoAmountError;
19
- class VtxoStatusError extends OmniaVtxoError {
14
+ export class VtxoStatusError extends OmniaVtxoError {
20
15
  constructor(message, currentStatus) {
21
16
  super(message);
22
17
  this.name = 'VtxoStatusError';
23
18
  this.currentStatus = currentStatus;
24
19
  }
25
20
  }
26
- exports.VtxoStatusError = VtxoStatusError;
27
- class VtxoOwnershipError extends OmniaVtxoError {
21
+ export class VtxoOwnershipError extends OmniaVtxoError {
28
22
  constructor(message) {
29
23
  super(message);
30
24
  this.name = 'VtxoOwnershipError';
31
25
  }
32
26
  }
33
- exports.VtxoOwnershipError = VtxoOwnershipError;
34
- class VtxoProofError extends OmniaVtxoError {
27
+ export class VtxoProofError extends OmniaVtxoError {
35
28
  constructor(message) {
36
29
  super(message);
37
30
  this.name = 'VtxoProofError';
38
31
  }
39
32
  }
40
- exports.VtxoProofError = VtxoProofError;
41
- class VtxoPoolCapacityError extends OmniaVtxoError {
33
+ export class VtxoPoolCapacityError extends OmniaVtxoError {
42
34
  constructor(message, requested, available) {
43
35
  super(message);
44
36
  this.name = 'VtxoPoolCapacityError';
@@ -46,32 +38,27 @@ class VtxoPoolCapacityError extends OmniaVtxoError {
46
38
  this.available = available;
47
39
  }
48
40
  }
49
- exports.VtxoPoolCapacityError = VtxoPoolCapacityError;
50
- class VtxoPolicyError extends OmniaVtxoError {
41
+ export class VtxoPolicyError extends OmniaVtxoError {
51
42
  constructor(message) {
52
43
  super(message);
53
44
  this.name = 'VtxoPolicyError';
54
45
  }
55
46
  }
56
- exports.VtxoPolicyError = VtxoPolicyError;
57
- class VtxoMergeError extends OmniaVtxoError {
47
+ export class VtxoMergeError extends OmniaVtxoError {
58
48
  constructor(message) {
59
49
  super(message);
60
50
  this.name = 'VtxoMergeError';
61
51
  }
62
52
  }
63
- exports.VtxoMergeError = VtxoMergeError;
64
- class VtxoSplitError extends OmniaVtxoError {
53
+ export class VtxoSplitError extends OmniaVtxoError {
65
54
  constructor(message) {
66
55
  super(message);
67
56
  this.name = 'VtxoSplitError';
68
57
  }
69
58
  }
70
- exports.VtxoSplitError = VtxoSplitError;
71
- class VtxoExitError extends OmniaVtxoError {
59
+ export class VtxoExitError extends OmniaVtxoError {
72
60
  constructor(message) {
73
61
  super(message);
74
62
  this.name = 'VtxoExitError';
75
63
  }
76
64
  }
77
- exports.VtxoExitError = VtxoExitError;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * VTXO exit transaction (RFC-038 V2).
3
+ *
4
+ * Builds a real exit transaction: spend the pool's on-chain coin and pay the
5
+ * VTXO's owner, carrying the exit delay in STATE(102). The digest is signed
6
+ * (injected signer) and, when a chain provider is supplied, mined + broadcast
7
+ * (injected miner). This replaces the `'mock-exit'` draft with a concrete tx.
8
+ *
9
+ * NOTE (RFC-038 §3.4): the full owner-unilateral covenant (a timelock branch the
10
+ * owner can exercise without the operator, or a pre-signed exit tx) is a
11
+ * follow-on; this builds and broadcasts the transaction with the delay encoded.
12
+ */
13
+ import { type OmniaTxDraft } from '@totemsdk/omnia';
14
+ import type { ChainStateProvider } from '@totemsdk/chain-provider';
15
+ import type { OmniaVtxo } from './types.js';
16
+ /** State port carrying the exit delay. */
17
+ export declare const EXIT_DELAY_PORT = 102;
18
+ export interface ExitTxParams {
19
+ vtxo: OmniaVtxo;
20
+ /** The pool's on-chain coin (input). */
21
+ fundingCoinId: string;
22
+ fundingAddress: string;
23
+ fundingScript: string;
24
+ /** Raw base-unit amount to pay the owner (the VTXO amount, pre-scaled). */
25
+ amount: bigint;
26
+ /** Exit delay carried as STATE(102). */
27
+ exitDelayBlocks: number;
28
+ }
29
+ /** Draft that spends the pool coin to the VTXO owner with the exit delay encoded. */
30
+ export declare function buildExitTransactionDraft(params: ExitTxParams): OmniaTxDraft;
31
+ export declare function exitTransactionDigest(draft: OmniaTxDraft): Uint8Array;
32
+ export declare function exitTransactionHex(draft: OmniaTxDraft): string;
33
+ interface MinedTxPow {
34
+ minedHeaderBytes: Uint8Array;
35
+ txpowId: Uint8Array;
36
+ }
37
+ export interface ExitTxDeps {
38
+ signDigest: (digest: Uint8Array) => Promise<Uint8Array>;
39
+ chainProvider?: ChainStateProvider;
40
+ mine?: (txBody: Uint8Array, difficulty: Uint8Array) => Promise<MinedTxPow>;
41
+ }
42
+ export interface ExitTxResult {
43
+ vtxoId: string;
44
+ owner: string;
45
+ amount: string;
46
+ exitDelayBlocks: number;
47
+ draftType: 'vtxo-exit';
48
+ txHex: string;
49
+ digest: string;
50
+ witnessHex: string;
51
+ txpowId?: string;
52
+ }
53
+ /** Build, sign (and optionally mine + broadcast) a VTXO exit transaction. */
54
+ export declare function buildExitTransaction(params: ExitTxParams, deps: ExitTxDeps): Promise<ExitTxResult>;
55
+ export {};
@@ -0,0 +1,74 @@
1
+ /**
2
+ * VTXO exit transaction (RFC-038 V2).
3
+ *
4
+ * Builds a real exit transaction: spend the pool's on-chain coin and pay the
5
+ * VTXO's owner, carrying the exit delay in STATE(102). The digest is signed
6
+ * (injected signer) and, when a chain provider is supplied, mined + broadcast
7
+ * (injected miner). This replaces the `'mock-exit'` draft with a concrete tx.
8
+ *
9
+ * NOTE (RFC-038 §3.4): the full owner-unilateral covenant (a timelock branch the
10
+ * owner can exercise without the operator, or a pre-signed exit tx) is a
11
+ * follow-on; this builds and broadcasts the transaction with the delay encoded.
12
+ */
13
+ import { computeOmniaTxDigest, omniaDraftToMinimaBytes, serializeTxDraft, } from '@totemsdk/omnia';
14
+ import { serializeTxBody, mineTxPoW, TX_POW_MIN_DIFFICULTY } from '@totemsdk/txpow';
15
+ import { concatBytes } from '@totemsdk/core';
16
+ /** State port carrying the exit delay. */
17
+ export const EXIT_DELAY_PORT = 102;
18
+ /** Draft that spends the pool coin to the VTXO owner with the exit delay encoded. */
19
+ export function buildExitTransactionDraft(params) {
20
+ const stateVariables = [
21
+ { port: EXIT_DELAY_PORT, value: BigInt(params.exitDelayBlocks), type: 'number' },
22
+ ];
23
+ return {
24
+ type: 'settlement',
25
+ inputs: [{
26
+ coinId: params.fundingCoinId,
27
+ address: params.fundingAddress,
28
+ amount: params.amount,
29
+ tokenId: params.vtxo.tokenId,
30
+ scriptHex: params.fundingScript,
31
+ }],
32
+ outputs: [{
33
+ address: params.vtxo.owner,
34
+ amount: params.amount,
35
+ tokenId: params.vtxo.tokenId,
36
+ storeState: false,
37
+ stateVariables,
38
+ }],
39
+ storeState: false,
40
+ stateVariables,
41
+ };
42
+ }
43
+ export function exitTransactionDigest(draft) {
44
+ return computeOmniaTxDigest(draft);
45
+ }
46
+ export function exitTransactionHex(draft) {
47
+ return serializeTxDraft(draft);
48
+ }
49
+ /** Build, sign (and optionally mine + broadcast) a VTXO exit transaction. */
50
+ export async function buildExitTransaction(params, deps) {
51
+ const draft = buildExitTransactionDraft(params);
52
+ const digest = exitTransactionDigest(draft);
53
+ const signature = await deps.signDigest(digest);
54
+ const result = {
55
+ vtxoId: params.vtxo.vtxoId,
56
+ owner: params.vtxo.owner,
57
+ amount: params.vtxo.amount.toString(),
58
+ exitDelayBlocks: params.exitDelayBlocks,
59
+ draftType: 'vtxo-exit',
60
+ txHex: exitTransactionHex(draft),
61
+ digest: Buffer.from(digest).toString('hex'),
62
+ witnessHex: Buffer.from(signature).toString('hex'),
63
+ };
64
+ if (deps.chainProvider) {
65
+ const mine = deps.mine ?? ((txBody, difficulty) => mineTxPoW(txBody, difficulty));
66
+ const draftBytes = omniaDraftToMinimaBytes(draft);
67
+ const txBody = serializeTxBody(draftBytes, signature);
68
+ const mined = await mine(txBody, TX_POW_MIN_DIFFICULTY);
69
+ const fullTxPoW = concatBytes(concatBytes(mined.minedHeaderBytes, new Uint8Array([0x01])), txBody);
70
+ await deps.chainProvider.broadcastTxPoW(Buffer.from(fullTxPoW).toString('hex'));
71
+ result.txpowId = Buffer.from(mined.txpowId).toString('hex');
72
+ }
73
+ return result;
74
+ }
package/dist/exit.d.ts CHANGED
@@ -8,12 +8,22 @@ export declare function markExiting(vtxo: OmniaVtxo, now?: number): OmniaVtxo;
8
8
  * Transitions an exiting VTXO to 'exited' status.
9
9
  * @param now - Timestamp in ms. Defaults to Date.now() if omitted — pass explicitly for determinism.
10
10
  */
11
- export declare function markExited(vtxo: OmniaVtxo, now?: number): OmniaVtxo;
11
+ export declare function markExited(vtxo: OmniaVtxo, options?: {
12
+ txpowId?: string;
13
+ now?: number;
14
+ }): OmniaVtxo;
12
15
  /**
13
- * Creates a mock exit draft for a VTXO. Validates the proof leaf before drafting.
16
+ * Creates a mock exit draft for a VTXO. Validates the proof leaf before
17
+ * drafting, caps the exit at the verified share, and consumes the VTXO's
18
+ * receipt so a second exit draft is refused (double-exit prevention, #28).
14
19
  * @param now - Timestamp in ms. Defaults to Date.now() if omitted — pass explicitly for determinism.
15
20
  */
16
21
  export declare function createExitDraft(vtxo: OmniaVtxo, now?: number): {
17
22
  draft: ExitDraft;
18
23
  receipt: VtxoOperatorReceipt;
19
24
  };
25
+ /**
26
+ * Consume a VTXO's exit receipt — the single-spend marker that prevents a
27
+ * second exit draft for the same VTXO. Returns the updated VTXO.
28
+ */
29
+ export declare function consumeExitReceipt(vtxo: OmniaVtxo, receiptId: string, now?: number): OmniaVtxo;