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.
- package/COMPATIBILITY.md +58 -0
- package/LICENSE +21 -0
- package/README.md +307 -0
- package/README.zh-CN.md +307 -0
- package/SKILL.md +490 -0
- package/adapters/binance-ai.md +22 -0
- package/adapters/claude.md +21 -0
- package/adapters/gemini.md +26 -0
- package/adapters/gpt.md +28 -0
- package/adapters/kimi.md +26 -0
- package/adapters/mimo.md +22 -0
- package/adapters/openclaw.md +29 -0
- package/assets/skillpp-banner.png +0 -0
- package/package.json +59 -0
- package/pipelines.md +310 -0
- package/prompts/newbie-mode.md +48 -0
- package/prompts/router-prompt.md +32 -0
- package/prompts/universal-system-prompt.md +41 -0
- package/registry.md +209 -0
- package/rules.md +323 -0
- package/schemas/audit.schema.json +67 -0
- package/schemas/checkpoint.schema.json +86 -0
- package/schemas/handoff.schema.json +82 -0
- package/schemas/token.schema.json +36 -0
- package/scripts/compatibility-check.mjs +130 -0
- package/scripts/selftest.mjs +384 -0
- package/scripts/skillpp.mjs +448 -0
- package/scripts/validate-skillpp.mjs +140 -0
- package/skillpp.manifest.json +714 -0
- package/skills/audit-plus/SKILL.md +612 -0
- package/skills/binance/binance/CHANGELOG.md +112 -0
- package/skills/binance/binance/LICENSE.md +9 -0
- package/skills/binance/binance/SKILL.md +69 -0
- package/skills/binance/binance/references/algo.md +21 -0
- package/skills/binance/binance/references/alpha.md +9 -0
- package/skills/binance/binance/references/auth.md +32 -0
- package/skills/binance/binance/references/c2c.md +5 -0
- package/skills/binance/binance/references/convert.md +19 -0
- package/skills/binance/binance/references/copy-trading.md +6 -0
- package/skills/binance/binance/references/crypto-loan.md +27 -0
- package/skills/binance/binance/references/derivatives-options-streams.md +25 -0
- package/skills/binance/binance/references/derivatives-options.md +85 -0
- package/skills/binance/binance/references/derivatives-portfolio-margin-pro-streams.md +5 -0
- package/skills/binance/binance/references/derivatives-portfolio-margin-pro.md +34 -0
- package/skills/binance/binance/references/derivatives-portfolio-margin-streams.md +5 -0
- package/skills/binance/binance/references/derivatives-portfolio-margin.md +146 -0
- package/skills/binance/binance/references/dual-investment.md +15 -0
- package/skills/binance/binance/references/fiat.md +9 -0
- package/skills/binance/binance/references/futures-coin-streams.md +29 -0
- package/skills/binance/binance/references/futures-coin.md +109 -0
- package/skills/binance/binance/references/futures-usds-streams.md +35 -0
- package/skills/binance/binance/references/futures-usds.md +144 -0
- package/skills/binance/binance/references/gift-card.md +10 -0
- package/skills/binance/binance/references/margin-trading-streams.md +6 -0
- package/skills/binance/binance/references/margin-trading.md +101 -0
- package/skills/binance/binance/references/mining.md +17 -0
- package/skills/binance/binance/references/pay.md +5 -0
- package/skills/binance/binance/references/rebate.md +5 -0
- package/skills/binance/binance/references/simple-earn.md +56 -0
- package/skills/binance/binance/references/spot-streams.md +25 -0
- package/skills/binance/binance/references/spot.md +114 -0
- package/skills/binance/binance/references/staking.md +59 -0
- package/skills/binance/binance/references/sub-account.md +67 -0
- package/skills/binance/binance/references/vip-loan.md +27 -0
- package/skills/binance/binance/references/wallet.md +75 -0
- package/skills/binance/fiat/CHANGELOG.md +11 -0
- package/skills/binance/fiat/LICENSE.md +9 -0
- package/skills/binance/fiat/SKILL.md +169 -0
- package/skills/binance/fiat/references/authentication.md +126 -0
- package/skills/binance/fiat/references/sapi-endpoints.md +217 -0
- package/skills/binance/onchain-pay/.local.md.example +10 -0
- package/skills/binance/onchain-pay/CHANGELOG.md +20 -0
- package/skills/binance/onchain-pay/LICENSE.md +9 -0
- package/skills/binance/onchain-pay/SKILL.md +466 -0
- package/skills/binance/onchain-pay/references/authentication.md +92 -0
- package/skills/binance/onchain-pay/scripts/sign_and_call.sh +52 -0
- package/skills/binance/p2p/CHANGELOG.md +33 -0
- package/skills/binance/p2p/LICENSE.md +9 -0
- package/skills/binance/p2p/SKILL.md +1082 -0
- package/skills/binance/p2p/references/agent-sapi-api.md +795 -0
- package/skills/binance/p2p/references/authentication.md +100 -0
- package/skills/binance/payment/SKILL.md +824 -0
- package/skills/binance/payment/common.py +560 -0
- package/skills/binance/payment/payment_skill.py +86 -0
- package/skills/binance/payment/receive.py +109 -0
- package/skills/binance/payment/references/setup-guide.md +77 -0
- package/skills/binance/payment/requirements.txt +4 -0
- package/skills/binance/payment/send.py +952 -0
- package/skills/binance/payment/send_extension/__init__.py +43 -0
- package/skills/binance/payment/send_extension/base.py +48 -0
- package/skills/binance/payment/send_extension/c2c.py +193 -0
- package/skills/binance/payment/send_extension/pix.py +316 -0
- package/skills/binance/square-post/README.md +62 -0
- package/skills/binance/square-post/SKILL.md +171 -0
- package/skills/binance/square-post/scripts/lib.mjs +175 -0
- package/skills/binance/square-post/scripts/post-image.mjs +80 -0
- package/skills/binance/square-post/scripts/post-text.mjs +41 -0
- package/skills/binance/square-post/scripts/post-video.mjs +110 -0
- package/skills/binance/square-post/scripts/save-key.mjs +34 -0
- package/skills/binance-web3/binance-agentic-wallet/SKILL.md +150 -0
- package/skills/binance-web3/binance-agentic-wallet/references/authentication.md +136 -0
- package/skills/binance-web3/binance-agentic-wallet/references/limit-order.md +204 -0
- package/skills/binance-web3/binance-agentic-wallet/references/market-order.md +179 -0
- package/skills/binance-web3/binance-agentic-wallet/references/prediction.md +489 -0
- package/skills/binance-web3/binance-agentic-wallet/references/preflight.md +66 -0
- package/skills/binance-web3/binance-agentic-wallet/references/security.md +47 -0
- package/skills/binance-web3/binance-agentic-wallet/references/send.md +53 -0
- package/skills/binance-web3/binance-agentic-wallet/references/wallet-setting.md +86 -0
- package/skills/binance-web3/binance-agentic-wallet/references/wallet-view.md +312 -0
- package/skills/binance-web3/binance-agentic-wallet/references/x402-payment.md +259 -0
- package/skills/binance-web3/binance-tokenized-securities-info/SKILL.md +613 -0
- package/skills/binance-web3/crypto-market-rank/SKILL.md +91 -0
- package/skills/binance-web3/crypto-market-rank/references/cli.md +219 -0
- package/skills/binance-web3/crypto-market-rank/scripts/cli.mjs +149 -0
- package/skills/binance-web3/meme-rush/SKILL.md +72 -0
- package/skills/binance-web3/meme-rush/references/cli.md +158 -0
- package/skills/binance-web3/meme-rush/scripts/cli.mjs +101 -0
- package/skills/binance-web3/query-address-info/SKILL.md +61 -0
- package/skills/binance-web3/query-address-info/references/cli.md +56 -0
- package/skills/binance-web3/query-address-info/scripts/cli.mjs +132 -0
- package/skills/binance-web3/query-token-audit/SKILL.md +162 -0
- package/skills/binance-web3/query-token-info/SKILL.md +83 -0
- package/skills/binance-web3/query-token-info/references/cli.md +135 -0
- package/skills/binance-web3/query-token-info/scripts/cli.mjs +112 -0
- package/skills/binance-web3/trading-signal/SKILL.md +66 -0
- package/skills/binance-web3/trading-signal/references/cli.md +90 -0
- package/skills/binance-web3/trading-signal/scripts/cli.mjs +92 -0
- package/skills/four-meme/four-guard/API-Contract-TaxToken.md +277 -0
- package/skills/four-meme/four-guard/API-CreateToken.02-02-2026.md +285 -0
- package/skills/four-meme/four-guard/API-Documents.03-03-2026.md +789 -0
- package/skills/four-meme/four-guard/AgentIdentifier.abi +585 -0
- package/skills/four-meme/four-guard/README.md +21 -0
- package/skills/four-meme/four-guard/SKILL.md +31 -0
- package/skills/four-meme/four-guard/TaxToken.abi +969 -0
- package/skills/four-meme/four-guard/TokenIdentifierSample.js_ +81 -0
- package/skills/four-meme/four-guard/TokenIdentifierSample.sol +69 -0
- package/skills/four-meme/four-guard/TokenManager.lite.abi +836 -0
- package/skills/four-meme/four-guard/TokenManager2.lite.abi +2325 -0
- package/skills/four-meme/four-guard/TokenManagerHelper3.abi +999 -0
- package/skills/four-meme/four-guard/go.mod +36 -0
- package/skills/four-meme/four-guard/go.sum +127 -0
- package/skills/four-meme/four-guard/main.go +183 -0
- package/skills/four-meme/four-meme-ai/SKILL.md +31 -0
- package/skills/four-meme/four-meme-ai/references/agent-creator-and-wallets.md +87 -0
- package/skills/four-meme/four-meme-ai/references/api-create-token.md +55 -0
- package/skills/four-meme/four-meme-ai/references/contract-addresses.md +47 -0
- package/skills/four-meme/four-meme-ai/references/create-token-scripts.md +131 -0
- package/skills/four-meme/four-meme-ai/references/errors.md +29 -0
- package/skills/four-meme/four-meme-ai/references/event-listening.md +75 -0
- package/skills/four-meme/four-meme-ai/references/execute-trade.md +31 -0
- package/skills/four-meme/four-meme-ai/references/tax-token-query.md +38 -0
- package/skills/four-meme/four-meme-ai/references/token-query-api.md +44 -0
- package/skills/four-meme/four-meme-ai/references/token-tax-info.md +77 -0
- package/skills/four-meme/four-meme-ai/scripts/8004-balance.ts +52 -0
- package/skills/four-meme/four-meme-ai/scripts/8004-register.ts +108 -0
- package/skills/four-meme/four-meme-ai/scripts/create-token-api.ts +321 -0
- package/skills/four-meme/four-meme-ai/scripts/create-token-chain.ts +102 -0
- package/skills/four-meme/four-meme-ai/scripts/create-token-instant.ts +106 -0
- package/skills/four-meme/four-meme-ai/scripts/execute-buy.ts +198 -0
- package/skills/four-meme/four-meme-ai/scripts/execute-sell.ts +150 -0
- package/skills/four-meme/four-meme-ai/scripts/get-public-config.ts +25 -0
- package/skills/four-meme/four-meme-ai/scripts/get-recent-events.ts +76 -0
- package/skills/four-meme/four-meme-ai/scripts/get-tax-token-info.ts +69 -0
- package/skills/four-meme/four-meme-ai/scripts/get-token-info.ts +94 -0
- package/skills/four-meme/four-meme-ai/scripts/quote-buy.ts +85 -0
- package/skills/four-meme/four-meme-ai/scripts/quote-sell.ts +66 -0
- package/skills/four-meme/four-meme-ai/scripts/send-token.ts +98 -0
- package/skills/four-meme/four-meme-ai/scripts/token-get.ts +31 -0
- package/skills/four-meme/four-meme-ai/scripts/token-list.ts +134 -0
- package/skills/four-meme/four-meme-ai/scripts/token-rankings.ts +162 -0
- package/skills/four-meme/four-meme-ai/scripts/verify-events.ts +47 -0
- package/skills/four-meme/four-meme-integration/SKILL.md +374 -0
- package/skills/four-meme/four-meme-integration/references/agent-creator-and-wallets.md +87 -0
- package/skills/four-meme/four-meme-integration/references/api-create-token.md +55 -0
- package/skills/four-meme/four-meme-integration/references/contract-addresses.md +47 -0
- package/skills/four-meme/four-meme-integration/references/create-token-scripts.md +131 -0
- package/skills/four-meme/four-meme-integration/references/errors.md +29 -0
- package/skills/four-meme/four-meme-integration/references/event-listening.md +75 -0
- package/skills/four-meme/four-meme-integration/references/execute-trade.md +31 -0
- package/skills/four-meme/four-meme-integration/references/tax-token-query.md +38 -0
- package/skills/four-meme/four-meme-integration/references/token-query-api.md +44 -0
- package/skills/four-meme/four-meme-integration/references/token-tax-info.md +77 -0
- package/skills/four-meme/four-meme-integration/scripts/8004-balance.ts +52 -0
- package/skills/four-meme/four-meme-integration/scripts/8004-register.ts +108 -0
- package/skills/four-meme/four-meme-integration/scripts/create-token-api.ts +321 -0
- package/skills/four-meme/four-meme-integration/scripts/create-token-chain.ts +102 -0
- package/skills/four-meme/four-meme-integration/scripts/create-token-instant.ts +106 -0
- package/skills/four-meme/four-meme-integration/scripts/execute-buy.ts +198 -0
- package/skills/four-meme/four-meme-integration/scripts/execute-sell.ts +150 -0
- package/skills/four-meme/four-meme-integration/scripts/get-public-config.ts +25 -0
- package/skills/four-meme/four-meme-integration/scripts/get-recent-events.ts +76 -0
- package/skills/four-meme/four-meme-integration/scripts/get-tax-token-info.ts +69 -0
- package/skills/four-meme/four-meme-integration/scripts/get-token-info.ts +94 -0
- package/skills/four-meme/four-meme-integration/scripts/quote-buy.ts +85 -0
- package/skills/four-meme/four-meme-integration/scripts/quote-sell.ts +66 -0
- package/skills/four-meme/four-meme-integration/scripts/send-token.ts +98 -0
- package/skills/four-meme/four-meme-integration/scripts/token-get.ts +31 -0
- package/skills/four-meme/four-meme-integration/scripts/token-list.ts +134 -0
- package/skills/four-meme/four-meme-integration/scripts/token-rankings.ts +162 -0
- package/skills/four-meme/four-meme-integration/scripts/verify-events.ts +47 -0
- package/skills/skillpp/contract-profiler/SKILL.md +118 -0
- package/skills/skillpp/newbie-tutor/SKILL.md +85 -0
- package/skills/skillpp/opportunity-board/SKILL.md +87 -0
- package/skills/skillpp/risk-fusion/SKILL.md +146 -0
- package/skills/skillpp/scam-pattern-lab/SKILL.md +115 -0
- package/skills/skillpp/wallet-doctor/SKILL.md +119 -0
- package/skills/skillpp/watchtower/SKILL.md +72 -0
- 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()
|