diff --git a/src/dto/Network.ts b/src/dto/Network.ts index 4cb2a55de..319efca62 100644 --- a/src/dto/Network.ts +++ b/src/dto/Network.ts @@ -41,6 +41,7 @@ export enum Network { PALM = 'palm-mainnet', POLYGON = 'polygon-mainnet', POLKADOT = 'dot-mainnet', + ROSTRUM = 'rostrum-mainnet', RSK = 'rsk-mainnet', SOLANA = 'solana-mainnet', STELLAR = 'stellar-mainnet', @@ -246,6 +247,7 @@ export const ALGORAND_INDEXER_NETWORKS = [Network.ALGORAND_INDEXER, Network.ALGO export const CARDANO_NETWORKS = [Network.CARDANO_ROSETTA, Network.CARDANO_ROSETTA_PREPROD] 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 LOAD_BALANCER_NETWORKS = [ ...UTXO_LOAD_BALANCER_NETWORKS, @@ -263,6 +265,7 @@ export const LOAD_BALANCER_NETWORKS = [ ...CARDANO_NETWORKS, ...STELLAR_LOAD_BALANCER_NETWORKS, ...KADENA_LOAD_BALANCER_NETWORKS, + ...ROSTRUM_LOAD_BALANCER_NETWORKS, ] export const EVM_ARCHIVE_NON_ARCHIVE_LOAD_BALANCER_NETWORKS = [ @@ -331,6 +334,9 @@ export const isXrpLoadBalancerNetwork = (network: Network) => XRP_LOAD_BALANCER_ export const isKadenaLoadBalancerNetwork = (network: Network) => KADENA_LOAD_BALANCER_NETWORKS.includes(network) +export const isRostrumLoadBalancerNetwork = (network: Network) => + ROSTRUM_LOAD_BALANCER_NETWORKS.includes(network) + export const isNativeEvmLoadBalancerNetwork = (network: Network) => NATIVE_EVM_LOAD_BALANCER_NETWORKS.includes(network) @@ -888,4 +894,8 @@ export const NETWORK_METADATA: Record = { testnet: true, chainId: 84532, }, + [Network.ROSTRUM]: { + currency: Currency.BCH, + testnet: false, + }, } diff --git a/src/dto/rpc/RostrumRpcSuite.ts b/src/dto/rpc/RostrumRpcSuite.ts new file mode 100644 index 000000000..fe526d50f --- /dev/null +++ b/src/dto/rpc/RostrumRpcSuite.ts @@ -0,0 +1,808 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { JsonRpcResponse } from '../JsonRpcResponse.dto' + +export interface BlockAddressDecodeResult { + payload: string + type: string + isTokenAware: boolean +} + +export type Filter = 'include_tokens' | 'tokens_only' | 'exclude_token' + +export interface AddressAndFilterParam { + address: string + filter?: Filter +} + +export interface ScriptHashAndFilterParam { + scripthash: string + filter?: Filter +} + +/** + * The expected result format for balance queries. + * Provides both confirmed and unconfirmed balances of the address. + */ +export interface BlockchainGetBalanceResult { + confirmed: number + unconfirmed: number +} + +export interface BlockchainGetFirstUseResult { + block_hash: string + block_height: number + tx_hash: string +} + +export interface BlockchainHistoryResult { + height: number + tx_hash: string + fee?: number +} + +export interface BlockchainMempoolResult { + tx_hash: string + height: number + fee: number +} + +export interface BlockchainAddressToScriptHashResult { + scripthash: string +} + +export interface BlockchainUnspentOutputResult { + height: number + tx_pos: number + tx_hash: string + value: number + outpoint_hash?: string +} + +export interface AddressSubscribeResult { + status: string +} + +export interface AddressUnsubscribeResult { + success: boolean +} + +export interface BlockHeaderParams { + height: number + cp_height?: number +} + +export interface BlockHeaderResult { + branch?: string[] + header: string + root?: string +} + +export interface BlockHeaderVerboseResult { + bits: number + hash: string + height: number + hex: string + mediantime: number + merkleroot: string + nonce: number | string + previousblockhash: string + time: number + version: number + // Fields specific to Nexa or other additional fields + ancestorhash?: string + chainwork?: string + feepoolamt?: number + minerdata?: string + size?: number + txcount?: number + txfilter?: string + utxocommitment?: string +} + +export interface BlockHeadersParams { + start_height: number + count: number + cp_height?: number +} + +export interface BlockHeadersResult { + count: number + hex: string + max: number + root?: string + branch?: string[] +} + +export interface HexHeightResult { + hex: string + height: number +} + +export interface TransactionGetParams { + tx_hash: string + verbose?: boolean +} + +export interface TransactionGetResult { + hex: string +} + +export interface VerboseTransactionResult { + hex: string + txid: string + hash: string + size: number + version: number + locktime: number + vin: Array + vout: Array + blockhash?: string + confirmations?: number + time?: number + blocktime?: number + fee?: number + height?: number +} + +export interface TransactionGetConfirmedBlockhashResult { + block_hash: string + block_height: number +} + +export interface TransactionGetMerkleParams { + tx_hash: string + height?: number +} + +export interface TransactionGetMerkleResult { + block_height: number + merkle: string[] + pos: number +} + +export interface TransactionIdFromPosParams { + height: number + tx_pos: number + merkle?: boolean +} + +export interface TransactionIdFromPosResult { + tx_hash: string + merkle?: string[] +} + +export interface UtxoGetParams { + tx_hash: string + output_index?: number + outpoint_hash?: string +} + +export interface UtxoGetResult { + status: string + height: number + value: number + scripthash: string + scriptpubkey: string + spent?: { + tx_pos?: number + tx_hash?: string + height?: number + } + token_id?: string + token_amount?: number + commitment?: string + token_bitfield?: number + tx_idem?: string + tx_pos?: number + group?: string + token_id_hex?: string + group_quantity?: number + template_scripthash?: string + template_argumenthash?: string +} + +export interface TokenScripthashGetBalanceParams { + scripthash: string + cursor?: string + token?: string +} + +export interface TokenBalance { + [tokenId: string]: number +} + +export interface TokenGetBalanceResult { + confirmed: TokenBalance + unconfirmed: TokenBalance + cursor?: string +} + +export interface TokenScripthashGetHistoryParams { + scripthash: string + cursor?: string + token?: string +} + +export interface TokenAddressGetHistoryParams { + address: string + cursor?: string + token?: string +} + +export interface TokenHistoryTransaction { + height: number + tx_hash: string + fee?: number +} + +export interface TokenGetHistoryResult { + transactions: TokenHistoryTransaction[] + cursor?: string +} + +export interface TokenScripthashGetMempoolParams { + scripthash: string + cursor?: string + token?: string +} + +export interface MempoolTransaction { + height: number + tx_hash: string + fee: number +} + +export interface TokenGetMempoolResult { + transactions: MempoolTransaction[] + cursor?: string +} + +export interface TokenScripthashListUnspentParams { + scripthash: string + cursor?: string + token?: string +} + +export interface UnspentTokenOutput { + tx_pos: number + value: number + tx_hash: string + height: number + token_id?: string + token_amount: number + commitment?: string + token_id_hex?: string + group?: string + outpoint_hash?: string +} + +export interface TokenListUnspentResult { + unspent: UnspentTokenOutput[] + cursor?: string +} + +export interface TokenTransactionGetHistoryParams { + token: string + cursor?: string + commitment?: string +} + +export interface TokenTransactionHistory { + height: number + tx_hash: string +} + +export interface TokenTransactionGetHistoryResult { + history: TokenTransactionHistory[] + cursor?: string +} + +export interface TokenAddressGetBalanceParams { + address: string + cursor?: string + token?: string +} + +export interface TokenGenesisInfoResultBCH { + tx_hash: string + height: number + bcmr?: { + hash: string + uris: string[] + } + crc20?: { + symbol: string + name: string + decimals: number + genesis_out: string + } +} + +export interface TokenGenesisInfoResultNexa { + document_hash?: string + document_url?: string + decimal_places?: number + height: number + name?: string + ticker?: string + group?: string + token_id_hex?: string + txid: string + txidem?: string + op_return?: string +} + +export interface TokenNftListParams { + token: string + cursor?: string +} + +export interface NftBCH { + token_id: string + commitment: string +} + +export interface NftNexa { + group: string + token_id_hex: string +} + +export interface TokenNftListResultBCH { + nft: NftBCH[] + cursor?: string +} + +export interface TokenNftListResultNexa { + nft: NftNexa[] + cursor?: string +} + +export interface ServerVersionParams { + client_name: string + protocol_version: string +} + +export interface ServerVersionResult { + server_version: string + protocol_version: string +} + +export interface CashAccountQueryNameParams { + name: string + height: number +} + +export interface CashAccountQueryResult { + blockhash: string + height: number + tx: string +} + +export interface RostrumRpcInterface { + /** + * Decodes a Bitcoin Cash or Nexa address to its raw payload. This function is designed + * to be useful for clients that need to understand the encoded contents of an address + * but do not have the necessary local libraries for decoding. + * + * @param address The address as a Cash Address string (with or without the prefix). + * Some server implementations might also support Legacy (base58) + * addresses but are not required to do so by this specification. + * @returns A promise that resolves to the decoded payload of the address, including + * the type of data it represents and its token awareness status. + */ + blockchainAddressDecode: (address: string) => Promise> + + /** + * Retrieves the confirmed and unconfirmed balances of a specified Bitcoin Cash address. + * The function allows for optional filtering of the balance by UTXO type. + * + * @param params The parameters for the balance query, including the address and an optional filter. + * @returns A promise that resolves to the balance of the address, separated into confirmed and unconfirmed amounts. + */ + blockchainAddressGetBalance: ( + params: AddressAndFilterParam, + ) => Promise> + + /** + * Fetches the first occurrence of usage of an address or scripthash on the blockchain. + * This method helps in identifying the initial transaction that involved the address or scripthash. + * + * @param params Parameters including the address or scripthash and an optional filter. + * @returns A promise resolving to the first usage details, including block hash, block height, and transaction hash. + */ + blockchainAddressGetFirstUse: ( + params: AddressAndFilterParam, + ) => Promise> + + /** + * Fetches the confirmed and unconfirmed transaction history of a Bitcoin Cash address or a script hash. + * The method consolidates transaction information, including block height and transaction hashes, optionally filtering by UTXO type. + * + * @param params Parameters including the address or scripthash and an optional filter. + * @returns A promise resolving to a list of transactions, each with details such as height, transaction hash, and optionally fee for mempool transactions. + */ + blockchainAddressGetHistory: ( + params: AddressAndFilterParam, + ) => Promise> + + /** + * Fetches the unconfirmed transactions of a Bitcoin Cash address or a script hash from the mempool. + * This method returns details of each transaction, including its hash, height, and fee, optionally filtering by UTXO type. + * + * @param params Parameters including the address or scripthash and an optional filter. + * @returns A promise resolving to a list of mempool transactions, each with details such as transaction hash, height, and fee. + */ + blockchainAddressGetMempool: ( + params: AddressAndFilterParam, + ) => Promise> + + /** + * Converts a Bitcoin Cash or Nexa address to its corresponding script hash. + * This method is useful for clients that prefer working with script hashes but lack the local libraries for generation. + * + * @param params An object containing the address to be converted. + * @returns A promise resolving to the script hash corresponding to the given address. + */ + blockchainAddressToScriptHash: ( + address: string, + ) => Promise> + + /** + * Fetches an ordered list of unspent transaction outputs (UTXOs) sent to a Bitcoin Cash address or a script hash. + * The query can optionally be filtered to include all UTXOs, only token UTXOs, or exclude token UTXOs. + * + * @param params Parameters including the address or script hash and an optional filter. + * @returns A promise resolving to a JSON RPC response wrapping a list of UTXOs. + */ + blockchainListUnspent: ( + params: AddressAndFilterParam, + ) => Promise> + + /** + * Subscribes to notifications for changes in the status of a specified Bitcoin Cash or Nexa address. + * Returns a JsonRpcResponse wrapper containing the initial status of the address as the result. + * + * @param address The address to subscribe to. + * @returns A promise resolving to a JsonRpcResponse with the initial status of the subscribed address. + */ + blockchainAddressSubscribe: (address: string) => Promise> + + /** + * Unsubscribes from notifications for a specified Bitcoin Cash or Nexa address. + * This prevents the client from receiving future notifications if the status of the address changes. + * The method returns true if the address was successfully unsubscribed from, otherwise false. + * A false return value may indicate that the address was not previously subscribed or the subscription was dropped. + * + * @param address The address to unsubscribe from. + * @returns A promise that resolves to a JsonRpcResponse wrapping the success status of the unsubscription attempt. + */ + blockchainAddressUnsubscribe: (address: string) => Promise> + + /** + * Fetches the block hash of a block by its height. + * + * @param heightOrHash The height or hash of the block to fetch. + * @returns A promise resolving to the hash of the block at the specified height. + */ + blockchainBlockGet: (heightOrHash: string | number) => Promise> + + /** + * Fetches the block header of a block by its height. + * + * @param params The parameters for the block header query, including the height of the block. + * @returns A promise resolving to the header of the block at the specified height. + */ + blockchainBlockHeader: (params: BlockHeaderParams) => Promise> + + /** + * Fetches the block header of a block by its height. + * + * @param heightOrHash The height or hash of the block to fetch. + * @returns A promise resolving to the header of the block at the specified height. + */ + blockchainBlockHeaderVerbose: ( + heightOrHash: string | number, + ) => Promise> + + /** + * Fetches a range of block headers from a specified starting height. + * + * @param params The parameters for the block headers query, including the starting height and the count of headers to fetch. + * @returns A promise resolving to the requested block headers. + */ + blockchainBlockHeaders: (params: BlockHeadersParams) => Promise> + + /** + * Fetches the estimated fee for a transaction to be included in a block within a specified number of blocks. + * + * @param blocks The number of blocks in which the transaction should be included. + * @returns A promise resolving to the estimated fee for the transaction. + */ + blockchainEstimateFee: (blocks: number) => Promise> + + /** + * Fetches the latest block height and hash. + * + * @returns A promise resolving to the latest block height and hash. + */ + blockchainHeadersSubscribe: () => Promise> + + /** + * Fetches the latest block height and hash. + * + * @returns A promise resolving to the latest block height and hash. + */ + blockchainHeadersTip: () => Promise> + + /** + * Fetches the current relay fee for the blockchain. + * + * @returns A promise resolving to the current relay fee. + */ + blockchainRelayFee: () => Promise> + + /** + * Fetches the confirmed and unconfirmed balances of a specified Bitcoin Cash script hash. + * The function allows for optional filtering of the balance by UTXO type. + * + * @param params The parameters for the balance query, including the script hash and an optional filter. + * @returns A promise that resolves to the balance of the script hash, separated into confirmed and unconfirmed amounts. + */ + blockchainScriptHashGetBalance: ( + params: ScriptHashAndFilterParam, + ) => Promise> + + /** + * Fetches the first occurrence of usage of a script hash on the blockchain. + * This method helps in identifying the initial transaction that involved the script hash. + * + * @param params Parameters including the script hash and an optional filter. + * @returns A promise resolving to the first usage details, including block hash, block height, and transaction hash. + */ + blockchainScriptHashGetFirstUse: ( + params: ScriptHashAndFilterParam, + ) => Promise> + + /** + * Fetches the confirmed and unconfirmed transaction history of a Bitcoin Cash script hash. + * The method consolidates transaction information, including block height and transaction hashes, optionally filtering by UTXO type. + * + * @param params Parameters including the script hash and an optional filter. + * @returns A promise resolving to a list of transactions, each with details such as height, transaction hash, and optionally fee for mempool transactions. + */ + blockchainScriptHashGetHistory: ( + params: ScriptHashAndFilterParam, + ) => Promise> + + /** + * Fetches the unconfirmed transactions of a Bitcoin Cash script hash from the mempool. + * This method returns details of each transaction, including its hash, height, and fee, optionally filtering by UTXO type. + * + * @param params Parameters including the script hash and an optional filter. + * @returns A promise resolving to a list of mempool transactions, each with details such as transaction hash, height, and fee. + */ + blockchainScriptHashGetMempool: ( + params: ScriptHashAndFilterParam, + ) => Promise> + + /** + * Fetches an ordered list of unspent transaction outputs (UTXOs) sent to a Bitcoin Cash script hash. + * The query can optionally be filtered to include all UTXOs, only token UTXOs, or exclude token UTXOs. + * + * @param params Parameters including the script hash and an optional filter. + * @returns A promise resolving to a JSON RPC response wrapping a list of UTXOs. + */ + blockchainScriptHashListUnspent: ( + params: ScriptHashAndFilterParam, + ) => Promise> + + /** + * Subscribes to notifications for changes in the status of a specified Bitcoin Cash script hash. + * Returns a JsonRpcResponse wrapper containing the initial status of the script hash as the result. + * + * @param scripthash The script hash to subscribe to. + * @returns A promise resolving to a JsonRpcResponse with the initial status of the subscribed script hash. + */ + blockchainScriptHashSubscribe: (scripthash: string) => Promise> + + /** + * Unsubscribes from notifications for a specified Bitcoin Cash script hash. + * This prevents the client from receiving future notifications if the status of the script hash changes. + * The method returns true if the script hash was successfully unsubscribed from, otherwise false. + * A false return value may indicate that the script hash was not previously subscribed or the subscription was dropped. + * + * @param scripthash The script hash to unsubscribe from. + * @returns A promise that resolves to a JsonRpcResponse wrapping the success status of the unsubscription attempt. + */ + blockchainScriptHashUnsubscribe: (scripthash: string) => Promise> + + /** + * Broadcasts a raw transaction to the blockchain network. + * The method returns the transaction hash if the broadcast is successful. + */ + blockchainTransactionBroadcast: (raw_tx: string) => Promise> + + /** + * Fetches a transaction by its hash. + * The method returns the transaction in raw format by default. + * If the verbose flag is set to true, the method returns additional details about the transaction. + * + * @param params The parameters for the transaction query, including the transaction hash and an optional verbose flag. + * @returns A promise resolving to the transaction details. + */ + blockchainTransactionGet: ( + params: TransactionGetParams, + ) => Promise> + + /** + * Fetches the block hash and height of a transaction by its hash. + * + * @param tx_hash The hash of the transaction to fetch the block hash and height for. + * @returns A promise resolving to the block hash and height of the transaction. + */ + blockchainTransactionGetConfirmedBlockhash: ( + tx_hash: string, + ) => Promise> + + /** + * Fetches the Merkle proof of a transaction by its hash. + * The method returns the block height, the Merkle proof, and the position of the transaction in the block. + * + * @param params The parameters for the Merkle proof query, including the transaction hash and an optional block height. + * @returns A promise resolving to the Merkle proof details. + */ + blockchainTransactionGetMerkle: ( + params: TransactionGetMerkleParams, + ) => Promise> + + /** + * Fetches the transaction hash by its position in a block. + * The method returns the transaction hash and optionally the Merkle proof for the transaction. + * + * @param params The parameters for the transaction ID query, including the block height and transaction position. + * @returns A promise resolving to the transaction hash and optionally the Merkle proof. + */ + blockchainTransactionIdFromPos: ( + params: TransactionIdFromPosParams, + ) => Promise> + + /** + * Fetches the unspent transaction output (UTXO) for a transaction output. + * The method returns the status of the UTXO, including its height, value, and script hash. + * + * @param params The parameters for the UTXO query, including the transaction hash and output index. + * @returns A promise resolving to the UTXO details. + */ + blockchainUtxoGet: (params: UtxoGetParams) => Promise> + + /** + * Fetches the confirmed and unconfirmed token balances of a specified Bitcoin Cash script hash. + * The function allows for optional filtering of the balance by token ID. + * + * @param params The parameters for the balance query, including the script hash and an optional token ID. + * @returns A promise that resolves to the balance of the script hash, separated into confirmed and unconfirmed amounts. + */ + tokenScripthashGetBalance: ( + params: TokenScripthashGetBalanceParams, + ) => Promise> + + /** + * Fetches the confirmed and unconfirmed transaction history of a Bitcoin Cash script hash. + * The method consolidates transaction information, including block height and transaction hashes, optionally filtering by token ID. + * + * @param params Parameters including the script hash and an optional token ID. + * @returns A promise resolving to a list of transactions, each with details such as height and transaction hash. + */ + tokenScripthashGetHistory: ( + params: TokenScripthashGetHistoryParams, + ) => Promise> + + /** + * Fetches the unconfirmed transactions of a Bitcoin Cash script hash from the mempool. + * This method returns details of each transaction, including its hash, height, and fee, optionally filtering by token ID. + * + * @param params Parameters including the script hash and an optional token ID. + * @returns A promise resolving to a list of mempool transactions, each with details such as transaction hash, height, and fee. + */ + tokenScripthashGetMempool: ( + params: TokenScripthashGetMempoolParams, + ) => Promise> + + /** + * Fetches an ordered list of unspent transaction outputs (UTXOs) sent to a Bitcoin Cash script hash. + * The query can optionally be filtered to include all UTXOs, only token UTXOs, or exclude token UTXOs. + * + * @param params Parameters including the script hash and an optional token ID. + * @returns A promise resolving to a JSON RPC response wrapping a list of UTXOs. + */ + tokenScripthashListUnspent: ( + params: TokenScripthashListUnspentParams, + ) => Promise> + + /** + * Fetches the transaction history of a token. + * The method consolidates transaction information, including block height and transaction hashes, optionally filtering by commitment. + * + * @param params Parameters including the token ID and an optional commitment. + * @returns A promise resolving to a list of transactions, each with details such as height and transaction hash. + */ + tokenTransactionGetHistory: ( + params: TokenTransactionGetHistoryParams, + ) => Promise> + + /** + * Fetches the confirmed and unconfirmed balances of a specified Bitcoin Cash address. + * The function allows for optional filtering of the balance by token ID. + * + * @param params The parameters for the balance query, including the address and an optional token ID. + * @returns A promise that resolves to the balance of the address, separated into confirmed and unconfirmed amounts. + */ + tokenAddressGetBalance: ( + params: TokenAddressGetBalanceParams, + ) => Promise> + + /** + * Fetches the confirmed and unconfirmed transaction history of a Bitcoin Cash address. + * The method consolidates transaction information, including block height and transaction hashes, optionally filtering by token ID. + * + * @param params Parameters including the address and an optional token ID. + * @returns A promise resolving to a list of transactions, each with details such as height and transaction hash. + */ + tokenAddressGetHistory: ( + params: TokenAddressGetHistoryParams, + ) => Promise> + + /** + * Fetches the unconfirmed transactions of a Bitcoin Cash address from the mempool. + * This method returns details of each transaction, including its hash, height, and fee, optionally filtering by token ID. + * + * @param params Parameters including the address and an optional token ID. + * @returns A promise resolving to a list of mempool transactions, each with details such as transaction hash, height, and fee. + */ + tokenAddressGetMempool: ( + params: TokenAddressGetHistoryParams, + ) => Promise> + + /** + * Fetches an ordered list of unspent transaction outputs (UTXOs) sent to a Bitcoin Cash address. + * The query can optionally be filtered to include all UTXOs, only token UTXOs, or exclude token UTXOs. + * + * @param params Parameters including the address and an optional token ID. + * @returns A promise resolving to a JSON RPC response wrapping a list of UTXOs. + */ + tokenAddressListUnspent: ( + params: TokenAddressGetHistoryParams, + ) => Promise> + + /** + * Fetches the genesis information of a token by its token ID. + * The method returns details such as the transaction hash, block height, and additional information specific to the token type. + * + * @param tokenId The token ID to fetch the genesis information for. + * @returns A promise resolving to the genesis information of the token. + */ + tokenGenesisInfo: ( + tokenId: string, + ) => Promise> + + /** + * Fetches the list of non-fungible tokens (NFTs) associated with a token. + * The method returns the list of NFTs, including the token ID and commitment. + * + * @param params The parameters for the NFT list query, including the token ID and an optional cursor. + * @returns A promise resolving to the list of NFTs associated with the token. + */ + tokenNftList: ( + params: TokenNftListParams, + ) => Promise> + + /** + * Fetches the version information of the server. + * The method returns the server version and protocol version. + */ + serverVersion: (params: ServerVersionParams) => Promise> + + cashAccountQueryName: ( + params: CashAccountQueryNameParams, + ) => Promise> +} diff --git a/src/e2e/rpc/other/tatum.roc.algorand.indexer.spec.ts b/src/e2e/rpc/other/tatum.rpc.algorand.indexer.spec.ts similarity index 100% rename from src/e2e/rpc/other/tatum.roc.algorand.indexer.spec.ts rename to src/e2e/rpc/other/tatum.rpc.algorand.indexer.spec.ts diff --git a/src/e2e/rpc/other/kadena.rpc.spec.ts b/src/e2e/rpc/other/tatum.rpc.kadena.spec.ts similarity index 100% rename from src/e2e/rpc/other/kadena.rpc.spec.ts rename to src/e2e/rpc/other/tatum.rpc.kadena.spec.ts diff --git a/src/e2e/rpc/other/tatum.rpc.rostrum.spec.ts b/src/e2e/rpc/other/tatum.rpc.rostrum.spec.ts new file mode 100644 index 000000000..14105fc8a --- /dev/null +++ b/src/e2e/rpc/other/tatum.rpc.rostrum.spec.ts @@ -0,0 +1,75 @@ +import { Network, Rostrum, TatumSDK } from '../../../service' +import { e2eUtil } from '../../e2e.util' + +const getRostrumRpc = async () => await TatumSDK.init(e2eUtil.initConfig(Network.ROSTRUM)) + +describe('Rostrum', () => { + it('server.version', async () => { + const rostrum = await getRostrumRpc() + const result = await rostrum.rpc.serverVersion({ + client_name: '1.9.5', + protocol_version: '0.6', + }) + await rostrum.destroy() + expect(result.result).toBeDefined() + expect(result.result?.length).toEqual(2) + }) + + it('blockchain.headers.tip', async () => { + const rostrum = await getRostrumRpc() + const result = await rostrum.rpc.blockchainHeadersTip() + await rostrum.destroy() + expect(result.result?.hex).toBeDefined() + expect(result.result?.height).toBeDefined() + }) + + it('blockchain.headers.subscribe', async () => { + const rostrum = await getRostrumRpc() + const result = await rostrum.rpc.blockchainHeadersSubscribe() + await rostrum.destroy() + expect(result.result?.hex).toBeDefined() + expect(result.result?.height).toBeDefined() + }) + + it('blockchain.address.get_balance', async () => { + const rostrum = await getRostrumRpc() + const result = await rostrum.rpc.blockchainAddressGetBalance({ + address: 'qrmfkegyf83zh5kauzwgygf82sdahd5a55x9wse7ve', + }) + await rostrum.destroy() + expect(result.result?.confirmed).toBeDefined() + expect(result.result?.unconfirmed).toBeDefined() + }) + + it('blockchain.address.get_history', async () => { + const rostrum = await getRostrumRpc() + const result = await rostrum.rpc.blockchainAddressGetHistory({ + address: 'qrmfkegyf83zh5kauzwgygf82sdahd5a55x9wse7ve', + }) + await rostrum.destroy() + expect(result.result?.length).toBeGreaterThan(0) + }) + + it('blockchain.block.get', async () => { + const rostrum = await getRostrumRpc() + const result = await rostrum.rpc.blockchainBlockGet(800000) + await rostrum.destroy() + expect(result.result).toBeDefined() + }) + + it('blockchain.block.header', async () => { + const rostrum = await getRostrumRpc() + const result = await rostrum.rpc.blockchainBlockHeader({ height: 800000 }) + await rostrum.destroy() + expect(result.result).toBeDefined() + }) + + it('blockchain.transaction.get', async () => { + const rostrum = await getRostrumRpc() + const result = await rostrum.rpc.blockchainTransactionGet({ + tx_hash: '05ad7b2bd59e33df49827f2a62002b8f5cccb2a6dc5d96e87089bee9d2f705e2', + }) + await rostrum.destroy() + expect(result.result).toBeDefined() + }) +}) diff --git a/src/service/rpc/other/AbstractRostrumRpc.ts b/src/service/rpc/other/AbstractRostrumRpc.ts new file mode 100644 index 000000000..cd0301fbc --- /dev/null +++ b/src/service/rpc/other/AbstractRostrumRpc.ts @@ -0,0 +1,485 @@ +import { Service } from 'typedi' +import { JsonRpcResponse } from '../../../dto' +import { + AddressAndFilterParam, + AddressSubscribeResult, + AddressUnsubscribeResult, + BlockAddressDecodeResult, + BlockHeaderParams, + BlockHeaderResult, + BlockHeaderVerboseResult, + BlockHeadersParams, + BlockHeadersResult, + BlockchainAddressToScriptHashResult, + BlockchainGetBalanceResult, + BlockchainGetFirstUseResult, + BlockchainHistoryResult, + BlockchainMempoolResult, + BlockchainUnspentOutputResult, + CashAccountQueryNameParams, + CashAccountQueryResult, + HexHeightResult, + RostrumRpcInterface, + ScriptHashAndFilterParam, + ServerVersionParams, + TokenAddressGetBalanceParams, + TokenAddressGetHistoryParams, + TokenGenesisInfoResultBCH, + TokenGenesisInfoResultNexa, + TokenGetBalanceResult, + TokenGetHistoryResult, + TokenGetMempoolResult, + TokenListUnspentResult, + TokenNftListParams, + TokenNftListResultBCH, + TokenNftListResultNexa, + TokenScripthashGetBalanceParams, + TokenScripthashGetHistoryParams, + TokenScripthashGetMempoolParams, + TokenScripthashListUnspentParams, + TokenTransactionGetHistoryParams, + TokenTransactionGetHistoryResult, + TransactionGetConfirmedBlockhashResult, + TransactionGetMerkleParams, + TransactionGetMerkleResult, + TransactionGetParams, + TransactionGetResult, + TransactionIdFromPosParams, + TransactionIdFromPosResult, + UtxoGetParams, + UtxoGetResult, + VerboseTransactionResult, +} from '../../../dto/rpc/RostrumRpcSuite' + +@Service() +export abstract class AbstractRostrumRpc implements RostrumRpcInterface { + protected abstract rpcCall(method: string, params?: unknown[]): Promise + + async blockchainAddressDecode(address: string): Promise> { + return this.rpcCall('blockchain.address.decode', [address]) + } + + async blockchainAddressGetBalance({ + address, + filter, + }: AddressAndFilterParam): Promise> { + const params = [address] + + if (filter) { + params.push(filter) + } + + return this.rpcCall('blockchain.address.get_balance', params) + } + + async blockchainAddressGetFirstUse({ + address, + filter, + }: AddressAndFilterParam): Promise> { + const params = [address] + + if (filter) { + params.push(filter) + } + + return this.rpcCall('blockchain.address.get_first_use', params) + } + + async blockchainAddressGetHistory({ + address, + filter, + }: AddressAndFilterParam): Promise> { + const params = [address] + + if (filter) { + params.push(filter) + } + + return this.rpcCall('blockchain.address.get_history', params) + } + + async blockchainAddressGetMempool({ + address, + filter, + }: AddressAndFilterParam): Promise> { + const params = [address] + + if (filter) { + params.push(filter) + } + + return this.rpcCall('blockchain.address.get_mempool', params) + } + + async blockchainAddressToScriptHash( + address: string, + ): Promise> { + return this.rpcCall('blockchain.address.get_scripthash', [address]) + } + + async blockchainListUnspent({ + address, + filter, + }: AddressAndFilterParam): Promise> { + const params = [address] + + if (filter) { + params.push(filter) + } + + return this.rpcCall('blockchain.address.listunspent', params) + } + + async blockchainAddressSubscribe(address: string): Promise> { + return this.rpcCall('blockchain.address.subscribe', [address]) + } + + async blockchainAddressUnsubscribe(address: string): Promise> { + return this.rpcCall('blockchain.address.unsubscribe', [address]) + } + + async blockchainBlockGet(heightOrHash: string | number): Promise> { + return this.rpcCall('blockchain.block.get', [heightOrHash]) + } + + async blockchainBlockHeader({ + height, + cp_height = 0, + }: BlockHeaderParams): Promise> { + return this.rpcCall('blockchain.block.header', [height, cp_height]) + } + + async blockchainBlockHeaderVerbose( + heightOrHash: string | number, + ): Promise> { + return this.rpcCall('blockchain.block.header_verbose', [heightOrHash]) + } + + async blockchainBlockHeaders({ + start_height, + count, + cp_height = 0, + }: BlockHeadersParams): Promise> { + return this.rpcCall('blockchain.block.headers', [start_height, count, cp_height]) + } + + async blockchainEstimateFee(blocks: number): Promise> { + return this.rpcCall('blockchain.estimatefee', [blocks]) + } + + async blockchainHeadersSubscribe(): Promise> { + return this.rpcCall('blockchain.headers.subscribe') + } + + async blockchainHeadersTip(): Promise> { + return this.rpcCall('blockchain.headers.tip') + } + + async blockchainRelayFee(): Promise> { + return this.rpcCall('blockchain.relayfee') + } + + async blockchainScriptHashGetBalance({ + scripthash, + filter, + }: ScriptHashAndFilterParam): Promise> { + const params = [scripthash] + + if (filter) { + params.push(filter) + } + + return this.rpcCall('blockchain.scripthash.get_balance', params) + } + + async blockchainScriptHashGetFirstUse({ + scripthash, + filter, + }: ScriptHashAndFilterParam): Promise> { + const params = [scripthash] + + if (filter) { + params.push(filter) + } + + return this.rpcCall('blockchain.scripthash.get_first_use', params) + } + + async blockchainScriptHashGetHistory({ + scripthash, + filter, + }: ScriptHashAndFilterParam): Promise> { + const params = [scripthash] + + if (filter) { + params.push(filter) + } + + return this.rpcCall('blockchain.scripthash.get_history', params) + } + + async blockchainScriptHashGetMempool({ + scripthash, + filter, + }: ScriptHashAndFilterParam): Promise> { + const params = [scripthash] + + if (filter) { + params.push(filter) + } + + return this.rpcCall('blockchain.scripthash.get_mempool', params) + } + + async blockchainScriptHashListUnspent({ + scripthash, + filter, + }: ScriptHashAndFilterParam): Promise> { + const params = [scripthash] + + if (filter) { + params.push(filter) + } + + return this.rpcCall('blockchain.scripthash.listunspent', params) + } + + async blockchainScriptHashSubscribe(scripthash: string): Promise> { + return this.rpcCall('blockchain.scripthash.subscribe', [scripthash]) + } + + async blockchainScriptHashUnsubscribe( + scripthash: string, + ): Promise> { + return this.rpcCall('blockchain.scripthash.unsubscribe', [scripthash]) + } + + async blockchainTransactionBroadcast(raw_tx: string): Promise> { + return this.rpcCall('blockchain.transaction.broadcast', [raw_tx]) + } + + async blockchainTransactionGet({ + tx_hash, + verbose, + }: TransactionGetParams): Promise> { + const params: (string | boolean)[] = [tx_hash] + + if (verbose) { + params.push(verbose) + } + + return this.rpcCall('blockchain.transaction.get', params) + } + + async blockchainTransactionGetConfirmedBlockhash( + tx_hash: string, + ): Promise> { + return this.rpcCall('blockchain.transaction.get_confirmed_blockhash', [tx_hash]) + } + + async blockchainTransactionGetMerkle({ + tx_hash, + height, + }: TransactionGetMerkleParams): Promise> { + const params: (string | number)[] = [tx_hash] + + if (height) { + params.push(height) + } + + return this.rpcCall('blockchain.transaction.get_merkle', params) + } + + async blockchainTransactionIdFromPos({ + height, + tx_pos, + merkle = false, + }: TransactionIdFromPosParams): Promise> { + const params = [height, tx_pos, merkle] + + return this.rpcCall('blockchain.transaction.id_from_pos', params) + } + + async blockchainUtxoGet(params: UtxoGetParams): Promise> { + const rpcParams = + params.output_index !== undefined ? [params.tx_hash, params.output_index] : [params.outpoint_hash] + + return this.rpcCall('blockchain.utxo.get', rpcParams) + } + + async tokenScripthashGetBalance({ + scripthash, + cursor, + token, + }: TokenScripthashGetBalanceParams): Promise> { + const params = [scripthash] + + if (cursor !== undefined) { + params.push(cursor) + } + if (token !== undefined) { + params.push(token) + } + + return this.rpcCall('token.scripthash.get_balance', params) + } + + async tokenScripthashGetHistory({ + scripthash, + cursor, + token, + }: TokenScripthashGetHistoryParams): Promise> { + const params = [scripthash] + + if (cursor !== undefined) { + params.push(cursor) + } + if (token !== undefined) { + params.push(token) + } + + return this.rpcCall('token.scripthash.get_history', params) + } + + async tokenScripthashGetMempool({ + scripthash, + cursor, + token, + }: TokenScripthashGetMempoolParams): Promise> { + const params = [scripthash] + + if (cursor !== undefined) { + params.push(cursor) + } + if (token !== undefined) { + params.push(token) + } + + return this.rpcCall('token.scripthash.get_mempool', params) + } + + async tokenScripthashListUnspent({ + scripthash, + cursor, + token, + }: TokenScripthashListUnspentParams): Promise> { + const params = [scripthash] + + if (cursor !== undefined) { + params.push(cursor) + } + if (token !== undefined) { + params.push(token) + } + + return this.rpcCall('token.scripthash.listunspent', params) + } + + async tokenTransactionGetHistory({ + token, + cursor, + commitment, + }: TokenTransactionGetHistoryParams): Promise> { + const params = [token] + + if (cursor !== undefined) { + params.push(cursor) + } + if (commitment !== undefined) { + params.push(commitment) + } + + return this.rpcCall('token.transaction.get_history', params) + } + + async tokenAddressGetBalance({ + address, + cursor, + token, + }: TokenAddressGetBalanceParams): Promise> { + const params = [address] + + if (cursor !== undefined) { + params.push(cursor) + } + if (token !== undefined) { + params.push(token) + } + + return this.rpcCall('token.address.get_balance', params) + } + + async tokenAddressGetHistory({ + address, + cursor, + token, + }: TokenAddressGetHistoryParams): Promise> { + const params = [address] + + if (cursor !== undefined) { + params.push(cursor) + } + if (token !== undefined) { + params.push(token) + } + + return this.rpcCall('token.address.get_history', params) + } + + async tokenAddressGetMempool({ + address, + cursor, + token, + }: TokenAddressGetBalanceParams): Promise> { + const params = [address] + + if (cursor !== undefined) { + params.push(cursor) + } + if (token !== undefined) { + params.push(token) + } + + return this.rpcCall('token.address.get_mempool', params) + } + + async tokenAddressListUnspent({ + address, + cursor, + token, + }: TokenAddressGetBalanceParams): Promise> { + const params = [address] + + if (cursor !== undefined) { + params.push(cursor) + } + if (token !== undefined) { + params.push(token) + } + + return this.rpcCall('token.address.listunspent', params) + } + + async tokenGenesisInfo( + tokenId: string, + ): Promise> { + return this.rpcCall('token.genesis.info', [tokenId]) + } + + async tokenNftList( + params: TokenNftListParams, + ): Promise> { + return this.rpcCall('token.nft.list', [params.token, params.cursor]) + } + + async serverVersion(params: ServerVersionParams): Promise> { + return this.rpcCall('server.version', [params.client_name, params.protocol_version]) + } + + async cashAccountQueryName({ + name, + height, + }: CashAccountQueryNameParams): Promise> { + return this.rpcCall('cashaccount.query.name', [name, height]) + } +} diff --git a/src/service/rpc/other/RostrumLoadBalancerRpc.ts b/src/service/rpc/other/RostrumLoadBalancerRpc.ts new file mode 100644 index 000000000..2e0eb8b19 --- /dev/null +++ b/src/service/rpc/other/RostrumLoadBalancerRpc.ts @@ -0,0 +1,35 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Container, Service } from 'typedi' +import { Utils } from '../../../util' +// Need to import like this to keep browser working +import { RostrumRpcInterface } from '../../../dto/rpc/RostrumRpcSuite' +import { LoadBalancer } from '../generic/LoadBalancer' +import { AbstractRostrumRpc } from './AbstractRostrumRpc' + +@Service({ + factory: (data: { id: string }) => { + return new RostrumLoadBalancerRpc(data.id) + }, + transient: true, +}) +export class RostrumLoadBalancerRpc extends AbstractRostrumRpc implements RostrumRpcInterface { + protected readonly loadBalancer: LoadBalancer + + constructor(id: string) { + super() + this.loadBalancer = Container.of(id).get(LoadBalancer) + } + + protected async rpcCall(method: string, params?: unknown[]): Promise { + const preparedCall = Utils.prepareRpcCall(method, params) + return (await this.loadBalancer.rawRpcCall(preparedCall)) as T + } + + public destroy() { + this.loadBalancer.destroy() + } + + public getRpcNodeUrl(): string { + return this.loadBalancer.getActiveNormalUrlWithFallback().url + } +} diff --git a/src/service/tatum/tatum.other.ts b/src/service/tatum/tatum.other.ts index f98974c8c..92451e997 100644 --- a/src/service/tatum/tatum.other.ts +++ b/src/service/tatum/tatum.other.ts @@ -6,6 +6,7 @@ import { BnbRpcSuite } from '../../dto/rpc/BnbRpcSuite' import { CardanoRpcSuite } from '../../dto/rpc/CardanoRpcSuite' import { EosRpcSuite } from '../../dto/rpc/EosRpcSuite' import { KadenaRpcInterface } from '../../dto/rpc/KadenaRpcSuite' +import { RostrumRpcInterface } from '../../dto/rpc/RostrumRpcSuite' import { StellarRpcSuite } from '../../dto/rpc/StellarRpcSuite' import { CONFIG, Utils } from '../../util' import { Address, AddressTezos, AddressTron } from '../address' @@ -111,6 +112,15 @@ export class Kadena extends BaseOther { } } +export class Rostrum 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/util/constant.ts b/src/util/constant.ts index 0b5c48edb..339dd1ac6 100644 --- a/src/util/constant.ts +++ b/src/util/constant.ts @@ -29,6 +29,7 @@ export const Constant = { [Network.NEAR]: 24, [Network.NEAR_TESTNET]: 24, [Network.BITCOIN_CASH]: 8, + [Network.ROSTRUM]: 8, [Network.BITCOIN_CASH_TESTNET]: 8, [Network.LITECOIN]: 8, [Network.LITECOIN_TESTNET]: 8, @@ -218,6 +219,7 @@ export const Constant = { [Network.BASE_SEPOLIA]: 'BASE', [Network.KADENA]: 'KADENA', [Network.KADENA_TESTNET]: 'KADENA', + [Network.ROSTRUM]: 'BCH', }, RPC: { MAINNETS: [ diff --git a/src/util/util.shared.ts b/src/util/util.shared.ts index bf73881bd..58f475622 100644 --- a/src/util/util.shared.ts +++ b/src/util/util.shared.ts @@ -17,6 +17,7 @@ import { isEvmLoadBalancerNetwork, isKadenaLoadBalancerNetwork, isNativeEvmLoadBalancerNetwork, + isRostrumLoadBalancerNetwork, isSameGetBlockNetwork, isSolanaNetwork, isStellarLoadBalancerNetwork, @@ -76,6 +77,7 @@ import { Optimism, Palm, Polygon, + Rostrum, Solana, Stellar, TatumConfig, @@ -99,6 +101,7 @@ import { CardanoLoadBalancerRpc } from '../service/rpc/other/CardanoLoadBalancer import { EosLoadBalancerRpc } from '../service/rpc/other/EosLoadBalancerRpc' import { EosRpc } from '../service/rpc/other/EosRpc' import { KadenaLoadBalancerRpc } from '../service/rpc/other/KadenaLoadBalancerRpc' +import { RostrumLoadBalancerRpc } from '../service/rpc/other/RostrumLoadBalancerRpc' import { SolanaArchiveLoadBalancerRpc } from '../service/rpc/other/SolanaArchiveLoadBalancerRpc' import { StellarLoadBalancerRpc } from '../service/rpc/other/StellarLoadBalancerRpc' import { StellarRpc } from '../service/rpc/other/StellarRpc' @@ -115,6 +118,10 @@ export const Utils = { getRpc: (id: string, config: TatumConfig): T => { const { network } = config + if (isRostrumLoadBalancerNetwork(network)) { + return Container.of(id).get(RostrumLoadBalancerRpc) as T + } + if (isKadenaLoadBalancerNetwork(network)) { return Container.of(id).get(KadenaLoadBalancerRpc) as T } @@ -226,6 +233,15 @@ export const Utils = { return mappedNetwork ?? network }, getStatusPayload: (network: Network) => { + if (isRostrumLoadBalancerNetwork(network)) { + return { + jsonrpc: '2.0', + method: 'blockchain.headers.tip', + params: [], + id: 1, + } + } + if (isXrpNetwork(network)) { return { method: 'ledger', @@ -329,6 +345,10 @@ export const Utils = { return url } + if (isRostrumLoadBalancerNetwork(network)) { + return url + } + if (isTezosNetwork(network)) { return `${url}chains/main/blocks/head/header` } @@ -396,6 +416,10 @@ export const Utils = { return new BigNumber((response.hashes[0].height as number) || -1).toNumber() } + if (isRostrumLoadBalancerNetwork(network)) { + return new BigNumber((response.result.height as number) || -1).toNumber() + } + throw new Error(`Network ${network} is not supported.`) }, isResponseOk: (network: Network, response: JsonRpcResponse | any) => { @@ -439,6 +463,10 @@ export const Utils = { return response?.hashes?.[0]?.height !== undefined } + if (isRostrumLoadBalancerNetwork(network)) { + return response?.result?.height !== undefined + } + throw new Error(`Network ${network} is not supported.`) }, mapNotificationChainToNetwork: (chain: AddressEventNotificationChain): Network => { @@ -782,6 +810,8 @@ export const Utils = { case Network.KADENA: case Network.KADENA_TESTNET: return new Kadena(id) as T + case Network.ROSTRUM: + return new Rostrum(id) as T default: return new FullSdk(id) as T }