Skip to content

Commit

Permalink
Merge pull request #515 from nimiq/usdt
Browse files Browse the repository at this point in the history
Support USDT
  • Loading branch information
sisou authored Oct 24, 2024
2 parents b8a8492 + 4234ff6 commit 6fd6189
Show file tree
Hide file tree
Showing 15 changed files with 292 additions and 34 deletions.
2 changes: 1 addition & 1 deletion client/src/PublicRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ export type PolygonTransactionInfo = {

/**
* The sender's nonce in the token contract, required when calling the
* contract function `swapWithApproval` for bridged USDC.e.
* contract function `swapWithApproval` for bridged USDC.e or `transferWithApproval` for bridged USDT.
*/
approval?: {
tokenNonce: number,
Expand Down
3 changes: 3 additions & 0 deletions src/assets/icons/usdt.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/common.css
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,10 @@ body.loading .page:target .page-footer > .loading-spinner {
content: "USDC";
}

.usdt-symbol::before {
content: "USDT";
}

.eur-symbol::before {
content: "EUR";
}
Expand Down
9 changes: 7 additions & 2 deletions src/components/PolygonAddressInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class PolygonAddressInfo { // eslint-disable-line no-unused-vars
/**
* @param {string} address
* @param {string} [label]
* @param {'none' | 'usdc' | 'usdc_dark' | 'unknown'} [logo = 'none']
* @param {'none' | 'usdc' | 'usdc_dark' | 'usdt' | 'unknown'} [logo = 'none']
*/
constructor(address, label, logo = 'none') {
this._address = address;
Expand Down Expand Up @@ -37,7 +37,12 @@ class PolygonAddressInfo { // eslint-disable-line no-unused-vars
$avatar.classList.add('unlabelled');
}
$el.appendChild($avatar);
} else if (this._logo === 'usdc' || this._logo === 'usdc_dark' || this._logo === 'unknown') {
} else if (
this._logo === 'usdc'
|| this._logo === 'usdc_dark'
|| this._logo === 'usdt'
|| this._logo === 'unknown'
) {
const $img = document.createElement('img');
$img.classList.add('logo');
$img.src = `../../assets/icons/${this._logo}.svg`;
Expand Down
18 changes: 11 additions & 7 deletions src/config/config.local.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@ const CONFIG = { // eslint-disable-line no-unused-vars
BTC_NETWORK: /** @type {'MAIN' | 'TEST'} */ ('TEST'), // BitcoinConstants is not included in the common bundle
ROOT_REDIRECT: 'https://wallet.nimiq-testnet.com',

POLYGON_CHAIN_ID: 80001,
BRIDGED_USDC_CONTRACT_ADDRESS: '0x0FA8781a83E46826621b3BC094Ea2A0212e71B23',
POLYGON_CHAIN_ID: 80002, // Amoy testnet
BRIDGED_USDC_CONTRACT_ADDRESS: '',
/** @deprecated */
BRIDGED_USDC_HTLC_CONTRACT_ADDRESS: '0x2EB7cd7791b947A25d629219ead941fCd8f364BF',
BRIDGED_USDC_HTLC_CONTRACT_ADDRESS: '',

NATIVE_USDC_CONTRACT_ADDRESS: '0x9999f7Fea5938fD3b1E26A12c3f2fb024e194f97',
NATIVE_USDC_TRANSFER_CONTRACT_ADDRESS: '0x5D101A320547f8D640c44fDfe5d1f35224f00B8B', // v1
NATIVE_USDC_HTLC_CONTRACT_ADDRESS: '0xA9fAbABE97375565e4A9Ac69A57Df33c91FCB897',
NATIVE_USDC_CONTRACT_ADDRESS: '0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582',
NATIVE_USDC_TRANSFER_CONTRACT_ADDRESS: '',
NATIVE_USDC_HTLC_CONTRACT_ADDRESS: '',

USDC_SWAP_CONTRACT_ADDRESS: '0xf4a619F6561CeE543BDa9BBA0cAC68758B607714',
USDC_SWAP_CONTRACT_ADDRESS: '',

BRIDGED_USDT_CONTRACT_ADDRESS: '0x1616d425Cd540B256475cBfb604586C8598eC0FB',
BRIDGED_USDT_TRANSFER_CONTRACT_ADDRESS: '',
BRIDGED_USDT_HTLC_CONTRACT_ADDRESS: '',
};
4 changes: 4 additions & 0 deletions src/config/config.mainnet.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ const CONFIG = { // eslint-disable-line no-unused-vars
NATIVE_USDC_HTLC_CONTRACT_ADDRESS: '0x0cFD862bE942846Cebad797d7c1BC6e47714959b',

USDC_SWAP_CONTRACT_ADDRESS: '0xfAbBed813017bF535b40013c13b8702638aC25CD',

BRIDGED_USDT_CONTRACT_ADDRESS: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F',
BRIDGED_USDT_TRANSFER_CONTRACT_ADDRESS: '0x98E69a6927747339d5E543586FC0262112eBe4BD',
BRIDGED_USDT_HTLC_CONTRACT_ADDRESS: '0xF615bD7EA00C4Cc7F39Faad0895dB5f40891359f',
};
18 changes: 11 additions & 7 deletions src/config/config.testnet.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ const CONFIG = { // eslint-disable-line no-unused-vars
BTC_NETWORK: 'TEST', // BitcoinConstants is not included in the common bundle
ROOT_REDIRECT: 'https://wallet.nimiq-testnet.com',

POLYGON_CHAIN_ID: 80001,
BRIDGED_USDC_CONTRACT_ADDRESS: '0x0FA8781a83E46826621b3BC094Ea2A0212e71B23',
BRIDGED_USDC_HTLC_CONTRACT_ADDRESS: '0x2EB7cd7791b947A25d629219ead941fCd8f364BF',
POLYGON_CHAIN_ID: 80002,
BRIDGED_USDC_CONTRACT_ADDRESS: '',
BRIDGED_USDC_HTLC_CONTRACT_ADDRESS: '',

NATIVE_USDC_CONTRACT_ADDRESS: '0x9999f7Fea5938fD3b1E26A12c3f2fb024e194f97',
NATIVE_USDC_TRANSFER_CONTRACT_ADDRESS: '0x5D101A320547f8D640c44fDfe5d1f35224f00B8B', // v1
NATIVE_USDC_HTLC_CONTRACT_ADDRESS: '0xA9fAbABE97375565e4A9Ac69A57Df33c91FCB897',
NATIVE_USDC_CONTRACT_ADDRESS: '0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582',
NATIVE_USDC_TRANSFER_CONTRACT_ADDRESS: '',
NATIVE_USDC_HTLC_CONTRACT_ADDRESS: '',

USDC_SWAP_CONTRACT_ADDRESS: '0xf4a619F6561CeE543BDa9BBA0cAC68758B607714',
USDC_SWAP_CONTRACT_ADDRESS: '',

BRIDGED_USDT_CONTRACT_ADDRESS: '0x1616d425Cd540B256475cBfb604586C8598eC0FB',
BRIDGED_USDT_TRANSFER_CONTRACT_ADDRESS: '',
BRIDGED_USDT_HTLC_CONTRACT_ADDRESS: '',
};
96 changes: 93 additions & 3 deletions src/lib/polygon/PolygonContractABIs.full.js.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/* eslint-disable max-len */
const PolygonContractABIsFull = { // eslint-disable-line no-unused-vars
const PolygonContractABIsFull = {
BRIDGED_USDC_CONTRACT_ABI: [
'event Approval(address indexed owner, address indexed spender, uint256 value)',
'event AuthorizationCanceled(address indexed authorizer, bytes32 indexed nonce)',
Expand Down Expand Up @@ -353,5 +352,96 @@ const PolygonContractABIsFull = { // eslint-disable-line no-unused-vars
"function withdrawToken(address token, uint256 amount, address target)",
"function wrappedChainToken() view returns (address)",
],

BRIDGED_USDT_CONTRACT_ABI: [
'constructor()',
'event Approval(address indexed owner, address indexed spender, uint256 value)',
'event MetaTransactionExecuted(address userAddress, address relayerAddress, bytes functionSignature)',
'event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole)',
'event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender)',
'event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender)',
'event Transfer(address indexed from, address indexed to, uint256 value)',
'function CHILD_CHAIN_ID() view returns (uint256)',
'function CHILD_CHAIN_ID_BYTES() view returns (bytes)',
'function DEFAULT_ADMIN_ROLE() view returns (bytes32)',
'function DEPOSITOR_ROLE() view returns (bytes32)',
'function ERC712_VERSION() view returns (string)',
'function ROOT_CHAIN_ID() view returns (uint256)',
'function ROOT_CHAIN_ID_BYTES() view returns (bytes)',
'function allowance(address owner, address spender) view returns (uint256)',
'function approve(address spender, uint256 amount) returns (bool)',
'function balanceOf(address account) view returns (uint256)',
'function changeName(string name_)',
'function decimals() view returns (uint8)',
'function decreaseAllowance(address spender, uint256 subtractedValue) returns (bool)',
'function deposit(address user, bytes depositData)',
'function executeMetaTransaction(address userAddress, bytes functionSignature, bytes32 sigR, bytes32 sigS, uint8 sigV) payable returns (bytes)',
'function getChainId() pure returns (uint256)',
'function getDomainSeperator() view returns (bytes32)',
'function getNonce(address user) view returns (uint256 nonce)',
'function getRoleAdmin(bytes32 role) view returns (bytes32)',
'function getRoleMember(bytes32 role, uint256 index) view returns (address)',
'function getRoleMemberCount(bytes32 role) view returns (uint256)',
'function grantRole(bytes32 role, address account)',
'function hasRole(bytes32 role, address account) view returns (bool)',
'function increaseAllowance(address spender, uint256 addedValue) returns (bool)',
'function initialize(string name_, string symbol_, uint8 decimals_, address childChainManager)',
'function name() view returns (string)',
'function renounceRole(bytes32 role, address account)',
'function revokeRole(bytes32 role, address account)',
'function symbol() view returns (string)',
'function totalSupply() view returns (uint256)',
'function transfer(address recipient, uint256 amount) returns (bool)',
'function transferFrom(address sender, address recipient, uint256 amount) returns (bool)',
'function withdraw(uint256 amount)',
]

BRIDGED_USDT_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((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 ((uint256 acceptanceBudget, uint256 preRelayedCallGasLimit, uint256 postRelayedCallGasLimit, uint256 calldataSizeLimit) limits)',
'function getHubAddr() view returns (address)',
'function getMinimumRelayFee((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((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, (uint256 gasPrice, uint256 pctRelayFee, uint256 baseRelayFee, address relayWorker, address paymaster, address forwarder, bytes paymasterData, uint256 clientId) relayData)',
'function preRelayedCall(((address from, address to, uint256 value, uint256 gas, uint256 nonce, bytes data, uint256 validUntil) request, (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((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((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)',
'receive() payable',
],
};
/* eslint-enable max-len */
9 changes: 9 additions & 0 deletions src/lib/polygon/PolygonContractABIs.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,14 @@ const PolygonContractABIs = { // eslint-disable-line no-unused-vars
'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)',
],

BRIDGED_USDT_CONTRACT_ABI: [
'function approve(address spender, uint256 amount) returns (bool)',
],

BRIDGED_USDT_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)',
],
};
/* eslint-enable max-len */
47 changes: 47 additions & 0 deletions src/lib/polygon/PolygonKey.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,53 @@ class PolygonKey { // eslint-disable-line no-unused-vars
return this._signatureToParts(signature);
}

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

// TODO: Make the domain parameters configurable in the request?
const domain = {
name: '(PoS) Tether USD', // This is currently the same for testnet and mainnet
version: '1', // This is currently the same for testnet and mainnet
verifyingContract: CONFIG.BRIDGED_USDT_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,
);

return this._signatureToParts(signature);
}

/**
* @param {string} path
* @param {string} forwarderContractAddress
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
font-size: 5rem;
}

#confirm-transaction .total .usdc-symbol {
#confirm-transaction .total .stablecoin-symbol {
margin-left: 1rem;
font-weight: 700;
}
Expand Down
Loading

0 comments on commit 6fd6189

Please sign in to comment.