diff --git a/client/src/PublicRequest.ts b/client/src/PublicRequest.ts index 34e71678a..567471098 100644 --- a/client/src/PublicRequest.ts +++ b/client/src/PublicRequest.ts @@ -221,7 +221,7 @@ export type PolygonTransactionInfo = { /** * The sender's nonce in the token contract, required when calling the - * contract function `transferWithApproval` for bridged USDC.e. + * contract function `swapWithApproval` for bridged USDC.e. */ approval?: { tokenNonce: number, diff --git a/src/assets/icons/usdc_dark.svg b/src/assets/icons/usdc_dark.svg new file mode 100644 index 000000000..7f80ce961 --- /dev/null +++ b/src/assets/icons/usdc_dark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/PolygonAddressInfo.js b/src/components/PolygonAddressInfo.js index 1bb71c2a6..3e9e789bd 100644 --- a/src/components/PolygonAddressInfo.js +++ b/src/components/PolygonAddressInfo.js @@ -7,7 +7,7 @@ class PolygonAddressInfo { // eslint-disable-line no-unused-vars /** * @param {string} address * @param {string} [label] - * @param {'none' | 'usdc' | 'unknown'} [logo = 'none'] + * @param {'none' | 'usdc' | 'usdc_dark' | 'unknown'} [logo = 'none'] */ constructor(address, label, logo = 'none') { this._address = address; @@ -37,7 +37,7 @@ class PolygonAddressInfo { // eslint-disable-line no-unused-vars $avatar.classList.add('unlabelled'); } $el.appendChild($avatar); - } else if (this._logo === 'usdc' || this._logo === 'unknown') { + } else if (this._logo === 'usdc' || this._logo === 'usdc_dark' || this._logo === 'unknown') { const $img = document.createElement('img'); $img.classList.add('logo'); $img.src = `../../assets/icons/${this._logo}.svg`; diff --git a/src/config/config.local.js b/src/config/config.local.js index 4eee1fb41..d0902ddfd 100644 --- a/src/config/config.local.js +++ b/src/config/config.local.js @@ -17,10 +17,11 @@ const CONFIG = { // eslint-disable-line no-unused-vars ROOT_REDIRECT: 'https://wallet.nimiq-testnet.com', POLYGON_CHAIN_ID: 80001, - USDC_CONTRACT_ADDRESS: '0x0FA8781a83E46826621b3BC094Ea2A0212e71B23', - USDC_TRANSFER_CONTRACT_ADDRESS: '0x2805f3187dcDfa424EFA8c55Db6012Cf08Fa6eEc', // v3 - USDC_HTLC_CONTRACT_ADDRESS: '0x2EB7cd7791b947A25d629219ead941fCd8f364BF', + BRIDGED_USDC_CONTRACT_ADDRESS: '0x0FA8781a83E46826621b3BC094Ea2A0212e71B23', + BRIDGED_USDC_HTLC_CONTRACT_ADDRESS: '0x2EB7cd7791b947A25d629219ead941fCd8f364BF', NATIVE_USDC_CONTRACT_ADDRESS: '0x9999f7Fea5938fD3b1E26A12c3f2fb024e194f97', NATIVE_USDC_TRANSFER_CONTRACT_ADDRESS: '0x5D101A320547f8D640c44fDfe5d1f35224f00B8B', // v1 + + USDC_SWAP_CONTRACT_ADDRESS: '0xf4a619F6561CeE543BDa9BBA0cAC68758B607714', }; diff --git a/src/config/config.mainnet.js b/src/config/config.mainnet.js index c49802267..0ea16cc40 100644 --- a/src/config/config.mainnet.js +++ b/src/config/config.mainnet.js @@ -8,10 +8,11 @@ const CONFIG = { // eslint-disable-line no-unused-vars ROOT_REDIRECT: 'https://wallet.nimiq.com', POLYGON_CHAIN_ID: 137, - USDC_CONTRACT_ADDRESS: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', - USDC_TRANSFER_CONTRACT_ADDRESS: '0x98E69a6927747339d5E543586FC0262112eBe4BD', - USDC_HTLC_CONTRACT_ADDRESS: '0xF615bD7EA00C4Cc7F39Faad0895dB5f40891359f', + BRIDGED_USDC_CONTRACT_ADDRESS: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', + BRIDGED_USDC_HTLC_CONTRACT_ADDRESS: '0xF615bD7EA00C4Cc7F39Faad0895dB5f40891359f', NATIVE_USDC_CONTRACT_ADDRESS: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', NATIVE_USDC_TRANSFER_CONTRACT_ADDRESS: '0x3157d422cd1be13AC4a7cb00957ed717e648DFf2', // v1 + + USDC_SWAP_CONTRACT_ADDRESS: '', }; diff --git a/src/config/config.testnet.js b/src/config/config.testnet.js index cf13e7271..0a344811c 100644 --- a/src/config/config.testnet.js +++ b/src/config/config.testnet.js @@ -8,10 +8,11 @@ const CONFIG = { // eslint-disable-line no-unused-vars ROOT_REDIRECT: 'https://wallet.nimiq-testnet.com', POLYGON_CHAIN_ID: 80001, - USDC_CONTRACT_ADDRESS: '0x0FA8781a83E46826621b3BC094Ea2A0212e71B23', - USDC_TRANSFER_CONTRACT_ADDRESS: '0x2805f3187dcDfa424EFA8c55Db6012Cf08Fa6eEc', - USDC_HTLC_CONTRACT_ADDRESS: '0x2EB7cd7791b947A25d629219ead941fCd8f364BF', + BRIDGED_USDC_CONTRACT_ADDRESS: '0x0FA8781a83E46826621b3BC094Ea2A0212e71B23', + BRIDGED_USDC_HTLC_CONTRACT_ADDRESS: '0x2EB7cd7791b947A25d629219ead941fCd8f364BF', NATIVE_USDC_CONTRACT_ADDRESS: '0x9999f7Fea5938fD3b1E26A12c3f2fb024e194f97', NATIVE_USDC_TRANSFER_CONTRACT_ADDRESS: '0x5D101A320547f8D640c44fDfe5d1f35224f00B8B', // v1 + + USDC_SWAP_CONTRACT_ADDRESS: '0xf4a619F6561CeE543BDa9BBA0cAC68758B607714', }; diff --git a/src/lib/polygon/PolygonContractABIs.full.js.txt b/src/lib/polygon/PolygonContractABIs.full.js.txt index 0f731aa89..275215d27 100644 --- a/src/lib/polygon/PolygonContractABIs.full.js.txt +++ b/src/lib/polygon/PolygonContractABIs.full.js.txt @@ -1,6 +1,6 @@ /* eslint-disable max-len */ const PolygonContractABIsFull = { // eslint-disable-line no-unused-vars - USDC_CONTRACT_ABI: [ + BRIDGED_USDC_CONTRACT_ABI: [ 'event Approval(address indexed owner, address indexed spender, uint256 value)', 'event AuthorizationCanceled(address indexed authorizer, bytes32 indexed nonce)', 'event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce)', @@ -74,55 +74,7 @@ const PolygonContractABIsFull = { // eslint-disable-line no-unused-vars 'function withdrawWithAuthorization(address owner, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s)', ], - USDC_TRANSFER_CONTRACT_ABI: [ - 'constructor()', - 'event DomainRegistered(bytes32 indexed domainSeparator, bytes domainValue)', - 'event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)', - 'event RequestTypeRegistered(bytes32 indexed typeHash, string typeStr)', - 'function EIP712_DOMAIN_TYPE() view returns (string)', - 'function deposits(address) view returns (uint256)', - 'function domains(bytes32) view returns (bool)', - 'function execute(tuple(address from, address to, uint256 value, uint256 gas, uint256 nonce, bytes data, uint256 validUntil) request, bytes32 domainSeparator, bytes32 requestTypeHash, bytes suffixData, bytes signature) payable returns (bool success, bytes ret)', - 'function getGasAndDataLimits() view returns (tuple(uint256 acceptanceBudget, uint256 preRelayedCallGasLimit, uint256 postRelayedCallGasLimit, uint256 calldataSizeLimit) limits)', - 'function getHubAddr() view returns (address)', - 'function getMinimumRelayFee(tuple(uint256 gasPrice, uint256 pctRelayFee, uint256 baseRelayFee, address relayWorker, address paymaster, address forwarder, bytes paymasterData, uint256 clientId) relayData) view returns (uint256 amount)', - 'function getNonce(address from) view returns (uint256)', - 'function getRelayHubDeposit() view returns (uint256)', - 'function getRequiredRelayFee(tuple(uint256 gasPrice, uint256 pctRelayFee, uint256 baseRelayFee, address relayWorker, address paymaster, address forwarder, bytes paymasterData, uint256 clientId) relayData, bytes4 methodId) view returns (uint256 amount)', - 'function getRequiredRelayGas(bytes4 methodId) view returns (uint256 gas)', - 'function isTrustedForwarder(address forwarder) view returns (bool)', - 'function owner() view returns (address)', - 'function postRelayedCall(bytes context, bool success, uint256 gasUseWithoutPost, tuple(uint256 gasPrice, uint256 pctRelayFee, uint256 baseRelayFee, address relayWorker, address paymaster, address forwarder, bytes paymasterData, uint256 clientId) relayData)', - 'function preRelayedCall(tuple(tuple(address from, address to, uint256 value, uint256 gas, uint256 nonce, bytes data, uint256 validUntil) request, tuple(uint256 gasPrice, uint256 pctRelayFee, uint256 baseRelayFee, address relayWorker, address paymaster, address forwarder, bytes paymasterData, uint256 clientId) relayData) relayRequest, bytes signature, bytes approvalData, uint256 maxPossibleGas) returns (bytes context, bool revertOnRecipientRevert)', - 'function registerDomainSeparator(string name, string version)', - 'function registerRequestType(string typeName, string typeSuffix)', - 'function registerToken(address token, address pool)', - 'function registeredTokenPool(address) view returns (address)', - 'function registeredTokenPoolFee(address token) view returns (uint24 fee)', - 'function renounceOwnership()', - 'function requiredRelayGas() view returns (uint256)', - 'function setGasAndDataLimits(tuple(uint256 acceptanceBudget, uint256 preRelayedCallGasLimit, uint256 postRelayedCallGasLimit, uint256 calldataSizeLimit) limits)', - 'function setMaxRequiredRelayGas(uint256 gas)', - 'function setRelayHub(address hub)', - 'function setRequiredRelayGas(bytes4 methodId, uint256 gas)', - 'function setWrappedChainToken(address _wrappedChainToken)', - 'function transfer(address token, uint256 amount, address target, uint256 fee)', - 'function transferOwnership(address newOwner)', - 'function transferWithApproval(address token, uint256 amount, address target, uint256 fee, uint256 approval, bytes32 sigR, bytes32 sigS, uint8 sigV)', - 'function trustedForwarder() view returns (address forwarder)', - 'function typeHashes(bytes32) view returns (bool)', - 'function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes _data)', - 'function unregisterToken(address token)', - 'function verify(tuple(address from, address to, uint256 value, uint256 gas, uint256 nonce, bytes data, uint256 validUntil) forwardRequest, bytes32 domainSeparator, bytes32 requestTypeHash, bytes suffixData, bytes signature) view', - 'function versionPaymaster() view returns (string)', - 'function versionRecipient() view returns (string)', - 'function withdraw(uint256 amount, address target)', - 'function withdrawRelayHubDeposit(uint256 amount, address target)', - 'function withdrawToken(address token, uint256 amount, address target)', - 'function wrappedChainToken() view returns (address)', - ], - - USDC_HTLC_CONTRACT_ABI: [ + BRIDGED_USDC_HTLC_CONTRACT_ABI: [ 'constructor()', 'event DomainRegistered(bytes32 indexed domainSeparator, bytes domainValue)', 'event Open(bytes32 indexed id, address token, uint256 amount, address recipient, bytes32 hash, uint256 timeout)', @@ -295,5 +247,55 @@ const PolygonContractABIsFull = { // eslint-disable-line no-unused-vars 'function withdrawToken(address token, uint256 amount, address target)', 'function wrappedChainToken() view returns (address)', ], + + SWAP_CONTRACT_ABI: [ + "constructor()", + "event DomainRegistered(bytes32 indexed domainSeparator, bytes domainValue)", + "event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)", + "event RequestTypeRegistered(bytes32 indexed typeHash, string typeStr)", + "function EIP712_DOMAIN_TYPE() view returns (string)", + "function deposits(address) view returns (uint256)", + "function domains(bytes32) view returns (bool)", + "function execute(tuple(address from, address to, uint256 value, uint256 gas, uint256 nonce, bytes data, uint256 validUntil) request, bytes32 domainSeparator, bytes32 requestTypeHash, bytes suffixData, bytes signature) payable returns (bool success, bytes ret)", + "function getGasAndDataLimits() view returns (tuple(uint256 acceptanceBudget, uint256 preRelayedCallGasLimit, uint256 postRelayedCallGasLimit, uint256 calldataSizeLimit) limits)", + "function getHubAddr() view returns (address)", + "function getMinimumRelayFee(tuple(uint256 gasPrice, uint256 pctRelayFee, uint256 baseRelayFee, address relayWorker, address paymaster, address forwarder, bytes paymasterData, uint256 clientId) relayData) view returns (uint256 amount)", + "function getNonce(address from) view returns (uint256)", + "function getRelayHubDeposit() view returns (uint256)", + "function getRequiredRelayFee(tuple(uint256 gasPrice, uint256 pctRelayFee, uint256 baseRelayFee, address relayWorker, address paymaster, address forwarder, bytes paymasterData, uint256 clientId) relayData, bytes4 methodId) view returns (uint256 amount)", + "function getRequiredRelayGas(bytes4 methodId) view returns (uint256 gas)", + "function isTrustedForwarder(address forwarder) view returns (bool)", + "function owner() view returns (address)", + "function postRelayedCall(bytes context, bool success, uint256 gasUseWithoutPost, tuple(uint256 gasPrice, uint256 pctRelayFee, uint256 baseRelayFee, address relayWorker, address paymaster, address forwarder, bytes paymasterData, uint256 clientId) relayData)", + "function preRelayedCall(tuple(tuple(address from, address to, uint256 value, uint256 gas, uint256 nonce, bytes data, uint256 validUntil) request, tuple(uint256 gasPrice, uint256 pctRelayFee, uint256 baseRelayFee, address relayWorker, address paymaster, address forwarder, bytes paymasterData, uint256 clientId) relayData) relayRequest, bytes signature, bytes approvalData, uint256 maxPossibleGas) returns (bytes context, bool revertOnRecipientRevert)", + "function registerDomainSeparator(string name, string version)", + "function registerRequestType(string typeName, string typeSuffix)", + "function registerSwapPool(address pool)", + "function registerToken(address token, address pool)", + "function registeredSwapPool(address) view returns (bool)", + "function registeredTokenPool(address) view returns (address)", + "function registeredTokenPoolFee(address token) view returns (uint24 fee)", + "function renounceOwnership()", + "function requiredRelayGas() view returns (uint256)", + "function setGasAndDataLimits(tuple(uint256 acceptanceBudget, uint256 preRelayedCallGasLimit, uint256 postRelayedCallGasLimit, uint256 calldataSizeLimit) limits)", + "function setMaxRequiredRelayGas(uint256 gas)", + "function setRelayHub(address hub)", + "function setRequiredRelayGas(bytes4 methodId, uint256 gas)", + "function setWrappedChainToken(address _wrappedChainToken)", + "function swap(address token, uint256 amount, address pool, uint256 targetAmount, uint256 fee)", + "function swapWithApproval(address token, uint256 amount, address pool, uint256 targetAmount, uint256 fee, uint256 approval, bytes32 sigR, bytes32 sigS, uint8 sigV)", + "function transferOwnership(address newOwner)", + "function trustedForwarder() view returns (address forwarder)", + "function typeHashes(bytes32) view returns (bool)", + "function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes _data)", + "function unregisterToken(address token)", + "function verify(tuple(address from, address to, uint256 value, uint256 gas, uint256 nonce, bytes data, uint256 validUntil) forwardRequest, bytes32 domainSeparator, bytes32 requestTypeHash, bytes suffixData, bytes signature) view", + "function versionPaymaster() view returns (string)", + "function versionRecipient() view returns (string)", + "function withdraw(uint256 amount, address target)", + "function withdrawRelayHubDeposit(uint256 amount, address target)", + "function withdrawToken(address token, uint256 amount, address target)", + "function wrappedChainToken() view returns (address)", + ], }; /* eslint-enable max-len */ diff --git a/src/lib/polygon/PolygonContractABIs.js b/src/lib/polygon/PolygonContractABIs.js index 30516a83c..767c59967 100644 --- a/src/lib/polygon/PolygonContractABIs.js +++ b/src/lib/polygon/PolygonContractABIs.js @@ -1,15 +1,10 @@ /* eslint-disable max-len */ const PolygonContractABIs = { // eslint-disable-line no-unused-vars - USDC_CONTRACT_ABI: [ + BRIDGED_USDC_CONTRACT_ABI: [ 'function approve(address spender, uint256 amount) returns (bool)', ], - USDC_TRANSFER_CONTRACT_ABI: [ - 'function transfer(address token, uint256 amount, address target, uint256 fee)', - 'function transferWithApproval(address token, uint256 amount, address target, uint256 fee, uint256 approval, bytes32 sigR, bytes32 sigS, uint8 sigV)', - ], - - USDC_HTLC_CONTRACT_ABI: [ + BRIDGED_USDC_HTLC_CONTRACT_ABI: [ 'function open(bytes32 id, address token, uint256 amount, address refundAddress, address recipientAddress, bytes32 hash, uint256 timeout, uint256 fee)', 'function openWithApproval(bytes32 id, address token, uint256 amount, address refundAddress, address recipientAddress, bytes32 hash, uint256 timeout, uint256 fee, uint256 approval, bytes32 sigR, bytes32 sigS, uint8 sigV)', 'function redeem(bytes32 id, address target, bytes32 secret, uint256 fee)', @@ -23,5 +18,10 @@ const PolygonContractABIs = { // eslint-disable-line no-unused-vars 'function transfer(address token, uint256 amount, address target, uint256 fee)', 'function transferWithPermit(address token, uint256 amount, address target, uint256 fee, uint256 value, bytes32 sigR, bytes32 sigS, uint8 sigV)', ], + + SWAP_CONTRACT_ABI: [ + 'function swap(address token, uint256 amount, address pool, uint256 targetAmount, uint256 fee)', + 'function swapWithApproval(address token, uint256 amount, address pool, uint256 targetAmount, uint256 fee, uint256 approval, bytes32 sigR, bytes32 sigS, uint8 sigV)', + ], }; /* eslint-enable max-len */ diff --git a/src/lib/polygon/PolygonKey.js b/src/lib/polygon/PolygonKey.js index 3ea2c81f7..9f0e6efed 100644 --- a/src/lib/polygon/PolygonKey.js +++ b/src/lib/polygon/PolygonKey.js @@ -65,7 +65,7 @@ class PolygonKey { // eslint-disable-line no-unused-vars const domain = { name: 'USD Coin (PoS)', // This is currently the same for testnet and mainnet version: '1', // This is currently the same for testnet and mainnet - verifyingContract: CONFIG.USDC_CONTRACT_ADDRESS, + verifyingContract: CONFIG.BRIDGED_USDC_CONTRACT_ADDRESS, salt: ethers.utils.hexZeroPad(ethers.utils.hexlify(CONFIG.POLYGON_CHAIN_ID), 32), }; diff --git a/src/request/sign-polygon-transaction/SignPolygonTransaction.js b/src/request/sign-polygon-transaction/SignPolygonTransaction.js index c3834b2cb..2e1aeb567 100644 --- a/src/request/sign-polygon-transaction/SignPolygonTransaction.js +++ b/src/request/sign-polygon-transaction/SignPolygonTransaction.js @@ -34,18 +34,29 @@ class SignPolygonTransaction { const $sender = (this.$el.querySelector('.accounts .sender')); if (request.description.name === 'refund') { new PolygonAddressInfo(relayRequest.to, request.senderLabel, 'unknown').renderTo($sender); + } else if (request.description.name === 'swap' || request.description.name === 'swapWithApproval') { + new PolygonAddressInfo(relayRequest.from, 'USDC.e', 'usdc_dark').renderTo($sender); } else { new PolygonAddressInfo(relayRequest.from, request.keyLabel, 'usdc').renderTo($sender); } /** @type {HTMLLinkElement} */ const $recipient = (this.$el.querySelector('.accounts .recipient')); - const recipientAddress = /** @type {string} */ (request.description.args.target); - new PolygonAddressInfo( - recipientAddress, - request.description.name === 'refund' ? request.keyLabel : request.recipientLabel, - request.description.name === 'refund' ? 'usdc' : 'none', - ).renderTo($recipient); + if (request.description.name === 'refund') { + new PolygonAddressInfo( + /** @type {string} */ (request.description.args.target), + request.keyLabel, + 'usdc', + ).renderTo($recipient); + } else if (request.description.name === 'swap' || request.description.name === 'swapWithApproval') { + new PolygonAddressInfo(relayRequest.from, 'USDC', 'usdc').renderTo($recipient); + } else { + new PolygonAddressInfo( + /** @type {string} */ (request.description.args.target), + request.recipientLabel, + 'none', + ).renderTo($recipient); + } /** @type {HTMLDivElement} */ const $value = (this.$el.querySelector('#value')); @@ -120,60 +131,61 @@ class SignPolygonTransaction { // Has been validated to be an approved transfer contract address const transferContract = request.request.to; - if (request.description.name === 'transferWithApproval') { - const { sigR, sigS, sigV } = await polygonKey.signUsdcApproval( + if (request.description.name === 'transferWithPermit') { + const { sigR, sigS, sigV } = await polygonKey.signUsdcPermit( request.keyPath, - new ethers.Contract( - CONFIG.USDC_CONTRACT_ADDRESS, - PolygonContractABIs.USDC_CONTRACT_ABI, - ), transferContract, - request.description.args.approval, - // Has been validated to be defined when function called is `transferWithApproval` - /** @type {{ tokenNonce: number }} */ (request.approval).tokenNonce, + // `value` is the permit approval amount - the transaction value is called `amount` + request.description.args.value, + // Has been validated to be defined when function called is `transferWithPermit` + /** @type {{ tokenNonce: number }} */ (request.permit).tokenNonce, request.request.from, ); - const usdcTransfer = new ethers.Contract( + const nativeUsdcTransfer = new ethers.Contract( transferContract, - PolygonContractABIs.USDC_TRANSFER_CONTRACT_ABI, + PolygonContractABIs.NATIVE_USDC_TRANSFER_CONTRACT_ABI, ); - request.request.data = usdcTransfer.interface.encodeFunctionData(request.description.name, [ + request.request.data = nativeUsdcTransfer.interface.encodeFunctionData(request.description.name, [ /* address token */ request.description.args.token, /* uint256 amount */ request.description.args.amount, /* address target */ request.description.args.target, /* uint256 fee */ request.description.args.fee, - /* uint256 approval */ request.description.args.approval, + // `value` is the permit approval amount - the transaction value is called `amount` (above) + /* uint256 value */ request.description.args.value, /* bytes32 sigR */ sigR, /* bytes32 sigS */ sigS, /* uint8 sigV */ sigV, ]); } - if (request.description.name === 'transferWithPermit') { - const { sigR, sigS, sigV } = await polygonKey.signUsdcPermit( + if (request.description.name === 'swapWithApproval') { + const { sigR, sigS, sigV } = await polygonKey.signUsdcApproval( request.keyPath, + new ethers.Contract( + CONFIG.BRIDGED_USDC_CONTRACT_ADDRESS, + PolygonContractABIs.BRIDGED_USDC_CONTRACT_ABI, + ), transferContract, - // `value` is the permit approval amount - the transaction value is called `amount` - request.description.args.value, - // Has been validated to be defined when function called is `transferWithPermit` - /** @type {{ tokenNonce: number }} */ (request.permit).tokenNonce, + request.description.args.approval, + // Has been validated to be defined when function called is `swapWithApproval` + /** @type {{ tokenNonce: number }} */ (request.approval).tokenNonce, request.request.from, ); - const nativeUsdcTransfer = new ethers.Contract( + const swapContract = new ethers.Contract( transferContract, - PolygonContractABIs.NATIVE_USDC_TRANSFER_CONTRACT_ABI, + PolygonContractABIs.SWAP_CONTRACT_ABI, ); - request.request.data = nativeUsdcTransfer.interface.encodeFunctionData(request.description.name, [ + request.request.data = swapContract.interface.encodeFunctionData(request.description.name, [ /* address token */ request.description.args.token, /* uint256 amount */ request.description.args.amount, - /* address target */ request.description.args.target, + /* address pool */ request.description.args.pool, + /* uint256 targetAmount */ request.description.args.targetAmount, /* uint256 fee */ request.description.args.fee, - // `value` is the permit approval amount - the transaction value is called `amount` (above) - /* uint256 value */ request.description.args.value, + /* uint256 approval */ request.description.args.approval, /* bytes32 sigR */ sigR, /* bytes32 sigS */ sigS, /* uint8 sigV */ sigV, diff --git a/src/request/sign-polygon-transaction/SignPolygonTransactionApi.js b/src/request/sign-polygon-transaction/SignPolygonTransactionApi.js index 12d2c7571..ff6669726 100644 --- a/src/request/sign-polygon-transaction/SignPolygonTransactionApi.js +++ b/src/request/sign-polygon-transaction/SignPolygonTransactionApi.js @@ -58,9 +58,10 @@ class SignPolygonTransactionApi extends PolygonRequestParserMixin(TopLevelApi) { * @returns {[ * KeyguardRequest.OpenGsnForwardRequest, * PolygonTransferDescription - * | PolygonTransferWithApprovalDescription * | PolygonTransferWithPermitDescription - * | PolygonRefundDescription, + * | PolygonRefundDescription + * | PolygonSwapDescription + * | PolygonSwapWithApprovalDescription, * ]} */ parseOpenGsnForwardRequest(request) { @@ -68,32 +69,14 @@ class SignPolygonTransactionApi extends PolygonRequestParserMixin(TopLevelApi) { /** * @type {PolygonTransferDescription - * | PolygonTransferWithApprovalDescription * | PolygonTransferWithPermitDescription - * | PolygonRefundDescription} + * | PolygonRefundDescription + * | PolygonSwapDescription + * | PolygonSwapWithApprovalDescription} */ let description; - if (forwardRequest.to === CONFIG.USDC_TRANSFER_CONTRACT_ADDRESS) { - const usdcTransferContract = new ethers.Contract( - CONFIG.USDC_TRANSFER_CONTRACT_ADDRESS, - PolygonContractABIs.USDC_TRANSFER_CONTRACT_ABI, - ); - - /** @type {PolygonTransferDescription | PolygonTransferWithApprovalDescription} */ - description = (usdcTransferContract.interface.parseTransaction({ - data: forwardRequest.data, - value: forwardRequest.value, - })); - - if (description.args.token !== CONFIG.USDC_CONTRACT_ADDRESS) { - throw new Errors.InvalidRequestError('Invalid USDC token contract in request data'); - } - - if (!['transfer', 'transferWithApproval'].includes(description.name)) { - throw new Errors.InvalidRequestError('Requested Polygon contract method is invalid'); - } - } else if (forwardRequest.to === CONFIG.NATIVE_USDC_TRANSFER_CONTRACT_ADDRESS) { + if (forwardRequest.to === CONFIG.NATIVE_USDC_TRANSFER_CONTRACT_ADDRESS) { const nativeUsdcTransferContract = new ethers.Contract( CONFIG.NATIVE_USDC_TRANSFER_CONTRACT_ADDRESS, PolygonContractABIs.NATIVE_USDC_TRANSFER_CONTRACT_ABI, @@ -112,10 +95,10 @@ class SignPolygonTransactionApi extends PolygonRequestParserMixin(TopLevelApi) { if (!['transfer', 'transferWithPermit'].includes(description.name)) { throw new Errors.InvalidRequestError('Requested Polygon contract method is invalid'); } - } else if (forwardRequest.to === CONFIG.USDC_HTLC_CONTRACT_ADDRESS) { + } else if (forwardRequest.to === CONFIG.BRIDGED_USDC_HTLC_CONTRACT_ADDRESS) { const usdcHtlcContract = new ethers.Contract( - CONFIG.USDC_HTLC_CONTRACT_ADDRESS, - PolygonContractABIs.USDC_HTLC_CONTRACT_ABI, + CONFIG.BRIDGED_USDC_HTLC_CONTRACT_ADDRESS, + PolygonContractABIs.BRIDGED_USDC_HTLC_CONTRACT_ABI, ); /** @type {PolygonRefundDescription} */ @@ -127,25 +110,57 @@ class SignPolygonTransactionApi extends PolygonRequestParserMixin(TopLevelApi) { if (!['refund'].includes(description.name)) { throw new Errors.InvalidRequestError('Requested Polygon contract method is invalid'); } + } else if (forwardRequest.to === CONFIG.USDC_SWAP_CONTRACT_ADDRESS) { + const usdcTransferContract = new ethers.Contract( + CONFIG.USDC_SWAP_CONTRACT_ADDRESS, + PolygonContractABIs.SWAP_CONTRACT_ABI, + ); + + /** @type {PolygonSwapDescription | PolygonSwapWithApprovalDescription} */ + description = (usdcTransferContract.interface.parseTransaction({ + data: forwardRequest.data, + value: forwardRequest.value, + })); + + if (description.args.token !== CONFIG.BRIDGED_USDC_CONTRACT_ADDRESS) { + throw new Errors.InvalidRequestError('Invalid USDC token contract in request data'); + } + + if (!['swap', 'swapWithApproval'].includes(description.name)) { + throw new Errors.InvalidRequestError('Requested Polygon contract method is invalid'); + } + + // Ensure swap `targetAmount` is not too low + const inputAmount = /** @type {PolygonSwapDescription | PolygonSwapWithApprovalDescription} */ (description) + .args + .amount; + const targetAmount = /** @type {PolygonSwapDescription | PolygonSwapWithApprovalDescription} */ (description) // eslint-disable-line max-len + .args + .targetAmount; + if (targetAmount.lt(inputAmount.mul(99).div(100))) { + throw new Errors.InvalidRequestError( + 'Requested Polygon swap `targetAmount` more than 1% lower than the input `amount`', + ); + } } else { throw new Errors.InvalidRequestError('request.to address is not allowed'); } + // Check that permit object exists when method is 'transferWithPermit', and unset for other methods. + if ((description.name === 'transferWithPermit') !== !!request.permit) { + throw new Errors.InvalidRequestError('`permit` object is only allowed for contract method ' + + '"transferWithPermit"'); + } + // Check that amount exists when method is 'refund', and unset for other methods. if ((description.name === 'refund') !== !!request.amount) { throw new Errors.InvalidRequestError('`amount` is only allowed for contract method "refund"'); } - // Check that approval object exists when method is 'transferWithApproval', and unset for other methods. - if ((description.name === 'transferWithApproval') !== !!request.approval) { + // Check that approval object exists when method is 'swapWithApproval', and unset for other methods. + if ((description.name === 'swapWithApproval') !== !!request.approval) { throw new Errors.InvalidRequestError('`approval` object is only allowed for contract method ' - + '"transferWithApproval"'); - } - - // Check that permit object exists when method is 'transferWithPermit', and unset for other methods. - if ((description.name === 'transferWithPermit') !== !!request.permit) { - throw new Errors.InvalidRequestError('`permit` object is only allowed for contract method ' - + '"transferWithPermit"'); + + '"swapWithApproval"'); } return [forwardRequest, description]; diff --git a/src/request/sign-swap/SignSwap.js b/src/request/sign-swap/SignSwap.js index a180f7826..4047c7c3b 100644 --- a/src/request/sign-swap/SignSwap.js +++ b/src/request/sign-swap/SignSwap.js @@ -549,10 +549,10 @@ class SignSwap { const { sigR, sigS, sigV } = await polygonKey.signUsdcApproval( request.fund.keyPath, new ethers.Contract( - CONFIG.USDC_CONTRACT_ADDRESS, - PolygonContractABIs.USDC_CONTRACT_ABI, + CONFIG.BRIDGED_USDC_CONTRACT_ADDRESS, + PolygonContractABIs.BRIDGED_USDC_CONTRACT_ABI, ), - CONFIG.USDC_HTLC_CONTRACT_ADDRESS, + CONFIG.BRIDGED_USDC_HTLC_CONTRACT_ADDRESS, request.fund.description.args.approval, // Has been validated to be defined when function called is `openWithApproval` /** @type {{ tokenNonce: number }} */ (request.fund.approval).tokenNonce, @@ -560,8 +560,8 @@ class SignSwap { ); const htlcContract = new ethers.Contract( - CONFIG.USDC_HTLC_CONTRACT_ADDRESS, - PolygonContractABIs.USDC_HTLC_CONTRACT_ABI, + CONFIG.BRIDGED_USDC_HTLC_CONTRACT_ADDRESS, + PolygonContractABIs.BRIDGED_USDC_HTLC_CONTRACT_ABI, ); request.fund.request.data = htlcContract.interface.encodeFunctionData(request.fund.description.name, [ diff --git a/src/request/sign-swap/SignSwapApi.js b/src/request/sign-swap/SignSwapApi.js index 6b593ab40..3baa8329e 100644 --- a/src/request/sign-swap/SignSwapApi.js +++ b/src/request/sign-swap/SignSwapApi.js @@ -320,13 +320,13 @@ class SignSwapApi extends PolygonRequestParserMixin(BitcoinRequestParserMixin(To parseOpenGsnForwardRequest(request, allowedMethods) { const forwardRequest = this.parseOpenGsnForwardRequestRoot(request.request); - if (forwardRequest.to !== CONFIG.USDC_HTLC_CONTRACT_ADDRESS) { + if (forwardRequest.to !== CONFIG.BRIDGED_USDC_HTLC_CONTRACT_ADDRESS) { throw new Errors.InvalidRequestError('request.to address is not allowed'); } const usdcHtlcContract = new ethers.Contract( - CONFIG.USDC_HTLC_CONTRACT_ADDRESS, - PolygonContractABIs.USDC_HTLC_CONTRACT_ABI, + CONFIG.BRIDGED_USDC_HTLC_CONTRACT_ADDRESS, + PolygonContractABIs.BRIDGED_USDC_HTLC_CONTRACT_ABI, ); // eslint-disable-next-line operator-linebreak @@ -345,7 +345,7 @@ class SignSwapApi extends PolygonRequestParserMixin(BitcoinRequestParserMixin(To } if (description.name === 'open' || description.name === 'openWithApproval') { - if (description.args.token !== CONFIG.USDC_CONTRACT_ADDRESS) { + if (description.args.token !== CONFIG.BRIDGED_USDC_CONTRACT_ADDRESS) { throw new Errors.InvalidRequestError('Invalid USDC token contract in request data'); } diff --git a/src/request/swap-iframe/SwapIFrameApi.js b/src/request/swap-iframe/SwapIFrameApi.js index f55d48789..5f02cc55d 100644 --- a/src/request/swap-iframe/SwapIFrameApi.js +++ b/src/request/swap-iframe/SwapIFrameApi.js @@ -87,8 +87,8 @@ class SwapIFrameApi extends BitcoinRequestParserMixin(RequestParser) { // eslint } if (storedRawRequest.fund.type === 'USDC') { const usdcHtlcContract = new ethers.Contract( - CONFIG.USDC_HTLC_CONTRACT_ADDRESS, - PolygonContractABIs.USDC_HTLC_CONTRACT_ABI, + CONFIG.BRIDGED_USDC_HTLC_CONTRACT_ADDRESS, + PolygonContractABIs.BRIDGED_USDC_HTLC_CONTRACT_ABI, ); storedRawRequest.fund.description = usdcHtlcContract.interface.parseTransaction({ @@ -102,8 +102,8 @@ class SwapIFrameApi extends BitcoinRequestParserMixin(RequestParser) { // eslint } if (storedRawRequest.redeem.type === 'USDC') { const usdcHtlcContract = new ethers.Contract( - CONFIG.USDC_HTLC_CONTRACT_ADDRESS, - PolygonContractABIs.USDC_HTLC_CONTRACT_ABI, + CONFIG.BRIDGED_USDC_HTLC_CONTRACT_ADDRESS, + PolygonContractABIs.BRIDGED_USDC_HTLC_CONTRACT_ABI, ); storedRawRequest.redeem.description = usdcHtlcContract.interface.parseTransaction({ @@ -226,8 +226,8 @@ class SwapIFrameApi extends BitcoinRequestParserMixin(RequestParser) { // eslint if (request.fund.type === 'USDC' && storedRequest.fund.type === 'USDC') { const usdcHtlcContract = new ethers.Contract( - CONFIG.USDC_HTLC_CONTRACT_ADDRESS, - PolygonContractABIs.USDC_HTLC_CONTRACT_ABI, + CONFIG.BRIDGED_USDC_HTLC_CONTRACT_ADDRESS, + PolygonContractABIs.BRIDGED_USDC_HTLC_CONTRACT_ABI, ); const description = /** @type {PolygonOpenDescription} */ (usdcHtlcContract.interface.parseTransaction({ @@ -243,7 +243,7 @@ class SwapIFrameApi extends BitcoinRequestParserMixin(RequestParser) { // eslint } // Verify already known parts of the data - if (description.args.token !== CONFIG.USDC_CONTRACT_ADDRESS) { + if (description.args.token !== CONFIG.BRIDGED_USDC_CONTRACT_ADDRESS) { throw new Errors.InvalidRequestError('Invalid USDC token contract in HTLC data'); } @@ -546,8 +546,8 @@ class SwapIFrameApi extends BitcoinRequestParserMixin(RequestParser) { // eslint if (parsedRequest.fund.type === 'USDC' && storedRequest.fund.type === 'USDC') { const usdcHtlcContract = new ethers.Contract( - CONFIG.USDC_HTLC_CONTRACT_ADDRESS, - PolygonContractABIs.USDC_HTLC_CONTRACT_ABI, + CONFIG.BRIDGED_USDC_HTLC_CONTRACT_ADDRESS, + PolygonContractABIs.BRIDGED_USDC_HTLC_CONTRACT_ABI, ); // Place contract details into existing function call data @@ -572,10 +572,14 @@ class SwapIFrameApi extends BitcoinRequestParserMixin(RequestParser) { // eslint ], ); - const typedData = new OpenGSN.TypedRequestData(CONFIG.POLYGON_CHAIN_ID, CONFIG.USDC_HTLC_CONTRACT_ADDRESS, { - request: storedRequest.fund.request, - relayData: storedRequest.fund.relayData, - }); + const typedData = new OpenGSN.TypedRequestData( + CONFIG.POLYGON_CHAIN_ID, + CONFIG.BRIDGED_USDC_HTLC_CONTRACT_ADDRESS, + { + request: storedRequest.fund.request, + relayData: storedRequest.fund.relayData, + }, + ); const { EIP712Domain, ...cleanedTypes } = typedData.types; @@ -695,8 +699,8 @@ class SwapIFrameApi extends BitcoinRequestParserMixin(RequestParser) { // eslint if (parsedRequest.redeem.type === 'USDC' && storedRequest.redeem.type === 'USDC') { const usdcHtlcContract = new ethers.Contract( - CONFIG.USDC_HTLC_CONTRACT_ADDRESS, - PolygonContractABIs.USDC_HTLC_CONTRACT_ABI, + CONFIG.BRIDGED_USDC_HTLC_CONTRACT_ADDRESS, + PolygonContractABIs.BRIDGED_USDC_HTLC_CONTRACT_ABI, ); // Place contract details into existing function call data @@ -712,10 +716,14 @@ class SwapIFrameApi extends BitcoinRequestParserMixin(RequestParser) { // eslint ], ); - const typedData = new OpenGSN.TypedRequestData(CONFIG.POLYGON_CHAIN_ID, CONFIG.USDC_HTLC_CONTRACT_ADDRESS, { - request: storedRequest.redeem.request, - relayData: storedRequest.redeem.relayData, - }); + const typedData = new OpenGSN.TypedRequestData( + CONFIG.POLYGON_CHAIN_ID, + CONFIG.BRIDGED_USDC_HTLC_CONTRACT_ADDRESS, + { + request: storedRequest.redeem.request, + relayData: storedRequest.redeem.relayData, + }, + ); const { EIP712Domain, ...cleanedTypes } = typedData.types; diff --git a/types/Keyguard.d.ts b/types/Keyguard.d.ts index dde5d8a0a..9dd3a3b8c 100644 --- a/types/Keyguard.d.ts +++ b/types/Keyguard.d.ts @@ -81,13 +81,6 @@ type PolygonTransferDescription = ethers.utils.TransactionDescription & { readonly args: PolygonTransferArgs, }; -interface PolygonTransferWithApprovalArgs extends PolygonTransferArgs, PolygonUsdcApproval {} - -type PolygonTransferWithApprovalDescription = ethers.utils.TransactionDescription & { - readonly name: 'transferWithApproval', - readonly args: PolygonTransferWithApprovalArgs, -}; - interface PolygonTransferWithPermitArgs extends PolygonTransferArgs, PolygonUsdcPermit {} type PolygonTransferWithPermitDescription = ethers.utils.TransactionDescription & { @@ -152,6 +145,26 @@ type PolygonRefundDescription = ethers.utils.TransactionDescription & { readonly args: PolygonRefundArgs, }; +interface PolygonSwapArgs extends ReadonlyArray { + readonly token: string, + readonly amount: ethers.BigNumber, + readonly pool: string, + readonly targetAmount: ethers.BigNumber, + readonly fee: ethers.BigNumber, +} + +type PolygonSwapDescription = ethers.utils.TransactionDescription & { + readonly name: 'swap', + readonly args: PolygonSwapArgs, +}; + +interface PolygonSwapWithApprovalArgs extends PolygonSwapArgs, PolygonUsdcApproval {} + +type PolygonSwapWithApprovalDescription = ethers.utils.TransactionDescription & { + readonly name: 'swapWithApproval', + readonly args: PolygonSwapWithApprovalArgs, +}; + type NimHtlcContents = { refundAddress: string, redeemAddress: string, @@ -292,9 +305,10 @@ type Parsed = T extends Is ? KeyId2KeyInfo & { description: PolygonTransferDescription - | PolygonTransferWithApprovalDescription | PolygonTransferWithPermitDescription - | PolygonRefundDescription } : + | PolygonRefundDescription + | PolygonSwapDescription + | PolygonSwapWithApprovalDescription } : T extends Is ? KeyId2KeyInfo> & { layout: KeyguardRequest.SignSwapRequestLayout } :