uvd-x402-sdk 0.4.0__py3-none-any.whl → 0.4.2__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/networks/algorand.py +316 -53
- uvd_x402_sdk/networks/solana.py +72 -1
- {uvd_x402_sdk-0.4.0.dist-info → uvd_x402_sdk-0.4.2.dist-info}/METADATA +4 -2
- {uvd_x402_sdk-0.4.0.dist-info → uvd_x402_sdk-0.4.2.dist-info}/RECORD +7 -7
- {uvd_x402_sdk-0.4.0.dist-info → uvd_x402_sdk-0.4.2.dist-info}/LICENSE +0 -0
- {uvd_x402_sdk-0.4.0.dist-info → uvd_x402_sdk-0.4.2.dist-info}/WHEEL +0 -0
- {uvd_x402_sdk-0.4.0.dist-info → uvd_x402_sdk-0.4.2.dist-info}/top_level.txt +0 -0
|
@@ -1,33 +1,51 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Algorand network configurations.
|
|
2
|
+
Algorand network configurations for x402 payments.
|
|
3
3
|
|
|
4
4
|
This module supports Algorand blockchain networks:
|
|
5
|
-
- Algorand mainnet
|
|
6
|
-
- Algorand testnet
|
|
5
|
+
- Algorand mainnet (network: "algorand-mainnet" or "algorand")
|
|
6
|
+
- Algorand testnet (network: "algorand-testnet")
|
|
7
7
|
|
|
8
8
|
Algorand uses ASA (Algorand Standard Assets) for USDC:
|
|
9
9
|
- Mainnet USDC ASA ID: 31566704
|
|
10
10
|
- Testnet USDC ASA ID: 10458941
|
|
11
11
|
|
|
12
|
-
Payment Flow:
|
|
13
|
-
1.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
Payment Flow (GoPlausible x402-avm Atomic Group Spec):
|
|
13
|
+
1. Client creates an ATOMIC GROUP of TWO transactions:
|
|
14
|
+
- Transaction 0 (fee tx): Zero-amount payment FROM facilitator TO facilitator
|
|
15
|
+
This transaction pays fees for both txns. Client creates this UNSIGNED.
|
|
16
|
+
- Transaction 1 (payment tx): ASA transfer FROM client TO merchant
|
|
17
|
+
Client SIGNS this transaction.
|
|
18
|
+
2. Both transactions share a GROUP ID computed by Algorand SDK.
|
|
19
|
+
3. Fee pooling: Transaction 0's fee covers Transaction 1's fee (gasless).
|
|
20
|
+
4. Facilitator completes: Signs transaction 0 and submits the atomic group.
|
|
21
|
+
|
|
22
|
+
Payload Format:
|
|
23
|
+
{
|
|
24
|
+
"x402Version": 1,
|
|
25
|
+
"scheme": "exact",
|
|
26
|
+
"network": "algorand-mainnet",
|
|
27
|
+
"payload": {
|
|
28
|
+
"paymentIndex": 1,
|
|
29
|
+
"paymentGroup": [
|
|
30
|
+
"<base64-msgpack-UNSIGNED-fee-tx>",
|
|
31
|
+
"<base64-msgpack-SIGNED-asa-transfer>"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
22
35
|
|
|
23
36
|
Address Format:
|
|
24
37
|
- Algorand addresses are 58 characters, base32 encoded
|
|
25
38
|
- Example: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ
|
|
39
|
+
|
|
40
|
+
Dependencies:
|
|
41
|
+
- algosdk (optional): Required for building atomic groups
|
|
42
|
+
Install with: pip install py-algorand-sdk
|
|
26
43
|
"""
|
|
27
44
|
|
|
28
45
|
import base64
|
|
29
46
|
import re
|
|
30
|
-
from
|
|
47
|
+
from dataclasses import dataclass
|
|
48
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
31
49
|
|
|
32
50
|
from uvd_x402_sdk.networks.base import (
|
|
33
51
|
NetworkConfig,
|
|
@@ -63,6 +81,8 @@ ALGORAND = NetworkConfig(
|
|
|
63
81
|
"genesis_id": "mainnet-v1.0",
|
|
64
82
|
# Genesis hash (for CAIP-2)
|
|
65
83
|
"genesis_hash": "wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=",
|
|
84
|
+
# x402 network name (facilitator expects this format)
|
|
85
|
+
"x402_network": "algorand-mainnet",
|
|
66
86
|
},
|
|
67
87
|
)
|
|
68
88
|
|
|
@@ -89,6 +109,8 @@ ALGORAND_TESTNET = NetworkConfig(
|
|
|
89
109
|
"genesis_id": "testnet-v1.0",
|
|
90
110
|
# Genesis hash
|
|
91
111
|
"genesis_hash": "SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=",
|
|
112
|
+
# x402 network name (facilitator expects this format)
|
|
113
|
+
"x402_network": "algorand-testnet",
|
|
92
114
|
},
|
|
93
115
|
)
|
|
94
116
|
|
|
@@ -97,6 +119,34 @@ register_network(ALGORAND)
|
|
|
97
119
|
register_network(ALGORAND_TESTNET)
|
|
98
120
|
|
|
99
121
|
|
|
122
|
+
# =============================================================================
|
|
123
|
+
# Algorand Payment Payload (x402-avm Atomic Group Spec)
|
|
124
|
+
# =============================================================================
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@dataclass
|
|
128
|
+
class AlgorandPaymentPayload:
|
|
129
|
+
"""
|
|
130
|
+
Algorand payment payload for x402 atomic group format.
|
|
131
|
+
|
|
132
|
+
Attributes:
|
|
133
|
+
payment_index: Index of the payment transaction in the group (typically 1)
|
|
134
|
+
payment_group: List of base64-encoded msgpack transactions
|
|
135
|
+
[0] = unsigned fee transaction
|
|
136
|
+
[1] = signed ASA transfer transaction
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
payment_index: int
|
|
140
|
+
payment_group: List[str]
|
|
141
|
+
|
|
142
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
143
|
+
"""Convert to dictionary for JSON serialization."""
|
|
144
|
+
return {
|
|
145
|
+
"paymentIndex": self.payment_index,
|
|
146
|
+
"paymentGroup": self.payment_group,
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
|
|
100
150
|
# =============================================================================
|
|
101
151
|
# Algorand-specific utilities
|
|
102
152
|
# =============================================================================
|
|
@@ -162,59 +212,99 @@ def is_valid_algorand_address(address: str) -> bool:
|
|
|
162
212
|
|
|
163
213
|
def validate_algorand_payload(payload: Dict[str, Any]) -> bool:
|
|
164
214
|
"""
|
|
165
|
-
Validate an Algorand payment payload structure.
|
|
215
|
+
Validate an Algorand payment payload structure (x402-avm atomic group format).
|
|
166
216
|
|
|
167
217
|
The payload must contain:
|
|
168
|
-
-
|
|
169
|
-
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
- signedTxn: Base64-encoded signed transaction
|
|
218
|
+
- paymentIndex: Index of the payment transaction (typically 1)
|
|
219
|
+
- paymentGroup: List of base64-encoded msgpack transactions
|
|
220
|
+
- [0]: Unsigned fee transaction (facilitator -> facilitator)
|
|
221
|
+
- [1]: Signed ASA transfer (client -> merchant)
|
|
173
222
|
|
|
174
223
|
Args:
|
|
175
|
-
payload: Payload dictionary from x402 payment
|
|
224
|
+
payload: Payload dictionary from x402 payment (the inner "payload" field)
|
|
176
225
|
|
|
177
226
|
Returns:
|
|
178
227
|
True if valid, raises ValueError if invalid
|
|
228
|
+
|
|
229
|
+
Example:
|
|
230
|
+
>>> payload = {
|
|
231
|
+
... "paymentIndex": 1,
|
|
232
|
+
... "paymentGroup": [
|
|
233
|
+
... "base64-unsigned-fee-tx...",
|
|
234
|
+
... "base64-signed-payment-tx..."
|
|
235
|
+
... ]
|
|
236
|
+
... }
|
|
237
|
+
>>> validate_algorand_payload(payload)
|
|
238
|
+
True
|
|
179
239
|
"""
|
|
180
|
-
|
|
240
|
+
# Check required fields
|
|
241
|
+
if "paymentIndex" not in payload:
|
|
242
|
+
raise ValueError("Algorand payload missing 'paymentIndex' field")
|
|
243
|
+
if "paymentGroup" not in payload:
|
|
244
|
+
raise ValueError("Algorand payload missing 'paymentGroup' field")
|
|
245
|
+
|
|
246
|
+
# Validate paymentIndex
|
|
247
|
+
payment_index = payload["paymentIndex"]
|
|
248
|
+
if not isinstance(payment_index, int) or payment_index < 0:
|
|
249
|
+
raise ValueError(f"paymentIndex must be a non-negative integer: {payment_index}")
|
|
250
|
+
|
|
251
|
+
# Validate paymentGroup
|
|
252
|
+
payment_group = payload["paymentGroup"]
|
|
253
|
+
if not isinstance(payment_group, list):
|
|
254
|
+
raise ValueError("paymentGroup must be a list")
|
|
255
|
+
|
|
256
|
+
if len(payment_group) < 2:
|
|
257
|
+
raise ValueError(
|
|
258
|
+
f"paymentGroup must contain at least 2 transactions, got {len(payment_group)}"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
if payment_index >= len(payment_group):
|
|
262
|
+
raise ValueError(
|
|
263
|
+
f"paymentIndex ({payment_index}) out of range for paymentGroup "
|
|
264
|
+
f"(length {len(payment_group)})"
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Validate each transaction in the group is valid base64
|
|
268
|
+
for i, txn_b64 in enumerate(payment_group):
|
|
269
|
+
if not isinstance(txn_b64, str):
|
|
270
|
+
raise ValueError(f"paymentGroup[{i}] must be a string")
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
txn_bytes = base64.b64decode(txn_b64)
|
|
274
|
+
if len(txn_bytes) < 50:
|
|
275
|
+
raise ValueError(
|
|
276
|
+
f"paymentGroup[{i}] transaction too short: {len(txn_bytes)} bytes"
|
|
277
|
+
)
|
|
278
|
+
except Exception as e:
|
|
279
|
+
raise ValueError(
|
|
280
|
+
f"paymentGroup[{i}] is not valid base64: {e}"
|
|
281
|
+
) from e
|
|
181
282
|
|
|
182
|
-
|
|
183
|
-
if field not in payload:
|
|
184
|
-
raise ValueError(f"Algorand payload missing '{field}' field")
|
|
283
|
+
return True
|
|
185
284
|
|
|
186
|
-
# Validate addresses
|
|
187
|
-
if not is_valid_algorand_address(payload["from"]):
|
|
188
|
-
raise ValueError(f"Invalid 'from' address: {payload['from']}")
|
|
189
|
-
if not is_valid_algorand_address(payload["to"]):
|
|
190
|
-
raise ValueError(f"Invalid 'to' address: {payload['to']}")
|
|
191
285
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if amount <= 0:
|
|
196
|
-
raise ValueError(f"Amount must be positive: {amount}")
|
|
197
|
-
except (ValueError, TypeError) as e:
|
|
198
|
-
raise ValueError(f"Invalid amount: {payload['amount']}") from e
|
|
286
|
+
def get_x402_network_name(network_name: str) -> str:
|
|
287
|
+
"""
|
|
288
|
+
Get the x402 network name for an Algorand network.
|
|
199
289
|
|
|
200
|
-
|
|
201
|
-
try:
|
|
202
|
-
asset_id = int(payload["assetId"])
|
|
203
|
-
if asset_id <= 0:
|
|
204
|
-
raise ValueError(f"Asset ID must be positive: {asset_id}")
|
|
205
|
-
except (ValueError, TypeError) as e:
|
|
206
|
-
raise ValueError(f"Invalid assetId: {payload['assetId']}") from e
|
|
290
|
+
The facilitator expects "algorand-mainnet" or "algorand-testnet".
|
|
207
291
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
signed_txn = payload["signedTxn"]
|
|
211
|
-
tx_bytes = base64.b64decode(signed_txn)
|
|
212
|
-
if len(tx_bytes) < 50:
|
|
213
|
-
raise ValueError(f"Signed transaction too short: {len(tx_bytes)} bytes")
|
|
214
|
-
except Exception as e:
|
|
215
|
-
raise ValueError(f"Invalid signedTxn (not valid base64): {e}") from e
|
|
292
|
+
Args:
|
|
293
|
+
network_name: SDK network name ('algorand' or 'algorand-testnet')
|
|
216
294
|
|
|
217
|
-
|
|
295
|
+
Returns:
|
|
296
|
+
x402 network name ('algorand-mainnet' or 'algorand-testnet')
|
|
297
|
+
"""
|
|
298
|
+
from uvd_x402_sdk.networks.base import get_network
|
|
299
|
+
|
|
300
|
+
network = get_network(network_name)
|
|
301
|
+
if not network:
|
|
302
|
+
# Default mapping
|
|
303
|
+
if network_name == "algorand":
|
|
304
|
+
return "algorand-mainnet"
|
|
305
|
+
return network_name
|
|
306
|
+
|
|
307
|
+
return network.extra_config.get("x402_network", network_name)
|
|
218
308
|
|
|
219
309
|
|
|
220
310
|
def get_explorer_tx_url(network_name: str, tx_id: str) -> Optional[str]:
|
|
@@ -285,3 +375,176 @@ def get_usdc_asa_id(network_name: str) -> Optional[int]:
|
|
|
285
375
|
return int(network.usdc_address)
|
|
286
376
|
except (ValueError, TypeError):
|
|
287
377
|
return None
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
# =============================================================================
|
|
381
|
+
# Atomic Group Builder (requires algosdk)
|
|
382
|
+
# =============================================================================
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def build_atomic_group(
|
|
386
|
+
sender_address: str,
|
|
387
|
+
recipient_address: str,
|
|
388
|
+
amount: int,
|
|
389
|
+
asset_id: int,
|
|
390
|
+
facilitator_address: str,
|
|
391
|
+
sign_transaction: Callable,
|
|
392
|
+
algod_client: Optional[Any] = None,
|
|
393
|
+
suggested_params: Optional[Any] = None,
|
|
394
|
+
) -> AlgorandPaymentPayload:
|
|
395
|
+
"""
|
|
396
|
+
Build an Algorand atomic group for x402 payment.
|
|
397
|
+
|
|
398
|
+
This creates the two-transaction atomic group required by the facilitator:
|
|
399
|
+
- Transaction 0: Unsigned fee payment (facilitator -> facilitator, 0 amount)
|
|
400
|
+
- Transaction 1: Signed ASA transfer (sender -> recipient)
|
|
401
|
+
|
|
402
|
+
Requires: pip install py-algorand-sdk
|
|
403
|
+
|
|
404
|
+
Args:
|
|
405
|
+
sender_address: Client's Algorand address
|
|
406
|
+
recipient_address: Merchant's Algorand address (from payTo)
|
|
407
|
+
amount: Amount in micro-units (1 USDC = 1,000,000)
|
|
408
|
+
asset_id: USDC ASA ID (31566704 mainnet, 10458941 testnet)
|
|
409
|
+
facilitator_address: Facilitator address (from extra.feePayer)
|
|
410
|
+
sign_transaction: Function that signs a transaction.
|
|
411
|
+
Signature: (transaction) -> SignedTransaction
|
|
412
|
+
Can use algosdk's transaction.sign(private_key)
|
|
413
|
+
algod_client: Optional AlgodClient for getting suggested params.
|
|
414
|
+
If not provided, suggested_params must be given.
|
|
415
|
+
suggested_params: Optional SuggestedParams. If not provided,
|
|
416
|
+
algod_client.suggested_params() is called.
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
AlgorandPaymentPayload with paymentIndex and paymentGroup
|
|
420
|
+
|
|
421
|
+
Raises:
|
|
422
|
+
ImportError: If algosdk is not installed
|
|
423
|
+
ValueError: If neither algod_client nor suggested_params provided
|
|
424
|
+
|
|
425
|
+
Example:
|
|
426
|
+
>>> from algosdk import transaction
|
|
427
|
+
>>> from algosdk.v2client import algod
|
|
428
|
+
>>>
|
|
429
|
+
>>> client = algod.AlgodClient("", "https://mainnet-api.algonode.cloud")
|
|
430
|
+
>>> payload = build_atomic_group(
|
|
431
|
+
... sender_address="SENDER...",
|
|
432
|
+
... recipient_address="MERCHANT...",
|
|
433
|
+
... amount=1000000, # 1 USDC
|
|
434
|
+
... asset_id=31566704,
|
|
435
|
+
... facilitator_address="FACILITATOR...",
|
|
436
|
+
... sign_transaction=lambda txn: txn.sign(private_key),
|
|
437
|
+
... algod_client=client,
|
|
438
|
+
... )
|
|
439
|
+
"""
|
|
440
|
+
try:
|
|
441
|
+
from algosdk import encoding, transaction
|
|
442
|
+
except ImportError as e:
|
|
443
|
+
raise ImportError(
|
|
444
|
+
"algosdk is required for building atomic groups. "
|
|
445
|
+
"Install with: pip install py-algorand-sdk"
|
|
446
|
+
) from e
|
|
447
|
+
|
|
448
|
+
# Get suggested params
|
|
449
|
+
if suggested_params is None:
|
|
450
|
+
if algod_client is None:
|
|
451
|
+
raise ValueError(
|
|
452
|
+
"Either algod_client or suggested_params must be provided"
|
|
453
|
+
)
|
|
454
|
+
suggested_params = algod_client.suggested_params()
|
|
455
|
+
|
|
456
|
+
# Transaction 0: Fee payment (facilitator -> facilitator, 0 amount)
|
|
457
|
+
# This transaction pays fees for both txns in the group
|
|
458
|
+
fee_txn = transaction.PaymentTxn(
|
|
459
|
+
sender=facilitator_address,
|
|
460
|
+
receiver=facilitator_address, # self-transfer
|
|
461
|
+
amt=0,
|
|
462
|
+
sp=suggested_params,
|
|
463
|
+
)
|
|
464
|
+
# Cover both transactions (1000 microAlgos each = 2000 total)
|
|
465
|
+
fee_txn.fee = 2000
|
|
466
|
+
|
|
467
|
+
# Transaction 1: ASA transfer (client -> merchant)
|
|
468
|
+
payment_txn = transaction.AssetTransferTxn(
|
|
469
|
+
sender=sender_address,
|
|
470
|
+
receiver=recipient_address,
|
|
471
|
+
amt=amount,
|
|
472
|
+
index=asset_id,
|
|
473
|
+
sp=suggested_params,
|
|
474
|
+
)
|
|
475
|
+
# Fee paid by transaction 0
|
|
476
|
+
payment_txn.fee = 0
|
|
477
|
+
|
|
478
|
+
# Assign group ID to both transactions
|
|
479
|
+
group_id = transaction.calculate_group_id([fee_txn, payment_txn])
|
|
480
|
+
fee_txn.group = group_id
|
|
481
|
+
payment_txn.group = group_id
|
|
482
|
+
|
|
483
|
+
# Encode fee transaction (UNSIGNED - facilitator will sign)
|
|
484
|
+
unsigned_fee_txn_bytes = encoding.msgpack_encode(fee_txn)
|
|
485
|
+
unsigned_fee_txn_base64 = base64.b64encode(unsigned_fee_txn_bytes).decode("utf-8")
|
|
486
|
+
|
|
487
|
+
# Sign and encode payment transaction
|
|
488
|
+
signed_payment_txn = sign_transaction(payment_txn)
|
|
489
|
+
signed_payment_txn_bytes = encoding.msgpack_encode(signed_payment_txn)
|
|
490
|
+
signed_payment_txn_base64 = base64.b64encode(signed_payment_txn_bytes).decode("utf-8")
|
|
491
|
+
|
|
492
|
+
return AlgorandPaymentPayload(
|
|
493
|
+
payment_index=1, # Index of the payment transaction
|
|
494
|
+
payment_group=[
|
|
495
|
+
unsigned_fee_txn_base64, # Transaction 0: unsigned fee tx
|
|
496
|
+
signed_payment_txn_base64, # Transaction 1: signed payment tx
|
|
497
|
+
],
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def create_private_key_signer(private_key: str) -> Callable:
|
|
502
|
+
"""
|
|
503
|
+
Create a transaction signer from a private key.
|
|
504
|
+
|
|
505
|
+
Args:
|
|
506
|
+
private_key: Algorand private key (base64 encoded)
|
|
507
|
+
|
|
508
|
+
Returns:
|
|
509
|
+
Function that signs transactions
|
|
510
|
+
|
|
511
|
+
Example:
|
|
512
|
+
>>> signer = create_private_key_signer(my_private_key)
|
|
513
|
+
>>> payload = build_atomic_group(..., sign_transaction=signer)
|
|
514
|
+
"""
|
|
515
|
+
def sign(txn: Any) -> Any:
|
|
516
|
+
return txn.sign(private_key)
|
|
517
|
+
return sign
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def build_x402_payment_request(
|
|
521
|
+
payload: AlgorandPaymentPayload,
|
|
522
|
+
network: str = "algorand-mainnet",
|
|
523
|
+
scheme: str = "exact",
|
|
524
|
+
version: int = 1,
|
|
525
|
+
) -> Dict[str, Any]:
|
|
526
|
+
"""
|
|
527
|
+
Build a complete x402 payment request for Algorand.
|
|
528
|
+
|
|
529
|
+
Args:
|
|
530
|
+
payload: AlgorandPaymentPayload from build_atomic_group()
|
|
531
|
+
network: Network name ("algorand-mainnet" or "algorand-testnet")
|
|
532
|
+
scheme: Payment scheme (default "exact")
|
|
533
|
+
version: x402 version (default 1)
|
|
534
|
+
|
|
535
|
+
Returns:
|
|
536
|
+
Complete x402 payment request dictionary
|
|
537
|
+
|
|
538
|
+
Example:
|
|
539
|
+
>>> payload = build_atomic_group(...)
|
|
540
|
+
>>> request = build_x402_payment_request(payload)
|
|
541
|
+
>>> # Send as X-PAYMENT header (base64 encoded JSON)
|
|
542
|
+
>>> import json, base64
|
|
543
|
+
>>> header = base64.b64encode(json.dumps(request).encode()).decode()
|
|
544
|
+
"""
|
|
545
|
+
return {
|
|
546
|
+
"x402Version": version,
|
|
547
|
+
"scheme": scheme,
|
|
548
|
+
"network": network,
|
|
549
|
+
"payload": payload.to_dict(),
|
|
550
|
+
}
|
uvd_x402_sdk/networks/solana.py
CHANGED
|
@@ -5,6 +5,10 @@ This module supports all SVM-compatible chains:
|
|
|
5
5
|
- Solana (mainnet)
|
|
6
6
|
- Fogo (fast finality SVM)
|
|
7
7
|
|
|
8
|
+
Supported Tokens:
|
|
9
|
+
- USDC: Standard SPL Token (TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA)
|
|
10
|
+
- AUSD: Token2022 (TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb)
|
|
11
|
+
|
|
8
12
|
All SVM chains use the same payment flow:
|
|
9
13
|
1. User creates a partially-signed VersionedTransaction
|
|
10
14
|
2. Transaction contains SPL token TransferChecked instruction
|
|
@@ -14,7 +18,7 @@ All SVM chains use the same payment flow:
|
|
|
14
18
|
Transaction Structure (flexible, facilitator v1.9.4+):
|
|
15
19
|
- SetComputeUnitLimit instruction (recommended: 20,000 units)
|
|
16
20
|
- SetComputeUnitPrice instruction (recommended: 100,000 microLamports)
|
|
17
|
-
- TransferChecked (
|
|
21
|
+
- TransferChecked (SPL or Token2022 transfer)
|
|
18
22
|
- Optional: CreateAssociatedTokenAccount (if recipient ATA doesn't exist)
|
|
19
23
|
- Additional instructions may be added by wallets (e.g., Phantom memo)
|
|
20
24
|
|
|
@@ -30,6 +34,7 @@ from typing import Dict, Any, Optional
|
|
|
30
34
|
from uvd_x402_sdk.networks.base import (
|
|
31
35
|
NetworkConfig,
|
|
32
36
|
NetworkType,
|
|
37
|
+
TokenConfig,
|
|
33
38
|
register_network,
|
|
34
39
|
)
|
|
35
40
|
|
|
@@ -50,9 +55,28 @@ SOLANA = NetworkConfig(
|
|
|
50
55
|
usdc_domain_version="",
|
|
51
56
|
rpc_url="https://api.mainnet-beta.solana.com",
|
|
52
57
|
enabled=True,
|
|
58
|
+
tokens={
|
|
59
|
+
# USDC - Standard SPL Token
|
|
60
|
+
"usdc": TokenConfig(
|
|
61
|
+
address="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
62
|
+
decimals=6,
|
|
63
|
+
name="", # Not applicable for SVM
|
|
64
|
+
version="",
|
|
65
|
+
),
|
|
66
|
+
# AUSD - Token2022 (Agora USD)
|
|
67
|
+
# Uses Token2022 with extensions: PermanentDelegate, TransferHook, Metadata
|
|
68
|
+
"ausd": TokenConfig(
|
|
69
|
+
address="AUSD1jCcCyPLybk1YnvPWsHQSrZ46dxwoMniN4N2UEB9",
|
|
70
|
+
decimals=6,
|
|
71
|
+
name="", # Not applicable for SVM
|
|
72
|
+
version="",
|
|
73
|
+
),
|
|
74
|
+
},
|
|
53
75
|
extra_config={
|
|
54
76
|
# Token program ID (standard SPL token program)
|
|
55
77
|
"token_program_id": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
|
|
78
|
+
# Token2022 program ID (for AUSD and other Token2022 tokens)
|
|
79
|
+
"token_2022_program_id": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
|
|
56
80
|
# Associated Token Account program
|
|
57
81
|
"ata_program_id": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL",
|
|
58
82
|
# Default compute units for transfer
|
|
@@ -280,3 +304,50 @@ DEFAULT_COMPUTE_UNITS = 20000
|
|
|
280
304
|
# Use 100k microlamports/CU for fast landing on mainnet
|
|
281
305
|
# Lower values (like 1) cause transactions to be deprioritized and time out
|
|
282
306
|
DEFAULT_PRIORITY_FEE_MICROLAMPORTS = 100_000
|
|
307
|
+
|
|
308
|
+
# =============================================================================
|
|
309
|
+
# Token Program Constants
|
|
310
|
+
# =============================================================================
|
|
311
|
+
|
|
312
|
+
# Standard SPL Token Program (used by USDC)
|
|
313
|
+
TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
|
|
314
|
+
|
|
315
|
+
# Token2022 Program (used by AUSD - has extensions like TransferHook)
|
|
316
|
+
TOKEN_2022_PROGRAM_ID = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
|
|
317
|
+
|
|
318
|
+
# Associated Token Account Program (same for both SPL and Token2022)
|
|
319
|
+
ATA_PROGRAM_ID = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
|
|
320
|
+
|
|
321
|
+
# Tokens that use Token2022 instead of standard SPL
|
|
322
|
+
TOKEN_2022_TOKENS = {"ausd"}
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def get_token_program_id(token_type: str) -> str:
|
|
326
|
+
"""
|
|
327
|
+
Get the token program ID for a given token type.
|
|
328
|
+
|
|
329
|
+
USDC uses standard SPL Token program.
|
|
330
|
+
AUSD uses Token2022 (with extensions like TransferHook).
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
token_type: Token type (e.g., 'usdc', 'ausd')
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
Token program ID string
|
|
337
|
+
"""
|
|
338
|
+
if token_type.lower() in TOKEN_2022_TOKENS:
|
|
339
|
+
return TOKEN_2022_PROGRAM_ID
|
|
340
|
+
return TOKEN_PROGRAM_ID
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def is_token_2022(token_type: str) -> bool:
|
|
344
|
+
"""
|
|
345
|
+
Check if a token uses the Token2022 program.
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
token_type: Token type (e.g., 'usdc', 'ausd')
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
True if token uses Token2022, False for standard SPL
|
|
352
|
+
"""
|
|
353
|
+
return token_type.lower() in TOKEN_2022_TOKENS
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: uvd-x402-sdk
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Python SDK for x402 payments - gasless crypto payments across 16 blockchains with multi-stablecoin support (USDC, EURC, AUSD, PYUSD, USDT)
|
|
5
5
|
Author-email: Ultravioleta DAO <dev@ultravioletadao.xyz>
|
|
6
6
|
Project-URL: Homepage, https://github.com/UltravioletaDAO/uvd-x402-sdk-python
|
|
@@ -24,8 +24,10 @@ Description-Content-Type: text/markdown
|
|
|
24
24
|
License-File: LICENSE
|
|
25
25
|
Requires-Dist: httpx>=0.24.0
|
|
26
26
|
Requires-Dist: pydantic>=2.0.0
|
|
27
|
+
Provides-Extra: algorand
|
|
28
|
+
Requires-Dist: py-algorand-sdk>=2.0.0; extra == "algorand"
|
|
27
29
|
Provides-Extra: all
|
|
28
|
-
Requires-Dist: uvd-x402-sdk[aws,django,fastapi,flask,web3]; extra == "all"
|
|
30
|
+
Requires-Dist: uvd-x402-sdk[algorand,aws,django,fastapi,flask,web3]; extra == "all"
|
|
29
31
|
Provides-Extra: aws
|
|
30
32
|
Requires-Dist: boto3>=1.26.0; extra == "aws"
|
|
31
33
|
Provides-Extra: dev
|
|
@@ -11,14 +11,14 @@ uvd_x402_sdk/integrations/fastapi_integration.py,sha256=j5h1IJwFLBBoWov7ANLCFaxe
|
|
|
11
11
|
uvd_x402_sdk/integrations/flask_integration.py,sha256=0iQKO5-WRxE76Pv-1jEl4lYhjCLmq_R-jxR5g9xIcKw,8825
|
|
12
12
|
uvd_x402_sdk/integrations/lambda_integration.py,sha256=nRf4o3nS6Syx-d5P0kEhz66y7jb_S4w-mwaIazgiA9c,10184
|
|
13
13
|
uvd_x402_sdk/networks/__init__.py,sha256=LKl_TljVoCDb27YB4X_VbQN8XKbdwWFAsCwgiqQtlgo,2092
|
|
14
|
-
uvd_x402_sdk/networks/algorand.py,sha256=
|
|
14
|
+
uvd_x402_sdk/networks/algorand.py,sha256=zcpU5U4V9weiFZ_Zfc-khWaadTldO6KKOIg5ZdxXaRs,17819
|
|
15
15
|
uvd_x402_sdk/networks/base.py,sha256=gOPWfqasGbgtg9w2uG5pWnfjdOEain92L2egnDSBguc,14863
|
|
16
16
|
uvd_x402_sdk/networks/evm.py,sha256=4IbeaMH2I1c9DYCijghys0qYNeL2Nl92IMKLwq-b0Zg,10065
|
|
17
17
|
uvd_x402_sdk/networks/near.py,sha256=sxbxT1NqjcENh8ysFLDpAx5DGizf1EI0YjwgviLfqcY,11608
|
|
18
|
-
uvd_x402_sdk/networks/solana.py,sha256
|
|
18
|
+
uvd_x402_sdk/networks/solana.py,sha256=-snAeE3OxJU_kaGb_z4NOU6k0SGAD4DBPhJcPbgrdgo,11675
|
|
19
19
|
uvd_x402_sdk/networks/stellar.py,sha256=c-6re-dVc2-6gJ5rL4krUTaFsPz5vkactOJD-0wowBA,3534
|
|
20
|
-
uvd_x402_sdk-0.4.
|
|
21
|
-
uvd_x402_sdk-0.4.
|
|
22
|
-
uvd_x402_sdk-0.4.
|
|
23
|
-
uvd_x402_sdk-0.4.
|
|
24
|
-
uvd_x402_sdk-0.4.
|
|
20
|
+
uvd_x402_sdk-0.4.2.dist-info/LICENSE,sha256=OcLzB_iSgMbvk7b0dlyvleY_IbL2WUaPxvn1CHw2uAc,1073
|
|
21
|
+
uvd_x402_sdk-0.4.2.dist-info/METADATA,sha256=dOuyD4J4cWQtHx3Ia0uhst7xIf4BMwhEr3ULgPkbljw,29711
|
|
22
|
+
uvd_x402_sdk-0.4.2.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
23
|
+
uvd_x402_sdk-0.4.2.dist-info/top_level.txt,sha256=Exyjj_Kl7CDAGFMi72lT9oFPOYiRNZb3l8tr906mMmc,13
|
|
24
|
+
uvd_x402_sdk-0.4.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|