From bdd60c61fcc2fcf665660b79df16cdac1108a22c Mon Sep 17 00:00:00 2001 From: Lukas Kotol Date: Mon, 8 Apr 2024 12:27:23 +0200 Subject: [PATCH] ALL-5089 Add IOTA rpc --- README.md | 12 +- package.json | 2 +- src/dto/Currency.ts | 1 + src/dto/Network.ts | 10 + src/dto/rpc/IotaRpcSuite.ts | 1368 +++++++++++++++++ src/e2e/rpc/evm/tatum.rpc.iota.spec.ts | 29 + src/e2e/rpc/other/tatum.rpc.solana.spec.ts | 4 +- src/service/rpc/evm/AbstractBeaconV1EvmRpc.ts | 10 +- src/service/rpc/generic/LoadBalancer.ts | 18 + .../rpc/other/AbstractAlgorandAlgodRpc.ts | 14 +- .../rpc/other/AbstractAlgorandIndexerRpc.ts | 8 +- src/service/rpc/other/AbstractIotaRpc.ts | 708 +++++++++ src/service/rpc/other/AbstractKadenaRpc.ts | 12 +- src/service/rpc/other/AbstractStellarRpc.ts | 14 +- src/service/rpc/other/AbstractTezosRpc.ts | 14 +- src/service/rpc/other/IotaLoadBalancerRpc.ts | 47 + src/service/tatum/tatum.other.ts | 10 + src/util/constant.ts | 2 + src/util/util.shared.ts | 47 +- 19 files changed, 2303 insertions(+), 27 deletions(-) create mode 100644 src/dto/rpc/IotaRpcSuite.ts create mode 100644 src/e2e/rpc/evm/tatum.rpc.iota.spec.ts create mode 100644 src/service/rpc/other/AbstractIotaRpc.ts create mode 100644 src/service/rpc/other/IotaLoadBalancerRpc.ts diff --git a/README.md b/README.md index 4015898d54..28eec574d5 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Need help or want to contribute? [Report Bugs](https://github.com/tatumio/tatum- 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) | @@ -53,6 +53,8 @@ Interact seamlessly with various blockchains through native RPC calls. Say goodb | [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) | | [XinFin RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/xinfin-rpc-documentation) | +| [Fantom RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/fantom-rpc-documentation) | +| [Base RPC](https://docs.tatum.io/docs/rpc/evm-blockchains/base-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) | @@ -68,6 +70,10 @@ Interact seamlessly with various blockchains through native RPC calls. Say goodb | [Agorand RPC](https://docs.tatum.io/docs/rpc/algo-rpc-documentation) | | [Cardano RPC](https://docs.tatum.io/docs/rpc/cardano-rpc-documentation) | | [Stellar RPC](https://docs.tatum.io/docs/rpc/stellar-rpc-documentation) | +| [Iota RPC](https://docs.tatum.io/docs/rpc/iota-rpc-documentation) | +| [Kadena RPC](https://docs.tatum.io/docs/rpc/kadena-rpc-documentation) | +| [Rostrum RPC](https://docs.tatum.io/docs/rpc/rostrum-rpc-documentation) | +| [Cronos RPC](https://docs.tatum.io/docs/rpc/cronos-rpc-documentation) | ### 🔔 Create Notifications @@ -487,7 +493,8 @@ This section provides a list of various blockchain network status pages, powered | [kadena-testnet.status.tatum.io](https://kadena-testnet.status.tatum.io) | | [rostrum-mainnet.status.tatum.io](https://rostrum-mainnet.status.tatum.io) | | [cronos-mainnet-archive.status.tatum.io](https://cronos-mainnet-archive.status.tatum.io) | -| [fantom-mainnet-archive.status.tatum.io](https://fantom-mainnet-archive.status.tatum.io) | +| [fantom-mainnet-archive.status.tatum.io](https://fantom-mainnet-archive.status.tatum.io) | +| [iota-mainnet.status.tatum.io](https://iota-mainnet.status.tatum.io) | ### Load Balancer @@ -605,6 +612,7 @@ Here are the list of nodes for each blockchain: | [rpc.tatum.io/rostrum-mainnet/list.json](https://rpc.tatum.io/rostrum-mainnet/list.json) | | [rpc.tatum.io/cronos-mainnet-archive/list.json](https://rpc.tatum.io/cronos-mainnet-archive/list.json) | | [rpc.tatum.io/fantom-mainnet-archive/list.json](https://rpc.tatum.io/fantom-mainnet-archive/list.json) | +| [rpc.tatum.io/iota-mainnet/list.json](https://rpc.tatum.io/iota-mainnet/list.json) | Following pattern defines the URL for fetching the list of nodes: diff --git a/package.json b/package.json index 4c7cbafc04..d42964ffab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@tatumio/tatum", - "version": "4.2.21", + "version": "4.2.22", "description": "Tatum JS SDK", "author": "Tatum", "repository": "https://github.com/tatumio/tatum-js", diff --git a/src/dto/Currency.ts b/src/dto/Currency.ts index bbb5f0ef88..10e6cc04f0 100644 --- a/src/dto/Currency.ts +++ b/src/dto/Currency.ts @@ -50,6 +50,7 @@ export enum Currency { BASE = 'BASE', KADENA = 'KADENA', ATOM = 'ATOM', + IOTA = 'IOTA', } export function networkToCurrency(network: Network): Currency { diff --git a/src/dto/Network.ts b/src/dto/Network.ts index 45c5a26879..de82df2dcf 100644 --- a/src/dto/Network.ts +++ b/src/dto/Network.ts @@ -31,6 +31,7 @@ export enum Network { GNOSIS = 'gno-mainnet', HAQQ = 'haqq-mainnet', HARMONY_ONE_SHARD_0 = 'one-mainnet-s0', + IOTA = 'iota-mainnet', KADENA = 'kadena-mainnet', KLAYTN = 'klaytn-mainnet', KUCOIN = 'kcs-mainnet', @@ -253,6 +254,7 @@ 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 IOTA_LOAD_BALANCER_NETWORKS = [Network.IOTA] export const LOAD_BALANCER_NETWORKS = [ ...UTXO_LOAD_BALANCER_NETWORKS, @@ -271,6 +273,7 @@ export const LOAD_BALANCER_NETWORKS = [ ...STELLAR_LOAD_BALANCER_NETWORKS, ...KADENA_LOAD_BALANCER_NETWORKS, ...ROSTRUM_LOAD_BALANCER_NETWORKS, + ...IOTA_LOAD_BALANCER_NETWORKS, ] export const EVM_ARCHIVE_NON_ARCHIVE_LOAD_BALANCER_NETWORKS = [ @@ -361,6 +364,8 @@ 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 isSameGetBlockNetwork = (network: Network) => isUtxoBasedNetwork(network) || isEvmBasedNetwork(network) || @@ -908,4 +913,9 @@ export const NETWORK_METADATA: Record = { currency: Currency.BCH, testnet: false, }, + [Network.IOTA]: { + currency: Currency.IOTA, + testnet: false, + defaultMainnet: true, + }, } diff --git a/src/dto/rpc/IotaRpcSuite.ts b/src/dto/rpc/IotaRpcSuite.ts new file mode 100644 index 0000000000..2d83737bb1 --- /dev/null +++ b/src/dto/rpc/IotaRpcSuite.ts @@ -0,0 +1,1368 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +// Core interfaces + +export interface Milestone { + index: number + timestamp: number + milestoneId: string +} + +export interface Status { + isHealthy: boolean + latestMilestone: Milestone + confirmedMilestone: Milestone + pruningIndex: number +} + +export interface Metrics { + blocksPerSecond: number + referencedBlocksPerSecond: number + referencedRate: number +} + +export interface RentStructure { + vByteCost: number + vByteFactorData: number + vByteFactoKey: number +} + +export interface Protocol { + networkName: string + bech32Hrp: string + tokenSupply: string + version: number + minPowScore: number + belowMaxDepth: number + rentStructure: RentStructure +} + +export interface PendingProtocolParameter { + type: number + targetMilestoneIndex: number + protocolVersion: number + params: string +} + +export interface BaseToken { + name: string + tickerSymbol: string + unit: string + decimals: number + subunit: string + useMetricPrefix: boolean +} + +export interface NodeInfo { + name: string + version: string + status: Status + metrics: Metrics + supportedProtocolVersions: number[] + protocol: Protocol + pendingProtocolParameters: PendingProtocolParameter[] + baseToken: BaseToken + features: string[] +} + +export interface Block { + protocolVersion: number + parents: string[] + payload?: TransactionPayload | MilestonePayload | TaggedDataPayload | ReceiptPayload + nonce: string +} + +export interface TransactionPayload { + type: number // Set to value 6 to denote a Transaction Payload. + essence: TransactionEssence + unlocks: Array +} + +export interface TransactionEssence { + type: number + networkId: string + inputsCommitment: string + inputs: UTXOInput[] // Assume UTXOInput is an existing interface + outputs: (BasicOutput | AliasOutput | FoundryOutput | NFTOutput)[] // Assume these are existing interfaces + payload?: TaggedDataPayload // Assume TaggedDataPayload is an existing interface +} + +export interface UTXOInput { + type: number + transactionId: string + transactionOutputIndex: number +} + +export interface BasicOutput { + type: number // Set to value 3 to denote a Basic Output. + amount: string // The amount of IOTA tokens to deposit with this BasicOutput output. Plain string encoded number. + nativeTokens?: NativeToken[] // Native tokens held by the output. + unlockConditions: ( + | AddressUnlockCondition + | StorageDepositReturnUnlockCondition + | TimelockUnlockCondition + | ExpirationUnlockCondition + )[] // Unlock conditions that define how the output can be unlocked in a transaction. + features?: (SenderFeature | MetadataFeature | TagFeature)[] // Features that add utility to the output but do not impose unlocking conditions. +} + +export interface AliasOutput { + type: number + amount: string + nativeTokens?: NativeToken[] + aliasId: string + stateIndex: number + stateMetadata?: string + foundryCounter: number + unlockConditions: (StateControllerAddressUnlockCondition | GovernorAddressUnlockCondition)[] + features?: (SenderFeature | MetadataFeature)[] + immutableFeatures?: (IssuerFeature | MetadataFeature)[] +} + +export interface FoundryOutput { + type: number + amount: string + nativeTokens?: NativeToken[] + serialNumber: number + tokenScheme: SimpleTokenScheme[] + unlockConditions: ImmutableAliasAddressUnlockCondition[] + features?: MetadataFeature[] + immutableFeatures?: MetadataFeature[] +} + +export interface NFTOutput { + type: number + amount: string + nativeTokens?: NativeToken[] + nftId: string + unlockConditions: ( + | AddressUnlockCondition + | StorageDepositReturnUnlockCondition + | TimelockUnlockCondition + | ExpirationUnlockCondition + )[] + features?: (SenderFeature | IssuerFeature | MetadataFeature | TagFeature)[] + immutableFeatures?: (IssuerFeature | MetadataFeature)[] +} + +export interface NativeToken { + id: string + amount: string +} + +export interface Ed25519Address { + type: number + pubKeyHash: string +} + +export interface AliasAddress { + type: number + aliasId: string +} + +export interface NFTAddress { + type: number + nftId: string +} + +export interface AddressUnlockCondition { + type: number + address: Ed25519Address | AliasAddress | NFTAddress +} + +export interface ImmutableAliasAddressUnlockCondition { + type: number + address: AliasAddress +} + +export interface StorageDepositReturnUnlockCondition { + type: number + returnAddress: Ed25519Address | AliasAddress | NFTAddress + returnAmount: string +} + +export interface TimelockUnlockCondition { + type: number + unixTime: number +} + +export interface ExpirationUnlockCondition { + type: number + returnAddress: Ed25519Address | AliasAddress | NFTAddress + unixTime: number +} + +export interface StateControllerAddressUnlockCondition { + type: number + address: Ed25519Address | AliasAddress | NFTAddress +} + +export interface GovernorAddressUnlockCondition { + type: number + address: Ed25519Address | AliasAddress | NFTAddress +} + +export interface SenderFeature { + type: number + address: Ed25519Address | AliasAddress | NFTAddress +} + +export interface IssuerFeature { + type: number + address: Ed25519Address | AliasAddress | NFTAddress +} + +export interface MetadataFeature { + type: number + data: string +} + +export interface TagFeature { + type: number + tag: string +} + +export interface SimpleTokenScheme { + type: number + mintedTokens: string + meltedTokens: string + maxSupply: string +} + +export interface SignatureUnlock { + type: number + signature: Ed25519Signature +} + +export interface Ed25519Signature { + type: number // Set to value 0 to denote an Ed25519 Signature. + publicKey: string // The public key of the Ed25519 keypair which is used to verify the signature. Hex-encoded with 0x prefix. + signature: string // The signature signing the serialized Transaction Essence. Hex-encoded with 0x prefix. +} + +export interface ReferenceUnlock { + type: number + reference: number +} + +export interface AliasUnlock { + type: number // Set to value 2 to denote an Alias Unlock. + reference: number // Represents the index of a previous unlock. +} + +export interface NFTUnlock { + type: number + reference: number +} + +export interface MilestonePayload { + type: number + index: number + timestamp: number + protocolVersion: number + previousMilestoneId: string + parents: string[] + inclusionMerkleRoot: string + appliedMerkleRoot: string + options: (ReceiptPayload | ProtocolParamsMilestoneOpt)[] + metadata?: string + signatures: Ed25519Signature[] +} + +export interface TaggedDataPayload { + type: number + tag?: string + data?: string +} + +export interface TreasuryTransactionPayload { + type: number + input: TreasuryInput + output: TreasuryOutput +} + +export interface TreasuryInput { + type: number + milestoneId: string +} + +export interface TreasuryOutput { + type: number + amount: string +} + +export interface Peer { + id: string + multiAddresses: string[] + alias?: string + relation: 'known' | 'unknown' | 'autopeered' + connected: boolean + gossip?: Gossip +} + +export interface Gossip { + heartbeat: Heartbeat | null + metrics: Metrics +} + +export interface Heartbeat { + solidMilestoneIndex: number + prunedMilestoneIndex: number + latestMilestoneIndex: number + connectedNeighbors: number + syncedNeighbors: number +} + +export interface Metrics { + newBlocks: number + knownBlocks: number + receivedBlocks: number + receivedBlockRequests: number + receivedMilestoneRequests: number + receivedHeartbeats: number + sentBlocks: number + sentBlockRequests: number + sentMilestoneRequests: number + sentHeartbeats: number + droppedPackets: number +} + +export interface ProtocolParamsMilestoneOpt { + type: number + targetMilestoneIndex: number + protocolVersion: number + params: string +} + +export interface ReceiptTuple { + receipt: ReceiptPayload + milestoneIndex: number +} + +export interface ReceiptPayload { + type: number + migratedAt: number + final: boolean + funds: MigratedFundsEntry[] + transaction: TreasuryTransactionPayload +} + +export interface MigratedFundsEntry { + tailTransactionHash: string + address: Ed25519Address + deposit: number +} + +export interface ErrorFormat { + error: { + code: string + message: string + } +} + +export interface TipsResponse { + tips: string[] +} + +export interface SubmitBlock { + protocolVersion: number + parents?: string[] + payload?: + | TransactionPayload + | MilestonePayload + | TaggedDataPayload + | TreasuryTransactionPayload + | ReceiptPayload + nonce?: string +} + +export interface BlockIdentifier { + blockId: string +} + +export interface BlockMetadata { + blockId: string + parents: string[] + isSolid: boolean + referencedByMilestoneIndex?: number + milestoneIndex?: number + ledgerInclusionState?: 'included' | 'conflicting' | 'noTransaction' + conflictReason?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 255 + whiteFlagIndex?: number + shouldPromote?: boolean + shouldReattach?: boolean +} + +export interface OutputResponse { + metadata: OutputMetadata + output: BasicOutput | AliasOutput | FoundryOutput | NFTOutput +} + +export interface OutputMetadata { + blockId: string + transactionId: string + outputIndex: number + isSpent: boolean + milestoneIndexSpent?: number + milestoneTimestampSpent?: number + transactionIdSpent?: string + milestoneIndexBooked: number + milestoneTimestampBooked: number + ledgerIndex: number +} + +export interface ReceiptsResponse { + receipts: ReceiptTuple[] +} + +export interface TreasuryResponse { + milestoneId: string + amount: string +} + +export interface UTXOChanges { + index: number + createdOutputs: string[] + consumedOutputs: string[] +} + +export type PeerResponse = Array + +export interface AddPeerRequest { + multiAddress: string + alias?: string +} + +export interface ComputeWhiteFlagRequest { + index: number + timestamp: number + parents: string[] + previousMilestoneId: string +} + +export interface ComputedMerkleRootResult { + inclusionMerkleRoot: string + appliedMerkleRoot: string +} + +export interface PruneDatabaseRequest { + index: number + depth: number + targetDatabaseSize: string +} + +export interface PruneDatabaseResponse { + index: number +} + +export interface CreateSnapshotsRequest { + index: number +} + +export interface CreateSnapshotsResponse { + index: number + filePath: string +} + +// Indexer interfaces +export interface OutputIdResponse { + ledgerIndex: number + cursor?: string | null + items: string[] +} + +export interface OutputSearchParams { + unlockableByAddress?: string + hasNativeTokens?: boolean + minNativeTokenCount?: number + maxNativeTokenCount?: number + createdBefore?: number + createdAfter?: number + pageSize?: number + cursor?: string +} + +export interface BasicOutputSearchParams extends OutputSearchParams { + address?: string + hasStorageDepositReturn?: boolean + storageDepositReturnAddress?: string + hasTimelock?: boolean + timelockedBefore?: number + timelockedAfter?: number + hasExpiration?: boolean + expiresBefore?: number + expiresAfter?: number + expirationReturnAddress?: string + sender?: string + tag?: string +} + +export interface AliasOutputSearchParams extends OutputSearchParams { + stateController?: string + governor?: string + issuer?: string + sender?: string +} + +export interface FoundryOutputsFilterParams extends OutputSearchParams { + aliasAddress?: string +} + +export interface NftOutputSearchParams extends BasicOutputSearchParams { + issuer?: string +} + +// Explorer interfaces + +export interface Balance { + totalBalance: string + availableBalance: string + ledgerIndex: number +} + +export interface BlockChildrenResponse { + blockId: string + maxResults?: number + count?: number + children?: string[] +} + +export interface LedgerUpdate { + address: string + outputId: string + isSpent: boolean +} + +export interface LedgerUpdateList { + milestoneIndex: number + items: LedgerUpdate[] + cursor?: string +} + +export interface LedgerUpdates { + address: string + items: Array<{ + milestoneIndex: number + milestoneTimestamp: number + outputId: string + isSpent: boolean + }> + cursor?: string +} + +export interface PagedMilestones { + items: { + milestoneId: string + index: number + }[] + cursor?: string +} + +export interface PagedBlockIdsByMilestone { + blocks: { + blockId: string + payloadType?: number + }[] + cursor?: string +} + +export interface RichestAddressesStatistics { + top: { + address: Ed25519Address | AliasAddress | NFTAddress + balance: string + }[] +} + +export interface WealthDistributionStatistics { + distribution: { + range: { + start: number + end: number + } + addressCount: string + totalBalance: string + }[] +} + +export interface MilestonesParams { + startTimestamp?: number + endTimestamp?: number + sort?: string + pageSize?: number + cursor?: string +} + +export interface BlocksByMilestoneParams { + milestoneId: string + sort?: string + pageSize?: number + cursor?: string +} + +export interface BlocksByMilestoneIndexParams { + milestoneIndex: number + sort?: string + pageSize?: number + cursor?: string +} + +export interface LedgerUpdatesByAddressParams { + address: string + pageSize?: number + sort?: 'asc' | 'desc' + startMilestoneIndex?: number + cursor?: string +} + +export interface LedgerUpdatesByMilestoneParams { + milestoneId: string + pageSize?: number + cursor?: string +} + +export interface TopRichestAddressesParams { + ledgerIndex?: number + top?: number +} + +// Wasp methods + +export interface AccountFoundriesResponse { + foundrySerialNumbers: number[] +} + +export interface AccountListResponse { + accounts: string[] +} + +export interface AccountNFTsResponse { + nftIds: string[] +} + +export interface AccountNonceResponse { + nonce: string +} + +export interface AddUserRequest { + password: string + permissions: string[] + username: string +} + +export interface AssetsJSON { + baseTokens: string + nativeTokens: NativeTokenJSON[] + nfts: string[] +} + +export interface AssetsResponse { + baseTokens: string + nativeTokens: NativeTokenJSON[] +} + +export interface AuthInfoModel { + authURL: string + scheme: string +} + +export interface Blob { + hash: string + size: number +} + +export interface BlobInfoResponse { + fields: { + [key: string]: number + } +} + +export interface BlobListResponse { + Blobs: Blob[] +} + +export interface BlobValueResponse { + valueData: string +} + +export interface BlockInfoResponse { + blockIndex: number + numSuccessfulRequests: number + gasFeeCharged: string + previousAliasOutput: string + gasBurned: string + totalRequests: number + numOffLedgerRequests: number + timestamp: string +} + +export interface BurnRecord { + code: number + gasBurned: number +} + +export interface CallTargetJSON { + contractHName: string + functionHName: string +} + +export interface ChainInfoResponse { + chainOwnerId: string + metadata: PublicChainMetadata + gasLimits: Limits + chainID: string + evmChainId: number + publicURL: string + gasFeePolicy: FeePolicy + isActive: boolean +} + +export interface LastMessageGovernanceTx { + txId: string +} + +export interface LastMessageStateTx { + stateIndex: number + txId: string +} + +export interface LastMessageTxInclusionState { + txId: string + state: string +} + +export interface LastMessageOnLedgerRequest { + output: Output + outputId: string + raw: string + id: string +} + +export interface LastMessageOutput { + output: Output + outputId: string +} + +export interface LastMessageAliasOutput { + outputType: number + raw: string +} + +interface LastMessagePullTxInclusionState { + txId: string +} + +export interface LastMessagePullOutputByID { + outputId: string +} + +export interface ChainMessageMetrics { + outPublishGovernanceTransaction: OutPublishGovernanceTransaction + outPublisherStateTransaction: OutPublisherStateTransaction + inTxInclusionState: InTxInclusionState + inOnLedgerRequest: InOnLedgerRequest + inOutput: InOutput + inAliasOutput: InAliasOutput + inStateOutput: InStateOutput + outPullTxInclusionState: OutPullTxInclusionState + outPullLatestOutput: OutPullLatestOutput + outPullOutputByID: OutPullOutputByID +} + +export interface OutPublishGovernanceTransaction { + lastMessage: LastMessageGovernanceTx + messages: number + timestamp: string +} + +export interface OutPublisherStateTransaction { + lastMessage: LastMessageStateTx + messages: number + timestamp: string +} + +export interface InTxInclusionState { + lastMessage: LastMessageTxInclusionState + messages: number + timestamp: string +} + +export interface InOnLedgerRequest { + lastMessage: LastMessageOnLedgerRequest + messages: number + timestamp: string +} + +export interface InOutput { + lastMessage: LastMessageOutput + messages: number + timestamp: string +} + +export interface InAliasOutput { + lastMessage: LastMessageAliasOutput + messages: number + timestamp: string +} + +export interface InStateOutput { + lastMessage: LastMessageOutput + messages: number + timestamp: string +} + +export interface OutPullTxInclusionState { + lastMessage: LastMessagePullTxInclusionState + messages: number + timestamp: string +} + +export interface OutPullLatestOutput { + lastMessage: string + messages: number + timestamp: string +} + +export interface OutPullOutputByID { + lastMessage: LastMessagePullOutputByID + messages: number + timestamp: string +} + +export interface ChainRecord extends ChainIDParam { + accessNodes: string[] + isActive: boolean +} + +export interface CommitteeNode { + accessAPI: string + node: PeeringNodeStatusResponse +} + +export interface ConsensusPipeMetrics { + eventACSMsgPipeSize: number + eventPeerLogIndexMsgPipeSize: number + eventStateTransitionMsgPipeSize: number + eventTimerMsgPipeSize: number + eventVMResultMsgPipeSize: number +} + +export interface ConsensusWorkflowMetrics { + currentStateIndex: number + flagBatchProposalSent: boolean + flagConsensusBatchKnown: boolean + flagInProgress: boolean + flagStateReceived: boolean + flagTransactionFinalized: boolean + flagTransactionPosted: boolean + flagTransactionSeen: boolean + flagVMResultSigned: boolean + flagVMStarted: boolean + timeBatchProposalSent: string + timeCompleted: string + timeConsensusBatchKnown: string + timeTransactionFinalized: string + timeTransactionPosted: string + timeTransactionSeen: string + timeVMResultSigned: string + timeVMStarted: string +} + +export interface ContractCallViewRequest { + arguments: JSONDict + block: string + contractHName: string + contractName: string + functionHName: string + functionName: string +} + +export interface ContractInfoResponse { + hName: string + name: string + programHash: string +} + +export interface ControlAddressesResponse { + governingAddress: string + sinceBlockIndex: number + stateAddress: string +} + +export interface DKSharesInfo { + address: string + peerIdentities: string[] + peerIndex: number + publicKey: string + publicKeyShares: string[] + threshold: number +} + +export interface DKSharesPostRequest { + peerIdentities: string[] + threshold: number + timeoutMS: number +} + +export interface ErrorMessageFormatResponse { + messageFormat: string +} + +export interface EstimateGasRequestOffledger extends ChainIDParam { + fromAddress: string + requestBytes: string +} + +export interface EstimateGasRequestOnledger extends ChainIDParam { + outputBytes: string +} + +export interface EventJSON { + contractID: number + payload: string + timestamp: number + topic: string +} + +export interface EventsResponse { + events: EventJSON[] +} + +export interface FeePolicy { + evmGasRatio: Ratio32 + gasPerToken: Ratio32 + validatorFeeShare: number +} + +export interface FoundryOutputResponse { + assets: AssetsResponse + foundryId: string +} + +export interface GovAllowedStateControllerAddressesResponse { + addresses: string[] +} + +export interface GovChainInfoResponse { + chainOwnerId: string + metadata: GovPublicChainMetadata + gasLimits: Limits + chainID: string + publicURL: string + gasFeePolicy: FeePolicy +} + +export interface GovChainOwnerResponse { + chainOwner: string +} + +export interface GovPublicChainMetadata { + description: string + evmJsonRpcURL: string + evmWebSocketURL: string + name: string + website: string +} + +export interface InOutput { + output: Output + outputId: string +} + +export interface InStateOutput { + output: Output + outputId: string +} + +export interface InfoResponse { + peeringURL: string + l1Params: L1Params + publicKey: string + version: string +} + +export interface L1Params { + protocol: Protocol + maxPayloadSize: number + baseToken: BaseToken +} + +export interface Item { + key: string + value: string +} + +export interface Item { + key: string + value: string +} + +export interface JSONDict { + Items: Item[] +} + +export interface JSONDict { + Items: Item[] +} + +export interface L1Params { + baseToken: BaseToken + maxPayloadSize: number + protocol: ProtocolParameters +} + +export interface Limits { + maxGasExternalViewCall: number + maxGasPerBlock: number + maxGasPerRequest: number + minGasPerRequest: number +} + +interface LoginRequest { + password: string + username: string +} + +export interface LoginResponse { + error: string + jwt: string +} + +export interface NFTJSON { + id: string + issuer: string + metadata: string + owner: string +} + +export interface NativeTokenIDRegistryResponse { + nativeTokenRegistryIds: string[] +} + +export interface NativeTokenJSON { + amount: string + id: string +} + +export interface NodeMessageMetrics { + registeredChainIDs: string[] +} + +export interface NodeOwnerCertificateResponse { + certificate: string +} + +export interface OffLedgerRequest { + chainId: string + request: string +} + +export interface Output { + outputType: number + raw: string +} + +export interface PeeringNodeIdentityResponse { + isTrusted: boolean + name: string + peeringURL: string + publicKey: string +} + +export interface PeeringNodeStatusResponse { + isAlive: boolean + isTrusted: boolean + name: string + numUsers: number + peeringURL: string + publicKey: string +} + +export interface PeeringTrustRequest { + name: string + peeringURL: string + publicKey: string +} + +export interface ProtocolParameters { + bech32Hrp: string + belowMaxDepth: number + minPowScore: number + networkName: string + rentStructure: RentStructure + tokenSupply: string + version: number +} + +export interface PublicChainMetadata { + description: string + evmJsonRpcURL: string + evmWebSocketURL: string + name: string + website: string +} + +export interface WaitForRequestParams { + chainID: string + requestID: string + timeoutSeconds?: number + waitForL1Confirmation?: boolean +} + +export interface Ratio32 { + a: number + b: number +} + +export interface ReceiptResponse { + blockIndex: number + errorMessage?: string + gasBudget: string + gasBurnLog: BurnRecord[] + gasBurned: string + gasFeeCharged: string + request: RequestJSON + requestIndex: number + storageDepositCharged: string + rawError?: UnresolvedVMErrorJSON +} + +export interface RequestIDsResponse { + requestIds: string[] +} + +export interface RequestJSON { + allowance: AssetsJSON + callTarget: CallTargetJSON + fungibleTokens: AssetsJSON + gasBudget: string + isEVM: boolean + isOffLedger: boolean + nft: NFTJSON + params: JSONDict + requestId: string + senderAccount: string + targetAddress: string +} + +export interface RequestProcessedResponse { + chainId: string + isProcessed: boolean + requestId: string +} + +export interface StateResponse { + state: string +} + +export interface UnresolvedVMErrorJSON { + code: string + params: string[] +} + +export interface UpdateUserPasswordRequest { + password: string + username: string +} + +export interface UpdateUserPermissionsRequest { + permissions: string[] + username: string +} + +export interface User { + permissions: string[] + username: string +} + +export interface VersionResponse { + version: string +} + +export interface ChainIDParam { + chainID: string +} + +export interface ChainIDAndBlockParam extends ChainIDParam { + block?: string +} + +export interface ChainIDAndPeerParam extends ChainIDParam { + peer: string +} + +export interface ChainIDAndRequestIDParam extends ChainIDParam { + requestID: string +} + +export interface ChainIDAndBlockIndexParam extends ChainIDParam { + blockIndex: number + block?: string +} + +export interface ChainIDAndSerialNumberParam extends ChainIDParam { + serialNumber: number + block?: string +} + +export interface ChainIDAndBlobHashParam extends ChainIDParam { + blobHash: string + fieldKey?: string + block?: string +} + +export interface ChainIDAndContractHnameParam extends ChainIDParam { + contractHname: string + block?: string +} + +export interface ChainIDAndAgentIDParam extends ChainIDParam { + agentID: string + block?: string +} + +export interface ChainIDAndNftIDParam extends ChainIDParam { + nftID: string + block?: string +} + +export interface CallViewParamsChainId extends ChainIDParam, ContractCallViewRequest {} + +export interface StateValueParams { + chainID: string + stateKey: string +} + +export interface ChainIDAndContractHnameErrorParam extends ChainIDAndContractHnameParam { + errorID: number +} + +export interface AuthParam { + loginRequest: LoginRequest +} + +export interface IotaRpcSuite { + // Core methods + getNodeHealth(): Promise + getAvailableRouteGroups(): Promise + getNodeInfo(): Promise + getTips(): Promise + submitBlock(params: SubmitBlock): Promise + getBlockDataById(params: BlockIdentifier): Promise + getBlockMetadata(params: BlockIdentifier): Promise + findOutputById(outputId: string): Promise + getOutputMetadata(outputId: string): Promise + getAllReceipts(): Promise + getReceiptsByMigrationIndex(migratedAt: number): Promise + getTransactionIncludedBlock(transactionId: string): Promise + findIncludedBlockMetadata(transactionId: string): Promise + getMilestoneById(milestoneId: string): Promise + getMilestoneUtxoChangesByMilestone(milestoneId: string): Promise + lookupMilestoneByIndex(index: number): Promise + getMilestoneUtxoChangesById(index: number): Promise + computeMerkleRouteHashes(params: ComputeWhiteFlagRequest): Promise + pruneDatabase(request: PruneDatabaseRequest): Promise + createSnapshot(requestData: CreateSnapshotsRequest): Promise + getTreasuryInformation(): Promise + getPeerInfo(peerId: string): Promise + getPeers(): Promise + addPeer(peerData: AddPeerRequest): Promise + + // Indexer methods + getOutputs(params: OutputSearchParams): Promise + getBasicOutputs(params: BasicOutputSearchParams): Promise + getAliasOutputs(params: AliasOutputSearchParams): Promise + getCurrentUnspentAliasOutput(aliasId: string): Promise + getFoundryOutputs(params: FoundryOutputsFilterParams): Promise + getCurrentUnspentFoundryOutput(foundryId: string): Promise + getNftOutputs(params: NftOutputSearchParams): Promise + getCurrentNftOutput(nftId: string): Promise + + // Explorer methods + getBalanceByAddress(address: string): Promise + getBlockChildren(blockId: string): Promise + getMilestones(params?: MilestonesParams): Promise + getBlocksByMilestone(params: BlocksByMilestoneParams): Promise + getBlocksByMilestoneIndex(params: BlocksByMilestoneIndexParams): Promise + getLedgerUpdatesByAddress(params: LedgerUpdatesByAddressParams): Promise + getLedgerUpdatesByMilestone(params: LedgerUpdatesByMilestoneParams): Promise + getTopRichestAddresses(params: TopRichestAddressesParams): Promise + getTokenDistribution(ledgerIndex: number): Promise + + // Wasp methods + authenticate(params: AuthParam): Promise + authInfo(): Promise + getChains(): Promise + getChainInfo(params: ChainIDAndBlockParam): Promise + removeAccessNode(params: ChainIDAndPeerParam): Promise + addAccessNode(params: ChainIDAndPeerParam): Promise + activateChain(params: ChainIDParam): Promise + callView(params: CallViewParamsChainId): Promise + setChainRecord(params: ChainRecord): Promise + getCommitteeInfo(params: ChainIDAndBlockParam): Promise + getContracts(params: ChainIDAndBlockParam): Promise + getAccounts(params: ChainIDAndBlockParam): Promise + accountsGetAccountBalance(params: ChainIDAndAgentIDParam): Promise + accountsGetAccountFoundries(params: ChainIDAndAgentIDParam): Promise + accountsGetAccountNFTIDs(params: ChainIDAndAgentIDParam): Promise + accountsGetAccountNonce(params: ChainIDAndAgentIDParam): Promise + accountsGetFoundryOutput(params: ChainIDAndSerialNumberParam): Promise + accountsGetNFTData(params: ChainIDAndNftIDParam): Promise + accountsGetNativeTokenIDRegistry(params: ChainIDAndBlockParam): Promise + accountsGetTotalAssets(params: ChainIDAndBlockParam): Promise + blobsGetAllBlobs(params: ChainIDAndBlockParam): Promise + blobsGetBlobInfo(params: ChainIDAndBlobHashParam): Promise + blobsGetBlobValue(params: ChainIDAndBlobHashParam): Promise + blocklogGetLatestBlockInfo(params: ChainIDAndBlockParam): Promise + blocklogGetRequestReceiptsOfLatestBlock(params: ChainIDAndBlockParam): Promise + blocklogGetRequestIDsForLatestBlock(params: ChainIDAndBlockParam): Promise + blocklogGetBlockInfo(params: ChainIDAndBlockIndexParam): Promise + blocklogGetRequestReceiptsOfBlock(params: ChainIDAndBlockIndexParam): Promise + blocklogGetRequestIDsForBlock(params: ChainIDAndBlockIndexParam): Promise + blocklogGetControlAddresses(params: ChainIDAndBlockParam): Promise + blocklogGetEventsOfLatestBlock(params: ChainIDAndBlockParam): Promise + blocklogGetEventsOfBlock(params: ChainIDAndBlockIndexParam): Promise + blocklogGetEventsOfContract(params: ChainIDAndContractHnameParam): Promise + blocklogGetEventsOfRequest(params: ChainIDAndRequestIDParam): Promise + blocklogGetRequestReceipt(params: ChainIDAndRequestIDParam): Promise + blocklogGetRequestIsProcessed(params: ChainIDAndRequestIDParam): Promise + errorsGetErrorMessageFormat(params: ChainIDAndContractHnameErrorParam): Promise + getAllowedStateControllerAddresses( + params: ChainIDAndBlockParam, + ): Promise + governanceGetChainInfo(params: ChainIDAndBlockParam): Promise + governanceGetChainOwner(params: ChainIDAndBlockParam): Promise + deactivateChain(params: ChainIDParam): Promise + estimateGasOffledger( + params: ChainIDParam, + requestBody: EstimateGasRequestOffledger, + ): Promise + estimateGasOnledger(params: ChainIDParam, requestBody: EstimateGasRequestOnledger): Promise + submitJSONRPCRequest(params: ChainIDParam): Promise + getMempoolContents(params: ChainIDParam): Promise + getReceipt(params: ChainIDAndRequestIDParam): Promise + waitForRequest(params: WaitForRequestParams): Promise + getStateValue(params: StateValueParams): Promise + getChainMessageMetrics(params: ChainIDParam): Promise + getChainPipeMetrics(params: ChainIDParam): Promise + getChainWorkflowMetrics(params: ChainIDParam): Promise + getNodeMessageMetrics(): Promise + getConfiguration(): Promise + generateDKS(params: DKSharesPostRequest): Promise + getDKSInfo(sharedAddress: string): Promise + getInfo(): Promise + ownerCertificate(): Promise + getAllPeers(): Promise + getPeeringIdentity(): Promise + getTrustedPeers(): Promise + trustPeer(requestBody: PeeringTrustRequest): Promise + distrustPeer(peer: string): Promise + shutdownNode(): Promise + getVersion(): Promise + offLedger(requestBody: OffLedgerRequest): Promise + getUsers(): Promise + addUser(body: AddUserRequest): Promise + deleteUser(username: string): Promise + getUser(username: string): Promise + changeUserPassword(params: UpdateUserPasswordRequest): Promise + changeUserPermissions(params: UpdateUserPermissionsRequest): Promise +} diff --git a/src/e2e/rpc/evm/tatum.rpc.iota.spec.ts b/src/e2e/rpc/evm/tatum.rpc.iota.spec.ts new file mode 100644 index 0000000000..6ea95ae660 --- /dev/null +++ b/src/e2e/rpc/evm/tatum.rpc.iota.spec.ts @@ -0,0 +1,29 @@ +import { Iota, Network, TatumSDK } from '../../../service' +import { e2eUtil } from '../../e2e.util' + +const getIotaRpc = async () => await TatumSDK.init(e2eUtil.initConfig(Network.IOTA)) + +describe('Iota', () => { + describe('mainnet', () => { + it('getNodeInfo', async () => { + const tatum = await getIotaRpc() + const info = await tatum.rpc.getNodeInfo() + await tatum.destroy() + expect(info).toBeDefined() + }) + + it('getTips', async () => { + const tatum = await getIotaRpc() + const tips = await tatum.rpc.getTips() + await tatum.destroy() + expect(tips).toBeDefined() + }) + + it('getReceipts', async () => { + const tatum = await getIotaRpc() + const receipts = await tatum.rpc.getAllReceipts() + await tatum.destroy() + expect(receipts).toBeDefined() + }) + }) +}) diff --git a/src/e2e/rpc/other/tatum.rpc.solana.spec.ts b/src/e2e/rpc/other/tatum.rpc.solana.spec.ts index 5f0b2af1da..798a5f063b 100644 --- a/src/e2e/rpc/other/tatum.rpc.solana.spec.ts +++ b/src/e2e/rpc/other/tatum.rpc.solana.spec.ts @@ -13,7 +13,7 @@ const getClient = async (testnet?: boolean): Promise => const blockNumber = 203046000 // TODO: Too unstable -describe.skip('Solana', () => { +describe('Solana', () => { describe('mainnet', () => { describe('getSignaturesForAddress', () => { it('should return getSignatureForAddress', async () => { @@ -49,7 +49,7 @@ describe.skip('Solana', () => { expect(result?.context.slot).toBeGreaterThan(0) }) - it('should return error if an invalid public key is provided', async () => { + it.skip('should return error if an invalid public key is provided', async () => { const tatum = await getClient() const publicKey = 'invalid-public-key' diff --git a/src/service/rpc/evm/AbstractBeaconV1EvmRpc.ts b/src/service/rpc/evm/AbstractBeaconV1EvmRpc.ts index 50ba24181a..2c7f9723b9 100644 --- a/src/service/rpc/evm/AbstractBeaconV1EvmRpc.ts +++ b/src/service/rpc/evm/AbstractBeaconV1EvmRpc.ts @@ -18,11 +18,11 @@ export abstract class AbstractBeaconV1EvmRpc implements EvmBeaconV1Interface { protected abstract get(get: GetI): Promise private sendGet(path: string, params: QueryParams, prefix?: string): Promise { - const fullPath = Utils.addQueryParams( - `${prefix ?? Constant.BEACON_PREFIX}/${path}`, - Utils.camelToSnakeCase, - params, - ) + const fullPath = Utils.addQueryParams({ + basePath: `${prefix ?? Constant.BEACON_PREFIX}/${path}`, + strategy: Utils.camelToSnakeCase, + queryParams: params, + }) return this.get({ path: fullPath }) } diff --git a/src/service/rpc/generic/LoadBalancer.ts b/src/service/rpc/generic/LoadBalancer.ts index 7495d1a4d6..00c2e71375 100644 --- a/src/service/rpc/generic/LoadBalancer.ts +++ b/src/service/rpc/generic/LoadBalancer.ts @@ -50,6 +50,7 @@ enum RequestType { GET = 'GET', BATCH = 'BATCH', PUT = 'PUT', + DELETE = 'DELETE', } interface HandleFailedRpcCallParams { @@ -576,6 +577,23 @@ export class LoadBalancer implements AbstractRpcInterface { } } + async delete({ path, prefix }: GetI): Promise { + const { url, type } = this.getActiveNormalUrlWithFallback() + const basePath = prefix ? `${url}${prefix}` : url + try { + return await this.connector.delete({ basePath, path }) + } catch (e) { + await this.handleFailedRpcCall({ + rpcCall: { path }, + e, + nodeType: type, + requestType: RequestType.DELETE, + url: basePath, + }) + return await this.delete({ path, prefix }) + } + } + async get({ path, prefix }: GetI): Promise { const { url, type } = this.getActiveNormalUrlWithFallback() const basePath = prefix ? `${url}${prefix}` : url diff --git a/src/service/rpc/other/AbstractAlgorandAlgodRpc.ts b/src/service/rpc/other/AbstractAlgorandAlgodRpc.ts index 9f25eefc59..4ae454042e 100644 --- a/src/service/rpc/other/AbstractAlgorandAlgodRpc.ts +++ b/src/service/rpc/other/AbstractAlgorandAlgodRpc.ts @@ -53,7 +53,11 @@ export abstract class AbstractAlgorandAlgodRpc implements AlgorandAlgodRpcSuite queryParams?: QueryParams }): Promise { const post: PostI = { - path: Utils.addQueryParams(path, Utils.camelToDashCase, queryParams), + path: Utils.addQueryParams({ + basePath: path, + strategy: Utils.camelToDashCase, + queryParams: queryParams, + }), } if (body) { @@ -64,7 +68,13 @@ export abstract class AbstractAlgorandAlgodRpc implements AlgorandAlgodRpcSuite } private async sendGet({ path, queryParams }: { path: string; queryParams?: QueryParams }): Promise { - return this.get({ path: Utils.addQueryParams(path, Utils.camelToDashCase, queryParams) }) + return this.get({ + path: Utils.addQueryParams({ + basePath: path, + strategy: Utils.camelToDashCase, + queryParams: queryParams, + }), + }) } broadcastTransaction(params: TransactionBroadcastRequest): Promise { diff --git a/src/service/rpc/other/AbstractAlgorandIndexerRpc.ts b/src/service/rpc/other/AbstractAlgorandIndexerRpc.ts index 0855807ee4..fed4f2aa02 100644 --- a/src/service/rpc/other/AbstractAlgorandIndexerRpc.ts +++ b/src/service/rpc/other/AbstractAlgorandIndexerRpc.ts @@ -49,7 +49,13 @@ export abstract class AbstractAlgorandIndexerRpc implements AlgorandIndexerRpcSu protected abstract get(get: GetI): Promise private async sendGet({ path, queryParams }: { path: string; queryParams?: QueryParams }): Promise { - return this.get({ path: Utils.addQueryParams(path, Utils.camelToDashCase, queryParams) }) + return this.get({ + path: Utils.addQueryParams({ + basePath: path, + strategy: Utils.camelToDashCase, + queryParams: queryParams, + }), + }) } getAccount(params: AccountInformationRequest): Promise { diff --git a/src/service/rpc/other/AbstractIotaRpc.ts b/src/service/rpc/other/AbstractIotaRpc.ts new file mode 100644 index 0000000000..43aa8fb817 --- /dev/null +++ b/src/service/rpc/other/AbstractIotaRpc.ts @@ -0,0 +1,708 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { Service } from 'typedi' +import { QueryParams } from '../../../dto' +import { GetI } from '../../../dto/GetI' +import { PostI } from '../../../dto/PostI' +import { + AccountFoundriesResponse, + AccountListResponse, + AccountNFTsResponse, + AccountNonceResponse, + AddPeerRequest, + AddUserRequest, + AliasOutputSearchParams, + AssetsResponse, + AuthInfoModel, + AuthParam, + Balance, + BasicOutputSearchParams, + BlobInfoResponse, + BlobListResponse, + BlobValueResponse, + Block, + BlockChildrenResponse, + BlockIdentifier, + BlockInfoResponse, + BlockMetadata, + BlocksByMilestoneIndexParams, + BlocksByMilestoneParams, + CallViewParamsChainId, + ChainIDAndAgentIDParam, + ChainIDAndBlobHashParam, + ChainIDAndBlockIndexParam, + ChainIDAndBlockParam, + ChainIDAndContractHnameErrorParam, + ChainIDAndContractHnameParam, + ChainIDAndNftIDParam, + ChainIDAndPeerParam, + ChainIDAndRequestIDParam, + ChainIDAndSerialNumberParam, + ChainIDParam, + ChainInfoResponse, + ChainMessageMetrics, + ChainRecord, + CommitteeNode, + ComputeWhiteFlagRequest, + ComputedMerkleRootResult, + ConsensusPipeMetrics, + ConsensusWorkflowMetrics, + ContractInfoResponse, + ControlAddressesResponse, + CreateSnapshotsRequest, + CreateSnapshotsResponse, + DKSharesInfo, + DKSharesPostRequest, + ErrorFormat, + ErrorMessageFormatResponse, + EstimateGasRequestOffledger, + EstimateGasRequestOnledger, + EventsResponse, + FoundryOutputResponse, + FoundryOutputsFilterParams, + GovAllowedStateControllerAddressesResponse, + GovChainInfoResponse, + GovChainOwnerResponse, + InfoResponse, + IotaRpcSuite, + JSONDict, + LedgerUpdateList, + LedgerUpdatesByAddressParams, + LedgerUpdatesByMilestoneParams, + LoginResponse, + Milestone, + MilestonePayload, + MilestonesParams, + NFTJSON, + NativeTokenIDRegistryResponse, + NftOutputSearchParams, + NodeInfo, + NodeMessageMetrics, + NodeOwnerCertificateResponse, + OffLedgerRequest, + OutputIdResponse, + OutputMetadata, + OutputResponse, + OutputSearchParams, + PagedBlockIdsByMilestone, + Peer, + PeerResponse, + PeeringNodeIdentityResponse, + PeeringNodeStatusResponse, + PeeringTrustRequest, + PruneDatabaseRequest, + PruneDatabaseResponse, + ReceiptResponse, + ReceiptsResponse, + RequestIDsResponse, + RequestProcessedResponse, + RichestAddressesStatistics, + StateResponse, + StateValueParams, + SubmitBlock, + TipsResponse, + TopRichestAddressesParams, + TreasuryResponse, + UTXOChanges, + UpdateUserPasswordRequest, + UpdateUserPermissionsRequest, + User, + VersionResponse, + WaitForRequestParams, + WealthDistributionStatistics, +} from '../../../dto/rpc/IotaRpcSuite' +import { Utils } from '../../../util' + +@Service() +export abstract class AbstractIotaRpc implements IotaRpcSuite { + protected abstract post(post: PostI): Promise + + protected abstract get(get: GetI): Promise + protected abstract delete(get: GetI): Promise + protected abstract put(put: PostI): Promise + + private async sendGet({ path, queryParams }: { path: string; queryParams?: QueryParams }): Promise { + return this.get({ path: Utils.addQueryParams({ basePath: path, queryParams: queryParams }) }) + } + + getNodeHealth(): Promise { + return this.get({ path: '/health' }) + } + + getAvailableRouteGroups(): Promise { + return this.get({ path: '/api/routes' }) + } + + getNodeInfo(): Promise { + return this.get({ path: '/api/core/v2/info' }) + } + + getTips(): Promise { + return this.get({ path: '/api/core/v2/tips' }) + } + + submitBlock(params: SubmitBlock): Promise { + return this.post({ path: '/api/core/v2/blocks', body: params }) + } + + getBlockDataById(params: BlockIdentifier): Promise { + return this.get({ path: `/api/core/v2/blocks/${params.blockId}` }) + } + + getBlockMetadata(params: BlockIdentifier): Promise { + return this.get({ path: `/api/core/v2/blocks/${params.blockId}/metadata` }) + } + + findOutputById(outputId: string): Promise { + return this.get({ path: `/api/core/v2/outputs/${encodeURIComponent(outputId)}` }) + } + + getOutputMetadata(outputId: string): Promise { + return this.get({ path: `/api/core/v2/outputs/${encodeURIComponent(outputId)}/metadata` }) + } + + getAllReceipts(): Promise { + return this.get({ path: `/api/core/v2/receipts` }) + } + + getReceiptsByMigrationIndex(migratedAt: number): Promise { + return this.get({ path: `/api/core/v2/receipts/${migratedAt}` }) + } + + getTransactionIncludedBlock(transactionId: string): Promise { + return this.get({ path: `/api/core/v2/transactions/${transactionId}/included-block` }) + } + + findIncludedBlockMetadata(transactionId: string): Promise { + return this.get({ path: `/api/core/v2/transactions/${transactionId}/included-block/metadata` }) + } + + getMilestoneById(milestoneId: string): Promise { + return this.get({ path: `/api/core/v2/milestones/${milestoneId}` }) + } + + getMilestoneUtxoChangesByMilestone(milestoneId: string): Promise { + return this.get({ path: `/api/core/v2/milestones/${milestoneId}/utxo-changes` }) + } + + lookupMilestoneByIndex(index: number): Promise { + return this.get({ path: `/api/core/v2/milestones/by-index/${index}` }) + } + + getMilestoneUtxoChangesById(index: number): Promise { + return this.get({ path: `/api/core/v2/milestones/by-index/${index}/utxo-changes` }) + } + + computeMerkleRouteHashes(params: ComputeWhiteFlagRequest): Promise { + return this.post({ path: '/api/core/v2/whiteflag', body: params }) + } + + pruneDatabase(request: PruneDatabaseRequest): Promise { + return this.post({ path: '/api/core/v2/control/database/prune', body: request }) + } + + createSnapshot(requestData: CreateSnapshotsRequest): Promise { + return this.post({ path: `/api/core/v2/control/snapshot/create`, body: requestData }) + } + + getTreasuryInformation(): Promise { + return this.get({ path: '/api/core/v2/treasury' }) + } + + getPeerInfo(peerId: string): Promise { + return this.get({ path: `api/core/v2/peers/${peerId}` }) + } + + getPeers(): Promise { + return this.get({ path: '/api/core/v2/peers' }) + } + + addPeer(peerData: AddPeerRequest): Promise { + return this.post({ path: '/api/core/v2/peers', body: peerData }) + } + + getOutputs(params: OutputSearchParams): Promise { + return this.sendGet({ path: '/api/indexer/v1/outputs', queryParams: params as QueryParams }) + } + + getBasicOutputs(params: BasicOutputSearchParams): Promise { + return this.sendGet({ path: '/api/indexer/v1/outputs/basic', queryParams: params as QueryParams }) + } + + getAliasOutputs(params: AliasOutputSearchParams): Promise { + return this.sendGet({ path: '/api/indexer/v1/outputs/alias', queryParams: params as QueryParams }) + } + + getCurrentUnspentAliasOutput(aliasId: string): Promise { + return this.sendGet({ path: `/api/indexer/v1/outputs/alias/${aliasId}` }) + } + + getFoundryOutputs(params: FoundryOutputsFilterParams): Promise { + return this.sendGet({ path: '/api/indexer/v1/outputs/foundry', queryParams: params as QueryParams }) + } + + getCurrentUnspentFoundryOutput(foundryId: string): Promise { + return this.sendGet({ path: `/api/indexer/v1/outputs/foundry/${foundryId}` }) + } + + getNftOutputs(params: NftOutputSearchParams): Promise { + return this.sendGet({ path: '/api/indexer/v1/outputs/nft', queryParams: params as QueryParams }) + } + + getCurrentNftOutput(nftId: string): Promise { + return this.sendGet({ path: `/api/indexer/v1/outputs/nft/${nftId}` }) + } + + async getBalanceByAddress(address: string): Promise { + return this.sendGet({ path: `/api/explorer/v2/balance/${address}` }) + } + + async getBlockChildren(blockId: string): Promise { + return this.sendGet({ path: `/api/explorer/v2/blocks/${blockId}/children` }) + } + + async getMilestones(params?: MilestonesParams): Promise { + return this.sendGet({ + path: `/api/explorer/v2/milestones`, + queryParams: params as QueryParams, + }) + } + + async getBlocksByMilestone(params: BlocksByMilestoneParams): Promise { + const { milestoneId, ...rest } = params + return this.sendGet({ + path: `/api/explorer/v2/milestones/${milestoneId}/blocks`, + queryParams: rest, + }) + } + + async getBlocksByMilestoneIndex(params: BlocksByMilestoneIndexParams): Promise { + const { milestoneIndex, ...rest } = params + return this.sendGet({ + path: `/api/explorer/v2/milestones/by-index/${milestoneIndex}/blocks`, + queryParams: rest, + }) + } + + async getLedgerUpdatesByAddress(params: LedgerUpdatesByAddressParams): Promise { + const { address, ...rest } = params + return this.sendGet({ + path: `/api/explorer/v2/ledger/updates/by-address/${address}`, + queryParams: rest, + }) + } + + async getLedgerUpdatesByMilestone(params: LedgerUpdatesByMilestoneParams): Promise { + const { milestoneId, ...rest } = params + return this.sendGet({ + path: `/api/explorer/v2/ledger/updates/by-milestone/${milestoneId}`, + queryParams: rest, + }) + } + + async getTopRichestAddresses(params: TopRichestAddressesParams): Promise { + return this.sendGet({ + path: `/api/explorer/v2/ledger/richest-addresses`, + queryParams: params as QueryParams, + }) + } + + async getTokenDistribution(ledgerIndex: number): Promise { + return this.sendGet({ + path: `/api/explorer/v2/ledger/token-distribution`, + queryParams: { ledgerIndex }, + }) + } + + async authenticate(params: AuthParam): Promise { + return this.post({ + path: '/auth', + body: params, + }) + } + + async authInfo(): Promise { + return this.sendGet({ path: '/auth/info' }) + } + + async getChains(): Promise { + return this.sendGet({ path: '/v1/chains' }) + } + + async getChainInfo(params: ChainIDAndBlockParam): Promise { + const { chainID, ...rest } = params + return this.sendGet({ path: `/v1/chains/${chainID}`, queryParams: rest }) + } + + async removeAccessNode(params: ChainIDAndPeerParam): Promise { + const { chainID, peer } = params + return this.delete({ path: `/v1/chains/${chainID}/remove-node/${peer}` }) + } + + async addAccessNode(params: ChainIDAndPeerParam): Promise { + const { chainID, peer } = params + return this.put({ path: `/v1/chains/${chainID}/access-node/${peer}` }) + } + + async activateChain(params: ChainIDParam): Promise { + return this.post({ path: `/v1/chains/${params.chainID}/activate` }) + } + + async callView(params: CallViewParamsChainId): Promise { + const { chainID, ...rest } = params + return this.post({ path: `/v1/chains/${chainID}/callview`, body: rest }) + } + + async setChainRecord(params: ChainRecord): Promise { + const { chainID, ...rest } = params + return this.post({ path: `/v1/chains/${params.chainID}/chainrecord`, body: rest }) + } + + async getCommitteeInfo(params: ChainIDAndBlockParam): Promise { + const { chainID, ...rest } = params + return this.sendGet({ path: `/v1/chains/${chainID}/committee`, queryParams: rest }) + } + + async getContracts(params: ChainIDAndBlockParam): Promise { + const { chainID, ...rest } = params + return this.sendGet({ path: `/v1/chains/${params.chainID}/contracts`, queryParams: rest }) + } + + async getAccounts(params: ChainIDAndBlockParam): Promise { + const { chainID, ...rest } = params + return this.sendGet({ path: `/v1/chains/${chainID}/core/accounts`, queryParams: rest }) + } + + async accountsGetAccountBalance(params: ChainIDAndAgentIDParam): Promise { + const { chainID, agentID, ...rest } = params + return this.sendGet({ path: `/v1/chains/${chainID}/core/accounts/${agentID}/balance`, queryParams: rest }) + } + + async accountsGetAccountFoundries(params: ChainIDAndAgentIDParam): Promise { + const { chainID, agentID, ...rest } = params + return this.sendGet({ + path: `/v1/chains/${chainID}/core/accounts/${agentID}/foundries`, + queryParams: rest, + }) + } + + async accountsGetAccountNFTIDs(params: ChainIDAndAgentIDParam): Promise { + const { chainID, agentID, ...rest } = params + return this.sendGet({ path: `/v1/chains/${chainID}/core/accounts/${agentID}/nfts`, queryParams: rest }) + } + + async accountsGetAccountNonce(params: ChainIDAndAgentIDParam): Promise { + const { chainID, agentID, ...rest } = params + return this.sendGet({ path: `/v1/chains/${chainID}/core/accounts/${agentID}/nonce`, queryParams: rest }) + } + + async accountsGetFoundryOutput(params: ChainIDAndSerialNumberParam): Promise { + const { chainID, serialNumber, ...rest } = params + return this.sendGet({ + path: `/v1/chains/${chainID}/core/accounts/foundry_output/${serialNumber}`, + queryParams: rest, + }) + } + + async accountsGetNFTData(params: ChainIDAndNftIDParam): Promise { + const { chainID, nftID, ...rest } = params + return this.sendGet({ path: `/v1/chains/${chainID}/core/accounts/nftdata/${nftID}`, queryParams: rest }) + } + + async accountsGetNativeTokenIDRegistry( + params: ChainIDAndBlockParam, + ): Promise { + const { chainID, ...rest } = params + return this.sendGet({ + path: `/v1/chains/${chainID}/core/accounts/token_registry`, + queryParams: rest, + }) + } + + async accountsGetTotalAssets(params: ChainIDAndBlockParam): Promise { + const { chainID, ...rest } = params + return this.sendGet({ path: `/v1/chains/${chainID}/core/accounts/total_assets`, queryParams: rest }) + } + + async blobsGetAllBlobs(params: ChainIDAndBlockParam): Promise { + const { chainID, ...rest } = params + return this.sendGet({ path: `/v1/chains/${chainID}/core/blobs`, queryParams: rest }) + } + + async blobsGetBlobInfo(params: ChainIDAndBlobHashParam): Promise { + const { chainID, blobHash, ...rest } = params + return this.sendGet({ path: `/v1/chains/${chainID}/core/blobs/${blobHash}`, queryParams: rest }) + } + + async blobsGetBlobValue(params: ChainIDAndBlobHashParam): Promise { + const { chainID, blobHash, fieldKey, ...rest } = params + return this.sendGet({ + path: `/v1/chains/${chainID}/core/blobs/${blobHash}/data/${fieldKey}`, + queryParams: rest, + }) + } + + async blocklogGetLatestBlockInfo(params: ChainIDAndBlockParam): Promise { + const { chainID, ...rest } = params + return this.sendGet({ path: `/v1/chains/${chainID}/core/blocklog/blocks/latest`, queryParams: rest }) + } + + async blocklogGetRequestReceiptsOfLatestBlock(params: ChainIDAndBlockParam): Promise { + const { chainID, ...rest } = params + return this.sendGet({ + path: `/v1/chains/${chainID}/core/blocklog/blocks/latest/receipts`, + queryParams: rest, + }) + } + + async blocklogGetRequestIDsForLatestBlock(params: ChainIDAndBlockParam): Promise { + const { chainID, ...rest } = params + return this.sendGet({ + path: `/v1/chains/${chainID}/core/blocklog/blocks/latest/requestids`, + queryParams: rest, + }) + } + + async blocklogGetBlockInfo(params: ChainIDAndBlockIndexParam): Promise { + const { chainID, blockIndex, ...rest } = params + return this.sendGet({ + path: `/v1/chains/${chainID}/core/blocklog/blocks/${blockIndex}`, + queryParams: rest, + }) + } + + async blocklogGetRequestReceiptsOfBlock(params: ChainIDAndBlockIndexParam): Promise { + const { chainID, blockIndex, ...rest } = params + return this.sendGet({ + path: `/v1/chains/${chainID}/core/blocklog/blocks/${blockIndex}/receipts`, + queryParams: rest, + }) + } + + async blocklogGetRequestIDsForBlock(params: ChainIDAndBlockIndexParam): Promise { + const { chainID, blockIndex, ...rest } = params + return this.sendGet({ + path: `/v1/chains/${chainID}/core/blocklog/blocks/${blockIndex}/requestids`, + queryParams: rest, + }) + } + + async blocklogGetControlAddresses(params: ChainIDAndBlockParam): Promise { + const { chainID, ...rest } = params + return this.sendGet({ + path: `/v1/chains/${chainID}/core/blocklog/controladdresses`, + queryParams: rest, + }) + } + + async blocklogGetEventsOfLatestBlock(params: ChainIDAndBlockParam): Promise { + const { chainID, ...rest } = params + return this.sendGet({ + path: `/v1/chains/${chainID}/core/blocklog/events/latest`, + queryParams: rest, + }) + } + + async blocklogGetEventsOfBlock(params: ChainIDAndBlockIndexParam): Promise { + const { chainID, blockIndex, ...rest } = params + return this.sendGet({ + path: `/v1/chains/${chainID}/core/blocklog/events/${blockIndex}`, + queryParams: rest, + }) + } + + async blocklogGetEventsOfContract(params: ChainIDAndContractHnameParam): Promise { + const { chainID, contractHname, ...rest } = params + return this.sendGet({ + path: `/v1/chains/${chainID}/core/blocklog/events/contract/${contractHname}`, + queryParams: rest, + }) + } + + async blocklogGetEventsOfRequest(params: ChainIDAndRequestIDParam): Promise { + const { chainID, requestID, ...rest } = params + return this.sendGet({ + path: `/v1/chains/${chainID}/core/blocklog/events/request/${requestID}`, + queryParams: rest, + }) + } + + async blocklogGetRequestReceipt(params: ChainIDAndRequestIDParam): Promise { + const { chainID, requestID, ...rest } = params + return this.sendGet({ + path: `/v1/chains/${chainID}/core/blocklog/requests/${requestID}`, + queryParams: rest, + }) + } + + async blocklogGetRequestIsProcessed(params: ChainIDAndRequestIDParam): Promise { + const { chainID, requestID, ...rest } = params + return this.sendGet({ + path: `/v1/chains/${chainID}/core/blocklog/requests/${requestID}/processed`, + queryParams: rest, + }) + } + + async errorsGetErrorMessageFormat( + params: ChainIDAndContractHnameErrorParam, + ): Promise { + const { chainID, contractHname, errorID, ...rest } = params + return this.sendGet({ + path: `/v1/chains/${chainID}/core/errors/${contractHname}/message/${errorID}`, + queryParams: rest, + }) + } + + async getAllowedStateControllerAddresses( + params: ChainIDAndBlockParam, + ): Promise { + const { chainID, ...rest } = params + return this.sendGet({ + path: `/v1/chains/${chainID}/core/governance/allowedstatecontrollers`, + queryParams: rest, + }) + } + + async governanceGetChainInfo(params: ChainIDAndBlockParam): Promise { + const { chainID, ...rest } = params + return this.sendGet({ path: `/v1/chains/${chainID}/core/governance/chaininfo`, queryParams: rest }) + } + + async governanceGetChainOwner(params: ChainIDAndBlockParam): Promise { + const { chainID, ...rest } = params + return this.sendGet({ path: `/v1/chains/${chainID}/core/governance/chainowner`, queryParams: rest }) + } + + async deactivateChain(params: ChainIDParam): Promise { + return this.post({ path: `/v1/chains/${params.chainID}/deactivate` }) + } + + async estimateGasOffledger( + params: ChainIDParam, + requestBody: EstimateGasRequestOffledger, + ): Promise { + return this.post({ path: `/v1/chains/${params.chainID}/estimategas-offledger`, body: requestBody }) + } + + async estimateGasOnledger( + params: ChainIDParam, + requestBody: EstimateGasRequestOnledger, + ): Promise { + return this.post({ path: `/v1/chains/${params.chainID}/estimategas-onledger`, body: requestBody }) + } + + async submitJSONRPCRequest(params: ChainIDParam): Promise { + return this.post({ path: `/v1/chains/${params.chainID}/evm`, body: {} }) + } + + async getMempoolContents(params: ChainIDParam): Promise { + return this.sendGet({ path: `/v1/chains/${params.chainID}/mempool` }) + } + + async getReceipt(params: ChainIDAndRequestIDParam): Promise { + const { chainID, requestID, ...rest } = params + return this.sendGet({ path: `/v1/chains/${chainID}/receipts/${requestID}`, queryParams: rest }) + } + + async waitForRequest(params: WaitForRequestParams): Promise { + const { chainID, requestID, ...rest } = params + return this.sendGet({ path: `/v1/chains/${chainID}/requests/${requestID}/wait`, queryParams: rest }) + } + + async getStateValue(params: StateValueParams): Promise { + const { chainID, stateKey, ...rest } = params + return this.sendGet({ path: `/v1/chains/${chainID}/state/${stateKey}`, queryParams: rest }) + } + + async getChainMessageMetrics(params: ChainIDParam): Promise { + return this.sendGet({ path: `/v1/metrics/chains/${params.chainID}/messages` }) + } + + async getChainPipeMetrics(params: ChainIDParam): Promise { + return this.sendGet({ path: `/v1/metrics/chains/${params.chainID}/pipes` }) + } + + async getChainWorkflowMetrics(params: ChainIDParam): Promise { + return this.sendGet({ path: `/v1/metrics/chains/${params.chainID}/workflows` }) + } + + async getNodeMessageMetrics(): Promise { + return this.sendGet({ path: `/v1/metrics/node/messages` }) + } + + async getConfiguration(): Promise { + return this.sendGet({ path: `/v1/node/config` }) + } + + async generateDKS(params: DKSharesPostRequest): Promise { + return this.post({ path: `/v1/node/dks`, body: params }) + } + + async getDKSInfo(sharedAddress: string): Promise { + return this.sendGet({ path: `/v1/node/dks/${sharedAddress}` }) + } + + async getInfo(): Promise { + return this.sendGet({ path: `/v1/node/info` }) + } + + async ownerCertificate(): Promise { + return this.sendGet({ path: `/v1/node/owner/certificate` }) + } + + async getAllPeers(): Promise { + return this.sendGet({ path: `/v1/node/peers` }) + } + + async getPeeringIdentity(): Promise { + return this.sendGet({ path: `/v1/node/peers/identity` }) + } + + async getTrustedPeers(): Promise { + return this.sendGet({ path: `/v1/node/peers/trusted` }) + } + + async trustPeer(requestBody: PeeringTrustRequest): Promise { + return this.post({ path: `/v1/node/peers/trusted`, body: requestBody }) + } + + async distrustPeer(peer: string): Promise { + return this.delete({ path: `/v1/node/peers/trusted/${peer}` }) + } + + async shutdownNode(): Promise { + return this.post({ path: `/v1/node/shutdown` }) + } + + async getVersion(): Promise { + return this.sendGet({ path: `/v1/node/version` }) + } + + async offLedger(requestBody: OffLedgerRequest): Promise { + return this.post({ path: `/v1/requests/offledger`, body: requestBody }) + } + + async getUsers(): Promise { + return this.sendGet({ path: `/v1/users` }) + } + + async addUser(body: AddUserRequest): Promise { + return this.post({ path: `/v1/users`, body }) + } + + async deleteUser(username: string): Promise { + return this.delete({ path: `/v1/users/${username}` }) + } + + async getUser(username: string): Promise { + return this.sendGet({ path: `/v1/users/${username}` }) + } + + async changeUserPassword(params: UpdateUserPasswordRequest): Promise { + return this.put({ path: `/v1/users/${params.username}/password`, body: params }) + } + + async changeUserPermissions(params: UpdateUserPermissionsRequest): Promise { + return this.put({ path: `/v1/users/${params.username}/permissions`, body: params }) + } +} diff --git a/src/service/rpc/other/AbstractKadenaRpc.ts b/src/service/rpc/other/AbstractKadenaRpc.ts index e1e3a9c9da..5b388dcbe9 100644 --- a/src/service/rpc/other/AbstractKadenaRpc.ts +++ b/src/service/rpc/other/AbstractKadenaRpc.ts @@ -56,7 +56,11 @@ export abstract class AbstractKadenaRpc implements KadenaRpcInterface { private prepareRequest({ path, body, queryParams, network }: RequestI): PostI { return { - path: Utils.addQueryParams(path, Utils.camelToDashCase, queryParams), + path: Utils.addQueryParams({ + basePath: path, + strategy: Utils.camelToDashCase, + queryParams: queryParams, + }), prefix: network ? this.urlWithPrefix(network) : undefined, body, } @@ -80,7 +84,11 @@ export abstract class AbstractKadenaRpc implements KadenaRpcInterface { network?: NetworkParams | ApiParams }): Promise { return this.get({ - path: Utils.addQueryParams(path, Utils.camelToDashCase, queryParams), + path: Utils.addQueryParams({ + basePath: path, + strategy: Utils.camelToDashCase, + queryParams: queryParams, + }), prefix: network ? this.urlWithPrefix(network) : undefined, }) } diff --git a/src/service/rpc/other/AbstractStellarRpc.ts b/src/service/rpc/other/AbstractStellarRpc.ts index de43d9ea04..36b3cdbc21 100644 --- a/src/service/rpc/other/AbstractStellarRpc.ts +++ b/src/service/rpc/other/AbstractStellarRpc.ts @@ -82,7 +82,13 @@ export abstract class AbstractStellarRpc implements StellarRpcSuite { private async sendGet({ path, queryParams }: { path: string; queryParams?: QueryParams }): Promise { if (queryParams && Object.keys(queryParams).length > 0) { - return this.get({ path: Utils.addQueryParams(path, Utils.camelToSnakeCase, queryParams) }) + return this.get({ + path: Utils.addQueryParams({ + basePath: path, + strategy: Utils.camelToSnakeCase, + queryParams: queryParams, + }), + }) } return this.get({ path }) @@ -102,7 +108,11 @@ export abstract class AbstractStellarRpc implements StellarRpcSuite { } if (queryParams && Object.keys(queryParams).length > 0) { - post.path = Utils.addQueryParams(path, Utils.camelToSnakeCase, queryParams) + post.path = Utils.addQueryParams({ + basePath: path, + strategy: Utils.camelToSnakeCase, + queryParams: queryParams, + }) } if (body) { diff --git a/src/service/rpc/other/AbstractTezosRpc.ts b/src/service/rpc/other/AbstractTezosRpc.ts index 6ca5d98c3b..ac9af78e87 100644 --- a/src/service/rpc/other/AbstractTezosRpc.ts +++ b/src/service/rpc/other/AbstractTezosRpc.ts @@ -30,7 +30,13 @@ export abstract class AbstractTezosRpc implements TezosRpcInterface { private async sendGet({ path, queryParams }: { path: string; queryParams?: QueryParams }): Promise { if (queryParams && Object.keys(queryParams).length > 0) { - return this.get({ path: Utils.addQueryParams(path, Utils.camelToSnakeCase, queryParams) }) + return this.get({ + path: Utils.addQueryParams({ + basePath: path, + strategy: Utils.camelToSnakeCase, + queryParams: queryParams, + }), + }) } return this.get({ path }) @@ -50,7 +56,11 @@ export abstract class AbstractTezosRpc implements TezosRpcInterface { } if (queryParams && Object.keys(queryParams).length > 0) { - post.path = Utils.addQueryParams(path, Utils.camelToSnakeCase, queryParams) + post.path = Utils.addQueryParams({ + basePath: path, + strategy: Utils.camelToSnakeCase, + queryParams: queryParams, + }) } if (body) { diff --git a/src/service/rpc/other/IotaLoadBalancerRpc.ts b/src/service/rpc/other/IotaLoadBalancerRpc.ts new file mode 100644 index 0000000000..b485c3225b --- /dev/null +++ b/src/service/rpc/other/IotaLoadBalancerRpc.ts @@ -0,0 +1,47 @@ +/* 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 { LoadBalancer } from '../generic/LoadBalancer' +import { AbstractIotaRpc } from './AbstractIotaRpc' + +@Service({ + factory: (data: { id: string }) => { + return new IotaLoadBalancerRpc(data.id) + }, + transient: true, +}) +export class IotaLoadBalancerRpc extends AbstractIotaRpc implements IotaRpcSuite { + protected readonly loadBalancer: LoadBalancer + + constructor(id: string) { + super() + this.loadBalancer = Container.of(id).get(LoadBalancer) + } + + public destroy() { + this.loadBalancer.destroy() + } + + public getRpcNodeUrl(): string { + return this.loadBalancer.getActiveNormalUrlWithFallback().url + } + + protected get(get: GetI): Promise { + return this.loadBalancer.get(get) + } + + protected post(post: PostI): Promise { + return this.loadBalancer.post(post) + } + + protected put(put: PostI): Promise { + return this.loadBalancer.put(put) + } + + protected delete(get: GetI): Promise { + return this.loadBalancer.delete(get) + } +} diff --git a/src/service/tatum/tatum.other.ts b/src/service/tatum/tatum.other.ts index f846194a2b..84ccd4c1bc 100644 --- a/src/service/tatum/tatum.other.ts +++ b/src/service/tatum/tatum.other.ts @@ -5,6 +5,7 @@ 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 { IotaRpcSuite } from '../../dto/rpc/IotaRpcSuite' import { KadenaRpcInterface } from '../../dto/rpc/KadenaRpcSuite' import { RostrumRpcInterface } from '../../dto/rpc/RostrumRpcSuite' import { StellarRpcSuite } from '../../dto/rpc/StellarRpcSuite' @@ -112,6 +113,15 @@ export class Kadena extends BaseOther { } } +export class Iota extends BaseOther { + rpc: IotaRpcSuite + + constructor(id: string) { + super(id) + this.rpc = Utils.getRpc(id, Container.of(id).get(CONFIG)) + } +} + export class Rostrum extends BaseOther { rpc: RostrumRpcInterface diff --git a/src/util/constant.ts b/src/util/constant.ts index 9349793d48..e3979dd4ae 100644 --- a/src/util/constant.ts +++ b/src/util/constant.ts @@ -121,6 +121,7 @@ export const Constant = { [Network.KADENA]: 18, [Network.KADENA_TESTNET]: 18, [Network.COSMONS_ROSETTA]: 18, + [Network.IOTA]: 18, }, CURRENCY_NAMES: { [Network.BITCOIN]: 'BTC', @@ -222,6 +223,7 @@ export const Constant = { [Network.KADENA_TESTNET]: 'KADENA', [Network.ROSTRUM]: 'BCH', [Network.COSMONS_ROSETTA]: 'ATOM', + [Network.IOTA]: 'IOTA', }, RPC: { MAINNETS: [ diff --git a/src/util/util.shared.ts b/src/util/util.shared.ts index b7d3fe126a..beb779072d 100644 --- a/src/util/util.shared.ts +++ b/src/util/util.shared.ts @@ -15,6 +15,7 @@ import { isEvmArchiveNonArchiveLoadBalancerNetwork, isEvmBasedNetwork, isEvmLoadBalancerNetwork, + isIotaNetwork, isKadenaLoadBalancerNetwork, isNativeEvmLoadBalancerNetwork, isRostrumLoadBalancerNetwork, @@ -69,6 +70,7 @@ import { Haqq, HarmonyOne, HorizenEon, + Iota, Kadena, Klaytn, Kucoin, @@ -100,6 +102,7 @@ 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 { IotaLoadBalancerRpc } from '../service/rpc/other/IotaLoadBalancerRpc' import { KadenaLoadBalancerRpc } from '../service/rpc/other/KadenaLoadBalancerRpc' import { RostrumLoadBalancerRpc } from '../service/rpc/other/RostrumLoadBalancerRpc' import { SolanaArchiveLoadBalancerRpc } from '../service/rpc/other/SolanaArchiveLoadBalancerRpc' @@ -118,6 +121,10 @@ export const Utils = { getRpc: (id: string, config: TatumConfig): T => { const { network } = config + if (isIotaNetwork(network)) { + return Container.of(id).get(IotaLoadBalancerRpc) as T + } + if (isRostrumLoadBalancerNetwork(network)) { return Container.of(id).get(RostrumLoadBalancerRpc) as T } @@ -309,7 +316,8 @@ export const Utils = { isAlgorandAlgodNetwork(network) || isAlgorandIndexerNetwork(network) || isStellarLoadBalancerNetwork(network) || - isKadenaLoadBalancerNetwork(network) + isKadenaLoadBalancerNetwork(network) || + isIotaNetwork(network) ) { return null } @@ -317,6 +325,10 @@ export const Utils = { throw new Error(`Network ${network} is not supported.`) }, getStatusUrl(network: Network, url: string): string { + if (isIotaNetwork(network)) { + return `${url}api/core/v2/info` + } + if (isEosNetwork(network)) { return `${url}${Constant.EOS_PREFIX}get_info` } @@ -369,7 +381,8 @@ export const Utils = { isAlgorandAlgodNetwork(network) || isAlgorandIndexerNetwork(network) || isStellarLoadBalancerNetwork(network) || - isKadenaLoadBalancerNetwork(network) + isKadenaLoadBalancerNetwork(network) || + isIotaNetwork(network) ) { return 'GET' } @@ -420,6 +433,10 @@ export const Utils = { return new BigNumber((response.result.height as number) || -1).toNumber() } + if (isIotaNetwork(network)) { + return new BigNumber((response?.status?.latestMilestone?.index as number) || -1).toNumber() + } + throw new Error(`Network ${network} is not supported.`) }, isResponseOk: (network: Network, response: JsonRpcResponse | any) => { @@ -467,6 +484,10 @@ export const Utils = { return response?.result?.height !== undefined } + if (isIotaNetwork(network)) { + return response?.status?.latestMilestone?.index !== undefined + } + throw new Error(`Network ${network} is not supported.`) }, mapNotificationChainToNetwork: (chain: AddressEventNotificationChain): Network => { @@ -660,6 +681,7 @@ export const Utils = { padWithZero: (data: string, length = 64) => data.replace('0x', '').padStart(length, '0'), camelToSnakeCase: (str: string) => str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`), camelToDashCase: (str: string) => str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`), + identity: (x: T) => x, convertObjectWithStrategy: ( obj: object, strategy: (key: string) => string, @@ -814,6 +836,8 @@ export const Utils = { return new Kadena(id) as T case Network.ROSTRUM: return new Rostrum(id) as T + case Network.IOTA: + return new Iota(id) as T default: return new FullSdk(id) as T } @@ -881,17 +905,24 @@ export const Utils = { } return rpc?.nodes?.[0].url || `${Constant.TATUM_API_URL.V3}blockchain/node/${network}`.concat(path || '') }, - addQueryParams: ( - basePath: string, - strategy: (key: string) => string, - queryParams?: QueryParams, - ): string => { + addQueryParams: ({ + basePath, + strategy, + queryParams, + }: { + basePath: string + strategy?: (key: string) => string + queryParams?: QueryParams + }): string => { let queryString = '' if (queryParams) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - const query: Record = Utils.convertObjectWithStrategy(queryParams, strategy) + const query: Record = Utils.convertObjectWithStrategy( + queryParams, + strategy ?? Utils.identity, + ) const params: string[] = [] Object.entries(query).forEach(([key, value]) => {