diff --git a/README.md b/README.md index 31b2726..149bc97 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ language, description, and tags to help you find what you need quickly. | [typescript/bnbchain-mcp](./typescript/bnbchain-mcp) | TypeScript | AI-powered blockchain assistant using Claude | AI, BSC, MCP | | [typescript/eliza-chatbot](./typescript/eliza-chatbot) | TypeScript | A chatbot example using Eliza plugin-bnb | AI, BSC, opBNB | | [typescript/ai-trading-assistant](./typescript/ai-trading-assistant) | Typescript | AI-powered trading assistant for BNB Chain ecosystem with real USDT→BNB swaps via PancakeSwap, technical analysis, and natural language interface | BNBChain, trading, analysis, PancakeSwap, AI, MCP | +| [typescript/quick-test-swap](./typescript/quick-test-swap) | Typescript | A simple example demonstrating a token swap on BNB Chain. | BSC, opBNB, swap, DeFi, BNBChain | More examples are coming soon—stay tuned for updates! ## How to Add a New Example diff --git a/package-lock.json b/package-lock.json index 2a50e8e..e42e78a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,9 @@ "@types/node": "^22.13.14", "husky": "^9.1.7", "semantic-release": "^24.2.3", - "tsx": "^4.19.3" + "ts-node": "^10.9.2", + "tsx": "^4.19.3", + "typescript": "^5.9.2" } }, "node_modules/@babel/code-frame": { @@ -311,6 +313,19 @@ "node": ">=v18" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.6", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.6.tgz", @@ -753,6 +768,34 @@ "node": ">=18" } }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1523,6 +1566,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/conventional-commits-parser": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.1.tgz", @@ -1550,6 +1621,32 @@ "dev": true, "license": "MIT" }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -1643,6 +1740,13 @@ "dev": true, "license": "MIT" }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2046,6 +2150,13 @@ "typescript": ">=5" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2131,6 +2242,16 @@ "node": ">=4.0.0" } }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -3472,6 +3593,13 @@ "dev": true, "license": "ISC" }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, "node_modules/marked": { "version": "15.0.12", "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", @@ -7773,6 +7901,50 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tsx": { "version": "4.20.3", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.3.tgz", @@ -7807,12 +7979,11 @@ } }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7915,6 +8086,13 @@ "dev": true, "license": "MIT" }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -8016,6 +8194,16 @@ "node": ">=12" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", diff --git a/package.json b/package.json index 9f98b9e..17b3985 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,11 @@ "@commitlint/cli": "^19.8.0", "@commitlint/config-conventional": "^19.8.0", "@semantic-release/git": "^10.0.1", + "@types/node": "^22.13.14", "husky": "^9.1.7", "semantic-release": "^24.2.3", - "@types/node": "^22.13.14", - "tsx": "^4.19.3" + "ts-node": "^10.9.2", + "tsx": "^4.19.3", + "typescript": "^5.9.2" } } diff --git a/typescript/quick-test-swap/README.md b/typescript/quick-test-swap/README.md new file mode 100644 index 0000000..c1cc412 --- /dev/null +++ b/typescript/quick-test-swap/README.md @@ -0,0 +1,73 @@ +# Quick Test Swap Example + +A minimal end-to-end example that demonstrates a token swap on BNB Chain Testnet using the PancakeSwap V2 Router. + +This project shows both: +A local AMM simulation (constant-product x * y = k math). +A real on-chain swap on BNB Smart Chain Testnet (tBNB → CAKE). + +# Features +Local AMM (automated market maker) math demo. +Quoting expected swap output with slippage check. +Executing a real swap on BSC Testnet. +Written in TypeScript, with clean formatting via Prettier. + +# Getting Started + +1. Clone & install dependencies +git clone https://github.com//quick-swap.git +cd quick-swap/typescript/quick-test-swap +npm install + +2. Set up environment variables +Create a .env file in this folder: +RPC_URL=https://data-seed-prebsc-1-s1.binance.org:8545 +PRIVATE_KEY= +⚠️ Never commit real keys. Use only Testnet accounts. + +# Running Examples +Local AMM Simulation +Runs a simple math-only swap with no chain connection: +npx ts-node index.ts --chain local --amount 1 --slippage 0.5 + +Testnet Quote (Dry Run) +Fetches expected output on PancakeSwap V2 Router (BSC Testnet): +npm run quote:testnet + +Testnet Swap (Send Transaction) +Executes a real swap (tBNB → CAKE) on testnet: +npm run swap:testnet -- --execute true + +📖 Example Output +Local simulation + +➡️ Local AMM simulation (no on-chain tx) +In: 1 tBNB +Out: 298.95 CAKE +Impact: 0.19% +✅ Local simulation complete. + +Testnet swap +➡️ Testnet mode (PancakeSwap V2 Router on BSC Testnet) +Amount In: 0.01 tBNB +Quoted Out: 13.3103970 CAKE +Slippage: 0.5% +amountOutMin: 13.2483450 CAKE +⏳ Sending swap... +Tx submitted: 0x +✅ Mined in block 65714391 + +# Project Scripts +Available scripts in package.json: +npm run dev → run local AMM demo. +npm run quote:testnet → get a swap quote (dry run). +npm run swap:testnet -- --execute true → execute a real swap. + +# Notes +Uses Prettier for consistent formatting. +Requires Node.js ≥ 18 and npm ≥ 9. +Meant for learning/demo purposes only. +Runs only on BSC Testnet with test tokens (tBNB, CAKE). + +# License +MIT – free to use and modify for educational purposes. \ No newline at end of file diff --git a/typescript/quick-test-swap/amm.ts b/typescript/quick-test-swap/amm.ts new file mode 100644 index 0000000..7ea9603 --- /dev/null +++ b/typescript/quick-test-swap/amm.ts @@ -0,0 +1,78 @@ +// amm.ts +// Minimal constant-product AMM (x * y = k) with slippage protection. + +export type Token = "tBNB" | "BUSD"; + +export interface Pool { + token0: Token; // e.g., tBNB + token1: Token; // e.g., BUSD + reserve0: number; // reserves for token0 + reserve1: number; // reserves for token1 + feeBps: number; // fee in basis points (e.g., 25 = 0.25%) +} + +export interface SwapResult { + amountIn: number; + amountOut: number; + priceImpactPct: number; + newReserve0: number; + newReserve1: number; +} + +export function assertPositive(n: number, msg: string) { + if (!(Number.isFinite(n) && n > 0)) throw new Error(msg); +} + +/** + * Simulate swap tokenIn -> tokenOut using x*y=k with fee. + * Returns the output amount, price impact, and new reserves (post-swap). + */ +export function swap(pool: Pool, tokenIn: Token, amountIn: number): SwapResult { + assertPositive(amountIn, "amountIn must be > 0"); + + const is0In = tokenIn === pool.token0; + let x = pool.reserve0; + let y = pool.reserve1; + + // Apply fee on input + const fee = pool.feeBps / 10_000; // 25 bps -> 0.0025 + const amountInAfterFee = amountIn * (1 - fee); + + // Invariant: (x + dx) * (y - dy) = k => dy = (y * dx) / (x + dx) + const dx = is0In ? amountInAfterFee : 0; + const dy = is0In ? (y * dx) / (x + dx) : 0; + + const dx1 = !is0In ? amountInAfterFee : 0; + const dy1 = !is0In ? (x * dx1) / (y + dx1) : 0; + + const amountOut = is0In ? dy : dy1; + + // Update reserves after swap + const newX = is0In ? x + amountInAfterFee : x - amountOut; + const newY = is0In ? y - amountOut : y + amountInAfterFee; + + // Price impact approximation: compare pre/post mid-price + const priceBefore = y / x; // token1 per token0 + const priceAfter = newY / newX; + const priceImpactPct = + Math.abs((priceAfter - priceBefore) / priceBefore) * 100; + + return { + amountIn, + amountOut, + priceImpactPct, + newReserve0: newX, + newReserve1: newY, + }; +} + +/** + * Helper to enforce minimum received (slippage check). + */ +export function enforceMinOut(actual: number, minOut: number) { + if (actual < minOut) { + throw new Error( + `Slippage too high: received ${actual.toFixed(6)} < minOut ${minOut.toFixed(6)}`, + ); + } +} diff --git a/typescript/quick-test-swap/index.ts b/typescript/quick-test-swap/index.ts new file mode 100644 index 0000000..e35efb5 --- /dev/null +++ b/typescript/quick-test-swap/index.ts @@ -0,0 +1,24 @@ +// index.ts +// Entrypoint for Quick Test Swap Example +import { quoteBNBToCAKE, swapBNBToCAKE } from "./onchain"; +import yargs from "yargs"; +import { hideBin } from "yargs/helpers"; + +const argv = yargs(hideBin(process.argv)) + .option("amount", { type: "string", demandOption: true }) + .option("slippage", { type: "number", default: 0.5 }) + .option("execute", { type: "boolean", default: false }) + .parseSync(); + +async function main() { + console.log("➡️ Testnet mode (PancakeSwap V2 Router on BSC Testnet)"); + + if (!argv.execute) { + await quoteBNBToCAKE(argv.amount, argv.slippage); + console.log("ℹ️ Dry run only. Add --execute true to send the transaction."); + } else { + await swapBNBToCAKE(argv.amount, argv.slippage); + } +} + +main(); diff --git a/typescript/quick-test-swap/onchain-abi.ts b/typescript/quick-test-swap/onchain-abi.ts new file mode 100644 index 0000000..4ab9df1 --- /dev/null +++ b/typescript/quick-test-swap/onchain-abi.ts @@ -0,0 +1,31 @@ +// onchain-abi.ts +// PancakeSwap V2 Router ABI (only the functions we need) + +export const routerAbi = [ + { + inputs: [ + { internalType: "uint256", name: "amountOutMin", type: "uint256" }, + { internalType: "address[]", name: "path", type: "address[]" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "deadline", type: "uint256" }, + ], + name: "swapExactETHForTokens", + outputs: [ + { internalType: "uint256[]", name: "amounts", type: "uint256[]" }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "amountIn", type: "uint256" }, + { internalType: "address[]", name: "path", type: "address[]" }, + ], + name: "getAmountsOut", + outputs: [ + { internalType: "uint256[]", name: "amounts", type: "uint256[]" }, + ], + stateMutability: "view", + type: "function", + }, +]; diff --git a/typescript/quick-test-swap/onchain-addresses.ts b/typescript/quick-test-swap/onchain-addresses.ts new file mode 100644 index 0000000..2161c7f --- /dev/null +++ b/typescript/quick-test-swap/onchain-addresses.ts @@ -0,0 +1,8 @@ +// onchain-addresses.ts +// Testnet addresses for PancakeSwap Router and tokens + +export const addresses = { + router: "0x9Ac64Cc6e4415144C455BD8E4837Fea55603e5c3", // PancakeSwap V2 Router (BSC Testnet) + WBNB: "0xae13d989dac2f0debff460ac112a837c89baa7cd", // WBNB (Wrapped tBNB on testnet) + CAKE: "0xFa60D973F7642B748046464e165A65B7323b0DEE", // CAKE token (testnet) +}; diff --git a/typescript/quick-test-swap/onchain.ts b/typescript/quick-test-swap/onchain.ts new file mode 100644 index 0000000..7adb7a0 --- /dev/null +++ b/typescript/quick-test-swap/onchain.ts @@ -0,0 +1,48 @@ +// onchain.ts +import { ethers } from "ethers"; +import { addresses } from "./onchain-addresses"; +import { routerAbi } from "./onchain-abi"; +import * as dotenv from "dotenv"; +dotenv.config(); + +const provider = new ethers.JsonRpcProvider( + "https://data-seed-prebsc-1-s1.binance.org:8545", +); +const wallet = new ethers.Wallet(process.env.PRIVATE_KEY as string, provider); +const router = new ethers.Contract(addresses.router, routerAbi, wallet); + +// Quote function +export async function quoteBNBToCAKE(amountIn: string, slippage: number) { + const path = [addresses.WBNB, addresses.CAKE]; + const amounts = await router.getAmountsOut(ethers.parseEther(amountIn), path); + const quotedOut = Number(ethers.formatEther(amounts[1])); + const minOut = quotedOut * (1 - slippage / 100); + + console.log("=== Quote ==="); + console.log("Amount In:", amountIn, "tBNB"); + console.log("Quoted Out:", quotedOut, "CAKE"); + console.log("Slippage:", slippage, "%"); + console.log("amountOutMin:", minOut, "CAKE"); + + return ethers.parseEther(minOut.toString()); +} + +// Swap function +export async function swapBNBToCAKE(amountIn: string, slippage: number) { + const path = [addresses.WBNB, addresses.CAKE]; + const to = await wallet.getAddress(); + const deadline = Math.floor(Date.now() / 1000) + 60 * 10; // 10 minutes + + console.log("⏳ Sending swap..."); + const tx = await router.swapExactETHForTokens( + await quoteBNBToCAKE(amountIn, slippage), + path, + to, + deadline, + { value: ethers.parseEther(amountIn), gasLimit: 300000 }, + ); + + console.log("Tx submitted:", tx.hash); + const receipt = await tx.wait(); + console.log("✅ Mined in block", receipt.blockNumber); +} diff --git a/typescript/quick-test-swap/package-lock.json b/typescript/quick-test-swap/package-lock.json new file mode 100644 index 0000000..a991c6b --- /dev/null +++ b/typescript/quick-test-swap/package-lock.json @@ -0,0 +1,170 @@ +{ + "name": "quick-test-swap", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "dotenv": "^17.2.2", + "ethers": "^6.15.0" + }, + "devDependencies": { + "@types/node": "^24.5.1", + "@types/yargs": "^17.0.33" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", + "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", + "license": "MIT" + }, + "node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@types/node": { + "version": "24.5.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.1.tgz", + "integrity": "sha512-/SQdmUP2xa+1rdx7VwB9yPq8PaKej8TD5cQ+XfKDPWWC+VDJU4rvVVagXqKUzhKjtFoNA8rXDJAkCxQPAe00+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.12.0" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/aes-js": { + "version": "4.0.0-beta.5", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", + "license": "MIT" + }, + "node_modules/dotenv": { + "version": "17.2.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz", + "integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/ethers": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.15.0.tgz", + "integrity": "sha512-Kf/3ZW54L4UT0pZtsY/rf+EkBU7Qi5nnhonjUb8yTXcxH3cdcWrV2cRyk0Xk/4jK6OoHhxxZHriyhje20If2hQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/ethers-io/" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "1.10.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", + "@types/node": "22.7.5", + "aes-js": "4.0.0-beta.5", + "tslib": "2.7.0", + "ws": "8.17.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ethers/node_modules/@types/node": { + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/ethers/node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "license": "0BSD" + }, + "node_modules/undici-types": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", + "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/typescript/quick-test-swap/package.json b/typescript/quick-test-swap/package.json new file mode 100644 index 0000000..38fba10 --- /dev/null +++ b/typescript/quick-test-swap/package.json @@ -0,0 +1,17 @@ +{ + "scripts": { + "build": "tsc", + "start": "node dist/index.js", + "dev": "ts-node index.ts", + "quote:testnet": "ts-node index.ts --amount 0.01 --slippage 0.5", + "swap:testnet": "ts-node index.ts --amount 0.01 --slippage 0.5 --execute true" + }, + "devDependencies": { + "@types/node": "^24.5.1", + "@types/yargs": "^17.0.33" + }, + "dependencies": { + "dotenv": "^17.2.2", + "ethers": "^6.15.0" + } +} diff --git a/typescript/quick-test-swap/tsconfig.json b/typescript/quick-test-swap/tsconfig.json new file mode 100644 index 0000000..dcf6774 --- /dev/null +++ b/typescript/quick-test-swap/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "moduleResolution": "node", + "outDir": "dist", + "rootDir": ".", + "strict": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "verbatimModuleSyntax": false + }, + "include": ["*.ts"] +} diff --git a/web/list.json b/web/list.json index fba6400..1a60391 100644 --- a/web/list.json +++ b/web/list.json @@ -160,5 +160,22 @@ "guide": "https://github.com/bnb-chain/example-hub/tree/main/typescript/4everland-hosting-mcp", "otherLink": "", "imgUrl": "https://cms-static.bnbchain.org/dcms/static/303d0c6a-af8f-4098-a2d0-a5b96ef964ba.png" + }, + { + "caseTitle": "Quick Test Swap Example", + "caseDesc": "A simple example demonstrating a token swap on BNB Chain.", + "tags": [ + "BSC", + "opBNB", + "swap", + "DeFi", + "BNBChain" + ], + "github": "https://github.com/bnb-chain/example-hub/tree/main/typescript/quick-test-swap", + "replit": "", + "video": {}, + "guide": "", + "otherLink": "https://www.bnbchain.org/en/solutions", + "imgUrl": "https://cms-static.bnbchain.org/dcms/static/303d0c6a-af8f-4098-a2d0-a5b96ef964ba.png" } ]