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
|
@@ -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
|
+
})
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import type { Account } from 'viem'
|
|
2
|
+
import { getAddress, parseUnits } from 'viem'
|
|
3
|
+
|
|
4
|
+
import * as Credential from '../../Credential.js'
|
|
5
|
+
import * as Method from '../../Method.js'
|
|
6
|
+
import * as x402_Exact from '../../x402/client/Exact.js'
|
|
7
|
+
import * as x402_ChallengeBrand from '../../x402/internal/ChallengeBrand.js'
|
|
8
|
+
import * as z from '../../zod.js'
|
|
9
|
+
import * as Assets from '../Assets.js'
|
|
10
|
+
import * as Methods from '../Methods.js'
|
|
11
|
+
import * as Types from '../Types.js'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Creates an EVM charge client method.
|
|
15
|
+
*
|
|
16
|
+
* Signs native Payment-auth `authorization` credentials for EVM charges. It
|
|
17
|
+
* also keeps x402 exact signing for x402 compatibility challenges.
|
|
18
|
+
*/
|
|
19
|
+
export function charge(parameters: charge.Parameters) {
|
|
20
|
+
return Method.toClient(Methods.charge, {
|
|
21
|
+
context: z.object({
|
|
22
|
+
account: z.optional(z.custom<Account>()),
|
|
23
|
+
}),
|
|
24
|
+
async createCredential({ challenge, context }) {
|
|
25
|
+
if (isX402Challenge(challenge))
|
|
26
|
+
return x402_Exact.createCredential({
|
|
27
|
+
challenge: challenge as never,
|
|
28
|
+
config: parameters as x402_Exact.Config,
|
|
29
|
+
context,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const account = (context?.account ?? parameters.account) as charge.Signer
|
|
33
|
+
if (!account.signTypedData) throw new Error('EVM authorization requires a typed-data signer.')
|
|
34
|
+
|
|
35
|
+
const request = challenge.request as Types.ChargeRequest
|
|
36
|
+
assertPolicy(parameters, request)
|
|
37
|
+
if (!request.methodDetails.credentialTypes?.includes('authorization')) {
|
|
38
|
+
throw new Error('EVM authorization is not accepted by this challenge.')
|
|
39
|
+
}
|
|
40
|
+
if (request.methodDetails.splits?.length) {
|
|
41
|
+
throw new Error('EVM authorization does not support payment splits.')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const authorization = resolveAuthorization(parameters, request)
|
|
45
|
+
const validBefore = challenge.expires
|
|
46
|
+
? Math.floor(new Date(challenge.expires).getTime() / 1000).toString()
|
|
47
|
+
: (Math.floor(Date.now() / 1000) + 300).toString()
|
|
48
|
+
|
|
49
|
+
const payload: Types.AuthorizationPayload = {
|
|
50
|
+
from: getAddress(account.address),
|
|
51
|
+
nonce: Types.challengeHash(challenge),
|
|
52
|
+
signature: await account.signTypedData({
|
|
53
|
+
domain: Types.authorizationDomain({
|
|
54
|
+
authorization,
|
|
55
|
+
chainId: request.methodDetails.chainId,
|
|
56
|
+
currency: request.currency as `0x${string}`,
|
|
57
|
+
}),
|
|
58
|
+
message: {
|
|
59
|
+
from: getAddress(account.address),
|
|
60
|
+
nonce: Types.challengeHash(challenge),
|
|
61
|
+
to: getAddress(request.recipient),
|
|
62
|
+
validAfter: 0n,
|
|
63
|
+
validBefore: BigInt(validBefore),
|
|
64
|
+
value: BigInt(request.amount),
|
|
65
|
+
},
|
|
66
|
+
primaryType: 'TransferWithAuthorization',
|
|
67
|
+
types: Types.authorizationTypes,
|
|
68
|
+
}),
|
|
69
|
+
to: getAddress(request.recipient),
|
|
70
|
+
type: 'authorization',
|
|
71
|
+
validAfter: '0',
|
|
72
|
+
validBefore,
|
|
73
|
+
value: request.amount,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return Credential.serialize(
|
|
77
|
+
Credential.from({
|
|
78
|
+
challenge,
|
|
79
|
+
payload,
|
|
80
|
+
source: Types.toSource({
|
|
81
|
+
address: getAddress(account.address),
|
|
82
|
+
chainId: request.methodDetails.chainId,
|
|
83
|
+
}),
|
|
84
|
+
}),
|
|
85
|
+
)
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export declare namespace charge {
|
|
91
|
+
type Signer = Account & {
|
|
92
|
+
signTypedData?: (parameters: any) => Promise<`0x${string}`>
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
type Parameters = {
|
|
96
|
+
/** Account used to sign EVM charge credentials. */
|
|
97
|
+
account: Account
|
|
98
|
+
/** EIP-3009 token domain metadata for custom currencies. */
|
|
99
|
+
authorization?: Types.AuthorizationConfig | undefined
|
|
100
|
+
/** Optional token decimals used to parse `maxAmount` when currency metadata is not provided. */
|
|
101
|
+
decimals?: number | undefined
|
|
102
|
+
/** Optional maximum display-unit amount the client is willing to pay. */
|
|
103
|
+
maxAmount?: string | undefined
|
|
104
|
+
/** Optional maximum atomic amount the client is willing to pay. */
|
|
105
|
+
maxAtomicAmount?: string | undefined
|
|
106
|
+
/** Optional allowlist of supported EVM chain IDs. */
|
|
107
|
+
networks?: readonly number[] | undefined
|
|
108
|
+
/** Optional allowlist of supported currencies. */
|
|
109
|
+
currencies?: readonly (`0x${string}` | Assets.KnownAsset)[] | undefined
|
|
110
|
+
/** Legacy alias for `currencies`. */
|
|
111
|
+
assets?: readonly (`0x${string}` | Assets.KnownAsset)[] | undefined
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function isX402Challenge(challenge: { request: Record<string, unknown> }) {
|
|
116
|
+
return (
|
|
117
|
+
x402_ChallengeBrand.is(challenge) &&
|
|
118
|
+
challenge.request.scheme === 'exact' &&
|
|
119
|
+
typeof challenge.request.network === 'string'
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function assertPolicy(parameters: charge.Parameters, request: Types.ChargeRequest) {
|
|
124
|
+
const chainId = request.methodDetails.chainId
|
|
125
|
+
const network = Types.networkOf(chainId)
|
|
126
|
+
if (parameters.networks && !parameters.networks.includes(chainId))
|
|
127
|
+
throw new Error(`EVM chain ID is not allowed: ${chainId}.`)
|
|
128
|
+
|
|
129
|
+
const currencies = parameters.currencies ?? parameters.assets
|
|
130
|
+
if (currencies?.some((currency) => !Assets.isAsset(currency)) && !parameters.networks?.length)
|
|
131
|
+
throw new Error('EVM raw currency allowlists require networks.')
|
|
132
|
+
if (currencies) {
|
|
133
|
+
const acceptedCurrency = getAddress(request.currency as `0x${string}`)
|
|
134
|
+
const allowed = currencies.some((currency) =>
|
|
135
|
+
currencyMatches(currency, acceptedCurrency, network),
|
|
136
|
+
)
|
|
137
|
+
if (!allowed) throw new Error(`EVM currency is not allowed: ${acceptedCurrency}.`)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (
|
|
141
|
+
parameters.maxAtomicAmount !== undefined &&
|
|
142
|
+
BigInt(request.amount) > BigInt(parameters.maxAtomicAmount)
|
|
143
|
+
)
|
|
144
|
+
throw new Error('EVM charge amount exceeds maxAtomicAmount.')
|
|
145
|
+
|
|
146
|
+
if (parameters.maxAmount !== undefined) {
|
|
147
|
+
const decimals = decimalsOfAcceptedCurrency(parameters, request)
|
|
148
|
+
if (decimals === undefined) throw new Error('EVM charge maxAmount requires currency decimals.')
|
|
149
|
+
if (BigInt(request.amount) > parseUnits(parameters.maxAmount, decimals))
|
|
150
|
+
throw new Error('EVM charge amount exceeds maxAmount.')
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function resolveAuthorization(
|
|
155
|
+
parameters: charge.Parameters,
|
|
156
|
+
request: Types.ChargeRequest,
|
|
157
|
+
): Types.AuthorizationConfig {
|
|
158
|
+
const currencies = parameters.currencies ?? parameters.assets
|
|
159
|
+
const acceptedCurrency = getAddress(request.currency as `0x${string}`)
|
|
160
|
+
const network = Types.networkOf(request.methodDetails.chainId)
|
|
161
|
+
const currency = currencies?.find((currency) =>
|
|
162
|
+
currencyMatches(currency, acceptedCurrency, network),
|
|
163
|
+
)
|
|
164
|
+
if (currency && Assets.isAsset(currency) && currency.transfer.type === Types.eip3009)
|
|
165
|
+
return {
|
|
166
|
+
name: currency.transfer.name,
|
|
167
|
+
version: currency.transfer.version,
|
|
168
|
+
}
|
|
169
|
+
if (parameters.authorization) return parameters.authorization
|
|
170
|
+
throw new Error('EVM authorization requires token name and version.')
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function addressOf(currency: `0x${string}` | Assets.KnownAsset): `0x${string}` {
|
|
174
|
+
return Assets.isAsset(currency) ? currency.address : currency
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function currencyMatches(
|
|
178
|
+
currency: `0x${string}` | Assets.KnownAsset,
|
|
179
|
+
acceptedCurrency: `0x${string}`,
|
|
180
|
+
network: Types.EvmNetwork,
|
|
181
|
+
): boolean {
|
|
182
|
+
if (getAddress(addressOf(currency)) !== acceptedCurrency) return false
|
|
183
|
+
return !Assets.isAsset(currency) || currency.network === network
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function decimalsOfAcceptedCurrency(
|
|
187
|
+
parameters: charge.Parameters,
|
|
188
|
+
request: Types.ChargeRequest,
|
|
189
|
+
): number | undefined {
|
|
190
|
+
const currencies = parameters.currencies ?? parameters.assets
|
|
191
|
+
const acceptedCurrency = getAddress(request.currency as `0x${string}`)
|
|
192
|
+
const network = Types.networkOf(request.methodDetails.chainId)
|
|
193
|
+
const currency = currencies?.find((currency) =>
|
|
194
|
+
currencyMatches(currency, acceptedCurrency, network),
|
|
195
|
+
)
|
|
196
|
+
if (currency && Assets.isAsset(currency)) return currency.decimals
|
|
197
|
+
return parameters.decimals
|
|
198
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as Assets from '../Assets.js'
|
|
2
|
+
import * as Chains from '../Chains.js'
|
|
3
|
+
import { charge as charge_ } from './Charge.js'
|
|
4
|
+
|
|
5
|
+
/** Creates EVM client methods from shared charge parameters. */
|
|
6
|
+
export function evm(parameters: evm.Parameters) {
|
|
7
|
+
return [evm.charge(parameters)] as const
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export namespace evm {
|
|
11
|
+
export type Parameters = charge_.Parameters
|
|
12
|
+
|
|
13
|
+
/** Creates an EVM `charge` client method. */
|
|
14
|
+
export const charge = charge_
|
|
15
|
+
/** Known EVM asset metadata for public config. */
|
|
16
|
+
export const assets = Assets
|
|
17
|
+
/** Common EVM chain IDs for public config. */
|
|
18
|
+
export const chains = Chains
|
|
19
|
+
}
|
package/src/evm/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * as Assets from './Assets.js'
|
|
2
|
+
export * as Chains from './Chains.js'
|
|
3
|
+
export * as Methods from './Methods.js'
|
|
4
|
+
export * as Types from './Types.js'
|
|
5
|
+
export * as assets from './Assets.js'
|
|
6
|
+
export * as chains from './Chains.js'
|
|
7
|
+
export * from './Types.js'
|
|
8
|
+
export type {
|
|
9
|
+
ExactEip3009Transfer,
|
|
10
|
+
ExactPermit2Transfer,
|
|
11
|
+
ExactTransfer,
|
|
12
|
+
ResourceInfo,
|
|
13
|
+
} from '../x402/Types.js'
|