前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >web3钱包接入之viem

web3钱包接入之viem

作者头像
Maic
发布于 2025-02-12 06:11:37
发布于 2025-02-12 06:11:37
21303
代码可运行
举报
文章被收录于专栏:Web技术学苑Web技术学苑
运行总次数:3
代码可运行

过去一年,接入了很多第三方钱包,有solana,rainbow,web3Modal【现在是reown】了、aptos相关区块链钱包,第三方钱包接入已经相对非常成熟,API通常来讲非常简易,基本不用考虑更底层的API,常用的hook也会更高效,但通常接入钱包过程中,似乎总有种,知其然而不知所以然的感觉,本文是一篇钱包偏底层的接入流程。

本文主要以evm钱包为例子

首先我们要初步了解viem,我们在项目中,你也许会看到大量的wagmi,实际上这是对viem更上层的封装,wagmi是以太坊交互官方封装便捷好用的react库,让你非常快捷方便的与钱包以及以太坊交互,但本质上所有与以太坊交互的hook依赖于viem

在开始本文之前,你的浏览器得提前安装一个metaMask插件钱包

查看区块block

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 
// test.js
import { createPublicClient, http } from "viem";
import { mainnet } from "viem/chains";
const client = createPublicClient({
  chain: mainnet,
  transport: http(),
});
console.log(client);
const getBlock = async () => {
  const blockNumber = await client.getBlockNumber();
  console.log(blockNumber);
};
getBlock();

当我们运行node test.js

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 
{
  account: undefined,
  batch: undefined,
  cacheTime: 4000,
  ccipRead: undefined,
  chain: {
    formatters: undefined,
    fees: undefined,
    serializers: undefined,
    id: 1,
    name: 'Ethereum',
    nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
    rpcUrls: { default: [Object] },
    blockExplorers: { default: [Object] },
    contracts: {
      ensRegistry: [Object],
      ensUniversalResolver: [Object],
      multicall3: [Object]
    }
  },
  key: 'public',
  name: 'Public Client',
  pollingInterval: 4000,
  request: [AsyncFunction (anonymous)],
  transport: {
    key: 'http',
    name: 'HTTP JSON-RPC',
    request: [AsyncFunction: request],
    retryCount: 3,
    retryDelay: 150,
    timeout: 10000,
    type: 'http',
    fetchOptions: undefined,
    url: 'https://cloudflare-eth.com'
  },
  type: 'publicClient',
  uid: '2523e6fdcf6',
  extend: [Function (anonymous)],
  call: [Function: call],
  createBlockFilter: [Function: createBlockFilter],
  createContractEventFilter: [Function: createContractEventFilter],
  createEventFilter: [Function: createEventFilter],
  createPendingTransactionFilter: [Function: createPendingTransactionFilter],
  estimateContractGas: [Function: estimateContractGas],
  estimateGas: [Function: estimateGas],
  getBalance: [Function: getBalance],
  getBlobBaseFee: [Function: getBlobBaseFee],
  getBlock: [Function: getBlock],
  getBlockNumber: [Function: getBlockNumber],
  getBlockTransactionCount: [Function: getBlockTransactionCount],
  getBytecode: [Function: getBytecode],
  getChainId: [Function: getChainId],
  getCode: [Function: getCode],
  getContractEvents: [Function: getContractEvents],
  getEip712Domain: [Function: getEip712Domain],
  getEnsAddress: [Function: getEnsAddress],
  getEnsAvatar: [Function: getEnsAvatar],
  getEnsName: [Function: getEnsName],
  getEnsResolver: [Function: getEnsResolver],
  getEnsText: [Function: getEnsText],
  getFeeHistory: [Function: getFeeHistory],
  estimateFeesPerGas: [Function: estimateFeesPerGas],
  getFilterChanges: [Function: getFilterChanges],
  getFilterLogs: [Function: getFilterLogs],
  getGasPrice: [Function: getGasPrice],
  getLogs: [Function: getLogs],
  getProof: [Function: getProof],
  estimateMaxPriorityFeePerGas: [Function: estimateMaxPriorityFeePerGas],
  getStorageAt: [Function: getStorageAt],
  getTransaction: [Function: getTransaction],
  getTransactionConfirmations: [Function: getTransactionConfirmations],
  getTransactionCount: [Function: getTransactionCount],
  getTransactionReceipt: [Function: getTransactionReceipt],
  multicall: [Function: multicall],
  prepareTransactionRequest: [Function: prepareTransactionRequest],
  readContract: [Function: readContract],
  sendRawTransaction: [Function: sendRawTransaction],
  simulateContract: [Function: simulateContract],
  verifyMessage: [Function: verifyMessage],
  verifySiweMessage: [Function: verifySiweMessage],
  verifyTypedData: [Function: verifyTypedData],
  uninstallFilter: [Function: uninstallFilter],
  waitForTransactionReceipt: [Function: waitForTransactionReceipt],
  watchBlocks: [Function: watchBlocks],
  watchBlockNumber: [Function: watchBlockNumber],
  watchContractEvent: [Function: watchContractEvent],
  watchEvent: [Function: watchEvent],
  watchPendingTransactions: [Function: watchPendingTransactions]
}

