小试牛刀-walletconnect二维码及交互

nodcat
12
2025-06-10

Welcome to Code Block's blog

本篇文章主要介绍了

[walletconnect二维码及交互]
❤博主广交技术好友,喜欢文章的可以关注一下❤

1.编写目的

       最近在使用walletconnect协议和typescript语言实现相关交互功能,在此对从walletconnet协议二维码生成连接后发送交易事务签名事务签名任意信息的处理进行记录,加深对walletconnect的理解,熟悉对其组件的使用,同时希望帮助到有实现相关功能的朋友。

2.实现功能

  1. 二维码生成:生成wc:协议二维码供用户扫码连接.

  2. 发送交易事务:向用户发送Transaction以供用户签名.

  3. 签名事务:用户签名后将transaction提交的链上.

  4. 签名任意信息:用户对任意信息签名,同时可以完成对签名信息的验证.

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交互的主要实现类,在开始时要进行初始化,初始化要使用参数分别是metadataprojectId,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_signTransactionsolana_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地址和合约地址以及发送的数量,同时为加快交易的速度,需要设置UnitPriceUnitLimit(即增加一些交易费用来保证用户交易速度),实现代码如下:

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;
    }
}

区块链内容感兴趣可以查看我的专栏:小试牛刀-区块链

感谢您的关注和收藏!!!!!!

6ff05862ca25a4824ca547b76677698d.jpeg

动物装饰