目录
Welcome to Code Block's blog
本篇文章主要介绍了
[walletconnect二维码及交互]
❤博主广交技术好友,喜欢文章的可以关注一下❤
1.编写目的
最近在使用walletconnect协议和typescript语言实现相关交互功能,在此对从walletconnet协议二维码生成、连接后发送交易事务、签名事务、签名任意信息的处理进行记录,加深对walletconnect的理解,熟悉对其组件的使用,同时希望帮助到有实现相关功能的朋友。
2.实现功能
- 二维码生成:生成wc:协议二维码供用户扫码连接.
- 发送交易事务:向用户发送Transaction以供用户签名.
- 签名事务:用户签名后将transaction提交的链上.
- 签名任意信息:用户对任意信息签名,同时可以完成对签名信息的验证.
3.功能详解
依赖组件
名称 | 版本 | 作用 |
@solana/web3.js |
1.95.2 |
链上相关操作:生成交易事务,提交事务到链上 |
@walletconnect/sign-client |
2.14.0 |
walletconnect协议相关操作:生成二维码、发送事务 |
solana/spl-token |
0.4.8 |
SPL代币事务操作:生成SPL代币事务 |
qrcode |
1.5.3 |
生成二维码 |
注:这里的链是SOL链,其它链用法类似.SPL代币即除主要代币之外的代币.
3.1 二维码生成
3.1.1 初始化SignClient
SignClient作为与用户wallet交互的主要实现类,在开始时要进行初始化,初始化要使用参数分别是metadata和projectId,metadata是项目相关信息,这些信息会在连接时进行展示.projectId是在walletconnect官网申请的项目ID.代码如下:
const metadata={ //项目名称 name: 'BoggyGame', //项目解释 description: 'BoggyGame Bot', //项目官网 url: 'https://www.boggycoin.com', //项目图片 icons: [ "https://i.postimg.cc/sftPCk3M/photo-2024-07-12-14-12-43.jpg" ] } //项目ID const projectId="0176e783e7c5b0713450333ff866c2d6"
然后就可以对SignClient进行初始化,为保证性能,这里SignClient使用单例,代码如下:
async function getSignClient() { if (!signClient) { signClient = await SignClient.init({ projectId: projectId, // 替换为你的项目ID metadata: metadata }); } return signClient; }
3.1.2 创建会话空间获取WC协议uri
使用signClient进行和中继器的对等连接配对(实际为websocket链接),并获取订阅的主题(topic),然后将会话空间数据上传到对应的主题,即可获取uri和等待授权方法,实现代码如下:
export async function initWalletConnect(onApproval: (approval: any) => void):Promise<String|undefined> { const signClient = await getSignClient() // 创建对等连接获取主题 const {topic} = await signClient.core.pairing.create() // 发送命名空间,获取uri和等待授权的函数 const { uri,approval } = await signClient.connect({ pairingTopic: topic, //空间方法 requiredNamespaces: { solana: { methods: [ "solana_signTransaction", "solana_signMessage", ], chains: [ "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" ], events: [] } } }) // 调用回调函数处理 approval if (approval) { onApproval(approval); } return uri; }
这里存在两个方法solana_signTransaction和solana_signMessage方法,分别是签名交易事务和签名消息,空间内未定义的方法将无法调用.onApproval: (approval: any)用于接收外部传输的监听方法,方便在外部获取approval.
3.1.3 生成二维码供用户扫描
生成二维码可以使用qrcode库,直接将uri的连接字符串生成为二维码,代码如下:
import QRCode from 'qrcode'; export async function generateQR(data: string): Promise<any> { const qrBuffer = await QRCode.toBuffer(data, { width: 300, //宽度和高度 margin: 4 //边框距离 }); return qrBuffer; }
3.1.4 等待扫描
可以使用await approval()方法实现等待扫描授权,授权完成会获得当前连接session,同时可以获得当前连接的account,实现代码如下:
//等待扫码后授权 const session=await approval(); //获取链接的账号 const account=session.namespaces.solana.accounts[0].split(':')[2]; //打印账号 console.log(account)
3.2 发送交易事务
3.2.1 创建交易事务
创建交易事务时需要用到@solana/web3.js库,这里我们创建一个转移SPL代币事务,我们需要两个地址(即发送方和接收方),同时因为是Solana链,所以需要获取这两个账户实际的AssociatedToken地址(即实际存储SPL代币的账户地址),同时需要代币的Mint地址和合约地址以及发送的数量,同时为加快交易的速度,需要设置UnitPrice和UnitLimit(即增加一些交易费用来保证用户交易速度),实现代码如下:
export async function getTransaction( senderPublicKey: string, drawPublicKey: string, tokenAmount: number):Promise<Transaction> { //发送方公钥 const senderPubkey = new PublicKey(senderPublicKey); //接收方公钥 const drawPubkey = new PublicKey(drawPublicKey); //代币MINT地址 const tokenMintAddress = BOGGY_TOKEN_MINT; //获取发送方AssociatedToken账户 const sourceTokenAccount = await getAssociatedTokenAddress(tokenMintAddress,senderPubkey); //获取ACT账户 //获取接收方AssociatedToke账户 const destTokenAccount = await getAssociatedTokenAddress(tokenMintAddress,drawPubkey); //创建转移数据 const transferInstruction = createTransferInstruction( sourceTokenAccount, destTokenAccount, senderPubkey, tokenAmount * 1e9, [], TOKEN_PROGRAM_ID ); // 创建 compute unit price 指令,提高交易速度 const computeUnitPriceInstruction = ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 7500, }); const computeUnitLimitInstruction=ComputeBudgetProgram.setComputeUnitLimit({ units:200000 }) //创建事务并添加上面的三个交易数据信息 const transaction=new Transaction().add(computeUnitPriceInstruction,computeUnitLimitInstruction,transferInstruction) //设置最新的区块hash transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash; //设置交易费用由发送方支出 transaction.feePayer=senderPubkey; return transaction; }
3.2.2 向用户发送交易事务
通过SignClient.request方法可以向用户发送(通过中继器转发)交易事务,并等待用户的签名,实现代码如下:
const transaction=await getTransaction( 发送方地址,接收方地址,代币数量 ) const result = await signClient!.request<{ signature: string }>({ chainId:'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', topic: session!.topic, request: { method: "solana_signTransaction", params: { //付款方地址 feePayer: transaction.feePayer!.toBase58(), //最近区块链hash recentBlockhash: transaction.recentBlockhash, //transaction中数据遍历封装 instructions: transaction.instructions.map((i) => ({ //合约ID programId: i.programId.toBase58(), //数据 data: Array.from(i.data), //发送方和接收方 keys: i.keys.map((k) => ({ isSigner: k.isSigner, isWritable: k.isWritable, pubkey: k.pubkey.toBase58(), })), })), }, }, });
这里因为transaction没有直接转换为walletconnect通信格式的方法,所以需要将transaction中的数据取出重新封装,当然也可以直接封装为walletconnet通信数据格式使用.
3.3 签名事务
3.3.1 接收签名事务并验证
用户签名数据后,即可获得签名后的Signature值,这里需要对返回后的Signature验证是否有效,然后添加到签名到transaction中,实现代码如下:
//添加签名 transaction.addSignature( //签名方即发送方 transaction.feePayer, //获取到的签名信息 Buffer.from(bs58.decode(result.signature)) ); //验证签名是否有效 const valid = transaction.verifySignatures();
3.3.2 发送签名并发送到链上
直接使用sendRawTransaction方法,将数据发送到链上,返回的txId值应该和用户的签名值相同,实现代码如下:
export async function sendTransaction(transaction:Transaction){ const txId =await connection.sendRawTransaction(transaction.serialize()) return txId; }
3.4 签名任意数据并验证
可以使用签名完成任意数据的认证,这种认证主要用于用户登录的确认,如让用户签名一段随机信息,签名有效则可以认为用户完成登录,从而完成用户wallet网站登录,实现代码如下:
const response=await signClient.request({ topic:session.topic, //链ID chainId:'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', request:{ method: 'solana_signMessage', params: { //随机字符串 message:"37u9WtQpcm6ULa3VtWDFAWoQc1hUvybPrA3dtx99tgHvvcE7pKRZjuGmn7VX2tC3JmYDYGG7", pubkey: 链接后用户地址 } } })
签名后可以使用公钥对签名后的数据进行验证,代码如下:
async function verifyMessageSignature(message: string, signature: string, userPublicKeyBase58: string) { try { const connection = new Connection(clusterApiUrl('devnet'), 'confirmed'); // 将 Base58 格式的用户公钥转换为 PublicKey 对象 const userPublicKey = new PublicKey(userPublicKeyBase58); // 将签名数据从 Base58 格式解码为 Uint8Array const signatureBytes = bs58.decode(signature); // 将消息字符串转换为 Uint8Array const messageBytes = new TextEncoder().encode(message); // 使用 PublicKey 对象和消息数据来验证签名 const isSignatureValid = await connection.verifySignature( messageBytes, signatureBytes, userPublicKey ); return isSignatureValid; } catch (error) { console.error('Error verifying signature:', error); return false; } }
对区块链内容感兴趣可以查看我的专栏:小试牛刀-区块链
感谢您的关注和收藏!!!!!!
编辑