t402 1.9.0__tar.gz → 1.10.0__tar.gz

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 (219) hide show
  1. t402-1.10.0/.gitignore +59 -0
  2. {t402-1.9.0 → t402-1.10.0}/PKG-INFO +42 -1
  3. {t402-1.9.0 → t402-1.10.0}/README.md +41 -0
  4. {t402-1.9.0 → t402-1.10.0}/pyproject.toml +3 -1
  5. {t402-1.9.0 → t402-1.10.0}/src/t402/__init__.py +2 -1
  6. t402-1.10.0/src/t402/a2a/__init__.py +73 -0
  7. t402-1.10.0/src/t402/a2a/helpers.py +158 -0
  8. t402-1.10.0/src/t402/a2a/types.py +145 -0
  9. {t402-1.9.0 → t402-1.10.0}/src/t402/bridge/client.py +13 -5
  10. {t402-1.9.0 → t402-1.10.0}/src/t402/bridge/constants.py +4 -2
  11. {t402-1.9.0 → t402-1.10.0}/src/t402/bridge/router.py +1 -1
  12. {t402-1.9.0 → t402-1.10.0}/src/t402/bridge/scan.py +3 -1
  13. t402-1.10.0/src/t402/chains.py +359 -0
  14. {t402-1.9.0 → t402-1.10.0}/src/t402/cli.py +31 -9
  15. {t402-1.9.0 → t402-1.10.0}/src/t402/common.py +2 -0
  16. t402-1.10.0/src/t402/cosmos_paywall_template.py +2 -0
  17. t402-1.10.0/src/t402/django/__init__.py +42 -0
  18. t402-1.10.0/src/t402/django/middleware.py +596 -0
  19. {t402-1.9.0 → t402-1.10.0}/src/t402/encoding.py +9 -3
  20. {t402-1.9.0 → t402-1.10.0}/src/t402/erc4337/accounts.py +56 -51
  21. {t402-1.9.0 → t402-1.10.0}/src/t402/erc4337/bundlers.py +105 -99
  22. {t402-1.9.0 → t402-1.10.0}/src/t402/erc4337/paymasters.py +100 -109
  23. {t402-1.9.0 → t402-1.10.0}/src/t402/erc4337/types.py +39 -26
  24. t402-1.10.0/src/t402/errors.py +213 -0
  25. t402-1.10.0/src/t402/evm_paywall_template.py +2 -0
  26. {t402-1.9.0 → t402-1.10.0}/src/t402/facilitator.py +125 -0
  27. {t402-1.9.0 → t402-1.10.0}/src/t402/fastapi/middleware.py +1 -3
  28. {t402-1.9.0 → t402-1.10.0}/src/t402/mcp/constants.py +3 -6
  29. t402-1.10.0/src/t402/mcp/server.py +944 -0
  30. t402-1.10.0/src/t402/mcp/web3_utils.py +493 -0
  31. t402-1.10.0/src/t402/multisig/__init__.py +120 -0
  32. t402-1.10.0/src/t402/multisig/constants.py +54 -0
  33. t402-1.10.0/src/t402/multisig/safe.py +441 -0
  34. t402-1.10.0/src/t402/multisig/signature.py +228 -0
  35. t402-1.10.0/src/t402/multisig/transaction.py +238 -0
  36. t402-1.10.0/src/t402/multisig/types.py +108 -0
  37. t402-1.10.0/src/t402/multisig/utils.py +77 -0
  38. t402-1.10.0/src/t402/near_paywall_template.py +2 -0
  39. {t402-1.9.0 → t402-1.10.0}/src/t402/networks.py +34 -1
  40. {t402-1.9.0 → t402-1.10.0}/src/t402/paywall.py +1 -3
  41. t402-1.10.0/src/t402/schemes/__init__.py +307 -0
  42. t402-1.10.0/src/t402/schemes/aptos/__init__.py +70 -0
  43. t402-1.10.0/src/t402/schemes/aptos/constants.py +349 -0
  44. t402-1.10.0/src/t402/schemes/aptos/exact_direct/__init__.py +44 -0
  45. t402-1.10.0/src/t402/schemes/aptos/exact_direct/client.py +202 -0
  46. t402-1.10.0/src/t402/schemes/aptos/exact_direct/facilitator.py +426 -0
  47. t402-1.10.0/src/t402/schemes/aptos/exact_direct/server.py +272 -0
  48. t402-1.10.0/src/t402/schemes/aptos/types.py +237 -0
  49. t402-1.10.0/src/t402/schemes/cosmos/__init__.py +114 -0
  50. t402-1.10.0/src/t402/schemes/cosmos/constants.py +211 -0
  51. t402-1.10.0/src/t402/schemes/cosmos/exact_direct/__init__.py +21 -0
  52. t402-1.10.0/src/t402/schemes/cosmos/exact_direct/client.py +198 -0
  53. t402-1.10.0/src/t402/schemes/cosmos/exact_direct/facilitator.py +493 -0
  54. t402-1.10.0/src/t402/schemes/cosmos/exact_direct/server.py +315 -0
  55. t402-1.10.0/src/t402/schemes/cosmos/types.py +501 -0
  56. t402-1.10.0/src/t402/schemes/evm/__init__.py +91 -0
  57. {t402-1.9.0 → t402-1.10.0}/src/t402/schemes/evm/exact/__init__.py +11 -0
  58. {t402-1.9.0 → t402-1.10.0}/src/t402/schemes/evm/exact/client.py +3 -1
  59. t402-1.10.0/src/t402/schemes/evm/exact/facilitator.py +894 -0
  60. {t402-1.9.0 → t402-1.10.0}/src/t402/schemes/evm/exact/server.py +1 -1
  61. t402-1.10.0/src/t402/schemes/evm/exact_legacy/__init__.py +38 -0
  62. t402-1.10.0/src/t402/schemes/evm/exact_legacy/client.py +291 -0
  63. t402-1.10.0/src/t402/schemes/evm/exact_legacy/facilitator.py +777 -0
  64. t402-1.10.0/src/t402/schemes/evm/exact_legacy/server.py +231 -0
  65. {t402-1.9.0 → t402-1.10.0}/src/t402/schemes/evm/upto/__init__.py +12 -0
  66. {t402-1.9.0 → t402-1.10.0}/src/t402/schemes/evm/upto/client.py +6 -2
  67. t402-1.10.0/src/t402/schemes/evm/upto/facilitator.py +625 -0
  68. t402-1.10.0/src/t402/schemes/evm/upto/server.py +243 -0
  69. {t402-1.9.0 → t402-1.10.0}/src/t402/schemes/evm/upto/types.py +3 -1
  70. {t402-1.9.0 → t402-1.10.0}/src/t402/schemes/interfaces.py +6 -2
  71. t402-1.10.0/src/t402/schemes/near/__init__.py +137 -0
  72. t402-1.10.0/src/t402/schemes/near/constants.py +189 -0
  73. t402-1.10.0/src/t402/schemes/near/exact_direct/__init__.py +21 -0
  74. t402-1.10.0/src/t402/schemes/near/exact_direct/client.py +204 -0
  75. t402-1.10.0/src/t402/schemes/near/exact_direct/facilitator.py +455 -0
  76. t402-1.10.0/src/t402/schemes/near/exact_direct/server.py +303 -0
  77. t402-1.10.0/src/t402/schemes/near/types.py +419 -0
  78. t402-1.10.0/src/t402/schemes/near/upto/__init__.py +54 -0
  79. t402-1.10.0/src/t402/schemes/near/upto/types.py +272 -0
  80. t402-1.10.0/src/t402/schemes/polkadot/__init__.py +72 -0
  81. t402-1.10.0/src/t402/schemes/polkadot/constants.py +155 -0
  82. t402-1.10.0/src/t402/schemes/polkadot/exact_direct/__init__.py +43 -0
  83. t402-1.10.0/src/t402/schemes/polkadot/exact_direct/client.py +235 -0
  84. t402-1.10.0/src/t402/schemes/polkadot/exact_direct/facilitator.py +428 -0
  85. t402-1.10.0/src/t402/schemes/polkadot/exact_direct/server.py +292 -0
  86. t402-1.10.0/src/t402/schemes/polkadot/types.py +385 -0
  87. {t402-1.9.0 → t402-1.10.0}/src/t402/schemes/registry.py +6 -2
  88. t402-1.10.0/src/t402/schemes/stacks/__init__.py +68 -0
  89. t402-1.10.0/src/t402/schemes/stacks/constants.py +122 -0
  90. t402-1.10.0/src/t402/schemes/stacks/exact_direct/__init__.py +43 -0
  91. t402-1.10.0/src/t402/schemes/stacks/exact_direct/client.py +222 -0
  92. t402-1.10.0/src/t402/schemes/stacks/exact_direct/facilitator.py +424 -0
  93. t402-1.10.0/src/t402/schemes/stacks/exact_direct/server.py +292 -0
  94. t402-1.10.0/src/t402/schemes/stacks/types.py +380 -0
  95. t402-1.10.0/src/t402/schemes/svm/__init__.py +44 -0
  96. t402-1.10.0/src/t402/schemes/svm/exact/__init__.py +35 -0
  97. t402-1.10.0/src/t402/schemes/svm/exact/client.py +23 -0
  98. t402-1.10.0/src/t402/schemes/svm/exact/facilitator.py +24 -0
  99. t402-1.10.0/src/t402/schemes/svm/exact/server.py +20 -0
  100. t402-1.10.0/src/t402/schemes/svm/upto/__init__.py +23 -0
  101. t402-1.10.0/src/t402/schemes/svm/upto/types.py +193 -0
  102. t402-1.10.0/src/t402/schemes/tezos/__init__.py +84 -0
  103. t402-1.10.0/src/t402/schemes/tezos/constants.py +372 -0
  104. t402-1.10.0/src/t402/schemes/tezos/exact_direct/__init__.py +22 -0
  105. t402-1.10.0/src/t402/schemes/tezos/exact_direct/client.py +226 -0
  106. t402-1.10.0/src/t402/schemes/tezos/exact_direct/facilitator.py +491 -0
  107. t402-1.10.0/src/t402/schemes/tezos/exact_direct/server.py +277 -0
  108. t402-1.10.0/src/t402/schemes/tezos/types.py +220 -0
  109. t402-1.10.0/src/t402/schemes/ton/__init__.py +44 -0
  110. {t402-1.9.0 → t402-1.10.0}/src/t402/schemes/ton/exact/__init__.py +7 -0
  111. t402-1.10.0/src/t402/schemes/ton/exact/facilitator.py +730 -0
  112. {t402-1.9.0 → t402-1.10.0}/src/t402/schemes/ton/exact/server.py +1 -1
  113. t402-1.10.0/src/t402/schemes/ton/upto/__init__.py +31 -0
  114. t402-1.10.0/src/t402/schemes/ton/upto/types.py +215 -0
  115. t402-1.10.0/src/t402/schemes/tron/__init__.py +48 -0
  116. {t402-1.9.0 → t402-1.10.0}/src/t402/schemes/tron/exact/__init__.py +9 -0
  117. t402-1.10.0/src/t402/schemes/tron/exact/facilitator.py +673 -0
  118. {t402-1.9.0 → t402-1.10.0}/src/t402/schemes/tron/exact/server.py +1 -1
  119. t402-1.10.0/src/t402/schemes/tron/upto/__init__.py +30 -0
  120. t402-1.10.0/src/t402/schemes/tron/upto/types.py +213 -0
  121. t402-1.10.0/src/t402/stacks_paywall_template.py +2 -0
  122. t402-1.10.0/src/t402/starlette/__init__.py +38 -0
  123. t402-1.10.0/src/t402/starlette/middleware.py +522 -0
  124. {t402-1.9.0 → t402-1.10.0}/src/t402/svm.py +45 -11
  125. t402-1.10.0/src/t402/svm_paywall_template.py +2 -0
  126. {t402-1.9.0 → t402-1.10.0}/src/t402/ton.py +6 -2
  127. t402-1.10.0/src/t402/ton_paywall_template.py +2 -0
  128. {t402-1.9.0 → t402-1.10.0}/src/t402/tron.py +2 -0
  129. t402-1.10.0/src/t402/tron_paywall_template.py +2 -0
  130. {t402-1.9.0 → t402-1.10.0}/src/t402/types.py +103 -3
  131. {t402-1.9.0 → t402-1.10.0}/src/t402/wdk/chains.py +1 -1
  132. {t402-1.9.0 → t402-1.10.0}/src/t402/wdk/errors.py +15 -5
  133. {t402-1.9.0 → t402-1.10.0}/src/t402/wdk/signer.py +11 -2
  134. t402-1.10.0/tests/test_a2a.py +285 -0
  135. t402-1.10.0/tests/test_aptos_scheme.py +1627 -0
  136. {t402-1.9.0 → t402-1.10.0}/tests/test_bridge.py +550 -87
  137. t402-1.10.0/tests/test_cli.py +675 -0
  138. t402-1.10.0/tests/test_cosmos_constants.py +197 -0
  139. t402-1.10.0/tests/test_cosmos_exact_direct.py +1145 -0
  140. t402-1.10.0/tests/test_cosmos_types.py +407 -0
  141. t402-1.10.0/tests/test_discovery_client.py +270 -0
  142. t402-1.10.0/tests/test_django_middleware.py +913 -0
  143. t402-1.10.0/tests/test_erc4337.py +1087 -0
  144. t402-1.10.0/tests/test_errors.py +125 -0
  145. t402-1.10.0/tests/test_evm_facilitator.py +1593 -0
  146. {t402-1.9.0 → t402-1.10.0}/tests/test_fastapi.py +2 -6
  147. {t402-1.9.0 → t402-1.10.0}/tests/test_mcp.py +0 -10
  148. t402-1.10.0/tests/test_mcp_tools.py +1050 -0
  149. t402-1.10.0/tests/test_multisig.py +380 -0
  150. t402-1.10.0/tests/test_near_scheme.py +1457 -0
  151. t402-1.10.0/tests/test_polkadot_scheme.py +1672 -0
  152. {t402-1.9.0 → t402-1.10.0}/tests/test_schemes.py +13 -17
  153. t402-1.10.0/tests/test_stacks_scheme.py +996 -0
  154. t402-1.10.0/tests/test_starlette_middleware.py +638 -0
  155. {t402-1.9.0 → t402-1.10.0}/tests/test_svm.py +48 -36
  156. t402-1.10.0/tests/test_svm_scheme.py +335 -0
  157. t402-1.10.0/tests/test_tezos_scheme.py +1326 -0
  158. {t402-1.9.0 → t402-1.10.0}/tests/test_ton.py +3 -1
  159. t402-1.10.0/tests/test_ton_facilitator.py +1277 -0
  160. {t402-1.9.0 → t402-1.10.0}/tests/test_tron.py +0 -4
  161. t402-1.10.0/tests/test_tron_facilitator.py +1256 -0
  162. {t402-1.9.0 → t402-1.10.0}/tests/test_upto.py +0 -1
  163. {t402-1.9.0 → t402-1.10.0}/tests/test_upto_evm.py +3 -5
  164. t402-1.10.0/tests/test_upto_evm_facilitator.py +881 -0
  165. t402-1.10.0/tests/test_upto_evm_server.py +582 -0
  166. t402-1.10.0/tests/test_upto_near.py +393 -0
  167. t402-1.10.0/tests/test_upto_svm.py +382 -0
  168. t402-1.10.0/tests/test_upto_ton.py +344 -0
  169. t402-1.10.0/tests/test_upto_tron.py +339 -0
  170. {t402-1.9.0 → t402-1.10.0}/tests/test_v2_types.py +0 -3
  171. {t402-1.9.0 → t402-1.10.0}/tests/test_wdk.py +0 -5
  172. {t402-1.9.0 → t402-1.10.0}/uv.lock +73 -2
  173. t402-1.9.0/.gitignore +0 -15
  174. t402-1.9.0/src/t402/chains.py +0 -92
  175. t402-1.9.0/src/t402/evm_paywall_template.py +0 -2
  176. t402-1.9.0/src/t402/mcp/server.py +0 -527
  177. t402-1.9.0/src/t402/schemes/__init__.py +0 -164
  178. t402-1.9.0/src/t402/schemes/evm/__init__.py +0 -46
  179. t402-1.9.0/src/t402/schemes/ton/__init__.py +0 -22
  180. t402-1.9.0/src/t402/schemes/tron/__init__.py +0 -22
  181. t402-1.9.0/src/t402/svm_paywall_template.py +0 -2
  182. t402-1.9.0/src/t402/ton_paywall_template.py +0 -193
  183. {t402-1.9.0 → t402-1.10.0}/.python-version +0 -0
  184. {t402-1.9.0 → t402-1.10.0}/src/t402/bridge/__init__.py +0 -0
  185. {t402-1.9.0 → t402-1.10.0}/src/t402/bridge/types.py +0 -0
  186. {t402-1.9.0 → t402-1.10.0}/src/t402/clients/__init__.py +0 -0
  187. {t402-1.9.0 → t402-1.10.0}/src/t402/clients/base.py +0 -0
  188. {t402-1.9.0 → t402-1.10.0}/src/t402/clients/httpx.py +0 -0
  189. {t402-1.9.0 → t402-1.10.0}/src/t402/clients/requests.py +0 -0
  190. {t402-1.9.0 → t402-1.10.0}/src/t402/erc4337/__init__.py +0 -0
  191. {t402-1.9.0 → t402-1.10.0}/src/t402/exact.py +0 -0
  192. {t402-1.9.0 → t402-1.10.0}/src/t402/fastapi/__init__.py +0 -0
  193. {t402-1.9.0 → t402-1.10.0}/src/t402/fastapi/dependencies.py +0 -0
  194. {t402-1.9.0 → t402-1.10.0}/src/t402/flask/__init__.py +0 -0
  195. {t402-1.9.0 → t402-1.10.0}/src/t402/flask/middleware.py +0 -0
  196. {t402-1.9.0 → t402-1.10.0}/src/t402/mcp/__init__.py +0 -0
  197. {t402-1.9.0 → t402-1.10.0}/src/t402/mcp/tools.py +0 -0
  198. {t402-1.9.0 → t402-1.10.0}/src/t402/mcp/types.py +0 -0
  199. {t402-1.9.0 → t402-1.10.0}/src/t402/path.py +0 -0
  200. {t402-1.9.0 → t402-1.10.0}/src/t402/py.typed +0 -0
  201. {t402-1.9.0 → t402-1.10.0}/src/t402/schemes/ton/exact/client.py +0 -0
  202. {t402-1.9.0 → t402-1.10.0}/src/t402/schemes/tron/exact/client.py +0 -0
  203. {t402-1.9.0 → t402-1.10.0}/src/t402/schemes/upto/__init__.py +0 -0
  204. {t402-1.9.0 → t402-1.10.0}/src/t402/schemes/upto/types.py +0 -0
  205. {t402-1.9.0 → t402-1.10.0}/src/t402/wdk/__init__.py +0 -0
  206. {t402-1.9.0 → t402-1.10.0}/src/t402/wdk/types.py +0 -0
  207. {t402-1.9.0 → t402-1.10.0}/tests/clients/__init__.py +0 -0
  208. {t402-1.9.0 → t402-1.10.0}/tests/clients/test_base.py +0 -0
  209. {t402-1.9.0 → t402-1.10.0}/tests/clients/test_httpx.py +0 -0
  210. {t402-1.9.0 → t402-1.10.0}/tests/clients/test_requests.py +0 -0
  211. {t402-1.9.0 → t402-1.10.0}/tests/fastapi_tests/__init__.py +0 -0
  212. {t402-1.9.0 → t402-1.10.0}/tests/fastapi_tests/test_middleware.py +0 -0
  213. {t402-1.9.0 → t402-1.10.0}/tests/flask_tests/__init__.py +0 -0
  214. {t402-1.9.0 → t402-1.10.0}/tests/flask_tests/test_middleware.py +0 -0
  215. {t402-1.9.0 → t402-1.10.0}/tests/test_common.py +0 -0
  216. {t402-1.9.0 → t402-1.10.0}/tests/test_encoding.py +0 -0
  217. {t402-1.9.0 → t402-1.10.0}/tests/test_exact.py +0 -0
  218. {t402-1.9.0 → t402-1.10.0}/tests/test_paywall.py +0 -0
  219. {t402-1.9.0 → t402-1.10.0}/tests/test_types.py +0 -0
