@totemsdk/omnia-vtxo 0.6.2 → 0.7.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.
@@ -5,6 +5,7 @@ export * from './commitment.js';
5
5
  export * from './operator.js';
6
6
  export * from './receipt.js';
7
7
  export * from './pool-script.js';
8
+ export * from './owner-pool.js';
8
9
  export * from './anchor.js';
9
10
  export * from './exit-tx.js';
10
11
  export * from './pool.js';
package/dist/cjs/index.js CHANGED
@@ -21,6 +21,7 @@ __exportStar(require("./commitment.js"), exports);
21
21
  __exportStar(require("./operator.js"), exports);
22
22
  __exportStar(require("./receipt.js"), exports);
23
23
  __exportStar(require("./pool-script.js"), exports);
24
+ __exportStar(require("./owner-pool.js"), exports);
24
25
  __exportStar(require("./anchor.js"), exports);
25
26
  __exportStar(require("./exit-tx.js"), exports);
26
27
  __exportStar(require("./pool.js"), exports);
@@ -0,0 +1,95 @@
1
+ /**
2
+ * V1 transferable owner-pool covenant.
3
+ *
4
+ * A single-owner claim coin whose **current owner is committed in state**, not
5
+ * in the script. The owner can:
6
+ *
7
+ * - transfer the whole coin by signing a state update that recreates it at
8
+ * the same address/amount with a new owner (epoch increments); or
9
+ * - exit after `exitBlock` to the committed owner address, with no operator
10
+ * signature or liveness.
11
+ *
12
+ * The script authenticates the **previous** committed owner
13
+ * (`SIGNEDBY(PREVSTATE(103))`), so a spender cannot substitute their own key.
14
+ * `PREVSTATE(104)` is the committed payout address. This is the V1 recovery
15
+ * path: a recipient holds a coin they can spend unilaterally.
16
+ */
17
+ import type { OmniaTxDraft, StateValue } from '@totemsdk/omnia';
18
+ export declare const OWNER_POOL_ROOT_PORT = 100;
19
+ export declare const OWNER_POOL_EPOCH_PORT = 101;
20
+ export declare const OWNER_POOL_EXIT_BLOCK_PORT = 102;
21
+ export declare const OWNER_POOL_PK_PORT = 103;
22
+ export declare const OWNER_POOL_ADDR_PORT = 104;
23
+ export interface OwnerPoolState {
24
+ /** Batch/root commitment (opaque to this covenant; owner cannot change it). */
25
+ root: string;
26
+ /** Monotonic state epoch; every transfer increments it. */
27
+ epoch: number;
28
+ /** Absolute block after which the committed owner may exit unilaterally. */
29
+ exitBlock: number;
30
+ /** Current owner public-key digest (32-byte hex, `0x`-prefixed). */
31
+ ownerPk: string;
32
+ /** Current owner payout address (derived from `ownerPk` by the transfer builder). */
33
+ ownerAddr: string;
34
+ }
35
+ /** Derive the committed payout address from an owner public-key digest. */
36
+ export declare function ownerAddressFor(ownerPk: string): string;
37
+ /**
38
+ * The transferable owner-pool script. State ports 100–104 are the only state;
39
+ * there are no fixed holder constants.
40
+ */
41
+ export declare function buildOwnerPoolScript(): string;
42
+ export declare function buildOwnerPoolState(state: OwnerPoolState): StateValue[];
43
+ export declare function ownerPoolAddress(): string;
44
+ export interface OwnerPoolCoin {
45
+ coinId: string;
46
+ address: string;
47
+ amount: bigint;
48
+ tokenId: string;
49
+ scriptHex?: string;
50
+ mmrEntry?: bigint;
51
+ }
52
+ export interface OwnerTransferParams {
53
+ /** The coin being transferred (input). */
54
+ coin: OwnerPoolCoin;
55
+ /** The previously committed state (must match the input coin's state). */
56
+ from: OwnerPoolState;
57
+ /** The new owner. `epoch` must be greater than `from.epoch`. */
58
+ to: OwnerPoolState;
59
+ }
60
+ /**
61
+ * Build the transfer transaction: same script address, same amount/token,
62
+ * new owner state and a higher epoch.
63
+ */
64
+ export declare function buildOwnerTransferDraft(params: OwnerTransferParams): OmniaTxDraft;
65
+ export interface OwnerExitParams {
66
+ coin: OwnerPoolCoin;
67
+ state: OwnerPoolState;
68
+ }
69
+ /** Build the unilateral exit: full amount to the committed owner address. */
70
+ export declare function buildOwnerExitDraft(params: OwnerExitParams): OmniaTxDraft;
71
+ import type { ChainStateProvider } from '@totemsdk/chain-provider';
72
+ export interface OwnerPoolTxDeps {
73
+ /** Sign the transaction digest with the previous (transfer) or current (exit) owner key. */
74
+ signDigest: (digest: Uint8Array) => Promise<Uint8Array>;
75
+ coinProofs?: Uint8Array[];
76
+ scriptProofs?: Uint8Array[];
77
+ chainProvider?: ChainStateProvider;
78
+ mine?: (txBody: Uint8Array, difficulty: Uint8Array) => Promise<{
79
+ minedHeaderBytes: Uint8Array;
80
+ txpowId: Uint8Array;
81
+ }>;
82
+ }
83
+ export interface OwnerPoolTxResult {
84
+ kind: 'owner-transfer' | 'owner-exit';
85
+ txHex: string;
86
+ digest: string;
87
+ witnessHex: string;
88
+ txpowId?: string;
89
+ /** The state committed by the new pool output (transfer) or spent state (exit). */
90
+ state: OwnerPoolState;
91
+ }
92
+ /** Sign and assemble the owner-rotation transfer transaction. */
93
+ export declare function buildOwnerTransferTransaction(params: OwnerTransferParams, deps: OwnerPoolTxDeps): Promise<OwnerPoolTxResult>;
94
+ /** Sign and assemble the unilateral owner exit transaction. */
95
+ export declare function buildOwnerExitTransaction(params: OwnerExitParams, deps: OwnerPoolTxDeps): Promise<OwnerPoolTxResult>;
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ /**
3
+ * V1 transferable owner-pool covenant.
4
+ *
5
+ * A single-owner claim coin whose **current owner is committed in state**, not
6
+ * in the script. The owner can:
7
+ *
8
+ * - transfer the whole coin by signing a state update that recreates it at
9
+ * the same address/amount with a new owner (epoch increments); or
10
+ * - exit after `exitBlock` to the committed owner address, with no operator
11
+ * signature or liveness.
12
+ *
13
+ * The script authenticates the **previous** committed owner
14
+ * (`SIGNEDBY(PREVSTATE(103))`), so a spender cannot substitute their own key.
15
+ * `PREVSTATE(104)` is the committed payout address. This is the V1 recovery
16
+ * path: a recipient holds a coin they can spend unilaterally.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.OWNER_POOL_ADDR_PORT = exports.OWNER_POOL_PK_PORT = exports.OWNER_POOL_EXIT_BLOCK_PORT = exports.OWNER_POOL_EPOCH_PORT = exports.OWNER_POOL_ROOT_PORT = void 0;
20
+ exports.ownerAddressFor = ownerAddressFor;
21
+ exports.buildOwnerPoolScript = buildOwnerPoolScript;
22
+ exports.buildOwnerPoolState = buildOwnerPoolState;
23
+ exports.ownerPoolAddress = ownerPoolAddress;
24
+ exports.buildOwnerTransferDraft = buildOwnerTransferDraft;
25
+ exports.buildOwnerExitDraft = buildOwnerExitDraft;
26
+ exports.buildOwnerTransferTransaction = buildOwnerTransferTransaction;
27
+ exports.buildOwnerExitTransaction = buildOwnerExitTransaction;
28
+ const core_1 = require("@totemsdk/core");
29
+ exports.OWNER_POOL_ROOT_PORT = 100;
30
+ exports.OWNER_POOL_EPOCH_PORT = 101;
31
+ exports.OWNER_POOL_EXIT_BLOCK_PORT = 102;
32
+ exports.OWNER_POOL_PK_PORT = 103;
33
+ exports.OWNER_POOL_ADDR_PORT = 104;
34
+ /** Derive the committed payout address from an owner public-key digest. */
35
+ function ownerAddressFor(ownerPk) {
36
+ return (0, core_1.wotsAddressData)(ownerPk);
37
+ }
38
+ /**
39
+ * The transferable owner-pool script. State ports 100–104 are the only state;
40
+ * there are no fixed holder constants.
41
+ */
42
+ function buildOwnerPoolScript() {
43
+ return (0, core_1.toNodeScript)([
44
+ // Unilateral exit: only the previously committed owner, only after the
45
+ // committed block, and only to the committed owner address.
46
+ 'IF @BLOCK GT PREVSTATE(102) THEN',
47
+ ' ASSERT SIGNEDBY(PREVSTATE(103))',
48
+ ' ASSERT VERIFYOUT(0 PREVSTATE(104) @AMOUNT @TOKENID FALSE)',
49
+ ' RETURN TRUE',
50
+ 'ENDIF',
51
+ // Transfer: the previous owner authorizes a new owner. Root and exit block
52
+ // are immutable; the coin stays at the same address with the same amount.
53
+ 'ASSERT SIGNEDBY(PREVSTATE(103))',
54
+ 'ASSERT STATE(101) GT PREVSTATE(101)',
55
+ 'ASSERT STATE(100) EQ PREVSTATE(100)',
56
+ 'ASSERT STATE(102) EQ PREVSTATE(102)',
57
+ 'ASSERT STATE(103) NEQ PREVSTATE(103)',
58
+ 'ASSERT STATE(103) NEQ 0x00',
59
+ 'ASSERT STATE(104) NEQ 0x00',
60
+ 'ASSERT VERIFYOUT(0 @ADDRESS @AMOUNT @TOKENID TRUE)',
61
+ 'RETURN TRUE',
62
+ ].join('\n'));
63
+ }
64
+ function buildOwnerPoolState(state) {
65
+ if (!Number.isInteger(state.exitBlock) || state.exitBlock < 0) {
66
+ throw new Error(`owner pool: invalid exitBlock ${String(state.exitBlock)}`);
67
+ }
68
+ if (!/^0x[0-9a-fA-F]{64}$/.test(state.ownerPk)) {
69
+ throw new Error('owner pool: ownerPk must be a 0x-prefixed 32-byte hex digest');
70
+ }
71
+ if (!state.ownerAddr)
72
+ throw new Error('owner pool: ownerAddr is required');
73
+ return [
74
+ { port: exports.OWNER_POOL_ROOT_PORT, value: state.root, type: 'string' },
75
+ { port: exports.OWNER_POOL_EPOCH_PORT, value: BigInt(state.epoch), type: 'number' },
76
+ { port: exports.OWNER_POOL_EXIT_BLOCK_PORT, value: BigInt(state.exitBlock), type: 'number' },
77
+ { port: exports.OWNER_POOL_PK_PORT, value: state.ownerPk, type: 'string' },
78
+ { port: exports.OWNER_POOL_ADDR_PORT, value: state.ownerAddr, type: 'string' },
79
+ ];
80
+ }
81
+ function ownerPoolAddress() {
82
+ return (0, core_1.computeScriptAddress)(buildOwnerPoolScript());
83
+ }
84
+ /**
85
+ * Build the transfer transaction: same script address, same amount/token,
86
+ * new owner state and a higher epoch.
87
+ */
88
+ function buildOwnerTransferDraft(params) {
89
+ if (params.to.epoch <= params.from.epoch) {
90
+ throw new Error('owner transfer: new epoch must be greater than the previous epoch');
91
+ }
92
+ if (params.to.exitBlock !== params.from.exitBlock) {
93
+ throw new Error('owner transfer: exitBlock is immutable');
94
+ }
95
+ if (params.to.ownerAddr !== ownerAddressFor(params.to.ownerPk)) {
96
+ throw new Error('owner transfer: ownerAddr must be derived from ownerPk');
97
+ }
98
+ return {
99
+ type: 'settlement',
100
+ inputs: [{
101
+ coinId: params.coin.coinId,
102
+ address: params.coin.address,
103
+ amount: params.coin.amount,
104
+ tokenId: params.coin.tokenId,
105
+ scriptHex: params.coin.scriptHex ?? buildOwnerPoolScript(),
106
+ ...(params.coin.mmrEntry !== undefined ? { mmrEntry: params.coin.mmrEntry } : {}),
107
+ }],
108
+ outputs: [{
109
+ address: params.coin.address,
110
+ amount: params.coin.amount,
111
+ tokenId: params.coin.tokenId,
112
+ storeState: true,
113
+ stateVariables: buildOwnerPoolState(params.to),
114
+ }],
115
+ storeState: true,
116
+ stateVariables: buildOwnerPoolState(params.to),
117
+ };
118
+ }
119
+ /** Build the unilateral exit: full amount to the committed owner address. */
120
+ function buildOwnerExitDraft(params) {
121
+ if (params.state.ownerAddr !== ownerAddressFor(params.state.ownerPk)) {
122
+ throw new Error('owner exit: ownerAddr must be derived from ownerPk');
123
+ }
124
+ return {
125
+ type: 'settlement',
126
+ inputs: [{
127
+ coinId: params.coin.coinId,
128
+ address: params.coin.address,
129
+ amount: params.coin.amount,
130
+ tokenId: params.coin.tokenId,
131
+ scriptHex: params.coin.scriptHex ?? buildOwnerPoolScript(),
132
+ ...(params.coin.mmrEntry !== undefined ? { mmrEntry: params.coin.mmrEntry } : {}),
133
+ }],
134
+ outputs: [{
135
+ address: params.state.ownerAddr,
136
+ amount: params.coin.amount,
137
+ tokenId: params.coin.tokenId,
138
+ storeState: false,
139
+ stateVariables: [],
140
+ }],
141
+ storeState: false,
142
+ stateVariables: [],
143
+ };
144
+ }
145
+ // ── Signed transaction assembly ───────────────────────────────────────────────
146
+ const omnia_1 = require("@totemsdk/omnia");
147
+ const core_2 = require("@totemsdk/core");
148
+ const txpow_1 = require("@totemsdk/txpow");
149
+ async function assembleOwnerPoolTx(draft, kind, state, deps) {
150
+ const digest = (0, omnia_1.computeOmniaTxDigest)(draft);
151
+ const signature = await deps.signDigest(digest);
152
+ const witness = (0, omnia_1.serializeOmniaWitness)({
153
+ signatures: [signature],
154
+ coinProofs: deps.coinProofs ?? [],
155
+ scriptProofs: deps.scriptProofs ?? [],
156
+ });
157
+ const result = {
158
+ kind,
159
+ txHex: (0, omnia_1.serializeTxDraft)(draft),
160
+ digest: Buffer.from(digest).toString('hex'),
161
+ witnessHex: Buffer.from(witness).toString('hex'),
162
+ state,
163
+ };
164
+ if (deps.chainProvider) {
165
+ const draftBytes = (0, omnia_1.omniaDraftToMinimaBytes)(draft);
166
+ if (typeof deps.chainProvider.submitTransaction === 'function') {
167
+ const submitted = await deps.chainProvider.submitTransaction(draftBytes, witness);
168
+ if (submitted?.success === false) {
169
+ throw new Error(`owner-pool submit failed: ${submitted.message ?? 'unknown error'}`);
170
+ }
171
+ result.txpowId = submitted?.txpowid;
172
+ }
173
+ else {
174
+ const mine = deps.mine ?? ((txBody, difficulty) => (0, txpow_1.mineTxPoW)(txBody, difficulty));
175
+ const txBody = (0, txpow_1.serializeTxBody)(draftBytes, witness);
176
+ const mined = await mine(txBody, txpow_1.TX_POW_MIN_DIFFICULTY);
177
+ const fullTxPoW = (0, core_2.concatBytes)((0, core_2.concatBytes)(mined.minedHeaderBytes, new Uint8Array([0x01])), txBody);
178
+ const broadcast = await deps.chainProvider.broadcastTxPoW(Buffer.from(fullTxPoW).toString('hex'));
179
+ if (broadcast?.success === false) {
180
+ throw new Error(`owner-pool broadcast failed: ${broadcast.message ?? 'unknown error'}`);
181
+ }
182
+ result.txpowId = broadcast?.txpowid ?? Buffer.from(mined.txpowId).toString('hex');
183
+ }
184
+ }
185
+ return result;
186
+ }
187
+ /** Sign and assemble the owner-rotation transfer transaction. */
188
+ async function buildOwnerTransferTransaction(params, deps) {
189
+ return assembleOwnerPoolTx(buildOwnerTransferDraft(params), 'owner-transfer', params.to, deps);
190
+ }
191
+ /** Sign and assemble the unilateral owner exit transaction. */
192
+ async function buildOwnerExitTransaction(params, deps) {
193
+ return assembleOwnerPoolTx(buildOwnerExitDraft(params), 'owner-exit', params.state, deps);
194
+ }
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export * from './commitment.js';
5
5
  export * from './operator.js';
