Last active
March 19, 2025 14:52
-
-
Save claytantor/c8d1c2e6ef879a295089bf117ea094b0 to your computer and use it in GitHub Desktop.
PookaSwaps for ReactJs and ethers.js 5.8.0
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
| const SwapFormEnabled = ({config, signer}) => { | |
| // Contract addresses | |
| const DAI_ADDRESS = config.DAI_ADDRESS; | |
| const POOKA_ADDRESS = config.POOKA_ADDRESS; | |
| const SWAP_ROUTER_ADDRESS = config.UNIVERSAL_ROUTER_V2_ADDRESS; | |
| const POOKA_HOOK_ADDRESS = config.POOKA_HOOK_ADDRESS; | |
| const STATE_VIEW_ADDRESS = config.STATE_VIEW_ADDRESS; | |
| const [swapFrom, setSwapFrom] = useState(""); | |
| const [swapTo, setSwapTo] = useState(""); | |
| const [loading, setLoading] = useState(false); | |
| const [txHash, setTxHash] = useState(null); | |
| const [slot, setSlot] = useState(0); | |
| const [allowApprove, setAllowApprove] = useState(false); | |
| const [poolKey, setPoolKey] = useState({ | |
| currency0: POOKA_ADDRESS, | |
| currency1: DAI_ADDRESS, | |
| fee: 3000, | |
| tickSpacing: 60, | |
| hooks: POOKA_HOOK_ADDRESS | |
| }); | |
| const [slot0, setSlot0] = useState(null); | |
| const [sqrtPriceX96, setSqrtPriceX96] = useState(0); | |
| const poolSlots = { | |
| POOKA:0, | |
| DAI:1 | |
| }; | |
| useEffect(() => { | |
| console.log(`SwapFormEnabled config: ${JSON.stringify(config)}`); | |
| // get the slot 0 balance | |
| getPoolId(poolKey).then((poolId) => { | |
| console.log("Pool ID:", poolId); | |
| getSlot0Data(poolId, STATE_VIEW_ADDRESS, signer).then((slot0) => { | |
| console.log("Slot 0:", slot0); | |
| setSlot0(slot0); | |
| setSqrtPriceX96(slot0.sqrtPriceX96); | |
| }); | |
| }); | |
| setSwapTo(""); | |
| setSwapFrom(""); | |
| setTxHash(null); | |
| setAllowApprove(false); | |
| }, [config, slot]); | |
| useEffect(() => { | |
| if (!slot0) return; | |
| if (swapFrom && swapFrom > 0) { | |
| setTxHash(null); | |
| if (slot == 0) { | |
| calculateAmount1FromSqrtX96( | |
| swapFrom, | |
| slot0.sqrtPriceX96, | |
| 18, | |
| 18 | |
| ).then((amount1) => { | |
| console.log("Amount 1:", amount1); | |
| let fixedAmount = Number(amount1 * 0.997).toFixed(4); | |
| setSwapTo(fixedAmount); | |
| }); | |
| } else { | |
| calculateAmount1FromSqrtX96( | |
| swapFrom, | |
| slot0.sqrtPriceX96, | |
| 18, | |
| 18 | |
| ).then((amount1) => { | |
| // calculate the inverse | |
| let ivSwap = (swapFrom/amount1); | |
| const inverse = swapFrom * ivSwap; | |
| let fixedAmount = Number(inverse* 0.997).toFixed(4); | |
| setSwapTo(fixedAmount); | |
| }); | |
| } | |
| setAllowApprove(true); | |
| } else { | |
| setAllowApprove(false); | |
| } | |
| }, [swapFrom]); | |
| // toggle swap slot 0 or 1 | |
| const toggleSwap = () => { | |
| setSlot(slot == 0 ? 1 : 0); | |
| }; | |
| const pookaForDai = async () => { | |
| console.log("pookaForDai"); | |
| setLoading(true); | |
| try { | |
| // 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, | |
| // inSlot, | |
| const swapInfo = await swapExactInputSingleSlot( | |
| poolKey, | |
| swapFrom, | |
| 0, | |
| signer, | |
| SWAP_ROUTER_ADDRESS, | |
| Math.floor(Date.now() / 1000) + 60 * 10, | |
| poolSlots.POOKA // slot 0 | |
| ); | |
| console.log("Swap tx:", swapInfo.txReciept); | |
| setTxHash(swapInfo.txReciept.transactionHash); | |
| setSwapTo(""); | |
| setSwapFrom(""); | |
| } catch (error) { | |
| console.error("Error swapping tokens:", error); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| const daiForPooka = async () => { | |
| console.log("daiForPooka"); | |
| setLoading(true); | |
| try { | |
| // make the pool key DAI -> POOKA | |
| // "POOKA_ADDRESS":"0xAb01f22e992D01f5453057dA2F73833221E57848", 0 | |
| // "DAI_ADDRESS":"0xFF34B3d4Aee8ddCd6F9AFFFB6Fe49bD371b8a357", 1 | |
| const swapInfo = await swapExactInputSingleSlot( | |
| poolKey, | |
| swapFrom, | |
| 0, | |
| signer, | |
| SWAP_ROUTER_ADDRESS, | |
| Math.floor(Date.now() / 1000) + 60 * 10, | |
| POOKA_HOOK_ADDRESS, | |
| poolSlots.DAI // slot 0 | |
| ); | |
| console.log("Swap tx:", swapInfo.txReciept); | |
| setTxHash(swapInfo.txReciept.transactionHash); | |
| setSwapTo(""); | |
| setSwapFrom(""); | |
| } catch (error) { | |
| console.error("Error swapping tokens:", error); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| const handleSwap = async () => { | |
| if (slot == 0) { | |
| pookaForDai(); | |
| } else { | |
| daiForPooka(); | |
| } | |
| }; | |
| 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>} | |
| {txHash && | |
| <div className='flex justify-center items-center h-10 bg-green-200 rounded p-2 mb-2'> | |
| <span className='mr-3'>Success!</span> | |
| <a href={`https://dashboard.tenderly.co/tx/${txHash}`} target="_blank" rel="noreferrer" className='text-green-800 underline'>View Transaction Details</a> | |
| </div>} | |
| <div> | |
| <div> | |
| {slot == 0 ? | |
| <PookaForDaiForm swapFrom={swapFrom} setSwapFrom={setSwapFrom} toggleSwap={toggleSwap} swapTo={swapTo}/> | |
| : <DaiForPookaForm swapFrom={swapFrom} setSwapFrom={setSwapFrom} toggleSwap={toggleSwap} swapTo={swapTo}/>} | |
| </div> | |
| {/* Review Button */} | |
| {allowApprove ? | |
| <button className="w-full mt-4 bg-green-500 hover:bg-green-600 text-white py-3 rounded-lg text-lg font-semibold" onClick={handleSwap}> | |
| Approve Swap | |
| </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> | |
| Approve Swap | |
| </button>} | |
| </div> | |
| </div> | |
| ) | |
| }; |
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
| export const swapExactInputSingleSlot = 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, | |
| inSlot, | |
| ) => { | |
| console.log(`swapExactInputSingleSlot: ${JSON.stringify(key)}`); | |
| const signerAddress = await signer.getAddress(); | |
| console.log(`signerAddress: ${signerAddress}`); | |
| // amountInWei = '10' //HARDCODE | |
| const amountInWei = ethers.utils.parseUnits(`${amountIn}`, 18); | |
| const amountOutMinimumWei = ethers.BigNumber.from(0); | |
| console.log(`amountInWei: ${amountInWei}, minAmountOut: ${minAmountOut}`); | |
| const zeroForOne = inSlot==0; | |
| console.log(`zeroForOne: ${zeroForOne} inSlot: ${inSlot} zf1: ${inSlot==0}`); | |
| // Determine token addresses | |
| const tokenInAddress = inSlot === 0 ? key.currency0 : key.currency1; | |
| const tokenOutAddress = inSlot === 0 ? key.currency1 : key.currency0; | |
| // determine contracts | |
| const tokenIn = new ethers.Contract(tokenInAddress, ERC20_ABI, signer); | |
| const tokenOut = new ethers.Contract(tokenOutAddress, ERC20_ABI, signer); | |
| // approve permit2 for tokenIn | |
| const permit2Address = "0x000000000022d473030f116ddee9f6b43ac78ba3"; | |
| const currentPermit2Allowance = await tokenIn.allowance( | |
| signerAddress, | |
| permit2Address | |
| ); | |
| console.log( | |
| `currentAllowance: ${currentPermit2Allowance} amountInWei: ${amountInWei}` | |
| ); | |
| if (currentPermit2Allowance.lt(amountInWei)) { | |
| const approvalTxPermit2 = await tokenIn.approve( | |
| permit2Address, | |
| ethers.constants.MaxUint256 | |
| ); | |
| await approvalTxPermit2.wait(); | |
| } | |
| // tell permit2 to approve router to spend tokenIn | |
| const permit2 = new ethers.Contract(permit2Address, PERMIT2_ABI, signer); | |
| // calculate the deadline | |
| const deadlinePermit2 = Math.floor(Date.now() / 1000) + 600 * 10; | |
| // function approve(address token, address spender, uint160 amount, uint48 expiration) external | |
| const approvalTxPermit2Router = await permit2.approve( | |
| tokenInAddress, | |
| routerAddress, | |
| amountInWei, | |
| deadlinePermit2 | |
| ); | |
| await approvalTxPermit2Router.wait(); | |
| // 2. Prepare commands and actions | |
| const COMMANDS = { | |
| V4_SWAP: 0x10, | |
| }; | |
| // 3. Encode commands (single byte) | |
| const commands = ethers.utils.hexlify(new Uint8Array([COMMANDS.V4_SWAP])); | |
| // ACTIONS | |
| const actions = ethers.utils.hexlify( | |
| new Uint8Array([ | |
| 0x06, // SWAP_EXACT_IN_SINGLE | |
| 0x0c, // SETTLE_ALL | |
| 0x0f, // TAKE_ALL | |
| ]) | |
| ); | |
| const encodedAddress = ethers.utils.defaultAbiCoder.encode( | |
| ["address"], | |
| [signerAddress] | |
| ); | |
| console.log(`signerAddress: ${signerAddress}`); | |
| const exactInputSingleParams = [ | |
| [ | |
| // PoolKey Struct | |
| key.currency0, | |
| key.currency1, | |
| key.fee, | |
| key.tickSpacing, | |
| key.hooks, | |
| ], | |
| zeroForOne, // zeroForOne | |
| amountInWei, // amountIn | |
| amountOutMinimumWei, // amountOutMinimum | |
| encodedAddress, // hookData | |
| ]; | |
| console.log( | |
| `exactInputSingleParams: ${JSON.stringify(exactInputSingleParams)}` | |
| ); | |
| // Encode Settlement Actions | |
| const settleAllParams = [tokenInAddress, amountInWei]; | |
| const takeAllParams = [tokenOutAddress, amountOutMinimumWei]; | |
| const encodedSwapParams = ethers.utils.defaultAbiCoder.encode( | |
| [ | |
| "tuple(tuple(address,address,uint24,int24,address),bool,uint128,uint128,bytes)", | |
| ], | |
| [exactInputSingleParams] | |
| ); | |
| const encodedSettleAllParams = ethers.utils.defaultAbiCoder.encode( | |
| ["address", "uint128"], | |
| settleAllParams | |
| ); | |
| const encodedTakeAllParams = ethers.utils.defaultAbiCoder.encode( | |
| ["address", "uint128"], | |
| takeAllParams | |
| ); | |
| const actionsAndInputs = ethers.utils.defaultAbiCoder.encode( | |
| ["bytes", "bytes[]"], | |
| [actions, [encodedSwapParams, encodedSettleAllParams, encodedTakeAllParams]] | |
| ); | |
| // // 7. Execute router call | |
| const router = new ethers.Contract(routerAddress, ROUTER_ABI, signer); | |
| const tx = await router.execute( | |
| commands, | |
| [actionsAndInputs], // 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 finalBalance = await tokenOut.balanceOf(await signer.getAddress()); | |
| if (finalBalance.lt(minAmountOut)) { | |
| throw new Error("Insufficient output amount"); | |
| } | |
| return { finalBalance, txReciept }; | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment