Subsidize Ethereum blockchain transaction costs for your users

海外精选
海外精选的内容汇集了全球优质的亚马逊云科技相关技术内容。同时,内容中提到的“AWS” 是 “Amazon Web Services” 的缩写,在此网站不作为商标展示。
0
0
{"value":"Ownership is one of the core tenets of non-fungible tokens (NFTs)—you can own an NFT and prove that you own it. On blockchain, such a proof of ownership means that the NFT is in a wallet that the owner controls. Only the owner has the private key to that wallet, and only the owner can transfer tokens from the wallet. However, if a new user gets their first wallet with their freshly minted tokens, they run into a UX problem. They must own the native cryptocurrency of that blockchain to pay for transaction fees, limiting the set of potential end-users to those knowledgeable about cryptocurrency.\n\nOn example of NFTs in the music industry is how [Global Rockstar](https://www.globalrockstar.com/) offers digital shares in music production. An investor can invest in the production of a song and receive shares of that song. These shares bear the right of earning royalties. The holder receives revenues if the song is streamed or played on the radio. Global Rockstar distributes these revenues to the shareholders of the song each month.\n\nIn this post, we share how Global Rockstar updated their system to manage shareholdings and subsidize blockchain transaction costs. The token management solution uses [Amazon API Gateway](https://aws.amazon.com/api-gateway), [Amazon Web Services Lambda](http://aws.amazon.com/lambda), and [Amazon Managed Blockchain](http://aws.amazon.com/managed-blockchain).\n\n### **Legacy system and challenges**\n\nUntil recently, Global Rockstar was using a proprietary backend to manage the shareholdings, which had two main pain points:\n\n- They couldn’t easily transfer tokens from one user to another.\n- Owners couldn’t trade existing shares, making them a very long-term investment. Because the copyright for a song holds for 70 years, each token accrues revenues for that timeframe.\n\nA blockchain-based enhancement solves these problems. Shares, now called S-Music NFTs, are created as tokens on blockchain, which track current ownership. There are 1,000 tokens for each song, each representing 0.1% of the ownership. Tokens qualify the owners to royalties proportional to their token holdings.\n\nWith the records of current and historical ownership on blockchain, the existing system calculates the royalties for each investor. Transferring ownership also reduces to a simple blockchain transaction. The blockchain tracks who owned which tokens at what time. Furthermore, the token-based backend paves the way to a secondary market. With the new system, only legal hurdles for regulatory compliance remain—the technology is already in place. For an overview of Global Rockstar’s blockchain solution, watch the [Amazon Web Services re:Invent 2021 presentation](https://youtu.be/FkjkUzCgvkM?t=1554) from Christoph Straub, Global Rockstar CEO.\n\nGlobal Rockstar is appealing to music lovers and wants to keep that audience. Their existing user base is not necessarily experienced with blockchain technology. Therefore, the barrier of entry should remain low. An investor should be able to buy shares without providing their own wallet. In particular, users shouldn’t need to buy Ether first to pay for later transactions.\n\nWithin the Global Rockstar ecosystem, end-users will be able to trade existing tokens on a marketplace. After each sale, the system transfers the tokens from seller to buyer in exchange for payment. One problem remains: each transaction on the public blockchain carries a transaction fee, payable in its native cryptocurrency. To simplify operations for their users, Global Rockstar decided to subsidize some transactions. Therefore, users don’t need to buy cryptocurrency before they can do anything.\n\n### **Solution overview**\n\nThe main architecture on Amazon Web Services is a token management service consisting of an API Gateway backed by Lambda, which is responsible for sending transactions into the blockchain, and Managed Blockchain to connect to the Ethereum mainnet. The following diagram illustrates this architecture.\n\n![image.png](https://dev-media.amazoncloud.cn/537f79104fd54b0c91554c0ceef8e099_image.png)\n\nEnd-users are using the smart contract in an indirect way. First, users sign a message and hand it to Global Rockstar. Global Rockstar then wraps the message in a transaction and posts it to blockchain. With this approach, Global Rockstar calls the smart contract and pays the transaction fees.\n\nThe pattern is useful for the secondary market. Initially, a seller puts a listing on the marketplace. A buyer signals interest. The smart contract matches seller and buyer and facilitates the trade. It transfers tokens from seller to buyer in exchange for payment. Normally, this transfer could only be initiated by the owner. On the secondary market however, the market itself has to transfer them. Such a transfer by a third party (the marketplace) on the owner’s behalf needs explicit permissions. The marketplace smart contract must be approved.\n\n### **Approval functions**\n\nThe default approval function is ```setApprovalForAll(<operator_account>, true).```It’s part of the [ERC-1155 standard](https://eips.ethereum.org/EIPS/eip-1155#approval). A token owner can call it to approve an operator to transfer tokens on the owner’s behalf. (The owner can revoke the permission by setting the second parameter to false.) The token owner has to pay for the transaction, because they’re sending it to blockchain.\n\nOn the secondary market, the owner wants to approve the marketplace itself to transfer the tokens. Sellers have to approve it for token transfers. They can call ```setApprovalForAll(<marketplace>, true)```on the token contract and pay the transaction fees.\n\nGlobal Rockstar doesn’t want to require users to fund their wallet with cryptocurrencies first. This presents a problem. A user can’t send an approval transaction without funds in their wallet. Without the approval transaction, Global Rockstar’s marketplace can’t transfer tokens on the user’s behalf.\n\nBecause of the missing funds in users’ wallets, we can’t approve the marketplace directly. Instead, we use an indirect approach. With a new ```setPermitForAll(<owner>, <operator>, true, <signature>)```function, we realize free approvals for the users. The function takes additional parameters:\n\n- **owner** – An intermediary sends the transaction to the blockchain and not the original owner. Therefore, we need to set the owner explicitly.\n- **signature** – With direct approvals, the signed transaction proves that the owner is the actual owner of the tokens. Only the tokens’ owner has access to the private key, and only they can sign the transaction. With the indirect approach, an intermediary sends the transaction, so we can’t use the transaction signature to verify the token owner. Instead, the transaction carries a signed message from the owner as payload. The signature on the message serves as a proof of ownership. In contrast to direct approvals, it’s not the transaction signature itself.\n\nWith these parameters, we can use ```setPermitForAll```. The smart contract has to verify the signature and then set the correct approvals. The indirect approach follows three steps:\n\n1. **Message signature** – The user creates an approval message. It effectively says, “The marketplace may transfer tokens on my behalf.” The user signs the message with their private key and hands it to Global Rockstar’s backend. Signing doesn’t cost any transaction fees yet. The message is not yet included in the blockchain.\n2. **Message submission** – Global Rockstar takes the message and calls ```setPermitForAll```with it. They pay transaction fees as they send it to the blockchain.\n3. **Signature verification** – The smart contract receives the message and processes the signature. It verifies the signature belonging to the account that is allegedly approving token transfers. The smart contract requires a valid signature. Then it approves the operator (marketplace). The operator can now transfer tokens on the owner’s (user) behalf.\n\nThis setup achieves our goal. A user can approve the marketplace to transfer tokens on their behalf. This is a prerequisite to trading on the secondary market. To simplify things for the user, Global Rockstar pays for these transactions (and only these). If a user wants to trade on the secondary market, they can do so with minimal effort.\n\n### **Implementation**\n\nThe main idea for implementing these so-called gasless approvals has been pioneered by the [Open Gasstation Network](https://opengsn.org/). In a parallel attempt, [MakerDAO has implemented](https://github.com/makerdao/developerguides/blob/master/dai/how-to-use-permit-function/how-to-use-permit-function.md) permit for their DAI token. Our smart contract uses OpenZeppelin’s smart contract suite. They provide default contracts for the major token standards. In our case, we use an ERC-1155 token; the procedure is similar for ERC-20 or ERC-721. For ERC-20, OpenZeppelin has already provided the extension [ERC20Permit](https://docs.openzeppelin.com/contracts/4.x/api/token/erc20#ERC20Permit). It enables a similar functionality for ERC-20 tokens.\n\nWe start with the OpenZeppelin [ERC-1155 smart contract](https://docs.openzeppelin.com/contracts/4.x/erc1155). For gasless approvals, we add a new function to the contract. It verifies a signature and then calls an internal function to update the mapping of approvals. Off-chain, a user signs a message and hands it to another party (Global Rockstar’s backend). They transmit it to blockchain. We start with signature generation, follow up with message submission, and conclude with its verification.\n\n### **Message signature**\n\nIn our three-step process, we start out with signing a message. This happens entirely off-chain. The smart contract only verifies that the signature is, in fact, correct. To create a signature, we use Ethereum’s ```eth_signTypedData_v4```call. In contrast to earlier signing methods, it can sign a message and display some useful information about it (instead of a random hex string). With it, a user has more control over what they’re actually signing. For our use case, it’s the perfect match because our message is highly structured. It follows the message format as specified in [EIP712](https://eips.ethereum.org/EIPS/eip-712), which specifies a domain, types, and the actual values. The following is the code (in JavaScript, using [ethers.js](https://docs.ethers.io/v5/) as Web3 library) for creating the message:\n\n```\nconst { chainId } = await ethers.provider.getNetwork()\nconst domain = {\n name: await token.uri(1),\n version: '1',\n chainId,\n verifyingContract: token.address\n};\n\nconst EIP712Domain = [\n { name: \"name\", type: \"string\" },\n { name: \"version\", type: \"string\" },\n { name: \"chainId\", type: \"uint256\" },\n { name: \"verifyingContract\", type: \"address\" },\n];\n\nconst types = {\n EIP712Domain,\n Permit: [\n { name: 'owner', type: 'address' },\n { name: 'operator', type: 'address' },\n { name: 'approved', type: 'bool' },\n { name: 'nonce', type: 'uint256' }\n ]\n}\n\nconst nonce = await token.nonces(tokenOwner)\nconst values = {\n owner: tokenOwner,\n operator: operator,\n approved: true,\n nonce: nonce.toString(),\n};\n\nconst typedMessage = {\n types: types,\n domain: domain,\n primaryType: 'Permit',\n message: values,\n}\n```\n\nThere are a few things to note here:\n\n- The message is bound to the specific smart contract on a particular chain (specified by ```domain.name```, ```domain.chainId```, and ```domain.verifyingContract```).\n- The values bind the message to the combination of ```tokenOwner```, ```operator```, and ```approved```. Additionally, there is a ```nonce```, which the smart contract tracks for each account. The nonce provides a replay protection: the same message can’t be used twice. (This nonce is different from the nonce that each transaction has. This one is only used for the message and not for the transaction itself.)\n\nAfter all this setup, ```typedMessage```holds the clear text message. The next step is the actual signing. Ethereum’s ```eth_signTypedMessage_v4```helps with that:\n\n```\n// get the default provider\nconst provider = hre.ethers.provider\n\n// sign by calling the provider\nconst rawSignature = await provider.send(\n 'eth_signTypedData_v4', \n [tokenOwner, typedMessage]\n)\n\nvar sig = ethers.utils.splitSignature(rawSignature);\n```\nAfter this step, ```sig```contains the values ```v```, ```r```, and ```s```, which are used in the verification function call.\n\n### **Message submission**\n\nThe second step is sending the transaction to the smart contract. Any account can do this; it doesn’t need to be the account that signed the message. In our case, the Global Rockstar account sends the transaction to the network. They subsidize these transactions for their users:\n\n```\n// setPermitForAll\nawait token.setPermitForAll(tokenOwner, operator, true, \n sig.v, sig.r, sig.s)\n```\nThe smart contract verifies the signature and updates the approval mapping if the signature is correct. The operator (Global Rockstar) has paid the transaction fees.\n\n### **Signature verification**\n\nThe final step is the signature verification. OpenZeppelin’s contracts manage approvals in ```mapping(address => mapping(address => bool)) private _operatorApprovals```. The mapping takes an address as key and stores address/boolean pairs as value. For each token owner (address 1), we can query if an operator (address 2) can transfer tokens on the owner’s behalf (boolean). The mapping is private; only the contract itself can modify it. Derived contracts can’t directly change the mapping. They can use the internal function ```_setApprovalForAll(address owner, address operator, bool approved)```. It updates the mapping, but it doesn’t verify any signatures.\n\nTo add verification, we create a contract, ```ERC1155Permit```, which extends ERC1155 itself. Among the normal smart contract it adds the new function ```setPermitForAll```:\n\n```\nfunction setPermitForAll(\n address owner,\n address operator,\n bool approved,\n uint8 v,\n bytes32 r,\n bytes32 s\n) public virtual override {\n bytes32 structHash = keccak256(\n abi.encode(\n _PERMIT_TYPEHASH,\n owner,\n operator,\n approved,\n _useNonce(owner)\n )\n );\n\n bytes32 hash = _hashTypedDataV4(structHash);\n\n address signer = ECDSA.recover(hash, v, r, s);\n require(signer == owner,\n \"ERC1155Permit: invalid signature\");\n require(owner != operator,\n \"ERC1155: setting approval status for self\");\n\n _setApprovalForAll(owner, operator, approved);\n}\n```\nThe function takes the usual parameters ```owner```, ```operator```, and ```approved```. Additionally, it takes a signature in the form of three values: ```v```, ```r```, and ```s```. This is the default format for signatures so that the smart contract can recover the public address that signed the message. The function does two things:\n\n- It recreates the message from the parameters and an account-specific nonce. One thing worth noting it the ```_useNonce(owner)```call. It retrieves the current account specific nonce and increases it by one. That way, the next time a different nonce value is returned. Finally, the function recovers the public address that signed the message with ```ECDSA.revover(hash, v, r, s)```.\n- It checks if the signature originates from the alleged owner with require```(signer == owner, ...)```. This is the main check; it verifies that the owner has in fact signed a message approving the operator. After another check, the function finally approves the operator. It modifies the approval mapping with the new values.\n\nWhen the function has succeeded, the operator is approved for token transfers on the owner’s behalf. A user can always revoke the approval by calling the same function with approved set to false.\n\n### **Conclusion**\n\nThe pattern of using signed messages for approvals provides flexibility and lowers the barrier of entry for end-users. If you want to onboard new users without prior blockchain knowledge, you can make it easier for them by paying for some of their transaction fees. Not only is this a direct monetary value for your users, it also makes onboarding much simpler. Your users don’t need to top up their wallet with cryptocurrency before they can interact with your application. Instead, they can just start using the app.\n\nAs a developer, you have full control over which transactions you want to subsidize. For example, you could pay for approval transactions for your users, but not for transfer transactions. A user can still transfer tokens to a different wallet, but they need to pay for the transaction. The OpenZeppelin smart contracts already implement the pattern for ERC-20. In this post, we extended it to ERC-1155, which Global Rockstar is using.\n\nLeave a comment on how you would simplify the onboarding workflow with this technique.\n\n#### **About the author**\n\n![image.png](https://dev-media.amazoncloud.cn/58a750099c3b4d26bffe127a0807b1d3_image.png)\n\n**Christoph Niemann** is a Senior Blockchain Architect with Amazon Web Services Professional Services. He likes Blockchains and helps customers designing and building blockchain based solutions. If he’s not building with blockchains, he’s probably drinking coffee.","render":"<p>Ownership is one of the core tenets of non-fungible tokens (NFTs)—you can own an NFT and prove that you own it. On blockchain, such a proof of ownership means that the NFT is in a wallet that the owner controls. Only the owner has the private key to that wallet, and only the owner can transfer tokens from the wallet. However, if a new user gets their first wallet with their freshly minted tokens, they run into a UX problem. They must own the native cryptocurrency of that blockchain to pay for transaction fees, limiting the set of potential end-users to those knowledgeable about cryptocurrency.</p>\n<p>On example of NFTs in the music industry is how <a href=\"https://www.globalrockstar.com/\" target=\"_blank\">Global Rockstar</a> offers digital shares in music production. An investor can invest in the production of a song and receive shares of that song. These shares bear the right of earning royalties. The holder receives revenues if the song is streamed or played on the radio. Global Rockstar distributes these revenues to the shareholders of the song each month.</p>\n<p>In this post, we share how Global Rockstar updated their system to manage shareholdings and subsidize blockchain transaction costs. The token management solution uses <a href=\"https://aws.amazon.com/api-gateway\" target=\"_blank\">Amazon API Gateway</a>, <a href=\"http://aws.amazon.com/lambda\" target=\"_blank\">Amazon Web Services Lambda</a>, and <a href=\"http://aws.amazon.com/managed-blockchain\" target=\"_blank\">Amazon Managed Blockchain</a>.</p>\n<h3><a id=\"Legacy_system_and_challenges_6\"></a><strong>Legacy system and challenges</strong></h3>\n<p>Until recently, Global Rockstar was using a proprietary backend to manage the shareholdings, which had two main pain points:</p>\n<ul>\n<li>They couldn’t easily transfer tokens from one user to another.</li>\n<li>Owners couldn’t trade existing shares, making them a very long-term investment. Because the copyright for a song holds for 70 years, each token accrues revenues for that timeframe.</li>\n</ul>\n<p>A blockchain-based enhancement solves these problems. Shares, now called S-Music NFTs, are created as tokens on blockchain, which track current ownership. There are 1,000 tokens for each song, each representing 0.1% of the ownership. Tokens qualify the owners to royalties proportional to their token holdings.</p>\n<p>With the records of current and historical ownership on blockchain, the existing system calculates the royalties for each investor. Transferring ownership also reduces to a simple blockchain transaction. The blockchain tracks who owned which tokens at what time. Furthermore, the token-based backend paves the way to a secondary market. With the new system, only legal hurdles for regulatory compliance remain—the technology is already in place. For an overview of Global Rockstar’s blockchain solution, watch the <a href=\"https://youtu.be/FkjkUzCgvkM?t=1554\" target=\"_blank\">Amazon Web Services re:Invent 2021 presentation</a> from Christoph Straub, Global Rockstar CEO.</p>\n<p>Global Rockstar is appealing to music lovers and wants to keep that audience. Their existing user base is not necessarily experienced with blockchain technology. Therefore, the barrier of entry should remain low. An investor should be able to buy shares without providing their own wallet. In particular, users shouldn’t need to buy Ether first to pay for later transactions.</p>\n<p>Within the Global Rockstar ecosystem, end-users will be able to trade existing tokens on a marketplace. After each sale, the system transfers the tokens from seller to buyer in exchange for payment. One problem remains: each transaction on the public blockchain carries a transaction fee, payable in its native cryptocurrency. To simplify operations for their users, Global Rockstar decided to subsidize some transactions. Therefore, users don’t need to buy cryptocurrency before they can do anything.</p>\n<h3><a id=\"Solution_overview_21\"></a><strong>Solution overview</strong></h3>\n<p>The main architecture on Amazon Web Services is a token management service consisting of an API Gateway backed by Lambda, which is responsible for sending transactions into the blockchain, and Managed Blockchain to connect to the Ethereum mainnet. The following diagram illustrates this architecture.</p>\n<p><img src=\"https://dev-media.amazoncloud.cn/537f79104fd54b0c91554c0ceef8e099_image.png\" alt=\"image.png\" /></p>\n<p>End-users are using the smart contract in an indirect way. First, users sign a message and hand it to Global Rockstar. Global Rockstar then wraps the message in a transaction and posts it to blockchain. With this approach, Global Rockstar calls the smart contract and pays the transaction fees.</p>\n<p>The pattern is useful for the secondary market. Initially, a seller puts a listing on the marketplace. A buyer signals interest. The smart contract matches seller and buyer and facilitates the trade. It transfers tokens from seller to buyer in exchange for payment. Normally, this transfer could only be initiated by the owner. On the secondary market however, the market itself has to transfer them. Such a transfer by a third party (the marketplace) on the owner’s behalf needs explicit permissions. The marketplace smart contract must be approved.</p>\n<h3><a id=\"Approval_functions_31\"></a><strong>Approval functions</strong></h3>\n<p>The default approval function is <code>setApprovalForAll(&lt;operator_account&gt;, true).</code>It’s part of the <a href=\"https://eips.ethereum.org/EIPS/eip-1155#approval\" target=\"_blank\">ERC-1155 standard</a>. A token owner can call it to approve an operator to transfer tokens on the owner’s behalf. (The owner can revoke the permission by setting the second parameter to false.) The token owner has to pay for the transaction, because they’re sending it to blockchain.</p>\n<p>On the secondary market, the owner wants to approve the marketplace itself to transfer the tokens. Sellers have to approve it for token transfers. They can call <code>setApprovalForAll(&lt;marketplace&gt;, true)</code>on the token contract and pay the transaction fees.</p>\n<p>Global Rockstar doesn’t want to require users to fund their wallet with cryptocurrencies first. This presents a problem. A user can’t send an approval transaction without funds in their wallet. Without the approval transaction, Global Rockstar’s marketplace can’t transfer tokens on the user’s behalf.</p>\n<p>Because of the missing funds in users’ wallets, we can’t approve the marketplace directly. Instead, we use an indirect approach. With a new <code>setPermitForAll(&lt;owner&gt;, &lt;operator&gt;, true, &lt;signature&gt;)</code>function, we realize free approvals for the users. The function takes additional parameters:</p>\n<ul>\n<li><strong>owner</strong> – An intermediary sends the transaction to the blockchain and not the original owner. Therefore, we need to set the owner explicitly.</li>\n<li><strong>signature</strong> – With direct approvals, the signed transaction proves that the owner is the actual owner of the tokens. Only the tokens’ owner has access to the private key, and only they can sign the transaction. With the indirect approach, an intermediary sends the transaction, so we can’t use the transaction signature to verify the token owner. Instead, the transaction carries a signed message from the owner as payload. The signature on the message serves as a proof of ownership. In contrast to direct approvals, it’s not the transaction signature itself.</li>\n</ul>\n<p>With these parameters, we can use <code>setPermitForAll</code>. The smart contract has to verify the signature and then set the correct approvals. The indirect approach follows three steps:</p>\n<ol>\n<li><strong>Message signature</strong> – The user creates an approval message. It effectively says, “The marketplace may transfer tokens on my behalf.” The user signs the message with their private key and hands it to Global Rockstar’s backend. Signing doesn’t cost any transaction fees yet. The message is not yet included in the blockchain.</li>\n<li><strong>Message submission</strong> – Global Rockstar takes the message and calls <code>setPermitForAll</code>with it. They pay transaction fees as they send it to the blockchain.</li>\n<li><strong>Signature verification</strong> – The smart contract receives the message and processes the signature. It verifies the signature belonging to the account that is allegedly approving token transfers. The smart contract requires a valid signature. Then it approves the operator (marketplace). The operator can now transfer tokens on the owner’s (user) behalf.</li>\n</ol>\n<p>This setup achieves our goal. A user can approve the marketplace to transfer tokens on their behalf. This is a prerequisite to trading on the secondary market. To simplify things for the user, Global Rockstar pays for these transactions (and only these). If a user wants to trade on the secondary market, they can do so with minimal effort.</p>\n<h3><a id=\"Implementation_52\"></a><strong>Implementation</strong></h3>\n<p>The main idea for implementing these so-called gasless approvals has been pioneered by the <a href=\"https://opengsn.org/\" target=\"_blank\">Open Gasstation Network</a>. In a parallel attempt, <a href=\"https://github.com/makerdao/developerguides/blob/master/dai/how-to-use-permit-function/how-to-use-permit-function.md\" target=\"_blank\">MakerDAO has implemented</a> permit for their DAI token. Our smart contract uses OpenZeppelin’s smart contract suite. They provide default contracts for the major token standards. In our case, we use an ERC-1155 token; the procedure is similar for ERC-20 or ERC-721. For ERC-20, OpenZeppelin has already provided the extension <a href=\"https://docs.openzeppelin.com/contracts/4.x/api/token/erc20#ERC20Permit\" target=\"_blank\">ERC20Permit</a>. It enables a similar functionality for ERC-20 tokens.</p>\n<p>We start with the OpenZeppelin <a href=\"https://docs.openzeppelin.com/contracts/4.x/erc1155\" target=\"_blank\">ERC-1155 smart contract</a>. For gasless approvals, we add a new function to the contract. It verifies a signature and then calls an internal function to update the mapping of approvals. Off-chain, a user signs a message and hands it to another party (Global Rockstar’s backend). They transmit it to blockchain. We start with signature generation, follow up with message submission, and conclude with its verification.</p>\n<h3><a id=\"Message_signature_58\"></a><strong>Message signature</strong></h3>\n<p>In our three-step process, we start out with signing a message. This happens entirely off-chain. The smart contract only verifies that the signature is, in fact, correct. To create a signature, we use Ethereum’s <code>eth_signTypedData_v4</code>call. In contrast to earlier signing methods, it can sign a message and display some useful information about it (instead of a random hex string). With it, a user has more control over what they’re actually signing. For our use case, it’s the perfect match because our message is highly structured. It follows the message format as specified in <a href=\"https://eips.ethereum.org/EIPS/eip-712\" target=\"_blank\">EIP712</a>, which specifies a domain, types, and the actual values. The following is the code (in JavaScript, using <a href=\"https://docs.ethers.io/v5/\" target=\"_blank\">ethers.js</a> as Web3 library) for creating the message:</p>\n<pre><code class=\"lang-\">const { chainId } = await ethers.provider.getNetwork()\nconst domain = {\n name: await token.uri(1),\n version: '1',\n chainId,\n verifyingContract: token.address\n};\n\nconst EIP712Domain = [\n { name: &quot;name&quot;, type: &quot;string&quot; },\n { name: &quot;version&quot;, type: &quot;string&quot; },\n { name: &quot;chainId&quot;, type: &quot;uint256&quot; },\n { name: &quot;verifyingContract&quot;, type: &quot;address&quot; },\n];\n\nconst types = {\n EIP712Domain,\n Permit: [\n { name: 'owner', type: 'address' },\n { name: 'operator', type: 'address' },\n { name: 'approved', type: 'bool' },\n { name: 'nonce', type: 'uint256' }\n ]\n}\n\nconst nonce = await token.nonces(tokenOwner)\nconst values = {\n owner: tokenOwner,\n operator: operator,\n approved: true,\n nonce: nonce.toString(),\n};\n\nconst typedMessage = {\n types: types,\n domain: domain,\n primaryType: 'Permit',\n message: values,\n}\n</code></pre>\n<p>There are a few things to note here:</p>\n<ul>\n<li>The message is bound to the specific smart contract on a particular chain (specified by <code>domain.name</code>, <code>domain.chainId</code>, and <code>domain.verifyingContract</code>).</li>\n<li>The values bind the message to the combination of <code>tokenOwner</code>, <code>operator</code>, and <code>approved</code>. Additionally, there is a <code>nonce</code>, which the smart contract tracks for each account. The nonce provides a replay protection: the same message can’t be used twice. (This nonce is different from the nonce that each transaction has. This one is only used for the message and not for the transaction itself.)</li>\n</ul>\n<p>After all this setup, <code>typedMessage</code>holds the clear text message. The next step is the actual signing. Ethereum’s <code>eth_signTypedMessage_v4</code>helps with that:</p>\n<pre><code class=\"lang-\">// get the default provider\nconst provider = hre.ethers.provider\n\n// sign by calling the provider\nconst rawSignature = await provider.send(\n 'eth_signTypedData_v4', \n [tokenOwner, typedMessage]\n)\n\nvar sig = ethers.utils.splitSignature(rawSignature);\n</code></pre>\n<p>After this step, <code>sig</code>contains the values <code>v</code>, <code>r</code>, and <code>s</code>, which are used in the verification function call.</p>\n<h3><a id=\"Message_submission_125\"></a><strong>Message submission</strong></h3>\n<p>The second step is sending the transaction to the smart contract. Any account can do this; it doesn’t need to be the account that signed the message. In our case, the Global Rockstar account sends the transaction to the network. They subsidize these transactions for their users:</p>\n<pre><code class=\"lang-\">// setPermitForAll\nawait token.setPermitForAll(tokenOwner, operator, true, \n sig.v, sig.r, sig.s)\n</code></pre>\n<p>The smart contract verifies the signature and updates the approval mapping if the signature is correct. The operator (Global Rockstar) has paid the transaction fees.</p>\n<h3><a id=\"Signature_verification_136\"></a><strong>Signature verification</strong></h3>\n<p>The final step is the signature verification. OpenZeppelin’s contracts manage approvals in <code>mapping(address =&gt; mapping(address =&gt; bool)) private _operatorApprovals</code>. The mapping takes an address as key and stores address/boolean pairs as value. For each token owner (address 1), we can query if an operator (address 2) can transfer tokens on the owner’s behalf (boolean). The mapping is private; only the contract itself can modify it. Derived contracts can’t directly change the mapping. They can use the internal function <code>_setApprovalForAll(address owner, address operator, bool approved)</code>. It updates the mapping, but it doesn’t verify any signatures.</p>\n<p>To add verification, we create a contract, <code>ERC1155Permit</code>, which extends ERC1155 itself. Among the normal smart contract it adds the new function <code>setPermitForAll</code>:</p>\n<pre><code class=\"lang-\">function setPermitForAll(\n address owner,\n address operator,\n bool approved,\n uint8 v,\n bytes32 r,\n bytes32 s\n) public virtual override {\n bytes32 structHash = keccak256(\n abi.encode(\n _PERMIT_TYPEHASH,\n owner,\n operator,\n approved,\n _useNonce(owner)\n )\n );\n\n bytes32 hash = _hashTypedDataV4(structHash);\n\n address signer = ECDSA.recover(hash, v, r, s);\n require(signer == owner,\n &quot;ERC1155Permit: invalid signature&quot;);\n require(owner != operator,\n &quot;ERC1155: setting approval status for self&quot;);\n\n _setApprovalForAll(owner, operator, approved);\n}\n</code></pre>\n<p>The function takes the usual parameters <code>owner</code>, <code>operator</code>, and <code>approved</code>. Additionally, it takes a signature in the form of three values: <code>v</code>, <code>r</code>, and <code>s</code>. This is the default format for signatures so that the smart contract can recover the public address that signed the message. The function does two things:</p>\n<ul>\n<li>It recreates the message from the parameters and an account-specific nonce. One thing worth noting it the <code>_useNonce(owner)</code>call. It retrieves the current account specific nonce and increases it by one. That way, the next time a different nonce value is returned. Finally, the function recovers the public address that signed the message with <code>ECDSA.revover(hash, v, r, s)</code>.</li>\n<li>It checks if the signature originates from the alleged owner with require<code>(signer == owner, ...)</code>. This is the main check; it verifies that the owner has in fact signed a message approving the operator. After another check, the function finally approves the operator. It modifies the approval mapping with the new values.</li>\n</ul>\n<p>When the function has succeeded, the operator is approved for token transfers on the owner’s behalf. A user can always revoke the approval by calling the same function with approved set to false.</p>\n<h3><a id=\"Conclusion_179\"></a><strong>Conclusion</strong></h3>\n<p>The pattern of using signed messages for approvals provides flexibility and lowers the barrier of entry for end-users. If you want to onboard new users without prior blockchain knowledge, you can make it easier for them by paying for some of their transaction fees. Not only is this a direct monetary value for your users, it also makes onboarding much simpler. Your users don’t need to top up their wallet with cryptocurrency before they can interact with your application. Instead, they can just start using the app.</p>\n<p>As a developer, you have full control over which transactions you want to subsidize. For example, you could pay for approval transactions for your users, but not for transfer transactions. A user can still transfer tokens to a different wallet, but they need to pay for the transaction. The OpenZeppelin smart contracts already implement the pattern for ERC-20. In this post, we extended it to ERC-1155, which Global Rockstar is using.</p>\n<p>Leave a comment on how you would simplify the onboarding workflow with this technique.</p>\n<h4><a id=\"About_the_author_187\"></a><strong>About the author</strong></h4>\n<p><img src=\"https://dev-media.amazoncloud.cn/58a750099c3b4d26bffe127a0807b1d3_image.png\" alt=\"image.png\" /></p>\n<p><strong>Christoph Niemann</strong> is a Senior Blockchain Architect with Amazon Web Services Professional Services. He likes Blockchains and helps customers designing and building blockchain based solutions. If he’s not building with blockchains, he’s probably drinking coffee.</p>\n"}
目录
亚马逊云科技解决方案 基于行业客户应用场景及技术领域的解决方案
联系亚马逊云科技专家
亚马逊云科技解决方案
基于行业客户应用场景及技术领域的解决方案
联系专家
0
目录
关闭
contact-us