Web3Modal开发简明教程
Web3Modal为 dApp 提供直观的界面来验证用户身份并请求签名交易等操作。本文展示如何使用 WalletConnect 的 Web3Modal 进行用户身份验证和创建用户会话。
一键发币: SOL | BNB | ETH | BASE | Blast | ARB | OP | POLYGON | AVAX | FTM | OK
WalletConnect 为 Web3 开发人员提供了强大的工具,使构建安全、交互式和令人愉快的去中心化应用程序变得更加容易。 该工具将一流的 UX 和 UI 与一套 SDK 和 API 的模块化方法相结合。 对于许多希望在不牺牲安全性或质量的情况下加快开发节奏的团队来说,WalletConnect 的各种 SDK 是显而易见的选择。
我们最喜欢的之一是 Web3Modal - 一个工具集,它为 dApp 提供直观的界面来验证用户身份并请求签名交易等操作。 Web3Modal 支持多个浏览器钱包(例如 MetaMask 和 Trust Wallet),并在其文档中提供全面的说明,以帮助开发人员跨多个框架(React、Next、Vue 等)启动和运行。 在本教程中,我们将展示如何使用 WalletConnect 的 Web3Modal 进行用户身份验证和创建用户会话。
准备好了? 让我们开始吧
1、我们要构建什么?
在本教程中,我们将构建一个应用程序来跟踪活动出席情况。 这里的用例有些基本 - 想象一个会议想要跟踪哪些参与者参加了哪个活动。 他们可能允许参与者扫描二维码,将他们带到此应用程序,他们可以在其中登录(使用钱包),选择共享他们的位置,并生成显示他们参加的徽章。
这是用户流程的简单视觉效果:
根据上面的总结,Web3Modal 的适用范围可能是显而易见的。没错 - 我们将使用此 SDK 来验证用户身份并根据钱包地址跟踪谁参加了哪些活动。
我们虚构了两个虚构事件来配合此用例:
- 加密事件
- 钱包活动
下面是我们应用程序 UI 的预览:
2、我们的技术栈包含哪些内容?
为了支持这个简单的应用程序,我们需要一些东西:
- 一个在与会者浏览器中运行的前端框架和一个用于处理我们需要的任何内部 API 调用的后端 - 我们将使用 NextJS
- 钱包工具使我们不必从头开始构建身份验证逻辑 - Web3Modal
- React hooks 与我们的浏览器钱包一起使用,所以我们也不必构建这些 - 我们将使用 Wagmi
- 去中心化数据存储 - 我们将使用 ComposeDB(基于 Ceramic 的图形数据库)
3、为什么选择 ComposeDB?
如果要处理这些假想事件的潜在数千(或更多)参与者(大型会议经常出现这种情况),将这些记录存储在链上既昂贵又低效。 每条记录都会产生Gas费,并且在数以万计的记录中查询区块链将是一项艰巨的任务。
尽管如此,我们希望我们的应用程序能够为参加活动的用户提供数据控制。 而且,在我们想象的用例中,其他会议必须有权访问这些数据(而不仅仅是我们的应用程序),以便他们可以确定谁应该获得准入优先权。 因此,我们需要某种去中心化的数据网络。
在 Ceramic(这是 ComposeDB 的基础)中,用户数据被组织成可验证的事件流,这些事件流由创建每个流的用户帐户专门控制。 由于 Ceramic 是一个无需许可的开放数据网络,因此任何应用程序都可以轻松加入和访问预先存在的用户数据(满足上面列出的要求之一)。
基于 Ceramic/ComposeDB 构建的应用程序对用户进行身份验证(使用以太坊登录),为应用程序创建代表用户将数据写入网络的严格范围的权限。 这对我们来说很重要,因为在代表用户将输出保存在 Ceramic 中之前,我们的应用程序的服务器需要对徽章进行加密签名(以证明徽章确实是通过我们的应用程序生成的)。
最后,ComposeDB 在 Ceramic 之上添加了一个图形数据库接口,可以轻松地在大量文档中进行查询、过滤、排序等(使用 GraphQL)——非常适合任何想要使用这些徽章并执行计算的团队。 以有效的方式。
我们将在本教程中详细介绍。
4、入门
我们为你建立了一个特殊的存储库,以帮助指导你完成整个过程 - 请记住,我们需要使用以下步骤添加它才能工作。
首先克隆演示应用程序存储库并安装依赖项:
git clone https://github.com/ceramicstudio/walletconnect-tutorial
cd walletconnect-tutorial
npm install
继续并在你选择的代码编辑器中打开该目录。 如果查看 package.json 文件,你将看到上面提到的 @web3modal/wagmi
和 wagmi
包,以及几个 @ceramicnetwork
和 @composedb
包来满足我们的存储需求。
5、获取 WalletConnect 项目 ID
下载依赖项时,你可以创建一个 WalletConnect 项目 ID(我们需要它来配置我们的 Web3Modal - 有关其文档的更多信息)。 你可以通过访问他们的 WalletConnect Cloud 网站、创建一个新项目(选择“应用程序”类型)并选择一个名称来免费执行此操作:
单击“创建”后,你将被引导至刚刚设置的项目的设置页面。 继续并复制你在“项目 ID”旁边看到的字母数字值。
返回文本编辑器,导航到 /src/pages/_app.tsx 文件,然后将刚刚复制到 projectId 常量旁边的空白字段中的 ID 输入。 请注意,在定义 wagmiConfig(稍后用于创建 Web3Modal)时,我们如何使用此 ID 和主网链设置。 正如 Web3Modal 文档所指示的那样,我们在 React 组件之外设置这些函数,并使用 WagmiConfig 包装器包装所有子组件:
const projectId = '<your project ID>'
const chains = [mainnet]
const wagmiConfig = defaultWagmiConfig({ chains, projectId })
createWeb3Modal({ wagmiConfig, projectId, chains })
const MyApp = ({ Component, pageProps }: AppProps) => {
return (
<WagmiConfig config={wagmiConfig}>
<ComposeDB>
<Component {...pageProps} ceramic />
</ComposeDB>
</WagmiConfig>
);
}
export default MyApp
现在,我们可以使应用程序的子组件可以访问 Web3Modal 按钮,以允许用户登录。如果查看 /src/components/nav.tsx
,你会发现我们放置了 <w3m-button/>
组件直接进入我们的导航,以允许用户在我们应用程序的任何页面上登录/退出(目前我们的应用程序只有 1 个页面)。
请注意我们如何使用大小和平衡属性 - 这是开发人员可以用来进一步自定义模式外观的几个设置中的两个。 这两个非常容易理解 - 一个改变按钮的大小,而另一个在用户通过身份验证时隐藏用户的余额。
最后,你可能在 /src/pages/_app.tsx
文件中注意到我们还在利用 上下文包装器。 这就是我们接下来要解释的。
6、创建 ComposeDB 配置
现在我们已经创建了 Wagmi 配置,我们需要设置 ComposeDB 数据存储。 涉及几个步骤(所有这些都已为您处理好)。 这些包括:
- 设计我们的应用程序所需的数据模型
- 为此演示创建本地节点/服务器配置(在生产中)
- 将我们的数据模型部署到我们的节点上
- 定义我们的应用程序将用于读取 + 写入 ComposeDB 节点的逻辑
6.1 数据模型
如果查看 /composites
文件夹,你将看到一个 /attendance.graphql
文件,我们已经在其中定义了应用程序将使用的模型。 在 ComposeDB 中,数据模型是 GraphQL 架构,除了与其他模型和帐户的关系之外,还包含单条数据(例如社交帖子)的要求。 由于 Ceramic 是一个开放数据网络,开发人员可以在预先存在的数据模型上构建(你可以探索 S3 等工具来观察现有模式),或者为你的应用程序定义全新的数据模型。
在我们的例子中,我们的应用程序将利用我们的两种事件类型将实现的通用事件接口:
interface GeneralAttendance
@createModel(description: "An interface to query general attendance") {
controller: DID! @documentAccount
recipient: String! @string(minLength: 42, maxLength: 42)
latitude: Float
longitude: Float
timestamp: DateTime!
jwt: String! @string(maxLength: 100000)
}
type EncryptionEvent implements GeneralAttendance
@createModel(accountRelation: SINGLE, description: "An encryption event attendance") {
controller: DID! @documentAccount
recipient: String! @string(minLength: 42, maxLength: 42)
latitude: Float
longitude: Float
timestamp: DateTime!
jwt: String! @string(maxLength: 100000)
}
type WalletEvent implements GeneralAttendance
@createModel(accountRelation: SINGLE, description: "A wallet event attendance") {
controller: DID! @documentAccount
recipient: String! @string(minLength: 42, maxLength: 42)
latitude: Float
longitude: Float
timestamp: DateTime!
jwt: String! @string(maxLength: 100000)
}
请注意,我们如何将两种类型的 accountRelation 字段设置为“SINGLE” - 这意味着 1 个用户只能拥有 1 个该类型的模型实例,从而创建 1:1 帐户关系。 这与“LIST”accountRelation 形成对比,“LIST”accountRelation 表示一对多关系。
你还会注意到我们的纬度和经度字段不使用! 在它们的标量定义旁边 - 这意味着它们是可选的,因此可以在定义或不定义这些字段的情况下创建模型实例。
最后,我们将使用 jwt 字段来记录我们的服务器将为用户创建的签名徽章有效负载。 由于用户最终将控制他们的数据,因此潜在的欺骗者可能会尝试在我们的应用程序范围之外更改其模型实例的值。 鉴于我们的架构需要一种方法让我们的应用程序和其他会议都能够读取和验证这些数据,jwt 字段将通过将我们应用程序的 DID 的加密签名与数据绑定在一起来创建针对这些值的防篡改证明。
6.2 创建本地服务器配置
由于这只是一个演示应用程序,我们没有可访问的云托管节点端点,因此我们将定义一个服务器配置以在我们的计算机上本地运行。 虽然应用程序可以利用多种服务器设置,但此演示需要了解的关键事项如下:
- 我们的应用程序将在内存中运行,而生产应用程序将使用主网进行网络设置
- 我们的服务器将定义 sqlite 作为我们的 SQL 索引,而生产应用程序将使用 PostgreSQL
- 我们的 IPFS 将以捆绑模式运行(非常适合早期原型设计),而生产应用程序将在远程运行
最后,每个 Ceramic 节点都配置有一个管理 DID,用于对节点进行身份验证并执行部署模型等任务。 这与最终用户在使用钱包进行身份验证并将数据写入网络时使用的 DID 不同。
幸运的是,我们已经通过创建命令为你解决了这个问题。 安装依赖项后,只需在终端中运行以下命令:
npm run generate
如果查看 admin_seed.txt 文件,你将看到 Ceramic 节点将使用的管理种子。 你可以在composedb.config.json 文件中找到刚刚创建的服务器配置。
6.3 将模型部署到我们的节点上
由于我们没有使用已设置为索引我们关心的数据模型的预先存在的节点端点,因此我们需要一种将定义部署到节点上的方法。 如果查看 /scripts/composites.mjs,您会发现我们为你创建的 writeComposite 方法,该方法从 GraphQL 文件中读取、创建编码的运行时定义并将组合部署到在端口 7007 上运行的本地节点上。
这里需要注意的重要一点是 writeEncodedCompositeRuntime 方法如何在我们的Definition.js 文件中生成定义。 我们将在下一步中解释我们的客户端库如何使用它来允许我们的应用程序与这些数据模型和陶瓷节点进行交互。
暂时不要采取任何操作 - 我们将在接下来的步骤中解释如何使用此脚本。
6.4 将 ComposeDB 与我们的应用程序集成
最后,如上所述,我们的应用程序需要一种方法来读取和写入 ComposeDB 节点。 我们还需要一种方法将 Web3Modal 身份验证逻辑与对节点上的用户进行身份验证的需求相结合。
如果查看 /src/fragments/index.tsx,你会发现一个 ComposeDB 组件,它允许我们利用 React 的 createContext 钩子并创建我们自己的包装器。 由于我们知道 Web3Modal 将使用我们的钱包客户端,因此我们可以利用钱包客户端向用户请求 Ceramic 用户会话身份验证。
请注意以下几点:
const CERAMIC_URL = process.env.URL ?? "http://localhost:7007";
/**
* Configure ceramic Client & create context.
*/
const ceramic = new CeramicClient(CERAMIC_URL);
const compose = new ComposeClient({
ceramic,
definition: definition as RuntimeCompositeDefinition,
});
let isAuthenticated = false;
const Context = createContext({ compose, isAuthenticated });
export const ComposeDB = ({ children }: ComposeDBProps) => {
function StartAuth() {
const { data: walletClient } = useWalletClient();
const [isAuth, setAuth] = useState(false);
useEffect(() => {
async function authenticate(
walletClient: GetWalletClientResult | undefined,
) {
if (walletClient) {
const accountId = await getAccountId(
walletClient,
walletClient.account.address,
);
const authMethod = await EthereumWebAuth.getAuthMethod(
walletClient,
accountId,
);
const session = await DIDSession.get(accountId, authMethod, {
resources: compose.resources,
});
await ceramic.setDID(session.did as unknown as DID);
console.log("Auth'd:", session.did.parent);
localStorage.setItem("did", session.did.parent);
setAuth(true);
}
}
void authenticate(walletClient);
}, [walletClient]);
return isAuth;
}
if (!isAuthenticated) {
isAuthenticated = StartAuth();
}
return (
<Context.Provider value={{ compose, isAuthenticated }}>
{children}
</Context.Provider>
);
};
请注意我们如何使用钱包客户端的帐户地址来启动 DID 会话,该会话向 compose 请求特定资源。 如果你更深入地跟踪,会发现 compose 是使用从我们的部署脚本写入的文件中导入的定义来实例化的。 这使我们能够访问有限的范围,以代表用户专门为我们的应用程序使用的数据模型写入数据(这些会话在 24 小时后自动过期)。
最后,为了完成这个循环,回到我们的 /src/pages/_app.tsx 文件,您现在应该了解我们如何使用 ComposeDB 作为上下文包装器,使我们能够访问 ComposeDB 客户端库和我们的模型 来自任何子组件内的定义。 例如,如果查看 /src/components/index.tsx,你将看到我们现在如何利用 useComposeDB 挂钩,该挂钩允许我们对节点的客户端运行查询。
6.5 为应用程序服务器 DID 创建种子
我们上面提到,我们希望我们的应用程序在将文档控制权交还给最终用户之前对每个徽章有效负载进行签名。 虽然这种流程并不总是如此(请阅读有关 Ceramic 中常见数据控制模式的博客以了解更多信息),但我们希望实现此流程以确保数据的可验证性。
在 /src/pages/api/create.ts 中,我们创建了一个 API,我们的应用程序服务器将公开它,它正是执行此操作 - 它获取与事件相关的数据,使用 SECRET_KEY 环境变量实例化静态 DID,并返回 包含签名数据的 Base64 字符串编码 JSON Web 签名。
因此,我们需要创建一个单独的静态种子来存储在我们将创建的 .env 文件中:
touch .env
对于本教程,请在新文件中输入以下键值对:
SECRET_KEY = "11b574d316903ced6cc3f4787bbcc3047d9c72d1da4d83e36fe714ef785d10c1"
当使用上面的种子实例化 DID 时,这将产生以下可预测的 did:
did:key:z6MkqusKQfvJm7CPiSRkPsGkdrVhTy8EVcQ65uB5H2wWzMMQ
如果回顾 /src/components/index.tsx,你将看到我们冗长的 getParams 方法如何对用户已持有的任何现有 EncryptionEvent 或 WalletEvent 徽章执行快速检查,以测试 jwt 值是否确实由我们的应用程序签名 (更彻底的版本可能包括验证签名数据是否与其他字段中的相同值匹配,但我们将由你来添加)。
就是这样! 我们终于准备好运行我们的应用程序了!
7、运行应用程序
现在我们已经设置了应用程序在本地运行所需的一切,我们可以在开发人员模式下启动它。 确保首先选择正确的节点版本:
nvm use 20
npm run dev
一旦在终端中看到以下内容,你的应用程序就可以在浏览器中查看了:
在浏览器中,导航到 http://localhost:3000
- 你应该看到以下内容:
7.1 使用 Web3Modal 登录
如上所述,我们已经使我们的 Web3Modal 可以从我们的导航中访问,这就是我们的“连接钱包”按钮的来源。 继续点击此按钮并选择您选择的钱包。
在登录过程中,你会注意到钱包中出现一条额外的授权消息,如下所示:
如果你还记得上面“将 ComposeDB 与我们的应用程序集成”部分中介绍的内容,会记得我们讨论了如何通过请求对我们的应用程序将使用的特定资源(数据模型)进行授权来创建 DIDSession。 你应该看到登录请求的“资源”部分下列出的 3 项。
最后,登录后,你的 Web3Modal 现在将显示你地址的截断版本:
7.2 创建徽章
正如你所看到的,我们的应用程序不允许用户输入他们参加了哪个活动 - 这将根据二维码发送给用户的 URL 确定,格式如下:
http://localhost:3000/?event={event id}
查看浏览器控制台 - 你应该看到类似于以下内容的日志:
我们通过读取导入到 /src/components/index.tsx 组件中的运行时复合定义来为您预设这些日志。 继续复制这些字段之一并构建您的 URL,如下所示:
http://localhost:3000/?event=kjzl6hvfrbw6c8njv24a3g4e3w2jsm5dojwpayf4pobuasbpvskv21vwztal9l2
如果已复制与 EncryptionEvent 模型对应的流 ID,你的 UI 现在应如下所示:
你可以选择共享你的坐标。 最后,继续为你在 URL 中输入的任何事件创建徽章:
如果导航回 /src/components/index.tsx 文件,你可以观察 createBadge 中发生的情况。 调用 /api/create 路由(使用应用程序服务器的静态 DID 来签署事件数据)后,我们将执行突变查询,创建与你在 URL 参数中使用的标识符一致的事件实例。 由于我们的用户是当前在我们的节点上进行身份验证的帐户(从 DID 会话的创建开始),因此生成的文档将置于最终用户的控制之下(将我们的防篡改签名数据输入到 jwt 字段中)。
如果查看 /src/components/index.tsx 文件中的 getParams 方法,你会注意到我们已经针对 ComposeDB 节点创建了一个查询,该查询在 useEffect React 挂钩内以及每个徽章之后运行 创作事件。 请注意我们如何根据用户的 did:pkh: did:pkh:eip155:${chainId}:${address?.toLowerCase()} 进行查询
如果查看我们的 chainId 和地址分配,你会意识到这些来自我们提到的我们需要的 Wagmi 挂钩(特别是 useAccount 和 useChainId)。
8、结束语
我们希望你喜欢这个相当简单的演练,了解如何使用 WalletConnect 的 Web3Modal 工具包来验证用户身份、在 Ceramic 中创建用户会话以及根据经过身份验证的用户查询 ComposeDB! 虽然这就是本教程的全部内容,但我们鼓励你探索 Ceramic 提供的其他可能性和旅程。
原文链接:WalletConnect Tutorial: Create User Sessions with Web3Modal
DefiPlot翻译整理,转载请标明出处