@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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Totem SDK Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# `@totemsdk/omnia-vtxo`
|
|
2
|
+
|
|
3
|
+
Virtual UTXO (vTXO) / payment-pool claim layer for the Totem Edge ecosystem.
|
|
4
|
+
|
|
5
|
+
## What is Omnia VTXO?
|
|
6
|
+
|
|
7
|
+
`@totemsdk/omnia-vtxo` is a **cash-like off-chain balance primitive** that gives Totem Edge applications a way to hold, transfer, split, merge, and exit token balances without requiring a live Minima node for every operation. Each VTXO (Virtual Transaction Output) is a claim on a pool of on-chain capacity, backed by a deterministic Merkle commitment tree.
|
|
8
|
+
|
|
9
|
+
Think of it as a bearer note inside a shared liquidity pool. The pool operator holds the on-chain UTXO; the holder holds the VTXO proof.
|
|
10
|
+
|
|
11
|
+
### How it differs from Statechains
|
|
12
|
+
|
|
13
|
+
| | Statechain | Omnia VTXO |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| Ownership transfer | Blind SE co-signature | Pure functional, no live signing needed |
|
|
16
|
+
| Privacy model | Blind Mercury SE | Local commitment root |
|
|
17
|
+
| Splitting | Not supported | Supported natively |
|
|
18
|
+
| Merging | Not supported | Supported natively |
|
|
19
|
+
| Exit model | On-chain UTXO handoff | Pool epoch settlement |
|
|
20
|
+
| Use case | Privacy UTXO custody | Merchant receipts, credits, pool liquidity |
|
|
21
|
+
|
|
22
|
+
### Relation to other `@totemsdk` packages
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
@totemsdk/omnia ← L2 channel layer (eltoo state machine)
|
|
26
|
+
@totemsdk/omnia-factory ← N-of-N funding factory
|
|
27
|
+
@totemsdk/omnia-vtxo ← Virtual UTXO claim layer (this package)
|
|
28
|
+
@totemsdk/statechain ← Mercury off-chain ownership records
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pnpm add @totemsdk/omnia-vtxo @noble/hashes
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Import
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import {
|
|
41
|
+
createPool,
|
|
42
|
+
mintVtxo,
|
|
43
|
+
transferVtxo,
|
|
44
|
+
splitVtxo,
|
|
45
|
+
mergeVtxos,
|
|
46
|
+
refreshVtxo,
|
|
47
|
+
createExitDraft,
|
|
48
|
+
verifyVtxo,
|
|
49
|
+
verifyConservation,
|
|
50
|
+
MemoryOmniaVtxoStore,
|
|
51
|
+
serializeVtxo,
|
|
52
|
+
deserializeVtxo,
|
|
53
|
+
} from '@totemsdk/omnia-vtxo';
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Full Lifecycle Example
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import {
|
|
60
|
+
createPool,
|
|
61
|
+
mintVtxo,
|
|
62
|
+
transferVtxo,
|
|
63
|
+
splitVtxo,
|
|
64
|
+
mergeVtxos,
|
|
65
|
+
refreshVtxo,
|
|
66
|
+
createExitDraft,
|
|
67
|
+
markExiting,
|
|
68
|
+
markExited,
|
|
69
|
+
verifyVtxo,
|
|
70
|
+
verifyConservation,
|
|
71
|
+
MemoryOmniaVtxoStore,
|
|
72
|
+
} from '@totemsdk/omnia-vtxo';
|
|
73
|
+
|
|
74
|
+
const NOW = Date.now();
|
|
75
|
+
|
|
76
|
+
// 1. Create a pool (deterministic — poolId derived from operator + tokenId + nonce)
|
|
77
|
+
const pool = createPool({
|
|
78
|
+
operator: 'MxOperatorAddress',
|
|
79
|
+
tokenId: '0x00', // Minima native token
|
|
80
|
+
totalCapacity: BigInt(1_000_000_000),
|
|
81
|
+
nonce: 'pool-genesis-v1',
|
|
82
|
+
}, NOW);
|
|
83
|
+
|
|
84
|
+
// 2. Mint a VTXO (reduces pool.availableCapacity)
|
|
85
|
+
const { pool: p1, vtxo: note, receipt: mintReceipt } = mintVtxo(
|
|
86
|
+
pool,
|
|
87
|
+
{ owner: 'MxAlice', amount: BigInt(100_000), nonce: 'note-1' },
|
|
88
|
+
NOW,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// 3. Transfer the note to Bob (full transfer: note → transferred, bobNote → active)
|
|
92
|
+
const { input: spentNote, output: bobNote, transfer } = transferVtxo(
|
|
93
|
+
note,
|
|
94
|
+
{ recipient: 'MxBob', amount: BigInt(100_000), nonce: 'tx-1' },
|
|
95
|
+
NOW + 1,
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// 4. Split Bob's note into two pieces
|
|
99
|
+
const { input: splitInput, outputs: [piece1, piece2] } = splitVtxo(
|
|
100
|
+
bobNote,
|
|
101
|
+
{ amounts: [BigInt(60_000), BigInt(40_000)], nonces: ['sp-1', 'sp-2'] },
|
|
102
|
+
NOW + 2,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// 5. Merge the two pieces back together
|
|
106
|
+
const { output: mergedNote } = mergeVtxos(
|
|
107
|
+
[piece1, piece2],
|
|
108
|
+
{ nonce: 'mg-1', owner: 'MxBob' },
|
|
109
|
+
NOW + 3,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// 6. Refresh the note to a new epoch (re-anchor against pool epoch)
|
|
113
|
+
const { old: staleNote, refreshed: freshNote } = refreshVtxo(
|
|
114
|
+
mergedNote,
|
|
115
|
+
{ newEpoch: 1, nonce: 'ref-1' },
|
|
116
|
+
NOW + 4,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// 7. Create a mock exit draft
|
|
120
|
+
const { draft, receipt: exitReceipt } = createExitDraft(freshNote, NOW + 5);
|
|
121
|
+
console.log('Exit draft type:', draft.draftType); // 'mock-exit'
|
|
122
|
+
|
|
123
|
+
// 8. Verify proofs and conservation
|
|
124
|
+
const vtxoCheck = verifyVtxo(freshNote);
|
|
125
|
+
console.log('VTXO valid:', vtxoCheck.valid);
|
|
126
|
+
|
|
127
|
+
const conservation = verifyConservation({
|
|
128
|
+
inputs: [note],
|
|
129
|
+
outputs: [spentNote], // full transfer: input sum must equal output sum
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// 9. Persist with MemoryOmniaVtxoStore
|
|
133
|
+
const store = new MemoryOmniaVtxoStore();
|
|
134
|
+
await store.savePool(p1);
|
|
135
|
+
await store.saveVtxo(freshNote);
|
|
136
|
+
const loaded = await store.getVtxo(freshNote.vtxoId);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## API Reference
|
|
140
|
+
|
|
141
|
+
### Pool
|
|
142
|
+
|
|
143
|
+
| Function | Description |
|
|
144
|
+
|---|---|
|
|
145
|
+
| `createPool(params, now)` | Creates a new VTXO pool. `poolId` is deterministic from `operator + tokenId + nonce`. Never calls `Date.now()` internally. |
|
|
146
|
+
| `assertPoolCanMint(pool, amount)` | Validates capacity and policy. Throws `VtxoPoolCapacityError` or `VtxoPolicyError`. |
|
|
147
|
+
| `updatePoolRoot(pool, root)` | Returns a new pool with an updated commitment root (immutable). |
|
|
148
|
+
| `advancePoolEpoch(pool, newEpoch)` | Advances epoch; throws if `newEpoch <= pool.epoch`. |
|
|
149
|
+
|
|
150
|
+
### VTXO Lifecycle
|
|
151
|
+
|
|
152
|
+
| Function | Status transition | Notes |
|
|
153
|
+
|---|---|---|
|
|
154
|
+
| `mintVtxo(pool, params, now)` | — → `active` | Reduces `pool.availableCapacity`. |
|
|
155
|
+
| `transferVtxo(vtxo, params, now)` | `active` → `transferred`/`split` | Full: input `transferred`, output `active`. Partial: input `split`, output+change `active`. |
|
|
156
|
+
| `splitVtxo(vtxo, params, now)` | `active` → `split` | Outputs `active`. Sum must equal input amount. |
|
|
157
|
+
| `mergeVtxos(vtxos, params, now)` | `active[]` → `merged` | Output `active`. Rejects duplicate IDs. Same pool+token required. |
|
|
158
|
+
| `refreshVtxo(vtxo, params, now)` | `active` → `refreshed` | New VTXO `active` with higher epoch. |
|
|
159
|
+
| `markExiting(vtxo, now)` | `active` → `exiting` | Initiates unilateral exit. |
|
|
160
|
+
| `markExited(vtxo, now)` | `exiting` → `exited` | Finalises exit. |
|
|
161
|
+
| `createExitDraft(vtxo, now)` | — | Returns `draftType: 'mock-exit'` and a receipt. |
|
|
162
|
+
| `markVtxoSpent(vtxo, now)` | `active` → `spent` | Direct spend (used by operator settlement). |
|
|
163
|
+
|
|
164
|
+
### Verification & Conservation
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
verifyVtxo(vtxo) // validate fields + proof
|
|
168
|
+
verifyVtxoProof(vtxo, proof) // verify Merkle path
|
|
169
|
+
verifyVtxoTransfer(input, output, tx) // validate transfer struct
|
|
170
|
+
verifyConservation({ inputs, outputs }) // sum(inputs) must equal sum(outputs)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Serialization (BigInt-safe)
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
serializeVtxo(vtxo) // → JSON string (BigInt serialized as "__bigint__:<value>")
|
|
177
|
+
deserializeVtxo(json) // → OmniaVtxo (BigInt restored)
|
|
178
|
+
serializePool(pool) // → JSON string
|
|
179
|
+
deserializePool(json) // → OmniaVtxoPool
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Store Interface
|
|
183
|
+
|
|
184
|
+
`OmniaVtxoStore` is a simple async interface:
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
interface OmniaVtxoStore {
|
|
188
|
+
savePool(pool): Promise<void>;
|
|
189
|
+
getPool(poolId): Promise<OmniaVtxoPool | undefined>;
|
|
190
|
+
saveVtxo(vtxo): Promise<void>;
|
|
191
|
+
getVtxo(vtxoId): Promise<OmniaVtxo | undefined>;
|
|
192
|
+
listVtxos(poolId?): Promise<OmniaVtxo[]>;
|
|
193
|
+
markVtxoSpent(vtxoId, now?): Promise<void>;
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`MemoryOmniaVtxoStore` ships as the in-memory MVP implementation.
|
|
198
|
+
|
|
199
|
+
### Policy Helpers
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
buildVtxoTransferIntent(vtxo, recipient, amount, nonce, changeNonce?)
|
|
203
|
+
buildVtxoExitIntent(vtxo)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## VtxoStatus State Machine
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
┌──────── mint ────────┐
|
|
210
|
+
▼ │
|
|
211
|
+
active ──transfer──► transferred
|
|
212
|
+
│
|
|
213
|
+
├─ partial-transfer ──► split (input)
|
|
214
|
+
│ │
|
|
215
|
+
├─ split ────────────► split (input)
|
|
216
|
+
│
|
|
217
|
+
├─ merge ────────────► merged (inputs)
|
|
218
|
+
│
|
|
219
|
+
├─ refresh ──────────► refreshed (old)
|
|
220
|
+
│
|
|
221
|
+
├─ exit_initiated ───► exiting
|
|
222
|
+
│ │
|
|
223
|
+
│ exited
|
|
224
|
+
└─ spent ──────────── spent
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Error Hierarchy
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
OmniaVtxoError
|
|
231
|
+
├── VtxoAmountError — invalid amounts (zero, overflow, sum mismatch)
|
|
232
|
+
├── VtxoStatusError — wrong status for the requested operation
|
|
233
|
+
├── VtxoOwnershipError — owner mismatch
|
|
234
|
+
├── VtxoProofError — Merkle proof verification failure
|
|
235
|
+
├── VtxoPoolCapacityError — insufficient pool capacity
|
|
236
|
+
├── VtxoPolicyError — policy violation (min/max amounts, epoch ordering)
|
|
237
|
+
├── VtxoMergeError — merge precondition failure (cross-pool, duplicates)
|
|
238
|
+
├── VtxoSplitError — split precondition failure
|
|
239
|
+
└── VtxoExitError — exit precondition failure
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Security Notes
|
|
243
|
+
|
|
244
|
+
> **This is an MVP SDK — not a production Ark implementation.**
|
|
245
|
+
|
|
246
|
+
The current implementation provides deterministic, locally-verifiable commitment proofs, but several critical components are stubs:
|
|
247
|
+
|
|
248
|
+
- **Operator receipts** are signed with `MOCK_OPERATOR_SIGNATURE`. In production, the operator signs over the commitment root using a real WOTS or Schnorr key, and clients must verify against the operator's published public key.
|
|
249
|
+
- **Commitment roots** are single-batch local roots, not pool-wide epoch roots. A full deployment requires the operator to broadcast a pool-wide commitment root across all live VTXOs each epoch, giving every holder a verifiable place in the pool's state tree.
|
|
250
|
+
- **Exit scripts** use `draftType: 'mock-exit'`. Real exits require audited KISSVM covenant exit scripts, a watchtower monitoring the on-chain dispute window, and Omnia factory settlement logic.
|
|
251
|
+
- **No watchtower / dispute monitoring.** Operator equivocation (signing two conflicting pool roots) is not detected. A production system needs an independent watchtower service.
|
|
252
|
+
- **No replay protection beyond nonce uniqueness.** Nonces must be globally unique per pool. Implementations must enforce this.
|
|
253
|
+
|
|
254
|
+
## Future Roadmap
|
|
255
|
+
|
|
256
|
+
- **Real Omnia factory backing** — Pool funds held in an N-of-N Omnia factory UTXO, with batch settlement on exit.
|
|
257
|
+
- **Batch refresh rounds** — Operator posts a new commitment root; all live VTXOs are refreshed in a single Minima transaction.
|
|
258
|
+
- **Operator signing** — Real WOTS receipts replacing `MOCK_OPERATOR_SIGNATURE`, with client-side signature verification.
|
|
259
|
+
- **Wallet integration** — Direct integration with Totem Wallet's coin selection and WOTS signing infrastructure.
|
|
260
|
+
- **Connect API methods** — `totem_mintVtxo`, `totem_transferVtxo`, `totem_listVtxos` exposed via the TOTEM_CONNECT dApp API.
|
|
261
|
+
- **Persistence adapters** — PostgreSQL and IndexedDB adapters for `OmniaVtxoStore`.
|
|
262
|
+
- **KISSVM exit script** — Audited on-chain exit covenant for unilateral exit with timelock enforcement.
|
|
263
|
+
|
|
264
|
+
## Canary Scenario
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
// Create pool → mint note → transfer → split → merge → refresh → exit → verify
|
|
268
|
+
|
|
269
|
+
const pool = createPool({ operator, tokenId, totalCapacity, nonce }, NOW);
|
|
270
|
+
const { pool: p1, vtxo: note } = mintVtxo(pool, { owner, amount, nonce: 'n1' }, NOW);
|
|
271
|
+
const { output: received } = transferVtxo(note, { recipient: 'MxBob', amount, nonce: 'n2' }, NOW + 1);
|
|
272
|
+
const { outputs: [a, b] } = splitVtxo(received, { amounts: [amount / 2n, amount / 2n], nonces: ['n3', 'n4'] }, NOW + 2);
|
|
273
|
+
const { output: merged } = mergeVtxos([a, b], { nonce: 'n5', owner: 'MxBob' }, NOW + 3);
|
|
274
|
+
const { refreshed } = refreshVtxo(merged, { newEpoch: 1, nonce: 'n6' }, NOW + 4);
|
|
275
|
+
const { draft } = createExitDraft(refreshed, NOW + 5);
|
|
276
|
+
|
|
277
|
+
const check = verifyVtxo(refreshed);
|
|
278
|
+
const conservation = verifyConservation({ inputs: [note], outputs: [received] });
|
|
279
|
+
|
|
280
|
+
console.assert(check.valid, 'VTXO proof must be valid');
|
|
281
|
+
console.assert(conservation.valid, 'Conservation must hold');
|
|
282
|
+
console.assert(draft.draftType === 'mock-exit', 'Exit draft type must be mock-exit');
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Runtime Compatibility
|
|
286
|
+
|
|
287
|
+
This package is **runtime-neutral** and compatible with Bare/Pear-style Totem Edge deployments. It does not import `node:crypto`, `fs`, `path`, `net`, `http`, `child_process`, DOM APIs, `window`, `document`, or `localStorage`. All deterministic functions accept injected `now` and `nonce` parameters — no hidden `Date.now()` or `randomUUID()` calls inside pure logic.
|
|
288
|
+
|
|
289
|
+
The only runtime dependency is `@noble/hashes` (SHA3-256), which is itself runtime-neutral and browser/Bare-compatible.
|
|
290
|
+
|
|
291
|
+
## License
|
|
292
|
+
|
|
293
|
+
MIT
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { OmniaVtxo, VtxoId, VtxoProof } from './types.js';
|
|
2
|
+
export declare function computeVtxoLeaf(vtxo: OmniaVtxo): string;
|
|
3
|
+
export interface ComputeVtxoIdParams {
|
|
4
|
+
poolId: string;
|
|
5
|
+
owner: string;
|
|
6
|
+
amount: bigint;
|
|
7
|
+
tokenId: string;
|
|
8
|
+
nonce: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function computeVtxoId(params: ComputeVtxoIdParams): string;
|
|
11
|
+
export interface ComputePoolIdParams {
|
|
12
|
+
operator: string;
|
|
13
|
+
tokenId: string;
|
|
14
|
+
nonce: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function computePoolId(params: ComputePoolIdParams): string;
|
|
17
|
+
export declare function computeCommitmentRoot(leaves: string[]): string;
|
|
18
|
+
export interface MerkleProofNode {
|
|
19
|
+
sibling: string;
|
|
20
|
+
position: 'left' | 'right';
|
|
21
|
+
}
|
|
22
|
+
export declare function verifyMerkleProof(leaf: string, proof: MerkleProofNode[], root: string): boolean;
|
|
23
|
+
export interface VtxoProofSet {
|
|
24
|
+
root: string;
|
|
25
|
+
proofs: Record<VtxoId, VtxoProof>;
|
|
26
|
+
}
|
|
27
|
+
export declare function buildVtxoProofSet(vtxos: OmniaVtxo[]): VtxoProofSet;
|
|
28
|
+
export declare function computeReceiptId(poolId: string, op: string, inputIds: string[], outputIds: string[], at: number): string;
|
|
@@ -0,0 +1,153 @@
|
|
|
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 sha3_1 = require("@noble/hashes/sha3");
|
|
11
|
+
const utils_1 = require("@noble/hashes/utils");
|
|
12
|
+
const constants_js_1 = require("./constants.js");
|
|
13
|
+
function sha3hex(input) {
|
|
14
|
+
const bytes = new TextEncoder().encode(input);
|
|
15
|
+
return (0, utils_1.bytesToHex)((0, sha3_1.sha3_256)(bytes));
|
|
16
|
+
}
|
|
17
|
+
function hashPair(a, b) {
|
|
18
|
+
const [left, right] = a <= b ? [a, b] : [b, a];
|
|
19
|
+
const combined = left + right;
|
|
20
|
+
const bytes = (0, utils_1.hexToBytes)(combined);
|
|
21
|
+
return (0, utils_1.bytesToHex)((0, sha3_1.sha3_256)(bytes));
|
|
22
|
+
}
|
|
23
|
+
function computeVtxoLeaf(vtxo) {
|
|
24
|
+
const input = [
|
|
25
|
+
vtxo.vtxoId,
|
|
26
|
+
vtxo.owner,
|
|
27
|
+
vtxo.amount.toString(),
|
|
28
|
+
vtxo.tokenId,
|
|
29
|
+
vtxo.epoch.toString(),
|
|
30
|
+
vtxo.poolId,
|
|
31
|
+
].join(':');
|
|
32
|
+
return sha3hex(input);
|
|
33
|
+
}
|
|
34
|
+
function computeVtxoId(params) {
|
|
35
|
+
const input = [
|
|
36
|
+
'vtxo',
|
|
37
|
+
params.poolId,
|
|
38
|
+
params.owner,
|
|
39
|
+
params.amount.toString(),
|
|
40
|
+
params.tokenId,
|
|
41
|
+
params.nonce,
|
|
42
|
+
].join(':');
|
|
43
|
+
return sha3hex(input);
|
|
44
|
+
}
|
|
45
|
+
function computePoolId(params) {
|
|
46
|
+
const input = [
|
|
47
|
+
'pool',
|
|
48
|
+
params.operator,
|
|
49
|
+
params.tokenId,
|
|
50
|
+
params.nonce,
|
|
51
|
+
].join(':');
|
|
52
|
+
return sha3hex(input);
|
|
53
|
+
}
|
|
54
|
+
function nextPowerOfTwo(n) {
|
|
55
|
+
if (n <= 1)
|
|
56
|
+
return 1;
|
|
57
|
+
let p = 1;
|
|
58
|
+
while (p < n)
|
|
59
|
+
p <<= 1;
|
|
60
|
+
return p;
|
|
61
|
+
}
|
|
62
|
+
function computeCommitmentRoot(leaves) {
|
|
63
|
+
if (leaves.length === 0)
|
|
64
|
+
return constants_js_1.EMPTY_LEAF;
|
|
65
|
+
const size = nextPowerOfTwo(leaves.length);
|
|
66
|
+
const level = new Array(size).fill(constants_js_1.EMPTY_LEAF);
|
|
67
|
+
for (let i = 0; i < leaves.length; i++) {
|
|
68
|
+
level[i] = leaves[i];
|
|
69
|
+
}
|
|
70
|
+
let current = level;
|
|
71
|
+
while (current.length > 1) {
|
|
72
|
+
const next = [];
|
|
73
|
+
for (let i = 0; i < current.length; i += 2) {
|
|
74
|
+
next.push(hashPair(current[i], current[i + 1]));
|
|
75
|
+
}
|
|
76
|
+
current = next;
|
|
77
|
+
}
|
|
78
|
+
return current[0];
|
|
79
|
+
}
|
|
80
|
+
function verifyMerkleProof(leaf, proof, root) {
|
|
81
|
+
let current = leaf;
|
|
82
|
+
for (const node of proof) {
|
|
83
|
+
if (node.position === 'left') {
|
|
84
|
+
current = hashPair(node.sibling, current);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
current = hashPair(current, node.sibling);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return current === root;
|
|
91
|
+
}
|
|
92
|
+
function buildVtxoProofSet(vtxos) {
|
|
93
|
+
if (vtxos.length === 0) {
|
|
94
|
+
return { root: constants_js_1.EMPTY_LEAF, proofs: {} };
|
|
95
|
+
}
|
|
96
|
+
const sorted = [...vtxos].sort((a, b) => a.vtxoId < b.vtxoId ? -1 : a.vtxoId > b.vtxoId ? 1 : 0);
|
|
97
|
+
const leafMap = new Map();
|
|
98
|
+
const leaves = sorted.map(v => {
|
|
99
|
+
const leaf = computeVtxoLeaf(v);
|
|
100
|
+
leafMap.set(v.vtxoId, leaf);
|
|
101
|
+
return leaf;
|
|
102
|
+
});
|
|
103
|
+
const size = nextPowerOfTwo(leaves.length);
|
|
104
|
+
const paddedLeaves = [...leaves];
|
|
105
|
+
while (paddedLeaves.length < size)
|
|
106
|
+
paddedLeaves.push(constants_js_1.EMPTY_LEAF);
|
|
107
|
+
const levels = [paddedLeaves];
|
|
108
|
+
let current = paddedLeaves;
|
|
109
|
+
while (current.length > 1) {
|
|
110
|
+
const next = [];
|
|
111
|
+
for (let i = 0; i < current.length; i += 2) {
|
|
112
|
+
next.push(hashPair(current[i], current[i + 1]));
|
|
113
|
+
}
|
|
114
|
+
levels.push(next);
|
|
115
|
+
current = next;
|
|
116
|
+
}
|
|
117
|
+
const root = current[0];
|
|
118
|
+
const proofs = {};
|
|
119
|
+
sorted.forEach((vtxo, idx) => {
|
|
120
|
+
const siblings = [];
|
|
121
|
+
const positions = [];
|
|
122
|
+
let pos = idx;
|
|
123
|
+
for (let lvl = 0; lvl < levels.length - 1; lvl++) {
|
|
124
|
+
const isLeft = pos % 2 === 0;
|
|
125
|
+
const siblingIdx = isLeft ? pos + 1 : pos - 1;
|
|
126
|
+
const sibling = levels[lvl][siblingIdx] ?? constants_js_1.EMPTY_LEAF;
|
|
127
|
+
siblings.push(sibling);
|
|
128
|
+
positions.push(isLeft ? 'right' : 'left');
|
|
129
|
+
pos = Math.floor(pos / 2);
|
|
130
|
+
}
|
|
131
|
+
const leaf = leafMap.get(vtxo.vtxoId);
|
|
132
|
+
proofs[vtxo.vtxoId] = {
|
|
133
|
+
leaf,
|
|
134
|
+
root,
|
|
135
|
+
siblings,
|
|
136
|
+
positions,
|
|
137
|
+
epoch: vtxo.epoch,
|
|
138
|
+
batchId: constants_js_1.MOCK_BATCH_ID,
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
return { root, proofs };
|
|
142
|
+
}
|
|
143
|
+
function computeReceiptId(poolId, op, inputIds, outputIds, at) {
|
|
144
|
+
const input = [
|
|
145
|
+
'receipt',
|
|
146
|
+
poolId,
|
|
147
|
+
op,
|
|
148
|
+
inputIds.sort().join(','),
|
|
149
|
+
outputIds.sort().join(','),
|
|
150
|
+
at.toString(),
|
|
151
|
+
].join(':');
|
|
152
|
+
return sha3hex(input);
|
|
153
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const EMPTY_LEAF = "0000000000000000000000000000000000000000000000000000000000000000";
|
|
2
|
+
export declare const EMPTY_TREE_ROOT = "0000000000000000000000000000000000000000000000000000000000000000";
|
|
3
|
+
export declare const EPOCH_ZERO = 0;
|
|
4
|
+
export declare const DEFAULT_POLICY: {
|
|
5
|
+
readonly minAmount: bigint;
|
|
6
|
+
readonly maxAmount: bigint;
|
|
7
|
+
readonly maxMergeInputs: 8;
|
|
8
|
+
readonly maxSplitOutputs: 8;
|
|
9
|
+
readonly exitTimelockSeconds: 86400;
|
|
10
|
+
};
|
|
11
|
+
export declare const MOCK_OPERATOR_SIGNATURE = "mock-operator-sig-v0";
|
|
12
|
+
export declare const MOCK_BATCH_ID = "batch-local-mvp";
|
|
@@ -0,0 +1,15 @@
|
|
|
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 = {
|
|
8
|
+
minAmount: BigInt(1),
|
|
9
|
+
maxAmount: BigInt(1000000000000000),
|
|
10
|
+
maxMergeInputs: 8,
|
|
11
|
+
maxSplitOutputs: 8,
|
|
12
|
+
exitTimelockSeconds: 86400,
|
|
13
|
+
};
|
|
14
|
+
exports.MOCK_OPERATOR_SIGNATURE = 'mock-operator-sig-v0';
|
|
15
|
+
exports.MOCK_BATCH_ID = 'batch-local-mvp';
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export declare class OmniaVtxoError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
export declare class VtxoAmountError extends OmniaVtxoError {
|
|
5
|
+
constructor(message: string);
|
|
6
|
+
}
|
|
7
|
+
export declare class VtxoStatusError extends OmniaVtxoError {
|
|
8
|
+
readonly currentStatus: string;
|
|
9
|
+
constructor(message: string, currentStatus: string);
|
|
10
|
+
}
|
|
11
|
+
export declare class VtxoOwnershipError extends OmniaVtxoError {
|
|
12
|
+
constructor(message: string);
|
|
13
|
+
}
|
|
14
|
+
export declare class VtxoProofError extends OmniaVtxoError {
|
|
15
|
+
constructor(message: string);
|
|
16
|
+
}
|
|
17
|
+
export declare class VtxoPoolCapacityError extends OmniaVtxoError {
|
|
18
|
+
readonly requested: bigint;
|
|
19
|
+
readonly available: bigint;
|
|
20
|
+
constructor(message: string, requested: bigint, available: bigint);
|
|
21
|
+
}
|
|
22
|
+
export declare class VtxoPolicyError extends OmniaVtxoError {
|
|
23
|
+
constructor(message: string);
|
|
24
|
+
}
|
|
25
|
+
export declare class VtxoMergeError extends OmniaVtxoError {
|
|
26
|
+
constructor(message: string);
|
|
27
|
+
}
|
|
28
|
+
export declare class VtxoSplitError extends OmniaVtxoError {
|
|
29
|
+
constructor(message: string);
|
|
30
|
+
}
|
|
31
|
+
export declare class VtxoExitError extends OmniaVtxoError {
|
|
32
|
+
constructor(message: string);
|
|
33
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
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 {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'OmniaVtxoError';
|
|
8
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
exports.OmniaVtxoError = OmniaVtxoError;
|
|
12
|
+
class VtxoAmountError extends OmniaVtxoError {
|
|
13
|
+
constructor(message) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = 'VtxoAmountError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.VtxoAmountError = VtxoAmountError;
|
|
19
|
+
class VtxoStatusError extends OmniaVtxoError {
|
|
20
|
+
constructor(message, currentStatus) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = 'VtxoStatusError';
|
|
23
|
+
this.currentStatus = currentStatus;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.VtxoStatusError = VtxoStatusError;
|
|
27
|
+
class VtxoOwnershipError extends OmniaVtxoError {
|
|
28
|
+
constructor(message) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = 'VtxoOwnershipError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.VtxoOwnershipError = VtxoOwnershipError;
|
|
34
|
+
class VtxoProofError extends OmniaVtxoError {
|
|
35
|
+
constructor(message) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = 'VtxoProofError';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.VtxoProofError = VtxoProofError;
|
|
41
|
+
class VtxoPoolCapacityError extends OmniaVtxoError {
|
|
42
|
+
constructor(message, requested, available) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.name = 'VtxoPoolCapacityError';
|
|
45
|
+
this.requested = requested;
|
|
46
|
+
this.available = available;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.VtxoPoolCapacityError = VtxoPoolCapacityError;
|
|
50
|
+
class VtxoPolicyError extends OmniaVtxoError {
|
|
51
|
+
constructor(message) {
|
|
52
|
+
super(message);
|
|
53
|
+
this.name = 'VtxoPolicyError';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.VtxoPolicyError = VtxoPolicyError;
|
|
57
|
+
class VtxoMergeError extends OmniaVtxoError {
|
|
58
|
+
constructor(message) {
|
|
59
|
+
super(message);
|
|
60
|
+
this.name = 'VtxoMergeError';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.VtxoMergeError = VtxoMergeError;
|
|
64
|
+
class VtxoSplitError extends OmniaVtxoError {
|
|
65
|
+
constructor(message) {
|
|
66
|
+
super(message);
|
|
67
|
+
this.name = 'VtxoSplitError';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
exports.VtxoSplitError = VtxoSplitError;
|
|
71
|
+
class VtxoExitError extends OmniaVtxoError {
|
|
72
|
+
constructor(message) {
|
|
73
|
+
super(message);
|
|
74
|
+
this.name = 'VtxoExitError';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.VtxoExitError = VtxoExitError;
|
package/dist/exit.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { OmniaVtxo, ExitDraft, VtxoOperatorReceipt } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Transitions an active VTXO to 'exiting' status.
|
|
4
|
+
* @param now - Timestamp in ms. Defaults to Date.now() if omitted — pass explicitly for determinism.
|
|
5
|
+
*/
|
|
6
|
+
export declare function markExiting(vtxo: OmniaVtxo, now?: number): OmniaVtxo;
|
|
7
|
+
/**
|
|
8
|
+
* Transitions an exiting VTXO to 'exited' status.
|
|
9
|
+
* @param now - Timestamp in ms. Defaults to Date.now() if omitted — pass explicitly for determinism.
|
|
10
|
+
*/
|
|
11
|
+
export declare function markExited(vtxo: OmniaVtxo, now?: number): OmniaVtxo;
|
|
12
|
+
/**
|
|
13
|
+
* Creates a mock exit draft for a VTXO. Validates the proof leaf before drafting.
|
|
14
|
+
* @param now - Timestamp in ms. Defaults to Date.now() if omitted — pass explicitly for determinism.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createExitDraft(vtxo: OmniaVtxo, now?: number): {
|
|
17
|
+
draft: ExitDraft;
|
|
18
|
+
receipt: VtxoOperatorReceipt;
|
|
19
|
+
};
|