6
6
  export * from './receipt.js';
7
7
  export * from './pool-script.js';
8
+ export * from './owner-pool.js';
8
9
  export * from './anchor.js';
9
10
  export * from './exit-tx.js';
10
11
  export * from './pool.js';
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ export * from './commitment.js';
5
5
  export * from './operator.js';
6
6
  export * from './receipt.js';
7
7
  export * from './pool-script.js';
8
+ export * from './owner-pool.js';
8
9
  export * from './anchor.js';
9
10
  export * from './exit-tx.js';
10
11
  export * from './pool.js';
@@ -0,0 +1,95 @@
1
+ /**
2
+ * V1 transferable owner-pool covenant.
3
+ *
4
+ * A single-owner claim coin whose **current owner is committed in state**, not
5
+ * in the script. The owner can:
6
+ *
7
+ * - transfer the whole coin by signing a state update that recreates it at
8
+ * the same address/amount with a new owner (epoch increments); or
9
+ * - exit after `exitBlock` to the committed owner address, with no operator
10
+ * signature or liveness.
11
+ *
12
+ * The script authenticates the **previous** committed owner
13
+ * (`SIGNEDBY(PREVSTATE(103))`), so a spender cannot substitute their own key.
14
+ * `PREVSTATE(104)` is the committed payout address. This is the V1 recovery
15
+ * path: a recipient holds a coin they can spend unilaterally.
16
+ */
17
+ import type { OmniaTxDraft, StateValue } from '@totemsdk/omnia';
18
+ export declare const OWNER_POOL_ROOT_PORT = 100;
19
+ export declare const OWNER_POOL_EPOCH_PORT = 101;
20
+ export declare const OWNER_POOL_EXIT_BLOCK_PORT = 102;
21
+ export declare const OWNER_POOL_PK_PORT = 103;
22
+ export declare const OWNER_POOL_ADDR_PORT = 104;
23
+ export interface OwnerPoolState {
24
+ /** Batch/root commitment (opaque to this covenant; owner cannot change it). */
25
+ root: string;
26
+ /** Monotonic state epoch; every transfer increments it. */
27
+ epoch: number;
28
+ /** Absolute block after which the committed owner may exit unilaterally. */
29
+ exitBlock: number;
30
+ /** Current owner public-key digest (32-byte hex, `0x`-prefixed). */
31
+ ownerPk: string;
32
+ /** Current owner payout address (derived from `ownerPk` by the transfer builder). */
33
+ ownerAddr: string;
34
+ }
35
+ /** Derive the committed payout address from an owner public-key digest. */
36
+ export declare function ownerAddressFor(ownerPk: string): string;
37
+ /**
38
+ * The transferable owner-pool script. State ports 100–104 are the only state;
39
+ * there are no fixed holder constants.
40
+ */
41
+ export declare function buildOwnerPoolScript(): string;
42
+ export declare function buildOwnerPoolState(state: OwnerPoolState): StateValue[];
43
+ export declare function ownerPoolAddress(): string;
44
+ export interface OwnerPoolCoin {
45
+ coinId: string;
46
+ address: string;
47
+ amount: bigint;
48
+ tokenId: string;
49
+ scriptHex?: string;
50
+ mmrEntry?: bigint;
51
+ }
52
+ export interface OwnerTransferParams {
53
+ /** The coin being transferred (input). */
54
+ coin: OwnerPoolCoin;
55
+ /** The previously committed state (must match the input coin's state). */
56
+ from: OwnerPoolState;
57
+ /** The new owner. `epoch` must be greater than `from.epoch`. */
58
+ to: OwnerPoolState;
59
+ }
60
+ /**
61
+ * Build the transfer transaction: same script address, same amount/token,
62
+ * new owner state and a higher epoch.
63
+ */
64
+ export declare function buildOwnerTransferDraft(params: OwnerTransferParams): OmniaTxDraft;
65
+ export interface OwnerExitParams {
66
+ coin: OwnerPoolCoin;
67
+ state: OwnerPoolState;
68
+ }
69
+ /** Build the unilateral exit: full amount to the committed owner address. */
70
+ export declare function buildOwnerExitDraft(params: OwnerExitParams): OmniaTxDraft;
71
+ import type { ChainStateProvider } from '@totemsdk/chain-provider';
72
+ export interface OwnerPoolTxDeps {
73
+ /** Sign the transaction digest with the previous (transfer) or current (exit) owner key. */
74
+ signDigest: (digest: Uint8Array) => Promise<Uint8Array>;
75
+ coinProofs?: Uint8Array[];
76
+ scriptProofs?: Uint8Array[];
77
+ chainProvider?: ChainStateProvider;
78
+ mine?: (txBody: Uint8Array, difficulty: Uint8Array) => Promise<{
79
+ minedHeaderBytes: Uint8Array;
80
+ txpowId: Uint8Array;
81
+ }>;
82
+ }
83
+ export interface OwnerPoolTxResult {
84
+ kind: 'owner-transfer' | 'owner-exit';
85
+ txHex: string;
86
+ digest: string;
87
+ witnessHex: string;
88
+ txpowId?: string;
89
+ /** The state committed by the new pool output (transfer) or spent state (exit). */
90
+ state: OwnerPoolState;
91
+ }
92
+ /** Sign and assemble the owner-rotation transfer transaction. */
93
+ export declare function buildOwnerTransferTransaction(params: OwnerTransferParams, deps: OwnerPoolTxDeps): Promise<OwnerPoolTxResult>;
94
+ /** Sign and assemble the unilateral owner exit transaction. */
95
+ export declare function buildOwnerExitTransaction(params: OwnerExitParams, deps: OwnerPoolTxDeps): Promise<OwnerPoolTxResult>;
@@ -0,0 +1,183 @@
1
+ /**
2
+ * V1 transferable owner-pool covenant.
3
+ *
4
+ * A single-owner claim coin whose **current owner is committed in state**, not
5
+ * in the script. The owner can:
6
+ *
7
+ * - transfer the whole coin by signing a state update that recreates it at
8
+ * the same address/amount with a new owner (epoch increments); or
9
+ * - exit after `exitBlock` to the committed owner address, with no operator
10
+ * signature or liveness.
11
+ *
12
+ * The script authenticates the **previous** committed owner
13
+ * (`SIGNEDBY(PREVSTATE(103))`), so a spender cannot substitute their own key.
14
+ * `PREVSTATE(104)` is the committed payout address. This is the V1 recovery
15
+ * path: a recipient holds a coin they can spend unilaterally.
16
+ */
17
+ import { computeScriptAddress, toNodeScript, wotsAddressData } from '@totemsdk/core';
18
+ export const OWNER_POOL_ROOT_PORT = 100;
19
+ export const OWNER_POOL_EPOCH_PORT = 101;
20
+ export const OWNER_POOL_EXIT_BLOCK_PORT = 102;
21
+ export const OWNER_POOL_PK_PORT = 103;
22
+ export const OWNER_POOL_ADDR_PORT = 104;
23
+ /** Derive the committed payout address from an owner public-key digest. */
24
+ export function ownerAddressFor(ownerPk) {
25
+ return wotsAddressData(ownerPk);
26
+ }
27
+ /**
28
+ * The transferable owner-pool script. State ports 100–104 are the only state;
29
+ * there are no fixed holder constants.
30
+ */
31
+ export function buildOwnerPoolScript() {
32
+ return toNodeScript([
33
+ // Unilateral exit: only the previously committed owner, only after the
34
+ // committed block, and only to the committed owner address.
35
+ 'IF @BLOCK GT PREVSTATE(102) THEN',
36
+ ' ASSERT SIGNEDBY(PREVSTATE(103))',
37
+ ' ASSERT VERIFYOUT(0 PREVSTATE(104) @AMOUNT @TOKENID FALSE)',
38
+ ' RETURN TRUE',
39
+ 'ENDIF',
40
+ // Transfer: the previous owner authorizes a new owner. Root and exit block
41
+ // are immutable; the coin stays at the same address with the same amount.
42
+ 'ASSERT SIGNEDBY(PREVSTATE(103))',
43
+ 'ASSERT STATE(101) GT PREVSTATE(101)',
44
+ 'ASSERT STATE(100) EQ PREVSTATE(100)',
45
+ 'ASSERT STATE(102) EQ PREVSTATE(102)',
46
+ 'ASSERT STATE(103) NEQ PREVSTATE(103)',
47
+ 'ASSERT STATE(103) NEQ 0x00',
48
+ 'ASSERT STATE(104) NEQ 0x00',
49
+ 'ASSERT VERIFYOUT(0 @ADDRESS @AMOUNT @TOKENID TRUE)',
50
+ 'RETURN TRUE',
51
+ ].join('\n'));
52
+ }
53
+ export function buildOwnerPoolState(state) {
54
+ if (!Number.isInteger(state.exitBlock) || state.exitBlock < 0) {
55
+ throw new Error(`owner pool: invalid exitBlock ${String(state.exitBlock)}`);
56
+ }
57
+ if (!/^0x[0-9a-fA-F]{64}$/.test(state.ownerPk)) {
58
+ throw new Error('owner pool: ownerPk must be a 0x-prefixed 32-byte hex digest');
59
+ }
60
+ if (!state.ownerAddr)
61
+ throw new Error('owner pool: ownerAddr is required');
62
+ return [
63
+ { port: OWNER_POOL_ROOT_PORT, value: state.root, type: 'string' },
64
+ { port: OWNER_POOL_EPOCH_PORT, value: BigInt(state.epoch), type: 'number' },
65
+ { port: OWNER_POOL_EXIT_BLOCK_PORT, value: BigInt(state.exitBlock), type: 'number' },
66
+ { port: OWNER_POOL_PK_PORT, value: state.ownerPk, type: 'string' },
67
+ { port: OWNER_POOL_ADDR_PORT, value: state.ownerAddr, type: 'string' },
68
+ ];
69
+ }
70
+ export function ownerPoolAddress() {
71
+ return computeScriptAddress(buildOwnerPoolScript());
72
+ }
73
+ /**
74
+ * Build the transfer transaction: same script address, same amount/token,
75
+ * new owner state and a higher epoch.
76
+ */
77
+ export function buildOwnerTransferDraft(params) {
78
+ if (params.to.epoch <= params.from.epoch) {
79
+ throw new Error('owner transfer: new epoch must be greater than the previous epoch');
80
+ }
81
+ if (params.to.exitBlock !== params.from.exitBlock) {
82
+ throw new Error('owner transfer: exitBlock is immutable');
83
+ }
84
+ if (params.to.ownerAddr !== ownerAddressFor(params.to.ownerPk)) {
85
+ throw new Error('owner transfer: ownerAddr must be derived from ownerPk');
86
+ }
87
+ return {
88
+ type: 'settlement',
89
+ inputs: [{
90
+ coinId: params.coin.coinId,
91
+ address: params.coin.address,
92
+ amount: params.coin.amount,
93
+ tokenId: params.coin.tokenId,
94
+ scriptHex: params.coin.scriptHex ?? buildOwnerPoolScript(),
95
+ ...(params.coin.mmrEntry !== undefined ? { mmrEntry: params.coin.mmrEntry } : {}),
96
+ }],
97
+ outputs: [{
98
+ address: params.coin.address,
99
+ amount: params.coin.amount,
100
+ tokenId: params.coin.tokenId,
101
+ storeState: true,
102
+ stateVariables: buildOwnerPoolState(params.to),
103
+ }],
104
+ storeState: true,
105
+ stateVariables: buildOwnerPoolState(params.to),
106
+ };
107
+ }
108
+ /** Build the unilateral exit: full amount to the committed owner address. */
109
+ export function buildOwnerExitDraft(params) {
110
+ if (params.state.ownerAddr !== ownerAddressFor(params.state.ownerPk)) {
111
+ throw new Error('owner exit: ownerAddr must be derived from ownerPk');
112
+ }
113
+ return {
114
+ type: 'settlement',
115
+ inputs: [{
116
+ coinId: params.coin.coinId,
117
+ address: params.coin.address,
118
+ amount: params.coin.amount,
119
+ tokenId: params.coin.tokenId,
120
+ scriptHex: params.coin.scriptHex ?? buildOwnerPoolScript(),
121
+ ...(params.coin.mmrEntry !== undefined ? { mmrEntry: params.coin.mmrEntry } : {}),
122
+ }],
123
+ outputs: [{
124
+ address: params.state.ownerAddr,
125
+ amount: params.coin.amount,
126
+ tokenId: params.coin.tokenId,
127
+ storeState: false,
128
+ stateVariables: [],
129
+ }],
130
+ storeState: false,
131
+ stateVariables: [],
132
+ };
133
+ }
134
+ // ── Signed transaction assembly ───────────────────────────────────────────────
135
+ import { computeOmniaTxDigest, omniaDraftToMinimaBytes, serializeOmniaWitness, serializeTxDraft, } from '@totemsdk/omnia';
136
+ import { concatBytes } from '@totemsdk/core';
137
+ import { serializeTxBody, mineTxPoW, TX_POW_MIN_DIFFICULTY } from '@totemsdk/txpow';
138
+ async function assembleOwnerPoolTx(draft, kind, state, deps) {
139
+ const digest = computeOmniaTxDigest(draft);
140
+ const signature = await deps.signDigest(digest);
141
+ const witness = serializeOmniaWitness({
142
+ signatures: [signature],
143
+ coinProofs: deps.coinProofs ?? [],
144
+ scriptProofs: deps.scriptProofs ?? [],
145
+ });
146
+ const result = {
147
+ kind,
148
+ txHex: serializeTxDraft(draft),
149
+ digest: Buffer.from(digest).toString('hex'),
150
+ witnessHex: Buffer.from(witness).toString('hex'),
151
+ state,
152
+ };
153
+ if (deps.chainProvider) {
154
+ const draftBytes = omniaDraftToMinimaBytes(draft);
155
+ if (typeof deps.chainProvider.submitTransaction === 'function') {
156
+ const submitted = await deps.chainProvider.submitTransaction(draftBytes, witness);
157
+ if (submitted?.success === false) {
158
+ throw new Error(`owner-pool submit failed: ${submitted.message ?? 'unknown error'}`);
159
+ }
160
+ result.txpowId = submitted?.txpowid;
161
+ }
162
+ else {
163
+ const mine = deps.mine ?? ((txBody, difficulty) => mineTxPoW(txBody, difficulty));
164
+ const txBody = serializeTxBody(draftBytes, witness);
165
+ const mined = await mine(txBody, TX_POW_MIN_DIFFICULTY);
166
+ const fullTxPoW = concatBytes(concatBytes(mined.minedHeaderBytes, new Uint8Array([0x01])), txBody);
167
+ const broadcast = await deps.chainProvider.broadcastTxPoW(Buffer.from(fullTxPoW).toString('hex'));
168
+ if (broadcast?.success === false) {
169
+ throw new Error(`owner-pool broadcast failed: ${broadcast.message ?? 'unknown error'}`);
170
+ }
171
+ result.txpowId = broadcast?.txpowid ?? Buffer.from(mined.txpowId).toString('hex');
172
+ }
173
+ }
174
+ return result;
175
+ }
176
+ /** Sign and assemble the owner-rotation transfer transaction. */
177
+ export async function buildOwnerTransferTransaction(params, deps) {
178
+ return assembleOwnerPoolTx(buildOwnerTransferDraft(params), 'owner-transfer', params.to, deps);
179
+ }
180
+ /** Sign and assemble the unilateral owner exit transaction. */
181
+ export async function buildOwnerExitTransaction(params, deps) {
182
+ return assembleOwnerPoolTx(buildOwnerExitDraft(params), 'owner-exit', params.state, deps);
183
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@totemsdk/omnia-vtxo",
3
- "version": "0.6.2",
3
+ "version": "0.7.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
  "NOTICE"
26
26
  ],
27
27
  "dependencies": {
28
+ "@totemsdk/chain-provider": "^0.3.0",
28
29
  "@totemsdk/core": "^1.4.0",
30
+ "@totemsdk/txpow": "^1.1.0",
29
31
  "@totemsdk/omnia": "^2.3.2",
30
32
  "@totemsdk/storage": "^0.2.1",
31
- "@totemsdk/txpow": "^1.1.0",
32
- "@totemsdk/chain-provider": "^0.3.0",
33
33
  "@totemsdk/wots-lease": "^1.0.1"
34
34
  },
35
35
  "devDependencies": {
@@ -37,7 +37,8 @@
37
37
  "@types/node": "^20.0.0",
38
38
  "jest": "^30.4.2",
39
39
  "ts-jest": "^29.0.0",
40
- "typescript": "^7.0.2"
40
+ "typescript": "^7.0.2",
41
+ "@totemsdk/kissvm": "^1.3.0"
41
42
  },
42
43
  "engines": {
43
44
  "node": ">=18.0.0"