MetaMask Snaps — это дорожная карта, которая сделает MetaMask самым расширяемым кошельком в мире. Как разработчик, вы можете добавлять свои функции и API в MetaMask совершенно новыми способами. Web3-разработчики — основа этого роста, и данная серия материалов призвана продемонстрировать новаторские MetaMask Snaps, создаваемые сегодня.
Aleph Zero Snap с Пётром и Майклом
Мы беседуем с Web3-разработчиками Пётром и Майклом, которые создали Aleph Zero Snap на хакатоне ETHWarsaw.
Репозиторий Snap: https://github.com/piotr-roslaniec/ethwarsaw-2022/tree/main/azero-snap/packages/snap-adapter
Почему вы его создали?
На наш взгляд, работа с кошельком в экосистеме Polkadot по-прежнему оставляет желать лучшего. С другой стороны, интеграция с MetaMask Snaps могла бы помочь привлечь уже существующую аудиторию web3-пользователей в сеть Aleph Zero.
Расскажите о технической реализации.
Адаптер
Наш snap состоит из двух частей. Первая — это веб-сайт, с которым взаимодействуют пользователи. Вторая — сам Snap. Эти два элемента работают по принципу, схожему с архитектурой клиент-сервер, где snap выступает сервером, а веб-сайт — клиентом. Чтобы отразить эту естественную границу, мы вынесли API snap в отдельный пакет под названием 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", []);
}
Приложение
Итак, мы объяснили связующее звено между нашими слоями — перейдём к первому из них: веб-сайту. Мы сделали форк официального браузерного приложения Polkadot. Затем нам потребовалось заменить ключевые функции — такие как подписание транзакций, создание и восстановление аккаунтов — с браузерного кошелька на кошелёк на основе Snap. Например, для подписания транзакций внутри MetaMask мы изменили реализацию QrSigner, чтобы использовать signTransaction из Snap.
Интеграция MetaMask Snap с приложением Polkadot принесла два улучшения:
Во-первых, появилась возможность унаследовать модель безопасности расширения MetaMask вместо использования кошелька приложения Polkadot, основанного на локальном хранилище.
Во-вторых, мы смогли использовать seed MetaMask для детерминированного создания аккаунта в приложении Polkadot — таким образом, при экспорте seed ключ 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_ — для получения энтропии из seed MetaMask. Документация
Стоит упомянуть замечательную библиотеку MetaMask passworder, которая позволяет легко шифровать и расшифровывать состояние. В нашем случае в качестве симметричного ключа для шифрования состояния мы снова использовали энтропию из seed MetaMask.
SDK
Подведём итог: мы модифицировали приложение Polkadot, внедрив фрагменты JavaScript, обеспечивающие использование Snap. Snap может получать энтропию из seed внутри MetaMask и управлять зашифрованным состоянием. Последним шагом стала интеграция с SDK для Aleph Zero — polkadotjs.
Казалось бы, простая задача: выполни npm install @polkadot/api и импортируй. Однако импорт зависимостей в Snap может быть непростым делом. Сложности возникают потому, что Snap выполняется не напрямую на виртуальной машине JavaScript — сначала он должен быть изолирован в Secure ECMAScript (SES). Если коротко: код, который в обычных условиях компилируется без проблем, может не загрузиться в SES.
Код
При работе с большинством библиотек проблем не возникает, но в нашем случае всё оказалось иначе. У нас было две проблемы с выполнением polkadotjs SDK внутри SES; для их устранения мы написали два скрипта, которые запускались на собранном исходном коде.
Первая была вызвана некорректно отформатированным wasm-кодом, встроенным в JavaScript-файл. Вот исправляющий скрипт:
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.
Расскажите немного о себе и своей команде.
Майкл — программист в польском банке, ежедневно работающий с технологией блокчейн. Он увлечён программированием и любит осваивать новое.
Пётр — инженер-программист в NuCypher. Его интересуют приватность, данные и распределённые системы.
Когда вы впервые узнали о MetaMask Snaps и каким было ваше первое впечатление?
Пётр — Впервые узнал о MetaMask Snaps на Devcon в Осаке. Тема снова всплыла в начале этого года, незадолго до хакатона EthDenver.
Майкл — Пётр познакомил меня с MetaMask Snaps вскоре после EthDenver.
Чем MetaMask Snaps отличается от других кошельков?
Возможность получить доступ к криптографии и приватным ключам, специфичным для приложения, прямо в вашем dapp — это открывает возможность создавать новые пользовательские сценарии поверх привычного опыта работы с браузерным кошельком.
Расскажите, каково это — создавать MetaMask Snaps вместе с MetaMask для вас и вашей команды?
Основа есть, но создание более сложных MetaMask Snaps, особенно тех, которые требуют нестандартных элементов UI, — непростая задача. Мы потратили немало времени, пытаясь вписать нашу модель опыта веб-разработчика в Snaps — такие вещи, как деплой, тестирование и прочее.
Что для вас значит MetaMask Snaps?
Это инструмент, который даёт реальную власть сообществу: каждый сам решает, какие функции должны быть в его MetaMask. Плагины позволяют демократизировать разработку и обеспечивают более быстрый рост.
Какие возможности вы видите в MetaMask Snaps и что это открывает для пространства Web3?
Доступ к большему числу сетей (особенно не-EVM); возможность встраиваться в жизненный цикл транзакций и добавлять пользовательскую логику (например, проверки безопасности) или настраивать UX; зашифрованное хранилище в браузере для конфиденциального контента, специфичного для dapp, а также возможность «принести свою криптографию» пользователям с помощью MetaMask Snaps.
Какой совет вы хотели бы дать разработчикам, желающим попробовать MetaMask Snaps?
Компиляция JS-библиотек в SES может быть непростой, но, по нашему опыту, это не невозможно. Цикл обратной связи при разработке MetaMask довольно длинный, поэтому обязательно пишите юнит-тесты.
Вокруг MetaMask Snaps сложилось небольшое, но активное сообщество разработчиков — посещение Discord, обсуждений на GitHub и других площадок может быть очень полезным, особенно учитывая, что продукт ещё новый и за пределами этого круга образовательных материалов пока немного.
Создание с MetaMask Snaps
Чтобы начать работу с MetaMask Snaps:
Ознакомьтесь с документацией для разработчиков
Установите MetaMask Flask
Изучите руководство по MetaMask Snaps
Оставайтесь на связи с нами в Twitter, обсуждениях на GitHub и Discord
Следите за нашей командой на следующем хакатоне рядом с вами! Удачного BUIDLing'а ⚒️
Отказ от ответственности: MetaMask Snaps, как правило, разрабатываются третьими сторонами, а не Consensys Software. Использование MetaMask Snaps, разработанных третьими сторонами, осуществляется по вашему собственному усмотрению и на ваш риск, и вы соглашаетесь с тем, что несёте единоличную ответственность за любые убытки или ущерб, возникшие в результате такой деятельности. Consensys не даёт никаких явных или подразумеваемых гарантий, устных или письменных, в отношении MetaMask Snaps, разработанных третьими сторонами, и отказывается от какой-либо ответственности за них. Использование программного обеспечения, связанного с блокчейном, сопряжено с рисками, которые вы принимаете в полной мере при использовании MetaMask Snaps.