Como criar um recurso de convite social no seu dapp usando o Delegation Toolkit

Crie um recurso de convite social no seu dapp usando o Delegation Toolkit

Como criar um recurso de convite social no seu dapp usando o Delegation Toolkit

Em nosso post anterior, O que é o Delegation Toolkit e o que você pode criar com ele?, exploramos como o Delegation Toolkit da MetaMask capacita desenvolvedores a criar dapps mais flexíveis e fáceis de usar. Um caso de uso interessante é a criação de um recurso de convite social que permite aos usuários integrar seus amigos com o mínimo de fricção.

Este tutorial mostra como implementar esse tipo de fluxo, no qual um usuário existente pode delegar permissões limitadas a outro, facilitando que novos usuários explorem um dapp sem as barreiras habituais de configurar e financiar uma carteira antecipadamente.

Imagine o seguinte: Alice quer que Bob experimente um dapp que ela está usando. Ela envia a ele um convite que permite reivindicar uma pequena quantidade de ETH da carteira dela — digamos, 0,001 ETH — dentro de um prazo determinado. Bob pode começar a usar o dapp imediatamente, sem precisar instalar uma extensão de carteira ou pagar taxas de gas por conta própria. Enquanto isso, Alice mantém total controle de sua carteira, sem expor sua chave privada.

Esse é o tipo de experiência de integração simplificada que o Delegation Toolkit torna possível, usando delegação com permissões e assinaturas seguras off-chain. Vamos esboçar rapidamente a estrutura do que vamos construir.

Convidante

O convidante é o usuário que deseja compartilhar acesso limitado à sua carteira para ajudar a integrar outra pessoa. Neste tutorial, o convidante usará uma conta MetaMask Delegator, que é uma carteira de contrato inteligente baseada no EIP-4337. Você pode criar uma nova conta delegadora ou usar uma existente, caso já tenha configurado uma. Para lidar com a implantação e a infraestrutura, usaremos os serviços EIP-4337 da Pimlico.

Convite

Um convite, neste contexto, é simplesmente uma delegação — um objeto assinado criado pela conta delegadora e armazenado off-chain. Depois que a conta delegadora for implantada e financiada com ETH, você poderá criar uma delegação que permite a outro usuário reivindicar uma determinada quantidade de tokens dentro de um prazo específico. Essa configuração elimina a necessidade de o convidado financiar uma carteira ou pagar gas antecipadamente, tornando a integração muito mais fluida.

Para simplificar, usaremos o que é chamado de delegação aberta. Isso significa que qualquer pessoa com acesso à delegação assinada pode resgatá-la. Como ela não está vinculada a um destinatário específico, não há necessidade de gerenciar ou expor a chave privada do convidante.

Convidado

O convidado é a pessoa sendo integrada. Ele pode usar uma Conta de Propriedade Externa (EOA) ou uma Conta de Contrato Inteligente (SCA), dependendo da sua implementação. Neste tutorial, geraremos automaticamente uma EOA para o convidado quando ele clicar no link de reivindicação. Em um dapp em produção, o ideal seria detectar se o convidado já tem uma carteira conectada e usá-la, mas implementar essa lógica está além do escopo deste guia.

Condições

Cada convite pode incluir condições para controlar como e quando ele pode ser usado. Essas condições, também conhecidas como caveats, podem especificar um limite de financiamento, um prazo de resgate ou restrições sobre quantas vezes a delegação pode ser usada. No nosso caso, configuraremos a delegação para ser resgatável apenas uma vez, com um valor máximo de tokens (X) e uma janela de tempo (Y) para garantir segurança básica.

Aceitar o Convite

Quando o convidado clica no link de reivindicação e aceita o convite, ele está resgatando a delegação. Esse processo aciona uma operação de usuário na blockchain para transferir os fundos delegados. Vamos contar com a infraestrutura da Pimlico novamente para enviar essa operação e concluir o fluxo.

Agora que você entende o fluxo, vamos começar a implementação.

Pré-requisitos

Antes de começarmos a construir, certifique-se de ter o seguinte configurado:

  • Node.js (versão 18 ou posterior)

  • Um endpoint RPC da Infura para a testnet Sepolia

  • Conhecimento básico de abstração de contas EIP-4337, incluindo conceitos-chave como Paymaster, Bundler e User Operations

  • URLs do Bundler e Paymaster da Pimlico

Instalar dependências

Primeiro, instale o delegation toolkit:

npm install @metamask/delegation-toolkit

Em seguida, instale o Viem para todas as interações com o Ethereum:

npm install viem

Criar a conta do Convidante

