连接钱包
创建Context组件
import React, { FC, ReactNode, useMemo } from 'react'
import { ConnectionProvider, WalletProvider } from '@solana/wallet-adapter-react'
import { WalletModalProvider } from '@solana/wallet-adapter-react-ui'
import * as walletAdapterWallets from '@solana/wallet-adapter-wallets'
import { clusterApiUrl } from '@solana/web3.js'
require('@solana/wallet-adapter-react-ui/styles.css')
const SolanaContextProvider: FC<{ children: ReactNode }> = ({ children }) => {
const endpoint = clusterApiUrl(SOLANA_CLUSTER)
const wallets = useMemo(() => {
return [new walletAdapterWallets.PhantomWalletAdapter()]
}, [])
return (
<ConnectionProvider endpoint={endpoint}>
<WalletProvider wallets={wallets} autoConnect={true}>
<WalletModalProvider>{children}</WalletModalProvider>
</WalletProvider>
</ConnectionProvider>
)
}
export default SolanaContextProvider
使用上述Context包裹组件树。
显示钱包弹窗
官方的wallet-adapter-react-ui库中已经封装了连接钱包的逻辑,因此实现钱包连接功能时,我们只需要显示钱包弹窗即可。
import { useWalletModal } from '@solana/wallet-adapter-react-ui'
const ShowWalletModalButton = () => {
const { setVisible } = useWalletModal()
const showModal = () => {
setVisible(true)
}
return <button onClick={showModal}>Connect</button>
}
Token相关
查询SOL余额
import { Connection, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js'
const getSolBalance = async (connection, address: string) => {
const publicKey = new PublicKey(address)
const response = await connection.getBalance(publicKey)
return response / LAMPORTS_PER_SOL
}
查询非原生代币余额
单次查询
getTokenAccountBalance会返回一个 SPL 代币帐户的余额。
import { Connection } from '@solana/web3.js'
async function getSolanaTokenBalance(connection: Connection, tokenAccount: PublicKey) {
const info = await connection.getTokenAccountBalance(tokenAccount)
return info?.value
}
批量查询
export const getMultipleTokenAccountsBalance = async (addresses: string[], mint: string) => {
const createGetTokenAccountBalanceParams = (address: string) => {
return {
jsonrpc: '2.0',
id: 1,
method: 'getTokenAccountBalance',
params: [
address,
{
commitment: 'confirmed',
},
],
}
}
const atas = await Promise.allSettled(
addresses.map((addr) => getAssociatedTokenAddress(new PublicKey(mint), new PublicKey(addr))),
).then((results) =>
results.map((result) => {
if (result.status === 'fulfilled') {
return result.value
} else {
return null
}
}),
)
const params = atas.map((ata) => createGetTokenAccountBalanceParams(ata.toString()))
const response = await fetch(clusterApiUrl(SOLANA_CLUSTER), {
method: 'post',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(params),
}).then((res) => res.json())
return response
}
对信息进行签名
import { useWallet } from '@solana/wallet-adapter-react'
import { bs58 } from '@coral-xyz/anchor/dist/cjs/utils/bytes'
export const SignMessage = () => {
const wallet = useWallet()
const signMsg = async (message: string) => {
if (!wallet) {
return Promise.reject(new Error('No Solana wallet connected'))
}
const encodedMessage = new TextEncoder().encode(message)
const signature = await wallet?.signMessage(encodedMessage)
if (!signature) {
return Promise.reject(new Error('Failed to sign message'))
}
const decodedSignature = bs58.encode(signature)
return decodedSignature
}
return <button onClick={() => {signMsg('12345')}}>Sign Message</button>
}
调用合约
获取Anchor provider
import { AnchorProvider, Provider, getProvider, setProvider } from '@coral-xyz/anchor'
import { useAnchorWallet, useConnection } from '@solana/wallet-adapter-react'
import { useMemo } from 'react'
const useAnchorProvider = (withWallet = true) => {
const { connection } = useConnection()
const wallet = useAnchorWallet()
return useMemo(() => {
let provider: Provider
try {
if (!withWallet) {
provider = getProvider()
} else {
provider = new AnchorProvider(connection, wallet, {})
setProvider(provider)
}
} catch {
provider = new AnchorProvider(connection, wallet, {})
setProvider(provider)
}
return provider
}, [connection, wallet, withWallet])
}
export default useAnchorProvider
初始时,getProvider()获取到的provider比new AnchorProvider()获取到的provider少了字段,所以默认使用后者获取provider。
配置IDL
IDL(Interface Definition Language)定义了一个program的公共接口。
使用形如以下代码的IDL才能在调用program时获得类型提示。
export type XxxIdl = {}
export const XXX_IDL: XxxIdl = {}
获取Program实例
import { Program } from '@coral-xyz/anchor'
import useAnchorProvider from './useAnchorProvider'
const useXxxProgram = () => {
const provider = useAnchorProvider()
const program = new Program(XXX_IDL, XxxProgramId, provider)
return program
}
export default useXxxProgram
查找account
Solana的链上数据存储在各种account中,调用Solana合约时常常需要传入相关的account。
查找PDA
PDA: Program Derived Addresse
import { PublicKey } from '@solana/web3.js'
const [programAddress] = PublicKey.findProgramAddressSync([...seeds], ProgramId)
查找ATA
ATA: Associated Token Accounts,存储某个帐户与某个代币的关联数据的帐户。
import { getAssociatedTokenAddress } from '@solana/spl-token'
const associatedTokenAddress = await getAssociatedTokenAddress(mint, account)
获取合约中的数据
假设有一个计数器合约,这个合约有一个字段存储了每个调用者的计数的数据,获取计数数据的逻辑如下。
const counter = await program.account.counter.fetch(address);
其中counter表示要获取的数据的字段,传入fetch()的地址是存储这组数据的PDA,需要通过findProgramAddressSync()计算得到。
调用合约方法
const tx = await program.methods.xxx(args).accounts(accounts).rpc()