Skip to content

Instantly share code, notes, and snippets.

@claytantor
Last active March 17, 2025 05:54
Show Gist options
  • Select an option

  • Save claytantor/b00564ececfbaa4a91fc0bdfeee1b21d to your computer and use it in GitHub Desktop.

Select an option

Save claytantor/b00564ececfbaa4a91fc0bdfeee1b21d to your computer and use it in GitHub Desktop.
{
"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"
}
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> &lt; $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> &lt; $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