A conta do convidante é a carteira que compartilha permissões limitadas para ajudar a integrar um novo usuário. Ela deve ter criptomoedas suficientes para cobrir as taxas de gas do convidado durante suas interações iniciais com o dapp. Neste tutorial, usaremos uma conta de contrato inteligente, frequentemente chamada de conta Gator, para desempenhar esse papel.

Dito isso, graças à atualização Pectra, EOAs comuns com suporte ao EIP-7702 agora também podem delegar permissões. Isso abre a possibilidade de carteiras tradicionais participarem do compartilhamento de permissões sem precisar migrar para uma conta de contrato inteligente. Vamos explorar essa abordagem com mais profundidade em um post separado.

Para começar, crie um arquivo chamado index.ts na raiz do seu projeto e adicione os seguintes trechos de código:

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 },
});
Copiar

Ao criar uma conta delegadora, você tem duas opções principais:

  • Conta híbrida: Este tipo requer apenas um signatário, que pode ser uma EOA ou qualquer signatário compatível com P256. É especificado usando implementation: Implementation.Hybrid.

  • Conta multisig: Esta opção requer múltiplas EOAs para assinar em nome da conta delegadora. É especificado usando implementation: Implementation.MultiSig.

Neste tutorial, estamos usando uma conta híbrida e definindo o signatário como uma EOA comum. Observe que, embora estejamos gerando uma chave privada no exemplo acima, isso pode ser ajustado para usar a chave privada de uma carteira existente de forma programática.

Agora que criamos a conta delegadora, há um detalhe importante a ter em mente: contas criadas usando toMetaMaskSmartAccount são contrafactuais. Isso significa que a conta não existe on-chain até ser implantada. Você ainda pode recuperar o endereço da conta usando account.address, mas se tentar buscá-lo em um explorador de blocos, ainda não o encontrará.

No entanto, você ainda pode enviar tokens para esse endereço antecipadamente. Assim que a conta for implantada, esses tokens estarão acessíveis e quaisquer ações pendentes vinculadas a esse endereço poderão ser executadas imediatamente.

Implantar a Conta Delegadora do Convidante

Para avançar, precisamos implantar a conta contrafactual para que ela possa interagir com a blockchain. Na maioria dos dapps, essa etapa normalmente seria acionada durante a integração, como quando um usuário faz login ou se cadastra.

Para lidar com a implantação, usaremos a infraestrutura da Pimlico. Acesse seu painel da Pimlico, crie uma chave de API e copie a URL fornecida. Essa URL serve tanto como bundler (para retransmitir operações de usuário) quanto como paymaster (para patrocinar taxas de gas).

Como não há uma função dedicada para implantar uma conta contrafactual, acionaremos a implantação enviando uma operação de usuário fictícia ao bundler. Essa operação não será bem-sucedida, mas a Pimlico implantará a conta antes de tentar executá-la, que é tudo o que precisamos.

Configurar o bundler e o paymaster da Pimlico

Instale o pacote @pimlico/permissionless para interagir com o bundler, paymaster e ferramentas de operação de usuário da Pimlico:

npm install permissionless

Em seguida, configuraremos o bundler e o paymaster da Pimlico e prosseguiremos com a implantação da conta 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 });
}
Copiar

No trecho acima, primeiro inicializamos os clientes de paymaster e bundler da Pimlico, seguidos pelo próprio cliente Pimlico. Em seguida, verificamos se a conta Gator já foi implantada. Se não foi, enviamos uma operação de usuário fictícia usando a conta ainda não implantada como remetente.

O bundler da Pimlico retorna um hash de transação assim que a operação de usuário é agrupada e enviada ao mempool. Usamos esse hash para aguardar que a transação seja minerada e confirmada on-chain.

Para mais detalhes sobre como esse processo funciona internamente, consulte a documentação da Pimlico.

O passo final é financiar sua conta delegadora recém-implantada com algum ETH de teste. Você pode fazer isso visitando a faucet Sepolia da MetaMask.

Criar um Convite

Com uma conta delegadora financiada agora configurada para o convidante, o próximo passo é criar e assinar uma delegação aberta. Essa delegação permitirá que o convidado reivindique uma quantidade específica de ETH de teste da conta do convidante.

As delegações são definidas usando o tipo DelegationStruct, que tem a seguinte estrutura:

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
};
Copiar

Neste caso, criaremos uma delegação raiz, que é o primeiro elo em uma cadeia de delegação. Embora seja possível construir sobre uma delegação raiz para criar uma cadeia de re-delegações, este tutorial focará apenas na delegação raiz em si.

Há duas maneiras de criar uma delegação raiz:

  • Se você não souber o endereço do delegado com antecedência (como neste tutorial), pode criar uma delegação aberta, que pode ser resgatada por qualquer conta.

  • Se você souber o endereço do delegado, pode criar uma delegação fechada definindo explicitamente o campo delegate para esse endereço.

