@totemsdk/omnia-vtxo 0.1.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/LICENSE +21 -0
- package/README.md +293 -0
- package/dist/commitment.d.ts +28 -0
- package/dist/commitment.js +153 -0
- package/dist/constants.d.ts +12 -0
- package/dist/constants.js +15 -0
- package/dist/errors.d.ts +33 -0
- package/dist/errors.js +77 -0
- package/dist/exit.d.ts +19 -0
- package/dist/exit.js +93 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +31 -0
- package/dist/memory-store.d.ts +17 -0
- package/dist/memory-store.js +51 -0
- package/dist/merge.d.ts +8 -0
- package/dist/merge.js +107 -0
- package/dist/policy.d.ts +22 -0
- package/dist/policy.js +27 -0
- package/dist/pool.d.ts +5 -0
- package/dist/pool.js +60 -0
- package/dist/refresh.d.ts +8 -0
- package/dist/refresh.js +85 -0
- package/dist/serialization.d.ts +5 -0
- package/dist/serialization.js +31 -0
- package/dist/split.d.ts +8 -0
- package/dist/split.js +97 -0
- package/dist/transfer.d.ts +8 -0
- package/dist/transfer.js +153 -0
- package/dist/types.d.ts +155 -0
- package/dist/types.js +2 -0
- package/dist/verify.d.ts +23 -0
- package/dist/verify.js +121 -0
- package/dist/vtxo.d.ts +14 -0
- package/dist/vtxo.js +96 -0
- package/package.json +76 -0
package/dist/split.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.splitVtxo = splitVtxo;
|
|
4
|
+
const errors_js_1 = require("./errors.js");
|
|
5
|
+
const commitment_js_1 = require("./commitment.js");
|
|
6
|
+
const constants_js_1 = require("./constants.js");
|
|
7
|
+
/**
|
|
8
|
+
* Splits a VTXO into multiple outputs.
|
|
9
|
+
* @param now - Timestamp in ms. Defaults to Date.now() if omitted — pass explicitly for determinism.
|
|
10
|
+
*/
|
|
11
|
+
function splitVtxo(vtxo, params, now) {
|
|
12
|
+
const ts = now !== undefined ? now : Date.now();
|
|
13
|
+
if (vtxo.status !== 'active') {
|
|
14
|
+
throw new errors_js_1.VtxoStatusError(`Cannot split VTXO ${vtxo.vtxoId}: status is '${vtxo.status}'`, vtxo.status);
|
|
15
|
+
}
|
|
16
|
+
if (params.amounts.length < 2) {
|
|
17
|
+
throw new errors_js_1.VtxoSplitError('Split requires at least 2 output amounts');
|
|
18
|
+
}
|
|
19
|
+
if (params.amounts.length !== params.nonces.length) {
|
|
20
|
+
throw new errors_js_1.VtxoSplitError('amounts and nonces arrays must have the same length');
|
|
21
|
+
}
|
|
22
|
+
for (const amt of params.amounts) {
|
|
23
|
+
if (amt <= BigInt(0)) {
|
|
24
|
+
throw new errors_js_1.VtxoAmountError(`All split amounts must be > 0, got ${amt}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const sum = params.amounts.reduce((a, b) => a + b, BigInt(0));
|
|
28
|
+
if (sum !== vtxo.amount) {
|
|
29
|
+
throw new errors_js_1.VtxoAmountError(`Split amounts sum ${sum} does not equal VTXO amount ${vtxo.amount}`);
|
|
30
|
+
}
|
|
31
|
+
const outputIds = params.amounts.map((amount, i) => (0, commitment_js_1.computeVtxoId)({
|
|
32
|
+
poolId: vtxo.poolId,
|
|
33
|
+
owner: vtxo.owner,
|
|
34
|
+
amount,
|
|
35
|
+
tokenId: vtxo.tokenId,
|
|
36
|
+
nonce: params.nonces[i],
|
|
37
|
+
}));
|
|
38
|
+
const inputVtxo = {
|
|
39
|
+
...vtxo,
|
|
40
|
+
status: 'split',
|
|
41
|
+
updatedAt: ts,
|
|
42
|
+
history: [
|
|
43
|
+
...vtxo.history,
|
|
44
|
+
{
|
|
45
|
+
op: 'split',
|
|
46
|
+
at: ts,
|
|
47
|
+
from: vtxo.owner,
|
|
48
|
+
relatedIds: outputIds,
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
const rawOutputs = params.amounts.map((amount, i) => ({
|
|
53
|
+
vtxoId: outputIds[i],
|
|
54
|
+
poolId: vtxo.poolId,
|
|
55
|
+
owner: vtxo.owner,
|
|
56
|
+
amount,
|
|
57
|
+
tokenId: vtxo.tokenId,
|
|
58
|
+
status: 'active',
|
|
59
|
+
epoch: vtxo.epoch,
|
|
60
|
+
proof: {
|
|
61
|
+
leaf: '',
|
|
62
|
+
root: '',
|
|
63
|
+
siblings: [],
|
|
64
|
+
positions: [],
|
|
65
|
+
epoch: vtxo.epoch,
|
|
66
|
+
batchId: constants_js_1.MOCK_BATCH_ID,
|
|
67
|
+
},
|
|
68
|
+
history: [
|
|
69
|
+
{
|
|
70
|
+
op: 'split',
|
|
71
|
+
at: ts,
|
|
72
|
+
from: vtxo.owner,
|
|
73
|
+
relatedIds: [vtxo.vtxoId],
|
|
74
|
+
meta: { splitIndex: i },
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
createdAt: ts,
|
|
78
|
+
updatedAt: ts,
|
|
79
|
+
}));
|
|
80
|
+
const { proofs } = (0, commitment_js_1.buildVtxoProofSet)(rawOutputs);
|
|
81
|
+
const outputs = rawOutputs.map(o => ({ ...o, proof: proofs[o.vtxoId] }));
|
|
82
|
+
const receiptId = (0, commitment_js_1.computeReceiptId)(vtxo.poolId, 'split', [vtxo.vtxoId], outputIds, ts);
|
|
83
|
+
return {
|
|
84
|
+
input: inputVtxo,
|
|
85
|
+
outputs,
|
|
86
|
+
receipt: {
|
|
87
|
+
receiptId,
|
|
88
|
+
poolId: vtxo.poolId,
|
|
89
|
+
op: 'split',
|
|
90
|
+
inputIds: [vtxo.vtxoId],
|
|
91
|
+
outputIds,
|
|
92
|
+
epoch: vtxo.epoch,
|
|
93
|
+
at: ts,
|
|
94
|
+
signature: constants_js_1.MOCK_OPERATOR_SIGNATURE,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { OmniaVtxo, TransferVtxoParams, TransferResult, VtxoOperatorReceipt } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Transfers a VTXO (fully or partially) to a recipient.
|
|
4
|
+
* @param now - Timestamp in ms. Defaults to Date.now() if omitted — pass explicitly for determinism.
|
|
5
|
+
*/
|
|
6
|
+
export declare function transferVtxo(vtxo: OmniaVtxo, params: TransferVtxoParams, now?: number): TransferResult & {
|
|
7
|
+
receipt: VtxoOperatorReceipt;
|
|
8
|
+
};
|
package/dist/transfer.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.transferVtxo = transferVtxo;
|
|
4
|
+
const errors_js_1 = require("./errors.js");
|
|
5
|
+
const commitment_js_1 = require("./commitment.js");
|
|
6
|
+
const constants_js_1 = require("./constants.js");
|
|
7
|
+
/**
|
|
8
|
+
* Transfers a VTXO (fully or partially) to a recipient.
|
|
9
|
+
* @param now - Timestamp in ms. Defaults to Date.now() if omitted — pass explicitly for determinism.
|
|
10
|
+
*/
|
|
11
|
+
function transferVtxo(vtxo, params, now) {
|
|
12
|
+
const ts = now !== undefined ? now : Date.now();
|
|
13
|
+
if (vtxo.status !== 'active') {
|
|
14
|
+
throw new errors_js_1.VtxoStatusError(`Cannot transfer VTXO ${vtxo.vtxoId}: status is '${vtxo.status}'`, vtxo.status);
|
|
15
|
+
}
|
|
16
|
+
if (params.amount <= BigInt(0)) {
|
|
17
|
+
throw new errors_js_1.VtxoAmountError(`Transfer amount must be > 0, got ${params.amount}`);
|
|
18
|
+
}
|
|
19
|
+
if (params.amount > vtxo.amount) {
|
|
20
|
+
throw new errors_js_1.VtxoAmountError(`Transfer amount ${params.amount} exceeds VTXO amount ${vtxo.amount}`);
|
|
21
|
+
}
|
|
22
|
+
const isPartial = params.amount < vtxo.amount;
|
|
23
|
+
const changeAmount = vtxo.amount - params.amount;
|
|
24
|
+
const outputId = (0, commitment_js_1.computeVtxoId)({
|
|
25
|
+
poolId: vtxo.poolId,
|
|
26
|
+
owner: params.recipient,
|
|
27
|
+
amount: params.amount,
|
|
28
|
+
tokenId: vtxo.tokenId,
|
|
29
|
+
nonce: params.nonce,
|
|
30
|
+
});
|
|
31
|
+
let changeVtxo;
|
|
32
|
+
let changeId;
|
|
33
|
+
if (isPartial) {
|
|
34
|
+
const changeNonce = params.changeNonce ?? params.nonce + ':change';
|
|
35
|
+
changeId = (0, commitment_js_1.computeVtxoId)({
|
|
36
|
+
poolId: vtxo.poolId,
|
|
37
|
+
owner: vtxo.owner,
|
|
38
|
+
amount: changeAmount,
|
|
39
|
+
tokenId: vtxo.tokenId,
|
|
40
|
+
nonce: changeNonce,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const inputVtxo = {
|
|
44
|
+
...vtxo,
|
|
45
|
+
status: isPartial ? 'split' : 'transferred',
|
|
46
|
+
updatedAt: ts,
|
|
47
|
+
history: [
|
|
48
|
+
...vtxo.history,
|
|
49
|
+
{
|
|
50
|
+
op: isPartial ? 'split' : 'transfer',
|
|
51
|
+
at: ts,
|
|
52
|
+
from: vtxo.owner,
|
|
53
|
+
to: params.recipient,
|
|
54
|
+
relatedIds: changeId ? [outputId, changeId] : [outputId],
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
const outputVtxo = {
|
|
59
|
+
vtxoId: outputId,
|
|
60
|
+
poolId: vtxo.poolId,
|
|
61
|
+
owner: params.recipient,
|
|
62
|
+
amount: params.amount,
|
|
63
|
+
tokenId: vtxo.tokenId,
|
|
64
|
+
status: 'active',
|
|
65
|
+
epoch: vtxo.epoch,
|
|
66
|
+
proof: {
|
|
67
|
+
leaf: '',
|
|
68
|
+
root: '',
|
|
69
|
+
siblings: [],
|
|
70
|
+
positions: [],
|
|
71
|
+
epoch: vtxo.epoch,
|
|
72
|
+
batchId: constants_js_1.MOCK_BATCH_ID,
|
|
73
|
+
},
|
|
74
|
+
history: [
|
|
75
|
+
{
|
|
76
|
+
op: 'transfer',
|
|
77
|
+
at: ts,
|
|
78
|
+
from: vtxo.owner,
|
|
79
|
+
to: params.recipient,
|
|
80
|
+
relatedIds: [vtxo.vtxoId],
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
createdAt: ts,
|
|
84
|
+
updatedAt: ts,
|
|
85
|
+
};
|
|
86
|
+
const outputIds = [outputId];
|
|
87
|
+
if (isPartial && changeId) {
|
|
88
|
+
changeVtxo = {
|
|
89
|
+
vtxoId: changeId,
|
|
90
|
+
poolId: vtxo.poolId,
|
|
91
|
+
owner: vtxo.owner,
|
|
92
|
+
amount: changeAmount,
|
|
93
|
+
tokenId: vtxo.tokenId,
|
|
94
|
+
status: 'active',
|
|
95
|
+
epoch: vtxo.epoch,
|
|
96
|
+
proof: {
|
|
97
|
+
leaf: '',
|
|
98
|
+
root: '',
|
|
99
|
+
siblings: [],
|
|
100
|
+
positions: [],
|
|
101
|
+
epoch: vtxo.epoch,
|
|
102
|
+
batchId: constants_js_1.MOCK_BATCH_ID,
|
|
103
|
+
},
|
|
104
|
+
history: [
|
|
105
|
+
{
|
|
106
|
+
op: 'split',
|
|
107
|
+
at: ts,
|
|
108
|
+
from: vtxo.owner,
|
|
109
|
+
relatedIds: [vtxo.vtxoId],
|
|
110
|
+
meta: { role: 'change' },
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
createdAt: ts,
|
|
114
|
+
updatedAt: ts,
|
|
115
|
+
};
|
|
116
|
+
outputIds.push(changeId);
|
|
117
|
+
}
|
|
118
|
+
const activeOutputs = changeVtxo ? [outputVtxo, changeVtxo] : [outputVtxo];
|
|
119
|
+
const { proofs } = (0, commitment_js_1.buildVtxoProofSet)(activeOutputs);
|
|
120
|
+
const patchedOutput = { ...outputVtxo, proof: proofs[outputId] };
|
|
121
|
+
const patchedChange = changeVtxo
|
|
122
|
+
? { ...changeVtxo, proof: proofs[changeId] }
|
|
123
|
+
: undefined;
|
|
124
|
+
const transfer = {
|
|
125
|
+
inputId: vtxo.vtxoId,
|
|
126
|
+
outputId,
|
|
127
|
+
changeId,
|
|
128
|
+
from: vtxo.owner,
|
|
129
|
+
to: params.recipient,
|
|
130
|
+
amount: params.amount,
|
|
131
|
+
changeAmount: isPartial ? changeAmount : undefined,
|
|
132
|
+
poolId: vtxo.poolId,
|
|
133
|
+
tokenId: vtxo.tokenId,
|
|
134
|
+
at: ts,
|
|
135
|
+
};
|
|
136
|
+
const receiptId = (0, commitment_js_1.computeReceiptId)(vtxo.poolId, 'transfer', [vtxo.vtxoId], outputIds, ts);
|
|
137
|
+
return {
|
|
138
|
+
input: inputVtxo,
|
|
139
|
+
output: patchedOutput,
|
|
140
|
+
change: patchedChange,
|
|
141
|
+
transfer,
|
|
142
|
+
receipt: {
|
|
143
|
+
receiptId,
|
|
144
|
+
poolId: vtxo.poolId,
|
|
145
|
+
op: 'transfer',
|
|
146
|
+
inputIds: [vtxo.vtxoId],
|
|
147
|
+
outputIds,
|
|
148
|
+
epoch: vtxo.epoch,
|
|
149
|
+
at: ts,
|
|
150
|
+
signature: constants_js_1.MOCK_OPERATOR_SIGNATURE,
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
export type VtxoId = string;
|
|
2
|
+
export type VtxoStatus = 'active' | 'transferred' | 'split' | 'merged' | 'refreshed' | 'exiting' | 'exited' | 'spent' | 'expired' | 'invalid';
|
|
3
|
+
export type VtxoOp = 'mint' | 'transfer' | 'split' | 'merge' | 'refresh' | 'exit_initiated' | 'exit_complete' | 'spent';
|
|
4
|
+
export interface VtxoPoolPolicy {
|
|
5
|
+
minAmount: bigint;
|
|
6
|
+
maxAmount: bigint;
|
|
7
|
+
maxMergeInputs: number;
|
|
8
|
+
maxSplitOutputs: number;
|
|
9
|
+
exitTimelockSeconds: number;
|
|
10
|
+
}
|
|
11
|
+
export interface OmniaVtxoPool {
|
|
12
|
+
poolId: string;
|
|
13
|
+
operator: string;
|
|
14
|
+
tokenId: string;
|
|
15
|
+
totalCapacity: bigint;
|
|
16
|
+
availableCapacity: bigint;
|
|
17
|
+
epoch: number;
|
|
18
|
+
commitmentRoot: string;
|
|
19
|
+
createdAt: number;
|
|
20
|
+
policy: VtxoPoolPolicy;
|
|
21
|
+
}
|
|
22
|
+
export interface VtxoProof {
|
|
23
|
+
leaf: string;
|
|
24
|
+
root: string;
|
|
25
|
+
siblings: string[];
|
|
26
|
+
positions: Array<'left' | 'right'>;
|
|
27
|
+
epoch: number;
|
|
28
|
+
batchId: string;
|
|
29
|
+
}
|
|
30
|
+
export interface VtxoHistoryEntry {
|
|
31
|
+
op: VtxoOp;
|
|
32
|
+
at: number;
|
|
33
|
+
from?: string;
|
|
34
|
+
to?: string;
|
|
35
|
+
relatedIds?: VtxoId[];
|
|
36
|
+
meta?: Record<string, unknown>;
|
|
37
|
+
}
|
|
38
|
+
export interface OmniaVtxo {
|
|
39
|
+
vtxoId: VtxoId;
|
|
40
|
+
poolId: string;
|
|
41
|
+
owner: string;
|
|
42
|
+
amount: bigint;
|
|
43
|
+
tokenId: string;
|
|
44
|
+
status: VtxoStatus;
|
|
45
|
+
epoch: number;
|
|
46
|
+
proof: VtxoProof;
|
|
47
|
+
history: VtxoHistoryEntry[];
|
|
48
|
+
createdAt: number;
|
|
49
|
+
updatedAt: number;
|
|
50
|
+
expiresAt?: number;
|
|
51
|
+
}
|
|
52
|
+
export interface VtxoTransfer {
|
|
53
|
+
inputId: VtxoId;
|
|
54
|
+
outputId: VtxoId;
|
|
55
|
+
changeId?: VtxoId;
|
|
56
|
+
from: string;
|
|
57
|
+
to: string;
|
|
58
|
+
amount: bigint;
|
|
59
|
+
changeAmount?: bigint;
|
|
60
|
+
poolId: string;
|
|
61
|
+
tokenId: string;
|
|
62
|
+
at: number;
|
|
63
|
+
}
|
|
64
|
+
export interface VtxoOperatorReceipt {
|
|
65
|
+
receiptId: string;
|
|
66
|
+
poolId: string;
|
|
67
|
+
op: VtxoOp;
|
|
68
|
+
inputIds: VtxoId[];
|
|
69
|
+
outputIds: VtxoId[];
|
|
70
|
+
epoch: number;
|
|
71
|
+
at: number;
|
|
72
|
+
signature: string;
|
|
73
|
+
draftType?: string;
|
|
74
|
+
}
|
|
75
|
+
export interface CreatePoolParams {
|
|
76
|
+
operator: string;
|
|
77
|
+
tokenId: string;
|
|
78
|
+
totalCapacity: bigint;
|
|
79
|
+
policy?: Partial<VtxoPoolPolicy>;
|
|
80
|
+
nonce: string;
|
|
81
|
+
}
|
|
82
|
+
export interface MintVtxoParams {
|
|
83
|
+
owner: string;
|
|
84
|
+
amount: bigint;
|
|
85
|
+
nonce: string;
|
|
86
|
+
expiresAt?: number;
|
|
87
|
+
}
|
|
88
|
+
export interface TransferVtxoParams {
|
|
89
|
+
recipient: string;
|
|
90
|
+
amount: bigint;
|
|
91
|
+
nonce: string;
|
|
92
|
+
changeNonce?: string;
|
|
93
|
+
}
|
|
94
|
+
export interface SplitVtxoParams {
|
|
95
|
+
amounts: bigint[];
|
|
96
|
+
nonces: string[];
|
|
97
|
+
}
|
|
98
|
+
export interface MergeVtxosParams {
|
|
99
|
+
nonce: string;
|
|
100
|
+
owner: string;
|
|
101
|
+
}
|
|
102
|
+
export interface RefreshVtxoParams {
|
|
103
|
+
newEpoch: number;
|
|
104
|
+
nonce: string;
|
|
105
|
+
}
|
|
106
|
+
export interface MintResult {
|
|
107
|
+
pool: OmniaVtxoPool;
|
|
108
|
+
vtxo: OmniaVtxo;
|
|
109
|
+
receipt: VtxoOperatorReceipt;
|
|
110
|
+
}
|
|
111
|
+
export interface TransferResult {
|
|
112
|
+
input: OmniaVtxo;
|
|
113
|
+
output: OmniaVtxo;
|
|
114
|
+
change?: OmniaVtxo;
|
|
115
|
+
transfer: VtxoTransfer;
|
|
116
|
+
}
|
|
117
|
+
export interface SplitResult {
|
|
118
|
+
input: OmniaVtxo;
|
|
119
|
+
outputs: OmniaVtxo[];
|
|
120
|
+
}
|
|
121
|
+
export interface MergeResult {
|
|
122
|
+
inputs: OmniaVtxo[];
|
|
123
|
+
output: OmniaVtxo;
|
|
124
|
+
}
|
|
125
|
+
export interface RefreshResult {
|
|
126
|
+
old: OmniaVtxo;
|
|
127
|
+
refreshed: OmniaVtxo;
|
|
128
|
+
}
|
|
129
|
+
export interface ExitDraft {
|
|
130
|
+
vtxoId: VtxoId;
|
|
131
|
+
poolId: string;
|
|
132
|
+
owner: string;
|
|
133
|
+
amount: bigint;
|
|
134
|
+
tokenId: string;
|
|
135
|
+
draftType: 'mock-exit';
|
|
136
|
+
timelockSeconds: number;
|
|
137
|
+
createdAt: number;
|
|
138
|
+
}
|
|
139
|
+
export interface VerifyVtxoResult {
|
|
140
|
+
valid: boolean;
|
|
141
|
+
errors: string[];
|
|
142
|
+
}
|
|
143
|
+
export interface OmniaVtxoStore {
|
|
144
|
+
savePool(pool: OmniaVtxoPool): Promise<void>;
|
|
145
|
+
getPool(poolId: string): Promise<OmniaVtxoPool | undefined>;
|
|
146
|
+
saveVtxo(vtxo: OmniaVtxo): Promise<void>;
|
|
147
|
+
getVtxo(vtxoId: VtxoId): Promise<OmniaVtxo | undefined>;
|
|
148
|
+
listVtxos(poolId?: string): Promise<OmniaVtxo[]>;
|
|
149
|
+
markVtxoSpent(vtxoId: VtxoId, now?: number): Promise<void>;
|
|
150
|
+
}
|
|
151
|
+
export interface OmniaVtxoOperator {
|
|
152
|
+
poolId: string;
|
|
153
|
+
sign(data: Uint8Array): Promise<string>;
|
|
154
|
+
verify(data: Uint8Array, sig: string): Promise<boolean>;
|
|
155
|
+
}
|
package/dist/types.js
ADDED
package/dist/verify.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { OmniaVtxo, VtxoProof, VtxoTransfer, VerifyVtxoResult } from './types.js';
|
|
2
|
+
export declare function verifyVtxo(vtxo: OmniaVtxo): VerifyVtxoResult;
|
|
3
|
+
export declare function verifyVtxoProof(vtxo: OmniaVtxo, proof: VtxoProof): VerifyVtxoResult;
|
|
4
|
+
export declare function verifyVtxoTransfer(input: OmniaVtxo, output: OmniaVtxo, transfer: VtxoTransfer): VerifyVtxoResult;
|
|
5
|
+
export interface ConservationInput {
|
|
6
|
+
inputs: OmniaVtxo[];
|
|
7
|
+
outputs: OmniaVtxo[];
|
|
8
|
+
/**
|
|
9
|
+
* Conservation mode:
|
|
10
|
+
* - `'lte'` (default) — outputs may be less than or equal to inputs (allows exit/fee/burn flows)
|
|
11
|
+
* - `'strict'` — outputs must equal inputs exactly (required for transfer, split, merge)
|
|
12
|
+
*/
|
|
13
|
+
mode?: 'lte' | 'strict';
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Verifies amount conservation across a set of inputs and outputs.
|
|
17
|
+
*
|
|
18
|
+
* Default mode (`'lte'`): `sum(outputs) <= sum(inputs)` — allows exits, fees, and burn flows.
|
|
19
|
+
* Strict mode (`'strict'`): `sum(outputs) === sum(inputs)` — required for transfer, split, merge.
|
|
20
|
+
*
|
|
21
|
+
* Always requires: same `poolId` and `tokenId` across all inputs and outputs.
|
|
22
|
+
*/
|
|
23
|
+
export declare function verifyConservation(params: ConservationInput): VerifyVtxoResult;
|
package/dist/verify.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.verifyVtxo = verifyVtxo;
|
|
4
|
+
exports.verifyVtxoProof = verifyVtxoProof;
|
|
5
|
+
exports.verifyVtxoTransfer = verifyVtxoTransfer;
|
|
6
|
+
exports.verifyConservation = verifyConservation;
|
|
7
|
+
const commitment_js_1 = require("./commitment.js");
|
|
8
|
+
function verifyVtxo(vtxo) {
|
|
9
|
+
const errors = [];
|
|
10
|
+
if (!vtxo.vtxoId)
|
|
11
|
+
errors.push('Missing vtxoId');
|
|
12
|
+
if (!vtxo.poolId)
|
|
13
|
+
errors.push('Missing poolId');
|
|
14
|
+
if (!vtxo.owner)
|
|
15
|
+
errors.push('Missing owner');
|
|
16
|
+
if (vtxo.amount <= BigInt(0))
|
|
17
|
+
errors.push(`Amount must be > 0, got ${vtxo.amount}`);
|
|
18
|
+
if (!vtxo.tokenId)
|
|
19
|
+
errors.push('Missing tokenId');
|
|
20
|
+
if (!vtxo.status)
|
|
21
|
+
errors.push('Missing status');
|
|
22
|
+
if (vtxo.proof && vtxo.proof.leaf && vtxo.proof.root) {
|
|
23
|
+
const proofResult = verifyVtxoProof(vtxo, vtxo.proof);
|
|
24
|
+
if (!proofResult.valid) {
|
|
25
|
+
errors.push(...proofResult.errors.map(e => `Proof: ${e}`));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return { valid: errors.length === 0, errors };
|
|
29
|
+
}
|
|
30
|
+
function verifyVtxoProof(vtxo, proof) {
|
|
31
|
+
const errors = [];
|
|
32
|
+
const expectedLeaf = (0, commitment_js_1.computeVtxoLeaf)(vtxo);
|
|
33
|
+
if (proof.leaf !== expectedLeaf) {
|
|
34
|
+
errors.push(`Proof leaf mismatch: expected ${expectedLeaf}, got ${proof.leaf}`);
|
|
35
|
+
}
|
|
36
|
+
if (proof.siblings.length > 0) {
|
|
37
|
+
const proofNodes = proof.siblings.map((sibling, i) => ({
|
|
38
|
+
sibling,
|
|
39
|
+
position: proof.positions[i],
|
|
40
|
+
}));
|
|
41
|
+
const valid = (0, commitment_js_1.verifyMerkleProof)(proof.leaf, proofNodes, proof.root);
|
|
42
|
+
if (!valid) {
|
|
43
|
+
errors.push('Merkle proof verification failed');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
if (proof.leaf !== proof.root) {
|
|
48
|
+
errors.push('Single-leaf proof: leaf must equal root');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { valid: errors.length === 0, errors };
|
|
52
|
+
}
|
|
53
|
+
function verifyVtxoTransfer(input, output, transfer) {
|
|
54
|
+
const errors = [];
|
|
55
|
+
if (transfer.inputId !== input.vtxoId) {
|
|
56
|
+
errors.push(`Transfer inputId ${transfer.inputId} does not match input VTXO ${input.vtxoId}`);
|
|
57
|
+
}
|
|
58
|
+
if (transfer.outputId !== output.vtxoId) {
|
|
59
|
+
errors.push(`Transfer outputId ${transfer.outputId} does not match output VTXO ${output.vtxoId}`);
|
|
60
|
+
}
|
|
61
|
+
if (input.poolId !== output.poolId) {
|
|
62
|
+
errors.push(`Pool mismatch: input ${input.poolId}, output ${output.poolId}`);
|
|
63
|
+
}
|
|
64
|
+
if (input.tokenId !== output.tokenId) {
|
|
65
|
+
errors.push(`Token mismatch: input ${input.tokenId}, output ${output.tokenId}`);
|
|
66
|
+
}
|
|
67
|
+
if (transfer.amount > input.amount) {
|
|
68
|
+
errors.push(`Transfer amount ${transfer.amount} exceeds input amount ${input.amount}`);
|
|
69
|
+
}
|
|
70
|
+
return { valid: errors.length === 0, errors };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Verifies amount conservation across a set of inputs and outputs.
|
|
74
|
+
*
|
|
75
|
+
* Default mode (`'lte'`): `sum(outputs) <= sum(inputs)` — allows exits, fees, and burn flows.
|
|
76
|
+
* Strict mode (`'strict'`): `sum(outputs) === sum(inputs)` — required for transfer, split, merge.
|
|
77
|
+
*
|
|
78
|
+
* Always requires: same `poolId` and `tokenId` across all inputs and outputs.
|
|
79
|
+
*/
|
|
80
|
+
function verifyConservation(params) {
|
|
81
|
+
const errors = [];
|
|
82
|
+
const { inputs, outputs, mode = 'lte' } = params;
|
|
83
|
+
if (inputs.length === 0 || outputs.length === 0) {
|
|
84
|
+
errors.push('Conservation check requires at least one input and one output');
|
|
85
|
+
return { valid: false, errors };
|
|
86
|
+
}
|
|
87
|
+
const poolId = inputs[0].poolId;
|
|
88
|
+
const tokenId = inputs[0].tokenId;
|
|
89
|
+
for (const vtxo of inputs) {
|
|
90
|
+
if (vtxo.poolId !== poolId) {
|
|
91
|
+
errors.push(`Input pool mismatch: ${vtxo.poolId} vs ${poolId}`);
|
|
92
|
+
}
|
|
93
|
+
if (vtxo.tokenId !== tokenId) {
|
|
94
|
+
errors.push(`Input token mismatch: ${vtxo.tokenId} vs ${tokenId}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
for (const vtxo of outputs) {
|
|
98
|
+
if (vtxo.poolId !== poolId) {
|
|
99
|
+
errors.push(`Output pool mismatch: ${vtxo.poolId} vs ${poolId}`);
|
|
100
|
+
}
|
|
101
|
+
if (vtxo.tokenId !== tokenId) {
|
|
102
|
+
errors.push(`Output token mismatch: ${vtxo.tokenId} vs ${tokenId}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (errors.length > 0) {
|
|
106
|
+
return { valid: false, errors };
|
|
107
|
+
}
|
|
108
|
+
const inputSum = inputs.reduce((sum, v) => sum + v.amount, BigInt(0));
|
|
109
|
+
const outputSum = outputs.reduce((sum, v) => sum + v.amount, BigInt(0));
|
|
110
|
+
if (mode === 'strict') {
|
|
111
|
+
if (outputSum !== inputSum) {
|
|
112
|
+
errors.push(`Conservation violation (strict): input sum ${inputSum} !== output sum ${outputSum}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
if (outputSum > inputSum) {
|
|
117
|
+
errors.push(`Conservation violation: output sum ${outputSum} exceeds input sum ${inputSum}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return { valid: errors.length === 0, errors };
|
|
121
|
+
}
|
package/dist/vtxo.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { OmniaVtxoPool, OmniaVtxo, MintVtxoParams, MintResult } from './types.js';
|
|
2
|
+
export declare function isVtxoActive(vtxo: OmniaVtxo): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Mints a new VTXO against a pool.
|
|
5
|
+
* @param pool - The pool to mint from.
|
|
6
|
+
* @param params - Mint parameters (owner, amount, nonce).
|
|
7
|
+
* @param now - Timestamp in ms. Defaults to Date.now() if omitted — pass explicitly for determinism.
|
|
8
|
+
*/
|
|
9
|
+
export declare function mintVtxo(pool: OmniaVtxoPool, params: MintVtxoParams, now?: number): MintResult;
|
|
10
|
+
/**
|
|
11
|
+
* Marks an active VTXO as spent.
|
|
12
|
+
* @param now - Timestamp in ms. Defaults to Date.now() if omitted — pass explicitly for determinism.
|
|
13
|
+
*/
|
|
14
|
+
export declare function markVtxoSpent(vtxo: OmniaVtxo, now?: number): OmniaVtxo;
|
package/dist/vtxo.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isVtxoActive = isVtxoActive;
|
|
4
|
+
exports.mintVtxo = mintVtxo;
|
|
5
|
+
exports.markVtxoSpent = markVtxoSpent;
|
|
6
|
+
const errors_js_1 = require("./errors.js");
|
|
7
|
+
const constants_js_1 = require("./constants.js");
|
|
8
|
+
const commitment_js_1 = require("./commitment.js");
|
|
9
|
+
const pool_js_1 = require("./pool.js");
|
|
10
|
+
function isVtxoActive(vtxo) {
|
|
11
|
+
return vtxo.status === 'active';
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Mints a new VTXO against a pool.
|
|
15
|
+
* @param pool - The pool to mint from.
|
|
16
|
+
* @param params - Mint parameters (owner, amount, nonce).
|
|
17
|
+
* @param now - Timestamp in ms. Defaults to Date.now() if omitted — pass explicitly for determinism.
|
|
18
|
+
*/
|
|
19
|
+
function mintVtxo(pool, params, now) {
|
|
20
|
+
const ts = now !== undefined ? now : Date.now();
|
|
21
|
+
(0, pool_js_1.assertPoolCanMint)(pool, params.amount);
|
|
22
|
+
const vtxoId = (0, commitment_js_1.computeVtxoId)({
|
|
23
|
+
poolId: pool.poolId,
|
|
24
|
+
owner: params.owner,
|
|
25
|
+
amount: params.amount,
|
|
26
|
+
tokenId: pool.tokenId,
|
|
27
|
+
nonce: params.nonce,
|
|
28
|
+
});
|
|
29
|
+
const vtxo = {
|
|
30
|
+
vtxoId,
|
|
31
|
+
poolId: pool.poolId,
|
|
32
|
+
owner: params.owner,
|
|
33
|
+
amount: params.amount,
|
|
34
|
+
tokenId: pool.tokenId,
|
|
35
|
+
status: 'active',
|
|
36
|
+
epoch: pool.epoch,
|
|
37
|
+
proof: {
|
|
38
|
+
leaf: '',
|
|
39
|
+
root: '',
|
|
40
|
+
siblings: [],
|
|
41
|
+
positions: [],
|
|
42
|
+
epoch: pool.epoch,
|
|
43
|
+
batchId: constants_js_1.MOCK_BATCH_ID,
|
|
44
|
+
},
|
|
45
|
+
history: [
|
|
46
|
+
{
|
|
47
|
+
op: 'mint',
|
|
48
|
+
at: ts,
|
|
49
|
+
to: params.owner,
|
|
50
|
+
meta: { poolId: pool.poolId },
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
createdAt: ts,
|
|
54
|
+
updatedAt: ts,
|
|
55
|
+
expiresAt: params.expiresAt,
|
|
56
|
+
};
|
|
57
|
+
const { root, proofs } = (0, commitment_js_1.buildVtxoProofSet)([vtxo]);
|
|
58
|
+
const proof = proofs[vtxoId];
|
|
59
|
+
const mintedVtxo = { ...vtxo, proof };
|
|
60
|
+
const updatedPool = {
|
|
61
|
+
...pool,
|
|
62
|
+
availableCapacity: pool.availableCapacity - params.amount,
|
|
63
|
+
commitmentRoot: root,
|
|
64
|
+
};
|
|
65
|
+
const receiptId = (0, commitment_js_1.computeReceiptId)(pool.poolId, 'mint', [], [vtxoId], ts);
|
|
66
|
+
const receipt = {
|
|
67
|
+
receiptId,
|
|
68
|
+
poolId: pool.poolId,
|
|
69
|
+
op: 'mint',
|
|
70
|
+
inputIds: [],
|
|
71
|
+
outputIds: [vtxoId],
|
|
72
|
+
epoch: pool.epoch,
|
|
73
|
+
at: ts,
|
|
74
|
+
signature: constants_js_1.MOCK_OPERATOR_SIGNATURE,
|
|
75
|
+
};
|
|
76
|
+
return { pool: updatedPool, vtxo: mintedVtxo, receipt };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Marks an active VTXO as spent.
|
|
80
|
+
* @param now - Timestamp in ms. Defaults to Date.now() if omitted — pass explicitly for determinism.
|
|
81
|
+
*/
|
|
82
|
+
function markVtxoSpent(vtxo, now) {
|
|
83
|
+
const ts = now !== undefined ? now : Date.now();
|
|
84
|
+
if (!isVtxoActive(vtxo)) {
|
|
85
|
+
throw new errors_js_1.VtxoStatusError(`Cannot mark VTXO ${vtxo.vtxoId} as spent: status is '${vtxo.status}'`, vtxo.status);
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
...vtxo,
|
|
89
|
+
status: 'spent',
|
|
90
|
+
updatedAt: ts,
|
|
91
|
+
history: [
|
|
92
|
+
...vtxo.history,
|
|
93
|
+
{ op: 'spent', at: ts },
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
}
|