Skip to content

Instantly share code, notes, and snippets.

@claytantor
Last active March 19, 2025 14:52
Show Gist options
  • Select an option

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

Select an option

Save claytantor/c8d1c2e6ef879a295089bf117ea094b0 to your computer and use it in GitHub Desktop.
PookaSwaps for ReactJs and ethers.js 5.8.0
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>
)
};
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