diff --git a/packages/extension/src/background/tasks/setupNetworkConfig.js b/packages/extension/src/background/tasks/setupNetworkConfig.js index d861f724b..254e0ac69 100644 --- a/packages/extension/src/background/tasks/setupNetworkConfig.js +++ b/packages/extension/src/background/tasks/setupNetworkConfig.js @@ -10,6 +10,7 @@ const backgroundContracts = [ 'AccountRegistry', 'ZkAsset', 'ERC20', + 'IERC20Permit', ]; export default async function setupNetworkConfig({ diff --git a/packages/extension/src/client/services/MetaMaskService/index.js b/packages/extension/src/client/services/MetaMaskService/index.js index eef3baeae..1149768b3 100644 --- a/packages/extension/src/client/services/MetaMaskService/index.js +++ b/packages/extension/src/client/services/MetaMaskService/index.js @@ -41,7 +41,6 @@ const handleAction = async (action, params) => { const sigParams = ethUtil.fromRpcSig(signature); const publicKey = ethUtil.ecrecover(hash, sigParams.v, sigParams.r, sigParams.s); - response = { signature: result, publicKey, @@ -102,38 +101,39 @@ const handleAction = async (action, params) => { break; } - case 'metamask.eip712.permit': { - const { - allowed, - exipiry, - nonce, - spender, - verifyingContract, - } = params; + case 'metamask.eip712.permit': { + const { + allowed, + expiry, + nonce, + spender, + verifyingContract, + } = params; - const permitSchema = permit({ - allowed, - exipiry, - nonce, - spender, - verifyingContract - }); - const method = 'eth_signTypedData_v4'; - const { result } = await Web3Service.sendAsync({ - method, - params: [address, permitSchema], - from: address, - }); + const permit = permitSchema({ + allowed, + expiry, + nonce, + spender, + holder: address, + verifyingContract, + chainId: Web3Service.networkId, + }); + const method = 'eth_signTypedData_v4'; + const { result } = await Web3Service.sendAsync({ + method, + params: [address, permit], + from: address, + }); - response = { - signature: result, - }; + response = { + signature: result, + }; - break; - } - default: + break; } default: + break; } return response; diff --git a/packages/extension/src/client/services/MetaMaskService/permitSchema.js b/packages/extension/src/client/services/MetaMaskService/permitSchema.js index b29dc71c5..71223d784 100644 --- a/packages/extension/src/client/services/MetaMaskService/permitSchema.js +++ b/packages/extension/src/client/services/MetaMaskService/permitSchema.js @@ -7,21 +7,22 @@ const { }, } = utils; - export default ({ spender, verifyingContract, allowed, expiry, - spender, - nonce + nonce, + chainId, + holder, }) => { - const domain = signer.generateDAIDomainparams(chainId, verifyingContract); + const domain = signer.generateDAIDomainParams(chainId, verifyingContract); const schema = eip712.PERMIT_SIGNATURE; const message = { allowed, spender, + holder, expiry, nonce, }; diff --git a/packages/extension/src/config/contracts/contracts.js b/packages/extension/src/config/contracts/contracts.js index 2565118a3..623425c33 100644 --- a/packages/extension/src/config/contracts/contracts.js +++ b/packages/extension/src/config/contracts/contracts.js @@ -27,4 +27,7 @@ export default { ERC20: { name: 'ERC20', }, + IERC20Permit: { + name: 'IERC20Permit', + }, }; diff --git a/packages/extension/src/config/contracts/development.js b/packages/extension/src/config/contracts/development.js index 5ec989014..4abc71c9d 100644 --- a/packages/extension/src/config/contracts/development.js +++ b/packages/extension/src/config/contracts/development.js @@ -2,6 +2,7 @@ import AccountRegistry from '~contracts/IAccountRegistryBehaviour.json'; import AccountRegistryManager from '~contracts/IAccountRegistryManager.json'; import ACE from '~contracts/ACE.json'; import IERC20 from '~contracts/IERC20Mintable.json'; +import IERC20Permit from '~contracts/IERC20Permit.json'; import IZkAsset from '~contracts/IZkAsset.json'; export default { @@ -10,4 +11,5 @@ export default { ACE, ZkAsset: IZkAsset, ERC20: IERC20, + IERC20Permit, }; diff --git a/packages/extension/src/config/contracts/index.js b/packages/extension/src/config/contracts/index.js index 61b27ada0..c08ed4238 100644 --- a/packages/extension/src/config/contracts/index.js +++ b/packages/extension/src/config/contracts/index.js @@ -20,6 +20,7 @@ const { ACE, ZkAsset, ERC20, + IERC20Permit, } = configs; export { @@ -28,4 +29,5 @@ export { ACE, ZkAsset, ERC20, + IERC20Permit, }; diff --git a/packages/extension/src/config/contracts/production.js b/packages/extension/src/config/contracts/production.js index 18106258f..65c5da5ee 100644 --- a/packages/extension/src/config/contracts/production.js +++ b/packages/extension/src/config/contracts/production.js @@ -2,6 +2,7 @@ import AccountRegistry from '~contracts/IAccountRegistryBehaviour.json'; import AccountRegistryManager from '~contracts/IAccountRegistryManager.json'; import ACE from '~contracts/IACE.json'; import IERC20 from '~contracts/IERC20Mintable.json'; +import IERC20Permit from '~contracts/IERC20Permit.json'; import IZkAsset from '~contracts/IZkAsset.json'; export default { @@ -10,4 +11,5 @@ export default { ACE, ZkAsset: IZkAsset, ERC20: IERC20, + IERC20Permit, }; diff --git a/packages/extension/src/config/supportsPermitSignature.js b/packages/extension/src/config/supportsPermitSignature.js index 25baf0b10..2cdd92a3d 100644 --- a/packages/extension/src/config/supportsPermitSignature.js +++ b/packages/extension/src/config/supportsPermitSignature.js @@ -1,3 +1,3 @@ export default { - '0x6b175474e89094c44da98b954eedeac495271d0f': true, + '0x6B175474E89094C44Da98b954EedeAC495271d0F': true, }; diff --git a/packages/extension/src/ui/apis/asset/depositWithPermit.js b/packages/extension/src/ui/apis/asset/depositWithPermit.js new file mode 100644 index 000000000..8d534fda0 --- /dev/null +++ b/packages/extension/src/ui/apis/asset/depositWithPermit.js @@ -0,0 +1,44 @@ +import ConnectionService from '~/ui/services/ConnectionService'; + +export default async function deposit({ + currentAccount: { + address: currentAddress, + }, + erc20Amount, + assetAddress, + proof, + isGSNAvailable, + signature, + expiry, + nonce, +}) { + const proofHash = proof.hash; + const proofData = proof.encodeABI(assetAddress); + + const transactionData = { + contract: 'AccountRegistry', + method: 'deposit', + data: [ + assetAddress, + currentAddress, + proofHash, + proofData, + erc20Amount.toString(), + signature, + nonce, + expiry, + ], + }; + + const response = isGSNAvailable + ? await ConnectionService.sendTransaction(transactionData) + : await ConnectionService.post({ + action: 'metamask.send', + data: transactionData, + }); + + return { + ...response, + success: !!(response && response.txReceipt), + }; +} diff --git a/packages/extension/src/ui/apis/asset/index.js b/packages/extension/src/ui/apis/asset/index.js index 68b21af71..55f8c88ca 100644 --- a/packages/extension/src/ui/apis/asset/index.js +++ b/packages/extension/src/ui/apis/asset/index.js @@ -3,10 +3,12 @@ import getAssets from './getAssets'; import getDomainAssets from './getDomainAssets'; import getLinkedTokenAddress from './getLinkedTokenAddress'; import approveERC20Allowance from './approveERC20Allowance'; +import permitERC20Allowance from './permitERC20Allowance'; import confidentialTransferFrom from './confidentialTransferFrom'; import confidentialTransfer from './confidentialTransfer'; import updateNoteMetadata from './updateNoteMetadata'; import deposit from './deposit'; +import depositWithPermit from './depositWithPermit'; export const getPastTransactions = () => []; @@ -16,8 +18,10 @@ export { getDomainAssets, getLinkedTokenAddress, approveERC20Allowance, + permitERC20Allowance, confidentialTransferFrom, confidentialTransfer, updateNoteMetadata, deposit, + depositWithPermit }; diff --git a/packages/extension/src/ui/apis/asset/permitERC20Allowance.js b/packages/extension/src/ui/apis/asset/permitERC20Allowance.js index 340048fbd..096d9ea01 100644 --- a/packages/extension/src/ui/apis/asset/permitERC20Allowance.js +++ b/packages/extension/src/ui/apis/asset/permitERC20Allowance.js @@ -3,52 +3,52 @@ import Web3Service from '~/helpers/Web3Service'; export default async function permitERC20({ asset, - requestedAllowance, allowanceSpender, }) { - - const { linkedTokenAddress, } = asset; let error; + let nonce; try { - const nonce = await Web3Service - .useContract('ERC20') + nonce = await Web3Service + .useContract('IERC20Permit') .at(linkedTokenAddress) .method('nonces') - .send( + .call( allowanceSpender, ); } catch (e) { + console.log(e); error = e; } - const exipry = Date.now() + 24 * 60 * 60; + const expiry = Date.now() + 24 * 60 * 60; const { signature, - error, + error: errorSig, } = await ConnectionService.post({ action: 'metamask.eip712.permit', data: { nonce, expiry, - allowed: requestedAllowance + allowed: true, + verifyingContract: linkedTokenAddress, spender: allowanceSpender, }, }); - if (error) { + if (errorSig || error) { return { - error, + error: errorSig || error, }; } return { - spender: sender, + spender: allowanceSpender, nonce, expiry, - allowed; requestedAllowance, + allowed: true, signature, }; } diff --git a/packages/extension/src/ui/steps/deposit.js b/packages/extension/src/ui/steps/deposit.js index 573b2bb1c..d4b1e628d 100644 --- a/packages/extension/src/ui/steps/deposit.js +++ b/packages/extension/src/ui/steps/deposit.js @@ -124,6 +124,32 @@ const stepSendViaGSN = { submitTextKey: 'transaction.send.submit', }; +const stepSendViaGSNPermit = { + name: 'send', + descriptionKey: 'transaction.gsn.send.description', + blockStyle: 'sealed', + tasks: [ + { + titleKey: 'transaction.step.create.proof', + run: apis.mock, + }, + { + titleKey: 'transaction.step.sign', + run: apis.mock, + }, + { + titleKey: 'transaction.step.relay', + run: apis.asset.depositWithPermit, + }, + { + titleKey: 'transaction.confirmed', + }, + ], + showTaskList: true, + autoStart: true, + submitTextKey: 'transaction.send.submit', +}; + export default { gsn: [ stepApproveERC20, @@ -138,7 +164,7 @@ export default { gsnWithPermit: [ stepPermitERC20, stepConfirmForGSN, - stepSendViaGSN, + stepSendViaGSNPermit, ], metamask: [ diff --git a/packages/extension/src/ui/utils/makeAsset.js b/packages/extension/src/ui/utils/makeAsset.js index 1f29bc7aa..618e50343 100644 --- a/packages/extension/src/ui/utils/makeAsset.js +++ b/packages/extension/src/ui/utils/makeAsset.js @@ -29,12 +29,14 @@ export default async function makeAsset(asset) { icon, symbol, decimals, + supportsPermit, } = linkedTokenAddress ? makeToken(linkedTokenAddress) : {}; return { ...assetObj, + supportsPermit, name, icon, symbol, diff --git a/packages/extension/src/ui/views/DepositContent.jsx b/packages/extension/src/ui/views/DepositContent.jsx index 0dbb07880..1ee1132fe 100644 --- a/packages/extension/src/ui/views/DepositContent.jsx +++ b/packages/extension/src/ui/views/DepositContent.jsx @@ -34,8 +34,8 @@ class DepositContent extends StepContentHelper { } = this.props; const currentStepName = steps[currentStep].name; - const approved = steps.every(({ name }) => name !== 'approveERC20') - || isStepAfter(steps, currentStepName, 'approveERC20'); + const approved = steps.every(({ name }) => name !== 'approveERC20' && name !== 'permitERC20') + || isStepAfter(steps, currentStepName, 'approveERC20') || isStepAfter(steps, currentStepName, 'permitERC20'); const { name: assetName, diff --git a/packages/protocol/contracts/interfaces/IERC20Permit.sol b/packages/protocol/contracts/interfaces/IERC20Permit.sol index fef64d2a9..b7b4b942a 100644 --- a/packages/protocol/contracts/interfaces/IERC20Permit.sol +++ b/packages/protocol/contracts/interfaces/IERC20Permit.sol @@ -8,8 +8,9 @@ pragma solidity >=0.5.0 <0.6.0; * (https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC20/IERC20.sol) * and with an added permit() and mint() function. */ -interface IERC20Permit { +contract IERC20Permit { + mapping (address => uint) public nonces; function transfer(address to, uint256 value) external returns (bool); function approve(address spender, uint256 value) external returns (bool);