gpt4 book ai didi

javascript - 如何通过 '@solana/web3.js' 和 '@solana/sol-wallet-adapter' 传输自定义 SPL token

转载 作者:行者123 更新时间:2023-12-05 00:24:45 79 4
gpt4 key购买 nike

我正在尝试使用 solana-wallet 适配器传输自定义 SPL token 。但是,我无法获取钱包的 key /签署交易。
我已经查看了这些用于编写传输代码的答案,但我需要获取签名者并且我无法弄清楚如何使用 solana-wallet 适配器:
How can you transfer SOL using the web3.js sdk for Solana?
How to transfer custom token by '@solana/web3.js'
这些示例对 key 进行硬编码,因为我使用的是钱包扩展,所以这是不可能的。
根据 webadapter repo https://github.com/solana-labs/wallet-adapter/issues/120 上的这个问题你需要:

  • 创建@solana/web3.js 事务对象并向其添加指令
  • 用钱包签名交易
  • 通过连接发送事务

  • 但是我很难找到关于如何执行第 1 步和第 2 步的示例或文档。
    const SendTransaction: React.FC<Props> = ({ children }) => {
    const { connection } = useConnection()
    const { publicKey, sendTransaction } = useWallet()

    const onSendSPLTransaction = useCallback(
    async (toPubkey: string, amount: number) => {
    if (!toPubkey || !amount) return
    const toastId = toast.loading('Processing transaction...')

    try {
    if (!publicKey) throw new WalletNotConnectedError()
    const toPublicKey = new PublicKey(toPubkey)
    const mint = new PublicKey('Mint address')
    const payer = '????' // how to get this Signer
    const token = new Token(connection, mint, TOKEN_PROGRAM_ID, payer)
    const fromTokenAccount = await token.getOrCreateAssociatedAccountInfo(publicKey)
    const toTokenAccount = await token.getOrCreateAssociatedAccountInfo(toPublicKey)

    const transaction = new Transaction().add(
    Token.createTransferInstruction(
    TOKEN_PROGRAM_ID,
    fromTokenAccount.address,
    toTokenAccount.address,
    publicKey,
    [],
    0
    )
    )

    const signature = await sendTransaction(transaction, connection)

    const response = await connection.confirmTransaction(signature, 'processed')
    console.log('response', response)
    toast.success('Transaction sent', {
    id: toastId,
    })
    } catch (error) {
    toast.error(`Transaction failed: ${error.message}`, {
    id: toastId,
    })
    }
    },
    [publicKey, sendTransaction, connection]
    )

    return <>{children(onSendSPLTransaction)}</>
    }

    最佳答案

    所以我找到了一种方法来做到这一点,它需要一些清理和错误处理,但允许通过 @solana/wallet-adapter 进行自定义 token 事务.

    // sendTransaction.tsx
    import { WalletNotConnectedError } from '@solana/wallet-adapter-base'
    import { useConnection, useWallet } from '@solana/wallet-adapter-react'
    import { Transaction, PublicKey, LAMPORTS_PER_SOL } from '@solana/web3.js'
    import React, { useCallback } from 'react'
    import { toast } from 'react-hot-toast'
    import { TOKEN_PROGRAM_ID } from '@solana/spl-token'
    import { getOrCreateAssociatedTokenAccount } from './getOrCreateAssociatedTokenAccount'
    import { createTransferInstruction } from './createTransferInstructions'

    interface Props {
    children: (sendTransaction: OnSendTransaction) => React.ReactNode
    }

    type OnSendTransaction = (toPublicKey: string, amount: number) => void

    // Docs: https://github.com/solana-labs/solana-program-library/pull/2539/files
    // https://github.com/solana-labs/wallet-adapter/issues/189
    // repo: https://github.com/solana-labs/example-token/blob/v1.1/src/client/token.js
    // creating a token for testing: https://learn.figment.io/tutorials/sol-mint-token
    const SendTransaction: React.FC<Props> = ({ children }) => {
    const { connection } = useConnection()
    const { publicKey, signTransaction, sendTransaction } = useWallet()

    const onSendSPLTransaction = useCallback(
    async (toPubkey: string, amount: number) => {
    if (!toPubkey || !amount) return
    const toastId = toast.loading('Processing transaction...')

    try {
    if (!publicKey || !signTransaction) throw new WalletNotConnectedError()
    const toPublicKey = new PublicKey(toPubkey)
    const mint = new PublicKey('MINT ADDRESS')

    const fromTokenAccount = await getOrCreateAssociatedTokenAccount(
    connection,
    publicKey,
    mint,
    publicKey,
    signTransaction
    )

    const toTokenAccount = await getOrCreateAssociatedTokenAccount(
    connection,
    publicKey,
    mint,
    toPublicKey,
    signTransaction
    )

    const transaction = new Transaction().add(
    createTransferInstruction(
    fromTokenAccount.address, // source
    toTokenAccount.address, // dest
    publicKey,
    amount * LAMPORTS_PER_SOL,
    [],
    TOKEN_PROGRAM_ID
    )
    )

    const blockHash = await connection.getRecentBlockhash()
    transaction.feePayer = await publicKey
    transaction.recentBlockhash = await blockHash.blockhash
    const signed = await signTransaction(transaction)

    await connection.sendRawTransaction(signed.serialize())

    toast.success('Transaction sent', {
    id: toastId,
    })
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    } catch (error: any) {
    toast.error(`Transaction failed: ${error.message}`, {
    id: toastId,
    })
    }
    },
    [publicKey, sendTransaction, connection]
    )

    return <>{children(onSendSPLTransaction)}</>
    }

    export default SendTransaction

    // getOrCreateAssociatedTokenAccount.ts
    import { TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID } from '@solana/spl-token'
    import { SignerWalletAdapterProps } from '@solana/wallet-adapter-base'
    import { Connection, PublicKey, Commitment, Transaction } from '@solana/web3.js'
    import { createAssociatedTokenAccountInstruction } from './createAssociatedTokenAccountInstruction'
    import { getAccountInfo } from './getAccountInfo'
    import { getAssociatedTokenAddress } from './getAssociatedTokerAddress'

    export async function getOrCreateAssociatedTokenAccount(
    connection: Connection,
    payer: PublicKey,
    mint: PublicKey,
    owner: PublicKey,
    signTransaction: SignerWalletAdapterProps['signTransaction'],
    allowOwnerOffCurve = false,
    commitment?: Commitment,
    programId = TOKEN_PROGRAM_ID,
    associatedTokenProgramId = ASSOCIATED_TOKEN_PROGRAM_ID
    ) {
    const associatedToken = await getAssociatedTokenAddress(
    mint,
    owner,
    allowOwnerOffCurve,
    programId,
    associatedTokenProgramId
    )

    // This is the optimal logic, considering TX fee, client-side computation, RPC roundtrips and guaranteed idempotent.
    // Sadly we can't do this atomically.
    let account
    try {
    account = await getAccountInfo(connection, associatedToken, commitment, programId)
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    } catch (error: any) {
    // TokenAccountNotFoundError can be possible if the associated address has already received some lamports,
    // becoming a system account. Assuming program derived addressing is safe, this is the only case for the
    // TokenInvalidAccountOwnerError in this code path.
    if (error.message === 'TokenAccountNotFoundError' || error.message === 'TokenInvalidAccountOwnerError') {
    // As this isn't atomic, it's possible others can create associated accounts meanwhile.
    try {
    const transaction = new Transaction().add(
    createAssociatedTokenAccountInstruction(
    payer,
    associatedToken,
    owner,
    mint,
    programId,
    associatedTokenProgramId
    )
    )

    const blockHash = await connection.getRecentBlockhash()
    transaction.feePayer = await payer
    transaction.recentBlockhash = await blockHash.blockhash
    const signed = await signTransaction(transaction)

    const signature = await connection.sendRawTransaction(signed.serialize())

    await connection.confirmTransaction(signature)
    } catch (error: unknown) {
    // Ignore all errors; for now there is no API-compatible way to selectively ignore the expected
    // instruction error if the associated account exists already.
    }

    // Now this should always succeed
    account = await getAccountInfo(connection, associatedToken, commitment, programId)
    } else {
    throw error
    }
    }

    if (!account.mint.equals(mint.toBuffer())) throw Error('TokenInvalidMintError')
    if (!account.owner.equals(owner.toBuffer())) throw new Error('TokenInvalidOwnerError')

    return account
    }
    // createAssociatedTokenAccountInstruction.ts
    import { TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID } from '@solana/spl-token'
    import { PublicKey, TransactionInstruction, SystemProgram, SYSVAR_RENT_PUBKEY } from '@solana/web3.js'

    export function createAssociatedTokenAccountInstruction(
    payer: PublicKey,
    associatedToken: PublicKey,
    owner: PublicKey,
    mint: PublicKey,
    programId = TOKEN_PROGRAM_ID,
    associatedTokenProgramId = ASSOCIATED_TOKEN_PROGRAM_ID
    ): TransactionInstruction {
    const keys = [
    { pubkey: payer, isSigner: true, isWritable: true },
    { pubkey: associatedToken, isSigner: false, isWritable: true },
    { pubkey: owner, isSigner: false, isWritable: false },
    { pubkey: mint, isSigner: false, isWritable: false },
    { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
    { pubkey: programId, isSigner: false, isWritable: false },
    { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
    ]

    return new TransactionInstruction({
    keys,
    programId: associatedTokenProgramId,
    data: Buffer.alloc(0),
    })
    }

    // createTransferInstructions.ts
    import { TOKEN_PROGRAM_ID } from '@solana/spl-token'
    import { AccountMeta, PublicKey, Signer, TransactionInstruction } from '@solana/web3.js'
    import BufferLayout from 'buffer-layout'
    import BN from 'bn.js'

    export enum TokenInstruction {
    InitializeMint = 0,
    InitializeAccount = 1,
    InitializeMultisig = 2,
    Transfer = 3,
    Approve = 4,
    Revoke = 5,
    SetAuthority = 6,
    MintTo = 7,
    Burn = 8,
    CloseAccount = 9,
    FreezeAccount = 10,
    ThawAccount = 11,
    TransferChecked = 12,
    ApproveChecked = 13,
    MintToChecked = 14,
    BurnChecked = 15,
    InitializeAccount2 = 16,
    SyncNative = 17,
    InitializeAccount3 = 18,
    InitializeMultisig2 = 19,
    InitializeMint2 = 20,
    }

    /**
    * Construct a Transfer instruction
    *
    * @param source Source account
    * @param destination Destination account
    * @param owner Owner of the source account
    * @param amount Number of tokens to transfer
    * @param multiSigners Signing accounts if `owner` is a multisig
    * @param programId SPL Token program account
    *
    * @return Instruction to add to a transaction
    */
    export function createTransferInstruction(
    source: PublicKey,
    destination: PublicKey,
    owner: PublicKey,
    amount: number,
    multiSigners: Signer[] = [],
    programId = TOKEN_PROGRAM_ID
    ): TransactionInstruction {
    const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction'), BufferLayout.blob(8, 'amount')])

    const keys = addSigners(
    [
    { pubkey: source, isSigner: false, isWritable: true },
    { pubkey: destination, isSigner: false, isWritable: true },
    ],
    owner,
    multiSigners
    )

    const data = Buffer.alloc(dataLayout.span)
    dataLayout.encode(
    {
    instruction: TokenInstruction.Transfer,
    amount: new TokenAmount(amount).toBuffer(),
    },
    data
    )

    return new TransactionInstruction({ keys, programId, data })
    }

    export function addSigners(keys: AccountMeta[], ownerOrAuthority: PublicKey, multiSigners: Signer[]): AccountMeta[] {
    if (multiSigners.length) {
    keys.push({ pubkey: ownerOrAuthority, isSigner: false, isWritable: false })
    for (const signer of multiSigners) {
    keys.push({ pubkey: signer.publicKey, isSigner: true, isWritable: false })
    }
    } else {
    keys.push({ pubkey: ownerOrAuthority, isSigner: true, isWritable: false })
    }
    return keys
    }

    class TokenAmount extends BN {
    /**
    * Convert to Buffer representation
    */
    toBuffer(): Buffer {
    const a = super.toArray().reverse()
    const b = Buffer.from(a)
    if (b.length === 8) {
    return b
    }

    if (b.length >= 8) {
    throw new Error('TokenAmount too large')
    }

    const zeroPad = Buffer.alloc(8)
    b.copy(zeroPad)
    return zeroPad
    }

    /**
    * Construct a TokenAmount from Buffer representation
    */
    static fromBuffer(buffer: Buffer): TokenAmount {
    if (buffer.length !== 8) {
    throw new Error(`Invalid buffer length: ${buffer.length}`)
    }

    return new BN(
    [...buffer]
    .reverse()
    .map((i) => `00${i.toString(16)}`.slice(-2))
    .join(''),
    16
    )
    }
    }
    // getAccountInfo.ts
    import { TOKEN_PROGRAM_ID, AccountLayout } from '@solana/spl-token'
    import { Connection, PublicKey, Commitment } from '@solana/web3.js'

    export enum AccountState {
    Uninitialized = 0,
    Initialized = 1,
    Frozen = 2,
    }

    export async function getAccountInfo(
    connection: Connection,
    address: PublicKey,
    commitment?: Commitment,
    programId = TOKEN_PROGRAM_ID
    ) {
    const info = await connection.getAccountInfo(address, commitment)
    if (!info) throw new Error('TokenAccountNotFoundError')
    if (!info.owner.equals(programId)) throw new Error('TokenInvalidAccountOwnerError')
    if (info.data.length != AccountLayout.span) throw new Error('TokenInvalidAccountSizeError')

    const rawAccount = AccountLayout.decode(Buffer.from(info.data))

    return {
    address,
    mint: rawAccount.mint,
    owner: rawAccount.owner,
    amount: rawAccount.amount,
    delegate: rawAccount.delegateOption ? rawAccount.delegate : null,
    delegatedAmount: rawAccount.delegatedAmount,
    isInitialized: rawAccount.state !== AccountState.Uninitialized,
    isFrozen: rawAccount.state === AccountState.Frozen,
    isNative: !!rawAccount.isNativeOption,
    rentExemptReserve: rawAccount.isNativeOption ? rawAccount.isNative : null,
    closeAuthority: rawAccount.closeAuthorityOption ? rawAccount.closeAuthority : null,
    }
    }

    // getAssociatedTokerAddress.ts
    import { TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID } from '@solana/spl-token'
    import { PublicKey } from '@solana/web3.js'

    export async function getAssociatedTokenAddress(
    mint: PublicKey,
    owner: PublicKey,
    allowOwnerOffCurve = false,
    programId = TOKEN_PROGRAM_ID,
    associatedTokenProgramId = ASSOCIATED_TOKEN_PROGRAM_ID
    ): Promise<PublicKey> {
    if (!allowOwnerOffCurve && !PublicKey.isOnCurve(owner.toBuffer())) throw new Error('TokenOwnerOffCurveError')

    const [address] = await PublicKey.findProgramAddress(
    [owner.toBuffer(), programId.toBuffer(), mint.toBuffer()],
    associatedTokenProgramId
    )

    return address
    }
    希望这对其他人有帮助。如果有人有任何意见指针,请发表评论。

    关于javascript - 如何通过 '@solana/web3.js' 和 '@solana/sol-wallet-adapter' 传输自定义 SPL token ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70224185/

    79 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com