diff --git a/CHANGELOG.md b/CHANGELOG.md index 8df6f2a4b..503036727 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Unreleased - added: (zkSync) Add USDC +- added: (Cosmos) Add chain ID updater +- added: (Cosmos) Support multiple archive nodes - fixed: (zkSync) Fix USDC.e currency code ## 4.17.1 (2024-07-30) diff --git a/package.json b/package.json index a1a4e6619..a474d5110 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "biggystring": "^4.1.3", "bip39": "^3.0.2", "bs58": "4.0.1", - "chain-registry": "^1.47.9", + "chain-registry": "^1.63.50", "cleaners": "^0.3.14", "ed25519-hd-key": "^1.3.0", "eth-sig-util": "^3.0.1", diff --git a/src/cosmos/CosmosEngine.ts b/src/cosmos/CosmosEngine.ts index bf0637ade..c85b6b9a3 100644 --- a/src/cosmos/CosmosEngine.ts +++ b/src/cosmos/CosmosEngine.ts @@ -56,6 +56,7 @@ import { EdgeTokenId, MakeTxParams } from '../common/types' import { cleanTxLogs } from '../common/utils' import { CosmosTools } from './CosmosTools' import { + asChainIdUpdate, asCosmosPrivateKeys, asCosmosTxOtherParams, asCosmosWalletOtherData, @@ -110,6 +111,7 @@ export class CosmosEngine extends CurrencyEngine< feeCache: Map stakedBalanceCache: string stakingSupported: boolean + chainId: string constructor( env: PluginEnvironment, @@ -129,6 +131,7 @@ export class CosmosEngine extends CurrencyEngine< this.feeCache = new Map() this.stakedBalanceCache = '0' this.stakingSupported = true + this.chainId = this.networkInfo.defaultChainId this.otherMethods = { getMaxTx: async (params: MakeTxParams) => { switch (params.type) { @@ -492,6 +495,25 @@ export class CosmosEngine extends CurrencyEngine< } } + async queryChainId(): Promise { + if (this.networkInfo.chainIdUpdateUrl != null) { + try { + const res = await this.fetchCors(this.networkInfo.chainIdUpdateUrl) + if (!res.ok) { + const message = await res.text() + throw new Error(message) + } + const raw = await res.json() + const clean = asChainIdUpdate(raw) + this.chainId = clean.result.node_info.network + } catch (e: any) { + this.error(`queryChainId Error `, e) + return + } + } + clearTimeout(this.timers.queryChainId) + } + async queryTransactions(): Promise { let progress = 0 const allCurrencyCodes = [ @@ -500,42 +522,59 @@ export class CosmosEngine extends CurrencyEngine< tokenId => this.allTokensMap[tokenId].currencyCode ) ] - - for (const query of txQueryStrings) { - let startingPage = 1 - let clients = this.getClients() - - if ( - this.networkInfo.archiveNode != null && - Date.now() - TWO_WEEKS > this.otherData.archivedTxLastCheckTime - ) { - startingPage = this.otherData[query].lastPage - clients = await createCosmosClients( - this.fetchCors, - rpcWithApiKey(this.networkInfo.archiveNode, this.tools.initOptions) - ) - } - - const { newestTxid, lastPage } = await this.queryTransactionsInner( - query, - clients, - startingPage + const clientsList: CosmosClients[] = [] + if ( + this.networkInfo.archiveNodes != null && + Date.now() - TWO_WEEKS > this.otherData.archivedTxLastCheckTime + ) { + const sortedArchiveNodes = this.networkInfo.archiveNodes.sort( + (a, b) => a.blockTimeRangeSeconds.start - b.blockTimeRangeSeconds.start ) - if ( - (newestTxid != null && - this.otherData[query]?.newestTxid !== newestTxid) || - lastPage !== this.otherData[query]?.lastPage - ) { - this.otherData[query] = { newestTxid, lastPage } - this.walletLocalDataDirty = true + for (const node of sortedArchiveNodes) { + if ( + node.blockTimeRangeSeconds.end == null || + node.blockTimeRangeSeconds.end > + this.otherData.archivedTxLastCheckTime + ) { + const archiveClients = await createCosmosClients( + this.fetchCors, + rpcWithApiKey(node.endpoint, this.tools.initOptions) + ) + clientsList.push(archiveClients) + } } - progress += 0.5 - allCurrencyCodes.forEach( - code => (this.tokenCheckTransactionsStatus[code] = progress) - ) - this.updateOnAddressesChecked() + } + clientsList.push(this.getClients()) + + for (const clients of clientsList) { + let archivedTxLastCheckTime = 0 + for (const query of txQueryStrings) { + const { newestTxid, lastTimestamp } = await this.queryTransactionsInner( + query, + clients + ) + if ( + newestTxid != null && + this.otherData[query]?.newestTxid !== newestTxid + ) { + this.otherData[query] = { newestTxid } + this.walletLocalDataDirty = true + archivedTxLastCheckTime = Math.max( + archivedTxLastCheckTime, + lastTimestamp + ) + } + progress += 0.5 / clientsList.length + allCurrencyCodes.forEach( + code => (this.tokenCheckTransactionsStatus[code] = progress) + ) + this.updateOnAddressesChecked() + } + this.otherData.archivedTxLastCheckTime = archivedTxLastCheckTime + this.walletLocalDataDirty = true } this.otherData.archivedTxLastCheckTime = Date.now() + this.walletLocalDataDirty = true if (this.transactionsChangedArray.length > 0) { this.currencyEngineCallbacks.onTransactionsChanged( @@ -547,16 +586,16 @@ export class CosmosEngine extends CurrencyEngine< async queryTransactionsInner( queryString: typeof txQueryStrings[number], - clients: CosmosClients, - startingPage: number - ): Promise<{ newestTxid: string | undefined; lastPage: number }> { + clients: CosmosClients + ): Promise<{ newestTxid: string | undefined; lastTimestamp: number }> { const txSearchParams = { query: `${queryString}='${this.walletInfo.keys.bech32Address}'`, per_page: TXS_PER_PAGE, // sdk default 50 order_by: 'asc' } let newestTxid: string | undefined - let page = startingPage + let lastTimestamp = 0 + let page = 1 do { try { const { totalCount, txs } = await clients.cometClient.txSearch({ @@ -619,7 +658,8 @@ export class CosmosEngine extends CurrencyEngine< }) newestTxid = txidHex - this.otherData[queryString] = { newestTxid: txidHex, lastPage: page } + this.otherData[queryString] = { newestTxid: txidHex } + lastTimestamp = date * 1000 this.walletLocalDataDirty = true } @@ -639,11 +679,11 @@ export class CosmosEngine extends CurrencyEngine< } page++ - this.otherData[queryString] = { newestTxid, lastPage: page } + this.otherData[queryString] = { newestTxid } this.walletLocalDataDirty = true } while (true) - return { newestTxid, lastPage: page } + return { newestTxid, lastTimestamp } } processCosmosTransaction( @@ -874,6 +914,9 @@ export class CosmosEngine extends CurrencyEngine< async startEngine(): Promise { this.engineOn = true await this.tools.connectClient() + this.addToLoop('queryChainId', TRANSACTION_POLL_MILLISECONDS).catch( + () => {} + ) this.addToLoop('queryBalance', ACCOUNT_POLL_MILLISECONDS).catch(() => {}) this.addToLoop('queryBlockheight', ACCOUNT_POLL_MILLISECONDS).catch( () => {} @@ -1166,7 +1209,7 @@ export class CosmosEngine extends CurrencyEngine< accountNumber: longify(this.accountNumber), authInfoBytes, bodyBytes, - chainId: this.tools.chainData.chain_id + chainId: this.chainId }) const signer = await this.tools.createSigner(keys.mnemonic) const signResponse = await signer.signDirect( diff --git a/src/cosmos/CosmosTools.ts b/src/cosmos/CosmosTools.ts index cc9227d54..e4421d8e7 100644 --- a/src/cosmos/CosmosTools.ts +++ b/src/cosmos/CosmosTools.ts @@ -60,9 +60,10 @@ export class CosmosTools implements EdgeCurrencyTools { ) this.methods = methods this.registry = registry - const { chainId, url } = this.networkInfo.chainInfo + const { chainName, url } = this.networkInfo.chainInfo const chainData = chains.find( - chain => chain.chain_id === chainId && chain.network_type === 'mainnet' + chain => + chain.chain_name === chainName && chain.network_type === 'mainnet' ) if (chainData == null) { throw new Error('Unknown chain') diff --git a/src/cosmos/cosmosTypes.ts b/src/cosmos/cosmosTypes.ts index 853357353..813a2af46 100644 --- a/src/cosmos/cosmosTypes.ts +++ b/src/cosmos/cosmosTypes.ts @@ -70,14 +70,23 @@ export interface CosmosNetworkInfo { bech32AddressPrefix: string bip39Path: string chainInfo: { - chainId: string + chainName: string url: string } + defaultChainId: string + chainIdUpdateUrl?: string defaultTransactionFeeUrl?: HttpEndpoint nativeDenom: string pluginMnemonicKeyName: string rpcNode: HttpEndpoint - archiveNode?: HttpEndpoint // If no archive node, the rpc node will be used and only grab transaction in a limited range + // If no archive node, the rpc node will be used and only grab transaction in a limited range + archiveNodes?: Array<{ + blockTimeRangeSeconds: { + start: number + end?: number + } + endpoint: HttpEndpoint + }> } const asHttpEndpoint = asObject({ url: asString, @@ -90,19 +99,16 @@ export const txQueryStrings = [ ] as const const asLocalTxQueryParams = asObject({ - newestTxid: asMaybe(asString), - lastPage: asMaybe(asNumber, 1) + newestTxid: asMaybe(asString) }) export const asCosmosWalletOtherData = asObject({ archivedTxLastCheckTime: asMaybe(asNumber, 0), 'coin_spent.spender': asMaybe(asLocalTxQueryParams, () => ({ - newestTxid: undefined, - lastPage: 1 + newestTxid: undefined })), 'coin_received.receiver': asMaybe(asLocalTxQueryParams, () => ({ - newestTxid: undefined, - lastPage: 1 + newestTxid: undefined })) }) export type CosmosWalletOtherData = ReturnType @@ -272,6 +278,14 @@ export interface IbcChannel { export const asCosmosInfoPayload = asObject({ rpcNode: asOptional(asHttpEndpoint), - archiveNode: asOptional(asHttpEndpoint) + archiveNodes: asOptional(asHttpEndpoint) }) export type CosmosInfoPayload = ReturnType + +export const asChainIdUpdate = asObject({ + result: asObject({ + node_info: asObject({ + network: asString // 'thorchain-mainnet-v1', + }) + }) +}) diff --git a/src/cosmos/info/axelarInfo.ts b/src/cosmos/info/axelarInfo.ts index c1f9b4d5e..a8eee28c7 100644 --- a/src/cosmos/info/axelarInfo.ts +++ b/src/cosmos/info/axelarInfo.ts @@ -12,19 +12,27 @@ const networkInfo: CosmosNetworkInfo = { bech32AddressPrefix: 'axelar', bip39Path: `m/44'/118'/0'/0/0`, chainInfo: { - chainId: 'axelar-dojo-1', + chainName: 'axelar', url: 'https://raw.githubusercontent.com/cosmos/chain-registry/master/axelar/chain.json' }, + defaultChainId: 'axelar-dojo-1', nativeDenom: 'uaxl', pluginMnemonicKeyName: 'axelarMnemonic', rpcNode: { url: 'https://axelar-rpc.publicnode.com:443', headers: {} }, - archiveNode: { - url: 'https://axelararchive-rpc.quickapi.com:443', - headers: {} - } + archiveNodes: [ + { + blockTimeRangeSeconds: { + start: 0 + }, + endpoint: { + url: 'https://axelararchive-rpc.quickapi.com:443', + headers: {} + } + } + ] } const currencyInfo: EdgeCurrencyInfo = { diff --git a/src/cosmos/info/coreumInfo.ts b/src/cosmos/info/coreumInfo.ts index b1fb15a06..de9e95ba3 100644 --- a/src/cosmos/info/coreumInfo.ts +++ b/src/cosmos/info/coreumInfo.ts @@ -133,19 +133,27 @@ const networkInfo: CosmosNetworkInfo = { bech32AddressPrefix: 'core', bip39Path: `m/44'/990'/0'/0/0`, chainInfo: { - chainId: 'coreum-mainnet-1', + chainName: 'coreum', url: 'https://raw.githubusercontent.com/cosmos/chain-registry/master/coreum/chain.json' }, + defaultChainId: 'coreum-mainnet-1', nativeDenom: 'ucore', pluginMnemonicKeyName: 'coreumMnemonic', rpcNode: { url: 'https://full-node.mainnet-1.coreum.dev:26657', headers: {} }, - archiveNode: { - url: 'https://full-node.mainnet-1.coreum.dev:26657', - headers: {} - } + archiveNodes: [ + { + blockTimeRangeSeconds: { + start: 0 + }, + endpoint: { + url: 'https://full-node.mainnet-1.coreum.dev:26657', + headers: {} + } + } + ] } const currencyInfo: EdgeCurrencyInfo = { diff --git a/src/cosmos/info/cosmoshubInfo.ts b/src/cosmos/info/cosmoshubInfo.ts index 92ea5ba6b..301bf064f 100644 --- a/src/cosmos/info/cosmoshubInfo.ts +++ b/src/cosmos/info/cosmoshubInfo.ts @@ -12,9 +12,10 @@ const networkInfo: CosmosNetworkInfo = { bech32AddressPrefix: 'cosmos', bip39Path: `m/44'/118'/0'/0/0`, chainInfo: { - chainId: 'cosmoshub-4', + chainName: 'cosmoshub', url: 'https://raw.githubusercontent.com/cosmos/chain-registry/master/cosmoshub/chain.json' }, + defaultChainId: 'cosmoshub-4', nativeDenom: 'uatom', pluginMnemonicKeyName: 'cosmoshubMnemonic', rpcNode: { diff --git a/src/cosmos/info/osmosisInfo.ts b/src/cosmos/info/osmosisInfo.ts index 47cbc6f55..0a805bc89 100644 --- a/src/cosmos/info/osmosisInfo.ts +++ b/src/cosmos/info/osmosisInfo.ts @@ -25,19 +25,27 @@ const networkInfo: CosmosNetworkInfo = { bech32AddressPrefix: 'osmo', bip39Path: `m/44'/118'/0'/0/0`, chainInfo: { - chainId: 'osmosis-1', + chainName: 'osmosis', url: 'https://raw.githubusercontent.com/cosmos/chain-registry/master/osmosis/chain.json' }, + defaultChainId: 'osmosis-1', nativeDenom: 'uosmo', pluginMnemonicKeyName: 'osmosisMnemonic', rpcNode: { url: 'https://rpc.osmosis.zone:443', headers: {} }, - archiveNode: { - url: 'https://osmosisarchive-rpc.quickapi.com:443', - headers: {} - } + archiveNodes: [ + { + blockTimeRangeSeconds: { + start: 0 + }, + endpoint: { + url: 'https://osmosisarchive-rpc.quickapi.com:443', + headers: {} + } + } + ] } const currencyInfo: EdgeCurrencyInfo = { diff --git a/src/cosmos/info/thorchainruneInfo.ts b/src/cosmos/info/thorchainruneInfo.ts index c01e158bb..f1cd7b6d2 100644 --- a/src/cosmos/info/thorchainruneInfo.ts +++ b/src/cosmos/info/thorchainruneInfo.ts @@ -13,9 +13,11 @@ const networkInfo: CosmosNetworkInfo = { bech32AddressPrefix: 'thor', bip39Path: `m/44'/931'/0'/0/0`, chainInfo: { - chainId: 'thorchain-mainnet-v1', + chainName: 'thorchain', url: 'https://raw.githubusercontent.com/cosmos/chain-registry/master/thorchain/chain.json' }, + defaultChainId: 'thorchain-mainnet-v1', + chainIdUpdateUrl: 'https://rpc.ninerealms.com/status', defaultTransactionFeeUrl: { url: 'https://thornode.ninerealms.com/thorchain/network', headers: { 'x-client-id': '{{ninerealmsClientId}}' } @@ -26,10 +28,27 @@ const networkInfo: CosmosNetworkInfo = { url: 'https://rpc.ninerealms.com', headers: { 'x-client-id': '{{ninerealmsClientId}}' } }, - archiveNode: { - url: 'https://rpc-v1.ninerealms.com', - headers: { 'x-client-id': '{{ninerealmsClientId}}' } - } + archiveNodes: [ + { + blockTimeRangeSeconds: { + start: 1647912564649 // 2022-03-22T01:29:24.649Z + // end: TBD + }, + endpoint: { + url: 'https://rpc-v1.ninerealms.com', + headers: { 'x-client-id': '{{ninerealmsClientId}}' } + } + } + // { + // blockTimeRangeSeconds: { + // start: TBD + // }, + // endpoint: { + // url: 'https://rpc-v2.ninerealms.com', + // headers: { 'x-client-id': '{{ninerealmsClientId}}' } + // } + // } + ] } const currencyInfo: EdgeCurrencyInfo = { diff --git a/yarn.lock b/yarn.lock index dc5e64227..7fb94e55d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -269,10 +269,10 @@ dependencies: "@babel/runtime" "^7.21.0" -"@chain-registry/types@^0.28.13": - version "0.28.13" - resolved "https://registry.yarnpkg.com/@chain-registry/types/-/types-0.28.13.tgz#cf656483ea5e71f9d950a22c549be93a2342540e" - integrity sha512-mgv3reASu/AFMkrRmNaXQZ0RxHIegmq02joZ2HzysoSOUgB1cmLsBwTWoUuPz4z9V3WSGfyw2san9WhrKqPggQ== +"@chain-registry/types@^0.45.40": + version "0.45.40" + resolved "https://registry.yarnpkg.com/@chain-registry/types/-/types-0.45.40.tgz#c04d426c5ac001d3cbd8b6b664244448bc919b5e" + integrity sha512-WV7ZaubZyDJD+x6Rbw8m6Iv7Vn2k4+VK4tJ59RaSUtfyPzF8ZI080g53+McmrRilxbvUt+BhgNva0zhLB5sXKQ== "@chain-registry/utils@^1.14.0": version "1.14.0" @@ -3248,12 +3248,12 @@ chai@^4.2.0: pathval "^1.1.1" type-detect "^4.0.5" -chain-registry@^1.47.9: - version "1.47.9" - resolved "https://registry.yarnpkg.com/chain-registry/-/chain-registry-1.47.9.tgz#be7a5621affadcf5b84f41c7609c09dea5e757f7" - integrity sha512-GMh3NANzhnnIacI4zhWAQjWwDbj0rVdu8LhgYNdyZ8VmbCXu3Og3UPMtJRmNuLJmdVjwZN912j3IbVzm8IeQuA== +chain-registry@^1.63.50: + version "1.63.50" + resolved "https://registry.yarnpkg.com/chain-registry/-/chain-registry-1.63.50.tgz#c818a589b2728f37b8fecdca01617217c1885230" + integrity sha512-o7PWswcjVwZgU3Wdx6ogBNNvChawpV9S+HMl6L+yzOhBNfRqUlrDBwSfPW+XHhfVbwsnGrBpMn71Jic0yMvImA== dependencies: - "@chain-registry/types" "^0.28.13" + "@chain-registry/types" "^0.45.40" chalk@^2.4.1, chalk@^2.4.2: version "2.4.2"