Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions src/all-token-balance-history.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { fetchFromRPC } from "./utils/fetch-from-rpc";
import { convertFTBalance } from "./utils/convert-ft-balance";
import prisma from "./prisma";
import { tokens } from "./constants/tokens";
import { periodMap } from "./constants/period-map";
import { NearTokenMetadata, periodMap } from "./constants/common";
import { getUserStakeBalances } from "./utils/lib";
import {
BLOCKS_PER_HOUR,
Expand All @@ -25,10 +24,12 @@ export async function getAllTokenBalanceHistory(
): Promise<Record<string, BalanceHistoryEntry[]>> {
let rpcCallCount = 0;

const token = tokens[token_id as keyof typeof tokens];
let decimals = token?.decimals || 24;
let decimals =
token_id === "near" || token_id === "wrap.near"
? NearTokenMetadata.decimals
: 0;

if (!token?.decimals) {
if (!decimals) {
try {
const tokenDetails = await fetchFromRPC(
{
Expand Down
22 changes: 22 additions & 0 deletions src/constants/period-map.ts → src/constants/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,25 @@ export const periodMap = [
{ period: "1H", value: 1, interval: 6 }, // 1 point per 10 minutes
{ period: "All", value: 24 * 365 * 2, interval: 20 }, // assuming 2 years of chain history
];

export const WrapNearIcon =
"https://img.rhea.finance/images/w-NEAR-no-border.png";

export const NearIcon = "https://img.rhea.finance/images/NEARIcon.png";

export const NearTokenMetadata = {
contract: "near",
spec: "ft-1.0.0",
name: "NEAR",
symbol: "NEAR",
icon: NearIcon,
reference: "",
reference_hash: "",
decimals: 24,
price: null,
total_supply: "0",
onchain_market_cap: "0",
change_24: "0",
market_cap: "0",
volume_24h: "0",
};
1,235 changes: 0 additions & 1,235 deletions src/constants/tokens.ts

This file was deleted.

7 changes: 2 additions & 5 deletions src/ft-tokens.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import axios from "axios";
import Big from "big.js";
import prisma from "./prisma";
import { tokens } from "./constants/tokens";
import { WrapNearIcon } from "./constants/common";

type FTCache = {
get: (key: string) => any;
Expand Down Expand Up @@ -135,10 +135,7 @@ export async function getFTTokens(account_id: string, cache: FTCache) {

const finalFts = sorted.map((ft) => {
const isWrapped = ft.contract === "wrap.near";
const icon =
ft.ft_meta.icon ||
(isWrapped ? tokens[ft.contract]?.icon : undefined) ||
tokens[ft.contract]?.icon;
const icon = ft.ft_meta.icon || (isWrapped ? WrapNearIcon : undefined);
return {
...ft,
ft_meta: {
Expand Down
2 changes: 1 addition & 1 deletion src/intents-graph.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { fetchFromRPC } from "./utils/fetch-from-rpc";
import prisma from "./prisma";
import { periodMap } from "./constants/period-map";
import { periodMap } from "./constants/common";
import axios from "axios";
import Big from "big.js";
import {
Expand Down
24 changes: 5 additions & 19 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import axios from "axios";
import treasuryRoutes from "./routes/metrics";
import oneclickTreasuryRoutes from "./routes/oneclick-treasury";
import cron from "node-cron";
import { searchFT } from "./utils/lib";
import { getBlockchainsOptions } from "./constants/intents-chains";

dotenv.config();
Expand Down Expand Up @@ -85,7 +86,7 @@ app.get("/api/swap", async (req: Request, res: Response) => {
params.slippage = "0.01"; // 1% default slippage
}

const result = await getSwap(params);
const result = await getSwap(params, cache);
return res.json(result);
} catch (error) {
console.error("Error in /api/swap:", error);
Expand Down Expand Up @@ -400,29 +401,14 @@ app.get("/api/search-ft", async (req: Request, res: Response) => {
const { query } = req.query;

if (!query || typeof query !== "string") {
return res.status(400).send({ error: "query is required" });
return res.status(400).json({ error: "query is required" });
}
const cacheKey = `search-ft-${query}`;

const cachedSearchedFt = cache.get(cacheKey);
if (cachedSearchedFt !== undefined) {
console.log(`🔁 Returning cached FT ${query}`);
return res.send(cachedSearchedFt);
}

const { data } = await axios.get(
`https://api.nearblocks.io/v1/fts/?search=${query}`,
{
headers: {
Authorization: `Bearer ${process.env.NEARBLOCKS_API_KEY}`,
},
}
);
const searchedFt = data?.tokens?.[0];
cache.set(cacheKey, searchedFt, 60 * 60 * 24); // 1 day
const searchedFt = await searchFT(query, cache);
return res.send(searchedFt);
} catch (error) {
console.error("Error searching FT:", error);

return res.status(500).send({ error: "Failed to search FT" });
}
});
Expand Down
25 changes: 12 additions & 13 deletions src/swap.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { searchToken } from "./utils/search-token";
import Big from "big.js";
import { SmartRouter } from "./utils/interface";
import { swapFromServer, unWrapNear, wrapNear } from "./utils/lib";
import { searchFT, swapFromServer, unWrapNear, wrapNear } from "./utils/lib";
import axios from "axios";

export type SwapParams = {
Expand All @@ -12,18 +11,18 @@ export type SwapParams = {
slippage: string;
};

export async function getSwap({
accountId,
tokenIn,
tokenOut,
amountIn,
slippage,
}: SwapParams) {
export async function getSwap(
{ accountId, tokenIn, tokenOut, amountIn, slippage }: SwapParams,
cache: {
get: (key: string) => any;
set: (key: string, value: any, ttl: number) => void;
}
) {
try {
const isWrapNearInputToken = tokenIn === "wrap.near";
const isWrapNearOutputToken = tokenOut === "wrap.near";
const tokenInData = await searchToken(tokenIn);
const tokenOutData = await searchToken(tokenOut);
const tokenInData = await searchFT(tokenIn, cache);
const tokenOutData = await searchFT(tokenOut, cache);

if (!tokenInData || !tokenOutData) {
throw new Error(
Expand All @@ -32,7 +31,7 @@ export async function getSwap({
}

// (un)wrap NEAR
if (tokenInData.id === tokenOutData.id) {
if (tokenInData.contract === tokenOutData.contract) {
if (isWrapNearInputToken && !isWrapNearOutputToken) {
return {
transactions: [await unWrapNear({ amountIn })],
Expand All @@ -55,7 +54,7 @@ export async function getSwap({
let swapRes: SmartRouter;
try {
const response = await axios.get(
`https://smartrouter.ref.finance/findPath?amountIn=${sendAmount}&tokenIn=${tokenInData.id}&tokenOut=${tokenOutData.id}&pathDeep=3&slippage=${slippage}`
`https://smartrouter.ref.finance/findPath?amountIn=${sendAmount}&tokenIn=${tokenInData.contract}&tokenOut=${tokenOutData.contract}&pathDeep=3&slippage=${slippage}`
);
swapRes = response.data;
} catch (error) {
Expand Down
33 changes: 28 additions & 5 deletions src/utils/interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { TokenMetadata } from "./search-token";

export interface BalanceResp {
balance: number;
contract_id: string;
Expand All @@ -24,11 +22,36 @@ export interface IServerPool {
token_out: string;
}

export interface NearBlockTokenMetadata {
contract: string;
name: string;
symbol: string;
decimals: number;
icon: string;
reference: string | null;
price: string | null;
total_supply: string;
onchain_market_cap: string;
change_24: string;
market_cap: string;
volume_24h: string;
}

export interface RefFinanceTokenMetadata {
spec: string;
name: string;
symbol: string;
icon: string;
reference: string;
reference_hash: string;
decimals: number;
}

export interface IServerRoute {
amount_in: string;
min_amount_out: string;
pools: IServerPool[];
tokens?: TokenMetadata[];
tokens?: RefFinanceTokenMetadata[];
}

export interface IEstimateSwapServerView {
Expand All @@ -42,8 +65,8 @@ export interface IEstimateSwapServerView {

export interface SwapOptions {
useNearBalance?: boolean;
tokenIn: TokenMetadata;
tokenOut: TokenMetadata;
tokenIn: NearBlockTokenMetadata;
tokenOut: NearBlockTokenMetadata;
amountIn: string;
slippageTolerance?: number;
accountId: string;
Expand Down
58 changes: 49 additions & 9 deletions src/utils/lib.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import {
FTStorageBalance,
RefFiFunctionCallOptions,
TokenMetadata,
Transaction,
WRAP_NEAR_CONTRACT_ID,
ftGetStorageBalance,
ftViewFunction,
} from "@ref-finance/ref-sdk";
import { SwapOptions } from "./interface";
import { SwapOptions, NearBlockTokenMetadata } from "./interface";
import Big from "big.js";
import axios from "axios";
import { fetchFromRPC } from "./fetch-from-rpc";
import { NearTokenMetadata } from "../constants/common";

const NO_REQUIRED_REGISTRATION_TOKEN_IDS = [
"17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
Expand Down Expand Up @@ -79,17 +79,17 @@ export const swapFromServer = async ({
const transactions: Transaction[] = [];
const tokenOutActions: RefFiFunctionCallOptions[] = [];
const { routes } = swapsToDoServer;
const registerToken = async (token: TokenMetadata) => {
const registerToken = async (token: NearBlockTokenMetadata) => {
const tokenRegistered = await ftGetStorageBalance(
token.id,
token.contract,
accountId
).catch(() => {
throw new Error(`${token.id} doesn't exist.`);
throw new Error(`${token.contract} doesn't exist.`);
});

if (tokenRegistered === null) {
if (NO_REQUIRED_REGISTRATION_TOKEN_IDS.includes(token.id)) {
const r = await native_usdc_has_upgraded(token.id, accountId);
if (NO_REQUIRED_REGISTRATION_TOKEN_IDS.includes(token.contract)) {
const r = await native_usdc_has_upgraded(token.contract, accountId);
if (r) {
tokenOutActions.push({
methodName: "storage_deposit",
Expand Down Expand Up @@ -121,7 +121,7 @@ export const swapFromServer = async ({
});
}
transactions.push({
receiverId: token.id,
receiverId: token.contract,
functionCalls: tokenOutActions,
});
}
Expand All @@ -140,7 +140,7 @@ export const swapFromServer = async ({
});
});
transactions.push({
receiverId: tokenIn.id,
receiverId: tokenIn.contract,
functionCalls: [
{
methodName: "ft_transfer_call",
Expand Down Expand Up @@ -293,6 +293,46 @@ export async function fetchAdditionalPage(
return results.flatMap((result) => result.body);
}

export async function searchFT(
query: string,
cache: {
get: (key: string) => any;
set: (key: string, value: any, ttl: number) => void;
}
): Promise<NearBlockTokenMetadata | null> {
if (!query || typeof query !== "string") {
throw new Error("query is required");
}

const cacheKey = `search-ft-${query}`;
const cachedSearchedFt = cache.get(cacheKey);

if (cachedSearchedFt !== undefined) {
console.log(`🔁 Returning cached FT ${query}`);
return cachedSearchedFt;
}

try {
const { data } = await axios.get(
`https://api.nearblocks.io/v1/fts/?search=${
query === "near" ? "wrap.near" : query
}`,
{
headers: {
Authorization: `Bearer ${process.env.NEARBLOCKS_API_KEY}`,
},
}
);

const searchedFt = data?.tokens?.[0] || null;
cache.set(cacheKey, searchedFt, 60 * 60 * 24); // 1 day
return searchedFt;
} catch (error) {
console.error("Error searching FT:", error);
throw new Error("Failed to search FT");
}
}

type StakeCache = {
get: (key: string) => any;
set: (key: string, value: any, ttl: number) => void;
Expand Down
19 changes: 0 additions & 19 deletions src/utils/search-token.ts

This file was deleted.

Loading