@hazbase/simplicity 0.0.4 → 0.1.1
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/README.md +679 -42
- package/dist/cli.js +2268 -5
- package/dist/client/SimplicityClient.d.ts +64 -25
- package/dist/client/SimplicityClient.js +65 -3
- package/dist/core/executor.js +67 -92
- package/dist/core/outputBinding.d.ts +61 -0
- package/dist/core/outputBinding.js +552 -0
- package/dist/core/schnorr.d.ts +5 -0
- package/dist/core/schnorr.js +34 -0
- package/dist/core/types.d.ts +586 -1
- package/dist/docs/definitions/bond-anchor.simf +19 -0
- package/dist/docs/definitions/bond-definition.json +10 -0
- package/dist/docs/definitions/bond-descriptor-bound-settlement-machine.simf +144 -0
- package/dist/docs/definitions/bond-issuance-anchor.simf +26 -0
- package/dist/docs/definitions/bond-issuance-state-partial-redemption.json +18 -0
- package/dist/docs/definitions/bond-issuance-state-redeemed.json +18 -0
- package/dist/docs/definitions/bond-issuance-state.json +12 -0
- package/dist/docs/definitions/bond-redemption-state-machine.simf +118 -0
- package/dist/docs/definitions/bond-redemption-transition.simf +41 -0
- package/dist/docs/definitions/bond-script-bound-settlement-machine.simf +142 -0
- package/dist/docs/definitions/fund-capital-call-open.simf +82 -0
- package/dist/docs/definitions/fund-capital-call-refund-only.simf +33 -0
- package/dist/docs/definitions/fund-capital-call-state.json +11 -0
- package/dist/docs/definitions/fund-definition.json +8 -0
- package/dist/docs/definitions/fund-distribution-claim.simf +57 -0
- package/dist/docs/definitions/recursive-delay-direct-next.simf +60 -0
- package/dist/docs/definitions/recursive-delay-optional.simf +88 -0
- package/dist/docs/definitions/recursive-delay-required.simf +72 -0
- package/dist/docs/definitions/recursive-delay.simf +83 -0
- package/dist/docs/definitions/recursive-policy-transfer-machine.simf +65 -0
- package/dist/domain/bond.d.ts +9855 -1
- package/dist/domain/bond.js +2156 -1
- package/dist/domain/bondSettlementValidation.d.ts +20 -0
- package/dist/domain/bondSettlementValidation.js +150 -0
- package/dist/domain/bondValidation.d.ts +26 -0
- package/dist/domain/bondValidation.js +278 -3
- package/dist/domain/fund.d.ts +2069 -0
- package/dist/domain/fund.js +1384 -0
- package/dist/domain/fundValidation.d.ts +122 -0
- package/dist/domain/fundValidation.js +635 -0
- package/dist/domain/policies.d.ts +1051 -0
- package/dist/domain/policies.js +1605 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +92 -1
- package/package.json +15 -2
|
@@ -0,0 +1,1384 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.define = define;
|
|
7
|
+
exports.verify = verify;
|
|
8
|
+
exports.load = load;
|
|
9
|
+
exports.prepareCapitalCall = prepareCapitalCall;
|
|
10
|
+
exports.verifyCapitalCall = verifyCapitalCall;
|
|
11
|
+
exports.signPositionReceipt = signPositionReceipt;
|
|
12
|
+
exports.verifyPositionReceipt = verifyPositionReceipt;
|
|
13
|
+
exports.inspectCapitalCallClaim = inspectCapitalCallClaim;
|
|
14
|
+
exports.executeCapitalCallClaim = executeCapitalCallClaim;
|
|
15
|
+
exports.inspectCapitalCallRollover = inspectCapitalCallRollover;
|
|
16
|
+
exports.executeCapitalCallRollover = executeCapitalCallRollover;
|
|
17
|
+
exports.inspectCapitalCallRefund = inspectCapitalCallRefund;
|
|
18
|
+
exports.executeCapitalCallRefund = executeCapitalCallRefund;
|
|
19
|
+
exports.prepareDistribution = prepareDistribution;
|
|
20
|
+
exports.reconcilePosition = reconcilePosition;
|
|
21
|
+
exports.verifyDistribution = verifyDistribution;
|
|
22
|
+
exports.inspectDistributionClaim = inspectDistributionClaim;
|
|
23
|
+
exports.executeDistributionClaim = executeDistributionClaim;
|
|
24
|
+
exports.prepareClosing = prepareClosing;
|
|
25
|
+
exports.verifyClosing = verifyClosing;
|
|
26
|
+
exports.exportEvidence = exportEvidence;
|
|
27
|
+
exports.exportFinalityPayload = exportFinalityPayload;
|
|
28
|
+
const node_fs_1 = require("node:fs");
|
|
29
|
+
const promises_1 = require("node:fs/promises");
|
|
30
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
31
|
+
const errors_1 = require("../core/errors");
|
|
32
|
+
const summary_1 = require("../core/summary");
|
|
33
|
+
const outputBinding_1 = require("../core/outputBinding");
|
|
34
|
+
const fundValidation_1 = require("./fundValidation");
|
|
35
|
+
const FUND_VERIFICATION_REPORT_SCHEMA_VERSION = "fund-verification-report/v1";
|
|
36
|
+
const FUND_EVIDENCE_BUNDLE_SCHEMA_VERSION = "fund-evidence-bundle/v1";
|
|
37
|
+
const FUND_FINALITY_PAYLOAD_SCHEMA_VERSION = "fund-finality-payload/v1";
|
|
38
|
+
function resolveValueOrPath(options) {
|
|
39
|
+
if (options.pathValue)
|
|
40
|
+
return { jsonPath: options.pathValue };
|
|
41
|
+
if (options.objectValue !== undefined)
|
|
42
|
+
return { value: options.objectValue };
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
function resolveFundDocsAsset(filename) {
|
|
46
|
+
const cwdCandidate = node_path_1.default.resolve(process.cwd(), "docs/definitions", filename);
|
|
47
|
+
if ((0, node_fs_1.existsSync)(cwdCandidate))
|
|
48
|
+
return cwdCandidate;
|
|
49
|
+
const bundledCandidate = node_path_1.default.resolve(__dirname, "../docs/definitions", filename);
|
|
50
|
+
if ((0, node_fs_1.existsSync)(bundledCandidate))
|
|
51
|
+
return bundledCandidate;
|
|
52
|
+
return cwdCandidate;
|
|
53
|
+
}
|
|
54
|
+
function satToBtcAmount(sat) {
|
|
55
|
+
return Number((sat / 1e8).toFixed(8));
|
|
56
|
+
}
|
|
57
|
+
function nowIso() {
|
|
58
|
+
return new Date().toISOString();
|
|
59
|
+
}
|
|
60
|
+
function defaultPositionId(capitalCall) {
|
|
61
|
+
return `${capitalCall.fundId}:${capitalCall.lpId}:${capitalCall.callId}`;
|
|
62
|
+
}
|
|
63
|
+
function assertOpenCapitalCall(state) {
|
|
64
|
+
if (state.status !== "OPEN") {
|
|
65
|
+
throw new errors_1.ValidationError("Capital call must be OPEN for claim or rollover execution", {
|
|
66
|
+
code: "FUND_CAPITAL_CALL_NOT_OPEN",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function assertRefundOnlyCapitalCall(state) {
|
|
71
|
+
if (state.status !== "REFUND_ONLY") {
|
|
72
|
+
throw new errors_1.ValidationError("Capital call must be REFUND_ONLY for refund execution", {
|
|
73
|
+
code: "FUND_CAPITAL_CALL_NOT_REFUND_ONLY",
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function deriveCapitalCallStage(state) {
|
|
78
|
+
if (state.status === "CLAIMED")
|
|
79
|
+
return "claimed";
|
|
80
|
+
if (state.status === "REFUND_ONLY")
|
|
81
|
+
return "refund-only";
|
|
82
|
+
if (state.status === "REFUNDED")
|
|
83
|
+
return "refunded";
|
|
84
|
+
return "open";
|
|
85
|
+
}
|
|
86
|
+
function summarizeFundPayoutDescriptor(descriptor) {
|
|
87
|
+
const canonicalJson = (0, summary_1.stableStringify)({
|
|
88
|
+
receiverAddress: descriptor.receiverAddress,
|
|
89
|
+
nextOutputHash: descriptor.nextOutputHash ?? null,
|
|
90
|
+
nextOutputScriptHash: descriptor.nextOutputScriptHash ?? null,
|
|
91
|
+
amountSat: descriptor.amountSat,
|
|
92
|
+
assetId: descriptor.assetId,
|
|
93
|
+
requestedOutputBindingMode: descriptor.requestedOutputBindingMode ?? descriptor.outputBindingMode,
|
|
94
|
+
outputForm: (0, outputBinding_1.normalizeOutputForm)(descriptor.outputForm),
|
|
95
|
+
rawOutput: (0, outputBinding_1.normalizeOutputRawFields)(descriptor.rawOutput) ?? null,
|
|
96
|
+
feeIndex: descriptor.feeIndex,
|
|
97
|
+
nextOutputIndex: descriptor.nextOutputIndex,
|
|
98
|
+
maxFeeSat: descriptor.maxFeeSat,
|
|
99
|
+
outputBindingMode: descriptor.outputBindingMode,
|
|
100
|
+
});
|
|
101
|
+
return { canonicalJson, hash: (0, summary_1.sha256HexUtf8)(canonicalJson) };
|
|
102
|
+
}
|
|
103
|
+
function validateFundPayoutDescriptor(descriptor) {
|
|
104
|
+
if (!descriptor.receiverAddress || descriptor.receiverAddress.trim().length === 0) {
|
|
105
|
+
throw new errors_1.ValidationError("receiverAddress must be a non-empty string", {
|
|
106
|
+
code: "FUND_PAYOUT_RECEIVER_REQUIRED",
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (!Number.isInteger(descriptor.amountSat) || descriptor.amountSat <= 0) {
|
|
110
|
+
throw new errors_1.ValidationError("amountSat must be a positive integer", {
|
|
111
|
+
code: "FUND_PAYOUT_AMOUNT_INVALID",
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (!descriptor.assetId || descriptor.assetId.trim().length === 0) {
|
|
115
|
+
throw new errors_1.ValidationError("assetId must be a non-empty string", {
|
|
116
|
+
code: "FUND_PAYOUT_ASSET_REQUIRED",
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if (!Number.isInteger(descriptor.feeIndex) || descriptor.feeIndex < 0) {
|
|
120
|
+
throw new errors_1.ValidationError("feeIndex must be a non-negative integer", {
|
|
121
|
+
code: "FUND_PAYOUT_FEE_INDEX_INVALID",
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (!Number.isInteger(descriptor.nextOutputIndex) || descriptor.nextOutputIndex < 0) {
|
|
125
|
+
throw new errors_1.ValidationError("nextOutputIndex must be a non-negative integer", {
|
|
126
|
+
code: "FUND_PAYOUT_NEXT_OUTPUT_INDEX_INVALID",
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (!Number.isInteger(descriptor.maxFeeSat) || descriptor.maxFeeSat < 0) {
|
|
130
|
+
throw new errors_1.ValidationError("maxFeeSat must be a non-negative integer", {
|
|
131
|
+
code: "FUND_PAYOUT_MAX_FEE_INVALID",
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (!["none", "script-bound", "descriptor-bound"].includes(descriptor.outputBindingMode)) {
|
|
135
|
+
throw new errors_1.ValidationError("outputBindingMode must be none, script-bound, or descriptor-bound", {
|
|
136
|
+
code: "FUND_PAYOUT_BINDING_MODE_INVALID",
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
if (descriptor.requestedOutputBindingMode
|
|
140
|
+
&& !["none", "script-bound", "descriptor-bound"].includes(descriptor.requestedOutputBindingMode)) {
|
|
141
|
+
throw new errors_1.ValidationError("requestedOutputBindingMode must be none, script-bound, or descriptor-bound", {
|
|
142
|
+
code: "FUND_PAYOUT_REQUESTED_BINDING_MODE_INVALID",
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (descriptor.nextOutputHash && !/^[0-9a-f]{64}$/i.test(descriptor.nextOutputHash)) {
|
|
146
|
+
throw new errors_1.ValidationError("nextOutputHash must be a 64-character hex string", {
|
|
147
|
+
code: "FUND_PAYOUT_OUTPUT_HASH_INVALID",
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
if (descriptor.nextOutputScriptHash && !/^[0-9a-f]{64}$/i.test(descriptor.nextOutputScriptHash)) {
|
|
151
|
+
throw new errors_1.ValidationError("nextOutputScriptHash must be a 64-character hex string", {
|
|
152
|
+
code: "FUND_PAYOUT_OUTPUT_SCRIPT_HASH_INVALID",
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
descriptor.rawOutput = (0, outputBinding_1.normalizeOutputRawFields)(descriptor.rawOutput);
|
|
156
|
+
descriptor.outputForm = (0, outputBinding_1.normalizeOutputForm)(descriptor.outputForm);
|
|
157
|
+
descriptor.requestedOutputBindingMode = descriptor.requestedOutputBindingMode ?? descriptor.outputBindingMode;
|
|
158
|
+
return descriptor;
|
|
159
|
+
}
|
|
160
|
+
function buildFundOutputBindingReport(input) {
|
|
161
|
+
if (!input.descriptor)
|
|
162
|
+
return undefined;
|
|
163
|
+
const runtimeBound = input.descriptor.outputBindingMode !== "none";
|
|
164
|
+
return {
|
|
165
|
+
mode: input.descriptor.outputBindingMode,
|
|
166
|
+
requestedMode: input.descriptor.requestedOutputBindingMode,
|
|
167
|
+
supportedForm: input.supportedForm ?? "unsupported",
|
|
168
|
+
nextReceiverRuntimeCommitted: runtimeBound,
|
|
169
|
+
outputCountRuntimeBound: runtimeBound,
|
|
170
|
+
feeIndexRuntimeBound: runtimeBound,
|
|
171
|
+
nextOutputHashRuntimeBound: input.descriptor.outputBindingMode === "descriptor-bound",
|
|
172
|
+
nextOutputScriptRuntimeBound: input.descriptor.outputBindingMode === "script-bound",
|
|
173
|
+
amountRuntimeBound: input.descriptor.outputBindingMode === "descriptor-bound",
|
|
174
|
+
reasonCode: input.reasonCode
|
|
175
|
+
?? (input.descriptor.outputBindingMode === "descriptor-bound"
|
|
176
|
+
? "OK_MANUAL_HASH"
|
|
177
|
+
: input.descriptor.outputBindingMode === "script-bound"
|
|
178
|
+
? "OK_SCRIPT_BOUND"
|
|
179
|
+
: "OK_NONE"),
|
|
180
|
+
autoDerived: input.autoDerived,
|
|
181
|
+
fallbackReason: input.fallbackReason,
|
|
182
|
+
bindingInputs: input.bindingInputs
|
|
183
|
+
?? {
|
|
184
|
+
assetId: input.descriptor.assetId,
|
|
185
|
+
assetForm: input.descriptor.outputForm?.assetForm ?? "explicit",
|
|
186
|
+
amountForm: input.descriptor.outputForm?.amountForm ?? "explicit",
|
|
187
|
+
nonceForm: input.descriptor.outputForm?.nonceForm ?? "null",
|
|
188
|
+
rangeProofForm: input.descriptor.outputForm?.rangeProofForm ?? "empty",
|
|
189
|
+
nextAmountSat: input.descriptor.amountSat,
|
|
190
|
+
nextOutputIndex: input.descriptor.nextOutputIndex,
|
|
191
|
+
feeIndex: input.descriptor.feeIndex,
|
|
192
|
+
maxFeeSat: input.descriptor.maxFeeSat,
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
async function buildFundPayoutDescriptor(sdk, input) {
|
|
197
|
+
const nextScriptPubKeyHex = await (0, outputBinding_1.getScriptPubKeyHexViaRpc)(sdk, input.receiverAddress);
|
|
198
|
+
const nextOutputScriptHash = (0, outputBinding_1.hashHexBytes)(nextScriptPubKeyHex);
|
|
199
|
+
const requestedBindingMode = input.outputBindingMode ?? "descriptor-bound";
|
|
200
|
+
const outputForm = (0, outputBinding_1.normalizeOutputForm)(input.outputForm);
|
|
201
|
+
const rawOutput = (0, outputBinding_1.normalizeOutputRawFields)(input.rawOutput);
|
|
202
|
+
const rawOutputAnalysis = (0, outputBinding_1.analyzeOutputRawFields)(rawOutput);
|
|
203
|
+
const explicitAssetHex = requestedBindingMode !== "none" && (0, outputBinding_1.isExplicitV1OutputForm)(outputForm)
|
|
204
|
+
? await (0, outputBinding_1.resolveExplicitAssetHex)(sdk, input.assetId)
|
|
205
|
+
: undefined;
|
|
206
|
+
const autoDerivedNextOutputHash = requestedBindingMode === "descriptor-bound" && !input.nextOutputHash
|
|
207
|
+
? rawOutputAnalysis.valid && rawOutputAnalysis.normalized
|
|
208
|
+
? (0, outputBinding_1.computeRawOutputV1Hash)(rawOutputAnalysis.normalized)
|
|
209
|
+
: explicitAssetHex
|
|
210
|
+
? (0, outputBinding_1.computeExplicitV1OutputHash)({
|
|
211
|
+
assetHex: explicitAssetHex,
|
|
212
|
+
nextAmountSat: input.amountSat,
|
|
213
|
+
nextOutputScriptHash,
|
|
214
|
+
})
|
|
215
|
+
: undefined
|
|
216
|
+
: undefined;
|
|
217
|
+
const nextOutputHash = input.nextOutputHash ?? autoDerivedNextOutputHash;
|
|
218
|
+
const bindingResolution = (0, outputBinding_1.resolveOutputBindingDecision)({
|
|
219
|
+
requestedBindingMode,
|
|
220
|
+
nextOutputHash,
|
|
221
|
+
nextOutputScriptHash,
|
|
222
|
+
autoDerivedNextOutputHash: Boolean(autoDerivedNextOutputHash && !input.nextOutputHash),
|
|
223
|
+
explicitAssetSupported: Boolean(explicitAssetHex),
|
|
224
|
+
outputForm,
|
|
225
|
+
rawOutput,
|
|
226
|
+
});
|
|
227
|
+
const descriptor = validateFundPayoutDescriptor({
|
|
228
|
+
receiverAddress: input.receiverAddress,
|
|
229
|
+
nextOutputHash: bindingResolution.outputBindingMode === "descriptor-bound" ? nextOutputHash : undefined,
|
|
230
|
+
nextOutputScriptHash: bindingResolution.outputBindingMode !== "none" ? nextOutputScriptHash : undefined,
|
|
231
|
+
amountSat: input.amountSat,
|
|
232
|
+
assetId: input.assetId,
|
|
233
|
+
requestedOutputBindingMode: requestedBindingMode,
|
|
234
|
+
outputForm,
|
|
235
|
+
rawOutput,
|
|
236
|
+
feeIndex: input.feeIndex ?? 1,
|
|
237
|
+
nextOutputIndex: input.nextOutputIndex ?? 0,
|
|
238
|
+
maxFeeSat: input.maxFeeSat ?? 100,
|
|
239
|
+
outputBindingMode: bindingResolution.outputBindingMode,
|
|
240
|
+
});
|
|
241
|
+
const bindingInputs = {
|
|
242
|
+
assetId: descriptor.assetId,
|
|
243
|
+
assetForm: descriptor.outputForm?.assetForm ?? "explicit",
|
|
244
|
+
amountForm: descriptor.outputForm?.amountForm ?? "explicit",
|
|
245
|
+
nonceForm: descriptor.outputForm?.nonceForm ?? "null",
|
|
246
|
+
rangeProofForm: descriptor.outputForm?.rangeProofForm ?? "empty",
|
|
247
|
+
nextAmountSat: descriptor.amountSat,
|
|
248
|
+
nextOutputIndex: descriptor.nextOutputIndex,
|
|
249
|
+
feeIndex: descriptor.feeIndex,
|
|
250
|
+
maxFeeSat: descriptor.maxFeeSat,
|
|
251
|
+
...(rawOutputAnalysis.valid && rawOutputAnalysis.normalized
|
|
252
|
+
? {
|
|
253
|
+
rawOutputComponents: {
|
|
254
|
+
scriptPubKey: rawOutputAnalysis.normalized.scriptComponentSource,
|
|
255
|
+
rangeProof: rawOutputAnalysis.normalized.rangeProofComponentSource,
|
|
256
|
+
},
|
|
257
|
+
}
|
|
258
|
+
: {}),
|
|
259
|
+
};
|
|
260
|
+
return {
|
|
261
|
+
descriptor,
|
|
262
|
+
summary: summarizeFundPayoutDescriptor(descriptor),
|
|
263
|
+
supportedForm: bindingResolution.supportedForm,
|
|
264
|
+
autoDerivedNextOutputHash: bindingResolution.autoDerived,
|
|
265
|
+
reasonCode: bindingResolution.reasonCode,
|
|
266
|
+
fallbackReason: bindingResolution.fallbackReason,
|
|
267
|
+
bindingInputs,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function buildBindingModeWitnessValue(mode) {
|
|
271
|
+
return mode === "descriptor-bound" ? "0x01" : mode === "script-bound" ? "0x02" : "0x03";
|
|
272
|
+
}
|
|
273
|
+
function buildClaimWitness(descriptor) {
|
|
274
|
+
return {
|
|
275
|
+
values: {
|
|
276
|
+
ACTION_8: { type: "u8", value: "0x01" },
|
|
277
|
+
EXPECTED_PAYOUT_OUTPUT_HASH: {
|
|
278
|
+
type: "u256",
|
|
279
|
+
value: `0x${descriptor.nextOutputHash ?? "0000000000000000000000000000000000000000000000000000000000000000"}`,
|
|
280
|
+
},
|
|
281
|
+
EXPECTED_PAYOUT_OUTPUT_SCRIPT_HASH: {
|
|
282
|
+
type: "u256",
|
|
283
|
+
value: `0x${descriptor.nextOutputScriptHash ?? "0000000000000000000000000000000000000000000000000000000000000000"}`,
|
|
284
|
+
},
|
|
285
|
+
OUTPUT_BINDING_MODE: {
|
|
286
|
+
type: "u8",
|
|
287
|
+
value: buildBindingModeWitnessValue(descriptor.outputBindingMode),
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
function buildRolloverWitness() {
|
|
293
|
+
return {
|
|
294
|
+
values: {
|
|
295
|
+
ACTION_8: { type: "u8", value: "0x02" },
|
|
296
|
+
EXPECTED_PAYOUT_OUTPUT_HASH: {
|
|
297
|
+
type: "u256",
|
|
298
|
+
value: "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
299
|
+
},
|
|
300
|
+
EXPECTED_PAYOUT_OUTPUT_SCRIPT_HASH: {
|
|
301
|
+
type: "u256",
|
|
302
|
+
value: "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
303
|
+
},
|
|
304
|
+
OUTPUT_BINDING_MODE: { type: "u8", value: "0x00" },
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
function buildRefundWitness() {
|
|
309
|
+
return { values: {} };
|
|
310
|
+
}
|
|
311
|
+
function buildDistributionWitness(descriptor) {
|
|
312
|
+
return {
|
|
313
|
+
values: {
|
|
314
|
+
EXPECTED_PAYOUT_OUTPUT_HASH: {
|
|
315
|
+
type: "u256",
|
|
316
|
+
value: `0x${descriptor.nextOutputHash ?? "0000000000000000000000000000000000000000000000000000000000000000"}`,
|
|
317
|
+
},
|
|
318
|
+
EXPECTED_PAYOUT_OUTPUT_SCRIPT_HASH: {
|
|
319
|
+
type: "u256",
|
|
320
|
+
value: `0x${descriptor.nextOutputScriptHash ?? "0000000000000000000000000000000000000000000000000000000000000000"}`,
|
|
321
|
+
},
|
|
322
|
+
OUTPUT_BINDING_MODE: {
|
|
323
|
+
type: "u8",
|
|
324
|
+
value: buildBindingModeWitnessValue(descriptor.outputBindingMode),
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
async function loadFundDefinitionDocument(sdk, input) {
|
|
330
|
+
const source = resolveValueOrPath({ pathValue: input.definitionPath, objectValue: input.definitionValue });
|
|
331
|
+
const initial = await sdk.loadDefinition({
|
|
332
|
+
type: "fund-definition",
|
|
333
|
+
id: input.definitionValue?.fundId ?? "FUND-001",
|
|
334
|
+
...source,
|
|
335
|
+
});
|
|
336
|
+
const value = (0, fundValidation_1.validateFundDefinition)(JSON.parse(initial.canonicalJson));
|
|
337
|
+
const descriptor = await sdk.loadDefinition({
|
|
338
|
+
type: "fund-definition",
|
|
339
|
+
id: value.fundId,
|
|
340
|
+
...(source.jsonPath ? { jsonPath: source.jsonPath } : { value }),
|
|
341
|
+
});
|
|
342
|
+
return { descriptor, value, summary: (0, fundValidation_1.summarizeFundDefinition)(value) };
|
|
343
|
+
}
|
|
344
|
+
async function loadCapitalCallDocument(sdk, input) {
|
|
345
|
+
const source = resolveValueOrPath({ pathValue: input.capitalCallPath, objectValue: input.capitalCallValue });
|
|
346
|
+
const initial = await sdk.loadStateDocument({
|
|
347
|
+
type: "fund-capital-call",
|
|
348
|
+
id: input.capitalCallValue?.callId ?? "CALL-001",
|
|
349
|
+
...source,
|
|
350
|
+
});
|
|
351
|
+
const value = (0, fundValidation_1.validateCapitalCallState)(JSON.parse(initial.canonicalJson));
|
|
352
|
+
const descriptor = await sdk.loadStateDocument({
|
|
353
|
+
type: "fund-capital-call",
|
|
354
|
+
id: value.callId,
|
|
355
|
+
...(source.jsonPath ? { jsonPath: source.jsonPath } : { value }),
|
|
356
|
+
});
|
|
357
|
+
return { descriptor, value, summary: (0, fundValidation_1.summarizeCapitalCallState)(value) };
|
|
358
|
+
}
|
|
359
|
+
async function loadBarePositionReceiptDocument(sdk, input) {
|
|
360
|
+
const source = resolveValueOrPath({ pathValue: input.positionReceiptPath, objectValue: input.positionReceiptValue });
|
|
361
|
+
const initial = await sdk.loadStateDocument({
|
|
362
|
+
type: "fund-position-receipt",
|
|
363
|
+
id: input.positionReceiptValue?.positionId ?? "POSITION-001",
|
|
364
|
+
...source,
|
|
365
|
+
});
|
|
366
|
+
const value = (0, fundValidation_1.validateLPPositionReceipt)(JSON.parse(initial.canonicalJson));
|
|
367
|
+
const descriptor = await sdk.loadStateDocument({
|
|
368
|
+
type: "fund-position-receipt",
|
|
369
|
+
id: value.positionId,
|
|
370
|
+
...(source.jsonPath ? { jsonPath: source.jsonPath } : { value }),
|
|
371
|
+
});
|
|
372
|
+
return { descriptor, value, summary: (0, fundValidation_1.summarizeLPPositionReceipt)(value) };
|
|
373
|
+
}
|
|
374
|
+
async function loadPositionReceiptEnvelopeDocument(sdk, input) {
|
|
375
|
+
const source = resolveValueOrPath({ pathValue: input.positionReceiptPath, objectValue: input.positionReceiptValue });
|
|
376
|
+
const initial = await sdk.loadStateDocument({
|
|
377
|
+
type: "fund-position-receipt-envelope",
|
|
378
|
+
id: input.positionReceiptValue?.receipt.positionId ?? "POSITION-001",
|
|
379
|
+
...source,
|
|
380
|
+
});
|
|
381
|
+
const value = (0, fundValidation_1.validateLPPositionReceiptEnvelope)(JSON.parse(initial.canonicalJson));
|
|
382
|
+
const descriptor = await sdk.loadStateDocument({
|
|
383
|
+
type: "fund-position-receipt-envelope",
|
|
384
|
+
id: value.receipt.positionId,
|
|
385
|
+
...(source.jsonPath ? { jsonPath: source.jsonPath } : { value }),
|
|
386
|
+
});
|
|
387
|
+
return {
|
|
388
|
+
descriptor,
|
|
389
|
+
value,
|
|
390
|
+
receiptSummary: (0, fundValidation_1.summarizeLPPositionReceipt)(value.receipt),
|
|
391
|
+
envelopeSummary: (0, fundValidation_1.summarizeLPPositionReceiptEnvelope)(value),
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
async function loadDistributionDocument(sdk, input) {
|
|
395
|
+
const source = resolveValueOrPath({ pathValue: input.distributionPath, objectValue: input.distributionValue });
|
|
396
|
+
let value;
|
|
397
|
+
if (source.jsonPath || source.value) {
|
|
398
|
+
const initial = await sdk.loadStateDocument({
|
|
399
|
+
type: "fund-distribution",
|
|
400
|
+
id: input.distributionValue?.distributionId ?? input.distributionId ?? "DIST-001",
|
|
401
|
+
...source,
|
|
402
|
+
});
|
|
403
|
+
value = (0, fundValidation_1.validateDistributionDescriptor)(JSON.parse(initial.canonicalJson));
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
if (!input.positionReceipt || !input.distributionId || !input.assetId || input.amountSat === undefined || !input.approvedAt) {
|
|
407
|
+
throw new errors_1.ValidationError("distributionPath/distributionValue or positionReceipt + distributionId + assetId + amountSat + approvedAt is required", { code: "FUND_DISTRIBUTION_INPUT_REQUIRED" });
|
|
408
|
+
}
|
|
409
|
+
value = (0, fundValidation_1.buildDistributionDescriptor)({
|
|
410
|
+
distributionId: input.distributionId,
|
|
411
|
+
receipt: input.positionReceipt,
|
|
412
|
+
assetId: input.assetId,
|
|
413
|
+
amountSat: input.amountSat,
|
|
414
|
+
approvedAt: input.approvedAt,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
const descriptor = await sdk.loadStateDocument({
|
|
418
|
+
type: "fund-distribution",
|
|
419
|
+
id: value.distributionId,
|
|
420
|
+
...(source.jsonPath ? { jsonPath: source.jsonPath } : { value }),
|
|
421
|
+
});
|
|
422
|
+
return { descriptor, value, summary: (0, fundValidation_1.summarizeDistributionDescriptor)(value) };
|
|
423
|
+
}
|
|
424
|
+
async function loadDistributionDocuments(sdk, input) {
|
|
425
|
+
const combinedPaths = [...(input.distributionPaths ?? []), ...(input.distributionPath ? [input.distributionPath] : [])];
|
|
426
|
+
const combinedValues = [...(input.distributionValues ?? []), ...(input.distributionValue ? [input.distributionValue] : [])];
|
|
427
|
+
const results = [];
|
|
428
|
+
for (const distributionPath of combinedPaths)
|
|
429
|
+
results.push(await loadDistributionDocument(sdk, { distributionPath }));
|
|
430
|
+
for (const distributionValue of combinedValues)
|
|
431
|
+
results.push(await loadDistributionDocument(sdk, { distributionValue }));
|
|
432
|
+
return results;
|
|
433
|
+
}
|
|
434
|
+
async function loadClosingDocument(sdk, input) {
|
|
435
|
+
const source = resolveValueOrPath({ pathValue: input.closingPath, objectValue: input.closingValue });
|
|
436
|
+
let value;
|
|
437
|
+
if (source.jsonPath || source.value) {
|
|
438
|
+
const initial = await sdk.loadStateDocument({
|
|
439
|
+
type: "fund-closing",
|
|
440
|
+
id: input.closingValue?.closingId ?? input.closingId ?? "CLOSE-001",
|
|
441
|
+
...source,
|
|
442
|
+
});
|
|
443
|
+
value = (0, fundValidation_1.validateFundClosingDescriptor)(JSON.parse(initial.canonicalJson));
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
if (!input.positionReceipt || !input.closingId || !input.finalDistributionHashes || !input.closedAt) {
|
|
447
|
+
throw new errors_1.ValidationError("closingPath/closingValue or positionReceipt + closingId + finalDistributionHashes + closedAt is required", { code: "FUND_CLOSING_INPUT_REQUIRED" });
|
|
448
|
+
}
|
|
449
|
+
value = (0, fundValidation_1.buildFundClosingDescriptor)({
|
|
450
|
+
receipt: input.positionReceipt,
|
|
451
|
+
closingId: input.closingId,
|
|
452
|
+
finalDistributionHashes: input.finalDistributionHashes,
|
|
453
|
+
closedAt: input.closedAt,
|
|
454
|
+
closingReason: input.closingReason,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
const descriptor = await sdk.loadStateDocument({
|
|
458
|
+
type: "fund-closing",
|
|
459
|
+
id: value.closingId,
|
|
460
|
+
...(source.jsonPath ? { jsonPath: source.jsonPath } : { value }),
|
|
461
|
+
});
|
|
462
|
+
return { descriptor, value, summary: (0, fundValidation_1.summarizeFundClosingDescriptor)(value) };
|
|
463
|
+
}
|
|
464
|
+
function buildBaseFundReport() {
|
|
465
|
+
return { schemaVersion: FUND_VERIFICATION_REPORT_SCHEMA_VERSION };
|
|
466
|
+
}
|
|
467
|
+
async function compileOpenCapitalCallContract(sdk, input) {
|
|
468
|
+
return sdk.compileFromFile({
|
|
469
|
+
simfPath: input.simfPath ?? resolveFundDocsAsset("fund-capital-call-open.simf"),
|
|
470
|
+
templateVars: {
|
|
471
|
+
MANAGER_XONLY: input.definition.managerXonly,
|
|
472
|
+
LP_XONLY: input.capitalCall.lpXonly,
|
|
473
|
+
CLAIM_CUTOFF_HEIGHT: input.capitalCall.claimCutoffHeight,
|
|
474
|
+
REFUND_ONLY_SCRIPT_HASH: input.refundOnlyScriptHash,
|
|
475
|
+
CAPITAL_CALL_STAGE: "OPEN",
|
|
476
|
+
},
|
|
477
|
+
definition: {
|
|
478
|
+
type: "fund-definition",
|
|
479
|
+
id: input.definition.fundId,
|
|
480
|
+
value: input.definition,
|
|
481
|
+
anchorMode: "on-chain-constant-committed",
|
|
482
|
+
},
|
|
483
|
+
state: {
|
|
484
|
+
type: "fund-capital-call",
|
|
485
|
+
id: input.capitalCall.callId,
|
|
486
|
+
value: input.capitalCall,
|
|
487
|
+
anchorMode: "on-chain-constant-committed",
|
|
488
|
+
},
|
|
489
|
+
artifactPath: input.artifactPath,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
async function compileRefundOnlyCapitalCallContract(sdk, input) {
|
|
493
|
+
return sdk.compileFromFile({
|
|
494
|
+
simfPath: input.simfPath ?? resolveFundDocsAsset("fund-capital-call-refund-only.simf"),
|
|
495
|
+
templateVars: {
|
|
496
|
+
LP_XONLY: input.capitalCall.lpXonly,
|
|
497
|
+
CAPITAL_CALL_STAGE: "REFUND_ONLY",
|
|
498
|
+
},
|
|
499
|
+
definition: {
|
|
500
|
+
type: "fund-definition",
|
|
501
|
+
id: input.definition.fundId,
|
|
502
|
+
value: input.definition,
|
|
503
|
+
anchorMode: "on-chain-constant-committed",
|
|
504
|
+
},
|
|
505
|
+
state: {
|
|
506
|
+
type: "fund-capital-call",
|
|
507
|
+
id: input.capitalCall.callId,
|
|
508
|
+
value: input.capitalCall,
|
|
509
|
+
anchorMode: "on-chain-constant-committed",
|
|
510
|
+
},
|
|
511
|
+
artifactPath: input.artifactPath,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
async function compileDistributionClaimContract(sdk, input) {
|
|
515
|
+
return sdk.compileFromFile({
|
|
516
|
+
simfPath: input.simfPath ?? resolveFundDocsAsset("fund-distribution-claim.simf"),
|
|
517
|
+
templateVars: {
|
|
518
|
+
LP_XONLY: input.receipt.lpXonly,
|
|
519
|
+
},
|
|
520
|
+
definition: {
|
|
521
|
+
type: "fund-definition",
|
|
522
|
+
id: input.definition.fundId,
|
|
523
|
+
value: input.definition,
|
|
524
|
+
anchorMode: "on-chain-constant-committed",
|
|
525
|
+
},
|
|
526
|
+
state: {
|
|
527
|
+
type: "fund-distribution",
|
|
528
|
+
id: input.distribution.distributionId,
|
|
529
|
+
value: input.distribution,
|
|
530
|
+
anchorMode: "on-chain-constant-committed",
|
|
531
|
+
},
|
|
532
|
+
artifactPath: input.artifactPath,
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
async function requireFundArtifact(sdk, input) {
|
|
536
|
+
const artifact = input.artifact ?? (input.artifactPath ? (await sdk.loadArtifact(input.artifactPath)).artifact : undefined);
|
|
537
|
+
if (!artifact) {
|
|
538
|
+
throw new errors_1.ValidationError("artifactPath or artifact is required", { code: "FUND_ARTIFACT_REQUIRED" });
|
|
539
|
+
}
|
|
540
|
+
return artifact;
|
|
541
|
+
}
|
|
542
|
+
async function requireRefundOnlyArtifact(sdk, input) {
|
|
543
|
+
const artifact = input.refundOnlyArtifact
|
|
544
|
+
?? (input.refundOnlyArtifactPath ? (await sdk.loadArtifact(input.refundOnlyArtifactPath)).artifact : undefined);
|
|
545
|
+
if (!artifact) {
|
|
546
|
+
throw new errors_1.ValidationError("refundOnlyArtifactPath or refundOnlyArtifact is required", {
|
|
547
|
+
code: "FUND_REFUND_ONLY_ARTIFACT_REQUIRED",
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
return artifact;
|
|
551
|
+
}
|
|
552
|
+
function ensureVerifiedEnvelope(definition, envelope) {
|
|
553
|
+
const checks = (0, fundValidation_1.verifyLPPositionReceiptEnvelope)({
|
|
554
|
+
envelope,
|
|
555
|
+
expectedManagerXonly: definition.managerXonly,
|
|
556
|
+
});
|
|
557
|
+
if (!checks.positionReceiptHashMatch
|
|
558
|
+
|| !checks.sequenceMatch
|
|
559
|
+
|| !checks.sequenceMonotonic
|
|
560
|
+
|| !checks.attestingSignerMatch
|
|
561
|
+
|| !checks.attestationVerified) {
|
|
562
|
+
throw new errors_1.ValidationError("Position receipt envelope must be attested and internally consistent", {
|
|
563
|
+
code: "FUND_POSITION_ENVELOPE_VERIFY_FAILED",
|
|
564
|
+
checks,
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
return checks;
|
|
568
|
+
}
|
|
569
|
+
function buildReceiptTrust(input) {
|
|
570
|
+
return {
|
|
571
|
+
generated: input.generated,
|
|
572
|
+
positionReceiptHash: (0, fundValidation_1.summarizeLPPositionReceipt)(input.envelope.receipt).hash,
|
|
573
|
+
positionStatus: input.envelope.receipt.status,
|
|
574
|
+
attested: true,
|
|
575
|
+
attestationVerified: input.checks.attestationVerified,
|
|
576
|
+
sequence: input.envelope.receipt.sequence,
|
|
577
|
+
sequenceMonotonic: input.checks.sequenceMonotonic && input.checks.sequenceMatch,
|
|
578
|
+
attestingSignerMatch: input.checks.attestingSignerMatch,
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
async function define(sdk, input) {
|
|
582
|
+
const definition = await loadFundDefinitionDocument(sdk, input);
|
|
583
|
+
return {
|
|
584
|
+
ok: true,
|
|
585
|
+
definition: definition.descriptor,
|
|
586
|
+
definitionValue: definition.value,
|
|
587
|
+
summary: definition.summary,
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
async function verify(sdk, input) {
|
|
591
|
+
return define(sdk, input);
|
|
592
|
+
}
|
|
593
|
+
async function load(sdk, input) {
|
|
594
|
+
const result = await define(sdk, input);
|
|
595
|
+
return {
|
|
596
|
+
definition: result.definition,
|
|
597
|
+
definitionValue: result.definitionValue,
|
|
598
|
+
summary: result.summary,
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
async function prepareCapitalCall(sdk, input) {
|
|
602
|
+
const definition = await loadFundDefinitionDocument(sdk, input);
|
|
603
|
+
const capitalCall = await loadCapitalCallDocument(sdk, input);
|
|
604
|
+
assertOpenCapitalCall(capitalCall.value);
|
|
605
|
+
const crossChecks = (0, fundValidation_1.validateFundCrossChecks)(definition.value, capitalCall.value);
|
|
606
|
+
const refundOnlyCapitalCallValue = (0, fundValidation_1.buildRefundOnlyCapitalCallState)({ previous: capitalCall.value });
|
|
607
|
+
const refundOnlyCompiled = await compileRefundOnlyCapitalCallContract(sdk, {
|
|
608
|
+
definition: definition.value,
|
|
609
|
+
capitalCall: refundOnlyCapitalCallValue,
|
|
610
|
+
simfPath: input.refundOnlySimfPath,
|
|
611
|
+
artifactPath: input.refundOnlyArtifactPath,
|
|
612
|
+
});
|
|
613
|
+
const refundOnlyScriptPubKeyHex = await (0, outputBinding_1.getScriptPubKeyHexViaRpc)(sdk, refundOnlyCompiled.deployment().contractAddress);
|
|
614
|
+
const openCompiled = await compileOpenCapitalCallContract(sdk, {
|
|
615
|
+
definition: definition.value,
|
|
616
|
+
capitalCall: capitalCall.value,
|
|
617
|
+
refundOnlyScriptHash: (0, outputBinding_1.hashHexBytes)(refundOnlyScriptPubKeyHex),
|
|
618
|
+
simfPath: input.openSimfPath,
|
|
619
|
+
artifactPath: input.openArtifactPath,
|
|
620
|
+
});
|
|
621
|
+
return {
|
|
622
|
+
definition: definition.descriptor,
|
|
623
|
+
definitionValue: definition.value,
|
|
624
|
+
definitionSummary: definition.summary,
|
|
625
|
+
capitalCall: capitalCall.descriptor,
|
|
626
|
+
capitalCallValue: capitalCall.value,
|
|
627
|
+
capitalCallSummary: capitalCall.summary,
|
|
628
|
+
refundOnlyCapitalCallValue,
|
|
629
|
+
refundOnlyCapitalCallSummary: (0, fundValidation_1.summarizeCapitalCallState)(refundOnlyCapitalCallValue),
|
|
630
|
+
crossChecks,
|
|
631
|
+
openCompiled,
|
|
632
|
+
refundOnlyCompiled,
|
|
633
|
+
report: {
|
|
634
|
+
...buildBaseFundReport(),
|
|
635
|
+
capitalCallTrust: {
|
|
636
|
+
capitalCallStage: deriveCapitalCallStage(capitalCall.value),
|
|
637
|
+
cutoffMode: "rollover-window",
|
|
638
|
+
fundIdMatch: crossChecks.fundIdMatch,
|
|
639
|
+
currencyMatch: crossChecks.currencyMatch,
|
|
640
|
+
managerMatch: crossChecks.managerMatch,
|
|
641
|
+
claimCutoffCommitted: true,
|
|
642
|
+
lpCommitted: true,
|
|
643
|
+
managerCommitted: true,
|
|
644
|
+
claimPathRuntimeAvailable: true,
|
|
645
|
+
refundPathRuntimeAvailable: false,
|
|
646
|
+
statusValid: true,
|
|
647
|
+
},
|
|
648
|
+
},
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
async function verifyCapitalCall(sdk, input) {
|
|
652
|
+
const definition = await loadFundDefinitionDocument(sdk, input);
|
|
653
|
+
const capitalCall = await loadCapitalCallDocument(sdk, input);
|
|
654
|
+
const crossChecks = (0, fundValidation_1.validateFundCrossChecks)(definition.value, capitalCall.value);
|
|
655
|
+
const artifact = input.artifact ?? (input.artifactPath ? (await sdk.loadArtifact(input.artifactPath)).artifact : undefined);
|
|
656
|
+
const definitionVerification = artifact
|
|
657
|
+
? await sdk.verifyDefinitionAgainstArtifact({
|
|
658
|
+
artifact,
|
|
659
|
+
type: "fund-definition",
|
|
660
|
+
id: definition.value.fundId,
|
|
661
|
+
value: definition.value,
|
|
662
|
+
})
|
|
663
|
+
: undefined;
|
|
664
|
+
const stateVerification = artifact
|
|
665
|
+
? await sdk.verifyStateAgainstArtifact({
|
|
666
|
+
artifact,
|
|
667
|
+
type: "fund-capital-call",
|
|
668
|
+
id: capitalCall.value.callId,
|
|
669
|
+
value: capitalCall.value,
|
|
670
|
+
})
|
|
671
|
+
: undefined;
|
|
672
|
+
const artifactVars = artifact?.source.templateVars ?? {};
|
|
673
|
+
const artifactStage = artifact ? String(artifactVars.CAPITAL_CALL_STAGE ?? "").toUpperCase() : "";
|
|
674
|
+
const claimCutoffCommitted = artifact
|
|
675
|
+
? Number(artifactVars.CLAIM_CUTOFF_HEIGHT ?? capitalCall.value.claimCutoffHeight) === capitalCall.value.claimCutoffHeight
|
|
676
|
+
: true;
|
|
677
|
+
const lpCommitted = artifact
|
|
678
|
+
? String(artifactVars.LP_XONLY ?? "").toLowerCase() === capitalCall.value.lpXonly.toLowerCase()
|
|
679
|
+
: true;
|
|
680
|
+
const managerCommitted = artifact
|
|
681
|
+
? artifactStage === "REFUND_ONLY"
|
|
682
|
+
? true
|
|
683
|
+
: String(artifactVars.MANAGER_XONLY ?? "").toLowerCase() === definition.value.managerXonly.toLowerCase()
|
|
684
|
+
: true;
|
|
685
|
+
const stageValid = artifact
|
|
686
|
+
? (capitalCall.value.status === "OPEN" && artifactStage === "OPEN")
|
|
687
|
+
|| (capitalCall.value.status === "REFUND_ONLY" && artifactStage === "REFUND_ONLY")
|
|
688
|
+
: true;
|
|
689
|
+
const claimPathRuntimeAvailable = capitalCall.value.status === "OPEN";
|
|
690
|
+
const refundPathRuntimeAvailable = capitalCall.value.status === "REFUND_ONLY";
|
|
691
|
+
const statusValid = true;
|
|
692
|
+
const ok = (!definitionVerification || definitionVerification.ok)
|
|
693
|
+
&& (!stateVerification || stateVerification.ok)
|
|
694
|
+
&& crossChecks.fundIdMatch
|
|
695
|
+
&& crossChecks.currencyMatch
|
|
696
|
+
&& crossChecks.managerMatch
|
|
697
|
+
&& claimCutoffCommitted
|
|
698
|
+
&& lpCommitted
|
|
699
|
+
&& managerCommitted
|
|
700
|
+
&& stageValid
|
|
701
|
+
&& statusValid;
|
|
702
|
+
return {
|
|
703
|
+
ok,
|
|
704
|
+
reason: ok ? undefined : "Fund capital call verification failed",
|
|
705
|
+
artifact,
|
|
706
|
+
definition: definition.descriptor,
|
|
707
|
+
definitionValue: definition.value,
|
|
708
|
+
capitalCall: capitalCall.descriptor,
|
|
709
|
+
capitalCallValue: capitalCall.value,
|
|
710
|
+
definitionSummary: definition.summary,
|
|
711
|
+
capitalCallSummary: capitalCall.summary,
|
|
712
|
+
artifactVerification: artifact && definitionVerification && stateVerification
|
|
713
|
+
? { definition: definitionVerification, state: stateVerification }
|
|
714
|
+
: undefined,
|
|
715
|
+
report: {
|
|
716
|
+
...buildBaseFundReport(),
|
|
717
|
+
...(definitionVerification && stateVerification
|
|
718
|
+
? {
|
|
719
|
+
artifactTrust: {
|
|
720
|
+
definition: definitionVerification.trust,
|
|
721
|
+
state: stateVerification.trust,
|
|
722
|
+
},
|
|
723
|
+
stateTrust: stateVerification.trust,
|
|
724
|
+
}
|
|
725
|
+
: {}),
|
|
726
|
+
capitalCallTrust: {
|
|
727
|
+
capitalCallStage: deriveCapitalCallStage(capitalCall.value),
|
|
728
|
+
cutoffMode: "rollover-window",
|
|
729
|
+
fundIdMatch: crossChecks.fundIdMatch,
|
|
730
|
+
currencyMatch: crossChecks.currencyMatch,
|
|
731
|
+
managerMatch: crossChecks.managerMatch,
|
|
732
|
+
claimCutoffCommitted,
|
|
733
|
+
lpCommitted,
|
|
734
|
+
managerCommitted,
|
|
735
|
+
claimPathRuntimeAvailable,
|
|
736
|
+
refundPathRuntimeAvailable,
|
|
737
|
+
statusValid,
|
|
738
|
+
},
|
|
739
|
+
},
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
async function signPositionReceipt(sdk, input) {
|
|
743
|
+
const definition = await loadFundDefinitionDocument(sdk, input);
|
|
744
|
+
const receipt = await loadBarePositionReceiptDocument(sdk, input);
|
|
745
|
+
const positionReceiptEnvelope = (0, fundValidation_1.signLPPositionReceipt)({
|
|
746
|
+
receipt: receipt.value,
|
|
747
|
+
managerXonly: definition.value.managerXonly,
|
|
748
|
+
signer: input.signer,
|
|
749
|
+
signedAt: input.signedAt,
|
|
750
|
+
});
|
|
751
|
+
return {
|
|
752
|
+
definition: definition.descriptor,
|
|
753
|
+
definitionValue: definition.value,
|
|
754
|
+
definitionSummary: definition.summary,
|
|
755
|
+
positionReceipt: receipt.descriptor,
|
|
756
|
+
positionReceiptValue: receipt.value,
|
|
757
|
+
positionReceiptSummary: receipt.summary,
|
|
758
|
+
positionReceiptEnvelope,
|
|
759
|
+
positionReceiptEnvelopeSummary: (0, fundValidation_1.summarizeLPPositionReceiptEnvelope)(positionReceiptEnvelope),
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
async function verifyPositionReceipt(sdk, input) {
|
|
763
|
+
const definition = await loadFundDefinitionDocument(sdk, input);
|
|
764
|
+
const envelope = await loadPositionReceiptEnvelopeDocument(sdk, input);
|
|
765
|
+
const receiptChecks = ensureVerifiedEnvelope(definition.value, envelope.value);
|
|
766
|
+
return {
|
|
767
|
+
verified: true,
|
|
768
|
+
definition: definition.descriptor,
|
|
769
|
+
definitionValue: definition.value,
|
|
770
|
+
definitionSummary: definition.summary,
|
|
771
|
+
positionReceipt: envelope.descriptor,
|
|
772
|
+
positionReceiptValue: envelope.value,
|
|
773
|
+
positionReceiptSummary: envelope.receiptSummary,
|
|
774
|
+
positionReceiptEnvelopeSummary: envelope.envelopeSummary,
|
|
775
|
+
receiptChecks,
|
|
776
|
+
report: {
|
|
777
|
+
...buildBaseFundReport(),
|
|
778
|
+
receiptTrust: buildReceiptTrust({ generated: false, envelope: envelope.value, checks: receiptChecks }),
|
|
779
|
+
},
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
async function inspectCapitalCallClaim(sdk, input) {
|
|
783
|
+
const verified = await verifyCapitalCall(sdk, input);
|
|
784
|
+
if (!verified.ok) {
|
|
785
|
+
throw new errors_1.ValidationError(verified.reason ?? "Fund capital call verification failed", {
|
|
786
|
+
code: "FUND_CAPITAL_CALL_VERIFY_FAILED",
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
assertOpenCapitalCall(verified.capitalCallValue);
|
|
790
|
+
const definition = await loadFundDefinitionDocument(sdk, input);
|
|
791
|
+
const artifact = await requireFundArtifact(sdk, input);
|
|
792
|
+
const payout = await buildFundPayoutDescriptor(sdk, {
|
|
793
|
+
receiverAddress: input.payoutAddress,
|
|
794
|
+
amountSat: verified.capitalCallValue.amount,
|
|
795
|
+
assetId: verified.capitalCallValue.currencyAssetId,
|
|
796
|
+
nextOutputHash: input.nextOutputHash,
|
|
797
|
+
outputForm: input.outputForm,
|
|
798
|
+
rawOutput: input.rawOutput,
|
|
799
|
+
outputBindingMode: input.outputBindingMode,
|
|
800
|
+
});
|
|
801
|
+
const claimedAt = input.claimedAt ?? nowIso();
|
|
802
|
+
const claimedCapitalCall = (0, fundValidation_1.buildClaimedCapitalCallState)({ previous: verified.capitalCallValue, claimedAt });
|
|
803
|
+
const positionReceipt = (0, fundValidation_1.buildLPPositionReceipt)({
|
|
804
|
+
positionId: input.positionId ?? defaultPositionId(verified.capitalCallValue),
|
|
805
|
+
capitalCall: verified.capitalCallValue,
|
|
806
|
+
effectiveAt: claimedAt,
|
|
807
|
+
});
|
|
808
|
+
const positionReceiptEnvelope = (0, fundValidation_1.signLPPositionReceipt)({
|
|
809
|
+
receipt: positionReceipt,
|
|
810
|
+
managerXonly: definition.value.managerXonly,
|
|
811
|
+
signer: input.signer,
|
|
812
|
+
signedAt: claimedAt,
|
|
813
|
+
});
|
|
814
|
+
const receiptChecks = ensureVerifiedEnvelope(definition.value, positionReceiptEnvelope);
|
|
815
|
+
const contract = sdk.fromArtifact(artifact);
|
|
816
|
+
const inspect = await contract.inspectCall({
|
|
817
|
+
wallet: input.wallet,
|
|
818
|
+
toAddress: payout.descriptor.receiverAddress,
|
|
819
|
+
signer: input.signer,
|
|
820
|
+
sendAmount: satToBtcAmount(payout.descriptor.amountSat),
|
|
821
|
+
feeSat: input.feeSat,
|
|
822
|
+
utxoPolicy: input.utxoPolicy,
|
|
823
|
+
witness: buildClaimWitness(payout.descriptor),
|
|
824
|
+
});
|
|
825
|
+
return {
|
|
826
|
+
mode: "claim",
|
|
827
|
+
verified,
|
|
828
|
+
payoutDescriptor: payout.descriptor,
|
|
829
|
+
payoutSummary: payout.summary,
|
|
830
|
+
claimedCapitalCall,
|
|
831
|
+
positionReceipt,
|
|
832
|
+
positionReceiptEnvelope,
|
|
833
|
+
positionReceiptEnvelopeSummary: (0, fundValidation_1.summarizeLPPositionReceiptEnvelope)(positionReceiptEnvelope),
|
|
834
|
+
inspect,
|
|
835
|
+
report: {
|
|
836
|
+
...verified.report,
|
|
837
|
+
outputBindingTrust: buildFundOutputBindingReport({
|
|
838
|
+
descriptor: payout.descriptor,
|
|
839
|
+
supportedForm: payout.supportedForm,
|
|
840
|
+
reasonCode: payout.reasonCode,
|
|
841
|
+
autoDerived: payout.autoDerivedNextOutputHash,
|
|
842
|
+
fallbackReason: payout.fallbackReason,
|
|
843
|
+
bindingInputs: payout.bindingInputs,
|
|
844
|
+
}),
|
|
845
|
+
receiptTrust: buildReceiptTrust({ generated: true, envelope: positionReceiptEnvelope, checks: receiptChecks }),
|
|
846
|
+
},
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
async function executeCapitalCallClaim(sdk, input) {
|
|
850
|
+
const inspected = await inspectCapitalCallClaim(sdk, input);
|
|
851
|
+
const artifact = await requireFundArtifact(sdk, input);
|
|
852
|
+
const contract = sdk.fromArtifact(artifact);
|
|
853
|
+
const execution = await contract.execute({
|
|
854
|
+
wallet: input.wallet,
|
|
855
|
+
toAddress: inspected.payoutDescriptor.receiverAddress,
|
|
856
|
+
signer: input.signer,
|
|
857
|
+
sendAmount: satToBtcAmount(inspected.payoutDescriptor.amountSat),
|
|
858
|
+
feeSat: input.feeSat,
|
|
859
|
+
utxoPolicy: input.utxoPolicy,
|
|
860
|
+
broadcast: input.broadcast,
|
|
861
|
+
witness: buildClaimWitness(inspected.payoutDescriptor),
|
|
862
|
+
});
|
|
863
|
+
return {
|
|
864
|
+
...inspected,
|
|
865
|
+
mode: "claim",
|
|
866
|
+
execution,
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
async function inspectCapitalCallRollover(sdk, input) {
|
|
870
|
+
const verified = await verifyCapitalCall(sdk, input);
|
|
871
|
+
if (!verified.ok) {
|
|
872
|
+
throw new errors_1.ValidationError(verified.reason ?? "Fund capital call verification failed", {
|
|
873
|
+
code: "FUND_CAPITAL_CALL_VERIFY_FAILED",
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
assertOpenCapitalCall(verified.capitalCallValue);
|
|
877
|
+
const artifact = await requireFundArtifact(sdk, input);
|
|
878
|
+
const refundOnlyArtifact = await requireRefundOnlyArtifact(sdk, input);
|
|
879
|
+
const rolledOverCapitalCall = (0, fundValidation_1.buildRefundOnlyCapitalCallState)({ previous: verified.capitalCallValue });
|
|
880
|
+
const contract = sdk.fromArtifact(artifact);
|
|
881
|
+
const inspect = await contract.inspectCall({
|
|
882
|
+
wallet: input.wallet,
|
|
883
|
+
toAddress: refundOnlyArtifact.compiled.contractAddress,
|
|
884
|
+
signer: input.signer,
|
|
885
|
+
sendAmount: satToBtcAmount(verified.capitalCallValue.amount),
|
|
886
|
+
feeSat: input.feeSat,
|
|
887
|
+
utxoPolicy: input.utxoPolicy,
|
|
888
|
+
locktimeHeight: verified.capitalCallValue.claimCutoffHeight,
|
|
889
|
+
witness: buildRolloverWitness(),
|
|
890
|
+
});
|
|
891
|
+
return {
|
|
892
|
+
mode: "rollover",
|
|
893
|
+
verified,
|
|
894
|
+
refundOnlyArtifact,
|
|
895
|
+
rolledOverCapitalCall,
|
|
896
|
+
inspect,
|
|
897
|
+
report: verified.report,
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
async function executeCapitalCallRollover(sdk, input) {
|
|
901
|
+
const inspected = await inspectCapitalCallRollover(sdk, input);
|
|
902
|
+
const artifact = await requireFundArtifact(sdk, input);
|
|
903
|
+
const contract = sdk.fromArtifact(artifact);
|
|
904
|
+
const execution = await contract.execute({
|
|
905
|
+
wallet: input.wallet,
|
|
906
|
+
toAddress: inspected.refundOnlyArtifact.compiled.contractAddress,
|
|
907
|
+
signer: input.signer,
|
|
908
|
+
sendAmount: satToBtcAmount(inspected.verified.capitalCallValue.amount),
|
|
909
|
+
feeSat: input.feeSat,
|
|
910
|
+
utxoPolicy: input.utxoPolicy,
|
|
911
|
+
locktimeHeight: inspected.verified.capitalCallValue.claimCutoffHeight,
|
|
912
|
+
broadcast: input.broadcast,
|
|
913
|
+
witness: buildRolloverWitness(),
|
|
914
|
+
});
|
|
915
|
+
return {
|
|
916
|
+
...inspected,
|
|
917
|
+
mode: "rollover",
|
|
918
|
+
execution,
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
async function inspectCapitalCallRefund(sdk, input) {
|
|
922
|
+
const verified = await verifyCapitalCall(sdk, input);
|
|
923
|
+
if (!verified.ok) {
|
|
924
|
+
throw new errors_1.ValidationError(verified.reason ?? "Fund capital call verification failed", {
|
|
925
|
+
code: "FUND_CAPITAL_CALL_VERIFY_FAILED",
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
assertRefundOnlyCapitalCall(verified.capitalCallValue);
|
|
929
|
+
const artifact = await requireFundArtifact(sdk, input);
|
|
930
|
+
const refundedAt = input.refundedAt ?? nowIso();
|
|
931
|
+
const refundedCapitalCall = (0, fundValidation_1.buildRefundedCapitalCallState)({ previous: verified.capitalCallValue, refundedAt });
|
|
932
|
+
const feeSat = input.feeSat ?? 100;
|
|
933
|
+
const refundAmountSat = verified.capitalCallValue.amount - feeSat;
|
|
934
|
+
if (refundAmountSat <= 0) {
|
|
935
|
+
throw new errors_1.ValidationError("Refund amount must remain positive after fee deduction", {
|
|
936
|
+
code: "FUND_REFUND_AMOUNT_INVALID",
|
|
937
|
+
details: {
|
|
938
|
+
amount: verified.capitalCallValue.amount,
|
|
939
|
+
feeSat,
|
|
940
|
+
},
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
const contract = sdk.fromArtifact(artifact);
|
|
944
|
+
const inspect = await contract.inspectCall({
|
|
945
|
+
wallet: input.wallet,
|
|
946
|
+
toAddress: input.refundAddress,
|
|
947
|
+
signer: input.signer,
|
|
948
|
+
sendAmount: satToBtcAmount(refundAmountSat),
|
|
949
|
+
feeSat,
|
|
950
|
+
utxoPolicy: input.utxoPolicy,
|
|
951
|
+
witness: buildRefundWitness(),
|
|
952
|
+
});
|
|
953
|
+
return {
|
|
954
|
+
mode: "refund",
|
|
955
|
+
verified,
|
|
956
|
+
refundedCapitalCall,
|
|
957
|
+
inspect,
|
|
958
|
+
report: verified.report,
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
async function executeCapitalCallRefund(sdk, input) {
|
|
962
|
+
const inspected = await inspectCapitalCallRefund(sdk, input);
|
|
963
|
+
const artifact = await requireFundArtifact(sdk, input);
|
|
964
|
+
const contract = sdk.fromArtifact(artifact);
|
|
965
|
+
const feeSat = input.feeSat ?? 100;
|
|
966
|
+
const refundAmountSat = inspected.verified.capitalCallValue.amount - feeSat;
|
|
967
|
+
const execution = await contract.execute({
|
|
968
|
+
wallet: input.wallet,
|
|
969
|
+
toAddress: input.refundAddress,
|
|
970
|
+
signer: input.signer,
|
|
971
|
+
sendAmount: satToBtcAmount(refundAmountSat),
|
|
972
|
+
feeSat,
|
|
973
|
+
utxoPolicy: input.utxoPolicy,
|
|
974
|
+
broadcast: input.broadcast,
|
|
975
|
+
witness: buildRefundWitness(),
|
|
976
|
+
});
|
|
977
|
+
return {
|
|
978
|
+
...inspected,
|
|
979
|
+
mode: "refund",
|
|
980
|
+
execution,
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
async function prepareDistribution(sdk, input) {
|
|
984
|
+
const definition = await loadFundDefinitionDocument(sdk, input);
|
|
985
|
+
const receipt = await loadPositionReceiptEnvelopeDocument(sdk, input);
|
|
986
|
+
const receiptChecks = ensureVerifiedEnvelope(definition.value, receipt.value);
|
|
987
|
+
const distribution = await loadDistributionDocument(sdk, {
|
|
988
|
+
distributionPath: input.distributionPath,
|
|
989
|
+
distributionValue: input.distributionValue,
|
|
990
|
+
positionReceipt: receipt.value.receipt,
|
|
991
|
+
distributionId: input.distributionId,
|
|
992
|
+
assetId: input.assetId,
|
|
993
|
+
amountSat: input.amountSat,
|
|
994
|
+
approvedAt: input.approvedAt,
|
|
995
|
+
});
|
|
996
|
+
const crossChecks = (0, fundValidation_1.validateDistributionAgainstReceipt)(receipt.value.receipt, distribution.value);
|
|
997
|
+
const compiled = await compileDistributionClaimContract(sdk, {
|
|
998
|
+
definition: definition.value,
|
|
999
|
+
distribution: distribution.value,
|
|
1000
|
+
receipt: receipt.value.receipt,
|
|
1001
|
+
simfPath: input.simfPath,
|
|
1002
|
+
artifactPath: input.artifactPath,
|
|
1003
|
+
});
|
|
1004
|
+
return {
|
|
1005
|
+
definition: definition.descriptor,
|
|
1006
|
+
definitionValue: definition.value,
|
|
1007
|
+
definitionSummary: definition.summary,
|
|
1008
|
+
positionReceipt: receipt.descriptor,
|
|
1009
|
+
positionReceiptValue: receipt.value,
|
|
1010
|
+
positionReceiptSummary: receipt.receiptSummary,
|
|
1011
|
+
positionReceiptEnvelopeSummary: receipt.envelopeSummary,
|
|
1012
|
+
distribution: distribution.descriptor,
|
|
1013
|
+
distributionValue: distribution.value,
|
|
1014
|
+
distributionSummary: distribution.summary,
|
|
1015
|
+
crossChecks,
|
|
1016
|
+
compiled,
|
|
1017
|
+
report: {
|
|
1018
|
+
...buildBaseFundReport(),
|
|
1019
|
+
distributionTrust: crossChecks,
|
|
1020
|
+
receiptTrust: buildReceiptTrust({ generated: false, envelope: receipt.value, checks: receiptChecks }),
|
|
1021
|
+
},
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
async function reconcilePosition(sdk, input) {
|
|
1025
|
+
const definition = await loadFundDefinitionDocument(sdk, input);
|
|
1026
|
+
const receipt = await loadPositionReceiptEnvelopeDocument(sdk, input);
|
|
1027
|
+
ensureVerifiedEnvelope(definition.value, receipt.value);
|
|
1028
|
+
const distributions = await loadDistributionDocuments(sdk, input);
|
|
1029
|
+
const reconciledReceiptValue = (0, fundValidation_1.reconcileLPPositionReceipt)({
|
|
1030
|
+
previousEnvelope: receipt.value,
|
|
1031
|
+
distributions: distributions.map((entry) => entry.value),
|
|
1032
|
+
});
|
|
1033
|
+
const reconciledReceiptEnvelope = (0, fundValidation_1.signLPPositionReceipt)({
|
|
1034
|
+
receipt: reconciledReceiptValue,
|
|
1035
|
+
managerXonly: definition.value.managerXonly,
|
|
1036
|
+
signer: input.signer,
|
|
1037
|
+
signedAt: input.signedAt,
|
|
1038
|
+
});
|
|
1039
|
+
return {
|
|
1040
|
+
definition: definition.descriptor,
|
|
1041
|
+
definitionValue: definition.value,
|
|
1042
|
+
definitionSummary: definition.summary,
|
|
1043
|
+
positionReceipt: receipt.descriptor,
|
|
1044
|
+
positionReceiptValue: receipt.value,
|
|
1045
|
+
positionReceiptSummary: receipt.receiptSummary,
|
|
1046
|
+
positionReceiptEnvelopeSummary: receipt.envelopeSummary,
|
|
1047
|
+
distributions: distributions.map((distribution) => distribution.descriptor),
|
|
1048
|
+
distributionValues: distributions.map((distribution) => distribution.value),
|
|
1049
|
+
distributionSummaries: distributions.map((distribution) => distribution.summary),
|
|
1050
|
+
reconciledReceiptValue,
|
|
1051
|
+
reconciledReceiptSummary: (0, fundValidation_1.summarizeLPPositionReceipt)(reconciledReceiptValue),
|
|
1052
|
+
reconciledReceiptEnvelope,
|
|
1053
|
+
reconciledReceiptEnvelopeSummary: (0, fundValidation_1.summarizeLPPositionReceiptEnvelope)(reconciledReceiptEnvelope),
|
|
1054
|
+
totalDistributedAmount: reconciledReceiptValue.distributedAmount,
|
|
1055
|
+
distributionCount: reconciledReceiptValue.distributionCount,
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
async function verifyDistribution(sdk, input) {
|
|
1059
|
+
const definition = await loadFundDefinitionDocument(sdk, input);
|
|
1060
|
+
const receipt = await loadPositionReceiptEnvelopeDocument(sdk, input);
|
|
1061
|
+
const receiptChecks = ensureVerifiedEnvelope(definition.value, receipt.value);
|
|
1062
|
+
const distribution = await loadDistributionDocument(sdk, {
|
|
1063
|
+
distributionPath: input.distributionPath,
|
|
1064
|
+
distributionValue: input.distributionValue,
|
|
1065
|
+
});
|
|
1066
|
+
const crossChecks = (0, fundValidation_1.validateDistributionAgainstReceipt)(receipt.value.receipt, distribution.value);
|
|
1067
|
+
const artifact = input.artifact ?? (input.artifactPath ? (await sdk.loadArtifact(input.artifactPath)).artifact : undefined);
|
|
1068
|
+
const definitionVerification = artifact
|
|
1069
|
+
? await sdk.verifyDefinitionAgainstArtifact({
|
|
1070
|
+
artifact,
|
|
1071
|
+
type: "fund-definition",
|
|
1072
|
+
id: definition.value.fundId,
|
|
1073
|
+
value: definition.value,
|
|
1074
|
+
})
|
|
1075
|
+
: undefined;
|
|
1076
|
+
const stateVerification = artifact
|
|
1077
|
+
? await sdk.verifyStateAgainstArtifact({
|
|
1078
|
+
artifact,
|
|
1079
|
+
type: "fund-distribution",
|
|
1080
|
+
id: distribution.value.distributionId,
|
|
1081
|
+
value: distribution.value,
|
|
1082
|
+
})
|
|
1083
|
+
: undefined;
|
|
1084
|
+
const artifactVars = artifact?.source.templateVars ?? {};
|
|
1085
|
+
const lpCommitted = artifact
|
|
1086
|
+
? String(artifactVars.LP_XONLY ?? "").toLowerCase() === receipt.value.receipt.lpXonly.toLowerCase()
|
|
1087
|
+
: true;
|
|
1088
|
+
const ok = (!definitionVerification || definitionVerification.ok)
|
|
1089
|
+
&& (!stateVerification || stateVerification.ok)
|
|
1090
|
+
&& crossChecks.fundIdMatch
|
|
1091
|
+
&& crossChecks.lpIdMatch
|
|
1092
|
+
&& crossChecks.positionIdMatch
|
|
1093
|
+
&& crossChecks.positionReceiptHashMatch
|
|
1094
|
+
&& crossChecks.positionStatusEligible
|
|
1095
|
+
&& lpCommitted;
|
|
1096
|
+
return {
|
|
1097
|
+
ok,
|
|
1098
|
+
reason: ok ? undefined : "Fund distribution verification failed",
|
|
1099
|
+
artifact,
|
|
1100
|
+
definition: definition.descriptor,
|
|
1101
|
+
definitionValue: definition.value,
|
|
1102
|
+
positionReceipt: receipt.descriptor,
|
|
1103
|
+
positionReceiptValue: receipt.value,
|
|
1104
|
+
distribution: distribution.descriptor,
|
|
1105
|
+
distributionValue: distribution.value,
|
|
1106
|
+
definitionSummary: definition.summary,
|
|
1107
|
+
positionReceiptSummary: receipt.receiptSummary,
|
|
1108
|
+
positionReceiptEnvelopeSummary: receipt.envelopeSummary,
|
|
1109
|
+
distributionSummary: distribution.summary,
|
|
1110
|
+
artifactVerification: artifact && definitionVerification && stateVerification
|
|
1111
|
+
? { definition: definitionVerification, state: stateVerification }
|
|
1112
|
+
: undefined,
|
|
1113
|
+
report: {
|
|
1114
|
+
...buildBaseFundReport(),
|
|
1115
|
+
...(definitionVerification && stateVerification
|
|
1116
|
+
? {
|
|
1117
|
+
artifactTrust: {
|
|
1118
|
+
definition: definitionVerification.trust,
|
|
1119
|
+
state: stateVerification.trust,
|
|
1120
|
+
},
|
|
1121
|
+
stateTrust: stateVerification.trust,
|
|
1122
|
+
}
|
|
1123
|
+
: {}),
|
|
1124
|
+
distributionTrust: {
|
|
1125
|
+
...crossChecks,
|
|
1126
|
+
positionStatusEligible: crossChecks.positionStatusEligible && lpCommitted,
|
|
1127
|
+
},
|
|
1128
|
+
receiptTrust: buildReceiptTrust({ generated: false, envelope: receipt.value, checks: receiptChecks }),
|
|
1129
|
+
},
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
async function inspectDistributionClaim(sdk, input) {
|
|
1133
|
+
const verified = await verifyDistribution(sdk, input);
|
|
1134
|
+
if (!verified.ok) {
|
|
1135
|
+
throw new errors_1.ValidationError(verified.reason ?? "Fund distribution verification failed", {
|
|
1136
|
+
code: "FUND_DISTRIBUTION_VERIFY_FAILED",
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
const artifact = await requireFundArtifact(sdk, input);
|
|
1140
|
+
const payout = await buildFundPayoutDescriptor(sdk, {
|
|
1141
|
+
receiverAddress: input.payoutAddress,
|
|
1142
|
+
amountSat: verified.distributionValue.amountSat,
|
|
1143
|
+
assetId: verified.distributionValue.assetId,
|
|
1144
|
+
nextOutputHash: input.nextOutputHash,
|
|
1145
|
+
outputForm: input.outputForm,
|
|
1146
|
+
rawOutput: input.rawOutput,
|
|
1147
|
+
outputBindingMode: input.outputBindingMode,
|
|
1148
|
+
});
|
|
1149
|
+
const contract = sdk.fromArtifact(artifact);
|
|
1150
|
+
const inspect = await contract.inspectCall({
|
|
1151
|
+
wallet: input.wallet,
|
|
1152
|
+
toAddress: payout.descriptor.receiverAddress,
|
|
1153
|
+
signer: input.signer,
|
|
1154
|
+
sendAmount: satToBtcAmount(payout.descriptor.amountSat),
|
|
1155
|
+
feeSat: input.feeSat,
|
|
1156
|
+
utxoPolicy: input.utxoPolicy,
|
|
1157
|
+
witness: buildDistributionWitness(payout.descriptor),
|
|
1158
|
+
});
|
|
1159
|
+
return {
|
|
1160
|
+
mode: "distribution-claim",
|
|
1161
|
+
verified,
|
|
1162
|
+
payoutDescriptor: payout.descriptor,
|
|
1163
|
+
payoutSummary: payout.summary,
|
|
1164
|
+
inspect,
|
|
1165
|
+
report: {
|
|
1166
|
+
...verified.report,
|
|
1167
|
+
outputBindingTrust: buildFundOutputBindingReport({
|
|
1168
|
+
descriptor: payout.descriptor,
|
|
1169
|
+
supportedForm: payout.supportedForm,
|
|
1170
|
+
reasonCode: payout.reasonCode,
|
|
1171
|
+
autoDerived: payout.autoDerivedNextOutputHash,
|
|
1172
|
+
fallbackReason: payout.fallbackReason,
|
|
1173
|
+
bindingInputs: payout.bindingInputs,
|
|
1174
|
+
}),
|
|
1175
|
+
},
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
async function executeDistributionClaim(sdk, input) {
|
|
1179
|
+
const inspected = await inspectDistributionClaim(sdk, input);
|
|
1180
|
+
const artifact = await requireFundArtifact(sdk, input);
|
|
1181
|
+
const contract = sdk.fromArtifact(artifact);
|
|
1182
|
+
const execution = await contract.execute({
|
|
1183
|
+
wallet: input.wallet,
|
|
1184
|
+
toAddress: inspected.payoutDescriptor.receiverAddress,
|
|
1185
|
+
signer: input.signer,
|
|
1186
|
+
sendAmount: satToBtcAmount(inspected.payoutDescriptor.amountSat),
|
|
1187
|
+
feeSat: input.feeSat,
|
|
1188
|
+
utxoPolicy: input.utxoPolicy,
|
|
1189
|
+
broadcast: input.broadcast,
|
|
1190
|
+
witness: buildDistributionWitness(inspected.payoutDescriptor),
|
|
1191
|
+
});
|
|
1192
|
+
return {
|
|
1193
|
+
...inspected,
|
|
1194
|
+
mode: "distribution-claim",
|
|
1195
|
+
execution,
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
async function prepareClosing(sdk, input) {
|
|
1199
|
+
const definition = input.definitionPath || input.definitionValue
|
|
1200
|
+
? await loadFundDefinitionDocument(sdk, input)
|
|
1201
|
+
: undefined;
|
|
1202
|
+
const receipt = await loadPositionReceiptEnvelopeDocument(sdk, input);
|
|
1203
|
+
const receiptChecks = definition ? ensureVerifiedEnvelope(definition.value, receipt.value) : (0, fundValidation_1.verifyLPPositionReceiptEnvelope)({ envelope: receipt.value });
|
|
1204
|
+
const closing = await loadClosingDocument(sdk, {
|
|
1205
|
+
closingPath: input.closingPath,
|
|
1206
|
+
closingValue: input.closingValue,
|
|
1207
|
+
positionReceipt: receipt.value.receipt,
|
|
1208
|
+
closingId: input.closingId,
|
|
1209
|
+
finalDistributionHashes: input.finalDistributionHashes,
|
|
1210
|
+
closedAt: input.closedAt,
|
|
1211
|
+
closingReason: input.closingReason,
|
|
1212
|
+
});
|
|
1213
|
+
const checks = (0, fundValidation_1.validateClosingAgainstReceipt)(receipt.value.receipt, closing.value);
|
|
1214
|
+
return {
|
|
1215
|
+
positionReceipt: receipt.descriptor,
|
|
1216
|
+
positionReceiptValue: receipt.value,
|
|
1217
|
+
positionReceiptSummary: receipt.receiptSummary,
|
|
1218
|
+
positionReceiptEnvelopeSummary: receipt.envelopeSummary,
|
|
1219
|
+
closing: closing.descriptor,
|
|
1220
|
+
closingValue: closing.value,
|
|
1221
|
+
closingSummary: closing.summary,
|
|
1222
|
+
closingHash: closing.summary.hash,
|
|
1223
|
+
checks,
|
|
1224
|
+
report: {
|
|
1225
|
+
...buildBaseFundReport(),
|
|
1226
|
+
closingTrust: checks,
|
|
1227
|
+
receiptTrust: buildReceiptTrust({
|
|
1228
|
+
generated: false,
|
|
1229
|
+
envelope: receipt.value,
|
|
1230
|
+
checks: receiptChecks,
|
|
1231
|
+
}),
|
|
1232
|
+
},
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
async function verifyClosing(sdk, input) {
|
|
1236
|
+
const definition = input.definitionPath || input.definitionValue
|
|
1237
|
+
? await loadFundDefinitionDocument(sdk, input)
|
|
1238
|
+
: undefined;
|
|
1239
|
+
const receipt = await loadPositionReceiptEnvelopeDocument(sdk, input);
|
|
1240
|
+
const receiptChecks = definition ? ensureVerifiedEnvelope(definition.value, receipt.value) : (0, fundValidation_1.verifyLPPositionReceiptEnvelope)({ envelope: receipt.value });
|
|
1241
|
+
const closing = await loadClosingDocument(sdk, { closingPath: input.closingPath, closingValue: input.closingValue });
|
|
1242
|
+
const checks = (0, fundValidation_1.validateClosingAgainstReceipt)(receipt.value.receipt, closing.value);
|
|
1243
|
+
return {
|
|
1244
|
+
verified: true,
|
|
1245
|
+
positionReceipt: receipt.descriptor,
|
|
1246
|
+
positionReceiptValue: receipt.value,
|
|
1247
|
+
positionReceiptSummary: receipt.receiptSummary,
|
|
1248
|
+
positionReceiptEnvelopeSummary: receipt.envelopeSummary,
|
|
1249
|
+
closing: closing.descriptor,
|
|
1250
|
+
closingValue: closing.value,
|
|
1251
|
+
closingSummary: closing.summary,
|
|
1252
|
+
checks,
|
|
1253
|
+
report: {
|
|
1254
|
+
...buildBaseFundReport(),
|
|
1255
|
+
closingTrust: checks,
|
|
1256
|
+
receiptTrust: buildReceiptTrust({ generated: false, envelope: receipt.value, checks: receiptChecks }),
|
|
1257
|
+
},
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
async function exportEvidence(sdk, input) {
|
|
1261
|
+
const definition = await loadFundDefinitionDocument(sdk, input);
|
|
1262
|
+
const capitalCall = input.capitalCallPath || input.capitalCallValue ? await loadCapitalCallDocument(sdk, input) : undefined;
|
|
1263
|
+
const receipt = input.positionReceiptPath || input.positionReceiptValue
|
|
1264
|
+
? await loadPositionReceiptEnvelopeDocument(sdk, input)
|
|
1265
|
+
: undefined;
|
|
1266
|
+
const distributions = input.distributionPath || input.distributionValue || input.distributionPaths || input.distributionValues
|
|
1267
|
+
? await loadDistributionDocuments(sdk, {
|
|
1268
|
+
distributionPath: input.distributionPath,
|
|
1269
|
+
distributionValue: input.distributionValue,
|
|
1270
|
+
distributionPaths: input.distributionPaths,
|
|
1271
|
+
distributionValues: input.distributionValues,
|
|
1272
|
+
})
|
|
1273
|
+
: [];
|
|
1274
|
+
const distribution = distributions.at(-1);
|
|
1275
|
+
const closing = input.closingPath || input.closingValue
|
|
1276
|
+
? await loadClosingDocument(sdk, { closingPath: input.closingPath, closingValue: input.closingValue })
|
|
1277
|
+
: undefined;
|
|
1278
|
+
const artifact = input.artifact ?? (input.artifactPath ? (await sdk.loadArtifact(input.artifactPath)).artifact : undefined);
|
|
1279
|
+
const trust = input.verificationReportValue
|
|
1280
|
+
?? (distribution && receipt && artifact
|
|
1281
|
+
? (await verifyDistribution(sdk, {
|
|
1282
|
+
artifact,
|
|
1283
|
+
definitionValue: definition.value,
|
|
1284
|
+
positionReceiptValue: receipt.value,
|
|
1285
|
+
distributionValue: distribution.value,
|
|
1286
|
+
})).report
|
|
1287
|
+
: capitalCall && artifact
|
|
1288
|
+
? (await verifyCapitalCall(sdk, {
|
|
1289
|
+
artifact,
|
|
1290
|
+
definitionValue: definition.value,
|
|
1291
|
+
capitalCallValue: capitalCall.value,
|
|
1292
|
+
})).report
|
|
1293
|
+
: closing && receipt
|
|
1294
|
+
? (await verifyClosing(sdk, {
|
|
1295
|
+
definitionValue: definition.value,
|
|
1296
|
+
positionReceiptValue: receipt.value,
|
|
1297
|
+
closingValue: closing.value,
|
|
1298
|
+
})).report
|
|
1299
|
+
: receipt
|
|
1300
|
+
? (await verifyPositionReceipt(sdk, {
|
|
1301
|
+
definitionValue: definition.value,
|
|
1302
|
+
positionReceiptValue: receipt.value,
|
|
1303
|
+
})).report
|
|
1304
|
+
: buildBaseFundReport());
|
|
1305
|
+
const renderedSourceHash = artifact?.source.simfPath && (0, node_fs_1.existsSync)(artifact.source.simfPath)
|
|
1306
|
+
? (0, summary_1.sha256HexUtf8)(await (0, promises_1.readFile)(artifact.source.simfPath, "utf8"))
|
|
1307
|
+
: undefined;
|
|
1308
|
+
return {
|
|
1309
|
+
schemaVersion: FUND_EVIDENCE_BUNDLE_SCHEMA_VERSION,
|
|
1310
|
+
...(artifact ? { artifact } : {}),
|
|
1311
|
+
definition: definition.summary,
|
|
1312
|
+
...(capitalCall ? { capitalCall: capitalCall.summary } : {}),
|
|
1313
|
+
...(receipt
|
|
1314
|
+
? {
|
|
1315
|
+
positionReceipt: receipt.receiptSummary,
|
|
1316
|
+
positionReceiptEnvelope: receipt.envelopeSummary,
|
|
1317
|
+
}
|
|
1318
|
+
: {}),
|
|
1319
|
+
...(distribution ? { distribution: distribution.summary } : {}),
|
|
1320
|
+
...(distributions.length > 1 ? { distributions: distributions.map((entry) => entry.summary) } : {}),
|
|
1321
|
+
...(closing ? { closing: closing.summary } : {}),
|
|
1322
|
+
trust,
|
|
1323
|
+
renderedSourceHash,
|
|
1324
|
+
sourceVerificationMode: renderedSourceHash ? "source-reloaded" : "artifact-only",
|
|
1325
|
+
...(artifact
|
|
1326
|
+
? {
|
|
1327
|
+
compiled: {
|
|
1328
|
+
program: artifact.compiled.program,
|
|
1329
|
+
cmr: artifact.compiled.cmr,
|
|
1330
|
+
contractAddress: artifact.compiled.contractAddress,
|
|
1331
|
+
},
|
|
1332
|
+
}
|
|
1333
|
+
: {}),
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
async function exportFinalityPayload(sdk, input) {
|
|
1337
|
+
const evidence = await exportEvidence(sdk, input);
|
|
1338
|
+
const definition = await loadFundDefinitionDocument(sdk, input);
|
|
1339
|
+
const capitalCall = input.capitalCallPath || input.capitalCallValue ? await loadCapitalCallDocument(sdk, input) : undefined;
|
|
1340
|
+
const receipt = input.positionReceiptPath || input.positionReceiptValue
|
|
1341
|
+
? await loadPositionReceiptEnvelopeDocument(sdk, input)
|
|
1342
|
+
: undefined;
|
|
1343
|
+
const distributions = input.distributionPath || input.distributionValue || input.distributionPaths || input.distributionValues
|
|
1344
|
+
? await loadDistributionDocuments(sdk, {
|
|
1345
|
+
distributionPath: input.distributionPath,
|
|
1346
|
+
distributionValue: input.distributionValue,
|
|
1347
|
+
distributionPaths: input.distributionPaths,
|
|
1348
|
+
distributionValues: input.distributionValues,
|
|
1349
|
+
})
|
|
1350
|
+
: [];
|
|
1351
|
+
const distribution = distributions.at(-1);
|
|
1352
|
+
const closing = input.closingPath || input.closingValue
|
|
1353
|
+
? await loadClosingDocument(sdk, { closingPath: input.closingPath, closingValue: input.closingValue })
|
|
1354
|
+
: undefined;
|
|
1355
|
+
const lpId = closing?.value.lpId ?? distribution?.value.lpId ?? receipt?.value.receipt.lpId ?? capitalCall?.value.lpId;
|
|
1356
|
+
if (!lpId) {
|
|
1357
|
+
throw new errors_1.ValidationError("Finality payload requires at least one LP-linked document", {
|
|
1358
|
+
code: "FUND_FINALITY_LP_ID_REQUIRED",
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
return {
|
|
1362
|
+
schemaVersion: FUND_FINALITY_PAYLOAD_SCHEMA_VERSION,
|
|
1363
|
+
fundId: definition.value.fundId,
|
|
1364
|
+
lpId,
|
|
1365
|
+
...(capitalCall?.value.callId ? { callId: capitalCall.value.callId } : {}),
|
|
1366
|
+
...(receipt?.value.receipt.positionId ?? distribution?.value.positionId ?? closing?.value.positionId
|
|
1367
|
+
? { positionId: receipt?.value.receipt.positionId ?? distribution?.value.positionId ?? closing?.value.positionId }
|
|
1368
|
+
: {}),
|
|
1369
|
+
definitionHash: definition.summary.hash,
|
|
1370
|
+
capitalCallStateHash: capitalCall?.summary.hash ?? null,
|
|
1371
|
+
positionReceiptHash: receipt?.receiptSummary.hash ?? null,
|
|
1372
|
+
positionReceiptEnvelopeHash: receipt?.envelopeSummary.hash ?? null,
|
|
1373
|
+
distributionHash: distribution?.summary.hash ?? null,
|
|
1374
|
+
distributionHashes: distributions.length > 0 ? distributions.map((entry) => entry.summary.hash) : null,
|
|
1375
|
+
closingHash: closing?.summary.hash ?? null,
|
|
1376
|
+
bindingMode: evidence.trust.outputBindingTrust?.mode ?? "none",
|
|
1377
|
+
trust: evidence.trust,
|
|
1378
|
+
trustSummary: {
|
|
1379
|
+
definition: evidence.trust.artifactTrust?.definition,
|
|
1380
|
+
state: evidence.trust.stateTrust,
|
|
1381
|
+
bindingMode: evidence.trust.outputBindingTrust?.mode ?? "none",
|
|
1382
|
+
},
|
|
1383
|
+
};
|
|
1384
|
+
}
|