mppx 0.6.21 → 0.6.23
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 +12 -0
- package/dist/tempo/client/Subscription.d.ts.map +1 -1
- package/dist/tempo/client/Subscription.js +4 -3
- package/dist/tempo/client/Subscription.js.map +1 -1
- package/dist/tempo/server/Subscription.d.ts +7 -0
- package/dist/tempo/server/Subscription.d.ts.map +1 -1
- package/dist/tempo/server/Subscription.js +47 -6
- 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/package.json +1 -1
- package/src/tempo/client/Subscription.test.ts +51 -0
- package/src/tempo/client/Subscription.ts +4 -3
- package/src/tempo/server/Subscription.ts +62 -3
- package/src/tempo/server/internal/html.gen.ts +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"html.gen.js","sourceRoot":"","sources":["../../../../src/tempo/server/internal/html.gen.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,MAAM,CAAC,MAAM,IAAI,GAAG,
|
|
1
|
+
{"version":3,"file":"html.gen.js","sourceRoot":"","sources":["../../../../src/tempo/server/internal/html.gen.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,MAAM,CAAC,MAAM,IAAI,GAAG,8yteAA8yte,CAAA"}
|
package/package.json
CHANGED
|
@@ -104,6 +104,57 @@ describe('tempo.subscription client', () => {
|
|
|
104
104
|
)
|
|
105
105
|
})
|
|
106
106
|
|
|
107
|
+
test('passes hex-encoded `scopes` and `limits` to wallet_authorizeAccessKey', async () => {
|
|
108
|
+
const challenge = createChallenge()
|
|
109
|
+
const keyAuthorization = await signSubscriptionKeyAuthorization({
|
|
110
|
+
accessKey,
|
|
111
|
+
account: selectedAccount,
|
|
112
|
+
chainId,
|
|
113
|
+
request: challenge.request,
|
|
114
|
+
})
|
|
115
|
+
if (!keyAuthorization) throw new Error('expected key authorization')
|
|
116
|
+
|
|
117
|
+
let capturedParams: Record<string, unknown> | undefined
|
|
118
|
+
const method = subscription({
|
|
119
|
+
account: selectedAccount.address,
|
|
120
|
+
getClient: async () =>
|
|
121
|
+
({
|
|
122
|
+
request: async ({ params }: { params: readonly Record<string, unknown>[] }) => {
|
|
123
|
+
capturedParams = params[0]
|
|
124
|
+
return { keyAuthorization: KeyAuthorization.toRpc(keyAuthorization) }
|
|
125
|
+
},
|
|
126
|
+
}) as never,
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
await method.createCredential({ challenge, context: {} })
|
|
130
|
+
|
|
131
|
+
expect(capturedParams).toMatchObject({
|
|
132
|
+
address: accessKey.accessKeyAddress.toLowerCase(),
|
|
133
|
+
keyType: accessKey.keyType,
|
|
134
|
+
expiry: expect.any(Number),
|
|
135
|
+
limits: [
|
|
136
|
+
{
|
|
137
|
+
token: expect.stringMatching(/^0x[0-9a-fA-F]{40}$/),
|
|
138
|
+
limit: '0xf4240',
|
|
139
|
+
period: expect.any(Number),
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
scopes: [
|
|
143
|
+
{
|
|
144
|
+
address: expect.stringMatching(/^0x[0-9a-fA-F]{40}$/),
|
|
145
|
+
selector: expect.stringMatching(/^0x/),
|
|
146
|
+
recipients: expect.any(Array),
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
address: expect.stringMatching(/^0x[0-9a-fA-F]{40}$/),
|
|
150
|
+
selector: expect.stringMatching(/^0x/),
|
|
151
|
+
recipients: expect.any(Array),
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
})
|
|
155
|
+
expect(capturedParams).not.toHaveProperty('allowedCalls')
|
|
156
|
+
})
|
|
157
|
+
|
|
107
158
|
test('rejects key authorizations signed by a different account', async () => {
|
|
108
159
|
const challenge = createChallenge()
|
|
109
160
|
const keyAuthorization = await signSubscriptionKeyAuthorization({
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Hex } from 'ox'
|
|
1
2
|
import { KeyAuthorization } from 'ox/tempo'
|
|
2
3
|
import { isAddressEqual, type Address } from 'viem'
|
|
3
4
|
import { tempo as tempo_chain } from 'viem/chains'
|
|
@@ -11,7 +12,7 @@ import * as z from '../../zod.js'
|
|
|
11
12
|
import * as defaults from '../internal/defaults.js'
|
|
12
13
|
import * as Methods from '../Methods.js'
|
|
13
14
|
import {
|
|
14
|
-
|
|
15
|
+
getSubscriptionScopes,
|
|
15
16
|
signSubscriptionKeyAuthorization,
|
|
16
17
|
toSubscriptionExpiryDate,
|
|
17
18
|
toSubscriptionExpirySeconds,
|
|
@@ -117,16 +118,16 @@ async function authorizeAccessKey(
|
|
|
117
118
|
params: [
|
|
118
119
|
{
|
|
119
120
|
address: accessKey.accessKeyAddress,
|
|
120
|
-
allowedCalls: getSubscriptionRpcAllowedCalls(request),
|
|
121
121
|
expiry: toSubscriptionExpirySeconds(toSubscriptionExpiryDate(request.subscriptionExpires)),
|
|
122
122
|
keyType: accessKey.keyType,
|
|
123
123
|
limits: [
|
|
124
124
|
{
|
|
125
125
|
token: request.currency as Address,
|
|
126
|
-
limit: BigInt(request.amount),
|
|
126
|
+
limit: Hex.fromNumber(BigInt(request.amount)),
|
|
127
127
|
period: toSubscriptionPeriodSeconds(request),
|
|
128
128
|
},
|
|
129
129
|
],
|
|
130
|
+
scopes: getSubscriptionScopes(request),
|
|
130
131
|
},
|
|
131
132
|
],
|
|
132
133
|
} as never)) as {
|
|
@@ -3,6 +3,7 @@ import { KeyAuthorization } from 'ox/tempo'
|
|
|
3
3
|
import { encodeFunctionData, isAddressEqual, type Address, type Client as ViemClient } from 'viem'
|
|
4
4
|
import {
|
|
5
5
|
call as viem_call,
|
|
6
|
+
prepareTransactionRequest,
|
|
6
7
|
sendRawTransaction,
|
|
7
8
|
sendRawTransactionSync,
|
|
8
9
|
signTransaction,
|
|
@@ -19,6 +20,7 @@ import * as ClientResolver from '../../viem/Client.js'
|
|
|
19
20
|
import * as Attribution from '../Attribution.js'
|
|
20
21
|
import * as Account from '../internal/account.js'
|
|
21
22
|
import * as defaults from '../internal/defaults.js'
|
|
23
|
+
import * as FeePayer from '../internal/fee-payer.js'
|
|
22
24
|
import * as Proof from '../internal/proof.js'
|
|
23
25
|
import type * as types from '../internal/types.js'
|
|
24
26
|
import * as Methods from '../Methods.js'
|
|
@@ -72,7 +74,7 @@ export function subscription<const parameters extends subscription.Parameters>(
|
|
|
72
74
|
activationTimeoutMs: parameters.activationTimeoutMs,
|
|
73
75
|
renewalTimeoutMs: parameters.renewalTimeoutMs,
|
|
74
76
|
})
|
|
75
|
-
const { recipient } = Account.resolve(parameters)
|
|
77
|
+
const { feePayer, recipient } = Account.resolve(parameters)
|
|
76
78
|
const getClient = ClientResolver.getResolver({
|
|
77
79
|
chain: tempo_chain,
|
|
78
80
|
getClient: parameters.getClient,
|
|
@@ -104,6 +106,8 @@ export function subscription<const parameters extends subscription.Parameters>(
|
|
|
104
106
|
const periodIndex = getPeriodIndex(subscription)
|
|
105
107
|
if (periodIndex > subscription.lastChargedPeriod) {
|
|
106
108
|
const renew = resolveRenewalHandler({
|
|
109
|
+
feePayer,
|
|
110
|
+
feePayerPolicy: parameters.feePayerPolicy,
|
|
107
111
|
getClient,
|
|
108
112
|
parameters,
|
|
109
113
|
store,
|
|
@@ -235,6 +239,8 @@ export function subscription<const parameters extends subscription.Parameters>(
|
|
|
235
239
|
accessKey,
|
|
236
240
|
auto: {
|
|
237
241
|
challengeId: credential.challenge.id,
|
|
242
|
+
feePayer,
|
|
243
|
+
feePayerPolicy: parameters.feePayerPolicy,
|
|
238
244
|
getClient,
|
|
239
245
|
keyAuthorization: (credential.payload as SubscriptionCredentialPayload).signature,
|
|
240
246
|
realm: credential.challenge.realm,
|
|
@@ -346,6 +352,8 @@ async function activateSubscription(parameters: {
|
|
|
346
352
|
accessKey: SubscriptionAccessKey
|
|
347
353
|
auto: {
|
|
348
354
|
challengeId: string
|
|
355
|
+
feePayer?: ReturnType<typeof Account.resolve>['feePayer'] | undefined
|
|
356
|
+
feePayerPolicy?: Partial<FeePayer.Policy> | undefined
|
|
349
357
|
getClient: (parameters: { chainId?: number | undefined }) => MaybePromise<ViemClient>
|
|
350
358
|
keyAuthorization: `0x${string}`
|
|
351
359
|
realm: string
|
|
@@ -391,6 +399,8 @@ async function activateSubscription(parameters: {
|
|
|
391
399
|
// billing authority needed for request-path and background renewals.
|
|
392
400
|
const reference = await submitSubscriptionPayment({
|
|
393
401
|
accessKey,
|
|
402
|
+
feePayer: auto.feePayer,
|
|
403
|
+
feePayerPolicy: auto.feePayerPolicy,
|
|
394
404
|
getClient: auto.getClient,
|
|
395
405
|
keyAuthorization: auto.keyAuthorization,
|
|
396
406
|
lookupKey: resolved.key,
|
|
@@ -689,6 +699,8 @@ function subscriptionBinding(request: SubscriptionRequest) {
|
|
|
689
699
|
}
|
|
690
700
|
|
|
691
701
|
function resolveRenewalHandler(parameters: {
|
|
702
|
+
feePayer?: ReturnType<typeof Account.resolve>['feePayer'] | undefined
|
|
703
|
+
feePayerPolicy?: Partial<FeePayer.Policy> | undefined
|
|
692
704
|
getClient: (parameters: { chainId?: number | undefined }) => MaybePromise<ViemClient>
|
|
693
705
|
parameters: {
|
|
694
706
|
renew?:
|
|
@@ -710,6 +722,8 @@ function resolveRenewalHandler(parameters: {
|
|
|
710
722
|
}) => Promise<subscription.RenewalResult>)
|
|
711
723
|
| undefined {
|
|
712
724
|
const {
|
|
725
|
+
feePayer,
|
|
726
|
+
feePayerPolicy,
|
|
713
727
|
getClient,
|
|
714
728
|
parameters: subscriptionParameters,
|
|
715
729
|
store,
|
|
@@ -721,6 +735,8 @@ function resolveRenewalHandler(parameters: {
|
|
|
721
735
|
return async ({ inFlightReference, periodIndex, subscription }) => {
|
|
722
736
|
const reference = await submitSubscriptionPayment({
|
|
723
737
|
accessKey: subscription.accessKey!,
|
|
738
|
+
feePayer,
|
|
739
|
+
feePayerPolicy,
|
|
724
740
|
getClient,
|
|
725
741
|
lookupKey: subscription.lookupKey,
|
|
726
742
|
request: subscription,
|
|
@@ -744,6 +760,8 @@ function resolveRenewalHandler(parameters: {
|
|
|
744
760
|
|
|
745
761
|
async function submitSubscriptionPayment(parameters: {
|
|
746
762
|
accessKey: SubscriptionAccessKey
|
|
763
|
+
feePayer?: ReturnType<typeof Account.resolve>['feePayer'] | undefined
|
|
764
|
+
feePayerPolicy?: Partial<FeePayer.Policy> | undefined
|
|
747
765
|
getClient: (parameters: { chainId?: number | undefined }) => MaybePromise<ViemClient>
|
|
748
766
|
keyAuthorization?: `0x${string}` | undefined
|
|
749
767
|
lookupKey: string
|
|
@@ -757,6 +775,8 @@ async function submitSubscriptionPayment(parameters: {
|
|
|
757
775
|
}) {
|
|
758
776
|
const {
|
|
759
777
|
accessKey,
|
|
778
|
+
feePayer,
|
|
779
|
+
feePayerPolicy,
|
|
760
780
|
getClient,
|
|
761
781
|
keyAuthorization,
|
|
762
782
|
lookupKey,
|
|
@@ -786,7 +806,7 @@ async function submitSubscriptionPayment(parameters: {
|
|
|
786
806
|
challengeId: settlementReference,
|
|
787
807
|
serverId: lookupKey,
|
|
788
808
|
})
|
|
789
|
-
const
|
|
809
|
+
const baseTransaction = {
|
|
790
810
|
account,
|
|
791
811
|
calls: [
|
|
792
812
|
{
|
|
@@ -802,7 +822,39 @@ async function submitSubscriptionPayment(parameters: {
|
|
|
802
822
|
...(keyAuthorization
|
|
803
823
|
? { keyAuthorization: KeyAuthorization.deserialize(keyAuthorization) }
|
|
804
824
|
: {}),
|
|
805
|
-
}
|
|
825
|
+
}
|
|
826
|
+
const serializedTransaction = await (async () => {
|
|
827
|
+
if (!feePayer) return await signTransaction(client, baseTransaction as never)
|
|
828
|
+
// For sponsored payments, prepare the tx via `prepareTransactionRequest`
|
|
829
|
+
// (without `feePayer: true`) so viem returns the chain's full
|
|
830
|
+
// proof-inclusive gas estimate. With `feePayer: true` viem sets a
|
|
831
|
+
// dummy sig + null feePayerSignature, dropping signature and key
|
|
832
|
+
// authorization verification costs — see chainConfig.js FIXME. We add
|
|
833
|
+
// a small buffer for fee-payer overhead, then flip `feePayer = true`
|
|
834
|
+
// and re-sign with the fee-payer-sponsored envelope.
|
|
835
|
+
const prepared = await prepareTransactionRequest(client, {
|
|
836
|
+
...baseTransaction,
|
|
837
|
+
nonceKey: 'expiring',
|
|
838
|
+
} as never)
|
|
839
|
+
prepared.gas = (prepared.gas ?? 0n) + 5_000n
|
|
840
|
+
;(prepared as Record<string, unknown>).feePayer = true
|
|
841
|
+
const userSerialized = await signTransaction(client, prepared as never)
|
|
842
|
+
const userTransaction = Transaction.deserialize(
|
|
843
|
+
userSerialized as Transaction.TransactionSerializedTempo,
|
|
844
|
+
)
|
|
845
|
+
const sponsored = FeePayer.prepareSponsoredTransaction({
|
|
846
|
+
account: feePayer,
|
|
847
|
+
chainId: chainId ?? client.chain!.id,
|
|
848
|
+
details: {
|
|
849
|
+
amount: String(request.amount),
|
|
850
|
+
currency: String(request.currency),
|
|
851
|
+
recipient: String(request.recipient),
|
|
852
|
+
},
|
|
853
|
+
...(feePayerPolicy ? { policy: feePayerPolicy } : {}),
|
|
854
|
+
transaction: userTransaction as never,
|
|
855
|
+
})
|
|
856
|
+
return await signTransaction(client, sponsored as never)
|
|
857
|
+
})()
|
|
806
858
|
const transaction = Transaction.deserialize(
|
|
807
859
|
serializedTransaction as Transaction.TransactionSerializedTempo,
|
|
808
860
|
)
|
|
@@ -810,6 +862,7 @@ async function submitSubscriptionPayment(parameters: {
|
|
|
810
862
|
...transaction,
|
|
811
863
|
account: transaction.from,
|
|
812
864
|
calls: transaction.calls,
|
|
865
|
+
feePayerSignature: undefined,
|
|
813
866
|
} as never)
|
|
814
867
|
|
|
815
868
|
if (!waitForConfirmation) {
|
|
@@ -950,6 +1003,12 @@ export declare namespace subscription {
|
|
|
950
1003
|
* Keeps concurrent renewal safe while allowing recovery from abandoned attempts.
|
|
951
1004
|
*/
|
|
952
1005
|
renewalTimeoutMs?: number | undefined
|
|
1006
|
+
/**
|
|
1007
|
+
* Override the fee-payer policy for sponsored subscription payments.
|
|
1008
|
+
* Useful when the access key + key authorization tx requires more gas
|
|
1009
|
+
* than the default policy allows.
|
|
1010
|
+
*/
|
|
1011
|
+
feePayerPolicy?: Partial<FeePayer.Policy> | undefined
|
|
953
1012
|
activate?:
|
|
954
1013
|
| ((parameters: {
|
|
955
1014
|
/** Custom activation must verify this access key matches the resolved subscription. */
|