MetaMask Snaps 是让 MetaMask 成为全球可扩展性最强钱包的发展路线图。作为开发者,您可以以全新的方式将您的功能和 API 带入 MetaMask。Web3 开发者是这一增长的核心,本系列旨在展示当今正在构建的创新 MetaMask Snaps。
Aleph Zero Snap——与 Piotr 和 Michael 的对话
我们与 Web3 开发者 Piotr 和 Michael 进行了交流,他们在 ETHWarsaw 黑客马拉松上构建了 Aleph Zero Snap。
Snap 代码仓库: https://github.com/piotr-roslaniec/ethwarsaw-2022/tree/main/azero-snap/packages/snap-adapter
你们为什么要构建它?
在我们看来,Polkadot 生态系统中的钱包体验仍有不足。另一方面,与 MetaMask Snaps 集成有助于将现有的 Web3 用户群引入 Aleph Zero 网络。
能带我们了解一下技术实现吗?
适配器(Adapter)
我们的 Snap 由两部分组成。第一部分是用户交互的网站,第二部分是实际的 Snap。这两个元素的工作方式类似于客户端-服务器架构,其中 Snap 是服务器,网站是客户端。为了体现这一自然边界,我们将 Snap API 提取到一个名为 adapter 的独立包中。它暴露了网站与 Snap 交互可能用到的所有方法。这样的抽象层便于其他客户端(网站)使用 Snap。我们的 API 可以用四个方法来描述,其名称相当直观。如果您想深入了解,请查看完整代码。
export const getAccountFromSeed = async (seed: string): Promise<PublicAccount> => {
return await requestSnap("getAccountFromSeed", [seed]);
}
export const generateNewAccount = async (): Promise<PublicAccount> => {
return await requestSnap("generateNewAccount", []);
}
export const signTransaction = async (transaction: SignerPayloadJSON): Promise<{ signature: string }> => {
return await requestSnap("signTransaction", [transaction as unknown as string]);
}
export const getAccounts = async (): Promise<string[]> => {
return await requestSnap("getAccounts", []);
}
应用程序
我们已经解释了各层之间的粘合剂,现在让我们进入第一层——网站。我们 fork 了官方基于浏览器的 polkadot app。然后,我们需要将核心功能(如签署交易、创建账户或恢复账户)从基于浏览器的钱包替换为基于 Snap 的实现。例如,为了在 MetaMask 内部签署交易,我们修改了 QrSigner 的实现,改为使用 Snap 的 signTransaction。
将 MetaMask Snap 与 polkadot app 集成带来了两项改进:
首先,它允许继承 MetaMask 扩展的安全模型,而不是使用基于本地存储的 polkadot app 钱包。
其次,我们可以使用 MetaMask 助记词以确定性方式创建 polkadot app,因此每当我们导出助记词时,Aleph Zero 密钥也会一并导出。
MetaMask API
MetaMask 提供了 API 调用,在用户授权的情况下,MetaMask Snaps 可以使用这些调用。在本例中,我们使用了其中两个:
snap_manageState —— 允许我们在 MetaMask 内部存储加密数据。例如,我们用它来存储已创建的账户。与状态的交互非常简单,可以简化为:
const request = async (method: 'clear' | 'get' | 'update', newState?: unknown): Promise<any> => {
if (['get', 'clear'].includes(method) && newState) {
throw new Error(`Cannot ${method} with newState`);
}
if (method == 'update' && !newState) {
throw new Error('Cannot call update without newState');
}
const params = newState ? [method, newState] : [method];
return await wallet.request({
method: `snap_manageState`,
params,
});
}
snap_getBip44Entropy_ —— 从 MetaMask 助记词中获取熵。文档
值得一提的是,MetaMask 有一个非常实用的库 passworder,它让我们能够轻松地加密和解密状态。在我们的案例中,我们再次使用 MetaMask 助记词的熵作为加密状态的对称密钥。
SDK
让我们回顾一下:我们通过在各处注入一些 JavaScript 来修改 polkadot app,从而启用 Snap 的使用。Snap 可以从 MetaMask 内部的助记词获取熵并管理加密状态。因此,最后一步是与 Aleph Zero 的 SDK——polkadotjs 进行集成。
这看起来是个简单的任务,只需运行 npm install @polkadot/api 并导入即可。然而,向 Snap 导入依赖项可能颇具挑战。这些困难的出现是因为 Snap 不是直接在 JavaScript 虚拟机上执行的,它必须首先在安全 ECMAScript(SES)中进行沙箱化。简而言之,通常可以正常编译的代码在 SES 中加载时可能会失败。
代码
在使用大多数库时我们不会遇到任何问题,但对我们来说情况并非如此。我们在 SES 中执行 polkadotjs SDK 时遇到了两个问题,为了解决这些问题,我们编写了两个脚本,在打包后的源代码上运行。
第一个问题是由嵌入在 JavaScript 文件中格式不正确的 wasm 代码引起的。以下是修复脚本:
const fs = require('fs').promises;
const path = require('path');
const dirPath =
'../../../../node_modules/@substrate/smoldot-light/dist/cjs/instance/autogen';
async function fixDoubleSlash() {
const files = ['wasm0.js', 'wasm1.js', 'wasm2.js'];
for (const file of files) {
console.log('Fixing ${file}');
const filePath = path.join(__dirname, dirPath, file);
const contents = await fs.readFile(filePath, 'utf8');
let fixedContents = contents;
while (fixedContents.includes('//')) {
fixedContents = fixedContents.split('//').join('/"+"/');
}
await fs.writeFile(filePath, fixedContents);
}
}
fixDoubleSlash();
第二个问题同样由 polkadot 扩展引起,它试图访问 window.addEventListener 方法,而该方法在 SES 中是被禁止的。这个问题也通过简单的方式轻松解决了:
const fs = require('fs').promises;
const path = require('path');
const bundlePath = path.join(__dirname, '../dist/bundle.js');
async function fix() {
console.log(`Fixing ${bundlePath}`);
const contents = await fs.readFile(bundlePath, 'utf8');
const replaceMissingTypes = '\n\nwindow.addEventListener = function(){};\n\n';
const fixed = contents.replace('\n\n', replaceMissingTypes);
await fs.writeFile(bundlePath, fixed);
}
fix();
如果你们继续实现这个项目,下一步是什么?
我们希望让我们的 Snap 与 polkadot.js 完全兼容,并成为 Polkadot 生态系统中可用的 Web 钱包。
能介绍一下你们自己和团队吗?
Michael 是一名在波兰某银行工作的程序员,日常工作涉及区块链技术。他热爱编程,喜欢学习新事物。
Piotr 是一名在 NuCypher 工作的软件工程师,对隐私、数据和分布式系统感兴趣。
你们是什么时候第一次接触 MetaMask Snaps 的,体验如何?
Piotr —— 最初是在大阪的 Devcon 上了解到 MetaMask Snaps 的。这个话题在今年早些时候、EthDenver 黑客马拉松前不久再次浮现。
Michaeł —— 是 Piotr 在 EthDenver 之后不久向我介绍了 MetaMask Snaps。
MetaMask Snaps 与其他钱包有何不同?
它能够在您的 dapp 中访问特定应用的加密算法/私钥——这使得在熟悉的浏览器钱包体验之上构建全新的用户流程成为可能。
请谈谈您和团队使用 MetaMask 构建 MetaMask Snaps 的体验?
基础功能已经具备,但构建更复杂的 MetaMask Snaps,尤其是需要自定义 UI 元素的 Snaps,颇具挑战。我们花了大量时间思考如何将 Web 开发者的思维模型融入 Snaps 中——比如部署、测试等方面。
MetaMask Snaps 对你们意味着什么?
它是一个赋予社区真正力量的工具,每个人都可以自行决定他们的 MetaMask 应具备哪些功能。插件让开发更加民主化,并推动更快速的增长。
您认为 MetaMask Snaps 为 Web3 领域带来了哪些机遇和可能性?
接入更多网络(尤其是非 EVM 网络);能够介入交易生命周期并添加自定义逻辑(如安全检查)或自定义用户体验;在浏览器中为 dapp 特定的敏感内容提供加密存储,以及通过 MetaMask Snaps 为用户提供"自带加密算法"的能力。
对于有意尝试 MetaMask Snaps 的开发者,您有什么建议?
在 SES 中编译 JS 库可能很困难,但根据我们的经验,这并非不可能。MetaMask 开发中的反馈周期相当长,因此请务必编写单元测试。
MetaMask Snaps 有一个规模不大但活跃的开发者社区——访问 Discord、GitHub 讨论区等可以收获颇丰,尤其是该产品仍处于早期阶段,这个圈子之外的教育资源还很有限。
使用 MetaMask Snaps 进行构建
开始使用 MetaMask Snaps:
查阅开发者文档
安装 MetaMask Flask
查看 MetaMask Snaps 指南
通过 Twitter、GitHub 讨论区 和 Discord 与我们保持联系
请关注我们团队在您附近举办的下一场黑客马拉松!祝 BUIDL 愉快 ⚒️
免责声明:MetaMask Snaps 通常由 Consensys Software 以外的第三方开发。使用第三方开发的 MetaMask Snaps 完全由您自行决定并承担风险,您同意对因此类活动导致的任何损失或损害承担全部责任。Consensys 对第三方开发的 MetaMask Snaps 不作任何明示或暗示的保证,并对第三方开发的 MetaMask Snaps 免除一切责任。使用区块链相关软件存在风险,使用 MetaMask Snaps 即表示您完全接受这些风险。