Skip to content

Commit

Permalink
Extract USDC approval signing into method on PolygonKey
Browse files Browse the repository at this point in the history
  • Loading branch information
sisou committed Oct 13, 2023
1 parent 151e112 commit e6cc0e4
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 96 deletions.
52 changes: 52 additions & 0 deletions src/lib/polygon/PolygonKey.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* global Nimiq */
/* global Errors */
/* global ethers */
/* global CONFIG */

class PolygonKey { // eslint-disable-line no-unused-vars
/**
Expand Down Expand Up @@ -45,6 +46,57 @@ class PolygonKey { // eslint-disable-line no-unused-vars
return wallet._signTypedData(domain, types, value);
}

/**
* @param {string} path
* @param {ethers.Contract} usdcContract
* @param {string} forwarderContractAddress
* @param {ethers.BigNumber} approvalAmount
* @param {number} tokenNonce
* @param {string} fromAddress
* @returns {Promise<{sigR: string, sigS: string, sigV: number}>}
*/
async signUsdcApproval(path, usdcContract, forwarderContractAddress, approvalAmount, tokenNonce, fromAddress) {
const functionSignature = usdcContract.interface.encodeFunctionData(
'approve',
[forwarderContractAddress, approvalAmount],
);

// TODO: Make the domain parameters configurable in the request?
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,
salt: ethers.utils.hexZeroPad(ethers.utils.hexlify(CONFIG.POLYGON_CHAIN_ID), 32),
};

const types = {
MetaTransaction: [
{ name: 'nonce', type: 'uint256' },
{ name: 'from', type: 'address' },
{ name: 'functionSignature', type: 'bytes' },
],
};

const message = {
nonce: tokenNonce,
from: fromAddress,
functionSignature,
};

const signature = await this.signTypedData(
path,
domain,
types,
message,
);

const sigR = signature.slice(0, 66); // 0x prefix plus 32 bytes = 66 characters
const sigS = `0x${signature.slice(66, 130)}`; // 32 bytes = 64 characters
const sigV = parseInt(signature.slice(130, 132), 16); // last byte = 2 characters

return { sigR, sigS, sigV };
}

/**
* @param {string} path
* @param {Uint8Array} message - A byte array
Expand Down
58 changes: 10 additions & 48 deletions src/request/sign-polygon-transaction/SignPolygonTransaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,57 +118,19 @@ class SignPolygonTransaction {
const polygonKey = new PolygonKey(key);

if (request.description.name === 'transferWithApproval') {
// Sign approval
const usdcContract = new ethers.Contract(
CONFIG.USDC_CONTRACT_ADDRESS,
PolygonContractABIs.USDC_CONTRACT_ABI,
);

const functionSignature = usdcContract.interface.encodeFunctionData(
'approve',
[CONFIG.USDC_TRANSFER_CONTRACT_ADDRESS, request.description.args.approval],
);

// TODO: Make the domain parameters configurable in the request?
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,
salt: ethers.utils.hexZeroPad(ethers.utils.hexlify(CONFIG.POLYGON_CHAIN_ID), 32),
};

const types = {
MetaTransaction: [
{ name: 'nonce', type: 'uint256' },
{ name: 'from', type: 'address' },
{ name: 'functionSignature', type: 'bytes' },
],
};

const message = {
// Has been validated to be defined when function called is `transferWithApproval`
nonce: /** @type {{ tokenNonce: number }} */ (request.approval).tokenNonce,
from: request.request.from,
functionSignature,
};

const signature = await polygonKey.signTypedData(
const { sigR, sigS, sigV } = await polygonKey.signUsdcApproval(
request.keyPath,
domain,
types,
message,
new ethers.Contract(
CONFIG.USDC_CONTRACT_ADDRESS,
PolygonContractABIs.USDC_CONTRACT_ABI,
),
CONFIG.USDC_TRANSFER_CONTRACT_ADDRESS,
request.description.args.approval,
// Has been validated to be defined when function called is `transferWithApproval`
/** @type {{ tokenNonce: number }} */ (request.approval).tokenNonce,
request.request.from,
);

const signerAddress = ethers.utils.verifyTypedData(domain, types, message, signature);
if (signerAddress !== request.request.from) {
reject(new Errors.CoreError('Failed to sign approval'));
return;
}

const sigR = signature.slice(0, 66); // 0x prefix plus 32 bytes = 66 characters
const sigS = `0x${signature.slice(66, 130)}`; // 32 bytes = 64 characters
const sigV = parseInt(signature.slice(130, 132), 16); // last byte = 2 characters

const usdcTransfer = new ethers.Contract(
CONFIG.USDC_TRANSFER_CONTRACT_ADDRESS,
PolygonContractABIs.USDC_TRANSFER_CONTRACT_ABI,
Expand Down
58 changes: 10 additions & 48 deletions src/request/sign-swap/SignSwap.js
Original file line number Diff line number Diff line change
Expand Up @@ -546,57 +546,19 @@ class SignSwap {

if (request.fund.type === 'USDC') {
if (request.fund.description.name === 'openWithApproval') {
// Sign approval
const usdcContract = new ethers.Contract(
CONFIG.USDC_CONTRACT_ADDRESS,
PolygonContractABIs.USDC_CONTRACT_ABI,
);

const functionSignature = usdcContract.interface.encodeFunctionData(
'approve',
[CONFIG.USDC_HTLC_CONTRACT_ADDRESS, request.fund.description.args.approval],
);

// TODO: Make the domain parameters configurable in the request?
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,
salt: ethers.utils.hexZeroPad(ethers.utils.hexlify(CONFIG.POLYGON_CHAIN_ID), 32),
};

const types = {
MetaTransaction: [
{ name: 'nonce', type: 'uint256' },
{ name: 'from', type: 'address' },
{ name: 'functionSignature', type: 'bytes' },
],
};

const message = {
// Has been validated to be defined when function called is `openWithApproval`
nonce: /** @type {{ tokenNonce: number }} */ (request.fund.approval).tokenNonce,
from: request.fund.request.from,
functionSignature,
};

const signature = await polygonKey.signTypedData(
const { sigR, sigS, sigV } = await polygonKey.signUsdcApproval(
request.fund.keyPath,
domain,
types,
message,
new ethers.Contract(
CONFIG.USDC_CONTRACT_ADDRESS,
PolygonContractABIs.USDC_CONTRACT_ABI,
),
CONFIG.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,
request.fund.request.from,
);

const signerAddress = ethers.utils.verifyTypedData(domain, types, message, signature);
if (signerAddress !== request.fund.request.from) {
reject(new Errors.CoreError('Failed to sign approval'));
return;
}

const sigR = signature.slice(0, 66); // 0x prefix plus 32 bytes = 66 characters
const sigS = `0x${signature.slice(66, 130)}`; // 32 bytes = 64 characters
const sigV = parseInt(signature.slice(130, 132), 16); // last byte = 2 characters

const htlcContract = new ethers.Contract(
CONFIG.USDC_HTLC_CONTRACT_ADDRESS,
PolygonContractABIs.USDC_HTLC_CONTRACT_ABI,
Expand Down

0 comments on commit e6cc0e4

Please sign in to comment.