@x402r/evm 0.0.2 → 0.0.3

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.
Files changed (89) hide show
  1. package/README.md +20 -16
  2. package/dist/cjs/escrow/client/index.cjs +227 -0
  3. package/dist/cjs/escrow/client/index.cjs.map +1 -0
  4. package/dist/cjs/escrow/client/index.d.cts +23 -0
  5. package/dist/cjs/escrow/client/index.d.ts +23 -0
  6. package/dist/cjs/escrow/client/index.js +223 -0
  7. package/dist/cjs/escrow/client/index.js.map +1 -0
  8. package/dist/cjs/escrow/facilitator/index.cjs +359 -0
  9. package/dist/cjs/escrow/facilitator/index.cjs.map +1 -0
  10. package/dist/cjs/escrow/facilitator/index.d.cts +53 -0
  11. package/dist/{escrow → cjs/escrow}/facilitator/index.d.ts +17 -13
  12. package/dist/cjs/escrow/facilitator/index.js +358 -0
  13. package/dist/cjs/escrow/facilitator/index.js.map +1 -0
  14. package/dist/cjs/escrow/server/index.cjs +222 -0
  15. package/dist/cjs/escrow/server/index.cjs.map +1 -0
  16. package/dist/cjs/escrow/server/index.d.cts +78 -0
  17. package/dist/{escrow → cjs/escrow}/server/index.d.ts +15 -9
  18. package/dist/cjs/escrow/server/index.js +217 -0
  19. package/dist/cjs/escrow/server/index.js.map +1 -0
  20. package/dist/{shared/types.d.ts → cjs/escrow/types/index.d.ts} +7 -6
  21. package/dist/cjs/escrow/types/index.js +40 -0
  22. package/dist/cjs/escrow/types/index.js.map +1 -0
  23. package/dist/cjs/index.cjs +215 -0
  24. package/dist/cjs/index.cjs.map +1 -0
  25. package/dist/cjs/index.d.cts +22 -0
  26. package/dist/cjs/index.d.ts +54 -0
  27. package/dist/cjs/index.js +223 -0
  28. package/dist/cjs/index.js.map +1 -0
  29. package/dist/cjs/scheme-CNrmuyp3.d.ts +22 -0
  30. package/dist/esm/chunk-DLIBGHEY.mjs +85 -0
  31. package/dist/esm/chunk-DLIBGHEY.mjs.map +1 -0
  32. package/dist/esm/chunk-IYUU7AJZ.mjs +187 -0
  33. package/dist/esm/chunk-IYUU7AJZ.mjs.map +1 -0
  34. package/dist/esm/chunk-JBHVAJN3.mjs +13 -0
  35. package/dist/esm/chunk-JBHVAJN3.mjs.map +1 -0
  36. package/dist/esm/chunk-NSSMTXJJ.mjs +8 -0
  37. package/dist/esm/chunk-NSSMTXJJ.mjs.map +1 -0
  38. package/dist/esm/escrow/client/index.d.mts +23 -0
  39. package/dist/esm/escrow/client/index.mjs +20 -0
  40. package/dist/esm/escrow/client/index.mjs.map +1 -0
  41. package/dist/esm/escrow/facilitator/index.d.mts +53 -0
  42. package/dist/esm/escrow/facilitator/index.mjs +230 -0
  43. package/dist/esm/escrow/facilitator/index.mjs.map +1 -0
  44. package/dist/esm/escrow/server/index.d.mts +78 -0
  45. package/dist/esm/escrow/server/index.mjs +191 -0
  46. package/dist/esm/escrow/server/index.mjs.map +1 -0
  47. package/dist/esm/index.d.mts +54 -0
  48. package/dist/esm/index.mjs +15 -0
  49. package/dist/esm/index.mjs.map +1 -0
  50. package/dist/esm/scheme-CNrmuyp3.d.mts +22 -0
  51. package/package.json +42 -16
  52. package/src/escrow/client/index.ts +3 -161
  53. package/src/escrow/client/register.ts +33 -0
  54. package/src/escrow/client/scheme.ts +107 -0
  55. package/src/escrow/facilitator/index.ts +3 -388
  56. package/src/escrow/facilitator/register.ts +33 -0
  57. package/src/escrow/facilitator/scheme.ts +289 -0
  58. package/src/escrow/index.ts +3 -0
  59. package/src/escrow/server/index.ts +3 -261
  60. package/src/escrow/server/register.ts +34 -0
  61. package/src/escrow/server/scheme.ts +226 -0
  62. package/src/escrow/shared/constants.ts +65 -0
  63. package/src/escrow/shared/nonce.ts +175 -0
  64. package/src/escrow/shared/types.ts +69 -0
  65. package/src/escrow/shared/utils.ts +16 -0
  66. package/dist/escrow/client/index.d.ts +0 -40
  67. package/dist/escrow/client/index.d.ts.map +0 -1
  68. package/dist/escrow/client/index.js +0 -104
  69. package/dist/escrow/client/index.js.map +0 -1
  70. package/dist/escrow/facilitator/index.d.ts.map +0 -1
  71. package/dist/escrow/facilitator/index.js +0 -300
  72. package/dist/escrow/facilitator/index.js.map +0 -1
  73. package/dist/escrow/server/index.d.ts.map +0 -1
  74. package/dist/escrow/server/index.js +0 -214
  75. package/dist/escrow/server/index.js.map +0 -1
  76. package/dist/shared/constants.d.ts +0 -112
  77. package/dist/shared/constants.d.ts.map +0 -1
  78. package/dist/shared/constants.js +0 -51
  79. package/dist/shared/constants.js.map +0 -1
  80. package/dist/shared/nonce.d.ts +0 -41
  81. package/dist/shared/nonce.d.ts.map +0 -1
  82. package/dist/shared/nonce.js +0 -154
  83. package/dist/shared/nonce.js.map +0 -1
  84. package/dist/shared/types.d.ts.map +0 -1
  85. package/dist/shared/types.js +0 -21
  86. package/dist/shared/types.js.map +0 -1
  87. package/src/shared/constants.ts +0 -58
  88. package/src/shared/nonce.ts +0 -203
  89. package/src/shared/types.ts +0 -69
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Escrow Scheme - Server
3
+ * Handles price parsing and requirement enhancement for resource servers.
4
+ *
5
+ * Implements x402's SchemeNetworkServer interface so it can be registered
6
+ * on an x402ResourceServer via server.register('eip155:84532', new EscrowServerScheme()).
7
+ */
8
+
9
+ import type {
10
+ AssetAmount,
11
+ MoneyParser,
12
+ Network,
13
+ PaymentRequirements,
14
+ Price,
15
+ SchemeNetworkServer,
16
+ } from '@x402/core/types'
17
+
18
+ /**
19
+ * Asset info including EIP-712 domain parameters per network
20
+ */
21
+ const ASSET_INFO: Record<
22
+ string,
23
+ { address: string; name: string; version: string; decimals: number }
24
+ > = {
25
+ // Base Sepolia
26
+ 'eip155:84532': {
27
+ address: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
28
+ name: 'USDC',
29
+ version: '2',
30
+ decimals: 6,
31
+ },
32
+ // Base mainnet
33
+ 'eip155:8453': {
34
+ address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
35
+ name: 'USD Coin',
36
+ version: '2',
37
+ decimals: 6,
38
+ },
39
+ // Ethereum Sepolia
40
+ 'eip155:11155111': {
41
+ address: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
42
+ name: 'USDC',
43
+ version: '2',
44
+ decimals: 6,
45
+ },
46
+ // Ethereum mainnet
47
+ 'eip155:1': {
48
+ address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
49
+ name: 'USD Coin',
50
+ version: '2',
51
+ decimals: 6,
52
+ },
53
+ // Polygon
54
+ 'eip155:137': {
55
+ address: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',
56
+ name: 'USD Coin',
57
+ version: '2',
58
+ decimals: 6,
59
+ },
60
+ // Arbitrum
61
+ 'eip155:42161': {
62
+ address: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
63
+ name: 'USD Coin',
64
+ version: '2',
65
+ decimals: 6,
66
+ },
67
+ // Celo
68
+ 'eip155:42220': {
69
+ address: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
70
+ name: 'USD Coin',
71
+ version: '2',
72
+ decimals: 6,
73
+ },
74
+ // Monad
75
+ 'eip155:143': {
76
+ address: '0x754704Bc059F8C67012fEd69BC8A327a5aafb603',
77
+ name: 'USDC',
78
+ version: '2',
79
+ decimals: 6,
80
+ },
81
+ // Avalanche
82
+ 'eip155:43114': {
83
+ address: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',
84
+ name: 'USD Coin',
85
+ version: '2',
86
+ decimals: 6,
87
+ },
88
+ }
89
+
90
+ /**
91
+ * Convert decimal amount to token units using string-based conversion
92
+ * (e.g., 0.10 -> 100000 for 6-decimal tokens)
93
+ * Avoids floating-point precision issues from BigInt(Math.round(...))
94
+ */
95
+ function convertToTokenAmount(decimalAmount: string, decimals: number): string {
96
+ const amount = parseFloat(decimalAmount)
97
+ if (isNaN(amount)) {
98
+ throw new Error(`Invalid amount: ${decimalAmount}`)
99
+ }
100
+ const [intPart, decPart = ''] = String(amount).split('.')
101
+ const paddedDec = decPart.padEnd(decimals, '0').slice(0, decimals)
102
+ const tokenAmount = (intPart + paddedDec).replace(/^0+/, '') || '0'
103
+ return tokenAmount
104
+ }
105
+
106
+ /**
107
+ * Server scheme - handles price parsing and requirement enhancement.
108
+ * Implements x402's SchemeNetworkServer interface.
109
+ */
110
+ export class EscrowServerScheme implements SchemeNetworkServer {
111
+ readonly scheme = 'escrow'
112
+ private moneyParsers: MoneyParser[] = []
113
+
114
+ /**
115
+ * Register a custom money parser in the parser chain.
116
+ * Multiple parsers can be registered — they will be tried in registration order.
117
+ * Each parser receives a decimal amount (e.g., 1.50 for $1.50).
118
+ * If a parser returns null, the next parser in the chain will be tried.
119
+ * The default parser (USDC) is always the final fallback.
120
+ *
121
+ * @param parser - Custom function to convert amount to AssetAmount (or null to skip)
122
+ * @returns The server instance for chaining
123
+ */
124
+ registerMoneyParser(parser: MoneyParser): EscrowServerScheme {
125
+ this.moneyParsers.push(parser)
126
+ return this
127
+ }
128
+
129
+ /**
130
+ * Parse a price into an x402 AssetAmount.
131
+ *
132
+ * Accepts x402's Price type:
133
+ * - string: "$0.01", "0.01", "10000"
134
+ * - number: 0.01
135
+ * - AssetAmount: { asset: "0x...", amount: "10000" }
136
+ */
137
+ async parsePrice(price: Price, network: Network): Promise<AssetAmount> {
138
+ // If already an AssetAmount, pass through with validation
139
+ if (typeof price === 'object' && price !== null && 'amount' in price) {
140
+ if (!price.asset) {
141
+ throw new Error(`Asset address must be specified for AssetAmount on network ${network}`)
142
+ }
143
+ return {
144
+ amount: price.amount,
145
+ asset: price.asset,
146
+ extra: price.extra || {},
147
+ }
148
+ }
149
+
150
+ // Parse Money to decimal number
151
+ const numericAmount = this.parseMoneyToDecimal(price)
152
+
153
+ // Try each custom money parser in order
154
+ for (const parser of this.moneyParsers) {
155
+ const result = await parser(numericAmount, network)
156
+ if (result !== null) {
157
+ return result
158
+ }
159
+ }
160
+
161
+ // All custom parsers returned null (or none registered), use default conversion
162
+ return this.defaultMoneyConversion(numericAmount, network)
163
+ }
164
+
165
+ /**
166
+ * Parse Money (string | number) to a decimal number.
167
+ */
168
+ private parseMoneyToDecimal(money: string | number): number {
169
+ if (typeof money === 'number') {
170
+ return money
171
+ }
172
+ const cleaned = String(money).replace(/[$,]/g, '').trim()
173
+ const amount = parseFloat(cleaned)
174
+ if (isNaN(amount)) {
175
+ throw new Error(`Cannot parse price: ${money}`)
176
+ }
177
+ return amount
178
+ }
179
+
180
+ /**
181
+ * Default money conversion — converts decimal amount to the default stablecoin on the network.
182
+ */
183
+ private defaultMoneyConversion(amount: number, network: Network): AssetAmount {
184
+ const assetInfo = ASSET_INFO[network]
185
+ if (!assetInfo) {
186
+ throw new Error(`No USDC address configured for network: ${network}`)
187
+ }
188
+
189
+ const tokenAmount = convertToTokenAmount(String(amount), assetInfo.decimals)
190
+
191
+ return {
192
+ asset: assetInfo.address,
193
+ amount: tokenAmount,
194
+ extra: {
195
+ name: assetInfo.name,
196
+ version: assetInfo.version,
197
+ },
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Enhance payment requirements with facilitator's extra fields.
203
+ *
204
+ * Merges supportedKind.extra (from facilitator's /supported endpoint) into
205
+ * the requirements, so escrow addresses flow from facilitator → merchant
206
+ * requirements automatically.
207
+ */
208
+ async enhancePaymentRequirements(
209
+ requirements: PaymentRequirements,
210
+ supportedKind: {
211
+ x402Version: number
212
+ scheme: string
213
+ network: Network
214
+ extra?: Record<string, unknown>
215
+ },
216
+ _facilitatorExtensions: string[],
217
+ ): Promise<PaymentRequirements> {
218
+ return {
219
+ ...requirements,
220
+ extra: {
221
+ ...supportedKind.extra,
222
+ ...requirements.extra,
223
+ },
224
+ }
225
+ }
226
+ }
@@ -0,0 +1,65 @@
1
+ export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' as const
2
+ export const MAX_UINT48 = 281474976710655
3
+ export const MAX_UINT32 = 4294967295
4
+
5
+ // PaymentInfo struct for AuthCaptureEscrow (matches commerce-payments contract)
6
+ export const PAYMENT_INFO_COMPONENTS = [
7
+ { name: 'operator', type: 'address' },
8
+ { name: 'payer', type: 'address' },
9
+ { name: 'receiver', type: 'address' },
10
+ { name: 'token', type: 'address' },
11
+ { name: 'maxAmount', type: 'uint120' },
12
+ { name: 'preApprovalExpiry', type: 'uint48' },
13
+ { name: 'authorizationExpiry', type: 'uint48' },
14
+ { name: 'refundExpiry', type: 'uint48' },
15
+ { name: 'minFeeBps', type: 'uint16' },
16
+ { name: 'maxFeeBps', type: 'uint16' },
17
+ { name: 'feeReceiver', type: 'address' },
18
+ { name: 'salt', type: 'uint256' },
19
+ ] as const
20
+
21
+ export const OPERATOR_ABI = [
22
+ {
23
+ name: 'authorize',
24
+ type: 'function',
25
+ stateMutability: 'nonpayable',
26
+ inputs: [
27
+ {
28
+ name: 'paymentInfo',
29
+ type: 'tuple',
30
+ components: PAYMENT_INFO_COMPONENTS,
31
+ },
32
+ { name: 'amount', type: 'uint256' },
33
+ { name: 'tokenCollector', type: 'address' },
34
+ { name: 'collectorData', type: 'bytes' },
35
+ ],
36
+ outputs: [],
37
+ },
38
+ ] as const
39
+
40
+ // ERC-3009 TransferWithAuthorization type hash
41
+ export const TRANSFER_WITH_AUTHORIZATION_TYPEHASH =
42
+ '0x7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267' as const
43
+
44
+ // ERC-3009 ReceiveWithAuthorization EIP-712 types
45
+ export const RECEIVE_AUTHORIZATION_TYPES = {
46
+ ReceiveWithAuthorization: [
47
+ { name: 'from', type: 'address' },
48
+ { name: 'to', type: 'address' },
49
+ { name: 'value', type: 'uint256' },
50
+ { name: 'validAfter', type: 'uint256' },
51
+ { name: 'validBefore', type: 'uint256' },
52
+ { name: 'nonce', type: 'bytes32' },
53
+ ],
54
+ } as const
55
+
56
+ // ERC-20 balanceOf ABI for balance checks
57
+ export const ERC20_BALANCE_OF_ABI = [
58
+ {
59
+ name: 'balanceOf',
60
+ type: 'function',
61
+ stateMutability: 'view',
62
+ inputs: [{ name: 'account', type: 'address' }],
63
+ outputs: [{ name: 'balance', type: 'uint256' }],
64
+ },
65
+ ] as const
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Nonce computation and ERC-3009 signing utilities
3
+ * Adapted from @agentokratia/x402-escrow (MIT)
4
+ */
5
+
6
+ import { encodeAbiParameters, getAddress, keccak256, toHex } from 'viem'
7
+ import type { ClientEvmSigner } from '@x402/evm'
8
+ import { ZERO_ADDRESS, RECEIVE_AUTHORIZATION_TYPES } from './constants'
9
+ import type { EscrowExtra, EscrowPayload } from './types'
10
+
11
+ /**
12
+ * PaymentInfo typehash - must match AuthCaptureEscrow.PAYMENT_INFO_TYPEHASH
13
+ */
14
+ const PAYMENT_INFO_TYPEHASH = keccak256(
15
+ new TextEncoder().encode(
16
+ 'PaymentInfo(address operator,address payer,address receiver,address token,uint120 maxAmount,uint48 preApprovalExpiry,uint48 authorizationExpiry,uint48 refundExpiry,uint16 minFeeBps,uint16 maxFeeBps,address feeReceiver,uint256 salt)',
17
+ ),
18
+ )
19
+
20
+ /**
21
+ * Compute escrow nonce for ERC-3009 authorization
22
+ * Must match AuthCaptureEscrow.getHash() with payer=address(0)
23
+ */
24
+ export function computeEscrowNonce(
25
+ chainId: number,
26
+ escrowAddress: `0x${string}`,
27
+ paymentInfo: EscrowPayload['paymentInfo'],
28
+ ): `0x${string}` {
29
+ // Step 1: Encode paymentInfo with payer=0 (payer-agnostic)
30
+ const paymentInfoEncoded = encodeAbiParameters(
31
+ [
32
+ { name: 'typehash', type: 'bytes32' },
33
+ { name: 'operator', type: 'address' },
34
+ { name: 'payer', type: 'address' },
35
+ { name: 'receiver', type: 'address' },
36
+ { name: 'token', type: 'address' },
37
+ { name: 'maxAmount', type: 'uint120' },
38
+ { name: 'preApprovalExpiry', type: 'uint48' },
39
+ { name: 'authorizationExpiry', type: 'uint48' },
40
+ { name: 'refundExpiry', type: 'uint48' },
41
+ { name: 'minFeeBps', type: 'uint16' },
42
+ { name: 'maxFeeBps', type: 'uint16' },
43
+ { name: 'feeReceiver', type: 'address' },
44
+ { name: 'salt', type: 'uint256' },
45
+ ],
46
+ [
47
+ PAYMENT_INFO_TYPEHASH,
48
+ paymentInfo.operator,
49
+ ZERO_ADDRESS, // payer-agnostic
50
+ paymentInfo.receiver,
51
+ paymentInfo.token,
52
+ BigInt(paymentInfo.maxAmount),
53
+ paymentInfo.preApprovalExpiry,
54
+ paymentInfo.authorizationExpiry,
55
+ paymentInfo.refundExpiry,
56
+ paymentInfo.minFeeBps,
57
+ paymentInfo.maxFeeBps,
58
+ paymentInfo.feeReceiver,
59
+ BigInt(paymentInfo.salt),
60
+ ],
61
+ )
62
+ const paymentInfoHash = keccak256(paymentInfoEncoded)
63
+
64
+ // Step 2: Encode (chainId, escrow, paymentInfoHash) and hash
65
+ const outerEncoded = encodeAbiParameters(
66
+ [
67
+ { name: 'chainId', type: 'uint256' },
68
+ { name: 'escrow', type: 'address' },
69
+ { name: 'paymentInfoHash', type: 'bytes32' },
70
+ ],
71
+ [BigInt(chainId), escrowAddress, paymentInfoHash],
72
+ )
73
+
74
+ return keccak256(outerEncoded)
75
+ }
76
+
77
+ /**
78
+ * Sign ERC-3009 ReceiveWithAuthorization
79
+ * Note: receiveWithAuthorization uses a different primary type than transferWithAuthorization
80
+ */
81
+ export async function signERC3009(
82
+ signer: ClientEvmSigner,
83
+ authorization: EscrowPayload['authorization'],
84
+ extra: EscrowExtra,
85
+ tokenAddress: `0x${string}`,
86
+ chainId: number,
87
+ ): Promise<`0x${string}`> {
88
+ // EIP-712 domain - name must match the token's EIP-712 domain
89
+ // (e.g., "USDC" for Base USDC, not "USD Coin")
90
+ const domain = {
91
+ name: extra.name,
92
+ version: extra.version,
93
+ chainId,
94
+ verifyingContract: getAddress(tokenAddress),
95
+ }
96
+
97
+ const message = {
98
+ from: getAddress(authorization.from),
99
+ to: getAddress(authorization.to),
100
+ value: BigInt(authorization.value),
101
+ validAfter: BigInt(authorization.validAfter),
102
+ validBefore: BigInt(authorization.validBefore),
103
+ nonce: authorization.nonce,
104
+ }
105
+
106
+ return signer.signTypedData({
107
+ domain,
108
+ types: RECEIVE_AUTHORIZATION_TYPES,
109
+ primaryType: 'ReceiveWithAuthorization',
110
+ message,
111
+ })
112
+ }
113
+
114
+ /**
115
+ * Verify ERC-3009 signature (facilitator-side)
116
+ * @param signer - The signer with verifyTypedData method
117
+ * @param authorization - ERC-3009 authorization data
118
+ * @param signature - The signature to verify
119
+ * @param extra - Extra configuration including chainId
120
+ * @param tokenAddress - The token contract address (verifyingContract for EIP-712)
121
+ */
122
+ export async function verifyERC3009Signature(
123
+ signer: {
124
+ verifyTypedData: (_args: {
125
+ address: `0x${string}`
126
+ domain: Record<string, unknown>
127
+ types: Record<string, unknown>
128
+ primaryType: string
129
+ message: Record<string, unknown>
130
+ signature: `0x${string}`
131
+ }) => Promise<boolean>
132
+ },
133
+ authorization: EscrowPayload['authorization'],
134
+ signature: `0x${string}`,
135
+ extra: EscrowExtra & { chainId: number },
136
+ tokenAddress: `0x${string}`,
137
+ ): Promise<boolean> {
138
+ const domain = {
139
+ name: extra.name,
140
+ version: extra.version,
141
+ chainId: extra.chainId,
142
+ verifyingContract: getAddress(tokenAddress),
143
+ }
144
+
145
+ const message = {
146
+ from: getAddress(authorization.from),
147
+ to: getAddress(authorization.to),
148
+ value: BigInt(authorization.value),
149
+ validAfter: BigInt(authorization.validAfter),
150
+ validBefore: BigInt(authorization.validBefore),
151
+ nonce: authorization.nonce,
152
+ }
153
+
154
+ try {
155
+ return await signer.verifyTypedData({
156
+ address: getAddress(authorization.from),
157
+ domain,
158
+ types: RECEIVE_AUTHORIZATION_TYPES,
159
+ primaryType: 'ReceiveWithAuthorization',
160
+ message,
161
+ signature,
162
+ })
163
+ } catch {
164
+ return false
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Generate random salt for paymentInfo
170
+ */
171
+ export function generateSalt(): `0x${string}` {
172
+ const bytes = new Uint8Array(32)
173
+ crypto.getRandomValues(bytes)
174
+ return toHex(bytes)
175
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Type guard for EscrowPayload
3
+ */
4
+ export function isEscrowPayload(value: unknown): value is EscrowPayload {
5
+ return (
6
+ typeof value === 'object' &&
7
+ value !== null &&
8
+ 'authorization' in value &&
9
+ 'signature' in value &&
10
+ 'paymentInfo' in value
11
+ )
12
+ }
13
+
14
+ /**
15
+ * Type guard for EscrowExtra
16
+ */
17
+ export function isEscrowExtra(value: unknown): value is EscrowExtra {
18
+ return (
19
+ typeof value === 'object' &&
20
+ value !== null &&
21
+ 'escrowAddress' in value &&
22
+ 'operatorAddress' in value &&
23
+ 'tokenCollector' in value
24
+ )
25
+ }
26
+
27
+ // EscrowExtra - fields in PaymentRequirements.extra
28
+ export interface EscrowExtra {
29
+ escrowAddress: `0x${string}`
30
+ operatorAddress: `0x${string}`
31
+ tokenCollector: `0x${string}`
32
+ authorizeAddress?: `0x${string}`
33
+ minDeposit?: string
34
+ maxDeposit?: string
35
+ preApprovalExpirySeconds?: number
36
+ authorizationExpirySeconds?: number
37
+ refundExpirySeconds?: number
38
+ minFeeBps?: number
39
+ maxFeeBps?: number
40
+ feeReceiver?: `0x${string}`
41
+ name: string // EIP-712 domain name (e.g., "USDC" for Base USDC)
42
+ version: string // EIP-712 domain version (e.g., "2" for USDC)
43
+ }
44
+
45
+ // EscrowPayload - the payload field in PaymentPayload
46
+ export interface EscrowPayload {
47
+ authorization: {
48
+ from: `0x${string}`
49
+ to: `0x${string}`
50
+ value: string
51
+ validAfter: string
52
+ validBefore: string
53
+ nonce: `0x${string}`
54
+ }
55
+ signature: `0x${string}`
56
+ paymentInfo: {
57
+ operator: `0x${string}`
58
+ receiver: `0x${string}`
59
+ token: `0x${string}`
60
+ maxAmount: string
61
+ preApprovalExpiry: number
62
+ authorizationExpiry: number
63
+ refundExpiry: number
64
+ minFeeBps: number
65
+ maxFeeBps: number
66
+ feeReceiver: `0x${string}`
67
+ salt: `0x${string}`
68
+ }
69
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Parse chainId from CAIP-2 network identifier
3
+ * @param network - CAIP-2 network identifier (e.g., 'eip155:84532')
4
+ * @returns The chain ID as a number
5
+ */
6
+ export function parseChainId(network: string): number {
7
+ const parts = network.split(':')
8
+ if (parts.length !== 2 || parts[0] !== 'eip155') {
9
+ throw new Error(`Invalid network format: ${network}. Expected 'eip155:<chainId>'`)
10
+ }
11
+ const chainId = parseInt(parts[1], 10)
12
+ if (isNaN(chainId)) {
13
+ throw new Error(`Invalid chainId in network: ${network}`)
14
+ }
15
+ return chainId
16
+ }
@@ -1,40 +0,0 @@
1
- /**
2
- * Escrow Scheme - Client
3
- * Creates payment payloads for escrow payments.
4
- *
5
- * Implements x402's SchemeNetworkClient interface so it can be registered
6
- * on an x402Client via client.register('eip155:84532', new EscrowEvmScheme(signer)).
7
- */
8
- import type { Network, PaymentPayload, PaymentRequirements, SchemeNetworkClient } from "@x402/core/types";
9
- import type { ClientEvmSigner } from "@x402/evm";
10
- import { x402Client } from "@x402/core/client";
11
- /**
12
- * Escrow Client Scheme - implements x402's SchemeNetworkClient
13
- */
14
- export declare class EscrowEvmScheme implements SchemeNetworkClient {
15
- private readonly signer;
16
- readonly scheme = "escrow";
17
- constructor(signer: ClientEvmSigner);
18
- createPaymentPayload(x402Version: number, requirements: PaymentRequirements): Promise<Pick<PaymentPayload, "x402Version" | "payload">>;
19
- }
20
- /**
21
- * Register escrow client scheme with x402Client
22
- *
23
- * @example
24
- * ```typescript
25
- * const client = new x402Client();
26
- * registerEscrowScheme(client, { signer, networks: "eip155:84532" });
27
- * ```
28
- */
29
- export declare function registerEscrowScheme(client: x402Client, config: {
30
- signer: ClientEvmSigner;
31
- networks: Network | Network[];
32
- }): x402Client;
33
- /**
34
- * @deprecated Use `new EscrowEvmScheme(signer)` directly
35
- */
36
- export declare const EscrowScheme: {
37
- scheme: "escrow";
38
- };
39
- export type { EscrowExtra, EscrowPayload } from "../../shared/types.js";
40
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/escrow/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,OAAO,EACP,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AA0B/C;;GAEG;AACH,qBAAa,eAAgB,YAAW,mBAAmB;IAG7C,OAAO,CAAC,QAAQ,CAAC,MAAM;IAFnC,QAAQ,CAAC,MAAM,YAAY;gBAEE,MAAM,EAAE,eAAe;IAE9C,oBAAoB,CACxB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,mBAAmB,GAChC,OAAO,CAAC,IAAI,CAAC,cAAc,EAAE,aAAa,GAAG,SAAS,CAAC,CAAC;CA4E5D;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE;IAAE,MAAM,EAAE,eAAe,CAAC;IAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,EAAE,CAAA;CAAE,GACjE,UAAU,CASZ;AAED;;GAEG;AACH,eAAO,MAAM,YAAY;;CAExB,CAAC;AAEF,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC"}
@@ -1,104 +0,0 @@
1
- /**
2
- * Escrow Scheme - Client
3
- * Creates payment payloads for escrow payments.
4
- *
5
- * Implements x402's SchemeNetworkClient interface so it can be registered
6
- * on an x402Client via client.register('eip155:84532', new EscrowEvmScheme(signer)).
7
- */
8
- import { computeEscrowNonce, signERC3009, generateSalt, } from "../../shared/nonce.js";
9
- import { MAX_UINT48 } from "../../shared/constants.js";
10
- /**
11
- * Parse chainId from CAIP-2 network identifier
12
- */
13
- function parseChainId(network) {
14
- const parts = network.split(":");
15
- if (parts.length !== 2 || parts[0] !== "eip155") {
16
- throw new Error(`Invalid network format: ${network}. Expected 'eip155:<chainId>'`);
17
- }
18
- const chainId = parseInt(parts[1], 10);
19
- if (isNaN(chainId)) {
20
- throw new Error(`Invalid chainId in network: ${network}`);
21
- }
22
- return chainId;
23
- }
24
- /**
25
- * Escrow Client Scheme - implements x402's SchemeNetworkClient
26
- */
27
- export class EscrowEvmScheme {
28
- signer;
29
- scheme = "escrow";
30
- constructor(signer) {
31
- this.signer = signer;
32
- }
33
- async createPaymentPayload(x402Version, requirements) {
34
- if (x402Version !== 2) {
35
- throw new Error(`Unsupported x402Version: ${x402Version}. Only version 2 is supported.`);
36
- }
37
- const extra = requirements.extra;
38
- // Validate required EIP-712 domain parameters (M3, M10)
39
- if (!extra.name) {
40
- throw new Error(`EIP-712 domain parameter 'name' is required in payment requirements for asset ${requirements.asset}`);
41
- }
42
- if (!extra.version) {
43
- throw new Error(`EIP-712 domain parameter 'version' is required in payment requirements for asset ${requirements.asset}`);
44
- }
45
- const { escrowAddress, operatorAddress, tokenCollector, minFeeBps = 0, maxFeeBps = 0, feeReceiver, preApprovalExpirySeconds, refundExpirySeconds, authorizationExpirySeconds, } = extra;
46
- const chainId = parseChainId(requirements.network);
47
- const maxAmount = requirements.amount;
48
- const paymentInfo = {
49
- operator: operatorAddress,
50
- receiver: requirements.payTo,
51
- token: requirements.asset,
52
- maxAmount,
53
- preApprovalExpiry: preApprovalExpirySeconds ?? MAX_UINT48,
54
- authorizationExpiry: authorizationExpirySeconds ?? MAX_UINT48,
55
- refundExpiry: refundExpirySeconds ?? MAX_UINT48,
56
- minFeeBps,
57
- maxFeeBps,
58
- feeReceiver: feeReceiver ?? operatorAddress,
59
- salt: generateSalt(),
60
- };
61
- const nonce = computeEscrowNonce(chainId, escrowAddress, paymentInfo);
62
- // ERC-3009 authorization - validBefore MUST match what contract passes to receiveWithAuthorization
63
- // The contract uses paymentInfo.preApprovalExpiry as validBefore
64
- const authorization = {
65
- from: this.signer.address,
66
- to: tokenCollector,
67
- value: maxAmount,
68
- validAfter: "0",
69
- validBefore: String(paymentInfo.preApprovalExpiry),
70
- nonce,
71
- };
72
- const signature = await signERC3009(this.signer, authorization, extra, requirements.asset, chainId);
73
- return {
74
- x402Version,
75
- payload: { authorization, signature, paymentInfo },
76
- };
77
- }
78
- }
79
- /**
80
- * Register escrow client scheme with x402Client
81
- *
82
- * @example
83
- * ```typescript
84
- * const client = new x402Client();
85
- * registerEscrowScheme(client, { signer, networks: "eip155:84532" });
86
- * ```
87
- */
88
- export function registerEscrowScheme(client, config) {
89
- const scheme = new EscrowEvmScheme(config.signer);
90
- const networks = Array.isArray(config.networks)
91
- ? config.networks
92
- : [config.networks];
93
- for (const network of networks) {
94
- client.register(network, scheme);
95
- }
96
- return client;
97
- }
98
- /**
99
- * @deprecated Use `new EscrowEvmScheme(signer)` directly
100
- */
101
- export const EscrowScheme = {
102
- scheme: "escrow",
103
- };
104
- //# sourceMappingURL=index.js.map