@bananapus/nana-sdk-core 2.28.0 → 2.29.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.
Files changed (55) hide show
  1. package/dist/cjs/review/decode.d.ts.map +1 -1
  2. package/dist/cjs/review/decode.js +43 -2
  3. package/dist/cjs/review/decode.js.map +1 -1
  4. package/dist/cjs/review/writeRecovery.d.ts +15 -1
  5. package/dist/cjs/review/writeRecovery.d.ts.map +1 -1
  6. package/dist/cjs/review/writeRecovery.js +81 -37
  7. package/dist/cjs/review/writeRecovery.js.map +1 -1
  8. package/dist/cjs/v6/defifa.d.ts +154 -0
  9. package/dist/cjs/v6/defifa.d.ts.map +1 -0
  10. package/dist/cjs/v6/defifa.js +684 -0
  11. package/dist/cjs/v6/defifa.js.map +1 -0
  12. package/dist/cjs/v6/defifaDeployments.d.ts +13 -0
  13. package/dist/cjs/v6/defifaDeployments.d.ts.map +1 -0
  14. package/dist/cjs/v6/defifaDeployments.js +25 -0
  15. package/dist/cjs/v6/defifaDeployments.js.map +1 -0
  16. package/dist/cjs/v6/defifaHoldings.d.ts +50 -0
  17. package/dist/cjs/v6/defifaHoldings.d.ts.map +1 -0
  18. package/dist/cjs/v6/defifaHoldings.js +181 -0
  19. package/dist/cjs/v6/defifaHoldings.js.map +1 -0
  20. package/dist/cjs/v6/index.d.ts +2 -0
  21. package/dist/cjs/v6/index.d.ts.map +1 -1
  22. package/dist/cjs/v6/index.js +4 -0
  23. package/dist/cjs/v6/index.js.map +1 -1
  24. package/dist/cjs/v6/nftInventory.d.ts +1 -1
  25. package/dist/cjs/v6/nftInventory.d.ts.map +1 -1
  26. package/dist/cjs/v6/nftInventory.js +3 -21
  27. package/dist/cjs/v6/nftInventory.js.map +1 -1
  28. package/dist/esm/review/decode.d.ts.map +1 -1
  29. package/dist/esm/review/decode.js +43 -2
  30. package/dist/esm/review/decode.js.map +1 -1
  31. package/dist/esm/review/writeRecovery.d.ts +15 -1
  32. package/dist/esm/review/writeRecovery.d.ts.map +1 -1
  33. package/dist/esm/review/writeRecovery.js +80 -37
  34. package/dist/esm/review/writeRecovery.js.map +1 -1
  35. package/dist/esm/v6/defifa.d.ts +154 -0
  36. package/dist/esm/v6/defifa.d.ts.map +1 -0
  37. package/dist/esm/v6/defifa.js +671 -0
  38. package/dist/esm/v6/defifa.js.map +1 -0
  39. package/dist/esm/v6/defifaDeployments.d.ts +13 -0
  40. package/dist/esm/v6/defifaDeployments.d.ts.map +1 -0
  41. package/dist/esm/v6/defifaDeployments.js +22 -0
  42. package/dist/esm/v6/defifaDeployments.js.map +1 -0
  43. package/dist/esm/v6/defifaHoldings.d.ts +50 -0
  44. package/dist/esm/v6/defifaHoldings.d.ts.map +1 -0
  45. package/dist/esm/v6/defifaHoldings.js +177 -0
  46. package/dist/esm/v6/defifaHoldings.js.map +1 -0
  47. package/dist/esm/v6/index.d.ts +2 -0
  48. package/dist/esm/v6/index.d.ts.map +1 -1
  49. package/dist/esm/v6/index.js +2 -0
  50. package/dist/esm/v6/index.js.map +1 -1
  51. package/dist/esm/v6/nftInventory.d.ts +1 -1
  52. package/dist/esm/v6/nftInventory.d.ts.map +1 -1
  53. package/dist/esm/v6/nftInventory.js +1 -19
  54. package/dist/esm/v6/nftInventory.js.map +1 -1
  55. package/package.json +1 -1
