@satora/escrow 0.0.1-alpha.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/2-of-2/ark-contract.d.ts +29 -0
- package/dist/2-of-2/ark-contract.d.ts.map +1 -0
- package/dist/2-of-2/ark-contract.js +49 -0
- package/dist/2-of-2/ark-contract.js.map +1 -0
- package/dist/2-of-2/contract-handler.d.ts +26 -0
- package/dist/2-of-2/contract-handler.d.ts.map +1 -0
- package/dist/2-of-2/contract-handler.js +140 -0
- package/dist/2-of-2/contract-handler.js.map +1 -0
- package/dist/2-of-2/escrow-script.d.ts +57 -0
- package/dist/2-of-2/escrow-script.d.ts.map +1 -0
- package/dist/2-of-2/escrow-script.js +61 -0
- package/dist/2-of-2/escrow-script.js.map +1 -0
- package/dist/2-of-2/index.d.ts +7 -0
- package/dist/2-of-2/index.d.ts.map +1 -0
- package/dist/2-of-2/index.js +7 -0
- package/dist/2-of-2/index.js.map +1 -0
- package/dist/2-of-2/monitor.d.ts +67 -0
- package/dist/2-of-2/monitor.d.ts.map +1 -0
- package/dist/2-of-2/monitor.js +101 -0
- package/dist/2-of-2/monitor.js.map +1 -0
- package/dist/2-of-2/release.d.ts +80 -0
- package/dist/2-of-2/release.d.ts.map +1 -0
- package/dist/2-of-2/release.js +133 -0
- package/dist/2-of-2/release.js.map +1 -0
- package/dist/2-of-2/verify.d.ts +40 -0
- package/dist/2-of-2/verify.d.ts.map +1 -0
- package/dist/2-of-2/verify.js +100 -0
- package/dist/2-of-2/verify.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/payout-commitment.d.ts +12 -0
- package/dist/payout-commitment.d.ts.map +1 -0
- package/dist/payout-commitment.js +14 -0
- package/dist/payout-commitment.js.map +1 -0
- package/dist/sign.d.ts +19 -0
- package/dist/sign.d.ts.map +1 -0
- package/dist/sign.js +26 -0
- package/dist/sign.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Contract, type ContractState, type CreateContractParams, type Network } from "@arkade-os/sdk";
|
|
2
|
+
import { type EscrowScriptOptions } from "./escrow-script.js";
|
|
3
|
+
/** Optional descriptive fields carried alongside a registered contract. */
|
|
4
|
+
export interface EscrowContractMeta {
|
|
5
|
+
label?: string;
|
|
6
|
+
state?: ContractState;
|
|
7
|
+
metadata?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Build {@link CreateContractParams} for a 2-of-2 escrow: derives the pkScript
|
|
11
|
+
* and funding address from the escrow parameters, ready to hand to
|
|
12
|
+
* `ContractManager.createContract`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function escrowCreateContractParams(options: EscrowScriptOptions, network: Network, meta?: EscrowContractMeta): CreateContractParams;
|
|
15
|
+
/**
|
|
16
|
+
* Encode a 2-of-2 escrow as a NArk-compatible `arkcontract=` string for the
|
|
17
|
+
* server→client handoff. Carries the escrow parameters only — the receiver
|
|
18
|
+
* re-derives the script and address via {@link decodeEscrowArkContract}.
|
|
19
|
+
*/
|
|
20
|
+
export declare function encodeEscrowArkContract(options: EscrowScriptOptions): string;
|
|
21
|
+
/**
|
|
22
|
+
* Decode an escrow `arkcontract=` string into a full {@link Contract},
|
|
23
|
+
* re-deriving the pkScript and funding address from the embedded parameters.
|
|
24
|
+
*
|
|
25
|
+
* Registers the escrow handler if needed, so the caller does not have to.
|
|
26
|
+
* `aspPubKey` is the ASP x-only key the funding address is built from.
|
|
27
|
+
*/
|
|
28
|
+
export declare function decodeEscrowArkContract(encoded: string, aspPubKey: Uint8Array, network: Network, meta?: EscrowContractMeta): Contract;
|
|
29
|
+
//# sourceMappingURL=ark-contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ark-contract.d.ts","sourceRoot":"","sources":["../../src/2-of-2/ark-contract.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,oBAAoB,EAGzB,KAAK,OAAO,EACb,MAAM,gBAAgB,CAAC;AAOxB,OAAO,EAAE,KAAK,mBAAmB,EAAoB,MAAM,oBAAoB,CAAC;AAEhF,2EAA2E;AAC3E,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,mBAAmB,EAC5B,OAAO,EAAE,OAAO,EAChB,IAAI,GAAE,kBAAuB,GAC5B,oBAAoB,CAWtB;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAM5E;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,UAAU,EACrB,OAAO,EAAE,OAAO,EAChB,IAAI,GAAE,kBAAuB,GAC5B,QAAQ,CAOV"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { contractFromArkContractWithAddress, encodeArkContract, } from "@arkade-os/sdk";
|
|
2
|
+
import { hex } from "@scure/base";
|
|
3
|
+
import { ESCROW_2OF2_CONTRACT_TYPE, EscrowContractHandler, registerEscrowContractHandler, } from "./contract-handler.js";
|
|
4
|
+
import { EscrowVtxoScript } from "./escrow-script.js";
|
|
5
|
+
/**
|
|
6
|
+
* Build {@link CreateContractParams} for a 2-of-2 escrow: derives the pkScript
|
|
7
|
+
* and funding address from the escrow parameters, ready to hand to
|
|
8
|
+
* `ContractManager.createContract`.
|
|
9
|
+
*/
|
|
10
|
+
export function escrowCreateContractParams(options, network, meta = {}) {
|
|
11
|
+
const script = new EscrowVtxoScript(options);
|
|
12
|
+
return {
|
|
13
|
+
label: meta.label,
|
|
14
|
+
type: ESCROW_2OF2_CONTRACT_TYPE,
|
|
15
|
+
params: EscrowContractHandler.serializeParams(options),
|
|
16
|
+
script: hex.encode(script.pkScript),
|
|
17
|
+
address: script.arkAddress(network),
|
|
18
|
+
state: meta.state,
|
|
19
|
+
metadata: meta.metadata,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Encode a 2-of-2 escrow as a NArk-compatible `arkcontract=` string for the
|
|
24
|
+
* server→client handoff. Carries the escrow parameters only — the receiver
|
|
25
|
+
* re-derives the script and address via {@link decodeEscrowArkContract}.
|
|
26
|
+
*/
|
|
27
|
+
export function encodeEscrowArkContract(options) {
|
|
28
|
+
// `encodeArkContract` reads only `type` and `params`.
|
|
29
|
+
return encodeArkContract({
|
|
30
|
+
type: ESCROW_2OF2_CONTRACT_TYPE,
|
|
31
|
+
params: EscrowContractHandler.serializeParams(options),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Decode an escrow `arkcontract=` string into a full {@link Contract},
|
|
36
|
+
* re-deriving the pkScript and funding address from the embedded parameters.
|
|
37
|
+
*
|
|
38
|
+
* Registers the escrow handler if needed, so the caller does not have to.
|
|
39
|
+
* `aspPubKey` is the ASP x-only key the funding address is built from.
|
|
40
|
+
*/
|
|
41
|
+
export function decodeEscrowArkContract(encoded, aspPubKey, network, meta = {}) {
|
|
42
|
+
registerEscrowContractHandler();
|
|
43
|
+
return contractFromArkContractWithAddress(encoded, aspPubKey, network.hrp, {
|
|
44
|
+
label: meta.label,
|
|
45
|
+
state: meta.state,
|
|
46
|
+
metadata: meta.metadata,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=ark-contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ark-contract.js","sourceRoot":"","sources":["../../src/2-of-2/ark-contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,kCAAkC,EAClC,iBAAiB,GAElB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EACL,yBAAyB,EACzB,qBAAqB,EACrB,6BAA6B,GAC9B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAA4B,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAShF;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CACxC,OAA4B,EAC5B,OAAgB,EAChB,OAA2B,EAAE;IAE7B,MAAM,MAAM,GAAG,IAAI,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC7C,OAAO;QACL,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,IAAI,EAAE,yBAAyB;QAC/B,MAAM,EAAE,qBAAqB,CAAC,eAAe,CAAC,OAAO,CAAC;QACtD,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;QACnC,OAAO,EAAE,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC;QACnC,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;KACxB,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAA4B;IAClE,sDAAsD;IACtD,OAAO,iBAAiB,CAAC;QACvB,IAAI,EAAE,yBAAyB;QAC/B,MAAM,EAAE,qBAAqB,CAAC,eAAe,CAAC,OAAO,CAAC;KAC3C,CAAC,CAAC;AACjB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAAe,EACf,SAAqB,EACrB,OAAgB,EAChB,OAA2B,EAAE;IAE7B,6BAA6B,EAAE,CAAC;IAChC,OAAO,kCAAkC,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,GAAG,EAAE;QACzE,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;KACxB,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type ContractHandler } from "@arkade-os/sdk";
|
|
2
|
+
import { type EscrowScriptOptions, EscrowVtxoScript } from "./escrow-script.js";
|
|
3
|
+
/** Contract type identifier for the cooperative 2-of-2 escrow. */
|
|
4
|
+
export declare const ESCROW_2OF2_CONTRACT_TYPE = "escrow-2of2";
|
|
5
|
+
/** Role a wallet can play in a 2-of-2 escrow contract. */
|
|
6
|
+
export type EscrowRole = "seller" | "arbiter";
|
|
7
|
+
/**
|
|
8
|
+
* ContractHandler for the cooperative 2-of-2 escrow ({@link EscrowVtxoScript}).
|
|
9
|
+
*
|
|
10
|
+
* Spending paths:
|
|
11
|
+
* - cooperative (leaf A): 3-of-3 [seller, arbiter, asp]. Available whenever
|
|
12
|
+
* the server cooperates. Returned for ANY role — the witness is completed
|
|
13
|
+
* by the multi-party signing choreography (see `signEscrowArkTx`), not by a
|
|
14
|
+
* single wallet. This mirrors how the SDK's VHTLC handler returns its
|
|
15
|
+
* multi-party `refund`/`claim` leaves.
|
|
16
|
+
* - escape (leaf B): arbiter-only, after the CSV. The seller has no
|
|
17
|
+
* unilateral path by design, so `selectPath` returns null for the seller
|
|
18
|
+
* once collaboration is unavailable.
|
|
19
|
+
*/
|
|
20
|
+
export declare const EscrowContractHandler: ContractHandler<EscrowScriptOptions, EscrowVtxoScript>;
|
|
21
|
+
/**
|
|
22
|
+
* Register {@link EscrowContractHandler} in the SDK's global handler registry.
|
|
23
|
+
* Idempotent: a no-op if a handler for the type is already registered.
|
|
24
|
+
*/
|
|
25
|
+
export declare function registerEscrowContractHandler(): void;
|
|
26
|
+
//# sourceMappingURL=contract-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-handler.d.ts","sourceRoot":"","sources":["../../src/2-of-2/contract-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EAMrB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,KAAK,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEhF,kEAAkE;AAClE,eAAO,MAAM,yBAAyB,gBAAgB,CAAC;AAEvD,0DAA0D;AAC1D,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,SAAS,CAAC;AAiE9C;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qBAAqB,EAAE,eAAe,CACjD,mBAAmB,EACnB,gBAAgB,CA8DjB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,6BAA6B,IAAI,IAAI,CAIpD"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { contractHandlers, sequenceToTimelock, timelockToSequence, } from "@arkade-os/sdk";
|
|
2
|
+
import { hex } from "@scure/base";
|
|
3
|
+
import { EscrowVtxoScript } from "./escrow-script.js";
|
|
4
|
+
/** Contract type identifier for the cooperative 2-of-2 escrow. */
|
|
5
|
+
export const ESCROW_2OF2_CONTRACT_TYPE = "escrow-2of2";
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the wallet's role by matching its x-only pubkey (hex) against the
|
|
8
|
+
* contract's seller/arbiter params. An explicit `context.role` wins.
|
|
9
|
+
*
|
|
10
|
+
* The buyer holds no key in the script, so it is never a role here.
|
|
11
|
+
*/
|
|
12
|
+
function resolveEscrowRole(contract, context) {
|
|
13
|
+
if (context.role === "seller" || context.role === "arbiter") {
|
|
14
|
+
return context.role;
|
|
15
|
+
}
|
|
16
|
+
const walletKey = context.walletPubKey?.toLowerCase();
|
|
17
|
+
if (!walletKey)
|
|
18
|
+
return undefined;
|
|
19
|
+
if (walletKey === contract.params.sellerPubKey?.toLowerCase()) {
|
|
20
|
+
return "seller";
|
|
21
|
+
}
|
|
22
|
+
if (walletKey === contract.params.arbiterPubKey?.toLowerCase()) {
|
|
23
|
+
return "arbiter";
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* CSV maturity check for the escape leaf: is the relative (BIP-68) timelock
|
|
29
|
+
* satisfied for the vtxo under evaluation?
|
|
30
|
+
*
|
|
31
|
+
* TODO: drop this local copy and import the SDK's `isCsvSpendable` once
|
|
32
|
+
* https://github.com/arkade-os/ts-sdk/pull/541 lands and ships in a release —
|
|
33
|
+
* that PR exports this exact helper. Until then we mirror it here.
|
|
34
|
+
*/
|
|
35
|
+
function isCsvSpendable(context, sequence) {
|
|
36
|
+
if (sequence === undefined)
|
|
37
|
+
return true;
|
|
38
|
+
if (!context.vtxo)
|
|
39
|
+
return false;
|
|
40
|
+
const timelock = sequenceToTimelock(sequence);
|
|
41
|
+
if (timelock.type === "blocks") {
|
|
42
|
+
if (context.blockHeight === undefined ||
|
|
43
|
+
context.vtxo.status.block_height === undefined) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
return (context.blockHeight - context.vtxo.status.block_height >=
|
|
47
|
+
Number(timelock.value));
|
|
48
|
+
}
|
|
49
|
+
const blockTime = context.vtxo.status.block_time;
|
|
50
|
+
if (blockTime === undefined)
|
|
51
|
+
return false;
|
|
52
|
+
return context.currentTime / 1000 - blockTime >= Number(timelock.value);
|
|
53
|
+
}
|
|
54
|
+
/** nSequence (BIP-68) for the escape leaf, as stored in the contract params. */
|
|
55
|
+
function escapeSequence(contract) {
|
|
56
|
+
return contract.params.exitTimelock
|
|
57
|
+
? Number(contract.params.exitTimelock)
|
|
58
|
+
: undefined;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* ContractHandler for the cooperative 2-of-2 escrow ({@link EscrowVtxoScript}).
|
|
62
|
+
*
|
|
63
|
+
* Spending paths:
|
|
64
|
+
* - cooperative (leaf A): 3-of-3 [seller, arbiter, asp]. Available whenever
|
|
65
|
+
* the server cooperates. Returned for ANY role — the witness is completed
|
|
66
|
+
* by the multi-party signing choreography (see `signEscrowArkTx`), not by a
|
|
67
|
+
* single wallet. This mirrors how the SDK's VHTLC handler returns its
|
|
68
|
+
* multi-party `refund`/`claim` leaves.
|
|
69
|
+
* - escape (leaf B): arbiter-only, after the CSV. The seller has no
|
|
70
|
+
* unilateral path by design, so `selectPath` returns null for the seller
|
|
71
|
+
* once collaboration is unavailable.
|
|
72
|
+
*/
|
|
73
|
+
export const EscrowContractHandler = {
|
|
74
|
+
type: ESCROW_2OF2_CONTRACT_TYPE,
|
|
75
|
+
createScript(params) {
|
|
76
|
+
return new EscrowVtxoScript(this.deserializeParams(params));
|
|
77
|
+
},
|
|
78
|
+
serializeParams(params) {
|
|
79
|
+
return {
|
|
80
|
+
sellerPubKey: hex.encode(params.sellerPubKey),
|
|
81
|
+
arbiterPubKey: hex.encode(params.arbiterPubKey),
|
|
82
|
+
aspPubKey: hex.encode(params.aspPubKey),
|
|
83
|
+
exitTimelock: timelockToSequence(params.exitTimelock).toString(),
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
deserializeParams(params) {
|
|
87
|
+
return {
|
|
88
|
+
sellerPubKey: hex.decode(params.sellerPubKey),
|
|
89
|
+
arbiterPubKey: hex.decode(params.arbiterPubKey),
|
|
90
|
+
aspPubKey: hex.decode(params.aspPubKey),
|
|
91
|
+
exitTimelock: sequenceToTimelock(Number(params.exitTimelock)),
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
selectPath(script, contract, context) {
|
|
95
|
+
if (context.collaborative) {
|
|
96
|
+
return { leaf: script.cooperativeLeaf() };
|
|
97
|
+
}
|
|
98
|
+
// Unilateral: only the arbiter, only after the CSV elapses.
|
|
99
|
+
if (resolveEscrowRole(contract, context) !== "arbiter")
|
|
100
|
+
return null;
|
|
101
|
+
const sequence = escapeSequence(contract);
|
|
102
|
+
if (!isCsvSpendable(context, sequence))
|
|
103
|
+
return null;
|
|
104
|
+
return { leaf: script.escapeLeaf(), sequence };
|
|
105
|
+
},
|
|
106
|
+
getAllSpendingPaths(script, contract, context) {
|
|
107
|
+
const paths = [{ leaf: script.cooperativeLeaf() }];
|
|
108
|
+
// The escape leaf only exists for the arbiter (CSV checked at tx time).
|
|
109
|
+
if (resolveEscrowRole(contract, context) === "arbiter") {
|
|
110
|
+
paths.push({
|
|
111
|
+
leaf: script.escapeLeaf(),
|
|
112
|
+
sequence: escapeSequence(contract),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return paths;
|
|
116
|
+
},
|
|
117
|
+
getSpendablePaths(script, contract, context) {
|
|
118
|
+
const paths = [];
|
|
119
|
+
if (context.collaborative) {
|
|
120
|
+
paths.push({ leaf: script.cooperativeLeaf() });
|
|
121
|
+
}
|
|
122
|
+
if (resolveEscrowRole(contract, context) === "arbiter") {
|
|
123
|
+
const sequence = escapeSequence(contract);
|
|
124
|
+
if (isCsvSpendable(context, sequence)) {
|
|
125
|
+
paths.push({ leaf: script.escapeLeaf(), sequence });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return paths;
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Register {@link EscrowContractHandler} in the SDK's global handler registry.
|
|
133
|
+
* Idempotent: a no-op if a handler for the type is already registered.
|
|
134
|
+
*/
|
|
135
|
+
export function registerEscrowContractHandler() {
|
|
136
|
+
if (!contractHandlers.has(ESCROW_2OF2_CONTRACT_TYPE)) {
|
|
137
|
+
contractHandlers.register(EscrowContractHandler);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=contract-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-handler.js","sourceRoot":"","sources":["../../src/2-of-2/contract-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type Network, type RelativeTimelock, VtxoScript } from "@arkade-os/sdk";
|
|
2
|
+
export interface EscrowScriptOptions {
|
|
3
|
+
/** x-only Schnorr pubkey, 32 bytes. The funding party (seller). */
|
|
4
|
+
sellerPubKey: Uint8Array;
|
|
5
|
+
/** x-only Schnorr pubkey, 32 bytes. The escrow arbiter / cosigner. */
|
|
6
|
+
arbiterPubKey: Uint8Array;
|
|
7
|
+
/** x-only Schnorr pubkey, 32 bytes. Arkade ASP's signer key. */
|
|
8
|
+
aspPubKey: Uint8Array;
|
|
9
|
+
/**
|
|
10
|
+
* CSV timelock for the arbiter-only unilateral escape leaf — the
|
|
11
|
+
* ASP-mandated unilateral-exit closure. Must be ≥ the ASP's
|
|
12
|
+
* `unilateralExitDelay` (~2 days on mutinynet, ~30 days on
|
|
13
|
+
* mainnet) or `submitTx` rejects the script with
|
|
14
|
+
* INVALID_VTXO_SCRIPT "exit delay is too short".
|
|
15
|
+
*/
|
|
16
|
+
exitTimelock: RelativeTimelock;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Two-leaf VtxoScript for a cooperative 2-of-2 escrow on Ark.
|
|
20
|
+
*
|
|
21
|
+
* A — cooperative release : 3-of-3 [seller, arbiter, asp] (no CSV)
|
|
22
|
+
* B — arbiter unilateral exit : [arbiter] alone after a long CSV
|
|
23
|
+
*
|
|
24
|
+
* The two policy parties are the seller and the arbiter; both must sign
|
|
25
|
+
* the cooperative release. The Arkade ASP is added to leaf A as required
|
|
26
|
+
* by Ark's round/forfeit semantics — the ASP must cosign every
|
|
27
|
+
* cooperative VTXO spend — so at the script level leaf A is 3-of-3 even
|
|
28
|
+
* though only two parties hold policy.
|
|
29
|
+
*
|
|
30
|
+
* The seller has NO unilateral exit. Seller safety relies on a
|
|
31
|
+
* pre-signed cooperative refund ark-tx (created at funding time, held by
|
|
32
|
+
* the arbiter). After the CSV elapses, only the arbiter can sweep.
|
|
33
|
+
*/
|
|
34
|
+
export declare class EscrowVtxoScript extends VtxoScript {
|
|
35
|
+
readonly options: EscrowScriptOptions;
|
|
36
|
+
constructor(options: EscrowScriptOptions);
|
|
37
|
+
/** The cooperative-release tapleaf (3-of-3). Index 0. */
|
|
38
|
+
cooperativeLeaf(): import("@arkade-os/sdk").TapLeafScript;
|
|
39
|
+
/** The arbiter-only unilateral exit tapleaf (long CSV). Index 1. */
|
|
40
|
+
escapeLeaf(): import("@arkade-os/sdk").TapLeafScript;
|
|
41
|
+
/**
|
|
42
|
+
* SDK-conformant alias for {@link cooperativeLeaf}.
|
|
43
|
+
*
|
|
44
|
+
* The Arkade wallet/contract machinery (`deriveContractTapscripts`) expects
|
|
45
|
+
* every contract VtxoScript to expose `forfeit()` as its collaborative
|
|
46
|
+
* forfeit/intent leaf. For this escrow that is the cooperative 3-of-3 leaf.
|
|
47
|
+
*/
|
|
48
|
+
forfeit(): import("@arkade-os/sdk").TapLeafScript;
|
|
49
|
+
/**
|
|
50
|
+
* SDK-conformant alias for {@link escapeLeaf}, matching the `DefaultVtxo`
|
|
51
|
+
* `exit()` convention for the unilateral (CSV) leaf.
|
|
52
|
+
*/
|
|
53
|
+
exit(): import("@arkade-os/sdk").TapLeafScript;
|
|
54
|
+
/** Encoded Ark address (bech32m) for funding this escrow. */
|
|
55
|
+
arkAddress(network: Network): string;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=escrow-script.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"escrow-script.d.ts","sourceRoot":"","sources":["../../src/2-of-2/escrow-script.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,OAAO,EACZ,KAAK,gBAAgB,EACrB,UAAU,EACX,MAAM,gBAAgB,CAAC;AAExB,MAAM,WAAW,mBAAmB;IAClC,mEAAmE;IACnE,YAAY,EAAE,UAAU,CAAC;IACzB,sEAAsE;IACtE,aAAa,EAAE,UAAU,CAAC;IAC1B,gEAAgE;IAChE,SAAS,EAAE,UAAU,CAAC;IACtB;;;;;;OAMG;IACH,YAAY,EAAE,gBAAgB,CAAC;CAChC;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,gBAAiB,SAAQ,UAAU;IAC9C,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;gBAE1B,OAAO,EAAE,mBAAmB;IAexC,yDAAyD;IACzD,eAAe;IAIf,oEAAoE;IACpE,UAAU;IAIV;;;;;;OAMG;IACH,OAAO;IAIP;;;OAGG;IACH,IAAI;IAIJ,6DAA6D;IAC7D,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM;CAGrC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { CSVMultisigTapscript, MultisigTapscript, VtxoScript, } from "@arkade-os/sdk";
|
|
2
|
+
/**
|
|
3
|
+
* Two-leaf VtxoScript for a cooperative 2-of-2 escrow on Ark.
|
|
4
|
+
*
|
|
5
|
+
* A — cooperative release : 3-of-3 [seller, arbiter, asp] (no CSV)
|
|
6
|
+
* B — arbiter unilateral exit : [arbiter] alone after a long CSV
|
|
7
|
+
*
|
|
8
|
+
* The two policy parties are the seller and the arbiter; both must sign
|
|
9
|
+
* the cooperative release. The Arkade ASP is added to leaf A as required
|
|
10
|
+
* by Ark's round/forfeit semantics — the ASP must cosign every
|
|
11
|
+
* cooperative VTXO spend — so at the script level leaf A is 3-of-3 even
|
|
12
|
+
* though only two parties hold policy.
|
|
13
|
+
*
|
|
14
|
+
* The seller has NO unilateral exit. Seller safety relies on a
|
|
15
|
+
* pre-signed cooperative refund ark-tx (created at funding time, held by
|
|
16
|
+
* the arbiter). After the CSV elapses, only the arbiter can sweep.
|
|
17
|
+
*/
|
|
18
|
+
export class EscrowVtxoScript extends VtxoScript {
|
|
19
|
+
options;
|
|
20
|
+
constructor(options) {
|
|
21
|
+
const cooperativeLeaf = MultisigTapscript.encode({
|
|
22
|
+
pubkeys: [options.sellerPubKey, options.arbiterPubKey, options.aspPubKey],
|
|
23
|
+
});
|
|
24
|
+
const escapeLeaf = CSVMultisigTapscript.encode({
|
|
25
|
+
pubkeys: [options.arbiterPubKey],
|
|
26
|
+
timelock: options.exitTimelock,
|
|
27
|
+
});
|
|
28
|
+
super([cooperativeLeaf.script, escapeLeaf.script]);
|
|
29
|
+
this.options = options;
|
|
30
|
+
}
|
|
31
|
+
/** The cooperative-release tapleaf (3-of-3). Index 0. */
|
|
32
|
+
cooperativeLeaf() {
|
|
33
|
+
return this.leaves[0];
|
|
34
|
+
}
|
|
35
|
+
/** The arbiter-only unilateral exit tapleaf (long CSV). Index 1. */
|
|
36
|
+
escapeLeaf() {
|
|
37
|
+
return this.leaves[1];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* SDK-conformant alias for {@link cooperativeLeaf}.
|
|
41
|
+
*
|
|
42
|
+
* The Arkade wallet/contract machinery (`deriveContractTapscripts`) expects
|
|
43
|
+
* every contract VtxoScript to expose `forfeit()` as its collaborative
|
|
44
|
+
* forfeit/intent leaf. For this escrow that is the cooperative 3-of-3 leaf.
|
|
45
|
+
*/
|
|
46
|
+
forfeit() {
|
|
47
|
+
return this.cooperativeLeaf();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* SDK-conformant alias for {@link escapeLeaf}, matching the `DefaultVtxo`
|
|
51
|
+
* `exit()` convention for the unilateral (CSV) leaf.
|
|
52
|
+
*/
|
|
53
|
+
exit() {
|
|
54
|
+
return this.escapeLeaf();
|
|
55
|
+
}
|
|
56
|
+
/** Encoded Ark address (bech32m) for funding this escrow. */
|
|
57
|
+
arkAddress(network) {
|
|
58
|
+
return this.address(network.hrp, this.options.aspPubKey).encode();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=escrow-script.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"escrow-script.js","sourceRoot":"","sources":["../../src/2-of-2/escrow-script.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EAGjB,UAAU,GACX,MAAM,gBAAgB,CAAC;AAmBxB;;;;;;;;;;;;;;;GAeG;AACH,MAAM,OAAO,gBAAiB,SAAQ,UAAU;IACrC,OAAO,CAAsB;IAEtC,YAAY,OAA4B;QACtC,MAAM,eAAe,GAAG,iBAAiB,CAAC,MAAM,CAAC;YAC/C,OAAO,EAAE,CAAC,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,SAAS,CAAC;SAC1E,CAAC,CAAC;QAEH,MAAM,UAAU,GAAG,oBAAoB,CAAC,MAAM,CAAC;YAC7C,OAAO,EAAE,CAAC,OAAO,CAAC,aAAa,CAAC;YAChC,QAAQ,EAAE,OAAO,CAAC,YAAY;SAC/B,CAAC,CAAC;QAEH,KAAK,CAAC,CAAC,eAAe,CAAC,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;QAEnD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,yDAAyD;IACzD,eAAe;QACb,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC;IAED,oEAAoE;IACpE,UAAU;QACR,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC;IAED;;;;;;OAMG;IACH,OAAO;QACL,OAAO,IAAI,CAAC,eAAe,EAAE,CAAC;IAChC,CAAC;IAED;;;OAGG;IACH,IAAI;QACF,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC;IAC3B,CAAC;IAED,6DAA6D;IAC7D,UAAU,CAAC,OAAgB;QACzB,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,CAAC;IACpE,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/2-of-2/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/2-of-2/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type Contract, type ContractManagerConfig, type ContractVtxo, type IContractManager, type Network } from "@arkade-os/sdk";
|
|
2
|
+
import { type EscrowContractMeta } from "./ark-contract.js";
|
|
3
|
+
import type { EscrowScriptOptions } from "./escrow-script.js";
|
|
4
|
+
/** The subset of `IContractManager` the monitor relies on. */
|
|
5
|
+
export type EscrowManagerLike = Pick<IContractManager, "createContract" | "getContractsWithVtxos" | "onContractEvent" | "deleteContract" | "dispose">;
|
|
6
|
+
/** Funding observed at an escrow address. */
|
|
7
|
+
export interface EscrowFundedEvent {
|
|
8
|
+
contract: Contract;
|
|
9
|
+
vtxos: ContractVtxo[];
|
|
10
|
+
/** Sum of the received vtxo values, in sats. */
|
|
11
|
+
totalSats: number;
|
|
12
|
+
}
|
|
13
|
+
/** The escrow VTXO was spent (cooperative release or arbiter escape). */
|
|
14
|
+
export interface EscrowReleasedEvent {
|
|
15
|
+
contract: Contract;
|
|
16
|
+
vtxos: ContractVtxo[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A thin facade over the Arkade {@link ContractManager} scoped to 2-of-2
|
|
20
|
+
* escrows. It registers the escrow handler, owns the manager lifecycle, and
|
|
21
|
+
* exposes escrow-shaped helpers so a consumer never touches the handler
|
|
22
|
+
* registry, the watcher, or the repositories directly.
|
|
23
|
+
*
|
|
24
|
+
* Watching, persistence, reconnection, and failsafe polling are all handled
|
|
25
|
+
* by the underlying manager.
|
|
26
|
+
*/
|
|
27
|
+
export declare class EscrowMonitor {
|
|
28
|
+
private readonly manager;
|
|
29
|
+
private constructor();
|
|
30
|
+
/**
|
|
31
|
+
* Construct a monitor, creating and starting a real {@link ContractManager}
|
|
32
|
+
* from the given providers and repositories.
|
|
33
|
+
*/
|
|
34
|
+
static create(config: ContractManagerConfig): Promise<EscrowMonitor>;
|
|
35
|
+
/**
|
|
36
|
+
* Wrap an existing manager (or a test double). Registers the escrow handler.
|
|
37
|
+
*/
|
|
38
|
+
static fromManager(manager: EscrowManagerLike): EscrowMonitor;
|
|
39
|
+
/**
|
|
40
|
+
* Register an escrow (built from its parameters) for monitoring. Returns the
|
|
41
|
+
* persisted {@link Contract}; its `address` is where the seller funds.
|
|
42
|
+
*/
|
|
43
|
+
watch(options: EscrowScriptOptions, network: Network, meta?: EscrowContractMeta): Promise<Contract>;
|
|
44
|
+
/**
|
|
45
|
+
* Register an escrow received as an `arkcontract=` string (the server→client
|
|
46
|
+
* handoff) for monitoring.
|
|
47
|
+
*/
|
|
48
|
+
watchArkContract(encoded: string, aspPubKey: Uint8Array, network: Network, meta?: EscrowContractMeta): Promise<Contract>;
|
|
49
|
+
/** Stop monitoring an escrow by its pkScript hex. */
|
|
50
|
+
unwatch(script: string): Promise<void>;
|
|
51
|
+
/** List all monitored escrows together with their current virtual outputs. */
|
|
52
|
+
listEscrows(): Promise<EscrowFundedEvent[]>;
|
|
53
|
+
/**
|
|
54
|
+
* Fire `cb` when funds land at an escrow address (the FUNDED signal). Only
|
|
55
|
+
* fires for escrow contracts. Returns an unsubscribe function.
|
|
56
|
+
*/
|
|
57
|
+
onFunded(cb: (event: EscrowFundedEvent) => void): () => void;
|
|
58
|
+
/**
|
|
59
|
+
* Fire `cb` when an escrow VTXO is spent (the RELEASED signal — cooperative
|
|
60
|
+
* release or arbiter escape). Only fires for escrow contracts. Returns an
|
|
61
|
+
* unsubscribe function.
|
|
62
|
+
*/
|
|
63
|
+
onReleased(cb: (event: EscrowReleasedEvent) => void): () => void;
|
|
64
|
+
/** Release the manager's resources (stop watching, clear listeners). */
|
|
65
|
+
dispose(): void;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=monitor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"monitor.d.ts","sourceRoot":"","sources":["../../src/2-of-2/monitor.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,QAAQ,EAEb,KAAK,qBAAqB,EAC1B,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,OAAO,EACb,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAEL,KAAK,kBAAkB,EAExB,MAAM,mBAAmB,CAAC;AAK3B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,8DAA8D;AAC9D,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAClC,gBAAgB,EACd,gBAAgB,GAChB,uBAAuB,GACvB,iBAAiB,GACjB,gBAAgB,GAChB,SAAS,CACZ,CAAC;AAEF,6CAA6C;AAC7C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,yEAAyE;AACzE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAMD;;;;;;;;GAQG;AACH,qBAAa,aAAa;IACJ,OAAO,CAAC,QAAQ,CAAC,OAAO;IAA5C,OAAO;IAEP;;;OAGG;WACU,MAAM,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,aAAa,CAAC;IAM1E;;OAEG;IACH,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,iBAAiB,GAAG,aAAa;IAK7D;;;OAGG;IACH,KAAK,CACH,OAAO,EAAE,mBAAmB,EAC5B,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE,kBAAkB,GACxB,OAAO,CAAC,QAAQ,CAAC;IAMpB;;;OAGG;IACH,gBAAgB,CACd,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,UAAU,EACrB,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE,kBAAkB,GACxB,OAAO,CAAC,QAAQ,CAAC;IAUpB,qDAAqD;IACrD,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAItC,8EAA8E;IACxE,WAAW,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAWjD;;;OAGG;IACH,QAAQ,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAAG,MAAM,IAAI;IAe5D;;;;OAIG;IACH,UAAU,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,GAAG,MAAM,IAAI;IAWhE,wEAAwE;IACxE,OAAO,IAAI,IAAI;CAGhB"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { ContractManager, } from "@arkade-os/sdk";
|
|
2
|
+
import { decodeEscrowArkContract, escrowCreateContractParams, } from "./ark-contract.js";
|
|
3
|
+
import { ESCROW_2OF2_CONTRACT_TYPE, registerEscrowContractHandler, } from "./contract-handler.js";
|
|
4
|
+
function totalSats(vtxos) {
|
|
5
|
+
return vtxos.reduce((acc, v) => acc + v.value, 0);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A thin facade over the Arkade {@link ContractManager} scoped to 2-of-2
|
|
9
|
+
* escrows. It registers the escrow handler, owns the manager lifecycle, and
|
|
10
|
+
* exposes escrow-shaped helpers so a consumer never touches the handler
|
|
11
|
+
* registry, the watcher, or the repositories directly.
|
|
12
|
+
*
|
|
13
|
+
* Watching, persistence, reconnection, and failsafe polling are all handled
|
|
14
|
+
* by the underlying manager.
|
|
15
|
+
*/
|
|
16
|
+
export class EscrowMonitor {
|
|
17
|
+
manager;
|
|
18
|
+
constructor(manager) {
|
|
19
|
+
this.manager = manager;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Construct a monitor, creating and starting a real {@link ContractManager}
|
|
23
|
+
* from the given providers and repositories.
|
|
24
|
+
*/
|
|
25
|
+
static async create(config) {
|
|
26
|
+
registerEscrowContractHandler();
|
|
27
|
+
const manager = await ContractManager.create(config);
|
|
28
|
+
return new EscrowMonitor(manager);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Wrap an existing manager (or a test double). Registers the escrow handler.
|
|
32
|
+
*/
|
|
33
|
+
static fromManager(manager) {
|
|
34
|
+
registerEscrowContractHandler();
|
|
35
|
+
return new EscrowMonitor(manager);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Register an escrow (built from its parameters) for monitoring. Returns the
|
|
39
|
+
* persisted {@link Contract}; its `address` is where the seller funds.
|
|
40
|
+
*/
|
|
41
|
+
watch(options, network, meta) {
|
|
42
|
+
return this.manager.createContract(escrowCreateContractParams(options, network, meta));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Register an escrow received as an `arkcontract=` string (the server→client
|
|
46
|
+
* handoff) for monitoring.
|
|
47
|
+
*/
|
|
48
|
+
watchArkContract(encoded, aspPubKey, network, meta) {
|
|
49
|
+
const { createdAt: _createdAt, ...params } = decodeEscrowArkContract(encoded, aspPubKey, network, meta);
|
|
50
|
+
return this.manager.createContract(params);
|
|
51
|
+
}
|
|
52
|
+
/** Stop monitoring an escrow by its pkScript hex. */
|
|
53
|
+
unwatch(script) {
|
|
54
|
+
return this.manager.deleteContract(script);
|
|
55
|
+
}
|
|
56
|
+
/** List all monitored escrows together with their current virtual outputs. */
|
|
57
|
+
async listEscrows() {
|
|
58
|
+
const all = await this.manager.getContractsWithVtxos({
|
|
59
|
+
type: ESCROW_2OF2_CONTRACT_TYPE,
|
|
60
|
+
});
|
|
61
|
+
return all.map(({ contract, vtxos }) => ({
|
|
62
|
+
contract,
|
|
63
|
+
vtxos,
|
|
64
|
+
totalSats: totalSats(vtxos),
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Fire `cb` when funds land at an escrow address (the FUNDED signal). Only
|
|
69
|
+
* fires for escrow contracts. Returns an unsubscribe function.
|
|
70
|
+
*/
|
|
71
|
+
onFunded(cb) {
|
|
72
|
+
return this.manager.onContractEvent((event) => {
|
|
73
|
+
if (event.type === "vtxo_received" &&
|
|
74
|
+
event.contract.type === ESCROW_2OF2_CONTRACT_TYPE) {
|
|
75
|
+
cb({
|
|
76
|
+
contract: event.contract,
|
|
77
|
+
vtxos: event.vtxos,
|
|
78
|
+
totalSats: totalSats(event.vtxos),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Fire `cb` when an escrow VTXO is spent (the RELEASED signal — cooperative
|
|
85
|
+
* release or arbiter escape). Only fires for escrow contracts. Returns an
|
|
86
|
+
* unsubscribe function.
|
|
87
|
+
*/
|
|
88
|
+
onReleased(cb) {
|
|
89
|
+
return this.manager.onContractEvent((event) => {
|
|
90
|
+
if (event.type === "vtxo_spent" &&
|
|
91
|
+
event.contract.type === ESCROW_2OF2_CONTRACT_TYPE) {
|
|
92
|
+
cb({ contract: event.contract, vtxos: event.vtxos });
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/** Release the manager's resources (stop watching, clear listeners). */
|
|
97
|
+
dispose() {
|
|
98
|
+
this.manager.dispose();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=monitor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"monitor.js","sourceRoot":"","sources":["../../src/2-of-2/monitor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { type ArkInfo, type ArkProvider, CSVMultisigTapscript, type RelativeTimelock, Transaction } from "@arkade-os/sdk";
|
|
2
|
+
import type { EscrowVtxoScript } from "./escrow-script.js";
|
|
3
|
+
/**
|
|
4
|
+
* ASP-derived parameters needed to build and submit an escrow release.
|
|
5
|
+
*
|
|
6
|
+
* Derive once at startup from `ArkProvider.getInfo()` via
|
|
7
|
+
* {@link escrowArkConfigFromInfo}; these values are fixed by the ASP and
|
|
8
|
+
* cannot be chosen by the escrow parties.
|
|
9
|
+
*/
|
|
10
|
+
export interface EscrowArkConfig {
|
|
11
|
+
/** ASP x-only pubkey (32 bytes). */
|
|
12
|
+
aspPubKey: Uint8Array;
|
|
13
|
+
/** ASP-mandated unilateral-exit timelock (the escrow escape-leaf CSV). */
|
|
14
|
+
exitTimelock: RelativeTimelock;
|
|
15
|
+
/** CSV+multisig tapscript used as the second leaf of every checkpoint VTXO. */
|
|
16
|
+
serverUnrollScript: CSVMultisigTapscript.Type;
|
|
17
|
+
/** ASP dust threshold (sats). Sub-dust outputs use an OP_RETURN-shaped script. */
|
|
18
|
+
dust: bigint;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Derive {@link EscrowArkConfig} from the ASP's `getInfo()` response.
|
|
22
|
+
*
|
|
23
|
+
* Mirrors the connect step every party performs: x-only the signer key,
|
|
24
|
+
* decode the checkpoint tapscript, and map the unilateral-exit delay to a
|
|
25
|
+
* relative timelock.
|
|
26
|
+
*/
|
|
27
|
+
export declare function escrowArkConfigFromInfo(info: ArkInfo): EscrowArkConfig;
|
|
28
|
+
/** The escrow funding VTXO being spent by the release. */
|
|
29
|
+
export interface EscrowFundingOutpoint {
|
|
30
|
+
txid: string;
|
|
31
|
+
vout: number;
|
|
32
|
+
valueSats: number;
|
|
33
|
+
}
|
|
34
|
+
/** The two release outputs: buyer payout and escrow fee. */
|
|
35
|
+
export interface EscrowReleaseOutputs {
|
|
36
|
+
/** Buyer's payout Ark address (committed to at take time). */
|
|
37
|
+
buyerArkAddress: string;
|
|
38
|
+
/** Sats paid to the buyer. */
|
|
39
|
+
buyerAmountSats: number;
|
|
40
|
+
/** Fee-collection Ark address. */
|
|
41
|
+
feeArkAddress: string;
|
|
42
|
+
/** Sats paid to the fee output. */
|
|
43
|
+
feeSats: number;
|
|
44
|
+
}
|
|
45
|
+
export interface BuiltEscrowRelease {
|
|
46
|
+
arkTx: Transaction;
|
|
47
|
+
checkpoints: Transaction[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Build the cooperative release ark-tx and its checkpoint(s) spending the
|
|
51
|
+
* escrow funding VTXO to `buyer` and `fee` outputs via the cooperative leaf.
|
|
52
|
+
*
|
|
53
|
+
* Deterministic: identical inputs produce identical PSBT bytes and txids, so
|
|
54
|
+
* the arbiter can rebuild on a later round instead of persisting state.
|
|
55
|
+
*/
|
|
56
|
+
export declare function buildEscrowReleaseTx(escrow: EscrowVtxoScript, funding: EscrowFundingOutpoint, outputs: EscrowReleaseOutputs, config: EscrowArkConfig): BuiltEscrowRelease;
|
|
57
|
+
/**
|
|
58
|
+
* Sign input 0 of the ark-tx and every checkpoint with `secretKey`, in place.
|
|
59
|
+
*
|
|
60
|
+
* Defaults to a deterministic auxRand (see above). Used by the arbiter, who
|
|
61
|
+
* signs across rounds; a single-shot signer (the seller) can use
|
|
62
|
+
* {@link signEscrowArkTx} on the base64 PSBTs instead.
|
|
63
|
+
*/
|
|
64
|
+
export declare function signEscrowReleaseInPlace(built: BuiltEscrowRelease, secretKey: Uint8Array, auxRand?: Uint8Array): void;
|
|
65
|
+
/**
|
|
66
|
+
* Submit the fully-signed ark-tx with UNSIGNED checkpoints to the ASP, merge
|
|
67
|
+
* the user (seller+arbiter) checkpoint sigs into the ASP-signed responses, and
|
|
68
|
+
* finalize. Returns the arkTxid.
|
|
69
|
+
*
|
|
70
|
+
* The submit/finalize split lets the ASP reject a malformed ark-tx before any
|
|
71
|
+
* checkpoint signature is spent. Crash recovery between submit and finalize is
|
|
72
|
+
* the caller's responsibility.
|
|
73
|
+
*
|
|
74
|
+
* @param provider ASP provider used to submit and finalize the ark-tx
|
|
75
|
+
* @param fullySignedArkTx ark-tx carrying both arbiter and seller tapscript sigs
|
|
76
|
+
* @param userSignedCheckpoints checkpoints with arbiter+seller sigs (kept aside)
|
|
77
|
+
* @param unsignedCheckpoints fresh-from-build checkpoints (no signatures)
|
|
78
|
+
*/
|
|
79
|
+
export declare function submitAndFinalizeEscrowRelease(provider: ArkProvider, fullySignedArkTx: Transaction, userSignedCheckpoints: Transaction[], unsignedCheckpoints: Transaction[]): Promise<string>;
|
|
80
|
+
//# sourceMappingURL=release.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"release.d.ts","sourceRoot":"","sources":["../../src/2-of-2/release.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,OAAO,EACZ,KAAK,WAAW,EAGhB,oBAAoB,EAEpB,KAAK,gBAAgB,EACrB,WAAW,EACZ,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,oCAAoC;IACpC,SAAS,EAAE,UAAU,CAAC;IACtB,0EAA0E;IAC1E,YAAY,EAAE,gBAAgB,CAAC;IAC/B,+EAA+E;IAC/E,kBAAkB,EAAE,oBAAoB,CAAC,IAAI,CAAC;IAC9C,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,OAAO,GAAG,eAAe,CAYtE;AAED,0DAA0D;AAC1D,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,4DAA4D;AAC5D,MAAM,WAAW,oBAAoB;IACnC,8DAA8D;IAC9D,eAAe,EAAE,MAAM,CAAC;IACxB,8BAA8B;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,kCAAkC;IAClC,aAAa,EAAE,MAAM,CAAC;IACtB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,WAAW,CAAC;IACnB,WAAW,EAAE,WAAW,EAAE,CAAC;CAC5B;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,qBAAqB,EAC9B,OAAO,EAAE,oBAAoB,EAC7B,MAAM,EAAE,eAAe,GACtB,kBAAkB,CA8BpB;AAUD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,kBAAkB,EACzB,SAAS,EAAE,UAAU,EACrB,OAAO,GAAE,UAAmC,GAC3C,IAAI,CAKN;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,8BAA8B,CAClD,QAAQ,EAAE,WAAW,EACrB,gBAAgB,EAAE,WAAW,EAC7B,qBAAqB,EAAE,WAAW,EAAE,EACpC,mBAAmB,EAAE,WAAW,EAAE,GACjC,OAAO,CAAC,MAAM,CAAC,CAkBjB"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { ArkAddress, buildOffchainTx, CSVMultisigTapscript, combineTapscriptSigs, Transaction, } from "@arkade-os/sdk";
|
|
2
|
+
import { base64, hex } from "@scure/base";
|
|
3
|
+
/**
|
|
4
|
+
* Derive {@link EscrowArkConfig} from the ASP's `getInfo()` response.
|
|
5
|
+
*
|
|
6
|
+
* Mirrors the connect step every party performs: x-only the signer key,
|
|
7
|
+
* decode the checkpoint tapscript, and map the unilateral-exit delay to a
|
|
8
|
+
* relative timelock.
|
|
9
|
+
*/
|
|
10
|
+
export function escrowArkConfigFromInfo(info) {
|
|
11
|
+
if (!info.checkpointTapscript) {
|
|
12
|
+
throw new Error("ASP info is missing checkpointTapscript");
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
aspPubKey: toXOnly(hex.decode(info.signerPubkey)),
|
|
16
|
+
exitTimelock: delayToTimelock(info.unilateralExitDelay),
|
|
17
|
+
serverUnrollScript: CSVMultisigTapscript.decode(hex.decode(info.checkpointTapscript)),
|
|
18
|
+
dust: info.dust,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Build the cooperative release ark-tx and its checkpoint(s) spending the
|
|
23
|
+
* escrow funding VTXO to `buyer` and `fee` outputs via the cooperative leaf.
|
|
24
|
+
*
|
|
25
|
+
* Deterministic: identical inputs produce identical PSBT bytes and txids, so
|
|
26
|
+
* the arbiter can rebuild on a later round instead of persisting state.
|
|
27
|
+
*/
|
|
28
|
+
export function buildEscrowReleaseTx(escrow, funding, outputs, config) {
|
|
29
|
+
const arkInput = {
|
|
30
|
+
txid: funding.txid,
|
|
31
|
+
vout: funding.vout,
|
|
32
|
+
value: funding.valueSats,
|
|
33
|
+
tapLeafScript: escrow.cooperativeLeaf(),
|
|
34
|
+
tapTree: escrow.encode(),
|
|
35
|
+
};
|
|
36
|
+
const buyerAmount = BigInt(outputs.buyerAmountSats);
|
|
37
|
+
const feeAmount = BigInt(outputs.feeSats);
|
|
38
|
+
const buyerAddress = ArkAddress.decode(outputs.buyerArkAddress);
|
|
39
|
+
const feeAddress = ArkAddress.decode(outputs.feeArkAddress);
|
|
40
|
+
const { arkTx, checkpoints } = buildOffchainTx([arkInput], [
|
|
41
|
+
{
|
|
42
|
+
script: pkScriptFor(buyerAddress, buyerAmount, config.dust),
|
|
43
|
+
amount: buyerAmount,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
script: pkScriptFor(feeAddress, feeAmount, config.dust),
|
|
47
|
+
amount: feeAmount,
|
|
48
|
+
},
|
|
49
|
+
], config.serverUnrollScript);
|
|
50
|
+
return { arkTx, checkpoints };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Deterministic auxRand so signing the same PSBT twice yields the same
|
|
54
|
+
* signature. Required because the arbiter rebuilds and re-signs the release
|
|
55
|
+
* across rounds, and `combineTapscriptSigs` rejects a second, differing
|
|
56
|
+
* signature at the same (pubkey, leafHash) slot.
|
|
57
|
+
*/
|
|
58
|
+
const DETERMINISTIC_AUX_RAND = new Uint8Array(32);
|
|
59
|
+
/**
|
|
60
|
+
* Sign input 0 of the ark-tx and every checkpoint with `secretKey`, in place.
|
|
61
|
+
*
|
|
62
|
+
* Defaults to a deterministic auxRand (see above). Used by the arbiter, who
|
|
63
|
+
* signs across rounds; a single-shot signer (the seller) can use
|
|
64
|
+
* {@link signEscrowArkTx} on the base64 PSBTs instead.
|
|
65
|
+
*/
|
|
66
|
+
export function signEscrowReleaseInPlace(built, secretKey, auxRand = DETERMINISTIC_AUX_RAND) {
|
|
67
|
+
built.arkTx.signIdx(secretKey, 0, undefined, auxRand);
|
|
68
|
+
for (const checkpoint of built.checkpoints) {
|
|
69
|
+
checkpoint.signIdx(secretKey, 0, undefined, auxRand);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Submit the fully-signed ark-tx with UNSIGNED checkpoints to the ASP, merge
|
|
74
|
+
* the user (seller+arbiter) checkpoint sigs into the ASP-signed responses, and
|
|
75
|
+
* finalize. Returns the arkTxid.
|
|
76
|
+
*
|
|
77
|
+
* The submit/finalize split lets the ASP reject a malformed ark-tx before any
|
|
78
|
+
* checkpoint signature is spent. Crash recovery between submit and finalize is
|
|
79
|
+
* the caller's responsibility.
|
|
80
|
+
*
|
|
81
|
+
* @param provider ASP provider used to submit and finalize the ark-tx
|
|
82
|
+
* @param fullySignedArkTx ark-tx carrying both arbiter and seller tapscript sigs
|
|
83
|
+
* @param userSignedCheckpoints checkpoints with arbiter+seller sigs (kept aside)
|
|
84
|
+
* @param unsignedCheckpoints fresh-from-build checkpoints (no signatures)
|
|
85
|
+
*/
|
|
86
|
+
export async function submitAndFinalizeEscrowRelease(provider, fullySignedArkTx, userSignedCheckpoints, unsignedCheckpoints) {
|
|
87
|
+
const { arkTxid, signedCheckpointTxs } = await provider.submitTx(base64.encode(fullySignedArkTx.toPSBT()), unsignedCheckpoints.map((c) => base64.encode(c.toPSBT())));
|
|
88
|
+
const finalCheckpoints = signedCheckpointTxs.map((c, i) => {
|
|
89
|
+
const aspSigned = Transaction.fromPSBT(base64.decode(c));
|
|
90
|
+
const userSigned = userSignedCheckpoints[i];
|
|
91
|
+
if (!userSigned) {
|
|
92
|
+
throw new Error(`missing user-signed checkpoint at index ${i}`);
|
|
93
|
+
}
|
|
94
|
+
combineTapscriptSigs(userSigned, aspSigned);
|
|
95
|
+
return base64.encode(aspSigned.toPSBT());
|
|
96
|
+
});
|
|
97
|
+
await provider.finalizeTx(arkTxid, finalCheckpoints);
|
|
98
|
+
return arkTxid;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The ASP rejects any non-OP_RETURN output below `dust`. Ark addresses expose
|
|
102
|
+
* a {@link ArkAddress.subdustPkScript} encoding the destination as an
|
|
103
|
+
* OP_RETURN-shaped script for amounts the recipient still wants but that are
|
|
104
|
+
* sub-dust on L1 (e.g. a 1-sat fee on a small trade).
|
|
105
|
+
*/
|
|
106
|
+
function pkScriptFor(address, amount, dust) {
|
|
107
|
+
return amount < dust ? address.subdustPkScript : address.pkScript;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* BIP-68: a relative-timelock value below 512 is encoded as a block height;
|
|
111
|
+
* otherwise it is a 512-second-granularity time value (rounded up to the next
|
|
112
|
+
* valid multiple). ASP-reported values are already valid.
|
|
113
|
+
*/
|
|
114
|
+
function delayToTimelock(delay) {
|
|
115
|
+
if (delay < 512n) {
|
|
116
|
+
return { value: delay, type: "blocks" };
|
|
117
|
+
}
|
|
118
|
+
const rounded = ((delay + 511n) / 512n) * 512n;
|
|
119
|
+
return { value: rounded, type: "seconds" };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Drop the sign byte from a 33-byte compressed secp256k1 pubkey to get the
|
|
123
|
+
* 32-byte x-only form used by BIP-340 / tapscripts. The ASP returns its
|
|
124
|
+
* `signerPubkey` compressed.
|
|
125
|
+
*/
|
|
126
|
+
function toXOnly(pubkey) {
|
|
127
|
+
if (pubkey.length === 32)
|
|
128
|
+
return pubkey;
|
|
129
|
+
if (pubkey.length === 33)
|
|
130
|
+
return pubkey.subarray(1);
|
|
131
|
+
throw new Error(`unexpected pubkey length ${pubkey.length}`);
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=release.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"release.js","sourceRoot":"","sources":["../../src/2-of-2/release.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EAIV,eAAe,EACf,oBAAoB,EACpB,oBAAoB,EAEpB,WAAW,GACZ,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAqB1C;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAAa;IACnD,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO;QACL,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACjD,YAAY,EAAE,eAAe,CAAC,IAAI,CAAC,mBAAmB,CAAC;QACvD,kBAAkB,EAAE,oBAAoB,CAAC,MAAM,CAC7C,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,CACrC;QACD,IAAI,EAAE,IAAI,CAAC,IAAI;KAChB,CAAC;AACJ,CAAC;AA0BD;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAAwB,EACxB,OAA8B,EAC9B,OAA6B,EAC7B,MAAuB;IAEvB,MAAM,QAAQ,GAAe;QAC3B,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,KAAK,EAAE,OAAO,CAAC,SAAS;QACxB,aAAa,EAAE,MAAM,CAAC,eAAe,EAAE;QACvC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;KACzB,CAAC;IAEF,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IACpD,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,MAAM,YAAY,GAAG,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAChE,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAE5D,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,eAAe,CAC5C,CAAC,QAAQ,CAAC,EACV;QACE;YACE,MAAM,EAAE,WAAW,CAAC,YAAY,EAAE,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC;YAC3D,MAAM,EAAE,WAAW;SACpB;QACD;YACE,MAAM,EAAE,WAAW,CAAC,UAAU,EAAE,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC;YACvD,MAAM,EAAE,SAAS;SAClB;KACF,EACD,MAAM,CAAC,kBAAkB,CAC1B,CAAC;IAEF,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;AAChC,CAAC;AAED;;;;;GAKG;AACH,MAAM,sBAAsB,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;AAElD;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CACtC,KAAyB,EACzB,SAAqB,EACrB,UAAsB,sBAAsB;IAE5C,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IACtD,KAAK,MAAM,UAAU,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;QAC3C,UAAU,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IACvD,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAClD,QAAqB,EACrB,gBAA6B,EAC7B,qBAAoC,EACpC,mBAAkC;IAElC,MAAM,EAAE,OAAO,EAAE,mBAAmB,EAAE,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAC9D,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,CAAC,EACxC,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAC1D,CAAC;IAEF,MAAM,gBAAgB,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACxD,MAAM,SAAS,GAAG,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QACzD,MAAM,UAAU,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAC5C,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,EAAE,CAAC,CAAC;QAClE,CAAC;QACD,oBAAoB,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;QAC5C,OAAO,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,MAAM,QAAQ,CAAC,UAAU,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;IACrD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;GAKG;AACH,SAAS,WAAW,CAClB,OAAmB,EACnB,MAAc,EACd,IAAY;IAEZ,OAAO,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;AACpE,CAAC;AAED;;;;GAIG;AACH,SAAS,eAAe,CAAC,KAAa;IACpC,IAAI,KAAK,GAAG,IAAI,EAAE,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAC1C,CAAC;IACD,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAC/C,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AAC7C,CAAC;AAED;;;;GAIG;AACH,SAAS,OAAO,CAAC,MAAkB;IACjC,IAAI,MAAM,CAAC,MAAM,KAAK,EAAE;QAAE,OAAO,MAAM,CAAC;IACxC,IAAI,MAAM,CAAC,MAAM,KAAK,EAAE;QAAE,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IACpD,MAAM,IAAI,KAAK,CAAC,4BAA4B,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;AAC/D,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type Transaction } from "@arkade-os/sdk";
|
|
2
|
+
export interface ReleaseArkTxExpectations {
|
|
3
|
+
/** The escrow VTXO outpoint that funds the release. */
|
|
4
|
+
escrowOutpoint: {
|
|
5
|
+
txid: string;
|
|
6
|
+
vout: number;
|
|
7
|
+
};
|
|
8
|
+
/** Buyer's payout Ark address (committed to during take). */
|
|
9
|
+
buyerArkAddress: string;
|
|
10
|
+
/** Buyer payout amount in sats. */
|
|
11
|
+
buyerAmountSats: bigint;
|
|
12
|
+
/** Escrow fee Ark address. */
|
|
13
|
+
feeArkAddress: string;
|
|
14
|
+
/** Escrow fee amount in sats. */
|
|
15
|
+
feeAmountSats: bigint;
|
|
16
|
+
}
|
|
17
|
+
/** The release as the seller receives it: the ark-tx and its checkpoint(s). */
|
|
18
|
+
export interface ReleaseToVerify {
|
|
19
|
+
arkTx: Transaction;
|
|
20
|
+
checkpoints: Transaction[];
|
|
21
|
+
}
|
|
22
|
+
export declare class ReleaseArkTxValidationError extends Error {
|
|
23
|
+
constructor(message: string);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Seller-side check before signing the cooperative release.
|
|
27
|
+
*
|
|
28
|
+
* An Arkade offchain spend is a two-tx chain: a **checkpoint** spends the
|
|
29
|
+
* funding VTXO, and the **ark-tx** spends the checkpoint and pays the final
|
|
30
|
+
* outputs (plus a zero-value P2A fee-bump anchor). This verifies the whole
|
|
31
|
+
* chain the seller is about to sign:
|
|
32
|
+
* - the checkpoint spends the escrow funding outpoint,
|
|
33
|
+
* - the ark-tx spends that checkpoint,
|
|
34
|
+
* - the ark-tx pays the agreed buyer and fee amounts — and nothing else
|
|
35
|
+
* except the anchor (no rogue payout).
|
|
36
|
+
*
|
|
37
|
+
* Throws on any mismatch. Caller must NOT sign if this throws.
|
|
38
|
+
*/
|
|
39
|
+
export declare function verifyReleaseArkTx(release: ReleaseToVerify, expected: ReleaseArkTxExpectations): void;
|
|
40
|
+
//# sourceMappingURL=verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/2-of-2/verify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGnE,MAAM,WAAW,wBAAwB;IACvC,uDAAuD;IACvD,cAAc,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C,6DAA6D;IAC7D,eAAe,EAAE,MAAM,CAAC;IACxB,mCAAmC;IACnC,eAAe,EAAE,MAAM,CAAC;IACxB,8BAA8B;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,+EAA+E;AAC/E,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,WAAW,CAAC;IACnB,WAAW,EAAE,WAAW,EAAE,CAAC;CAC5B;AAED,qBAAa,2BAA4B,SAAQ,KAAK;gBACxC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,wBAAwB,GACjC,IAAI,CA2FN"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { ArkAddress, P2A } from "@arkade-os/sdk";
|
|
2
|
+
import { hex } from "@scure/base";
|
|
3
|
+
export class ReleaseArkTxValidationError extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "ReleaseArkTxValidationError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Seller-side check before signing the cooperative release.
|
|
11
|
+
*
|
|
12
|
+
* An Arkade offchain spend is a two-tx chain: a **checkpoint** spends the
|
|
13
|
+
* funding VTXO, and the **ark-tx** spends the checkpoint and pays the final
|
|
14
|
+
* outputs (plus a zero-value P2A fee-bump anchor). This verifies the whole
|
|
15
|
+
* chain the seller is about to sign:
|
|
16
|
+
* - the checkpoint spends the escrow funding outpoint,
|
|
17
|
+
* - the ark-tx spends that checkpoint,
|
|
18
|
+
* - the ark-tx pays the agreed buyer and fee amounts — and nothing else
|
|
19
|
+
* except the anchor (no rogue payout).
|
|
20
|
+
*
|
|
21
|
+
* Throws on any mismatch. Caller must NOT sign if this throws.
|
|
22
|
+
*/
|
|
23
|
+
export function verifyReleaseArkTx(release, expected) {
|
|
24
|
+
const { arkTx, checkpoints } = release;
|
|
25
|
+
// A single funding VTXO yields exactly one checkpoint.
|
|
26
|
+
if (checkpoints.length !== 1) {
|
|
27
|
+
throw new ReleaseArkTxValidationError(`expected exactly 1 checkpoint, got ${checkpoints.length}`);
|
|
28
|
+
}
|
|
29
|
+
const checkpoint = checkpoints[0];
|
|
30
|
+
// (1) The checkpoint must spend the escrow funding outpoint.
|
|
31
|
+
const cpIn = checkpoint.getInput(0);
|
|
32
|
+
if (!cpIn.txid || cpIn.index === undefined) {
|
|
33
|
+
throw new ReleaseArkTxValidationError("checkpoint input 0 missing prevout");
|
|
34
|
+
}
|
|
35
|
+
const cpInTxid = hex.encode(cpIn.txid);
|
|
36
|
+
if (cpInTxid !== expected.escrowOutpoint.txid ||
|
|
37
|
+
cpIn.index !== expected.escrowOutpoint.vout) {
|
|
38
|
+
throw new ReleaseArkTxValidationError(`checkpoint input ${cpInTxid}:${cpIn.index} does not spend funding ${expected.escrowOutpoint.txid}:${expected.escrowOutpoint.vout}`);
|
|
39
|
+
}
|
|
40
|
+
// (2) The ark-tx must spend that checkpoint.
|
|
41
|
+
if (arkTx.inputsLength !== 1) {
|
|
42
|
+
throw new ReleaseArkTxValidationError(`expected exactly 1 ark-tx input, got ${arkTx.inputsLength}`);
|
|
43
|
+
}
|
|
44
|
+
const arkIn = arkTx.getInput(0);
|
|
45
|
+
if (!arkIn.txid) {
|
|
46
|
+
throw new ReleaseArkTxValidationError("ark-tx input 0 missing prevout");
|
|
47
|
+
}
|
|
48
|
+
const arkInTxid = hex.encode(arkIn.txid);
|
|
49
|
+
if (arkInTxid !== checkpoint.id) {
|
|
50
|
+
throw new ReleaseArkTxValidationError(`ark-tx input ${arkInTxid} does not spend checkpoint ${checkpoint.id}`);
|
|
51
|
+
}
|
|
52
|
+
// (3) The ark-tx must pay buyer + fee, and nothing else but the anchor.
|
|
53
|
+
const buyerAddress = ArkAddress.decode(expected.buyerArkAddress);
|
|
54
|
+
const feeAddress = ArkAddress.decode(expected.feeArkAddress);
|
|
55
|
+
const anchorScriptHex = hex.encode(P2A.script);
|
|
56
|
+
let foundBuyer = false;
|
|
57
|
+
let foundFee = false;
|
|
58
|
+
for (let i = 0; i < arkTx.outputsLength; i++) {
|
|
59
|
+
const output = arkTx.getOutput(i);
|
|
60
|
+
if (!output.script || output.amount === undefined) {
|
|
61
|
+
throw new ReleaseArkTxValidationError(`ark-tx output ${i} missing script or amount`);
|
|
62
|
+
}
|
|
63
|
+
if (matchesAddress(output.script, buyerAddress) &&
|
|
64
|
+
output.amount === expected.buyerAmountSats) {
|
|
65
|
+
foundBuyer = true;
|
|
66
|
+
}
|
|
67
|
+
else if (matchesAddress(output.script, feeAddress) &&
|
|
68
|
+
output.amount === expected.feeAmountSats) {
|
|
69
|
+
foundFee = true;
|
|
70
|
+
}
|
|
71
|
+
else if (hex.encode(output.script) === anchorScriptHex &&
|
|
72
|
+
output.amount === P2A.amount) {
|
|
73
|
+
// Zero-value P2A fee-bump anchor — expected.
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
throw new ReleaseArkTxValidationError(`unexpected ark-tx output ${i}: ${output.amount} sats to ${hex.encode(output.script)}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (!foundBuyer) {
|
|
80
|
+
throw new ReleaseArkTxValidationError(`no output paying ${expected.buyerAmountSats} sats to buyer ${expected.buyerArkAddress}`);
|
|
81
|
+
}
|
|
82
|
+
if (!foundFee) {
|
|
83
|
+
throw new ReleaseArkTxValidationError(`no output paying ${expected.feeAmountSats} sats to fee ${expected.feeArkAddress}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** Match a pkScript against an Ark address, allowing its sub-dust form. */
|
|
87
|
+
function matchesAddress(script, address) {
|
|
88
|
+
return (bytesEqual(script, address.pkScript) ||
|
|
89
|
+
bytesEqual(script, address.subdustPkScript));
|
|
90
|
+
}
|
|
91
|
+
function bytesEqual(a, b) {
|
|
92
|
+
if (a.length !== b.length)
|
|
93
|
+
return false;
|
|
94
|
+
for (let i = 0; i < a.length; i++) {
|
|
95
|
+
if (a[i] !== b[i])
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/2-of-2/verify.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,wBAAwB,CAAC;AACvC,cAAc,WAAW,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,wBAAwB,CAAC;AACvC,cAAc,WAAW,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Buyer's commitment to a payout Ark address.
|
|
3
|
+
*
|
|
4
|
+
* The buyer's destination is an Ark address (bech32m wrapping a server
|
|
5
|
+
* pubkey + vtxo taproot key), not an L1 P2WPKH/P2TR address, so an L1
|
|
6
|
+
* BIP-322 flow does not apply directly. Instead the buyer Schnorr-signs a
|
|
7
|
+
* deterministic message tying the offer, the destination address, and the
|
|
8
|
+
* x-only pubkey embedded in that destination — binding them to the payout
|
|
9
|
+
* destination used in the cooperative release.
|
|
10
|
+
*/
|
|
11
|
+
export declare function payoutCommitmentMessage(offerId: string, payoutArkAddress: string): string;
|
|
12
|
+
//# sourceMappingURL=payout-commitment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payout-commitment.d.ts","sourceRoot":"","sources":["../src/payout-commitment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GACvB,MAAM,CAER"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Buyer's commitment to a payout Ark address.
|
|
3
|
+
*
|
|
4
|
+
* The buyer's destination is an Ark address (bech32m wrapping a server
|
|
5
|
+
* pubkey + vtxo taproot key), not an L1 P2WPKH/P2TR address, so an L1
|
|
6
|
+
* BIP-322 flow does not apply directly. Instead the buyer Schnorr-signs a
|
|
7
|
+
* deterministic message tying the offer, the destination address, and the
|
|
8
|
+
* x-only pubkey embedded in that destination — binding them to the payout
|
|
9
|
+
* destination used in the cooperative release.
|
|
10
|
+
*/
|
|
11
|
+
export function payoutCommitmentMessage(offerId, payoutArkAddress) {
|
|
12
|
+
return `escrow:take:${offerId}:${payoutArkAddress}`;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=payout-commitment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payout-commitment.js","sourceRoot":"","sources":["../src/payout-commitment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAAe,EACf,gBAAwB;IAExB,OAAO,eAAe,OAAO,IAAI,gBAAgB,EAAE,CAAC;AACtD,CAAC"}
|
package/dist/sign.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface SignedEscrowTx {
|
|
2
|
+
signedPsbt: string;
|
|
3
|
+
txid: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Sign input 0 of an Arkade ark-tx PSBT with `secretKey`.
|
|
7
|
+
*
|
|
8
|
+
* Equivalent to `signEscrowArkTx` from `@lendasat/lendaswap-sdk-pure`,
|
|
9
|
+
* lifted into this SDK so consumers don't have to pull in Lendaswap's
|
|
10
|
+
* EVM dependency stack (zerodev/viem) just to sign one tapscript leaf.
|
|
11
|
+
*
|
|
12
|
+
* The PSBT must already carry the cooperative tapleaf script, control
|
|
13
|
+
* block, and the sighash type on input 0. The arbiter (server) attaches
|
|
14
|
+
* those when it builds the release tx. Does not finalize.
|
|
15
|
+
*/
|
|
16
|
+
export declare function signEscrowArkTx(psbtB64: string, secretKey: Uint8Array): SignedEscrowTx;
|
|
17
|
+
/** Same as {@link signEscrowArkTx} but for an array of checkpoint PSBTs. */
|
|
18
|
+
export declare function signEscrowCheckpoints(psbtB64s: string[], secretKey: Uint8Array): string[];
|
|
19
|
+
//# sourceMappingURL=sign.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign.d.ts","sourceRoot":"","sources":["../src/sign.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,UAAU,GACpB,cAAc,CAOhB;AAED,4EAA4E;AAC5E,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAAE,EAClB,SAAS,EAAE,UAAU,GACpB,MAAM,EAAE,CAEV"}
|
package/dist/sign.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Transaction } from "@arkade-os/sdk";
|
|
2
|
+
import { base64 } from "@scure/base";
|
|
3
|
+
/**
|
|
4
|
+
* Sign input 0 of an Arkade ark-tx PSBT with `secretKey`.
|
|
5
|
+
*
|
|
6
|
+
* Equivalent to `signEscrowArkTx` from `@lendasat/lendaswap-sdk-pure`,
|
|
7
|
+
* lifted into this SDK so consumers don't have to pull in Lendaswap's
|
|
8
|
+
* EVM dependency stack (zerodev/viem) just to sign one tapscript leaf.
|
|
9
|
+
*
|
|
10
|
+
* The PSBT must already carry the cooperative tapleaf script, control
|
|
11
|
+
* block, and the sighash type on input 0. The arbiter (server) attaches
|
|
12
|
+
* those when it builds the release tx. Does not finalize.
|
|
13
|
+
*/
|
|
14
|
+
export function signEscrowArkTx(psbtB64, secretKey) {
|
|
15
|
+
const tx = Transaction.fromPSBT(base64.decode(psbtB64));
|
|
16
|
+
tx.signIdx(secretKey, 0);
|
|
17
|
+
return {
|
|
18
|
+
signedPsbt: base64.encode(tx.toPSBT()),
|
|
19
|
+
txid: tx.id,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Same as {@link signEscrowArkTx} but for an array of checkpoint PSBTs. */
|
|
23
|
+
export function signEscrowCheckpoints(psbtB64s, secretKey) {
|
|
24
|
+
return psbtB64s.map((b) => signEscrowArkTx(b, secretKey).signedPsbt);
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=sign.js.map
|
package/dist/sign.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign.js","sourceRoot":"","sources":["../src/sign.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAOrC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAe,EACf,SAAqB;IAErB,MAAM,EAAE,GAAG,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IACxD,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;IACzB,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC;QACtC,IAAI,EAAE,EAAE,CAAC,EAAE;KACZ,CAAC;AACJ,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,qBAAqB,CACnC,QAAkB,EAClB,SAAqB;IAErB,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,UAAU,CAAC,CAAC;AACvE,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@satora/escrow",
|
|
3
|
+
"version": "0.0.1-alpha.0",
|
|
4
|
+
"description": "Escrow primitives for Arkade",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./2-of-2": {
|
|
15
|
+
"types": "./dist/2-of-2/index.d.ts",
|
|
16
|
+
"import": "./dist/2-of-2/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@arkade-os/sdk": "^0.4.32",
|
|
24
|
+
"@scure/base": "^2.0.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@biomejs/biome": "2.3.15",
|
|
28
|
+
"@noble/curves": "^2.0.0",
|
|
29
|
+
"@types/node": "^22.0.0",
|
|
30
|
+
"typescript": "^5.7.0",
|
|
31
|
+
"vitest": "^3.0.0"
|
|
32
|
+
},
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc -p tsconfig.build.json",
|
|
39
|
+
"check-types": "tsc -p tsconfig.json --noEmit",
|
|
40
|
+
"test": "vitest",
|
|
41
|
+
"test:run": "vitest run",
|
|
42
|
+
"lint": "biome check .",
|
|
43
|
+
"lint:fix": "biome check --write .",
|
|
44
|
+
"clean": "rm -rf dist"
|
|
45
|
+
}
|
|
46
|
+
}
|