From 0214594cd861b802783e67971e3df2145963acf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Kotol?= Date: Wed, 15 Nov 2023 13:42:31 +0100 Subject: [PATCH] ALL-3289 - Add Cardano Rpc support (#1022) --- CHANGELOG.md | 4 + README.md | 59 +- package.json | 2 +- src/api/api.dto.ts | 2 - src/dto/Network.ts | 12 +- src/dto/rpc/CardanoRpcSuite.ts | 631 ++++++++++++++++++ .../other/tatum.rpc.cardano.rosetta.spec.ts | 51 ++ src/e2e/tatum.address.spec.ts | 2 +- src/service/rpc/other/AbstractCardanoRpc.ts | 146 ++++ .../rpc/other/CardanoLoadBalancerRpc.ts | 34 + src/service/tatum/tatum.other.ts | 10 + src/util/constant.ts | 12 +- src/util/util.shared.ts | 31 + 13 files changed, 955 insertions(+), 41 deletions(-) create mode 100644 src/dto/rpc/CardanoRpcSuite.ts create mode 100644 src/e2e/rpc/other/tatum.rpc.cardano.rosetta.spec.ts create mode 100644 src/service/rpc/other/AbstractCardanoRpc.ts create mode 100644 src/service/rpc/other/CardanoLoadBalancerRpc.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 48a7f41f38..ddbb35982f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [4.1.29] - 2023.11.15 +### Added +- Added RPC support for the CARDANO_ROSETTA network. Users can now make RPC calls to these network using the `Network.CARDANO_ROSETTA` network. + ## [4.1.28] - 2023.11.13 ### Fixed - Fixed lb archive fallback diff --git a/README.md b/README.md index f3ffbf68d4..cc2d5587a5 100644 --- a/README.md +++ b/README.md @@ -44,34 +44,35 @@ With Tatum SDK, you can: Interact seamlessly with various blockchains through native RPC calls. Say goodbye to the hassle of juggling separate RPC clients for each blockchain. -| Documentation | -|----------------------------------------------------------------------------------------------------------| -| **EVM Blockchains** | -| [Ethereum RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/ethereum-rpc-documentation) | -| [Polygon RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/polygon-rpc-documentation) | -| [Flare RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/flare-rpc-documentation) | -| [Haqq RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/haqq-rpc-documentation) | -| [Optimism RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/optimism-rpc-documentation) | -| [Horizen EON RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/horizen-eon-rpc-documentation) | -| [Arbitrum One RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/arbitrum-rpc-documentation) | -| [Chiliz RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/chiliz-rpc-documentation) | +| Documentation | +|-----------------------------------------------------------------------------------------------------------| +| **EVM Blockchains** | +| [Ethereum RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/ethereum-rpc-documentation) | +| [Polygon RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/polygon-rpc-documentation) | +| [Flare RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/flare-rpc-documentation) | +| [Haqq RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/haqq-rpc-documentation) | +| [Optimism RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/optimism-rpc-documentation) | +| [Horizen EON RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/horizen-eon-rpc-documentation) | +| [Arbitrum One RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/arbitrum-rpc-documentation) | +| [Chiliz RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/chiliz-rpc-documentation) | | [Ethereum Classic RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/ethereum-classic-rpc-documentation) | -| [Klaytn RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/klaytn-rpc-documentation) | -| [Avalanche RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/avalanche-rpc-documentation) | -| [Celo RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/celo-rpc-documentation) | -| **UTXO Blockchains** | -| [Bitcoin RPC](https://docs.tatum.io/docs/rpc/utxo-blockchains/bitcoin-rpc-documentation) | -| [Litecoin RPC](https://docs.tatum.io/docs/rpc/utxo-blockchains/litecoin-rpc-documentation) | -| [Dogecoin RPC](https://docs.tatum.io/docs/rpc/utxo-blockchains/dogecoin-rpc-documentation) | -| [ZCash RPC](https://docs.tatum.io/docs/rpc/utxo-blockchains/zcash-rpc-documentation) | -| [Bitcoin Cash RPC](https://docs.tatum.io/docs/rpc/utxo-blockchains/bitcion-cash-rpc-documentation) | -| **Other Blockchains** | -| [Solana RPC](https://docs.tatum.io/docs/rpc/solana-rpc-documentation) | -| [XPR RPC](https://docs.tatum.io/docs/rpc/xrp-rpc-documentation) | -| [Tron RPC](https://docs.tatum.io/docs/rpc/tron-rpc-documentation) | -| [Eos RPC](https://docs.tatum.io/docs/rpc/eos-rpc-documentation) | -| [Tezos RPC](https://docs.tatum.io/docs/rpc/tezos-rpc-documentation) | -| [Agorand RPC](https://docs.tatum.io/docs/rpc/algorand-rpc-documentation) | +| [Klaytn RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/klaytn-rpc-documentation) | +| [Avalanche RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/avalanche-rpc-documentation) | +| [Celo RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/celo-rpc-documentation) | +| **UTXO Blockchains** | +| [Bitcoin RPC](https://docs.tatum.io/docs/rpc/utxo-blockchains/bitcoin-rpc-documentation) | +| [Litecoin RPC](https://docs.tatum.io/docs/rpc/utxo-blockchains/litecoin-rpc-documentation) | +| [Dogecoin RPC](https://docs.tatum.io/docs/rpc/utxo-blockchains/dogecoin-rpc-documentation) | +| [ZCash RPC](https://docs.tatum.io/docs/rpc/utxo-blockchains/zcash-rpc-documentation) | +| [Bitcoin Cash RPC](https://docs.tatum.io/docs/rpc/utxo-blockchains/bitcion-cash-rpc-documentation) | +| **Other Blockchains** | +| [Solana RPC](https://docs.tatum.io/docs/rpc/solana-rpc-documentation) | +| [XPR RPC](https://docs.tatum.io/docs/rpc/xrp-rpc-documentation) | +| [Tron RPC](https://docs.tatum.io/docs/rpc/tron-rpc-documentation) | +| [Eos RPC](https://docs.tatum.io/docs/rpc/eos-rpc-documentation) | +| [Tezos RPC](https://docs.tatum.io/docs/rpc/tezos-rpc-documentation) | +| [Agorand RPC](https://docs.tatum.io/docs/rpc/algo-rpc-documentation) | +| [Cardano RPC](https://docs.tatum.io/docs/rpc/cardano-rpc-documentation) | ### 🔔 Create Notifications @@ -489,6 +490,8 @@ This section provides a list of various blockchain network status pages, powered | [algorand-testnet-algod.status.tatum.io](https://algorand-testnet-algod.status.tatum.io) | | [algorand-mainnet-indexer.status.tatum.io](https://algorand-mainnet-indexer.status.tatum.io) | | [algorand-testnet-indexer.status.tatum.io](https://algorand-testnet-indexer.status.tatum.io) | +| [cardano-mainnet.status.tatum.io](https://cardano-mainnet.status.tatum.io) | +| [cardano-preprod.status.tatum.io](https://cardano-preprod.status.tatum.io) | @@ -594,6 +597,8 @@ Here are the list of nodes for each blockchain: | [rpc.tatum.io/algorand-testnet-algod/list.json](https://rpc.tatum.io/algorand-testnet-algod/list.json) | | [rpc.tatum.io/algorand-mainnet-indexer/list.json](https://rpc.tatum.io/algorand-mainnet-indexer/list.json) | | [rpc.tatum.io/algorand-testnet-indexer/list.json](https://rpc.tatum.io/algorand-testnet-indexer/list.json) | +| [rpc.tatum.io/cardano-mainnet/list.json](https://rpc.tatum.io/cardano-mainnet/list.json) | +| [rpc.tatum.io/cardano-preprod/list.json](https://rpc.tatum.io/cardano-preprod/list.json) | Following pattern defines the URL for fetching the list of nodes: diff --git a/package.json b/package.json index 90d0903d08..adfbf3bdb7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@tatumio/tatum", - "version": "4.1.28", + "version": "4.1.29", "description": "Tatum JS SDK", "author": "Tatum", "repository": "https://github.com/tatumio/tatum-js", diff --git a/src/api/api.dto.ts b/src/api/api.dto.ts index 8bed12e5cf..5c764b8604 100644 --- a/src/api/api.dto.ts +++ b/src/api/api.dto.ts @@ -54,8 +54,6 @@ export function networkToChain(network: Network): Chain { return ChainEnum.POLYGON case Network.POLYGON_MUMBAI: return ChainEnum.POLYGON_MUMBAI - case Network.CARDANO: - return ChainEnum.POLYGON_MUMBAI default: throw new Error(`Unsupported network ${network}`) } diff --git a/src/dto/Network.ts b/src/dto/Network.ts index d0f3dc2ae0..cae3a12b59 100644 --- a/src/dto/Network.ts +++ b/src/dto/Network.ts @@ -12,7 +12,7 @@ export enum Network { BNB = 'bnb-beacon-chain-mainnet', BITCOIN = 'bitcoin-mainnet', BITCOIN_CASH = 'bitcoin-cash-mainnet', - CARDANO = 'cardano-mainnet', + CARDANO_ROSETTA = 'cardano-mainnet', CELO = 'celo-mainnet', CRONOS = 'cro-mainnet', DOGECOIN = 'doge-mainnet', @@ -59,7 +59,7 @@ export enum Network { BINANCE_SMART_CHAIN_TESTNET = 'bsc-testnet', BITCOIN_TESTNET = 'bitcoin-testnet', BITCOIN_CASH_TESTNET = 'bch-testnet', - CARDANO_PREPROD = 'cardano-preprod', + CARDANO_ROSETTA_PREPROD = 'cardano-preprod', CELO_ALFAJORES = 'celo-testnet', CRONOS_TESTNET = 'cro-testnet', DOGECOIN_TESTNET = 'doge-testnet', @@ -172,8 +172,8 @@ export const DATA_API_UTXO_NETWORKS = [ Network.BITCOIN_TESTNET, Network.LITECOIN, Network.LITECOIN_TESTNET, - Network.CARDANO, - Network.CARDANO_PREPROD, + Network.CARDANO_ROSETTA, + Network.CARDANO_ROSETTA_PREPROD, Network.DOGECOIN, Network.DOGECOIN_TESTNET, ] @@ -238,6 +238,7 @@ export const BNB_LOAD_BALANCER_NETWORKS = [Network.BNB] export const TEZOS_NETWORKS = [Network.TEZOS, Network.TEZOS_TESTNET] export const ALGORAND_ALGOD_NETWORKS = [Network.ALGORAND_ALGOD, Network.ALGORAND_ALGOD_TESTNET] export const ALGORAND_INDEXER_NETWORKS = [Network.ALGORAND_INDEXER, Network.ALGORAND_INDEXER_TESTNET] +export const CARDANO_NETWORKS = [Network.CARDANO_ROSETTA, Network.CARDANO_ROSETTA_PREPROD] export const LOAD_BALANCER_NETWORKS = [ ...UTXO_LOAD_BALANCER_NETWORKS, @@ -251,6 +252,7 @@ export const LOAD_BALANCER_NETWORKS = [ ...TEZOS_NETWORKS, ...ALGORAND_ALGOD_NETWORKS, ...ALGORAND_INDEXER_NETWORKS, + ...CARDANO_NETWORKS, ] export const EVM_ARCHIVE_NON_ARCHIVE_LOAD_BALANCER_NETWORKS = [ @@ -324,6 +326,8 @@ export const isAlgorandAlgodNetwork = (network: Network) => ALGORAND_ALGOD_NETWO export const isAlgorandIndexerNetwork = (network: Network) => ALGORAND_INDEXER_NETWORKS.includes(network) +export const isCardanoNetwork = (network: Network) => CARDANO_NETWORKS.includes(network) + export const isSameGetBlockNetwork = (network: Network) => isUtxoBasedNetwork(network) || isEvmBasedNetwork(network) || diff --git a/src/dto/rpc/CardanoRpcSuite.ts b/src/dto/rpc/CardanoRpcSuite.ts new file mode 100644 index 0000000000..3ef75405a0 --- /dev/null +++ b/src/dto/rpc/CardanoRpcSuite.ts @@ -0,0 +1,631 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export interface NetworkIdentifier { + blockchain: string + network: string + sub_network_identifier?: SubNetworkIdentifier +} + +export interface SubNetworkIdentifier { + network: string + metadata?: { + [key: string]: any + } +} + +export interface BlockIdentifier { + index: number + hash: string +} + +export interface PartialBlockIdentifier { + index?: number + hash?: string +} + +export interface TransactionIdentifier { + hash: string +} + +export interface OperationIdentifier { + index: number + network_index?: number +} + +export interface AccountIdentifier { + address: string + sub_account?: SubAccountIdentifier + metadata?: { + chain_code?: string + } +} + +export interface SubAccountIdentifier { + address: string + metadata?: { + [key: string]: any + } +} + +export interface Block { + block_identifier: BlockIdentifier + parent_block_identifier: BlockIdentifier + timestamp: number + transactions: Transaction[] + metadata?: { + transactionsCount?: number + createdBy?: string + size?: number + epochNo?: number + slotNo?: number + } +} + +export interface Transaction { + transaction_identifier: TransactionIdentifier + operations: Operation[] + related_transactions?: RelatedTransaction[] + metadata?: { + size: number + scriptSize: number + } +} + +export interface Operation { + operation_identifier: OperationIdentifier + related_operations?: OperationIdentifier[] + type: string + status?: string + account?: AccountIdentifier + amount?: Amount + coin_change?: CoinChange + metadata?: OperationMetadata +} + +export interface OperationMetadata { + withdrawalAmount?: Amount + depositAmount?: Amount + refundAmount?: Amount + staking_credential?: PublicKey + pool_key_hash?: string + epoch?: number + tokenBundle?: TokenBundleItem[] + poolRegistrationCert?: string + poolRegistrationParams?: PoolRegistrationParams + voteRegistrationMetadata?: VoteRegistrationMetadata +} + +export interface VoteRegistrationMetadata { + stakeKey: PublicKey + votingKey: PublicKey + rewardAddress: string + votingNonce: number + votingSignature: string +} + +export interface PoolRegistrationParams { + vrfKeyHash: string + rewardAddress: string + pledge: string + cost: string + poolOwners: string[] + relays: Relay[] + margin: PoolMargin + margin_percentage: string + poolMetadata: PoolMetadata +} + +export interface Relay { + type: string + ipv4: string + ipv6: string + dnsName: string + port: string +} + +export interface PoolMargin { + numerator: string + denominator: string +} + +export interface PoolMetadata { + url: string + hash: string +} + +export interface TokenBundleItem { + policyId: string + tokens: Amount[] +} + +export interface Amount { + value: string + currency: Currency + metadata?: any +} + +export interface UnspentSetForGivenAccount { + value: string + index: number + transactionHash: string +} + +export interface Currency { + symbol: string + decimals: number + metadata?: any +} + +export interface SyncStatus { + current_index: number + target_index: number + stage: string + synced: boolean +} + +export interface Peer { + peer_id: string + metadata: Record +} + +export interface Version { + rosetta_version: string + node_version: string + middleware_version?: string + metadata?: { + [key: string]: any + } +} + +export interface Allow { + operation_statuses: OperationStatus[] + operation_types: string[] + errors: Error[] + historical_balance_lookup: boolean + timestamp_start_index?: number + call_methods: string[] + balance_exemptions: BalanceExemption[] + mempool_coins: boolean +} + +export interface OperationStatus { + status: string + successful: boolean +} + +export interface Block { + description: string + type: number + format: string + minimum: number + example: number +} + +export interface IAddress { + description: string + type: string +} + +export interface PublicKey { + hex_bytes: string + curve_type: CurveType +} + +export type CurveType = 'secp256k1' | 'secp256r1' | 'edwards25519' | 'tweedle' + +export interface SigningPayload { + address?: string + account_identifier?: AccountIdentifier + hex_bytes: string + signature_type?: SignatureType +} + +export interface Signature { + signing_payload: SigningPayload + public_key: PublicKey + signature_type: SignatureType + hex_bytes: string +} + +export interface SignatureType { + description: string + type: string + enum: string[] +} + +export type CoinActions = 'coin_created' | 'coin_spent' + +export interface CoinIdentifier { + identifier: string +} + +export interface CoinChange { + coin_identifier: CoinIdentifier + coin_action: CoinAction +} + +export enum CoinAction { + CoinCreated = 'coin_created', + CoinSpent = 'coin_spent', +} + +export interface Coin { + coin_identifier: CoinIdentifier + amount: Amount + metadata?: { [key: string]: TokenBundleItem[] } +} + +export interface BalanceExemption { + sub_account_address: string + currency: Currency + exemption_type: ExemptionType +} + +export interface ExemptionType { + description: string + type: string + enum: 'greater_or_equal' | 'less_or_equal' | 'dynamic' +} + +export interface BlockEvent { + sequence: number + block_identifier: BlockIdentifier + type: BlockEventType +} + +export interface BlockEventType { + description: string + type: string + enum: 'block_added' | 'block_removed' +} + +export interface Operator { + description?: string + type: string + enum: 'or' | 'and' +} + +export interface BlockTransaction { + block_identifier: BlockIdentifier + transaction: Transaction +} + +export interface RelatedTransaction { + network_identifier?: NetworkIdentifier + transaction_identifier: TransactionIdentifier + direction: Direction +} + +export enum Direction { + forward = 'forward', + backward = 'backward', +} + +export enum RelationDirection { + Forward = 'forward', + Backward = 'backward', +} + +export interface AccountBalanceRequest { + networkIdentifier: NetworkIdentifier + accountIdentifier: AccountIdentifier + blockIdentifier?: PartialBlockIdentifier + currencies?: Currency[] +} + +export interface AccountBalanceResponse { + block_identifier: BlockIdentifier + balances: Amount[] + metadata?: { + [key: string]: any + } +} + +export interface AccountCoinsRequest { + networkIdentifier: NetworkIdentifier + accountIdentifier: AccountIdentifier + includeMempool?: boolean + currencies?: Currency[] +} + +export interface AccountCoinsResponse { + block_identifier: BlockIdentifier + coins: Coin[] + metadata?: { + [key: string]: any + } +} + +export interface BlockRequest { + networkIdentifier: NetworkIdentifier + blockIdentifier: PartialBlockIdentifier +} + +export interface BlockResponse { + block: Block + other_transactions?: TransactionIdentifier[] +} + +export interface BlockTransactionRequest { + networkIdentifier: NetworkIdentifier + blockIdentifier: BlockIdentifier + transactionIdentifier: TransactionIdentifier +} + +export interface BlockTransactionResponse { + transaction: Transaction +} + +export interface MempoolResponse { + transaction_identifiers: TransactionIdentifier[] +} + +export interface MempoolTransactionRequest { + networkIdentifier: NetworkIdentifier + transactionIdentifier: TransactionIdentifier +} + +export interface MempoolTransactionResponse { + transaction: Transaction + metadata?: { + descendant_fees?: number + ancestor_count?: number + } +} + +export interface MetadataRequest { + metadata?: object +} + +export interface NetworkListResponse { + network_identifiers: NetworkIdentifier[] +} + +export interface NetworkRequest { + networkIdentifier: NetworkIdentifier + metadata?: object +} + +export interface NetworkStatusResponse { + current_block_identifier: BlockIdentifier + current_block_timestamp: number + genesis_block_identifier: BlockIdentifier + oldest_block_identifier?: BlockIdentifier + sync_status?: SyncStatus + peers: Peer[] +} + +export interface NetworkOptionsResponse { + version: Version + allow: Allow +} + +export interface ConstructionMetadataRequest { + networkIdentifier: NetworkIdentifier + options: { + relativeTtl: number + transactionSize: number + } + publicKeys?: PublicKey[] +} + +export interface ConstructionMetadataResponse { + metadata: { + ttl: string + protocol_parameters: ProtocolParameters + } + suggested_fee?: Amount[] +} + +export interface ConstructionDeriveRequest { + networkIdentifier: NetworkIdentifier + publicKey: PublicKey + metadata?: { + stakingCredential?: PublicKey + addressType?: string + } +} + +export interface ConstructionDeriveResponse { + address?: string + account_identifier: AccountIdentifier + metadata?: Record +} + +export interface ConstructionPreprocessRequest { + networkIdentifier: NetworkIdentifier + operations: Operation[] + metadata?: { + relativeTtl?: number + depositParameters?: DepositParameters + } + maxFee?: Amount[] + suggestedFeeMultiplier?: number +} + +export interface ConstructionPreprocessResponse { + options?: { + relative_ttl: number + transaction_size: number + } + required_public_keys?: AccountIdentifier[] +} + +export interface ConstructionPayloadsRequest { + networkIdentifier: NetworkIdentifier + operations: Operation[] + metadata: { + ttl: string + protocolParameters: ProtocolParameters + } + publicKeys?: PublicKey[] +} + +export interface ConstructionTransactionResponse { + unsigned_transaction: string + payloads: SigningPayload[] +} + +export interface ConstructionCombineRequest { + networkIdentifier: NetworkIdentifier + unsignedTransaction: string + signatures: Signature[] +} + +export interface ConstructionCombineResponse { + signed_transaction: string +} + +export interface ConstructionParseRequest { + networkIdentifier: NetworkIdentifier + signed: boolean + transaction: string +} + +export interface ConstructionParseResponse { + operations: Operation[] + account_identifier_signers: AccountIdentifier[] + signers?: string[] + metadata?: { + [key: string]: any + } +} + +export interface ConstructionHashRequest { + networkIdentifier: NetworkIdentifier + signedTransaction: string +} + +export interface TransactionSubmissionRequest { + networkIdentifier: NetworkIdentifier + signedTransaction: string +} + +export interface TransactionIdentifierResponse { + transaction_identifier: TransactionIdentifier + metadata: object +} + +export interface CallRequest { + networkIdentifier: NetworkIdentifier + method: string + parameters: { + [key: string]: any + } +} + +export interface CallResponse { + result: { + [key: string]: any + } + idempotent: boolean +} + +export interface EventsBlocksRequest { + networkIdentifier: NetworkIdentifier + offset?: number + limit?: number +} + +export interface EventsBlocksResponse { + max_sequence: number + events: BlockEvent[] +} + +export interface SearchTransactionsRequest { + networkIdentifier: NetworkIdentifier + operator?: Operator + maxBlock?: number + offset?: number + limit?: number + transactionIdentifier?: TransactionIdentifier + accountIdentifier?: AccountIdentifier + coinIdentifier?: CoinIdentifier + currency?: Currency + status?: string + type?: string + address?: string + success?: boolean +} + +export interface SearchTransactionsResponse { + transactions: BlockTransaction[] + total_count: number + next_offset?: number +} + +export interface ProtocolParameters { + coinsPerUtxoSize: string + maxTxSize: number + maxValSize: number + keyDeposit: string // key registration cost in Lovelace + maxCollateralInputs: number + minFeeCoefficient: number + minFeeConstant: number + minPoolCost: string + poolDeposit: string // pool registration cost in Lovelace + protocol: number +} + +export interface DepositParameters { + keyDeposit: string + poolDeposit: string +} + +export interface Details { + message: string +} + +export interface Error { + code: number + message: string + description?: string + retriable: boolean + details?: Details +} + +export interface CardanoRpcSuite { + getNetworkList(params?: MetadataRequest): Promise + + getNetworkStatus(params: NetworkRequest): Promise + + getNetworkOptions(params: NetworkRequest): Promise + + getBlock(params: BlockRequest): Promise + + getBlockTransaction(params: BlockTransactionRequest): Promise + + getMempool(params: NetworkRequest): Promise + + getMempoolTransaction(params: MempoolTransactionRequest): Promise + + getAccountBalance(requestBody: AccountBalanceRequest): Promise + + getAccountCoins(params: AccountCoinsRequest): Promise + + deriveAccount(params: ConstructionDeriveRequest): Promise + + constructionPreprocess(params: ConstructionPreprocessRequest): Promise + + getTransactionConstructionMetadata( + params: ConstructionMetadataRequest, + ): Promise + + generateUnsignedTransactionAndSigningPayloads( + params: ConstructionPayloadsRequest, + ): Promise + + createNetworkTransaction(params: ConstructionCombineRequest): Promise + + parseTransaction(params: ConstructionParseRequest): Promise + + getHashOfTransaction(params: ConstructionHashRequest): Promise + + submitTransaction(params: TransactionSubmissionRequest): Promise + + call(params: CallRequest): Promise + + getEventsBlocks(params: EventsBlocksRequest): Promise + + searchTransactions(params: SearchTransactionsRequest): Promise +} diff --git a/src/e2e/rpc/other/tatum.rpc.cardano.rosetta.spec.ts b/src/e2e/rpc/other/tatum.rpc.cardano.rosetta.spec.ts new file mode 100644 index 0000000000..c29855fa93 --- /dev/null +++ b/src/e2e/rpc/other/tatum.rpc.cardano.rosetta.spec.ts @@ -0,0 +1,51 @@ +import process from 'process' +import { CardanoRosetta, Network, TatumSDK } from '../../../service' +import { e2eUtil } from '../../e2e.util' + +const getCardanoRosettaRpc = async (testnet?: boolean) => + await TatumSDK.init({ + network: testnet ? Network.CARDANO_ROSETTA_PREPROD : Network.CARDANO_ROSETTA, + apiKey: { + v4: testnet ? process.env.V4_API_KEY_TESTNET : process.env.V4_API_KEY_MAINNET, + }, + verbose: e2eUtil.isVerbose, + }) + +const networks = [ + { testnet: false, blockchain: 'cardano', network: 'mainnet' }, + { testnet: true, blockchain: 'cardano', network: 'preprod' }, +] + +describe.each(networks)('Cardano Rosetta', ({ testnet, network, blockchain }) => { + describe(`${testnet ? 'Testnet' : 'Mainnet'}`, () => { + let tatum: CardanoRosetta + + beforeEach(async () => { + tatum = await getCardanoRosettaRpc(testnet) + }) + + afterEach(async () => { + await tatum.destroy() + }) + + it('should get network status', async () => { + const response = await tatum.rpc.getNetworkStatus({ + networkIdentifier: { blockchain, network }, + }) + expect(response).toBeDefined() + }) + + it('should get network list', async () => { + const response = await tatum.rpc.getNetworkList({}) + expect(response).toBeDefined() + }) + + it('should get block', async () => { + const response = await tatum.rpc.getBlock({ + networkIdentifier: { blockchain, network }, + blockIdentifier: { index: 1 }, + }) + expect(response).toBeDefined() + }) + }) +}) diff --git a/src/e2e/tatum.address.spec.ts b/src/e2e/tatum.address.spec.ts index 5085942646..3ff2aac7b8 100644 --- a/src/e2e/tatum.address.spec.ts +++ b/src/e2e/tatum.address.spec.ts @@ -205,7 +205,7 @@ describe.skip('Address', () => { let tatum: FullSdk beforeAll(async () => { - tatum = await TatumSDK.init({ network: Network.CARDANO_PREPROD }) + tatum = await TatumSDK.init({ network: Network.CARDANO_ROSETTA_PREPROD }) }) it('should get balance with native assets only', async () => { diff --git a/src/service/rpc/other/AbstractCardanoRpc.ts b/src/service/rpc/other/AbstractCardanoRpc.ts new file mode 100644 index 0000000000..0fa4cec864 --- /dev/null +++ b/src/service/rpc/other/AbstractCardanoRpc.ts @@ -0,0 +1,146 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { PostI } from '../../../dto/PostI' +import { + AccountBalanceRequest, + AccountBalanceResponse, + AccountCoinsRequest, + AccountCoinsResponse, + BlockRequest, + BlockResponse, + BlockTransactionRequest, + BlockTransactionResponse, + CallRequest, + CallResponse, + CardanoRpcSuite, + ConstructionCombineRequest, + ConstructionCombineResponse, + ConstructionDeriveRequest, + ConstructionDeriveResponse, + ConstructionHashRequest, + ConstructionMetadataRequest, + ConstructionMetadataResponse, + ConstructionParseRequest, + ConstructionParseResponse, + ConstructionPayloadsRequest, + ConstructionPreprocessRequest, + ConstructionPreprocessResponse, + ConstructionTransactionResponse, + EventsBlocksRequest, + EventsBlocksResponse, + MempoolResponse, + MempoolTransactionRequest, + MempoolTransactionResponse, + MetadataRequest, + NetworkListResponse, + NetworkOptionsResponse, + NetworkRequest, + NetworkStatusResponse, + SearchTransactionsRequest, + SearchTransactionsResponse, + TransactionIdentifierResponse, + TransactionSubmissionRequest, +} from '../../../dto/rpc/CardanoRpcSuite' +import { Utils } from '../../../util' + +export abstract class AbstractCardanoRpc implements CardanoRpcSuite { + protected abstract post(post: PostI): Promise + + private sendPost({ path, body }: { path: string; body?: any }): Promise { + const post: PostI = { + path, + } + + if (body) { + post.body = Utils.convertObjCamelToSnake(body) + } + + return this.post(post) + } + + getNetworkList(body: MetadataRequest): Promise { + return this.sendPost({ path: '/network/list', body }) + } + + getNetworkStatus(body: NetworkRequest): Promise { + return this.sendPost({ path: '/network/status', body: body }) + } + + getNetworkOptions(body: NetworkRequest): Promise { + return this.sendPost({ path: '/network/options', body: body }) + } + + getBlock(body: BlockRequest): Promise { + return this.sendPost({ path: '/block', body }) + } + + getBlockTransaction(body: BlockTransactionRequest): Promise { + return this.sendPost({ path: '/block/transaction', body }) + } + + getMempool(body: NetworkRequest): Promise { + return this.sendPost({ path: '/mempool', body }) + } + + getMempoolTransaction(body: MempoolTransactionRequest): Promise { + return this.sendPost({ path: '/mempool/transaction', body }) + } + + getAccountBalance(body: AccountBalanceRequest): Promise { + return this.sendPost({ path: '/account/balance', body }) + } + + getAccountCoins(body: AccountCoinsRequest): Promise { + return this.sendPost({ path: '/account/coins', body }) + } + + deriveAccount(body: ConstructionDeriveRequest): Promise { + return this.sendPost({ path: '/construction/derive', body }) + } + + constructionPreprocess(body: ConstructionPreprocessRequest): Promise { + return this.sendPost({ path: '/construction/preprocess', body }) + } + + getTransactionConstructionMetadata( + body: ConstructionMetadataRequest, + ): Promise { + return this.sendPost({ path: '/construction/metadata', body }) + } + + generateUnsignedTransactionAndSigningPayloads( + body: ConstructionPayloadsRequest, + ): Promise { + return this.sendPost({ path: '/construction/payloads', body }) + } + + createNetworkTransaction(body: ConstructionCombineRequest): Promise { + return this.sendPost({ path: '/construction/combine', body }) + } + + parseTransaction(body: ConstructionParseRequest): Promise { + return this.sendPost({ + path: '/construction/parse', + body, + }) + } + + getHashOfTransaction(body: ConstructionHashRequest): Promise { + return this.sendPost({ path: '/construction/hash', body }) + } + + submitTransaction(body: TransactionSubmissionRequest): Promise { + return this.sendPost({ path: '/construction/submit', body }) + } + + call(body: CallRequest): Promise { + return this.sendPost({ path: '/call', body }) + } + + getEventsBlocks(body: EventsBlocksRequest): Promise { + return this.sendPost({ path: '/events/blocks', body }) + } + + searchTransactions(body: SearchTransactionsRequest): Promise { + return this.sendPost({ path: '/search/transactions', body }) + } +} diff --git a/src/service/rpc/other/CardanoLoadBalancerRpc.ts b/src/service/rpc/other/CardanoLoadBalancerRpc.ts new file mode 100644 index 0000000000..d8cec089ef --- /dev/null +++ b/src/service/rpc/other/CardanoLoadBalancerRpc.ts @@ -0,0 +1,34 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Container, Service } from 'typedi' +import { PostI } from '../../../dto/PostI' +import { CardanoRpcSuite } from '../../../dto/rpc/CardanoRpcSuite' +// Need to import like this to keep browser working +import { LoadBalancer } from '../generic/LoadBalancer' +import { AbstractCardanoRpc } from './AbstractCardanoRpc' + +@Service({ + factory: (data: { id: string }) => { + return new CardanoLoadBalancerRpc(data.id) + }, + transient: true, +}) +export class CardanoLoadBalancerRpc extends AbstractCardanoRpc implements CardanoRpcSuite { + protected readonly loadBalancer: LoadBalancer + + constructor(id: string) { + super() + this.loadBalancer = Container.of(id).get(LoadBalancer) + } + + public destroy() { + this.loadBalancer.destroy() + } + + getRpcNodeUrl(): string { + return this.loadBalancer.getActiveNormalUrlWithFallback().url + } + + protected post(post: PostI): Promise { + return this.loadBalancer.post(post) + } +} diff --git a/src/service/tatum/tatum.other.ts b/src/service/tatum/tatum.other.ts index 21c98b171c..6260383043 100644 --- a/src/service/tatum/tatum.other.ts +++ b/src/service/tatum/tatum.other.ts @@ -3,6 +3,7 @@ import { SolanaRpcSuite, TezosRpcInterface, TronRpcSuite, XrpRpcInterface } from import { AlgorandAlgodRpcSuite } from '../../dto/rpc/AlgorandAlgodRpcSuite' import { AlgorandIndexerRpcSuite } from '../../dto/rpc/AlgorandIndexerRpcSuite' import { BnbRpcSuite } from '../../dto/rpc/BnbRpcSuite' +import { CardanoRpcSuite } from '../../dto/rpc/CardanoRpcSuite' import { EosRpcSuite } from '../../dto/rpc/EosRpcSuite' import { CONFIG, Utils } from '../../util' import { Address, AddressTezos, AddressTron } from '../address' @@ -118,6 +119,15 @@ export class AlgorandIndexer extends BaseOther { } } +export class CardanoRosetta extends BaseOther { + rpc: CardanoRpcSuite + + constructor(id: string) { + super(id) + this.rpc = Utils.getRpc(id, Container.of(id).get(CONFIG)) + } +} + export class FullSdk extends TatumSdkChain { notification: Notification nft: Nft diff --git a/src/util/constant.ts b/src/util/constant.ts index 698657294c..797aaf447a 100644 --- a/src/util/constant.ts +++ b/src/util/constant.ts @@ -123,8 +123,8 @@ export const Constant = { [Network.ARBITRUM_NOVA]: 18, [Network.AURORA]: 18, [Network.AURORA_TESTNET]: 18, - [Network.CARDANO]: 6, - [Network.CARDANO_PREPROD]: 6, + [Network.CARDANO_ROSETTA]: 6, + [Network.CARDANO_ROSETTA_PREPROD]: 6, [Network.GNOSIS]: 18, [Network.GNOSIS_TESTNET]: 18, [Network.FLOW]: 8, @@ -219,8 +219,8 @@ export const Constant = { [Network.ARBITRUM_NOVA]: 'ARB', [Network.AURORA]: 'AURA', [Network.AURORA_TESTNET]: 'AURA', - [Network.CARDANO]: 'ADA', - [Network.CARDANO_PREPROD]: 'ADA', + [Network.CARDANO_ROSETTA]: 'ADA', + [Network.CARDANO_ROSETTA_PREPROD]: 'ADA', [Network.GNOSIS]: 'GNO', [Network.GNOSIS_TESTNET]: 'GNO', [Network.FLOW]: 'FLOW', @@ -275,7 +275,7 @@ export const Constant = { Network.BINANCE_SMART_CHAIN, Network.BITCOIN, Network.BITCOIN_CASH, - Network.CARDANO, + Network.CARDANO_ROSETTA, Network.CELO, Network.CRONOS, Network.DOGECOIN, @@ -318,7 +318,7 @@ export const Constant = { Network.BINANCE_SMART_CHAIN_TESTNET, Network.BITCOIN_TESTNET, Network.BITCOIN_CASH_TESTNET, - Network.CARDANO_PREPROD, + Network.CARDANO_ROSETTA_PREPROD, Network.CELO_ALFAJORES, Network.CRONOS_TESTNET, Network.DOGECOIN_TESTNET, diff --git a/src/util/util.shared.ts b/src/util/util.shared.ts index 9104d2e028..689231af52 100644 --- a/src/util/util.shared.ts +++ b/src/util/util.shared.ts @@ -7,6 +7,7 @@ import { isAlgorandAlgodNetwork, isAlgorandIndexerNetwork, isBnbLoadBalancerNetwork, + isCardanoNetwork, isEosLoadBalancerNetwork, isEosNetwork, isEvmArchiveNonArchiveBeaconLoadBalancerNetwork, @@ -44,6 +45,7 @@ import { Bitcoin, BitcoinCash, Bnb, + CardanoRosetta, Celo, Chiliz, Cronos, @@ -86,6 +88,7 @@ import { TronRpc } from '../service/rpc/evm/TronRpc' import { AlgorandAlgodLoadBalancerRpc } from '../service/rpc/other/AlgorandAlgodLoadBalancerRpc' import { AlgorandIndexerLoadBalancerRpc } from '../service/rpc/other/AlgorandIndexerLoadBalancerRpc' import { BnbLoadBalancerRpc } from '../service/rpc/other/BnbLoadBalancerRpc' +import { CardanoLoadBalancerRpc } from '../service/rpc/other/CardanoLoadBalancerRpc' import { EosLoadBalancerRpc } from '../service/rpc/other/EosLoadBalancerRpc' import { EosRpc } from '../service/rpc/other/EosRpc' import { SolanaLoadBalancerRpc } from '../service/rpc/other/SolanaLoadBalancerRpc' @@ -101,6 +104,10 @@ export const Utils = { getRpc: (id: string, config: TatumConfig): T => { const { network } = config + if (isCardanoNetwork(network)) { + return Container.of(id).get(CardanoLoadBalancerRpc) as T + } + if (isAlgorandIndexerNetwork(network)) { return Container.of(id).get(AlgorandIndexerLoadBalancerRpc) as T } @@ -228,6 +235,15 @@ export const Utils = { } } + if (isCardanoNetwork(network)) { + return { + network_identifier: { + blockchain: 'cardano', + network: Network.CARDANO_ROSETTA === network ? 'mainnet' : 'preprod', + }, + } + } + if ( isEosNetwork(network) || isTezosNetwork(network) || @@ -252,6 +268,10 @@ export const Utils = { return `${url}health` } + if (isCardanoNetwork(network)) { + return `${url}network/status` + } + if (isSameGetBlockNetwork(network)) { return url } @@ -297,6 +317,10 @@ export const Utils = { return new BigNumber((response['round'] as number) || -1).toNumber() } + if (isCardanoNetwork(network)) { + return new BigNumber((response.current_block_identifier.index as number) || -1).toNumber() + } + throw new Error(`Network ${network} is not supported.`) }, isResponseOk: (network: Network, response: JsonRpcResponse | any) => { @@ -324,6 +348,10 @@ export const Utils = { return response['round'] !== undefined } + if (isCardanoNetwork(network)) { + return response.current_block_identifier.index !== undefined + } + throw new Error(`Network ${network} is not supported.`) }, mapNotificationChainToNetwork: (chain: AddressEventNotificationChain): Network => { @@ -620,6 +648,9 @@ export const Utils = { case Network.ALGORAND_INDEXER: case Network.ALGORAND_INDEXER_TESTNET: return new AlgorandIndexer(id) as T + case Network.CARDANO_ROSETTA: + case Network.CARDANO_ROSETTA_PREPROD: + return new CardanoRosetta(id) as T default: return new FullSdk(id) as T }