mppx 0.6.27 → 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 +25 -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/Store.d.ts +32 -9
- package/dist/Store.d.ts.map +1 -1
- package/dist/Store.js +42 -10
- package/dist/Store.js.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 +13 -1
- package/dist/proxy/internal/Headers.d.ts.map +1 -1
- package/dist/proxy/internal/Headers.js +25 -2
- 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/Charge.d.ts +31 -1
- package/dist/stripe/server/Charge.d.ts.map +1 -1
- package/dist/stripe/server/Charge.js +88 -11
- package/dist/stripe/server/Charge.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 +6 -0
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +30 -3
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Session.d.ts +6 -0
- package/dist/tempo/server/Session.d.ts.map +1 -1
- package/dist/tempo/server/Session.js +14 -13
- package/dist/tempo/server/Session.js.map +1 -1
- package/dist/tempo/server/Subscription.d.ts +6 -0
- package/dist/tempo/server/Subscription.d.ts.map +1 -1
- package/dist/tempo/server/Subscription.js +1 -1
- 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/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/Store.test-d.ts +58 -0
- package/src/Store.test.ts +77 -0
- package/src/Store.ts +155 -74
- 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 +38 -0
- package/src/proxy/internal/Headers.ts +26 -2
- 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/Charge.test.ts +215 -1
- package/src/stripe/server/Charge.ts +150 -20
- 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 +91 -1
- package/src/tempo/server/Charge.ts +48 -2
- package/src/tempo/server/Session.test.ts +30 -0
- package/src/tempo/server/Session.ts +24 -17
- package/src/tempo/server/Subscription.ts +7 -1
- 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/ChannelStore.test.ts +21 -0
- 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/tempo/subscription/Store.test.ts +55 -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
package/src/client/Mppx.ts
CHANGED
|
@@ -7,7 +7,7 @@ import * as Fetch from './internal/Fetch.js'
|
|
|
7
7
|
import * as Transport from './Transport.js'
|
|
8
8
|
|
|
9
9
|
export type Methods = readonly (Method.AnyClient | readonly Method.AnyClient[])[]
|
|
10
|
-
type EventResponseOf<transport extends Transport.
|
|
10
|
+
type EventResponseOf<transport extends Transport.AnyTransport> =
|
|
11
11
|
| Response
|
|
12
12
|
| Transport.ResponseOf<transport>
|
|
13
13
|
|
|
@@ -16,7 +16,7 @@ type EventResponseOf<transport extends Transport.Transport> =
|
|
|
16
16
|
*/
|
|
17
17
|
export type Mppx<
|
|
18
18
|
methods extends Methods = Methods,
|
|
19
|
-
transport extends Transport.
|
|
19
|
+
transport extends Transport.AnyTransport = Transport.Transport,
|
|
20
20
|
> = {
|
|
21
21
|
/** Payment-aware fetch function that automatically handles 402 responses. */
|
|
22
22
|
fetch: Fetch.from.Fetch<FlattenMethods<methods>>
|
|
@@ -125,6 +125,7 @@ export function create<
|
|
|
125
125
|
...(resolvedOnChallenge && { onChallenge: resolvedOnChallenge }),
|
|
126
126
|
...(orderChallenges && { orderChallenges }),
|
|
127
127
|
methods,
|
|
128
|
+
transport,
|
|
128
129
|
} satisfies Fetch.from.Config<FlattenMethods<methods>>
|
|
129
130
|
const fetch = Fetch.from<FlattenMethods<methods>>(config_fetch)
|
|
130
131
|
|
|
@@ -282,7 +283,7 @@ export function restore(): void {
|
|
|
282
283
|
export declare namespace create {
|
|
283
284
|
type Config<
|
|
284
285
|
methods extends Methods = Methods,
|
|
285
|
-
transport extends Transport.
|
|
286
|
+
transport extends Transport.AnyTransport = Transport.Transport,
|
|
286
287
|
> = {
|
|
287
288
|
/** Controls when `Accept-Payment` is injected. */
|
|
288
289
|
acceptPaymentPolicy?: Fetch.from.Config['acceptPaymentPolicy'] | undefined
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Challenge, Credential, Mcp } from 'mppx'
|
|
2
2
|
import { Transport } from 'mppx/client'
|
|
3
3
|
import { Methods } from 'mppx/tempo'
|
|
4
|
+
import { Header as x402_Header, Types as x402_Types, type PaymentRequired } from 'mppx/x402'
|
|
4
5
|
import { describe, expect, test } from 'vp/test'
|
|
5
6
|
|
|
6
7
|
const realm = 'api.example.com'
|
|
@@ -23,27 +24,34 @@ const credential = Credential.from({
|
|
|
23
24
|
payload: { signature: '0xabc123', type: 'transaction' },
|
|
24
25
|
})
|
|
25
26
|
|
|
27
|
+
const x402PaymentRequired = {
|
|
28
|
+
accepts: [
|
|
29
|
+
{
|
|
30
|
+
amount: '10000',
|
|
31
|
+
asset: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
|
|
32
|
+
maxTimeoutSeconds: 60,
|
|
33
|
+
network: 'eip155:84532',
|
|
34
|
+
payTo: '0x209693Bc6afc0C5328bA36FaF03C514EF312287C',
|
|
35
|
+
scheme: x402_Types.schemes[0],
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
resource: {
|
|
39
|
+
url: 'https://api.example.com/x402',
|
|
40
|
+
},
|
|
41
|
+
x402Version: 2,
|
|
42
|
+
} satisfies PaymentRequired
|
|
43
|
+
|
|
26
44
|
describe('http', () => {
|
|
27
45
|
describe('isPaymentRequired', () => {
|
|
28
|
-
test(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
46
|
+
test.each([
|
|
47
|
+
{ expected: true, status: 402 },
|
|
48
|
+
{ expected: false, status: 200 },
|
|
49
|
+
{ expected: false, status: 401 },
|
|
50
|
+
])('returns $expected for $status response', ({ expected, status }) => {
|
|
51
|
+
const response = new Response(null, { status })
|
|
34
52
|
|
|
35
|
-
test('returns false for 200 response', () => {
|
|
36
53
|
const transport = Transport.http()
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
expect(transport.isPaymentRequired(response)).toBe(false)
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
test('returns false for other error responses', () => {
|
|
43
|
-
const transport = Transport.http()
|
|
44
|
-
const response = new Response(null, { status: 401 })
|
|
45
|
-
|
|
46
|
-
expect(transport.isPaymentRequired(response)).toBe(false)
|
|
54
|
+
expect(transport.isPaymentRequired(response)).toBe(expected)
|
|
47
55
|
})
|
|
48
56
|
})
|
|
49
57
|
|
|
@@ -80,32 +88,159 @@ describe('http', () => {
|
|
|
80
88
|
})
|
|
81
89
|
|
|
82
90
|
describe('getChallenges', () => {
|
|
83
|
-
test(
|
|
91
|
+
test.each([
|
|
92
|
+
{
|
|
93
|
+
expectedIds: [challenge.id, 'alternate'],
|
|
94
|
+
expectedMethods: ['tempo', 'stripe'],
|
|
95
|
+
headers: () => ({
|
|
96
|
+
'WWW-Authenticate': `${Challenge.serialize(challenge)}, ${Challenge.serialize({
|
|
97
|
+
...challenge,
|
|
98
|
+
id: 'alternate',
|
|
99
|
+
method: 'stripe' as const,
|
|
100
|
+
})}`,
|
|
101
|
+
}),
|
|
102
|
+
name: 'Payment auth challenges',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
expectedIds: [`${x402_Types.syntheticChallengeIdPrefix}0`],
|
|
106
|
+
expectedMethods: [x402_Types.paymentMethod],
|
|
107
|
+
headers: () => ({
|
|
108
|
+
'PAYMENT-REQUIRED': x402_Header.encodePaymentRequired(x402PaymentRequired),
|
|
109
|
+
}),
|
|
110
|
+
name: 'x402 challenges',
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
expectedIds: [
|
|
114
|
+
`${x402_Types.syntheticChallengeIdPrefix}0`,
|
|
115
|
+
`${x402_Types.syntheticChallengeIdPrefix}1`,
|
|
116
|
+
],
|
|
117
|
+
expectedMethods: [x402_Types.paymentMethod, x402_Types.paymentMethod],
|
|
118
|
+
headers: () => ({
|
|
119
|
+
'PAYMENT-REQUIRED': x402_Header.encodePaymentRequired({
|
|
120
|
+
...x402PaymentRequired,
|
|
121
|
+
accepts: [
|
|
122
|
+
x402PaymentRequired.accepts[0]!,
|
|
123
|
+
{
|
|
124
|
+
...x402PaymentRequired.accepts[0]!,
|
|
125
|
+
amount: '20000',
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
}),
|
|
129
|
+
}),
|
|
130
|
+
name: 'multiple x402 accepts',
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
expectedIds: [challenge.id, `${x402_Types.syntheticChallengeIdPrefix}0`],
|
|
134
|
+
expectedMethods: ['tempo', x402_Types.paymentMethod],
|
|
135
|
+
headers: () => ({
|
|
136
|
+
'PAYMENT-REQUIRED': x402_Header.encodePaymentRequired(x402PaymentRequired),
|
|
137
|
+
'WWW-Authenticate': Challenge.serialize(challenge),
|
|
138
|
+
}),
|
|
139
|
+
name: 'Payment auth and x402 challenges when both are present',
|
|
140
|
+
},
|
|
141
|
+
])('returns $name', ({ expectedIds, expectedMethods, headers }) => {
|
|
84
142
|
const transport = Transport.http()
|
|
85
|
-
const alternate = { ...challenge, id: 'alternate', method: 'stripe' as const }
|
|
86
143
|
const response = new Response(null, {
|
|
87
144
|
status: 402,
|
|
88
|
-
headers:
|
|
89
|
-
'WWW-Authenticate': `${Challenge.serialize(challenge)}, ${Challenge.serialize(alternate)}`,
|
|
90
|
-
},
|
|
145
|
+
headers: headers(),
|
|
91
146
|
})
|
|
147
|
+
const challenges = transport.getChallenges?.(response) ?? []
|
|
92
148
|
|
|
93
|
-
expect(
|
|
94
|
-
|
|
95
|
-
'alternate',
|
|
96
|
-
])
|
|
149
|
+
expect(challenges.map((entry) => entry.id)).toEqual(expectedIds)
|
|
150
|
+
expect(challenges.map((entry) => entry.method)).toEqual(expectedMethods)
|
|
97
151
|
})
|
|
98
152
|
})
|
|
99
153
|
|
|
100
154
|
describe('setCredential', () => {
|
|
101
|
-
test(
|
|
155
|
+
test.each([
|
|
156
|
+
{
|
|
157
|
+
challenge,
|
|
158
|
+
credential: Credential.serialize(credential),
|
|
159
|
+
expectedHeader: 'Authorization',
|
|
160
|
+
expectedValue: Credential.serialize(credential),
|
|
161
|
+
name: 'Payment auth credential for Payment auth challenge',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
challenge: Transport.http().getChallenges!(
|
|
165
|
+
new Response(null, {
|
|
166
|
+
status: 402,
|
|
167
|
+
headers: {
|
|
168
|
+
'PAYMENT-REQUIRED': x402_Header.encodePaymentRequired(x402PaymentRequired),
|
|
169
|
+
},
|
|
170
|
+
}),
|
|
171
|
+
)[0],
|
|
172
|
+
credential: 'x402-signature',
|
|
173
|
+
expectedHeader: 'PAYMENT-SIGNATURE',
|
|
174
|
+
expectedValue: 'x402-signature',
|
|
175
|
+
name: 'raw x402 credential for x402 challenge',
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
challenge,
|
|
179
|
+
credential: 'custom-credential',
|
|
180
|
+
expectedHeader: 'Authorization',
|
|
181
|
+
expectedValue: 'custom-credential',
|
|
182
|
+
name: 'non-Payment credential for non-x402 challenge',
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
challenge: undefined,
|
|
186
|
+
credential: 'custom-credential',
|
|
187
|
+
expectedHeader: 'Authorization',
|
|
188
|
+
expectedValue: 'custom-credential',
|
|
189
|
+
name: 'credential without selected challenge',
|
|
190
|
+
},
|
|
191
|
+
])('writes $name', ({ challenge, credential, expectedHeader, expectedValue }) => {
|
|
102
192
|
const transport = Transport.http()
|
|
103
|
-
const serialized = Credential.serialize(credential)
|
|
104
193
|
|
|
105
|
-
const result = transport.setCredential({},
|
|
194
|
+
const result = transport.setCredential({}, credential, { challenge })
|
|
195
|
+
const headers = result.headers as Headers
|
|
196
|
+
|
|
197
|
+
expect(headers.get(expectedHeader)).toBe(expectedValue)
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
test('does not treat unbranded Payment-auth challenges as x402', () => {
|
|
201
|
+
const transport = Transport.http()
|
|
202
|
+
const untrustedChallenge = Challenge.from({
|
|
203
|
+
id: `${x402_Types.syntheticChallengeIdPrefix}0`,
|
|
204
|
+
intent: x402_Types.exactIntent,
|
|
205
|
+
method: x402_Types.paymentMethod,
|
|
206
|
+
realm: 'api.example.com',
|
|
207
|
+
request: x402PaymentRequired.accepts[0]!,
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
const result = transport.setCredential({}, 'credential', {
|
|
211
|
+
challenge: untrustedChallenge,
|
|
212
|
+
})
|
|
213
|
+
const headers = result.headers as Headers
|
|
214
|
+
|
|
215
|
+
expect(headers.get('Authorization')).toBe('credential')
|
|
216
|
+
expect(headers.get(x402_Types.paymentSignatureHeader)).toBeNull()
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
test('removes stale credential headers before setting the retry credential', () => {
|
|
220
|
+
const transport = Transport.http()
|
|
221
|
+
const x402Challenge = Transport.http().getChallenges!(
|
|
222
|
+
new Response(null, {
|
|
223
|
+
status: 402,
|
|
224
|
+
headers: {
|
|
225
|
+
'PAYMENT-REQUIRED': x402_Header.encodePaymentRequired(x402PaymentRequired),
|
|
226
|
+
},
|
|
227
|
+
}),
|
|
228
|
+
)[0]
|
|
229
|
+
|
|
230
|
+
const result = transport.setCredential(
|
|
231
|
+
{
|
|
232
|
+
headers: {
|
|
233
|
+
Authorization: 'Payment stale',
|
|
234
|
+
[x402_Types.paymentSignatureHeader]: 'stale-x402',
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
'fresh-x402',
|
|
238
|
+
{ challenge: x402Challenge },
|
|
239
|
+
)
|
|
106
240
|
const headers = result.headers as Headers
|
|
107
241
|
|
|
108
|
-
expect(headers.get('Authorization')).
|
|
242
|
+
expect(headers.get('Authorization')).toBeNull()
|
|
243
|
+
expect(headers.get(x402_Types.paymentSignatureHeader)).toBe('fresh-x402')
|
|
109
244
|
})
|
|
110
245
|
|
|
111
246
|
test('preserves existing headers', () => {
|
package/src/client/Transport.ts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import * as Challenge from '../Challenge.js'
|
|
2
2
|
import * as Credential from '../Credential.js'
|
|
3
3
|
import * as Mcp from '../Mcp.js'
|
|
4
|
+
import * as x402_Header from '../x402/Header.js'
|
|
5
|
+
import * as x402_ChallengeBrand from '../x402/internal/ChallengeBrand.js'
|
|
6
|
+
import * as x402_Types from '../x402/Types.js'
|
|
7
|
+
|
|
8
|
+
const paymentRequiredStatus = 402
|
|
9
|
+
const paymentAuthChallengeHeader = 'WWW-Authenticate'
|
|
10
|
+
const paymentAuthCredentialHeader = 'Authorization'
|
|
11
|
+
const credentialHeaders = [
|
|
12
|
+
paymentAuthCredentialHeader,
|
|
13
|
+
x402_Types.paymentRequiredHeader,
|
|
14
|
+
x402_Types.paymentResponseHeader,
|
|
15
|
+
x402_Types.paymentSignatureHeader,
|
|
16
|
+
]
|
|
4
17
|
|
|
5
18
|
/**
|
|
6
19
|
* Client-side transport adapter.
|
|
@@ -18,10 +31,21 @@ export type Transport<in out request = unknown, in out response = unknown> = {
|
|
|
18
31
|
/** Extracts the challenge from a payment-required response. */
|
|
19
32
|
getChallenge: (response: response) => Challenge.Challenge
|
|
20
33
|
/** Attaches a credential to a request. */
|
|
21
|
-
setCredential: (
|
|
34
|
+
setCredential: (
|
|
35
|
+
request: request,
|
|
36
|
+
credential: string,
|
|
37
|
+
options?: setCredential.Options | undefined,
|
|
38
|
+
) => request
|
|
22
39
|
}
|
|
23
40
|
export type AnyTransport = Transport<any, any>
|
|
24
41
|
|
|
42
|
+
export declare namespace setCredential {
|
|
43
|
+
type Options = {
|
|
44
|
+
/** Challenge selected for credential creation. */
|
|
45
|
+
challenge?: Challenge.Challenge | undefined
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
25
49
|
/** Extracts the response type from a transport. */
|
|
26
50
|
export type ResponseOf<transport extends Transport> =
|
|
27
51
|
transport extends Transport<any, infer response> ? response : never
|
|
@@ -55,33 +79,77 @@ export function from<request, response>(
|
|
|
55
79
|
* HTTP transport for client-side payment handling.
|
|
56
80
|
*
|
|
57
81
|
* - Detects payment required via 402 status
|
|
58
|
-
* - Extracts challenges from `WWW-Authenticate`
|
|
59
|
-
* -
|
|
82
|
+
* - Extracts Payment auth challenges from `WWW-Authenticate`
|
|
83
|
+
* - Falls back to x402 exact challenges from `PAYMENT-REQUIRED`
|
|
84
|
+
* - Sends credentials via `Authorization` or `PAYMENT-SIGNATURE`
|
|
60
85
|
*/
|
|
61
86
|
export function http() {
|
|
62
87
|
return from<RequestInit, Response>({
|
|
63
88
|
name: 'http',
|
|
64
89
|
|
|
65
90
|
isPaymentRequired(response) {
|
|
66
|
-
return response.status ===
|
|
91
|
+
return response.status === paymentRequiredStatus
|
|
67
92
|
},
|
|
68
93
|
|
|
69
94
|
getChallenges(response) {
|
|
70
|
-
return
|
|
95
|
+
return paymentRequiredChallenges(response)
|
|
71
96
|
},
|
|
72
97
|
|
|
73
98
|
getChallenge(response) {
|
|
74
|
-
|
|
99
|
+
const challenge = paymentRequiredChallenges(response)[0]
|
|
100
|
+
if (!challenge) throw new Error('No challenge in response.')
|
|
101
|
+
return challenge
|
|
75
102
|
},
|
|
76
103
|
|
|
77
|
-
setCredential(request, credential) {
|
|
104
|
+
setCredential(request, credential, options) {
|
|
78
105
|
const headers = new Headers(request.headers)
|
|
79
|
-
headers.
|
|
106
|
+
for (const header of credentialHeaders) headers.delete(header)
|
|
107
|
+
if (isX402Challenge(options?.challenge)) {
|
|
108
|
+
headers.set(x402_Types.paymentSignatureHeader, credential)
|
|
109
|
+
} else {
|
|
110
|
+
headers.set(paymentAuthCredentialHeader, credential)
|
|
111
|
+
}
|
|
80
112
|
return { ...request, headers }
|
|
81
113
|
},
|
|
82
114
|
})
|
|
83
115
|
}
|
|
84
116
|
|
|
117
|
+
function paymentRequiredChallenges(response: Response): Challenge.Challenge[] {
|
|
118
|
+
return [
|
|
119
|
+
...(response.headers.has(paymentAuthChallengeHeader)
|
|
120
|
+
? Challenge.fromResponseList(response)
|
|
121
|
+
: []),
|
|
122
|
+
...x402Challenges(response),
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function x402Challenges(response: Response): Challenge.Challenge[] {
|
|
127
|
+
const header = response.headers.get(x402_Types.paymentRequiredHeader)
|
|
128
|
+
if (!header) return []
|
|
129
|
+
const paymentRequired = x402_Header.decodePaymentRequired(header)
|
|
130
|
+
if (response.url && paymentRequired.resource.url !== response.url)
|
|
131
|
+
throw new Error('x402 payment-required resource does not match response URL.')
|
|
132
|
+
return paymentRequired.accepts.map((accepted, index) =>
|
|
133
|
+
x402_ChallengeBrand.mark(
|
|
134
|
+
Challenge.from({
|
|
135
|
+
id: `${x402_Types.syntheticChallengeIdPrefix}${index}`,
|
|
136
|
+
intent: x402_Types.exactIntent,
|
|
137
|
+
method: x402_Types.paymentMethod,
|
|
138
|
+
realm: new URL(paymentRequired.resource.url).host,
|
|
139
|
+
request: {
|
|
140
|
+
...accepted,
|
|
141
|
+
...(paymentRequired.extensions ? { extensions: paymentRequired.extensions } : {}),
|
|
142
|
+
resource: paymentRequired.resource,
|
|
143
|
+
},
|
|
144
|
+
}),
|
|
145
|
+
),
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function isX402Challenge(challenge: Challenge.Challenge | undefined): boolean {
|
|
150
|
+
return x402_ChallengeBrand.is(challenge)
|
|
151
|
+
}
|
|
152
|
+
|
|
85
153
|
/**
|
|
86
154
|
* MCP transport for client-side payment handling.
|
|
87
155
|
*
|
package/src/client/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * as Expires from '../Expires.js'
|
|
2
2
|
export * as Fetch from './internal/Fetch.js'
|
|
3
|
-
export { session, stripe, tempo } from './Methods.js'
|
|
3
|
+
export { evm, session, stripe, tempo } from './Methods.js'
|
|
4
4
|
export * as Mppx from './Mppx.js'
|
|
5
5
|
export * as Transport from './Transport.js'
|
|
@@ -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
|
+
})
|