mppx 0.8.3 → 0.8.5
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 +14 -0
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +2 -0
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/plugins/stripe.d.ts.map +1 -1
- package/dist/cli/plugins/stripe.js +32 -12
- package/dist/cli/plugins/stripe.js.map +1 -1
- package/dist/cli/utils.d.ts.map +1 -1
- package/dist/cli/utils.js +1 -1
- package/dist/cli/utils.js.map +1 -1
- package/dist/cli/validate/challenge.d.ts +14 -0
- package/dist/cli/validate/challenge.d.ts.map +1 -0
- package/dist/cli/validate/challenge.js +211 -0
- package/dist/cli/validate/challenge.js.map +1 -0
- package/dist/cli/validate/discovery.d.ts +10 -0
- package/dist/cli/validate/discovery.d.ts.map +1 -0
- package/dist/cli/validate/discovery.js +128 -0
- package/dist/cli/validate/discovery.js.map +1 -0
- package/dist/cli/validate/helpers.d.ts +33 -0
- package/dist/cli/validate/helpers.d.ts.map +1 -0
- package/dist/cli/validate/helpers.js +136 -0
- package/dist/cli/validate/helpers.js.map +1 -0
- package/dist/cli/validate/index.d.ts +17 -0
- package/dist/cli/validate/index.d.ts.map +1 -0
- package/dist/cli/validate/index.js +224 -0
- package/dist/cli/validate/index.js.map +1 -0
- package/dist/cli/validate/payment.d.ts +7 -0
- package/dist/cli/validate/payment.d.ts.map +1 -0
- package/dist/cli/validate/payment.js +381 -0
- package/dist/cli/validate/payment.js.map +1 -0
- package/dist/evm/client/Charge.d.ts +2 -2
- package/dist/evm/client/Charge.d.ts.map +1 -1
- package/dist/evm/client/Charge.js +11 -17
- package/dist/evm/client/Charge.js.map +1 -1
- package/dist/evm/server/Charge.d.ts +5 -5
- package/dist/evm/server/Charge.d.ts.map +1 -1
- package/dist/evm/server/Charge.js +15 -7
- package/dist/evm/server/Charge.js.map +1 -1
- package/dist/tempo/internal/auto-swap.d.ts.map +1 -1
- package/dist/tempo/internal/auto-swap.js +6 -1
- package/dist/tempo/internal/auto-swap.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/x402/Assets.d.ts +40 -0
- package/dist/x402/Assets.d.ts.map +1 -1
- package/dist/x402/Assets.js +73 -0
- package/dist/x402/Assets.js.map +1 -1
- package/dist/x402/client/Exact.d.ts +2 -2
- package/dist/x402/client/Exact.d.ts.map +1 -1
- package/dist/x402/client/Exact.js +6 -13
- package/dist/x402/client/Exact.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/cli.test.ts +134 -29
- package/src/cli/cli.ts +2 -0
- package/src/cli/mcp.test.ts +1 -0
- package/src/cli/plugins/stripe.ts +31 -12
- package/src/cli/utils.test.ts +36 -2
- package/src/cli/utils.ts +2 -1
- package/src/cli/validate/challenge.ts +335 -0
- package/src/cli/validate/discovery.ts +120 -0
- package/src/cli/validate/helpers.ts +154 -0
- package/src/cli/validate/index.ts +348 -0
- package/src/cli/validate/payment.ts +488 -0
- package/src/cli/validate.test.ts +539 -0
- package/src/evm/PublicInterface.test-d.ts +15 -2
- package/src/evm/client/Charge.test.ts +248 -0
- package/src/evm/client/Charge.ts +12 -23
- package/src/evm/server/Charge.test.ts +140 -0
- package/src/evm/server/Charge.ts +19 -12
- package/src/tempo/internal/auto-swap.test.ts +26 -9
- package/src/tempo/internal/auto-swap.ts +15 -2
- package/src/tempo/server/internal/html.gen.ts +1 -1
- package/src/x402/Assets.test.ts +211 -0
- package/src/x402/Assets.ts +116 -0
- package/src/x402/client/Exact.test.ts +129 -0
- package/src/x402/client/Exact.ts +7 -19
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { usdc } from 'viem/tokens'
|
|
2
|
+
import { describe, expect, test } from 'vp/test'
|
|
3
|
+
|
|
4
|
+
import * as Assets from './Assets.js'
|
|
5
|
+
|
|
6
|
+
describe('x402 assets', () => {
|
|
7
|
+
test('defines branded asset metadata', () => {
|
|
8
|
+
const asset = Assets.define({
|
|
9
|
+
address: '0x1111111111111111111111111111111111111111',
|
|
10
|
+
decimals: 18,
|
|
11
|
+
network: 'eip155:1',
|
|
12
|
+
transfer: {
|
|
13
|
+
name: 'USD Coin',
|
|
14
|
+
type: 'eip3009',
|
|
15
|
+
version: '2',
|
|
16
|
+
},
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
expect(Assets.isAsset(asset)).toBe(true)
|
|
20
|
+
expect(asset).toMatchObject({
|
|
21
|
+
address: '0x1111111111111111111111111111111111111111',
|
|
22
|
+
decimals: 18,
|
|
23
|
+
network: 'eip155:1',
|
|
24
|
+
transfer: {
|
|
25
|
+
name: 'USD Coin',
|
|
26
|
+
type: 'eip3009',
|
|
27
|
+
version: '2',
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('rejects unbranded values', () => {
|
|
33
|
+
expect(Assets.isAsset(null)).toBe(false)
|
|
34
|
+
expect(Assets.isAsset('0x1111111111111111111111111111111111111111')).toBe(false)
|
|
35
|
+
expect(
|
|
36
|
+
Assets.isAsset({
|
|
37
|
+
address: '0x1111111111111111111111111111111111111111',
|
|
38
|
+
decimals: 18,
|
|
39
|
+
network: 'eip155:1',
|
|
40
|
+
transfer: {
|
|
41
|
+
name: 'USD Coin',
|
|
42
|
+
type: 'eip3009',
|
|
43
|
+
version: '2',
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
46
|
+
).toBe(false)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('defines asset metadata from viem tokens', () => {
|
|
50
|
+
const asset = Assets.fromToken(usdc, {
|
|
51
|
+
chainId: 84532,
|
|
52
|
+
transfer: {
|
|
53
|
+
type: 'eip3009',
|
|
54
|
+
version: '2',
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
expect(Assets.isAsset(asset)).toBe(true)
|
|
59
|
+
expect(asset).toMatchObject({
|
|
60
|
+
address: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
|
|
61
|
+
decimals: 6,
|
|
62
|
+
network: 'eip155:84532',
|
|
63
|
+
transfer: {
|
|
64
|
+
name: 'USD Coin',
|
|
65
|
+
type: 'eip3009',
|
|
66
|
+
version: '2',
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('allows viem token transfer metadata overrides', () => {
|
|
72
|
+
const eip3009Asset = Assets.fromToken(usdc, {
|
|
73
|
+
chainId: 84532,
|
|
74
|
+
transfer: {
|
|
75
|
+
name: 'USDC',
|
|
76
|
+
type: 'eip3009',
|
|
77
|
+
version: '2',
|
|
78
|
+
},
|
|
79
|
+
})
|
|
80
|
+
const permit2Asset = Assets.fromToken(usdc, {
|
|
81
|
+
chainId: 84532,
|
|
82
|
+
transfer: {
|
|
83
|
+
type: 'permit2',
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
expect(eip3009Asset.transfer).toEqual({
|
|
88
|
+
name: 'USDC',
|
|
89
|
+
type: 'eip3009',
|
|
90
|
+
version: '2',
|
|
91
|
+
})
|
|
92
|
+
expect(permit2Asset.transfer).toEqual({
|
|
93
|
+
type: 'permit2',
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test('requires token names for EIP-3009 viem token assets', () => {
|
|
98
|
+
const tokenWithoutName = Object.assign(
|
|
99
|
+
() => ({
|
|
100
|
+
address: '0x1111111111111111111111111111111111111111',
|
|
101
|
+
decimals: 6,
|
|
102
|
+
symbol: 'TEST',
|
|
103
|
+
}),
|
|
104
|
+
{
|
|
105
|
+
addresses: { 1: '0x1111111111111111111111111111111111111111' },
|
|
106
|
+
decimals: 6,
|
|
107
|
+
},
|
|
108
|
+
) as unknown as Assets.ViemToken
|
|
109
|
+
|
|
110
|
+
expect(() =>
|
|
111
|
+
Assets.fromToken(tokenWithoutName, {
|
|
112
|
+
chainId: 1,
|
|
113
|
+
transfer: {
|
|
114
|
+
type: 'eip3009',
|
|
115
|
+
version: '2',
|
|
116
|
+
},
|
|
117
|
+
}),
|
|
118
|
+
).toThrow('EIP-3009 token assets require a token name.')
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('identifies currency kinds and converts networks', () => {
|
|
122
|
+
expect(Assets.isToken(usdc)).toBe(true)
|
|
123
|
+
expect(Assets.isToken(Assets.baseSepolia.USDC)).toBe(false)
|
|
124
|
+
expect(Assets.isRawAddress('0x1111111111111111111111111111111111111111')).toBe(true)
|
|
125
|
+
expect(Assets.isRawAddress(Assets.baseSepolia.USDC)).toBe(false)
|
|
126
|
+
expect(Assets.toNetwork(84532)).toBe('eip155:84532')
|
|
127
|
+
expect(Assets.toChainId('eip155:84532')).toBe(84532)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
test('resolves and matches raw addresses, known assets, and viem tokens', () => {
|
|
131
|
+
expect(Assets.resolve('0x1111111111111111111111111111111111111111', 'eip155:84532')).toEqual({
|
|
132
|
+
address: '0x1111111111111111111111111111111111111111',
|
|
133
|
+
})
|
|
134
|
+
expect(Assets.resolve(Assets.baseSepolia.USDC, 'eip155:84532')).toEqual({
|
|
135
|
+
address: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
|
|
136
|
+
decimals: 6,
|
|
137
|
+
transfer: {
|
|
138
|
+
name: 'USDC',
|
|
139
|
+
type: 'eip3009',
|
|
140
|
+
version: '2',
|
|
141
|
+
},
|
|
142
|
+
})
|
|
143
|
+
expect(Assets.resolve(Assets.baseSepolia.USDC, 'eip155:8453')).toBeUndefined()
|
|
144
|
+
expect(Assets.resolve(usdc, 'eip155:84532')).toEqual({
|
|
145
|
+
address: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
|
|
146
|
+
decimals: 6,
|
|
147
|
+
name: 'USD Coin',
|
|
148
|
+
})
|
|
149
|
+
expect(Assets.resolve(usdc, 'eip155:999999')).toBeUndefined()
|
|
150
|
+
expect(Assets.matches(usdc, '0x036CbD53842c5426634e7929541eC2318f3dCF7e', 'eip155:84532')).toBe(
|
|
151
|
+
true,
|
|
152
|
+
)
|
|
153
|
+
expect(Assets.matches(usdc, '0x036cbd53842c5426634e7929541ec2318f3dcf7e', 'eip155:84532')).toBe(
|
|
154
|
+
true,
|
|
155
|
+
)
|
|
156
|
+
expect(
|
|
157
|
+
Assets.matches(
|
|
158
|
+
'0x1111111111111111111111111111111111111111',
|
|
159
|
+
'0x1111111111111111111111111111111111111111',
|
|
160
|
+
'eip155:84532',
|
|
161
|
+
),
|
|
162
|
+
).toBe(true)
|
|
163
|
+
expect(
|
|
164
|
+
Assets.matches(
|
|
165
|
+
Assets.baseSepolia.USDC,
|
|
166
|
+
'0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
|
|
167
|
+
'eip155:84532',
|
|
168
|
+
),
|
|
169
|
+
).toBe(false)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
test('throws when creating assets from tokens unavailable on a chain', () => {
|
|
173
|
+
expect(() =>
|
|
174
|
+
Assets.fromToken(usdc, {
|
|
175
|
+
chainId: 999_999,
|
|
176
|
+
transfer: {
|
|
177
|
+
type: 'eip3009',
|
|
178
|
+
version: '2',
|
|
179
|
+
},
|
|
180
|
+
}),
|
|
181
|
+
).toThrow('Token has no address for chain id "999999".')
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
test('exports Base USDC metadata', () => {
|
|
185
|
+
expect(Assets.isAsset(Assets.base.USDC)).toBe(true)
|
|
186
|
+
expect(Assets.base.USDC).toMatchObject({
|
|
187
|
+
address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
|
|
188
|
+
decimals: 6,
|
|
189
|
+
network: 'eip155:8453',
|
|
190
|
+
transfer: {
|
|
191
|
+
name: 'USD Coin',
|
|
192
|
+
type: 'eip3009',
|
|
193
|
+
version: '2',
|
|
194
|
+
},
|
|
195
|
+
})
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
test('exports Base Sepolia USDC metadata', () => {
|
|
199
|
+
expect(Assets.isAsset(Assets.baseSepolia.USDC)).toBe(true)
|
|
200
|
+
expect(Assets.baseSepolia.USDC).toMatchObject({
|
|
201
|
+
address: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
|
|
202
|
+
decimals: 6,
|
|
203
|
+
network: 'eip155:84532',
|
|
204
|
+
transfer: {
|
|
205
|
+
name: 'USDC',
|
|
206
|
+
type: 'eip3009',
|
|
207
|
+
version: '2',
|
|
208
|
+
},
|
|
209
|
+
})
|
|
210
|
+
})
|
|
211
|
+
})
|
package/src/x402/Assets.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { getAddress } from 'viem'
|
|
2
|
+
import type { Token } from 'viem/tokens'
|
|
3
|
+
|
|
1
4
|
import type { Asset, EvmNetwork, ExactTransfer } from './Types.js'
|
|
2
5
|
|
|
3
6
|
const knownAsset = Symbol('mppx.x402.asset')
|
|
@@ -8,6 +11,12 @@ export type KnownAsset = Asset & {
|
|
|
8
11
|
network: EvmNetwork
|
|
9
12
|
}
|
|
10
13
|
|
|
14
|
+
/** Viem token metadata from `viem/tokens`. */
|
|
15
|
+
export type ViemToken = Token
|
|
16
|
+
|
|
17
|
+
/** Currency metadata accepted by EVM and x402 payment config. */
|
|
18
|
+
export type Currency = `0x${string}` | KnownAsset | ViemToken
|
|
19
|
+
|
|
11
20
|
/** Creates typed x402 asset metadata for custom tokens. */
|
|
12
21
|
export function define(parameters: define.Parameters): KnownAsset {
|
|
13
22
|
return {
|
|
@@ -28,6 +37,30 @@ export declare namespace define {
|
|
|
28
37
|
}
|
|
29
38
|
}
|
|
30
39
|
|
|
40
|
+
/** Creates x402 asset metadata from a `viem/tokens` token definition. */
|
|
41
|
+
export function fromToken(token: ViemToken, parameters: fromToken.Parameters): KnownAsset {
|
|
42
|
+
const resolved = token(parameters.chainId)
|
|
43
|
+
return define({
|
|
44
|
+
address: resolved.address,
|
|
45
|
+
decimals: resolved.decimals,
|
|
46
|
+
network: toNetwork(parameters.chainId),
|
|
47
|
+
transfer: withTokenDefaults(parameters.transfer, resolved),
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export declare namespace fromToken {
|
|
52
|
+
type Parameters = {
|
|
53
|
+
chainId: number
|
|
54
|
+
transfer: Transfer
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
type Transfer =
|
|
58
|
+
| (Omit<Extract<ExactTransfer, { type: 'eip3009' }>, 'name'> & {
|
|
59
|
+
name?: string | undefined
|
|
60
|
+
})
|
|
61
|
+
| Extract<ExactTransfer, { type: 'permit2' }>
|
|
62
|
+
}
|
|
63
|
+
|
|
31
64
|
/** Base network known assets. */
|
|
32
65
|
export const base = {
|
|
33
66
|
USDC: define({
|
|
@@ -63,3 +96,86 @@ export function isAsset(value: unknown): value is KnownAsset {
|
|
|
63
96
|
if (typeof value !== 'object' || value === null) return false
|
|
64
97
|
return (value as Partial<KnownAsset>)[knownAsset] === true
|
|
65
98
|
}
|
|
99
|
+
|
|
100
|
+
/** Returns true when a value is a `viem/tokens` token definition. */
|
|
101
|
+
export function isToken(value: unknown): value is ViemToken {
|
|
102
|
+
return (
|
|
103
|
+
typeof value === 'function' &&
|
|
104
|
+
typeof (value as Partial<ViemToken>).addresses === 'object' &&
|
|
105
|
+
typeof (value as Partial<ViemToken>).decimals === 'number'
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Returns true when a currency is a raw address without chain metadata. */
|
|
110
|
+
export function isRawAddress(currency: Currency): currency is `0x${string}` {
|
|
111
|
+
return typeof currency === 'string'
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Resolves currency metadata for an EVM network. */
|
|
115
|
+
export function resolve(currency: Currency, network: EvmNetwork): resolve.Result | undefined {
|
|
116
|
+
if (isAsset(currency)) {
|
|
117
|
+
if (currency.network !== network) return undefined
|
|
118
|
+
return {
|
|
119
|
+
address: currency.address,
|
|
120
|
+
decimals: currency.decimals,
|
|
121
|
+
transfer: currency.transfer,
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (isToken(currency)) {
|
|
126
|
+
const address = currency.addresses[toChainId(network)]
|
|
127
|
+
if (!address) return undefined
|
|
128
|
+
return {
|
|
129
|
+
address,
|
|
130
|
+
decimals: currency.decimals,
|
|
131
|
+
name: currency.name,
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
address: currency,
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export declare namespace resolve {
|
|
141
|
+
type Result = {
|
|
142
|
+
address: `0x${string}`
|
|
143
|
+
decimals?: number | undefined
|
|
144
|
+
name?: string | undefined
|
|
145
|
+
transfer?: ExactTransfer | undefined
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Returns true when a currency resolves to the accepted address on the network. */
|
|
150
|
+
export function matches(
|
|
151
|
+
currency: Currency,
|
|
152
|
+
acceptedCurrency: `0x${string}`,
|
|
153
|
+
network: EvmNetwork,
|
|
154
|
+
): boolean {
|
|
155
|
+
const resolved = resolve(currency, network)
|
|
156
|
+
if (!resolved) return false
|
|
157
|
+
return getAddress(resolved.address) === getAddress(acceptedCurrency)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Converts an EVM chain ID to a CAIP-2 network identifier. */
|
|
161
|
+
export function toNetwork(chainId: number): EvmNetwork {
|
|
162
|
+
return `eip155:${chainId}`
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Converts a CAIP-2 EVM network identifier to a chain ID. */
|
|
166
|
+
export function toChainId(network: EvmNetwork): number {
|
|
167
|
+
return Number(network.slice('eip155:'.length))
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function withTokenDefaults(
|
|
171
|
+
transfer: fromToken.Transfer,
|
|
172
|
+
token: ReturnType<ViemToken>,
|
|
173
|
+
): ExactTransfer {
|
|
174
|
+
if (transfer.type !== 'eip3009') return transfer
|
|
175
|
+
if (transfer.name) return { ...transfer, name: transfer.name }
|
|
176
|
+
if (!token.name) throw new Error('EIP-3009 token assets require a token name.')
|
|
177
|
+
return {
|
|
178
|
+
...transfer,
|
|
179
|
+
name: token.name,
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Challenge } from 'mppx'
|
|
2
2
|
import type { Account } from 'viem'
|
|
3
|
+
import { tokens } from 'viem/tokens'
|
|
3
4
|
import { describe, expect, test, vi } from 'vp/test'
|
|
4
5
|
|
|
5
6
|
import * as Chains from '../../evm/Chains.js'
|
|
@@ -78,6 +79,134 @@ describe('x402 exact credential helper', () => {
|
|
|
78
79
|
).rejects.toThrow('x402 exact raw currency allowlists require networks.')
|
|
79
80
|
})
|
|
80
81
|
|
|
82
|
+
test('uses known asset metadata for currency policy and maxAmount decimals', async () => {
|
|
83
|
+
const signTypedData = vi.fn(async () => '0x1234')
|
|
84
|
+
|
|
85
|
+
const credential = await createCredential({
|
|
86
|
+
challenge: challenge(),
|
|
87
|
+
config: {
|
|
88
|
+
account: {
|
|
89
|
+
...account,
|
|
90
|
+
signTypedData,
|
|
91
|
+
} as unknown as Account,
|
|
92
|
+
currencies: [Assets.baseSepolia.USDC],
|
|
93
|
+
maxAmount: '0.01',
|
|
94
|
+
},
|
|
95
|
+
context: {},
|
|
96
|
+
})
|
|
97
|
+
const paymentPayload = Header.decodePaymentSignature(credential)
|
|
98
|
+
|
|
99
|
+
expect(signTypedData).toHaveBeenCalledOnce()
|
|
100
|
+
expect(paymentPayload.accepted.asset).toBe(Assets.baseSepolia.USDC.address)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test('pins known asset currency policy to its network', async () => {
|
|
104
|
+
const signTypedData = vi.fn(async () => '0x1234')
|
|
105
|
+
|
|
106
|
+
await expect(
|
|
107
|
+
createCredential({
|
|
108
|
+
challenge: challenge({ network: 'eip155:8453' }),
|
|
109
|
+
config: {
|
|
110
|
+
account: {
|
|
111
|
+
...account,
|
|
112
|
+
signTypedData,
|
|
113
|
+
} as unknown as Account,
|
|
114
|
+
currencies: [Assets.baseSepolia.USDC],
|
|
115
|
+
maxAmount: '0.01',
|
|
116
|
+
},
|
|
117
|
+
context: {},
|
|
118
|
+
}),
|
|
119
|
+
).rejects.toThrow(
|
|
120
|
+
'x402 exact currency is not allowed: 0x036CbD53842c5426634e7929541eC2318f3dCF7e.',
|
|
121
|
+
)
|
|
122
|
+
expect(signTypedData).not.toHaveBeenCalled()
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('accepts viem token sets for currency policy and decimals', async () => {
|
|
126
|
+
const signTypedData = vi.fn(async () => '0x1234')
|
|
127
|
+
|
|
128
|
+
const credential = await createCredential({
|
|
129
|
+
challenge: challenge({ amount: '1000000' }),
|
|
130
|
+
config: {
|
|
131
|
+
account: {
|
|
132
|
+
...account,
|
|
133
|
+
signTypedData,
|
|
134
|
+
} as unknown as Account,
|
|
135
|
+
currencies: tokens.popular,
|
|
136
|
+
maxAmount: '1',
|
|
137
|
+
},
|
|
138
|
+
context: {},
|
|
139
|
+
})
|
|
140
|
+
const paymentPayload = Header.decodePaymentSignature(credential)
|
|
141
|
+
|
|
142
|
+
expect(signTypedData).toHaveBeenCalledOnce()
|
|
143
|
+
expect(paymentPayload.accepted.asset).toBe(Assets.baseSepolia.USDC.address)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
test('uses viem token decimals for maxAmount policy', async () => {
|
|
147
|
+
const signTypedData = vi.fn(async () => '0x1234')
|
|
148
|
+
|
|
149
|
+
await expect(
|
|
150
|
+
createCredential({
|
|
151
|
+
challenge: challenge({ amount: '1000001' }),
|
|
152
|
+
config: {
|
|
153
|
+
account: {
|
|
154
|
+
...account,
|
|
155
|
+
signTypedData,
|
|
156
|
+
} as unknown as Account,
|
|
157
|
+
currencies: tokens.popular,
|
|
158
|
+
maxAmount: '1',
|
|
159
|
+
},
|
|
160
|
+
context: {},
|
|
161
|
+
}),
|
|
162
|
+
).rejects.toThrow('x402 exact amount exceeds maxAmount.')
|
|
163
|
+
expect(signTypedData).not.toHaveBeenCalled()
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
test('accepts viem token sets through legacy assets policy', async () => {
|
|
167
|
+
const signTypedData = vi.fn(async () => '0x1234')
|
|
168
|
+
|
|
169
|
+
const credential = await createCredential({
|
|
170
|
+
challenge: challenge({ amount: '1000000' }),
|
|
171
|
+
config: {
|
|
172
|
+
account: {
|
|
173
|
+
...account,
|
|
174
|
+
signTypedData,
|
|
175
|
+
} as unknown as Account,
|
|
176
|
+
assets: tokens.popular,
|
|
177
|
+
maxAmount: '1',
|
|
178
|
+
},
|
|
179
|
+
context: {},
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
expect(signTypedData).toHaveBeenCalledOnce()
|
|
183
|
+
expect(Header.decodePaymentSignature(credential).accepted.asset).toBe(
|
|
184
|
+
Assets.baseSepolia.USDC.address,
|
|
185
|
+
)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
test('pins viem token currency policy to available networks', async () => {
|
|
189
|
+
const signTypedData = vi.fn(async () => '0x1234')
|
|
190
|
+
|
|
191
|
+
await expect(
|
|
192
|
+
createCredential({
|
|
193
|
+
challenge: challenge({ network: 'eip155:999999' }),
|
|
194
|
+
config: {
|
|
195
|
+
account: {
|
|
196
|
+
...account,
|
|
197
|
+
signTypedData,
|
|
198
|
+
} as unknown as Account,
|
|
199
|
+
currencies: tokens.popular,
|
|
200
|
+
maxAmount: '1',
|
|
201
|
+
},
|
|
202
|
+
context: {},
|
|
203
|
+
}),
|
|
204
|
+
).rejects.toThrow(
|
|
205
|
+
'x402 exact currency is not allowed: 0x036CbD53842c5426634e7929541eC2318f3dCF7e.',
|
|
206
|
+
)
|
|
207
|
+
expect(signTypedData).not.toHaveBeenCalled()
|
|
208
|
+
})
|
|
209
|
+
|
|
81
210
|
test('signs EIP-3009 exact payment payloads', async () => {
|
|
82
211
|
const signTypedData = vi.fn(async () => '0x1234')
|
|
83
212
|
const config = {
|
package/src/x402/client/Exact.ts
CHANGED
|
@@ -99,9 +99,9 @@ export type Config = {
|
|
|
99
99
|
/** Optional allowlist of supported EVM chain IDs. */
|
|
100
100
|
networks?: readonly number[] | undefined
|
|
101
101
|
/** Optional allowlist of supported currencies. */
|
|
102
|
-
currencies?: readonly
|
|
102
|
+
currencies?: readonly Assets.Currency[] | undefined
|
|
103
103
|
/** Legacy alias for `currencies`. */
|
|
104
|
-
assets?: readonly
|
|
104
|
+
assets?: readonly Assets.Currency[] | undefined
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
const authorizationTypes = {
|
|
@@ -125,12 +125,12 @@ function assertPolicy(parameters: Config, accepted: Types.PaymentRequirements) {
|
|
|
125
125
|
throw new Error(`x402 exact chain ID is not allowed: ${chainId}.`)
|
|
126
126
|
|
|
127
127
|
const currencies = parameters.currencies ?? parameters.assets
|
|
128
|
-
if (currencies?.some((currency) =>
|
|
128
|
+
if (currencies?.some((currency) => Assets.isRawAddress(currency)) && !parameters.networks?.length)
|
|
129
129
|
throw new Error('x402 exact raw currency allowlists require networks.')
|
|
130
130
|
if (currencies) {
|
|
131
131
|
const acceptedCurrency = getAddress(accepted.asset as `0x${string}`)
|
|
132
132
|
const allowed = currencies.some((currency) =>
|
|
133
|
-
|
|
133
|
+
Assets.matches(currency, acceptedCurrency, accepted.network),
|
|
134
134
|
)
|
|
135
135
|
if (!allowed) throw new Error(`x402 exact currency is not allowed: ${acceptedCurrency}.`)
|
|
136
136
|
}
|
|
@@ -149,19 +149,6 @@ function assertPolicy(parameters: Config, accepted: Types.PaymentRequirements) {
|
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
function addressOf(currency: `0x${string}` | Assets.KnownAsset): `0x${string}` {
|
|
153
|
-
return Assets.isAsset(currency) ? currency.address : currency
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function currencyMatches(
|
|
157
|
-
currency: `0x${string}` | Assets.KnownAsset,
|
|
158
|
-
acceptedCurrency: `0x${string}`,
|
|
159
|
-
network: Types.EvmNetwork,
|
|
160
|
-
): boolean {
|
|
161
|
-
if (getAddress(addressOf(currency)) !== acceptedCurrency) return false
|
|
162
|
-
return !Assets.isAsset(currency) || currency.network === network
|
|
163
|
-
}
|
|
164
|
-
|
|
165
152
|
function decimalsOfAcceptedCurrency(
|
|
166
153
|
parameters: Config,
|
|
167
154
|
accepted: Types.PaymentRequirements,
|
|
@@ -169,9 +156,10 @@ function decimalsOfAcceptedCurrency(
|
|
|
169
156
|
const currencies = parameters.currencies ?? parameters.assets
|
|
170
157
|
const acceptedCurrency = getAddress(accepted.asset as `0x${string}`)
|
|
171
158
|
const currency = currencies?.find((currency) =>
|
|
172
|
-
|
|
159
|
+
Assets.matches(currency, acceptedCurrency, accepted.network),
|
|
173
160
|
)
|
|
174
|
-
|
|
161
|
+
const resolved = currency ? Assets.resolve(currency, accepted.network) : undefined
|
|
162
|
+
if (resolved?.decimals !== undefined) return resolved.decimals
|
|
175
163
|
return parameters.decimals
|
|
176
164
|
}
|
|
177
165
|
|