t402 1.7.1__py3-none-any.whl → 1.9.1__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.
Files changed (102) hide show
  1. t402/__init__.py +2 -1
  2. t402/bridge/client.py +13 -5
  3. t402/bridge/constants.py +3 -1
  4. t402/bridge/router.py +1 -1
  5. t402/bridge/scan.py +3 -1
  6. t402/chains.py +268 -1
  7. t402/cli.py +31 -9
  8. t402/common.py +2 -0
  9. t402/cosmos_paywall_template.py +2 -0
  10. t402/encoding.py +9 -3
  11. t402/erc4337/accounts.py +56 -51
  12. t402/erc4337/bundlers.py +105 -99
  13. t402/erc4337/paymasters.py +100 -109
  14. t402/erc4337/types.py +39 -26
  15. t402/evm_paywall_template.py +1 -1
  16. t402/fastapi/middleware.py +1 -3
  17. t402/mcp/server.py +79 -46
  18. t402/near_paywall_template.py +2 -0
  19. t402/networks.py +34 -1
  20. t402/paywall.py +1 -3
  21. t402/schemes/__init__.py +164 -1
  22. t402/schemes/aptos/__init__.py +70 -0
  23. t402/schemes/aptos/constants.py +349 -0
  24. t402/schemes/aptos/exact_direct/__init__.py +44 -0
  25. t402/schemes/aptos/exact_direct/client.py +202 -0
  26. t402/schemes/aptos/exact_direct/facilitator.py +426 -0
  27. t402/schemes/aptos/exact_direct/server.py +272 -0
  28. t402/schemes/aptos/types.py +237 -0
  29. t402/schemes/evm/__init__.py +67 -1
  30. t402/schemes/evm/exact/__init__.py +11 -0
  31. t402/schemes/evm/exact/client.py +3 -1
  32. t402/schemes/evm/exact/facilitator.py +894 -0
  33. t402/schemes/evm/exact/server.py +1 -1
  34. t402/schemes/evm/exact_legacy/__init__.py +38 -0
  35. t402/schemes/evm/exact_legacy/client.py +291 -0
  36. t402/schemes/evm/exact_legacy/facilitator.py +777 -0
  37. t402/schemes/evm/exact_legacy/server.py +231 -0
  38. t402/schemes/evm/upto/__init__.py +70 -0
  39. t402/schemes/evm/upto/client.py +244 -0
  40. t402/schemes/evm/upto/facilitator.py +625 -0
  41. t402/schemes/evm/upto/server.py +243 -0
  42. t402/schemes/evm/upto/types.py +307 -0
  43. t402/schemes/interfaces.py +6 -2
  44. t402/schemes/near/__init__.py +112 -0
  45. t402/schemes/near/constants.py +189 -0
  46. t402/schemes/near/exact_direct/__init__.py +21 -0
  47. t402/schemes/near/exact_direct/client.py +204 -0
  48. t402/schemes/near/exact_direct/facilitator.py +455 -0
  49. t402/schemes/near/exact_direct/server.py +303 -0
  50. t402/schemes/near/types.py +419 -0
  51. t402/schemes/polkadot/__init__.py +72 -0
  52. t402/schemes/polkadot/constants.py +155 -0
  53. t402/schemes/polkadot/exact_direct/__init__.py +43 -0
  54. t402/schemes/polkadot/exact_direct/client.py +235 -0
  55. t402/schemes/polkadot/exact_direct/facilitator.py +428 -0
  56. t402/schemes/polkadot/exact_direct/server.py +292 -0
  57. t402/schemes/polkadot/types.py +385 -0
  58. t402/schemes/registry.py +6 -2
  59. t402/schemes/stacks/__init__.py +68 -0
  60. t402/schemes/stacks/constants.py +122 -0
  61. t402/schemes/stacks/exact_direct/__init__.py +43 -0
  62. t402/schemes/stacks/exact_direct/client.py +222 -0
  63. t402/schemes/stacks/exact_direct/facilitator.py +424 -0
  64. t402/schemes/stacks/exact_direct/server.py +292 -0
  65. t402/schemes/stacks/types.py +380 -0
  66. t402/schemes/svm/__init__.py +29 -0
  67. t402/schemes/svm/exact/__init__.py +35 -0
  68. t402/schemes/svm/exact/client.py +23 -0
  69. t402/schemes/svm/exact/facilitator.py +24 -0
  70. t402/schemes/svm/exact/server.py +20 -0
  71. t402/schemes/tezos/__init__.py +84 -0
  72. t402/schemes/tezos/constants.py +372 -0
  73. t402/schemes/tezos/exact_direct/__init__.py +22 -0
  74. t402/schemes/tezos/exact_direct/client.py +226 -0
  75. t402/schemes/tezos/exact_direct/facilitator.py +491 -0
  76. t402/schemes/tezos/exact_direct/server.py +277 -0
  77. t402/schemes/tezos/types.py +220 -0
  78. t402/schemes/ton/__init__.py +9 -2
  79. t402/schemes/ton/exact/__init__.py +7 -0
  80. t402/schemes/ton/exact/facilitator.py +730 -0
  81. t402/schemes/ton/exact/server.py +1 -1
  82. t402/schemes/tron/__init__.py +11 -2
  83. t402/schemes/tron/exact/__init__.py +9 -0
  84. t402/schemes/tron/exact/facilitator.py +673 -0
  85. t402/schemes/tron/exact/server.py +1 -1
  86. t402/schemes/upto/__init__.py +80 -0
  87. t402/schemes/upto/types.py +376 -0
  88. t402/stacks_paywall_template.py +2 -0
  89. t402/svm.py +45 -11
  90. t402/svm_paywall_template.py +1 -1
  91. t402/ton.py +5 -1
  92. t402/ton_paywall_template.py +1 -192
  93. t402/tron.py +2 -0
  94. t402/tron_paywall_template.py +2 -0
  95. t402/types.py +4 -2
  96. t402/wdk/errors.py +15 -5
  97. t402/wdk/signer.py +11 -2
  98. {t402-1.7.1.dist-info → t402-1.9.1.dist-info}/METADATA +42 -1
  99. t402-1.9.1.dist-info/RECORD +125 -0
  100. t402-1.7.1.dist-info/RECORD +0 -67
  101. {t402-1.7.1.dist-info → t402-1.9.1.dist-info}/WHEEL +0 -0
  102. {t402-1.7.1.dist-info → t402-1.9.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,231 @@
1
+ """EVM Exact-Legacy Scheme - Server Implementation.
2
+
3
+ This module provides the server-side implementation of the exact-legacy payment scheme
4
+ for EVM networks using the approve + transferFrom pattern.
5
+
6
+ .. deprecated:: 2.3.0
7
+ The exact-legacy scheme is deprecated in favor of using USDT0 with the "exact" scheme.
8
+ USDT0 supports EIP-3009 for gasless transfers and is available on 19+ chains via LayerZero.
9
+
10
+ **Migration Guide:**
11
+ - Replace: `SCHEME_EXACT_LEGACY` with `SCHEME_EXACT`
12
+ - Replace: legacy USDT tokens with USDT0 tokens
13
+ - See https://docs.t402.io/migration/exact-legacy for full migration guide
14
+
15
+ **Why Migrate:**
16
+ 1. Gasless transfers: USDT0 supports EIP-3009, eliminating gas costs for users
17
+ 2. Cross-chain: USDT0 is available on 19+ chains with LayerZero bridging
18
+ 3. Better UX: No separate approve transaction required
19
+ 4. Future support: exact-legacy will be removed in v3.0.0
20
+
21
+ **Supported Chains for Migration:**
22
+ - Ethereum (1) - USDT0: 0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee
23
+ - Arbitrum (42161) - USDT0: 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9
24
+ - Base (8453) - Use USDC: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
25
+ - And 16+ more chains
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ from decimal import Decimal
31
+ from typing import Any, Dict, List, Union
32
+
33
+ from t402.types import (
34
+ PaymentRequirementsV2,
35
+ Network,
36
+ )
37
+ from t402.schemes.interfaces import AssetAmount, SupportedKindDict
38
+ from t402.chains import (
39
+ get_chain_id,
40
+ get_token_decimals,
41
+ KNOWN_TOKENS,
42
+ )
43
+
44
+
45
+ # Constants
46
+ SCHEME_EXACT_LEGACY = "exact-legacy"
47
+
48
+ # Legacy USDT token addresses by chain ID
49
+ LEGACY_USDT_TOKENS = {
50
+ "56": "0x55d398326f99059fF775485246999027B3197955", # BNB Chain
51
+ "43114": "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7", # Avalanche
52
+ "250": "0x049d68029688eabf473097a2fc38ef61633a3c7a", # Fantom
53
+ "42220": "0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e", # Celo
54
+ "8217": "0xcee8faf64bb97a73bb51e115aa89c17ffa8dd167", # Kaia
55
+ }
56
+
57
+
58
+ def get_default_legacy_token(chain_id: str) -> str:
59
+ """Get the default legacy USDT token address for a chain.
60
+
61
+ Args:
62
+ chain_id: Chain ID as string
63
+
64
+ Returns:
65
+ Token address
66
+
67
+ Raises:
68
+ ValueError: If chain doesn't have legacy USDT
69
+ """
70
+ if chain_id in LEGACY_USDT_TOKENS:
71
+ return LEGACY_USDT_TOKENS[chain_id]
72
+
73
+ # Check KNOWN_TOKENS for usdt entry
74
+ if chain_id in KNOWN_TOKENS:
75
+ for token in KNOWN_TOKENS[chain_id]:
76
+ if token.get("human_name") == "usdt":
77
+ return token["address"]
78
+
79
+ raise ValueError(f"No legacy USDT token found for chain {chain_id}")
80
+
81
+
82
+ class ExactLegacyEvmServerScheme:
83
+ """Server scheme for EVM exact-legacy payments.
84
+
85
+ Handles parsing user-friendly prices and enhancing payment requirements
86
+ with EIP-712 domain information needed for clients.
87
+
88
+ Example:
89
+ ```python
90
+ scheme = ExactLegacyEvmServerScheme()
91
+
92
+ # Parse price
93
+ asset_amount = await scheme.parse_price("$0.10", "eip155:56")
94
+ # Returns: {"amount": "100000000000000000", "asset": "0x55d398...", "extra": {...}}
95
+
96
+ # Enhance requirements
97
+ enhanced = await scheme.enhance_requirements(
98
+ requirements,
99
+ supported_kind,
100
+ facilitator_extensions,
101
+ )
102
+ ```
103
+ """
104
+
105
+ scheme = SCHEME_EXACT_LEGACY
106
+ caip_family = "eip155:*"
107
+
108
+ async def parse_price(
109
+ self,
110
+ price: Union[str, int, float, Dict[str, Any]],
111
+ network: Network,
112
+ ) -> AssetAmount:
113
+ """Parse a user-friendly price to atomic amount and asset.
114
+
115
+ Supports:
116
+ - String with $ prefix: "$0.10" -> 100000000000000000 (18 decimals for BSC)
117
+ - String without prefix: "0.10" -> 100000000000000000
118
+ - Integer/float: 0.10 -> 100000000000000000
119
+ - Dict (TokenAmount): {"amount": "100000", "asset": "0x..."}
120
+
121
+ Args:
122
+ price: User-friendly price
123
+ network: Network identifier (CAIP-2 format)
124
+
125
+ Returns:
126
+ AssetAmount dict with amount, asset, and extra metadata
127
+ """
128
+ chain_id = self._get_chain_id(network)
129
+
130
+ # Handle dict (already in TokenAmount format)
131
+ if isinstance(price, dict):
132
+ return {
133
+ "amount": str(price.get("amount", "0")),
134
+ "asset": price.get("asset", ""),
135
+ "extra": price.get("extra", {}),
136
+ }
137
+
138
+ # Get legacy USDT token for the network
139
+ chain_id_str = str(chain_id)
140
+ asset_address = get_default_legacy_token(chain_id_str)
141
+ decimals = get_token_decimals(chain_id_str, asset_address)
142
+
143
+ # Parse price string/number
144
+ if isinstance(price, str):
145
+ if price.startswith("$"):
146
+ price = price[1:]
147
+ amount_decimal = Decimal(price)
148
+ else:
149
+ amount_decimal = Decimal(str(price))
150
+
151
+ # Convert to atomic units
152
+ atomic_amount = int(amount_decimal * Decimal(10**decimals))
153
+
154
+ # Get EIP-712 domain info
155
+ extra = {
156
+ "name": "T402LegacyTransfer",
157
+ "version": "1",
158
+ "decimals": decimals,
159
+ "tokenType": "legacy",
160
+ }
161
+
162
+ return {
163
+ "amount": str(atomic_amount),
164
+ "asset": asset_address,
165
+ "extra": extra,
166
+ }
167
+
168
+ async def enhance_requirements(
169
+ self,
170
+ requirements: Union[PaymentRequirementsV2, Dict[str, Any]],
171
+ supported_kind: SupportedKindDict,
172
+ facilitator_extensions: List[str],
173
+ ) -> Union[PaymentRequirementsV2, Dict[str, Any]]:
174
+ """Enhance payment requirements with EVM-specific metadata.
175
+
176
+ Adds EIP-712 domain information and spender address to the extra field
177
+ so clients can properly sign the authorization.
178
+
179
+ Args:
180
+ requirements: Base payment requirements
181
+ supported_kind: Matched SupportedKind from facilitator
182
+ facilitator_extensions: Extensions supported by facilitator
183
+
184
+ Returns:
185
+ Enhanced requirements with EIP-712 domain in extra
186
+ """
187
+ # Convert to dict for modification
188
+ if hasattr(requirements, "model_dump"):
189
+ req = requirements.model_dump(by_alias=True)
190
+ else:
191
+ req = dict(requirements)
192
+
193
+ # Ensure extra exists
194
+ if "extra" not in req or req["extra"] is None:
195
+ req["extra"] = {}
196
+
197
+ # Add EIP-712 domain info if not present
198
+ if "name" not in req["extra"]:
199
+ req["extra"]["name"] = "T402LegacyTransfer"
200
+ if "version" not in req["extra"]:
201
+ req["extra"]["version"] = "1"
202
+
203
+ # Mark as legacy token type
204
+ req["extra"]["tokenType"] = "legacy"
205
+
206
+ # Add facilitator extra data if available (includes spender address)
207
+ if supported_kind.get("extra"):
208
+ for key, value in supported_kind["extra"].items():
209
+ if key not in req["extra"]:
210
+ req["extra"][key] = value
211
+
212
+ return req
213
+
214
+ def _get_chain_id(self, network: str) -> int:
215
+ """Get chain ID from network identifier.
216
+
217
+ Args:
218
+ network: Network identifier (CAIP-2 or legacy format)
219
+
220
+ Returns:
221
+ Chain ID as integer
222
+ """
223
+ # Handle CAIP-2 format (eip155:8453)
224
+ if network.startswith("eip155:"):
225
+ return int(network.split(":")[1])
226
+
227
+ # Handle legacy format
228
+ try:
229
+ return int(get_chain_id(network))
230
+ except (KeyError, ValueError):
231
+ raise ValueError(f"Unknown network: {network}")
@@ -0,0 +1,70 @@
1
+ """EVM Up-To Payment Scheme.
2
+
3
+ This package provides the upto payment scheme implementation for EVM networks
4
+ using EIP-2612 Permit for gasless token approvals.
5
+
6
+ The upto scheme allows clients to authorize a maximum amount that can be
7
+ settled later based on actual usage.
8
+ """
9
+
10
+ from t402.schemes.evm.upto.types import (
11
+ # Type definitions
12
+ PERMIT_TYPES,
13
+ PERMIT_DOMAIN_TYPES,
14
+ # Models
15
+ PermitSignature,
16
+ PermitAuthorization,
17
+ UptoEIP2612Payload,
18
+ UptoCompactPayload,
19
+ UptoEvmExtra,
20
+ UptoEvmSettlement,
21
+ UptoEvmUsageDetails,
22
+ # Type guards
23
+ is_eip2612_payload,
24
+ # Helper functions
25
+ create_permit_domain,
26
+ create_permit_message,
27
+ payload_from_dict,
28
+ )
29
+
30
+ from t402.schemes.evm.upto.client import (
31
+ UptoEvmClientScheme,
32
+ create_payment_nonce,
33
+ SCHEME_UPTO,
34
+ )
35
+
36
+ from t402.schemes.evm.upto.server import (
37
+ UptoEvmServerScheme,
38
+ )
39
+
40
+ from t402.schemes.evm.upto.facilitator import (
41
+ UptoEvmFacilitatorScheme,
42
+ )
43
+
44
+ __all__ = [
45
+ # Constants
46
+ "SCHEME_UPTO",
47
+ "PERMIT_TYPES",
48
+ "PERMIT_DOMAIN_TYPES",
49
+ # Client
50
+ "UptoEvmClientScheme",
51
+ "create_payment_nonce",
52
+ # Server
53
+ "UptoEvmServerScheme",
54
+ # Facilitator
55
+ "UptoEvmFacilitatorScheme",
56
+ # Types
57
+ "PermitSignature",
58
+ "PermitAuthorization",
59
+ "UptoEIP2612Payload",
60
+ "UptoCompactPayload",
61
+ "UptoEvmExtra",
62
+ "UptoEvmSettlement",
63
+ "UptoEvmUsageDetails",
64
+ # Type guards
65
+ "is_eip2612_payload",
66
+ # Helper functions
67
+ "create_permit_domain",
68
+ "create_permit_message",
69
+ "payload_from_dict",
70
+ ]
@@ -0,0 +1,244 @@
1
+ """EVM Up-To Scheme - Client Implementation.
2
+
3
+ This module provides the client-side implementation of the upto payment scheme
4
+ for EVM networks using EIP-2612 Permit.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import secrets
10
+ import time
11
+ from typing import Any, Dict, Optional, Union
12
+
13
+ from t402.types import PaymentRequirementsV2
14
+ from t402.chains import get_chain_id
15
+ from t402.schemes.evm.exact.client import EvmSigner
16
+ from t402.schemes.upto.types import UptoPaymentRequirements
17
+
18
+
19
+ # Constants
20
+ SCHEME_UPTO = "upto"
21
+
22
+
23
+ def create_payment_nonce() -> bytes:
24
+ """Create a random 32-byte payment nonce."""
25
+ return secrets.token_bytes(32)
26
+
27
+
28
+ class UptoEvmClientScheme:
29
+ """Client scheme for EVM upto payments using EIP-2612.
30
+
31
+ This scheme creates payment payloads using EIP-2612 Permit,
32
+ which allows gasless token approvals for up-to payments.
33
+
34
+ Example:
35
+ ```python
36
+ from eth_account import Account
37
+
38
+ # Create signer from private key
39
+ account = Account.from_key("0x...")
40
+
41
+ # Create scheme
42
+ scheme = UptoEvmClientScheme(account)
43
+
44
+ # Create payment payload
45
+ payload = await scheme.create_payment_payload(
46
+ t402_version=2,
47
+ requirements=requirements,
48
+ )
49
+ ```
50
+ """
51
+
52
+ scheme = SCHEME_UPTO
53
+ caip_family = "eip155:*"
54
+
55
+ def __init__(
56
+ self,
57
+ signer: EvmSigner,
58
+ router_address: Optional[str] = None,
59
+ ):
60
+ """Initialize with an EVM signer.
61
+
62
+ Args:
63
+ signer: Any object implementing the EvmSigner protocol
64
+ router_address: Optional default router contract address
65
+ """
66
+ self._signer = signer
67
+ self._router_address = router_address
68
+
69
+ @property
70
+ def address(self) -> str:
71
+ """Get the signer's address."""
72
+ return self._signer.address
73
+
74
+ async def create_payment_payload(
75
+ self,
76
+ t402_version: int,
77
+ requirements: Union[
78
+ UptoPaymentRequirements, PaymentRequirementsV2, Dict[str, Any]
79
+ ],
80
+ ) -> Dict[str, Any]:
81
+ """Create a payment payload for EVM upto scheme.
82
+
83
+ Creates an EIP-2612 Permit authorization and signs it.
84
+
85
+ Args:
86
+ t402_version: Protocol version (1 or 2)
87
+ requirements: Payment requirements with maxAmount, asset, payTo, etc.
88
+
89
+ Returns:
90
+ Dict with t402Version and payload containing permit signature
91
+ and authorization data.
92
+ """
93
+ # Extract requirements (handle both model and dict)
94
+ if hasattr(requirements, "model_dump"):
95
+ req = requirements.model_dump(by_alias=True)
96
+ else:
97
+ req = dict(requirements)
98
+
99
+ # Get network and chain ID
100
+ network = req.get("network", "")
101
+ chain_id = self._get_chain_id(network)
102
+
103
+ # Get maxAmount for upto scheme
104
+ max_amount = req.get("maxAmount") or req.get("max_amount", "0")
105
+
106
+ # Get router/spender address
107
+ extra = req.get("extra", {})
108
+ router_address = (
109
+ extra.get("routerAddress")
110
+ or extra.get("router_address")
111
+ or self._router_address
112
+ or req.get("payTo") # Fallback to payTo
113
+ )
114
+
115
+ # Get asset address
116
+ asset = req.get("asset", "")
117
+
118
+ # Get timeout
119
+ max_timeout = req.get("maxTimeoutSeconds") or req.get(
120
+ "max_timeout_seconds", 300
121
+ )
122
+
123
+ # Get EIP-712 domain info
124
+ token_name = extra.get("name", "USD Coin")
125
+ token_version = extra.get("version", "2")
126
+
127
+ # Create payment nonce
128
+ payment_nonce = create_payment_nonce()
129
+
130
+ # Calculate deadline
131
+ deadline = int(time.time()) + max_timeout
132
+
133
+ # Get permit nonce from token contract (would need RPC call in production)
134
+ # For now, use 0 as placeholder - real implementation needs contract call
135
+ permit_nonce = 0
136
+
137
+ # Create authorization
138
+ authorization = {
139
+ "owner": self._signer.address,
140
+ "spender": router_address,
141
+ "value": str(max_amount),
142
+ "deadline": str(deadline),
143
+ "nonce": permit_nonce,
144
+ }
145
+
146
+ # Sign the permit
147
+ signature = self._sign_permit(
148
+ authorization=authorization,
149
+ chain_id=chain_id,
150
+ asset_address=asset,
151
+ token_name=token_name,
152
+ token_version=token_version,
153
+ )
154
+
155
+ # Build payload
156
+ payload = {
157
+ "signature": signature,
158
+ "authorization": authorization,
159
+ "paymentNonce": f"0x{payment_nonce.hex()}",
160
+ }
161
+
162
+ return {
163
+ "t402Version": t402_version,
164
+ "payload": payload,
165
+ }
166
+
167
+ def _get_chain_id(self, network: str) -> int:
168
+ """Get chain ID from network identifier."""
169
+ if network.startswith("eip155:"):
170
+ return int(network.split(":")[1])
171
+
172
+ try:
173
+ return get_chain_id(network)
174
+ except (KeyError, ValueError):
175
+ raise ValueError(f"Unknown network: {network}")
176
+
177
+ def _sign_permit(
178
+ self,
179
+ authorization: Dict[str, Any],
180
+ chain_id: int,
181
+ asset_address: str,
182
+ token_name: str,
183
+ token_version: str,
184
+ ) -> Dict[str, Any]:
185
+ """Sign an EIP-2612 Permit.
186
+
187
+ Args:
188
+ authorization: Permit authorization data
189
+ chain_id: EVM chain ID
190
+ asset_address: Token contract address
191
+ token_name: Token name for EIP-712 domain
192
+ token_version: Token version for EIP-712 domain
193
+
194
+ Returns:
195
+ Signature as dict with v, r, s components
196
+ """
197
+ # Build EIP-712 typed data
198
+ domain = {
199
+ "name": token_name,
200
+ "version": token_version,
201
+ "chainId": chain_id,
202
+ "verifyingContract": asset_address,
203
+ }
204
+
205
+ types = {
206
+ "Permit": [
207
+ {"name": "owner", "type": "address"},
208
+ {"name": "spender", "type": "address"},
209
+ {"name": "value", "type": "uint256"},
210
+ {"name": "nonce", "type": "uint256"},
211
+ {"name": "deadline", "type": "uint256"},
212
+ ]
213
+ }
214
+
215
+ message = {
216
+ "owner": authorization["owner"],
217
+ "spender": authorization["spender"],
218
+ "value": int(authorization["value"]),
219
+ "nonce": authorization["nonce"],
220
+ "deadline": int(authorization["deadline"]),
221
+ }
222
+
223
+ # Sign
224
+ signed = self._signer.sign_typed_data(
225
+ domain_data=domain,
226
+ message_types=types,
227
+ message_data=message,
228
+ )
229
+
230
+ # Extract signature components
231
+ sig_hex = signed.signature.hex()
232
+ if sig_hex.startswith("0x"):
233
+ sig_hex = sig_hex[2:]
234
+
235
+ # Split into v, r, s
236
+ r = f"0x{sig_hex[:64]}"
237
+ s = f"0x{sig_hex[64:128]}"
238
+ v = int(sig_hex[128:], 16) if len(sig_hex) > 128 else 27
239
+
240
+ return {
241
+ "v": v,
242
+ "r": r,
243
+ "s": s,
244
+ }