Перейти к содержимому

Как создать функцию социальных приглашений в вашем dapp с помощью Delegation Toolkit

Создайте функцию социальных приглашений в своём dapp с помощью Delegation Toolkit

5 мин
Как создать функцию социальных приглашений в вашем dapp с помощью Delegation Toolkit

В нашем предыдущем посте, Что такое Delegation Toolkit и что с его помощью можно создать?, мы рассмотрели, как Delegation Toolkit от MetaMask позволяет разработчикам создавать более гибкие и удобные dapp-приложения. Один из интересных сценариев использования — создание функции социальных приглашений, позволяющей пользователям приглашать друзей с минимальными трудностями.

В этом руководстве мы разберём, как реализовать подобный сценарий: существующий пользователь может делегировать ограниченные разрешения другому, что позволяет новым пользователям начать работу с dapp без привычных сложностей, связанных с настройкой и пополнением кошелька заранее.

Представьте: Алиса хочет, чтобы Боб попробовал dapp, которым она пользуется. Она отправляет ему приглашение, позволяющее получить небольшое количество ETH с её кошелька — например, 0,001 ETH — в течение установленного времени. Боб может сразу начать пользоваться dapp, не устанавливая расширение кошелька и не оплачивая газ самостоятельно. При этом Алиса сохраняет полный контроль над своим кошельком, не раскрывая приватный ключ.

Именно такой упрощённый процесс онбординга делает возможным Delegation Toolkit — с помощью делегирования разрешений и безопасных подписей вне блокчейна. Давайте кратко опишем структуру того, что мы будем создавать.

Приглашающий

Приглашающий — это пользователь, который хочет предоставить ограниченный доступ к своему кошельку, чтобы помочь другому пользователю пройти онбординг. В этом руководстве приглашающий будет использовать аккаунт MetaMask Delegator — смарт-контрактный кошелёк на основе EIP-4337. Вы можете создать новый аккаунт делегатора или использовать уже существующий, если он у вас есть. Для развёртывания и инфраструктуры мы будем использовать сервисы EIP-4337 от Pimlico.

Приглашение

Приглашение в данном контексте — это просто делегирование: подписанный объект, созданный аккаунтом делегатора и хранящийся вне блокчейна. После того как аккаунт делегатора развёрнут и пополнен ETH, можно создать делегирование, позволяющее другому пользователю получить определённое количество токенов в течение заданного времени. Такой подход избавляет приглашённого от необходимости пополнять кошелёк или оплачивать газ заранее, что значительно упрощает онбординг.

Для простоты мы будем использовать так называемое открытое делегирование. Это означает, что любой, у кого есть доступ к подписанному делегированию, может его использовать. Поскольку оно не привязано к конкретному получателю, нет необходимости управлять приватным ключом приглашающего или раскрывать его.

Приглашённый

Приглашённый — это пользователь, проходящий онбординг. Он может использовать либо Externally Owned Account (EOA), либо Smart Contract Account (SCA) — в зависимости от вашей реализации. В этом руководстве мы автоматически создадим EOA для приглашённого, когда тот перейдёт по ссылке-приглашению. В production-приложении в идеале следует проверять, подключён ли у приглашённого уже существующий кошелёк, и использовать его, однако реализация этой логики выходит за рамки данного руководства.

Условия

Каждое приглашение может включать условия, определяющие, как и когда оно может быть использовано. Эти условия, также известные как caveats, могут задавать лимит финансирования, срок действия или ограничения на количество использований делегирования. В нашем случае мы настроим делегирование так, чтобы оно могло быть использовано только один раз, с максимальной суммой токенов (X) и временным окном (Y) для базовой безопасности.

Принятие приглашения

Когда приглашённый переходит по ссылке и принимает приглашение, он использует делегирование. Этот процесс инициирует пользовательскую операцию в блокчейне для перевода делегированных средств. Для отправки этой операции и завершения процесса мы снова воспользуемся инфраструктурой Pimlico.

Теперь, когда вы понимаете общий сценарий, давайте приступим к реализации.

Предварительные требования

Перед началом разработки убедитесь, что у вас настроено следующее:

  • Node.js (версия 18 или выше)

  • RPC-эндпоинт Infura для тестовой сети Sepolia

  • Базовое понимание абстракции аккаунтов EIP-4337, включая ключевые концепции: Paymaster, Bundler и User Operations

  • URL-адреса Pimlico Bundler и Paymaster

Установка зависимостей

Сначала установите delegation toolkit:

npm install @metamask/delegation-toolkit

Затем установите Viem для всех взаимодействий с Ethereum:

npm install viem

Создание аккаунта приглашающего

