Web3-Wallet 谷歌插件钱包开发(一)

1,299 阅读3分钟

背景

本文将学习谷歌插件的钱包开发。实现最基本的钱包插件开发。

准备工作

我们需要开发一款运行在浏览器插件的钱包,我们的开发环境是插件环境,所以我们必须使用一个完善的类似于create-react-app ,来为我们生成代码仓库。

这里我们使用的是Plasmo,借用 Plasmo 文档的话,这是 “一个一体化平台,可让浏览器扩展程序开发人员轻松创建、测试和发布出色的扩展程序。借助 Plasmo,您可以告别繁琐的样板代码,享受更快、更无缝的开发体验。”
开发文档地址:docs.plasmo.com/
github 仓库地址:github.com/PlasmoHQ/pl… image.png
目前已经有 8.7k star属于一个比较成熟的框架。 所以选用这个作为我们的插件模板。

生成代码模板

使用

pnpm create plasmo
# OR
yarn create plasmo
# OR
npm create plasmo

生成如下代码:

image.png

这里着重说一下popup.tsx此文件导出默认的 React 组件,该组件将呈现到您的弹出页面中。这就是您在扩展弹出窗口上工作所需的全部内容!

安装其他组件

Web3 交互需要使用到的 ethers.js

其他的工具方法:

  • react-loadable 组件懒加载
  • react-loadable 组件懒加载
  • queue 队列,用于插件通信
  • eth-rpc-errors rpc 报错
  • @extend-chrome/storage 插件存储
  • @metamask/browser-passworder 插件密码管理
  • ahooks 公共 hooks
  • classnames

至此我们的插件模板就安装完成!!

下面进入到代码层面的开发阶段

路由配置

这里添加我们页面的路由,比如创建钱包页面、资产页面、设置页面等、以及密码页

我们需要加入ErrorBoundary,这个方法用于监听页面错误。

export class ErrorBoundary extends React.Component {
  state = { hasError: false }

  static getDerivedStateFromError(error: any) {
    return { hasError: true }
  }

  componentDidCatch(error: any, errorInfo: any) {
    console.error(error, errorInfo)
  }

  render() {
    if (this.state.hasError) {
      return (
        <Result
          status="error"
          title="系统异常"
          subTitle="系统异常,请稍后重试"
          extra={[
            <Button
              type="primary"
              onClick={() => {
                message.success("上报成功")
              }}>
              上报错误
            </Button>,
            <Button
              key="buy"
              onClick={() => {
                window.location.reload()
              }}>
              刷新重试
            </Button>
          ]}></Result>
      )
    }

    // @ts-ignore
    return this.props.children
  }
}

接下来需要配置一下我们的状态管理。插件钱包需要很多的状态管理,比如账号状态、钱包状态、当前链等等。
这里我们使用unstated-next作为我们的状态管理。

在根目录下创建 stroe文件夹,预先创建 2 个文件 ChainStore.ts WalletStore.ts

代码目录结构如下:

image.png index.js

import { ChainStore } from "./ChainStore";
import { WalletStore } from "./WalletStore";

export const walletProviders = [WalletStore.Provider, ChainStore.Provider];

ChainStore.ts

/**
 * 链的数据存储
 */
import { createContainer } from 'unstated-next';

const useChainStore = () => {};

export const ChainStore = createContainer(useChainStore);

Nested组件,这个组件的功能是可以接受多个 Providers,代码如下:

import React, { memo, type PropsWithChildren } from "react"

interface Props {
  components: Array<React.ComponentType | (({ children }) => JSX.Element)>
}
export const Nested = memo(function NestedComponents(
  props: PropsWithChildren<Props>
) {
  const { components, children } = props

  return (
    <>
      {components.reduceRight((Prev, Curr) => {
        return <Curr>{Prev}</Curr>
      }, children)}
    </>
  )
})

状态管理的基本代码书写完毕,接下来进行路由的配置: 我们使用的是react-loadablereact 组件懒加载,用于性能优化,再加载页面的过程中可以放入骨架屏等,减少用户等待时间等。所以这个在加载页面组件时,用懒加载进行处理,例如插件钱包一开始的进入的页面。

import Loadable from "react-loadable"
import React from "react";
import { PageLoading } from "~components/PageLoading";

/* 页面初始化 */
const InitPage = Loadable({
  loader: () => import("~pages/InitPage"),
  loading: PageLoading
})
import { Skeleton } from "antd-mobile";
import React from "react"
import { memo } from "react"

export const PageLoading = memo(() => {
  return (
    <>
      <Skeleton.Title animated />
      <Skeleton.Paragraph lineCount={5} animated />
    </>
  )
})

接下来需要写一个组件,用于路由跳转的逻辑,比如一开始用户没有创建钱包,我们需要引导去创建钱包页面。所以这里需要添加一个组件

export const RenderRoutes = memo(() => {
  const nav = useNavigate()

  useRequest(() => wallet.getAllWallets(), {
    onSuccess: (data) => {
      if (data?.length > 0) {
        nav("/home")
      }
    }
  })
  const Route = useRoutes(routes)
  return <div className="w-full h-full max-w-[360px] mx-auto">{Route}</div>
})

关于wallet.getAllWallets() 这些跟钱包交互相关的,这里就先埋个坑,等后续更新钱包交互,再来书写这块

组装我们的组件。

export const Root = memo(() => {
  return (
    <Nested components={walletProviders}>
      <HashRouter>
        <ErrorBoundary>
          <RenderRoutes />
        </ErrorBoundary>
      </HashRouter>
    </Nested>
  )
})

最后在 popup.tsx 替换为:

import React from "react";
import { Root } from "~routes";

export default function Popup() {
  return <Root />
}

运行pnpm dev,将 build 下的 chrome-mv3-dev文件夹,放入chrome://extensions/

20240625231521_rec_.gif

第一章完毕!!