diff --git a/CHANGELOG.md b/CHANGELOG.md index 57a22fa15..4d2a6ed36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## [4.2.16] - 2024.2.15 + +### Added + +- Added support for Cronos and Cronos testnet subscriptions. + ## [4.2.15] - 2024.3.11 ### Added diff --git a/package.json b/package.json index 46c12a712..f2377776c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@tatumio/tatum", - "version": "4.2.15", + "version": "4.2.16", "description": "Tatum JS SDK", "author": "Tatum", "repository": "https://github.com/tatumio/tatum-js", diff --git a/src/connector/tatum.connector.ts b/src/connector/tatum.connector.ts index 05fc0c283..5a9c1a3b2 100644 --- a/src/connector/tatum.connector.ts +++ b/src/connector/tatum.connector.ts @@ -106,7 +106,11 @@ export class TatumConnector { if (isDownload) { return await res.blob() } - return await res.json() + const response = await res.json() + if (response?.error) { + return await this.retry(url, request, res, retry) + } + return response } // Retry only in case of 5xx error diff --git a/src/dto/AddressEventNotificationChain.ts b/src/dto/AddressEventNotificationChain.ts index 809c6b838..7bef71ea7 100644 --- a/src/dto/AddressEventNotificationChain.ts +++ b/src/dto/AddressEventNotificationChain.ts @@ -15,4 +15,5 @@ export enum AddressEventNotificationChain { EON = 'EON', CHZ = 'CHZ', FLR = 'FLR', + CRO = 'CRO', } diff --git a/src/dto/Network.ts b/src/dto/Network.ts index 45b63ec3f..b6f423719 100644 --- a/src/dto/Network.ts +++ b/src/dto/Network.ts @@ -822,11 +822,13 @@ export const NETWORK_METADATA: Record = { currency: Currency.CRO, testnet: false, chainId: 25, + defaultMainnet: true, }, [Network.CRONOS_TESTNET]: { currency: Currency.CRO, testnet: true, chainId: 338, + defaultTestnet: true, }, [Network.KUCOIN]: { currency: Currency.KCS, diff --git a/src/dto/rpc/EvmBasedRpcInterface.ts b/src/dto/rpc/EvmBasedRpcInterface.ts index c0d6f1824..314f30737 100644 --- a/src/dto/rpc/EvmBasedRpcInterface.ts +++ b/src/dto/rpc/EvmBasedRpcInterface.ts @@ -275,6 +275,7 @@ export interface ValidatorQuery extends StateQuery { export interface EvmBeaconV1Interface { getGenesis(): Promise> + getNodeVersion(): Promise> getBlockHeaders(query?: { slot?: string; parentRoot?: string }): Promise> getBlockHeader(query: BlockQuery): Promise> getBlockRoot(query: BlockQuery): Promise> diff --git a/src/e2e/e2e.constant.ts b/src/e2e/e2e.constant.ts index 742f1d1a6..c09bba355 100644 --- a/src/e2e/e2e.constant.ts +++ b/src/e2e/e2e.constant.ts @@ -33,6 +33,11 @@ export const AddressEventNetworks = [ Network.XRP_TESTNET, Network.TEZOS, Network.FLARE, + Network.FLARE_COSTON, + Network.FLARE_COSTON_2, + Network.FLARE_SONGBIRD, + Network.CRONOS, + Network.CRONOS_TESTNET, ] export const IncomingNativeTxNetworks = [ @@ -62,6 +67,12 @@ export const IncomingNativeTxNetworks = [ Network.XRP, Network.XRP_TESTNET, Network.TEZOS, + Network.FLARE, + Network.FLARE_COSTON, + Network.FLARE_COSTON_2, + Network.FLARE_SONGBIRD, + Network.CRONOS, + Network.CRONOS_TESTNET, ] export const OutgoingNativeTxNetworks = [ @@ -89,6 +100,12 @@ export const OutgoingNativeTxNetworks = [ Network.XRP, Network.XRP_TESTNET, Network.TEZOS, + Network.FLARE, + Network.FLARE_COSTON, + Network.FLARE_COSTON_2, + Network.FLARE_SONGBIRD, + Network.CRONOS, + Network.CRONOS_TESTNET, ] export const OutgoingFailedNetworks = [ @@ -104,6 +121,12 @@ export const OutgoingFailedNetworks = [ Network.KLAYTN, Network.KLAYTN_BAOBAB, Network.TEZOS, + Network.FLARE, + Network.FLARE_COSTON, + Network.FLARE_COSTON_2, + Network.FLARE_SONGBIRD, + Network.CRONOS, + Network.CRONOS_TESTNET, ] export const PaidFeeNetworks = [ @@ -123,6 +146,12 @@ export const PaidFeeNetworks = [ Network.XRP, Network.XRP_TESTNET, Network.TEZOS, + Network.FLARE, + Network.FLARE_COSTON, + Network.FLARE_COSTON_2, + Network.FLARE_SONGBIRD, + Network.CRONOS, + Network.CRONOS_TESTNET, ] export const FungibleTxNetworks = [ @@ -142,6 +171,12 @@ export const FungibleTxNetworks = [ Network.SOLANA, Network.SOLANA_DEVNET, Network.TEZOS, + Network.FLARE, + Network.FLARE_COSTON, + Network.FLARE_COSTON_2, + Network.FLARE_SONGBIRD, + Network.CRONOS, + Network.CRONOS_TESTNET, ] export const NftNetworks = [ @@ -161,6 +196,12 @@ export const NftNetworks = [ Network.SOLANA, Network.SOLANA_DEVNET, Network.TEZOS, + Network.FLARE, + Network.FLARE_COSTON, + Network.FLARE_COSTON_2, + Network.FLARE_SONGBIRD, + Network.CRONOS, + Network.CRONOS_TESTNET, ] export const MultitokenNetworks = [ @@ -175,6 +216,12 @@ export const MultitokenNetworks = [ Network.BINANCE_SMART_CHAIN_TESTNET, Network.KLAYTN, Network.KLAYTN_BAOBAB, + Network.FLARE, + Network.FLARE_COSTON, + Network.FLARE_COSTON_2, + Network.FLARE_SONGBIRD, + Network.CRONOS, + Network.CRONOS_TESTNET, ] export const FailedTxPerBlockNetworks = [ @@ -194,6 +241,12 @@ export const FailedTxPerBlockNetworks = [ Network.SOLANA, Network.SOLANA_DEVNET, Network.TEZOS, + Network.FLARE, + Network.FLARE_COSTON, + Network.FLARE_COSTON_2, + Network.FLARE_SONGBIRD, + Network.CRONOS, + Network.CRONOS_TESTNET, ] export const ContractAddressLogEventNetworks = [ @@ -208,6 +261,10 @@ export const ContractAddressLogEventNetworks = [ Network.BINANCE_SMART_CHAIN_TESTNET, Network.KLAYTN, Network.KLAYTN_BAOBAB, + Network.FLARE, + Network.FLARE_COSTON, + Network.FLARE_COSTON_2, + Network.FLARE_SONGBIRD, ] export const InternalTxNetworks = [ @@ -217,4 +274,8 @@ export const InternalTxNetworks = [ Network.CELO, Network.CELO_ALFAJORES, Network.TEZOS, + Network.FLARE, + Network.FLARE_COSTON, + Network.FLARE_COSTON_2, + Network.FLARE_SONGBIRD, ] diff --git a/src/e2e/e2e.util.ts b/src/e2e/e2e.util.ts index 57586b6c8..7ed047209 100644 --- a/src/e2e/e2e.util.ts +++ b/src/e2e/e2e.util.ts @@ -7,6 +7,7 @@ import { ContractBasedNotificationDetail, FullSdk, Network, + NotificationSubscription, } from '../service' import { ResponseDto } from '../util' import { NetworkUtils } from '../util/network.utils' @@ -32,11 +33,13 @@ export const e2eUtil = { case Network.FLARE_COSTON: case Network.FLARE_COSTON_2: case Network.FLARE_SONGBIRD: + case Network.CRONOS: + case Network.CRONOS_TESTNET: return '0xdb4C3b4350EE869F2D0a2F43ce0292865E2Aa149' case Network.CELO_ALFAJORES: return '0xdf083B077F1FD890fC71feCaBbd3F68F94cD21Bf' case Network.POLYGON_MUMBAI: - return '0xd608d48c6ddB41254d73ec0619FE31B254Fb5ab8' + return '0xcf3c930601111c216fc0232d32cf5c2a86f107da' case Network.KLAYTN_BAOBAB: return '0xdc7Dfb8Aa86D41b7e39441711Fe1669f2d843C06' case Network.BINANCE_SMART_CHAIN_TESTNET: @@ -91,6 +94,7 @@ export const e2eUtil = { addressBasedNotificationDetail: AddressBasedNotificationDetail, ) => Promise>, ) => { + await e2eUtil.flushSubscriptions(tatum) const url = 'https://webhook.site/' const { data, error } = await func({ url, @@ -116,6 +120,7 @@ export const e2eUtil = { contractBasedNotificationDetail: ContractBasedNotificationDetail, ) => Promise>, ) => { + await e2eUtil.flushSubscriptions(tatum) const url = 'https://webhook.site/' const event = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' const { data, error } = await func({ @@ -144,6 +149,7 @@ export const e2eUtil = { blockBasedNotificationDetail: BlockBasedNotificationDetail, ) => Promise>, ) => { + await e2eUtil.flushSubscriptions(tatum) const url = 'https://webhook.site/' const { data, error } = await func({ @@ -157,4 +163,18 @@ export const e2eUtil = { }, }, isVerbose: process.env.E2E_VERBOSE === 'true', + flushSubscriptions: async (tatum: FullSdk) => { + try { + const notifications = await tatum.notification.getAll() + + if (notifications?.data?.length > 0) { + for (const notification of notifications.data as NotificationSubscription[]) { + await tatum.notification.unsubscribe(notification.id) + } + } + } catch (e) { + console.error('Error flushing subscriptions') + console.error(e) + } + }, } diff --git a/src/e2e/rpc/evm/eth/tatum.rpc.beacon.spec.ts b/src/e2e/rpc/evm/eth/tatum.rpc.beacon.spec.ts index 2c98efe53..5f54d23e5 100644 --- a/src/e2e/rpc/evm/eth/tatum.rpc.beacon.spec.ts +++ b/src/e2e/rpc/evm/eth/tatum.rpc.beacon.spec.ts @@ -7,6 +7,13 @@ describe('Beacon', () => { const networks = [Network.ETHEREUM_HOLESKY, Network.ETHEREUM_SEPOLIA, Network.ETHEREUM] describe.each(networks)('%s', (network) => { + it('should get node version', async () => { + const tatum = await EvmE2eUtils.initTatum(network, process.env.V4_API_KEY_TESTNET) + const { data } = await tatum.rpc.beacon.v1.getNodeVersion() + await tatum.destroy() + expect(data).toBeDefined() + }) + it('should get genesis', async () => { const tatum = await EvmE2eUtils.initTatum(network, process.env.V4_API_KEY_TESTNET) const { data } = await tatum.rpc.beacon.v1.getGenesis() diff --git a/src/e2e/rpc/other/tatum.rpc.xrp.spec.ts b/src/e2e/rpc/other/tatum.rpc.xrp.spec.ts index 81dc8ea74..664cb9fe9 100644 --- a/src/e2e/rpc/other/tatum.rpc.xrp.spec.ts +++ b/src/e2e/rpc/other/tatum.rpc.xrp.spec.ts @@ -5,11 +5,6 @@ const getXrpRpc = async (testnet?: boolean) => await TatumSDK.init(e2eUtil.initConfig(testnet ? Network.XRP_TESTNET : Network.XRP)) describe('RPCs', () => { - afterEach(async () => { - // wait for 200ms to avoid rate limit - await new Promise((resolve) => setTimeout(resolve, 100)) - }) - describe('XRP', () => { describe('testnet', () => { it('ping', async () => { @@ -18,6 +13,20 @@ describe('RPCs', () => { await tatum.destroy() expect(result.status).toBe('success') }) + + it('ledger_closed', async () => { + const tatum = await getXrpRpc(true) + const { result } = await tatum.rpc.ledgerClosed() + await tatum.destroy() + expect(result.ledger_index).toBeGreaterThan(0) + }) + + it('fee', async () => { + const tatum = await getXrpRpc(true) + const { result } = await tatum.rpc.fee() + await tatum.destroy() + expect(result.ledger_current_index).toBeGreaterThan(0) + }) }) }) describe('XRP', () => { diff --git a/src/e2e/tatum.notification.spec.ts b/src/e2e/tatum.notification.spec.ts index 5d454d9b1..941655dea 100644 --- a/src/e2e/tatum.notification.spec.ts +++ b/src/e2e/tatum.notification.spec.ts @@ -17,19 +17,7 @@ import { } from './e2e.constant' import { e2eUtil } from './e2e.util' -// TODO pipeline dont work with API keys - describe('notification', () => { - beforeAll(async () => { - const tatum = await TatumSDK.init(e2eUtil.initConfig(Network.ETHEREUM)) - const notifications = await tatum.notification.getAll() - - if (notifications?.data?.length > 0) { - for (const notification of notifications.data as NotificationSubscription[]) { - await tatum.notification.unsubscribe(notification.id) - } - } - }) describe('createSubscription', () => { describe('IP auth', () => { describe('Address Event', () => { diff --git a/src/service/rpc/evm/AbstractBeaconV1EvmRpc.ts b/src/service/rpc/evm/AbstractBeaconV1EvmRpc.ts index 890a19cbe..50ba24181 100644 --- a/src/service/rpc/evm/AbstractBeaconV1EvmRpc.ts +++ b/src/service/rpc/evm/AbstractBeaconV1EvmRpc.ts @@ -17,8 +17,12 @@ import { Constant, Utils } from '../../../util' export abstract class AbstractBeaconV1EvmRpc implements EvmBeaconV1Interface { protected abstract get(get: GetI): Promise - private sendGet(path: string, params: QueryParams): Promise { - const fullPath = Utils.addQueryParams(`${Constant.BEACON_PREFIX}/${path}`, Utils.camelToSnakeCase, params) + private sendGet(path: string, params: QueryParams, prefix?: string): Promise { + const fullPath = Utils.addQueryParams( + `${prefix ?? Constant.BEACON_PREFIX}/${path}`, + Utils.camelToSnakeCase, + params, + ) return this.get({ path: fullPath }) } @@ -80,4 +84,8 @@ export abstract class AbstractBeaconV1EvmRpc implements EvmBeaconV1Interface { getStateValidators({ stateId, ...rest }: ValidatorsQuery): Promise> { return this.sendGet(`states/${stateId}/validators`, rest) } + + getNodeVersion(): Promise> { + return this.sendGet('node/version', {}, Constant.BEACON_BASE_PREFIX) + } } diff --git a/src/service/rpc/generic/LoadBalancer.ts b/src/service/rpc/generic/LoadBalancer.ts index 4bb69f8a6..ddee57a00 100644 --- a/src/service/rpc/generic/LoadBalancer.ts +++ b/src/service/rpc/generic/LoadBalancer.ts @@ -104,7 +104,7 @@ export class LoadBalancer implements AbstractRpcInterface { if (config.rpc?.oneTimeLoadBalancing) { Utils.log({ id: this.id, message: 'oneTimeLoadBalancing enabled' }) - setTimeout(() => this.checkStatuses(), Constant.OPEN_RPC.LB_INTERVAL) + await this.checkStatuses() } else { this.interval = setInterval(() => this.checkStatuses(), Constant.OPEN_RPC.LB_INTERVAL) } diff --git a/src/service/tatum/tatum.evm.ts b/src/service/tatum/tatum.evm.ts index 25a9acfed..7ee507023 100644 --- a/src/service/tatum/tatum.evm.ts +++ b/src/service/tatum/tatum.evm.ts @@ -53,7 +53,7 @@ export class ArbitrumNova extends BaseEvm {} export class ArbitrumOne extends BaseEvm {} export class Aurora extends BaseEvm {} export class AvalancheC extends BaseEvm {} -export class Cronos extends BaseEvm {} +export class Cronos extends NotificationEvm {} export class EthereumClassic extends BaseEvm {} export class Fantom extends BaseEvm {} export class Gnosis extends BaseEvm {} diff --git a/src/util/constant.ts b/src/util/constant.ts index 73e5d61ef..f9d17ee56 100644 --- a/src/util/constant.ts +++ b/src/util/constant.ts @@ -314,4 +314,5 @@ export const Constant = { }, EOS_PREFIX: 'v1/chain/', BEACON_PREFIX: '/eth/v1/beacon', + BEACON_BASE_PREFIX: '/eth/v1', } diff --git a/src/util/util.shared.ts b/src/util/util.shared.ts index 2182d3f14..173e8e488 100644 --- a/src/util/util.shared.ts +++ b/src/util/util.shared.ts @@ -219,6 +219,22 @@ export const Utils = { return mappedNetwork ?? network }, getStatusPayload: (network: Network) => { + if (isXrpNetwork(network)) { + return { + method: 'ledger', + params: [ + { + ledger_index: 'current', + transactions: false, + expand: false, + owner_funds: false, + }, + ], + id: 1, + jsonrpc: '2.0', + } + } + if (isUtxoBasedNetwork(network)) { return { jsonrpc: '2.0', @@ -293,6 +309,10 @@ export const Utils = { return `${url}network/status` } + if (isXrpNetwork(network)) { + return url + } + if (isSameGetBlockNetwork(network)) { return url } @@ -355,6 +375,10 @@ export const Utils = { return new BigNumber((response.last_ledger as number) || -1).toNumber() } + if (isXrpNetwork(network)) { + return new BigNumber((response.result.ledger_current_index as number) || -1).toNumber() + } + throw new Error(`Network ${network} is not supported.`) }, isResponseOk: (network: Network, response: JsonRpcResponse | any) => { @@ -390,6 +414,10 @@ export const Utils = { return response.current_block_identifier.index !== undefined } + if (isXrpNetwork(network)) { + return response.result.ledger_current_index !== undefined + } + throw new Error(`Network ${network} is not supported.`) }, mapNotificationChainToNetwork: (chain: AddressEventNotificationChain): Network => { @@ -478,6 +506,9 @@ export const Utils = { case Network.FLARE_COSTON_2: case Network.FLARE_SONGBIRD: return AddressEventNotificationChain.FLR + case Network.CRONOS: + case Network.CRONOS_TESTNET: + return AddressEventNotificationChain.CRO default: throw new Error(`Network ${network} is not supported.`) }