Аккаунт приглашающего — это кошелёк, который предоставляет ограниченные разрешения для онбординга нового пользователя. На нём должно быть достаточно криптовалюты для покрытия комиссий за газ приглашённого при его первых взаимодействиях с dapp. В этом руководстве мы будем использовать смарт-контрактный аккаунт, часто называемый Gator-аккаунтом, для выполнения этой роли.

Тем не менее, благодаря обновлению Pectra, обычные EOA с поддержкой EIP-7702 теперь также могут делегировать разрешения. Это открывает возможность для традиционных кошельков участвовать в совместном использовании разрешений без перехода на смарт-контрактный аккаунт. Мы подробнее рассмотрим этот подход в отдельном посте.

Для начала создайте файл index.ts в корне вашего проекта и добавьте следующие фрагменты кода:

import { http, createPublicClient } from "viem";
import { privateKeyToAccount, generatePrivateKey } from "viem/accounts";
import { sepolia as chain } from "viem/chains";
import {
  Implementation,
  toMetaMaskSmartAccount,
} from "@metamask/delegator-core-viem";

const RPCEndpoint = "<INFURA RPC ENDPOINT HERE>";
const transport = http(RPCEndpoint);
const publicClient = createPublicClient({ transport, chain });

const privateKey = generatePrivateKey();
const owner = privateKeyToAccount(privateKey);

const deploySalt = "0x";

const account = await toMetaMaskSmartAccount({
  client: publicClient,
  implementation: Implementation.Hybrid,
  deployParams: [owner.address, [], [], []],
  deploySalt,
  signatory: { account: owner },
});
Копировать

При создании аккаунта делегатора у вас есть два основных варианта:

  • Hybrid account: Этот тип требует только одного подписанта — EOA или любого совместимого с P256 подписанта. Указывается с помощью implementation: Implementation.Hybrid.

  • Multisig account: Этот вариант требует нескольких EOA для подписи от имени аккаунта делегатора. Указывается с помощью implementation: Implementation.MultiSig.

В этом руководстве мы используем hybrid account и устанавливаем в качестве подписанта обычный EOA. Обратите внимание: хотя в приведённом примере мы генерируем приватный ключ, это можно изменить для программного использования приватного ключа существующего кошелька.

Теперь, когда мы создали аккаунт делегатора, важно учесть следующее: аккаунты, созданные с помощью toMetaMaskSmartAccount, являются контрфактическими. Это означает, что аккаунт не существует в блокчейне до его развёртывания. Вы по-прежнему можете получить адрес аккаунта с помощью account.address, но если вы попытаетесь найти его в блокчейн-эксплорере, вы его там не обнаружите.

Тем не менее вы уже можете отправлять токены на этот адрес. После развёртывания аккаунта эти токены станут доступны, а все ожидающие действия, привязанные к этому адресу, можно будет выполнить немедленно.

Развёртывание аккаунта делегатора приглашающего

Чтобы продолжить, нам нужно развернуть контрфактический аккаунт, чтобы он мог взаимодействовать с блокчейном. В большинстве dapp этот шаг обычно выполняется в процессе онбординга — например, когда пользователь входит в систему или регистрируется.

Для развёртывания мы воспользуемся инфраструктурой Pimlico. Перейдите в панель управления Pimlico, создайте API-ключ и скопируйте предоставленный URL. Этот URL используется как для bundler (для ретрансляции пользовательских операций), так и для paymaster (для спонсирования комиссий за газ).

Поскольку специальной функции для развёртывания контрфактического аккаунта не существует, мы инициируем развёртывание, отправив фиктивную пользовательскую операцию в bundler. Эта операция не будет выполнена успешно, но Pimlico развернёт аккаунт перед попыткой её выполнения — что нам и нужно.

Настройка Pimlico bundler и paymaster

Установите пакет @pimlico/permissionless для взаимодействия с bundler, paymaster и инструментами пользовательских операций Pimlico:

npm install permissionless

Далее настроим Pimlico bundler и paymaster и приступим к развёртыванию Gator-аккаунта:

… 
import {
  createBundlerClient,
  createPaymasterClient,
} from "viem/account-abstraction";
import { createPimlicoClient } from "permissionless/clients/pimlico";
import { zeroAddress } from "viem";

const pimlicoURL = process.env.PIMLICO_URL;

  const paymasterClient = createPaymasterClient({
    transport: http(pimlicoURL),
  });

  const bundlerClient = createBundlerClient({
    transport: http(pimlicoURL),
    paymaster: paymasterClient,
  });

  const pimlicoClient = createPimlicoClient({
    transport: http(pimlicoURL),
  });;

