@b3dotfun/anyspend-sdk
Version:
React Hooks and UI Components for AnySpend by B3
343 lines (315 loc) • 11.2 kB
text/typescript
import { ChainType, IBaseChain, IEVMChain, ISolanaChain, Token } from "@b3dotfun/anyspend-sdk/types";
import invariant from "invariant";
import {
Account,
Chain,
createPublicClient,
createWalletClient,
defineChain,
http,
parseEther,
PublicClient,
Transport,
WalletClient
} from "viem";
import {
arbitrum,
avalanche,
b3,
b3Sepolia,
base,
baseSepolia,
bsc,
mainnet,
optimism,
polygon,
sepolia
} from "viem/chains";
import { RELAY_SOLANA_MAINNET_CHAIN_ID } from "../constants";
import { getAvaxToken, getBnbToken, getEthToken, getPolToken, getSolanaToken } from "./token";
function getCustomEvmChain(chain: Chain, rpcUrl: string): Chain {
return defineChain({ ...chain, rpcUrls: { default: { http: [rpcUrl] } } });
}
// export const b4testnet = defineChain({
// id: 19934,
// name: "B4 Testnet",
// network: "b4-testnet",
// nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
// rpcUrls: { default: { http: ["https://19934.rpc.thirdweb.com/c2dce731fe7fa39edc1d0ea5a6a5e11e"] } },
// });
export const EVM_MAINNET: Record<number, IEVMChain> = {
[mainnet.id]: {
id: mainnet.id,
name: mainnet.name,
type: ChainType.EVM,
logoUrl: "https://assets.relay.link/icons/square/1/light.png",
nativeRequired: parseEther("0.001"),
canDepositNative: true,
defaultToken: getEthToken(mainnet.id),
nativeToken: getEthToken(mainnet.id),
viem: getCustomEvmChain(
mainnet,
"https://quick-chaotic-film.quiknode.pro/39a7aae6a7078f9f36c435e6f34c071c641cf863/"
),
pollingInterval: 4000, // 4 seconds for Ethereum mainnet
zapperEnum: "ETHEREUM_MAINNET",
coingeckoName: "eth"
},
[arbitrum.id]: {
id: arbitrum.id,
name: "Arbitrum",
type: ChainType.EVM,
logoUrl: "https://assets.relay.link/icons/square/42161/light.png",
nativeRequired: parseEther("0.0001"),
canDepositNative: true,
defaultToken: getEthToken(arbitrum.id),
nativeToken: getEthToken(arbitrum.id),
viem: getCustomEvmChain(
arbitrum,
"https://proportionate-twilight-patina.arbitrum-mainnet.quiknode.pro/60e4825626515233a0f566f5915601af6043127b/"
),
pollingInterval: 500, // 500ms for Arbitrum's fast blocks
zapperEnum: "ARBITRUM_MAINNET",
coingeckoName: "arbitrum"
},
[base.id]: {
id: base.id,
name: base.name,
logoUrl: "https://assets.relay.link/icons/square/8453/light.png",
type: ChainType.EVM,
nativeRequired: parseEther("0.0001"),
canDepositNative: true,
defaultToken: getEthToken(base.id),
nativeToken: getEthToken(base.id),
viem: getCustomEvmChain(
base,
"https://sly-indulgent-bird.base-mainnet.quiknode.pro/4e31fab6845eb29a2764723a43896999fe962e48/"
),
pollingInterval: 1000, // 1 second for Base
zapperEnum: "BASE_MAINNET",
coingeckoName: "base"
},
[optimism.id]: {
id: optimism.id,
name: optimism.name,
logoUrl: "https://assets.relay.link/icons/square/10/light.png",
type: ChainType.EVM,
nativeRequired: parseEther("0.0001"),
canDepositNative: true,
defaultToken: getEthToken(optimism.id),
nativeToken: getEthToken(optimism.id),
viem: getCustomEvmChain(
optimism,
"https://black-cosmopolitan-hexagon.optimism.quiknode.pro/18382925841f9d09f9e76eef954bf189aa234523/"
),
pollingInterval: 1000, // 1 second for Optimism
zapperEnum: "OPTIMISM_MAINNET",
coingeckoName: "optimism"
},
[polygon.id]: {
id: polygon.id,
name: polygon.name,
logoUrl: "https://assets.relay.link/icons/square/137/light.png",
type: ChainType.EVM,
nativeRequired: parseEther("0.1"),
canDepositNative: true,
defaultToken: getPolToken(),
nativeToken: getPolToken(),
viem: getCustomEvmChain(
polygon,
"https://purple-young-field.matic.quiknode.pro/ca54f365c1a4c7f970223eb8087e0fc579feba12/"
),
pollingInterval: 1000, // 1 second for Polygon
zapperEnum: "POLYGON_MAINNET",
coingeckoName: "polygon"
},
[avalanche.id]: {
id: avalanche.id,
name: avalanche.name,
logoUrl: "https://assets.relay.link/icons/square/43114/light.png",
type: ChainType.EVM,
nativeRequired: parseEther("0.005"),
canDepositNative: true,
defaultToken: getAvaxToken(),
nativeToken: getAvaxToken(),
viem: getCustomEvmChain(
avalanche,
"https://burned-billowing-pond.avalanche-mainnet.quiknode.pro/24289978a524a18ef42e568e04fe8cad8c7b6720/ext/bc/C/rpc/"
),
pollingInterval: 1000, // 1 second for Avalanche
zapperEnum: "AVALANCHE_MAINNET",
coingeckoName: "avalanche"
},
[bsc.id]: {
id: bsc.id,
name: bsc.name,
logoUrl: "https://avatars.githubusercontent.com/u/45615063?s=280&v=4",
type: ChainType.EVM,
nativeRequired: parseEther("0.00001"),
canDepositNative: false,
defaultToken: getBnbToken(),
nativeToken: getBnbToken(),
viem: getCustomEvmChain(bsc, "https://bsc-rpc.publicnode.com"),
pollingInterval: 1000, // 1 second for BSC
zapperEnum: "BSC_MAINNET",
coingeckoName: "bsc"
},
[b3.id]: {
id: b3.id,
name: b3.name,
logoUrl: "https://assets.relay.link/icons/square/8333/light.png",
type: ChainType.EVM,
nativeRequired: parseEther("0.0001"),
canDepositNative: true,
defaultToken: getEthToken(b3.id),
nativeToken: getEthToken(b3.id),
viem: getCustomEvmChain(
b3,
"https://late-dimensional-yard.b3-mainnet.quiknode.pro/461dbdbd44158cd7a7a764a58ffb01a67eef77f2/"
),
pollingInterval: 1000, // 1 second for B3
zapperEnum: "B3_MAINNET",
coingeckoName: "b3"
}
};
export const EVM_TESTNET: Record<number, IEVMChain> = {
[sepolia.id]: {
id: sepolia.id,
name: sepolia.name,
logoUrl: "https://assets.relay.link/icons/square/1/light.png",
type: ChainType.EVM,
nativeRequired: parseEther("0.00001"),
canDepositNative: true,
defaultToken: getEthToken(sepolia.id),
nativeToken: getEthToken(sepolia.id),
viem: sepolia,
pollingInterval: 1000 // 1 second for Sepolia
},
[baseSepolia.id]: {
id: baseSepolia.id,
name: baseSepolia.name,
logoUrl: "https://assets.relay.link/icons/square/8453/light.png",
type: ChainType.EVM,
nativeRequired: parseEther("0.00001"),
canDepositNative: true,
defaultToken: getEthToken(baseSepolia.id),
nativeToken: getEthToken(baseSepolia.id),
viem: baseSepolia,
pollingInterval: 1000 // 1 second for Base Sepolia
},
[b3Sepolia.id]: {
id: b3Sepolia.id,
name: b3Sepolia.name,
logoUrl: "https://assets.relay.link/icons/square/8333/light.png",
type: ChainType.EVM,
nativeRequired: parseEther("0.00001"),
canDepositNative: true,
defaultToken: getEthToken(b3Sepolia.id),
nativeToken: getEthToken(b3Sepolia.id),
viem: b3Sepolia,
pollingInterval: 1000 // 1 second for B3 Sepolia
}
// [b4testnet.id]: {
// id: b4testnet.id,
// logoUrl: "https://cdn.b3.fun/b4-logo.png",
// type: ChainType.EVM,
// viem: b4testnet,
// requireNativeBalance: parseEther("0.00001"),
// supportDepositNative: true,
// },
};
export const SOLANA_MAINNET: ISolanaChain = {
id: RELAY_SOLANA_MAINNET_CHAIN_ID,
name: "Solana",
logoUrl: "https://assets.relay.link/icons/square/792703809/light.png",
type: ChainType.SOLANA,
nativeRequired: BigInt(10000000), // 0.01 SOL
canDepositNative: true,
defaultToken: getSolanaToken(),
nativeToken: getSolanaToken()
};
export const EVM_CHAINS: Record<number, IEVMChain> = { ...EVM_MAINNET, ...EVM_TESTNET };
export const SOLANA_CHAINS: Record<number, ISolanaChain> = { [RELAY_SOLANA_MAINNET_CHAIN_ID]: SOLANA_MAINNET };
export const ALL_CHAINS: Record<number, IBaseChain> = { ...EVM_CHAINS, ...SOLANA_CHAINS };
export function getSolanaChains(network: "mainnet" | "testnet"): ISolanaChain {
invariant(network === "mainnet", "Solana chain is only supported on mainnet");
return SOLANA_MAINNET;
}
export function getAllEvmChains(network: "mainnet" | "testnet"): Record<number, IEVMChain> {
return network === "mainnet" ? EVM_MAINNET : EVM_TESTNET;
}
export function getChainType(chainId: number): ChainType {
invariant(ALL_CHAINS[chainId], `Chain ${chainId} is not supported`);
return ALL_CHAINS[chainId].type;
}
export function chainIdToPublicClient(chainId: number): PublicClient {
invariant(EVM_CHAINS[chainId], `Chain ${chainId} is not an EVM chain`);
return createPublicClient({
chain: EVM_CHAINS[chainId].viem,
transport: http(),
pollingInterval: EVM_CHAINS[chainId].pollingInterval
});
}
export function chainIdToWalletClient(chainId: number, account?: Account): WalletClient<Transport, Chain> {
invariant(EVM_CHAINS[chainId], `Chain ${chainId} is not an EVM chain`);
return createWalletClient({
chain: EVM_CHAINS[chainId].viem,
transport: http(),
account,
pollingInterval: EVM_CHAINS[chainId].pollingInterval
});
}
export function getNativeRequired(chainId: number): bigint {
invariant(ALL_CHAINS[chainId], `Chain ${chainId} is not supported`);
return ALL_CHAINS[chainId].nativeRequired;
}
export function canDepositNative(chainId: number): boolean {
invariant(ALL_CHAINS[chainId], `Chain ${chainId} is not supported`);
return ALL_CHAINS[chainId].canDepositNative;
}
export function isMainnet(chainId: number): boolean {
return EVM_MAINNET[chainId] !== undefined || RELAY_SOLANA_MAINNET_CHAIN_ID === chainId;
}
export function isTestnet(chainId: number): boolean {
return EVM_TESTNET[chainId] !== undefined;
}
export function getDefaultToken(chainId: number): Token {
invariant(ALL_CHAINS[chainId], `Chain ${chainId} is not supported`);
return ALL_CHAINS[chainId].defaultToken;
}
export function getChainName(chainId: number): string {
invariant(ALL_CHAINS[chainId], `Chain ${chainId} is not supported`);
return EVM_CHAINS[chainId] ? EVM_CHAINS[chainId].viem.name : "Solana";
}
export function getPaymentUrl(address: string, amount: bigint, currency: string) {
if (currency === "ETH") {
return `ethereum:${address}?value=${amount.toString()}`;
}
return `ethereum:${address}`;
}
export function getExplorerTxUrl(chainId: number, txHash: string) {
if (EVM_CHAINS[chainId]) {
return EVM_CHAINS[chainId].viem.blockExplorers?.default.url + "/tx/" + txHash;
}
return "https://solscan.io/tx/" + txHash;
}
export function getExplorerAddressUrl(chainId: number, address: string) {
if (EVM_CHAINS[chainId]) {
return EVM_CHAINS[chainId].viem.blockExplorers?.default.url + "/address/" + address;
}
return "https://solscan.io/account/" + address;
}
export function getMulticall3Address(chainId: number): string {
const chainType = getChainType(chainId);
invariant(chainType === ChainType.EVM, "chainType must be EVM");
const multicall3 = EVM_CHAINS[chainId].viem.contracts?.multicall3;
invariant(multicall3, `multicall3 of chain ${chainId} undefined`);
return multicall3.address;
}
export function getNativeToken(chainId: number): Token {
invariant(ALL_CHAINS[chainId], `Chain ${chainId} undefined`);
return ALL_CHAINS[chainId].nativeToken;
}
export function isEvmChain(chainId: number): boolean {
return Boolean(EVM_CHAINS[chainId]);
}