21074983n

我们发现client这个对象返回出了很多与以太坊交互的接口

http()其实会默认选择rpc就是cloudflare-eth.comcontracts也提供了一个默认的address,默认的交易对就是ETH,精度18

连接钱包

我们知道与以太坊交互去中心化,所有的交易账户需要一个账号与链上产生交互,所以需要链接钱包,当我们插件安装了metaMask后,浏览器的window对象就已经绑定了ethereum这个对象,所以基本上所有与钱包交互的接口的前提是你必须在插件商店安装对应的一个钱包才行

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 
   function App() {
        const handleConnetWallet = async () => {
        const [account] = await window.ethereum.request({ method: 'eth_requestAccounts' });
        console.log(account, '==account')
      }
  
   return (<div>
            <h1 onClick={handleConnetWallet}>Content Wallet</h1>
      </div>);
   }

当前的web端唤起小狐狸钱包或者其他钱包,基本就是依靠这个window.ethereum.request这个接口

我们注意到一个钱包有很多个账号,而且我们默认的就是ETH主网,因为我们的钱包会默认选择一个ETH主网,当你选择确认对应钱包后,此时的account就是我们选择的钱包地址

SignMessage

通常我们在连接钱包后,我们需要做一个签名,这个签名一般由客户端主动发起,当签名成功后,就会获取一个hash,然后这个hash一般会是通过一个后端登录的接口进行验证这个账户

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 
import  {memo, useRef} from "react";
...
function App() {
     const accountRef = useRef(null);
    const handleSignMessage = async () => {
    const walletClient = createWalletClient({
        account: accountRef.current,
        chain: mainnet,
        transport:custom(window.ethereum),
    });
    const hash = await walletClient.signMessage({
        message: "hello world"
    });
    console.log(hash);
  }
  const handleConnetWallet = async () => {
    const [account] = await window.ethereum.request({ method: 'eth_requestAccounts' });
    accountRef.current = account;
  }
  return (<div>
     <h1 onClick={handleConnetWallet}>Content Wallet</h1>
     <h1 onClick={handleSignMessage}>SignMessage</h1>
  </div>)
}

注意签名的前提必须是你已经链接钱包

返回的hash是这这样的

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 
0xaeedca0a7745ee7dbbabdf69226dd7c27aa0f17c8bdcf58616ced8f1b9fe7bc60da3fcdb850af23d1379c2a0b57349862db2010ceb50f8a33322aed68119b14b1c

我们会发现签名值的变化与message这个传入的字符串有关,如果这个字符串发生变化,那么签名后的hash就不回一样,反之当message不发生变化时,产生的hash是不会变化的

isAddress

主要是判断这个钱包地址是否是eth地址

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 
import {isAddress} from "viem";
...
function App() {
       const [address, setIsAddress] = useState("");
      const [isEthAddress, setIsEthAddress] = useState(false);
     const handleVerifyAddress = () => {
            const isEthAddress = isAddress(address);
            setIsEthAddress(isEthAddress);
      }
    return <>
        <div>
            <input type="text" onChange={(e) => setIsAddress(e.target.value)} />
            <button onClick={handleVerifyAddress}>Verify address</button>
            <p>{address} {isEthAddress ? 'is': 'is not'} eth address</p>
        </div>
    <>
}

sendTransaction

可以理解成一个钱包地址向另外一个地址转账,在操作前,必须有SignMessage操作

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 
  ...
  const handleSendTransaction = async () => {
    const res = await wallect.current.sendTransaction({
        to: "0x5A39756bAE97685917a292B33ddFb2B54DF1e806",
        value: parseEther("0.001"), // 0.001ETH
    });
    console.log(res);
  }
  return (<>
      <h1 onClick={handleSendTransaction}>sendTransaction</h1>
  </>)