// deploy the gator account
const { fast: gasPrice } = await pimlicoClient.getUserOperationGasPrice();
const isDeployed = await account.isDeployed();
if (!isDeployed) {
  const hash = await bundlerClient.sendUserOperation({
    account,
    calls: [{ to: zeroAddress }],
    ...gasPrice,
  });

  const { receipt } = await bundlerClient.waitForUserOperationReceipt({ hash });
}
Копировать

В приведённом фрагменте мы сначала инициализируем клиенты Pimlico paymaster и bundler, а затем сам клиент Pimlico. Затем мы проверяем, был ли Gator-аккаунт уже развёрнут. Если нет — отправляем фиктивную пользовательскую операцию, используя ещё не развёрнутый аккаунт в качестве отправителя.

Pimlico bundler возвращает хэш транзакции после того, как пользовательская операция упакована и отправлена в mempool. Мы используем этот хэш, чтобы дождаться майнинга и подтверждения транзакции в блокчейне.

Подробнее о том, как этот процесс работает под капотом, можно узнать в документации Pimlico.

Последний шаг — пополнить только что развёрнутый аккаунт делегатора тестовым ETH. Это можно сделать, посетив кран MetaMask Sepolia.

Создание приглашения

Теперь, когда у приглашающего настроен пополненный аккаунт делегатора, следующий шаг — создать и подписать открытое делегирование. Это делегирование позволит приглашённому получить определённое количество тестового ETH с аккаунта приглашающего.

Делегирования определяются с помощью типа DelegationStruct, который имеет следующую структуру:

export type DelegationStruct = {
  delegate: Hex;             // The address receiving the delegated permission
  delegator: Hex;            // The address granting the permission
  authority: Hex;            // The parent delegation hash, or ROOT_AUTHORITY if it's the root
  caveats: CaveatStruct[];   // Optional rules that limit what the delegate can do
  salt: bigint;              // A random value to avoid hash collisions
  signature: Hex;            // The delegator's signature
};
Копировать

В данном случае мы создадим корневое делегирование — первое звено в цепочке делегирований. Хотя на основе корневого делегирования можно строить цепочки повторных делегирований, в этом руководстве мы сосредоточимся только на корневом делегировании.

Существует два способа создать корневое делегирование:

  • Если вы заранее не знаете адрес делегата (как в этом руководстве), можно создать открытое делегирование, которое может использовать любой аккаунт.

  • Если адрес делегата известен, можно создать закрытое делегирование, явно указав его в поле delegate.

Мы выберем подход с открытым делегированием для гибкости и простоты:

...
import {
...
  createOpenDelegation,
} from "@metamask/delegator-core-viem";

const caveatBuilder = createCaveatBuilder(account.environment);
const caveats = caveatBuilder
  .addCaveat("limitedCalls", 1)
  .addCaveat("nativeTokenTransferAmount", BigInt(1))
  .addCaveat("timestamp", 0, Math.floor(Date.now() / 1000) + 86_400);

const openDelegation = createOpenDelegation({
  from: account.address,
  caveats,
});
Копировать

На этом шаге мы добавили три caveat для управления использованием делегирования:

  • Лимит выполнений: с помощью caveat limitedCalls мы ограничиваем количество действий, которые делегат может выполнять от имени приглашающего.

  • Ограничение на перевод токенов: caveat nativeTokenTransferAmount ограничивает общую сумму нативных токенов, которые можно перевести с аккаунта приглашающего.

  • Срок действия: мы добавляем caveat на основе временной метки, чтобы делегирование стало недействительным по истечении определённого времени. В данном случае приглашение истекает через 24 часа.

Существует множество других caveats, которые можно включить, но для нашего сценария этих достаточно. Полный список поддерживаемых caveats можно найти в документации; при необходимости вы также можете определить собственные caveats.

После определения делегирования последний шаг — подписать и сохранить его. Чтобы делегирование было действительным, оно должно быть подписано делегатором. Аккаунт MetaMask Delegator предоставляет метод signDelegation, который мы будем использовать для этой цели.

Что касается хранения, вы можете выбрать любой удобный способ. В этом руководстве мы воспользуемся простым вариантом — localStorage. Обратите внимание, что мы используем stringify из библиотеки superjson для сериализации делегирования, поскольку встроенный JSON.stringify в JavaScript не поддерживает значения BigInt.

npm install superjson
...
import { stringify } from "superjson";


const signature = await account.signDelegation({
  delegation: openDelegation,
});

localStorage.setItem(
  "DELEGATION",
  stringify({ delegation: openDelegation, signature })
);
Копировать

Использование приглашения

Теперь перейдём к перспективе приглашённого — пользователя, который использует делегирование и выполняет действие, например переводит токены с аккаунта приглашающего на свой.

