diff --git a/CHANGELOG.md b/CHANGELOG.md index 706d94309..feeb7d03f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## [4.2.30] - 2024.5.31 + +### Added + +- Support for Electrs calls and Iota testnet, renamed bch rostrum + ## [4.2.29] - 2024.5.27 ### Added diff --git a/package.json b/package.json index 9fdd36b9f..2b8e6119b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@tatumio/tatum", - "version": "4.2.29", + "version": "4.2.30", "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 3804cfd7b..76a53cf57 100644 --- a/src/connector/tatum.connector.ts +++ b/src/connector/tatum.connector.ts @@ -66,6 +66,7 @@ export class TatumConnector { externalUrl?: string, ): Promise { const url = externalUrl || this.getUrl({ path, params, basePath }) + console.log(url) const isUpload = body && body instanceof FormData const headers = isUpload ? Utils.getBasicHeaders(this.id) : Utils.getHeaders(this.id) @@ -162,7 +163,9 @@ export class TatumConnector { basePath, }: GetUrl) { const config = Container.of(this.id).get(CONFIG) - const url = new URL(path || '', basePath || this.getBaseUrl()) + const base = basePath || this.getBaseUrl() + + const url = new URL(path && path?.length > 1 ? `${base}${path}` : base) if (params) { Object.keys(params) diff --git a/src/dto/Network.ts b/src/dto/Network.ts index d718155a7..e1d14948f 100644 --- a/src/dto/Network.ts +++ b/src/dto/Network.ts @@ -43,7 +43,7 @@ export enum Network { PALM = 'palm-mainnet', POLYGON = 'polygon-mainnet', POLKADOT = 'dot-mainnet', - ROSTRUM = 'rostrum-mainnet', + ROSTRUM = 'bch-mainnet-rostrum', RSK = 'rsk-mainnet', SOLANA = 'solana-mainnet', STELLAR = 'stellar-mainnet', @@ -54,6 +54,8 @@ export enum Network { XRP = 'ripple-mainnet', ZCASH = 'zcash-mainnet', ZILLIQA = 'zilliqa-mainnet', + BITCOIN_ELECTRS = 'bitcoin-mainnet-electrs', + // Testnets ALGORAND_ALGOD_TESTNET = 'algorand-testnet-algod', @@ -104,6 +106,7 @@ export enum Network { XRP_TESTNET = 'ripple-testnet', ZCASH_TESTNET = 'zcash-testnet', ZILLIQA_TESTNET = 'zilliqa-testnet', + IOTA_TESTNET = 'iota-testnet', } export const EVM_BASED_NETWORKS = [ @@ -253,8 +256,10 @@ export const CARDANO_NETWORKS = [ ] export const STELLAR_LOAD_BALANCER_NETWORKS = [Network.STELLAR] export const KADENA_LOAD_BALANCER_NETWORKS = [Network.KADENA, Network.KADENA_TESTNET] -export const ROSTRUM_LOAD_BALANCER_NETWORKS = [Network.ROSTRUM] +export const ROSTRUM_LOAD_BALANCER_NETWORKS = [Network.ROSTRUM, Network.BITCOIN_ELECTRS] export const IOTA_LOAD_BALANCER_NETWORKS = [Network.IOTA] +export const BITCOIN_ELECTRS_NETWORKS = [Network.BITCOIN_ELECTRS] +export const IOTA_NETWORKS = [Network.IOTA, Network.IOTA_TESTNET] export const LOAD_BALANCER_NETWORKS = [ ...UTXO_LOAD_BALANCER_NETWORKS, @@ -274,6 +279,7 @@ export const LOAD_BALANCER_NETWORKS = [ ...KADENA_LOAD_BALANCER_NETWORKS, ...ROSTRUM_LOAD_BALANCER_NETWORKS, ...IOTA_LOAD_BALANCER_NETWORKS, + ...BITCOIN_ELECTRS_NETWORKS, ] export const EVM_ARCHIVE_NON_ARCHIVE_LOAD_BALANCER_NETWORKS = [ @@ -364,7 +370,11 @@ export const isStellarLoadBalancerNetwork = (network: Network) => export const isStellarNetwork = (network: Network) => [Network.STELLAR, Network.STELLAR_TESTNET].includes(network) -export const isIotaNetwork = (network: Network) => IOTA_LOAD_BALANCER_NETWORKS.includes(network) +export const isIotaLoadBalancerNetwork = (network: Network) => IOTA_LOAD_BALANCER_NETWORKS.includes(network) + +export const isIotaNetwork = (network: Network) => IOTA_NETWORKS.includes(network) + +export const isElectrsNetwork = (network: Network) => BITCOIN_ELECTRS_NETWORKS.includes(network) export const isSameGetBlockNetwork = (network: Network) => isUtxoBasedNetwork(network) || @@ -918,4 +928,13 @@ export const NETWORK_METADATA: Record = { testnet: false, defaultMainnet: true, }, + [Network.IOTA_TESTNET]: { + currency: Currency.IOTA, + testnet: true, + defaultTestnet: true, + }, + [Network.BITCOIN_ELECTRS]: { + currency: Currency.BTC, + testnet: false, + }, } diff --git a/src/e2e/rpc/other/tatum.rpc.electrs.spec.ts b/src/e2e/rpc/other/tatum.rpc.electrs.spec.ts new file mode 100644 index 000000000..e3582b800 --- /dev/null +++ b/src/e2e/rpc/other/tatum.rpc.electrs.spec.ts @@ -0,0 +1,22 @@ +import { Network, BitcoinElectrs, TatumSDK } from '../../../service' +import { e2eUtil } from '../../e2e.util' + +const getElectrsRpc = async () => await TatumSDK.init(e2eUtil.initConfig(Network.BITCOIN_ELECTRS)) + +describe('Electrs', () => { + + it('blockchain.headers.subscribe', async () => { + const electrs = await getElectrsRpc() + const result = await electrs.rpc.blockchainHeadersSubscribe() + await electrs.destroy() + expect(result.result?.hex).toBeDefined() + expect(result.result?.height).toBeDefined() + }) + + it('server.banner', async () => { + const electrs = await getElectrsRpc() + const result = await electrs.rpc.serverBanner() + await electrs.destroy() + expect(result.result).toBeDefined() + }) +}) diff --git a/src/e2e/rpc/evm/tatum.rpc.iota.spec.ts b/src/e2e/rpc/other/tatum.rpc.iota.spec.ts similarity index 58% rename from src/e2e/rpc/evm/tatum.rpc.iota.spec.ts rename to src/e2e/rpc/other/tatum.rpc.iota.spec.ts index 6ea95ae66..d4151b686 100644 --- a/src/e2e/rpc/evm/tatum.rpc.iota.spec.ts +++ b/src/e2e/rpc/other/tatum.rpc.iota.spec.ts @@ -1,26 +1,26 @@ import { Iota, Network, TatumSDK } from '../../../service' import { e2eUtil } from '../../e2e.util' -const getIotaRpc = async () => await TatumSDK.init(e2eUtil.initConfig(Network.IOTA)) +const getIotaRpc = async (testnet?: boolean) => await TatumSDK.init(e2eUtil.initConfig(testnet ? Network.IOTA_TESTNET : Network.IOTA)) -describe('Iota', () => { - describe('mainnet', () => { +describe.each([true, false])('Iota', (testnet) => { + describe(`${testnet ? Network.IOTA_TESTNET : Network.IOTA}`, () => { it('getNodeInfo', async () => { - const tatum = await getIotaRpc() + const tatum = await getIotaRpc(testnet) const info = await tatum.rpc.getNodeInfo() await tatum.destroy() expect(info).toBeDefined() }) it('getTips', async () => { - const tatum = await getIotaRpc() + const tatum = await getIotaRpc(testnet) const tips = await tatum.rpc.getTips() await tatum.destroy() expect(tips).toBeDefined() }) it('getReceipts', async () => { - const tatum = await getIotaRpc() + const tatum = await getIotaRpc(testnet) const receipts = await tatum.rpc.getAllReceipts() await tatum.destroy() expect(receipts).toBeDefined() diff --git a/src/service/rpc/evm/AbstractTronRpc.ts b/src/service/rpc/evm/AbstractTronRpc.ts index 591a693b6..3976d2142 100644 --- a/src/service/rpc/evm/AbstractTronRpc.ts +++ b/src/service/rpc/evm/AbstractTronRpc.ts @@ -12,7 +12,7 @@ import { FreezeAccountOptions, GetCanWithdrawUnfreezeAmountOptions, JsonRpcCall, - JsonRpcResponse, + JsonRpcResponse, QueryParams, TransferAssetIssueByAccountOptions, TriggerConstantContractOptions, TriggerSmartContractOptions, @@ -30,10 +30,12 @@ import { import { PostI } from '../../../dto/PostI' import { Utils } from '../../../util' import { AbstractEvmRpc } from './AbstractEvmRpc' +import { GetI } from '../../../dto/GetI' @Service() export abstract class AbstractTronRpc extends AbstractEvmRpc implements TronRpcSuite { protected abstract post(post: PostI): Promise + protected abstract get(get: GetI): Promise abstract destroy(): void abstract getRpcNodeUrl(): string @@ -61,6 +63,16 @@ export abstract class AbstractTronRpc extends AbstractEvmRpc implements TronRpcS return this.post(post) } + private async sendGet({ path, queryParams }: { path: string; queryParams?: QueryParams }): Promise { + return this.get({ + path: Utils.addQueryParams({ + basePath: path, + strategy: Utils.camelToDashCase, + queryParams: queryParams, + }), + }) + } + accountPermissionUpdate( ownerAddress: string, actives: TronPermission[], @@ -351,7 +363,7 @@ export abstract class AbstractTronRpc extends AbstractEvmRpc implements TronRpcS } getBurnTRX(): Promise { - return this.sendPost({ + return this.sendGet({ path: '/wallet/getburntrx', }) } @@ -378,7 +390,7 @@ export abstract class AbstractTronRpc extends AbstractEvmRpc implements TronRpcS } getChainParameters(): Promise { - return this.sendPost({ + return this.sendGet({ path: '/wallet/getchainparameters', }) } @@ -426,13 +438,13 @@ export abstract class AbstractTronRpc extends AbstractEvmRpc implements TronRpcS } getEnergyPrices(): Promise { - return this.sendPost({ + return this.sendGet({ path: '/wallet/getenergyprices', }) } getNodeInfo(): Promise { - return this.sendPost({ + return this.sendGet({ path: '/wallet/getnodeinfo', }) } @@ -472,7 +484,7 @@ export abstract class AbstractTronRpc extends AbstractEvmRpc implements TronRpcS } listNodes(): Promise { - return this.sendPost({ + return this.sendGet({ path: '/wallet/listnodes', }) } diff --git a/src/service/rpc/evm/TronLoadBalancerRpc.ts b/src/service/rpc/evm/TronLoadBalancerRpc.ts index e21119709..d726a8b6f 100644 --- a/src/service/rpc/evm/TronLoadBalancerRpc.ts +++ b/src/service/rpc/evm/TronLoadBalancerRpc.ts @@ -5,8 +5,9 @@ import { PostI } from '../../../dto/PostI' import { Logger } from '../../../service/logger/logger.types' import { LOGGER, Utils } from '../../../util' // Need to import like this to keep browser working -import { LoadBalancer } from '../generic/LoadBalancer' import { AbstractTronRpc } from './AbstractTronRpc' +import { GetI } from '../../../dto/GetI' +import { TronLoadBalancer } from '../generic/LoadBalancer' @Service({ factory: (data: { id: string }) => { @@ -15,12 +16,12 @@ import { AbstractTronRpc } from './AbstractTronRpc' transient: true, }) export class TronLoadBalancerRpc extends AbstractTronRpc implements EvmBasedRpcSuite { - protected readonly loadBalancer: LoadBalancer + protected readonly loadBalancer: TronLoadBalancer protected readonly logger: Logger constructor(id: string) { super() - this.loadBalancer = Container.of(id).get(LoadBalancer) + this.loadBalancer = Container.of(id).get(TronLoadBalancer) this.logger = Container.of(id).get(LOGGER) } @@ -29,6 +30,7 @@ export class TronLoadBalancerRpc extends AbstractTronRpc implements EvmBasedRpcS return (await this.loadBalancer.rawRpcCall(preparedCall)) as T } + async rawRpcCall(body: JsonRpcCall): Promise> { return this.loadBalancer.rawRpcCall(body) } @@ -45,6 +47,10 @@ export class TronLoadBalancerRpc extends AbstractTronRpc implements EvmBasedRpcS return this.loadBalancer.post(post) } + protected get(get: GetI): Promise { + return this.loadBalancer.get(get) + } + getRpcNodeUrl(): string { return this.loadBalancer.getActiveNormalUrlWithFallback().url } diff --git a/src/service/rpc/evm/TronRpc.ts b/src/service/rpc/evm/TronRpc.ts index f56714d5c..07d5f7c65 100644 --- a/src/service/rpc/evm/TronRpc.ts +++ b/src/service/rpc/evm/TronRpc.ts @@ -8,6 +8,7 @@ import { CONFIG, Constant, LOGGER, Utils } from '../../../util' import { TatumConfig } from '../../tatum' import { GenericRpc } from '../generic/GenericRpc' import { AbstractTronRpc } from './AbstractTronRpc' +import { GetI } from '../../../dto/GetI' @Service({ factory: (data: { id: string }) => { @@ -49,6 +50,12 @@ export class TronRpc extends AbstractTronRpc { }) } + protected get(get: GetI): Promise { + return this.connector.get({ + basePath: `${Constant.TRON_SHASTA_BASE_URL.BASE}${get.path}`, + }) + } + destroy(): void { // do nothing } diff --git a/src/service/rpc/generic/LoadBalancer.ts b/src/service/rpc/generic/LoadBalancer.ts index 00c2e7137..d23d562c9 100644 --- a/src/service/rpc/generic/LoadBalancer.ts +++ b/src/service/rpc/generic/LoadBalancer.ts @@ -543,69 +543,75 @@ export class LoadBalancer implements AbstractRpcInterface { } } - async post({ path, body, prefix }: PostI): Promise { + modifyNodeUrl(url: string) { + return url + } + + private getUrlForHttpMethod(prefix?: string) { const { url, type } = this.getActiveNormalUrlWithFallback() - const basePath = prefix ? `${url}${prefix}` : url + const modifiedUrl = this.modifyNodeUrl(url) + return { url: prefix ? `${modifiedUrl}${prefix}` : modifiedUrl, type } + } + + async post({ path, body, prefix }: PostI): Promise { + const { url, type } = this.getUrlForHttpMethod(prefix) try { - return await this.connector.post({ basePath, path, body }) + return await this.connector.post({ basePath: url, path, body }) } catch (e) { await this.handleFailedRpcCall({ rpcCall: { path, body }, e, nodeType: type, requestType: RequestType.POST, - url: basePath, + url, }) return await this.post({ path, body, prefix }) } } async put({ path, body, prefix }: PostI): Promise { - const { url, type } = this.getActiveNormalUrlWithFallback() - const basePath = prefix ? `${url}${prefix}` : url + const { url, type } = this.getUrlForHttpMethod(prefix) try { - return await this.connector.put({ basePath, path, body }) + return await this.connector.put({ basePath: url, path, body }) } catch (e) { await this.handleFailedRpcCall({ rpcCall: { path, body }, e, nodeType: type, requestType: RequestType.PUT, - url: basePath, + url, }) return await this.put({ path, body, prefix }) } } async delete({ path, prefix }: GetI): Promise { - const { url, type } = this.getActiveNormalUrlWithFallback() - const basePath = prefix ? `${url}${prefix}` : url + const { url, type } = this.getUrlForHttpMethod(prefix) try { - return await this.connector.delete({ basePath, path }) + return await this.connector.delete({ basePath: url, path }) } catch (e) { await this.handleFailedRpcCall({ rpcCall: { path }, e, nodeType: type, requestType: RequestType.DELETE, - url: basePath, + url, }) return await this.delete({ path, prefix }) } } async get({ path, prefix }: GetI): Promise { - const { url, type } = this.getActiveNormalUrlWithFallback() - const basePath = prefix ? `${url}${prefix}` : url + const { url, type } = this.getUrlForHttpMethod(prefix) try { - return await this.connector.get({ basePath, path }) + return await this.connector.get({ basePath: url, path }) } catch (e) { await this.handleFailedRpcCall({ rpcCall: { path }, e, nodeType: type, requestType: RequestType.GET, - url: basePath, + url, }) return await this.get({ path, prefix }) } @@ -615,3 +621,20 @@ export class LoadBalancer implements AbstractRpcInterface { return this.getActiveNormalUrlWithFallback().url } } + +@Service({ + factory: (data: { id: string }) => { + return new TronLoadBalancer(data.id) + }, + transient: true, +}) +export class TronLoadBalancer extends LoadBalancer { + constructor(id: string) { + super(id) + } + + // remove jsonrpc from end of the url + modifyNodeUrl(url: string): string { + return url.replace(/\/jsonrpc$/, '') + } +} diff --git a/src/service/rpc/other/AbstractEosRpc.ts b/src/service/rpc/other/AbstractEosRpc.ts index 7a2bdda85..1b85dbbbe 100644 --- a/src/service/rpc/other/AbstractEosRpc.ts +++ b/src/service/rpc/other/AbstractEosRpc.ts @@ -50,98 +50,98 @@ export abstract class AbstractEosRpc implements EosRpcSuite { } abiBinToJson(body: AbiBinToJson): Promise { - return this.sendPost({ path: 'abi_bin_to_json', body }) + return this.sendPost({ path: '/abi_bin_to_json', body }) } abiJsonToBin(body: AbiJsonToBin): Promise { - return this.sendPost({ path: 'abi_json_to_bin', body }) + return this.sendPost({ path: '/abi_json_to_bin', body }) } getAbi(body: AccountName): Promise { - return this.sendPost({ path: 'get_abi', body }) + return this.sendPost({ path: '/get_abi', body }) } getAccount(body: AccountName): Promise { - return this.sendPost({ path: 'get_account', body }) + return this.sendPost({ path: '/get_account', body }) } getAccountsByAuthorizers(body: GetAccountByAuthorizers): Promise { - return this.sendPost({ path: 'get_accounts_by_authorizers', body }) + return this.sendPost({ path: '/get_accounts_by_authorizers', body }) } getActivatedProtocolFeatures(body: GetActivatedProtocolFeatures): Promise { - return this.sendPost({ path: 'get_activated_protocol_features', body }) + return this.sendPost({ path: '/get_activated_protocol_features', body }) } getBlock(body: BlockNumOrId): Promise { - return this.sendPost({ path: 'get_block', body }) + return this.sendPost({ path: '/get_block', body }) } getBlockHeaderState(body: BlockNumOrId): Promise { - return this.sendPost({ path: 'get_block_header_state', body }) + return this.sendPost({ path: '/get_block_header_state', body }) } getBlockInfo(body: BlockNum): Promise { - return this.sendPost({ path: 'get_block_info', body }) + return this.sendPost({ path: '/get_block_info', body }) } getCode(body: GetCode): Promise { - return this.sendPost({ path: 'get_code', body }) + return this.sendPost({ path: '/get_code', body }) } getCurrencyBalance(body: GetCurrencyBalance): Promise { - return this.sendPost({ path: 'get_currency_balance', body }) + return this.sendPost({ path: '/get_currency_balance', body }) } getCurrencyStats(body: GetCurrencyStats): Promise { - return this.sendPost({ path: 'get_currency_stats', body }) + return this.sendPost({ path: '/get_currency_stats', body }) } getInfo(): Promise { - return this.sendPost({ path: 'get_info' }) + return this.sendPost({ path: '/get_info' }) } getKvTableRows(body: GetKVTableRows): Promise { - return this.sendPost({ path: 'get_kv_table_rows', body }) + return this.sendPost({ path: '/get_kv_table_rows', body }) } getProducers(body: GetProducers): Promise { - return this.sendPost({ path: 'get_producers', body }) + return this.sendPost({ path: '/get_producers', body }) } getRawAbi(body: AccountName): Promise { - return this.sendPost({ path: 'get_raw_abi', body }) + return this.sendPost({ path: '/get_raw_abi', body }) } getRawCodeAndAbi(body: AccountName): Promise { - return this.sendPost({ path: 'get_raw_code_and_abi', body }) + return this.sendPost({ path: '/get_raw_code_and_abi', body }) } getRequiredKeys(body: GetRequiredKeys): Promise { - return this.sendPost({ path: 'get_required_keys', body }) + return this.sendPost({ path: '/get_required_keys', body }) } getScheduledTransaction(body: GetProducers): Promise { - return this.sendPost({ path: 'get_scheduled_transaction', body }) + return this.sendPost({ path: '/get_scheduled_transaction', body }) } getTableByScope(body: GetTableByScope): Promise { - return this.sendPost({ path: 'get_table_by_scope', body }) + return this.sendPost({ path: '/get_table_by_scope', body }) } getTableRows(body: GetTableRows): Promise { - return this.sendPost({ path: 'get_table_rows', body }) + return this.sendPost({ path: '/get_table_rows', body }) } pushTransaction(body: PushTransaction): Promise { - return this.sendPost({ path: 'push_transaction', body }) + return this.sendPost({ path: '/push_transaction', body }) } pushTransactions(body: Transaction[]): Promise { - return this.sendPost({ path: 'push_transactions', body }) + return this.sendPost({ path: '/push_transactions', body }) } sendTransaction(body: PushTransaction): Promise { - return this.sendPost({ path: 'send_transaction', body }) + return this.sendPost({ path: '/send_transaction', body }) } } diff --git a/src/service/rpc/other/AbstractKadenaRpc.ts b/src/service/rpc/other/AbstractKadenaRpc.ts index 5b388dcbe..eb6bc55fc 100644 --- a/src/service/rpc/other/AbstractKadenaRpc.ts +++ b/src/service/rpc/other/AbstractKadenaRpc.ts @@ -95,18 +95,18 @@ export abstract class AbstractKadenaRpc implements KadenaRpcInterface { getCurrentCut(params: GetCurrentCutParams): Promise { const { network, ...rest } = params - return this.sendGet({ path: `cut`, queryParams: rest, network }) + return this.sendGet({ path: `/cut`, queryParams: rest, network }) } publishCut(params: Cut): Promise { const { network, ...rest } = params - return this.sendPut({ path: 'cut', body: rest, network }) + return this.sendPut({ path: '/cut', body: rest, network }) } getBlockHashes(params: GetBlockParams): Promise { const { network, query } = params return this.sendGet({ - path: `hash`, + path: `/hash`, queryParams: query, network, }) @@ -115,7 +115,7 @@ export abstract class AbstractKadenaRpc implements KadenaRpcInterface { getBlockHashBranches(params: GetBlockParamsLowerUpper): Promise { const { network, query, ...rest } = params return this.sendPost({ - path: `hash/branch`, + path: `/hash/branch`, queryParams: query, body: rest, network, @@ -125,7 +125,7 @@ export abstract class AbstractKadenaRpc implements KadenaRpcInterface { getBlock(params: GetBlockParams): Promise { const { network, query } = params return this.sendGet({ - path: `block`, + path: `/block`, queryParams: query, network, }) @@ -134,7 +134,7 @@ export abstract class AbstractKadenaRpc implements KadenaRpcInterface { getBlockBranches(params: GetBlockParamsLowerUpper): Promise { const { network, query, ...rest } = params return this.sendPost({ - path: `block/branch`, + path: `/block/branch`, queryParams: query, body: rest, }) @@ -143,7 +143,7 @@ export abstract class AbstractKadenaRpc implements KadenaRpcInterface { getBlockHeaders(params: GetBlockParams): Promise { const { network, query } = params return this.sendGet({ - path: `header`, + path: `/header`, queryParams: query, network, }) @@ -152,7 +152,7 @@ export abstract class AbstractKadenaRpc implements KadenaRpcInterface { getBlockHeaderByHash(params: GetBlockHeaderByHashParams): Promise { const { network, blockHash } = params return this.sendGet({ - path: `header/${blockHash}`, + path: `/header/${blockHash}`, network, }) } @@ -160,7 +160,7 @@ export abstract class AbstractKadenaRpc implements KadenaRpcInterface { getBlockHeaderBranches(params: GetBlockParamsLowerUpper): Promise { const { network, query, ...rest } = params return this.sendPost({ - path: `header/branch`, + path: `/header/branch`, queryParams: query, body: rest, network, @@ -170,7 +170,7 @@ export abstract class AbstractKadenaRpc implements KadenaRpcInterface { getPayloadByHash(params: GetPayloadByHashParams): Promise { const { network, payloadHash, height } = params return this.sendGet({ - path: `payload/${payloadHash}`, + path: `/payload/${payloadHash}`, queryParams: { height }, network, }) @@ -179,7 +179,7 @@ export abstract class AbstractKadenaRpc implements KadenaRpcInterface { getBatchOfBlockPayload(params: PayloadRequest): Promise { const { network, body } = params return this.sendPost({ - path: `payload/batch`, + path: `/payload/batch`, body: body, network, }) @@ -188,7 +188,7 @@ export abstract class AbstractKadenaRpc implements KadenaRpcInterface { getBlockPayloadWithOutputs(params: GetBlockPayloadWithOutputsParams): Promise { const { network, payloadHash, height } = params return this.sendGet({ - path: `payload/${payloadHash}/outputs`, + path: `/payload/${payloadHash}/outputs`, queryParams: { height }, network, }) @@ -197,7 +197,7 @@ export abstract class AbstractKadenaRpc implements KadenaRpcInterface { getBatchBlockPayloadWithOutputs(params: PayloadRequest): Promise> { const { network, body } = params return this.sendPost({ - path: `payload/outputs/batch`, + path: `/payload/outputs/batch`, body, network, }) @@ -206,7 +206,7 @@ export abstract class AbstractKadenaRpc implements KadenaRpcInterface { getPendingTransactions(params: GetPendingTransactionsParams): Promise { const { network, ...rest } = params return this.sendGet({ - path: `mempool/getPending`, + path: `/mempool/getPending`, queryParams: rest, network, }) @@ -215,7 +215,7 @@ export abstract class AbstractKadenaRpc implements KadenaRpcInterface { checkPendingTransactionsInMempool(params: MempoolTransactionsParams): Promise { const { network, headers } = params return this.sendPost({ - path: `mempool/member`, + path: `/mempool/member`, body: headers, network, }) @@ -226,7 +226,7 @@ export abstract class AbstractKadenaRpc implements KadenaRpcInterface { ): Promise> { const { network, headers } = params return this.sendPost({ - path: `mempool/lookup`, + path: `/mempool/lookup`, body: headers, network, }) @@ -235,7 +235,7 @@ export abstract class AbstractKadenaRpc implements KadenaRpcInterface { insertTransactionsIntoMempool(params: InsertTransactionIntoMempoolParams): Promise { const { network, body } = params return this.sendPut({ - path: `mempool/insert`, + path: `/mempool/insert`, body, network, }) @@ -243,13 +243,13 @@ export abstract class AbstractKadenaRpc implements KadenaRpcInterface { checkNodeHealth(): Promise { return this.sendGet({ - path: 'health', + path: '/health', }) } getNodeInfo(): Promise { return this.sendGet({ - path: 'info', + path: '/info', }) } @@ -258,7 +258,7 @@ export abstract class AbstractKadenaRpc implements KadenaRpcInterface { ): Promise<{ items: Peer[]; page: BlockHashesPage }> { const { network, next, limit } = params return this.sendGet({ - path: 'cut/peer', + path: '/cut/peer', queryParams: { limit, next, @@ -269,7 +269,7 @@ export abstract class AbstractKadenaRpc implements KadenaRpcInterface { putCutNetworkPeerInfo(peerData: Peer): Promise { return this.sendPut({ - path: 'cut/peer', + path: '/cut/peer', body: peerData, }) } diff --git a/src/service/rpc/other/AbstractStellarRpc.ts b/src/service/rpc/other/AbstractStellarRpc.ts index 36b3cdbc2..602e4bd28 100644 --- a/src/service/rpc/other/AbstractStellarRpc.ts +++ b/src/service/rpc/other/AbstractStellarRpc.ts @@ -127,153 +127,153 @@ export abstract class AbstractStellarRpc implements StellarRpcSuite { } getAccounts(params?: GetAccountsParams): Promise { - return this.sendGet({ path: 'accounts', queryParams: params as QueryParams }) + return this.sendGet({ path: '/accounts', queryParams: params as QueryParams }) } getAccount(params: GetAccountParams): Promise { - return this.sendGet({ path: `accounts/${params.accountId}` }) + return this.sendGet({ path: `/accounts/${params.accountId}` }) } getAccountTransactions(params: GetAccountTransactionsParams): Promise { const { accountId, ...rest } = params - return this.sendGet({ path: `accounts/${accountId}/transactions`, queryParams: rest }) + return this.sendGet({ path: `/accounts/${accountId}/transactions`, queryParams: rest }) } getAccountOperations(params: GetOperationsByAccountIdParams): Promise { const { accountId, ...rest } = params - return this.sendGet({ path: `accounts/${accountId}/operations`, queryParams: rest }) + return this.sendGet({ path: `/accounts/${accountId}/operations`, queryParams: rest }) } getAccountPayments(params: GetAccountPaymentsParams): Promise { const { accountId, ...rest } = params - return this.sendGet({ path: `accounts/${accountId}/payments`, queryParams: rest }) + return this.sendGet({ path: `/accounts/${accountId}/payments`, queryParams: rest }) } getAccountEffects(params: GetAccountEffectsParams): Promise { const { accountId, ...rest } = params - return this.sendGet({ path: `accounts/${accountId}/effects`, queryParams: rest }) + return this.sendGet({ path: `/accounts/${accountId}/effects`, queryParams: rest }) } getAccountOffers(params: GetOffersByAccountIdParams): Promise { const { accountId, ...rest } = params - return this.sendGet({ path: `accounts/${accountId}/offers`, queryParams: rest }) + return this.sendGet({ path: `/accounts/${accountId}/offers`, queryParams: rest }) } getAccountTrades(params: GetAccountTradesParams): Promise { const { accountId, ...rest } = params - return this.sendGet({ path: `accounts/${accountId}/trades`, queryParams: rest }) + return this.sendGet({ path: `/accounts/${accountId}/trades`, queryParams: rest }) } getAccountData(params: GetAccountDataParams): Promise<{ value: string }> { const { accountId, ...rest } = params - return this.sendGet({ path: `accounts/${accountId}/data/${rest.key}` }) + return this.sendGet({ path: `/accounts/${accountId}/data/${rest.key}` }) } getAssets(params?: GetAssetsParams): Promise { - return this.sendGet({ path: 'assets', queryParams: params as QueryParams }) + return this.sendGet({ path: '/assets', queryParams: params as QueryParams }) } getClaimableBalances(params?: GetClaimableBalancesParams): Promise { - return this.sendGet({ path: 'claimable_balances', queryParams: params as QueryParams }) + return this.sendGet({ path: '/claimable_balances', queryParams: params as QueryParams }) } getClaimableBalance(params: GetClaimableBalanceParams): Promise { - return this.sendGet({ path: `claimable_balances/${params.claimableBalanceId}` }) + return this.sendGet({ path: `/claimable_balances/${params.claimableBalanceId}` }) } getClaimableTransactions(params: GetClaimableTransactionsParams): Promise { const { claimableBalanceId, ...rest } = params - return this.sendGet({ path: `claimable_balances/${claimableBalanceId}/transactions`, queryParams: rest }) + return this.sendGet({ path: `/claimable_balances/${claimableBalanceId}/transactions`, queryParams: rest }) } getClaimableOperations(params: GetClaimableOperationsParams): Promise { const { claimableBalanceId, ...rest } = params - return this.sendGet({ path: `claimable_balances/${claimableBalanceId}/operations`, queryParams: rest }) + return this.sendGet({ path: `/claimable_balances/${claimableBalanceId}/operations`, queryParams: rest }) } getEffects(params?: GetEffectsParams): Promise { - return this.sendGet({ path: 'effects', queryParams: params as QueryParams }) + return this.sendGet({ path: '/effects', queryParams: params as QueryParams }) } getFeeStats(): Promise { - return this.sendGet({ path: 'fee_stats' }) + return this.sendGet({ path: '/fee_stats' }) } getLiquidityPools(params?: GetLiquidityPoolsParams): Promise { - return this.sendGet({ path: 'liquidity_pools', queryParams: params as QueryParams }) + return this.sendGet({ path: '/liquidity_pools', queryParams: params as QueryParams }) } getLiquidityPool(params: GetLiquidityPoolParams): Promise { const { liquidityPoolId, ...rest } = params - return this.sendGet({ path: `liquidity_pools/${liquidityPoolId}`, queryParams: rest }) + return this.sendGet({ path: `/liquidity_pools/${liquidityPoolId}`, queryParams: rest }) } getLiquidityPoolEffects(params: GetLiquidityPoolEffectsParams): Promise { const { liquidityPoolId, ...rest } = params - return this.sendGet({ path: `liquidity_pools/${liquidityPoolId}/effects`, queryParams: rest }) + return this.sendGet({ path: `/liquidity_pools/${liquidityPoolId}/effects`, queryParams: rest }) } getLiquidityPoolTrades(params: GetLiquidityPoolTradesParams): Promise { const { liquidityPoolId, ...rest } = params - return this.sendGet({ path: `liquidity_pools/${liquidityPoolId}/trades`, queryParams: rest }) + return this.sendGet({ path: `/liquidity_pools/${liquidityPoolId}/trades`, queryParams: rest }) } getLiquidityPoolTransactions(params: GetLiquidityPoolTransactionsParams): Promise { const { liquidityPoolId, ...rest } = params - return this.sendGet({ path: `liquidity_pools/${liquidityPoolId}/transactions`, queryParams: rest }) + return this.sendGet({ path: `/liquidity_pools/${liquidityPoolId}/transactions`, queryParams: rest }) } getLiquidityPoolOperations(params: GetLiquidityPoolOperationsParams): Promise { const { liquidityPoolId, ...rest } = params - return this.sendGet({ path: `liquidity_pools/${liquidityPoolId}/operations`, queryParams: rest }) + return this.sendGet({ path: `/liquidity_pools/${liquidityPoolId}/operations`, queryParams: rest }) } getLedger(params: GetLedgerParams): Promise { const { sequence, ...rest } = params - return this.sendGet({ path: `ledgers/${sequence}`, queryParams: rest }) + return this.sendGet({ path: `/ledgers/${sequence}`, queryParams: rest }) } getLedgerTransactions(params: GetLedgerTransactionsParams): Promise { const { sequence, ...rest } = params - return this.sendGet({ path: `ledgers/${sequence}/transactions`, queryParams: rest }) + return this.sendGet({ path: `/ledgers/${sequence}/transactions`, queryParams: rest }) } getLedgerPayments(params: GetLedgerPaymentsParams): Promise { const { sequence, ...rest } = params - return this.sendGet({ path: `ledgers/${sequence}/payments`, queryParams: rest }) + return this.sendGet({ path: `/ledgers/${sequence}/payments`, queryParams: rest }) } getLedgerOperations(params: GetLedgerOperationsParams): Promise { const { sequence, ...rest } = params - return this.sendGet({ path: `ledgers/${sequence}/operations`, queryParams: rest }) + return this.sendGet({ path: `/ledgers/${sequence}/operations`, queryParams: rest }) } getLedgerEffects(params: GetLedgerEffectsParams): Promise<(Links & Effect)[]> { const { sequence, ...rest } = params - return this.sendGet({ path: `ledgers/${sequence}/effects`, queryParams: rest }) + return this.sendGet({ path: `/ledgers/${sequence}/effects`, queryParams: rest }) } getLedgers(params?: BaseParams): Promise { - return this.sendGet({ path: 'ledgers', queryParams: params as QueryParams }) + return this.sendGet({ path: '/ledgers', queryParams: params as QueryParams }) } getOffers(params?: GetOffersParams): Promise { - return this.sendGet({ path: 'offers', queryParams: params as QueryParams }) + return this.sendGet({ path: '/offers', queryParams: params as QueryParams }) } getOffer(params: GetOfferParams): Promise { const { offerId, ...rest } = params - return this.sendGet({ path: `offers/${offerId}`, queryParams: rest }) + return this.sendGet({ path: `/offers/${offerId}`, queryParams: rest }) } getOfferTrades(params: GetOfferTradesParams): Promise { const { offerId, ...rest } = params - return this.sendGet({ path: `offers/${offerId}/trades`, queryParams: rest }) + return this.sendGet({ path: `/offers/${offerId}/trades`, queryParams: rest }) } getOrderBook(params: GetOrderBookParams): Promise { const { sellingAssetType, ...rest } = params - return this.sendGet({ path: `order_book/${sellingAssetType}`, queryParams: rest as QueryParams }) + return this.sendGet({ path: `/order_book/${sellingAssetType}`, queryParams: rest as QueryParams }) } getTradeAggregations(params: GetTradeAggregationsParams): Promise { @@ -285,51 +285,51 @@ export abstract class AbstractStellarRpc implements StellarRpcSuite { } getTrades(params?: GetTradesParams): Promise { - return this.sendGet({ path: 'trades', queryParams: params as QueryParams }) + return this.sendGet({ path: '/trades', queryParams: params as QueryParams }) } getTransaction(params: GetTransactionParams): Promise { const { transactionHash, ...rest } = params - return this.sendGet({ path: `transactions/${transactionHash}`, queryParams: rest }) + return this.sendGet({ path: `/transactions/${transactionHash}`, queryParams: rest }) } getTransactionOperations(params: GetTransactionOperationsParams): Promise { const { transactionHash, ...rest } = params - return this.sendGet({ path: `transactions/${transactionHash}/operations`, queryParams: rest }) + return this.sendGet({ path: `/transactions/${transactionHash}/operations`, queryParams: rest }) } getTransactionEffects(params: GetTransactionEffectsParams): Promise { const { transactionHash, ...rest } = params - return this.sendGet({ path: `transactions/${transactionHash}/effects`, queryParams: rest }) + return this.sendGet({ path: `/transactions/${transactionHash}/effects`, queryParams: rest }) } getTransactions(params?: GetTransactionsParams): Promise { - return this.sendGet({ path: 'transactions', queryParams: params as QueryParams }) + return this.sendGet({ path: '/transactions', queryParams: params as QueryParams }) } getOperation(params: GetOperationParams): Promise { const { id, ...rest } = params - return this.sendGet({ path: `operations/${id}`, queryParams: rest }) + return this.sendGet({ path: `/operations/${id}`, queryParams: rest }) } getOperationEffects(params: GetOperationEffectsParams): Promise { const { id, ...rest } = params - return this.sendGet({ path: `operations/${id}/effects`, queryParams: rest }) + return this.sendGet({ path: `/operations/${id}/effects`, queryParams: rest }) } getOperations(params?: GetOperationsParams): Promise { - return this.sendGet({ path: 'operations', queryParams: params as QueryParams }) + return this.sendGet({ path: '/operations', queryParams: params as QueryParams }) } getPayments(params?: GetPaymentsParams): Promise { - return this.sendGet({ path: 'payments', queryParams: params as QueryParams }) + return this.sendGet({ path: '/payments', queryParams: params as QueryParams }) } getStrictReceivePaymentPaths(params: GetStrictReceivePaymentPathsParams): Promise { const { sourceAssets, ...rest } = params const sourceAssetsString = sourceAssets?.join(',') return this.sendGet({ - path: `paths/strict-receive`, + path: `/paths/strict-receive`, queryParams: { ...rest, ...(sourceAssetsString && { sourceAssets: sourceAssetsString }), @@ -342,7 +342,7 @@ export abstract class AbstractStellarRpc implements StellarRpcSuite { const destinationAssetsString = destinationAssets?.join(',') const sourceAssetsString = sourceAssets?.join(',') return this.sendGet({ - path: `paths/strict-send`, + path: `/paths/strict-send`, queryParams: { ...rest, ...(destinationAssetsString && { destinationAssets: destinationAssetsString }), @@ -352,6 +352,6 @@ export abstract class AbstractStellarRpc implements StellarRpcSuite { } submitTransaction(params: SubmitTransactionParams): Promise { - return this.sendPost({ path: 'transactions', queryParams: params as unknown as QueryParams }) + return this.sendPost({ path: '/transactions', queryParams: params as unknown as QueryParams }) } } diff --git a/src/service/rpc/other/IotaRpc.ts b/src/service/rpc/other/IotaRpc.ts new file mode 100644 index 000000000..2d003bb55 --- /dev/null +++ b/src/service/rpc/other/IotaRpc.ts @@ -0,0 +1,51 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Container, Service } from 'typedi' +// Need to import like this to keep browser working +import { GetI } from 'src/dto/GetI' +import { PostI } from 'src/dto/PostI' +import { IotaRpcSuite } from '../../../dto/rpc/IotaRpcSuite' +import { AbstractIotaRpc } from './AbstractIotaRpc' +import { TatumConnector } from '../../../connector' +import { TatumConfig } from '../../tatum' +import { CONFIG, Utils } from '../../../util' + +@Service({ + factory: (data: { id: string }) => { + return new IotaRpc(data.id) + }, + transient: true, +}) +export class IotaRpc extends AbstractIotaRpc implements IotaRpcSuite { + protected readonly connector: TatumConnector + protected readonly config: TatumConfig + + constructor(id: string) { + super() + this.connector = Container.of(id).get(TatumConnector) + this.config = Container.of(id).get(CONFIG) + } + + getRpcNodeUrl(): string { + return Utils.getV3RpcUrl(this.config) + } + + protected get(get: GetI): Promise { + const basePath = Utils.getV3RpcUrl(this.config) + return this.connector.get({ ...get, basePath }) + } + + protected post(post: PostI): Promise { + const basePath = Utils.getV3RpcUrl(this.config) + return this.connector.post({ ...post, basePath }) + } + + protected put(put: PostI): Promise { + const basePath = Utils.getV3RpcUrl(this.config) + return this.connector.put({ ...put, basePath }) + } + + protected delete(get: GetI): Promise { + const basePath = Utils.getV3RpcUrl(this.config) + return this.connector.delete({ ...get, basePath }) + } +} diff --git a/src/service/tatum/tatum.other.ts b/src/service/tatum/tatum.other.ts index 84ccd4c1b..a8fa7f031 100644 --- a/src/service/tatum/tatum.other.ts +++ b/src/service/tatum/tatum.other.ts @@ -131,6 +131,15 @@ export class Rostrum extends BaseOther { } } +export class BitcoinElectrs extends BaseOther { + rpc: RostrumRpcInterface + + constructor(id: string) { + super(id) + this.rpc = Utils.getRpc(id, Container.of(id).get(CONFIG)) + } +} + export class AlgorandAlgod extends BaseOther { rpc: AlgorandAlgodRpcSuite diff --git a/src/service/tatum/tatum.ts b/src/service/tatum/tatum.ts index c4392da9d..1800ca9ed 100644 --- a/src/service/tatum/tatum.ts +++ b/src/service/tatum/tatum.ts @@ -1,5 +1,5 @@ import { Container, Service } from 'typedi' -import { isLoadBalancerNetwork } from '../../dto' +import { isLoadBalancerNetwork, Network } from '../../dto' import { CONFIG, Constant, EnvUtils, LOGGER, LoggerUtils, Utils } from '../../util' import { ExtensionConstructor, @@ -8,7 +8,7 @@ import { TatumSdkContainer, TatumSdkExtension, } from '../extensions' -import { LoadBalancer } from '../rpc/generic/LoadBalancer' +import { LoadBalancer, TronLoadBalancer } from '../rpc/generic/LoadBalancer' import { MetaMask, WalletProvider } from '../walletProvider' import { ApiVersion, TatumConfig } from './tatum.dto' @@ -48,7 +48,7 @@ export abstract class TatumSdkChain implements ITatumSdkChain { } // calls destroy on load balancer - Container.of(this.id).remove(LoadBalancer) + Container.of(this.id).remove(config.network === Network.TRON ? TronLoadBalancer : LoadBalancer) } private async destroyExtension(extensionConfig: ExtensionConstructorOrConfig, id: string) { @@ -103,7 +103,7 @@ export class TatumSDK { } if (isLoadBalancerNetwork(mergedConfig.network)) { - const loadBalancer = Container.of(id).get(LoadBalancer) + const loadBalancer = Container.of(id).get(mergedConfig.network === Network.TRON ? TronLoadBalancer : LoadBalancer) await loadBalancer.init() } diff --git a/src/util/constant.ts b/src/util/constant.ts index 17bc97e9f..0a23f9e76 100644 --- a/src/util/constant.ts +++ b/src/util/constant.ts @@ -122,6 +122,8 @@ export const Constant = { [Network.KADENA_TESTNET]: 18, [Network.COSMONS_ROSETTA]: 18, [Network.IOTA]: 18, + [Network.IOTA_TESTNET]: 18, + [Network.BITCOIN_ELECTRS]: 18 }, CURRENCY_NAMES: { [Network.BITCOIN]: 'BTC', @@ -224,6 +226,8 @@ export const Constant = { [Network.ROSTRUM]: 'BCH', [Network.COSMONS_ROSETTA]: 'ATOM', [Network.IOTA]: 'IOTA', + [Network.IOTA_TESTNET]: 'IOTA', + [Network.BITCOIN_ELECTRS]: 'BTC' }, RPC: { MAINNETS: [ diff --git a/src/util/util.shared.ts b/src/util/util.shared.ts index a902b6746..c5dd33fe3 100644 --- a/src/util/util.shared.ts +++ b/src/util/util.shared.ts @@ -15,7 +15,7 @@ import { isEvmArchiveNonArchiveLoadBalancerNetwork, isEvmBasedNetwork, isEvmLoadBalancerNetwork, - isIotaNetwork, + isIotaLoadBalancerNetwork, isIotaNetwork, isKadenaLoadBalancerNetwork, isNativeEvmLoadBalancerNetwork, isRostrumLoadBalancerNetwork, @@ -50,7 +50,7 @@ import { Base, BinanceSmartChain, Bitcoin, - BitcoinCash, + BitcoinCash, BitcoinElectrs, Bnb, CardanoRosetta, Celo, @@ -116,13 +116,17 @@ import { UtxoLoadBalancerRpcEstimateFee } from '../service/rpc/utxo/UtxoLoadBala import { UtxoRpcEstimateFee } from '../service/rpc/utxo/UtxoRpcEstimateFee' import { Constant } from './constant' import { CONFIG, LOGGER } from './di.tokens' +import { IotaRpc } from '../service/rpc/other/IotaRpc' export const Utils = { getRpc: (id: string, config: TatumConfig): T => { const { network } = config + if (isIotaLoadBalancerNetwork(network)) { + return Container.of(id).get(IotaLoadBalancerRpc) as T + } if (isIotaNetwork(network)) { - return Container.of(id).get(IotaLoadBalancerRpc) as T + return Container.of(id).get(IotaRpc) as T } if (isRostrumLoadBalancerNetwork(network)) { @@ -317,7 +321,7 @@ export const Utils = { isAlgorandIndexerNetwork(network) || isStellarLoadBalancerNetwork(network) || isKadenaLoadBalancerNetwork(network) || - isIotaNetwork(network) + isIotaLoadBalancerNetwork(network) ) { return null } @@ -325,7 +329,7 @@ export const Utils = { throw new Error(`Network ${network} is not supported.`) }, getStatusUrl(network: Network, url: string): string { - if (isIotaNetwork(network)) { + if (isIotaLoadBalancerNetwork(network)) { return `${url}api/core/v2/info` } @@ -382,7 +386,7 @@ export const Utils = { isAlgorandIndexerNetwork(network) || isStellarLoadBalancerNetwork(network) || isKadenaLoadBalancerNetwork(network) || - isIotaNetwork(network) + isIotaLoadBalancerNetwork(network) ) { return 'GET' } @@ -433,7 +437,7 @@ export const Utils = { return new BigNumber((response.result.height as number) || -1).toNumber() } - if (isIotaNetwork(network)) { + if (isIotaLoadBalancerNetwork(network)) { return new BigNumber((response?.status?.latestMilestone?.index as number) || -1).toNumber() } @@ -484,7 +488,7 @@ export const Utils = { return response?.result?.height !== undefined } - if (isIotaNetwork(network)) { + if (isIotaLoadBalancerNetwork(network)) { return response?.status?.latestMilestone?.index !== undefined } @@ -842,7 +846,10 @@ export const Utils = { case Network.ROSTRUM: return new Rostrum(id) as T case Network.IOTA: + case Network.IOTA_TESTNET: return new Iota(id) as T + case Network.BITCOIN_ELECTRS: + return new BitcoinElectrs(id) as T default: return new FullSdk(id) as T } @@ -947,4 +954,5 @@ export const Utils = { return basePath + queryString }, + removeLastSlash: (url: string) => url.replace(/\/$/, ''), }