你会发现,我发送2.66$,所需要的gas费用就是0.67$,所以实际上你在钱包中发起的一笔转账,大概总计就需要3.33$

如果你把实际转账的参数改成这样

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 
  const handleSendTransaction = async () => {
    const res = await wallect.current.sendTransaction({
        to: "0x5A39756bAE97685917a292B33ddFb2B54DF1e806",
        value: parseEther("1"), // 1 ETH
    });
    console.log(res);
  }
  return (<>
       <h1 onClick={handleSendTransaction}>sendTransaction</h1>
  </>)

你会发现gas费用是动态变化的,但是跟你当下交易的笔数的大小无关,gas费每次都会变化,如果多笔转账合并,那么会减少不少的gas费用

获取公共操作

主要是查询用户账户余额、检索当前区块号,或者检索当前的交易信息,查询这些并不需要签名等权限

  • getBalance
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 
import {createPublicClient, http, createWalletClient, custom, isAddress, parseEther, formatUnits } from "viem"

import { mainnet, bsc } from "viem/chains";

const client = createPublicClient({
    chain: bsc, // bsc链
    transport: http(),
 })
 
 function App() {
      const handleGetBlance = async () => {
          const res = await client.getBalance({
              address: "0x6c85e349A70791e95fD6D9D5e066C6Ec136E0a92"
          });
           console.log(`余额:${formatUnits(res, 18)} BNB`);
      }
     return <div>
         <div onClick={handleGetBlance}>get Blance</div>
     </div>
 }

