Skip to content

Commit

Permalink
feat(mod/erc-20): add uniswap fee
Browse files Browse the repository at this point in the history
  • Loading branch information
stephancill committed Jan 25, 2024
1 parent de7c32c commit 9843be8
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 57 deletions.
1 change: 0 additions & 1 deletion examples/api/src/app/api/erc-20/balances/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
chainByName,
getEthUsdPrice,
numberWithCommas,
parseTokenParam,
parseInfoRequestParams,
} from "../lib/utils";

Expand Down
62 changes: 21 additions & 41 deletions examples/api/src/app/api/erc-20/buy/route.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { NextRequest, NextResponse } from "next/server";
import { parseEther } from "viem2";
import { fromHex } from "viem2";
import {
chainByName,
getEthUsdPrice,
getSwapTransaction,
parseInfoRequestParams,
} from "../lib/utils";

export async function POST(request: NextRequest) {
const { blockchain, tokenAddress } = parseInfoRequestParams(request);

// TODO: Expose separate execution/receiver addresses
const userAddress = request.nextUrl.searchParams
.get("walletAddress")
?.toLowerCase();
Expand All @@ -26,48 +26,28 @@ export async function POST(request: NextRequest) {
const chain = chainByName[blockchain];

const ethPriceUsd = await getEthUsdPrice();
const ethInputAmount = parseEther((buyAmountUsd / ethPriceUsd).toString());

const swapCalldataParams: {
src: string;
dst: string;
amount: string;
from: string;
slippage: string;
receiver: string;
fee?: string;
referrer?: string;
} = {
src: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", // Native ETH
dst: tokenAddress,
amount: ethInputAmount.toString(),
from: userAddress,
slippage: "5",
receiver: userAddress,
};

// TODO: Use Uniswap to take advantage of configurable fee
// The referrer here is the 1inch referral program recipient (20% of surplus from trade)
// See https://blog.1inch.io/why-should-you-integrate-1inch-apis-into-your-service/
if (process.env.ERC_20_FEE_RECIPIENT) {
swapCalldataParams.referrer = process.env.ERC_20_FEE_RECIPIENT;
}

const swapCalldataRes = await fetch(
`https://api.1inch.dev/swap/v5.2/${chain.id}/swap?${new URLSearchParams(
swapCalldataParams
).toString()}`,
{
headers: {
Authorization: `Bearer ${process.env["ERC_20_1INCH_API_KEY"]}`,
},
}
);
const ethInputAmount = (buyAmountUsd / ethPriceUsd).toString();

const swapRoute = await getSwapTransaction({
blockchain,
ethInputAmountFormatted: ethInputAmount,
outTokenAddress: tokenAddress,
recipientAddress: userAddress,
feePercentageInt: 10,
feeRecipientAddress: process.env.ERC_20_FEE_RECIPIENT,
});

const swapCalldataJson = await swapCalldataRes.json();
const tx = swapRoute.methodParameters;

return NextResponse.json({
transaction: swapCalldataJson.tx,
transaction: {
from: userAddress,
to: tx.to,
value: fromHex(tx.value as `0x${string}`, {
to: "bigint",
}).toString(),
data: tx.calldata,
},
explorer: chain.blockExplorers.default,
});
}
144 changes: 129 additions & 15 deletions examples/api/src/app/api/erc-20/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
import { FarcasterUser } from "@mod-protocol/core";
import { Protocol } from "@uniswap/router-sdk";
import { Percent, Token, TradeType } from "@uniswap/sdk-core";
import {
AlphaRouter,
AlphaRouterConfig,
CurrencyAmount,
SwapOptions,
SwapType,
nativeOnChain,
} from "@uniswap/smart-order-router";
import { ethers } from "ethers";
import JSBI from "jsbi";
import { NextRequest } from "next/server";
import { publicActionReverseMirage } from "reverse-mirage";
import { createPublicClient, http } from "viem2";
Expand All @@ -10,9 +22,21 @@ export function numberWithCommas(x: string | number) {
return parts.join(".");
}

const { ERC_20_AIRSTACK_API_KEY } = process.env;
const AIRSTACK_API_URL = "https://api.airstack.xyz/gql";
const airstackQuery = `
export async function getFollowingHolderInfo({
fid,
tokenAddress,
blockchain,
}: {
fid: string;
tokenAddress: string;
blockchain: string;
}): Promise<{
holders: { user: FarcasterUser; amount: number }[];
holdersCount: number;
}> {
const { ERC_20_AIRSTACK_API_KEY } = process.env;
const AIRSTACK_API_URL = "https://api.airstack.xyz/gql";
const airstackQuery = `
query MyQuery($identity: Identity!, $token_address: Address!, $blockchain: TokenBlockchain, $cursor: String) {
SocialFollowings(
input: {
Expand Down Expand Up @@ -58,18 +82,6 @@ query MyQuery($identity: Identity!, $token_address: Address!, $blockchain: Token
}
`;

export async function getFollowingHolderInfo({
fid,
tokenAddress,
blockchain,
}: {
fid: string;
tokenAddress: string;
blockchain: string;
}): Promise<{
holders: { user: FarcasterUser; amount: number }[];
holdersCount: number;
}> {
const acc: any[] = [];

let hasNextPage = true;
Expand Down Expand Up @@ -314,6 +326,108 @@ export async function getEthUsdPrice(): Promise<number> {
return ethPriceUsd;
}

export async function getSwapTransaction({
outTokenAddress,
blockchain,
ethInputAmountFormatted,
recipientAddress,
feeRecipientAddress,
feePercentageInt,
}: {
outTokenAddress: string;
blockchain: string;
ethInputAmountFormatted: string;
recipientAddress: string;
feePercentageInt?: number;
feeRecipientAddress?: string;
}) {
const tokenOut = await getUniswapToken({
tokenAddress: outTokenAddress,
blockchain,
});
const chain = chainByName[blockchain];
const provider = new ethers.providers.JsonRpcProvider(
chain.rpcUrls.default.http[0]
);

const router = new AlphaRouter({
chainId: chain.id,
provider,
});

const tokenIn = nativeOnChain(chain.id);
const amountIn = CurrencyAmount.fromRawAmount(
tokenIn,
JSBI.BigInt(
ethers.utils.parseUnits(ethInputAmountFormatted, tokenIn.decimals)
)
);

let swapOptions: SwapOptions = {
type: SwapType.UNIVERSAL_ROUTER,
recipient: recipientAddress,
slippageTolerance: new Percent(5, 100),
deadlineOrPreviousBlockhash: parseDeadline("360"),
fee:
feeRecipientAddress && feePercentageInt
? {
fee: new Percent(feePercentageInt, 100),
recipient: feeRecipientAddress,
}
: undefined,
};

const partialRoutingConfig: Partial<AlphaRouterConfig> = {
protocols: [Protocol.V2, Protocol.V3, Protocol.MIXED],
};

const quote = await router.route(
amountIn,
tokenOut,
TradeType.EXACT_INPUT,
swapOptions,
partialRoutingConfig
);

if (!quote) return;
return quote;
}

async function getUniswapToken({
tokenAddress,
blockchain,
}: {
tokenAddress: string;
blockchain: string;
}): Promise<Token> {
const chain = chainByName[blockchain];
const client = createPublicClient({
transport: http(),
chain,
}).extend(publicActionReverseMirage);

const token = await client.getERC20({
erc20: {
address: tokenAddress as `0x${string}`,
chainID: chain.id,
},
});

const uniswapToken = new Token(
chain.id,
tokenAddress,
token.decimals,
token.symbol,
token.name
);

return uniswapToken;
}

function parseDeadline(deadline: string): number {
return Math.floor(Date.now() / 1000) + parseInt(deadline);
}

export function parseInfoRequestParams(request: NextRequest) {
const fid = request.nextUrl.searchParams.get("fid");
const token = request.nextUrl.searchParams.get("token")?.toLowerCase();
Expand Down

0 comments on commit 9843be8

Please sign in to comment.