@bit-gpt/h402
Version:
BitGPT's 402 open protocol for blockchain-native payments
152 lines • 6.74 kB
JavaScript
import { createSolanaRpc, address, getProgramDerivedAddress, } from "@solana/kit";
import { TOKEN_PROGRAM_ADDRESS } from "@solana-program/token";
import { TOKEN_2022_PROGRAM_ADDRESS } from "@solana-program/token-2022";
import { NATIVE_SOL_DECIMALS } from "./index.js";
import { getFacilitator } from "../next.js";
/**
* Convert a hex string to Uint8Array
* Browser-compatible replacement for Buffer.from(hex, 'hex')
*/
function hexToUint8Array(hexString) {
// Remove '0x' prefix if present
const hex = hexString.startsWith("0x") ? hexString.slice(2) : hexString;
// Ensure even length
const len = hex.length;
if (len % 2 !== 0) {
throw new Error(`Invalid hex string length: ${len}`);
}
const bytes = new Uint8Array(len / 2);
for (let i = 0; i < len; i += 2) {
bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
}
return bytes;
}
// Metadata program ID and address
const TOKEN_METADATA_PROGRAM_ID = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
const TOKEN_METADATA_PROGRAM_ADDRESS = address(TOKEN_METADATA_PROGRAM_ID);
// Well-known token addresses and their symbols
const WELL_KNOWN_TOKENS = {
EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v: "USDC", // USDC (Circle)
};
/**
* Get the number of decimals for a token
* For native SOL, returns 9
* For SPL tokens, fetches the mint info
*/
export async function getTokenDecimals(tokenAddress) {
// Special case for native SOL (empty string or special zero address)
if (!tokenAddress || tokenAddress === "11111111111111111111111111111111") {
return NATIVE_SOL_DECIMALS;
}
try {
const rpc = createSolanaRpc(`${getFacilitator()}/solana-rpc`);
const { value: mintInfo } = await rpc
.getAccountInfo(address(tokenAddress), {
encoding: "jsonParsed",
})
.send();
if (!mintInfo || !mintInfo.data) {
throw new Error(`Token account not found for ${tokenAddress}`);
}
const parsedData = mintInfo.data;
if (!("parsed" in parsedData) || parsedData.parsed.type !== "mint") {
throw new Error(`Account ${tokenAddress} is not a mint`);
}
return parsedData.parsed.info.decimals;
}
catch (error) {
throw new Error(`Failed to get decimals for token ${tokenAddress}: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Get the symbol for a token
* For native SOL, returns "SOL"
* For SPL tokens, attempts to fetch from token metadata program
*/
async function findMetadataAddress(mint) {
// Derive the metadata account address using Metaplex's Token Metadata Program
const [metadataAddress] = await getProgramDerivedAddress({
programAddress: TOKEN_METADATA_PROGRAM_ADDRESS,
seeds: [
// Use TextEncoder for string to bytes conversion instead of Buffer
new TextEncoder().encode("metadata"),
// For hex strings, convert to Uint8Array manually
hexToUint8Array(TOKEN_METADATA_PROGRAM_ID),
hexToUint8Array(mint),
],
});
return metadataAddress.toString();
}
export async function getTokenSymbol(tokenAddress) {
// Special case for native SOL
if (tokenAddress === "11111111111111111111111111111111") {
return "SOL";
}
try {
const rpc = createSolanaRpc(`${getFacilitator()}/solana-rpc`);
// First verify it's a mint account
const { value: mintInfo } = await rpc
.getAccountInfo(address(tokenAddress), {
encoding: "jsonParsed",
})
.send();
if (!mintInfo || !mintInfo.data) {
throw new Error(`Token account not found for ${tokenAddress}`);
}
const parsedData = mintInfo.data;
if (!("parsed" in parsedData) || parsedData.parsed.type !== "mint") {
throw new Error(`Account ${tokenAddress} is not a mint`);
}
// Check if this is a Token 2022 or regular SPL token
const isToken2022 = mintInfo.owner === TOKEN_2022_PROGRAM_ADDRESS;
const isRegularToken = mintInfo.owner === TOKEN_PROGRAM_ADDRESS;
console.log(`[getTokenSymbol] Token type - Token2022: ${isToken2022}, Regular SPL: ${isRegularToken}`);
if (!isToken2022 && !isRegularToken) {
throw new Error(`Account ${tokenAddress} is not owned by a token program`);
}
if (isToken2022) {
console.log("[getTokenSymbol] Processing Token 2022 metadata");
// For Token 2022, metadata is stored in the mint account's extension data
const parsedData = mintInfo.data;
if (!("parsed" in parsedData) || !parsedData.parsed.info) {
console.log("[getTokenSymbol] No parsed data found in Token 2022 mint account");
return undefined;
}
// Extract symbol from Token 2022 metadata extension
const symbol = parsedData.parsed.info.symbol;
console.log(`[getTokenSymbol] Token 2022 symbol found: ${symbol}`);
return symbol || undefined;
}
else {
console.log("[getTokenSymbol] Processing regular SPL token metadata");
// For regular SPL tokens, try the metadata program
const metadataAddress = await findMetadataAddress(tokenAddress);
console.log(`[getTokenSymbol] Derived metadata address: ${metadataAddress}`);
const { value: metadataInfo } = await rpc
.getAccountInfo(address(metadataAddress), {
encoding: "jsonParsed",
})
.send();
if (!metadataInfo || !metadataInfo.data) {
console.log("[getTokenSymbol] No metadata account found at derived address");
return undefined; // No metadata account found
}
console.log("[getTokenSymbol] Metadata account found:", metadataInfo);
const metadataData = metadataInfo.data;
if (!("parsed" in metadataData) || !metadataData.parsed.info) {
return undefined;
}
return metadataData.parsed.info.symbol || undefined;
}
}
catch (error) {
console.warn(`Failed to get symbol for token ${tokenAddress}: ${error instanceof Error ? error.message : String(error)}`);
// If on-chain metadata retrieval fails, fallback to hard-coded values
if (WELL_KNOWN_TOKENS[tokenAddress]) {
console.log(`[getTokenSymbol] Using hardcoded symbol for ${tokenAddress}: ${WELL_KNOWN_TOKENS[tokenAddress]}`);
return WELL_KNOWN_TOKENS[tokenAddress];
}
return undefined;
}
}
//# sourceMappingURL=tokenMetadata.js.map