diff --git a/python/pancake-swap-example/README.md b/python/pancake-swap-example/README.md new file mode 100644 index 0000000..d2f6d26 --- /dev/null +++ b/python/pancake-swap-example/README.md @@ -0,0 +1,232 @@ +# đŸŒŦī¸ PancakeSwap Testnet Python + Web3 Swap Example + +A complete starter project that lets you swap **tBNB to any token**, and **BEP-20 to BEP-20 tokens** on the **BNB Testnet**, using either a **web UI (TrustWallet)** or a **Python-based API with mnemonic/private key support**. + +--- + +## 📅 Table of Contents + +1. [Introduction](#introduction) +2. [Project Structure](#project-structure) +3. [Flow Overview](#flow-overview) +4. [Setup Guide](#setup-guide) +5. [Frontend Swap Usage](#frontend-swap-ui) +6. [Swagger API Usage](#api-swap-usage-via-swagger) +7. [Getting a Testnet Wallet & Address](#getting-testnet-wallet--address) +8. [Getting a Router Contract](#getting-testnet-swap-router-contract) +9. [Getting Testnet tBNB Tokens](#getting-tbnb-tokens) +10. [Web Swap Walkthrough (Trust Wallet)](#web-swap-trust-wallet) +11. [API Walkthrough: tBNB ➔ USDC](#api-walkthrough-swap-tbnb-to-usdc) +12. [API Walkthrough: USDC ➔ tCAKE](#api-walkthrough-swap-usdc-to-tcake) +13. [Note on Wallet Loading](#note-wallet-auth-mnemonic-vs-private-key) + +--- + +## ✨ Introduction +This repo demonstrates: + +- Swapping tokens on the **BNB Smart Chain (Testnet)** +- Using `web3.py` and `ethers.js` +- **Trust Wallet** frontend (tBNB ➔ Token) +- **Swagger-based API** (tBNB ➔ Token and Token ➔ Token) +- Secure wallet management via `.env` (mnemonic or private key) + +--- + +## 🌎 Flow Overview (Mermaid Diagram) + +```mermaid +graph TD; + A[User] -->|TrustWallet| B(HTML + ethers.js) + A -->|API Call| C(Flask + Swagger) + B --> D{"Router Contract (Testnet)"} + C --> D + D -->|Sends Txn| E[BNB Testnet Chain] + E --> F[BscScan Explorer] +``` + +--- + +## đŸ—‚ī¸ Project Structure + +```bash +. +├── app.py # Flask app + Swagger docs +├── swapper.py # Core backend swap logic +├── wallet_utils.py # Load wallet from mnemonic/private key +├── templates/ +│ └── index.html # Simple swap UI with TrustWallet +├── .env.example # Sample env vars +├── requirements.txt # Python deps +``` + +--- + +## âš™ī¸ Setup Guide + +```bash +# 1. Clone and install +pip install -r requirements.txt + +# 2. Copy and fill .env +cp .env.example .env +# Set either MNEMONIC= or PRIVATE_KEY= + +# 3. Run the app +python app.py + +# 4. Open: +http://localhost:5000 # Web UI +http://localhost:5000/apidocs # Swagger API +``` + +--- + +## 📉 Frontend Swap UI + +- Navigate to `http://localhost:5000` +- Connect Trust Wallet / TrustWallet +- Select a token to buy (e.g. tCAKE) +- Enter amount in **tBNB** +- Click **Swap** + +All transactions go through: +``` +Router: 0x9ac64cc6e4415144c455bd8e4837fea55603e5c3 (Testnet) +``` +- The *router* is a smart contract that acts as the main entry point for interacting with PancakeSwap (see it as a Pancake API Gateway). +- The router used here is the *testnet* Pancake Router, if you are deploying to production environment, remember to update it with the mainnet router address. +--- + +## 📆 API Swap Usage (via Swagger) + +Visit `http://localhost:5000/apidocs` to test: + +### `/swap` (tBNB ➔ Token) +```json +POST /swap +{ + "token_out": "0x...", // the token address of the token you want to receive + "amount_bnb": 0.01, + "slippage": 1, + "router_address": "0x9ac64cc6e4415144c455bd8e4837fea55603e5c3" +} +``` +- Pools are designed to pair WBNB ➔ Token, thus behind the scenes, we have to convert your tBNB to WBNB before swapping to your desired token. + +### `/token-swap` (Token ➔ Token) +```json +POST /token-swap +{ + "token_in": "0x...", // the token address of the token you want to swap + "token_out": "0x...", // the token address of the token you want to receive + "amount_in": 0.1, + "slippage": 1, + "router_address": "0x9ac64cc6e4415144c455bd8e4837fea55603e5c3" +} +``` +- Pools are designed to pair WBNB ➔ Token, thus behind the scenes, we have to convert your *token_in* to WBNB before swapping to your desired *token_out*. This is unless you are swapping to WBNB, the function will directly swap to WBNB. + +--- + +## 🚀 Getting Testnet Wallet & Address +1. Visit https://chainlist.org/?testnets=true&search=bsc +2. Add **BNB Smart Chain Testnet** to TrustWallet +3. Create a wallet and copy your address + +--- + +## 🏠 Getting a Testnet Swap Contract (Router) +You can use the PancakeSwap router on testnet: +``` +0x9ac64cc6e4415144c455bd8e4837fea55603e5c3 +``` +- https://developer.pancakeswap.finance/contracts/universal-router/addresses + +- This is PancakeSwap v2 router on BSC Testnet + +--- + +## â›Ŋī¸ Getting tBNB Tokens +1. Go to a BNB faucet: + - https://testnet.bnbchain.org/faucet-smart + - OR via Discord (https://discord.com/channels/789402563035660308/1121255685146034309) +2. Paste your testnet wallet address +3. Request a small amount of tBNB + +--- + +## đŸ•šī¸ Web Swap Walkthrough (Trust Wallet) + +1. Open the this app (http://localhost:5000) in your Trust Wallet browser +2. Connect your wallet (TrustWallet compatible) +3. Choose your token (e.g. tCAKE) +4. Enter amount of tBNB to swap +5. Click **Swap** +6. Confirm in wallet & view tx on BscScan + +--- + +## âžĄī¸ API Walkthrough: Swap tBNB ➔ USDC + +```json +POST /swap +{ + "token_out": "0x64544969ed7ebf5f083679233325356ebe738930", // USDC + "amount_bnb": 0.05, + "slippage": 1, + "router_address": "0x9ac64cc6e4415144c455bd8e4837fea55603e5c3" +} +``` + +> âš ī¸ The API automatically wraps BNB into WBNB for the swap path. + +--- + +## 🔄 API Walkthrough: Swap USDC ➔ tCAKE + +```json +POST /token-swap +{ + "token_in": "0x64544969ed7ebf5f083679233325356ebe738930", // USDC + "token_out": "0xFa60D973F7642B748046464e165A65B7323b0DEE", // tCAKE + "amount_in": 1.0, + "slippage": 1, + "router_address": "0x9ac64cc6e4415144c455bd8e4837fea55603e5c3" +} +``` + +> ✅ Automatically routes through WBNB if direct liquidity is missing. + +--- + +## 🔐 Note: Wallet Auth (Mnemonic vs Private Key) + +You can authenticate the backend wallet in 2 ways: + +### Option 1: Mnemonic Passphrase +```env +MNEMONIC="twelve word seed phrase here" +``` + +### Option 2: 256-bit Private Key +```env +PRIVATE_KEY="your_private_key_here" +``` + +Update `wallet_utils.py` to select one. + +--- + +## ✨ Credits +- Powered by [Web3.py](https://web3py.readthedocs.io) +- Router ABI: PancakeSwap v2 +- Frontend: ethers.js + TrustWallet / Trust Wallet +- Backend: Flask + Swagger + Python + +--- + +## â¤ī¸ Contributing +PRs welcome! Want to improve the slippage calc? Add token approval UI? Jump in! + +--- diff --git a/python/pancake-swap-example/app.py b/python/pancake-swap-example/app.py new file mode 100644 index 0000000..4888fa4 --- /dev/null +++ b/python/pancake-swap-example/app.py @@ -0,0 +1,201 @@ +""" +=============================================================================== + app.py - BNB Smart Chain Testnet DEX API Server +=============================================================================== + +Author: Lucas Liao +Description: + This Flask application provides a lightweight backend for performing + token swaps on the BNB Smart Chain Testnet using PancakeSwap-compatible + routers. It exposes a frontend (index.html) for MetaMask-based swaps, + and two API endpoints for programmatic token swap operations. + + - Supports swapping tBNB → token + - Supports swapping BEP-20 → BEP-20 (with auto WBNB routing) + - Provides live Swagger documentation at /apidocs + +Endpoints: + 1. GET / - Renders the frontend (MetaMask-enabled swap UI) + 2. POST /swap - Performs a swap from tBNB to another token + 3. POST /token-swap - Swaps one BEP-20 token for another + +Swagger Docs: + Accessible at http://localhost:5000/apidocs + +Environment: + Requires a `.env` file with: + - MNEMONIC or PRIVATE_KEY + - RPC_URL pointing to BNB Testnet + +Dependencies: + Flask, flasgger, web3, eth-account, python-dotenv, mnemonic + +=============================================================================== +""" + +from flask import Flask, jsonify, request, render_template +from flasgger import Swagger +from swapper import perform_swap, perform_token_to_token_swap + +# Initialize Flask app +app = Flask(__name__) + +# Initialize Swagger UI (accessible at /apidocs) +swagger = Swagger(app) + + +@app.route("/") +def index(): + # Render the frontend (MetaMask-based index.html) + return render_template("index.html") + + +@app.route("/swap", methods=["POST"]) +def swap(): + """ + Swap tBNB for selected token using a PancakeSwap-compatible router on BNB Testnet + --- + summary: Swap tBNB for a token + consumes: + - application/json + parameters: + - in: body + name: body + required: true + schema: + type: object + properties: + token_out: + type: string + description: Address of the token to buy + example: "0xFa60D973F7642B748046464e165A65B7323b0DEE" + amount_bnb: + type: number + description: Amount of tBNB to swap + example: 0.01 + slippage: + type: number + description: Slippage percentage + example: 1 + router_address: + type: string + description: PancakeSwap-compatible router contract + example: "0x9ac64cc6e4415144c455bd8e4837fea55603e5c3" + responses: + 200: + description: Swap success + schema: + type: object + properties: + status: + type: string + tx_hash: + type: string + bscscan_url: + type: string + 400: + description: Missing input + 500: + description: Swap error + """ + data = request.json + token_out = data.get("token_out") + amount_bnb = float(data.get("amount_bnb", 0.01)) + slippage = float(data.get("slippage", 1)) + router_address = data.get("router_address") + + if not router_address: + return ( + jsonify({"status": "error", "message": "router_address is required"}), + 400, + ) + + try: + # Call backend swapper logic + tx_hash = perform_swap(token_out, amount_bnb, slippage, router_address) + bscscan_url = f"https://testnet.bscscan.com/tx/{tx_hash}" + return jsonify( + {"status": "success", "tx_hash": tx_hash, "bscscan_url": bscscan_url} + ) + except Exception as e: + return jsonify({"status": "error", "message": str(e)}), 500 + + +@app.route("/token-swap", methods=["POST"]) +def token_swap(): + """ + Swap one BEP-20 token for another using a custom router on BNB Testnet + --- + summary: Swap BEP20 → BEP20 tokens + consumes: + - application/json + parameters: + - in: body + name: body + required: true + schema: + type: object + properties: + token_in: + type: string + description: Address of input BEP-20 token + example: "0x..." + token_out: + type: string + description: Address of output BEP-20 token + example: "0x..." + amount_in: + type: number + description: Amount of input token to swap + example: 1.5 + slippage: + type: number + description: Slippage percentage + example: 1 + router_address: + type: string + description: Router contract address + example: "0x9ac64cc6e4415144c455bd8e4837fea55603e5c3" + responses: + 200: + description: Token-to-token swap success + schema: + type: object + properties: + status: + type: string + tx_hash: + type: string + bscscan_url: + type: string + 400: + description: Missing input + 500: + description: Swap error + """ + data = request.json + token_in = data.get("token_in") + token_out = data.get("token_out") + amount_in = float(data.get("amount_in", 0)) + slippage = float(data.get("slippage", 1)) + router_address = data.get("router_address") + + if not all([token_in, token_out, amount_in, router_address]): + return jsonify({"status": "error", "message": "Missing required fields"}), 400 + + try: + # Call backend swapper logic + tx_hash = perform_token_to_token_swap( + token_in, token_out, amount_in, slippage, router_address + ) + bscscan_url = f"https://testnet.bscscan.com/tx/{tx_hash}" + return jsonify( + {"status": "success", "tx_hash": tx_hash, "bscscan_url": bscscan_url} + ) + except Exception as e: + return jsonify({"status": "error", "message": str(e)}), 500 + + +# Run Flask app +if __name__ == "__main__": + app.run(debug=True) diff --git a/python/pancake-swap-example/env.example b/python/pancake-swap-example/env.example new file mode 100644 index 0000000..09fc62b --- /dev/null +++ b/python/pancake-swap-example/env.example @@ -0,0 +1,2 @@ +MNEMONIC="your twelve word mnemonic seed phrase" +RPC_URL="https://bsc-dataseed.binance.org/" \ No newline at end of file diff --git a/python/pancake-swap-example/requirements.txt b/python/pancake-swap-example/requirements.txt new file mode 100644 index 0000000..4c20bf4 --- /dev/null +++ b/python/pancake-swap-example/requirements.txt @@ -0,0 +1,6 @@ +Flask==3.1.0 +web3==7.9.0 +eth-account==0.13.5 +mnemonic==0.21 +python-dotenv==1.0.1 +flasgger==0.9.7.1 diff --git a/python/pancake-swap-example/swapper.py b/python/pancake-swap-example/swapper.py new file mode 100644 index 0000000..321b3b3 --- /dev/null +++ b/python/pancake-swap-example/swapper.py @@ -0,0 +1,227 @@ +""" +swapper.py - BNB Smart Chain Testnet Token Swapper + +This module contains two core functions: +1. perform_swap: Swaps tBNB (native testnet BNB) to any BEP-20 token. +2. perform_token_to_token_swap: Swaps any BEP-20 token to another BEP-20 token. + +It uses Web3.py to construct, sign, and send transactions to a PancakeSwap-compatible +router contract. Wallet credentials are securely loaded via mnemonic or private key +using the wallet_utils module. + +The router path includes WBNB when needed, because: +- PancakeSwap liquidity pools are mostly paired with WBNB. +- Direct pairs between arbitrary tokens are often not available. +- Router contracts require WBNB when wrapping/unwrapping native BNB (tBNB). + +Author: Lucas Liao +""" + +import os +import time +import json +import logging +from web3 import Web3 +from dotenv import load_dotenv +from wallet_utils import get_wallet # Secure wallet access via mnemonic or key + +# Load environment variables +load_dotenv() + +# Configure logger +logging.basicConfig(level=logging.INFO) + +# Connect to BNB Smart Chain Testnet +RPC_URL = os.getenv("RPC_URL") +web3 = Web3(Web3.HTTPProvider(RPC_URL)) + +# WBNB testnet address (used for swap paths) +WBNB = Web3.to_checksum_address("0xae13d989dac2f0debff460ac112a837c89baa7cd") + + +def perform_swap(token_out, amount_bnb, slippage_percent, router_address): + """ + Swaps native tBNB to a specified BEP-20 token using swapExactETHForTokens. + + Args: + token_out (str): Token to buy (BEP-20 address) + amount_bnb (float): Amount of tBNB to send + slippage_percent (float): Not implemented yet (amountOutMin = 0) + router_address (str): PancakeSwap-compatible router + """ + wallet = get_wallet() + + router = web3.eth.contract( + address=Web3.to_checksum_address(router_address), + abi=json.loads( + """[ + { + "name": "swapExactETHForTokens", + "type": "function", + "stateMutability": "payable", + "inputs": [ + {"name": "amountOutMin", "type": "uint256"}, + {"name": "path", "type": "address[]"}, + {"name": "to", "type": "address"}, + {"name": "deadline", "type": "uint256"} + ], + "outputs": [{"name": "amounts", "type": "uint256[]"}] + } + ]""" + ), + ) + + logging.info( + f"Swapping {amount_bnb} tBNB → {token_out} via router {router_address}" + ) + + amount_in_wei = web3.to_wei(amount_bnb, "ether") + deadline = int(time.time()) + 600 # 10 minutes from now + + # BNB must be converted to WBNB for routing inside smart contracts + path = [WBNB, Web3.to_checksum_address(token_out)] + + txn = router.functions.swapExactETHForTokens( + 0, # TODO: implement slippage-based minOut + path, + wallet.address, + deadline, + ).build_transaction( + { + "from": wallet.address, + "value": amount_in_wei, + "gas": 250000, + "gasPrice": web3.to_wei("5", "gwei"), + "nonce": web3.eth.get_transaction_count(wallet.address), + } + ) + + signed_txn = web3.eth.account.sign_transaction(txn, private_key=wallet.key) + tx_hash = web3.eth.send_raw_transaction( + getattr(signed_txn, "rawTransaction", getattr(signed_txn, "raw_transaction")) + ) + + tx_hex = web3.to_hex(tx_hash) + logging.info(f"✅ Swap submitted: https://testnet.bscscan.com/tx/{tx_hex}") + return tx_hex + + +def perform_token_to_token_swap( + token_in, token_out, amount_in, slippage_percent, router_address +): + """ + Swaps token_in → token_out using swapExactTokensForTokens. + + Notes: + - Will insert WBNB into the path automatically if neither token is WBNB. + - Requires token_in to be approved for router first. + + Args: + token_in (str): Input token (BEP-20) + token_out (str): Output token (BEP-20) + amount_in (float): Amount of token_in to swap + slippage_percent (float): Not yet implemented (minOut = 0) + router_address (str): PancakeSwap-compatible router + """ + wallet = get_wallet() + + token_in = Web3.to_checksum_address(token_in) + token_out = Web3.to_checksum_address(token_out) + router_address = Web3.to_checksum_address(router_address) + + # Router contract setup + router = web3.eth.contract( + address=router_address, + abi=json.loads( + """[ + { + "name": "swapExactTokensForTokens", + "type": "function", + "inputs": [ + {"name": "amountIn", "type": "uint256"}, + {"name": "amountOutMin", "type": "uint256"}, + {"name": "path", "type": "address[]"}, + {"name": "to", "type": "address"}, + {"name": "deadline", "type": "uint256"} + ], + "outputs": [{"name": "amounts", "type": "uint256[]"}], + "stateMutability": "nonpayable", + "type": "function" + } + ]""" + ), + ) + + # ERC20 ABI for approval + erc20_abi = json.loads( + """[ + { + "constant": false, + "inputs": [ + {"name": "spender", "type": "address"}, + {"name": "amount", "type": "uint256"} + ], + "name": "approve", + "outputs": [{"name": "", "type": "bool"}], + "type": "function" + } + ]""" + ) + + token_contract = web3.eth.contract(address=token_in, abi=erc20_abi) + amount_in_wei = web3.to_wei(amount_in, "ether") + deadline = int(time.time()) + 600 + + # Route via WBNB if direct pair doesn't exist (common case) + if token_in != WBNB and token_out != WBNB: + path = [token_in, WBNB, token_out] + else: + path = [token_in, token_out] + + # Step 1: Approve router to spend tokens + nonce = web3.eth.get_transaction_count(wallet.address) + approval_txn = token_contract.functions.approve( + router_address, amount_in_wei + ).build_transaction( + { + "from": wallet.address, + "gas": 100000, + "gasPrice": web3.to_wei("5", "gwei"), + "nonce": nonce, + } + ) + + signed_approval = web3.eth.account.sign_transaction( + approval_txn, private_key=wallet.key + ) + raw_approval = getattr( + signed_approval, "rawTransaction", getattr(signed_approval, "raw_transaction") + ) + web3.eth.send_raw_transaction(raw_approval) + logging.info("✅ Approved router to spend token_in") + + # Step 2: Execute token swap + swap_txn = router.functions.swapExactTokensForTokens( + amount_in_wei, + 0, # TODO: add slippage-based minOut + path, + wallet.address, + deadline, + ).build_transaction( + { + "from": wallet.address, + "gas": 300000, + "gasPrice": web3.to_wei("5", "gwei"), + "nonce": nonce + 1, + } + ) + + signed_swap = web3.eth.account.sign_transaction(swap_txn, private_key=wallet.key) + raw_swap = getattr( + signed_swap, "rawTransaction", getattr(signed_swap, "raw_transaction") + ) + + tx_hash = web3.eth.send_raw_transaction(raw_swap) + tx_hex = web3.to_hex(tx_hash) + logging.info(f"✅ Token swap submitted: https://testnet.bscscan.com/tx/{tx_hex}") + return tx_hex diff --git a/python/pancake-swap-example/templates/index.html b/python/pancake-swap-example/templates/index.html new file mode 100644 index 0000000..76990cd --- /dev/null +++ b/python/pancake-swap-example/templates/index.html @@ -0,0 +1,136 @@ + + + + + + + + BNB Swap + + + + + + + + + + + + +

