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,43 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Payment Extension Registry.
|
|
3
|
+
|
|
4
|
+
Extensions are checked in order — first match wins.
|
|
5
|
+
PIX is checked before C2C because C2C is the catch-all fallback.
|
|
6
|
+
"""
|
|
7
|
+
from .base import PaymentExtension
|
|
8
|
+
from .c2c import C2cExtension
|
|
9
|
+
from .pix import PixExtension
|
|
10
|
+
|
|
11
|
+
# Ordered list: specific detectors first, fallback last
|
|
12
|
+
EXTENSIONS = [PixExtension(), C2cExtension()]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def detect_extension(raw_qr: str) -> PaymentExtension:
|
|
16
|
+
"""Find the matching extension for a given QR code string."""
|
|
17
|
+
for ext in EXTENSIONS:
|
|
18
|
+
if ext.detect(raw_qr):
|
|
19
|
+
return ext
|
|
20
|
+
# Should never reach here since C2C is catch-all, but just in case
|
|
21
|
+
return C2cExtension()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_extension_by_type(payment_type: str) -> PaymentExtension:
|
|
25
|
+
"""Look up extension by payment_type string (e.g. 'C2C', 'PIX')."""
|
|
26
|
+
for ext in EXTENSIONS:
|
|
27
|
+
if ext.payment_type == payment_type:
|
|
28
|
+
return ext
|
|
29
|
+
return C2cExtension()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_all_endpoints() -> dict:
|
|
33
|
+
"""Merge endpoints from all extensions into one dict.
|
|
34
|
+
|
|
35
|
+
Keys are prefixed with payment_type to avoid collisions.
|
|
36
|
+
e.g. 'c2c_parse_qr', 'pix_parse_qr'
|
|
37
|
+
"""
|
|
38
|
+
merged = {}
|
|
39
|
+
for ext in EXTENSIONS:
|
|
40
|
+
prefix = ext.payment_type.lower()
|
|
41
|
+
for key, path in ext.endpoints.items():
|
|
42
|
+
merged[f"{prefix}_{key}"] = path
|
|
43
|
+
return merged
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PaymentExtension base class.
|
|
3
|
+
|
|
4
|
+
Each payment type (C2C, PIX, ...) implements this interface.
|
|
5
|
+
The main payment_skill.py dispatches to the matched extension.
|
|
6
|
+
"""
|
|
7
|
+
from typing import Dict, Any, Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PaymentExtension:
|
|
11
|
+
"""Base class for payment type extensions."""
|
|
12
|
+
|
|
13
|
+
payment_type: str = '' # e.g. 'C2C', 'PIX'
|
|
14
|
+
endpoints: Dict[str, str] = {} # endpoint_key -> path
|
|
15
|
+
|
|
16
|
+
def detect(self, raw_qr: str) -> bool:
|
|
17
|
+
"""Return True if this extension handles the given QR data."""
|
|
18
|
+
return False
|
|
19
|
+
|
|
20
|
+
def purchase(self, api: Any, raw_qr: str, state_helpers: Dict[str, Any]):
|
|
21
|
+
"""
|
|
22
|
+
Step 1: Parse QR code and save order state.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
api: PaymentAPI instance (for making HTTP requests)
|
|
26
|
+
raw_qr: Raw QR code string
|
|
27
|
+
state_helpers: Dict with helper functions:
|
|
28
|
+
- set_order_status(status, **fields)
|
|
29
|
+
- update_state(updates)
|
|
30
|
+
- OrderStatus enum
|
|
31
|
+
"""
|
|
32
|
+
raise NotImplementedError
|
|
33
|
+
|
|
34
|
+
def build_confirm_params(self, state: Dict[str, Any], amount: str, currency: str) -> Dict[str, Any]:
|
|
35
|
+
"""Build request params for confirmPayment API."""
|
|
36
|
+
raise NotImplementedError
|
|
37
|
+
|
|
38
|
+
def get_confirm_endpoint(self) -> str:
|
|
39
|
+
"""Return the endpoint key for confirmPayment."""
|
|
40
|
+
raise NotImplementedError
|
|
41
|
+
|
|
42
|
+
def get_poll_endpoint(self) -> str:
|
|
43
|
+
"""Return the endpoint key for queryPaymentStatus."""
|
|
44
|
+
raise NotImplementedError
|
|
45
|
+
|
|
46
|
+
def build_poll_params(self, state: Dict[str, Any]) -> Dict[str, Any]:
|
|
47
|
+
"""Build request params for queryPaymentStatus API."""
|
|
48
|
+
return {'payOrderId': state.get('pay_order_id', '')}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""
|
|
2
|
+
C2C Payment Extension.
|
|
3
|
+
|
|
4
|
+
Handles Binance C2C QR Code payments (URL-based QR codes).
|
|
5
|
+
"""
|
|
6
|
+
import json
|
|
7
|
+
from typing import Dict, Any
|
|
8
|
+
|
|
9
|
+
from .base import PaymentExtension
|
|
10
|
+
|
|
11
|
+
# Payment type constant
|
|
12
|
+
PAYMENT_TYPE_C2C = 'C2C'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# ============================================================
|
|
16
|
+
# Data Models
|
|
17
|
+
# ============================================================
|
|
18
|
+
class C2cParseQrResponse:
|
|
19
|
+
"""Response from C2C parseQr API"""
|
|
20
|
+
def __init__(self, data: Dict[str, Any]):
|
|
21
|
+
self.checkout_id = data.get('checkoutId', '')
|
|
22
|
+
self.checkout_type = data.get('checkoutType', '')
|
|
23
|
+
self.biz_type = data.get('bizType', '')
|
|
24
|
+
self.nickname = data.get('nickname', '')
|
|
25
|
+
self.avatar_url = data.get('avatarUrl', '')
|
|
26
|
+
self.currency = data.get('currency', '')
|
|
27
|
+
self.currency_fixed = data.get('currencyFixed', False)
|
|
28
|
+
self.amount = data.get('amount')
|
|
29
|
+
self.has_preset_amount = data.get('hasPresetAmount', False)
|
|
30
|
+
self.description = data.get('description', '')
|
|
31
|
+
self.single_transaction_limit = data.get('singleTransactionLimit')
|
|
32
|
+
self.daily_limit = data.get('dailyLimit')
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class C2cConfirmPaymentResponse:
|
|
36
|
+
"""Response from C2C confirmPayment API"""
|
|
37
|
+
def __init__(self, data: Dict[str, Any]):
|
|
38
|
+
self.pay_order_id = data.get('payOrderId', '')
|
|
39
|
+
self.status = data.get('status', '')
|
|
40
|
+
self.usd_amount = data.get('usdAmount')
|
|
41
|
+
self.daily_used_before = data.get('dailyUsedBefore')
|
|
42
|
+
self.daily_used_after = data.get('dailyUsedAfter')
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ============================================================
|
|
46
|
+
# C2C Extension
|
|
47
|
+
# ============================================================
|
|
48
|
+
class C2cExtension(PaymentExtension):
|
|
49
|
+
"""C2C QR Code payment extension."""
|
|
50
|
+
|
|
51
|
+
payment_type = PAYMENT_TYPE_C2C
|
|
52
|
+
|
|
53
|
+
# OpenAPI endpoints
|
|
54
|
+
endpoints = {
|
|
55
|
+
'parse_qr': '/binancepay/openapi/user/c2c/parseQr',
|
|
56
|
+
'confirm_payment': '/binancepay/openapi/user/c2c/confirmPayment',
|
|
57
|
+
'query_payment_status': '/binancepay/openapi/user/c2c/queryPaymentStatus',
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
def detect(self, raw_qr: str) -> bool:
|
|
61
|
+
"""C2C is the default/fallback — matches anything that isn't PIX."""
|
|
62
|
+
# C2C detection is intentionally broad; PIX is checked first in registry.
|
|
63
|
+
return True
|
|
64
|
+
|
|
65
|
+
def purchase(self, api, raw_qr: str, state_helpers: Dict[str, Any]):
|
|
66
|
+
"""C2C purchase flow - Step 1: Parse C2C QR code"""
|
|
67
|
+
set_order_status = state_helpers['set_order_status']
|
|
68
|
+
update_state = state_helpers['update_state']
|
|
69
|
+
OrderStatus = state_helpers['OrderStatus']
|
|
70
|
+
|
|
71
|
+
print("🔍 [Step 1] Parsing QR code...")
|
|
72
|
+
parse_result = api.make_parsed_request(
|
|
73
|
+
self.endpoints['parse_qr'],
|
|
74
|
+
{'rawQr': raw_qr},
|
|
75
|
+
C2cParseQrResponse,
|
|
76
|
+
use_body=True
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if not parse_result['success']:
|
|
80
|
+
error_status = parse_result.get('status', 'ERROR')
|
|
81
|
+
error_msg = parse_result.get('message', 'Parse QR failed')
|
|
82
|
+
error_hint = parse_result.get('hint', '')
|
|
83
|
+
|
|
84
|
+
print(f"❌ {error_msg}")
|
|
85
|
+
if error_hint:
|
|
86
|
+
print(f"💡 {error_hint}")
|
|
87
|
+
|
|
88
|
+
set_order_status(OrderStatus.FAILED, error_message=error_msg, error_code=parse_result.get('code'))
|
|
89
|
+
print(json.dumps({
|
|
90
|
+
'status': error_status,
|
|
91
|
+
'code': parse_result.get('code'),
|
|
92
|
+
'message': error_msg,
|
|
93
|
+
'hint': error_hint
|
|
94
|
+
}))
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
order_info = parse_result['order_info']
|
|
98
|
+
|
|
99
|
+
# Save order info to state
|
|
100
|
+
set_order_status(OrderStatus.QR_PARSED,
|
|
101
|
+
checkout_id=order_info.checkout_id,
|
|
102
|
+
biz_type=order_info.biz_type,
|
|
103
|
+
nickname=order_info.nickname,
|
|
104
|
+
avatar_url=order_info.avatar_url,
|
|
105
|
+
currency=order_info.currency or 'USDT',
|
|
106
|
+
currency_fixed=order_info.currency_fixed,
|
|
107
|
+
has_preset_amount=order_info.has_preset_amount,
|
|
108
|
+
preset_amount=str(order_info.amount) if order_info.amount else None,
|
|
109
|
+
description=order_info.description,
|
|
110
|
+
single_transaction_limit=str(order_info.single_transaction_limit) if order_info.single_transaction_limit else None,
|
|
111
|
+
daily_limit=str(order_info.daily_limit) if order_info.daily_limit else None
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
print(f"✅ QR Parsed Successfully")
|
|
115
|
+
print(f" 📝 Checkout ID: {order_info.checkout_id}")
|
|
116
|
+
print(f" 🏪 Payee: {order_info.nickname}")
|
|
117
|
+
print(f" 💱 Currency: {order_info.currency or 'Not specified'}")
|
|
118
|
+
if order_info.single_transaction_limit:
|
|
119
|
+
print(f" 📊 Single Limit: {order_info.single_transaction_limit} USD")
|
|
120
|
+
if order_info.daily_limit:
|
|
121
|
+
print(f" 📊 Daily Limit: {order_info.daily_limit} USD")
|
|
122
|
+
print()
|
|
123
|
+
|
|
124
|
+
# Output result based on preset amount
|
|
125
|
+
if order_info.has_preset_amount and order_info.amount:
|
|
126
|
+
currency = order_info.currency or 'USDT'
|
|
127
|
+
print("════════════════════════════════════════════════════")
|
|
128
|
+
print(f"💰 Preset Amount: {order_info.amount} {currency}")
|
|
129
|
+
print("════════════════════════════════════════════════════")
|
|
130
|
+
print()
|
|
131
|
+
print("💡 Reply 'y' to confirm payment, 'n' to cancel")
|
|
132
|
+
update_state({
|
|
133
|
+
'suggested_amount': float(order_info.amount),
|
|
134
|
+
'needs_amount_input': False,
|
|
135
|
+
'order_status': OrderStatus.AMOUNT_SET.value
|
|
136
|
+
})
|
|
137
|
+
print(json.dumps({
|
|
138
|
+
'status': 'AWAITING_CONFIRMATION',
|
|
139
|
+
'checkout_id': order_info.checkout_id,
|
|
140
|
+
'biz_type': order_info.biz_type,
|
|
141
|
+
'payment_type': PAYMENT_TYPE_C2C,
|
|
142
|
+
'payee': order_info.nickname,
|
|
143
|
+
'amount': str(order_info.amount),
|
|
144
|
+
'currency': currency,
|
|
145
|
+
'has_preset_amount': True,
|
|
146
|
+
'single_transaction_limit': str(order_info.single_transaction_limit) if order_info.single_transaction_limit else None,
|
|
147
|
+
'daily_limit': str(order_info.daily_limit) if order_info.daily_limit else None
|
|
148
|
+
}))
|
|
149
|
+
else:
|
|
150
|
+
currency = order_info.currency or 'USDT'
|
|
151
|
+
print("════════════════════════════════════════════════════")
|
|
152
|
+
print("📝 No preset amount")
|
|
153
|
+
print("════════════════════════════════════════════════════")
|
|
154
|
+
print()
|
|
155
|
+
print(f"💡 Please enter the amount (e.g., '100' or '100 USDT')")
|
|
156
|
+
update_state({
|
|
157
|
+
'needs_amount_input': True,
|
|
158
|
+
'order_status': OrderStatus.AWAITING_AMOUNT.value
|
|
159
|
+
})
|
|
160
|
+
print(json.dumps({
|
|
161
|
+
'status': 'AWAITING_AMOUNT',
|
|
162
|
+
'checkout_id': order_info.checkout_id,
|
|
163
|
+
'biz_type': order_info.biz_type,
|
|
164
|
+
'payment_type': PAYMENT_TYPE_C2C,
|
|
165
|
+
'payee': order_info.nickname,
|
|
166
|
+
'currency': currency,
|
|
167
|
+
'has_preset_amount': False,
|
|
168
|
+
'single_transaction_limit': str(order_info.single_transaction_limit) if order_info.single_transaction_limit else None,
|
|
169
|
+
'daily_limit': str(order_info.daily_limit) if order_info.daily_limit else None
|
|
170
|
+
}))
|
|
171
|
+
|
|
172
|
+
def build_confirm_params(self, state: Dict[str, Any], amount: str, currency: str) -> Dict[str, Any]:
|
|
173
|
+
"""Build C2C confirmPayment params (includes bizType)."""
|
|
174
|
+
return {
|
|
175
|
+
'checkoutId': state.get('checkout_id', ''),
|
|
176
|
+
'bizType': state.get('biz_type', 'C2C_QR_CODE'),
|
|
177
|
+
'currency': currency,
|
|
178
|
+
'amount': float(amount),
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
def get_confirm_endpoint(self) -> str:
|
|
182
|
+
return self.endpoints['confirm_payment']
|
|
183
|
+
|
|
184
|
+
def get_poll_endpoint(self) -> str:
|
|
185
|
+
return self.endpoints['query_payment_status']
|
|
186
|
+
|
|
187
|
+
def build_poll_params(self, state: Dict[str, Any]) -> Dict[str, Any]:
|
|
188
|
+
"""C2C poll includes bizType."""
|
|
189
|
+
params = {'payOrderId': state.get('pay_order_id', '')}
|
|
190
|
+
biz_type = state.get('biz_type')
|
|
191
|
+
if biz_type:
|
|
192
|
+
params['bizType'] = biz_type
|
|
193
|
+
return params
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PIX Payment Extension.
|
|
3
|
+
|
|
4
|
+
Handles Brazilian PIX EMV QR code payments (BR Code / Copia e Cola).
|
|
5
|
+
"""
|
|
6
|
+
import json
|
|
7
|
+
from typing import Dict, Any
|
|
8
|
+
|
|
9
|
+
from .base import PaymentExtension
|
|
10
|
+
|
|
11
|
+
# Payment type constant
|
|
12
|
+
PAYMENT_TYPE_PIX = 'PIX'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# ============================================================
|
|
16
|
+
# Data Models
|
|
17
|
+
# ============================================================
|
|
18
|
+
class PixParseQrResponse:
|
|
19
|
+
"""Response from Pix parseQr API"""
|
|
20
|
+
def __init__(self, data: Dict[str, Any]):
|
|
21
|
+
self.checkout_id = data.get('checkoutId', '')
|
|
22
|
+
self.status = data.get('status', '')
|
|
23
|
+
# Receiver info
|
|
24
|
+
self.receiver_name = data.get('receiverName', '')
|
|
25
|
+
self.receiver_psp = data.get('receiverPsp', '')
|
|
26
|
+
self.receiver_cnpj = data.get('receiverCnpj', '')
|
|
27
|
+
self.receiver_cpf = data.get('receiverCpf', '')
|
|
28
|
+
self.receiver_identifier = data.get('receiverIdentifier', '')
|
|
29
|
+
# Bill info
|
|
30
|
+
self.debtor_name = data.get('debtorName', '')
|
|
31
|
+
self.bill_due_date = data.get('billDueDate')
|
|
32
|
+
self.bill_amount = data.get('billAmount')
|
|
33
|
+
self.allow_amount_edit = data.get('allowAmountEdit', True)
|
|
34
|
+
# Limits (from Pix backend)
|
|
35
|
+
self.max_limit = data.get('maxLimit')
|
|
36
|
+
self.min_limit = data.get('minLimit')
|
|
37
|
+
self.limit_type = data.get('limitType', '')
|
|
38
|
+
self.limit_period_type = data.get('limitPeriodType', '')
|
|
39
|
+
# Limits (from Skills config)
|
|
40
|
+
self.single_transaction_limit = data.get('singleTransactionLimit')
|
|
41
|
+
self.daily_limit = data.get('dailyLimit')
|
|
42
|
+
# Additional info
|
|
43
|
+
self.additional_infos = data.get('additionalInfos', [])
|
|
44
|
+
self.allow_note_add = data.get('allowNoteAdd', False)
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def has_preset_amount(self) -> bool:
|
|
48
|
+
"""Check if this QR has a preset amount (non-editable bill)"""
|
|
49
|
+
return (self.bill_amount is not None
|
|
50
|
+
and float(self.bill_amount) > 0
|
|
51
|
+
and not self.allow_amount_edit)
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def display_name(self) -> str:
|
|
55
|
+
"""Get display name for the receiver"""
|
|
56
|
+
return self.receiver_name or self.debtor_name or 'Unknown'
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def display_document(self) -> str:
|
|
60
|
+
"""Get masked document for display"""
|
|
61
|
+
if self.receiver_cnpj:
|
|
62
|
+
return f"CNPJ: {self.receiver_cnpj}"
|
|
63
|
+
if self.receiver_cpf:
|
|
64
|
+
return f"CPF: {self.receiver_cpf}"
|
|
65
|
+
return ''
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class PixConfirmPaymentResponse:
|
|
69
|
+
"""Response from Pix confirmPayment API"""
|
|
70
|
+
def __init__(self, data: Dict[str, Any]):
|
|
71
|
+
self.pay_order_id = data.get('payOrderId', '')
|
|
72
|
+
self.status = data.get('status', '')
|
|
73
|
+
self.usd_amount = data.get('usdAmount')
|
|
74
|
+
self.daily_used_before = data.get('dailyUsedBefore')
|
|
75
|
+
self.daily_used_after = data.get('dailyUsedAfter')
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# ============================================================
|
|
79
|
+
# PIX EMV QR Code Parser (local preview)
|
|
80
|
+
# ============================================================
|
|
81
|
+
def parse_pix_emv_qr(qr_string: str) -> Dict[str, Any]:
|
|
82
|
+
"""
|
|
83
|
+
Parse PIX EMV QR code (TLV format) for local preview display.
|
|
84
|
+
|
|
85
|
+
This extracts merchant info from the QR data before calling the API.
|
|
86
|
+
The API response is authoritative; this is just for quick preview.
|
|
87
|
+
|
|
88
|
+
EMVCo TLV format: Tag(2) + Length(2) + Value(Length)
|
|
89
|
+
|
|
90
|
+
Key tags:
|
|
91
|
+
- 53: Transaction Currency (986=BRL)
|
|
92
|
+
- 54: Transaction Amount
|
|
93
|
+
- 58: Country Code
|
|
94
|
+
- 59: Merchant Name
|
|
95
|
+
- 60: Merchant City
|
|
96
|
+
- 26: Merchant Account Info (contains sub-TLV with br.gov.bcb.pix)
|
|
97
|
+
"""
|
|
98
|
+
result = {
|
|
99
|
+
'currency': 'BRL', # default for PIX
|
|
100
|
+
'country': 'BR',
|
|
101
|
+
}
|
|
102
|
+
try:
|
|
103
|
+
i = 0
|
|
104
|
+
while i + 4 <= len(qr_string):
|
|
105
|
+
tag = qr_string[i:i + 2]
|
|
106
|
+
length = int(qr_string[i + 2:i + 4])
|
|
107
|
+
if i + 4 + length > len(qr_string):
|
|
108
|
+
break
|
|
109
|
+
value = qr_string[i + 4:i + 4 + length]
|
|
110
|
+
i += 4 + length
|
|
111
|
+
|
|
112
|
+
if tag == '59':
|
|
113
|
+
result['merchant_name'] = value
|
|
114
|
+
elif tag == '60':
|
|
115
|
+
result['merchant_city'] = value
|
|
116
|
+
elif tag == '53':
|
|
117
|
+
result['currency_code'] = value
|
|
118
|
+
if value == '986':
|
|
119
|
+
result['currency'] = 'BRL'
|
|
120
|
+
elif tag == '54':
|
|
121
|
+
try:
|
|
122
|
+
result['amount'] = float(value)
|
|
123
|
+
except ValueError:
|
|
124
|
+
result['amount_raw'] = value
|
|
125
|
+
elif tag == '58':
|
|
126
|
+
result['country'] = value
|
|
127
|
+
except Exception:
|
|
128
|
+
pass
|
|
129
|
+
return result
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# ============================================================
|
|
133
|
+
# PIX Extension
|
|
134
|
+
# ============================================================
|
|
135
|
+
class PixExtension(PaymentExtension):
|
|
136
|
+
"""PIX EMV QR Code payment extension."""
|
|
137
|
+
|
|
138
|
+
payment_type = PAYMENT_TYPE_PIX
|
|
139
|
+
|
|
140
|
+
endpoints = {
|
|
141
|
+
'parse_qr': '/binancepay/openapi/user/pix/parseQr',
|
|
142
|
+
'confirm_payment': '/binancepay/openapi/user/pix/confirmPayment',
|
|
143
|
+
'query_payment_status': '/binancepay/openapi/user/pix/queryPaymentStatus',
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
def detect(self, raw_qr: str) -> bool:
|
|
147
|
+
"""PIX EMV QR codes contain 'br.gov.bcb.pix' as GUI identifier."""
|
|
148
|
+
if not raw_qr:
|
|
149
|
+
return False
|
|
150
|
+
return 'br.gov.bcb.pix' in raw_qr.lower()
|
|
151
|
+
|
|
152
|
+
def purchase(self, api, raw_qr: str, state_helpers: Dict[str, Any]):
|
|
153
|
+
"""PIX purchase flow - Step 1: Parse PIX QR code"""
|
|
154
|
+
set_order_status = state_helpers['set_order_status']
|
|
155
|
+
update_state = state_helpers['update_state']
|
|
156
|
+
OrderStatus = state_helpers['OrderStatus']
|
|
157
|
+
|
|
158
|
+
# Show local preview from EMV data (before API call)
|
|
159
|
+
preview = parse_pix_emv_qr(raw_qr)
|
|
160
|
+
if preview.get('merchant_name') or preview.get('amount'):
|
|
161
|
+
print("📋 QR Preview (local decode):")
|
|
162
|
+
if preview.get('merchant_name'):
|
|
163
|
+
print(f" 🏪 Merchant: {preview.get('merchant_name', '')}", end='')
|
|
164
|
+
if preview.get('merchant_city'):
|
|
165
|
+
print(f" ({preview['merchant_city']})", end='')
|
|
166
|
+
print()
|
|
167
|
+
if preview.get('amount'):
|
|
168
|
+
print(f" 💰 Amount: {preview['amount']} {preview.get('currency', 'BRL')}")
|
|
169
|
+
print()
|
|
170
|
+
|
|
171
|
+
# Call PIX parseQr API
|
|
172
|
+
print("🔍 [Step 1] Parsing PIX QR code...")
|
|
173
|
+
parse_result = api.make_parsed_request(
|
|
174
|
+
self.endpoints['parse_qr'],
|
|
175
|
+
{'rawQr': raw_qr},
|
|
176
|
+
PixParseQrResponse,
|
|
177
|
+
use_body=True
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
if not parse_result['success']:
|
|
181
|
+
error_status = parse_result.get('status', 'ERROR')
|
|
182
|
+
error_msg = parse_result.get('message', 'Parse QR failed')
|
|
183
|
+
error_hint = parse_result.get('hint', '')
|
|
184
|
+
|
|
185
|
+
print(f"❌ {error_msg}")
|
|
186
|
+
if error_hint:
|
|
187
|
+
print(f"💡 {error_hint}")
|
|
188
|
+
|
|
189
|
+
set_order_status(OrderStatus.FAILED, error_message=error_msg, error_code=parse_result.get('code'))
|
|
190
|
+
print(json.dumps({
|
|
191
|
+
'status': error_status,
|
|
192
|
+
'code': parse_result.get('code'),
|
|
193
|
+
'message': error_msg,
|
|
194
|
+
'hint': error_hint
|
|
195
|
+
}))
|
|
196
|
+
return
|
|
197
|
+
|
|
198
|
+
order_info = parse_result['order_info']
|
|
199
|
+
|
|
200
|
+
# Determine currency (PIX is typically BRL)
|
|
201
|
+
currency = 'BRL'
|
|
202
|
+
|
|
203
|
+
# Determine amount
|
|
204
|
+
amount = order_info.bill_amount
|
|
205
|
+
pix_has_amount = amount is not None and float(amount) > 0
|
|
206
|
+
pix_amount_locked = pix_has_amount # True = amount from QR, cannot be changed
|
|
207
|
+
|
|
208
|
+
# Save order info to state
|
|
209
|
+
set_order_status(OrderStatus.QR_PARSED,
|
|
210
|
+
checkout_id=order_info.checkout_id,
|
|
211
|
+
biz_type='PIX',
|
|
212
|
+
nickname=order_info.display_name,
|
|
213
|
+
receiver_psp=order_info.receiver_psp,
|
|
214
|
+
receiver_document=order_info.display_document,
|
|
215
|
+
currency=currency,
|
|
216
|
+
currency_fixed=True, # PIX is always BRL
|
|
217
|
+
has_preset_amount=pix_has_amount,
|
|
218
|
+
preset_amount=str(amount) if amount else None,
|
|
219
|
+
allow_amount_edit=not pix_amount_locked,
|
|
220
|
+
pix_amount_locked=pix_amount_locked,
|
|
221
|
+
single_transaction_limit=str(order_info.single_transaction_limit) if order_info.single_transaction_limit else None,
|
|
222
|
+
daily_limit=str(order_info.daily_limit) if order_info.daily_limit else None,
|
|
223
|
+
pix_max_limit=str(order_info.max_limit) if order_info.max_limit else None,
|
|
224
|
+
pix_min_limit=str(order_info.min_limit) if order_info.min_limit else None,
|
|
225
|
+
additional_infos=order_info.additional_infos,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
print(f"✅ PIX QR Parsed Successfully")
|
|
229
|
+
print(f" 📝 Checkout ID: {order_info.checkout_id}")
|
|
230
|
+
print(f" 🏪 Receiver: {order_info.display_name}")
|
|
231
|
+
if order_info.receiver_psp:
|
|
232
|
+
print(f" 🏦 Bank: {order_info.receiver_psp}")
|
|
233
|
+
if order_info.display_document:
|
|
234
|
+
print(f" 📄 {order_info.display_document}")
|
|
235
|
+
print(f" 💱 Currency: {currency}")
|
|
236
|
+
if order_info.single_transaction_limit:
|
|
237
|
+
print(f" 📊 Single Limit: {order_info.single_transaction_limit} USD")
|
|
238
|
+
if order_info.daily_limit:
|
|
239
|
+
print(f" 📊 Daily Limit: {order_info.daily_limit} USD")
|
|
240
|
+
if order_info.additional_infos:
|
|
241
|
+
print(f" 📎 Additional Info:")
|
|
242
|
+
for info in order_info.additional_infos:
|
|
243
|
+
print(f" {info.get('key', '')}: {info.get('value', '')}")
|
|
244
|
+
print()
|
|
245
|
+
|
|
246
|
+
# Output result based on whether QR has amount
|
|
247
|
+
if pix_has_amount:
|
|
248
|
+
print("════════════════════════════════════════════════════")
|
|
249
|
+
print(f"💰 Amount: {amount} {currency} (from QR, cannot be modified)")
|
|
250
|
+
print("════════════════════════════════════════════════════")
|
|
251
|
+
print()
|
|
252
|
+
print("💡 Reply 'y' to confirm payment, 'n' to cancel")
|
|
253
|
+
update_state({
|
|
254
|
+
'suggested_amount': float(amount),
|
|
255
|
+
'needs_amount_input': False,
|
|
256
|
+
'order_status': OrderStatus.AMOUNT_SET.value
|
|
257
|
+
})
|
|
258
|
+
print(json.dumps({
|
|
259
|
+
'status': 'AWAITING_CONFIRMATION',
|
|
260
|
+
'checkout_id': order_info.checkout_id,
|
|
261
|
+
'biz_type': 'PIX',
|
|
262
|
+
'payment_type': PAYMENT_TYPE_PIX,
|
|
263
|
+
'payee': order_info.display_name,
|
|
264
|
+
'receiver_psp': order_info.receiver_psp,
|
|
265
|
+
'amount': str(amount),
|
|
266
|
+
'currency': currency,
|
|
267
|
+
'has_preset_amount': True,
|
|
268
|
+
'pix_amount_locked': True,
|
|
269
|
+
'single_transaction_limit': str(order_info.single_transaction_limit) if order_info.single_transaction_limit else None,
|
|
270
|
+
'daily_limit': str(order_info.daily_limit) if order_info.daily_limit else None
|
|
271
|
+
}))
|
|
272
|
+
else:
|
|
273
|
+
print("════════════════════════════════════════════════════")
|
|
274
|
+
print("📝 No preset amount")
|
|
275
|
+
print("════════════════════════════════════════════════════")
|
|
276
|
+
print()
|
|
277
|
+
min_hint = f" (min: {order_info.min_limit})" if order_info.min_limit else ""
|
|
278
|
+
max_hint = f" (max: {order_info.max_limit})" if order_info.max_limit else ""
|
|
279
|
+
print(f"💡 Please enter the amount in {currency}{min_hint}{max_hint}")
|
|
280
|
+
update_state({
|
|
281
|
+
'needs_amount_input': True,
|
|
282
|
+
'order_status': OrderStatus.AWAITING_AMOUNT.value
|
|
283
|
+
})
|
|
284
|
+
print(json.dumps({
|
|
285
|
+
'status': 'AWAITING_AMOUNT',
|
|
286
|
+
'checkout_id': order_info.checkout_id,
|
|
287
|
+
'biz_type': 'PIX',
|
|
288
|
+
'payment_type': PAYMENT_TYPE_PIX,
|
|
289
|
+
'payee': order_info.display_name,
|
|
290
|
+
'receiver_psp': order_info.receiver_psp,
|
|
291
|
+
'currency': currency,
|
|
292
|
+
'has_preset_amount': False,
|
|
293
|
+
'pix_amount_locked': False,
|
|
294
|
+
'pix_min_limit': str(order_info.min_limit) if order_info.min_limit else None,
|
|
295
|
+
'pix_max_limit': str(order_info.max_limit) if order_info.max_limit else None,
|
|
296
|
+
'single_transaction_limit': str(order_info.single_transaction_limit) if order_info.single_transaction_limit else None,
|
|
297
|
+
'daily_limit': str(order_info.daily_limit) if order_info.daily_limit else None
|
|
298
|
+
}))
|
|
299
|
+
|
|
300
|
+
def build_confirm_params(self, state: Dict[str, Any], amount: str, currency: str) -> Dict[str, Any]:
|
|
301
|
+
"""Build PIX confirmPayment params (no bizType needed)."""
|
|
302
|
+
return {
|
|
303
|
+
'checkoutId': state.get('checkout_id', ''),
|
|
304
|
+
'currency': currency,
|
|
305
|
+
'amount': float(amount),
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
def get_confirm_endpoint(self) -> str:
|
|
309
|
+
return self.endpoints['confirm_payment']
|
|
310
|
+
|
|
311
|
+
def get_poll_endpoint(self) -> str:
|
|
312
|
+
return self.endpoints['query_payment_status']
|
|
313
|
+
|
|
314
|
+
def build_poll_params(self, state: Dict[str, Any]) -> Dict[str, Any]:
|
|
315
|
+
"""PIX poll does NOT include bizType."""
|
|
316
|
+
return {'payOrderId': state.get('pay_order_id', '')}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Square Post Skill
|
|
2
|
+
|
|
3
|
+
Publish text, image, article, and video posts to Binance Square through the
|
|
4
|
+
local Node.js scripts in `scripts/`.
|
|
5
|
+
|
|
6
|
+
## Dependencies
|
|
7
|
+
|
|
8
|
+
### Runtime
|
|
9
|
+
|
|
10
|
+
- Node.js 18 or newer. The scripts use native ES modules and the built-in
|
|
11
|
+
`fetch` API.
|
|
12
|
+
- Bash-capable shell for running the scripts through the agent tool.
|
|
13
|
+
|
|
14
|
+
### System Tools
|
|
15
|
+
|
|
16
|
+
- `ffmpeg` is required for video posts. `scripts/post-video.mjs` extracts the
|
|
17
|
+
first frame from the source video and uploads it as the post cover.
|
|
18
|
+
- `ffprobe` is required when the user does not provide a video duration. The
|
|
19
|
+
agent uses it to determine the duration before calling `post-video.mjs`.
|
|
20
|
+
|
|
21
|
+
### External Services
|
|
22
|
+
|
|
23
|
+
- Binance Square OpenAPI access through `BINANCE_SQUARE_OPENAPI_KEY` or the
|
|
24
|
+
local saved key file.
|
|
25
|
+
- Network access to the Square OpenAPI endpoints and to presigned upload URLs
|
|
26
|
+
returned by the API.
|
|
27
|
+
|
|
28
|
+
No npm package install is required for the current scripts; they only use
|
|
29
|
+
Node.js built-in modules.
|
|
30
|
+
|
|
31
|
+
## Authentication
|
|
32
|
+
|
|
33
|
+
Scripts read the OpenAPI key in this order:
|
|
34
|
+
|
|
35
|
+
1. `BINANCE_SQUARE_OPENAPI_KEY`
|
|
36
|
+
2. The saved key file at `~/.config/binance-square/openapi-key`
|
|
37
|
+
|
|
38
|
+
Do not pass API keys as CLI arguments. `--key` is rejected because command-line
|
|
39
|
+
arguments can appear in process listings and shell history.
|
|
40
|
+
|
|
41
|
+
To save a key for future runs, explicitly run:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
BINANCE_SQUARE_OPENAPI_KEY=<apiKey> node scripts/save-key.mjs
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The saved key file is written with `0600` permissions. To remove it, delete
|
|
48
|
+
`~/.config/binance-square/openapi-key`.
|
|
49
|
+
|
|
50
|
+
## Directory Structure
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
square-post/
|
|
54
|
+
├── SKILL.md # Skill instructions and publishing workflow
|
|
55
|
+
├── README.md # Directory overview
|
|
56
|
+
├── scripts/
|
|
57
|
+
│ ├── lib.mjs # Shared API, upload, polling, and publish helpers
|
|
58
|
+
│ ├── save-key.mjs # Saves the OpenAPI key to a local private config file
|
|
59
|
+
│ ├── post-text.mjs # Text and article publishing script
|
|
60
|
+
│ ├── post-image.mjs # Image post and article-with-cover publishing script
|
|
61
|
+
│ └── post-video.mjs # Video publishing script with generated cover
|
|
62
|
+
```
|