Recentemente escrevi um artigo sobre Karma Money , um sistema monetário alternativo baseado em um token ERC-20 exclusivo. Imaginei o Karma Money como um sistema fechado, onde os usuários também podem pagar taxas de transação com o karma. Construir nosso próprio blockchain seria uma forma de tornar isso realidade, mas é uma tarefa desafiadora. Para garantir a segurança e a credibilidade da blockchain, precisaríamos estabelecer a infraestrutura necessária e uma comunidade suficientemente grande. Seria muito mais simples usar um blockchain existente. Existem redes como ou , que são totalmente compatíveis com Ethereum e têm taxas de transação muito baixas. A taxa para uma transação ERC20 nessas redes é normalmente inferior a 1 centavo. O problema é que essa taxa deve ser paga na própria criptomoeda da rede, o que pode dificultar o uso da rede pelos usuários. Felizmente, existe uma solução intermediária, as transações de metal .
Em uma transação de carma, por exemplo, o usuário fornece o valor da transação (por exemplo, 10 dólares de carma), o endereço Ethereum para onde deseja enviar o valor e uma taxa de transação (em dólares de carma) que está disposto a oferecer. para a transação. Essa estrutura é assinada digitalmente e enviada para um nó retransmissor. Se o nó considerar a taxa de transação aceitável, ele submete a estrutura assinada digitalmente ao contrato de carma, que verifica a assinatura e executa a transação. Como a taxa de transação é paga na moeda nativa do blockchain pelo nó de retransmissão, parece ao usuário que ele está pagando com dólares de carma pela transação sem precisar de seu próprio blockchain.
async function main() { if (!window.ethereum || !window.ethereum.isMetaMask) { console.log("Please install MetaMask") return } const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }); const chainId = await window.ethereum.request({ method: 'eth_chainId' }); const eip712domain_type_definition = { "EIP712Domain": [ { "name": "name", "type": "string" }, { "name": "version", "type": "string" }, { "name": "chainId", "type": "uint256" }, { "name": "verifyingContract", "type": "address" } ] } const karma_request_domain = { "name": "Karma Request", "version": "1", "chainId": chainId, "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" } document.getElementById('transfer_request')?.addEventListener("click", async function () { const transfer_request = { "types": { ...eip712domain_type_definition, "TransferRequest": [ { "name": "to", "type": "address" }, { "name": "amount", "type": "uint256" } ] }, "primaryType": "TransferRequest", "domain": karma_request_domain, "message": { "to": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", "amount": 1234 } } let signature = await window.ethereum.request({ "method": "eth_signTypedData_v4", "params": [ accounts[0], transfer_request ] }) alert("Signature: " + signature) }) } main()
O eip712domain_type_definition é uma descrição de uma estrutura geral, que contém os metadados. O campo name é o nome da estrutura, o campo version é a versão de definição da estrutura e os campos chainId e verifyingContract determinam a qual contrato a mensagem se destina. O contrato em execução verifica esses metadados para garantir que a transação assinada seja executada apenas no contrato de destino.
O karma_request_domain contém o valor específico dos metadados definidos pela estrutura EIP712Domain.
A estrutura real que enviamos ao MetaMask para assinatura está contida na variável transfer_request . O bloco de tipos contém as definições de tipo. Aqui, o primeiro elemento é a definição obrigatória de EIP712Domain, que descreve os metadados. Isto é seguido pela definição da estrutura real, que neste caso é TransferRequest. Esta é a estrutura que aparecerá no MetaMask para o usuário. O bloco de domínio contém o valor específico dos metadados, enquanto a mensagem contém a estrutura específica que queremos assinar com o usuário.
const types = { "TransferRequest": [ { "name": "from", "type": "address" }, { "name": "to", "type": "address" }, { "name": "amount", "type": "uint256" }, { "name": "fee", "type": "uint256" }, { "name": "nonce", "type": "uint256" } ] } let nonce = await contract.connect(MINER).getNonce(ALICE.address) const message = { "from": ALICE.address, "to": JOHN.address, "amount": 10, "fee": 1, "nonce": nonce } const signature = await ALICE.signTypedData(karma_request_domain, types, message) await contract.connect(MINER).metaTransfer(ALICE.address, JOHN.address, 10, 1, nonce, signature) assert.equal(await contract.balanceOf(ALICE.address), ethers.toBigInt(11))
A variável types define a estrutura da transação. O “de” é o endereço do remetente, enquanto o “para” é o endereço do destinatário. O valor representa a quantidade de tokens a serem transferidos. A taxa é a “quantidade” de tokens que oferecemos ao nó de retransmissão em troca da execução de nossa transação e cobertura do custo na moeda nativa da cadeia. O “nonce” serve como contador para garantir a exclusividade da transação. Sem este campo, uma transação poderia ser executada várias vezes. No entanto, graças ao nonce, uma transação assinada só pode ser executada uma vez.
A função signTypedData fornecida por facilita a assinatura de estruturas EIP-712. Ele faz a mesma coisa que o código apresentado anteriormente, mas com um uso mais simples.
A metaTransfer é o método do contrato de carma para executar a metatransação. Vamos ver como isso funciona:
function metaTransfer( address from, address to, uint256 amount, uint256 fee, uint256 nonce, bytes calldata signature ) public virtual returns (bool) { uint256 currentNonce = _useNonce(from, nonce); (address recoveredAddress, ECDSA.RecoverError err) = ECDSA.tryRecover( _hashTypedDataV4( keccak256( abi.encode( TRANSFER_REQUEST_TYPEHASH, from, to, amount, fee, currentNonce ) ) ), signature ); require( err == ECDSA.RecoverError.NoError && recoveredAddress == from, "Signature error" ); _transfer(recoveredAddress, to, amount); _transfer(recoveredAddress, msg.sender, fee); return true; }
EIP-712 é um padrão geral para estruturas de assinatura, tornando-o apenas um dos muitos usos para implementação de metatransações. Como a assinatura pode ser validada não apenas com contratos inteligentes, também pode ser muito útil em aplicações não-blockchain. Por exemplo, pode ser usado para autenticação do lado do servidor, onde o usuário se identifica com sua chave privada. Tal sistema pode fornecer um alto nível de segurança normalmente associado a criptomoedas, permitindo a possibilidade de utilizar uma aplicação web apenas com uma chave de hardware. Além disso, chamadas de API individuais também podem ser assinadas com a ajuda do MetaMask.