mppx 0.5.4 → 0.5.5
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/CHANGELOG.md +8 -0
- package/dist/tempo/Attribution.d.ts +24 -7
- package/dist/tempo/Attribution.d.ts.map +1 -1
- package/dist/tempo/Attribution.js +33 -7
- package/dist/tempo/Attribution.js.map +1 -1
- package/dist/tempo/client/Charge.js +1 -1
- package/dist/tempo/client/Charge.js.map +1 -1
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +36 -2
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/internal/html.gen.d.ts +1 -1
- package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/server/internal/html.gen.js.map +1 -1
- package/package.json +1 -1
- package/src/tempo/Attribution.test.ts +129 -23
- package/src/tempo/Attribution.ts +39 -10
- package/src/tempo/client/Charge.ts +1 -1
- package/src/tempo/server/Charge.test.ts +205 -5
- package/src/tempo/server/Charge.ts +54 -3
- package/src/tempo/server/internal/html.gen.ts +1 -1
|
@@ -24,6 +24,7 @@ import type * as Html from '../../server/internal/html/config.ts'
|
|
|
24
24
|
import * as Store from '../../Store.js'
|
|
25
25
|
import * as Client from '../../viem/Client.js'
|
|
26
26
|
import type * as z from '../../zod.js'
|
|
27
|
+
import * as Attribution from '../Attribution.js'
|
|
27
28
|
import * as Account from '../internal/account.js'
|
|
28
29
|
import * as TempoAddress from '../internal/address.js'
|
|
29
30
|
import * as Charge_internal from '../internal/charge.js'
|
|
@@ -180,12 +181,22 @@ export function charge<const parameters extends charge.Parameters>(
|
|
|
180
181
|
|
|
181
182
|
const expectedTransfers = getExpectedTransfers({ amount, memo, methodDetails, recipient })
|
|
182
183
|
const receipt = await getTransactionReceipt(client, { hash })
|
|
183
|
-
assertTransferLogs(receipt, {
|
|
184
|
+
const matchedLogs = assertTransferLogs(receipt, {
|
|
184
185
|
currency,
|
|
185
186
|
sender: receipt.from,
|
|
186
187
|
transfers: expectedTransfers,
|
|
187
188
|
})
|
|
188
189
|
|
|
190
|
+
// Only verify challenge binding when using auto-generated attribution memos.
|
|
191
|
+
// Explicit memos (set by the server) are strictly matched by assertTransferLogs
|
|
192
|
+
// but are NOT challenge-bound — callers that set explicit memos are responsible
|
|
193
|
+
// for ensuring memo uniqueness per challenge to prevent cross-challenge hash reuse.
|
|
194
|
+
if (!memo)
|
|
195
|
+
assertChallengeBoundMemo(matchedLogs, {
|
|
196
|
+
challengeId: challenge.id,
|
|
197
|
+
realm: challenge.realm,
|
|
198
|
+
})
|
|
199
|
+
|
|
189
200
|
await markHashUsed(store, hash)
|
|
190
201
|
|
|
191
202
|
return toReceipt(receipt)
|
|
@@ -507,6 +518,18 @@ function decodeTransferCall(
|
|
|
507
518
|
return null
|
|
508
519
|
}
|
|
509
520
|
|
|
521
|
+
type TransferLog =
|
|
522
|
+
| {
|
|
523
|
+
kind: 'transfer'
|
|
524
|
+
args: { from: `0x${string}`; to: `0x${string}`; amount: bigint }
|
|
525
|
+
address: `0x${string}`
|
|
526
|
+
}
|
|
527
|
+
| {
|
|
528
|
+
kind: 'memo'
|
|
529
|
+
args: { from: `0x${string}`; to: `0x${string}`; amount: bigint; memo: `0x${string}` }
|
|
530
|
+
address: `0x${string}`
|
|
531
|
+
}
|
|
532
|
+
|
|
510
533
|
function assertTransferLogs(
|
|
511
534
|
receipt: TransactionReceipt,
|
|
512
535
|
parameters: {
|
|
@@ -514,7 +537,7 @@ function assertTransferLogs(
|
|
|
514
537
|
sender: `0x${string}`
|
|
515
538
|
transfers: readonly ExpectedTransfer[]
|
|
516
539
|
},
|
|
517
|
-
) {
|
|
540
|
+
): TransferLog[] {
|
|
518
541
|
const transferLogs = parseEventLogs({
|
|
519
542
|
abi: Abis.tip20,
|
|
520
543
|
eventName: 'Transfer',
|
|
@@ -527,8 +550,11 @@ function assertTransferLogs(
|
|
|
527
550
|
logs: receipt.logs,
|
|
528
551
|
}).map((log) => ({ ...log, kind: 'memo' as const }))
|
|
529
552
|
|
|
530
|
-
|
|
553
|
+
// Prefer memo logs so allowAnyMemo matches TransferWithMemo before Transfer,
|
|
554
|
+
// preserving the memo for challenge binding verification.
|
|
555
|
+
const logs = [...memoLogs, ...transferLogs]
|
|
531
556
|
const used = new Set<number>()
|
|
557
|
+
const matched: TransferLog[] = []
|
|
532
558
|
|
|
533
559
|
// Match memo-specific transfers before wildcards to avoid greedy
|
|
534
560
|
// consumption of memo-bearing logs by allowAnyMemo entries.
|
|
@@ -561,7 +587,10 @@ function assertTransferLogs(
|
|
|
561
587
|
}
|
|
562
588
|
|
|
563
589
|
used.add(matchIndex)
|
|
590
|
+
matched.push(logs[matchIndex]! as TransferLog)
|
|
564
591
|
}
|
|
592
|
+
|
|
593
|
+
return matched
|
|
565
594
|
}
|
|
566
595
|
|
|
567
596
|
/** @internal */
|
|
@@ -624,6 +653,28 @@ function toReceipt(receipt: TransactionReceipt) {
|
|
|
624
653
|
} as const
|
|
625
654
|
}
|
|
626
655
|
|
|
656
|
+
/**
|
|
657
|
+
* Asserts that at least one of the matched payment logs carries a
|
|
658
|
+
* challenge-bound memo nonce (keccak256(challengeId)[0..6] in bytes 25–31).
|
|
659
|
+
* Only checks logs that were matched by `assertTransferLogs`, not the
|
|
660
|
+
* entire receipt — preventing unrelated dust transfers from satisfying
|
|
661
|
+
* the binding.
|
|
662
|
+
* @internal
|
|
663
|
+
*/
|
|
664
|
+
function assertChallengeBoundMemo(
|
|
665
|
+
matchedLogs: readonly TransferLog[],
|
|
666
|
+
parameters: { challengeId: string; realm: string },
|
|
667
|
+
) {
|
|
668
|
+
const bound = matchedLogs.some((log) => {
|
|
669
|
+
if (log.kind !== 'memo') return false
|
|
670
|
+
if (!Attribution.verifyServer(log.args.memo, parameters.realm)) return false
|
|
671
|
+
return Attribution.verifyChallengeBinding(log.args.memo, parameters.challengeId)
|
|
672
|
+
})
|
|
673
|
+
|
|
674
|
+
if (!bound)
|
|
675
|
+
throw new MismatchError('Payment verification failed: memo is not bound to this challenge.', {})
|
|
676
|
+
}
|
|
677
|
+
|
|
627
678
|
/** @internal */
|
|
628
679
|
class MismatchError extends PaymentError {
|
|
629
680
|
override readonly name = 'MismatchError'
|