Integrating the PiperX swap functionality to your website makes it easy for you to leverge the liquidity from PiperX without the needs of maintain your own pools. Some example applications including swapping staked $IP tokens to $IP tokens on the LSD website, allow users to buy NFT with stable coins (auto swap stable coins to $IP) on the NFT marketplace.
Steps to Integration
There are three steps in conducting a swap: 1) prepare input, 2) approve tokens, 3) conduct swap. You need to install PiperX sdk and import it to proceed.
0. Prepare Input
You need the following inputs before you can conduct a swap
token1_address: address, // if it is $IP native token, use WIP_ADDRESS
token2_address: address, // if it is $IP native token, use WIP_ADDRESS
amount1: bigint, // amount of token 1 you want to give
amount2Min: bigint, // minimal number of token 2 you want to receive
expire_time: bigint// expiration timestamp for a swap
signer: ethers.Signer. //
Note that, if you are swapping $IP token, you should pass in the PiperX wrapped $IP token address, because the $IP token is not a ERC-20 token. For example, if you are swapping from staked $IP token to $IP token, please use wrapped $IP address WIP_ADDRESS
1. Finding the best swap path
Depends on the swaps that you plan to do, we will use a universal router to find the best swap path, either through standard (V2) or concentrated (V3) path.
import { routingExactInput } from '@piperx/sdk/src/routing'
import { v2_router_abi, piperv3SwapRouter_abi, } from '@piperx/sdk/src/abi'
import { v2RouterAddress, piperv3SwapRouterAddress } from '@piperx/sdk/src/constant'
let {bestRoute, max} = routingExactInput(token1_address, token2_address, amount1, signer)
2. Approve tokens
Then you need to approve the router to use your token before swap, depends on the bestRoute that you retreve (it's either a v2 or v3 route), example code are follows
Depends on the swaps that you plan to do, we will call different functions from the PiperX V2 or V3 contract. One could use the follow universal swap API to deal with different swap cases.
export const swap = async(
amount1: bigint,
amount2Min: bigint,
path: string[],
expirationTimestamp: bigint,
signer: ethers.Signer
) => {
if (path[1].length < 10) { // v3 swap
return await v3Swap(amount1, amount2Min, path, expirationTimestamp, signer);
} else { // v2 swap
return await v2Swap(amount1, amount2Min, path, expirationTimestamp, signer);
}
}
export const v3Swap = async(
amount1: bigint,
amount2Min: bigint,
path: string[],
expirationTimestamp: bigint,
signer: ethers.Signer
) => {
if (path.length != 3) {
throw new Error("path must contain 3 elements");
}
const address = await signer.getAddress();
try {
const router = new ethers.Contract(piperv3SwapRouterAddress, piperv3SwapRouter_abi, signer);
let tx;
const exactInputSingleParams = {
tokenIn: path[0],
tokenOut: path[2],
fee: path[1],
recipient: path[2] === WIP_ADDRESS ? ethers.constants.AddressZero : address,
deadline: expirationTimestamp,
amountIn: amount1,
amountOutMinimum: amount2Min,
sqrtPriceLimitX96: BigInt(0)
};
if (path[0] === WIP_ADDRESS) {
// Case 1: IP to Token (Native IP to ERC-20)
const swapRouterInterface = new ethers.utils.Interface(piperv3SwapRouter_abi);
const peripheryPaymentsInterface = new ethers.utils.Interface(IPeripheryPaymentsWithFee_abi);
const multicallData = [
swapRouterInterface.encodeFunctionData('exactInputSingle', [exactInputSingleParams]),
peripheryPaymentsInterface.encodeFunctionData('refundETH')
];
tx = await router.multicall(
multicallData
);
} else if (path[2] === WIP_ADDRESS) {
// Case 2: Token to IP (ERC-20 to Native IP)
const swapRouterInterface = new ethers.utils.Interface(piperv3SwapRouter_abi);
const peripheryPaymentsInterface = new ethers.utils.Interface([
"function unwrapWETH9(uint256 amountMinimum, address recipient) external payable"
]);
const multicallData = [
swapRouterInterface.encodeFunctionData('exactInputSingle', [exactInputSingleParams]),
peripheryPaymentsInterface.encodeFunctionData('unwrapWETH9', [amount2Min, address])
];
tx = await router.multicall(
multicallData
);
} else {
// Case 3: Token to Token
tx = await router.exactInputSingle(
exactInputSingleParams,
);
}
return await tx.wait();
} catch (error) {
console.error("Error in v3 swap:", error);
throw error;
}
}
export const v2Swap = async(
amount1: bigint,
amount2Min: bigint,
path: string[],
expirationTimestamp: bigint,
signer: ethers.Signer
) => {
try {
const router = new ethers.Contract(v2RouterAddress, v2_router_abi, signer);
let tx;
if (path[0] == WIP_ADDRESS) {
tx = await router.swapExactETHForTokens(
amount2Min,
path,
await signer.getAddress(), // Make sure we await this
expirationTimestamp,
{
...txOptions,
value: amount1
}
);
} else if (path[path.length - 1] == WIP_ADDRESS) {
tx = await router.swapExactTokensForETH(
amount1,
amount2Min,
path,
await signer.getAddress(), // Make sure we await this
expirationTimestamp,
txOptions
);
} else {
tx = await router.swapExactTokensForTokens(
amount1,
amount2Min,
path,
await signer.getAddress(), // Make sure we await this
expirationTimestamp,
txOptions
);
}
return tx.wait()
} catch (error) {
console.error("Error in swap:", error);
throw error;
}
}