uvd-x402-sdk 0.2.1__py3-none-any.whl → 0.2.3__py3-none-any.whl

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.
uvd_x402_sdk/models.py CHANGED
@@ -1,397 +1,397 @@
1
- """
2
- Pydantic models for x402 payment data structures.
3
-
4
- These models define the structure of payloads exchanged between:
5
- - Frontend -> Backend (X-PAYMENT header)
6
- - Backend -> Facilitator (verify/settle requests)
7
- - Facilitator -> Backend (responses)
8
-
9
- Supports both x402 v1 and v2 protocols:
10
- - v1: network as string ("base", "solana")
11
- - v2: network as CAIP-2 ("eip155:8453", "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")
12
- """
13
-
14
- from decimal import Decimal
15
- from typing import Any, Dict, List, Literal, Optional, Union
16
- from pydantic import BaseModel, Field, field_validator
17
-
18
-
19
- # =============================================================================
20
- # Network-Specific Payload Content Models
21
- # =============================================================================
22
-
23
-
24
- class EVMAuthorization(BaseModel):
25
- """
26
- ERC-3009 TransferWithAuthorization data for EVM chains.
27
-
28
- This represents a signed EIP-712 message authorizing USDC transfer.
29
- The facilitator uses this to call transferWithAuthorization() on-chain.
30
- """
31
-
32
- from_address: str = Field(..., alias="from", description="Sender wallet address")
33
- to: str = Field(..., description="Recipient wallet address")
34
- value: str = Field(..., description="Amount in token base units (e.g., 1000000 for $1 USDC)")
35
- validAfter: str = Field(..., description="Unix timestamp after which auth is valid")
36
- validBefore: str = Field(..., description="Unix timestamp before which auth is valid")
37
- nonce: str = Field(..., description="Random 32-byte nonce (prevents replay attacks)")
38
-
39
- class Config:
40
- populate_by_name = True
41
-
42
-
43
- class EVMPayloadContent(BaseModel):
44
- """
45
- Complete EVM payment payload with signature and authorization.
46
- """
47
-
48
- signature: str = Field(..., description="Full signature (r + s + v)")
49
- authorization: EVMAuthorization
50
-
51
-
52
- class SVMPayloadContent(BaseModel):
53
- """
54
- SVM (Solana Virtual Machine) payment payload containing a partially-signed transaction.
55
-
56
- Works with all SVM-compatible chains: Solana, Fogo, etc.
57
-
58
- The transaction structure must follow the facilitator's requirements:
59
- 1. Instruction 0: SetComputeUnitLimit (units: 20,000)
60
- 2. Instruction 1: SetComputeUnitPrice (microLamports: 1)
61
- 3. Instruction 2: TransferChecked (USDC transfer)
62
-
63
- Fee payer is the facilitator - user signature only authorizes USDC transfer.
64
- """
65
-
66
- transaction: str = Field(
67
- ..., description="Base64-encoded partially-signed VersionedTransaction"
68
- )
69
-
70
-
71
- # Alias for backward compatibility
72
- SolanaPayloadContent = SVMPayloadContent
73
-
74
-
75
- class NEARPayloadContent(BaseModel):
76
- """
77
- NEAR payment payload using NEP-366 meta-transactions.
78
-
79
- Contains a Borsh-serialized SignedDelegateAction that wraps ft_transfer.
80
- User signs the delegate action, facilitator submits and pays gas.
81
-
82
- NEP-366 Structure:
83
- - DelegateAction: sender_id, receiver_id, actions, nonce, max_block_height, public_key
84
- - SignedDelegateAction: delegate_action + ed25519 signature
85
- - Hash prefix: 2^30 + 366 = 0x4000016E (little-endian)
86
- """
87
-
88
- signedDelegateAction: str = Field(
89
- ..., description="Base64 Borsh-encoded SignedDelegateAction"
90
- )
91
-
92
-
93
- class StellarPayloadContent(BaseModel):
94
- """
95
- Stellar payment payload using Soroban Authorization Entries.
96
-
97
- Contains XDR-encoded authorization for SAC (Soroban Asset Contract) transfer.
98
- User signs auth entry, facilitator wraps in fee-bump transaction.
99
- """
100
-
101
- from_address: str = Field(..., alias="from", description="G... public key of sender")
102
- to: str = Field(..., description="G... public key of recipient")
103
- amount: str = Field(..., description="Amount in stroops (7 decimals)")
104
- tokenContract: str = Field(..., description="C... Soroban USDC contract address")
105
- authorizationEntryXdr: str = Field(
106
- ..., description="Base64 XDR-encoded SorobanAuthorizationEntry"
107
- )
108
- nonce: int = Field(..., description="Random 64-bit nonce")
109
- signatureExpirationLedger: int = Field(
110
- ..., description="Ledger number when authorization expires"
111
- )
112
-
113
- class Config:
114
- populate_by_name = True
115
-
116
-
117
- # Union type for all payload contents
118
- PayloadContent = Union[
119
- EVMPayloadContent,
120
- SVMPayloadContent,
121
- NEARPayloadContent,
122
- StellarPayloadContent,
123
- ]
124
-
125
-
126
- class PaymentPayload(BaseModel):
127
- """
128
- Complete x402 payment payload as received in X-PAYMENT header (after base64 decoding).
129
-
130
- Supports both x402 v1 and v2 protocols:
131
- - v1: network as string ("base", "solana")
132
- - v2: network as CAIP-2 ("eip155:8453", "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")
133
-
134
- The payload format varies by network type:
135
- - EVM: Contains signature + authorization (EIP-712)
136
- - SVM: Contains partially-signed transaction (Solana, Fogo, etc.)
137
- - NEAR: Contains SignedDelegateAction (NEP-366)
138
- - Stellar: Contains Soroban authorization entry XDR
139
- """
140
-
141
- x402Version: int = Field(default=1, description="x402 protocol version (1 or 2)")
142
- scheme: Literal["exact"] = Field(
143
- default="exact", description="Payment scheme (only 'exact' supported)"
144
- )
145
- network: str = Field(..., description="Network identifier (v1: 'base', v2: 'eip155:8453')")
146
- payload: Dict[str, Any] = Field(..., description="Network-specific payload content")
147
-
148
- @field_validator("x402Version")
149
- @classmethod
150
- def validate_version(cls, v: int) -> int:
151
- if v not in (1, 2):
152
- raise ValueError(f"Unsupported x402 version: {v}. Expected: 1 or 2")
153
- return v
154
-
155
- @field_validator("scheme")
156
- @classmethod
157
- def validate_scheme(cls, v: str) -> str:
158
- if v != "exact":
159
- raise ValueError(f"Unsupported scheme: {v}. Only 'exact' is supported")
160
- return v
161
-
162
- def is_v2(self) -> bool:
163
- """Check if this payload uses x402 v2 format."""
164
- # v2 uses CAIP-2 format (contains colon) OR explicitly sets version 2
165
- return self.x402Version == 2 or ":" in self.network
166
-
167
- def get_normalized_network(self) -> str:
168
- """
169
- Get the network name in v1 format (normalized).
170
-
171
- Handles both v1 ("base") and v2 CAIP-2 ("eip155:8453") formats.
172
-
173
- Returns:
174
- Normalized network name (e.g., "base", "solana")
175
- """
176
- from uvd_x402_sdk.networks.base import normalize_network
177
- return normalize_network(self.network)
178
-
179
- def get_evm_payload(self) -> EVMPayloadContent:
180
- """Parse payload as EVM format."""
181
- return EVMPayloadContent(**self.payload)
182
-
183
- def get_svm_payload(self) -> SVMPayloadContent:
184
- """Parse payload as SVM format (Solana, Fogo, etc.)."""
185
- return SVMPayloadContent(**self.payload)
186
-
187
- def get_solana_payload(self) -> SolanaPayloadContent:
188
- """Parse payload as Solana format (alias for get_svm_payload)."""
189
- return self.get_svm_payload()
190
-
191
- def get_near_payload(self) -> NEARPayloadContent:
192
- """Parse payload as NEAR format."""
193
- return NEARPayloadContent(**self.payload)
194
-
195
- def get_stellar_payload(self) -> StellarPayloadContent:
196
- """Parse payload as Stellar format."""
197
- return StellarPayloadContent(**self.payload)
198
-
199
-
200
- class PaymentRequirements(BaseModel):
201
- """
202
- Payment requirements sent to the facilitator for verify/settle operations.
203
-
204
- This tells the facilitator what payment parameters to validate.
205
- Supports both x402 v1 and v2 formats.
206
- """
207
-
208
- scheme: Literal["exact"] = Field(default="exact")
209
- network: str = Field(..., description="Network identifier (v1 or CAIP-2)")
210
- maxAmountRequired: str = Field(..., description="Expected amount in token base units")
211
- resource: str = Field(..., description="Resource being purchased (URL or description)")
212
- description: str = Field(..., description="Human-readable description of purchase")
213
- mimeType: str = Field(default="application/json")
214
- payTo: str = Field(..., description="Recipient address for the payment")
215
- maxTimeoutSeconds: int = Field(default=60, description="Max settlement timeout")
216
- asset: str = Field(..., description="Token contract address/identifier")
217
- extra: Optional[Dict[str, str]] = Field(
218
- default=None, description="EIP-712 domain params for EVM chains"
219
- )
220
-
221
-
222
- # =============================================================================
223
- # x402 v2 Models
224
- # =============================================================================
225
-
226
-
227
- class PaymentOption(BaseModel):
228
- """
229
- Single payment option in x402 v2 accepts array.
230
-
231
- Represents one way the client can pay for the resource.
232
- """
233
-
234
- network: str = Field(..., description="CAIP-2 network identifier (e.g., 'eip155:8453')")
235
- asset: str = Field(..., description="Token contract/mint address")
236
- amount: str = Field(..., description="Required amount in token base units")
237
- payTo: str = Field(..., description="Recipient address for this network")
238
- extra: Optional[Dict[str, Any]] = Field(
239
- default=None, description="Network-specific extra data"
240
- )
241
-
242
-
243
- class PaymentRequirementsV2(BaseModel):
244
- """
245
- x402 v2 payment requirements with multiple payment options.
246
-
247
- The 'accepts' array allows servers to offer multiple ways to pay,
248
- and clients can choose based on their wallet/network preferences.
249
- """
250
-
251
- x402Version: int = Field(default=2, description="Protocol version (must be 2)")
252
- scheme: Literal["exact"] = Field(default="exact")
253
- resource: str = Field(..., description="Resource being purchased")
254
- description: str = Field(..., description="Human-readable description")
255
- mimeType: str = Field(default="application/json")
256
- maxTimeoutSeconds: int = Field(default=60)
257
- accepts: List[PaymentOption] = Field(
258
- ..., description="List of acceptable payment options"
259
- )
260
-
261
- def get_option_for_network(self, network: str) -> Optional[PaymentOption]:
262
- """
263
- Get the payment option for a specific network.
264
-
265
- Args:
266
- network: Network identifier (v1 or CAIP-2)
267
-
268
- Returns:
269
- PaymentOption if found, None otherwise
270
- """
271
- from uvd_x402_sdk.networks.base import normalize_network, to_caip2_network
272
-
273
- # Normalize the requested network
274
- try:
275
- normalized = normalize_network(network)
276
- except ValueError:
277
- return None
278
-
279
- for option in self.accepts:
280
- try:
281
- option_normalized = normalize_network(option.network)
282
- if option_normalized == normalized:
283
- return option
284
- except ValueError:
285
- continue
286
-
287
- return None
288
-
289
- def get_supported_networks(self) -> List[str]:
290
- """
291
- Get list of supported networks (normalized names).
292
-
293
- Returns:
294
- List of network names (e.g., ['base', 'solana', 'near'])
295
- """
296
- from uvd_x402_sdk.networks.base import normalize_network
297
-
298
- networks = []
299
- for option in self.accepts:
300
- try:
301
- networks.append(normalize_network(option.network))
302
- except ValueError:
303
- continue
304
- return networks
305
-
306
-
307
- class VerifyRequest(BaseModel):
308
- """
309
- Request body for facilitator /verify endpoint.
310
- """
311
-
312
- x402Version: int = 1
313
- paymentPayload: PaymentPayload
314
- paymentRequirements: PaymentRequirements
315
-
316
-
317
- class VerifyResponse(BaseModel):
318
- """
319
- Response from facilitator /verify endpoint.
320
- """
321
-
322
- isValid: bool = Field(..., description="Whether the payment signature is valid")
323
- payer: Optional[str] = Field(None, description="Verified payer address")
324
- message: Optional[str] = Field(None, description="Error message if invalid")
325
- invalidReason: Optional[str] = Field(None, description="Specific reason for invalidity")
326
- errors: List[str] = Field(default_factory=list, description="List of validation errors")
327
-
328
-
329
- class SettleRequest(BaseModel):
330
- """
331
- Request body for facilitator /settle endpoint.
332
- """
333
-
334
- x402Version: int = 1
335
- paymentPayload: PaymentPayload
336
- paymentRequirements: PaymentRequirements
337
-
338
-
339
- class SettleResponse(BaseModel):
340
- """
341
- Response from facilitator /settle endpoint.
342
- """
343
-
344
- success: bool = Field(..., description="Whether settlement succeeded")
345
- transaction: Optional[str] = Field(None, description="Transaction hash on-chain")
346
- tx_hash: Optional[str] = Field(None, description="Alternative field for tx hash")
347
- payer: Optional[str] = Field(None, description="Verified payer address")
348
- message: Optional[str] = Field(None, description="Error message if failed")
349
- errors: List[str] = Field(default_factory=list, description="List of errors")
350
-
351
- def get_transaction_hash(self) -> Optional[str]:
352
- """Get transaction hash from either field."""
353
- return self.transaction or self.tx_hash
354
-
355
-
356
- class PaymentResult(BaseModel):
357
- """
358
- Final result of a successful payment processing.
359
-
360
- This is the return type from X402Client.process_payment().
361
- """
362
-
363
- success: bool = Field(default=True)
364
- payer_address: str = Field(..., description="Verified wallet address that paid")
365
- transaction_hash: Optional[str] = Field(None, description="On-chain transaction hash")
366
- network: str = Field(..., description="Network where payment was settled")
367
- amount_usd: Decimal = Field(..., description="Amount paid in USD")
368
-
369
- class Config:
370
- json_encoders = {Decimal: str}
371
-
372
-
373
- class Payment402Response(BaseModel):
374
- """
375
- Standard 402 Payment Required response body.
376
-
377
- This is returned to clients when payment is required.
378
- """
379
-
380
- error: str = Field(default="Payment required")
381
- recipient: str = Field(..., description="Default recipient address (EVM)")
382
- recipients: Optional[Dict[str, str]] = Field(
383
- None,
384
- description="Network-specific recipients (evm, solana, near, stellar)",
385
- )
386
- facilitator: Optional[str] = Field(
387
- None, description="Solana facilitator address (fee payer)"
388
- )
389
- amount: str = Field(..., description="Amount required in USD")
390
- token: str = Field(default="USDC")
391
- supportedChains: List[Union[int, str]] = Field(
392
- ..., description="List of supported chain IDs or network names"
393
- )
394
- message: str = Field(..., description="Human-readable message")
395
-
396
- class Config:
397
- json_encoders = {Decimal: str}
1
+ """
2
+ Pydantic models for x402 payment data structures.
3
+
4
+ These models define the structure of payloads exchanged between:
5
+ - Frontend -> Backend (X-PAYMENT header)
6
+ - Backend -> Facilitator (verify/settle requests)
7
+ - Facilitator -> Backend (responses)
8
+
9
+ Supports both x402 v1 and v2 protocols:
10
+ - v1: network as string ("base", "solana")
11
+ - v2: network as CAIP-2 ("eip155:8453", "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")
12
+ """
13
+
14
+ from decimal import Decimal
15
+ from typing import Any, Dict, List, Literal, Optional, Union
16
+ from pydantic import BaseModel, Field, field_validator
17
+
18
+
19
+ # =============================================================================
20
+ # Network-Specific Payload Content Models
21
+ # =============================================================================
22
+
23
+
24
+ class EVMAuthorization(BaseModel):
25
+ """
26
+ ERC-3009 TransferWithAuthorization data for EVM chains.
27
+
28
+ This represents a signed EIP-712 message authorizing USDC transfer.
29
+ The facilitator uses this to call transferWithAuthorization() on-chain.
30
+ """
31
+
32
+ from_address: str = Field(..., alias="from", description="Sender wallet address")
33
+ to: str = Field(..., description="Recipient wallet address")
34
+ value: str = Field(..., description="Amount in token base units (e.g., 1000000 for $1 USDC)")
35
+ validAfter: str = Field(..., description="Unix timestamp after which auth is valid")
36
+ validBefore: str = Field(..., description="Unix timestamp before which auth is valid")
37
+ nonce: str = Field(..., description="Random 32-byte nonce (prevents replay attacks)")
38
+
39
+ class Config:
40
+ populate_by_name = True
41
+
42
+
43
+ class EVMPayloadContent(BaseModel):
44
+ """
45
+ Complete EVM payment payload with signature and authorization.
46
+ """
47
+
48
+ signature: str = Field(..., description="Full signature (r + s + v)")
49
+ authorization: EVMAuthorization
50
+
51
+
52
+ class SVMPayloadContent(BaseModel):
53
+ """
54
+ SVM (Solana Virtual Machine) payment payload containing a partially-signed transaction.
55
+
56
+ Works with all SVM-compatible chains: Solana, Fogo, etc.
57
+
58
+ The transaction structure must follow the facilitator's requirements:
59
+ 1. Instruction 0: SetComputeUnitLimit (units: 20,000)
60
+ 2. Instruction 1: SetComputeUnitPrice (microLamports: 1)
61
+ 3. Instruction 2: TransferChecked (USDC transfer)
62
+
63
+ Fee payer is the facilitator - user signature only authorizes USDC transfer.
64
+ """
65
+
66
+ transaction: str = Field(
67
+ ..., description="Base64-encoded partially-signed VersionedTransaction"
68
+ )
69
+
70
+
71
+ # Alias for backward compatibility
72
+ SolanaPayloadContent = SVMPayloadContent
73
+
74
+
75
+ class NEARPayloadContent(BaseModel):
76
+ """
77
+ NEAR payment payload using NEP-366 meta-transactions.
78
+
79
+ Contains a Borsh-serialized SignedDelegateAction that wraps ft_transfer.
80
+ User signs the delegate action, facilitator submits and pays gas.
81
+
82
+ NEP-366 Structure:
83
+ - DelegateAction: sender_id, receiver_id, actions, nonce, max_block_height, public_key
84
+ - SignedDelegateAction: delegate_action + ed25519 signature
85
+ - Hash prefix: 2^30 + 366 = 0x4000016E (little-endian)
86
+ """
87
+
88
+ signedDelegateAction: str = Field(
89
+ ..., description="Base64 Borsh-encoded SignedDelegateAction"
90
+ )
91
+
92
+
93
+ class StellarPayloadContent(BaseModel):
94
+ """
95
+ Stellar payment payload using Soroban Authorization Entries.
96
+
97
+ Contains XDR-encoded authorization for SAC (Soroban Asset Contract) transfer.
98
+ User signs auth entry, facilitator wraps in fee-bump transaction.
99
+ """
100
+
101
+ from_address: str = Field(..., alias="from", description="G... public key of sender")
102
+ to: str = Field(..., description="G... public key of recipient")
103
+ amount: str = Field(..., description="Amount in stroops (7 decimals)")
104
+ tokenContract: str = Field(..., description="C... Soroban USDC contract address")
105
+ authorizationEntryXdr: str = Field(
106
+ ..., description="Base64 XDR-encoded SorobanAuthorizationEntry"
107
+ )
108
+ nonce: int = Field(..., description="Random 64-bit nonce")
109
+ signatureExpirationLedger: int = Field(
110
+ ..., description="Ledger number when authorization expires"
111
+ )
112
+
113
+ class Config:
114
+ populate_by_name = True
115
+
116
+
117
+ # Union type for all payload contents
118
+ PayloadContent = Union[
119
+ EVMPayloadContent,
120
+ SVMPayloadContent,
121
+ NEARPayloadContent,
122
+ StellarPayloadContent,
123
+ ]
124
+
125
+
126
+ class PaymentPayload(BaseModel):
127
+ """
128
+ Complete x402 payment payload as received in X-PAYMENT header (after base64 decoding).
129
+
130
+ Supports both x402 v1 and v2 protocols:
131
+ - v1: network as string ("base", "solana")
132
+ - v2: network as CAIP-2 ("eip155:8453", "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")
133
+
134
+ The payload format varies by network type:
135
+ - EVM: Contains signature + authorization (EIP-712)
136
+ - SVM: Contains partially-signed transaction (Solana, Fogo, etc.)
137
+ - NEAR: Contains SignedDelegateAction (NEP-366)
138
+ - Stellar: Contains Soroban authorization entry XDR
139
+ """
140
+
141
+ x402Version: int = Field(default=1, description="x402 protocol version (1 or 2)")
142
+ scheme: Literal["exact"] = Field(
143
+ default="exact", description="Payment scheme (only 'exact' supported)"
144
+ )
145
+ network: str = Field(..., description="Network identifier (v1: 'base', v2: 'eip155:8453')")
146
+ payload: Dict[str, Any] = Field(..., description="Network-specific payload content")
147
+
148
+ @field_validator("x402Version")
149
+ @classmethod
150
+ def validate_version(cls, v: int) -> int:
151
+ if v not in (1, 2):
152
+ raise ValueError(f"Unsupported x402 version: {v}. Expected: 1 or 2")
153
+ return v
154
+
155
+ @field_validator("scheme")
156
+ @classmethod
157
+ def validate_scheme(cls, v: str) -> str:
158
+ if v != "exact":
159
+ raise ValueError(f"Unsupported scheme: {v}. Only 'exact' is supported")
160
+ return v
161
+
162
+ def is_v2(self) -> bool:
163
+ """Check if this payload uses x402 v2 format."""
164
+ # v2 uses CAIP-2 format (contains colon) OR explicitly sets version 2
165
+ return self.x402Version == 2 or ":" in self.network
166
+
167
+ def get_normalized_network(self) -> str:
168
+ """
169
+ Get the network name in v1 format (normalized).
170
+
171
+ Handles both v1 ("base") and v2 CAIP-2 ("eip155:8453") formats.
172
+
173
+ Returns:
174
+ Normalized network name (e.g., "base", "solana")
175
+ """
176
+ from uvd_x402_sdk.networks.base import normalize_network
177
+ return normalize_network(self.network)
178
+
179
+ def get_evm_payload(self) -> EVMPayloadContent:
180
+ """Parse payload as EVM format."""
181
+ return EVMPayloadContent(**self.payload)
182
+
183
+ def get_svm_payload(self) -> SVMPayloadContent:
184
+ """Parse payload as SVM format (Solana, Fogo, etc.)."""
185
+ return SVMPayloadContent(**self.payload)
186
+
187
+ def get_solana_payload(self) -> SolanaPayloadContent:
188
+ """Parse payload as Solana format (alias for get_svm_payload)."""
189
+ return self.get_svm_payload()
190
+
191
+ def get_near_payload(self) -> NEARPayloadContent:
192
+ """Parse payload as NEAR format."""
193
+ return NEARPayloadContent(**self.payload)
194
+
195
+ def get_stellar_payload(self) -> StellarPayloadContent:
196
+ """Parse payload as Stellar format."""
197
+ return StellarPayloadContent(**self.payload)
198
+
199
+
200
+ class PaymentRequirements(BaseModel):
201
+ """
202
+ Payment requirements sent to the facilitator for verify/settle operations.
203
+
204
+ This tells the facilitator what payment parameters to validate.
205
+ Supports both x402 v1 and v2 formats.
206
+ """
207
+
208
+ scheme: Literal["exact"] = Field(default="exact")
209
+ network: str = Field(..., description="Network identifier (v1 or CAIP-2)")
210
+ maxAmountRequired: str = Field(..., description="Expected amount in token base units")
211
+ resource: str = Field(..., description="Resource being purchased (URL or description)")
212
+ description: str = Field(..., description="Human-readable description of purchase")
213
+ mimeType: str = Field(default="application/json")
214
+ payTo: str = Field(..., description="Recipient address for the payment")
215
+ maxTimeoutSeconds: int = Field(default=60, description="Max settlement timeout")
216
+ asset: str = Field(..., description="Token contract address/identifier")
217
+ extra: Optional[Dict[str, str]] = Field(
218
+ default=None, description="EIP-712 domain params for EVM chains"
219
+ )
220
+
221
+
222
+ # =============================================================================
223
+ # x402 v2 Models
224
+ # =============================================================================
225
+
226
+
227
+ class PaymentOption(BaseModel):
228
+ """
229
+ Single payment option in x402 v2 accepts array.
230
+
231
+ Represents one way the client can pay for the resource.
232
+ """
233
+
234
+ network: str = Field(..., description="CAIP-2 network identifier (e.g., 'eip155:8453')")
235
+ asset: str = Field(..., description="Token contract/mint address")
236
+ amount: str = Field(..., description="Required amount in token base units")
237
+ payTo: str = Field(..., description="Recipient address for this network")
238
+ extra: Optional[Dict[str, Any]] = Field(
239
+ default=None, description="Network-specific extra data"
240
+ )
241
+
242
+
243
+ class PaymentRequirementsV2(BaseModel):
244
+ """
245
+ x402 v2 payment requirements with multiple payment options.
246
+
247
+ The 'accepts' array allows servers to offer multiple ways to pay,
248
+ and clients can choose based on their wallet/network preferences.
249
+ """
250
+
251
+ x402Version: int = Field(default=2, description="Protocol version (must be 2)")
252
+ scheme: Literal["exact"] = Field(default="exact")
253
+ resource: str = Field(..., description="Resource being purchased")
254
+ description: str = Field(..., description="Human-readable description")
255
+ mimeType: str = Field(default="application/json")
256
+ maxTimeoutSeconds: int = Field(default=60)
257
+ accepts: List[PaymentOption] = Field(
258
+ ..., description="List of acceptable payment options"
259
+ )
260
+
261
+ def get_option_for_network(self, network: str) -> Optional[PaymentOption]:
262
+ """
263
+ Get the payment option for a specific network.
264
+
265
+ Args:
266
+ network: Network identifier (v1 or CAIP-2)
267
+
268
+ Returns:
269
+ PaymentOption if found, None otherwise
270
+ """
271
+ from uvd_x402_sdk.networks.base import normalize_network, to_caip2_network
272
+
273
+ # Normalize the requested network
274
+ try:
275
+ normalized = normalize_network(network)
276
+ except ValueError:
277
+ return None
278
+
279
+ for option in self.accepts:
280
+ try:
281
+ option_normalized = normalize_network(option.network)
282
+ if option_normalized == normalized:
283
+ return option
284
+ except ValueError:
285
+ continue
286
+
287
+ return None
288
+
289
+ def get_supported_networks(self) -> List[str]:
290
+ """
291
+ Get list of supported networks (normalized names).
292
+
293
+ Returns:
294
+ List of network names (e.g., ['base', 'solana', 'near'])
295
+ """
296
+ from uvd_x402_sdk.networks.base import normalize_network
297
+
298
+ networks = []
299
+ for option in self.accepts:
300
+ try:
301
+ networks.append(normalize_network(option.network))
302
+ except ValueError:
303
+ continue
304
+ return networks
305
+
306
+
307
+ class VerifyRequest(BaseModel):
308
+ """
309
+ Request body for facilitator /verify endpoint.
310
+ """
311
+
312
+ x402Version: int = 1
313
+ paymentPayload: PaymentPayload
314
+ paymentRequirements: PaymentRequirements
315
+
316
+
317
+ class VerifyResponse(BaseModel):
318
+ """
319
+ Response from facilitator /verify endpoint.
320
+ """
321
+
322
+ isValid: bool = Field(..., description="Whether the payment signature is valid")
323
+ payer: Optional[str] = Field(None, description="Verified payer address")
324
+ message: Optional[str] = Field(None, description="Error message if invalid")
325
+ invalidReason: Optional[str] = Field(None, description="Specific reason for invalidity")
326
+ errors: List[str] = Field(default_factory=list, description="List of validation errors")
327
+
328
+
329
+ class SettleRequest(BaseModel):
330
+ """
331
+ Request body for facilitator /settle endpoint.
332
+ """
333
+
334
+ x402Version: int = 1
335
+ paymentPayload: PaymentPayload
336
+ paymentRequirements: PaymentRequirements
337
+
338
+
339
+ class SettleResponse(BaseModel):
340
+ """
341
+ Response from facilitator /settle endpoint.
342
+ """
343
+
344
+ success: bool = Field(..., description="Whether settlement succeeded")
345
+ transaction: Optional[str] = Field(None, description="Transaction hash on-chain")
346
+ tx_hash: Optional[str] = Field(None, description="Alternative field for tx hash")
347
+ payer: Optional[str] = Field(None, description="Verified payer address")
348
+ message: Optional[str] = Field(None, description="Error message if failed")
349
+ errors: List[str] = Field(default_factory=list, description="List of errors")
350
+
351
+ def get_transaction_hash(self) -> Optional[str]:
352
+ """Get transaction hash from either field."""
353
+ return self.transaction or self.tx_hash
354
+
355
+
356
+ class PaymentResult(BaseModel):
357
+ """
358
+ Final result of a successful payment processing.
359
+
360
+ This is the return type from X402Client.process_payment().
361
+ """
362
+
363
+ success: bool = Field(default=True)
364
+ payer_address: str = Field(..., description="Verified wallet address that paid")
365
+ transaction_hash: Optional[str] = Field(None, description="On-chain transaction hash")
366
+ network: str = Field(..., description="Network where payment was settled")
367
+ amount_usd: Decimal = Field(..., description="Amount paid in USD")
368
+
369
+ class Config:
370
+ json_encoders = {Decimal: str}
371
+
372
+
373
+ class Payment402Response(BaseModel):
374
+ """
375
+ Standard 402 Payment Required response body.
376
+
377
+ This is returned to clients when payment is required.
378
+ """
379
+
380
+ error: str = Field(default="Payment required")
381
+ recipient: str = Field(..., description="Default recipient address (EVM)")
382
+ recipients: Optional[Dict[str, str]] = Field(
383
+ None,
384
+ description="Network-specific recipients (evm, solana, near, stellar)",
385
+ )
386
+ facilitator: Optional[str] = Field(
387
+ None, description="Solana facilitator address (fee payer)"
388
+ )
389
+ amount: str = Field(..., description="Amount required in USD")
390
+ token: str = Field(default="USDC")
391
+ supportedChains: List[Union[int, str]] = Field(
392
+ ..., description="List of supported chain IDs or network names"
393
+ )
394
+ message: str = Field(..., description="Human-readable message")
395
+
396
+ class Config:
397
+ json_encoders = {Decimal: str}