skillpp 0.1.0

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 (208) hide show
  1. package/COMPATIBILITY.md +58 -0
  2. package/LICENSE +21 -0
  3. package/README.md +307 -0
  4. package/README.zh-CN.md +307 -0
  5. package/SKILL.md +490 -0
  6. package/adapters/binance-ai.md +22 -0
  7. package/adapters/claude.md +21 -0
  8. package/adapters/gemini.md +26 -0
  9. package/adapters/gpt.md +28 -0
  10. package/adapters/kimi.md +26 -0
  11. package/adapters/mimo.md +22 -0
  12. package/adapters/openclaw.md +29 -0
  13. package/assets/skillpp-banner.png +0 -0
  14. package/package.json +59 -0
  15. package/pipelines.md +310 -0
  16. package/prompts/newbie-mode.md +48 -0
  17. package/prompts/router-prompt.md +32 -0
  18. package/prompts/universal-system-prompt.md +41 -0
  19. package/registry.md +209 -0
  20. package/rules.md +323 -0
  21. package/schemas/audit.schema.json +67 -0
  22. package/schemas/checkpoint.schema.json +86 -0
  23. package/schemas/handoff.schema.json +82 -0
  24. package/schemas/token.schema.json +36 -0
  25. package/scripts/compatibility-check.mjs +130 -0
  26. package/scripts/selftest.mjs +384 -0
  27. package/scripts/skillpp.mjs +448 -0
  28. package/scripts/validate-skillpp.mjs +140 -0
  29. package/skillpp.manifest.json +714 -0
  30. package/skills/audit-plus/SKILL.md +612 -0
  31. package/skills/binance/binance/CHANGELOG.md +112 -0
  32. package/skills/binance/binance/LICENSE.md +9 -0
  33. package/skills/binance/binance/SKILL.md +69 -0
  34. package/skills/binance/binance/references/algo.md +21 -0
  35. package/skills/binance/binance/references/alpha.md +9 -0
  36. package/skills/binance/binance/references/auth.md +32 -0
  37. package/skills/binance/binance/references/c2c.md +5 -0
  38. package/skills/binance/binance/references/convert.md +19 -0
  39. package/skills/binance/binance/references/copy-trading.md +6 -0
  40. package/skills/binance/binance/references/crypto-loan.md +27 -0
  41. package/skills/binance/binance/references/derivatives-options-streams.md +25 -0
  42. package/skills/binance/binance/references/derivatives-options.md +85 -0
  43. package/skills/binance/binance/references/derivatives-portfolio-margin-pro-streams.md +5 -0
  44. package/skills/binance/binance/references/derivatives-portfolio-margin-pro.md +34 -0
  45. package/skills/binance/binance/references/derivatives-portfolio-margin-streams.md +5 -0
  46. package/skills/binance/binance/references/derivatives-portfolio-margin.md +146 -0
  47. package/skills/binance/binance/references/dual-investment.md +15 -0
  48. package/skills/binance/binance/references/fiat.md +9 -0
  49. package/skills/binance/binance/references/futures-coin-streams.md +29 -0
  50. package/skills/binance/binance/references/futures-coin.md +109 -0
  51. package/skills/binance/binance/references/futures-usds-streams.md +35 -0
  52. package/skills/binance/binance/references/futures-usds.md +144 -0
  53. package/skills/binance/binance/references/gift-card.md +10 -0
  54. package/skills/binance/binance/references/margin-trading-streams.md +6 -0
  55. package/skills/binance/binance/references/margin-trading.md +101 -0
  56. package/skills/binance/binance/references/mining.md +17 -0
  57. package/skills/binance/binance/references/pay.md +5 -0
  58. package/skills/binance/binance/references/rebate.md +5 -0
  59. package/skills/binance/binance/references/simple-earn.md +56 -0
  60. package/skills/binance/binance/references/spot-streams.md +25 -0
  61. package/skills/binance/binance/references/spot.md +114 -0
  62. package/skills/binance/binance/references/staking.md +59 -0
  63. package/skills/binance/binance/references/sub-account.md +67 -0
  64. package/skills/binance/binance/references/vip-loan.md +27 -0
  65. package/skills/binance/binance/references/wallet.md +75 -0
  66. package/skills/binance/fiat/CHANGELOG.md +11 -0
  67. package/skills/binance/fiat/LICENSE.md +9 -0
  68. package/skills/binance/fiat/SKILL.md +169 -0
  69. package/skills/binance/fiat/references/authentication.md +126 -0
  70. package/skills/binance/fiat/references/sapi-endpoints.md +217 -0
  71. package/skills/binance/onchain-pay/.local.md.example +10 -0
  72. package/skills/binance/onchain-pay/CHANGELOG.md +20 -0
  73. package/skills/binance/onchain-pay/LICENSE.md +9 -0
  74. package/skills/binance/onchain-pay/SKILL.md +466 -0
  75. package/skills/binance/onchain-pay/references/authentication.md +92 -0
  76. package/skills/binance/onchain-pay/scripts/sign_and_call.sh +52 -0
  77. package/skills/binance/p2p/CHANGELOG.md +33 -0
  78. package/skills/binance/p2p/LICENSE.md +9 -0
  79. package/skills/binance/p2p/SKILL.md +1082 -0
  80. package/skills/binance/p2p/references/agent-sapi-api.md +795 -0
  81. package/skills/binance/p2p/references/authentication.md +100 -0
  82. package/skills/binance/payment/SKILL.md +824 -0
  83. package/skills/binance/payment/common.py +560 -0
  84. package/skills/binance/payment/payment_skill.py +86 -0
  85. package/skills/binance/payment/receive.py +109 -0
  86. package/skills/binance/payment/references/setup-guide.md +77 -0
  87. package/skills/binance/payment/requirements.txt +4 -0
  88. package/skills/binance/payment/send.py +952 -0
  89. package/skills/binance/payment/send_extension/__init__.py +43 -0
  90. package/skills/binance/payment/send_extension/base.py +48 -0
  91. package/skills/binance/payment/send_extension/c2c.py +193 -0
  92. package/skills/binance/payment/send_extension/pix.py +316 -0
  93. package/skills/binance/square-post/README.md +62 -0
  94. package/skills/binance/square-post/SKILL.md +171 -0
  95. package/skills/binance/square-post/scripts/lib.mjs +175 -0
  96. package/skills/binance/square-post/scripts/post-image.mjs +80 -0
  97. package/skills/binance/square-post/scripts/post-text.mjs +41 -0
  98. package/skills/binance/square-post/scripts/post-video.mjs +110 -0
  99. package/skills/binance/square-post/scripts/save-key.mjs +34 -0
  100. package/skills/binance-web3/binance-agentic-wallet/SKILL.md +150 -0
  101. package/skills/binance-web3/binance-agentic-wallet/references/authentication.md +136 -0
  102. package/skills/binance-web3/binance-agentic-wallet/references/limit-order.md +204 -0
  103. package/skills/binance-web3/binance-agentic-wallet/references/market-order.md +179 -0
  104. package/skills/binance-web3/binance-agentic-wallet/references/prediction.md +489 -0
  105. package/skills/binance-web3/binance-agentic-wallet/references/preflight.md +66 -0
  106. package/skills/binance-web3/binance-agentic-wallet/references/security.md +47 -0
  107. package/skills/binance-web3/binance-agentic-wallet/references/send.md +53 -0
  108. package/skills/binance-web3/binance-agentic-wallet/references/wallet-setting.md +86 -0
  109. package/skills/binance-web3/binance-agentic-wallet/references/wallet-view.md +312 -0
  110. package/skills/binance-web3/binance-agentic-wallet/references/x402-payment.md +259 -0
  111. package/skills/binance-web3/binance-tokenized-securities-info/SKILL.md +613 -0
  112. package/skills/binance-web3/crypto-market-rank/SKILL.md +91 -0
  113. package/skills/binance-web3/crypto-market-rank/references/cli.md +219 -0
  114. package/skills/binance-web3/crypto-market-rank/scripts/cli.mjs +149 -0
  115. package/skills/binance-web3/meme-rush/SKILL.md +72 -0
  116. package/skills/binance-web3/meme-rush/references/cli.md +158 -0
  117. package/skills/binance-web3/meme-rush/scripts/cli.mjs +101 -0
  118. package/skills/binance-web3/query-address-info/SKILL.md +61 -0
  119. package/skills/binance-web3/query-address-info/references/cli.md +56 -0
  120. package/skills/binance-web3/query-address-info/scripts/cli.mjs +132 -0
  121. package/skills/binance-web3/query-token-audit/SKILL.md +162 -0
  122. package/skills/binance-web3/query-token-info/SKILL.md +83 -0
  123. package/skills/binance-web3/query-token-info/references/cli.md +135 -0
  124. package/skills/binance-web3/query-token-info/scripts/cli.mjs +112 -0
  125. package/skills/binance-web3/trading-signal/SKILL.md +66 -0
  126. package/skills/binance-web3/trading-signal/references/cli.md +90 -0
  127. package/skills/binance-web3/trading-signal/scripts/cli.mjs +92 -0
  128. package/skills/four-meme/four-guard/API-Contract-TaxToken.md +277 -0
  129. package/skills/four-meme/four-guard/API-CreateToken.02-02-2026.md +285 -0
  130. package/skills/four-meme/four-guard/API-Documents.03-03-2026.md +789 -0
  131. package/skills/four-meme/four-guard/AgentIdentifier.abi +585 -0
  132. package/skills/four-meme/four-guard/README.md +21 -0
  133. package/skills/four-meme/four-guard/SKILL.md +31 -0
  134. package/skills/four-meme/four-guard/TaxToken.abi +969 -0
  135. package/skills/four-meme/four-guard/TokenIdentifierSample.js_ +81 -0
  136. package/skills/four-meme/four-guard/TokenIdentifierSample.sol +69 -0
  137. package/skills/four-meme/four-guard/TokenManager.lite.abi +836 -0
  138. package/skills/four-meme/four-guard/TokenManager2.lite.abi +2325 -0
  139. package/skills/four-meme/four-guard/TokenManagerHelper3.abi +999 -0
  140. package/skills/four-meme/four-guard/go.mod +36 -0
  141. package/skills/four-meme/four-guard/go.sum +127 -0
  142. package/skills/four-meme/four-guard/main.go +183 -0
  143. package/skills/four-meme/four-meme-ai/SKILL.md +31 -0
  144. package/skills/four-meme/four-meme-ai/references/agent-creator-and-wallets.md +87 -0
  145. package/skills/four-meme/four-meme-ai/references/api-create-token.md +55 -0
  146. package/skills/four-meme/four-meme-ai/references/contract-addresses.md +47 -0
  147. package/skills/four-meme/four-meme-ai/references/create-token-scripts.md +131 -0
  148. package/skills/four-meme/four-meme-ai/references/errors.md +29 -0
  149. package/skills/four-meme/four-meme-ai/references/event-listening.md +75 -0
  150. package/skills/four-meme/four-meme-ai/references/execute-trade.md +31 -0
  151. package/skills/four-meme/four-meme-ai/references/tax-token-query.md +38 -0
  152. package/skills/four-meme/four-meme-ai/references/token-query-api.md +44 -0
  153. package/skills/four-meme/four-meme-ai/references/token-tax-info.md +77 -0
  154. package/skills/four-meme/four-meme-ai/scripts/8004-balance.ts +52 -0
  155. package/skills/four-meme/four-meme-ai/scripts/8004-register.ts +108 -0
  156. package/skills/four-meme/four-meme-ai/scripts/create-token-api.ts +321 -0
  157. package/skills/four-meme/four-meme-ai/scripts/create-token-chain.ts +102 -0
  158. package/skills/four-meme/four-meme-ai/scripts/create-token-instant.ts +106 -0
  159. package/skills/four-meme/four-meme-ai/scripts/execute-buy.ts +198 -0
  160. package/skills/four-meme/four-meme-ai/scripts/execute-sell.ts +150 -0
  161. package/skills/four-meme/four-meme-ai/scripts/get-public-config.ts +25 -0
  162. package/skills/four-meme/four-meme-ai/scripts/get-recent-events.ts +76 -0
  163. package/skills/four-meme/four-meme-ai/scripts/get-tax-token-info.ts +69 -0
  164. package/skills/four-meme/four-meme-ai/scripts/get-token-info.ts +94 -0
  165. package/skills/four-meme/four-meme-ai/scripts/quote-buy.ts +85 -0
  166. package/skills/four-meme/four-meme-ai/scripts/quote-sell.ts +66 -0
  167. package/skills/four-meme/four-meme-ai/scripts/send-token.ts +98 -0
  168. package/skills/four-meme/four-meme-ai/scripts/token-get.ts +31 -0
  169. package/skills/four-meme/four-meme-ai/scripts/token-list.ts +134 -0
  170. package/skills/four-meme/four-meme-ai/scripts/token-rankings.ts +162 -0
  171. package/skills/four-meme/four-meme-ai/scripts/verify-events.ts +47 -0
  172. package/skills/four-meme/four-meme-integration/SKILL.md +374 -0
  173. package/skills/four-meme/four-meme-integration/references/agent-creator-and-wallets.md +87 -0
  174. package/skills/four-meme/four-meme-integration/references/api-create-token.md +55 -0
  175. package/skills/four-meme/four-meme-integration/references/contract-addresses.md +47 -0
  176. package/skills/four-meme/four-meme-integration/references/create-token-scripts.md +131 -0
  177. package/skills/four-meme/four-meme-integration/references/errors.md +29 -0
  178. package/skills/four-meme/four-meme-integration/references/event-listening.md +75 -0
  179. package/skills/four-meme/four-meme-integration/references/execute-trade.md +31 -0
  180. package/skills/four-meme/four-meme-integration/references/tax-token-query.md +38 -0
  181. package/skills/four-meme/four-meme-integration/references/token-query-api.md +44 -0
  182. package/skills/four-meme/four-meme-integration/references/token-tax-info.md +77 -0
  183. package/skills/four-meme/four-meme-integration/scripts/8004-balance.ts +52 -0
  184. package/skills/four-meme/four-meme-integration/scripts/8004-register.ts +108 -0
  185. package/skills/four-meme/four-meme-integration/scripts/create-token-api.ts +321 -0
  186. package/skills/four-meme/four-meme-integration/scripts/create-token-chain.ts +102 -0
  187. package/skills/four-meme/four-meme-integration/scripts/create-token-instant.ts +106 -0
  188. package/skills/four-meme/four-meme-integration/scripts/execute-buy.ts +198 -0
  189. package/skills/four-meme/four-meme-integration/scripts/execute-sell.ts +150 -0
  190. package/skills/four-meme/four-meme-integration/scripts/get-public-config.ts +25 -0
  191. package/skills/four-meme/four-meme-integration/scripts/get-recent-events.ts +76 -0
  192. package/skills/four-meme/four-meme-integration/scripts/get-tax-token-info.ts +69 -0
  193. package/skills/four-meme/four-meme-integration/scripts/get-token-info.ts +94 -0
  194. package/skills/four-meme/four-meme-integration/scripts/quote-buy.ts +85 -0
  195. package/skills/four-meme/four-meme-integration/scripts/quote-sell.ts +66 -0
  196. package/skills/four-meme/four-meme-integration/scripts/send-token.ts +98 -0
  197. package/skills/four-meme/four-meme-integration/scripts/token-get.ts +31 -0
  198. package/skills/four-meme/four-meme-integration/scripts/token-list.ts +134 -0
  199. package/skills/four-meme/four-meme-integration/scripts/token-rankings.ts +162 -0
  200. package/skills/four-meme/four-meme-integration/scripts/verify-events.ts +47 -0
  201. package/skills/skillpp/contract-profiler/SKILL.md +118 -0
  202. package/skills/skillpp/newbie-tutor/SKILL.md +85 -0
  203. package/skills/skillpp/opportunity-board/SKILL.md +87 -0
  204. package/skills/skillpp/risk-fusion/SKILL.md +146 -0
  205. package/skills/skillpp/scam-pattern-lab/SKILL.md +115 -0
  206. package/skills/skillpp/wallet-doctor/SKILL.md +119 -0
  207. package/skills/skillpp/watchtower/SKILL.md +72 -0
  208. package/tests/compatibility/v0.1.0.json +117 -0
