t402 1.9.0__py3-none-any.whl → 1.10.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.
- t402/__init__.py +2 -1
- t402/a2a/__init__.py +73 -0
- t402/a2a/helpers.py +158 -0
- t402/a2a/types.py +145 -0
- t402/bridge/client.py +13 -5
- t402/bridge/constants.py +4 -2
- t402/bridge/router.py +1 -1
- t402/bridge/scan.py +3 -1
- t402/chains.py +268 -1
- t402/cli.py +31 -9
- t402/common.py +2 -0
- t402/cosmos_paywall_template.py +2 -0
- t402/django/__init__.py +42 -0
- t402/django/middleware.py +596 -0
- t402/encoding.py +9 -3
- t402/erc4337/accounts.py +56 -51
- t402/erc4337/bundlers.py +105 -99
- t402/erc4337/paymasters.py +100 -109
- t402/erc4337/types.py +39 -26
- t402/errors.py +213 -0
- t402/evm_paywall_template.py +1 -1
- t402/facilitator.py +125 -0
- t402/fastapi/middleware.py +1 -3
- t402/mcp/constants.py +3 -6
- t402/mcp/server.py +501 -84
- t402/mcp/web3_utils.py +493 -0
- t402/multisig/__init__.py +120 -0
- t402/multisig/constants.py +54 -0
- t402/multisig/safe.py +441 -0
- t402/multisig/signature.py +228 -0
- t402/multisig/transaction.py +238 -0
- t402/multisig/types.py +108 -0
- t402/multisig/utils.py +77 -0
- t402/near_paywall_template.py +2 -0
- t402/networks.py +34 -1
- t402/paywall.py +1 -3
- t402/schemes/__init__.py +143 -0
- t402/schemes/aptos/__init__.py +70 -0
- t402/schemes/aptos/constants.py +349 -0
- t402/schemes/aptos/exact_direct/__init__.py +44 -0
- t402/schemes/aptos/exact_direct/client.py +202 -0
- t402/schemes/aptos/exact_direct/facilitator.py +426 -0
- t402/schemes/aptos/exact_direct/server.py +272 -0
- t402/schemes/aptos/types.py +237 -0
- t402/schemes/cosmos/__init__.py +114 -0
- t402/schemes/cosmos/constants.py +211 -0
- t402/schemes/cosmos/exact_direct/__init__.py +21 -0
- t402/schemes/cosmos/exact_direct/client.py +198 -0
- t402/schemes/cosmos/exact_direct/facilitator.py +493 -0
- t402/schemes/cosmos/exact_direct/server.py +315 -0
- t402/schemes/cosmos/types.py +501 -0
- t402/schemes/evm/__init__.py +46 -1
- t402/schemes/evm/exact/__init__.py +11 -0
- t402/schemes/evm/exact/client.py +3 -1
- t402/schemes/evm/exact/facilitator.py +894 -0
- t402/schemes/evm/exact/server.py +1 -1
- t402/schemes/evm/exact_legacy/__init__.py +38 -0
- t402/schemes/evm/exact_legacy/client.py +291 -0
- t402/schemes/evm/exact_legacy/facilitator.py +777 -0
- t402/schemes/evm/exact_legacy/server.py +231 -0
- t402/schemes/evm/upto/__init__.py +12 -0
- t402/schemes/evm/upto/client.py +6 -2
- t402/schemes/evm/upto/facilitator.py +625 -0
- t402/schemes/evm/upto/server.py +243 -0
- t402/schemes/evm/upto/types.py +3 -1
- t402/schemes/interfaces.py +6 -2
- t402/schemes/near/__init__.py +137 -0
- t402/schemes/near/constants.py +189 -0
- t402/schemes/near/exact_direct/__init__.py +21 -0
- t402/schemes/near/exact_direct/client.py +204 -0
- t402/schemes/near/exact_direct/facilitator.py +455 -0
- t402/schemes/near/exact_direct/server.py +303 -0
- t402/schemes/near/types.py +419 -0
- t402/schemes/near/upto/__init__.py +54 -0
- t402/schemes/near/upto/types.py +272 -0
- t402/schemes/polkadot/__init__.py +72 -0
- t402/schemes/polkadot/constants.py +155 -0
- t402/schemes/polkadot/exact_direct/__init__.py +43 -0
- t402/schemes/polkadot/exact_direct/client.py +235 -0
- t402/schemes/polkadot/exact_direct/facilitator.py +428 -0
- t402/schemes/polkadot/exact_direct/server.py +292 -0
- t402/schemes/polkadot/types.py +385 -0
- t402/schemes/registry.py +6 -2
- t402/schemes/stacks/__init__.py +68 -0
- t402/schemes/stacks/constants.py +122 -0
- t402/schemes/stacks/exact_direct/__init__.py +43 -0
- t402/schemes/stacks/exact_direct/client.py +222 -0
- t402/schemes/stacks/exact_direct/facilitator.py +424 -0
- t402/schemes/stacks/exact_direct/server.py +292 -0
- t402/schemes/stacks/types.py +380 -0
- t402/schemes/svm/__init__.py +44 -0
- t402/schemes/svm/exact/__init__.py +35 -0
- t402/schemes/svm/exact/client.py +23 -0
- t402/schemes/svm/exact/facilitator.py +24 -0
- t402/schemes/svm/exact/server.py +20 -0
- t402/schemes/svm/upto/__init__.py +23 -0
- t402/schemes/svm/upto/types.py +193 -0
- t402/schemes/tezos/__init__.py +84 -0
- t402/schemes/tezos/constants.py +372 -0
- t402/schemes/tezos/exact_direct/__init__.py +22 -0
- t402/schemes/tezos/exact_direct/client.py +226 -0
- t402/schemes/tezos/exact_direct/facilitator.py +491 -0
- t402/schemes/tezos/exact_direct/server.py +277 -0
- t402/schemes/tezos/types.py +220 -0
- t402/schemes/ton/__init__.py +24 -2
- t402/schemes/ton/exact/__init__.py +7 -0
- t402/schemes/ton/exact/facilitator.py +730 -0
- t402/schemes/ton/exact/server.py +1 -1
- t402/schemes/ton/upto/__init__.py +31 -0
- t402/schemes/ton/upto/types.py +215 -0
- t402/schemes/tron/__init__.py +28 -2
- t402/schemes/tron/exact/__init__.py +9 -0
- t402/schemes/tron/exact/facilitator.py +673 -0
- t402/schemes/tron/exact/server.py +1 -1
- t402/schemes/tron/upto/__init__.py +30 -0
- t402/schemes/tron/upto/types.py +213 -0
- t402/stacks_paywall_template.py +2 -0
- t402/starlette/__init__.py +38 -0
- t402/starlette/middleware.py +522 -0
- t402/svm.py +45 -11
- t402/svm_paywall_template.py +1 -1
- t402/ton.py +6 -2
- t402/ton_paywall_template.py +1 -192
- t402/tron.py +2 -0
- t402/tron_paywall_template.py +2 -0
- t402/types.py +103 -3
- t402/wdk/chains.py +1 -1
- t402/wdk/errors.py +15 -5
- t402/wdk/signer.py +11 -2
- {t402-1.9.0.dist-info → t402-1.10.0.dist-info}/METADATA +42 -1
- t402-1.10.0.dist-info/RECORD +156 -0
- t402-1.9.0.dist-info/RECORD +0 -72
- {t402-1.9.0.dist-info → t402-1.10.0.dist-info}/WHEEL +0 -0
- {t402-1.9.0.dist-info → t402-1.10.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Stacks Blockchain Payment Schemes.
|
|
2
|
+
|
|
3
|
+
This package provides payment scheme implementations for Stacks (Bitcoin L2).
|
|
4
|
+
|
|
5
|
+
Supported schemes:
|
|
6
|
+
- exact-direct: SIP-010 token transfers verified on-chain
|
|
7
|
+
|
|
8
|
+
Supported networks:
|
|
9
|
+
- stacks:1 (Stacks Mainnet)
|
|
10
|
+
- stacks:2147483648 (Stacks Testnet)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from t402.schemes.stacks.exact_direct import (
|
|
14
|
+
ExactDirectStacksClientScheme,
|
|
15
|
+
ExactDirectStacksServerScheme,
|
|
16
|
+
ExactDirectStacksFacilitatorScheme,
|
|
17
|
+
ClientStacksSigner,
|
|
18
|
+
FacilitatorStacksSigner,
|
|
19
|
+
SCHEME_EXACT_DIRECT,
|
|
20
|
+
)
|
|
21
|
+
from t402.schemes.stacks.constants import (
|
|
22
|
+
STACKS_MAINNET_CAIP2,
|
|
23
|
+
STACKS_TESTNET_CAIP2,
|
|
24
|
+
SUSDC_DECIMALS,
|
|
25
|
+
NETWORKS,
|
|
26
|
+
get_network_config,
|
|
27
|
+
get_supported_networks,
|
|
28
|
+
is_stacks_network,
|
|
29
|
+
)
|
|
30
|
+
from t402.schemes.stacks.types import (
|
|
31
|
+
ExactDirectPayload,
|
|
32
|
+
TransactionResult,
|
|
33
|
+
ParsedTokenTransfer,
|
|
34
|
+
is_valid_stacks_address,
|
|
35
|
+
is_valid_tx_id,
|
|
36
|
+
parse_contract_identifier,
|
|
37
|
+
create_asset_identifier,
|
|
38
|
+
extract_token_transfer,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
# Schemes
|
|
43
|
+
"ExactDirectStacksClientScheme",
|
|
44
|
+
"ExactDirectStacksServerScheme",
|
|
45
|
+
"ExactDirectStacksFacilitatorScheme",
|
|
46
|
+
# Signer protocols
|
|
47
|
+
"ClientStacksSigner",
|
|
48
|
+
"FacilitatorStacksSigner",
|
|
49
|
+
# Constants
|
|
50
|
+
"SCHEME_EXACT_DIRECT",
|
|
51
|
+
"STACKS_MAINNET_CAIP2",
|
|
52
|
+
"STACKS_TESTNET_CAIP2",
|
|
53
|
+
"SUSDC_DECIMALS",
|
|
54
|
+
"NETWORKS",
|
|
55
|
+
# Functions
|
|
56
|
+
"get_network_config",
|
|
57
|
+
"get_supported_networks",
|
|
58
|
+
"is_stacks_network",
|
|
59
|
+
# Types
|
|
60
|
+
"ExactDirectPayload",
|
|
61
|
+
"TransactionResult",
|
|
62
|
+
"ParsedTokenTransfer",
|
|
63
|
+
"is_valid_stacks_address",
|
|
64
|
+
"is_valid_tx_id",
|
|
65
|
+
"parse_contract_identifier",
|
|
66
|
+
"create_asset_identifier",
|
|
67
|
+
"extract_token_transfer",
|
|
68
|
+
]
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Stacks Scheme Constants.
|
|
2
|
+
|
|
3
|
+
This module defines constants for the Stacks exact-direct payment scheme,
|
|
4
|
+
including network identifiers, token configurations, and default endpoints.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Dict
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Scheme identifier
|
|
14
|
+
SCHEME_EXACT_DIRECT = "exact-direct"
|
|
15
|
+
|
|
16
|
+
# CAIP-2 network identifiers for Stacks
|
|
17
|
+
STACKS_MAINNET_CAIP2 = "stacks:1"
|
|
18
|
+
STACKS_TESTNET_CAIP2 = "stacks:2147483648"
|
|
19
|
+
|
|
20
|
+
# Default Hiro API endpoints
|
|
21
|
+
STACKS_MAINNET_API = "https://api.hiro.so"
|
|
22
|
+
STACKS_TESTNET_API = "https://api.testnet.hiro.so"
|
|
23
|
+
|
|
24
|
+
# sUSDC token decimals
|
|
25
|
+
SUSDC_DECIMALS = 6
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class TokenInfo:
|
|
30
|
+
"""Token configuration for a Stacks asset."""
|
|
31
|
+
|
|
32
|
+
contract_address: str
|
|
33
|
+
symbol: str
|
|
34
|
+
name: str
|
|
35
|
+
decimals: int
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True)
|
|
39
|
+
class NetworkConfig:
|
|
40
|
+
"""Configuration for a Stacks network."""
|
|
41
|
+
|
|
42
|
+
name: str
|
|
43
|
+
caip2: str
|
|
44
|
+
api_url: str
|
|
45
|
+
chain_id: int
|
|
46
|
+
is_testnet: bool
|
|
47
|
+
default_token: TokenInfo
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# Default token configurations
|
|
51
|
+
SUSDC_MAINNET = TokenInfo(
|
|
52
|
+
contract_address="SP3Y2ZSH8P7D50B0VBTSX11S7XSG24M1VB9YFQA4K.token-susdc",
|
|
53
|
+
symbol="sUSDC",
|
|
54
|
+
name="Stacks USDC",
|
|
55
|
+
decimals=SUSDC_DECIMALS,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
SUSDC_TESTNET = TokenInfo(
|
|
59
|
+
contract_address="ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.token-susdc",
|
|
60
|
+
symbol="sUSDC",
|
|
61
|
+
name="Test Stacks USDC",
|
|
62
|
+
decimals=SUSDC_DECIMALS,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Network configurations indexed by CAIP-2 identifier
|
|
66
|
+
NETWORKS: Dict[str, NetworkConfig] = {
|
|
67
|
+
STACKS_MAINNET_CAIP2: NetworkConfig(
|
|
68
|
+
name="Stacks Mainnet",
|
|
69
|
+
caip2=STACKS_MAINNET_CAIP2,
|
|
70
|
+
api_url=STACKS_MAINNET_API,
|
|
71
|
+
chain_id=1,
|
|
72
|
+
is_testnet=False,
|
|
73
|
+
default_token=SUSDC_MAINNET,
|
|
74
|
+
),
|
|
75
|
+
STACKS_TESTNET_CAIP2: NetworkConfig(
|
|
76
|
+
name="Stacks Testnet",
|
|
77
|
+
caip2=STACKS_TESTNET_CAIP2,
|
|
78
|
+
api_url=STACKS_TESTNET_API,
|
|
79
|
+
chain_id=2147483648,
|
|
80
|
+
is_testnet=True,
|
|
81
|
+
default_token=SUSDC_TESTNET,
|
|
82
|
+
),
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_network_config(network: str) -> NetworkConfig:
|
|
87
|
+
"""Get the network configuration for a CAIP-2 identifier.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
network: CAIP-2 network identifier (e.g., "stacks:1")
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
NetworkConfig for the given network
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
ValueError: If the network is not supported
|
|
97
|
+
"""
|
|
98
|
+
config = NETWORKS.get(network)
|
|
99
|
+
if config is None:
|
|
100
|
+
raise ValueError(f"Unsupported Stacks network: {network}")
|
|
101
|
+
return config
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def is_stacks_network(network: str) -> bool:
|
|
105
|
+
"""Check if a network identifier is a Stacks network.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
network: Network identifier to check
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
True if the network starts with "stacks:"
|
|
112
|
+
"""
|
|
113
|
+
return network.startswith("stacks:")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def get_supported_networks() -> list:
|
|
117
|
+
"""Get all supported Stacks network identifiers.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
List of CAIP-2 network identifiers
|
|
121
|
+
"""
|
|
122
|
+
return list(NETWORKS.keys())
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Stacks Exact-Direct Payment Scheme.
|
|
2
|
+
|
|
3
|
+
This package provides the exact-direct payment scheme implementation for
|
|
4
|
+
Stacks (Bitcoin L2) networks using SIP-010 token transfers.
|
|
5
|
+
|
|
6
|
+
The exact-direct scheme works by:
|
|
7
|
+
1. Client executes a SIP-010 token transfer on-chain
|
|
8
|
+
2. Client returns the transaction ID as proof
|
|
9
|
+
3. Facilitator verifies the transfer on-chain via Hiro API
|
|
10
|
+
|
|
11
|
+
This is a "direct" scheme because the client pays on-chain before
|
|
12
|
+
submitting the payment proof, rather than providing an off-chain signature
|
|
13
|
+
for later settlement.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from t402.schemes.stacks.exact_direct.client import (
|
|
17
|
+
ExactDirectStacksClientScheme,
|
|
18
|
+
)
|
|
19
|
+
from t402.schemes.stacks.exact_direct.server import (
|
|
20
|
+
ExactDirectStacksServerScheme,
|
|
21
|
+
)
|
|
22
|
+
from t402.schemes.stacks.exact_direct.facilitator import (
|
|
23
|
+
ExactDirectStacksFacilitatorScheme,
|
|
24
|
+
)
|
|
25
|
+
from t402.schemes.stacks.constants import SCHEME_EXACT_DIRECT
|
|
26
|
+
from t402.schemes.stacks.types import (
|
|
27
|
+
ClientStacksSigner,
|
|
28
|
+
FacilitatorStacksSigner,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
# Client
|
|
33
|
+
"ExactDirectStacksClientScheme",
|
|
34
|
+
# Server
|
|
35
|
+
"ExactDirectStacksServerScheme",
|
|
36
|
+
# Facilitator
|
|
37
|
+
"ExactDirectStacksFacilitatorScheme",
|
|
38
|
+
# Signer protocols
|
|
39
|
+
"ClientStacksSigner",
|
|
40
|
+
"FacilitatorStacksSigner",
|
|
41
|
+
# Constants
|
|
42
|
+
"SCHEME_EXACT_DIRECT",
|
|
43
|
+
]
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""Stacks Exact-Direct Scheme - Client Implementation.
|
|
2
|
+
|
|
3
|
+
This module provides the client-side implementation of the exact-direct
|
|
4
|
+
payment scheme for Stacks (Bitcoin L2) networks.
|
|
5
|
+
|
|
6
|
+
The client:
|
|
7
|
+
1. Builds a SIP-010 token transfer contract call
|
|
8
|
+
2. Signs and submits it on-chain via the signer
|
|
9
|
+
3. Returns the transaction ID as payment proof
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
from typing import Any, Dict, Union
|
|
16
|
+
|
|
17
|
+
from t402.types import (
|
|
18
|
+
PaymentRequirementsV2,
|
|
19
|
+
T402_VERSION_V1,
|
|
20
|
+
T402_VERSION_V2,
|
|
21
|
+
)
|
|
22
|
+
from t402.schemes.stacks.constants import (
|
|
23
|
+
SCHEME_EXACT_DIRECT,
|
|
24
|
+
get_network_config,
|
|
25
|
+
is_stacks_network,
|
|
26
|
+
)
|
|
27
|
+
from t402.schemes.stacks.types import (
|
|
28
|
+
ClientStacksSigner,
|
|
29
|
+
ExactDirectPayload,
|
|
30
|
+
is_valid_stacks_address,
|
|
31
|
+
is_valid_tx_id,
|
|
32
|
+
parse_contract_identifier,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ExactDirectStacksClientScheme:
|
|
40
|
+
"""Client scheme for Stacks exact-direct payments.
|
|
41
|
+
|
|
42
|
+
Executes on-chain SIP-010 token transfers and returns the transaction
|
|
43
|
+
ID as a payment payload.
|
|
44
|
+
|
|
45
|
+
Example:
|
|
46
|
+
```python
|
|
47
|
+
scheme = ExactDirectStacksClientScheme(signer=my_stacks_signer)
|
|
48
|
+
|
|
49
|
+
payload = await scheme.create_payment_payload(
|
|
50
|
+
t402_version=2,
|
|
51
|
+
requirements={
|
|
52
|
+
"scheme": "exact-direct",
|
|
53
|
+
"network": "stacks:1",
|
|
54
|
+
"asset": "stacks:1/token:SP3Y2ZSH8P7D50B0VBTSX11S7XSG24M1VB9YFQA4K.token-susdc",
|
|
55
|
+
"amount": "1000000",
|
|
56
|
+
"payTo": "SP3Y2ZSH8P7D50B0VBTSX11S7XSG24M1VB9YFQA4K",
|
|
57
|
+
"maxTimeoutSeconds": 300,
|
|
58
|
+
},
|
|
59
|
+
)
|
|
60
|
+
```
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
scheme = SCHEME_EXACT_DIRECT
|
|
64
|
+
caip_family = "stacks:*"
|
|
65
|
+
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
signer: ClientStacksSigner,
|
|
69
|
+
):
|
|
70
|
+
"""Initialize the Stacks client scheme.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
signer: Stacks signer for signing and submitting token transfers
|
|
74
|
+
"""
|
|
75
|
+
self._signer = signer
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def address(self) -> str:
|
|
79
|
+
"""Return the signer's Stacks address."""
|
|
80
|
+
return self._signer.address
|
|
81
|
+
|
|
82
|
+
async def create_payment_payload(
|
|
83
|
+
self,
|
|
84
|
+
t402_version: int,
|
|
85
|
+
requirements: Union[PaymentRequirementsV2, Dict[str, Any]],
|
|
86
|
+
) -> Dict[str, Any]:
|
|
87
|
+
"""Create a payment payload by executing an on-chain token transfer.
|
|
88
|
+
|
|
89
|
+
Validates the requirements, resolves the contract address, calls
|
|
90
|
+
the signer to execute the transfer, then returns the proof.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
t402_version: Protocol version (1 or 2)
|
|
94
|
+
requirements: Payment requirements specifying the transfer details
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Payment payload dictionary with transaction proof
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
ValueError: If requirements are invalid (bad network, address, amount, etc.)
|
|
101
|
+
Exception: If signing or submission fails
|
|
102
|
+
"""
|
|
103
|
+
# Convert to dict for easier access
|
|
104
|
+
if hasattr(requirements, "model_dump"):
|
|
105
|
+
req = requirements.model_dump(by_alias=True)
|
|
106
|
+
else:
|
|
107
|
+
req = dict(requirements)
|
|
108
|
+
|
|
109
|
+
# Extract fields
|
|
110
|
+
network = req.get("network", "")
|
|
111
|
+
asset = req.get("asset", "")
|
|
112
|
+
amount = req.get("amount", "0")
|
|
113
|
+
pay_to = req.get("payTo", "")
|
|
114
|
+
extra = req.get("extra", {})
|
|
115
|
+
|
|
116
|
+
# Validate network
|
|
117
|
+
if not is_stacks_network(network):
|
|
118
|
+
raise ValueError(f"Unsupported network: {network}")
|
|
119
|
+
|
|
120
|
+
network_config = get_network_config(network)
|
|
121
|
+
|
|
122
|
+
# Validate payTo address
|
|
123
|
+
if not pay_to:
|
|
124
|
+
raise ValueError("payTo address is required")
|
|
125
|
+
if not is_valid_stacks_address(pay_to):
|
|
126
|
+
raise ValueError(f"Invalid payTo address: {pay_to}")
|
|
127
|
+
|
|
128
|
+
# Validate amount
|
|
129
|
+
if not amount:
|
|
130
|
+
raise ValueError("Amount is required")
|
|
131
|
+
try:
|
|
132
|
+
amount_int = int(amount)
|
|
133
|
+
except (ValueError, TypeError):
|
|
134
|
+
raise ValueError(f"Invalid amount format: {amount}")
|
|
135
|
+
if amount_int <= 0:
|
|
136
|
+
raise ValueError(f"Amount must be positive: {amount}")
|
|
137
|
+
|
|
138
|
+
# Resolve contract address
|
|
139
|
+
contract_address = self._resolve_contract_address(
|
|
140
|
+
asset, extra, network_config
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Get sender address
|
|
144
|
+
from_address = self._signer.address
|
|
145
|
+
if not from_address:
|
|
146
|
+
raise ValueError("Signer address is empty")
|
|
147
|
+
|
|
148
|
+
# Execute the token transfer
|
|
149
|
+
tx_id = await self._signer.transfer_token(
|
|
150
|
+
contract_address=contract_address,
|
|
151
|
+
to=pay_to,
|
|
152
|
+
amount=amount_int,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# Validate result
|
|
156
|
+
if not tx_id:
|
|
157
|
+
raise ValueError("Transfer returned empty transaction ID")
|
|
158
|
+
|
|
159
|
+
if not is_valid_tx_id(tx_id):
|
|
160
|
+
raise ValueError(f"Invalid transaction ID format: {tx_id}")
|
|
161
|
+
|
|
162
|
+
# Build the payload
|
|
163
|
+
payload = ExactDirectPayload(
|
|
164
|
+
tx_id=tx_id,
|
|
165
|
+
from_address=from_address,
|
|
166
|
+
to_address=pay_to,
|
|
167
|
+
amount=amount,
|
|
168
|
+
contract_address=contract_address,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
if t402_version == T402_VERSION_V1:
|
|
172
|
+
return {
|
|
173
|
+
"t402Version": T402_VERSION_V1,
|
|
174
|
+
"scheme": self.scheme,
|
|
175
|
+
"network": network,
|
|
176
|
+
"payload": payload.to_dict(),
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
# V2 format
|
|
180
|
+
return {
|
|
181
|
+
"t402Version": T402_VERSION_V2,
|
|
182
|
+
"payload": payload.to_dict(),
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
def _resolve_contract_address(
|
|
186
|
+
self,
|
|
187
|
+
asset: str,
|
|
188
|
+
extra: Dict[str, Any],
|
|
189
|
+
network_config: Any,
|
|
190
|
+
) -> str:
|
|
191
|
+
"""Resolve the contract address from requirements fields.
|
|
192
|
+
|
|
193
|
+
Tries to determine the contract address from:
|
|
194
|
+
1. The extra.contractAddress field
|
|
195
|
+
2. The CAIP-19 asset identifier
|
|
196
|
+
3. The network's default token
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
asset: CAIP-19 asset identifier string
|
|
200
|
+
extra: Extra metadata from requirements
|
|
201
|
+
network_config: Network configuration
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Resolved contract address
|
|
205
|
+
|
|
206
|
+
Raises:
|
|
207
|
+
ValueError: If contract address cannot be determined
|
|
208
|
+
"""
|
|
209
|
+
# Try extra.contractAddress first
|
|
210
|
+
if extra and "contractAddress" in extra:
|
|
211
|
+
contract_val = extra["contractAddress"]
|
|
212
|
+
if isinstance(contract_val, str) and contract_val:
|
|
213
|
+
return contract_val
|
|
214
|
+
|
|
215
|
+
# Try parsing CAIP-19 asset identifier
|
|
216
|
+
if asset:
|
|
217
|
+
parsed = parse_contract_identifier(asset)
|
|
218
|
+
if parsed is not None:
|
|
219
|
+
return parsed
|
|
220
|
+
|
|
221
|
+
# Fall back to network default
|
|
222
|
+
return network_config.default_token.contract_address
|