@totemsdk/omnia-vtxo 0.6.1 → 0.6.2
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 +202 -21
- package/NOTICE +60 -0
- package/README.md +1 -1
- package/dist/anchor.d.ts +14 -2
- package/dist/anchor.js +33 -10
- package/dist/cjs/anchor.d.ts +14 -2
- package/dist/cjs/anchor.js +32 -9
- package/dist/cjs/claim-policy.d.ts +94 -0
- package/dist/cjs/claim-policy.js +149 -0
- package/dist/cjs/commitment.d.ts +12 -0
- package/dist/cjs/commitment.js +21 -1
- package/dist/cjs/durable-store.js +8 -0
- package/dist/cjs/exit-entitlement.d.ts +35 -0
- package/dist/cjs/exit-entitlement.js +70 -0
- package/dist/cjs/exit-tx.d.ts +6 -0
- package/dist/cjs/exit-tx.js +31 -10
- package/dist/cjs/index.d.ts +3 -0
- package/dist/cjs/index.js +3 -0
- package/dist/cjs/memory-store.js +8 -0
- package/dist/cjs/receipt.d.ts +8 -1
- package/dist/cjs/receipt.js +29 -7
- package/dist/cjs/transfer.d.ts +20 -0
- package/dist/cjs/transfer.js +39 -0
- package/dist/cjs/types.d.ts +8 -0
- package/dist/cjs/yield.d.ts +2 -0
- package/dist/cjs/yield.js +26 -0
- package/dist/claim-policy.d.ts +94 -0
- package/dist/claim-policy.js +144 -0
- package/dist/commitment.d.ts +12 -0
- package/dist/commitment.js +18 -0
- package/dist/durable-store.js +8 -0
- package/dist/exit-entitlement.d.ts +35 -0
- package/dist/exit-entitlement.js +66 -0
- package/dist/exit-tx.d.ts +6 -0
- package/dist/exit-tx.js +32 -11
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/memory-store.js +8 -0
- package/dist/receipt.d.ts +8 -1
- package/dist/receipt.js +28 -8
- package/dist/transfer.d.ts +20 -0
- package/dist/transfer.js +38 -0
- package/dist/types.d.ts +8 -0
- package/dist/yield.d.ts +2 -0
- package/dist/yield.js +23 -0
- package/package.json +7 -6
- package/rust/pkg/package.json +1 -1
- package/rust/pkg-node/package.json +1 -1
package/dist/index.js
CHANGED
package/dist/memory-store.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { VtxoStatusError } from './errors.js';
|
|
2
|
+
const CONSUMED_STATUSES = new Set(['spent', 'transferred', 'split', 'merged', 'refreshed', 'exited']);
|
|
3
|
+
function isConsumed(status) {
|
|
4
|
+
return CONSUMED_STATUSES.has(status);
|
|
5
|
+
}
|
|
2
6
|
export class MemoryOmniaVtxoStore {
|
|
3
7
|
constructor() {
|
|
4
8
|
this.pools = new Map();
|
|
@@ -11,6 +15,10 @@ export class MemoryOmniaVtxoStore {
|
|
|
11
15
|
return this.pools.get(poolId);
|
|
12
16
|
}
|
|
13
17
|
async saveVtxo(vtxo) {
|
|
18
|
+
const existing = this.vtxos.get(vtxo.vtxoId);
|
|
19
|
+
if (existing && isConsumed(existing.status) && !isConsumed(vtxo.status)) {
|
|
20
|
+
throw new VtxoStatusError(`Cannot resurrect VTXO ${vtxo.vtxoId}: status is '${existing.status}'`, existing.status);
|
|
21
|
+
}
|
|
14
22
|
this.vtxos.set(vtxo.vtxoId, vtxo);
|
|
15
23
|
}
|
|
16
24
|
async getVtxo(vtxoId) {
|
package/dist/receipt.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* conservation. This prevents a caller from getting the operator to co-sign an
|
|
8
8
|
* arbitrary `(root, epoch, ids)` tuple whose root does not commit the outputs.
|
|
9
9
|
*/
|
|
10
|
-
import type { OmniaVtxo, OmniaVtxoOperator, VtxoOperatorReceipt } from './types.js';
|
|
10
|
+
import type { OmniaVtxo, OmniaVtxoOperator, OmniaVtxoPool, VtxoOperatorReceipt } from './types.js';
|
|
11
11
|
/** The batch the receipt belongs to — the operator's source of truth. */
|
|
12
12
|
export interface VtxoReceiptBatch {
|
|
13
13
|
/** The VTXOs committed in this epoch's batch. */
|
|
@@ -26,6 +26,13 @@ export interface VtxoReceiptBatch {
|
|
|
26
26
|
* @throws if the root does not commit the leaves, the epoch is stale, an output
|
|
27
27
|
* is not in the batch, or conservation fails.
|
|
28
28
|
*/
|
|
29
|
+
export declare function buildVtxoReceiptPayload(receipt: VtxoOperatorReceipt, batch: VtxoReceiptBatch): Uint8Array;
|
|
29
30
|
export declare function signVtxoReceipt(operator: OmniaVtxoOperator, receipt: VtxoOperatorReceipt, batch: VtxoReceiptBatch): Promise<VtxoOperatorReceipt>;
|
|
30
31
|
/** Verify a co-signed receipt against the operator and the batch root. */
|
|
31
32
|
export declare function verifyVtxoReceipt(operator: OmniaVtxoOperator, receipt: VtxoOperatorReceipt, root: string): Promise<boolean>;
|
|
33
|
+
/**
|
|
34
|
+
* Verify a receipt against the pool's current anchored state, not a
|
|
35
|
+
* caller-supplied root. Rejects a receipt from another pool/epoch and requires
|
|
36
|
+
* the pool's committed root to be present.
|
|
37
|
+
*/
|
|
38
|
+
export declare function verifyVtxoReceiptForPool(operator: OmniaVtxoOperator, receipt: VtxoOperatorReceipt, pool: Pick<OmniaVtxoPool, 'poolId' | 'epoch' | 'commitmentRoot'>): Promise<boolean>;
|
package/dist/receipt.js
CHANGED
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
* conservation. This prevents a caller from getting the operator to co-sign an
|
|
8
8
|
* arbitrary `(root, epoch, ids)` tuple whose root does not commit the outputs.
|
|
9
9
|
*/
|
|
10
|
-
import {
|
|
10
|
+
import { computeCommitmentRoot, computeVtxoLeaf, computeVtxoReceiptCommitmentBytes } from './commitment.js';
|
|
11
11
|
/**
|
|
12
12
|
* Co-sign a receipt with the operator, after verifying the batch.
|
|
13
13
|
* @throws if the root does not commit the leaves, the epoch is stale, an output
|
|
14
14
|
* is not in the batch, or conservation fails.
|
|
15
15
|
*/
|
|
16
|
-
export
|
|
16
|
+
export function buildVtxoReceiptPayload(receipt, batch) {
|
|
17
17
|
if (receipt.epoch !== batch.epoch) {
|
|
18
18
|
throw new Error(`receipt epoch ${receipt.epoch} does not match batch epoch ${batch.epoch}`);
|
|
19
19
|
}
|
|
@@ -29,13 +29,19 @@ export async function signVtxoReceipt(operator, receipt, batch) {
|
|
|
29
29
|
if (recomputedRoot !== batch.root) {
|
|
30
30
|
throw new Error('batch root does not commit the batch leaves');
|
|
31
31
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (
|
|
35
|
-
throw new Error(`conservation
|
|
32
|
+
const outputSum = batch.outputs.reduce((sum, v) => sum + v.amount, 0n);
|
|
33
|
+
if (receipt.op !== 'mint') {
|
|
34
|
+
if (batch.inputsAmount === undefined) {
|
|
35
|
+
throw new Error(`conservation required for ${receipt.op} receipts`);
|
|
36
|
+
}
|
|
37
|
+
if (outputSum !== batch.inputsAmount) {
|
|
38
|
+
throw new Error(`conservation violated: outputs ${outputSum} != inputs ${batch.inputsAmount}`);
|
|
36
39
|
}
|
|
37
40
|
}
|
|
38
|
-
|
|
41
|
+
return computeVtxoReceiptCommitmentBytes(receipt, batch.root);
|
|
42
|
+
}
|
|
43
|
+
export async function signVtxoReceipt(operator, receipt, batch) {
|
|
44
|
+
const data = buildVtxoReceiptPayload(receipt, batch);
|
|
39
45
|
const signature = await operator.sign(data);
|
|
40
46
|
return { ...receipt, signature };
|
|
41
47
|
}
|
|
@@ -43,6 +49,20 @@ export async function signVtxoReceipt(operator, receipt, batch) {
|
|
|
43
49
|
export async function verifyVtxoReceipt(operator, receipt, root) {
|
|
44
50
|
if (!receipt.signature)
|
|
45
51
|
return false;
|
|
46
|
-
const data =
|
|
52
|
+
const data = computeVtxoReceiptCommitmentBytes(receipt, root);
|
|
47
53
|
return operator.verify(data, receipt.signature);
|
|
48
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Verify a receipt against the pool's current anchored state, not a
|
|
57
|
+
* caller-supplied root. Rejects a receipt from another pool/epoch and requires
|
|
58
|
+
* the pool's committed root to be present.
|
|
59
|
+
*/
|
|
60
|
+
export async function verifyVtxoReceiptForPool(operator, receipt, pool) {
|
|
61
|
+
if (receipt.poolId !== pool.poolId)
|
|
62
|
+
return false;
|
|
63
|
+
if (receipt.epoch !== pool.epoch)
|
|
64
|
+
return false;
|
|
65
|
+
if (!pool.commitmentRoot)
|
|
66
|
+
return false;
|
|
67
|
+
return verifyVtxoReceipt(operator, receipt, pool.commitmentRoot);
|
|
68
|
+
}
|
package/dist/transfer.d.ts
CHANGED
|
@@ -1,8 +1,28 @@
|
|
|
1
1
|
import { OmniaVtxo, TransferVtxoParams, TransferResult, VtxoOperatorReceipt } from './types.js';
|
|
2
|
+
import { type VtxoExitEntitlement } from './exit-entitlement.js';
|
|
3
|
+
import { type ExitTxParams, type ExitTxDeps } from './exit-tx.js';
|
|
2
4
|
/**
|
|
3
5
|
* Transfers a VTXO (fully or partially) to a recipient.
|
|
6
|
+
*
|
|
7
|
+
* **Offline safety.** The returned output VTXOs are new claims. A Merkle proof
|
|
8
|
+
* establishes membership in a committed set; it does NOT prove the input was
|
|
9
|
+
* never spent elsewhere. The local host rejects a second transfer through its
|
|
10
|
+
* shared state, but disconnected receivers do not share that state. Treat a
|
|
11
|
+
* received VTXO as final only after an anti-double-spend check (online/operator
|
|
12
|
+
* spend check, operator-co-signed transfer, or an explicitly risk-bounded
|
|
13
|
+
* policy), and have the operator commit the new outputs — they do not
|
|
14
|
+
* automatically belong to the previous pool root.
|
|
15
|
+
*
|
|
4
16
|
* @param now - Timestamp in ms. Defaults to Date.now() if omitted — pass explicitly for determinism.
|
|
5
17
|
*/
|
|
6
18
|
export declare function transferVtxo(vtxo: OmniaVtxo, params: TransferVtxoParams, now?: number): TransferResult & {
|
|
7
19
|
receipt: VtxoOperatorReceipt;
|
|
8
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* V1 transfer: build the recipient's independent exit before moving the claim,
|
|
23
|
+
* then transfer only if the exit package verifies against the input VTXO.
|
|
24
|
+
*/
|
|
25
|
+
export declare function transferVtxoWithExit(vtxo: OmniaVtxo, params: Omit<TransferVtxoParams, 'exitEntitlement'>, exitParams: ExitTxParams, exitDeps: ExitTxDeps, now?: number): Promise<TransferResult & {
|
|
26
|
+
receipt: VtxoOperatorReceipt;
|
|
27
|
+
exitEntitlement: VtxoExitEntitlement;
|
|
28
|
+
}>;
|
package/dist/transfer.js
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { VtxoStatusError, VtxoAmountError } from './errors.js';
|
|
2
2
|
import { computeVtxoId, buildVtxoProofSet, computeReceiptId } from './commitment.js';
|
|
3
|
+
import { verifyVtxoExitEntitlement, createVtxoExitEntitlement } from './exit-entitlement.js';
|
|
4
|
+
import { buildExitTransaction } from './exit-tx.js';
|
|
3
5
|
/**
|
|
4
6
|
* Transfers a VTXO (fully or partially) to a recipient.
|
|
7
|
+
*
|
|
8
|
+
* **Offline safety.** The returned output VTXOs are new claims. A Merkle proof
|
|
9
|
+
* establishes membership in a committed set; it does NOT prove the input was
|
|
10
|
+
* never spent elsewhere. The local host rejects a second transfer through its
|
|
11
|
+
* shared state, but disconnected receivers do not share that state. Treat a
|
|
12
|
+
* received VTXO as final only after an anti-double-spend check (online/operator
|
|
13
|
+
* spend check, operator-co-signed transfer, or an explicitly risk-bounded
|
|
14
|
+
* policy), and have the operator commit the new outputs — they do not
|
|
15
|
+
* automatically belong to the previous pool root.
|
|
16
|
+
*
|
|
5
17
|
* @param now - Timestamp in ms. Defaults to Date.now() if omitted — pass explicitly for determinism.
|
|
6
18
|
*/
|
|
7
19
|
export function transferVtxo(vtxo, params, now) {
|
|
@@ -15,6 +27,15 @@ export function transferVtxo(vtxo, params, now) {
|
|
|
15
27
|
if (params.amount > vtxo.amount) {
|
|
16
28
|
throw new VtxoAmountError(`Transfer amount ${params.amount} exceeds VTXO amount ${vtxo.amount}`);
|
|
17
29
|
}
|
|
30
|
+
if (params.exitEntitlement) {
|
|
31
|
+
if (params.amount !== vtxo.amount) {
|
|
32
|
+
throw new VtxoAmountError('exit entitlement requires a full-value transfer');
|
|
33
|
+
}
|
|
34
|
+
const check = verifyVtxoExitEntitlement(params.exitEntitlement, vtxo);
|
|
35
|
+
if (!check.valid) {
|
|
36
|
+
throw new VtxoAmountError(`invalid exit entitlement: ${check.errors.join('; ')}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
18
39
|
const isPartial = params.amount < vtxo.amount;
|
|
19
40
|
const changeAmount = vtxo.amount - params.amount;
|
|
20
41
|
const outputId = computeVtxoId({
|
|
@@ -59,6 +80,7 @@ export function transferVtxo(vtxo, params, now) {
|
|
|
59
80
|
tokenId: vtxo.tokenId,
|
|
60
81
|
status: 'active',
|
|
61
82
|
epoch: vtxo.epoch,
|
|
83
|
+
...(params.exitEntitlement ? { exitEntitlement: params.exitEntitlement } : {}),
|
|
62
84
|
proof: {
|
|
63
85
|
leaf: '',
|
|
64
86
|
root: '',
|
|
@@ -147,3 +169,19 @@ export function transferVtxo(vtxo, params, now) {
|
|
|
147
169
|
},
|
|
148
170
|
};
|
|
149
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* V1 transfer: build the recipient's independent exit before moving the claim,
|
|
174
|
+
* then transfer only if the exit package verifies against the input VTXO.
|
|
175
|
+
*/
|
|
176
|
+
export async function transferVtxoWithExit(vtxo, params, exitParams, exitDeps, now) {
|
|
177
|
+
if (exitParams.vtxo.vtxoId !== vtxo.vtxoId) {
|
|
178
|
+
throw new VtxoAmountError('exit entitlement VTXO does not match transfer input');
|
|
179
|
+
}
|
|
180
|
+
if (params.amount !== vtxo.amount) {
|
|
181
|
+
throw new VtxoAmountError('transferVtxoWithExit requires a full-value transfer');
|
|
182
|
+
}
|
|
183
|
+
const exit = await buildExitTransaction(exitParams, exitDeps);
|
|
184
|
+
const entitlement = createVtxoExitEntitlement(exitParams, exit);
|
|
185
|
+
const transferred = transferVtxo(vtxo, { ...params, exitEntitlement: entitlement }, now);
|
|
186
|
+
return { ...transferred, exitEntitlement: entitlement };
|
|
187
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { VtxoClaimPolicy } from './claim-policy.js';
|
|
2
|
+
import type { VtxoExitEntitlement } from './exit-entitlement.js';
|
|
1
3
|
export type VtxoId = string;
|
|
2
4
|
export type VtxoStatus = 'active' | 'transferred' | 'split' | 'merged' | 'refreshed' | 'exiting' | 'exited' | 'spent' | 'expired' | 'invalid';
|
|
3
5
|
export type VtxoOp = 'mint' | 'transfer' | 'split' | 'merge' | 'refresh' | 'exit_initiated' | 'exit_complete' | 'spent';
|
|
@@ -50,6 +52,10 @@ export interface OmniaVtxo {
|
|
|
50
52
|
expiresAt?: number;
|
|
51
53
|
/** The on-chain funding proof recorded at mint (deep proof, #28). */
|
|
52
54
|
fundingProof?: unknown;
|
|
55
|
+
/** Opt-in multiparty claim policy (M8–M18). */
|
|
56
|
+
claimPolicy?: VtxoClaimPolicy;
|
|
57
|
+
/** V1: independently executable exit package delivered to the owner. */
|
|
58
|
+
exitEntitlement?: VtxoExitEntitlement;
|
|
53
59
|
/** Set when an exit draft consumed this VTXO — prevents double-exit. */
|
|
54
60
|
exitConsumedAt?: number;
|
|
55
61
|
exitReceiptId?: string;
|
|
@@ -99,6 +105,8 @@ export interface TransferVtxoParams {
|
|
|
99
105
|
amount: bigint;
|
|
100
106
|
nonce: string;
|
|
101
107
|
changeNonce?: string;
|
|
108
|
+
/** V1: exit package the recipient must receive with the claim. */
|
|
109
|
+
exitEntitlement?: VtxoExitEntitlement;
|
|
102
110
|
}
|
|
103
111
|
export interface SplitVtxoParams {
|
|
104
112
|
amounts: bigint[];
|
package/dist/yield.d.ts
ADDED
package/dist/yield.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Deterministic pro-rata yield distribution for multiparty VTXO/pool claims. */
|
|
2
|
+
export function distributeYield(total, weights) {
|
|
3
|
+
if (total < 0n)
|
|
4
|
+
throw new Error('yield total must be non-negative');
|
|
5
|
+
const entries = Object.entries(weights).filter(([, weight]) => weight > 0n);
|
|
6
|
+
if (entries.length === 0)
|
|
7
|
+
throw new Error('yield requires positive weights');
|
|
8
|
+
const denominator = entries.reduce((sum, [, weight]) => sum + weight, 0n);
|
|
9
|
+
if (denominator <= 0n)
|
|
10
|
+
throw new Error('yield weight sum must be positive');
|
|
11
|
+
const out = {};
|
|
12
|
+
let allocated = 0n;
|
|
13
|
+
for (const [id, weight] of entries) {
|
|
14
|
+
const share = (total * weight) / denominator;
|
|
15
|
+
out[id] = share;
|
|
16
|
+
allocated += share;
|
|
17
|
+
}
|
|
18
|
+
// Deterministic remainder assignment to the first entry.
|
|
19
|
+
const remainder = total - allocated;
|
|
20
|
+
if (remainder > 0n)
|
|
21
|
+
out[entries[0][0]] += remainder;
|
|
22
|
+
return out;
|
|
23
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@totemsdk/omnia-vtxo",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Virtual UTXO / payment-pool claim layer for Omnia — cash-like off-chain balance primitive for Totem Edge",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"types": "dist/cjs/index.d.ts",
|
|
@@ -21,14 +21,15 @@
|
|
|
21
21
|
"rust/pkg/",
|
|
22
22
|
"rust/pkg-node/",
|
|
23
23
|
"README.md",
|
|
24
|
-
"LICENSE"
|
|
24
|
+
"LICENSE",
|
|
25
|
+
"NOTICE"
|
|
25
26
|
],
|
|
26
27
|
"dependencies": {
|
|
27
28
|
"@totemsdk/core": "^1.4.0",
|
|
28
|
-
"@totemsdk/
|
|
29
|
-
"@totemsdk/
|
|
30
|
-
"@totemsdk/storage": "^0.1.0",
|
|
29
|
+
"@totemsdk/omnia": "^2.3.2",
|
|
30
|
+
"@totemsdk/storage": "^0.2.1",
|
|
31
31
|
"@totemsdk/txpow": "^1.1.0",
|
|
32
|
+
"@totemsdk/chain-provider": "^0.3.0",
|
|
32
33
|
"@totemsdk/wots-lease": "^1.0.1"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
@@ -44,7 +45,7 @@
|
|
|
44
45
|
"publishConfig": {
|
|
45
46
|
"access": "public"
|
|
46
47
|
},
|
|
47
|
-
"license": "
|
|
48
|
+
"license": "Apache-2.0",
|
|
48
49
|
"author": "Totem SDK",
|
|
49
50
|
"homepage": "https://docs.totem.ing",
|
|
50
51
|
"bugs": {
|
package/rust/pkg/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
],
|
|
7
7
|
"description": "Virtual UTXO commitment layer — Merkle tree hashing, VTXO ID computation, proof verification, amount conservation (Rust/WASM)",
|
|
8
8
|
"version": "0.1.0",
|
|
9
|
-
"license": "
|
|
9
|
+
"license": "Apache-2.0",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
12
|
"url": "https://github.com/Totem-Edge/totem-sdk"
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
],
|
|
6
6
|
"description": "Virtual UTXO commitment layer — Merkle tree hashing, VTXO ID computation, proof verification, amount conservation (Rust/WASM)",
|
|
7
7
|
"version": "0.1.0",
|
|
8
|
-
"license": "
|
|
8
|
+
"license": "Apache-2.0",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
11
|
"url": "https://github.com/Totem-Edge/totem-sdk"
|