t402-1.10.0/.gitignore ADDED
@@ -0,0 +1,59 @@
1
+ # Environment
2
+ .env
3
+ .env*.local
4
+
5
+ # Dependencies
6
+ node_modules/
7
+
8
+ # Build outputs
9
+ dist/
10
+ .next/
11
+ .turbo/
12
+ .vercel
13
+ .wrangler/
14
+ api-docs/
15
+
16
+ # Test coverage
17
+ coverage/
18
+ .coverage
19
+ coverage.out
20
+ *.coverprofile
21
+
22
+ # Python
23
+ __pycache__/
24
+ *.pyc
25
+ *.pyo
26
+ .venv/
27
+
28
+ # LaTeX build artifacts
29
+ whitepaper/build/
30
+ *.aux
31
+ *.bbl
32
+ *.blg
33
+ *.lof
34
+ *.lot
35
+ *.out
36
+ *.toc
37
+
38
+ # OS files
39
+ **/.DS_Store
40
+
41
+ # Editor
42
+ .claude/settings.local.json
43
+
44
+ # Project specific
45
+ proxy
46
+ e2e/facilitators/external-proxies/*
47
+ !e2e/facilitators/external-proxies/README.md
48
+
49
+ # Compiled Go binaries (e2e)
50
+ e2e/clients/go-http/client
51
+ e2e/clients/go-http/go-http
52
+ e2e/clients/go-http/main
53
+ e2e/facilitators/go/facilitator
54
+ e2e/facilitators/go/go
55
+ e2e/servers/gin/gin
56
+ e2e/servers/gin/server
57
+
58
+ # Archive
59
+ .archive/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: t402
3
- Version: 1.9.0
3
+ Version: 1.10.0
4
4
  Summary: t402: An internet native payments protocol
5
5
  Author-email: T402 Team <dev@t402.io>
6
6
  License: Apache-2.0
@@ -389,6 +389,47 @@ result = await bridge.bridge(
389
389
  )
390
390
  ```
391
391
 
392
+ ## Deprecation Notice: exact-legacy Scheme
393
+
394
+ > **⚠️ Deprecated**: The `exact-legacy` scheme is deprecated and will be removed in a future major version.
395
+
396
+ The `exact-legacy` scheme uses the traditional `approve + transferFrom` pattern for legacy USDT tokens. This has been superseded by the `exact` scheme with USDT0.
397
+
398
+ ### Why Migrate?
399
+
400
+ | Feature | exact-legacy | exact (USDT0) |
401
+ |---------|--------------|---------------|
402
+ | Transactions | 2 (approve + transfer) | 1 (single signature) |
403
+ | Gas Cost | User pays gas | Gasless (EIP-3009) |
404
+ | Chains | ~5 chains | 19+ chains |
405
+ | Cross-chain | ❌ | ✅ LayerZero bridge |
406
+
407
+ ### Migration Guide
408
+
409
+ ```python
410
+ # Before (deprecated)
411
+ from t402.schemes.evm import ExactLegacyEvmClientScheme, ExactLegacyEvmServerScheme
412
+
413
+ client_scheme = ExactLegacyEvmClientScheme(signer)
414
+ server_scheme = ExactLegacyEvmServerScheme()
415
+
416
+ # After (recommended)
417
+ from t402.schemes.evm import ExactEvmClientScheme, ExactEvmServerScheme
418
+
419
+ client_scheme = ExactEvmClientScheme(signer)
420
+ server_scheme = ExactEvmServerScheme()
421
+ ```
422
+
423
+ ### USDT0 Token Addresses
424
+
425
+ | Chain | USDT0 Address |
426
+ |-------|---------------|
427
+ | Ethereum | `0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee` |
428
+ | Arbitrum | `0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9` |
429
+ | Ink | `0x0200C29006150606B650577BBE7B6248F58470c1` |
430
+ | Berachain | `0x779Ded0c9e1022225f8E0630b35a9b54bE713736` |
431
+ | And 15+ more... | See [USDT0 documentation](https://docs.t402.io/chains) |
432
+
392
433
  ## WDK Integration
393
434
 
394
435
  Tether Wallet Development Kit support:
@@ -364,6 +364,47 @@ result = await bridge.bridge(
364
364
  )
365
365
  ```
366
366
 
367
+ ## Deprecation Notice: exact-legacy Scheme
368
+
369
+ > **⚠️ Deprecated**: The `exact-legacy` scheme is deprecated and will be removed in a future major version.
370
+
371
+ The `exact-legacy` scheme uses the traditional `approve + transferFrom` pattern for legacy USDT tokens. This has been superseded by the `exact` scheme with USDT0.
372
+
373
+ ### Why Migrate?
374
+
375
+ | Feature | exact-legacy | exact (USDT0) |
376
+ |---------|--------------|---------------|
377
+ | Transactions | 2 (approve + transfer) | 1 (single signature) |
378
+ | Gas Cost | User pays gas | Gasless (EIP-3009) |
379
+ | Chains | ~5 chains | 19+ chains |
380
+ | Cross-chain | ❌ | ✅ LayerZero bridge |
381
+
382
+ ### Migration Guide
383
+
384
+ ```python
385
+ # Before (deprecated)
386
+ from t402.schemes.evm import ExactLegacyEvmClientScheme, ExactLegacyEvmServerScheme
387
+
388
+ client_scheme = ExactLegacyEvmClientScheme(signer)
389
+ server_scheme = ExactLegacyEvmServerScheme()
390
+
391
+ # After (recommended)
392
+ from t402.schemes.evm import ExactEvmClientScheme, ExactEvmServerScheme
393
+
394
+ client_scheme = ExactEvmClientScheme(signer)
395
+ server_scheme = ExactEvmServerScheme()
396
+ ```
397
+
398
+ ### USDT0 Token Addresses
399
+
400
+ | Chain | USDT0 Address |
401
+ |-------|---------------|
402
+ | Ethereum | `0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee` |
403
+ | Arbitrum | `0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9` |
404
+ | Ink | `0x0200C29006150606B650577BBE7B6248F58470c1` |
405
+ | Berachain | `0x779Ded0c9e1022225f8E0630b35a9b54bE713736` |
406
+ | And 15+ more... | See [USDT0 documentation](https://docs.t402.io/chains) |
407
+
367
408
  ## WDK Integration
368
409
 
369
410
  Tether Wallet Development Kit support:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "t402"
3
- version = "1.9.0"
3
+ version = "1.10.0"
4
4
  description = "t402: An internet native payments protocol"
5
5
  readme = "README.md"
6
6
  license = { text = "Apache-2.0" }
@@ -46,10 +46,12 @@ dev = [
46
46
  "ruff>=0.11.9",
47
47
  "solana>=0.35.0",
48
48
  "solders>=0.21.0",
49
+ "django>=4.2",
49
50
  ]
50
51
 
51
52
  [tool.pytest.ini_options]
52
53
  asyncio_mode = "auto"
54
+ pythonpath = ["src"]
53
55
 
54
56
  [tool.hatch.build.targets.wheel]
55
57
  packages = ["src/t402"]
@@ -1,5 +1,5 @@
1
1
  # Package version
2
- __version__ = "1.7.1"
2
+ __version__ = "1.10.0"
3
3
 
4
4
  # Re-export commonly used items for convenience
5
5
  from t402.common import (
@@ -290,6 +290,7 @@ from t402.fastapi import (
290
290
  settle_payment,
291
291
  )
292
292
 
293
+
293
294
  def hello() -> str:
294
295
  return "Hello from t402!"
295
296
 
@@ -0,0 +1,73 @@
1
+ """A2A (Agent-to-Agent) transport for t402 payments."""
2
+
3
+ from t402.a2a.types import (
4
+ T402_A2A_EXTENSION_URI,
5
+ A2A_EXTENSIONS_HEADER,
6
+ META_PAYMENT_STATUS,
7
+ META_PAYMENT_REQUIRED,
8
+ META_PAYMENT_PAYLOAD,
9
+ META_PAYMENT_RECEIPTS,
10
+ META_PAYMENT_ERROR,
11
+ STATUS_PAYMENT_REQUIRED,
12
+ STATUS_PAYMENT_REJECTED,
13
+ STATUS_PAYMENT_SUBMITTED,
14
+ STATUS_PAYMENT_VERIFIED,
15
+ STATUS_PAYMENT_COMPLETED,
16
+ STATUS_PAYMENT_FAILED,
17
+ A2AMessagePart,
18
+ A2AMessage,
19
+ A2ATaskStatus,
20
+ A2ATask,
21
+ A2AExtension,
22
+ A2AAgentCard,
23
+ A2ASkill,
24
+ )
25
+ from t402.a2a.helpers import (
26
+ is_payment_required,
27
+ is_payment_completed,
28
+ is_payment_failed,
29
+ get_payment_required,
30
+ get_payment_receipts,
31
+ has_payment_payload,
32
+ extract_payment_payload,
33
+ create_payment_required_message,
34
+ create_payment_submission_message,
35
+ create_payment_completed_message,
36
+ create_payment_failed_message,
37
+ create_t402_extension,
38
+ )
39
+
40
+ __all__ = [
41
+ "T402_A2A_EXTENSION_URI",
42
+ "A2A_EXTENSIONS_HEADER",
43
+ "META_PAYMENT_STATUS",
44
+ "META_PAYMENT_REQUIRED",
45
+ "META_PAYMENT_PAYLOAD",
46
+ "META_PAYMENT_RECEIPTS",
47
+ "META_PAYMENT_ERROR",
48
+ "STATUS_PAYMENT_REQUIRED",
49
+ "STATUS_PAYMENT_REJECTED",
50
+ "STATUS_PAYMENT_SUBMITTED",
51
+ "STATUS_PAYMENT_VERIFIED",
52
+ "STATUS_PAYMENT_COMPLETED",
53
+ "STATUS_PAYMENT_FAILED",
54
+ "A2AMessagePart",
55
+ "A2AMessage",
56
+ "A2ATaskStatus",
57
+ "A2ATask",
58
+ "A2AExtension",
59
+ "A2AAgentCard",
60
+ "A2ASkill",
61
+ "is_payment_required",
62
+ "is_payment_completed",
63
+ "is_payment_failed",
64
+ "get_payment_required",
65
+ "get_payment_receipts",
66
+ "has_payment_payload",
67
+ "extract_payment_payload",
68
+ "create_payment_required_message",
69
+ "create_payment_submission_message",
70
+ "create_payment_completed_message",
71
+ "create_payment_failed_message",
72
+ "create_t402_extension",
73
+ ]
@@ -0,0 +1,158 @@
1
+ """A2A helper functions for t402 payment message handling."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ from t402.a2a.types import (
8
+ A2AMessage,
9
+ A2AMessagePart,
10
+ A2ATask,
11
+ A2AExtension,
12
+ META_PAYMENT_ERROR,
13
+ META_PAYMENT_PAYLOAD,
14
+ META_PAYMENT_RECEIPTS,
15
+ META_PAYMENT_REQUIRED,
16
+ META_PAYMENT_STATUS,
17
+ STATUS_PAYMENT_COMPLETED,
18
+ STATUS_PAYMENT_FAILED,
19
+ STATUS_PAYMENT_REQUIRED,
20
+ STATUS_PAYMENT_SUBMITTED,
21
+ STATE_COMPLETED,
22
+ STATE_FAILED,
23
+ STATE_INPUT_REQUIRED,
24
+ T402_A2A_EXTENSION_URI,
25
+ )
26
+
27
+
28
+ def is_payment_required(task: A2ATask) -> bool:
29
+ """Check if a task is in a payment-required state."""
30
+ if task.status.state != STATE_INPUT_REQUIRED:
31
+ return False
32
+ if task.status.message is None or task.status.message.metadata is None:
33
+ return False
34
+ return task.status.message.metadata.get(META_PAYMENT_STATUS) == STATUS_PAYMENT_REQUIRED
35
+
36
+
37
+ def is_payment_completed(task: A2ATask) -> bool:
38
+ """Check if a task has completed payment."""
39
+ if task.status.state != STATE_COMPLETED:
40
+ return False
41
+ if task.status.message is None or task.status.message.metadata is None:
42
+ return False
43
+ return task.status.message.metadata.get(META_PAYMENT_STATUS) == STATUS_PAYMENT_COMPLETED
44
+
45
+
46
+ def is_payment_failed(task: A2ATask) -> bool:
47
+ """Check if a task has failed payment."""
48
+ if task.status.state != STATE_FAILED:
49
+ return False
50
+ if task.status.message is None or task.status.message.metadata is None:
51
+ return False
52
+ return task.status.message.metadata.get(META_PAYMENT_STATUS) == STATUS_PAYMENT_FAILED
53
+
54
+
55
+ def get_payment_required(task: A2ATask) -> Optional[Dict[str, Any]]:
56
+ """Extract payment requirements from a task."""
57
+ if not is_payment_required(task):
58
+ return None
59
+ return task.status.message.metadata.get(META_PAYMENT_REQUIRED)
60
+
61
+
62
+ def get_payment_receipts(task: A2ATask) -> Optional[List[Any]]:
63
+ """Extract payment receipts from a task."""
64
+ if task.status.message is None or task.status.message.metadata is None:
65
+ return None
66
+ return task.status.message.metadata.get(META_PAYMENT_RECEIPTS)
67
+
68
+
69
+ def has_payment_payload(msg: A2AMessage) -> bool:
70
+ """Check if a message contains a payment submission."""
71
+ if msg.metadata is None:
72
+ return False
73
+ return (
74
+ msg.metadata.get(META_PAYMENT_STATUS) == STATUS_PAYMENT_SUBMITTED
75
+ and META_PAYMENT_PAYLOAD in msg.metadata
76
+ )
77
+
78
+
79
+ def extract_payment_payload(msg: A2AMessage) -> Optional[Dict[str, Any]]:
80
+ """Extract a payment payload from a message."""
81
+ if msg.metadata is None:
82
+ return None
83
+ return msg.metadata.get(META_PAYMENT_PAYLOAD)
84
+
85
+
86
+ def create_payment_required_message(
87
+ payment_required: Any,
88
+ text: str = "Payment is required to complete this request.",
89
+ ) -> A2AMessage:
90
+ """Create an agent message requesting payment."""
91
+ return A2AMessage(
92
+ kind="message",
93
+ role="agent",
94
+ parts=[A2AMessagePart(kind="text", text=text)],
95
+ metadata={
96
+ META_PAYMENT_STATUS: STATUS_PAYMENT_REQUIRED,
97
+ META_PAYMENT_REQUIRED: payment_required,
98
+ },
99
+ )
100
+
101
+
102
+ def create_payment_submission_message(
103
+ payment_payload: Any,
104
+ text: str = "Here is the payment authorization.",
105
+ ) -> A2AMessage:
106
+ """Create a user message submitting payment."""
107
+ return A2AMessage(
108
+ kind="message",
109
+ role="user",
110
+ parts=[A2AMessagePart(kind="text", text=text)],
111
+ metadata={
112
+ META_PAYMENT_STATUS: STATUS_PAYMENT_SUBMITTED,
113
+ META_PAYMENT_PAYLOAD: payment_payload,
114
+ },
115
+ )
116
+
117
+
118
+ def create_payment_completed_message(
119
+ receipts: Any,
120
+ text: str = "Payment successful.",
121
+ ) -> A2AMessage:
122
+ """Create an agent message confirming payment."""
123
+ return A2AMessage(
124
+ kind="message",
125
+ role="agent",
126
+ parts=[A2AMessagePart(kind="text", text=text)],
127
+ metadata={
128
+ META_PAYMENT_STATUS: STATUS_PAYMENT_COMPLETED,
129
+ META_PAYMENT_RECEIPTS: receipts,
130
+ },
131
+ )
132
+
133
+
134
+ def create_payment_failed_message(
135
+ receipts: Any,
136
+ error_code: str,
137
+ text: str = "Payment failed.",
138
+ ) -> A2AMessage:
139
+ """Create an agent message reporting payment failure."""
140
+ return A2AMessage(
141
+ kind="message",
142
+ role="agent",
143
+ parts=[A2AMessagePart(kind="text", text=text)],
144
+ metadata={
145
+ META_PAYMENT_STATUS: STATUS_PAYMENT_FAILED,
146
+ META_PAYMENT_ERROR: error_code,
147
+ META_PAYMENT_RECEIPTS: receipts,
148
+ },
149
+ )
150
+
151
+
152
+ def create_t402_extension(required: bool = False) -> A2AExtension:
153
+ """Create a T402 extension declaration for agent cards."""
154
+ return A2AExtension(
155
+ uri=T402_A2A_EXTENSION_URI,
156
+ description="Supports payments using the t402 protocol for on-chain settlement.",
157
+ required=required,
158
+ )
@@ -0,0 +1,145 @@
1
+ """A2A transport types for t402 payments."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Any, Dict, List, Optional
7
+
8
+ # Constants
9
+ T402_A2A_EXTENSION_URI = "https://github.com/google-a2a/a2a-t402/v0.1"
10
+ A2A_EXTENSIONS_HEADER = "X-A2A-Extensions"
11
+
12
+ # Payment metadata keys
13
+ META_PAYMENT_STATUS = "t402.payment.status"
14
+ META_PAYMENT_REQUIRED = "t402.payment.required"
15
+ META_PAYMENT_PAYLOAD = "t402.payment.payload"
16
+ META_PAYMENT_RECEIPTS = "t402.payment.receipts"
17
+ META_PAYMENT_ERROR = "t402.payment.error"
18
+
19
+ # Payment status values
20
+ STATUS_PAYMENT_REQUIRED = "payment-required"
21
+ STATUS_PAYMENT_REJECTED = "payment-rejected"
22
+ STATUS_PAYMENT_SUBMITTED = "payment-submitted"
23
+ STATUS_PAYMENT_VERIFIED = "payment-verified"
24
+ STATUS_PAYMENT_COMPLETED = "payment-completed"
25
+ STATUS_PAYMENT_FAILED = "payment-failed"
26
+
27
+ # Task state values
28
+ STATE_SUBMITTED = "submitted"
29
+ STATE_WORKING = "working"
30
+ STATE_INPUT_REQUIRED = "input-required"
31
+ STATE_COMPLETED = "completed"
32
+ STATE_CANCELED = "canceled"
33
+ STATE_FAILED = "failed"
34
+ STATE_UNKNOWN = "unknown"
35
+
36
+
37
+ @dataclass
38
+ class A2AMessagePart:
39
+ """A message part (text, file, or data)."""
40
+
41
+ kind: str
42
+ text: Optional[str] = None
43
+ file: Optional[Dict[str, Any]] = None
44
+ data: Optional[Dict[str, Any]] = None
45
+
46
+
47
+ @dataclass
48
+ class A2AMessage:
49
+ """An A2A message with optional payment metadata."""
50
+
51
+ kind: str # always "message"
52
+ role: str # "user" or "agent"
53
+ parts: List[A2AMessagePart]
54
+ message_id: Optional[str] = None
55
+ metadata: Optional[Dict[str, Any]] = None
56
+
57
+
58
+ @dataclass
59
+ class A2ATaskStatus:
60
+ """Current status of an A2A task."""
61
+
62
+ state: str
63
+ message: Optional[A2AMessage] = None
64
+ timestamp: Optional[str] = None
65
+
66
+
67
+ @dataclass
68
+ class A2AArtifact:
69
+ """Output from a completed task."""
70
+
71
+ kind: str
72
+ name: Optional[str] = None
73
+ mime_type: Optional[str] = None
74
+ data: Optional[str] = None
75
+ uri: Optional[str] = None
76
+ metadata: Optional[Dict[str, Any]] = None
77
+
78
+
79
+ @dataclass
80
+ class A2ATask:
81
+ """An A2A task with status and history."""
82
+
83
+ kind: str # always "task"
84
+ id: str
85
+ status: A2ATaskStatus
86
+ session_id: Optional[str] = None
87
+ artifacts: Optional[List[A2AArtifact]] = None
88
+ history: Optional[List[A2AMessage]] = None
89
+ metadata: Optional[Dict[str, Any]] = None
90
+
91
+
92
+ @dataclass
93
+ class A2AExtension:
94
+ """An A2A extension declaration."""
95
+
96
+ uri: str
97
+ description: Optional[str] = None
98
+ required: bool = False
99
+
100
+
101
+ @dataclass
102
+ class A2ACapabilities:
103
+ """A2A agent capabilities."""
104
+
105
+ streaming: bool = False
106
+ push_notifications: bool = False
107
+ state_transition_history: bool = False
108
+ extensions: Optional[List[A2AExtension]] = None
109
+
110
+
111
+ @dataclass
112
+ class A2AProvider:
113
+ """Agent provider information."""
114
+
115
+ organization: Optional[str] = None
116
+ url: Optional[str] = None
117
+
118
+
119
+ @dataclass
120
+ class A2ASkill:
121
+ """A2A skill definition."""
122
+
123
+ id: str
124
+ name: str
125
+ description: Optional[str] = None
126
+ tags: Optional[List[str]] = None
127
+ examples: Optional[List[str]] = None
128
+ input_modes: Optional[List[str]] = None
129
+ output_modes: Optional[List[str]] = None
130
+
131
+
132
+ @dataclass
133
+ class A2AAgentCard:
134
+ """A2A agent card (service advertisement)."""
135
+
136
+ name: str
137
+ url: str
138
+ description: Optional[str] = None
139
+ provider: Optional[A2AProvider] = None
140
+ version: Optional[str] = None
141
+ documentation_url: Optional[str] = None
142
+ capabilities: Optional[A2ACapabilities] = None
143
+ default_input_modes: Optional[List[str]] = None
144
+ default_output_modes: Optional[List[str]] = None
145
+ skills: Optional[List[A2ASkill]] = None
@@ -1,6 +1,5 @@
1
1
  """USDT0 Bridge Client for LayerZero OFT transfers."""
2
2
 
3
-
4
3
  from .constants import (
5
4
  DEFAULT_EXTRA_OPTIONS,
6
5
  DEFAULT_SLIPPAGE,
@@ -73,7 +72,7 @@ class Usdt0Bridge:
73
72
  if not supports_bridging(chain):
74
73
  raise ValueError(
75
74
  f'Chain "{chain}" does not support USDT0 bridging. '
76
- f'Supported chains: {", ".join(get_bridgeable_chains())}'
75
+ f"Supported chains: {', '.join(get_bridgeable_chains())}"
77
76
  )
78
77
 
79
78
  self._signer = signer
@@ -107,7 +106,9 @@ class Usdt0Bridge:
107
106
  False,
108
107
  )
109
108
 
110
- native_fee = fee[0] if isinstance(fee, (list, tuple)) else fee.get("nativeFee", 0)
109
+ native_fee = (
110
+ fee[0] if isinstance(fee, (list, tuple)) else fee.get("nativeFee", 0)
111
+ )
111
112
 
112
113
  return BridgeQuote(
113
114
  native_fee=int(native_fee),
@@ -139,7 +140,11 @@ class Usdt0Bridge:
139
140
  )
140
141
  )
141
142
 
142
- slippage = params.slippage_tolerance if params.slippage_tolerance > 0 else DEFAULT_SLIPPAGE
143
+ slippage = (
144
+ params.slippage_tolerance
145
+ if params.slippage_tolerance > 0
146
+ else DEFAULT_SLIPPAGE
147
+ )
143
148
  oft_address = get_usdt0_oft_address(params.from_chain)
144
149
  send_param = self._build_send_param(
145
150
  params.to_chain, params.amount, params.recipient, slippage
@@ -304,7 +309,10 @@ class Usdt0Bridge:
304
309
  def _extract_message_guid(self, receipt: BridgeTransactionReceipt) -> str:
305
310
  """Extract LayerZero message GUID from OFTSent event logs."""
306
311
  for log in receipt.logs:
307
- if len(log.topics) >= 2 and log.topics[0].lower() == OFT_SENT_EVENT_TOPIC.lower():
312
+ if (
313
+ len(log.topics) >= 2
314
+ and log.topics[0].lower() == OFT_SENT_EVENT_TOPIC.lower()
315
+ ):
308
316
  # GUID is the first indexed parameter (topics[1])
309
317
  return log.topics[1]
310
318
 
@@ -32,7 +32,7 @@ USDT0_OFT_ADDRESSES: dict[str, str] = {
32
32
  "arbitrum": "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9",
33
33
  "ink": "0x0200C29006150606B650577BBE7B6248F58470c1",
34
34
  "berachain": "0x779Ded0c9e1022225f8E0630b35a9b54bE713736",
35
- "unichain": "0x588ce4F028D8e7B53B687865d6A67b3A54C75518",
35
+ "unichain": "0x9151434b16b9763660705744891fA906F660EcC5",
36
36
  }
37
37
 
38
38
  # Network to chain name mapping
@@ -251,7 +251,9 @@ def address_to_bytes32(address: str) -> bytes:
251
251
  addr = address.lower().removeprefix("0x")
252
252
 
253
253
  if len(addr) != 40:
254
- raise ValueError(f"Invalid address length: expected 40 hex chars, got {len(addr)}")
254
+ raise ValueError(
255
+ f"Invalid address length: expected 40 hex chars, got {len(addr)}"
256
+ )
255
257
 
256
258
  try:
257
259
  addr_bytes = bytes.fromhex(addr)
@@ -199,7 +199,7 @@ class CrossChainPaymentRouter:
199
199
  raise ValueError(
200
200
  f'Cannot route payment from "{params.source_chain}" to '
201
201
  f'"{params.destination_chain}". '
202
- f'Supported chains: {", ".join(get_bridgeable_chains())}'
202
+ f"Supported chains: {', '.join(get_bridgeable_chains())}"
203
203
  )
204
204
 
205
205
  if params.amount <= 0:
@@ -206,7 +206,9 @@ class LayerZeroScanClient:
206
206
  dst_tx_hash=data.get("dstTxHash"),
207
207
  status=LayerZeroMessageStatus(data.get("status") or "INFLIGHT"),
208
208
  src_block_number=int(data.get("srcBlockNumber") or 0),
209
- dst_block_number=int(data.get("dstBlockNumber")) if data.get("dstBlockNumber") else None,
209
+ dst_block_number=int(data.get("dstBlockNumber"))
210
+ if data.get("dstBlockNumber")
211
+ else None,
210
212
  created=data.get("created") or data.get("createdAt") or "",
211
213
  updated=data.get("updated") or data.get("updatedAt") or "",
212
214
  )