mppx 0.6.28 → 0.6.30
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 +23 -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 +10 -0
- package/dist/evm/index.d.ts.map +1 -0
- package/dist/evm/index.js +9 -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 +54 -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 +114 -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 +14 -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
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Asset, EvmNetwork, ExactTransfer } from './Types.js'
|
|
2
|
+
|
|
3
|
+
const knownAsset = Symbol('mppx.x402.asset')
|
|
4
|
+
|
|
5
|
+
/** Known x402 asset metadata. */
|
|
6
|
+
export type KnownAsset = Asset & {
|
|
7
|
+
readonly [knownAsset]: true
|
|
8
|
+
network: EvmNetwork
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Creates typed x402 asset metadata for custom tokens. */
|
|
12
|
+
export function define(parameters: define.Parameters): KnownAsset {
|
|
13
|
+
return {
|
|
14
|
+
[knownAsset]: true,
|
|
15
|
+
address: parameters.address,
|
|
16
|
+
decimals: parameters.decimals,
|
|
17
|
+
network: parameters.network,
|
|
18
|
+
transfer: parameters.transfer,
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export declare namespace define {
|
|
23
|
+
type Parameters = {
|
|
24
|
+
address: `0x${string}`
|
|
25
|
+
decimals: number
|
|
26
|
+
network: EvmNetwork
|
|
27
|
+
transfer: ExactTransfer
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Base network known assets. */
|
|
32
|
+
export const base = {
|
|
33
|
+
USDC: define({
|
|
34
|
+
address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
|
|
35
|
+
decimals: 6,
|
|
36
|
+
network: 'eip155:8453',
|
|
37
|
+
transfer: {
|
|
38
|
+
// USDC's EIP-712 domain name differs between Base and Base Sepolia.
|
|
39
|
+
name: 'USD Coin',
|
|
40
|
+
type: 'eip3009',
|
|
41
|
+
version: '2',
|
|
42
|
+
},
|
|
43
|
+
}),
|
|
44
|
+
} as const
|
|
45
|
+
|
|
46
|
+
/** Base Sepolia known assets. */
|
|
47
|
+
export const baseSepolia = {
|
|
48
|
+
USDC: define({
|
|
49
|
+
address: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
|
|
50
|
+
decimals: 6,
|
|
51
|
+
network: 'eip155:84532',
|
|
52
|
+
transfer: {
|
|
53
|
+
// Base Sepolia test USDC signs with the shorter EIP-712 domain name.
|
|
54
|
+
name: 'USDC',
|
|
55
|
+
type: 'eip3009',
|
|
56
|
+
version: '2',
|
|
57
|
+
},
|
|
58
|
+
}),
|
|
59
|
+
} as const
|
|
60
|
+
|
|
61
|
+
/** Returns true when a value is known x402 asset metadata. */
|
|
62
|
+
export function isAsset(value: unknown): value is KnownAsset {
|
|
63
|
+
if (typeof value !== 'object' || value === null) return false
|
|
64
|
+
return (value as Partial<KnownAsset>)[knownAsset] === true
|
|
65
|
+
}
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import { evm as evmClient, Mppx as ClientMppx, tempo as tempoClient } from 'mppx/client'
|
|
2
|
+
import { evm, Mppx as ServerMppx, NodeListener, Request as ServerRequest, tempo } from 'mppx/server'
|
|
3
|
+
import { describe, expect, test } from 'vp/test'
|
|
4
|
+
import * as Http from '~test/Http.js'
|
|
5
|
+
import { accounts, asset, client } from '~test/tempo/viem.js'
|
|
6
|
+
|
|
7
|
+
import * as Header from './Header.js'
|
|
8
|
+
import * as RouteBinding from './internal/RouteBinding.js'
|
|
9
|
+
import * as Types from './Types.js'
|
|
10
|
+
|
|
11
|
+
const secretKey = 'test-secret'
|
|
12
|
+
const transaction = `0x${'1'.repeat(64)}`
|
|
13
|
+
|
|
14
|
+
describe('x402 exact e2e', () => {
|
|
15
|
+
test('serves tempo and x402 paid routes from a live server', async () => {
|
|
16
|
+
const tempoPayment = ServerMppx.create({
|
|
17
|
+
methods: [
|
|
18
|
+
tempo.charge({
|
|
19
|
+
account: accounts[0],
|
|
20
|
+
currency: asset,
|
|
21
|
+
getClient: () => client,
|
|
22
|
+
recipient: accounts[0].address,
|
|
23
|
+
}),
|
|
24
|
+
],
|
|
25
|
+
secretKey,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const facilitator: Types.Facilitator = {
|
|
29
|
+
async verify(paymentPayload) {
|
|
30
|
+
return {
|
|
31
|
+
isValid: true,
|
|
32
|
+
payer: payerOf(paymentPayload),
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
async settle(paymentPayload) {
|
|
36
|
+
return {
|
|
37
|
+
network: paymentPayload.accepted.network,
|
|
38
|
+
payer: payerOf(paymentPayload),
|
|
39
|
+
success: true,
|
|
40
|
+
transaction,
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
const x402Payment = ServerMppx.create({
|
|
45
|
+
methods: [
|
|
46
|
+
evm.charge({
|
|
47
|
+
currency: evm.assets.baseSepolia.USDC,
|
|
48
|
+
recipient: accounts[0].address,
|
|
49
|
+
x402: { facilitator },
|
|
50
|
+
}),
|
|
51
|
+
],
|
|
52
|
+
secretKey,
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const server = await Http.createServer(async (req, res) => {
|
|
56
|
+
const request = ServerRequest.fromNodeListener(req, res)
|
|
57
|
+
|
|
58
|
+
if (req.url === '/tempo') {
|
|
59
|
+
const result = await tempoPayment.tempo.charge({
|
|
60
|
+
amount: '0',
|
|
61
|
+
chainId: client.chain!.id,
|
|
62
|
+
})(request)
|
|
63
|
+
if (result.status === 402) return NodeListener.sendResponse(res, result.challenge)
|
|
64
|
+
return NodeListener.sendResponse(res, result.withReceipt(new Response('tempo ok')))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (req.url === '/x402') {
|
|
68
|
+
const result = await x402Payment.evm.charge({
|
|
69
|
+
amount: '0.01',
|
|
70
|
+
})(request)
|
|
71
|
+
if (result.status === 402) return NodeListener.sendResponse(res, result.challenge)
|
|
72
|
+
return NodeListener.sendResponse(res, result.withReceipt(new Response('x402 ok')))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return NodeListener.sendResponse(res, new Response('not found', { status: 404 }))
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const tempoClientPayment = ClientMppx.create({
|
|
80
|
+
methods: [
|
|
81
|
+
tempoClient.charge({
|
|
82
|
+
account: accounts[0],
|
|
83
|
+
getClient: () => client,
|
|
84
|
+
}),
|
|
85
|
+
],
|
|
86
|
+
polyfill: false,
|
|
87
|
+
})
|
|
88
|
+
const tempoResponse = await tempoClientPayment.fetch(`${server.url}/tempo`)
|
|
89
|
+
expect(tempoResponse.status).toBe(200)
|
|
90
|
+
expect(await tempoResponse.text()).toBe('tempo ok')
|
|
91
|
+
expect(tempoResponse.headers.has('Payment-Receipt')).toBe(true)
|
|
92
|
+
|
|
93
|
+
const x402ClientPayment = ClientMppx.create({
|
|
94
|
+
methods: [
|
|
95
|
+
evmClient.charge({
|
|
96
|
+
account: accounts[0],
|
|
97
|
+
}),
|
|
98
|
+
],
|
|
99
|
+
polyfill: false,
|
|
100
|
+
})
|
|
101
|
+
const x402Required = await x402ClientPayment.rawFetch(`${server.url}/x402`)
|
|
102
|
+
expect(x402Required.status).toBe(402)
|
|
103
|
+
expect(x402Required.headers.has(Types.paymentRequiredHeader)).toBe(true)
|
|
104
|
+
|
|
105
|
+
const paymentSignature = await x402ClientPayment.createCredential(
|
|
106
|
+
pureX402Challenge(x402Required),
|
|
107
|
+
)
|
|
108
|
+
const paymentPayload = Header.decodePaymentSignature(paymentSignature)
|
|
109
|
+
expect(paymentPayload.accepted.scheme).toBe('exact')
|
|
110
|
+
|
|
111
|
+
const x402Response = await x402ClientPayment.rawFetch(`${server.url}/x402`, {
|
|
112
|
+
headers: { [Types.paymentSignatureHeader]: paymentSignature },
|
|
113
|
+
})
|
|
114
|
+
expect(x402Response.status).toBe(200)
|
|
115
|
+
expect(await x402Response.text()).toBe('x402 ok')
|
|
116
|
+
|
|
117
|
+
const paymentResponseHeader = x402Response.headers.get(Types.paymentResponseHeader)
|
|
118
|
+
expect(paymentResponseHeader).toBeTruthy()
|
|
119
|
+
expect(Header.decodePaymentResponse(paymentResponseHeader!).transaction).toBe(transaction)
|
|
120
|
+
} finally {
|
|
121
|
+
server.close()
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('rejects x402 payment payload replayed across resources with same requirements', async () => {
|
|
126
|
+
let verifyCalls = 0
|
|
127
|
+
const payment = ServerMppx.create({
|
|
128
|
+
methods: [
|
|
129
|
+
evm.charge({
|
|
130
|
+
currency: evm.assets.baseSepolia.USDC,
|
|
131
|
+
recipient: accounts[0].address,
|
|
132
|
+
x402: {
|
|
133
|
+
facilitator: {
|
|
134
|
+
async verify() {
|
|
135
|
+
verifyCalls++
|
|
136
|
+
return { isValid: true }
|
|
137
|
+
},
|
|
138
|
+
async settle(paymentPayload: Types.PaymentPayload) {
|
|
139
|
+
return {
|
|
140
|
+
network: paymentPayload.accepted.network,
|
|
141
|
+
success: true,
|
|
142
|
+
transaction,
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
}),
|
|
148
|
+
],
|
|
149
|
+
secretKey,
|
|
150
|
+
})
|
|
151
|
+
const route = payment.evm.charge({ amount: '0.01' })
|
|
152
|
+
|
|
153
|
+
const routeAChallenge = await route(new Request('https://example.com/a'))
|
|
154
|
+
expect(routeAChallenge.status).toBe(402)
|
|
155
|
+
if (routeAChallenge.status !== 402) throw new Error()
|
|
156
|
+
|
|
157
|
+
const paymentRequired = Header.decodePaymentRequired(
|
|
158
|
+
routeAChallenge.challenge.headers.get(Types.paymentRequiredHeader)!,
|
|
159
|
+
)
|
|
160
|
+
const accepted = paymentRequired.accepts[0]!
|
|
161
|
+
const paymentSignature = Header.encodePaymentSignature({
|
|
162
|
+
accepted,
|
|
163
|
+
payload: {
|
|
164
|
+
authorization: {
|
|
165
|
+
from: accounts[0].address,
|
|
166
|
+
nonce: `0x${'1'.repeat(64)}`,
|
|
167
|
+
to: accepted.payTo as `0x${string}`,
|
|
168
|
+
validAfter: '0',
|
|
169
|
+
validBefore: '9999999999',
|
|
170
|
+
value: accepted.amount,
|
|
171
|
+
},
|
|
172
|
+
signature: `0x${'2'.repeat(130)}`,
|
|
173
|
+
},
|
|
174
|
+
resource: paymentRequired.resource,
|
|
175
|
+
x402Version: 2,
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
const result = await route(
|
|
179
|
+
new Request('https://example.com/b', {
|
|
180
|
+
headers: { [Types.paymentSignatureHeader]: paymentSignature },
|
|
181
|
+
}),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
expect(result.status).toBe(402)
|
|
185
|
+
expect(verifyCalls).toBe(0)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
test('rejects x402 route extensions with extra binding fields', async () => {
|
|
189
|
+
let verifyCalls = 0
|
|
190
|
+
const payment = ServerMppx.create({
|
|
191
|
+
methods: [
|
|
192
|
+
evm.charge({
|
|
193
|
+
currency: evm.assets.baseSepolia.USDC,
|
|
194
|
+
recipient: accounts[0].address,
|
|
195
|
+
x402: {
|
|
196
|
+
facilitator: {
|
|
197
|
+
async verify() {
|
|
198
|
+
verifyCalls++
|
|
199
|
+
return { isValid: true }
|
|
200
|
+
},
|
|
201
|
+
async settle(paymentPayload: Types.PaymentPayload) {
|
|
202
|
+
return {
|
|
203
|
+
network: paymentPayload.accepted.network,
|
|
204
|
+
success: true,
|
|
205
|
+
transaction,
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
}),
|
|
211
|
+
],
|
|
212
|
+
secretKey,
|
|
213
|
+
})
|
|
214
|
+
const route = payment.evm.charge({ amount: '0.01' })
|
|
215
|
+
|
|
216
|
+
const first = await route(new Request('https://example.com/a'))
|
|
217
|
+
expect(first.status).toBe(402)
|
|
218
|
+
if (first.status !== 402) throw new Error()
|
|
219
|
+
|
|
220
|
+
const paymentRequired = Header.decodePaymentRequired(
|
|
221
|
+
first.challenge.headers.get(Types.paymentRequiredHeader)!,
|
|
222
|
+
)
|
|
223
|
+
const accepted = paymentRequired.accepts[0]!
|
|
224
|
+
const mppxExtension = paymentRequired.extensions!.mppx
|
|
225
|
+
if (!mppxExtension) throw new Error()
|
|
226
|
+
const extensions: Types.Extensions = {
|
|
227
|
+
...paymentRequired.extensions!,
|
|
228
|
+
mppx: {
|
|
229
|
+
schema: mppxExtension.schema,
|
|
230
|
+
info: {
|
|
231
|
+
...mppxExtension.info,
|
|
232
|
+
extra: 'not allowed',
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
}
|
|
236
|
+
const paymentSignature = Header.encodePaymentSignature({
|
|
237
|
+
accepted,
|
|
238
|
+
extensions,
|
|
239
|
+
payload: {
|
|
240
|
+
authorization: {
|
|
241
|
+
from: accounts[0].address,
|
|
242
|
+
nonce: RouteBinding.nonce({
|
|
243
|
+
accepted,
|
|
244
|
+
extensions,
|
|
245
|
+
resource: paymentRequired.resource,
|
|
246
|
+
}),
|
|
247
|
+
to: accepted.payTo as `0x${string}`,
|
|
248
|
+
validAfter: '0',
|
|
249
|
+
validBefore: '9999999999',
|
|
250
|
+
value: accepted.amount,
|
|
251
|
+
},
|
|
252
|
+
signature: `0x${'2'.repeat(130)}`,
|
|
253
|
+
},
|
|
254
|
+
resource: paymentRequired.resource,
|
|
255
|
+
x402Version: 2,
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
const result = await route(
|
|
259
|
+
new Request('https://example.com/a', {
|
|
260
|
+
headers: { [Types.paymentSignatureHeader]: paymentSignature },
|
|
261
|
+
}),
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
expect(result.status).toBe(402)
|
|
265
|
+
expect(verifyCalls).toBe(0)
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
test('does not advertise x402 for body-bearing requests without a digest', async () => {
|
|
269
|
+
let verifyCalls = 0
|
|
270
|
+
const payment = ServerMppx.create({
|
|
271
|
+
methods: [
|
|
272
|
+
evm.charge({
|
|
273
|
+
currency: evm.assets.baseSepolia.USDC,
|
|
274
|
+
recipient: accounts[0].address,
|
|
275
|
+
x402: {
|
|
276
|
+
facilitator: {
|
|
277
|
+
async verify() {
|
|
278
|
+
verifyCalls++
|
|
279
|
+
return { isValid: true }
|
|
280
|
+
},
|
|
281
|
+
async settle(paymentPayload: Types.PaymentPayload) {
|
|
282
|
+
return {
|
|
283
|
+
network: paymentPayload.accepted.network,
|
|
284
|
+
success: true,
|
|
285
|
+
transaction,
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
}),
|
|
291
|
+
],
|
|
292
|
+
secretKey,
|
|
293
|
+
})
|
|
294
|
+
const route = payment.evm.charge({ amount: '0.01' })
|
|
295
|
+
|
|
296
|
+
const result = await route(
|
|
297
|
+
new Request('https://example.com/body', {
|
|
298
|
+
body: JSON.stringify({ a: 1 }),
|
|
299
|
+
method: 'POST',
|
|
300
|
+
}),
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
expect(result.status).toBe(402)
|
|
304
|
+
if (result.status !== 402) throw new Error()
|
|
305
|
+
expect(result.challenge.headers.has('WWW-Authenticate')).toBe(true)
|
|
306
|
+
expect(result.challenge.headers.has(Types.paymentRequiredHeader)).toBe(false)
|
|
307
|
+
|
|
308
|
+
const getChallenge = await route(new Request('https://example.com/body'))
|
|
309
|
+
expect(getChallenge.status).toBe(402)
|
|
310
|
+
if (getChallenge.status !== 402) throw new Error()
|
|
311
|
+
const paymentRequired = Header.decodePaymentRequired(
|
|
312
|
+
getChallenge.challenge.headers.get(Types.paymentRequiredHeader)!,
|
|
313
|
+
)
|
|
314
|
+
const accepted = paymentRequired.accepts[0]!
|
|
315
|
+
const paymentSignature = Header.encodePaymentSignature({
|
|
316
|
+
accepted,
|
|
317
|
+
extensions: paymentRequired.extensions,
|
|
318
|
+
payload: {
|
|
319
|
+
authorization: {
|
|
320
|
+
from: accounts[0].address,
|
|
321
|
+
nonce: RouteBinding.nonce({
|
|
322
|
+
accepted,
|
|
323
|
+
extensions: paymentRequired.extensions!,
|
|
324
|
+
resource: paymentRequired.resource,
|
|
325
|
+
}),
|
|
326
|
+
to: accepted.payTo as `0x${string}`,
|
|
327
|
+
validAfter: '0',
|
|
328
|
+
validBefore: '9999999999',
|
|
329
|
+
value: accepted.amount,
|
|
330
|
+
},
|
|
331
|
+
signature: `0x${'2'.repeat(130)}`,
|
|
332
|
+
},
|
|
333
|
+
resource: paymentRequired.resource,
|
|
334
|
+
x402Version: 2,
|
|
335
|
+
})
|
|
336
|
+
const replay = await route(
|
|
337
|
+
new Request('https://example.com/body', {
|
|
338
|
+
body: JSON.stringify({ a: 2 }),
|
|
339
|
+
headers: { [Types.paymentSignatureHeader]: paymentSignature },
|
|
340
|
+
method: 'POST',
|
|
341
|
+
}),
|
|
342
|
+
)
|
|
343
|
+
expect(replay.status).toBe(402)
|
|
344
|
+
expect(verifyCalls).toBe(0)
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
test('serves tempo and x402 from one composed live endpoint', async () => {
|
|
348
|
+
const payment = ServerMppx.create({
|
|
349
|
+
methods: [
|
|
350
|
+
tempo.charge({
|
|
351
|
+
account: accounts[0],
|
|
352
|
+
currency: asset,
|
|
353
|
+
getClient: () => client,
|
|
354
|
+
recipient: accounts[0].address,
|
|
355
|
+
}),
|
|
356
|
+
evm.charge({
|
|
357
|
+
currency: evm.assets.baseSepolia.USDC,
|
|
358
|
+
recipient: accounts[0].address,
|
|
359
|
+
x402: {
|
|
360
|
+
facilitator: {
|
|
361
|
+
async verify(paymentPayload: Types.PaymentPayload) {
|
|
362
|
+
return {
|
|
363
|
+
isValid: true,
|
|
364
|
+
payer: payerOf(paymentPayload),
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
async settle(paymentPayload: Types.PaymentPayload) {
|
|
368
|
+
return {
|
|
369
|
+
network: paymentPayload.accepted.network,
|
|
370
|
+
payer: payerOf(paymentPayload),
|
|
371
|
+
success: true,
|
|
372
|
+
transaction,
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
}),
|
|
378
|
+
],
|
|
379
|
+
secretKey,
|
|
380
|
+
})
|
|
381
|
+
const paid = payment.compose(
|
|
382
|
+
[payment.tempo.charge, { amount: '0', chainId: client.chain!.id }],
|
|
383
|
+
[payment.evm.charge, { amount: '0.01' }],
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
const server = await Http.createServer(async (req, res) => {
|
|
387
|
+
const request = ServerRequest.fromNodeListener(req, res)
|
|
388
|
+
const result = await paid(request)
|
|
389
|
+
if (result.status === 402) return NodeListener.sendResponse(res, result.challenge)
|
|
390
|
+
return NodeListener.sendResponse(res, result.withReceipt(new Response('paid ok')))
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
try {
|
|
394
|
+
const challenge = await fetch(server.url)
|
|
395
|
+
expect(challenge.status).toBe(402)
|
|
396
|
+
expect(challenge.headers.has('WWW-Authenticate')).toBe(true)
|
|
397
|
+
expect(challenge.headers.has(Types.paymentRequiredHeader)).toBe(true)
|
|
398
|
+
|
|
399
|
+
const tempoClientPayment = ClientMppx.create({
|
|
400
|
+
methods: [
|
|
401
|
+
tempoClient.charge({
|
|
402
|
+
account: accounts[0],
|
|
403
|
+
getClient: () => client,
|
|
404
|
+
}),
|
|
405
|
+
],
|
|
406
|
+
polyfill: false,
|
|
407
|
+
})
|
|
408
|
+
const tempoResponse = await tempoClientPayment.fetch(server.url)
|
|
409
|
+
expect(tempoResponse.status).toBe(200)
|
|
410
|
+
expect(await tempoResponse.text()).toBe('paid ok')
|
|
411
|
+
expect(tempoResponse.headers.has('Payment-Receipt')).toBe(true)
|
|
412
|
+
|
|
413
|
+
const x402ClientPayment = ClientMppx.create({
|
|
414
|
+
methods: [
|
|
415
|
+
evmClient.charge({
|
|
416
|
+
account: accounts[0],
|
|
417
|
+
}),
|
|
418
|
+
],
|
|
419
|
+
polyfill: false,
|
|
420
|
+
})
|
|
421
|
+
const paymentSignature = await x402ClientPayment.createCredential(
|
|
422
|
+
pureX402Challenge(challenge),
|
|
423
|
+
)
|
|
424
|
+
const x402Response = await x402ClientPayment.rawFetch(server.url, {
|
|
425
|
+
headers: { [Types.paymentSignatureHeader]: paymentSignature },
|
|
426
|
+
})
|
|
427
|
+
expect(x402Response.status).toBe(200)
|
|
428
|
+
expect(await x402Response.text()).toBe('paid ok')
|
|
429
|
+
expect(x402Response.headers.has(Types.paymentResponseHeader)).toBe(true)
|
|
430
|
+
} finally {
|
|
431
|
+
server.close()
|
|
432
|
+
}
|
|
433
|
+
})
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
function payerOf(paymentPayload: Types.PaymentPayload): string {
|
|
437
|
+
if ('authorization' in paymentPayload.payload) return paymentPayload.payload.authorization.from
|
|
438
|
+
return paymentPayload.payload.permit2Authorization.from
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function pureX402Challenge(response: Response): Response {
|
|
442
|
+
const paymentRequired = response.headers.get(Types.paymentRequiredHeader)
|
|
443
|
+
if (!paymentRequired) throw new Error('Missing PAYMENT-REQUIRED header.')
|
|
444
|
+
return new Response(null, {
|
|
445
|
+
headers: { [Types.paymentRequiredHeader]: paymentRequired },
|
|
446
|
+
status: 402,
|
|
447
|
+
})
|
|
448
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as Header from './Header.js'
|
|
2
|
+
import type * as Types from './Types.js'
|
|
3
|
+
|
|
4
|
+
describe('x402 headers', () => {
|
|
5
|
+
test('round trips PAYMENT-REQUIRED header values', () => {
|
|
6
|
+
const paymentRequired: Types.PaymentRequired = {
|
|
7
|
+
accepts: [
|
|
8
|
+
{
|
|
9
|
+
amount: '10000',
|
|
10
|
+
asset: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
|
|
11
|
+
extra: {
|
|
12
|
+
assetTransferMethod: 'eip3009',
|
|
13
|
+
name: 'USDC',
|
|
14
|
+
version: '2',
|
|
15
|
+
},
|
|
16
|
+
maxTimeoutSeconds: 60,
|
|
17
|
+
network: 'eip155:84532',
|
|
18
|
+
payTo: '0x209693Bc6afc0C5328bA36FaF03C514EF312287C',
|
|
19
|
+
scheme: 'exact',
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
resource: {
|
|
23
|
+
mimeType: 'application/json',
|
|
24
|
+
url: 'https://api.example.com/premium-data',
|
|
25
|
+
},
|
|
26
|
+
extensions: {
|
|
27
|
+
mppx: {
|
|
28
|
+
info: { method: 'GET' },
|
|
29
|
+
schema: {
|
|
30
|
+
properties: { method: { type: 'string' } },
|
|
31
|
+
required: ['method'],
|
|
32
|
+
type: 'object',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
x402Version: 2,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const header = Header.encodePaymentRequired(paymentRequired)
|
|
40
|
+
|
|
41
|
+
expect(Header.decodePaymentRequired(header)).toEqual(paymentRequired)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('round trips PAYMENT-SIGNATURE header values', () => {
|
|
45
|
+
const paymentPayload: Types.PaymentPayload = {
|
|
46
|
+
accepted: {
|
|
47
|
+
amount: '10000',
|
|
48
|
+
asset: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
|
|
49
|
+
maxTimeoutSeconds: 60,
|
|
50
|
+
network: 'eip155:84532',
|
|
51
|
+
payTo: '0x209693Bc6afc0C5328bA36FaF03C514EF312287C',
|
|
52
|
+
scheme: 'exact',
|
|
53
|
+
},
|
|
54
|
+
payload: {
|
|
55
|
+
authorization: {
|
|
56
|
+
from: '0x857b06519E91e3A54538791bDbb0E22373e36b66',
|
|
57
|
+
nonce: '0xf3746613c2d920b5fdabc0856f2aeb2d4f88ee6037b8cc5d04a71a4462f13480',
|
|
58
|
+
to: '0x209693Bc6afc0C5328bA36FaF03C514EF312287C',
|
|
59
|
+
validAfter: '1740672089',
|
|
60
|
+
validBefore: '1740672154',
|
|
61
|
+
value: '10000',
|
|
62
|
+
},
|
|
63
|
+
signature:
|
|
64
|
+
'0x2d6a7588d6acca505cbf0d9a4a227e0c52c6c34008c8e8986a1283259764173608a2ce6496642e377d6da8dbbf5836e9bd15092f9ecab05ded3d6293af148b571c',
|
|
65
|
+
},
|
|
66
|
+
x402Version: 2,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const header = Header.encodePaymentSignature(paymentPayload)
|
|
70
|
+
|
|
71
|
+
expect(Header.decodePaymentSignature(header)).toEqual(paymentPayload)
|
|
72
|
+
})
|
|
73
|
+
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as HeaderCodec from '../internal/HeaderCodec.js'
|
|
2
|
+
import {
|
|
3
|
+
PaymentPayloadSchema,
|
|
4
|
+
PaymentRequiredSchema,
|
|
5
|
+
SettleResponseSchema,
|
|
6
|
+
type PaymentPayload,
|
|
7
|
+
type PaymentRequired,
|
|
8
|
+
type SettleResponse,
|
|
9
|
+
} from './Types.js'
|
|
10
|
+
|
|
11
|
+
const paymentRequired = HeaderCodec.createJson(PaymentRequiredSchema)
|
|
12
|
+
const paymentSignature = HeaderCodec.createJson(PaymentPayloadSchema)
|
|
13
|
+
const paymentResponse = HeaderCodec.createJson(SettleResponseSchema)
|
|
14
|
+
|
|
15
|
+
/** Encodes an x402 payment-required object for the `PAYMENT-REQUIRED` header. */
|
|
16
|
+
export const encodePaymentRequired: (paymentRequired: PaymentRequired) => string =
|
|
17
|
+
paymentRequired.encode
|
|
18
|
+
|
|
19
|
+
/** Decodes an x402 `PAYMENT-REQUIRED` header value. */
|
|
20
|
+
export const decodePaymentRequired: (value: string) => PaymentRequired = paymentRequired.decode
|
|
21
|
+
|
|
22
|
+
/** Encodes an x402 payment payload for the `PAYMENT-SIGNATURE` header. */
|
|
23
|
+
export const encodePaymentSignature: (paymentPayload: PaymentPayload) => string =
|
|
24
|
+
paymentSignature.encode
|
|
25
|
+
|
|
26
|
+
/** Decodes an x402 `PAYMENT-SIGNATURE` header value. */
|
|
27
|
+
export const decodePaymentSignature: (value: string) => PaymentPayload = paymentSignature.decode
|
|
28
|
+
|
|
29
|
+
/** Encodes an x402 settlement response for the `PAYMENT-RESPONSE` header. */
|
|
30
|
+
export const encodePaymentResponse: (paymentResponse: SettleResponse) => string =
|
|
31
|
+
paymentResponse.encode
|
|
32
|
+
|
|
33
|
+
/** Decodes an x402 `PAYMENT-RESPONSE` header value. */
|
|
34
|
+
export const decodePaymentResponse: (value: string) => SettleResponse = paymentResponse.decode
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { evm, Mppx } from 'mppx/server'
|
|
2
|
+
import type { Account } from 'viem'
|
|
3
|
+
import { describe, expectTypeOf, test } from 'vp/test'
|
|
4
|
+
|
|
5
|
+
import { evm as clientEvm } from '../client/index.js'
|
|
6
|
+
|
|
7
|
+
const secretKey = 'test-secret'
|
|
8
|
+
|
|
9
|
+
describe('x402 public interface', () => {
|
|
10
|
+
test('server evm charge accepts known assets without transfer metadata', () => {
|
|
11
|
+
const mppx = Mppx.create({
|
|
12
|
+
methods: [
|
|
13
|
+
evm.charge({
|
|
14
|
+
currency: evm.assets.base.USDC,
|
|
15
|
+
recipient: '0x209693Bc6afc0C5328bA36FaF03C514EF312287C',
|
|
16
|
+
x402: {
|
|
17
|
+
facilitator: 'https://x402.org/facilitator',
|
|
18
|
+
},
|
|
19
|
+
}),
|
|
20
|
+
],
|
|
21
|
+
secretKey,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
expectTypeOf(mppx.evm.charge).toBeFunction()
|
|
25
|
+
expectTypeOf(mppx.evm.charge({ amount: '0.01' })).toBeFunction()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('client evm charge exposes account config and policies', () => {
|
|
29
|
+
const method = clientEvm.charge({
|
|
30
|
+
account: {} as Account,
|
|
31
|
+
currencies: [clientEvm.assets.baseSepolia.USDC],
|
|
32
|
+
maxAmount: '0.01',
|
|
33
|
+
networks: [clientEvm.chains.baseSepolia],
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
expectTypeOf(method.intent).toEqualTypeOf<'charge'>()
|
|
37
|
+
expectTypeOf(method.name).toEqualTypeOf<'evm'>()
|
|
38
|
+
})
|
|
39
|
+
})
|