import provider from '../utils/solana'; import * as solanaWeb3 from '@solana/web3.js'; import { getOrCreateAssociatedTokenAccount, transfer as transferToken, getMint, } from '@solana/spl-token'; import { BalancePayload, CreateWalletPayload, GenerateWalletFromMnemonicPayload, GetAddressFromPrivateKeyPayload, GetTransactionPayload, IGetTokenInfoPayload, IResponse, ISplTokenInfo, ITokenInfo, TransferPayload, } from '../utils/types'; import * as bs58 from 'bs58'; import { successResponse } from '../utils'; import * as bip39 from 'bip39'; import { derivePath } from 'ed25519-hd-key'; // @ts-ignore import * as BufferLayout from 'buffer-layout'; import axios from 'axios'; export const ACCOUNT_LAYOUT = BufferLayout.struct([ BufferLayout.blob(32, 'mint'), BufferLayout.blob(32, 'owner'), BufferLayout.nu64('amount'), BufferLayout.blob(93), ]); export const chainId = { 'mainnet-beta': 101, testnet: 102, devnet: 103, }; const getConnection = (rpcUrl?: string) => { const connection = provider(rpcUrl); return connection; }; const createWallet = ({ derivationPath }: CreateWalletPayload) => { const path = derivationPath || "m/44'/501'/0'/0'"; const mnemonic = bip39.generateMnemonic(); const seed = bip39.mnemonicToSeedSync(mnemonic); const derivedSeed = derivePath(path, seed.toString('hex')).key; const keyPair = solanaWeb3.Keypair.fromSeed( (derivedSeed as unknown) as Uint8Array ); return successResponse({ address: keyPair.publicKey.toBase58(), privateKey: bs58.encode(keyPair.secretKey), mnemonic, }); }; const generateWalletFromMnemonic = ({ mnemonic, derivationPath, }: GenerateWalletFromMnemonicPayload): IResponse => { const path = derivationPath || "m/44'/501'/0'/0'"; const seed = bip39.mnemonicToSeedSync(mnemonic); const derivedSeed = derivePath(path, seed.toString('hex')).key; const keyPair = solanaWeb3.Keypair.fromSeed( (derivedSeed as unknown) as Uint8Array ); return successResponse({ address: keyPair.publicKey.toBase58(), privateKey: bs58.encode(keyPair.secretKey), mnemonic, }); }; const getAddressFromPrivateKey = ({ privateKey, }: GetAddressFromPrivateKeyPayload): IResponse => { let secretKey; if (privateKey.split(',').length > 1) { secretKey = new Uint8Array(privateKey.split(',') as any); } else { secretKey = bs58.decode(privateKey); } const keyPair = solanaWeb3.Keypair.fromSecretKey(secretKey, { skipValidation: true, }); return successResponse({ address: keyPair.publicKey.toBase58(), }); }; const getBalance = async (args: BalancePayload): Promise => { const connection = getConnection(args.rpcUrl); try { let balance; if (args.tokenAddress) { const account = await connection.getTokenAccountsByOwner( new solanaWeb3.PublicKey(args.address), { mint: new solanaWeb3.PublicKey(args.tokenAddress), } ); balance = account.value.length > 0 ? ACCOUNT_LAYOUT.decode(account.value[0].account.data).amount : 0; return successResponse({ balance: balance / solanaWeb3.LAMPORTS_PER_SOL, }); } const publicKey = new solanaWeb3.PublicKey(args.address); balance = await connection.getBalance(publicKey); return successResponse({ balance: balance / solanaWeb3.LAMPORTS_PER_SOL, }); } catch (error) { throw error; } }; const transfer = async (args: TransferPayload): Promise => { const connection = getConnection(args.rpcUrl); try { const recipient = new solanaWeb3.PublicKey(args.recipientAddress); let secretKey; let signature; if (args.privateKey.split(',').length > 1) { secretKey = new Uint8Array(args.privateKey.split(',') as any); } else { secretKey = bs58.decode(args.privateKey); } const from = solanaWeb3.Keypair.fromSecretKey(secretKey, { skipValidation: true, }); if (args.tokenAddress) { // Get token mint const mint = await getMint( connection, new solanaWeb3.PublicKey(args.tokenAddress) ); // Get the token account of the from address, and if it does not exist, create it const fromTokenAccount = await getOrCreateAssociatedTokenAccount( connection, from, mint.address, from.publicKey ); // Get the token account of the recipient address, and if it does not exist, create it const recipientTokenAccount = await getOrCreateAssociatedTokenAccount( connection, from, mint.address, recipient ); signature = await transferToken( connection, from, fromTokenAccount.address, recipientTokenAccount.address, from.publicKey, solanaWeb3.LAMPORTS_PER_SOL * args.amount ); } else { const transaction = new solanaWeb3.Transaction().add( solanaWeb3.SystemProgram.transfer({ fromPubkey: from.publicKey, toPubkey: recipient, lamports: solanaWeb3.LAMPORTS_PER_SOL * args.amount, }) ); signature = await solanaWeb3.sendAndConfirmTransaction( connection, transaction, [from] ); } const tx = await connection.getTransaction(signature, { maxSupportedTransactionVersion: 0, }); return successResponse({ ...tx, }); } catch (error) { throw error; } }; const getTransaction = async ( args: GetTransactionPayload ): Promise => { const connection = getConnection(args.rpcUrl); try { const tx = await connection.getTransaction(args.hash, { maxSupportedTransactionVersion: 0, }); return successResponse({ ...tx, }); } catch (error) { throw error; } }; const getTokenInfo = async (args: IGetTokenInfoPayload): Promise => { try { const connection = getConnection(args.rpcUrl); const tokenList = await getTokenList(args.cluster!); const token = tokenList.find(token => token.address === args.address); if (!token) { throw new Error('Token not found'); } const data: ITokenInfo = { name: token.name, symbol: token.symbol, address: token.address, decimals: token.decimals, logoUrl: token.logoURI, totalSupply: '0', }; const tokenSupply = await connection.getTokenSupply( new solanaWeb3.PublicKey(data.address) ); data.totalSupply = tokenSupply.value.uiAmount!.toString(); return successResponse({ ...data }); } catch (error) { throw error; } }; const getTokenList = async ( cluster: 'mainnet-beta' | 'testnet' | 'devnet' ): Promise => { const tokenListUrl = 'https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/solana.tokenlist.json'; const response = await axios.get(tokenListUrl); if (response.data && response.data.tokens) { return response.data.tokens.filter( (data: ISplTokenInfo) => data.chainId === chainId[cluster] ); } return []; }; export default { getBalance, createWallet, generateWalletFromMnemonic, transfer, getAddressFromPrivateKey, getTransaction, getTokenInfo, };