uvd-x402-sdk 0.2.0__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.
@@ -0,0 +1,397 @@
1
+ """
2
+ NEAR Protocol network configuration.
3
+
4
+ NEAR uses NEP-366 meta-transactions for gasless payments:
5
+ 1. User creates a DelegateAction containing ft_transfer
6
+ 2. User signs to create SignedDelegateAction (Borsh serialized)
7
+ 3. Facilitator wraps in Action::Delegate and submits
8
+ 4. Facilitator pays all gas - user pays ZERO NEAR
9
+
10
+ NEP-366 Structure:
11
+ - DelegateAction: sender_id, receiver_id, actions, nonce, max_block_height, public_key
12
+ - SignedDelegateAction: delegate_action + ed25519 signature
13
+ - Hash prefix: 2^30 + 366 = 0x4000016E (little-endian)
14
+
15
+ Borsh Serialization Format:
16
+ - u8: 1 byte
17
+ - u32: 4 bytes little-endian
18
+ - u64: 8 bytes little-endian
19
+ - u128: 16 bytes little-endian
20
+ - string: u32 length prefix + UTF-8 bytes
21
+ - bytes: u32 length prefix + raw bytes
22
+ - fixed_bytes: raw bytes (no prefix)
23
+ """
24
+
25
+ import base64
26
+ import struct
27
+ from typing import Optional, Dict, Any
28
+
29
+ from uvd_x402_sdk.networks.base import (
30
+ NetworkConfig,
31
+ NetworkType,
32
+ register_network,
33
+ )
34
+
35
+ # NEP-366 hash prefix: (2^30 + 366) = 1073742190
36
+ NEP366_PREFIX = ((2**30) + 366).to_bytes(4, 'little')
37
+
38
+
39
+ # NEAR Mainnet
40
+ NEAR = NetworkConfig(
41
+ name="near",
42
+ display_name="NEAR Protocol",
43
+ network_type=NetworkType.NEAR,
44
+ chain_id=0, # Non-EVM, no chain ID
45
+ # Native Circle USDC on NEAR (account ID hash)
46
+ usdc_address="17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
47
+ usdc_decimals=6,
48
+ usdc_domain_name="", # Not applicable for NEAR
49
+ usdc_domain_version="",
50
+ rpc_url="https://rpc.mainnet.near.org",
51
+ enabled=True, # ENABLED: Facilitator supports NEAR via NEP-366
52
+ extra_config={
53
+ # Network identifier
54
+ "network_id": "mainnet",
55
+ # Alternative RPC endpoints (CORS-friendly)
56
+ "rpc_endpoints": [
57
+ "https://near.drpc.org",
58
+ "https://public-rpc.blockpi.io/http/near",
59
+ "https://endpoints.omniatech.io/v1/near/mainnet/public",
60
+ "https://rpc.mainnet.near.org",
61
+ "https://near.lava.build",
62
+ ],
63
+ # Block explorer
64
+ "explorer_url": "https://nearblocks.io",
65
+ # NEP-366 prefix (2^30 + 366 as u32 little-endian)
66
+ "nep366_prefix": NEP366_PREFIX,
67
+ # Default gas for ft_transfer
68
+ "ft_transfer_gas": 30_000_000_000_000, # 30 TGas
69
+ # Deposit for ft_transfer (1 yoctoNEAR)
70
+ "ft_transfer_deposit": 1,
71
+ },
72
+ )
73
+
74
+ # Register NEAR network
75
+ register_network(NEAR)
76
+
77
+
78
+ # =============================================================================
79
+ # NEAR-specific utilities
80
+ # =============================================================================
81
+
82
+
83
+ def create_ft_transfer_args(
84
+ receiver_id: str,
85
+ amount: int,
86
+ memo: str = "",
87
+ ) -> dict:
88
+ """
89
+ Create arguments for NEAR ft_transfer function call.
90
+
91
+ Args:
92
+ receiver_id: NEAR account ID to receive tokens
93
+ amount: Amount in base units (6 decimals for USDC)
94
+ memo: Optional transfer memo
95
+
96
+ Returns:
97
+ Dictionary of ft_transfer arguments
98
+ """
99
+ return {
100
+ "receiver_id": receiver_id,
101
+ "amount": str(amount),
102
+ "memo": memo or None,
103
+ }
104
+
105
+
106
+ def calculate_max_block_height(current_height: int, blocks_valid: int = 1000) -> int:
107
+ """
108
+ Calculate max_block_height for DelegateAction.
109
+
110
+ Args:
111
+ current_height: Current block height from RPC
112
+ blocks_valid: Number of blocks the action is valid for (~17 minutes at 1 block/s)
113
+
114
+ Returns:
115
+ Max block height for the action
116
+ """
117
+ return current_height + blocks_valid
118
+
119
+
120
+ def base58_decode(encoded: str) -> bytes:
121
+ """
122
+ Decode a base58 string to bytes.
123
+
124
+ NEAR public keys use ed25519:base58 format.
125
+
126
+ Args:
127
+ encoded: Base58 encoded string (without prefix)
128
+
129
+ Returns:
130
+ Decoded bytes
131
+ """
132
+ ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
133
+ BASE = 58
134
+
135
+ num = 0
136
+ for char in encoded:
137
+ index = ALPHABET.index(char)
138
+ if index == -1:
139
+ raise ValueError(f"Invalid base58 character: {char}")
140
+ num = num * BASE + index
141
+
142
+ # Convert to bytes (32 bytes for ED25519 public key)
143
+ result = []
144
+ while num > 0:
145
+ result.append(num & 0xFF)
146
+ num >>= 8
147
+
148
+ # Pad to 32 bytes and reverse
149
+ while len(result) < 32:
150
+ result.append(0)
151
+
152
+ return bytes(reversed(result))
153
+
154
+
155
+ def decode_near_public_key(public_key: str) -> bytes:
156
+ """
157
+ Decode a NEAR public key from ed25519:base58 format to raw bytes.
158
+
159
+ Args:
160
+ public_key: Public key in format 'ed25519:base58string'
161
+
162
+ Returns:
163
+ 32 bytes of the public key
164
+ """
165
+ # Remove ed25519: prefix if present
166
+ if public_key.startswith("ed25519:"):
167
+ public_key = public_key[8:]
168
+
169
+ return base58_decode(public_key)
170
+
171
+
172
+ # =============================================================================
173
+ # NEP-366 Borsh Serialization
174
+ # =============================================================================
175
+
176
+
177
+ class BorshSerializer:
178
+ """
179
+ Simple Borsh serializer for NEAR transactions.
180
+
181
+ Borsh (Binary Object Representation Serializer for Hashing) is NEAR's
182
+ canonical binary serialization format. This implementation handles
183
+ the subset needed for NEP-366 meta-transactions.
184
+ """
185
+
186
+ def __init__(self) -> None:
187
+ self.buffer = bytearray()
188
+
189
+ def write_u8(self, value: int) -> "BorshSerializer":
190
+ """Write unsigned 8-bit integer."""
191
+ self.buffer.extend(struct.pack('<B', value))
192
+ return self
193
+
194
+ def write_u32(self, value: int) -> "BorshSerializer":
195
+ """Write unsigned 32-bit integer (little-endian)."""
196
+ self.buffer.extend(struct.pack('<I', value))
197
+ return self
198
+
199
+ def write_u64(self, value: int) -> "BorshSerializer":
200
+ """Write unsigned 64-bit integer (little-endian)."""
201
+ self.buffer.extend(struct.pack('<Q', value))
202
+ return self
203
+
204
+ def write_u128(self, value: int) -> "BorshSerializer":
205
+ """Write unsigned 128-bit integer (little-endian)."""
206
+ low = value & 0xFFFFFFFFFFFFFFFF
207
+ high = value >> 64
208
+ self.buffer.extend(struct.pack('<QQ', low, high))
209
+ return self
210
+
211
+ def write_string(self, value: str) -> "BorshSerializer":
212
+ """Write length-prefixed UTF-8 string."""
213
+ encoded = value.encode('utf-8')
214
+ self.write_u32(len(encoded))
215
+ self.buffer.extend(encoded)
216
+ return self
217
+
218
+ def write_fixed_bytes(self, data: bytes) -> "BorshSerializer":
219
+ """Write fixed-length bytes (no prefix)."""
220
+ self.buffer.extend(data)
221
+ return self
222
+
223
+ def write_bytes(self, data: bytes) -> "BorshSerializer":
224
+ """Write length-prefixed bytes."""
225
+ self.write_u32(len(data))
226
+ self.buffer.extend(data)
227
+ return self
228
+
229
+ def get_bytes(self) -> bytes:
230
+ """Get the serialized bytes."""
231
+ return bytes(self.buffer)
232
+
233
+
234
+ def serialize_non_delegate_action(
235
+ receiver_id: str,
236
+ amount: int,
237
+ memo: Optional[str] = None,
238
+ ) -> bytes:
239
+ """
240
+ Serialize a NonDelegateAction for ft_transfer (NEP-366).
241
+
242
+ This creates the action that will be wrapped in a DelegateAction.
243
+ The action type is FunctionCall (type 2).
244
+
245
+ Args:
246
+ receiver_id: NEAR account ID to receive tokens
247
+ amount: Amount in raw units (6 decimals for USDC)
248
+ memo: Optional transfer memo
249
+
250
+ Returns:
251
+ Borsh-serialized action bytes
252
+ """
253
+ import json
254
+
255
+ # Build ft_transfer args
256
+ args: Dict[str, Any] = {
257
+ "receiver_id": receiver_id,
258
+ "amount": str(amount),
259
+ }
260
+ if memo:
261
+ args["memo"] = memo
262
+
263
+ args_json = json.dumps(args, separators=(',', ':')).encode('utf-8')
264
+
265
+ ser = BorshSerializer()
266
+ ser.write_u8(2) # FunctionCall action type
267
+ ser.write_string("ft_transfer") # Method name
268
+ ser.write_bytes(args_json) # Args as JSON bytes
269
+ ser.write_u64(30_000_000_000_000) # 30 TGas
270
+ ser.write_u128(1) # 1 yoctoNEAR deposit (required for ft_transfer)
271
+
272
+ return ser.get_bytes()
273
+
274
+
275
+ def serialize_delegate_action(
276
+ sender_id: str,
277
+ receiver_id: str,
278
+ actions_bytes: bytes,
279
+ nonce: int,
280
+ max_block_height: int,
281
+ public_key_bytes: bytes,
282
+ ) -> bytes:
283
+ """
284
+ Serialize a DelegateAction for NEP-366 meta-transactions.
285
+
286
+ Args:
287
+ sender_id: NEAR account ID of the sender
288
+ receiver_id: NEAR contract ID (USDC contract)
289
+ actions_bytes: Borsh-serialized NonDelegateAction
290
+ nonce: Access key nonce + 1
291
+ max_block_height: Block height when action expires
292
+ public_key_bytes: 32-byte ED25519 public key
293
+
294
+ Returns:
295
+ Borsh-serialized DelegateAction bytes
296
+ """
297
+ ser = BorshSerializer()
298
+ ser.write_string(sender_id)
299
+ ser.write_string(receiver_id)
300
+ ser.write_u32(1) # 1 action
301
+ ser.write_fixed_bytes(actions_bytes)
302
+ ser.write_u64(nonce)
303
+ ser.write_u64(max_block_height)
304
+ ser.write_u8(0) # ED25519 key type
305
+ ser.write_fixed_bytes(public_key_bytes)
306
+
307
+ return ser.get_bytes()
308
+
309
+
310
+ def serialize_signed_delegate_action(
311
+ delegate_action_bytes: bytes,
312
+ signature_bytes: bytes,
313
+ ) -> bytes:
314
+ """
315
+ Serialize a SignedDelegateAction for NEP-366.
316
+
317
+ Args:
318
+ delegate_action_bytes: Borsh-serialized DelegateAction
319
+ signature_bytes: 64-byte ED25519 signature
320
+
321
+ Returns:
322
+ Borsh-serialized SignedDelegateAction bytes
323
+ """
324
+ ser = BorshSerializer()
325
+ ser.write_fixed_bytes(delegate_action_bytes)
326
+ ser.write_u8(0) # ED25519 signature type
327
+ ser.write_fixed_bytes(signature_bytes)
328
+
329
+ return ser.get_bytes()
330
+
331
+
332
+ # =============================================================================
333
+ # NEP-366 Payload Validation
334
+ # =============================================================================
335
+
336
+
337
+ def validate_near_payload(payload: Dict[str, Any]) -> bool:
338
+ """
339
+ Validate a NEAR payment payload structure.
340
+
341
+ The payload must contain a base64-encoded SignedDelegateAction.
342
+
343
+ Args:
344
+ payload: Payload dictionary from x402 payment
345
+
346
+ Returns:
347
+ True if valid, raises ValueError if invalid
348
+ """
349
+ if "signedDelegateAction" not in payload:
350
+ raise ValueError("NEAR payload missing 'signedDelegateAction' field")
351
+
352
+ signed_delegate_b64 = payload["signedDelegateAction"]
353
+
354
+ try:
355
+ # Decode base64
356
+ signed_delegate_bytes = base64.b64decode(signed_delegate_b64)
357
+ except Exception as e:
358
+ raise ValueError(f"Invalid base64 in signedDelegateAction: {e}")
359
+
360
+ # Basic length validation
361
+ # Minimum: sender_id (4 + 1) + receiver_id (4 + 1) + actions_count (4) +
362
+ # action (at least 1) + nonce (8) + max_block_height (8) +
363
+ # key_type (1) + public_key (32) + sig_type (1) + signature (64)
364
+ min_length = 4 + 1 + 4 + 1 + 4 + 1 + 8 + 8 + 1 + 32 + 1 + 64
365
+ if len(signed_delegate_bytes) < min_length:
366
+ raise ValueError(
367
+ f"SignedDelegateAction too short: {len(signed_delegate_bytes)} bytes, "
368
+ f"minimum {min_length} bytes"
369
+ )
370
+
371
+ return True
372
+
373
+
374
+ def is_valid_near_account_id(account_id: str) -> bool:
375
+ """
376
+ Validate a NEAR account ID format.
377
+
378
+ NEAR account IDs:
379
+ - Are 2-64 characters
380
+ - Contain only a-z, 0-9, _, -, .
381
+ - Cannot start with _ or -
382
+ - Cannot end with .
383
+
384
+ Args:
385
+ account_id: NEAR account ID to validate
386
+
387
+ Returns:
388
+ True if valid format
389
+ """
390
+ if not account_id or len(account_id) < 2 or len(account_id) > 64:
391
+ return False
392
+
393
+ if account_id.startswith(('_', '-')) or account_id.endswith('.'):
394
+ return False
395
+
396
+ allowed = set('abcdefghijklmnopqrstuvwxyz0123456789_-.')
397
+ return all(c in allowed for c in account_id)
@@ -0,0 +1,269 @@
1
+ """
2
+ Solana Virtual Machine (SVM) network configurations.
3
+
4
+ This module supports all SVM-compatible chains:
5
+ - Solana (mainnet)
6
+ - Fogo (fast finality SVM)
7
+
8
+ All SVM chains use the same payment flow:
9
+ 1. User creates a partially-signed VersionedTransaction
10
+ 2. Transaction contains SPL token TransferChecked instruction
11
+ 3. Facilitator is fee payer (user pays ZERO SOL/tokens)
12
+ 4. Facilitator co-signs and submits transaction
13
+
14
+ Transaction Structure (REQUIRED by facilitator):
15
+ - Instruction 0: SetComputeUnitLimit (units: 20,000)
16
+ - Instruction 1: SetComputeUnitPrice (microLamports: 1)
17
+ - Instruction 2: TransferChecked (USDC transfer)
18
+
19
+ The facilitator validates this exact structure in src/chain/solana.rs.
20
+ """
21
+
22
+ import base64
23
+ from typing import Dict, Any, Optional
24
+
25
+ from uvd_x402_sdk.networks.base import (
26
+ NetworkConfig,
27
+ NetworkType,
28
+ register_network,
29
+ )
30
+
31
+
32
+ # =============================================================================
33
+ # SVM Networks Configuration
34
+ # =============================================================================
35
+
36
+ # Solana Mainnet
37
+ SOLANA = NetworkConfig(
38
+ name="solana",
39
+ display_name="Solana",
40
+ network_type=NetworkType.SVM, # Use SVM type for all Solana-compatible chains
41
+ chain_id=0, # Non-EVM, no chain ID
42
+ usdc_address="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", # USDC SPL token mint
43
+ usdc_decimals=6,
44
+ usdc_domain_name="", # Not applicable for SVM
45
+ usdc_domain_version="",
46
+ rpc_url="https://api.mainnet-beta.solana.com",
47
+ enabled=True,
48
+ extra_config={
49
+ # Token program ID (standard SPL token program)
50
+ "token_program_id": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
51
+ # Associated Token Account program
52
+ "ata_program_id": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL",
53
+ # Default compute units for transfer
54
+ "compute_units": 20000,
55
+ # Default priority fee in microLamports
56
+ "priority_fee_microlamports": 1,
57
+ # Block explorer
58
+ "explorer_url": "https://solscan.io",
59
+ # Genesis hash (first 32 chars for CAIP-2)
60
+ "genesis_hash": "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
61
+ # Network type identifier
62
+ "svm_network": "solana",
63
+ },
64
+ )
65
+
66
+ # Fogo (SVM chain with ultra-fast finality)
67
+ FOGO = NetworkConfig(
68
+ name="fogo",
69
+ display_name="Fogo",
70
+ network_type=NetworkType.SVM,
71
+ chain_id=0, # Non-EVM, no chain ID
72
+ usdc_address="uSd2czE61Evaf76RNbq4KPpXnkiL3irdzgLFUMe3NoG", # Fogo USDC mint
73
+ usdc_decimals=6,
74
+ usdc_domain_name="", # Not applicable for SVM
75
+ usdc_domain_version="",
76
+ rpc_url="https://rpc.fogo.nightly.app/",
77
+ enabled=True,
78
+ extra_config={
79
+ # Token program ID (standard SPL token program - same as Solana)
80
+ "token_program_id": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
81
+ # Associated Token Account program (same as Solana)
82
+ "ata_program_id": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL",
83
+ # Default compute units for transfer
84
+ "compute_units": 20000,
85
+ # Default priority fee in microLamports
86
+ "priority_fee_microlamports": 1,
87
+ # Block explorer (placeholder - update when available)
88
+ "explorer_url": "https://explorer.fogo.nightly.app",
89
+ # Network type identifier
90
+ "svm_network": "fogo",
91
+ # Fogo-specific: Ultra-fast finality (~400ms)
92
+ "finality_ms": 400,
93
+ },
94
+ )
95
+
96
+ # Register SVM networks
97
+ register_network(SOLANA)
98
+ register_network(FOGO)
99
+
100
+
101
+ # =============================================================================
102
+ # SVM-specific utilities
103
+ # =============================================================================
104
+
105
+
106
+ def is_svm_network(network_name: str) -> bool:
107
+ """
108
+ Check if a network is SVM-compatible.
109
+
110
+ Args:
111
+ network_name: Network name to check
112
+
113
+ Returns:
114
+ True if network uses SVM (Solana, Fogo, etc.)
115
+ """
116
+ from uvd_x402_sdk.networks.base import get_network, NetworkType
117
+
118
+ network = get_network(network_name)
119
+ if not network:
120
+ return False
121
+ return NetworkType.is_svm(network.network_type)
122
+
123
+
124
+ def get_svm_networks() -> list:
125
+ """
126
+ Get all registered SVM networks.
127
+
128
+ Returns:
129
+ List of SVM NetworkConfig instances
130
+ """
131
+ from uvd_x402_sdk.networks.base import list_networks, NetworkType
132
+
133
+ return [
134
+ n for n in list_networks(enabled_only=True)
135
+ if NetworkType.is_svm(n.network_type)
136
+ ]
137
+
138
+
139
+ def get_associated_token_address(owner: str, mint: str) -> str:
140
+ """
141
+ Derive the Associated Token Account (ATA) address for an owner and mint.
142
+
143
+ Note: This is a placeholder. For actual derivation, use the solana-py library:
144
+
145
+ from solders.pubkey import Pubkey
146
+ from spl.token.constants import TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID
147
+ from spl.token.instructions import get_associated_token_address
148
+
149
+ Args:
150
+ owner: Owner's public key (base58)
151
+ mint: Token mint address (base58)
152
+
153
+ Returns:
154
+ Associated token account address (base58)
155
+ """
156
+ # This would require solders/solana-py for actual implementation
157
+ # Returning empty string as placeholder
158
+ raise NotImplementedError(
159
+ "ATA derivation requires solana-py library. "
160
+ "Install with: pip install uvd-x402-sdk[solana]"
161
+ )
162
+
163
+
164
+ def validate_svm_transaction_structure(transaction_base64: str) -> bool:
165
+ """
166
+ Validate that an SVM transaction has the correct structure for x402.
167
+
168
+ The facilitator expects:
169
+ - VersionedTransaction with exactly 3 instructions
170
+ - Instruction 0: SetComputeUnitLimit
171
+ - Instruction 1: SetComputeUnitPrice
172
+ - Instruction 2: TransferChecked (SPL token)
173
+
174
+ Args:
175
+ transaction_base64: Base64-encoded serialized transaction
176
+
177
+ Returns:
178
+ True if structure is valid
179
+
180
+ Raises:
181
+ ValueError: If structure is invalid
182
+ """
183
+ try:
184
+ tx_bytes = base64.b64decode(transaction_base64)
185
+ except Exception as e:
186
+ raise ValueError(f"Invalid base64 transaction: {e}")
187
+
188
+ # Basic length validation
189
+ # Minimum: version (1) + header (3) + accounts array (varies) + blockhash (32) + instructions
190
+ if len(tx_bytes) < 50:
191
+ raise ValueError(f"Transaction too short: {len(tx_bytes)} bytes")
192
+
193
+ # Full validation requires solders/solana-py
194
+ # For now, we just check basic structure
195
+ return True
196
+
197
+
198
+ def validate_svm_payload(payload: Dict[str, Any]) -> bool:
199
+ """
200
+ Validate an SVM payment payload structure.
201
+
202
+ The payload must contain a base64-encoded partially-signed transaction.
203
+
204
+ Args:
205
+ payload: Payload dictionary from x402 payment
206
+
207
+ Returns:
208
+ True if valid, raises ValueError if invalid
209
+ """
210
+ if "transaction" not in payload:
211
+ raise ValueError("SVM payload missing 'transaction' field")
212
+
213
+ transaction_b64 = payload["transaction"]
214
+
215
+ try:
216
+ tx_bytes = base64.b64decode(transaction_b64)
217
+ except Exception as e:
218
+ raise ValueError(f"Invalid base64 in transaction: {e}")
219
+
220
+ # Basic length validation
221
+ if len(tx_bytes) < 50:
222
+ raise ValueError(f"Transaction too short: {len(tx_bytes)} bytes")
223
+
224
+ return True
225
+
226
+
227
+ def is_valid_solana_address(address: str) -> bool:
228
+ """
229
+ Validate a Solana/SVM public key format.
230
+
231
+ Solana addresses are base58-encoded 32-byte public keys.
232
+
233
+ Args:
234
+ address: Address to validate
235
+
236
+ Returns:
237
+ True if valid base58 address
238
+ """
239
+ if not address or not isinstance(address, str):
240
+ return False
241
+
242
+ # Base58 alphabet (no 0, O, I, l)
243
+ base58_alphabet = set("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")
244
+
245
+ if not all(c in base58_alphabet for c in address):
246
+ return False
247
+
248
+ # Typical Solana address length is 32-44 characters
249
+ return 32 <= len(address) <= 44
250
+
251
+
252
+ # =============================================================================
253
+ # Transaction Building Utilities (for reference)
254
+ # =============================================================================
255
+
256
+ # These constants are useful for building transactions programmatically
257
+
258
+ # Compute Budget Program
259
+ COMPUTE_BUDGET_PROGRAM_ID = "ComputeBudget111111111111111111111111111111"
260
+
261
+ # SetComputeUnitLimit instruction discriminator
262
+ SET_COMPUTE_UNIT_LIMIT_DISCRIMINATOR = 2
263
+
264
+ # SetComputeUnitPrice instruction discriminator
265
+ SET_COMPUTE_UNIT_PRICE_DISCRIMINATOR = 3
266
+
267
+ # Default values for x402 transactions
268
+ DEFAULT_COMPUTE_UNITS = 20000
269
+ DEFAULT_PRIORITY_FEE_MICROLAMPORTS = 1