@@ -0,0 +1,560 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Payment Assistant - Common Infrastructure
4
+
5
+ Shared by send.py and receive.py:
6
+ - Constants (paths, timing, error codes, headers, config templates)
7
+ - Configuration (load, validate, guide)
8
+ - State management (OrderStatus, save/load/update/clear)
9
+ - API client (PaymentAPI with HMAC signing, rate limiting)
10
+ - Data models (PaymentStatusResponse, ConfirmPaymentResponse)
11
+ """
12
+ import time
13
+ import hmac
14
+ import hashlib
15
+ import os
16
+ import json
17
+ import secrets
18
+ from typing import Dict, Any, Optional
19
+ from enum import Enum
20
+
21
+ try:
22
+ import requests
23
+ HAS_REQUESTS = True
24
+ except ImportError:
25
+ HAS_REQUESTS = False
26
+
27
+
28
+ # ============================================================
29
+ # Order Status State Machine
30
+ # ============================================================
31
+ class OrderStatus(Enum):
32
+ """Order status for state machine tracking"""
33
+ INIT = "INIT" # Initial state, QR received
34
+ QR_PARSED = "QR_PARSED" # parseQr success, has preset amount
35
+ AWAITING_AMOUNT = "AWAITING_AMOUNT" # Waiting for amount input (no preset)
36
+ AMOUNT_SET = "AMOUNT_SET" # Amount set, ready to confirm
37
+ PAYMENT_CONFIRMED = "PAYMENT_CONFIRMED" # confirmPayment called, polling
38
+ POLLING = "POLLING" # Polling for result
39
+ SUCCESS = "SUCCESS" # Payment successful
40
+ FAILED = "FAILED" # Payment failed
41
+
42
+
43
+ # ============================================================
44
+ # Skills Payment Error Codes
45
+ # ============================================================
46
+ SKILLS_ERROR_CODES = {
47
+ -7100: ('LIMIT_NOT_CONFIGURED', 'Please go to the Binance app payment setting page to set up your Agent Pay limits via MFA.'),
48
+ -7101: ('SINGLE_LIMIT_EXCEEDED', 'Amount exceeds your limits. Please pay manually in the App.'),
49
+ -7102: ('DAILY_LIMIT_EXCEEDED', 'Amount exceeds your limits. Please pay manually in the App.'),
50
+ -7110: ('INSUFFICIENT_FUNDS', 'Insufficient balance in your Binance account.'),
51
+ -7130: ('INVALID_QR_FORMAT', 'Invalid QR code format'),
52
+ -7131: ('QR_EXPIRED_OR_NOT_FOUND', 'PayCode is invalid or expired. Please request a new one.'),
53
+ -7199: ('INTERNAL_ERROR', 'System error, please try again later'),
54
+ }
55
+
56
+
57
+ # ============================================================
58
+ # Configuration
59
+ # ============================================================
60
+ SKILL_DIR = os.path.dirname(os.path.abspath(__file__))
61
+ CONFIG_FILE_PATH = os.path.join(SKILL_DIR, 'config.json')
62
+ STATE_FILE_PATH = os.path.join(SKILL_DIR, '.payment_state.json')
63
+ API_LOCK_FILE_PATH = os.path.join(SKILL_DIR, '.api_lock_time')
64
+ QR_CODE_OUTPUT_PATH = os.path.join(SKILL_DIR, 'payment_qr.png')
65
+ INBOX_DIR = os.path.join(SKILL_DIR, 'inbox')
66
+ CLIPBOARD_IMAGE_PATH = os.path.join(INBOX_DIR, 'qr_clipboard.png')
67
+
68
+ # Timing configurations
69
+ POLL_INTERVAL = 2
70
+ MAX_POLL_ATTEMPTS = 30
71
+ RECV_WINDOW = 30000
72
+ API_CALL_INTERVAL = 2.0
73
+
74
+ # OpenAPI Header names (for /binancepay/openapi/* endpoints)
75
+ OPENAPI_HEADER_TIMESTAMP = 'BinancePay-Timestamp'
76
+ OPENAPI_HEADER_NONCE = 'BinancePay-Nonce'
77
+ OPENAPI_HEADER_CERT = 'BinancePay-Certificate-SN'
78
+ OPENAPI_HEADER_SIGNATURE = 'BinancePay-Signature'
79
+
80
+ # OpenAPI path prefix for routing
81
+ OPENAPI_PATH_PREFIX = '/binancepay/openapi/'
82
+
83
+ # API Key Setup Guide Message
84
+ API_KEY_GUIDE_MESSAGE = 'Payment API key & secret not configured. Please set your API key & secret in Binance App first.'
85
+
86
+ # Default config for auto-creation when config.json is missing
87
+ DEFAULT_CONFIG_TEMPLATE = {
88
+ "_comment_1": "=== Payment Assistant Configuration ===",
89
+ "_comment_2": "Please fill in the required fields below and set 'configured' to true",
90
+ "_comment_3": "---",
91
+ "configured": False,
92
+ "_comment_api_key": "API Key: Please set your API key & secret in Binance App first",
93
+ "api_key": "",
94
+ "_comment_api_secret": "API Secret: Generated together with API Key, keep it safe!",
95
+ "api_secret": "",
96
+ "_comment_5": "--- After filling in, set 'configured' to true to enable payment ---"
97
+ }
98
+
99
+ # Template for configuration (shown in guide)
100
+ CONFIG_TEMPLATE = {
101
+ "configured": True,
102
+ "api_key": "YOUR_API_KEY",
103
+ "api_secret": "YOUR_API_SECRET"
104
+ }
105
+
106
+
107
+ def create_default_config() -> str:
108
+ """Create default config.json file with template and instructions."""
109
+ with open(CONFIG_FILE_PATH, 'w') as f:
110
+ json.dump(DEFAULT_CONFIG_TEMPLATE, f, indent=2, ensure_ascii=False)
111
+ return CONFIG_FILE_PATH
112
+
113
+
114
+ def load_config() -> Dict[str, Any]:
115
+ """
116
+ Load configuration with priority: ENV > config.json > defaults
117
+
118
+ If config.json doesn't exist, create a template and show setup guide.
119
+ """
120
+ config = {
121
+ 'api_key': '',
122
+ 'api_secret': '',
123
+ 'base_url': '',
124
+ 'configured': False
125
+ }
126
+
127
+ # Check if config.json exists, if not create template
128
+ config_created = False
129
+ if not os.path.exists(CONFIG_FILE_PATH):
130
+ create_default_config()
131
+ config_created = True
132
+
133
+ # Load from config.json
134
+ if os.path.exists(CONFIG_FILE_PATH):
135
+ try:
136
+ with open(CONFIG_FILE_PATH, 'r') as f:
137
+ file_config = json.load(f)
138
+ file_config = {k: v for k, v in file_config.items() if not k.startswith('_')}
139
+ config.update(file_config)
140
+ except Exception as e:
141
+ print(f"⚠️ Warning: Failed to load config.json: {e}")
142
+
143
+ if config_created:
144
+ print()
145
+ print("════════════════════════════════════════════════════")
146
+ print("📝 Config template created: config.json")
147
+ print("════════════════════════════════════════════════════")
148
+ print()
149
+ print("⚠️ Please complete the configuration before proceeding:")
150
+ print()
151
+ print(f" 📁 Edit: {CONFIG_FILE_PATH}")
152
+ print()
153
+ print(" 📋 Required steps:")
154
+ print(" 1. Fill in: api_key, api_secret")
155
+ print(' 2. Set "configured": true')
156
+ print()
157
+ print(f" 🔑 {API_KEY_GUIDE_MESSAGE}")
158
+ print()
159
+ print("════════════════════════════════════════════════════")
160
+ print("📝 Example configuration:")
161
+ print("════════════════════════════════════════════════════")
162
+ print()
163
+ print(' {')
164
+ print(' "configured": true,')
165
+ print(' "api_key": "your_api_key_here",')
166
+ print(' "api_secret": "your_api_secret_here"')
167
+ print(' }')
168
+ print()
169
+ print("════════════════════════════════════════════════════")
170
+ print()
171
+
172
+ # Override with environment variables (highest priority)
173
+ if os.environ.get('PAYMENT_API_KEY'):
174
+ config['api_key'] = os.environ['PAYMENT_API_KEY']
175
+ if os.environ.get('PAYMENT_API_SECRET'):
176
+ config['api_secret'] = os.environ['PAYMENT_API_SECRET']
177
+ if os.environ.get('PAYMENT_BASE_URL'):
178
+ config['base_url'] = os.environ['PAYMENT_BASE_URL']
179
+
180
+ # Fallback to production URL if not set via config/env
181
+ if not config.get('base_url'):
182
+ config['base_url'] = 'https://bpay.binanceapi.com'
183
+
184
+ return config
185
+
186
+
187
+
188
+ def is_config_ready(config: Dict[str, Any]) -> tuple:
189
+ """Check if configuration is ready for use."""
190
+ if not config.get('configured', False):
191
+ return False, 'not_configured', []
192
+
193
+ required_fields = ['api_key', 'api_secret']
194
+ missing = []
195
+ for field in required_fields:
196
+ value = config.get(field, '')
197
+ if not value or value.startswith('YOUR_'):
198
+ missing.append(field)
199
+ if missing:
200
+ return False, 'missing_fields', missing
201
+
202
+ return True, 'ready', []
203
+
204
+
205
+ def show_config_guide(config: Dict[str, Any], reason: str, missing_fields: list = None):
206
+ """Show configuration guide when config is not ready."""
207
+ print()
208
+ print("════════════════════════════════════════════════════")
209
+ print("⚠️ Configuration Required")
210
+ print("════════════════════════════════════════════════════")
211
+ print()
212
+ print("📋 Please complete the configuration before proceeding:")
213
+ print()
214
+ print(f" Edit: {CONFIG_FILE_PATH}")
215
+ print()
216
+
217
+ if reason == 'not_configured':
218
+ print(" 1. Fill in: api_key, api_secret")
219
+ print(' 2. Set "configured": true')
220
+ elif reason == 'missing_fields':
221
+ print(" Missing required fields:")
222
+ for field in (missing_fields or []):
223
+ print(f" ❌ {field}")
224
+ else:
225
+ print(f" Configuration error: {reason}")
226
+
227
+ print()
228
+ print(f"🔑 {API_KEY_GUIDE_MESSAGE}")
229
+ print()
230
+ print("════════════════════════════════════════════════════")
231
+ print("📝 Config Example:")
232
+ print("════════════════════════════════════════════════════")
233
+ print()
234
+ print(' {')
235
+ print(' "configured": true,')
236
+ print(' "api_key": "...",')
237
+ print(' "api_secret": "..."')
238
+ print(' }')
239
+ print()
240
+ print("════════════════════════════════════════════════════")
241
+
242
+ print(json.dumps({
243
+ 'status': 'CONFIG_REQUIRED',
244
+ 'reason': reason,
245
+ 'missing_fields': missing_fields or [],
246
+ 'config_path': CONFIG_FILE_PATH,
247
+ 'message': API_KEY_GUIDE_MESSAGE
248
+ }))
249
+
250
+
251
+ def validate_config(config: Dict[str, Any]) -> tuple:
252
+ """Validate configuration."""
253
+ required_fields = ['api_key', 'api_secret']
254
+ missing = []
255
+
256
+ for field in required_fields:
257
+ value = config.get(field, '')
258
+ if not value or value.startswith('YOUR_'):
259
+ missing.append(field)
260
+
261
+ return len(missing) == 0, missing
262
+
263
+
264
+ # ============================================================
265
+ # API Lock Management
266
+ # ============================================================
267
+ def get_last_api_call_time() -> float:
268
+ """Get timestamp of last API call"""
269
+ try:
270
+ if os.path.exists(API_LOCK_FILE_PATH):
271
+ with open(API_LOCK_FILE_PATH, 'r') as f:
272
+ return float(f.read().strip())
273
+ except:
274
+ pass
275
+ return 0
276
+
277
+
278
+ def set_last_api_call_time(t: float):
279
+ """Save timestamp of API call"""
280
+ try:
281
+ with open(API_LOCK_FILE_PATH, 'w') as f:
282
+ f.write(str(t))
283
+ except:
284
+ pass
285
+
286
+
287
+ def wait_before_api_call():
288
+ """Wait if needed to respect API rate limits"""
289
+ last_time = get_last_api_call_time()
290
+ if last_time > 0:
291
+ elapsed = time.time() - last_time
292
+ if elapsed < API_CALL_INTERVAL:
293
+ time.sleep(API_CALL_INTERVAL - elapsed)
294
+
295
+
296
+ def mark_api_call_end():
297
+ """Mark the end of an API call"""
298
+ set_last_api_call_time(time.time())
299
+
300
+
301
+ # ============================================================
302
+ # State Management
303
+ # ============================================================
304
+ def save_state(state: Dict[str, Any]):
305
+ """Save state to file"""
306
+ state['last_updated'] = time.strftime('%Y-%m-%d %H:%M:%S')
307
+ with open(STATE_FILE_PATH, 'w') as f:
308
+ json.dump(state, f, indent=2)
309
+
310
+
311
+ def load_state() -> Dict[str, Any]:
312
+ """Load state from file"""
313
+ if os.path.exists(STATE_FILE_PATH):
314
+ try:
315
+ with open(STATE_FILE_PATH, 'r') as f:
316
+ return json.load(f)
317
+ except:
318
+ pass
319
+ return {}
320
+
321
+
322
+ def update_state(updates: Dict[str, Any]) -> Dict[str, Any]:
323
+ """Update state with new values"""
324
+ state = load_state()
325
+ state.update(updates)
326
+ save_state(state)
327
+ return state
328
+
329
+
330
+ def set_order_status(status: OrderStatus, **extra_fields) -> Dict[str, Any]:
331
+ """Set order status and optionally update other fields"""
332
+ updates = {'order_status': status.value}
333
+ updates.update(extra_fields)
334
+ return update_state(updates)
335
+
336
+
337
+ def get_order_status() -> Optional[OrderStatus]:
338
+ """Get current order status"""
339
+ state = load_state()
340
+ status_str = state.get('order_status')
341
+ if status_str:
342
+ try:
343
+ return OrderStatus(status_str)
344
+ except ValueError:
345
+ pass
346
+ return None
347
+
348
+
349
+ def clear_state():
350
+ """Clear all state for a fresh start"""
351
+ if os.path.exists(STATE_FILE_PATH):
352
+ os.remove(STATE_FILE_PATH)
353
+
354
+
355
+ def get_status_hint(status: OrderStatus, state: Dict[str, Any]) -> str:
356
+ """Get hint for next action based on current status"""
357
+ currency = state.get('currency', 'USDT')
358
+ hints = {
359
+ OrderStatus.INIT: "Run: --action resume (will parse QR)",
360
+ OrderStatus.QR_PARSED: "Run: --action pay_confirm (or --action resume)",
361
+ OrderStatus.AWAITING_AMOUNT: f"Run: --action set_amount --amount <AMOUNT> [--currency {currency}]",
362
+ OrderStatus.AMOUNT_SET: "Run: --action pay_confirm (or --action resume)",
363
+ OrderStatus.PAYMENT_CONFIRMED: "Run: --action poll (or --action resume)",
364
+ OrderStatus.POLLING: "Run: --action poll (or --action resume)",
365
+ OrderStatus.SUCCESS: "Payment complete! Run: --action reset for new payment",
366
+ OrderStatus.FAILED: f"Failed: {state.get('error_message', 'Unknown')}. Run: --action reset",
367
+ }
368
+ return hints.get(status, "Run: --action status")
369
+
370
+
371
+ # ============================================================
372
+ # Shared Data Models
373
+ # ============================================================
374
+ class PaymentStatusResponse:
375
+ """Response from queryPaymentStatus API (shared by all payment types)"""
376
+ def __init__(self, data: Dict[str, Any]):
377
+ self.status = data.get('status', '')
378
+ self.asset_cost_vos = []
379
+ if 'assetCostVos' in data and data['assetCostVos']:
380
+ for vo in data['assetCostVos']:
381
+ self.asset_cost_vos.append({
382
+ 'asset': vo.get('asset', ''),
383
+ 'amount': vo.get('amount', '0'),
384
+ 'price': vo.get('price', '0')
385
+ })
386
+
387
+
388
+ class ConfirmPaymentResponse:
389
+ """Response from confirmPayment API (shared by all payment types)"""
390
+ def __init__(self, data: Dict[str, Any]):
391
+ self.pay_order_id = data.get('payOrderId', '')
392
+ self.status = data.get('status', '')
393
+ self.usd_amount = data.get('usdAmount')
394
+ self.daily_used_before = data.get('dailyUsedBefore')
395
+ self.daily_used_after = data.get('dailyUsedAfter')
396
+
397
+
398
+ # ============================================================
399
+ # API Client
400
+ # ============================================================
401
+ class PaymentAPI:
402
+ """Payment API client with HMAC signing.
403
+
404
+ Uses OpenAPI style: /binancepay/openapi/* endpoints with header-based signature.
405
+
406
+ Extensions provide endpoints and params; this class handles transport.
407
+ """
408
+
409
+ def __init__(self, config: Dict[str, Any] = None):
410
+ if config is None:
411
+ config = load_config()
412
+ self.config = config
413
+ self.api_key = config.get('api_key', '')
414
+ self.api_secret = config.get('api_secret', '')
415
+ self.base_url = config.get('base_url', '')
416
+
417
+ def _make_request(self, endpoint: str, params: Dict[str, Any], method: str = 'POST', use_body: bool = False) -> Dict[str, Any]:
418
+ """Make API request using OpenAPI signing method.
419
+
420
+ Args:
421
+ endpoint: API path (e.g. '/binancepay/openapi/user/c2c/parseQr')
422
+ params: Request parameters
423
+ method: HTTP method (GET or POST)
424
+ use_body: If True, send params as JSON body (for @RequestBody APIs)
425
+ """
426
+ if not HAS_REQUESTS:
427
+ return {'success': False, 'code': '-1', 'message': 'requests module not installed'}
428
+
429
+ if not self.base_url:
430
+ return {'success': False, 'code': '-1', 'message': 'Missing configuration. Run --action config for setup guide.'}
431
+
432
+ return self._make_openapi_request(endpoint, params)
433
+
434
+ def _make_openapi_request(self, endpoint: str, params: Dict[str, Any]) -> Dict[str, Any]:
435
+ """Make OpenAPI-style request with header-based signature.
436
+
437
+ Signature format: HMAC-SHA512(payload, api_secret)
438
+ Payload: timestamp\\n + nonce\\n + body\\n
439
+ Headers: BinancePay-Timestamp, BinancePay-Nonce, BinancePay-Certificate-SN, BinancePay-Signature
440
+ """
441
+ wait_before_api_call()
442
+
443
+ try:
444
+ url = f"{self.base_url}{endpoint}"
445
+ timestamp = int(time.time() * 1000)
446
+ nonce = secrets.token_hex(16) # 32-char random string
447
+
448
+ # Ensure body_json matches what requests.post(json=...) will send
449
+ # requests sends "{}" for empty dict, and the actual JSON for non-empty
450
+ body_json = json.dumps(params) if params is not None else ''
451
+
452
+ # Build signature (OpenAPI style: HMAC-SHA512 of timestamp + nonce + body)
453
+ payload = f"{timestamp}\n{nonce}\n{body_json}\n"
454
+ signature = hmac.new(self.api_secret.encode(), payload.encode(), hashlib.sha512).hexdigest().upper()
455
+
456
+ headers = {
457
+ 'Content-Type': 'application/json',
458
+ 'Accept': 'application/json',
459
+ OPENAPI_HEADER_TIMESTAMP: str(timestamp),
460
+ OPENAPI_HEADER_NONCE: nonce,
461
+ OPENAPI_HEADER_CERT: self.api_key,
462
+ OPENAPI_HEADER_SIGNATURE: signature,
463
+ }
464
+
465
+ # Add gray environment header if configured
466
+ gray_env = self.config.get('gray_env', '')
467
+ if gray_env:
468
+ headers['x-gray-env'] = gray_env
469
+
470
+ response = requests.post(url, headers=headers, json=params, timeout=30)
471
+ mark_api_call_end()
472
+ return self._parse_response(response)
473
+
474
+ except Exception as e:
475
+ mark_api_call_end()
476
+ return {'success': False, 'code': '-1', 'message': str(e)}
477
+
478
+ def _parse_response(self, response) -> Dict[str, Any]:
479
+ """Parse API response into unified format.
480
+
481
+ OpenAPI format: {"status": "SUCCESS", "code": "000000", "data": {...}, "errorMessage": null}
482
+
483
+ Returns:
484
+ {'success': True, 'data': ...} on success
485
+ {'success': False, 'code': ..., 'message': ...} on error
486
+ """
487
+ try:
488
+ result = response.json()
489
+ except:
490
+ return {'code': str(response.status_code), 'message': response.text, 'success': False}
491
+
492
+ code = result.get('code', '')
493
+
494
+ # Check success condition (OpenAPI style)
495
+ status = result.get('status', '')
496
+ is_success = (status == 'SUCCESS' and code == '000000')
497
+
498
+ if is_success:
499
+ return {'success': True, 'data': result.get('data')}
500
+ else:
501
+ error_code = None
502
+ try:
503
+ error_code = int(code)
504
+ except:
505
+ pass
506
+ error_message = result.get('errorMessage') or 'Unknown error'
507
+ return {
508
+ 'success': False,
509
+ 'code': error_code or code,
510
+ 'message': error_message
511
+ }
512
+
513
+ def _parse_error(self, result: Dict[str, Any]) -> Dict[str, Any]:
514
+ """Parse API error and return user-friendly info"""
515
+ code = result.get('code')
516
+ message = result.get('message', 'Unknown error')
517
+
518
+ if code in SKILLS_ERROR_CODES:
519
+ status, hint = SKILLS_ERROR_CODES[code]
520
+ return {'status': status, 'code': code, 'message': message, 'hint': hint}
521
+
522
+ return {'status': 'ERROR', 'code': code, 'message': message, 'hint': 'Please try again later'}
523
+
524
+ def make_parsed_request(self, endpoint: str, params: Dict[str, Any], response_cls, method: str = 'POST', use_body: bool = False) -> Dict[str, Any]:
525
+ """Make API request and parse response with given class.
526
+
527
+ Used by extensions to call APIs with their own response models.
528
+
529
+ Args:
530
+ endpoint: API path
531
+ params: Request parameters
532
+ response_cls: Class to wrap the response data (e.g. C2cParseQrResponse)
533
+ method: HTTP method
534
+ use_body: Send params as JSON body
535
+
536
+ Returns:
537
+ {'success': True, 'order_info': <response_cls instance>} on success
538
+ {'success': False, 'status': ..., 'message': ..., ...} on error
539
+ """
540
+ result = self._make_request(endpoint, params, method=method, use_body=use_body)
541
+ if result['success'] and result.get('data'):
542
+ return {'success': True, 'order_info': response_cls(result['data'])}
543
+ error_info = self._parse_error(result)
544
+ return {'success': False, **error_info}
545
+
546
+ def confirm_payment(self, endpoint: str, params: Dict[str, Any]) -> Dict[str, Any]:
547
+ """Call confirmPayment endpoint (shared response format)."""
548
+ result = self._make_request(endpoint, params, use_body=True)
549
+ if result['success'] and result.get('data'):
550
+ return {'success': True, 'payment_info': ConfirmPaymentResponse(result['data'])}
551
+ error_info = self._parse_error(result)
552
+ return {'success': False, **error_info}
553
+
554
+ def query_payment_status(self, endpoint: str, params: Dict[str, Any]) -> Dict[str, Any]:
555
+ """Call queryPaymentStatus endpoint (shared response format)."""
556
+ result = self._make_request(endpoint, params, method='POST', use_body=True)
557
+ if result['success'] and result.get('data'):
558
+ return {'success': True, 'status_info': PaymentStatusResponse(result['data'])}
559
+ error_info = self._parse_error(result)
560
+ return {'success': False, **error_info}
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Payment Assistant Skill - Entry Point
4
+ QR Code Payment - Funding Wallet Auto-deduction (C2C + PIX) + Receive
5
+
6
+ This is the CLI entry point. Business logic lives in:
7
+ - common.py: Shared infrastructure (config, state, API client)
8
+ - send.py: Send/pay actions + QR handling
9
+ - receive.py: Receive actions
10
+ """
11
+ import argparse
12
+
13
+ from common import load_config, update_state
14
+
15
+ # Import send actions
16
+ from send import (
17
+ action_config,
18
+ action_purchase,
19
+ action_set_amount,
20
+ action_pay_confirm,
21
+ action_poll,
22
+ action_status,
23
+ action_reset,
24
+ action_resume,
25
+ action_help,
26
+ action_decode_qr,
27
+ )
28
+
29
+ # Import receive actions
30
+ from receive import action_receive
31
+
32
+
33
+ def main():
34
+ parser = argparse.ArgumentParser(description='Payment Assistant Skill (C2C + PIX + Receive)')
35
+
36
+ available_actions = [
37
+ 'purchase', 'set_amount', 'pay_confirm', 'poll', 'query',
38
+ 'status', 'resume', 'reset', 'config', 'help', 'decode_qr',
39
+ 'receive',
40
+ ]
41
+
42
+ parser.add_argument('--action', type=str, required=True, choices=available_actions)
43
+ parser.add_argument('--raw_qr', type=str, help='Raw QR code data (C2C URL or PIX EMV string)')
44
+ parser.add_argument('--amount', type=float, help='Payment amount')
45
+ parser.add_argument('--currency', type=str, help='Payment currency (e.g., USDT, BRL, BTC)')
46
+ parser.add_argument('--image', type=str, help='Image file path for decode_qr')
47
+ parser.add_argument('--base64', type=str, help='Base64 encoded image data for decode_qr')
48
+ parser.add_argument('--clipboard', action='store_true', help='Explicitly read from system clipboard')
49
+ parser.add_argument('--note', type=str, help='Note/description for receive')
50
+
51
+ args = parser.parse_args()
52
+
53
+ config = load_config()
54
+
55
+ # Dispatch
56
+ if args.action == 'help':
57
+ action_help()
58
+ elif args.action == 'config':
59
+ action_config()
60
+ elif args.action == 'status':
61
+ action_status()
62
+ elif args.action == 'reset':
63
+ action_reset()
64
+ elif args.action == 'resume':
65
+ action_resume(config)
66
+ elif args.action == 'decode_qr':
67
+ action_decode_qr(image_path=args.image, base64_data=args.base64, use_clipboard=args.clipboard)
68
+ elif args.action == 'purchase':
69
+ action_purchase(config, args.raw_qr)
70
+ elif args.action == 'set_amount':
71
+ if args.amount is None:
72
+ print("❌ --amount required")
73
+ return
74
+ action_set_amount(args.amount, args.currency)
75
+ elif args.action == 'pay_confirm':
76
+ action_pay_confirm(config, args.amount, args.currency)
77
+ elif args.action == 'poll':
78
+ action_poll(config)
79
+ elif args.action == 'query':
80
+ action_poll(config)
81
+ elif args.action == 'receive':
82
+ action_receive(config, currency=args.currency, amount=args.amount, note=args.note)
83
+
84
+
85
+ if __name__ == '__main__':
86
+ main()