@totemsdk/omnia-vtxo 0.3.0 → 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 +5 -2
- package/dist/anchor.js +11 -6
- package/dist/cjs/anchor.d.ts +5 -2
- package/dist/cjs/anchor.js +11 -6
- package/dist/cjs/exit-tx.d.ts +11 -9
- package/dist/cjs/exit-tx.js +11 -9
- package/dist/cjs/pool-script.d.ts +28 -11
- package/dist/cjs/pool-script.js +36 -17
- package/dist/exit-tx.d.ts +11 -9
- package/dist/exit-tx.js +11 -9
- package/dist/pool-script.d.ts +28 -11
- package/dist/pool-script.js +36 -17
- package/package.json +5 -5
package/dist/anchor.d.ts
CHANGED
|
@@ -11,6 +11,9 @@ import { type OmniaTxDraft, type StateValue } from '@totemsdk/omnia';
|
|
|
11
11
|
import type { ChainStateProvider } from '@totemsdk/chain-provider';
|
|
12
12
|
export interface BatchAnchorParams {
|
|
13
13
|
operatorPublicKeyDigest: string;
|
|
14
|
+
ownerPublicKeyDigest: string;
|
|
15
|
+
/** Absolute block height after which the owner may exit unilaterally (STATE(102)). */
|
|
16
|
+
exitBlock: number;
|
|
14
17
|
/** The epoch's Merkle root (32-byte hex). */
|
|
15
18
|
root: string;
|
|
16
19
|
epoch: number;
|
|
@@ -22,8 +25,8 @@ export interface BatchAnchorParams {
|
|
|
22
25
|
amount: bigint;
|
|
23
26
|
tokenId: string;
|
|
24
27
|
}
|
|
25
|
-
/**
|
|
26
|
-
export declare function poolStateVariables(root: string, epoch: number): StateValue[];
|
|
28
|
+
/** Pool output state: STATE(100)=root, STATE(101)=epoch, STATE(102)=exitBlock. */
|
|
29
|
+
export declare function poolStateVariables(root: string, epoch: number, exitBlock: number): StateValue[];
|
|
27
30
|
/** Build the batch-anchor draft (input = pool coin, output = pool state output). */
|
|
28
31
|
export declare function buildBatchAnchorDraft(params: BatchAnchorParams): OmniaTxDraft;
|
|
29
32
|
export declare function batchAnchorDigest(draft: OmniaTxDraft): Uint8Array;
|
package/dist/anchor.js
CHANGED
|
@@ -11,24 +11,29 @@ import { computeOmniaTxDigest, omniaDraftToMinimaBytes, } from '@totemsdk/omnia'
|
|
|
11
11
|
import { serializeTxBody, mineTxPoW, TX_POW_MIN_DIFFICULTY } from '@totemsdk/txpow';
|
|
12
12
|
import { concatBytes } from '@totemsdk/core';
|
|
13
13
|
import { buildAndHashPoolScript } from './pool-script.js';
|
|
14
|
-
/**
|
|
15
|
-
export function poolStateVariables(root, epoch) {
|
|
14
|
+
/** Pool output state: STATE(100)=root, STATE(101)=epoch, STATE(102)=exitBlock. */
|
|
15
|
+
export function poolStateVariables(root, epoch, exitBlock) {
|
|
16
16
|
return [
|
|
17
17
|
{ port: 100, value: root, type: 'string' },
|
|
18
18
|
{ port: 101, value: BigInt(epoch), type: 'number' },
|
|
19
|
+
{ port: 102, value: BigInt(exitBlock), type: 'number' },
|
|
19
20
|
];
|
|
20
21
|
}
|
|
21
22
|
/** Build the batch-anchor draft (input = pool coin, output = pool state output). */
|
|
22
23
|
export function buildBatchAnchorDraft(params) {
|
|
23
|
-
const { script, address } = buildAndHashPoolScript(
|
|
24
|
+
const { script, address } = buildAndHashPoolScript({
|
|
25
|
+
operatorPublicKeyDigest: params.operatorPublicKeyDigest,
|
|
26
|
+
ownerPublicKeyDigest: params.ownerPublicKeyDigest,
|
|
27
|
+
exitBlock: params.exitBlock,
|
|
28
|
+
});
|
|
24
29
|
// The input must be the operator's own pool output, not a foreign script.
|
|
25
30
|
if (params.fundingScript !== script) {
|
|
26
|
-
throw new Error('anchorBatch: fundingScript does not match the
|
|
31
|
+
throw new Error('anchorBatch: fundingScript does not match the pool script');
|
|
27
32
|
}
|
|
28
33
|
if (params.fundingAddress !== address) {
|
|
29
|
-
throw new Error('anchorBatch: fundingAddress does not match the
|
|
34
|
+
throw new Error('anchorBatch: fundingAddress does not match the pool address');
|
|
30
35
|
}
|
|
31
|
-
const stateVariables = poolStateVariables(params.root, params.epoch);
|
|
36
|
+
const stateVariables = poolStateVariables(params.root, params.epoch, params.exitBlock);
|
|
32
37
|
return {
|
|
33
38
|
type: 'settlement',
|
|
34
39
|
inputs: [{
|
package/dist/cjs/anchor.d.ts
CHANGED
|
@@ -11,6 +11,9 @@ import { type OmniaTxDraft, type StateValue } from '@totemsdk/omnia';
|
|
|
11
11
|
import type { ChainStateProvider } from '@totemsdk/chain-provider';
|
|
12
12
|
export interface BatchAnchorParams {
|
|
13
13
|
operatorPublicKeyDigest: string;
|
|
14
|
+
ownerPublicKeyDigest: string;
|
|
15
|
+
/** Absolute block height after which the owner may exit unilaterally (STATE(102)). */
|
|
16
|
+
exitBlock: number;
|
|
14
17
|
/** The epoch's Merkle root (32-byte hex). */
|
|
15
18
|
root: string;
|
|
16
19
|
epoch: number;
|
|
@@ -22,8 +25,8 @@ export interface BatchAnchorParams {
|
|
|
22
25
|
amount: bigint;
|
|
23
26
|
tokenId: string;
|
|
24
27
|
}
|
|
25
|
-
/**
|
|
26
|
-
export declare function poolStateVariables(root: string, epoch: number): StateValue[];
|
|
28
|
+
/** Pool output state: STATE(100)=root, STATE(101)=epoch, STATE(102)=exitBlock. */
|
|
29
|
+
export declare function poolStateVariables(root: string, epoch: number, exitBlock: number): StateValue[];
|
|
27
30
|
/** Build the batch-anchor draft (input = pool coin, output = pool state output). */
|
|
28
31
|
export declare function buildBatchAnchorDraft(params: BatchAnchorParams): OmniaTxDraft;
|
|
29
32
|
export declare function batchAnchorDigest(draft: OmniaTxDraft): Uint8Array;
|
package/dist/cjs/anchor.js
CHANGED
|
@@ -18,24 +18,29 @@ const omnia_1 = require("@totemsdk/omnia");
|
|
|
18
18
|
const txpow_1 = require("@totemsdk/txpow");
|
|
19
19
|
const core_1 = require("@totemsdk/core");
|
|
20
20
|
const pool_script_js_1 = require("./pool-script.js");
|
|
21
|
-
/**
|
|
22
|
-
function poolStateVariables(root, epoch) {
|
|
21
|
+
/** Pool output state: STATE(100)=root, STATE(101)=epoch, STATE(102)=exitBlock. */
|
|
22
|
+
function poolStateVariables(root, epoch, exitBlock) {
|
|
23
23
|
return [
|
|
24
24
|
{ port: 100, value: root, type: 'string' },
|
|
25
25
|
{ port: 101, value: BigInt(epoch), type: 'number' },
|
|
26
|
+
{ port: 102, value: BigInt(exitBlock), type: 'number' },
|
|
26
27
|
];
|
|
27
28
|
}
|
|
28
29
|
/** Build the batch-anchor draft (input = pool coin, output = pool state output). */
|
|
29
30
|
function buildBatchAnchorDraft(params) {
|
|
30
|
-
const { script, address } = (0, pool_script_js_1.buildAndHashPoolScript)(
|
|
31
|
+
const { script, address } = (0, pool_script_js_1.buildAndHashPoolScript)({
|
|
32
|
+
operatorPublicKeyDigest: params.operatorPublicKeyDigest,
|
|
33
|
+
ownerPublicKeyDigest: params.ownerPublicKeyDigest,
|
|
34
|
+
exitBlock: params.exitBlock,
|
|
35
|
+
});
|
|
31
36
|
// The input must be the operator's own pool output, not a foreign script.
|
|
32
37
|
if (params.fundingScript !== script) {
|
|
33
|
-
throw new Error('anchorBatch: fundingScript does not match the
|
|
38
|
+
throw new Error('anchorBatch: fundingScript does not match the pool script');
|
|
34
39
|
}
|
|
35
40
|
if (params.fundingAddress !== address) {
|
|
36
|
-
throw new Error('anchorBatch: fundingAddress does not match the
|
|
41
|
+
throw new Error('anchorBatch: fundingAddress does not match the pool address');
|
|
37
42
|
}
|
|
38
|
-
const stateVariables = poolStateVariables(params.root, params.epoch);
|
|
43
|
+
const stateVariables = poolStateVariables(params.root, params.epoch, params.exitBlock);
|
|
39
44
|
return {
|
|
40
45
|
type: 'settlement',
|
|
41
46
|
inputs: [{
|
package/dist/cjs/exit-tx.d.ts
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* VTXO exit transaction (RFC-038 V2).
|
|
2
|
+
* VTXO exit transaction (RFC-038 V2 — owner-unilateral).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
|
6
9
|
* (injected signer) and, when a chain provider is supplied, mined + broadcast
|
|
7
|
-
* (injected miner).
|
|
10
|
+
* (injected miner).
|
|
8
11
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* follow-on; this builds and broadcasts the transaction with the delay encoded.
|
|
12
|
+
* The caller must pass the owner signer; the operator cannot construct a valid
|
|
13
|
+
* exit witness for itself.
|
|
12
14
|
*/
|
|
13
15
|
import { type OmniaTxDraft } from '@totemsdk/omnia';
|
|
14
16
|
import type { ChainStateProvider } from '@totemsdk/chain-provider';
|
|
15
17
|
import type { OmniaVtxo } from './types.js';
|
|
16
|
-
/** State port carrying the exit delay. */
|
|
17
|
-
export declare const EXIT_DELAY_PORT =
|
|
18
|
+
/** State port carrying the exit delay on the owner's output. */
|
|
19
|
+
export declare const EXIT_DELAY_PORT = 110;
|
|
18
20
|
export interface ExitTxParams {
|
|
19
21
|
vtxo: OmniaVtxo;
|
|
20
22
|
/** The pool's on-chain coin (input). */
|
package/dist/cjs/exit-tx.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* VTXO exit transaction (RFC-038 V2).
|
|
3
|
+
* VTXO exit transaction (RFC-038 V2 — owner-unilateral).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* The exit spends the pool's on-chain coin and pays the VTXO's owner, carrying
|
|
6
|
+
* the exit delay in STATE(110). It is signed with the **owner's** key (not the
|
|
7
|
+
* operator's): the pool script's `IF @BLOCK GT EXITBLOCK THEN RETURN
|
|
8
|
+
* SIGNEDBY(OWNER)` branch authorizes it after the exit window, so the owner can
|
|
9
|
+
* broadcast it without any operator signature or liveness. The digest is signed
|
|
7
10
|
* (injected signer) and, when a chain provider is supplied, mined + broadcast
|
|
8
|
-
* (injected miner).
|
|
11
|
+
* (injected miner).
|
|
9
12
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* follow-on; this builds and broadcasts the transaction with the delay encoded.
|
|
13
|
+
* The caller must pass the owner signer; the operator cannot construct a valid
|
|
14
|
+
* exit witness for itself.
|
|
13
15
|
*/
|
|
14
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
17
|
exports.EXIT_DELAY_PORT = void 0;
|
|
@@ -20,8 +22,8 @@ exports.buildExitTransaction = buildExitTransaction;
|
|
|
20
22
|
const omnia_1 = require("@totemsdk/omnia");
|
|
21
23
|
const txpow_1 = require("@totemsdk/txpow");
|
|
22
24
|
const core_1 = require("@totemsdk/core");
|
|
23
|
-
/** State port carrying the exit delay. */
|
|
24
|
-
exports.EXIT_DELAY_PORT =
|
|
25
|
+
/** State port carrying the exit delay on the owner's output. */
|
|
26
|
+
exports.EXIT_DELAY_PORT = 110;
|
|
25
27
|
/** Draft that spends the pool coin to the VTXO owner with the exit delay encoded. */
|
|
26
28
|
function buildExitTransactionDraft(params) {
|
|
27
29
|
const stateVariables = [
|
|
@@ -1,19 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* VTXO pool on-chain script (RFC-038
|
|
2
|
+
* VTXO pool on-chain script (RFC-038 V2 — non-custodial).
|
|
3
3
|
*
|
|
4
|
-
* The pool is an on-chain state output holding `totalCapacity
|
|
5
|
-
* root is committed as STATE(100)
|
|
6
|
-
*
|
|
7
|
-
* old root cannot be re-anchored (RFC-038 §5 "stale/rolled-back root").
|
|
4
|
+
* The pool is an on-chain state output holding `totalCapacity`. An epoch's batch
|
|
5
|
+
* root is committed as STATE(100), the epoch number in STATE(101), and the
|
|
6
|
+
* owner's absolute exit-eligibility block in STATE(102).
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* Custody (RFC-044 / CUSTODY-POLICY §2):
|
|
9
|
+
* - **Unilateral owner exit.** After `@BLOCK GT EXITBLOCK` the owner alone may
|
|
10
|
+
* spend (`RETURN SIGNEDBY(OWNER)`) — no operator signature or liveness. This
|
|
11
|
+
* is exercised by the pre-signed exit tx (`exit-tx.ts`).
|
|
12
|
+
* - **Cooperative update.** Before the exit window the pool may only move by
|
|
13
|
+
* `MULTISIG(2 OPERATOR OWNER)`, epoch-monotone (`ASSERT EPOCH GT
|
|
14
|
+
* PREVSTATE(101)`), with the exit block immutable (`ASSERT SAMESTATE(102
|
|
15
|
+
* 102)`) so the operator can never push the owner's escape clock, and the
|
|
16
|
+
* value confined to the same script address (`ASSERT VERIFYOUT(@INPUT
|
|
17
|
+
* @ADDRESS @AMOUNT @TOKENID TRUE)`) so the operator can never pay itself.
|
|
18
|
+
*
|
|
19
|
+
* The operator holds no unilateral path: it cannot move funds, cannot advance
|
|
20
|
+
* the exit block, and cannot stop the owner's exit. It is a liveness-only peer.
|
|
12
21
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
22
|
+
export interface PoolScriptParams {
|
|
23
|
+
/** Operator WOTS public-key digest (64 hex). */
|
|
24
|
+
operatorPublicKeyDigest: string;
|
|
25
|
+
/** Pool owner WOTS public-key digest (64 hex). */
|
|
26
|
+
ownerPublicKeyDigest: string;
|
|
27
|
+
/** Absolute block height after which the owner may exit unilaterally. */
|
|
28
|
+
exitBlock: number;
|
|
29
|
+
}
|
|
30
|
+
/** Non-custodial, epoch-monotone, owner-escapable state-carrying pool script. */
|
|
31
|
+
export declare function buildPoolScript(params: PoolScriptParams): string;
|
|
15
32
|
export declare function poolScriptAddress(script: string): string;
|
|
16
|
-
export declare function buildAndHashPoolScript(
|
|
33
|
+
export declare function buildAndHashPoolScript(params: PoolScriptParams): {
|
|
17
34
|
script: string;
|
|
18
35
|
address: string;
|
|
19
36
|
};
|
package/dist/cjs/pool-script.js
CHANGED
|
@@ -1,41 +1,60 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* VTXO pool on-chain script (RFC-038
|
|
3
|
+
* VTXO pool on-chain script (RFC-038 V2 — non-custodial).
|
|
4
4
|
*
|
|
5
|
-
* The pool is an on-chain state output holding `totalCapacity
|
|
6
|
-
* root is committed as STATE(100)
|
|
7
|
-
*
|
|
8
|
-
* old root cannot be re-anchored (RFC-038 §5 "stale/rolled-back root").
|
|
5
|
+
* The pool is an on-chain state output holding `totalCapacity`. An epoch's batch
|
|
6
|
+
* root is committed as STATE(100), the epoch number in STATE(101), and the
|
|
7
|
+
* owner's absolute exit-eligibility block in STATE(102).
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* Custody (RFC-044 / CUSTODY-POLICY §2):
|
|
10
|
+
* - **Unilateral owner exit.** After `@BLOCK GT EXITBLOCK` the owner alone may
|
|
11
|
+
* spend (`RETURN SIGNEDBY(OWNER)`) — no operator signature or liveness. This
|
|
12
|
+
* is exercised by the pre-signed exit tx (`exit-tx.ts`).
|
|
13
|
+
* - **Cooperative update.** Before the exit window the pool may only move by
|
|
14
|
+
* `MULTISIG(2 OPERATOR OWNER)`, epoch-monotone (`ASSERT EPOCH GT
|
|
15
|
+
* PREVSTATE(101)`), with the exit block immutable (`ASSERT SAMESTATE(102
|
|
16
|
+
* 102)`) so the operator can never push the owner's escape clock, and the
|
|
17
|
+
* value confined to the same script address (`ASSERT VERIFYOUT(@INPUT
|
|
18
|
+
* @ADDRESS @AMOUNT @TOKENID TRUE)`) so the operator can never pay itself.
|
|
19
|
+
*
|
|
20
|
+
* The operator holds no unilateral path: it cannot move funds, cannot advance
|
|
21
|
+
* the exit block, and cannot stop the owner's exit. It is a liveness-only peer.
|
|
13
22
|
*/
|
|
14
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
24
|
exports.buildPoolScript = buildPoolScript;
|
|
16
25
|
exports.poolScriptAddress = poolScriptAddress;
|
|
17
26
|
exports.buildAndHashPoolScript = buildAndHashPoolScript;
|
|
18
27
|
const core_1 = require("@totemsdk/core");
|
|
19
|
-
function
|
|
20
|
-
const raw =
|
|
28
|
+
function pkHex(digest, label) {
|
|
29
|
+
const raw = digest.replace(/^0x/i, '');
|
|
21
30
|
if (!/^[0-9a-fA-F]{64}$/.test(raw)) {
|
|
22
|
-
throw new Error(
|
|
31
|
+
throw new Error(`${label} public key digest must be 64 hex chars, got ${JSON.stringify(digest)}`);
|
|
23
32
|
}
|
|
24
33
|
return '0X' + raw.toUpperCase();
|
|
25
34
|
}
|
|
26
|
-
/**
|
|
27
|
-
function buildPoolScript(
|
|
35
|
+
/** Non-custodial, epoch-monotone, owner-escapable state-carrying pool script. */
|
|
36
|
+
function buildPoolScript(params) {
|
|
37
|
+
const owner = pkHex(params.ownerPublicKeyDigest, 'owner');
|
|
38
|
+
const operator = pkHex(params.operatorPublicKeyDigest, 'operator');
|
|
39
|
+
if (!Number.isInteger(params.exitBlock) || params.exitBlock < 0) {
|
|
40
|
+
throw new Error(`exitBlock must be a non-negative integer, got ${JSON.stringify(params.exitBlock)}`);
|
|
41
|
+
}
|
|
28
42
|
return [
|
|
29
|
-
'LET ROOT=STATE(100)',
|
|
30
43
|
'LET EPOCH=STATE(101)',
|
|
44
|
+
'LET EXITBLOCK=STATE(102)',
|
|
45
|
+
'IF @BLOCK GT EXITBLOCK THEN',
|
|
46
|
+
` RETURN SIGNEDBY(${owner})`,
|
|
47
|
+
'ENDIF',
|
|
31
48
|
'ASSERT EPOCH GT PREVSTATE(101)',
|
|
32
|
-
|
|
49
|
+
'ASSERT SAMESTATE(102 102)',
|
|
50
|
+
'ASSERT VERIFYOUT(@INPUT @ADDRESS @AMOUNT @TOKENID TRUE)',
|
|
51
|
+
`RETURN MULTISIG(2 ${operator} ${owner})`,
|
|
33
52
|
].join('\n');
|
|
34
53
|
}
|
|
35
54
|
function poolScriptAddress(script) {
|
|
36
55
|
return (0, core_1.computeScriptAddress)(script);
|
|
37
56
|
}
|
|
38
|
-
function buildAndHashPoolScript(
|
|
39
|
-
const script = buildPoolScript(
|
|
57
|
+
function buildAndHashPoolScript(params) {
|
|
58
|
+
const script = buildPoolScript(params);
|
|
40
59
|
return { script, address: poolScriptAddress(script) };
|
|
41
60
|
}
|
package/dist/exit-tx.d.ts
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* VTXO exit transaction (RFC-038 V2).
|
|
2
|
+
* VTXO exit transaction (RFC-038 V2 — owner-unilateral).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
|
6
9
|
* (injected signer) and, when a chain provider is supplied, mined + broadcast
|
|
7
|
-
* (injected miner).
|
|
10
|
+
* (injected miner).
|
|
8
11
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* follow-on; this builds and broadcasts the transaction with the delay encoded.
|
|
12
|
+
* The caller must pass the owner signer; the operator cannot construct a valid
|
|
13
|
+
* exit witness for itself.
|
|
12
14
|
*/
|
|
13
15
|
import { type OmniaTxDraft } from '@totemsdk/omnia';
|
|
14
16
|
import type { ChainStateProvider } from '@totemsdk/chain-provider';
|
|
15
17
|
import type { OmniaVtxo } from './types.js';
|
|
16
|
-
/** State port carrying the exit delay. */
|
|
17
|
-
export declare const EXIT_DELAY_PORT =
|
|
18
|
+
/** State port carrying the exit delay on the owner's output. */
|
|
19
|
+
export declare const EXIT_DELAY_PORT = 110;
|
|
18
20
|
export interface ExitTxParams {
|
|
19
21
|
vtxo: OmniaVtxo;
|
|
20
22
|
/** The pool's on-chain coin (input). */
|
package/dist/exit-tx.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* VTXO exit transaction (RFC-038 V2).
|
|
2
|
+
* VTXO exit transaction (RFC-038 V2 — owner-unilateral).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
|
6
9
|
* (injected signer) and, when a chain provider is supplied, mined + broadcast
|
|
7
|
-
* (injected miner).
|
|
10
|
+
* (injected miner).
|
|
8
11
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* follow-on; this builds and broadcasts the transaction with the delay encoded.
|
|
12
|
+
* The caller must pass the owner signer; the operator cannot construct a valid
|
|
13
|
+
* exit witness for itself.
|
|
12
14
|
*/
|
|
13
15
|
import { computeOmniaTxDigest, omniaDraftToMinimaBytes, serializeTxDraft, } from '@totemsdk/omnia';
|
|
14
16
|
import { serializeTxBody, mineTxPoW, TX_POW_MIN_DIFFICULTY } from '@totemsdk/txpow';
|
|
15
17
|
import { concatBytes } from '@totemsdk/core';
|
|
16
|
-
/** State port carrying the exit delay. */
|
|
17
|
-
export const EXIT_DELAY_PORT =
|
|
18
|
+
/** State port carrying the exit delay on the owner's output. */
|
|
19
|
+
export const EXIT_DELAY_PORT = 110;
|
|
18
20
|
/** Draft that spends the pool coin to the VTXO owner with the exit delay encoded. */
|
|
19
21
|
export function buildExitTransactionDraft(params) {
|
|
20
22
|
const stateVariables = [
|
package/dist/pool-script.d.ts
CHANGED
|
@@ -1,19 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* VTXO pool on-chain script (RFC-038
|
|
2
|
+
* VTXO pool on-chain script (RFC-038 V2 — non-custodial).
|
|
3
3
|
*
|
|
4
|
-
* The pool is an on-chain state output holding `totalCapacity
|
|
5
|
-
* root is committed as STATE(100)
|
|
6
|
-
*
|
|
7
|
-
* old root cannot be re-anchored (RFC-038 §5 "stale/rolled-back root").
|
|
4
|
+
* The pool is an on-chain state output holding `totalCapacity`. An epoch's batch
|
|
5
|
+
* root is committed as STATE(100), the epoch number in STATE(101), and the
|
|
6
|
+
* owner's absolute exit-eligibility block in STATE(102).
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* Custody (RFC-044 / CUSTODY-POLICY §2):
|
|
9
|
+
* - **Unilateral owner exit.** After `@BLOCK GT EXITBLOCK` the owner alone may
|
|
10
|
+
* spend (`RETURN SIGNEDBY(OWNER)`) — no operator signature or liveness. This
|
|
11
|
+
* is exercised by the pre-signed exit tx (`exit-tx.ts`).
|
|
12
|
+
* - **Cooperative update.** Before the exit window the pool may only move by
|
|
13
|
+
* `MULTISIG(2 OPERATOR OWNER)`, epoch-monotone (`ASSERT EPOCH GT
|
|
14
|
+
* PREVSTATE(101)`), with the exit block immutable (`ASSERT SAMESTATE(102
|
|
15
|
+
* 102)`) so the operator can never push the owner's escape clock, and the
|
|
16
|
+
* value confined to the same script address (`ASSERT VERIFYOUT(@INPUT
|
|
17
|
+
* @ADDRESS @AMOUNT @TOKENID TRUE)`) so the operator can never pay itself.
|
|
18
|
+
*
|
|
19
|
+
* The operator holds no unilateral path: it cannot move funds, cannot advance
|
|
20
|
+
* the exit block, and cannot stop the owner's exit. It is a liveness-only peer.
|
|
12
21
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
22
|
+
export interface PoolScriptParams {
|
|
23
|
+
/** Operator WOTS public-key digest (64 hex). */
|
|
24
|
+
operatorPublicKeyDigest: string;
|
|
25
|
+
/** Pool owner WOTS public-key digest (64 hex). */
|
|
26
|
+
ownerPublicKeyDigest: string;
|
|
27
|
+
/** Absolute block height after which the owner may exit unilaterally. */
|
|
28
|
+
exitBlock: number;
|
|
29
|
+
}
|
|
30
|
+
/** Non-custodial, epoch-monotone, owner-escapable state-carrying pool script. */
|
|
31
|
+
export declare function buildPoolScript(params: PoolScriptParams): string;
|
|
15
32
|
export declare function poolScriptAddress(script: string): string;
|
|
16
|
-
export declare function buildAndHashPoolScript(
|
|
33
|
+
export declare function buildAndHashPoolScript(params: PoolScriptParams): {
|
|
17
34
|
script: string;
|
|
18
35
|
address: string;
|
|
19
36
|
};
|
package/dist/pool-script.js
CHANGED
|
@@ -1,36 +1,55 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* VTXO pool on-chain script (RFC-038
|
|
2
|
+
* VTXO pool on-chain script (RFC-038 V2 — non-custodial).
|
|
3
3
|
*
|
|
4
|
-
* The pool is an on-chain state output holding `totalCapacity
|
|
5
|
-
* root is committed as STATE(100)
|
|
6
|
-
*
|
|
7
|
-
* old root cannot be re-anchored (RFC-038 §5 "stale/rolled-back root").
|
|
4
|
+
* The pool is an on-chain state output holding `totalCapacity`. An epoch's batch
|
|
5
|
+
* root is committed as STATE(100), the epoch number in STATE(101), and the
|
|
6
|
+
* owner's absolute exit-eligibility block in STATE(102).
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* Custody (RFC-044 / CUSTODY-POLICY §2):
|
|
9
|
+
* - **Unilateral owner exit.** After `@BLOCK GT EXITBLOCK` the owner alone may
|
|
10
|
+
* spend (`RETURN SIGNEDBY(OWNER)`) — no operator signature or liveness. This
|
|
11
|
+
* is exercised by the pre-signed exit tx (`exit-tx.ts`).
|
|
12
|
+
* - **Cooperative update.** Before the exit window the pool may only move by
|
|
13
|
+
* `MULTISIG(2 OPERATOR OWNER)`, epoch-monotone (`ASSERT EPOCH GT
|
|
14
|
+
* PREVSTATE(101)`), with the exit block immutable (`ASSERT SAMESTATE(102
|
|
15
|
+
* 102)`) so the operator can never push the owner's escape clock, and the
|
|
16
|
+
* value confined to the same script address (`ASSERT VERIFYOUT(@INPUT
|
|
17
|
+
* @ADDRESS @AMOUNT @TOKENID TRUE)`) so the operator can never pay itself.
|
|
18
|
+
*
|
|
19
|
+
* The operator holds no unilateral path: it cannot move funds, cannot advance
|
|
20
|
+
* the exit block, and cannot stop the owner's exit. It is a liveness-only peer.
|
|
12
21
|
*/
|
|
13
22
|
import { computeScriptAddress } from '@totemsdk/core';
|
|
14
|
-
function
|
|
15
|
-
const raw =
|
|
23
|
+
function pkHex(digest, label) {
|
|
24
|
+
const raw = digest.replace(/^0x/i, '');
|
|
16
25
|
if (!/^[0-9a-fA-F]{64}$/.test(raw)) {
|
|
17
|
-
throw new Error(
|
|
26
|
+
throw new Error(`${label} public key digest must be 64 hex chars, got ${JSON.stringify(digest)}`);
|
|
18
27
|
}
|
|
19
28
|
return '0X' + raw.toUpperCase();
|
|
20
29
|
}
|
|
21
|
-
/**
|
|
22
|
-
export function buildPoolScript(
|
|
30
|
+
/** Non-custodial, epoch-monotone, owner-escapable state-carrying pool script. */
|
|
31
|
+
export function buildPoolScript(params) {
|
|
32
|
+
const owner = pkHex(params.ownerPublicKeyDigest, 'owner');
|
|
33
|
+
const operator = pkHex(params.operatorPublicKeyDigest, 'operator');
|
|
34
|
+
if (!Number.isInteger(params.exitBlock) || params.exitBlock < 0) {
|
|
35
|
+
throw new Error(`exitBlock must be a non-negative integer, got ${JSON.stringify(params.exitBlock)}`);
|
|
36
|
+
}
|
|
23
37
|
return [
|
|
24
|
-
'LET ROOT=STATE(100)',
|
|
25
38
|
'LET EPOCH=STATE(101)',
|
|
39
|
+
'LET EXITBLOCK=STATE(102)',
|
|
40
|
+
'IF @BLOCK GT EXITBLOCK THEN',
|
|
41
|
+
` RETURN SIGNEDBY(${owner})`,
|
|
42
|
+
'ENDIF',
|
|
26
43
|
'ASSERT EPOCH GT PREVSTATE(101)',
|
|
27
|
-
|
|
44
|
+
'ASSERT SAMESTATE(102 102)',
|
|
45
|
+
'ASSERT VERIFYOUT(@INPUT @ADDRESS @AMOUNT @TOKENID TRUE)',
|
|
46
|
+
`RETURN MULTISIG(2 ${operator} ${owner})`,
|
|
28
47
|
].join('\n');
|
|
29
48
|
}
|
|
30
49
|
export function poolScriptAddress(script) {
|
|
31
50
|
return computeScriptAddress(script);
|
|
32
51
|
}
|
|
33
|
-
export function buildAndHashPoolScript(
|
|
34
|
-
const script = buildPoolScript(
|
|
52
|
+
export function buildAndHashPoolScript(params) {
|
|
53
|
+
const script = buildPoolScript(params);
|
|
35
54
|
return { script, address: poolScriptAddress(script) };
|
|
36
55
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@totemsdk/omnia-vtxo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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",
|
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@totemsdk/chain-provider": "^0.2.1",
|
|
28
|
-
"@totemsdk/omnia": "^
|
|
29
|
-
"@totemsdk/core": "^1.2.11",
|
|
28
|
+
"@totemsdk/omnia": "^2.0.0",
|
|
30
29
|
"@totemsdk/storage": "^0.1.0",
|
|
31
|
-
"@totemsdk/
|
|
32
|
-
"@totemsdk/
|
|
30
|
+
"@totemsdk/txpow": "^1.0.2",
|
|
31
|
+
"@totemsdk/core": "^1.2.11",
|
|
32
|
+
"@totemsdk/wots-lease": "^1.0.1"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/jest": "^30.0.0",
|