UNPKG

yamaswap-sdk

Version:
398 lines (327 loc) 15.6 kB
import * as anchor from "@coral-xyz/anchor"; import { Connection, PublicKey, Transaction } from "@solana/web3.js"; import idl from "../idl/iswap.json"; import type { Iswap } from "../types/iswap"; import { AnchorWallet, ETFCreateParams, ETFBurnParams, MintETFTokenParams, ETFCreateResult, MintETFResult } from "../types/params"; import { deriveEtfTokenMintAccount } from "../utils/getAddress"; import { ETFQueries } from "../utils/queries"; import { createAssociatedTokenAccountInstruction, getAccount, getAssociatedTokenAddressSync, TokenAccountNotFoundError, } from '@solana/spl-token'; import { checkATAExists, checkBalance, checkETFExists, validateETFCreateParams, validateMintETFParams } from '../utils/checks'; const DEFAULT_PROGRAM_ID = "dXgZyuguvD2m5G5385XkdokZBryUoSE6LbNJeWiFiN5"; export class DexClient { public readonly program: anchor.Program<Iswap>; public readonly queries: ETFQueries; private readonly wallet: AnchorWallet; constructor( public connection: Connection, wallet: AnchorWallet, provider: anchor.AnchorProvider, ) { this.connection = connection; this.wallet = wallet; this.program = new anchor.Program(idl as Iswap, provider) this.queries = new ETFQueries(this); } public async createETF(params: ETFCreateParams): Promise<ETFCreateResult> { try { validateETFCreateParams(params); const { name, symbol, description, url, assets } = params; const [etfAddress] = deriveEtfTokenMintAccount(this.program as unknown as anchor.Program, ["etf_token_v3", symbol]); const [etfCoreAddress] = deriveEtfTokenMintAccount(this.program as unknown as anchor.Program, ["etf_token_v3", etfAddress]); const etfInfo = await this.queries.getETFInfo(etfAddress); if (typeof etfInfo !== 'string') { return { success: false, error: 'etf already exists', data: { etfAddress, etfCoreAddress: etfInfo.etfCoreAddress, symbol, description: etfInfo.description, creator: etfInfo.creator, assets: etfInfo.assets } }; } const latestBlockhash = await this.connection.getLatestBlockhash('confirmed'); console.log('get the latest blockhash:', latestBlockhash.blockhash); let tx = new Transaction(); for (const { token } of assets) { const tokenKey = new PublicKey(token); const address = getAssociatedTokenAddressSync(tokenKey, etfCoreAddress, true); try { await getAccount(this.connection, address); } catch (e) { if (e instanceof TokenAccountNotFoundError) { tx.add( createAssociatedTokenAccountInstruction( this.wallet.publicKey, address, etfCoreAddress, tokenKey, ), ); } } } const ix = await this.program.methods .etfCreate({ name, symbol, description, url, assets: assets.map(c => ({ token: new PublicKey(c.token), weight: c.weight })) }) .transaction(); tx.add(ix); tx.recentBlockhash = latestBlockhash.blockhash; tx.feePayer = this.wallet.publicKey; console.log('transaction created:', { recentBlockhash: tx.recentBlockhash, feePayer: tx.feePayer.toBase58(), instructions: tx.instructions.length }); try { const signedTx = await this.wallet.signTransaction(tx); console.log('transaction signed'); const txid = await this.connection.sendRawTransaction(signedTx.serialize(), { skipPreflight: false, preflightCommitment: 'confirmed', maxRetries: 5 }); console.log('transaction sent, signed:', txid); const confirmation = await this.connection.confirmTransaction({ signature: txid, blockhash: latestBlockhash.blockhash, lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, }, 'confirmed'); if (confirmation.value.err) { throw new Error(`transaction confirm failed: ${JSON.stringify(confirmation.value.err)}`); } console.log('transaction confirmed'); // get the confirmed etf info const confirmedEtfInfo = await this.queries.getETFInfo(etfAddress); if (typeof confirmedEtfInfo === 'string') { throw new Error('etf create success but not found etf info'); } return { success: true, txid, data: { etfAddress, etfCoreAddress, symbol, name, description, creator: confirmedEtfInfo.creator, assets: confirmedEtfInfo.assets } }; } catch (error) { console.error("createETF error:", error); throw error; } } catch (error) { console.error("Error details:", error); if (error instanceof Error) { console.error("Error stack:", error.stack); } return { success: false, txid: '', error: error instanceof Error ? error.message : 'meet an unknown error when create ETF' }; } } public async purchaseETF(params: MintETFTokenParams): Promise<MintETFResult> { try { const etfAddress = new PublicKey(params.etfAddress); validateMintETFParams(params); const etfInfo = await checkETFExists(this, etfAddress); // check if the user has the required tokens await checkATAExists(this, etfInfo.assets.map(item => item.token), this.wallet.publicKey); // check if the contract has the required tokens, if the token is pda, need to pass the true await checkATAExists(this, etfInfo.assets.map(item => item.token), etfInfo.etfCoreAddress, true); for (const item of etfInfo.assets) { const requiredAmount = (params.lamports * item.weight) / 10000; await checkBalance(this, item.token, this.wallet.publicKey, requiredAmount); } const latestBlockhash = await this.connection.getLatestBlockhash('confirmed'); console.log('get the latest blockhash:', latestBlockhash.blockhash); const remainingAccounts = etfInfo.assets.flatMap((item) => { const userATA = getAssociatedTokenAddressSync(item.token, this.wallet.publicKey); const contractATA = getAssociatedTokenAddressSync(item.token, etfInfo.etfCoreAddress, true); return [userATA, contractATA]; }); const ix = await this.program.methods .etfMint(new anchor.BN(params.lamports)) .accounts({ etfTokenMintAccount: params.etfAddress, authority: this.wallet.publicKey, }) .remainingAccounts( remainingAccounts.map((item) => ({ pubkey: item, isSigner: false, isWritable: true })), ) .transaction(); const modifyComputeUnits = anchor.web3.ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000, }); let tx = new Transaction().add(ix).add(modifyComputeUnits); tx.recentBlockhash = latestBlockhash.blockhash; tx.feePayer = this.wallet.publicKey; console.log('transaction created:', { recentBlockhash: tx.recentBlockhash, feePayer: tx.feePayer.toBase58(), instructions: tx.instructions.length }); try { const signedTx = await this.wallet.signTransaction(tx); console.log('transaction signed'); // 发送交易 const txid = await this.connection.sendRawTransaction(signedTx.serialize(), { skipPreflight: false, preflightCommitment: 'confirmed', maxRetries: 5 }); console.log('transaction sent, signed:', txid); const confirmation = await this.connection.confirmTransaction({ signature: txid, blockhash: latestBlockhash.blockhash, lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, }, 'confirmed'); if (confirmation.value.err) { throw new Error(`transaction confirm failed: ${JSON.stringify(confirmation.value.err)}`); } console.log('transaction confirmed'); // get the mint tokenata account const mintTokenataAccount = getAssociatedTokenAddressSync(etfAddress, this.wallet.publicKey); const balance = await this.queries.getETFBalance(etfAddress, this.wallet.publicKey); return { success: true, txid, data: { mintTokenataAccount, balance, etfAddress: params.etfAddress } }; } catch (error) { console.error("purchaseETF error:", error); throw error; } } catch (error) { console.error("Error in purchaseETF:"); console.error("Error details:", error); if (error instanceof Error) { console.error("Error name:", error.name); console.error("Error message:", error.message); console.error("Error stack:", error.stack); } if (error instanceof anchor.web3.SendTransactionError) { console.error("Transaction error logs:", error.logs); } return { success: false, txid: '', error: error instanceof Error ? error.message : 'Mint ETF Token 时发生未知错误' }; } } public async burnETF(params: ETFBurnParams): Promise<MintETFResult> { try { const etfAddress = new PublicKey(params.etfAddress); const etfInfo = await checkETFExists(this, etfAddress); const tokensToCheck = [...etfInfo.assets.map(item => item.token), etfAddress]; await checkATAExists(this, tokensToCheck, this.wallet.publicKey); await checkBalance(this, etfAddress, this.wallet.publicKey, params.lamports as number); const latestBlockhash = await this.connection.getLatestBlockhash('confirmed'); console.log('get the latest blockhash:', latestBlockhash.blockhash); const remainingAccounts = etfInfo.assets.flatMap((item) => { const userATA = getAssociatedTokenAddressSync(item.token, this.wallet.publicKey); const contractATA = getAssociatedTokenAddressSync(item.token, etfInfo.etfCoreAddress, true); return [ userATA, contractATA, ]; }); const ix = await this.program.methods .etfBurn(new anchor.BN(params.lamports as number)) .accounts({ etfTokenMintAccount: params.etfAddress, authority: this.wallet.publicKey, }) .remainingAccounts( remainingAccounts.map((item) => ({ pubkey: item, isSigner: false, isWritable: true })), ) .transaction(); const modifyComputeUnits = anchor.web3.ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000, }); let tx = new Transaction().add(ix).add(modifyComputeUnits); tx.recentBlockhash = latestBlockhash.blockhash; tx.feePayer = this.wallet.publicKey; console.log('transaction created:', { recentBlockhash: tx.recentBlockhash, feePayer: tx.feePayer.toBase58(), instructions: tx.instructions.length }); try { const signedTx = await this.wallet.signTransaction(tx); console.log('transaction signed'); const txid = await this.connection.sendRawTransaction(signedTx.serialize(), { skipPreflight: false, preflightCommitment: 'confirmed', maxRetries: 5 }); console.log('transaction sent, signed:', txid); const confirmation = await this.connection.confirmTransaction({ signature: txid, blockhash: latestBlockhash.blockhash, lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, }, 'confirmed'); if (confirmation.value.err) { throw new Error(`transaction confirm failed: ${JSON.stringify(confirmation.value.err)}`); } console.log('transaction confirmed'); const mintTokenataAccount = getAssociatedTokenAddressSync(etfAddress, this.wallet.publicKey); const balance = await this.queries.getETFBalance(etfAddress, this.wallet.publicKey); return { success: true, txid, data: { mintTokenataAccount, balance, etfAddress: params.etfAddress } }; } catch (error) { console.error("burnETF error:", error); throw error; } } catch (error) { console.error("Error in burnETF:"); console.error("Error details:", error); if (error instanceof Error) { console.error("Error name:", error.name); console.error("Error message:", error.message); console.error("Error stack:", error.stack); } if (error instanceof anchor.web3.SendTransactionError) { console.error("Transaction error logs:", error.logs); } return { success: false, txid: '', error: error instanceof Error ? error.message : 'Burn ETF Token 时发生未知错误' }; } } }