Шаг 1: Получение делегирования из хранилища

Поскольку мы сохранили подписанное делегирование локально, приглашённый может получить его напрямую из localStorage:

import { parse } from "superjson";

const storedData = localStorage.getItem("DELEGATION");

if (!storedData) {
  throw new Error("Delegation not found in storage");
}

const { delegation, signature } = parse(storedData);
Копировать

Шаг 2: Создание выполнения

Выполнение представляет собой конкретное действие, которое делегат имеет право выполнить на аккаунте делегатора. Оно должно соответствовать всем caveats, определённым в делегировании.

Объект выполнения обычно выглядит следующим образом:

{
  target,   // the address being called as a hex string
  value,    // the value of the call as a bigint
  callData  // the calldata as a hex string
}
Копировать

Для упрощения можно использовать вспомогательный метод createExecution из SDK:

import { createExecution } from "@codefi/delegator-core-viem";

const execution = createExecution(
       target,   // the address being called as a hex string
  	 value,    // the value of the call as a bigint
  	  callData  // the calldata as a hex string
      );
Копировать

В нашем случае, поскольку мы не вызываем смарт-контракт, а просто переводим ETH от делегатора к делегату, выполнение выглядит следующим образом:

const execution = createExecution(
        delegateAccount.address,
        amount
      );
Копировать

Шаг 3: Отправка выполнения через UserOperation

Эта часть может показаться сложной, если вы не знакомы с концепциями абстракции аккаунтов, поэтому разберём её подробнее.

На высоком уровне наша цель — отправить UserOperation, которая содержит все необходимые данные транзакции для использования делегирования. Несмотря на кажущуюся абстрактность, в итоге это приведёт к выполнению стандартной транзакции Ethereum в блокчейне.

Эта транзакция вызовет функцию redeemDelegations на смарт-контрактном аккаунте делегатора. Эта функция отвечает за:

  1. Проверку делегирования и валидацию его caveats

  2. Определение способа обработки выполнения (режим погашения). Подробнее здесь

  3. Выполнение желаемого действия (в данном случае — перевод токенов)

Простыми словами, redeemDelegations проверяет, действительно ли делегирование, и если всё в порядке — выполняет запланированное действие.

Для отправки этой транзакции мы воспользуемся методом sendUserOperation, предоставляемым клиентом bundler. Он абстрагирует сложность построения и подписания UserOperation:

import { encodeFunctionData } from "viem";
import {
  encodeExecutionCalldatas,
  encodePermissionContexts,
  SINGLE_DEFAULT_MODE,
} from "@codefi/delegator-core-viem";
import { createPimlicoClient } from "permissionless/clients/pimlico";
import { createBundlerClient } from "viem/account-abstraction";

 const bundlerClient = createBundlerClient({
        transport: http(process.env.NEXT_PUBLIC_BUNDLER_URL),
        chain: lineaSepolia,
      });

      const pimlicoClient = createPimlicoClient({
        transport: http(process.env.NEXT_PUBLIC_BUNDLER_URL),
        chain: lineaSepolia,
      });

      const { fast } = await pimlicoClient.getUserOperationGasPrice();


     
  const redeemData = encodeFunctionData({
          abi: delegateAccount.abi,
          functionName: "redeemDelegations",
          args: [
            encodePermissionContexts([delegation]),
            [SINGLE_DEFAULT_MODE],
            encodeExecutionCalldatas([[execution]]),
          ],
        });

      const hash = await bundlerClient.sendUserOperation({
        account: delegateAccount,
        calls: [
          {
            to: delegateAccount.address,
            value: 0n,
            data: redeemData,
          },
        ],
        paymaster: true,
        ...fast,
      });


  await bundlerClient.waitForUserOperationReceipt({
        hash,
      });
Копировать

Переведено ИИ. Может содержать ошибки. Пожалуйста, всегда проверяйте информацию.

Оцените перевод

Автор статьи:

  • Kingsley Okonkwo

    Кингсли — сертифицированный разработчик блокчейна Ethereum, работающий техническим писателем в Consensys с особым акцентом на инструменты для разработчиков. До прихода в Consensys Кингсли работал фриланс-разработчиком бэкенда на нескольких платформах, включая Gigster и Braintrust. Имея за плечами 5 лет опыта в разработке программного обеспечения, он уверенно владеет такими инструментами, как JavaScript (Node.js и React), GoLang, SQL и Docker. Кингсли мастерски создаёт пошаговые руководства и практические гайды, используя новейшие технологии web3, чтобы помочь коллегам-разработчикам создавать более качественные dapps. В настоящее время проживая в Дубае, Кингсли в свободное время увлекается баскетболом и футболом.

    Читать все статьи