我们发现对于获取后的余额实际上精度是18位的,最终我们使用viem中的formatUnits帮我们处理成了与钱包余额一样的余额数值

  • getTransactionReceipt 主要是根本交易hash能查询当前的交易状态,比如
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 
 ...
 const handleGetTranstion = async () => {
    const res = await client.getTransactionReceipt({
        hash: "0xf6914af778b18ca1be98adead61af6351862fe26faad7e64d631df30fe925cd4"
    });
 console.log(res);
 function App() {
     return <>
             <h1 onClick={handleGetTranstion}>get transition hash</h1>
     </>
 }
  

我们会查询这个这交易hash的在区块链的实际交易状态,实际上你在https://bscscan.com/ 中根据交易hash也可查询看到对应的状态

至此一个从一个连接钱包登录,到签名,再到sendTransition的基础功能就基本结束,viem也提供了如何与以太坊合约交互的接口,这些都会有一个ABI的数据相关联,后续也会写个测试网合约进行调试,更多的详细的与以太坊交互的参考viem 文档

总结

  • 了解学习到使用更低层的viem官方接口唤起钱包,进行钱包连接签名sendTranstion操作
  • viem开放出来的公用区块查询,比如钱包余额,区块,以及交易状态
  • code example
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-10-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Web技术学苑 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
python学习笔记(11)python面向对象(二)面向对象的三大特点:封装,继承,多态
例子1: 1.TOM体重76.0公斤 2.TOM每次跑步会减肥0.5公斤 3.TOM每次吃东西会增重1攻击
大数据小禅
2021/08/16
4880
Python面向对象编程
而解决这一问题的比较有效的方法之一就是数据隐藏,即编码过程中尽可能的隐藏内部的实现细节。
讲编程的高老师
2020/08/14
5150
手撕Python之面向对象
下面我将通过一个简单的例子来展示这两种编程范式的区别。假设我们要编写一个程序,用于计算一个矩形的面积。
Undoom
2024/09/23
910
手撕Python之面向对象
【python入门到精通】python面向对象三大特点:封装,继承,多态
例子1: 1.TOM体重76.0公斤 2.TOM每次跑步会减肥0.5公斤 3.TOM每次吃东西会增重1攻击
大数据小禅
2021/12/21
3250
🐒🐒🐒**面向对象的编程,顾名思义就是面向你的男朋友or女朋友(俗称对象),去进行编程!** 这时候有的小伙伴会问啦:那我没对象怎么面向对象编程呢\~
🐒🐒🐒面向对象的编程,顾名思义就是面向你的男朋友or女朋友(俗称对象),去进行编程! 这时候有的小伙伴会问啦:那我没对象怎么面向对象编程呢~
是Dream呀
2024/05/08
1340
Python基础-9 类
类中的概念比较多,初学者掌握面向对象、定义和使用类、单继承、类变量和实例变量即可。迭代器和生成器是Python中迭代利器,推荐掌握。
一只大鸽子
2022/12/06
3840
Python学习笔记 面向对象编程
乐百川
2018/01/08
8010
【AICAMP —— Python】入门系列!(6. 面向对象)
目前我们大部分语言的设计都是面向对象的,说到面向对象,其实说起来挺容易理解,但是在实际运用的时候就会发现还是有很多讲究的。在面向对象基础之上,还包括了面向对象设计(OOD), 面向对象分析(OOA),面向对象编程(Object Oriented Programming),反正就是一句话,面向对象!如果没有对象?那可以考虑找个对象了!
灿视学长
2021/05/28
3900
Python面向对象编程Day 25部分
  说的就是数据属性,点的方式调用的就是属性,把函数封装成数据属性,使得外部在调用的时候感觉不到内部的逻辑。既可以访问实例属性还可以访问类属性。
py3study
2020/01/19
3340
Python面向对象编程Day 25部分
python之面向对象编程
1、面向对象介绍: 世界万物,皆可分类 世界万物,皆为对象 只要是对象,就肯定属于某种类 只要是对象,就肯定有属性 2、 面向对象的几个特性: class类: 一个类即对一类拥有相同属性的对象的抽象,
coders
2018/01/04
1.6K0
python之面向对象编程
全面深入理解Python面向对象编程
面向过程编程最易被初学者接受,其往往用一长段代码来实现指定功能,开发过程中最常见的操作就是粘贴复制,即:将之前实现的代码块复制到现需功能处。
顶级程序员
2018/07/23
8750
Python 基础(十一):面向对象
面向对象(OOP)是一种对现实世界理解和抽象的方法,对象的含义是指在现实生活中能够看得见摸得着的具体事物,一句比较经典的描述是一切皆对象,Python 是一门面向对象的语言,面向对象编程简单来说就是一种封装代码的方式。
Python小二
2020/08/17
3100
Python之面向对象二
面向对象的三大特性: 继承 继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类 python中类的继承分为:单继承和多继承 class Animal: def __init__(self,name,aggr,hp): self.name = name self.aggr = aggr self.hp = hp self.func() def func(se
新人小试
2018/04/12
8360
Python之面向对象二
【Python】面向对象编程—类的继承
面向对象中的继承也是指子类(派生类)可以继承父类(基类、超类)的特征和行为,使得子类具有父类的所有属性和方法,并且可以定义自己独有的属性和方法。
阿黎逸阳
2021/03/24
5850
Python3 面向对象编程基础
面向对象编程——Object Oriented Programming,简称 OOP,是一种程序设计思想。 OOP 把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
嵌入式视觉
2022/09/05
2350
面向对象
面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)
周小董
2019/03/25
6290
面向对象
Python - 面向对象编程 - 三大特性之继承
继承 继承也是面向对象编程三大特性之一 继承是类与类的一种关系 定义一个新的 class 时,可以从某个现有的 class 继承 新的 class 类就叫子类(Subclass) 被继承的类一般称为父类、基类、超类 通俗理解:子类与父类的关系,即爸爸与儿子,爸爸有了个儿子,儿子继承爸爸的属性和方法,爸爸的爸爸就是爷爷...以此类推 继承的实际栗子 猫、狗都是动物,所以动物是父类,猫、狗是动物的子类 小菠萝、大菠萝都是人类,所以人类是父类,小菠萝、大菠萝是人类的子类 动物、人类都是生物,所以生物是父类,动物、
小菠萝测试笔记
2021/09/08
2780
Python面向对象之面向对象基本概念
过程和函数:过程类似于函数,只能执行,但是没有返回结果;函数不仅能执行,还能返回结果。
py3study
2020/01/16
3960
【Python】教你彻底认识Python中的面向对象编程
面向对象编程(Object-Oriented Programming,简称OOP)是一种编程范式,它通过“对象”将数据和方法进行封装,以提高代码的可重用性和可维护性。Python是一种多范式的编程语言,它不仅支持面向过程编程,也支持面向对象编程。在这篇文章中,我们将深入探讨Python中的面向对象编程,包括其基本概念、类和对象的创建、继承、多态,以及一些实际应用示例。
E绵绵
2025/05/25
1440
面向对象基础篇
面向对象编程是一种编程方式,此编程方式的落地需要使用 “类” 和 “对象” 来实现,所以,面向对象编程其实就是对 “类” 和 “对象” 的使用。
py3study
2020/01/15
3840
推荐阅读
相关推荐
python学习笔记(11)python面向对象(二)面向对象的三大特点:封装,继承,多态
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验