feat: implement Solana support in SDK with caching and route fetching

This commit is contained in:
Kyle Fang
2025-05-18 05:52:28 +00:00
parent f47482141a
commit 14d1cdac9e
6 changed files with 72 additions and 64 deletions

View File

@@ -296,6 +296,9 @@ export class BroSDK {
tron: {
routesConfigCache: new Map(),
},
solana: {
routesConfigCache: new Map(),
},
}
}

View File

@@ -10,6 +10,7 @@ import { GeneralCacheInterface } from "../utils/types/GeneralCacheInterface"
import { KnownChainId } from "../utils/types/knownIds"
import { TransferProphet } from "../utils/types/TransferProphet"
import { TronSupportedRoute } from "../tronUtils/types"
import { SolanaSupportedRoute } from "../solanaUtils/types"
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface SDKGlobalContextCache<K, V>
@@ -94,4 +95,10 @@ export interface SDKGlobalContext {
Promise<TronSupportedRoute[]>
>
}
solana: {
routesConfigCache?: SDKGlobalContextCache<
"mainnet" | "testnet",
Promise<SolanaSupportedRoute[]>
>
}
}

View File

@@ -1,21 +1,11 @@
import { getStacksToken } from "../../stacksUtils/contractHelpers"
import { requestAPI } from "../../utils/apiHelpers"
import { BigNumber } from "../../utils/BigNumber"
import { isNotNull } from "../../utils/typeHelpers"
import { KnownChainId, KnownTokenId } from "../../utils/types/knownIds"
import { StacksContractAddress } from "../../sdkUtils/types"
import { SDKGlobalContext } from "../../sdkUtils/types.internal"
export interface SolanaSupportedRoute {
evmToken: KnownTokenId.EVMToken
stacksChain: KnownChainId.StacksChain
stacksToken: KnownTokenId.StacksToken
proxyStacksTokenContractAddress: null | StacksContractAddress
pegOutFeeRate: BigNumber
pegOutMinFeeAmount: null | BigNumber
pegOutMinAmount: null | BigNumber
pegOutMaxAmount: null | BigNumber
}
import { getStacksToken } from "../stacksUtils/contractHelpers"
import { requestAPI } from "../utils/apiHelpers"
import { BigNumber } from "../utils/BigNumber"
import { isNotNull } from "../utils/typeHelpers"
import { KnownChainId, KnownTokenId } from "../utils/types/knownIds"
import { StacksContractAddress } from "../sdkUtils/types"
import { SDKGlobalContext } from "../sdkUtils/types.internal"
import { SolanaSupportedRoute } from "./types"
type NetworkType = "mainnet" | "testnet"
@@ -29,22 +19,22 @@ async function getSolanaSupportedRoutesByNetwork(
sdkContext: SDKGlobalContext,
network: NetworkType,
): Promise<SolanaSupportedRoute[]> {
const cacheKey = `solana-${network}`
const cacheKey = network
if (
sdkContext.evm.routesConfigCache != null &&
sdkContext.evm.routesConfigCache.get(cacheKey) != null
sdkContext.solana.routesConfigCache != null &&
sdkContext.solana.routesConfigCache.get(cacheKey) != null
) {
return sdkContext.evm.routesConfigCache.get(cacheKey)!
return sdkContext.solana.routesConfigCache.get(cacheKey)!
}
const promise = _getSolanaSupportedRoutes(sdkContext, network).catch(err => {
const cachedPromise = sdkContext.evm.routesConfigCache?.get(cacheKey)
const cachedPromise = sdkContext.solana.routesConfigCache?.get(cacheKey)
if (promise === cachedPromise) {
sdkContext.evm.routesConfigCache?.delete(cacheKey)
sdkContext.solana.routesConfigCache?.delete(cacheKey)
}
throw err
})
sdkContext.evm.routesConfigCache?.set(cacheKey, promise)
sdkContext.solana.routesConfigCache?.set(cacheKey, promise)
return promise
}
@@ -70,7 +60,7 @@ async function _getSolanaSupportedRoutes(
const routes = await Promise.all(
resp.routes.map(async (route): Promise<null | SolanaSupportedRoute> => {
const evmToken = route.evmToken as KnownTokenId.KnownToken
const solanaToken = KnownTokenId.createSolanaToken(route.tokenAddress)
const stacksToken = await getStacksToken(
sdkContext,
stacksChain,
@@ -78,10 +68,10 @@ async function _getSolanaSupportedRoutes(
)
if (stacksToken == null) return null
if (!KnownTokenId.isEVMToken(evmToken)) return null
if (!KnownTokenId.isSolanaToken(solanaToken)) return null
return {
evmToken,
solanaToken,
stacksChain,
stacksToken,
proxyStacksTokenContractAddress: route.proxyStacksTokenContractAddress,
@@ -106,7 +96,7 @@ async function _getSolanaSupportedRoutes(
}
interface SupportedSolanaBridgeRoute {
evmToken: string
tokenAddress: `0x${string}`
stacksTokenContractAddress: StacksContractAddress
proxyStacksTokenContractAddress: null | StacksContractAddress
pegOutFeeRate: `${number}`

14
src/solanaUtils/types.ts Normal file
View File

@@ -0,0 +1,14 @@
import { BigNumber } from "../utils/BigNumber"
import { KnownChainId, KnownTokenId } from "../utils/types/knownIds"
import { StacksContractAddress } from "../sdkUtils/types"
export interface SolanaSupportedRoute {
solanaToken: KnownTokenId.SolanaToken
stacksChain: KnownChainId.StacksChain
stacksToken: KnownTokenId.StacksToken
proxyStacksTokenContractAddress: null | StacksContractAddress
pegOutFeeRate: BigNumber
pegOutMinFeeAmount: null | BigNumber
pegOutMinAmount: null | BigNumber
pegOutMaxAmount: null | BigNumber
}

View File

@@ -1,11 +1,11 @@
import { getStacksToken } from "../../stacksUtils/contractHelpers"
import { requestAPI } from "../../utils/apiHelpers"
import { BigNumber } from "../../utils/BigNumber"
import { isNotNull } from "../../utils/typeHelpers"
import { KnownChainId, KnownTokenId } from "../../utils/types/knownIds"
import { StacksContractAddress } from "../../sdkUtils/types"
import { SDKGlobalContext } from "../../sdkUtils/types.internal"
import { TronSupportedRoute } from "../../tronUtils/types"
import { getStacksToken } from "../stacksUtils/contractHelpers"
import { requestAPI } from "../utils/apiHelpers"
import { BigNumber } from "../utils/BigNumber"
import { isNotNull } from "../utils/typeHelpers"
import { KnownChainId, KnownTokenId } from "../utils/types/knownIds"
import { StacksContractAddress } from "../sdkUtils/types"
import { SDKGlobalContext } from "../sdkUtils/types.internal"
import { TronSupportedRoute } from "./types"
type NetworkType = "mainnet" | "testnet"
@@ -19,22 +19,22 @@ async function getTronSupportedRoutesByNetwork(
sdkContext: SDKGlobalContext,
network: NetworkType,
): Promise<TronSupportedRoute[]> {
const cacheKey = `tron-${network}`
const cacheKey = network
if (
sdkContext.tron?.routesConfigCache != null &&
sdkContext.tron.routesConfigCache != null &&
sdkContext.tron.routesConfigCache.get(cacheKey) != null
) {
return sdkContext.tron.routesConfigCache.get(cacheKey)!
}
const promise = _getTronSupportedRoutes(sdkContext, network).catch(err => {
const cachedPromise = sdkContext.tron?.routesConfigCache?.get(cacheKey)
const cachedPromise = sdkContext.tron.routesConfigCache?.get(cacheKey)
if (promise === cachedPromise) {
sdkContext.tron?.routesConfigCache?.delete(cacheKey)
sdkContext.tron.routesConfigCache?.delete(cacheKey)
}
throw err
})
sdkContext.tron?.routesConfigCache?.set(cacheKey, promise)
sdkContext.tron.routesConfigCache?.set(cacheKey, promise)
return promise
}
@@ -60,7 +60,7 @@ async function _getTronSupportedRoutes(
const routes = await Promise.all(
resp.routes.map(async (route): Promise<null | TronSupportedRoute> => {
const tronToken = route.evmToken as KnownTokenId.KnownToken
const tronToken = KnownTokenId.createTronToken(route.tokenAddress)
const stacksToken = await getStacksToken(
sdkContext,
stacksChain,
@@ -96,7 +96,7 @@ async function _getTronSupportedRoutes(
}
interface SupportedTronBridgeRoute {
evmToken: string
tokenAddress: `0x${string}`
stacksTokenContractAddress: StacksContractAddress
proxyStacksTokenContractAddress: null | StacksContractAddress
pegOutFeeRate: `${number}`

View File

@@ -177,38 +177,32 @@ export namespace KnownTokenId {
return value.startsWith("stx-")
}
/** A namespace that contains constants and types for Tron tokens. */
export namespace Tron {
/** Represents the USDT token ID on the Tron blockchain. */
export const USDT = tokenId("tron-usdt")
}
/** This type defines known tokens on the Tron blockchain. */
export type TronToken = (typeof _allKnownTronTokens)[number]
export type TronToken = TokenId<"a tron token">
export function isTronToken(value: TokenId): value is TronToken {
return _allKnownTronTokens.includes(value as any)
return value.startsWith("tron-")
}
export const createTronToken = (
evmTokenAddress: `0x${string}`,
): KnownTokenId.TronToken => {
return `tron-${evmTokenAddress}` as any
}
/** A namespace that contains constants and types for Solana tokens. */
export namespace Solana {
/** Represents the USDC token ID on the Solana blockchain. */
export const USDC = tokenId("solana-usdc")
/** Represents the USDT token ID on the Solana blockchain. */
export const USDT = tokenId("solana-usdt")
/** Represents the SOL token ID on the Solana blockchain. */
export const SOL = tokenId("solana-sol")
}
/** This type defines known tokens on the Solana blockchain. */
export type SolanaToken = (typeof _allKnownSolanaTokens)[number]
export type SolanaToken = TokenId<"a solana token">
export function isSolanaToken(value: TokenId): value is SolanaToken {
return _allKnownSolanaTokens.includes(value as any)
return value.startsWith("solana-")
}
export const createSolanaToken = (
evmTokenAddress: `0x${string}`,
): KnownTokenId.SolanaToken => {
return `solana-${evmTokenAddress}` as any
}
}
export const _allKnownBitcoinTokens = Object.values(KnownTokenId.Bitcoin)
export const _allKnownStacksTokens = Object.values(KnownTokenId.Stacks)
export const _allKnownEVMTokens = Object.values(KnownTokenId.EVM)
export const _allKnownTronTokens = Object.values(KnownTokenId.Tron)
export const _allKnownSolanaTokens = Object.values(KnownTokenId.Solana)
/**
* The `KnownChainId` namespace provides types of blockchain networks