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