@@ -0,0 +1,684 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DefifaNftTransactionRevertedError = exports.MAX_DEFIFA_ACTION_NFTS = void 0;
4
+ exports.defifaPhaseAllowsPay = defifaPhaseAllowsPay;
5
+ exports.defifaPhaseAllowsCashOut = defifaPhaseAllowsCashOut;
6
+ exports.buildDefifaPayMetadata = buildDefifaPayMetadata;
7
+ exports.prepareDefifaPay = prepareDefifaPay;
8
+ exports.prepareDefifaCashOut = prepareDefifaCashOut;
9
+ exports.prepareDefifaTransfer = prepareDefifaTransfer;
10
+ exports.restoreDefifaNftReceiptContext = restoreDefifaNftReceiptContext;
11
+ exports.verifyDefifaNftReceipt = verifyDefifaNftReceipt;
12
+ exports.verifyDefifaNftTransaction = verifyDefifaNftTransaction;
13
+ const viem_1 = require("viem");
14
+ const juicebox_js_1 = require("../generated/juicebox.js");
15
+ const hook_js_1 = require("../utils/hook.js");
16
+ const cashOut_js_1 = require("./cashOut.js");
17
+ const nft_js_1 = require("./nft.js");
18
+ const nftInventory_js_1 = require("./nftInventory.js");
19
+ const pay_js_1 = require("./pay.js");
20
+ const terminals_js_1 = require("./terminals.js");
21
+ const defifaDeployments_js_1 = require("./defifaDeployments.js");
22
+ const relayr_js_1 = require("../review/relayr.js");
23
+ const writeRecovery_js_1 = require("../review/writeRecovery.js");
24
+ // @ballkidz/defifa 1.0.2 native getters. Provenance belongs to nftInventory.
25
+ const defifaActionAbi = (0, viem_1.parseAbi)([
26
+ "function defaultAttestationDelegate() view returns (address)",
27
+ "function getTierDelegateOf(address,uint256) view returns (address)",
28
+ "function gamePhaseReporter() view returns (address)",
29
+ "function noContestTriggeredFor(uint256) view returns (bool)",
30
+ "function tokensClaimableFor(uint256[]) view returns (uint256,uint256)",
31
+ "function DEFIFA_TOKEN() view returns (address)",
32
+ "function BASE_PROTOCOL_TOKEN() view returns (address)",
33
+ ]);
34
+ /** Adapter RPC/review budget, not a native protocol supply or metadata limit. */
35
+ exports.MAX_DEFIFA_ACTION_NFTS = 128;
36
+ /** Phase-only display eligibility; preparation still authenticates current rules. */
37
+ function defifaPhaseAllowsPay(phase) {
38
+ return phase === 1;
39
+ }
40
+ /** NO_CONTEST additionally requires native activation, checked by preparation. */
41
+ function defifaPhaseAllowsCashOut(phase) {
42
+ return phase === 1 || phase === 2 || phase === 4 || phase === 5;
43
+ }
44
+ function assertActionSize(count) {
45
+ if (count < 1 || count > exports.MAX_DEFIFA_ACTION_NFTS)
46
+ throw new Error(`Defifa preparations accept 1–${exports.MAX_DEFIFA_ACTION_NFTS} NFTs per action.`);
47
+ }
48
+ function address(value, label, allowZero = false) {
49
+ if (!(0, viem_1.isAddress)(value) || (!allowZero && value === viem_1.zeroAddress))
50
+ throw new Error(`${label} must be a valid${allowZero ? "" : " nonzero"} address.`);
51
+ return value;
52
+ }
53
+ function sameAddress(left, right) {
54
+ return left.toLowerCase() === right.toLowerCase();
55
+ }
56
+ /**
57
+ * Native Defifa pay payload: (address attestationDelegate, uint16[] tierIds).
58
+ * Resolve metadataIdTarget from an authenticated inventory's CODE_ORIGIN, never
59
+ * its clone address. This pure encoder validates inputs, not contract identity.
60
+ * Zero delegates through the game's configured default, or to the beneficiary.
61
+ * For a self-payment this updates delegation for ALL holdings in selected tiers.
62
+ * Generic build721PayMetadata is incompatible: its true boolean becomes address 1.
63
+ */
64
+ function buildDefifaPayMetadata({ metadataIdTarget, tierIdsToMint, attestationDelegate = viem_1.zeroAddress, }) {
65
+ address(metadataIdTarget, "Defifa metadataIdTarget");
66
+ address(attestationDelegate, "Defifa attestation delegate", true);
67
+ if (tierIdsToMint.length === 0 ||
68
+ tierIdsToMint.some((id, index) => typeof id !== "bigint" ||
69
+ id < 1n ||
70
+ id > 65535n ||
71
+ (index > 0 && id < tierIdsToMint[index - 1]))) {
72
+ throw new Error("Defifa tier IDs must be nonempty, ascending positive uint16 values; repeats mint multiple NFTs.");
73
+ }
74
+ return (0, hook_js_1.createHookMetadata)([(0, hook_js_1.hookMetadataId)(metadataIdTarget, "pay")], [
75
+ (0, viem_1.encodeAbiParameters)([{ type: "address" }, { type: "uint16[]" }], [attestationDelegate, tierIdsToMint.map(Number)]),
76
+ ]);
77
+ }
78
+ async function actionContext(client, args, blockNumber) {
79
+ address(args.account, "Defifa account");
80
+ if (args.expectedHook)
81
+ address(args.expectedHook, "Defifa expected hook");
82
+ if (args.projectId <= 0n)
83
+ throw new Error("Defifa projectId must be positive.");
84
+ const inventory = await (0, nftInventory_js_1.getProjectNftInventory)(client, {
85
+ chainId: args.chainId,
86
+ projectId: args.projectId,
87
+ tierLimit: 1,
88
+ blockNumber,
89
+ });
90
+ if (inventory?.protocol !== "defifa")
91
+ throw new Error("Project is not an authenticated native Defifa game.");
92
+ if (args.expectedHook && !sameAddress(inventory.hook, args.expectedHook))
93
+ throw new Error("Defifa collection changed since review.");
94
+ const snapshot = {
95
+ ...client,
96
+ readContract: (parameters) => client.readContract({
97
+ ...parameters,
98
+ blockNumber: inventory.blockNumber,
99
+ }),
100
+ };
101
+ return { inventory, snapshot };
102
+ }
103
+ async function paymentTerminal(snapshot, args, inventory) {
104
+ const terminal = await (0, terminals_js_1.resolvePaymentTerminal)(snapshot, {
105
+ chainId: args.chainId,
106
+ projectId: args.projectId,
107
+ token: inventory.pricing.token,
108
+ });
109
+ if (terminal.isRouter)
110
+ throw new Error("Defifa settlement token has no primary terminal.");
111
+ const context = await snapshot.readContract({
112
+ address: terminal.address,
113
+ abi: juicebox_js_1.jbMultiTerminalAbi,
114
+ functionName: "accountingContextForTokenOf",
115
+ args: [args.projectId, inventory.pricing.token],
116
+ });
117
+ if (!sameAddress(context.token, inventory.pricing.token) ||
118
+ Number(context.currency) !== inventory.pricing.currency ||
119
+ Number(context.decimals) !== inventory.pricing.decimals)
120
+ throw new Error("Defifa terminal accounting does not match native pricing.");
121
+ return terminal.address;
122
+ }
123
+ /**
124
+ * Fresh native identity, MINT phase, selected supply and exact tier cost at one
125
+ * block. Re-run after review and compare the entire request before execution.
126
+ * ERC20 payments need allowance; after approval, simulate this EXACT request
127
+ * with account before sending. Preparation does not grant execution authority.
128
+ * Donations are separate addToBalanceOf calls: native Defifa rejects overpayment.
129
+ */
130
+ async function prepareDefifaPay(client, args) {
131
+ assertActionSize(args.tierIdsToMint.length);
132
+ args = { ...args, tierIdsToMint: [...args.tierIdsToMint] };
133
+ const beneficiary = address(args.beneficiary ?? args.account, "Defifa beneficiary");
134
+ const attestationDelegate = args.attestationDelegate ?? viem_1.zeroAddress;
135
+ // Validate before any RPC. The real metadata target comes from fresh identity.
136
+ buildDefifaPayMetadata({
137
+ metadataIdTarget: beneficiary,
138
+ tierIdsToMint: args.tierIdsToMint,
139
+ attestationDelegate,
140
+ });
141
+ const { inventory, snapshot } = await actionContext(client, args);
142
+ if (!defifaPhaseAllowsPay(inventory.phase) ||
143
+ inventory.ruleset.metadata.pausePay ||
144
+ !inventory.ruleset.metadata.useDataHookForPay)
145
+ throw new Error("Defifa NFT entry is not open.");
146
+ const counts = new Map();
147
+ for (const tierId of args.tierIdsToMint)
148
+ counts.set(tierId, (counts.get(tierId) ?? 0n) + 1n);
149
+ const [terminal, configuredDelegate, selectedTiers] = await Promise.all([
150
+ paymentTerminal(snapshot, args, inventory),
151
+ snapshot.readContract({
152
+ address: inventory.hook,
153
+ abi: defifaActionAbi,
154
+ functionName: "defaultAttestationDelegate",
155
+ }),
156
+ Promise.all([...counts].map(async ([tierId, count]) => {
157
+ const tier = await snapshot.readContract({
158
+ address: inventory.store,
159
+ abi: juicebox_js_1.jb721TiersHookStoreAbi,
160
+ functionName: "tierOf",
161
+ args: [inventory.hook, tierId, false],
162
+ });
163
+ if (BigInt(tier.id) !== tierId || BigInt(tier.remainingSupply) < count)
164
+ throw new Error(`Defifa tier ${tierId} is unavailable for the selected quantity.`);
165
+ return {
166
+ tierId,
167
+ count,
168
+ unitPrice: (0, nft_js_1.effectiveTierPrice)(tier.price, tier.discountPercent),
169
+ };
170
+ })),
171
+ ]);
172
+ const amount = selectedTiers.reduce((sum, tier) => sum + tier.unitPrice * tier.count, 0n);
173
+ const metadata = buildDefifaPayMetadata({
174
+ metadataIdTarget: inventory.implementation,
175
+ tierIdsToMint: args.tierIdsToMint,
176
+ attestationDelegate,
177
+ });
178
+ const resolvedDelegate = attestationDelegate !== viem_1.zeroAddress
179
+ ? attestationDelegate
180
+ : configuredDelegate !== viem_1.zeroAddress
181
+ ? configuredDelegate
182
+ : beneficiary;
183
+ const effectiveDelegates = await Promise.all([...counts.keys()].map(async (tierId) => {
184
+ if (sameAddress(args.account, beneficiary))
185
+ return { tierId, delegate: resolvedDelegate };
186
+ const prior = await snapshot.readContract({
187
+ address: inventory.hook,
188
+ abi: defifaActionAbi,
189
+ functionName: "getTierDelegateOf",
190
+ args: [beneficiary, tierId],
191
+ });
192
+ return { tierId, delegate: prior === viem_1.zeroAddress ? beneficiary : prior };
193
+ }));
194
+ return {
195
+ inventory,
196
+ amount,
197
+ selectedTiers,
198
+ attestationDelegate,
199
+ effectiveAttestationDelegate: new Set(effectiveDelegates.map(({ delegate }) => delegate.toLowerCase()))
200
+ .size === 1
201
+ ? effectiveDelegates[0].delegate
202
+ : null,
203
+ effectiveDelegates,
204
+ transaction: (0, pay_js_1.buildPayTx)({
205
+ chainId: args.chainId,
206
+ terminal,
207
+ projectId: args.projectId,
208
+ token: inventory.pricing.token,
209
+ amount,
210
+ beneficiary,
211
+ minReturnedTokens: 0n,
212
+ memo: args.memo,
213
+ metadata,
214
+ }),
215
+ };
216
+ }
217
+ async function requireOwner(snapshot, hook, tokenId, account) {
218
+ const owner = await snapshot.readContract({
219
+ address: hook,
220
+ abi: viem_1.erc721Abi,
221
+ functionName: "ownerOf",
222
+ args: [tokenId],
223
+ });
224
+ if (!sameAddress(owner, account))
225
+ throw new Error(`Defifa NFT ${tokenId} is not owned by the sender.`);
226
+ }
227
+ /** Fresh owner/phase checks and exact cashOutTokensOf simulation, with no ERC721 approval. */
228
+ async function prepareDefifaCashOut(client, args) {
229
+ assertActionSize(args.tokenIds.length);
230
+ args = { ...args, tokenIds: [...args.tokenIds] };
231
+ const beneficiary = address(args.beneficiary ?? args.account, "Defifa beneficiary");
232
+ const minimumReclaim = args.minimumReclaim ?? 0n;
233
+ if (minimumReclaim < 0n)
234
+ throw new Error("Defifa minimum reclaim cannot be negative.");
235
+ const tokenIds = [...args.tokenIds];
236
+ (0, cashOut_js_1.build721CashOutMetadata)({ metadataIdTarget: beneficiary, tokenIds });
237
+ const { inventory, snapshot } = await actionContext(client, args);
238
+ if (!defifaPhaseAllowsCashOut(inventory.phase))
239
+ throw new Error("Defifa NFT cash-out is not available in this phase.");
240
+ if (inventory.phase === 5) {
241
+ const reporter = await snapshot.readContract({
242
+ address: inventory.hook,
243
+ abi: defifaActionAbi,
244
+ functionName: "gamePhaseReporter",
245
+ });
246
+ const activated = await snapshot.readContract({
247
+ address: reporter,
248
+ abi: defifaActionAbi,
249
+ functionName: "noContestTriggeredFor",
250
+ args: [args.projectId],
251
+ });
252
+ if (!activated)
253
+ throw new Error("Defifa no-contest must be activated before refunds.");
254
+ }
255
+ await Promise.all(tokenIds.map((tokenId) => requireOwner(snapshot, inventory.hook, tokenId, args.account)));
256
+ const terminal = await paymentTerminal(snapshot, args, inventory);
257
+ const transaction = (0, cashOut_js_1.buildCashOutTx)({
258
+ chainId: args.chainId,
259
+ terminal,
260
+ holder: args.account,
261
+ projectId: args.projectId,
262
+ cashOutCount: 0n,
263
+ tokenToReclaim: inventory.pricing.token,
264
+ minTokensReclaimed: minimumReclaim,
265
+ beneficiary,
266
+ metadata: (0, cashOut_js_1.build721CashOutMetadata)({
267
+ metadataIdTarget: inventory.implementation,
268
+ tokenIds,
269
+ }),
270
+ });
271
+ const [simulation, rewards, defifaToken, baseProtocolToken] = await Promise.all([
272
+ client.simulateContract({
273
+ ...transaction,
274
+ account: args.account,
275
+ blockNumber: inventory.blockNumber,
276
+ }),
277
+ snapshot.readContract({
278
+ address: inventory.hook,
279
+ abi: defifaActionAbi,
280
+ functionName: "tokensClaimableFor",
281
+ args: [tokenIds],
282
+ }),
283
+ snapshot.readContract({
284
+ address: inventory.hook,
285
+ abi: defifaActionAbi,
286
+ functionName: "DEFIFA_TOKEN",
287
+ }),
288
+ snapshot.readContract({
289
+ address: inventory.hook,
290
+ abi: defifaActionAbi,
291
+ functionName: "BASE_PROTOCOL_TOKEN",
292
+ }),
293
+ ]);
294
+ const [defifaDecimals, baseProtocolDecimals] = await Promise.all([
295
+ snapshot.readContract({
296
+ address: defifaToken,
297
+ abi: viem_1.erc20Abi,
298
+ functionName: "decimals",
299
+ }),
300
+ snapshot.readContract({
301
+ address: baseProtocolToken,
302
+ abi: viem_1.erc20Abi,
303
+ functionName: "decimals",
304
+ }),
305
+ ]);
306
+ return {
307
+ inventory,
308
+ transaction,
309
+ reclaimAmount: simulation.result,
310
+ protocolRewards: {
311
+ defifaToken,
312
+ defifaAmount: rewards[0],
313
+ defifaDecimals,
314
+ baseProtocolToken,
315
+ baseProtocolAmount: rewards[1],
316
+ baseProtocolDecimals,
317
+ },
318
+ };
319
+ }
320
+ /** Standard NFT transfer; native pause flags and receiver callbacks are simulated exactly. */
321
+ async function prepareDefifaTransfer(client, args) {
322
+ args = { ...args };
323
+ address(args.recipient, "Defifa recipient");
324
+ if (args.tokenId <= 0n)
325
+ throw new Error("Defifa tokenId must be positive.");
326
+ const { inventory, snapshot } = await actionContext(client, args);
327
+ await requireOwner(snapshot, inventory.hook, args.tokenId, args.account);
328
+ const transaction = {
329
+ chainId: args.chainId,
330
+ address: inventory.hook,
331
+ abi: viem_1.erc721Abi,
332
+ functionName: "safeTransferFrom",
333
+ args: [args.account, args.recipient, args.tokenId],
334
+ };
335
+ await client.simulateContract({
336
+ ...transaction,
337
+ account: args.account,
338
+ blockNumber: inventory.blockNumber,
339
+ });
340
+ return { inventory, transaction };
341
+ }
342
+ const defifaEffectAbi = (0, viem_1.parseAbi)([
343
+ "event Mint(uint256 indexed tokenId,uint256 indexed tierId,address indexed beneficiary,uint256 totalAmountContributed,address caller)",
344
+ "event ClaimedTokens(address indexed beneficiary,uint256 defifaTokenAmount,uint256 baseProtocolTokenAmount,address caller)",
345
+ ]);
346
+ function requireNativeMetadata(metadata, expected) {
347
+ if (expected.toLowerCase() !== metadata.toLowerCase())
348
+ throw new Error("Receipt does not prove the exact reviewed Defifa NFT effects.");
349
+ }
350
+ function payTierIds(metadata, implementation) {
351
+ const [delegate, ids] = (0, viem_1.decodeAbiParameters)([{ type: "address" }, { type: "uint16[]" }], `0x${metadata.slice(130)}`);
352
+ const tierIds = ids.map(BigInt);
353
+ requireNativeMetadata(metadata, buildDefifaPayMetadata({
354
+ metadataIdTarget: implementation,
355
+ attestationDelegate: delegate,
356
+ tierIdsToMint: tierIds,
357
+ }));
358
+ return tierIds;
359
+ }
360
+ function cashOutTokenIds(metadata, implementation) {
361
+ const [tokenIds] = (0, viem_1.decodeAbiParameters)([{ type: "uint256[]" }], `0x${metadata.slice(130)}`);
362
+ requireNativeMetadata(metadata, (0, cashOut_js_1.build721CashOutMetadata)({
363
+ metadataIdTarget: implementation,
364
+ tokenIds: [...tokenIds],
365
+ }));
366
+ return [...tokenIds];
367
+ }
368
+ /**
369
+ * Reconstruct native verification context from an existing reviewed-write
370
+ * journal. Authenticate the project/terminal at the receipt's historical block;
371
+ * never prepare, simulate or send a new action. The wallet-returned identity is
372
+ * required: a hashless uncertain write cannot be matched to an old receipt.
373
+ * Call verifyDefifaNftTransaction afterward; restoration alone proves no outcome.
374
+ */
375
+ async function restoreDefifaNftReceiptContext(client, args) {
376
+ args = { ...args, receipt: { ...args.receipt } };
377
+ const saved = (0, writeRecovery_js_1.parseReviewedWriteRecoveryRecord)(args.record);
378
+ function fail() {
379
+ throw new Error("Saved call is not the reviewed native Defifa action. Keep this attempt pending.");
380
+ }
381
+ if (saved.chainId !== args.chainId ||
382
+ !saved.hash ||
383
+ (!saved.safe &&
384
+ saved.hash.toLowerCase() !==
385
+ args.receipt.transactionHash.toLowerCase()) ||
386
+ typeof args.receipt.blockNumber !== "bigint" ||
387
+ args.receipt.blockNumber < 0n)
388
+ fail();
389
+ const identity = {
390
+ chainId: args.chainId,
391
+ projectId: args.projectId,
392
+ account: saved.account,
393
+ expectedHook: args.expectedHook,
394
+ };
395
+ const { inventory, snapshot } = await actionContext(client, identity, args.receipt.blockNumber);
396
+ let transaction;
397
+ if (sameAddress(saved.call.to, inventory.hook)) {
398
+ const call = (0, viem_1.decodeFunctionData)({ abi: viem_1.erc721Abi, data: saved.call.data });
399
+ if (call.functionName !== "safeTransferFrom" || call.args.length !== 3)
400
+ fail();
401
+ const [from, recipient, tokenId] = call.args;
402
+ if (!sameAddress(from, saved.account) || tokenId <= 0n)
403
+ fail();
404
+ address(recipient, "Defifa recipient");
405
+ transaction = {
406
+ chainId: args.chainId,
407
+ address: inventory.hook,
408
+ abi: viem_1.erc721Abi,
409
+ functionName: "safeTransferFrom",
410
+ args: [from, recipient, tokenId],
411
+ };
412
+ }
413
+ else {
414
+ const terminal = await paymentTerminal(snapshot, identity, inventory);
415
+ if (!sameAddress(saved.call.to, terminal))
416
+ fail();
417
+ const call = (0, viem_1.decodeFunctionData)({
418
+ abi: juicebox_js_1.jbMultiTerminalAbi,
419
+ data: saved.call.data,
420
+ });
421
+ if (call.functionName === "pay") {
422
+ const [projectId, token, amount, beneficiary, minimum, memo, metadata] = call.args;
423
+ if (projectId !== args.projectId ||
424
+ !sameAddress(token, inventory.pricing.token))
425
+ fail();
426
+ payTierIds(metadata, inventory.implementation);
427
+ transaction = (0, pay_js_1.buildPayTx)({
428
+ chainId: args.chainId,
429
+ terminal,
430
+ projectId,
431
+ token,
432
+ amount,
433
+ beneficiary,
434
+ minReturnedTokens: minimum,
435
+ memo,
436
+ metadata,
437
+ });
438
+ }
439
+ else if (call.functionName === "cashOutTokensOf") {
440
+ const [holder, projectId, count, token, minimum, beneficiary, metadata] = call.args;
441
+ if (projectId !== args.projectId ||
442
+ !sameAddress(holder, saved.account) ||
443
+ !sameAddress(token, inventory.pricing.token) ||
444
+ count !== 0n)
445
+ fail();
446
+ cashOutTokenIds(metadata, inventory.implementation);
447
+ transaction = (0, cashOut_js_1.buildCashOutTx)({
448
+ chainId: args.chainId,
449
+ terminal,
450
+ projectId,
451
+ holder,
452
+ cashOutCount: count,
453
+ tokenToReclaim: token,
454
+ minTokensReclaimed: minimum,
455
+ beneficiary,
456
+ metadata,
457
+ });
458
+ }
459
+ else
460
+ fail();
461
+ }
462
+ const data = transaction.functionName === "safeTransferFrom"
463
+ ? (0, viem_1.encodeFunctionData)(transaction)
464
+ : (0, viem_1.encodeFunctionData)(transaction);
465
+ if (data.toLowerCase() !== saved.call.data.toLowerCase() ||
466
+ ("value" in transaction ? transaction.value : 0n) !==
467
+ BigInt(saved.call.value))
468
+ fail();
469
+ return {
470
+ hook: inventory.hook,
471
+ implementation: inventory.implementation,
472
+ account: saved.account,
473
+ transaction,
474
+ ...(saved.safe ? { safe: { proposalHash: saved.hash } } : {}),
475
+ };
476
+ }
477
+ /**
478
+ * Verify native NFT effects of one exact reviewed call; throw if proof is missing.
479
+ * The execution engine MUST first authenticate the canonical receipt and actual
480
+ * chain/account/call/value (including the executed inner call for Safe routes).
481
+ * This pure check is also usable during recovery without preparing a new action.
482
+ * Receipt logs, not end-of-block ownership, prove transfers: a receiver may
483
+ * forward an NFT in its callback or a later transaction in the same block.
484
+ */
485
+ function verifyDefifaNftReceipt({ hook, implementation, account, transaction }, receipt) {
486
+ address(hook, "Defifa receipt hook");
487
+ address(account, "Defifa receipt account");
488
+ const deployment = defifaDeployments_js_1.defifaDeployments[transaction.chainId];
489
+ if (!deployment || !sameAddress(implementation, deployment.implementation))
490
+ throw new Error("Defifa receipt context does not match the pinned chain implementation.");
491
+ if (receipt.status !== "success")
492
+ throw new Error("Defifa transaction did not succeed.");
493
+ const hookLogs = receipt.logs.filter((log) => sameAddress(log.address, hook));
494
+ const transfers = (0, viem_1.parseEventLogs)({
495
+ abi: viem_1.erc721Abi,
496
+ eventName: "Transfer",
497
+ logs: hookLogs,
498
+ });
499
+ const fail = () => {
500
+ throw new Error("Receipt does not prove the exact reviewed Defifa NFT effects.");
501
+ };
502
+ if (transaction.functionName === "safeTransferFrom") {
503
+ const [from, recipient, tokenId] = transaction.args;
504
+ if (!sameAddress(transaction.address, hook) || !sameAddress(from, account))
505
+ fail();
506
+ if (transfers.filter(({ args }) => sameAddress(args.from, from) &&
507
+ sameAddress(args.to, recipient) &&
508
+ args.tokenId === tokenId).length !== 1)
509
+ fail();
510
+ return;
511
+ }
512
+ const terminalLogs = receipt.logs.filter((log) => sameAddress(log.address, transaction.address));
513
+ if (transaction.functionName === "pay") {
514
+ const [projectId, , amount, beneficiary, , memo, metadata] = transaction.args;
515
+ const tierIds = payTierIds(metadata, implementation);
516
+ const payments = (0, viem_1.parseEventLogs)({
517
+ abi: juicebox_js_1.jbMultiTerminalAbi,
518
+ eventName: "Pay",
519
+ logs: terminalLogs,
520
+ }).filter(({ args }) => args.projectId === projectId &&
521
+ sameAddress(args.payer, account) &&
522
+ sameAddress(args.beneficiary, beneficiary) &&
523
+ args.amount === amount &&
524
+ args.memo === memo &&
525
+ args.metadata.toLowerCase() === metadata.toLowerCase() &&
526
+ sameAddress(args.caller, account));
527
+ const mints = (0, viem_1.parseEventLogs)({
528
+ abi: defifaEffectAbi,
529
+ eventName: "Mint",
530
+ logs: hookLogs,
531
+ });
532
+ if (payments.length !== 1 ||
533
+ mints.length !== tierIds.length ||
534
+ new Set(mints.map(({ args }) => args.tokenId)).size !== mints.length)
535
+ fail();
536
+ for (const [index, mint] of mints.entries()) {
537
+ if (mint.args.tierId !== tierIds[index] ||
538
+ !sameAddress(mint.args.beneficiary, beneficiary) ||
539
+ mint.args.totalAmountContributed !== amount ||
540
+ !sameAddress(mint.args.caller, transaction.address))
541
+ fail();
542
+ if (transfers.filter(({ args }) => args.from === viem_1.zeroAddress &&
543
+ sameAddress(args.to, beneficiary) &&
544
+ args.tokenId === mint.args.tokenId).length !== 1)
545
+ fail();
546
+ }
547
+ return;
548
+ }
549
+ const [holder, projectId, cashOutCount, , minimum, beneficiary, metadata] = transaction.args;
550
+ const tokenIds = cashOutTokenIds(metadata, implementation);
551
+ if (!sameAddress(holder, account) || cashOutCount !== 0n)
552
+ fail();
553
+ const cashOuts = (0, viem_1.parseEventLogs)({
554
+ abi: juicebox_js_1.jbMultiTerminalAbi,
555
+ eventName: "CashOutTokens",
556
+ logs: terminalLogs,
557
+ }).filter(({ args }) => args.projectId === projectId &&
558
+ sameAddress(args.holder, holder) &&
559
+ sameAddress(args.beneficiary, beneficiary) &&
560
+ args.cashOutCount === 0n &&
561
+ args.reclaimAmount >= minimum &&
562
+ args.metadata.toLowerCase() === metadata.toLowerCase() &&
563
+ sameAddress(args.caller, account));
564
+ const burns = transfers.filter(({ args }) => sameAddress(args.from, holder) && args.to === viem_1.zeroAddress);
565
+ if (cashOuts.length !== 1 ||
566
+ burns.length !== tokenIds.length ||
567
+ tokenIds.some((id) => burns.filter(({ args }) => args.tokenId === id).length !== 1))
568
+ fail();
569
+ if (cashOuts[0].args.reclaimAmount === 0n) {
570
+ const claims = (0, viem_1.parseEventLogs)({
571
+ abi: defifaEffectAbi,
572
+ eventName: "ClaimedTokens",
573
+ logs: hookLogs,
574
+ });
575
+ if (!claims.some(({ args }) => sameAddress(args.beneficiary, beneficiary) &&
576
+ sameAddress(args.caller, transaction.address) &&
577
+ (args.defifaTokenAmount > 0n || args.baseProtocolTokenAmount > 0n)))
578
+ fail();
579
+ }
580
+ }
581
+ /**
582
+ * The exact reviewed action failed in a canonical finalized block. This closes
583
+ * only that attempt; retain its evidence and review any new intent separately.
584
+ * A reverted Safe outer executor alone does not establish this outcome.
585
+ */
586
+ class DefifaNftTransactionRevertedError extends Error {
587
+ constructor(hash, chainId) {
588
+ super("The reviewed Defifa transaction failed onchain and is finalized.");
589
+ this.hash = hash;
590
+ this.chainId = chainId;
591
+ this.name = "DefifaNftTransactionRevertedError";
592
+ }
593
+ }
594
+ exports.DefifaNftTransactionRevertedError = DefifaNftTransactionRevertedError;
595
+ /**
596
+ * Authenticate canonical execution using the SDK's existing transaction/Safe
597
+ * proof owners, then prove native NFT effects. Only
598
+ * DefifaNftTransactionRevertedError proves finalized failure. Retain context
599
+ * and receipt on other errors and retry this read-only function; do not resend.
600
+ */
601
+ async function verifyDefifaNftTransaction(client, context, receipt) {
602
+ const { transaction, account } = context;
603
+ const call = {
604
+ to: transaction.address,
605
+ data: transaction.functionName === "safeTransferFrom"
606
+ ? (0, viem_1.encodeFunctionData)(transaction)
607
+ : (0, viem_1.encodeFunctionData)(transaction),
608
+ value: "value" in transaction ? transaction.value : 0n,
609
+ };
610
+ const actualPromise = client.getTransaction({
611
+ hash: receipt.transactionHash,
612
+ });
613
+ const canonicalPromise = client.getTransactionReceipt({
614
+ hash: receipt.transactionHash,
615
+ });
616
+ const [chainId, actual, canonical] = await Promise.all([
617
+ client.getChainId(),
618
+ actualPromise,
619
+ canonicalPromise,
620
+ ]);
621
+ if (chainId !== transaction.chainId ||
622
+ canonical.blockHash !== receipt.blockHash ||
623
+ canonical.blockNumber !== receipt.blockNumber ||
624
+ canonical.transactionHash.toLowerCase() !==
625
+ receipt.transactionHash.toLowerCase())
626
+ throw new Error("Defifa receipt identity is unavailable or changed; keep this transaction pending.");
627
+ // Both existing proof owners read this same exact transaction/receipt. Reuse
628
+ // the fetched evidence rather than issue duplicate RPC reads or copy checks.
629
+ const proofClient = {
630
+ ...client,
631
+ getTransaction: async () => actual,
632
+ getTransactionReceipt: async () => canonical,
633
+ };
634
+ const record = {
635
+ version: 1,
636
+ id: "defifa-nft",
637
+ chainId: transaction.chainId,
638
+ account,
639
+ safe: Boolean(context.safe),
640
+ hash: context.safe?.proposalHash ?? receipt.transactionHash,
641
+ call: { ...call, value: call.value.toString() },
642
+ };
643
+ if (context.safe) {
644
+ const status = await (0, writeRecovery_js_1.verifyReviewedWriteReceipt)(proofClient, record, canonical);
645
+ if (status !== "success")
646
+ throw new DefifaNftTransactionRevertedError(canonical.transactionHash, chainId);
647
+ }
648
+ else {
649
+ if (!sameAddress(actual.from, account))
650
+ throw new Error("Defifa transaction sender does not match the reviewed account.");
651
+ try {
652
+ await (0, relayr_js_1.verifyRelayrDestination)(proofClient, {
653
+ entry: {
654
+ chain: transaction.chainId,
655
+ target: call.to,
656
+ data: call.data,
657
+ value: call.value.toString(),
658
+ },
659
+ hash: receipt.transactionHash,
660
+ });
661
+ }
662
+ catch (error) {
663
+ if (!(error instanceof relayr_js_1.RelayrDestinationRevertedError))
664
+ throw error;
665
+ // Exact call/account proof precedes finality. The existing reviewed-write
666
+ // owner rejects nonfinal receipts and reorgs before this attempt releases.
667
+ const status = await (0, writeRecovery_js_1.verifyReviewedWriteReceipt)(proofClient, record, canonical);
668
+ if (status !== "failed")
669
+ throw error;
670
+ throw new DefifaNftTransactionRevertedError(canonical.transactionHash, chainId);
671
+ }
672
+ }
673
+ // The caller's supplied logs may be stale or fabricated. Only fresh canonical
674
+ // logs are admitted to the native effect verifier, including log identities.
675
+ if (canonical.logs.some((log) => log.removed !== false ||
676
+ log.transactionHash?.toLowerCase() !==
677
+ canonical.transactionHash.toLowerCase() ||
678
+ log.blockHash?.toLowerCase() !== canonical.blockHash.toLowerCase() ||
679
+ log.blockNumber !== canonical.blockNumber ||
680
+ log.transactionIndex !== canonical.transactionIndex))
681
+ throw new Error("Defifa receipt logs are not bound to the canonical transaction.");
682
+ verifyDefifaNftReceipt(context, canonical);
683
+ }
684
+ //# sourceMappingURL=defifa.js.map