Last active
March 17, 2025 05:54
-
-
Save claytantor/b00564ececfbaa4a91fc0bdfeee1b21d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "DAI_ADDRESS":"0xFF34B3d4Aee8ddCd6F9AFFFB6Fe49bD371b8a357", | |
| "POOKA_ADDRESS":"0xAb01f22e992D01f5453057dA2F73833221E57848", | |
| "POOKA_HOOK_ADDRESS":"0x6a5ab2649B3606452be27A06b541Bd2A09Dc8080", | |
| "UNISWAP_V4_POOL_MANAGER":"0xE03A1074c86CFeDd5C142C4F04F1a1536e203543", | |
| "UNISWAP_V4_SWAP_ROUTER":"0x3bFA4769FB09eefC5a80d6E87c3B9C650f7Ae48E", | |
| "UNIVERSAL_ROUTER_V2_ADDRESS":"0x3a9d48ab9751398bbfa63ad67599bb04e4bdf98b" | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import '../App.css'; | |
| import { ethers } from "ethers"; | |
| import React, { use, useEffect, useState } from 'react'; | |
| import { FaGasPump } from "react-icons/fa"; | |
| import token from '../assets/pooka_token.png'; | |
| import { useWallet } from "./WalletProvider"; | |
| import { Spinner } from "./Spinner"; | |
| const SwapFormEnabled = ({config}) => { | |
| const { account, provider, signer, connectWallet } = useWallet(); | |
| const [swapFrom, setSwapFrom] = useState(""); | |
| const [swapTo, setSwapTo] = useState(""); | |
| const [loading, setLoading] = useState(false); | |
| const [txHash, setTxHash] = useState(null); | |
| // const [poolManagerAddress, setPoolManagerAddress] = useState(""); | |
| // const [daiAddress] = useState(config.DAI_ADDRESS); | |
| // const [pookaAddress] = useState(config.POOKA_ADDRESS); | |
| useEffect(() => { | |
| console.log(`SwapFormEnabled config: ${JSON.stringify(config)}`); | |
| }, [config]); | |
| // Contract addresses | |
| const DAI_ADDRESS = config.DAI_ADDRESS; | |
| const POOKA_ADDRESS = config.POOKA_ADDRESS; | |
| const POOL_MANAGER_ADDRESS = config.UNISWAP_V4_POOL_MANAGER; | |
| const SWAP_ROUTER_ADDRESS = config.UNIVERSAL_ROUTER_V2_ADDRESS; | |
| const POOKA_HOOK_ADDRESS = config.POOKA_HOOK_ADDRESS; | |
| // V4 ABIs | |
| const POOL_MANAGER_ABI = [ | |
| "function swap(\ | |
| (address token0, address token1, uint24 fee, int24 tickSpacing, int24 hooks), \ | |
| (bool zeroForOne, int256 amountSpecified, uint160 sqrtPriceLimitX96), \ | |
| bytes calldata) external returns (int256 delta)" | |
| ]; | |
| const ERC20_ABI = [ | |
| "function approve(address spender, uint256 amount) public returns (bool)", | |
| "function balanceOf(address owner) view returns (uint256)", | |
| "function transfer(address to, uint256 amount) public returns (bool)", | |
| "function allowance(address owner, address spender) public view returns (uint256)" | |
| ]; | |
| // // 7. Execute router call | |
| // const routerAbi = ["function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline)"]; | |
| const ROUTER_ABI = [ | |
| "function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline) external" | |
| ]; | |
| const handleSwap = async () => { | |
| setLoading(true); | |
| try { | |
| // make the pool key | |
| const poolKey = { | |
| currency0: DAI_ADDRESS, | |
| currency1: POOKA_ADDRESS, | |
| fee: 3000, | |
| tickSpacing: 60, | |
| hooks: POOKA_HOOK_ADDRESS | |
| }; | |
| // execute swapExactInputSingle | |
| // {finalBalance, txReciept} | |
| const swapInfo = await swapExactInputSingle( | |
| poolKey, | |
| 10, | |
| 0, | |
| signer, | |
| SWAP_ROUTER_ADDRESS, | |
| Math.floor(Date.now() / 1000) + 60 * 10, | |
| POOKA_HOOK_ADDRESS | |
| ); | |
| console.log("Swap tx:", swapInfo.txReciept); | |
| setTxHash(swapInfo.txReciept.transactionHash); | |
| } catch (error) { | |
| console.error("Error swapping tokens:", error); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| const swapExactInputSingle = async( | |
| key, // PoolKey object with currency0, currency1, fee, hooks, tickSpacing | |
| amountIn, // uint128 as string or BigNumber | |
| minAmountOut, // uint128 as string or BigNumber | |
| signer, // ethers.js Signer | |
| routerAddress, // Address of the Universal Router | |
| deadline, // Optional deadline in seconds | |
| hookDataAddress // Address of the hookData contract | |
| ) => { | |
| amountInWei = '10' //HARDCODE | |
| const amountInWei = ethers.utils.parseUnits(`${amountIn}`, 18); | |
| const minAmountOutWei = ethers.BigNumber.from(0); | |
| console.log(`amountInWei: ${amountInWei}, minAmountOut: ${minAmountOut}`); | |
| // 1. Approve router to spend input tokens | |
| const tokenIn = new ethers.Contract(key.currency0, ERC20_ABI, signer); | |
| const approvalTx = await tokenIn.approve(routerAddress, amountInWei); | |
| await approvalTx.wait(); | |
| // 2. Prepare commands and actions | |
| const COMMANDS = { | |
| V4_SWAP: 0x10 | |
| }; | |
| const ACTIONS = { | |
| SWAP_EXACT_IN_SINGLE: 0x06, | |
| SETTLE_ALL: 0x0c, | |
| TAKE_ALL: 0x0f | |
| }; | |
| // 3. Encode commands (single byte) | |
| const commands = ethers.utils.hexlify(new Uint8Array([COMMANDS.V4_SWAP])); | |
| // 4. Prepare actions (packed uint8 array) | |
| const actionsBytes = ethers.utils.hexlify( | |
| new Uint8Array([ | |
| ACTIONS.SWAP_EXACT_IN_SINGLE, | |
| ACTIONS.SETTLE_ALL, | |
| ACTIONS.TAKE_ALL | |
| ]) | |
| ); | |
| // 5. Prepare parameters | |
| // 5a. ExactInputSingleParams | |
| const exactInputParams = [ | |
| [ // PoolKey struct | |
| key.currency0, | |
| key.currency1, | |
| key.fee, | |
| key.hooks, | |
| key.tickSpacing | |
| ], | |
| true, // zeroForOne | |
| amountInWei, | |
| minAmountOutWei, | |
| 0, // sqrtPriceLimitX96 | |
| hookDataAddress // hookData | |
| ]; | |
| const param0 = ethers.utils.defaultAbiCoder.encode( | |
| [ | |
| "tuple(address,address,uint24,address,int24)", | |
| "bool", | |
| "uint128", | |
| "uint128", | |
| "uint160", | |
| "bytes" | |
| ], | |
| exactInputParams | |
| ); | |
| // 5b. Settlement parameters | |
| const param1 = ethers.utils.defaultAbiCoder.encode( | |
| ["address", "uint128"], | |
| [key.currency0, amountIn] | |
| ); | |
| // 5c. Take parameters | |
| const param2 = ethers.utils.defaultAbiCoder.encode( | |
| ["address", "uint128"], | |
| [key.currency1, minAmountOut] | |
| ); | |
| // 6. Prepare inputs array | |
| const inputs = ethers.utils.defaultAbiCoder.encode( | |
| ["bytes", "bytes[]"], | |
| [actionsBytes, [param0, param1, param2]] | |
| ); | |
| // // 7. Execute router call | |
| // const routerAbi = ["function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline)"]; | |
| const router = new ethers.Contract(routerAddress, ROUTER_ABI, signer); | |
| const tx = await router.execute( | |
| commands, | |
| [inputs], // Wrapped in array to match bytes[] type | |
| deadline || Math.floor(Date.now() / 1000) + 60 * 10, // Default 10 min deadline | |
| { gasLimit: 5000000 } | |
| ); | |
| const txReciept = await tx.wait(); | |
| console.log("Swap tx:", tx.hash); | |
| // 8. Verify output amount | |
| const tokenOut = new ethers.Contract(key.currency1, erc20Abi, signer); | |
| const finalBalance = await tokenOut.balanceOf(await signer.getAddress()); | |
| if (finalBalance.lt(minAmountOut)) { | |
| throw new Error("Insufficient output amount"); | |
| } | |
| return {finalBalance, txReciept}; | |
| } | |
| return( | |
| <div className="max-w-md mx-auto bg-white shadow-lg rounded-xl p-6"> | |
| {loading && | |
| <div className='flex justify-center items-center h-10'> | |
| <span className='mr-3'>Please use wallet...</span> | |
| <Spinner/> | |
| </div>} | |
| <div className="bg-gray-100 p-4 rounded-lg"> | |
| <div className="flex justify-between text-gray-600 text-sm"> | |
| <span>Sell</span> | |
| <span className="text-gray-500">DAI</span> | |
| </div> | |
| <div className="flex justify-between items-center mt-2"> | |
| <input | |
| type="number" | |
| placeholder="0" | |
| value={swapFrom} | |
| onChange={(e) => setSwapFrom(e.target.value)} | |
| className="w-full text-3xl font-bold bg-transparent outline-none" | |
| /> | |
| <img | |
| src="https://cryptologos.cc/logos/multi-collateral-dai-dai-logo.png" | |
| alt="DAI" | |
| className="w-8 h-8" | |
| /> | |
| </div> | |
| <div className="text-gray-500 text-sm mt-1">$120.01</div> | |
| </div> | |
| {/* Swap Arrow */} | |
| <div className="flex justify-center my-2"> | |
| <button className="bg-gray-200 p-2 rounded-full"> | |
| ⬇️ | |
| </button> | |
| </div> | |
| {/* Buy Section */} | |
| <div className="bg-gray-100 p-4 rounded-lg"> | |
| <div className="flex justify-between text-gray-600 text-sm"> | |
| <span>Buy</span> | |
| <span className="text-gray-500">POOKA</span> | |
| </div> | |
| <div className="flex justify-between items-center mt-2"> | |
| <input | |
| type="number" | |
| placeholder="0" | |
| value={swapTo} | |
| onChange={(e) => setSwapTo(e.target.value)} | |
| className="w-full text-3xl font-bold bg-transparent outline-none" | |
| /> | |
| <img | |
| src={token} | |
| alt="POOKA" | |
| className="w-8 h-8" | |
| /> | |
| </div> | |
| <div className="text-gray-500 text-sm mt-1">$119.99</div> | |
| </div> | |
| {/* Review Button */} | |
| <button className="w-full mt-4 bg-pink-500 hover:bg-pink-600 text-white py-3 rounded-lg text-lg font-semibold" onClick={handleSwap}> | |
| Review | |
| </button> | |
| {/* Exchange Rate & Fee Info */} | |
| <div className="mt-4 text-gray-500 text-sm text-center"> | |
| <p>1 DAI = 0.99967 POOKA ($1.00)</p> | |
| <p className="flex justify-center items-center gap-1"> | |
| <span><FaGasPump/></span> < $0.01 | |
| </p> | |
| </div> | |
| </div> | |
| ) | |
| }; | |
| const SwapFormDisabled = () => { | |
| return( | |
| <div className="max-w-md mx-auto bg-gray-200 shadow-md rounded-xl p-6 opacity-70 cursor-not-allowed"> | |
| {/* Sell Section */} | |
| <div className="bg-gray-300 p-4 rounded-lg"> | |
| <div className="flex justify-between text-gray-500 text-sm"> | |
| <span>Sell</span> | |
| <span className="text-gray-400">DAI</span> | |
| </div> | |
| <div className="flex justify-between items-center mt-2"> | |
| <input | |
| type="number" | |
| className="w-full text-3xl font-bold bg-transparent outline-none text-gray-500 cursor-not-allowed" | |
| defaultValue="120.01" | |
| disabled | |
| /> | |
| <img | |
| src="https://cryptologos.cc/logos/multi-collateral-dai-dai-logo.png" | |
| alt="DAI" | |
| className="w-8 h-8 opacity-50" | |
| /> | |
| </div> | |
| <div className="text-gray-400 text-sm mt-1">$120.01</div> | |
| </div> | |
| {/* Swap Arrow */} | |
| <div className="flex justify-center my-2"> | |
| <button className="bg-gray-400 p-2 rounded-full cursor-not-allowed" disabled> | |
| ⬇️ | |
| </button> | |
| </div> | |
| {/* Buy Section */} | |
| <div className="bg-gray-300 p-4 rounded-lg"> | |
| <div className="flex justify-between text-gray-500 text-sm"> | |
| <span>Buy</span> | |
| <span className="text-gray-400">POOKA</span> | |
| </div> | |
| <div className="flex justify-between items-center mt-2"> | |
| <input | |
| type="number" | |
| className="w-full text-3xl font-bold bg-transparent outline-none text-gray-500 cursor-not-allowed" | |
| defaultValue="120.04" | |
| disabled | |
| /> | |
| <img | |
| src={token} | |
| alt="POOKA" | |
| className="w-8 h-8" | |
| /> | |
| </div> | |
| <div className="text-gray-400 text-sm mt-1">$119.99</div> | |
| </div> | |
| {/* Review Button */} | |
| <button | |
| className="w-full mt-4 bg-gray-400 text-gray-600 py-3 rounded-lg text-lg font-semibold cursor-not-allowed" | |
| disabled | |
| > | |
| Review | |
| </button> | |
| {/* Exchange Rate & Fee Info */} | |
| <div className="mt-4 text-gray-500 text-sm text-center"> | |
| <p>1 DAI = 0.99967 POOKA ($1.00)</p> | |
| <p className="flex justify-center items-center gap-1"> | |
| <span><FaGasPump className="text-gray-500" /></span> < $0.01 | |
| </p> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| export const SwapForm = ({config}) => { | |
| const { connectWallet, account } = useWallet(); | |
| return ( | |
| // if account is connected, show enabled form else show disabled form | |
| <div className="max-w-4xl mx-auto text-center"> | |
| {(account && (config.DAI_ADDRESS && config.DAI_ADDRESS && config.DAI_ADDRESS)) ? | |
| <SwapFormEnabled config={config}/> : | |
| <SwapFormDisabled />} | |
| </div> | |
| ); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment