Skip to content

Commit

Permalink
Fetch native USDC balances in parallel to the bridged USDC balances
Browse files Browse the repository at this point in the history
  • Loading branch information
sisou committed Oct 31, 2023
1 parent 9b1bcc4 commit cc9f4f3
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 5 deletions.
1 change: 1 addition & 0 deletions src/config/config.local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default {
// eslint-disable-next-line max-len
// rpcEndpoint: 'wss://shy-sparkling-wind.matic-testnet.discover.quiknode.pro/4461ca78cea96dd6a168a58d8fc30a021cabf01d/',
usdcContract: '0x0FA8781a83E46826621b3BC094Ea2A0212e71B23',
nativeUsdcContract: '0x9999f7fea5938fd3b1e26a12c3f2fb024e194f97',
transferContract: '0x2805f3187dcDfa424EFA8c55Db6012Cf08Fa6eEc', // v3
htlcContract: '0x2EB7cd7791b947A25d629219ead941fCd8f364BF', // v3
relayHubContract: '0x6646cD15d33cE3a6933e36de38990121e8ba2806',
Expand Down
1 change: 1 addition & 0 deletions src/config/config.mainnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default {
rpcEndpoint: 'wss://polygon-mainnet.g.alchemy.com/v2/#ALCHEMY_API_KEY#',
rpcMaxBlockRange: 1_296_000, // 30 days - Range not limited, only limited by number of logs returned
usdcContract: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174',
nativeUsdcContract: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
transferContract: '0x98E69a6927747339d5E543586FC0262112eBe4BD',
htlcContract: '0xF615bD7EA00C4Cc7F39Faad0895dB5f40891359f',
relayHubContract: '0x6C28AfC105e65782D9Ea6F2cA68df84C9e7d750d',
Expand Down
1 change: 1 addition & 0 deletions src/config/config.testnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default {
rpcEndpoint: 'wss://polygon-mumbai.g.alchemy.com/v2/#ALCHEMY_API_KEY#',
rpcMaxBlockRange: 1_296_000, // 30 days - Range not limited, only limited by number of logs returned
usdcContract: '0x0FA8781a83E46826621b3BC094Ea2A0212e71B23',
nativeUsdcContract: '0x9999f7fea5938fd3b1e26a12c3f2fb024e194f97',
transferContract: '0x2805f3187dcDfa424EFA8c55Db6012Cf08Fa6eEc', // v3
htlcContract: '0x2EB7cd7791b947A25d629219ead941fCd8f364BF', // v3
relayHubContract: '0x6646cD15d33cE3a6933e36de38990121e8ba2806',
Expand Down
57 changes: 52 additions & 5 deletions src/ethers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ import {
} from './stores/UsdcTransactions';
import { useConfig } from './composables/useConfig';
import { ENV_MAIN } from './lib/Constants';
import { USDC_TRANSFER_CONTRACT_ABI, USDC_CONTRACT_ABI, USDC_HTLC_CONTRACT_ABI } from './lib/usdc/ContractABIs';
import {
USDC_TRANSFER_CONTRACT_ABI,
USDC_CONTRACT_ABI,
USDC_HTLC_CONTRACT_ABI,
NATIVE_USDC_CONTRACT_ABI,
} from './lib/usdc/ContractABIs';
import {
getBestRelay,
getRelayAddr,
Expand All @@ -30,6 +35,7 @@ export interface PolygonClient {
provider: providers.Provider;
usdc: Contract;
usdcTransfer: Contract;
nativeUsdc: Contract;
ethers: typeof ethers;
}

Expand All @@ -42,7 +48,14 @@ function consensusEstablishedHandler(height: number) {
let isLaunched = false;

type Balances = Map<string, number>;
const balances: Balances = new Map(); // Balances in USDC-base units, excluding pending txs
/**
* Balances in bridged USDC-base units, excluding pending txs.
*/
const balances: Balances = new Map();
/**
* Balances in native USDC-base units, excluding pending txs.
*/
const nativeBalances: Balances = new Map();

let clientPromise: Promise<PolygonClient> | null = null;
let unwatchGetPolygonClientConfig: (() => void) | null = null;
Expand Down Expand Up @@ -111,10 +124,13 @@ export async function getPolygonClient(): Promise<PolygonClient> {
const usdc = new ethers.Contract(config.usdc.usdcContract, USDC_CONTRACT_ABI, provider);
const usdcTransfer = new ethers.Contract(config.usdc.transferContract, USDC_TRANSFER_CONTRACT_ABI, provider);

const nativeUsdc = new ethers.Contract(config.usdc.nativeUsdcContract, NATIVE_USDC_CONTRACT_ABI, provider);

resolver!({
provider,
usdc,
usdcTransfer,
nativeUsdc,
ethers,
});

Expand All @@ -127,13 +143,18 @@ async function getBalance(address: string) {
return balance.toNumber(); // With Javascript numbers we can represent up to 9,007,199,254 USDC, enough for now
}

async function getNativeBalance(address: string) {
const client = await getPolygonClient();
const balance = await client.nativeUsdc.balanceOf(address) as BigNumber;
return balance.toNumber(); // With Javascript numbers we can represent up to 9,007,199,254 USDC, enough for now
}

async function updateBalances(addresses: string[] = [...balances.keys()]) {
if (!addresses.length) return;
const accounts = await Promise.all(addresses.map((address) => getBalance(address)));
const newBalances: Balances = new Map(
accounts.map((balance, i) => [addresses[i], balance]),
);

for (const [address, newBalance] of newBalances) {
if (balances.get(address) === newBalance) {
// Balance did not change since last check.
Expand All @@ -145,17 +166,43 @@ async function updateBalances(addresses: string[] = [...balances.keys()]) {
}
}

if (!newBalances.size) return;
console.debug('Got new USDC balances for', [...newBalances.keys()], [...newBalances.values()]);
const nativeAccounts = await Promise.all(addresses.map((address) => getNativeBalance(address)));
const newNativeBalances: Balances = new Map(
nativeAccounts.map((balance, i) => [addresses[i], balance]),
);
for (const [address, newBalance] of newNativeBalances) {
if (nativeBalances.get(address) === newBalance) {
// Balance did not change since last check.
// Remove from newBalances Map to not update the store.
newNativeBalances.delete(address);
} else {
// Update balances cache
nativeBalances.set(address, newBalance);
}
}

if (!newBalances.size && !newNativeBalances.size) return;
if (newBalances.size) {
console.debug('Got new bridged USDC balances for', [...newBalances.keys()], [...newBalances.values()]);
}
if (newNativeBalances.size) {
console.warn(
'Got new native USDC balances for', [...newNativeBalances.keys()], [...newNativeBalances.values()],
);
}
const { patchAddress } = useUsdcAddressStore();
for (const [address, balance] of newBalances) {
patchAddress(address, { balance });
}
for (const [address, nativeBalance] of newNativeBalances) {
patchAddress(address, { nativeBalance });
}
}

function forgetBalances(addresses: string[]) {
for (const address of addresses) {
balances.delete(address);
nativeBalances.delete(address);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/hub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ function processAndStoreAccounts(accounts: Account[], replaceState = false): voi
usdcAddressInfos.push({
address: polygonAddress,
balance: usdcAddressStore.state.addressInfos[polygonAddress]?.balance ?? null,
nativeBalance: usdcAddressStore.state.addressInfos[polygonAddress]?.nativeBalance ?? null,
matic: usdcAddressStore.state.addressInfos[polygonAddress]?.matic ?? null,
});
}
Expand Down
70 changes: 70 additions & 0 deletions src/lib/usdc/ContractABIs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,76 @@ export const USDC_CONTRACT_ABI = [
// 'function withdrawWithAuthorization(address owner, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s)',
];

export const NATIVE_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)',
// 'event Blacklisted(address indexed _account)',
// 'event BlacklisterChanged(address indexed newBlacklister)',
// 'event Burn(address indexed burner, uint256 amount)',
// 'event MasterMinterChanged(address indexed newMasterMinter)',
// 'event Mint(address indexed minter, address indexed to, uint256 amount)',
// 'event MinterConfigured(address indexed minter, uint256 minterAllowedAmount)',
// 'event MinterRemoved(address indexed oldMinter)',
// 'event OwnershipTransferred(address previousOwner, address newOwner)',
// 'event Pause()',
// 'event PauserChanged(address indexed newAddress)',
// 'event RescuerChanged(address indexed newRescuer)',
// 'event Transfer(address indexed from, address indexed to, uint256 value)',
// 'event UnBlacklisted(address indexed _account)',
// 'event Unpause()',
// 'function CANCEL_AUTHORIZATION_TYPEHASH() view returns (bytes32)',
// 'function DOMAIN_SEPARATOR() view returns (bytes32)',
// 'function PERMIT_TYPEHASH() view returns (bytes32)',
// 'function RECEIVE_WITH_AUTHORIZATION_TYPEHASH() view returns (bytes32)',
// 'function TRANSFER_WITH_AUTHORIZATION_TYPEHASH() view returns (bytes32)',
// 'function allowance(address owner, address spender) view returns (uint256)',
// 'function approve(address spender, uint256 value) returns (bool)',
// 'function authorizationState(address authorizer, bytes32 nonce) view returns (bool)',
'function balanceOf(address account) view returns (uint256)',
// 'function blacklist(address _account)',
// 'function blacklister() view returns (address)',
// 'function burn(uint256 _amount)',
// 'function cancelAuthorization(address authorizer, bytes32 nonce, uint8 v, bytes32 r, bytes32 s)',
// 'function configureMinter(address minter, uint256 minterAllowedAmount) returns (bool)',
// 'function currency() view returns (string)',
// 'function decimals() view returns (uint8)',
// 'function decreaseAllowance(address spender, uint256 decrement) returns (bool)',
// 'function increaseAllowance(address spender, uint256 increment) returns (bool)',
// 'function initialize(string tokenName, string tokenSymbol, string tokenCurrency, uint8 tokenDecimals, address newMasterMinter, address newPauser, address newBlacklister, address newOwner)',
// 'function initializeV2(string newName)',
// 'function initializeV2_1(address lostAndFound)',
// 'function isBlacklisted(address _account) view returns (bool)',
// 'function isMinter(address account) view returns (bool)',
// 'function masterMinter() view returns (address)',
// 'function mint(address _to, uint256 _amount) returns (bool)',
// 'function minterAllowance(address minter) view returns (uint256)',
// 'function name() view returns (string)',
// 'function nonces(address owner) view returns (uint256)',
// 'function owner() view returns (address)',
// 'function pause()',
// 'function paused() view returns (bool)',
// 'function pauser() view returns (address)',
// 'function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)',
// 'function receiveWithAuthorization(address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s)',
// 'function removeMinter(address minter) returns (bool)',
// 'function rescueERC20(address tokenContract, address to, uint256 amount)',
// 'function rescuer() view returns (address)',
// 'function symbol() view returns (string)',
// 'function totalSupply() view returns (uint256)',
// 'function transfer(address to, uint256 value) returns (bool)',
// 'function transferFrom(address from, address to, uint256 value) returns (bool)',
// 'function transferOwnership(address newOwner)',
// 'function transferWithAuthorization(address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s)',
// 'function unBlacklist(address _account)',
// 'function unpause()',
// 'function updateBlacklister(address _newBlacklister)',
// 'function updateMasterMinter(address _newMasterMinter)',
// 'function updatePauser(address _newPauser)',
// 'function updateRescuer(address newRescuer)',
// 'function version() view returns (string)',
];

export const USDC_TRANSFER_CONTRACT_ABI = [
// 'constructor()',
// 'event DomainRegistered(bytes32 indexed domainSeparator, bytes domainValue)',
Expand Down
1 change: 1 addition & 0 deletions src/stores/UsdcAddress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type UsdcAddressState = {
export type UsdcAddressInfo = {
address: string,
balance: number | null,
nativeBalance: number | null,
matic: number | null, // For testing until OpenGSN contract is available
};

Expand Down

0 comments on commit cc9f4f3

Please sign in to comment.