mppx 0.6.28 → 0.6.29
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 +17 -0
- package/dist/Challenge.d.ts.map +1 -1
- package/dist/Challenge.js +16 -10
- package/dist/Challenge.js.map +1 -1
- package/dist/Method.d.ts +1 -1
- package/dist/Method.d.ts.map +1 -1
- package/dist/client/Methods.d.ts +1 -0
- package/dist/client/Methods.d.ts.map +1 -1
- package/dist/client/Methods.js +1 -0
- package/dist/client/Methods.js.map +1 -1
- package/dist/client/Mppx.d.ts +3 -3
- package/dist/client/Mppx.d.ts.map +1 -1
- package/dist/client/Mppx.js +1 -0
- package/dist/client/Mppx.js.map +1 -1
- package/dist/client/Transport.d.ts +10 -3
- package/dist/client/Transport.d.ts.map +1 -1
- package/dist/client/Transport.js +60 -7
- package/dist/client/Transport.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal/Fetch.d.ts +3 -0
- package/dist/client/internal/Fetch.d.ts.map +1 -1
- package/dist/client/internal/Fetch.js +12 -20
- package/dist/client/internal/Fetch.js.map +1 -1
- package/dist/evm/Assets.d.ts +2 -0
- package/dist/evm/Assets.d.ts.map +1 -0
- package/dist/evm/Assets.js +2 -0
- package/dist/evm/Assets.js.map +1 -0
- package/dist/evm/Chains.d.ts +5 -0
- package/dist/evm/Chains.d.ts.map +1 -0
- package/dist/evm/Chains.js +5 -0
- package/dist/evm/Chains.js.map +1 -0
- package/dist/evm/Methods.d.ts +68 -0
- package/dist/evm/Methods.d.ts.map +1 -0
- package/dist/evm/Methods.js +28 -0
- package/dist/evm/Methods.js.map +1 -0
- package/dist/evm/Types.d.ts +143 -0
- package/dist/evm/Types.d.ts.map +1 -0
- package/dist/evm/Types.js +102 -0
- package/dist/evm/Types.js.map +1 -0
- package/dist/evm/client/Charge.d.ts +102 -0
- package/dist/evm/client/Charge.d.ts.map +1 -0
- package/dist/evm/client/Charge.js +141 -0
- package/dist/evm/client/Charge.js.map +1 -0
- package/dist/evm/client/Methods.d.ts +81 -0
- package/dist/evm/client/Methods.d.ts.map +1 -0
- package/dist/evm/client/Methods.js +16 -0
- package/dist/evm/client/Methods.js.map +1 -0
- package/dist/evm/client/index.d.ts +6 -0
- package/dist/evm/client/index.d.ts.map +1 -0
- package/dist/evm/client/index.js +6 -0
- package/dist/evm/client/index.js.map +1 -0
- package/dist/evm/index.d.ts +9 -0
- package/dist/evm/index.d.ts.map +1 -0
- package/dist/evm/index.js +8 -0
- package/dist/evm/index.js.map +1 -0
- package/dist/evm/server/Charge.d.ts +62 -0
- package/dist/evm/server/Charge.d.ts.map +1 -0
- package/dist/evm/server/Charge.js +172 -0
- package/dist/evm/server/Charge.js.map +1 -0
- package/dist/evm/server/Methods.d.ts +80 -0
- package/dist/evm/server/Methods.d.ts.map +1 -0
- package/dist/evm/server/Methods.js +16 -0
- package/dist/evm/server/Methods.js.map +1 -0
- package/dist/evm/server/index.d.ts +6 -0
- package/dist/evm/server/index.d.ts.map +1 -0
- package/dist/evm/server/index.js +6 -0
- package/dist/evm/server/index.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/HeaderCodec.d.ts +18 -0
- package/dist/internal/HeaderCodec.d.ts.map +1 -0
- package/dist/internal/HeaderCodec.js +31 -0
- package/dist/internal/HeaderCodec.js.map +1 -0
- package/dist/middlewares/elysia.d.ts.map +1 -1
- package/dist/middlewares/elysia.js +2 -3
- package/dist/middlewares/elysia.js.map +1 -1
- package/dist/middlewares/express.js +2 -1
- package/dist/middlewares/express.js.map +1 -1
- package/dist/proxy/internal/Headers.d.ts.map +1 -1
- package/dist/proxy/internal/Headers.js +11 -1
- package/dist/proxy/internal/Headers.js.map +1 -1
- package/dist/proxy/services/openai.d.ts.map +1 -1
- package/dist/proxy/services/openai.js +2 -0
- package/dist/proxy/services/openai.js.map +1 -1
- package/dist/server/Methods.d.ts +1 -0
- package/dist/server/Methods.d.ts.map +1 -1
- package/dist/server/Methods.js +1 -0
- package/dist/server/Methods.js.map +1 -1
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +90 -12
- package/dist/server/Mppx.js.map +1 -1
- package/dist/server/Transport.d.ts +10 -0
- package/dist/server/Transport.d.ts.map +1 -1
- package/dist/server/Transport.js +9 -0
- package/dist/server/Transport.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/stripe/server/internal/html.gen.d.ts +1 -1
- package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
- package/dist/stripe/server/internal/html.gen.js +1 -1
- package/dist/stripe/server/internal/html.gen.js.map +1 -1
- package/dist/tempo/client/ChannelOps.d.ts +3 -3
- package/dist/tempo/client/ChannelOps.d.ts.map +1 -1
- package/dist/tempo/client/ChannelOps.js +13 -6
- package/dist/tempo/client/ChannelOps.js.map +1 -1
- package/dist/tempo/client/Charge.d.ts.map +1 -1
- package/dist/tempo/client/Charge.js +8 -5
- package/dist/tempo/client/Charge.js.map +1 -1
- package/dist/tempo/client/Methods.d.ts +0 -1
- package/dist/tempo/client/Methods.d.ts.map +1 -1
- package/dist/tempo/client/Session.d.ts +2 -4
- package/dist/tempo/client/Session.d.ts.map +1 -1
- package/dist/tempo/client/Session.js +10 -12
- package/dist/tempo/client/Session.js.map +1 -1
- package/dist/tempo/client/SessionManager.d.ts +3 -3
- package/dist/tempo/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/client/SessionManager.js +1 -1
- package/dist/tempo/client/SessionManager.js.map +1 -1
- package/dist/tempo/internal/account.d.ts +5 -0
- package/dist/tempo/internal/account.d.ts.map +1 -1
- package/dist/tempo/internal/account.js +8 -0
- package/dist/tempo/internal/account.js.map +1 -1
- package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
- package/dist/tempo/internal/fee-payer.js +5 -2
- package/dist/tempo/internal/fee-payer.js.map +1 -1
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +23 -1
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Session.d.ts.map +1 -1
- package/dist/tempo/server/Session.js +13 -12
- package/dist/tempo/server/Session.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/session/Chain.d.ts +2 -0
- package/dist/tempo/session/Chain.d.ts.map +1 -1
- package/dist/tempo/session/Chain.js +8 -8
- package/dist/tempo/session/Chain.js.map +1 -1
- package/dist/tempo/session/Voucher.d.ts +4 -3
- package/dist/tempo/session/Voucher.d.ts.map +1 -1
- package/dist/tempo/session/Voucher.js +71 -44
- package/dist/tempo/session/Voucher.js.map +1 -1
- package/dist/tempo/session/Ws.d.ts.map +1 -1
- package/dist/tempo/session/Ws.js +15 -0
- package/dist/tempo/session/Ws.js.map +1 -1
- package/dist/tempo/subscription/KeyAuthorization.d.ts +2 -2
- package/dist/x402/Assets.d.ts +29 -0
- package/dist/x402/Assets.d.ts.map +1 -0
- package/dist/x402/Assets.js +46 -0
- package/dist/x402/Assets.js.map +1 -0
- package/dist/x402/Header.d.ts +14 -0
- package/dist/x402/Header.d.ts.map +1 -0
- package/dist/x402/Header.js +18 -0
- package/dist/x402/Header.js.map +1 -0
- package/dist/x402/Types.d.ts +289 -0
- package/dist/x402/Types.d.ts.map +1 -0
- package/dist/x402/Types.js +139 -0
- package/dist/x402/Types.js.map +1 -0
- package/dist/x402/client/Exact.d.ts +38 -0
- package/dist/x402/client/Exact.d.ts.map +1 -0
- package/dist/x402/client/Exact.js +141 -0
- package/dist/x402/client/Exact.js.map +1 -0
- package/dist/x402/index.d.ts +6 -0
- package/dist/x402/index.d.ts.map +1 -0
- package/dist/x402/index.js +6 -0
- package/dist/x402/index.js.map +1 -0
- package/dist/x402/internal/ChallengeBrand.d.ts +5 -0
- package/dist/x402/internal/ChallengeBrand.d.ts.map +1 -0
- package/dist/x402/internal/ChallengeBrand.js +13 -0
- package/dist/x402/internal/ChallengeBrand.js.map +1 -0
- package/dist/x402/internal/RouteBinding.d.ts +8 -0
- package/dist/x402/internal/RouteBinding.d.ts.map +1 -0
- package/dist/x402/internal/RouteBinding.js +12 -0
- package/dist/x402/internal/RouteBinding.js.map +1 -0
- package/dist/x402/server/EvmCharge.d.ts +50 -0
- package/dist/x402/server/EvmCharge.d.ts.map +1 -0
- package/dist/x402/server/EvmCharge.js +301 -0
- package/dist/x402/server/EvmCharge.js.map +1 -0
- package/dist/x402/server/Facilitator.d.ts +12 -0
- package/dist/x402/server/Facilitator.d.ts.map +1 -0
- package/dist/x402/server/Facilitator.js +42 -0
- package/dist/x402/server/Facilitator.js.map +1 -0
- package/package.json +41 -21
- package/src/Challenge.test.ts +28 -0
- package/src/Challenge.ts +17 -10
- package/src/Method.ts +1 -1
- package/src/client/Methods.ts +1 -0
- package/src/client/Mppx.ts +4 -3
- package/src/client/Transport.test.ts +165 -30
- package/src/client/Transport.ts +76 -8
- package/src/client/index.ts +1 -1
- package/src/client/internal/Fetch.test.ts +31 -2
- package/src/client/internal/Fetch.ts +26 -19
- package/src/evm/Assets.ts +1 -0
- package/src/evm/Chains.ts +5 -0
- package/src/evm/Methods.ts +44 -0
- package/src/evm/PublicInterface.test-d.ts +109 -0
- package/src/evm/Types.ts +140 -0
- package/src/evm/client/Charge.test.ts +99 -0
- package/src/evm/client/Charge.ts +198 -0
- package/src/evm/client/Methods.ts +19 -0
- package/src/evm/client/index.ts +5 -0
- package/src/evm/index.ts +13 -0
- package/src/evm/server/Charge.test.ts +199 -0
- package/src/evm/server/Charge.ts +283 -0
- package/src/evm/server/Methods.ts +22 -0
- package/src/evm/server/index.ts +5 -0
- package/src/index.ts +2 -0
- package/src/internal/HeaderCodec.ts +36 -0
- package/src/middlewares/elysia.test.ts +25 -0
- package/src/middlewares/elysia.ts +1 -2
- package/src/middlewares/express.test.ts +28 -0
- package/src/middlewares/express.ts +1 -1
- package/src/middlewares/hono.test.ts +138 -2
- package/src/middlewares/nextjs.test.ts +22 -0
- package/src/proxy/internal/Headers.test.ts +20 -0
- package/src/proxy/internal/Headers.ts +12 -1
- package/src/proxy/services/openai.test.ts +57 -1
- package/src/proxy/services/openai.ts +2 -0
- package/src/server/Methods.ts +1 -0
- package/src/server/Mppx.test.ts +244 -1
- package/src/server/Mppx.ts +124 -11
- package/src/server/NodeListener.test.ts +28 -1
- package/src/server/Transport.test.ts +19 -0
- package/src/server/Transport.ts +20 -0
- package/src/server/index.ts +1 -1
- package/src/stripe/server/internal/html.gen.ts +1 -1
- package/src/tempo/AccessKeyAuthorization.test.ts +231 -0
- package/src/tempo/client/ChannelOps.test.ts +61 -7
- package/src/tempo/client/ChannelOps.ts +18 -7
- package/src/tempo/client/Charge.test.ts +126 -0
- package/src/tempo/client/Charge.ts +10 -6
- package/src/tempo/client/Session.test.ts +130 -1
- package/src/tempo/client/Session.ts +12 -19
- package/src/tempo/client/SessionManager.test.ts +69 -2
- package/src/tempo/client/SessionManager.ts +4 -4
- package/src/tempo/internal/account.ts +13 -0
- package/src/tempo/internal/fee-payer.test.ts +32 -2
- package/src/tempo/internal/fee-payer.ts +6 -2
- package/src/tempo/server/Charge.test.ts +69 -0
- package/src/tempo/server/Charge.ts +32 -0
- package/src/tempo/server/Session.test.ts +30 -0
- package/src/tempo/server/Session.ts +15 -16
- package/src/tempo/server/internal/html.gen.ts +1 -1
- package/src/tempo/session/Chain.test.ts +4 -4
- package/src/tempo/session/Chain.ts +10 -6
- package/src/tempo/session/Voucher.test.ts +230 -1
- package/src/tempo/session/Voucher.ts +96 -48
- package/src/tempo/session/Ws.test.ts +71 -0
- package/src/tempo/session/Ws.ts +13 -0
- package/src/x402/Assets.ts +65 -0
- package/src/x402/Exact.e2e.test.ts +448 -0
- package/src/x402/Header.test.ts +73 -0
- package/src/x402/Header.ts +34 -0
- package/src/x402/PublicInterface.test-d.ts +39 -0
- package/src/x402/Types.ts +248 -0
- package/src/x402/client/Exact.test.ts +180 -0
- package/src/x402/client/Exact.ts +198 -0
- package/src/x402/index.ts +5 -0
- package/src/x402/internal/ChallengeBrand.ts +14 -0
- package/src/x402/internal/RouteBinding.ts +18 -0
- package/src/x402/server/EvmCharge.ts +394 -0
- package/src/x402/server/Facilitator.test.ts +111 -0
- package/src/x402/server/Facilitator.ts +56 -0
|
@@ -665,8 +665,37 @@ describe('Fetch.from: 402 retry path', () => {
|
|
|
665
665
|
await fetch('https://example.com/api')
|
|
666
666
|
|
|
667
667
|
const retryInit = calls[1]!.init as Record<string, unknown>
|
|
668
|
-
const headers = retryInit.headers as
|
|
669
|
-
expect(headers.Authorization).toBe('credential')
|
|
668
|
+
const headers = new Headers(retryInit.headers as HeadersInit)
|
|
669
|
+
expect(headers.get('Authorization')).toBe('credential')
|
|
670
|
+
})
|
|
671
|
+
|
|
672
|
+
test('sends credential retry to the final 402 response URL', async () => {
|
|
673
|
+
let callCount = 0
|
|
674
|
+
const calls: { input: RequestInfo | URL; init: RequestInit | undefined }[] = []
|
|
675
|
+
const mockFetch: typeof globalThis.fetch = async (input, init) => {
|
|
676
|
+
calls.push({ input, init })
|
|
677
|
+
callCount++
|
|
678
|
+
if (callCount === 1) {
|
|
679
|
+
const response = make402()
|
|
680
|
+
Object.defineProperty(response, 'url', {
|
|
681
|
+
value: 'https://payments.example.com/protected',
|
|
682
|
+
})
|
|
683
|
+
return response
|
|
684
|
+
}
|
|
685
|
+
return new Response('OK', { status: 200 })
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
const fetch = Fetch.from({
|
|
689
|
+
fetch: mockFetch,
|
|
690
|
+
methods: [noopMethod],
|
|
691
|
+
})
|
|
692
|
+
|
|
693
|
+
const response = await fetch('https://api.example.com/protected')
|
|
694
|
+
|
|
695
|
+
expect(response.status).toBe(200)
|
|
696
|
+
expect(calls[0]!.input).toBe('https://api.example.com/protected')
|
|
697
|
+
expect(calls[1]!.input).toBe('https://payments.example.com/protected')
|
|
698
|
+
expect(new Headers(calls[1]!.init?.headers).get('Authorization')).toBe('credential')
|
|
670
699
|
})
|
|
671
700
|
|
|
672
701
|
test('emits client events and allows challenge handler to provide credential', async () => {
|
|
@@ -4,6 +4,7 @@ import * as AcceptPayment from '../../internal/AcceptPayment.js'
|
|
|
4
4
|
import type { MaybePromise } from '../../internal/types.js'
|
|
5
5
|
import type * as Method from '../../Method.js'
|
|
6
6
|
import type * as z from '../../zod.js'
|
|
7
|
+
import * as Transport from '../Transport.js'
|
|
7
8
|
|
|
8
9
|
// We tag wrappers with a global symbol so we can recognize wrappers created by mppx,
|
|
9
10
|
// even across multiple module instances/bundles. This lets restore() avoid clobbering
|
|
@@ -162,6 +163,7 @@ export function from<const methods extends readonly Method.AnyClient[]>(
|
|
|
162
163
|
methods,
|
|
163
164
|
onChallenge,
|
|
164
165
|
orderChallenges,
|
|
166
|
+
transport = Transport.http(),
|
|
165
167
|
} = config
|
|
166
168
|
const events = config.eventDispatcher ?? createEventDispatcher()
|
|
167
169
|
const resolvedAcceptPayment = acceptPayment ?? AcceptPayment.resolve(methods)
|
|
@@ -183,7 +185,7 @@ export function from<const methods extends readonly Method.AnyClient[]>(
|
|
|
183
185
|
)
|
|
184
186
|
const response = await baseFetch(initialRequest.input, initialRequest.init)
|
|
185
187
|
|
|
186
|
-
if (response
|
|
188
|
+
if (!transport.isPaymentRequired(response)) return response
|
|
187
189
|
|
|
188
190
|
// Only extract context for payment handling after confirming 402.
|
|
189
191
|
const context = (init as Record<string, unknown> | undefined)?.context
|
|
@@ -198,8 +200,9 @@ export function from<const methods extends readonly Method.AnyClient[]>(
|
|
|
198
200
|
let mi: methods[number] | undefined
|
|
199
201
|
|
|
200
202
|
try {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
+
challenges = transport.getChallenges
|
|
204
|
+
? transport.getChallenges(response)
|
|
205
|
+
: [transport.getChallenge(response)]
|
|
203
206
|
|
|
204
207
|
const candidates = AcceptPayment.selectChallengeCandidates(
|
|
205
208
|
challenges,
|
|
@@ -258,10 +261,17 @@ export function from<const methods extends readonly Method.AnyClient[]>(
|
|
|
258
261
|
}),
|
|
259
262
|
)
|
|
260
263
|
|
|
261
|
-
const paymentResponse = await baseFetch(
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
264
|
+
const paymentResponse = await baseFetch(
|
|
265
|
+
resolvePaymentRetryInput(response, initialRequest.input),
|
|
266
|
+
transport.setCredential(
|
|
267
|
+
{
|
|
268
|
+
...fetchInit,
|
|
269
|
+
headers: initialRequest.headers,
|
|
270
|
+
},
|
|
271
|
+
credential,
|
|
272
|
+
{ challenge: selectedChallenge },
|
|
273
|
+
),
|
|
274
|
+
)
|
|
265
275
|
if (paymentResponse.ok)
|
|
266
276
|
await events.emit(
|
|
267
277
|
'payment.response',
|
|
@@ -335,6 +345,8 @@ export declare namespace from {
|
|
|
335
345
|
| undefined
|
|
336
346
|
/** Filters and sorts supported challenges before credential creation. */
|
|
337
347
|
orderChallenges?: AcceptPayment.OrderChallenges<methods> | undefined
|
|
348
|
+
/** Transport to use for challenge extraction and credential attachment. */
|
|
349
|
+
transport?: Transport.AnyTransport | undefined
|
|
338
350
|
}
|
|
339
351
|
|
|
340
352
|
type Fetch<methods extends readonly Method.AnyClient[] = readonly Method.AnyClient[]> = (
|
|
@@ -688,18 +700,6 @@ function freezeSnapshot<value>(value: value): value {
|
|
|
688
700
|
return value
|
|
689
701
|
}
|
|
690
702
|
|
|
691
|
-
/** @internal */
|
|
692
|
-
function withAuthorizationHeader(headers: unknown, credential: string): Record<string, string> {
|
|
693
|
-
const normalized = normalizeHeaders(headers)
|
|
694
|
-
// Remove any existing Authorization header regardless of casing to avoid
|
|
695
|
-
// duplicate/conflicting credentials on retry.
|
|
696
|
-
for (const key of Object.keys(normalized)) {
|
|
697
|
-
if (key.toLowerCase() === 'authorization') delete normalized[key]
|
|
698
|
-
}
|
|
699
|
-
normalized.Authorization = credential
|
|
700
|
-
return normalized
|
|
701
|
-
}
|
|
702
|
-
|
|
703
703
|
/** @internal */
|
|
704
704
|
function prepareInitialRequest<methods extends readonly Method.AnyClient[]>(
|
|
705
705
|
input: RequestInfo | URL,
|
|
@@ -843,3 +843,10 @@ function resolveRequestUrl(input: RequestInfo | URL): URL {
|
|
|
843
843
|
if (input instanceof Request) return new URL(input.url)
|
|
844
844
|
return new URL(input, isBrowser() ? globalThis.location.href : undefined)
|
|
845
845
|
}
|
|
846
|
+
|
|
847
|
+
function resolvePaymentRetryInput(
|
|
848
|
+
response: Response,
|
|
849
|
+
fallback: RequestInfo | URL,
|
|
850
|
+
): RequestInfo | URL {
|
|
851
|
+
return response.url ? response.url : fallback
|
|
852
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../x402/Assets.js'
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { getAddress, parseUnits } from 'viem'
|
|
2
|
+
|
|
3
|
+
import * as Method from '../Method.js'
|
|
4
|
+
import * as z from '../zod.js'
|
|
5
|
+
import * as Types from './Types.js'
|
|
6
|
+
|
|
7
|
+
/** Native Payment-auth EVM charge method. */
|
|
8
|
+
export const charge = Method.from({
|
|
9
|
+
name: Types.paymentMethod,
|
|
10
|
+
intent: Types.chargeIntent,
|
|
11
|
+
schema: {
|
|
12
|
+
credential: {
|
|
13
|
+
payload: Types.ChargePayloadSchema,
|
|
14
|
+
},
|
|
15
|
+
request: z.pipe(
|
|
16
|
+
Types.ChargeRequestInputSchema,
|
|
17
|
+
z.transform(
|
|
18
|
+
({
|
|
19
|
+
amount,
|
|
20
|
+
chainId,
|
|
21
|
+
credentialTypes = ['authorization'],
|
|
22
|
+
currency,
|
|
23
|
+
decimals,
|
|
24
|
+
permit2Address,
|
|
25
|
+
recipient,
|
|
26
|
+
splits,
|
|
27
|
+
...request
|
|
28
|
+
}) => ({
|
|
29
|
+
...request,
|
|
30
|
+
amount: parseUnits(amount, decimals).toString(),
|
|
31
|
+
currency: getAddress(currency),
|
|
32
|
+
methodDetails: {
|
|
33
|
+
chainId,
|
|
34
|
+
credentialTypes,
|
|
35
|
+
decimals,
|
|
36
|
+
...(permit2Address ? { permit2Address: getAddress(permit2Address) } : {}),
|
|
37
|
+
...(splits ? { splits } : {}),
|
|
38
|
+
},
|
|
39
|
+
recipient: getAddress(recipient),
|
|
40
|
+
}),
|
|
41
|
+
),
|
|
42
|
+
),
|
|
43
|
+
},
|
|
44
|
+
})
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import * as evmRoot from 'mppx/evm'
|
|
2
|
+
import {
|
|
3
|
+
assets as clientAssets,
|
|
4
|
+
chains as clientChains,
|
|
5
|
+
charge as clientCharge,
|
|
6
|
+
evm as clientEvm,
|
|
7
|
+
} from 'mppx/evm/client'
|
|
8
|
+
import { assets as serverAssets, charge as serverCharge, evm as serverEvm } from 'mppx/evm/server'
|
|
9
|
+
import type { Account } from 'viem'
|
|
10
|
+
import { describe, expectTypeOf, test } from 'vp/test'
|
|
11
|
+
|
|
12
|
+
import { Mppx as ClientMppx } from '../client/index.js'
|
|
13
|
+
import { Mppx as ServerMppx } from '../server/index.js'
|
|
14
|
+
|
|
15
|
+
const account = {} as Account
|
|
16
|
+
const recipient = '0x209693Bc6afc0C5328bA36FaF03C514EF312287C'
|
|
17
|
+
const secretKey = 'test-secret'
|
|
18
|
+
const settle = async () => ({
|
|
19
|
+
reference: `0x${'1'.repeat(64)}` as `0x${string}`,
|
|
20
|
+
})
|
|
21
|
+
const facilitator = {
|
|
22
|
+
settle: async () => ({
|
|
23
|
+
network: 'eip155:8453',
|
|
24
|
+
success: true,
|
|
25
|
+
transaction: `0x${'1'.repeat(64)}` as `0x${string}`,
|
|
26
|
+
}),
|
|
27
|
+
verify: async () => ({ isValid: true }),
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe('evm public interface', () => {
|
|
31
|
+
test('exports EVM asset metadata from root and subpaths', () => {
|
|
32
|
+
expectTypeOf(evmRoot.assets.base.USDC).toMatchTypeOf<typeof serverAssets.base.USDC>()
|
|
33
|
+
expectTypeOf(clientAssets.baseSepolia.USDC).toMatchTypeOf<
|
|
34
|
+
typeof serverAssets.baseSepolia.USDC
|
|
35
|
+
>()
|
|
36
|
+
expectTypeOf(evmRoot.chains.base).toMatchTypeOf<number>()
|
|
37
|
+
expectTypeOf(clientChains.baseSepolia).toMatchTypeOf<number>()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('server charge works through subpath exports and tuple helper', () => {
|
|
41
|
+
const direct = serverCharge({
|
|
42
|
+
currency: serverAssets.base.USDC,
|
|
43
|
+
recipient,
|
|
44
|
+
x402: { facilitator },
|
|
45
|
+
})
|
|
46
|
+
expectTypeOf(direct.name).toEqualTypeOf<'evm'>()
|
|
47
|
+
expectTypeOf(direct.intent).toEqualTypeOf<'charge'>()
|
|
48
|
+
|
|
49
|
+
const mppx = ServerMppx.create({
|
|
50
|
+
methods: [
|
|
51
|
+
serverEvm({
|
|
52
|
+
currency: serverAssets.base.USDC,
|
|
53
|
+
recipient,
|
|
54
|
+
x402: { facilitator },
|
|
55
|
+
}),
|
|
56
|
+
],
|
|
57
|
+
secretKey,
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
expectTypeOf(mppx.evm.charge).toBeFunction()
|
|
61
|
+
expectTypeOf(mppx.evm.charge({ amount: '0.01' })).toBeFunction()
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('server charge accepts a custom settlement override', () => {
|
|
65
|
+
const direct = serverCharge({
|
|
66
|
+
currency: serverAssets.base.USDC,
|
|
67
|
+
recipient,
|
|
68
|
+
settle,
|
|
69
|
+
})
|
|
70
|
+
expectTypeOf(direct.name).toEqualTypeOf<'evm'>()
|
|
71
|
+
expectTypeOf(direct.intent).toEqualTypeOf<'charge'>()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('client charge works through subpath exports and tuple helper', () => {
|
|
75
|
+
const direct = clientCharge({
|
|
76
|
+
account,
|
|
77
|
+
currencies: [clientAssets.baseSepolia.USDC],
|
|
78
|
+
maxAmount: '0.01',
|
|
79
|
+
networks: [clientChains.baseSepolia],
|
|
80
|
+
})
|
|
81
|
+
expectTypeOf(direct.name).toEqualTypeOf<'evm'>()
|
|
82
|
+
expectTypeOf(direct.intent).toEqualTypeOf<'charge'>()
|
|
83
|
+
|
|
84
|
+
const mppx = ClientMppx.create({
|
|
85
|
+
methods: [
|
|
86
|
+
clientEvm({
|
|
87
|
+
account,
|
|
88
|
+
currencies: [clientAssets.baseSepolia.USDC],
|
|
89
|
+
maxAmount: '0.01',
|
|
90
|
+
networks: [clientEvm.chains.baseSepolia],
|
|
91
|
+
}),
|
|
92
|
+
],
|
|
93
|
+
polyfill: false,
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
expectTypeOf(mppx.createCredential).toBeFunction()
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('server charge rejects x402 exact config shape', () => {
|
|
100
|
+
serverCharge({
|
|
101
|
+
// @ts-expect-error evm.charge takes shared charge config, not x402 exact config.
|
|
102
|
+
config: {
|
|
103
|
+
currency: serverAssets.base.USDC,
|
|
104
|
+
recipient,
|
|
105
|
+
x402: { facilitator },
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
})
|
package/src/evm/Types.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Bytes, Hash } from 'ox'
|
|
2
|
+
|
|
3
|
+
import * as z from '../zod.js'
|
|
4
|
+
|
|
5
|
+
/** Payment method name for Payment-auth EVM challenges. */
|
|
6
|
+
export const paymentMethod = 'evm' as const
|
|
7
|
+
|
|
8
|
+
/** Payment intent name for one-time EVM charges. */
|
|
9
|
+
export const chargeIntent = 'charge' as const
|
|
10
|
+
|
|
11
|
+
/** CAIP-2 namespace prefix for EVM networks. */
|
|
12
|
+
export const evmNetworkPrefix = 'eip155:' as const
|
|
13
|
+
|
|
14
|
+
/** EIP-3009 transfer authorization method identifier. */
|
|
15
|
+
export const eip3009 = 'eip3009' as const
|
|
16
|
+
|
|
17
|
+
/** EVM charge credential types supported by this package. */
|
|
18
|
+
export const credentialTypes = ['authorization'] as const
|
|
19
|
+
|
|
20
|
+
const atomicAmount = z.string().check(z.regex(/^\d+$/, 'Invalid atomic amount'))
|
|
21
|
+
|
|
22
|
+
/** EIP-3009 domain metadata for `transferWithAuthorization` signatures. */
|
|
23
|
+
export const AuthorizationConfigSchema = z.object({
|
|
24
|
+
name: z.string().check(z.minLength(1)),
|
|
25
|
+
version: z.string().check(z.minLength(1)),
|
|
26
|
+
})
|
|
27
|
+
export type AuthorizationConfig = z.infer<typeof AuthorizationConfigSchema>
|
|
28
|
+
|
|
29
|
+
/** CAIP-2 EVM network identifier. */
|
|
30
|
+
export type EvmNetwork = `${typeof evmNetworkPrefix}${number}`
|
|
31
|
+
|
|
32
|
+
/** EVM-specific charge method details. */
|
|
33
|
+
export const MethodDetailsSchema = z.object({
|
|
34
|
+
chainId: z.number(),
|
|
35
|
+
credentialTypes: z.optional(z.array(z.enum(credentialTypes)).check(z.minLength(1))),
|
|
36
|
+
decimals: z.optional(z.number()),
|
|
37
|
+
permit2Address: z.optional(z.address()),
|
|
38
|
+
splits: z.optional(
|
|
39
|
+
z.array(
|
|
40
|
+
z.object({
|
|
41
|
+
amount: atomicAmount,
|
|
42
|
+
recipient: z.address(),
|
|
43
|
+
}),
|
|
44
|
+
),
|
|
45
|
+
),
|
|
46
|
+
})
|
|
47
|
+
export type MethodDetails = z.infer<typeof MethodDetailsSchema>
|
|
48
|
+
|
|
49
|
+
/** Canonical Payment-auth EVM charge request. */
|
|
50
|
+
export const ChargeRequestSchema = z.object({
|
|
51
|
+
amount: atomicAmount,
|
|
52
|
+
currency: z.address(),
|
|
53
|
+
description: z.optional(z.string()),
|
|
54
|
+
externalId: z.optional(z.string()),
|
|
55
|
+
methodDetails: MethodDetailsSchema,
|
|
56
|
+
recipient: z.address(),
|
|
57
|
+
})
|
|
58
|
+
export type ChargeRequest = z.infer<typeof ChargeRequestSchema>
|
|
59
|
+
|
|
60
|
+
/** Public route input before display-unit amounts are converted to atomic units. */
|
|
61
|
+
export const ChargeRequestInputSchema = z.object({
|
|
62
|
+
amount: z.amount(),
|
|
63
|
+
chainId: z.number(),
|
|
64
|
+
currency: z.address(),
|
|
65
|
+
credentialTypes: z.optional(z.array(z.enum(credentialTypes)).check(z.minLength(1))),
|
|
66
|
+
decimals: z.number(),
|
|
67
|
+
description: z.optional(z.string()),
|
|
68
|
+
externalId: z.optional(z.string()),
|
|
69
|
+
permit2Address: z.optional(z.address()),
|
|
70
|
+
recipient: z.address(),
|
|
71
|
+
splits: z.optional(
|
|
72
|
+
z.array(
|
|
73
|
+
z.object({
|
|
74
|
+
amount: atomicAmount,
|
|
75
|
+
recipient: z.address(),
|
|
76
|
+
}),
|
|
77
|
+
),
|
|
78
|
+
),
|
|
79
|
+
})
|
|
80
|
+
export type ChargeRequestInput = z.infer<typeof ChargeRequestInputSchema>
|
|
81
|
+
|
|
82
|
+
/** Payment-auth EVM authorization credential payload. */
|
|
83
|
+
export const AuthorizationPayloadSchema = z.object({
|
|
84
|
+
from: z.address(),
|
|
85
|
+
nonce: z.hash(),
|
|
86
|
+
signature: z.signature(),
|
|
87
|
+
to: z.address(),
|
|
88
|
+
type: z.literal('authorization'),
|
|
89
|
+
validAfter: atomicAmount,
|
|
90
|
+
validBefore: atomicAmount,
|
|
91
|
+
value: atomicAmount,
|
|
92
|
+
})
|
|
93
|
+
export type AuthorizationPayload = z.infer<typeof AuthorizationPayloadSchema>
|
|
94
|
+
|
|
95
|
+
/** Payment-auth EVM charge credential payload. */
|
|
96
|
+
export const ChargePayloadSchema = AuthorizationPayloadSchema
|
|
97
|
+
export type ChargePayload = z.infer<typeof ChargePayloadSchema>
|
|
98
|
+
|
|
99
|
+
/** EIP-712 type definition for EIP-3009 `transferWithAuthorization`. */
|
|
100
|
+
export const authorizationTypes = {
|
|
101
|
+
TransferWithAuthorization: [
|
|
102
|
+
{ name: 'from', type: 'address' },
|
|
103
|
+
{ name: 'to', type: 'address' },
|
|
104
|
+
{ name: 'value', type: 'uint256' },
|
|
105
|
+
{ name: 'validAfter', type: 'uint256' },
|
|
106
|
+
{ name: 'validBefore', type: 'uint256' },
|
|
107
|
+
{ name: 'nonce', type: 'bytes32' },
|
|
108
|
+
],
|
|
109
|
+
} as const
|
|
110
|
+
|
|
111
|
+
/** Returns the EIP-712 domain for an EIP-3009 authorization signature. */
|
|
112
|
+
export function authorizationDomain(parameters: {
|
|
113
|
+
authorization: AuthorizationConfig
|
|
114
|
+
chainId: number
|
|
115
|
+
currency: `0x${string}`
|
|
116
|
+
}) {
|
|
117
|
+
return {
|
|
118
|
+
chainId: parameters.chainId,
|
|
119
|
+
name: parameters.authorization.name,
|
|
120
|
+
verifyingContract: parameters.currency,
|
|
121
|
+
version: parameters.authorization.version,
|
|
122
|
+
} as const
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Computes the Payment-auth EVM challenge hash used as the EIP-3009 nonce. */
|
|
126
|
+
export function challengeHash(challenge: { id: string; realm: string }): `0x${string}` {
|
|
127
|
+
return Hash.keccak256(Bytes.fromString(`${challenge.id}${challenge.realm}`), {
|
|
128
|
+
as: 'Hex',
|
|
129
|
+
}) as `0x${string}`
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Converts a chain ID to the CAIP-2 EVM network identifier. */
|
|
133
|
+
export function networkOf(chainId: number): EvmNetwork {
|
|
134
|
+
return `${evmNetworkPrefix}${chainId}`
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Formats an EVM address as a did:pkh source identifier. */
|
|
138
|
+
export function toSource(parameters: { address: `0x${string}`; chainId: number }) {
|
|
139
|
+
return `did:pkh:eip155:${parameters.chainId}:${parameters.address}` as const
|
|
140
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Challenge } from 'mppx'
|
|
2
|
+
import type { Account } from 'viem'
|
|
3
|
+
import { describe, expect, test, vi } from 'vp/test'
|
|
4
|
+
|
|
5
|
+
import * as Assets from '../Assets.js'
|
|
6
|
+
import * as Chains from '../Chains.js'
|
|
7
|
+
import { charge } from './Charge.js'
|
|
8
|
+
|
|
9
|
+
const account = {
|
|
10
|
+
address: '0x1111111111111111111111111111111111111111',
|
|
11
|
+
signTypedData: vi.fn(async () => '0x1234'),
|
|
12
|
+
} as unknown as Account
|
|
13
|
+
|
|
14
|
+
describe('evm charge client', () => {
|
|
15
|
+
test('does not sign x402 payloads from unbranded Payment-auth challenges', async () => {
|
|
16
|
+
const signTypedData = vi.fn(async () => '0x1234')
|
|
17
|
+
const client = charge({
|
|
18
|
+
account: {
|
|
19
|
+
...account,
|
|
20
|
+
signTypedData,
|
|
21
|
+
} as unknown as Account,
|
|
22
|
+
currencies: [Assets.baseSepolia.USDC],
|
|
23
|
+
maxAmount: '0.01',
|
|
24
|
+
networks: [Chains.baseSepolia],
|
|
25
|
+
})
|
|
26
|
+
const challenge = Challenge.from({
|
|
27
|
+
id: 'attacker-controlled',
|
|
28
|
+
intent: 'charge',
|
|
29
|
+
method: 'evm',
|
|
30
|
+
realm: 'api.example.com',
|
|
31
|
+
request: {
|
|
32
|
+
amount: '10000',
|
|
33
|
+
asset: Assets.baseSepolia.USDC.address,
|
|
34
|
+
maxTimeoutSeconds: 60,
|
|
35
|
+
network: 'eip155:84532',
|
|
36
|
+
payTo: '0x2222222222222222222222222222222222222222',
|
|
37
|
+
scheme: 'exact',
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
await expect(client.createCredential({ challenge } as never)).rejects.toThrow()
|
|
42
|
+
expect(signTypedData).not.toHaveBeenCalled()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('does not use server-supplied decimals for maxAmount policy', async () => {
|
|
46
|
+
const client = charge({
|
|
47
|
+
account,
|
|
48
|
+
maxAmount: '1',
|
|
49
|
+
})
|
|
50
|
+
const challenge = Challenge.from({
|
|
51
|
+
id: 'native',
|
|
52
|
+
intent: 'charge',
|
|
53
|
+
method: 'evm',
|
|
54
|
+
realm: 'api.example.com',
|
|
55
|
+
request: {
|
|
56
|
+
amount: '1000000000000000000',
|
|
57
|
+
currency: Assets.baseSepolia.USDC.address,
|
|
58
|
+
methodDetails: {
|
|
59
|
+
chainId: 84532,
|
|
60
|
+
credentialTypes: ['authorization'],
|
|
61
|
+
decimals: 18,
|
|
62
|
+
},
|
|
63
|
+
recipient: '0x2222222222222222222222222222222222222222',
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
await expect(client.createCredential({ challenge } as never)).rejects.toThrow(
|
|
68
|
+
'EVM charge maxAmount requires currency decimals.',
|
|
69
|
+
)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('requires network policy for raw hex currencies', async () => {
|
|
73
|
+
const client = charge({
|
|
74
|
+
account,
|
|
75
|
+
currencies: [Assets.baseSepolia.USDC.address],
|
|
76
|
+
maxAmount: '1',
|
|
77
|
+
})
|
|
78
|
+
const challenge = Challenge.from({
|
|
79
|
+
id: 'native',
|
|
80
|
+
intent: 'charge',
|
|
81
|
+
method: 'evm',
|
|
82
|
+
realm: 'api.example.com',
|
|
83
|
+
request: {
|
|
84
|
+
amount: '1000000',
|
|
85
|
+
currency: Assets.baseSepolia.USDC.address,
|
|
86
|
+
methodDetails: {
|
|
87
|
+
chainId: 84532,
|
|
88
|
+
credentialTypes: ['authorization'],
|
|
89
|
+
decimals: 6,
|
|
90
|
+
},
|
|
91
|
+
recipient: '0x2222222222222222222222222222222222222222',
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
await expect(client.createCredential({ challenge } as never)).rejects.toThrow(
|
|
96
|
+
'EVM raw currency allowlists require networks.',
|
|
97
|
+
)
|
|
98
|
+
})
|
|
99
|
+
})
|