mppx 0.6.24 → 0.6.26
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 +15 -0
- package/dist/Method.d.ts +6 -4
- package/dist/Method.d.ts.map +1 -1
- package/dist/Method.js +2 -1
- package/dist/Method.js.map +1 -1
- package/dist/middlewares/internal/mppx.d.ts +3 -2
- package/dist/middlewares/internal/mppx.d.ts.map +1 -1
- package/dist/middlewares/internal/mppx.js +1 -0
- package/dist/middlewares/internal/mppx.js.map +1 -1
- package/dist/server/Mppx.d.ts +8 -3
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +4 -1
- package/dist/server/Mppx.js.map +1 -1
- package/dist/stripe/server/Charge.d.ts +1 -1
- package/dist/stripe/server/Charge.d.ts.map +1 -1
- package/dist/stripe/server/Methods.d.ts +1 -1
- package/dist/stripe/server/Methods.d.ts.map +1 -1
- package/dist/tempo/Methods.d.ts +3 -2
- package/dist/tempo/Methods.d.ts.map +1 -1
- package/dist/tempo/Methods.js +13 -4
- package/dist/tempo/Methods.js.map +1 -1
- package/dist/tempo/client/Subscription.d.ts +3 -2
- package/dist/tempo/client/Subscription.d.ts.map +1 -1
- package/dist/tempo/server/Charge.d.ts +19 -1
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +96 -31
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Methods.d.ts +2 -2
- package/dist/tempo/server/Methods.d.ts.map +1 -1
- package/dist/tempo/server/Session.d.ts +1 -1
- package/dist/tempo/server/Session.d.ts.map +1 -1
- package/dist/tempo/server/Subscription.d.ts +28 -12
- package/dist/tempo/server/Subscription.d.ts.map +1 -1
- package/dist/tempo/server/Subscription.js +82 -30
- package/dist/tempo/server/Subscription.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/dist/tempo/subscription/KeyAuthorization.d.ts +3 -14
- package/dist/tempo/subscription/KeyAuthorization.d.ts.map +1 -1
- package/dist/tempo/subscription/KeyAuthorization.js +11 -20
- package/dist/tempo/subscription/KeyAuthorization.js.map +1 -1
- package/dist/tempo/subscription/Types.d.ts +2 -2
- package/dist/tempo/subscription/Types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Method.ts +21 -5
- package/src/middlewares/internal/mppx.test.ts +24 -0
- package/src/middlewares/internal/mppx.ts +6 -2
- package/src/server/Mppx.ts +17 -4
- package/src/tempo/Methods.test.ts +17 -0
- package/src/tempo/Methods.ts +12 -4
- package/src/tempo/client/Subscription.test.ts +5 -7
- package/src/tempo/server/AtomicStore.test-d.ts +11 -0
- package/src/tempo/server/Charge.test.ts +189 -0
- package/src/tempo/server/Charge.ts +148 -31
- package/src/tempo/server/Subscription.test.ts +1 -4
- package/src/tempo/server/Subscription.ts +156 -67
- package/src/tempo/server/internal/html/package.json +1 -1
- package/src/tempo/server/internal/html.gen.ts +1 -1
- package/src/tempo/subscription/KeyAuthorization.test.ts +13 -4
- package/src/tempo/subscription/KeyAuthorization.ts +11 -20
- package/src/tempo/subscription/Types.ts +2 -2
|
@@ -3881,6 +3881,195 @@ describe('tempo', () => {
|
|
|
3881
3881
|
httpServer.close()
|
|
3882
3882
|
})
|
|
3883
3883
|
|
|
3884
|
+
test('server accepts hash transfers from a different source when validateSender allows it', async () => {
|
|
3885
|
+
let validateSenderCalled = false
|
|
3886
|
+
const validatingServer = Mppx_server.create({
|
|
3887
|
+
methods: [
|
|
3888
|
+
tempo_server.charge({
|
|
3889
|
+
getClient() {
|
|
3890
|
+
return client
|
|
3891
|
+
},
|
|
3892
|
+
currency: asset,
|
|
3893
|
+
account: accounts[0],
|
|
3894
|
+
validateSender({ expectedSender, sender, source }) {
|
|
3895
|
+
validateSenderCalled = true
|
|
3896
|
+
expect(sender.toLowerCase()).toBe(accounts[1].address.toLowerCase())
|
|
3897
|
+
expect(expectedSender.toLowerCase()).toBe(accounts[2].address.toLowerCase())
|
|
3898
|
+
expect(source).toEqual({
|
|
3899
|
+
address: accounts[2].address,
|
|
3900
|
+
chainId: chain.id,
|
|
3901
|
+
})
|
|
3902
|
+
return true
|
|
3903
|
+
},
|
|
3904
|
+
}),
|
|
3905
|
+
],
|
|
3906
|
+
realm,
|
|
3907
|
+
secretKey,
|
|
3908
|
+
})
|
|
3909
|
+
const httpServer = await Http.createServer(async (req, res) => {
|
|
3910
|
+
const result = await Mppx_server.toNodeListener(
|
|
3911
|
+
validatingServer.charge({ amount: '1', decimals: 6 }),
|
|
3912
|
+
)(req, res)
|
|
3913
|
+
if (result.status === 402) return
|
|
3914
|
+
res.end('OK')
|
|
3915
|
+
})
|
|
3916
|
+
|
|
3917
|
+
const response = await fetch(httpServer.url)
|
|
3918
|
+
expect(response.status).toBe(402)
|
|
3919
|
+
|
|
3920
|
+
const challenge = Challenge.fromResponse(response, {
|
|
3921
|
+
methods: [tempo_client.charge()],
|
|
3922
|
+
})
|
|
3923
|
+
const memo = Attribution.encode({ challengeId: challenge.id, serverId: challenge.realm })
|
|
3924
|
+
|
|
3925
|
+
const { receipt } = await Actions.token.transferSync(client, {
|
|
3926
|
+
account: accounts[1],
|
|
3927
|
+
amount: BigInt(challenge.request.amount),
|
|
3928
|
+
memo: memo as Hex.Hex,
|
|
3929
|
+
to: challenge.request.recipient as Hex.Hex,
|
|
3930
|
+
token: challenge.request.currency as Hex.Hex,
|
|
3931
|
+
})
|
|
3932
|
+
|
|
3933
|
+
const credential = Credential.from({
|
|
3934
|
+
challenge,
|
|
3935
|
+
payload: { hash: receipt.transactionHash, type: 'hash' as const },
|
|
3936
|
+
source: `did:pkh:eip155:${chain.id}:${accounts[2].address}`,
|
|
3937
|
+
})
|
|
3938
|
+
|
|
3939
|
+
{
|
|
3940
|
+
const response = await fetch(httpServer.url, {
|
|
3941
|
+
headers: { Authorization: Credential.serialize(credential) },
|
|
3942
|
+
})
|
|
3943
|
+
expect(response.status).toBe(200)
|
|
3944
|
+
expect(validateSenderCalled).toBe(true)
|
|
3945
|
+
}
|
|
3946
|
+
|
|
3947
|
+
httpServer.close()
|
|
3948
|
+
})
|
|
3949
|
+
|
|
3950
|
+
test('server rejects hash transfers that reuse one transferWithMemo for duplicate expected transfers', async () => {
|
|
3951
|
+
const validatingServer = Mppx_server.create({
|
|
3952
|
+
methods: [
|
|
3953
|
+
tempo_server.charge({
|
|
3954
|
+
getClient() {
|
|
3955
|
+
return client
|
|
3956
|
+
},
|
|
3957
|
+
currency: asset,
|
|
3958
|
+
account: accounts[0],
|
|
3959
|
+
validateSender() {
|
|
3960
|
+
return true
|
|
3961
|
+
},
|
|
3962
|
+
}),
|
|
3963
|
+
],
|
|
3964
|
+
realm,
|
|
3965
|
+
secretKey,
|
|
3966
|
+
})
|
|
3967
|
+
const httpServer = await Http.createServer(async (req, res) => {
|
|
3968
|
+
const result = await Mppx_server.toNodeListener(
|
|
3969
|
+
validatingServer.charge({
|
|
3970
|
+
amount: '2',
|
|
3971
|
+
currency: asset,
|
|
3972
|
+
recipient: accounts[0].address,
|
|
3973
|
+
splits: [{ amount: '1', recipient: accounts[0].address }],
|
|
3974
|
+
}),
|
|
3975
|
+
)(req, res)
|
|
3976
|
+
if (result.status === 402) return
|
|
3977
|
+
res.end('OK')
|
|
3978
|
+
})
|
|
3979
|
+
|
|
3980
|
+
const response = await fetch(httpServer.url)
|
|
3981
|
+
expect(response.status).toBe(402)
|
|
3982
|
+
|
|
3983
|
+
const challenge = Challenge.fromResponse(response, {
|
|
3984
|
+
methods: [tempo_client.charge()],
|
|
3985
|
+
})
|
|
3986
|
+
const memo = Attribution.encode({ challengeId: challenge.id, serverId: challenge.realm })
|
|
3987
|
+
const splits = challenge.request.methodDetails?.splits ?? []
|
|
3988
|
+
const primaryAmount = BigInt(challenge.request.amount) - BigInt(splits[0]!.amount)
|
|
3989
|
+
|
|
3990
|
+
const { receipt } = await Actions.token.transferSync(client, {
|
|
3991
|
+
account: accounts[1],
|
|
3992
|
+
amount: primaryAmount,
|
|
3993
|
+
memo: memo as Hex.Hex,
|
|
3994
|
+
to: challenge.request.recipient as Hex.Hex,
|
|
3995
|
+
token: challenge.request.currency as Hex.Hex,
|
|
3996
|
+
})
|
|
3997
|
+
|
|
3998
|
+
const credential = Credential.from({
|
|
3999
|
+
challenge,
|
|
4000
|
+
payload: { hash: receipt.transactionHash, type: 'hash' as const },
|
|
4001
|
+
source: `did:pkh:eip155:${chain.id}:${accounts[2].address}`,
|
|
4002
|
+
})
|
|
4003
|
+
|
|
4004
|
+
{
|
|
4005
|
+
const response = await fetch(httpServer.url, {
|
|
4006
|
+
headers: { Authorization: Credential.serialize(credential) },
|
|
4007
|
+
})
|
|
4008
|
+
expect(response.status).toBe(402)
|
|
4009
|
+
const body = (await response.json()) as { detail: string }
|
|
4010
|
+
expect(body.detail).toContain('no matching transfer found')
|
|
4011
|
+
}
|
|
4012
|
+
|
|
4013
|
+
httpServer.close()
|
|
4014
|
+
})
|
|
4015
|
+
|
|
4016
|
+
test('server skips validateSender when hash transfer source already matches', async () => {
|
|
4017
|
+
const validatingServer = Mppx_server.create({
|
|
4018
|
+
methods: [
|
|
4019
|
+
tempo_server.charge({
|
|
4020
|
+
getClient() {
|
|
4021
|
+
return client
|
|
4022
|
+
},
|
|
4023
|
+
currency: asset,
|
|
4024
|
+
account: accounts[0],
|
|
4025
|
+
validateSender() {
|
|
4026
|
+
throw new Error('validateSender should not run for matching senders')
|
|
4027
|
+
},
|
|
4028
|
+
}),
|
|
4029
|
+
],
|
|
4030
|
+
realm,
|
|
4031
|
+
secretKey,
|
|
4032
|
+
})
|
|
4033
|
+
const httpServer = await Http.createServer(async (req, res) => {
|
|
4034
|
+
const result = await Mppx_server.toNodeListener(
|
|
4035
|
+
validatingServer.charge({ amount: '1', decimals: 6 }),
|
|
4036
|
+
)(req, res)
|
|
4037
|
+
if (result.status === 402) return
|
|
4038
|
+
res.end('OK')
|
|
4039
|
+
})
|
|
4040
|
+
|
|
4041
|
+
const response = await fetch(httpServer.url)
|
|
4042
|
+
expect(response.status).toBe(402)
|
|
4043
|
+
|
|
4044
|
+
const challenge = Challenge.fromResponse(response, {
|
|
4045
|
+
methods: [tempo_client.charge()],
|
|
4046
|
+
})
|
|
4047
|
+
const memo = Attribution.encode({ challengeId: challenge.id, serverId: challenge.realm })
|
|
4048
|
+
|
|
4049
|
+
const { receipt } = await Actions.token.transferSync(client, {
|
|
4050
|
+
account: accounts[1],
|
|
4051
|
+
amount: BigInt(challenge.request.amount),
|
|
4052
|
+
memo: memo as Hex.Hex,
|
|
4053
|
+
to: challenge.request.recipient as Hex.Hex,
|
|
4054
|
+
token: challenge.request.currency as Hex.Hex,
|
|
4055
|
+
})
|
|
4056
|
+
|
|
4057
|
+
const credential = Credential.from({
|
|
4058
|
+
challenge,
|
|
4059
|
+
payload: { hash: receipt.transactionHash, type: 'hash' as const },
|
|
4060
|
+
source: `did:pkh:eip155:${chain.id}:${accounts[1].address}`,
|
|
4061
|
+
})
|
|
4062
|
+
|
|
4063
|
+
{
|
|
4064
|
+
const response = await fetch(httpServer.url, {
|
|
4065
|
+
headers: { Authorization: Credential.serialize(credential) },
|
|
4066
|
+
})
|
|
4067
|
+
expect(response.status).toBe(200)
|
|
4068
|
+
}
|
|
4069
|
+
|
|
4070
|
+
httpServer.close()
|
|
4071
|
+
})
|
|
4072
|
+
|
|
3884
4073
|
test('server rejects hash credentials with malformed source', async () => {
|
|
3885
4074
|
const httpServer = await Http.createServer(async (req, res) => {
|
|
3886
4075
|
const result = await Mppx_server.toNodeListener(
|
|
@@ -63,6 +63,7 @@ export function charge<const parameters extends charge.Parameters>(
|
|
|
63
63
|
feePayerPolicy,
|
|
64
64
|
html,
|
|
65
65
|
memo,
|
|
66
|
+
validateSender,
|
|
66
67
|
waitForConfirmation = true,
|
|
67
68
|
} = parameters
|
|
68
69
|
const store = (parameters.store ?? Store.memory()) as Store.AtomicStore<charge.StoreItemMap>
|
|
@@ -230,10 +231,12 @@ export function charge<const parameters extends charge.Parameters>(
|
|
|
230
231
|
})
|
|
231
232
|
const receipt = await getTransactionReceipt(client, { hash })
|
|
232
233
|
const sender = source?.address ?? receipt.from
|
|
233
|
-
const matchedLogs = assertTransferLogs(receipt, {
|
|
234
|
+
const matchedLogs = await assertTransferLogs(receipt, {
|
|
234
235
|
currency,
|
|
235
236
|
sender,
|
|
237
|
+
source,
|
|
236
238
|
transfers: expectedTransfers,
|
|
239
|
+
validateSender,
|
|
237
240
|
})
|
|
238
241
|
// Only verify challenge binding when using auto-generated attribution memos.
|
|
239
242
|
// Explicit memos (set by the server) are strictly matched by assertTransferLogs
|
|
@@ -415,7 +418,7 @@ export function charge<const parameters extends charge.Parameters>(
|
|
|
415
418
|
const receipt = await sendRawTransactionSync(client, {
|
|
416
419
|
serializedTransaction: serializedTransaction_final,
|
|
417
420
|
})
|
|
418
|
-
const matchedLogs = assertTransferLogs(receipt, {
|
|
421
|
+
const matchedLogs = await assertTransferLogs(receipt, {
|
|
419
422
|
currency,
|
|
420
423
|
sender: transaction.from! as `0x${string}`,
|
|
421
424
|
transfers,
|
|
@@ -490,6 +493,17 @@ export declare namespace charge {
|
|
|
490
493
|
|
|
491
494
|
type Defaults = LooseOmit<Method.RequestDefaults<typeof Methods.charge>, 'feePayer' | 'recipient'>
|
|
492
495
|
|
|
496
|
+
type ValidateSender = (parameters: ValidateSenderParameters) => boolean | Promise<boolean>
|
|
497
|
+
|
|
498
|
+
type ValidateSenderParameters = {
|
|
499
|
+
/** Actual TIP-20 `Transfer.from` address. */
|
|
500
|
+
sender: `0x${string}`
|
|
501
|
+
/** Address that mppx would normally require as the sender. */
|
|
502
|
+
expectedSender: `0x${string}`
|
|
503
|
+
/** Parsed hash credential source when the credential includes one. */
|
|
504
|
+
source?: { address: `0x${string}`; chainId: number } | undefined
|
|
505
|
+
}
|
|
506
|
+
|
|
493
507
|
type Parameters = {
|
|
494
508
|
/** Render payment page when Accept header is text/html (e.g. in browsers) */
|
|
495
509
|
html?: boolean | Html.Config | undefined
|
|
@@ -519,6 +533,12 @@ export declare namespace charge {
|
|
|
519
533
|
* proofs are visible across all server instances.
|
|
520
534
|
*/
|
|
521
535
|
store?: Store.AtomicStore | undefined
|
|
536
|
+
/**
|
|
537
|
+
* Validates a TIP-20 transfer sender when it differs from the credential
|
|
538
|
+
* source. Core verification still validates amount, currency, recipient,
|
|
539
|
+
* memo binding, transaction success, and replay protection.
|
|
540
|
+
*/
|
|
541
|
+
validateSender?: ValidateSender | undefined
|
|
522
542
|
/**
|
|
523
543
|
* Whether to wait for the charge transaction to confirm on-chain before
|
|
524
544
|
* responding. @default true
|
|
@@ -687,29 +707,17 @@ type TransferLog =
|
|
|
687
707
|
address: `0x${string}`
|
|
688
708
|
}
|
|
689
709
|
|
|
690
|
-
function assertTransferLogs(
|
|
710
|
+
async function assertTransferLogs(
|
|
691
711
|
receipt: TransactionReceipt,
|
|
692
712
|
parameters: {
|
|
693
713
|
currency: `0x${string}`
|
|
694
714
|
sender: `0x${string}`
|
|
715
|
+
source?: { address: `0x${string}`; chainId: number } | undefined
|
|
695
716
|
transfers: readonly ExpectedTransfer[]
|
|
717
|
+
validateSender?: charge.ValidateSender | undefined
|
|
696
718
|
},
|
|
697
|
-
): TransferLog[] {
|
|
698
|
-
const
|
|
699
|
-
abi: Abis.tip20,
|
|
700
|
-
eventName: 'Transfer',
|
|
701
|
-
logs: receipt.logs,
|
|
702
|
-
}).map((log) => ({ ...log, kind: 'transfer' as const }))
|
|
703
|
-
|
|
704
|
-
const memoLogs = parseEventLogs({
|
|
705
|
-
abi: Abis.tip20,
|
|
706
|
-
eventName: 'TransferWithMemo',
|
|
707
|
-
logs: receipt.logs,
|
|
708
|
-
}).map((log) => ({ ...log, kind: 'memo' as const }))
|
|
709
|
-
|
|
710
|
-
// Prefer memo logs so allowAnyMemo matches TransferWithMemo before Transfer,
|
|
711
|
-
// preserving the memo for challenge binding verification.
|
|
712
|
-
const logs = [...memoLogs, ...transferLogs]
|
|
719
|
+
): Promise<TransferLog[]> {
|
|
720
|
+
const logs = getTransferLogEffects(receipt)
|
|
713
721
|
const used = new Set<number>()
|
|
714
722
|
const matched: TransferLog[] = []
|
|
715
723
|
|
|
@@ -722,18 +730,31 @@ function assertTransferLogs(
|
|
|
722
730
|
})
|
|
723
731
|
|
|
724
732
|
for (const transfer of sorted) {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
if (
|
|
728
|
-
if (!TempoAddress.isEqual(log.
|
|
729
|
-
if (!TempoAddress.isEqual(log.args.to, transfer.recipient))
|
|
730
|
-
if (log.args.amount.toString() !== transfer.amount)
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
733
|
+
let matchIndex = -1
|
|
734
|
+
for (const [index, log] of logs.entries()) {
|
|
735
|
+
if (used.has(index)) continue
|
|
736
|
+
if (!TempoAddress.isEqual(log.address, parameters.currency)) continue
|
|
737
|
+
if (!TempoAddress.isEqual(log.args.to, transfer.recipient)) continue
|
|
738
|
+
if (log.args.amount.toString() !== transfer.amount) continue
|
|
739
|
+
const memoMatches = (() => {
|
|
740
|
+
if (transfer.memo)
|
|
741
|
+
return log.kind === 'memo' && log.args.memo.toLowerCase() === transfer.memo.toLowerCase()
|
|
742
|
+
if (transfer.allowAnyMemo) return log.kind === 'transfer' || log.kind === 'memo'
|
|
743
|
+
return log.kind === 'transfer'
|
|
744
|
+
})()
|
|
745
|
+
if (!memoMatches) continue
|
|
746
|
+
if (
|
|
747
|
+
!(await isValidTransferSender({
|
|
748
|
+
expectedSender: parameters.sender,
|
|
749
|
+
sender: log.args.from,
|
|
750
|
+
source: parameters.source,
|
|
751
|
+
validateSender: parameters.validateSender,
|
|
752
|
+
}))
|
|
753
|
+
)
|
|
754
|
+
continue
|
|
755
|
+
matchIndex = index
|
|
756
|
+
break
|
|
757
|
+
}
|
|
737
758
|
|
|
738
759
|
if (matchIndex === -1) {
|
|
739
760
|
throw new MismatchError('Payment verification failed: no matching transfer found.', {
|
|
@@ -750,6 +771,102 @@ function assertTransferLogs(
|
|
|
750
771
|
return matched
|
|
751
772
|
}
|
|
752
773
|
|
|
774
|
+
type ParsedTransferLog =
|
|
775
|
+
| {
|
|
776
|
+
kind: 'transfer'
|
|
777
|
+
args: { from: `0x${string}`; to: `0x${string}`; amount: bigint }
|
|
778
|
+
address: `0x${string}`
|
|
779
|
+
logIndex: number
|
|
780
|
+
}
|
|
781
|
+
| {
|
|
782
|
+
kind: 'memo'
|
|
783
|
+
args: { from: `0x${string}`; to: `0x${string}`; amount: bigint; memo: `0x${string}` }
|
|
784
|
+
address: `0x${string}`
|
|
785
|
+
logIndex: number
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function getTransferLogEffects(receipt: TransactionReceipt): TransferLog[] {
|
|
789
|
+
const transferLogs = parseEventLogs({
|
|
790
|
+
abi: Abis.tip20,
|
|
791
|
+
eventName: 'Transfer',
|
|
792
|
+
logs: receipt.logs,
|
|
793
|
+
}).map(
|
|
794
|
+
(log) =>
|
|
795
|
+
({
|
|
796
|
+
address: log.address,
|
|
797
|
+
args: log.args,
|
|
798
|
+
kind: 'transfer',
|
|
799
|
+
logIndex: log.logIndex,
|
|
800
|
+
}) as ParsedTransferLog,
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
const memoLogs = parseEventLogs({
|
|
804
|
+
abi: Abis.tip20,
|
|
805
|
+
eventName: 'TransferWithMemo',
|
|
806
|
+
logs: receipt.logs,
|
|
807
|
+
}).map(
|
|
808
|
+
(log) =>
|
|
809
|
+
({
|
|
810
|
+
address: log.address,
|
|
811
|
+
args: log.args,
|
|
812
|
+
kind: 'memo',
|
|
813
|
+
logIndex: log.logIndex,
|
|
814
|
+
}) as ParsedTransferLog,
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
const logs = [...transferLogs, ...memoLogs].sort((a, b) => a.logIndex - b.logIndex)
|
|
818
|
+
const effects: TransferLog[] = []
|
|
819
|
+
|
|
820
|
+
for (let index = 0; index < logs.length; index++) {
|
|
821
|
+
const log = logs[index]!
|
|
822
|
+
const next = logs[index + 1]
|
|
823
|
+
if (next && log.kind !== next.kind && isSameTransferLog(log, next)) {
|
|
824
|
+
const memoLog = log.kind === 'memo' ? log : next.kind === 'memo' ? next : undefined
|
|
825
|
+
if (!memoLog) continue
|
|
826
|
+
effects.push({
|
|
827
|
+
address: memoLog.address,
|
|
828
|
+
args: memoLog.args,
|
|
829
|
+
kind: 'memo',
|
|
830
|
+
})
|
|
831
|
+
index++
|
|
832
|
+
continue
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
effects.push({
|
|
836
|
+
address: log.address,
|
|
837
|
+
args: log.args,
|
|
838
|
+
kind: log.kind,
|
|
839
|
+
} as TransferLog)
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
return effects
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
function isSameTransferLog(a: ParsedTransferLog, b: ParsedTransferLog): boolean {
|
|
846
|
+
return (
|
|
847
|
+
TempoAddress.isEqual(a.address, b.address) &&
|
|
848
|
+
TempoAddress.isEqual(a.args.from, b.args.from) &&
|
|
849
|
+
TempoAddress.isEqual(a.args.to, b.args.to) &&
|
|
850
|
+
a.args.amount === b.args.amount &&
|
|
851
|
+
Math.abs(a.logIndex - b.logIndex) === 1
|
|
852
|
+
)
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
async function isValidTransferSender(parameters: {
|
|
856
|
+
expectedSender: `0x${string}`
|
|
857
|
+
sender: `0x${string}`
|
|
858
|
+
source?: { address: `0x${string}`; chainId: number } | undefined
|
|
859
|
+
validateSender?: charge.ValidateSender | undefined
|
|
860
|
+
}): Promise<boolean> {
|
|
861
|
+
if (TempoAddress.isEqual(parameters.sender, parameters.expectedSender)) return true
|
|
862
|
+
if (!parameters.validateSender) return false
|
|
863
|
+
return parameters.validateSender({
|
|
864
|
+
expectedSender: parameters.expectedSender,
|
|
865
|
+
sender: parameters.sender,
|
|
866
|
+
source: parameters.source,
|
|
867
|
+
})
|
|
868
|
+
}
|
|
869
|
+
|
|
753
870
|
/** @internal */
|
|
754
871
|
function getHashStoreKey(hash: `0x${string}`): `mppx:charge:${string}` {
|
|
755
872
|
return `mppx:charge:${hash.toLowerCase()}`
|
|
@@ -1395,11 +1395,8 @@ describe('tempo.subscription', () => {
|
|
|
1395
1395
|
reference: hashStale,
|
|
1396
1396
|
})
|
|
1397
1397
|
|
|
1398
|
-
const result = await renew({
|
|
1399
|
-
getClient: async () => client,
|
|
1400
|
-
store,
|
|
1398
|
+
const result = await mppx.tempo.subscription.renew({
|
|
1401
1399
|
subscriptionId: record.subscriptionId,
|
|
1402
|
-
waitForConfirmation: false,
|
|
1403
1400
|
})
|
|
1404
1401
|
|
|
1405
1402
|
expect(result?.receipt.reference).toBe(hashBackground)
|