Optaremos pela abordagem de delegação aberta para maior flexibilidade e simplicidade:

...
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,
});
Copiar

Nesta etapa, adicionamos três caveats para controlar como a delegação pode ser usada:

  • Limite de execução: Usando o caveat limitedCalls, restringimos o número de vezes que o delegado pode realizar ações em nome do convidante.

  • Limite de transferência de tokens: O caveat nativeTokenTransferAmount limita o valor total de tokens nativos que podem ser transferidos da conta do convidante.

  • Expiração: Adicionamos um caveat baseado em timestamp para garantir que a delegação se torne inválida após um determinado tempo. Neste caso, o convite expira em 24 horas.

Embora existam muitos outros caveats que você pode incluir, estes são suficientes para o nosso caso de uso. Você pode encontrar uma lista completa de caveats suportados na documentação e, se necessário, também pode definir caveats personalizados.

Depois que a delegação estiver definida, o passo final é assiná-la e armazená-la. Para que uma delegação seja válida, ela deve ser assinada pelo delegador. A conta delegadora da MetaMask expõe um método signDelegation, que usaremos para essa finalidade.

Quanto ao armazenamento, você pode escolher qualquer método de sua preferência. Neste tutorial, vamos simplificar e usar o localStorage. Observe que usamos stringify da biblioteca superjson para serializar a delegação, pois o JSON.stringify nativo do JavaScript não suporta valores BigInt.

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


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

localStorage.setItem(
  "DELEGATION",
  stringify({ delegation: openDelegation, signature })
);
Copiar

Resgatar o convite

Agora vamos mudar para a perspectiva do convidado, ou seja, o usuário que vai resgatar a delegação e realizar uma ação, como transferir tokens da conta do convidante para a sua própria.

Passo 1: Buscar a delegação do armazenamento

Como armazenamos a delegação assinada localmente, o convidado pode recuperá-la diretamente do 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);
Copiar

Passo 2: Criar uma execução

Uma execução representa a ação específica que o delegado está autorizado a realizar na conta do delegador. Ela deve respeitar todos os caveats definidos na delegação.

Um objeto de execução normalmente tem esta aparência:

{
  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
}
Copiar

Para simplificar, você pode usar o método auxiliar createExecution do 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
      );
Copiar

No nosso caso, como não estamos chamando um contrato inteligente, mas simplesmente transferindo ETH do delegador para o delegado, a execução fica assim:

const execution = createExecution(
        delegateAccount.address,
        amount
      );
Copiar

Passo 3: Enviar a execução com uma UserOperation

Esta parte pode ser um pouco complexa se você não estiver familiarizado com conceitos de abstração de contas, então vamos detalhar.

Em linhas gerais, nosso objetivo é enviar uma UserOperation que encapsula todos os dados de transação necessários para resgatar a delegação. Embora possa parecer abstrato, isso resultará em uma transação Ethereum padrão sendo executada on-chain.

Essa transação chamará uma função chamada redeemDelegations na Conta de Contrato Inteligente Delegadora. Essa função é responsável por:

  1. Verificar a delegação e validar seus caveats

  2. Determinar como a execução deve ser processada (modo de resgate). Mais detalhes aqui

  3. Executar a ação desejada (neste caso, transferir tokens)

Em termos simples, redeemDelegations verifica se a delegação ainda é válida e, se tudo estiver correto, prossegue para realizar a ação pretendida.

Para enviar essa transação, usaremos o método sendUserOperation fornecido pelo cliente bundler. Isso abstrai a complexidade de construir e assinar a 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,
      });
Copiar

Traduzido por IA. Pode conter erros. Por favor, verifique sempre as informações.

Avalie a tradução
  • Kingsley Okonkwo
    Kingsley Okonkwo

    Kingsley, desenvolvedor certificado de Blockchain Ethereum, atua como redator técnico na Consensys, com foco especial em ferramentas para desenvolvedores. Antes de ingressar na Consensys, Kingsley trabalhou como desenvolvedor backend freelance em diversas plataformas, incluindo Gigster e Braintrust. Com 5 anos de experiência em desenvolvimento de software, ele domina ferramentas como JavaScript (Node.js e React), GoLang, SQL e Docker. Kingsley se destaca na criação de tutoriais passo a passo e guias práticos, utilizando o que há de mais moderno em tecnologia web3 para ajudar outros desenvolvedores a criar dapps melhores. Atualmente radicado em Dubai, Kingsley aproveita seu tempo livre jogando basquete e futebol.

    Ler todos os artigos