BNB to Token Swap

+ (Make sure your TrustWallet is connected to the testnet environment. All contract addresses in this code are + testnet addresses.
+ + + +
+ + + +
+ + + +
+ + + + +

+

+ + + \ No newline at end of file diff --git a/python/pancake-swap-example/wallet_utils.py b/python/pancake-swap-example/wallet_utils.py new file mode 100644 index 0000000..cc920b8 --- /dev/null +++ b/python/pancake-swap-example/wallet_utils.py @@ -0,0 +1,22 @@ +from eth_account import Account +import os +from dotenv import load_dotenv + +load_dotenv() +Account.enable_unaudited_hdwallet_features() + + +# For Mnemonic Passphrase +def get_wallet(): + mnemonic = os.getenv("MNEMONIC") + account = Account.from_mnemonic(mnemonic, account_path="m/44'/60'/0'/0/0") + return account + + +# For Private Key String (Private key is a 256-bit (32 bytes) random integer) +# def get_wallet(): +# private_key = os.getenv("PRIVATE_KEY") +# if not private_key: +# raise ValueError("PRIVATE_KEY not found in environment.") +# account = Account.from_key(private_key) +# return account diff --git a/web/list.json b/web/list.json index 8c1e5ea..33b2124 100644 --- a/web/list.json +++ b/web/list.json @@ -2,7 +2,11 @@ { "caseTitle": "BNB Chain Eliza AgentKit", "caseDesc": "Create an AI Agent using the BNB Chain Plugin for Eliza AI", - "tags": ["BSC", "opBNB", "AI"], + "tags": [ + "BSC", + "opBNB", + "AI" + ], "github": "https://github.com/bnb-chain/example-hub/tree/main/typescript/eliza-chatbot", "replit": "https://replit.com/@BNBChainDev/BNBChain-Eliza-AgentKit-TypeScript?embed=true#README.md", "video": { @@ -16,7 +20,11 @@ { "caseTitle": "BNB Chain LangChain AgentKit", "caseDesc": "Create an AI Agent using the BNB Chain Plugin for LangChain AI", - "tags": ["BSC", "opBNB", "AI"], + "tags": [ + "BSC", + "opBNB", + "AI" + ], "github": "https://github.com/bnb-chain/example-hub/tree/main/python/langchain-chatbot", "replit": "https://replit.com/@BNBChainDev/BNBChain-LangChain-AgentKit-Python-Version?embed=true#chatbot.py", "video": { @@ -30,12 +38,36 @@ { "caseTitle": "EIP 7702 - Demo", "caseDesc": "This project demonstrates the implementation of EIP7702 (Account Abstraction via Authorized Operations) on BNB Smart Chain (BSC)", - "tags": ["BSC"], + "tags": [ + "BSC" + ], "github": "https://github.com/bnb-chain/example-hub/tree/main/go/eip7702-demo", "replit": "https://replit.com/@BNBChainDev/EIP-7702-Demo?embed=true", "video": {}, "guide": "", "otherLink": "https://www.bnbchain.org/en/blog/bnb-chain-announces-pascal-hard-fork", "imgUrl": "https://cms-static.bnbchain.org/dcms/static/303d0c6a-af8f-4098-a2d0-a5b96ef964ba.png" + }, + { + "caseTitle": "PancakeSwap Python Frontend + API Swap Example", + "caseDesc": "A complete example to swap tBNB to tokens or BEP-20 to BEP-20 using Flask API and a frontend Trust Wallet-enabled UI on the BNB Testnet.", + "tags": [ + "BSC", + "opBNB", + "Web3", + "Python", + "Flask", + "PancakeSwap", + "TrustWallet" + ], + "github": "https://github.com/bnb-chain/example-hub/tree/main/python/pancake-swap-example", + "replit": "https://replit.com/@0x11a0/pancake-swap-example", + "video": { + "type": "youtube", + "link": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" + }, + "guide": "https://github.com/bnb-chain/example-hub/blob/main/python/pancake-swap-example/README.md", + "otherLink": "", + "imgUrl": "https://dex-bin.bnbstatic.com/static/dapp-uploads/O6j-gZp80QCa524NkHfpf" } ]