@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.
- package/dist/anchor.d.ts +90 -0
- package/dist/anchor.js +112 -0
- package/dist/cjs/anchor.d.ts +90 -0
- package/dist/cjs/anchor.js +119 -0
- package/dist/cjs/commitment.d.ts +41 -0
- package/dist/cjs/commitment.js +173 -0
- package/dist/cjs/constants.d.ts +10 -0
- package/dist/cjs/constants.js +13 -0
- package/dist/cjs/durable-store.d.ts +33 -0
- package/dist/cjs/durable-store.js +93 -0
- package/dist/cjs/errors.d.ts +33 -0
- package/dist/cjs/errors.js +77 -0
- package/dist/cjs/exit-tx.d.ts +57 -0
- package/dist/cjs/exit-tx.js +83 -0
- package/dist/cjs/exit.d.ts +29 -0
- package/dist/cjs/exit.js +115 -0
- package/dist/cjs/index.d.ts +21 -0
- package/dist/cjs/index.js +37 -0
- package/dist/cjs/memory-store.d.ts +17 -0
- package/dist/cjs/memory-store.js +51 -0
- package/dist/cjs/merge.d.ts +8 -0
- package/dist/cjs/merge.js +106 -0
- package/dist/cjs/operator.d.ts +29 -0
- package/dist/cjs/operator.js +65 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/policy.d.ts +22 -0
- package/dist/cjs/policy.js +27 -0
- package/dist/cjs/pool-script.d.ts +36 -0
- package/dist/cjs/pool-script.js +60 -0
- package/dist/cjs/pool.d.ts +5 -0
- package/dist/cjs/pool.js +60 -0
- package/dist/cjs/receipt.d.ts +31 -0
- package/dist/cjs/receipt.js +52 -0
- package/dist/cjs/refresh.d.ts +8 -0
- package/dist/cjs/refresh.js +84 -0
- package/dist/cjs/serialization.d.ts +5 -0
- package/dist/cjs/serialization.js +31 -0
- package/dist/cjs/split.d.ts +8 -0
- package/dist/cjs/split.js +96 -0
- package/dist/cjs/transfer.d.ts +8 -0
- package/dist/cjs/transfer.js +152 -0
- package/dist/cjs/types.d.ts +166 -0
- package/dist/cjs/types.js +2 -0
- package/dist/cjs/verify.d.ts +23 -0
- package/dist/cjs/verify.js +121 -0
- package/dist/cjs/vtxo.d.ts +14 -0
- package/dist/cjs/vtxo.js +96 -0
- package/dist/commitment.d.ts +8 -0
- package/dist/commitment.js +38 -36
- package/dist/constants.d.ts +0 -2
- package/dist/constants.js +4 -9
- package/dist/durable-store.js +8 -11
- package/dist/errors.js +10 -23
- package/dist/exit-tx.d.ts +57 -0
- package/dist/exit-tx.js +76 -0
- package/dist/exit.d.ts +4 -1
- package/dist/exit.js +20 -26
- package/dist/index.d.ts +5 -0
- package/dist/index.js +21 -32
- package/dist/memory-store.js +3 -7
- package/dist/merge.js +15 -19
- package/dist/operator.d.ts +29 -0
- package/dist/operator.js +61 -0
- package/dist/policy.d.ts +1 -1
- package/dist/policy.js +3 -7
- package/dist/pool-script.d.ts +36 -0
- package/dist/pool-script.js +55 -0
- package/dist/pool.js +21 -27
- package/dist/receipt.d.ts +31 -0
- package/dist/receipt.js +48 -0
- package/dist/refresh.js +10 -14
- package/dist/serialization.js +4 -10
- package/dist/split.js +13 -17
- package/dist/transfer.js +13 -17
- package/dist/types.d.ts +3 -1
- package/dist/types.js +1 -2
- package/dist/verify.js +7 -13
- package/dist/vtxo.js +13 -19
- package/package.json +18 -8
- package/rust/pkg/omnia_vtxo_wasm_bg.wasm +0 -0
- package/rust/pkg/package.json +1 -1
- package/rust/pkg-node/omnia_vtxo_wasm_bg.wasm +0 -0
- package/rust/pkg-node/package.json +1 -1
package/dist/commitment.d.ts
CHANGED
|
@@ -4,6 +4,14 @@ export declare const VTXO_ID_DOMAIN = "totemsdk/omnia-vtxo/vtxo/v1";
|
|
|
4
4
|
export declare const VTXO_POOL_ID_DOMAIN = "totemsdk/omnia-vtxo/pool/v1";
|
|
5
5
|
export declare const VTXO_LEAF_DOMAIN = "totemsdk/omnia-vtxo/leaf/v1";
|
|
6
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;
|
|
7
15
|
export declare function computeVtxoLeaf(vtxo: OmniaVtxo): string;
|
|
8
16
|
export interface ComputeVtxoIdParams {
|
|
9
17
|
poolId: string;
|
package/dist/commitment.js
CHANGED
|
@@ -1,33 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.VTXO_RECEIPT_DOMAIN = exports.VTXO_LEAF_DOMAIN = exports.VTXO_POOL_ID_DOMAIN = exports.VTXO_ID_DOMAIN = void 0;
|
|
4
|
-
exports.computeVtxoLeaf = computeVtxoLeaf;
|
|
5
|
-
exports.computeVtxoId = computeVtxoId;
|
|
6
|
-
exports.computePoolId = computePoolId;
|
|
7
|
-
exports.computeCommitmentRoot = computeCommitmentRoot;
|
|
8
|
-
exports.verifyMerkleProof = verifyMerkleProof;
|
|
9
|
-
exports.buildVtxoProofSet = buildVtxoProofSet;
|
|
10
|
-
exports.computeReceiptId = computeReceiptId;
|
|
11
|
-
const core_1 = require("@totemsdk/core");
|
|
12
|
-
const constants_js_1 = require("./constants.js");
|
|
1
|
+
import { sha3_256, bytesToHex, hexToBytes } from '@totemsdk/core';
|
|
2
|
+
import { EMPTY_LEAF } from './constants.js';
|
|
13
3
|
/** Domain-separated hash prefixes (#33) — a hash on one record cannot replay against another. */
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
+
}
|
|
18
20
|
function sha3hex(input) {
|
|
19
21
|
const bytes = new TextEncoder().encode(input);
|
|
20
|
-
return
|
|
22
|
+
return bytesToHex(sha3_256(bytes));
|
|
21
23
|
}
|
|
22
24
|
function hashPair(a, b) {
|
|
23
25
|
const [left, right] = a <= b ? [a, b] : [b, a];
|
|
24
26
|
const combined = left + right;
|
|
25
|
-
const bytes =
|
|
26
|
-
return
|
|
27
|
+
const bytes = hexToBytes(combined);
|
|
28
|
+
return bytesToHex(sha3_256(bytes));
|
|
27
29
|
}
|
|
28
|
-
function computeVtxoLeaf(vtxo) {
|
|
30
|
+
export function computeVtxoLeaf(vtxo) {
|
|
29
31
|
const input = [
|
|
30
|
-
|
|
32
|
+
VTXO_LEAF_DOMAIN,
|
|
31
33
|
vtxo.vtxoId,
|
|
32
34
|
vtxo.owner,
|
|
33
35
|
vtxo.amount.toString(),
|
|
@@ -37,9 +39,9 @@ function computeVtxoLeaf(vtxo) {
|
|
|
37
39
|
].join(':');
|
|
38
40
|
return sha3hex(input);
|
|
39
41
|
}
|
|
40
|
-
function computeVtxoId(params) {
|
|
42
|
+
export function computeVtxoId(params) {
|
|
41
43
|
const input = [
|
|
42
|
-
|
|
44
|
+
VTXO_ID_DOMAIN,
|
|
43
45
|
params.poolId,
|
|
44
46
|
params.owner,
|
|
45
47
|
params.amount.toString(),
|
|
@@ -48,9 +50,9 @@ function computeVtxoId(params) {
|
|
|
48
50
|
].join(':');
|
|
49
51
|
return sha3hex(input);
|
|
50
52
|
}
|
|
51
|
-
function computePoolId(params) {
|
|
53
|
+
export function computePoolId(params) {
|
|
52
54
|
const input = [
|
|
53
|
-
|
|
55
|
+
VTXO_POOL_ID_DOMAIN,
|
|
54
56
|
params.operator,
|
|
55
57
|
params.tokenId,
|
|
56
58
|
params.nonce,
|
|
@@ -65,11 +67,11 @@ function nextPowerOfTwo(n) {
|
|
|
65
67
|
p <<= 1;
|
|
66
68
|
return p;
|
|
67
69
|
}
|
|
68
|
-
function computeCommitmentRoot(leaves) {
|
|
70
|
+
export function computeCommitmentRoot(leaves) {
|
|
69
71
|
if (leaves.length === 0)
|
|
70
|
-
return
|
|
72
|
+
return EMPTY_LEAF;
|
|
71
73
|
const size = nextPowerOfTwo(leaves.length);
|
|
72
|
-
const level = new Array(size).fill(
|
|
74
|
+
const level = new Array(size).fill(EMPTY_LEAF);
|
|
73
75
|
for (let i = 0; i < leaves.length; i++) {
|
|
74
76
|
level[i] = leaves[i];
|
|
75
77
|
}
|
|
@@ -83,7 +85,7 @@ function computeCommitmentRoot(leaves) {
|
|
|
83
85
|
}
|
|
84
86
|
return current[0];
|
|
85
87
|
}
|
|
86
|
-
function verifyMerkleProof(leaf, proof, root) {
|
|
88
|
+
export function verifyMerkleProof(leaf, proof, root) {
|
|
87
89
|
let current = leaf;
|
|
88
90
|
for (const node of proof) {
|
|
89
91
|
if (node.position === 'left') {
|
|
@@ -95,9 +97,9 @@ function verifyMerkleProof(leaf, proof, root) {
|
|
|
95
97
|
}
|
|
96
98
|
return current === root;
|
|
97
99
|
}
|
|
98
|
-
function buildVtxoProofSet(vtxos) {
|
|
100
|
+
export function buildVtxoProofSet(vtxos) {
|
|
99
101
|
if (vtxos.length === 0) {
|
|
100
|
-
return { root:
|
|
102
|
+
return { root: EMPTY_LEAF, proofs: {} };
|
|
101
103
|
}
|
|
102
104
|
const sorted = [...vtxos].sort((a, b) => a.vtxoId < b.vtxoId ? -1 : a.vtxoId > b.vtxoId ? 1 : 0);
|
|
103
105
|
const leafMap = new Map();
|
|
@@ -109,7 +111,7 @@ function buildVtxoProofSet(vtxos) {
|
|
|
109
111
|
const size = nextPowerOfTwo(leaves.length);
|
|
110
112
|
const paddedLeaves = [...leaves];
|
|
111
113
|
while (paddedLeaves.length < size)
|
|
112
|
-
paddedLeaves.push(
|
|
114
|
+
paddedLeaves.push(EMPTY_LEAF);
|
|
113
115
|
const levels = [paddedLeaves];
|
|
114
116
|
let current = paddedLeaves;
|
|
115
117
|
while (current.length > 1) {
|
|
@@ -129,7 +131,7 @@ function buildVtxoProofSet(vtxos) {
|
|
|
129
131
|
for (let lvl = 0; lvl < levels.length - 1; lvl++) {
|
|
130
132
|
const isLeft = pos % 2 === 0;
|
|
131
133
|
const siblingIdx = isLeft ? pos + 1 : pos - 1;
|
|
132
|
-
const sibling = levels[lvl][siblingIdx] ??
|
|
134
|
+
const sibling = levels[lvl][siblingIdx] ?? EMPTY_LEAF;
|
|
133
135
|
siblings.push(sibling);
|
|
134
136
|
positions.push(isLeft ? 'right' : 'left');
|
|
135
137
|
pos = Math.floor(pos / 2);
|
|
@@ -141,14 +143,14 @@ function buildVtxoProofSet(vtxos) {
|
|
|
141
143
|
siblings,
|
|
142
144
|
positions,
|
|
143
145
|
epoch: vtxo.epoch,
|
|
144
|
-
batchId:
|
|
146
|
+
batchId: root,
|
|
145
147
|
};
|
|
146
148
|
});
|
|
147
149
|
return { root, proofs };
|
|
148
150
|
}
|
|
149
|
-
function computeReceiptId(poolId, op, inputIds, outputIds, at) {
|
|
151
|
+
export function computeReceiptId(poolId, op, inputIds, outputIds, at) {
|
|
150
152
|
const input = [
|
|
151
|
-
|
|
153
|
+
VTXO_RECEIPT_DOMAIN,
|
|
152
154
|
poolId,
|
|
153
155
|
op,
|
|
154
156
|
inputIds.sort().join(','),
|
package/dist/constants.d.ts
CHANGED
package/dist/constants.js
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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';
|
package/dist/durable-store.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Durable VTXO/pool snapshot store (RFC-007 G6).
|
|
4
3
|
*
|
|
@@ -8,25 +7,23 @@
|
|
|
8
7
|
* transitions never lose an update, reopen restores every VTXO (incl. spent
|
|
9
8
|
* status and history), and corruption is surfaced — never treated as absence.
|
|
10
9
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const errors_1 = require("@totemsdk/storage/errors");
|
|
15
|
-
const errors_js_1 = require("./errors.js");
|
|
10
|
+
import { createRevisionedSnapshotStore, } from '@totemsdk/storage/snapshot';
|
|
11
|
+
import { StorageError } from '@totemsdk/storage/errors';
|
|
12
|
+
import { VtxoStatusError } from './errors.js';
|
|
16
13
|
const DEFAULT_NAMESPACE = 'totem_omnia_vtxo:v1:';
|
|
17
14
|
function assertRegistryState(state) {
|
|
18
15
|
if (typeof state !== 'object' || state === null || Array.isArray(state)) {
|
|
19
|
-
throw new
|
|
16
|
+
throw new StorageError('omnia-vtxo registry state is not an object', 'corrupt');
|
|
20
17
|
}
|
|
21
18
|
for (const section of ['pools', 'vtxos']) {
|
|
22
19
|
const value = state[section];
|
|
23
20
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
24
|
-
throw new
|
|
21
|
+
throw new StorageError(`omnia-vtxo registry state is missing section "${section}"`, 'corrupt');
|
|
25
22
|
}
|
|
26
23
|
}
|
|
27
24
|
}
|
|
28
|
-
function createDurableOmniaVtxoStore(adapter, options = {}) {
|
|
29
|
-
const snapshots =
|
|
25
|
+
export function createDurableOmniaVtxoStore(adapter, options = {}) {
|
|
26
|
+
const snapshots = createRevisionedSnapshotStore(adapter, {
|
|
30
27
|
namespace: options.namespace ?? DEFAULT_NAMESPACE,
|
|
31
28
|
requireAckMode: options.requireAckMode,
|
|
32
29
|
empty: () => ({ pools: {}, vtxos: {} }),
|
|
@@ -69,7 +66,7 @@ function createDurableOmniaVtxoStore(adapter, options = {}) {
|
|
|
69
66
|
throw new Error(`VTXO ${vtxoId} not found`);
|
|
70
67
|
}
|
|
71
68
|
if (existing.status !== 'active') {
|
|
72
|
-
throw new
|
|
69
|
+
throw new VtxoStatusError(`Cannot mark VTXO ${vtxoId} as spent: status is '${existing.status}'`, existing.status);
|
|
73
70
|
}
|
|
74
71
|
const ts = now !== undefined ? now : Date.now();
|
|
75
72
|
const vtxos = {
|
package/dist/errors.js
CHANGED
|
@@ -1,44 +1,36 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VTXO exit transaction (RFC-038 V2 — owner-unilateral).
|
|
3
|
+
*
|
|
4
|
+
* The exit spends the pool's on-chain coin and pays the VTXO's owner, carrying
|
|
5
|
+
* the exit delay in STATE(110). It is signed with the **owner's** key (not the
|
|
6
|
+
* operator's): the pool script's `IF @BLOCK GT EXITBLOCK THEN RETURN
|
|
7
|
+
* SIGNEDBY(OWNER)` branch authorizes it after the exit window, so the owner can
|
|
8
|
+
* broadcast it without any operator signature or liveness. The digest is signed
|
|
9
|
+
* (injected signer) and, when a chain provider is supplied, mined + broadcast
|
|
10
|
+
* (injected miner).
|
|
11
|
+
*
|
|
12
|
+
* The caller must pass the owner signer; the operator cannot construct a valid
|
|
13
|
+
* exit witness for itself.
|
|
14
|
+
*/
|
|
15
|
+
import { type OmniaTxDraft } from '@totemsdk/omnia';
|
|
16
|
+
import type { ChainStateProvider } from '@totemsdk/chain-provider';
|
|
17
|
+
import type { OmniaVtxo } from './types.js';
|
|
18
|
+
/** State port carrying the exit delay on the owner's output. */
|
|
19
|
+
export declare const EXIT_DELAY_PORT = 110;
|
|
20
|
+
export interface ExitTxParams {
|
|
21
|
+
vtxo: OmniaVtxo;
|
|
22
|
+
/** The pool's on-chain coin (input). */
|
|
23
|
+
fundingCoinId: string;
|
|
24
|
+
fundingAddress: string;
|
|
25
|
+
fundingScript: string;
|
|
26
|
+
/** Raw base-unit amount to pay the owner (the VTXO amount, pre-scaled). */
|
|
27
|
+
amount: bigint;
|
|
28
|
+
/** Exit delay carried as STATE(102). */
|
|
29
|
+
exitDelayBlocks: number;
|
|
30
|
+
}
|
|
31
|
+
/** Draft that spends the pool coin to the VTXO owner with the exit delay encoded. */
|
|
32
|
+
export declare function buildExitTransactionDraft(params: ExitTxParams): OmniaTxDraft;
|
|
33
|
+
export declare function exitTransactionDigest(draft: OmniaTxDraft): Uint8Array;
|
|
34
|
+
export declare function exitTransactionHex(draft: OmniaTxDraft): string;
|
|
35
|
+
interface MinedTxPow {
|
|
36
|
+
minedHeaderBytes: Uint8Array;
|
|
37
|
+
txpowId: Uint8Array;
|
|
38
|
+
}
|
|
39
|
+
export interface ExitTxDeps {
|
|
40
|
+
signDigest: (digest: Uint8Array) => Promise<Uint8Array>;
|
|
41
|
+
chainProvider?: ChainStateProvider;
|
|
42
|
+
mine?: (txBody: Uint8Array, difficulty: Uint8Array) => Promise<MinedTxPow>;
|
|
43
|
+
}
|
|
44
|
+
export interface ExitTxResult {
|
|
45
|
+
vtxoId: string;
|
|
46
|
+
owner: string;
|
|
47
|
+
amount: string;
|
|
48
|
+
exitDelayBlocks: number;
|
|
49
|
+
draftType: 'vtxo-exit';
|
|
50
|
+
txHex: string;
|
|
51
|
+
digest: string;
|
|
52
|
+
witnessHex: string;
|
|
53
|
+
txpowId?: string;
|
|
54
|
+
}
|
|
55
|
+
/** Build, sign (and optionally mine + broadcast) a VTXO exit transaction. */
|
|
56
|
+
export declare function buildExitTransaction(params: ExitTxParams, deps: ExitTxDeps): Promise<ExitTxResult>;
|
|
57
|
+
export {};
|
package/dist/exit-tx.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VTXO exit transaction (RFC-038 V2 — owner-unilateral).
|
|
3
|
+
*
|
|
4
|
+
* The exit spends the pool's on-chain coin and pays the VTXO's owner, carrying
|
|
5
|
+
* the exit delay in STATE(110). It is signed with the **owner's** key (not the
|
|
6
|
+
* operator's): the pool script's `IF @BLOCK GT EXITBLOCK THEN RETURN
|
|
7
|
+
* SIGNEDBY(OWNER)` branch authorizes it after the exit window, so the owner can
|
|
8
|
+
* broadcast it without any operator signature or liveness. The digest is signed
|
|
9
|
+
* (injected signer) and, when a chain provider is supplied, mined + broadcast
|
|
10
|
+
* (injected miner).
|
|
11
|
+
*
|
|
12
|
+
* The caller must pass the owner signer; the operator cannot construct a valid
|
|
13
|
+
* exit witness for itself.
|
|
14
|
+
*/
|
|
15
|
+
import { computeOmniaTxDigest, omniaDraftToMinimaBytes, serializeTxDraft, } from '@totemsdk/omnia';
|
|
16
|
+
import { serializeTxBody, mineTxPoW, TX_POW_MIN_DIFFICULTY } from '@totemsdk/txpow';
|
|
17
|
+
import { concatBytes } from '@totemsdk/core';
|
|
18
|
+
/** State port carrying the exit delay on the owner's output. */
|
|
19
|
+
export const EXIT_DELAY_PORT = 110;
|
|
20
|
+
/** Draft that spends the pool coin to the VTXO owner with the exit delay encoded. */
|
|
21
|
+
export function buildExitTransactionDraft(params) {
|
|
22
|
+
const stateVariables = [
|
|
23
|
+
{ port: EXIT_DELAY_PORT, value: BigInt(params.exitDelayBlocks), type: 'number' },
|
|
24
|
+
];
|
|
25
|
+
return {
|
|
26
|
+
type: 'settlement',
|
|
27
|
+
inputs: [{
|
|
28
|
+
coinId: params.fundingCoinId,
|
|
29
|
+
address: params.fundingAddress,
|
|
30
|
+
amount: params.amount,
|
|
31
|
+
tokenId: params.vtxo.tokenId,
|
|
32
|
+
scriptHex: params.fundingScript,
|
|
33
|
+
}],
|
|
34
|
+
outputs: [{
|
|
35
|
+
address: params.vtxo.owner,
|
|
36
|
+
amount: params.amount,
|
|
37
|
+
tokenId: params.vtxo.tokenId,
|
|
38
|
+
storeState: false,
|
|
39
|
+
stateVariables,
|
|
40
|
+
}],
|
|
41
|
+
storeState: false,
|
|
42
|
+
stateVariables,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export function exitTransactionDigest(draft) {
|
|
46
|
+
return computeOmniaTxDigest(draft);
|
|
47
|
+
}
|
|
48
|
+
export function exitTransactionHex(draft) {
|
|
49
|
+
return serializeTxDraft(draft);
|
|
50
|
+
}
|
|
51
|
+
/** Build, sign (and optionally mine + broadcast) a VTXO exit transaction. */
|
|
52
|
+
export async function buildExitTransaction(params, deps) {
|
|
53
|
+
const draft = buildExitTransactionDraft(params);
|
|
54
|
+
const digest = exitTransactionDigest(draft);
|
|
55
|
+
const signature = await deps.signDigest(digest);
|
|
56
|
+
const result = {
|
|
57
|
+
vtxoId: params.vtxo.vtxoId,
|
|
58
|
+
owner: params.vtxo.owner,
|
|
59
|
+
amount: params.vtxo.amount.toString(),
|
|
60
|
+
exitDelayBlocks: params.exitDelayBlocks,
|
|
61
|
+
draftType: 'vtxo-exit',
|
|
62
|
+
txHex: exitTransactionHex(draft),
|
|
63
|
+
digest: Buffer.from(digest).toString('hex'),
|
|
64
|
+
witnessHex: Buffer.from(signature).toString('hex'),
|
|
65
|
+
};
|
|
66
|
+
if (deps.chainProvider) {
|
|
67
|
+
const mine = deps.mine ?? ((txBody, difficulty) => mineTxPoW(txBody, difficulty));
|
|
68
|
+
const draftBytes = omniaDraftToMinimaBytes(draft);
|
|
69
|
+
const txBody = serializeTxBody(draftBytes, signature);
|
|
70
|
+
const mined = await mine(txBody, TX_POW_MIN_DIFFICULTY);
|
|
71
|
+
const fullTxPoW = concatBytes(concatBytes(mined.minedHeaderBytes, new Uint8Array([0x01])), txBody);
|
|
72
|
+
await deps.chainProvider.broadcastTxPoW(Buffer.from(fullTxPoW).toString('hex'));
|
|
73
|
+
result.txpowId = Buffer.from(mined.txpowId).toString('hex');
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
package/dist/exit.d.ts
CHANGED
|
@@ -8,7 +8,10 @@ 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,
|
|
11
|
+
export declare function markExited(vtxo: OmniaVtxo, options?: {
|
|
12
|
+
txpowId?: string;
|
|
13
|
+
now?: number;
|
|
14
|
+
}): OmniaVtxo;
|
|
12
15
|
/**
|
|
13
16
|
* Creates a mock exit draft for a VTXO. Validates the proof leaf before
|
|
14
17
|
* drafting, caps the exit at the verified share, and consumes the VTXO's
|
package/dist/exit.js
CHANGED
|
@@ -1,20 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.markExiting = markExiting;
|
|
4
|
-
exports.markExited = markExited;
|
|
5
|
-
exports.createExitDraft = createExitDraft;
|
|
6
|
-
exports.consumeExitReceipt = consumeExitReceipt;
|
|
7
|
-
const errors_js_1 = require("./errors.js");
|
|
8
|
-
const constants_js_1 = require("./constants.js");
|
|
9
|
-
const commitment_js_1 = require("./commitment.js");
|
|
1
|
+
import { VtxoStatusError, VtxoExitError, VtxoProofError } from './errors.js';
|
|
2
|
+
import { computeReceiptId, computeVtxoLeaf } from './commitment.js';
|
|
10
3
|
/**
|
|
11
4
|
* Transitions an active VTXO to 'exiting' status.
|
|
12
5
|
* @param now - Timestamp in ms. Defaults to Date.now() if omitted — pass explicitly for determinism.
|
|
13
6
|
*/
|
|
14
|
-
function markExiting(vtxo, now) {
|
|
7
|
+
export function markExiting(vtxo, now) {
|
|
15
8
|
const ts = now !== undefined ? now : Date.now();
|
|
16
9
|
if (vtxo.status !== 'active') {
|
|
17
|
-
throw new
|
|
10
|
+
throw new VtxoStatusError(`Cannot initiate exit for VTXO ${vtxo.vtxoId}: status is '${vtxo.status}'`, vtxo.status);
|
|
18
11
|
}
|
|
19
12
|
return {
|
|
20
13
|
...vtxo,
|
|
@@ -30,18 +23,19 @@ function markExiting(vtxo, now) {
|
|
|
30
23
|
* Transitions an exiting VTXO to 'exited' status.
|
|
31
24
|
* @param now - Timestamp in ms. Defaults to Date.now() if omitted — pass explicitly for determinism.
|
|
32
25
|
*/
|
|
33
|
-
function markExited(vtxo,
|
|
34
|
-
const ts = now !== undefined ? now : Date.now();
|
|
26
|
+
export function markExited(vtxo, options = {}) {
|
|
27
|
+
const ts = options.now !== undefined ? options.now : Date.now();
|
|
35
28
|
if (vtxo.status !== 'exiting') {
|
|
36
|
-
throw new
|
|
29
|
+
throw new VtxoStatusError(`Cannot mark VTXO ${vtxo.vtxoId} as exited: status is '${vtxo.status}'`, vtxo.status);
|
|
37
30
|
}
|
|
38
31
|
return {
|
|
39
32
|
...vtxo,
|
|
40
33
|
status: 'exited',
|
|
41
34
|
updatedAt: ts,
|
|
35
|
+
...(options.txpowId ? { exitTxpowId: options.txpowId } : {}),
|
|
42
36
|
history: [
|
|
43
37
|
...vtxo.history,
|
|
44
|
-
{ op: 'exit_complete', at: ts },
|
|
38
|
+
{ op: 'exit_complete', at: ts, ...(options.txpowId ? { meta: { txpowId: options.txpowId } } : {}) },
|
|
45
39
|
],
|
|
46
40
|
};
|
|
47
41
|
}
|
|
@@ -52,9 +46,9 @@ function markExited(vtxo, now) {
|
|
|
52
46
|
function assertProofLeafValid(vtxo) {
|
|
53
47
|
if (!vtxo.proof.leaf)
|
|
54
48
|
return;
|
|
55
|
-
const expectedLeaf =
|
|
49
|
+
const expectedLeaf = computeVtxoLeaf(vtxo);
|
|
56
50
|
if (vtxo.proof.leaf !== expectedLeaf) {
|
|
57
|
-
throw new
|
|
51
|
+
throw new VtxoProofError(`Exit draft rejected: VTXO ${vtxo.vtxoId} proof leaf mismatch. ` +
|
|
58
52
|
`Expected ${expectedLeaf}, got ${vtxo.proof.leaf}`);
|
|
59
53
|
}
|
|
60
54
|
}
|
|
@@ -64,13 +58,13 @@ function assertProofLeafValid(vtxo) {
|
|
|
64
58
|
* receipt so a second exit draft is refused (double-exit prevention, #28).
|
|
65
59
|
* @param now - Timestamp in ms. Defaults to Date.now() if omitted — pass explicitly for determinism.
|
|
66
60
|
*/
|
|
67
|
-
function createExitDraft(vtxo, now) {
|
|
61
|
+
export function createExitDraft(vtxo, now) {
|
|
68
62
|
const ts = now !== undefined ? now : Date.now();
|
|
69
63
|
if (vtxo.status !== 'active' && vtxo.status !== 'exiting') {
|
|
70
|
-
throw new
|
|
64
|
+
throw new VtxoExitError(`Cannot create exit draft for VTXO ${vtxo.vtxoId}: status is '${vtxo.status}'`);
|
|
71
65
|
}
|
|
72
66
|
if (vtxo.exitConsumedAt !== undefined) {
|
|
73
|
-
throw new
|
|
67
|
+
throw new VtxoExitError(`Cannot create exit draft for VTXO ${vtxo.vtxoId}: exit already consumed by ${vtxo.exitReceiptId}`);
|
|
74
68
|
}
|
|
75
69
|
assertProofLeafValid(vtxo);
|
|
76
70
|
const draft = {
|
|
@@ -79,12 +73,12 @@ function createExitDraft(vtxo, now) {
|
|
|
79
73
|
owner: vtxo.owner,
|
|
80
74
|
amount: vtxo.amount,
|
|
81
75
|
tokenId: vtxo.tokenId,
|
|
82
|
-
draftType: '
|
|
76
|
+
draftType: 'vtxo-exit',
|
|
83
77
|
timelockSeconds: 86400,
|
|
84
78
|
createdAt: ts,
|
|
85
79
|
verifiedShare: vtxo.amount,
|
|
86
80
|
};
|
|
87
|
-
const receiptId =
|
|
81
|
+
const receiptId = computeReceiptId(vtxo.poolId, 'exit_initiated', [vtxo.vtxoId], [], ts);
|
|
88
82
|
const receipt = {
|
|
89
83
|
receiptId,
|
|
90
84
|
poolId: vtxo.poolId,
|
|
@@ -93,8 +87,8 @@ function createExitDraft(vtxo, now) {
|
|
|
93
87
|
outputIds: [],
|
|
94
88
|
epoch: vtxo.epoch,
|
|
95
89
|
at: ts,
|
|
96
|
-
signature:
|
|
97
|
-
draftType: '
|
|
90
|
+
signature: '',
|
|
91
|
+
draftType: 'vtxo-exit',
|
|
98
92
|
};
|
|
99
93
|
return { draft, receipt };
|
|
100
94
|
}
|
|
@@ -102,9 +96,9 @@ function createExitDraft(vtxo, now) {
|
|
|
102
96
|
* Consume a VTXO's exit receipt — the single-spend marker that prevents a
|
|
103
97
|
* second exit draft for the same VTXO. Returns the updated VTXO.
|
|
104
98
|
*/
|
|
105
|
-
function consumeExitReceipt(vtxo, receiptId, now) {
|
|
99
|
+
export function consumeExitReceipt(vtxo, receiptId, now) {
|
|
106
100
|
if (vtxo.exitConsumedAt !== undefined) {
|
|
107
|
-
throw new
|
|
101
|
+
throw new VtxoExitError(`VTXO ${vtxo.vtxoId} exit already consumed by ${vtxo.exitReceiptId}`);
|
|
108
102
|
}
|
|
109
103
|
return {
|
|
110
104
|
...vtxo,
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,11 @@ export * from './types.js';
|
|
|
2
2
|
export * from './errors.js';
|
|
3
3
|
export * from './constants.js';
|
|
4
4
|
export * from './commitment.js';
|
|
5
|
+
export * from './operator.js';
|
|
6
|
+
export * from './receipt.js';
|
|
7
|
+
export * from './pool-script.js';
|
|
8
|
+
export * from './anchor.js';
|
|
9
|
+
export * from './exit-tx.js';
|
|
5
10
|
export * from './pool.js';
|
|
6
11
|
export * from './vtxo.js';
|
|
7
12
|
export * from './transfer.js';
|
package/dist/index.js
CHANGED
|
@@ -1,32 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
__exportStar(require("./vtxo.js"), exports);
|
|
23
|
-
__exportStar(require("./transfer.js"), exports);
|
|
24
|
-
__exportStar(require("./split.js"), exports);
|
|
25
|
-
__exportStar(require("./merge.js"), exports);
|
|
26
|
-
__exportStar(require("./refresh.js"), exports);
|
|
27
|
-
__exportStar(require("./exit.js"), exports);
|
|
28
|
-
__exportStar(require("./verify.js"), exports);
|
|
29
|
-
__exportStar(require("./memory-store.js"), exports);
|
|
30
|
-
__exportStar(require("./durable-store.js"), exports);
|
|
31
|
-
__exportStar(require("./serialization.js"), exports);
|
|
32
|
-
__exportStar(require("./policy.js"), exports);
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './errors.js';
|
|
3
|
+
export * from './constants.js';
|
|
4
|
+
export * from './commitment.js';
|
|
5
|
+
export * from './operator.js';
|
|
6
|
+
export * from './receipt.js';
|
|
7
|
+
export * from './pool-script.js';
|
|
8
|
+
export * from './anchor.js';
|
|
9
|
+
export * from './exit-tx.js';
|
|
10
|
+
export * from './pool.js';
|
|
11
|
+
export * from './vtxo.js';
|
|
12
|
+
export * from './transfer.js';
|
|
13
|
+
export * from './split.js';
|
|
14
|
+
export * from './merge.js';
|
|
15
|
+
export * from './refresh.js';
|
|
16
|
+
export * from './exit.js';
|
|
17
|
+
export * from './verify.js';
|
|
18
|
+
export * from './memory-store.js';
|
|
19
|
+
export * from './durable-store.js';
|
|
20
|
+
export * from './serialization.js';
|
|
21
|
+
export * from './policy.js';
|