├── .gitignore ├── .scalafmt.conf ├── .travis.yml ├── Readme.md ├── build.sbt ├── doc └── DesignNotes.md ├── docker-compose.yaml ├── modules ├── chain-grabber │ ├── Dockerfile │ └── src │ │ ├── main │ │ ├── resources │ │ │ ├── application.conf │ │ │ └── logback.xml │ │ └── scala │ │ │ └── org │ │ │ └── ergoplatform │ │ │ └── explorer │ │ │ ├── indexer │ │ │ ├── Application.scala │ │ │ ├── cache │ │ │ │ └── ApiQueryCache.scala │ │ │ ├── extractors │ │ │ │ ├── BlockInfoBuildFrom.scala │ │ │ │ ├── TokensBuildFromEip4.scala │ │ │ │ ├── blockStats.scala │ │ │ │ └── package.scala │ │ │ ├── models │ │ │ │ ├── FlatBlock.scala │ │ │ │ ├── SlotData.scala │ │ │ │ └── TotalStats.scala │ │ │ ├── modules │ │ │ │ └── RepoBundle.scala │ │ │ └── processes │ │ │ │ ├── ChainIndexer.scala │ │ │ │ └── EpochsIndexer.scala │ │ │ └── settings │ │ │ └── IndexerSettings.scala │ │ └── test │ │ ├── resources │ │ └── logback.xml │ │ └── scala │ │ └── org │ │ └── ergoplatform │ │ └── explorer │ │ └── indexer │ │ ├── CacheMock.scala │ │ ├── ChainGrabberSpec.scala │ │ ├── GrabberTestNetwork.scala │ │ └── RegistersParsingSpec.scala ├── explorer-api │ ├── Dockerfile │ └── src │ │ ├── main │ │ ├── resources │ │ │ ├── application.conf │ │ │ └── logback.xml │ │ └── scala │ │ │ └── org │ │ │ └── ergoplatform │ │ │ └── explorer │ │ │ ├── http │ │ │ └── api │ │ │ │ ├── ApiErr.scala │ │ │ │ ├── Application.scala │ │ │ │ ├── HttpApi.scala │ │ │ │ ├── algebra │ │ │ │ └── AdaptThrowable.scala │ │ │ │ ├── cache │ │ │ │ ├── ApiQueryCache.scala │ │ │ │ ├── CachingMiddleware.scala │ │ │ │ ├── models │ │ │ │ │ └── CachedResponse.scala │ │ │ │ └── types.scala │ │ │ │ ├── commonDirectives.scala │ │ │ │ ├── decodingFailureHandler.scala │ │ │ │ ├── defs.scala │ │ │ │ ├── models │ │ │ │ ├── AssetInstanceInfo.scala │ │ │ │ ├── HeightRange.scala │ │ │ │ ├── InclusionHeightRange.scala │ │ │ │ ├── Items.scala │ │ │ │ ├── Paging.scala │ │ │ │ └── Sorting.scala │ │ │ │ ├── streaming.scala │ │ │ │ ├── syntax │ │ │ │ ├── AdaptThrowableOps.scala │ │ │ │ ├── RoutesOps.scala │ │ │ │ └── package.scala │ │ │ │ ├── tapirInstances.scala │ │ │ │ ├── v0 │ │ │ │ ├── defs │ │ │ │ │ ├── AddressesEndpointDefs.scala │ │ │ │ │ ├── AssetsEndpointDefs.scala │ │ │ │ │ ├── BlocksEndpointDefs.scala │ │ │ │ │ ├── BoxesEndpointDefs.scala │ │ │ │ │ ├── ChartsEndpointDefs.scala │ │ │ │ │ ├── DexEndpointsDefs.scala │ │ │ │ │ ├── DocsEndpointDefs.scala │ │ │ │ │ ├── InfoEndpointDefs.scala │ │ │ │ │ ├── SearchEndpointDefs.scala │ │ │ │ │ ├── StatsEndpointDefs.scala │ │ │ │ │ ├── TransactionsEndpointDefs.scala │ │ │ │ │ └── package.scala │ │ │ │ ├── domain │ │ │ │ │ └── stats.scala │ │ │ │ ├── models │ │ │ │ │ ├── AddressInfo.scala │ │ │ │ │ ├── AssetSummary.scala │ │ │ │ │ ├── BalanceInfo.scala │ │ │ │ │ ├── BlockChainInfo.scala │ │ │ │ │ ├── BlockExtensionInfo.scala │ │ │ │ │ ├── BlockInfo.scala │ │ │ │ │ ├── BlockReferencesInfo.scala │ │ │ │ │ ├── BlockSummary.scala │ │ │ │ │ ├── ChartPoint.scala │ │ │ │ │ ├── DataInputInfo.scala │ │ │ │ │ ├── DexBuyOrderInfo.scala │ │ │ │ │ ├── DexSellOrderInfo.scala │ │ │ │ │ ├── FullBlockInfo.scala │ │ │ │ │ ├── HashRateDistributionSegment.scala │ │ │ │ │ ├── HeaderInfo.scala │ │ │ │ │ ├── InputInfo.scala │ │ │ │ │ ├── MinerInfo.scala │ │ │ │ │ ├── OutputInfo.scala │ │ │ │ │ ├── PowSolutionInfo.scala │ │ │ │ │ ├── SearchResult.scala │ │ │ │ │ ├── SpendingProofInfo.scala │ │ │ │ │ ├── StatsSummary.scala │ │ │ │ │ ├── TransactionInfo.scala │ │ │ │ │ ├── TransactionSummary.scala │ │ │ │ │ ├── TxIdResponse.scala │ │ │ │ │ ├── TxStats.scala │ │ │ │ │ ├── UDataInputInfo.scala │ │ │ │ │ ├── UInputInfo.scala │ │ │ │ │ ├── UOutputInfo.scala │ │ │ │ │ ├── UTransactionInfo.scala │ │ │ │ │ └── UTransactionSummary.scala │ │ │ │ ├── modules │ │ │ │ │ └── Search.scala │ │ │ │ ├── routes │ │ │ │ │ ├── AddressesRoutes.scala │ │ │ │ │ ├── AssetsRoutes.scala │ │ │ │ │ ├── BlocksRoutes.scala │ │ │ │ │ ├── BoxesRoutes.scala │ │ │ │ │ ├── ChartsRoutes.scala │ │ │ │ │ ├── DexRoutes.scala │ │ │ │ │ ├── DocsRoutes.scala │ │ │ │ │ ├── InfoRoutes.scala │ │ │ │ │ ├── RoutesV0Bundle.scala │ │ │ │ │ ├── SearchRoutes.scala │ │ │ │ │ ├── StatsRoutes.scala │ │ │ │ │ └── TransactionsRoutes.scala │ │ │ │ └── services │ │ │ │ │ ├── AddressesService.scala │ │ │ │ │ ├── AssetsService.scala │ │ │ │ │ ├── BlockChainService.scala │ │ │ │ │ ├── BoxesService.scala │ │ │ │ │ ├── DexService.scala │ │ │ │ │ ├── OffChainService.scala │ │ │ │ │ ├── StatsService.scala │ │ │ │ │ └── TransactionsService.scala │ │ │ │ └── v1 │ │ │ │ ├── TokenStatus.scala │ │ │ │ ├── defs │ │ │ │ ├── AddressesEndpointDefs.scala │ │ │ │ ├── AssetsEndpointDefs.scala │ │ │ │ ├── BlocksEndpointDefs.scala │ │ │ │ ├── BoxesEndpointDefs.scala │ │ │ │ ├── DocsEndpointDefs.scala │ │ │ │ ├── EpochsEndpointDefs.scala │ │ │ │ ├── ErgoTreeEndpointDefs.scala │ │ │ │ ├── MempoolEndpointDefs.scala │ │ │ │ ├── StatsEndpointsDefs.scala │ │ │ │ ├── TokensEndpointDefs.scala │ │ │ │ ├── TransactionsEndpointDefs.scala │ │ │ │ └── package.scala │ │ │ │ ├── implictis │ │ │ │ └── schemaForMapKV.scala │ │ │ │ ├── models │ │ │ │ ├── AddressInfo.scala │ │ │ │ ├── AnyOutputInfo.scala │ │ │ │ ├── AssetInfo.scala │ │ │ │ ├── Balance.scala │ │ │ │ ├── BlockExtensionInfo.scala │ │ │ │ ├── BlockHeader.scala │ │ │ │ ├── BlockInfo.scala │ │ │ │ ├── BlockPowSolutions.scala │ │ │ │ ├── BlockSummaryV1.scala │ │ │ │ ├── BoxAssetsQuery.scala │ │ │ │ ├── BoxQuery.scala │ │ │ │ ├── CheckTokenInfo.scala │ │ │ │ ├── DataInputInfo.scala │ │ │ │ ├── EpochInfo.scala │ │ │ │ ├── ErgoTreeHuman.scala │ │ │ │ ├── GenuineTokenInfo.scala │ │ │ │ ├── InputInfo.scala │ │ │ │ ├── MOutputInfo.scala │ │ │ │ ├── MinerInfo.scala │ │ │ │ ├── NetworkState.scala │ │ │ │ ├── NetworkStats.scala │ │ │ │ ├── OutputInfo.scala │ │ │ │ ├── PrettyErgoTree.scala │ │ │ │ ├── TokenAmount.scala │ │ │ │ ├── TokenInfo.scala │ │ │ │ ├── TotalBalance.scala │ │ │ │ ├── TransactionInfo.scala │ │ │ │ ├── UDataInputInfo.scala │ │ │ │ ├── UInputInfo.scala │ │ │ │ ├── UOutputInfo.scala │ │ │ │ └── UTransactionInfo.scala │ │ │ │ ├── routes │ │ │ │ ├── AddressesRoutes.scala │ │ │ │ ├── AssetsRoutes.scala │ │ │ │ ├── BlocksRoutes.scala │ │ │ │ ├── BoxesRoutes.scala │ │ │ │ ├── DocsRoutes.scala │ │ │ │ ├── EpochsRoutes.scala │ │ │ │ ├── ErgoTreeRoutes.scala │ │ │ │ ├── MempoolRoutes.scala │ │ │ │ ├── RoutesV1Bundle.scala │ │ │ │ ├── StatsRoutes.scala │ │ │ │ ├── TokensRoutes.scala │ │ │ │ └── TransactionsRoutes.scala │ │ │ │ ├── services │ │ │ │ ├── Addresses.scala │ │ │ │ ├── Assets.scala │ │ │ │ ├── Blocks.scala │ │ │ │ ├── Boxes.scala │ │ │ │ ├── Epochs.scala │ │ │ │ ├── Mempool.scala │ │ │ │ ├── Networks.scala │ │ │ │ ├── Tokens.scala │ │ │ │ └── Transactions.scala │ │ │ │ ├── shared │ │ │ │ └── MempoolProps.scala │ │ │ │ └── utils │ │ │ │ └── TokenVerificationOptionT.scala │ │ │ └── settings │ │ │ ├── ApiSettings.scala │ │ │ ├── HttpSettings.scala │ │ │ ├── RequestsSettings.scala │ │ │ └── ServiceSettings.scala │ │ └── test │ │ └── scala │ │ └── org │ │ └── ergoplatform │ │ └── explorer │ │ ├── testContainers │ │ └── RedisTest.scala │ │ └── v1 │ │ ├── services │ │ ├── AddressesSpec.scala │ │ ├── BoxSpec.scala │ │ ├── MempoolSpec.scala │ │ ├── TransactionSpec.scala │ │ └── constants.scala │ │ └── utils │ │ └── TokenVerificationSpec.scala ├── explorer-core │ └── src │ │ ├── main │ │ ├── resources │ │ │ └── db │ │ │ │ └── V9__Schema.sql │ │ └── scala │ │ │ └── org │ │ │ └── ergoplatform │ │ │ └── explorer │ │ │ ├── BuildFrom.scala │ │ │ ├── Err.scala │ │ │ ├── RegisterId.scala │ │ │ ├── SigmaType.scala │ │ │ ├── cache │ │ │ ├── Redis.scala │ │ │ ├── redisInstances.scala │ │ │ ├── redisTransaction.scala │ │ │ └── repositories │ │ │ │ └── ErgoLikeTransactionRepo.scala │ │ │ ├── db │ │ │ ├── DoobieLogHandler.scala │ │ │ ├── DoobieTrans.scala │ │ │ ├── Trans.scala │ │ │ ├── algebra │ │ │ │ └── LiftConnectionIO.scala │ │ │ ├── doobieInstances.scala │ │ │ ├── models │ │ │ │ ├── AdProof.scala │ │ │ │ ├── AnyOutput.scala │ │ │ │ ├── Asset.scala │ │ │ │ ├── BlockExtension.scala │ │ │ │ ├── BlockStats.scala │ │ │ │ ├── BlockedToken.scala │ │ │ │ ├── BoxRegister.scala │ │ │ │ ├── DataInput.scala │ │ │ │ ├── EpochParameters.scala │ │ │ │ ├── GenuineToken.scala │ │ │ │ ├── Header.scala │ │ │ │ ├── Input.scala │ │ │ │ ├── Miner.scala │ │ │ │ ├── Output.scala │ │ │ │ ├── ScriptConstant.scala │ │ │ │ ├── Token.scala │ │ │ │ ├── Transaction.scala │ │ │ │ ├── UAsset.scala │ │ │ │ ├── UDataInput.scala │ │ │ │ ├── UInput.scala │ │ │ │ ├── UOutput.scala │ │ │ │ ├── UTransaction.scala │ │ │ │ └── aggregates │ │ │ │ │ ├── AggregatedAsset.scala │ │ │ │ │ ├── AnyAsset.scala │ │ │ │ │ ├── BlockSize.scala │ │ │ │ │ ├── ExtendedAsset.scala │ │ │ │ │ ├── ExtendedBlockInfo.scala │ │ │ │ │ ├── ExtendedDataInput.scala │ │ │ │ │ ├── ExtendedInput.scala │ │ │ │ │ ├── ExtendedOutput.scala │ │ │ │ │ ├── ExtendedUAsset.scala │ │ │ │ │ ├── ExtendedUDataInput.scala │ │ │ │ │ ├── ExtendedUInput.scala │ │ │ │ │ ├── ExtendedUOutput.scala │ │ │ │ │ ├── FullDataInput.scala │ │ │ │ │ ├── FullInput.scala │ │ │ │ │ ├── MinerStats.scala │ │ │ │ │ └── TimePoint.scala │ │ │ ├── queries │ │ │ │ ├── AdProofQuerySet.scala │ │ │ │ ├── AssetQuerySet.scala │ │ │ │ ├── BlockExtensionQuerySet.scala │ │ │ │ ├── BlockInfoQuerySet.scala │ │ │ │ ├── BlockedTokenQuerySet.scala │ │ │ │ ├── BoxRegisterQuerySet.scala │ │ │ │ ├── DataInputQuerySet.scala │ │ │ │ ├── EpochParametersQuerySet.scala │ │ │ │ ├── GenuineTokenQuerySet.scala │ │ │ │ ├── HeaderQuerySet.scala │ │ │ │ ├── InputQuerySet.scala │ │ │ │ ├── OutputQuerySet.scala │ │ │ │ ├── QuerySet.scala │ │ │ │ ├── ScriptConstantsQuerySet.scala │ │ │ │ ├── StatsQuerySet.scala │ │ │ │ ├── TokensQuerySet.scala │ │ │ │ ├── TransactionQuerySet.scala │ │ │ │ ├── UAssetQuerySet.scala │ │ │ │ ├── UDataInputQuerySet.scala │ │ │ │ ├── UInputQuerySet.scala │ │ │ │ ├── UOutputQuerySet.scala │ │ │ │ ├── UTransactionQuerySet.scala │ │ │ │ └── package.scala │ │ │ ├── repositories │ │ │ │ ├── AdProofRepo.scala │ │ │ │ ├── AssetRepo.scala │ │ │ │ ├── BlockExtensionRepo.scala │ │ │ │ ├── BlockInfoRepo.scala │ │ │ │ ├── BlockedTokenRepo.scala │ │ │ │ ├── BoxRegisterRepo.scala │ │ │ │ ├── DataInputRepo.scala │ │ │ │ ├── EpochInfoRepo.scala │ │ │ │ ├── GenuineTokenRepo.scala │ │ │ │ ├── HeaderRepo.scala │ │ │ │ ├── InputRepo.scala │ │ │ │ ├── OutputRepo.scala │ │ │ │ ├── ScriptConstantsRepo.scala │ │ │ │ ├── StatsRepo.scala │ │ │ │ ├── TokenRepo.scala │ │ │ │ ├── TransactionRepo.scala │ │ │ │ ├── UAssetRepo.scala │ │ │ │ ├── UDataInputRepo.scala │ │ │ │ ├── UInputRepo.scala │ │ │ │ ├── UOutputRepo.scala │ │ │ │ ├── UTransactionRepo.scala │ │ │ │ └── bundles │ │ │ │ │ └── UtxRepoBundle.scala │ │ │ └── syntax │ │ │ │ ├── LiftConnectionIOSyntax.scala │ │ │ │ └── package.scala │ │ │ ├── package.scala │ │ │ ├── protocol │ │ │ ├── Emission.scala │ │ │ ├── RegistersParser.scala │ │ │ ├── TokenPropsParser.scala │ │ │ ├── TxValidation.scala │ │ │ ├── blocks.scala │ │ │ ├── constants.scala │ │ │ ├── dex.scala │ │ │ ├── ergoInstances.scala │ │ │ ├── models │ │ │ │ ├── ApiAdProof.scala │ │ │ │ ├── ApiAsset.scala │ │ │ │ ├── ApiBlockExtension.scala │ │ │ │ ├── ApiBlockTransactions.scala │ │ │ │ ├── ApiDataInput.scala │ │ │ │ ├── ApiDifficulty.scala │ │ │ │ ├── ApiFullBlock.scala │ │ │ │ ├── ApiHeader.scala │ │ │ │ ├── ApiInput.scala │ │ │ │ ├── ApiNodeInfo.scala │ │ │ │ ├── ApiNodeInfoEpochParameters.scala │ │ │ │ ├── ApiOutput.scala │ │ │ │ ├── ApiPowSolutions.scala │ │ │ │ ├── ApiSpendingProof.scala │ │ │ │ ├── ApiTransaction.scala │ │ │ │ ├── ExpandedRegister.scala │ │ │ │ ├── RegisterValue.scala │ │ │ │ └── TokenPropsEip4.scala │ │ │ ├── registers.scala │ │ │ └── sigma.scala │ │ │ ├── services │ │ │ ├── ErgoNetwork.scala │ │ │ ├── NodesPool.scala │ │ │ └── TxResponse.scala │ │ │ ├── settings │ │ │ ├── DbSettings.scala │ │ │ ├── NetworkSettings.scala │ │ │ ├── ProtocolSettings.scala │ │ │ ├── RedisSettings.scala │ │ │ ├── SettingCompanion.scala │ │ │ ├── UtxCacheSettings.scala │ │ │ └── pureConfigInstances.scala │ │ │ └── syntax │ │ │ ├── StreamEffectSyntax.scala │ │ │ ├── StreamOptionOps.scala │ │ │ └── package.scala │ │ └── test │ │ └── scala │ │ └── org │ │ └── ergoplatform │ │ └── explorer │ │ ├── CatsInstances.scala │ │ ├── MainNetConfiguration.scala │ │ ├── SigmaTypeSpec.scala │ │ ├── commonGenerators.scala │ │ ├── db │ │ ├── RealDbTest.scala │ │ ├── models │ │ │ └── generators.scala │ │ └── repositories │ │ │ ├── AdProofRepoSpec.scala │ │ │ ├── AssetRepoSpec.scala │ │ │ ├── BlockExtensionRepoSpec.scala │ │ │ ├── BlockedTokenRepoSpec.scala │ │ │ ├── GenuineTokenRepoSpec.scala │ │ │ ├── HeaderRepoSpec.scala │ │ │ ├── InputRepoSpec.scala │ │ │ ├── OutputRepoSpec.scala │ │ │ ├── TestOutputRepo.scala │ │ │ └── TransactionRepoSpec.scala │ │ ├── protocol │ │ ├── DexContractsSpec.scala │ │ ├── EmissionSpec.scala │ │ ├── RegistersParserSpec.scala │ │ └── models │ │ │ └── ModelsJsonDecodingSpec.scala │ │ ├── services │ │ ├── ErgoNetworkSpec.scala │ │ └── nodeApiGenerator.scala │ │ ├── testConstants.scala │ │ └── testSyntax │ │ ├── RunConnectionIOSyntax.scala │ │ └── package.scala ├── migrator │ ├── Dockerfile │ └── src │ │ └── main │ │ ├── resources │ │ └── application.conf │ │ └── scala │ │ └── org │ │ └── ergoplatform │ │ └── explorer │ │ └── migration │ │ ├── Application.scala │ │ ├── configs │ │ ├── MigrationConfig.scala │ │ └── ProcessingConfig.scala │ │ └── migrations │ │ ├── AssetsMigration.scala │ │ ├── BlockchainStatsMigration.scala │ │ ├── BlockedTokenMigration.scala │ │ ├── GenuineTokenMigration.scala │ │ ├── RegistersAndConstantsMigration.scala │ │ └── RegistersMigration.scala ├── utx-broadcaster │ ├── Dockerfile │ └── src │ │ └── main │ │ ├── resources │ │ ├── application.conf │ │ └── logback.xml │ │ └── scala │ │ └── org │ │ └── ergoplatform │ │ └── explorer │ │ ├── broadcaster │ │ ├── Application.scala │ │ └── UtxBroadcaster.scala │ │ └── settings │ │ └── UtxBroadcasterSettings.scala └── utx-tracker │ ├── Dockerfile │ └── src │ └── main │ ├── resources │ ├── application.conf │ └── logback.xml │ └── scala │ └── org │ └── ergoplatform │ └── explorer │ ├── db │ └── models │ │ └── aggregates │ │ └── FlatUTransaction.scala │ ├── settings │ └── UtxTrackerSettings.scala │ └── tracker │ ├── Application.scala │ ├── UtxTracker.scala │ └── extractors.scala ├── project ├── build.properties ├── dependencies.scala ├── plugins.sbt ├── utils.scala └── versions.scala └── setup ├── config ├── chain-grabber.application.conf ├── config.md ├── explorer-api.application.conf ├── utx-broadcaster.application.conf └── utx-tracker.application.conf ├── docker-clean.sh ├── docker-compose.yaml ├── psql.sh ├── setup.md ├── sql └── db.sql └── start.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | .DS_Store 5 | 6 | /.idea/ 7 | .metals 8 | .bloop 9 | target 10 | data 11 | /modules/*/target/ 12 | /target/ 13 | /project/target/ 14 | /project/project/target 15 | 16 | .vscode/ 17 | project/metals.sbt 18 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = "2.7.5" 2 | style = defaultWithAlign 3 | 4 | align.openParenCallSite = false 5 | align.openParenDefnSite = false 6 | align.arrowEnumeratorGenerator = true 7 | align.tokens = [ 8 | {code = "="}, 9 | {code = "->"}, 10 | {code = "<-"}, 11 | {code = "=>", owner = "Case"}, 12 | {code = "%", owner = "Term.ApplyInfix"}, 13 | {code = "%%", owner = "Term.ApplyInfix"} 14 | ] 15 | continuationIndent.callSite = 2 16 | continuationIndent.defnSite = 2 17 | continuationIndent.extendSite = 2 18 | danglingParentheses = true 19 | indentOperator = spray 20 | maxColumn = 120 21 | newlines.alwaysBeforeTopLevelStatements = true 22 | project.excludeFilters = [".*\\.sbt"] 23 | rewrite.rules = [RedundantParens, RedundantBraces, SortImports, SortModifiers] 24 | rewrite.sortModifiers.order = [ 25 | "implicit", "final", "sealed", "abstract", 26 | "override", "private", "protected", "lazy" 27 | ] 28 | rewrite.redundantBraces.stringInterpolation = true 29 | spaces.inImportCurlyBraces = false 30 | unindentTopLevelOperators = true -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | # Use container-based infrastructure 3 | sudo: required 4 | jdk: 5 | - openjdk9 6 | scala: 7 | - 2.12.12 8 | script: 9 | - sbt clean test 10 | # These directories are cached to S3 at the end of the build 11 | cache: 12 | directories: 13 | - $HOME/.ivy2/cache 14 | - $HOME/.sbt 15 | before_cache: 16 | # Cleanup the cached directories to avoid unnecessary cache updates 17 | - find $HOME/.ivy2/cache -name "ivydata-*.properties" -print -delete 18 | - find $HOME/.sbt -name "*.lock" -print -delete -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | 3 | services: 4 | postgres: 5 | image: postgres:11-alpine 6 | shm_size: 4g 7 | environment: 8 | POSTGRES_PASSWORD: foo 9 | ports: 10 | - "5432:5432" 11 | volumes: 12 | - ./data/postgres:/var/lib/postgresql/data:rw 13 | redis: 14 | image: redis:latest 15 | restart: always 16 | command: ["redis-server"] 17 | ports: 18 | - "127.0.0.1:6379:6379" 19 | volumes: 20 | - ./data/redis:/usr/local/etc/redis 21 | -------------------------------------------------------------------------------- /modules/chain-grabber/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-jre-slim as builder 2 | RUN apt-get update && \ 3 | apt-get install -y --no-install-recommends apt-transport-https apt-utils bc dirmngr gnupg && \ 4 | echo "deb https://repo.scala-sbt.org/scalasbt/debian /" | tee -a /etc/apt/sources.list.d/sbt.list && \ 5 | apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2EE0EA64E40A89B84B2DF73499E82A75642AC823 && \ 6 | apt-get update && \ 7 | apt-get upgrade -y && \ 8 | apt-get install -y --no-install-recommends sbt 9 | COPY . /explorer-backend 10 | WORKDIR /explorer-backend 11 | RUN sbt chain-grabber/assembly 12 | RUN mv `find . -name ChainGrabber-assembly-*.jar` /chain-grabber.jar 13 | CMD ["/usr/bin/java", "-jar", "/chain-grabber.jar"] 14 | 15 | FROM openjdk:8-jre-slim 16 | COPY --from=builder /chain-grabber.jar /chain-grabber.jar 17 | ENTRYPOINT java -jar /chain-grabber.jar $0 -------------------------------------------------------------------------------- /modules/chain-grabber/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | chain-poll-interval = 15s 2 | epoch-poll-interval = 60s 3 | write-orphans = true 4 | 5 | network { 6 | master-nodes = ["http://213.239.193.208:9053"] 7 | self-check-interval-requests = 8 8 | } 9 | 10 | indexes { 11 | box-registers = true 12 | script-constants = true 13 | block-extensions = true 14 | ad-proofs = true 15 | block-stats = true 16 | } 17 | 18 | db.url = "jdbc:postgresql://localhost:5432/explorer" 19 | db.user = "postgres" 20 | db.pass = "1234" 21 | db.cp-size = 8 22 | 23 | protocol { 24 | network-prefix = 0 25 | genesis-address = "2Z4YBkDsDvQj8BX7xiySFewjitqp2ge9c99jfes2whbtKitZTxdBYqbrVZUvZvKv6aqn9by4kp3LE1c26LCyosFnVnm6b6U1JYvWpYmL2ZnixJbXLjWAWuBThV1D6dLpqZJYQHYDznJCk49g5TUiS4q8khpag2aNmHwREV7JSsypHdHLgJT7MGaw51aJfNubyzSKxZ4AJXFS27EfXwyCLzW1K6GVqwkJtCoPvrcLqmqwacAWJPkmh78nke9H4oT88XmSbRt2n9aWZjosiZCafZ4osUDxmZcc5QVEeTWn8drSraY3eFKe8Mu9MSCcVU" 26 | 27 | # Monetary config for chain 28 | monetary { 29 | # number of blocks reward won't change (2 years) 30 | fixed-rate-period = 525600 31 | # number of coins issued every block during fixedRatePeriod (75 Ergo) 32 | fixed-rate = 75000000000 33 | # Part of coins issued, that is going to the foundation during fixedRatePeriod (7.5 Ergo) 34 | founders-initial-reward = 7500000000 35 | # number of blocks between reward reduction (90 days) 36 | epoch-length = 64800 37 | # number of coins reward decrease every epochs (3 Ergo) 38 | one-epoch-reduction = 3000000000 39 | # delay between the block mined and a time, when the reward can be spend. ~ 1 day. 40 | miner-reward-delay = 720 41 | } 42 | } 43 | 44 | redis-cache.url = "redis://localhost:6380" -------------------------------------------------------------------------------- /modules/chain-grabber/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %white(%d{HH:mm:ss.SSS}) %highlight(%-5level) %cyan(%logger{50}) - %msg %n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /modules/chain-grabber/src/main/scala/org/ergoplatform/explorer/indexer/cache/ApiQueryCache.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.indexer.cache 2 | 3 | import dev.profunktor.redis4cats.RedisCommands 4 | 5 | trait ApiQueryCache[F[_]] { 6 | def flushAll: F[Unit] 7 | } 8 | 9 | object ApiQueryCache { 10 | 11 | def make[F[_]](cmd: RedisCommands[F, String, String]): ApiQueryCache[F] = 12 | new Live[F](cmd) 13 | 14 | final private class Live[F[_]](cmd: RedisCommands[F, String, String]) extends ApiQueryCache[F] { 15 | def flushAll: F[Unit] = cmd.flushAll 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /modules/chain-grabber/src/main/scala/org/ergoplatform/explorer/indexer/extractors/TokensBuildFromEip4.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.indexer.extractors 2 | 3 | import cats.Applicative 4 | import org.ergoplatform.explorer.db.models.Token 5 | import org.ergoplatform.explorer.indexer.models.SlotData 6 | import org.ergoplatform.explorer.protocol.TokenPropsParser 7 | import org.ergoplatform.explorer.protocol.models.ApiTransaction 8 | import org.ergoplatform.explorer.{BuildFrom, TokenId, TokenType} 9 | import tofu.syntax.monadic._ 10 | 11 | final class TokensBuildFromEip4[F[_]: Applicative] extends BuildFrom[F, SlotData, List[Token]] { 12 | 13 | def apply(slot: SlotData): F[List[Token]] = 14 | slot.apiBlock.transactions.transactions.flatMap(tokensFrom).pure 15 | 16 | private def tokensFrom(tx: ApiTransaction): Option[Token] = { 17 | val allowedTokenId = TokenId.fromStringUnsafe(tx.inputs.head.boxId.value) 18 | for { 19 | out <- tx.outputs.toList.find(_.assets.map(_.tokenId).contains(allowedTokenId)) 20 | props = TokenPropsParser.eip4Partial.parse(out.additionalRegisters) 21 | assets = out.assets.filter(_.tokenId == allowedTokenId) 22 | headAsset <- assets.headOption 23 | assetAmount = assets.map(_.amount).sum 24 | } yield Token( 25 | headAsset.tokenId, 26 | out.boxId, 27 | assetAmount, 28 | props.map(_.name), 29 | props.map(_.description), 30 | props.map(_ => TokenType.Eip004), 31 | props.map(_.decimals) 32 | ) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /modules/chain-grabber/src/main/scala/org/ergoplatform/explorer/indexer/extractors/blockStats.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.indexer.extractors 2 | 3 | import cats.Functor 4 | import org.ergoplatform.explorer.db.models.BlockStats 5 | import org.ergoplatform.explorer.indexer.models.TotalStats 6 | import org.ergoplatform.explorer.settings.ProtocolSettings 7 | import tofu.WithContext 8 | import tofu.syntax.context._ 9 | import tofu.syntax.monadic._ 10 | 11 | object blockStats { 12 | 13 | @inline def recalculateStats[F[_]: Functor: WithContext[*[_], ProtocolSettings]]( 14 | currentBlockStats: BlockStats, 15 | prevBlockStats: BlockStats 16 | ): F[TotalStats] = 17 | context.map { protocolSettings => 18 | val talBlockchainSize = prevBlockStats.blockChainTotalSize + currentBlockStats.blockSize 19 | val totalTxsCount = prevBlockStats.totalTxsCount + currentBlockStats.txsCount 20 | val coinIssued = protocolSettings.emission.issuedCoinsAfterHeight(currentBlockStats.height) 21 | val blockMiningTime = currentBlockStats.timestamp - prevBlockStats.timestamp 22 | val totalMiningTime = prevBlockStats.totalMiningTime + blockMiningTime 23 | val totalFees = prevBlockStats.totalFees + currentBlockStats.blockFee 24 | val minerReward = prevBlockStats.totalMinersReward + currentBlockStats.minerReward 25 | val totalCoins = prevBlockStats.totalCoinsInTxs + currentBlockStats.blockCoins 26 | TotalStats( 27 | talBlockchainSize, 28 | totalTxsCount, 29 | coinIssued, 30 | totalMiningTime, 31 | totalFees, 32 | minerReward, 33 | totalCoins 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /modules/chain-grabber/src/main/scala/org/ergoplatform/explorer/indexer/models/FlatBlock.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.indexer.models 2 | 3 | import org.ergoplatform.explorer.db.models._ 4 | 5 | /** Flattened representation of a full block from 6 | * Ergo protocol enriched with statistics. 7 | */ 8 | final case class FlatBlock( 9 | header: Header, 10 | info: BlockStats, 11 | extension: BlockExtension, 12 | adProofOpt: Option[AdProof], 13 | txs: List[Transaction], 14 | inputs: List[Input], 15 | dataInputs: List[DataInput], 16 | outputs: List[Output], 17 | assets: List[Asset], 18 | registers: List[BoxRegister], 19 | tokens: List[Token], 20 | constants: List[ScriptConstant] 21 | ) 22 | -------------------------------------------------------------------------------- /modules/chain-grabber/src/main/scala/org/ergoplatform/explorer/indexer/models/SlotData.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.indexer.models 2 | 3 | import org.ergoplatform.explorer.db.models.BlockStats 4 | import org.ergoplatform.explorer.protocol.models.ApiFullBlock 5 | 6 | final case class SlotData(apiBlock: ApiFullBlock, prevBlockInfo: Option[BlockStats]) 7 | -------------------------------------------------------------------------------- /modules/chain-grabber/src/main/scala/org/ergoplatform/explorer/indexer/models/TotalStats.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.indexer.models 2 | 3 | final case class TotalStats( 4 | blockChainTotalSize: Long, // cumulative blockchain size including this block 5 | totalTxsCount: Long, // total number of txs in all blocks in the chain 6 | totalCoinsIssued: Long, // amount of nERGs issued in the block 7 | totalMiningTime: Long, // mining time of all the blocks in the chain 8 | totalFees: Long, // total amount of nERGs all miners received as a fee 9 | totalMinersReward: Long, // total amount of nERGs all miners received as a reward for all time 10 | totalCoinsInTxs: Long // total amount of nERGs in all blocks 11 | ) -------------------------------------------------------------------------------- /modules/chain-grabber/src/main/scala/org/ergoplatform/explorer/indexer/modules/RepoBundle.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.indexer.modules 2 | 3 | import cats.Monad 4 | import cats.effect.Sync 5 | import fs2.Stream 6 | import org.ergoplatform.explorer.db.algebra.LiftConnectionIO 7 | import org.ergoplatform.explorer.db.repositories._ 8 | import tofu.syntax.monadic._ 9 | 10 | final case class RepoBundle[D[_]]( 11 | headers: HeaderRepo[D, Stream], 12 | blocksInfo: BlockInfoRepo[D], 13 | blockExtensions: BlockExtensionRepo[D], 14 | adProofs: AdProofRepo[D], 15 | txs: TransactionRepo[D, Stream], 16 | inputs: InputRepo[D], 17 | dataInputs: DataInputRepo[D], 18 | epochInfoRepo: EpochInfoRepo[D], 19 | outputs: OutputRepo[D, Stream], 20 | assets: AssetRepo[D, Stream], 21 | registers: BoxRegisterRepo[D], 22 | tokens: TokenRepo[D], 23 | constants: ScriptConstantsRepo[D] 24 | ) 25 | 26 | object RepoBundle { 27 | 28 | def apply[F[_]: Sync, D[_]: LiftConnectionIO: Monad]: F[RepoBundle[D]] = 29 | ( 30 | HeaderRepo[F, D], 31 | BlockInfoRepo[F, D], 32 | BlockExtensionRepo[F, D], 33 | AdProofRepo[F, D], 34 | TransactionRepo[F, D], 35 | InputRepo[F, D], 36 | DataInputRepo[F, D], 37 | EpochInfoRepo[F, D], 38 | OutputRepo[F, D], 39 | AssetRepo[F, D], 40 | BoxRegisterRepo[F, D], 41 | TokenRepo[F, D], 42 | ScriptConstantsRepo[F, D] 43 | ).mapN(RepoBundle(_, _, _, _, _, _, _, _, _, _, _, _, _)) 44 | } 45 | -------------------------------------------------------------------------------- /modules/chain-grabber/src/main/scala/org/ergoplatform/explorer/settings/IndexerSettings.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.settings 2 | 3 | import scala.concurrent.duration.FiniteDuration 4 | 5 | final case class EnabledIndexes( 6 | boxRegisters: Boolean, 7 | scriptConstants: Boolean, 8 | blockExtensions: Boolean, 9 | adProofs: Boolean, 10 | blockStats: Boolean 11 | ) 12 | 13 | final case class IndexerSettings( 14 | chainPollInterval: FiniteDuration, 15 | epochPollInterval: FiniteDuration, 16 | writeOrphans: Boolean, 17 | network: NetworkSettings, 18 | db: DbSettings, 19 | protocol: ProtocolSettings, 20 | indexes: EnabledIndexes, 21 | startHeight: Option[Int], 22 | redisCache: RedisSettings 23 | ) 24 | 25 | object IndexerSettings extends SettingCompanion[IndexerSettings] 26 | -------------------------------------------------------------------------------- /modules/chain-grabber/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %white(%d{HH:mm:ss.SSS}) %highlight(%-5level) %cyan(%logger{50}) - %msg %n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /modules/chain-grabber/src/test/scala/org/ergoplatform/explorer/indexer/CacheMock.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.indexer 2 | 3 | import cats.Applicative 4 | import org.ergoplatform.explorer.indexer.cache.ApiQueryCache 5 | 6 | object CacheMock { 7 | def make[F[_]: Applicative]: ApiQueryCache[F] = new ApiQueryCache[F] { 8 | def flushAll: F[Unit] = Applicative[F].unit 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /modules/chain-grabber/src/test/scala/org/ergoplatform/explorer/indexer/GrabberTestNetwork.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.indexer 2 | 3 | import cats.Applicative 4 | import cats.syntax.applicative._ 5 | import org.ergoplatform.explorer.services.ErgoNetwork 6 | import org.ergoplatform.{ErgoLikeTransaction, explorer} 7 | import org.ergoplatform.explorer.indexer.GrabberTestNetwork.Source 8 | import org.ergoplatform.explorer.protocol.models.{ApiFullBlock, ApiNodeInfo, ApiTransaction} 9 | 10 | final class GrabberTestNetwork[F[_]: Applicative](val source: Source) 11 | extends ErgoNetwork[F] { 12 | 13 | def getBestHeight: F[Int] = 14 | source.blocksStorage.maxBy(_._1)._1.pure[F] 15 | 16 | def getBlockIdsAtHeight(height: Int): F[List[explorer.BlockId]] = 17 | source.blocksStorage.get(height).toList.flatten.map(_.header.id).pure[F] 18 | 19 | def getFullBlockById(id: explorer.BlockId): F[Option[ApiFullBlock]] = 20 | source.blocksStorage.values.flatten.find(_.header.id == id).pure[F] 21 | 22 | def getUnconfirmedTransactions: F[List[ApiTransaction]] = ??? 23 | 24 | def submitTransaction(tx: ErgoLikeTransaction): F[Unit] = ??? 25 | 26 | def getNodeInfo: F[ApiNodeInfo] = ??? 27 | } 28 | 29 | object GrabberTestNetwork { 30 | 31 | final case class Source(blocks: List[ApiFullBlock]) { 32 | 33 | lazy val blocksStorage: Map[Int, List[ApiFullBlock]] = 34 | blocks 35 | .groupBy(_.header.height) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /modules/explorer-api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-jre-slim as builder 2 | RUN apt-get update && \ 3 | apt-get install -y --no-install-recommends apt-transport-https apt-utils bc dirmngr gnupg && \ 4 | echo "deb https://repo.scala-sbt.org/scalasbt/debian /" | tee -a /etc/apt/sources.list.d/sbt.list && \ 5 | apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2EE0EA64E40A89B84B2DF73499E82A75642AC823 && \ 6 | apt-get update && \ 7 | apt-get upgrade -y && \ 8 | apt-get install -y --no-install-recommends sbt 9 | COPY . /explorer-backend 10 | WORKDIR /explorer-backend 11 | RUN sbt explorer-api/assembly 12 | RUN mv `find . -name ExplorerApi-assembly-*.jar` /explorer-api.jar 13 | CMD ["/usr/bin/java", "-jar", "/explorer-api.jar"] 14 | 15 | FROM openjdk:8-jre-slim 16 | COPY --from=builder /explorer-api.jar /explorer-api.jar 17 | ENTRYPOINT java -jar /explorer-api.jar $0 -------------------------------------------------------------------------------- /modules/explorer-api/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | http.port = 8080 2 | http.host = "0.0.0.0" 3 | 4 | db.url = "jdbc:postgresql://localhost:5432/explorer" 5 | db.user = "postgres" 6 | db.pass = "1234" 7 | db.cp-size = 8 8 | 9 | redis.url = "redis://localhost:6379" 10 | 11 | utx-cache.transaction-ttl = 48h 12 | 13 | requests.max-entities-per-request = 500 14 | requests.max-entities-per-heavy-request = 100 15 | requests.max-blocks-per-request = 1536 16 | service.chunk-size = 100 17 | 18 | protocol { 19 | network-prefix = 0 20 | genesis-address = "2Z4YBkDsDvQj8BX7xiySFewjitqp2ge9c99jfes2whbtKitZTxdBYqbrVZUvZvKv6aqn9by4kp3LE1c26LCyosFnVnm6b6U1JYvWpYmL2ZnixJbXLjWAWuBThV1D6dLpqZJYQHYDznJCk49g5TUiS4q8khpag2aNmHwREV7JSsypHdHLgJT7MGaw51aJfNubyzSKxZ4AJXFS27EfXwyCLzW1K6GVqwkJtCoPvrcLqmqwacAWJPkmh78nke9H4oT88XmSbRt2n9aWZjosiZCafZ4osUDxmZcc5QVEeTWn8drSraY3eFKe8Mu9MSCcVU" 21 | 22 | # Monetary config for chain 23 | monetary { 24 | # number of blocks reward won't change (2 years) 25 | fixed-rate-period = 525600 26 | # number of coins issued every block during fixedRatePeriod (75 Ergo) 27 | fixed-rate = 75000000000 28 | # Part of coins issued, that is going to the foundation during fixedRatePeriod (7.5 Ergo) 29 | founders-initial-reward = 7500000000 30 | # number of blocks between reward reduction (90 days) 31 | epoch-length = 64800 32 | # number of coins reward decrease every epochs (3 Ergo) 33 | one-epoch-reduction = 3000000000 34 | # delay between the block mined and a time, when the reward can be spend. ~ 1 day. 35 | miner-reward-delay = 720 36 | } 37 | } 38 | 39 | redis-cache.url = "redis://localhost:6380" -------------------------------------------------------------------------------- /modules/explorer-api/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %white(%d{HH:mm:ss.SSS}) %highlight(%-5level) %cyan(%logger{50}) - %msg %n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/algebra/AdaptThrowable.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.algebra 2 | 3 | import cats.MonadError 4 | import cats.data.EitherT 5 | 6 | /** Adapt effect which can fail with any [[Throwable]] to some effect with narrower static error type `E`. 7 | */ 8 | trait AdaptThrowable[F[_], G[_, _], E] { 9 | 10 | def adaptThrowable[A](fa: F[A]): G[E, A] 11 | } 12 | 13 | object AdaptThrowable { 14 | 15 | abstract class AdaptThrowableEitherT[F[_], E]( 16 | implicit F: MonadError[F, Throwable] 17 | ) extends AdaptThrowable[F, EitherT[F, *, *], E] { 18 | 19 | def adapter: Throwable => F[E] 20 | 21 | final def adaptThrowable[A](fa: F[A]): EitherT[F, E, A] = 22 | EitherT(F.attempt(fa)).leftSemiflatMap(adapter) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/cache/models/CachedResponse.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.cache.models 2 | 3 | import cats.syntax.either._ 4 | import derevo.circe.{decoder, encoder} 5 | import derevo.derive 6 | import io.circe.{Decoder, Encoder} 7 | import org.http4s.{Header, Headers, HttpVersion, Status} 8 | import org.typelevel.ci.CIString 9 | 10 | @derive(encoder, decoder) 11 | case class CachedResponse( 12 | status: Status, 13 | httpVersion: HttpVersion, 14 | headers: Headers, 15 | body: String 16 | ) 17 | 18 | object CachedResponse { 19 | implicit val encoderStatus: Encoder[Status] = Encoder[Int].contramap(_.code) 20 | implicit val decoderStatus: Decoder[Status] = Decoder[Int].emap(s => Status.fromInt(s).leftMap(_.message)) 21 | 22 | implicit val encoderHttpVersion: Encoder[HttpVersion] = Encoder[String].contramap(_.renderString) 23 | 24 | implicit val decoderHttpVersion: Decoder[HttpVersion] = 25 | Decoder[String].emap(s => HttpVersion.fromString(s).leftMap(_.message)) 26 | 27 | implicit val encoderHeaders: Encoder[Headers] = Encoder[List[(String, String)]].contramap { headers => 28 | headers.headers.map(h => h.name.toString -> h.value) 29 | } 30 | 31 | implicit val decoderHeaders: Decoder[Headers] = Decoder[List[(String, String)]].emap { s => 32 | Headers.apply(s.map(t => Header.Raw.apply(CIString(t._1), t._2))).asRight 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/cache/types.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.cache 2 | 3 | import cats.Show 4 | import cats.effect.Sync 5 | import io.estatico.newtype.macros.newtype 6 | import org.http4s.Request 7 | import tofu.logging.Loggable 8 | import cats.syntax.functor._ 9 | 10 | import java.security.MessageDigest 11 | 12 | object types { 13 | 14 | @newtype 15 | case class RequestHash32(value: String) 16 | 17 | object RequestHash32 { 18 | implicit val show: Show[RequestHash32] = deriving 19 | implicit val loggable: Loggable[RequestHash32] = Loggable.show 20 | 21 | def apply[F[_]: Sync](request: Request[F]): F[RequestHash32] = 22 | request.body.compile.toList.map { body => 23 | RequestHash32( 24 | new String( 25 | MessageDigest 26 | .getInstance("SHA-256") 27 | .digest( 28 | (request.method.toString ++ request.uri.toString ++ body.map(_.toChar).mkString).getBytes 29 | ) 30 | ) 31 | ) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/decodingFailureHandler.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api 2 | 3 | import cats.effect.{ContextShift, Sync} 4 | import sttp.model.{Header, StatusCode} 5 | import sttp.tapir._ 6 | import sttp.tapir.json.circe._ 7 | import sttp.tapir.server.http4s.Http4sServerOptions 8 | import sttp.tapir.server.interceptor.ValuedEndpointOutput 9 | import sttp.tapir.server.interceptor.decodefailure.DefaultDecodeFailureHandler 10 | import cats.syntax.option._ 11 | 12 | object decodingFailureHandler { 13 | 14 | //todo: check 15 | implicit def customServerOptions[F[_]: Sync: ContextShift]: Http4sServerOptions[F, F] = 16 | Http4sServerOptions.customInterceptors( 17 | decodeFailureHandler = decodingFailureHandler, 18 | exceptionHandler = none, 19 | serverLog = none 20 | ) 21 | 22 | private def decodingFailureResponse(c: StatusCode, hs: List[Header], m: String): ValuedEndpointOutput[_] = 23 | ValuedEndpointOutput(statusCode.and(headers).and(jsonBody[ApiErr.BadRequest]), (c, hs, ApiErr.badRequest(m))) 24 | 25 | private def decodingFailureHandler: DefaultDecodeFailureHandler = 26 | DefaultDecodeFailureHandler.handler.copy( 27 | response = decodingFailureResponse 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/defs.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api 2 | 3 | import sttp.model.StatusCode 4 | import sttp.tapir._ 5 | import sttp.tapir.json.circe._ 6 | 7 | object defs { 8 | 9 | def baseEndpointDef(basePrefix: EndpointInput[Unit]): Endpoint[Unit, ApiErr, Unit, Any] = 10 | endpoint 11 | .in(basePrefix) 12 | .errorOut( 13 | oneOf( 14 | oneOfMapping( 15 | StatusCode.NotFound, 16 | jsonBody[ApiErr.NotFound].description("Not found") 17 | ), 18 | oneOfMapping( 19 | StatusCode.BadRequest, 20 | jsonBody[ApiErr.BadRequest].description("Bad request") 21 | ), 22 | oneOfDefaultMapping(jsonBody[ApiErr.UnknownErr].description("Unknown error")) 23 | ) 24 | ) 25 | .asInstanceOf[Endpoint[Unit, ApiErr, Unit, Any]] 26 | } 27 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/models/HeightRange.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.models 2 | 3 | final case class HeightRange(minHeight: Int, maxHeight: Int) 4 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/models/InclusionHeightRange.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.models 2 | 3 | final case class InclusionHeightRange(fromHeight: Int, toHeight: Int) 4 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/models/Items.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.models 2 | 3 | import derevo.circe.{decoder, encoder} 4 | import derevo.derive 5 | import sttp.tapir.{Schema, Validator} 6 | 7 | @derive(encoder, decoder) 8 | final case class Items[A](items: List[A], total: Int) 9 | 10 | object Items { 11 | 12 | implicit def schema[A: Schema]: Schema[Items[A]] = 13 | Schema.derived[Items[A]] 14 | .modify(_.items)(_.description("Items in selection")) 15 | .modify(_.total)(_.description("Total qty of items")) 16 | 17 | implicit def validator[A: Schema]: Validator[Items[A]] = schema.validator 18 | } 19 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/models/Paging.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.models 2 | 3 | final case class Paging(offset: Int, limit: Int) 4 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/models/Sorting.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.models 2 | 3 | import org.ergoplatform.explorer.http.api.models.Sorting.SortOrder 4 | import sttp.tapir.{Codec, DecodeResult} 5 | import eu.timepit.refined.refineMV 6 | import org.ergoplatform.explorer.constraints.OrderingString 7 | 8 | final case class Sorting(sortBy: String, order: SortOrder) 9 | 10 | object Sorting { 11 | 12 | sealed trait SortOrder { def value: OrderingString } 13 | 14 | object SortOrder { 15 | 16 | implicit val codec: Codec.PlainCodec[SortOrder] = Codec.string 17 | .mapDecode(fromString)(_.toString) 18 | 19 | private def fromString(s: String): DecodeResult[SortOrder] = 20 | s.trim.toLowerCase match { 21 | case "asc" => DecodeResult.Value(Asc) 22 | case "desc" => DecodeResult.Value(Desc) 23 | case other => DecodeResult.Mismatch("`asc` or `desc`", other) 24 | } 25 | } 26 | 27 | case object Asc extends SortOrder { 28 | override def toString: String = value.value 29 | def value: OrderingString = refineMV("asc") 30 | } 31 | 32 | case object Desc extends SortOrder { 33 | override def toString: String = value.value 34 | def value: OrderingString = refineMV("desc") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/streaming.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api 2 | 3 | import cats.Applicative 4 | import cats.syntax.either._ 5 | import fs2.{Chunk, Stream} 6 | import io.circe.Encoder 7 | import io.circe.syntax._ 8 | import tofu.streams.Compile 9 | import tofu.syntax.monadic._ 10 | 11 | object streaming { 12 | 13 | type CompileStream[F[_]] = Compile[Stream[F, *], F] 14 | 15 | def bytesStream[F[_]: Applicative, A: Encoder](fa: Stream[F, A]): F[Either[ApiErr, Stream[F, Byte]]] = 16 | fa.flatMap(entity => Stream.chunk(Chunk.array(entity.asJson.noSpaces.getBytes))) 17 | .pure 18 | .map(_.asRight[ApiErr]) 19 | } 20 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/syntax/AdaptThrowableOps.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.syntax 2 | 3 | import org.ergoplatform.explorer.http.api.algebra.AdaptThrowable 4 | 5 | final class AdaptThrowableOps[F[_], G[_, _], E, A](fa: F[A])( 6 | implicit A: AdaptThrowable[F, G, E] 7 | ) { 8 | 9 | def adaptThrowable: G[E, A] = A.adaptThrowable(fa) 10 | } 11 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/syntax/RoutesOps.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.syntax 2 | 3 | import cats.Monad 4 | import cats.data.EitherT 5 | import cats.syntax.applicative._ 6 | import org.ergoplatform.explorer.http.api.ApiErr 7 | 8 | final class RoutesOps[F[_], A](val fa: EitherT[F, ApiErr, Option[A]]) extends AnyVal { 9 | 10 | def orNotFound(what: String)(implicit M: Monad[F]): EitherT[F, ApiErr, A] = 11 | fa.flatMap( 12 | _.fold[EitherT[F, ApiErr, A]](EitherT.left((ApiErr.notFound(what): ApiErr).pure))( 13 | EitherT.pure[F, ApiErr](_) 14 | ) 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/syntax/package.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api 2 | 3 | import cats.data.EitherT 4 | import org.ergoplatform.explorer.http.api.algebra.AdaptThrowable 5 | 6 | package object syntax { 7 | 8 | object adaptThrowable { 9 | 10 | implicit def toAdaptThrowableOps[F[_], G[_, _], E, A](fa: F[A])( 11 | implicit A: AdaptThrowable[F, G, E] 12 | ): AdaptThrowableOps[F, G, E, A] = 13 | new AdaptThrowableOps(fa) 14 | } 15 | 16 | object routes { 17 | 18 | implicit def toRoutesOps[F[_], A](fa: EitherT[F, ApiErr, Option[A]]): RoutesOps[F, A] = 19 | new RoutesOps(fa) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/tapirInstances.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api 2 | 3 | import sttp.tapir.Schema 4 | 5 | object tapirInstances { 6 | 7 | implicit val bigIntSchema: Schema[BigInt] = 8 | Schema.schemaForBigDecimal.map(x => Some(x.toBigInt()))(x => BigDecimal(x.bigInteger)) 9 | } 10 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/defs/AddressesEndpointDefs.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.defs 2 | 3 | import org.ergoplatform.explorer.http.api.ApiErr 4 | import org.ergoplatform.explorer.http.api.commonDirectives._ 5 | import org.ergoplatform.explorer.http.api.models.{Items, Paging} 6 | import org.ergoplatform.explorer.http.api.v0.models.{AddressInfo, BalanceInfo, TransactionInfo} 7 | import org.ergoplatform.explorer.{Address, TokenId} 8 | import sttp.tapir._ 9 | import sttp.tapir.json.circe._ 10 | 11 | object AddressesEndpointDefs { 12 | 13 | private val PathPrefix = "addresses" 14 | 15 | def endpoints: List[Endpoint[_, _, _, _]] = 16 | getAddressDef :: getTxsByAddressDef :: getAssetHoldersDef :: getBalancesDef :: Nil 17 | 18 | def getAddressDef: Endpoint[(Address, Int), ApiErr, AddressInfo, Any] = 19 | baseEndpointDef 20 | .in(PathPrefix / path[Address]) 21 | .in(confirmations) 22 | .out(jsonBody[AddressInfo]) 23 | 24 | def getTxsByAddressDef: Endpoint[(Address, Paging, Boolean), ApiErr, Items[TransactionInfo], Any] = 25 | baseEndpointDef 26 | .in(PathPrefix / path[Address] / "transactions") 27 | .in(paging) 28 | .in(concise) 29 | .out(jsonBody[Items[TransactionInfo]]) 30 | 31 | def getAssetHoldersDef: Endpoint[(TokenId, Paging), ApiErr, List[Address], Any] = 32 | baseEndpointDef 33 | .in(PathPrefix / "assetHolders" / path[TokenId]) 34 | .in(paging) 35 | .out(jsonBody[List[Address]]) 36 | 37 | def getBalancesDef: Endpoint[Paging, ApiErr, Items[BalanceInfo], Any] = 38 | baseEndpointDef 39 | .in(PathPrefix / "balances") 40 | .in(paging) 41 | .out(jsonBody[Items[BalanceInfo]]) 42 | } 43 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/defs/AssetsEndpointDefs.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.defs 2 | 3 | import org.ergoplatform.explorer.TokenId 4 | import org.ergoplatform.explorer.http.api.ApiErr 5 | import org.ergoplatform.explorer.http.api.commonDirectives.paging 6 | import org.ergoplatform.explorer.http.api.models.{Items, Paging} 7 | import org.ergoplatform.explorer.http.api.v0.models.OutputInfo 8 | import sttp.tapir._ 9 | import sttp.tapir.json.circe._ 10 | 11 | object AssetsEndpointDefs { 12 | 13 | private val PathPrefix = "assets" 14 | 15 | def endpoints: List[Endpoint[_, _, _, _]] = 16 | getAllIssuingBoxesDef :: getIssuingBoxDef :: Nil 17 | 18 | def getAllIssuingBoxesDef: Endpoint[Paging, ApiErr, Items[OutputInfo], Any] = 19 | baseEndpointDef 20 | .in(paging) 21 | .in(PathPrefix / "issuingBoxes") 22 | .out(jsonBody[Items[OutputInfo]]) 23 | 24 | def getIssuingBoxDef: Endpoint[TokenId, ApiErr, List[OutputInfo], Any] = 25 | baseEndpointDef 26 | .in(PathPrefix / path[TokenId] / "issuingBox") 27 | .out(jsonBody[List[OutputInfo]]) 28 | } 29 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/defs/BoxesEndpointDefs.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.defs 2 | 3 | import org.ergoplatform.explorer.{Address, BoxId, HexString} 4 | import org.ergoplatform.explorer.http.api.ApiErr 5 | import org.ergoplatform.explorer.http.api.v0.models.OutputInfo 6 | import sttp.tapir._ 7 | import sttp.tapir.json.circe._ 8 | 9 | object BoxesEndpointDefs { 10 | 11 | private val PathPrefix = "transactions" / "boxes" 12 | 13 | def endpoints: List[Endpoint[_, _, _, _]] = 14 | getOutputByIdDef :: getOutputsByErgoTreeDef :: getUnspentOutputsByErgoTreeDef :: 15 | getOutputsByAddressDef :: getUnspentOutputsByAddressDef :: Nil 16 | 17 | def getOutputByIdDef: Endpoint[BoxId, ApiErr, OutputInfo, Any] = 18 | baseEndpointDef.get.in(PathPrefix / path[BoxId]).out(jsonBody[OutputInfo]) 19 | 20 | def getOutputsByErgoTreeDef: Endpoint[HexString, ApiErr, List[OutputInfo], Any] = 21 | baseEndpointDef.get 22 | .in(PathPrefix / "byErgoTree" / path[HexString]) 23 | .out(jsonBody[List[OutputInfo]]) 24 | 25 | def getUnspentOutputsByErgoTreeDef: Endpoint[HexString, ApiErr, List[OutputInfo], Any] = 26 | baseEndpointDef.get 27 | .in(PathPrefix / "byErgoTree" / "unspent" / path[HexString]) 28 | .out(jsonBody[List[OutputInfo]]) 29 | 30 | def getOutputsByAddressDef: Endpoint[Address, ApiErr, List[OutputInfo], Any] = 31 | baseEndpointDef.get 32 | .in(PathPrefix / "byAddress" / path[Address]) 33 | .out(jsonBody[List[OutputInfo]]) 34 | 35 | def getUnspentOutputsByAddressDef: Endpoint[Address, ApiErr, List[OutputInfo], Any] = 36 | baseEndpointDef.get 37 | .in(PathPrefix / "byAddress" / "unspent" / path[Address]) 38 | .out(jsonBody[List[OutputInfo]]) 39 | } 40 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/defs/DexEndpointsDefs.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.defs 2 | 3 | import org.ergoplatform.explorer.TokenId 4 | import org.ergoplatform.explorer.http.api.ApiErr 5 | import org.ergoplatform.explorer.http.api.commonDirectives._ 6 | import org.ergoplatform.explorer.http.api.models.Paging 7 | import org.ergoplatform.explorer.http.api.v0.models.{DexBuyOrderInfo, DexSellOrderInfo} 8 | import sttp.tapir._ 9 | import sttp.tapir.json.circe._ 10 | 11 | object DexEndpointsDefs { 12 | 13 | private val PathPrefix = "dex" 14 | 15 | def endpoints: List[Endpoint[_, _, _, _]] = 16 | getUnspentSellOrdersDef :: getUnspentBuyOrdersDef :: Nil 17 | 18 | def getUnspentSellOrdersDef 19 | : Endpoint[(TokenId, Paging), ApiErr, List[DexSellOrderInfo], Any] = 20 | baseEndpointDef.get 21 | .description("DEX sell orders for a given token id") 22 | .in(PathPrefix / "tokens" / path[TokenId] / "unspentSellOrders") 23 | .in(paging) 24 | .out(jsonBody[List[DexSellOrderInfo]].description("Sell order for DEX, where ask is in tokenPrice, DEX fee is in outputInfo.value and token is in outputInfo.assets(0)")) 25 | 26 | def getUnspentBuyOrdersDef 27 | : Endpoint[(TokenId, Paging), ApiErr, List[DexBuyOrderInfo], Any] = 28 | baseEndpointDef.get 29 | .description("DEX buy orders for a given token id") 30 | .in(PathPrefix / "tokens" / path[TokenId] / "unspentBuyOrders") 31 | .in(paging) 32 | .out(jsonBody[List[DexBuyOrderInfo]].description("Buy order for DEX, where bid + DEX fee are in outputInfo.value and tokenId and tokenAmount are parsed from buyer's contract")) 33 | } 34 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/defs/DocsEndpointDefs.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.defs 2 | 3 | import org.ergoplatform.explorer.http.api.ApiErr 4 | import sttp.tapir._ 5 | 6 | object DocsEndpointDefs { 7 | 8 | private val PathPrefix = "docs" 9 | 10 | def endpoints: List[Endpoint[_, _, _, _]] = apiSpecDef :: Nil 11 | 12 | def apiSpecDef: Endpoint[Unit, ApiErr, String, Any] = 13 | baseEndpointDef.in(PathPrefix / "openapi").out(plainBody[String]) 14 | } 15 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/defs/InfoEndpointDefs.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.defs 2 | 3 | import org.ergoplatform.explorer.http.api.ApiErr 4 | import org.ergoplatform.explorer.http.api.v0.models.BlockChainInfo 5 | import sttp.tapir.Endpoint 6 | 7 | import sttp.tapir._ 8 | import sttp.tapir.json.circe._ 9 | 10 | object InfoEndpointDefs { 11 | 12 | private val PathPrefix = "info" 13 | 14 | def endpoints: List[Endpoint[_, _, _, _]] = 15 | getBlockChainInfoDef :: getCurrentSupplyDef :: Nil 16 | 17 | def getBlockChainInfoDef: Endpoint[Unit, ApiErr, BlockChainInfo, Any] = 18 | baseEndpointDef.get.in(PathPrefix).out(jsonBody[BlockChainInfo]) 19 | 20 | def getCurrentSupplyDef: Endpoint[Unit, ApiErr, String, Any] = 21 | baseEndpointDef.get.in(PathPrefix / "supply").out(plainBody[String]) 22 | } 23 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/defs/SearchEndpointDefs.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.defs 2 | 3 | import org.ergoplatform.explorer.http.api.ApiErr 4 | import org.ergoplatform.explorer.http.api.v0.models.SearchResult 5 | import sttp.tapir._ 6 | import sttp.tapir.json.circe._ 7 | 8 | object SearchEndpointDefs { 9 | 10 | private val PathPrefix = "search" 11 | 12 | def endpoints: List[Endpoint[_, _, _, _]] = Nil 13 | 14 | def searchDef: Endpoint[String, ApiErr, SearchResult, Any] = 15 | baseEndpointDef.get.in(PathPrefix).in(queryInput).out(jsonBody[SearchResult]) 16 | 17 | private def queryInput: EndpointInput.Query[String] = 18 | query[String]("query").validate(Validator.minLength(5)) 19 | } 20 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/defs/StatsEndpointDefs.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.defs 2 | 3 | import org.ergoplatform.explorer.http.api.ApiErr 4 | import org.ergoplatform.explorer.http.api.v0.models.StatsSummary 5 | import sttp.tapir.Endpoint 6 | import sttp.tapir._ 7 | import sttp.tapir.json.circe._ 8 | 9 | object StatsEndpointDefs { 10 | 11 | private val PathPrefix = "stats" 12 | 13 | def endpoints: List[Endpoint[_, _, _, _]] = getCurrentStatsDef :: Nil 14 | 15 | def getCurrentStatsDef: Endpoint[Unit, ApiErr, StatsSummary, Any] = 16 | baseEndpointDef.get.in(PathPrefix).out(jsonBody[StatsSummary]) 17 | } 18 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/defs/package.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0 2 | 3 | import org.ergoplatform.explorer.http 4 | import org.ergoplatform.explorer.http.api.ApiErr 5 | import sttp.tapir._ 6 | 7 | package object defs { 8 | 9 | private val V0Prefix: EndpointInput[Unit] = "api" / "v0" 10 | 11 | val baseEndpointDef: Endpoint[Unit, ApiErr, Unit, Any] = 12 | http.api.defs.baseEndpointDef(V0Prefix) 13 | } 14 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/models/AssetSummary.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.models 2 | 3 | import io.circe.Codec 4 | import io.circe.magnolia.derivation.decoder.semiauto.deriveMagnoliaDecoder 5 | import io.circe.magnolia.derivation.encoder.semiauto.deriveMagnoliaEncoder 6 | import org.ergoplatform.explorer.TokenId 7 | import sttp.tapir.{Schema, Validator} 8 | 9 | final case class AssetSummary(tokenId: TokenId, amount: Long, name: Option[String], decimals: Option[Int]) 10 | 11 | object AssetSummary { 12 | 13 | implicit val codec: Codec[AssetSummary] = Codec.from(deriveMagnoliaDecoder, deriveMagnoliaEncoder) 14 | 15 | implicit val schema: Schema[AssetSummary] = 16 | Schema 17 | .derived[AssetSummary] 18 | .modify(_.tokenId)(_.description("Token ID")) 19 | .modify(_.amount)(_.description("Amount of tokens")) 20 | .modify(_.decimals)(_.description("Number of decimal places")) 21 | .modify(_.name)(_.description("Name of a token")) 22 | 23 | implicit val validator: Validator[AssetSummary] = schema.validator 24 | } 25 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/models/BalanceInfo.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.models 2 | 3 | import io.circe.Codec 4 | import io.circe.magnolia.derivation.decoder.semiauto.deriveMagnoliaDecoder 5 | import io.circe.magnolia.derivation.encoder.semiauto.deriveMagnoliaEncoder 6 | import org.ergoplatform.explorer.Address 7 | import sttp.tapir.{Schema, Validator} 8 | 9 | final case class BalanceInfo(address: Address, balance: Long) 10 | 11 | object BalanceInfo { 12 | 13 | implicit val codec: Codec[BalanceInfo] = Codec.from(deriveMagnoliaDecoder, deriveMagnoliaEncoder) 14 | 15 | implicit val schema: Schema[BalanceInfo] = 16 | Schema 17 | .derived[BalanceInfo] 18 | .modify(_.address)(_.description("Address")) 19 | .modify(_.balance)(_.description("Balance in nanoERG")) 20 | 21 | implicit val validator: Validator[BalanceInfo] = schema.validator 22 | } 23 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/models/BlockChainInfo.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.models 2 | 3 | import io.circe.Codec 4 | import io.circe.magnolia.derivation.decoder.semiauto.deriveMagnoliaDecoder 5 | import io.circe.magnolia.derivation.encoder.semiauto.deriveMagnoliaEncoder 6 | import sttp.tapir.{Schema, Validator} 7 | import org.ergoplatform.explorer.http.api.tapirInstances._ 8 | 9 | final case class BlockChainInfo( 10 | version: String, 11 | supply: Long, 12 | transactionAverage: Int, // avg. number of transactions per block. 13 | hashRate: BigInt 14 | ) 15 | 16 | object BlockChainInfo { 17 | 18 | implicit val codec: Codec[BlockChainInfo] = Codec.from(deriveMagnoliaDecoder, deriveMagnoliaEncoder) 19 | 20 | implicit val schema: Schema[BlockChainInfo] = 21 | Schema 22 | .derived[BlockChainInfo] 23 | .modify(_.version)(_.description("Network protocol version")) 24 | .modify(_.supply)(_.description("Total supply in nanoErgs")) 25 | .modify(_.transactionAverage)(_.description("Average number of transactions per block")) 26 | .modify(_.hashRate)(_.description("Network hash rate")) 27 | 28 | implicit val validator: Validator[BlockChainInfo] = schema.validator 29 | 30 | def empty: BlockChainInfo = BlockChainInfo("0.0.0", 0L, 0, 0L) 31 | } 32 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/models/BlockExtensionInfo.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.models 2 | 3 | import io.circe.magnolia.derivation.decoder.semiauto.deriveMagnoliaDecoder 4 | import io.circe.magnolia.derivation.encoder.semiauto.deriveMagnoliaEncoder 5 | import io.circe.{Codec, Json} 6 | import org.ergoplatform.explorer.db.models.BlockExtension 7 | import org.ergoplatform.explorer.{BlockId, HexString} 8 | import sttp.tapir.{Schema, SchemaType, Validator} 9 | 10 | final case class BlockExtensionInfo( 11 | headerId: BlockId, 12 | digest: HexString, 13 | fields: Json 14 | ) 15 | 16 | object BlockExtensionInfo { 17 | 18 | implicit val codec: Codec[BlockExtensionInfo] = Codec.from(deriveMagnoliaDecoder, deriveMagnoliaEncoder) 19 | 20 | implicit private def registersSchema: Schema[Json] = 21 | Schema( 22 | SchemaType.SOpenProduct( 23 | Schema(SchemaType.SString[Json]()) 24 | )(_ => Map.empty) 25 | ) 26 | 27 | implicit val schema: Schema[BlockExtensionInfo] = 28 | Schema 29 | .derived[BlockExtensionInfo] 30 | .modify(_.headerId)(_.description("ID of the corresponding header")) 31 | .modify(_.digest)(_.description("Hex-encoded extension digest")) 32 | 33 | implicit val validator: Validator[BlockExtensionInfo] = schema.validator 34 | 35 | def apply(blockExtension: BlockExtension): BlockExtensionInfo = 36 | BlockExtensionInfo( 37 | blockExtension.headerId, 38 | blockExtension.digest, 39 | blockExtension.fields 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/models/BlockReferencesInfo.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.models 2 | 3 | import io.circe.Codec 4 | import io.circe.magnolia.derivation.decoder.semiauto.deriveMagnoliaDecoder 5 | import io.circe.magnolia.derivation.encoder.semiauto.deriveMagnoliaEncoder 6 | import org.ergoplatform.explorer.BlockId 7 | import sttp.tapir.{Schema, Validator} 8 | 9 | final case class BlockReferencesInfo(previousId: BlockId, nextId: Option[BlockId]) 10 | 11 | object BlockReferencesInfo { 12 | 13 | implicit val codec: Codec[BlockReferencesInfo] = Codec.from(deriveMagnoliaDecoder, deriveMagnoliaEncoder) 14 | 15 | implicit val schema: Schema[BlockReferencesInfo] = 16 | Schema 17 | .derived[BlockReferencesInfo] 18 | .modify(_.previousId)(_.description("ID of the previous block")) 19 | .modify(_.nextId)(_.description("ID of the next block (if one exists)")) 20 | 21 | implicit val validator: Validator[BlockReferencesInfo] = schema.validator 22 | } 23 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/models/BlockSummary.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.models 2 | 3 | import io.circe.Codec 4 | import io.circe.magnolia.derivation.decoder.semiauto.deriveMagnoliaDecoder 5 | import io.circe.magnolia.derivation.encoder.semiauto.deriveMagnoliaEncoder 6 | import sttp.tapir.{Schema, Validator} 7 | 8 | final case class BlockSummary(block: FullBlockInfo, references: BlockReferencesInfo) 9 | 10 | object BlockSummary { 11 | 12 | implicit val codec: Codec[BlockSummary] = Codec.from(deriveMagnoliaDecoder, deriveMagnoliaEncoder) 13 | 14 | implicit val schema: Schema[BlockSummary] = 15 | Schema 16 | .derived[BlockSummary] 17 | .modify(_.block)(_.description("Full block info")) 18 | .modify(_.references)( 19 | _.description("References to previous and next (if exists) blocks") 20 | ) 21 | 22 | implicit val validator: Validator[BlockSummary] = schema.validator 23 | } 24 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/models/ChartPoint.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.models 2 | 3 | import io.circe.Codec 4 | import io.circe.magnolia.derivation.decoder.semiauto.deriveMagnoliaDecoder 5 | import io.circe.magnolia.derivation.encoder.semiauto.deriveMagnoliaEncoder 6 | import org.ergoplatform.explorer.db.models.aggregates.TimePoint 7 | import sttp.tapir.{Schema, Validator} 8 | import org.ergoplatform.explorer.http.api.tapirInstances._ 9 | 10 | final case class ChartPoint(timestamp: Long, value: BigInt) 11 | 12 | object ChartPoint { 13 | 14 | def apply(point: TimePoint[Long]): ChartPoint = 15 | ChartPoint(point.ts, BigInt(point.value)) 16 | 17 | def fromBigIntPoint(point: TimePoint[BigInt]): ChartPoint = 18 | ChartPoint(point.ts, point.value) 19 | 20 | implicit val codec: Codec[ChartPoint] = Codec.from(deriveMagnoliaDecoder, deriveMagnoliaEncoder) 21 | 22 | implicit val schema: Schema[ChartPoint] = Schema.derived 23 | 24 | implicit val validator: Validator[ChartPoint] = schema.validator 25 | } 26 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/models/DexBuyOrderInfo.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.models 2 | 3 | import io.circe.Codec 4 | import io.circe.magnolia.derivation.decoder.semiauto.deriveMagnoliaDecoder 5 | import io.circe.magnolia.derivation.encoder.semiauto.deriveMagnoliaEncoder 6 | import org.ergoplatform.explorer.TokenId 7 | import org.ergoplatform.explorer.db.models.aggregates.{ExtendedAsset, ExtendedOutput} 8 | import sttp.tapir.{Schema, Validator} 9 | 10 | final case class DexBuyOrderInfo( 11 | outputInfo: OutputInfo, 12 | tokenId: TokenId, 13 | tokenAmount: Long 14 | ) 15 | 16 | object DexBuyOrderInfo { 17 | 18 | implicit val codec: Codec[DexBuyOrderInfo] = Codec.from(deriveMagnoliaDecoder, deriveMagnoliaEncoder) 19 | 20 | implicit val schema: Schema[DexBuyOrderInfo] = Schema.derived 21 | 22 | implicit val validator: Validator[DexBuyOrderInfo] = schema.validator 23 | 24 | def apply( 25 | output: ExtendedOutput, 26 | tokenId: TokenId, 27 | tokenAmount: Long, 28 | assets: List[ExtendedAsset] 29 | ): DexBuyOrderInfo = 30 | new DexBuyOrderInfo(OutputInfo(output, assets), tokenId, tokenAmount) 31 | } 32 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/models/DexSellOrderInfo.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.models 2 | 3 | import io.circe.Codec 4 | import io.circe.magnolia.derivation.decoder.semiauto.deriveMagnoliaDecoder 5 | import io.circe.magnolia.derivation.encoder.semiauto.deriveMagnoliaEncoder 6 | import org.ergoplatform.explorer.db.models.aggregates.{ExtendedAsset, ExtendedOutput} 7 | import sttp.tapir.{Schema, Validator} 8 | 9 | final case class DexSellOrderInfo( 10 | outputInfo: OutputInfo, 11 | amount: Long 12 | ) 13 | 14 | object DexSellOrderInfo { 15 | 16 | implicit val codec: Codec[DexSellOrderInfo] = Codec.from(deriveMagnoliaDecoder, deriveMagnoliaEncoder) 17 | 18 | implicit val schema: Schema[DexSellOrderInfo] = 19 | Schema 20 | .derived[DexSellOrderInfo] 21 | .modify(_.amount)(_.description("ERG amount")) 22 | 23 | implicit val validator: Validator[DexSellOrderInfo] = schema.validator 24 | 25 | def apply(o: ExtendedOutput, tokenPrice: Long, assets: List[ExtendedAsset]): DexSellOrderInfo = 26 | new DexSellOrderInfo(OutputInfo(o, assets), tokenPrice) 27 | } 28 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/models/HashRateDistributionSegment.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.models 2 | 3 | import io.circe.Codec 4 | import io.circe.magnolia.derivation.decoder.semiauto.deriveMagnoliaDecoder 5 | import io.circe.magnolia.derivation.encoder.semiauto.deriveMagnoliaEncoder 6 | import org.ergoplatform.explorer.db.models.aggregates.MinerStats 7 | import sttp.tapir.{Schema, Validator} 8 | 9 | final case class HashRateDistributionSegment(name: String, value: Int) 10 | 11 | object HashRateDistributionSegment { 12 | 13 | implicit val codec: Codec[HashRateDistributionSegment] = Codec.from(deriveMagnoliaDecoder, deriveMagnoliaEncoder) 14 | 15 | implicit val schema: Schema[HashRateDistributionSegment] = 16 | Schema 17 | .derived[HashRateDistributionSegment] 18 | .modify(_.name)(_.description("Segment name")) 19 | .modify(_.value)(_.description("Number of blocks mined")) 20 | 21 | implicit val validator: Validator[HashRateDistributionSegment] = schema.validator 22 | 23 | def batch(stats: List[MinerStats]): List[HashRateDistributionSegment] = { 24 | val totalCount = stats.map(_.blocksMined).sum 25 | def threshold(m: MinerStats): Boolean = 26 | ((m.blocksMined.toDouble * 100) / totalCount.toDouble) > 1.0 27 | 28 | val (major, other) = stats.partition(threshold) 29 | val otherSumStats = HashRateDistributionSegment("other", other.map(_.blocksMined).sum) 30 | val majorSegmants = major.map { info => 31 | HashRateDistributionSegment(info.verboseName, info.blocksMined) 32 | } 33 | (majorSegmants :+ otherSumStats).sortBy(x => -x.value).filterNot(_.value == 0L) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/models/MinerInfo.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.models 2 | 3 | import io.circe.Codec 4 | import io.circe.magnolia.derivation.decoder.semiauto.deriveMagnoliaDecoder 5 | import io.circe.magnolia.derivation.encoder.semiauto.deriveMagnoliaEncoder 6 | import org.ergoplatform.explorer.Address 7 | import sttp.tapir.{Schema, Validator} 8 | 9 | final case class MinerInfo(address: Address, name: String) 10 | 11 | object MinerInfo { 12 | 13 | implicit val codec: Codec[MinerInfo] = Codec.from(deriveMagnoliaDecoder, deriveMagnoliaEncoder) 14 | 15 | implicit val schema: Schema[MinerInfo] = 16 | Schema.derived[MinerInfo] 17 | .modify(_.address)(_.description("Miner reward address")) 18 | .modify(_.name)(_.description("Miner name")) 19 | 20 | implicit val validator: Validator[MinerInfo] = schema.validator 21 | } 22 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/models/PowSolutionInfo.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.models 2 | 3 | import io.circe.Codec 4 | import io.circe.magnolia.derivation.decoder.semiauto.deriveMagnoliaDecoder 5 | import io.circe.magnolia.derivation.encoder.semiauto.deriveMagnoliaEncoder 6 | import org.ergoplatform.explorer.HexString 7 | import sttp.tapir.{Schema, Validator} 8 | 9 | final case class PowSolutionInfo(pk: HexString, w: HexString, n: HexString, d: String) 10 | 11 | object PowSolutionInfo { 12 | 13 | implicit val codec: Codec[PowSolutionInfo] = Codec.from(deriveMagnoliaDecoder, deriveMagnoliaEncoder) 14 | 15 | implicit val schema: Schema[PowSolutionInfo] = 16 | Schema.derived[PowSolutionInfo] 17 | .modify(_.pk)(_.description("Miner public key")) 18 | .modify(_.d)(_.description("Autolykos.d")) 19 | 20 | implicit val validator: Validator[PowSolutionInfo] = schema.validator 21 | } 22 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/models/SearchResult.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.models 2 | 3 | import io.circe.Codec 4 | import io.circe.magnolia.derivation.decoder.semiauto.deriveMagnoliaDecoder 5 | import io.circe.magnolia.derivation.encoder.semiauto.deriveMagnoliaEncoder 6 | import org.ergoplatform.explorer.http.api.models.AssetInstanceInfo 7 | import org.ergoplatform.explorer.{Address, TxId} 8 | import sttp.tapir.{Schema, Validator} 9 | 10 | final case class SearchResult( 11 | blocks: List[BlockInfo], 12 | transactions: List[TxId], 13 | addresses: List[Address] 14 | ) 15 | 16 | object SearchResult { 17 | 18 | implicit def codec: Codec[SearchResult] = Codec.from(deriveMagnoliaDecoder, deriveMagnoliaEncoder) 19 | 20 | implicit val schema: Schema[SearchResult] = 21 | Schema 22 | .derived[SearchResult] 23 | .modify(_.blocks)(_.description("Blocks matching search query")) 24 | .modify(_.transactions)(_.description("Ids of transactions matching search query")) 25 | .modify(_.addresses)(_.description("Addresses matching search query")) 26 | 27 | implicit val validator: Validator[SearchResult] = schema.validator 28 | } 29 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/models/SpendingProofInfo.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.models 2 | 3 | import io.circe.magnolia.derivation.decoder.semiauto.deriveMagnoliaDecoder 4 | import io.circe.magnolia.derivation.encoder.semiauto.deriveMagnoliaEncoder 5 | import io.circe.{Codec, Json} 6 | import org.ergoplatform.explorer.HexString 7 | import sttp.tapir.{Schema, SchemaType, Validator} 8 | 9 | // in particular format of `extension` (what is inside it) 10 | final case class SpendingProofInfo(proofBytes: Option[HexString], extension: Json) 11 | 12 | object SpendingProofInfo { 13 | 14 | implicit val codec: Codec[SpendingProofInfo] = Codec.from(deriveMagnoliaDecoder, deriveMagnoliaEncoder) 15 | 16 | implicit val schema: Schema[SpendingProofInfo] = 17 | Schema 18 | .derived[SpendingProofInfo] 19 | .modify(_.proofBytes)(_.description("Hex-encoded serialized sigma proof")) 20 | .modify(_.extension)(_.description("Proof extension (key->value dictionary)")) 21 | 22 | implicit val validator: Validator[SpendingProofInfo] = schema.validator 23 | 24 | implicit private def extensionSchema: Schema[Json] = 25 | Schema( 26 | SchemaType.SOpenProduct( 27 | Schema(SchemaType.SString[Json]()) 28 | )(_ => Map.empty) 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/models/TxIdResponse.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.models 2 | 3 | import io.circe.Codec 4 | import io.circe.magnolia.derivation.decoder.semiauto.deriveMagnoliaDecoder 5 | import io.circe.magnolia.derivation.encoder.semiauto.deriveMagnoliaEncoder 6 | import org.ergoplatform.explorer.TxId 7 | import sttp.tapir.{Schema, Validator} 8 | 9 | final case class TxIdResponse(id: TxId) 10 | 11 | object TxIdResponse { 12 | 13 | implicit val codec: Codec[TxIdResponse] = Codec.from(deriveMagnoliaDecoder, deriveMagnoliaEncoder) 14 | 15 | implicit val schema: Schema[TxIdResponse] = 16 | Schema 17 | .derived[TxIdResponse] 18 | .modify(_.id)(_.description("Id of submitted transaction")) 19 | 20 | implicit val validator: Validator[TxIdResponse] = schema.validator 21 | } 22 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/modules/Search.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.modules 2 | 3 | import cats.Monad 4 | import org.ergoplatform.explorer.http.api.v0.models.SearchResult 5 | import org.ergoplatform.explorer.http.api.v0.services.{AddressesService, BlockChainService, TransactionsService} 6 | import tofu.Start 7 | import tofu.syntax.monadic._ 8 | import tofu.syntax.start._ 9 | 10 | trait Search[F[_]] { 11 | 12 | def search(query: String): F[SearchResult] 13 | } 14 | 15 | object Search { 16 | 17 | def apply[F[_]: Monad: Start]( 18 | blocks: BlockChainService[F], 19 | transactions: TransactionsService[F], 20 | addresses: AddressesService[F, fs2.Stream] 21 | ): Search[F] = 22 | new Search[F] { 23 | 24 | def search(query: String): F[SearchResult] = 25 | for { 26 | blocksF <- blocks.getBlocksByIdLike(query).start 27 | txsF <- transactions.getIdsLike(query).start 28 | addressesF <- addresses.getAllLike(query).start 29 | blocks <- blocksF.join 30 | txs <- txsF.join 31 | addresses <- addressesF.join 32 | } yield SearchResult(blocks, txs, addresses) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/routes/SearchRoutes.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.routes 2 | 3 | import cats.effect.{Concurrent, ContextShift, Timer} 4 | import io.chrisdavenport.log4cats.Logger 5 | import org.ergoplatform.explorer.http.api.ApiErr 6 | import org.ergoplatform.explorer.http.api.algebra.AdaptThrowable.AdaptThrowableEitherT 7 | import org.ergoplatform.explorer.http.api.syntax.adaptThrowable._ 8 | import org.ergoplatform.explorer.http.api.v0.modules.Search 9 | import org.http4s.HttpRoutes 10 | import sttp.tapir.server.http4s._ 11 | 12 | final class SearchRoutes[ 13 | F[_]: Concurrent: ContextShift: Timer: AdaptThrowableEitherT[*[_], ApiErr] 14 | ](search: Search[F])(implicit opts: Http4sServerOptions[F, F]) { 15 | 16 | import org.ergoplatform.explorer.http.api.v0.defs.SearchEndpointDefs._ 17 | 18 | val routes: HttpRoutes[F] = searchR 19 | 20 | private def interpreter = Http4sServerInterpreter(opts) 21 | 22 | private def searchR: HttpRoutes[F] = 23 | interpreter.toRoutes(searchDef) { q => 24 | search.search(q).adaptThrowable.value 25 | } 26 | } 27 | 28 | object SearchRoutes { 29 | 30 | def apply[F[_]: Concurrent: ContextShift: Timer: Logger](search: Search[F])(implicit 31 | opts: Http4sServerOptions[F, F] 32 | ): HttpRoutes[F] = 33 | new SearchRoutes(search).routes 34 | } 35 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v0/routes/StatsRoutes.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v0.routes 2 | 3 | import cats.effect.{Concurrent, ContextShift, Sync, Timer} 4 | import io.chrisdavenport.log4cats.Logger 5 | import org.ergoplatform.explorer.http.api.ApiErr 6 | import org.ergoplatform.explorer.http.api.algebra.AdaptThrowable.AdaptThrowableEitherT 7 | import org.ergoplatform.explorer.http.api.syntax.adaptThrowable._ 8 | import org.ergoplatform.explorer.http.api.v0.services.StatsService 9 | import org.http4s.HttpRoutes 10 | import sttp.tapir.server.http4s._ 11 | 12 | final class StatsRoutes[ 13 | F[_]: Concurrent: ContextShift: Timer: AdaptThrowableEitherT[*[_], ApiErr] 14 | ](service: StatsService[F])(implicit opts: Http4sServerOptions[F, F]) { 15 | 16 | import org.ergoplatform.explorer.http.api.v0.defs.StatsEndpointDefs._ 17 | 18 | val routes: HttpRoutes[F] = getCurrentStatsR 19 | 20 | private def interpreter = Http4sServerInterpreter(opts) 21 | 22 | private def getCurrentStatsR: HttpRoutes[F] = 23 | interpreter.toRoutes(getCurrentStatsDef) { _ => 24 | service.getCurrentStats.adaptThrowable.value 25 | } 26 | } 27 | 28 | object StatsRoutes { 29 | 30 | def apply[F[_]: Concurrent: ContextShift: Timer: Logger](service: StatsService[F])( 31 | implicit opts: Http4sServerOptions[F, F] 32 | ): HttpRoutes[F] = 33 | new StatsRoutes(service).routes 34 | } 35 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/defs/AssetsEndpointDefs.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.defs 2 | 3 | import org.ergoplatform.explorer.http.api.ApiErr 4 | import org.ergoplatform.explorer.http.api.commonDirectives._ 5 | import org.ergoplatform.explorer.http.api.models.Sorting.SortOrder 6 | import org.ergoplatform.explorer.http.api.models.{Items, Paging} 7 | import org.ergoplatform.explorer.http.api.v1.models.{AssetInfo, TokenInfo} 8 | import org.ergoplatform.explorer.settings.RequestsSettings 9 | import sttp.tapir._ 10 | import sttp.tapir.json.circe._ 11 | 12 | final class AssetsEndpointDefs(settings: RequestsSettings) { 13 | 14 | private val PathPrefix = "assets" 15 | 16 | def endpoints: List[Endpoint[_, _, _, _]] = 17 | listTokensDef :: 18 | searchByTokenIdDef :: 19 | Nil 20 | 21 | def searchByTokenIdDef: Endpoint[(String, Paging), ApiErr, Items[AssetInfo], Any] = 22 | baseEndpointDef.get 23 | .in(PathPrefix / "search" / "byTokenId") 24 | .in(query[String]("query").validate(Validator.minLength(5))) 25 | .in(paging(settings.maxEntitiesPerRequest)) 26 | .out(jsonBody[Items[AssetInfo]]) 27 | 28 | def listTokensDef: Endpoint[(Paging, SortOrder, Boolean), ApiErr, Items[TokenInfo], Any] = 29 | baseEndpointDef.get 30 | .in(PathPrefix) 31 | .in(paging(settings.maxEntitiesPerRequest)) 32 | .in(ordering) 33 | .in(hideNfts) 34 | .out(jsonBody[Items[TokenInfo]]) 35 | .description("Use '/tokens' instead") 36 | .deprecated() 37 | } 38 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/defs/DocsEndpointDefs.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.defs 2 | 3 | import org.ergoplatform.explorer.http.api.ApiErr 4 | import sttp.tapir._ 5 | 6 | object DocsEndpointDefs { 7 | 8 | private val PathPrefix = "docs" 9 | 10 | def endpoints: List[Endpoint[_, _, _, _]] = apiSpecDef :: Nil 11 | 12 | def apiSpecDef: Endpoint[Unit, ApiErr, String, Any] = 13 | baseEndpointDef.in(PathPrefix / "openapi").out(plainBody[String]) 14 | } 15 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/defs/EpochsEndpointDefs.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.defs 2 | 3 | import org.ergoplatform.explorer.http.api.ApiErr 4 | import org.ergoplatform.explorer.http.api.v1.models.EpochInfo 5 | import sttp.tapir.{Endpoint, _} 6 | import sttp.tapir.json.circe._ 7 | 8 | final class EpochsEndpointDefs { 9 | 10 | private val PathPrefix = "epochs" 11 | 12 | def endpoints: List[Endpoint[_, _, _, _]] = 13 | getEpochInfoDef :: Nil 14 | 15 | def getEpochInfoDef: Endpoint[Unit, ApiErr, EpochInfo, Any] = 16 | baseEndpointDef.get 17 | .in(PathPrefix / "params") 18 | .out(jsonBody[EpochInfo]) 19 | } 20 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/defs/ErgoTreeEndpointDefs.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.defs 2 | 3 | import org.ergoplatform.explorer.http.api.ApiErr 4 | import org.ergoplatform.explorer.http.api.commonDirectives._ 5 | import org.ergoplatform.explorer.http.api.v1.models.{ErgoTreeConversionRequest, ErgoTreeHuman} 6 | import sttp.tapir._ 7 | import sttp.tapir.json.circe._ 8 | 9 | final class ErgoTreeEndpointDefs[F[_]]() { 10 | 11 | private val PathPrefix = "ergotree" 12 | 13 | def endpoints: List[Endpoint[_, _, _, _]] = List(convertErgoTreeDef) 14 | 15 | def convertErgoTreeDef: Endpoint[ErgoTreeConversionRequest, ApiErr, ErgoTreeHuman, Any] = 16 | baseEndpointDef.post 17 | .in(PathPrefix / "convert") 18 | .in(jsonBody[ErgoTreeConversionRequest]) 19 | .out(jsonBody[ErgoTreeHuman]) 20 | } 21 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/defs/StatsEndpointsDefs.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.defs 2 | 3 | import org.ergoplatform.explorer.http.api.ApiErr 4 | import org.ergoplatform.explorer.http.api.v1.models.{NetworkState, NetworkStats} 5 | import sttp.tapir.Endpoint 6 | import sttp.tapir.json.circe.jsonBody 7 | 8 | final class StatsEndpointsDefs { 9 | 10 | def endpoints: List[Endpoint[_, _, _, _]] = 11 | getNetworkInfo :: getNetworkState :: getNetworkStats :: Nil 12 | 13 | def getNetworkInfo: Endpoint[Unit, ApiErr, NetworkState, Any] = 14 | baseEndpointDef.get 15 | .in("info") 16 | .out(jsonBody[NetworkState]) 17 | .deprecated() 18 | 19 | def getNetworkState: Endpoint[Unit, ApiErr, NetworkState, Any] = 20 | baseEndpointDef.get 21 | .in("networkState") 22 | .out(jsonBody[NetworkState]) 23 | 24 | def getNetworkStats: Endpoint[Unit, ApiErr, NetworkStats, Any] = 25 | baseEndpointDef.get 26 | .in("networkStats") 27 | .out(jsonBody[NetworkStats]) 28 | } 29 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/defs/package.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1 2 | 3 | import org.ergoplatform.explorer.http 4 | import org.ergoplatform.explorer.http.api.ApiErr 5 | import sttp.tapir._ 6 | 7 | package object defs { 8 | 9 | private val V1Prefix: EndpointInput[Unit] = "api" / "v1" 10 | 11 | val baseEndpointDef: Endpoint[Unit, ApiErr, Unit, Any] = 12 | http.api.defs.baseEndpointDef(V1Prefix) 13 | } 14 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/implictis/schemaForMapKV.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.implictis 2 | 3 | import io.circe.KeyEncoder 4 | import sttp.tapir.Schema 5 | 6 | object schemaForMapKV { 7 | 8 | implicit def schemaForMap[K: KeyEncoder, V: Schema]: Schema[Map[K, V]] = { 9 | 10 | val schemaType = 11 | Schema 12 | .schemaForMap[V] 13 | .schemaType 14 | .contramap[Map[K, V]](_.map { case (key, value) => 15 | (implicitly[KeyEncoder[K]].apply(key), value) 16 | }.toMap) 17 | 18 | Schema(schemaType) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/models/AssetInfo.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.models 2 | 3 | import derevo.circe.{decoder, encoder} 4 | import derevo.derive 5 | import org.ergoplatform.explorer.db.models.aggregates.ExtendedAsset 6 | import org.ergoplatform.explorer.{BlockId, BoxId, TokenId, TokenType} 7 | import sttp.tapir.{Schema, Validator} 8 | 9 | @derive(encoder, decoder) 10 | final case class AssetInfo( 11 | headerId: BlockId, 12 | boxId: BoxId, 13 | tokenId: TokenId, 14 | index: Int, 15 | amount: Long, 16 | name: Option[String], 17 | decimals: Option[Int], 18 | `type`: Option[TokenType] 19 | ) 20 | 21 | object AssetInfo { 22 | 23 | def apply(asset: ExtendedAsset): AssetInfo = 24 | AssetInfo( 25 | asset.headerId, 26 | asset.boxId, 27 | asset.tokenId, 28 | asset.index, 29 | asset.amount, 30 | asset.name, 31 | asset.decimals, 32 | asset.`type` 33 | ) 34 | 35 | implicit val schema: Schema[AssetInfo] = 36 | Schema 37 | .derived[AssetInfo] 38 | .modify(_.headerId)(_.description("Header ID this asset belongs to")) 39 | .modify(_.boxId)(_.description("Box ID this asset belongs to")) 40 | .modify(_.tokenId)(_.description("Token ID")) 41 | .modify(_.index)(_.description("Index of the asset in an output")) 42 | .modify(_.amount)(_.description("Amount of tokens")) 43 | .modify(_.name)(_.description("Name of the asset")) 44 | .modify(_.decimals)(_.description("Number of decimal places")) 45 | .modify(_.`type`)(_.description("Type of the asset (token standard)")) 46 | 47 | implicit val validator: Validator[AssetInfo] = schema.validator 48 | } 49 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/models/Balance.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.models 2 | 3 | import derevo.circe.{decoder, encoder} 4 | import derevo.derive 5 | import sttp.tapir.{Schema, Validator} 6 | 7 | @derive(encoder, decoder) 8 | final case class Balance(nanoErgs: Long, tokens: List[TokenAmount]) 9 | 10 | object Balance { 11 | 12 | def empty: Balance = Balance(0L, List.empty) 13 | 14 | implicit val schema: Schema[Balance] = 15 | Schema 16 | .derived[Balance] 17 | .modify(_.nanoErgs)(_.description("Ergo balance")) 18 | .modify(_.tokens)(_.description("Tokens balances")) 19 | 20 | implicit val validator: Validator[Balance] = schema.validator 21 | } 22 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/models/BlockExtensionInfo.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.models 2 | 3 | import io.circe.generic.semiauto.deriveCodec 4 | import io.circe.{Codec, Json} 5 | import org.ergoplatform.explorer.db.models.BlockExtension 6 | import org.ergoplatform.explorer.{BlockId, HexString} 7 | import sttp.tapir.{Schema, SchemaType, Validator} 8 | 9 | final case class BlockExtensionInfo( 10 | headerId: BlockId, 11 | digest: HexString, 12 | fields: Json 13 | ) 14 | 15 | object BlockExtensionInfo { 16 | 17 | implicit private def fieldsSchema: Schema[Json] = 18 | Schema( 19 | SchemaType.SOpenProduct( 20 | Schema(SchemaType.SString[Json]()) 21 | )(_ => Map.empty) 22 | ) 23 | 24 | implicit val codec: Codec[BlockExtensionInfo] = deriveCodec 25 | 26 | implicit val schema: Schema[BlockExtensionInfo] = 27 | Schema 28 | .derived[BlockExtensionInfo] 29 | .modify(_.headerId)(_.description("ID of the corresponding header")) 30 | .modify(_.digest)(_.description("Hex-encoded extension digest")) 31 | 32 | implicit val validator: Validator[BlockExtensionInfo] = schema.validator 33 | 34 | def apply(blockExtension: BlockExtension): BlockExtensionInfo = 35 | BlockExtensionInfo( 36 | blockExtension.headerId, 37 | blockExtension.digest, 38 | blockExtension.fields 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/models/BlockHeader.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.models 2 | 3 | import io.circe.Codec 4 | import io.circe.generic.semiauto.deriveCodec 5 | import org.ergoplatform.explorer.{BlockId, HexString} 6 | import org.ergoplatform.explorer.db.models.Header 7 | import sttp.tapir.{Schema, Validator} 8 | 9 | final case class BlockHeader( 10 | id: BlockId, 11 | parentId: BlockId, 12 | version: Int, 13 | timestamp: Long, 14 | height: Int, 15 | nBits: Long, 16 | votes: String, 17 | stateRoot: HexString, 18 | adProofsRoot: HexString, 19 | transactionsRoot: HexString, 20 | extensionHash: HexString, 21 | powSolutions: BlockPowSolutions 22 | ) 23 | 24 | object BlockHeader { 25 | 26 | def apply(header: Header): BlockHeader = 27 | new BlockHeader( 28 | header.id, 29 | header.parentId, 30 | header.version, 31 | header.timestamp, 32 | header.height, 33 | header.nBits, 34 | header.votes, 35 | header.stateRoot, 36 | header.adProofsRoot, 37 | header.transactionsRoot, 38 | header.extensionHash, 39 | BlockPowSolutions( 40 | header.minerPk, 41 | header.w, 42 | header.n, 43 | header.d 44 | ) 45 | ) 46 | 47 | implicit val codec: Codec[BlockHeader] = deriveCodec 48 | 49 | implicit val schema: Schema[BlockHeader] = Schema.derived[BlockHeader] 50 | 51 | implicit val validator: Validator[BlockHeader] = schema.validator 52 | 53 | } 54 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/models/BlockPowSolutions.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.models 2 | 3 | import io.circe.Codec 4 | import io.circe.generic.semiauto.deriveCodec 5 | import org.ergoplatform.explorer.HexString 6 | import sttp.tapir.{Schema, Validator} 7 | 8 | case class BlockPowSolutions( 9 | pk: HexString, 10 | w: HexString, 11 | n: HexString, 12 | d: String 13 | ) 14 | 15 | object BlockPowSolutions { 16 | 17 | implicit val codec: Codec[BlockPowSolutions] = deriveCodec 18 | 19 | implicit val schema: Schema[BlockPowSolutions] = Schema.derived[BlockPowSolutions] 20 | 21 | implicit val validator: Validator[BlockPowSolutions] = schema.validator 22 | } 23 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/models/BlockSummaryV1.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.models 2 | 3 | import io.circe.Codec 4 | import io.circe.magnolia.derivation.decoder.semiauto.deriveMagnoliaDecoder 5 | import io.circe.magnolia.derivation.encoder.semiauto.deriveMagnoliaEncoder 6 | import org.ergoplatform.explorer.db.models.aggregates.{ExtendedAsset, ExtendedDataInput, ExtendedInput, ExtendedOutput} 7 | import org.ergoplatform.explorer.db.models.{Header, Transaction} 8 | import org.ergoplatform.explorer.http.api.v0.models.{HeaderInfo, TransactionInfo => TxInf} 9 | import sttp.tapir.{Schema, Validator} 10 | 11 | final case class BlockSummaryV1( 12 | header: HeaderInfo, 13 | blockTransactions: List[TxInf] 14 | ) 15 | 16 | object BlockSummaryV1 { 17 | 18 | implicit val codec: Codec[BlockSummaryV1] = Codec.from(deriveMagnoliaDecoder, deriveMagnoliaEncoder) 19 | 20 | implicit val schema: Schema[BlockSummaryV1] = 21 | Schema 22 | .derived[BlockSummaryV1] 23 | 24 | implicit val validator: Validator[BlockSummaryV1] = schema.validator 25 | 26 | def apply( 27 | h: Header, 28 | txs: List[Transaction], 29 | numConfirmations: Int, 30 | inputs: List[ExtendedInput], 31 | dataInputs: List[ExtendedDataInput], 32 | outputs: List[ExtendedOutput], 33 | assets: List[ExtendedAsset], 34 | blockSize: Int 35 | ): BlockSummaryV1 = { 36 | val txsInfo = TxInf.batch(txs.map(_ -> numConfirmations), inputs, dataInputs, outputs, assets) 37 | val headerInfo = HeaderInfo(h, blockSize) 38 | new BlockSummaryV1(headerInfo, txsInfo) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/models/BoxAssetsQuery.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.models 2 | 3 | import derevo.circe.{decoder, encoder} 4 | import derevo.derive 5 | import org.ergoplatform.explorer.{ErgoTreeTemplateHash, TokenId} 6 | import sttp.tapir.{Schema, Validator} 7 | 8 | @derive(encoder, decoder) 9 | final case class BoxAssetsQuery( 10 | ergoTreeTemplateHash: ErgoTreeTemplateHash, 11 | assets: List[TokenId] 12 | ) 13 | 14 | object BoxAssetsQuery { 15 | 16 | implicit val schema: Schema[BoxAssetsQuery] = 17 | Schema 18 | .derived[BoxAssetsQuery] 19 | .modify(_.ergoTreeTemplateHash)(_.description("SHA-256 hash of ErgoTree template this box script should have")) 20 | .modify(_.assets)(_.description("IDs of tokens returned boxes should contain")) 21 | 22 | implicit val validator: Validator[BoxAssetsQuery] = Validator.pass 23 | } 24 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/models/BoxQuery.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.models 2 | 3 | import derevo.circe.{decoder, encoder} 4 | import derevo.derive 5 | import io.circe.{KeyDecoder, KeyEncoder} 6 | import org.ergoplatform.explorer.{ErgoTreeTemplateHash, RegisterId, TokenId} 7 | import org.ergoplatform.explorer.RegisterId._ 8 | import sttp.tapir.codec.enumeratum._ 9 | import sttp.tapir.{Schema, Validator} 10 | 11 | @derive(encoder, decoder) 12 | final case class BoxQuery( 13 | ergoTreeTemplateHash: ErgoTreeTemplateHash, 14 | registers: Option[Map[RegisterId, String]], 15 | constants: Option[Map[Int, String]], 16 | assets: Option[List[TokenId]] 17 | ) 18 | 19 | object BoxQuery { 20 | 21 | implicit def schemaForMap[K: KeyEncoder, V: Schema]: Schema[Map[K, V]] = { 22 | 23 | val schemaType = 24 | Schema 25 | .schemaForMap[V] 26 | .schemaType 27 | .contramap[Map[K, V]](_.map { case (key, value) => 28 | (implicitly[KeyEncoder[K]].apply(key), value) 29 | }.toMap) 30 | 31 | Schema(schemaType) 32 | } 33 | 34 | implicit val schema: Schema[BoxQuery] = 35 | Schema 36 | .derived[BoxQuery] 37 | .modify(_.ergoTreeTemplateHash)(_.description("SHA-256 hash of ErgoTree template this box script should have")) 38 | .modify(_.registers)(_.description("Pairs of (register ID, register value) this box should contain")) 39 | .modify(_.constants)(_.description("Pairs of (constant index, constant value) this box should contain")) 40 | .modify(_.assets)(_.description("IDs of tokens returned boxes should contain")) 41 | 42 | implicit val validator: Validator[BoxQuery] = Validator.pass 43 | } 44 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/models/CheckTokenInfo.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.models 2 | 3 | import derevo.circe.{decoder, encoder} 4 | import derevo.derive 5 | import org.ergoplatform.explorer.http.api.v1.TokenStatus 6 | import sttp.tapir.Schema 7 | 8 | @derive(encoder, decoder) 9 | final case class CheckTokenInfo( 10 | genuine: TokenStatus, 11 | token: Option[GenuineTokenInfo] 12 | ) 13 | 14 | object CheckTokenInfo { 15 | 16 | implicit val schema: Schema[CheckTokenInfo] = 17 | Schema 18 | .derived[CheckTokenInfo] 19 | .modify(_.genuine)( 20 | _.description("Flag with 0 unknown, \n 1 verified, \n 2 suspicious, \n 3 blocked (see EIP-21)") 21 | ) 22 | .modify(_.token)(_.description("Genuine Token Info")) 23 | } 24 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/models/EpochInfo.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.models 2 | 3 | import derevo.circe.{decoder, encoder} 4 | import derevo.derive 5 | import org.ergoplatform.explorer.db.models.EpochParameters 6 | import sttp.tapir.{Schema, Validator} 7 | 8 | /** Represents `epochs_parameters` table. 9 | */ 10 | @derive(encoder, decoder) 11 | final case class EpochInfo( 12 | height: Int, 13 | storageFeeFactor: Int, 14 | minValuePerByte: Int, 15 | maxBlockSize: Int, 16 | maxBlockCost: Int, 17 | blockVersion: Byte, 18 | tokenAccessCost: Int, 19 | inputCost: Int, 20 | dataInputCost: Int, 21 | outputCost: Int 22 | ) 23 | 24 | object EpochInfo { 25 | 26 | implicit def schema: Schema[EpochInfo] = Schema.derived[EpochInfo] 27 | implicit def validator: Validator[EpochInfo] = schema.validator 28 | 29 | def apply(params: EpochParameters): EpochInfo = 30 | new EpochInfo( 31 | params.height, 32 | params.storageFeeFactor, 33 | params.minValuePerByte, 34 | params.maxBlockSize, 35 | params.maxBlockCost, 36 | params.blockVersion, 37 | params.tokenAccessCost, 38 | params.inputCost, 39 | params.dataInputCost, 40 | params.outputCost 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/models/ErgoTreeHuman.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.models 2 | 3 | import derevo.circe.{decoder, encoder} 4 | import derevo.derive 5 | import sttp.tapir.{Schema, Validator} 6 | 7 | @derive(encoder, decoder) 8 | final case class ErgoTreeHuman(constants: String, script: String) 9 | 10 | object ErgoTreeHuman { 11 | 12 | implicit val schema: Schema[ErgoTreeHuman] = 13 | Schema 14 | .derived[ErgoTreeHuman] 15 | .modify(_.constants)(_.description("Constants use in ergo script")) 16 | .modify(_.script)(_.description("Human readable ergo script")) 17 | 18 | implicit val validator: Validator[ErgoTreeHuman] = schema.validator 19 | } 20 | 21 | @derive(encoder, decoder) 22 | final case class ErgoTreeConversionRequest(hashed: String) 23 | 24 | object ErgoTreeConversionRequest { 25 | 26 | implicit val schema: Schema[ErgoTreeConversionRequest] = 27 | Schema 28 | .derived[ErgoTreeConversionRequest] 29 | .modify(_.hashed)(_.description("Hashed value of ergo script")) 30 | 31 | implicit val validator: Validator[ErgoTreeConversionRequest] = schema.validator 32 | } -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/models/GenuineTokenInfo.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.models 2 | 3 | import derevo.circe.{decoder, encoder} 4 | import derevo.derive 5 | import org.ergoplatform.explorer.TokenId 6 | import org.ergoplatform.explorer.db.models.GenuineToken 7 | import sttp.tapir.{Schema, Validator} 8 | 9 | /** [[https://github.com/ergoplatform/eips/blob/master/eip-0021.md#genuine-tokens EIP0021: Genuine Tokens Verification]]:
10 | * 11 | * This EIP lists the common tokens of value with their unique identifier, so users as well as wallet and explorer applications can verify if a token is genuine. 12 | * The EIP is meant to be updated regularly when new tokens of value are added. 13 | */ 14 | 15 | @derive(encoder, decoder) 16 | final case class GenuineTokenInfo( 17 | tokenId: TokenId, 18 | tokenName: String, 19 | uniqueName: Boolean, 20 | issuer: Option[String] 21 | ) 22 | 23 | object GenuineTokenInfo { 24 | 25 | def apply(genuineToken: GenuineToken): GenuineTokenInfo = 26 | GenuineTokenInfo( 27 | genuineToken.id, 28 | genuineToken.tokenName, 29 | genuineToken.uniqueName, 30 | genuineToken.issuer 31 | ) 32 | 33 | implicit val schema: Schema[GenuineTokenInfo] = 34 | Schema 35 | .derived[GenuineTokenInfo] 36 | .modify(_.tokenName)(_.description("Token Verbose Name")) 37 | .modify(_.tokenId)(_.description("Token ID")) 38 | .modify(_.uniqueName)(_.description("Boolean Token Verbose Name is unique")) 39 | .modify(_.issuer)(_.description("Token Issuer")) 40 | 41 | implicit val validator: Validator[GenuineTokenInfo] = schema.validator 42 | } 43 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/models/MinerInfo.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.models 2 | 3 | import io.circe.Codec 4 | import io.circe.generic.semiauto.deriveCodec 5 | import org.ergoplatform.explorer.Address 6 | import sttp.tapir.{Schema, Validator} 7 | 8 | final case class MinerInfo(address: Address, name: String) 9 | 10 | object MinerInfo { 11 | 12 | implicit val codec: Codec[MinerInfo] = deriveCodec 13 | 14 | implicit val schema: Schema[MinerInfo] = 15 | Schema.derived[MinerInfo] 16 | .modify(_.address)(_.description("Miner reward address")) 17 | .modify(_.name)(_.description("Miner name")) 18 | 19 | implicit val validator: Validator[MinerInfo] = schema.validator 20 | } 21 | 22 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/models/NetworkState.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.models 2 | 3 | import derevo.circe.{decoder, encoder} 4 | import derevo.derive 5 | import org.ergoplatform.explorer.BlockId 6 | import org.ergoplatform.explorer.db.models.EpochParameters 7 | import sttp.tapir.{Schema, Validator} 8 | 9 | @derive(encoder, decoder) 10 | final case class NetworkState( 11 | lastBlockId: BlockId, 12 | height: Int, 13 | maxBoxGix: Long, 14 | maxTxGix: Long, 15 | params: EpochInfo 16 | ) 17 | 18 | object NetworkState { 19 | 20 | implicit def schema: Schema[NetworkState] = Schema.derived[NetworkState] 21 | implicit def validator: Validator[NetworkState] = schema.validator 22 | } 23 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/models/NetworkStats.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.models 2 | 3 | import derevo.circe.{decoder, encoder} 4 | import derevo.derive 5 | import sttp.tapir.{Schema, Validator} 6 | 7 | @derive(encoder, decoder) 8 | final case class NetworkStats( 9 | uniqueAddressesNum: Long 10 | ) 11 | 12 | object NetworkStats { 13 | 14 | implicit def schema: Schema[NetworkStats] = Schema.derived[NetworkStats] 15 | implicit def validator: Validator[NetworkStats] = schema.validator 16 | } 17 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/models/PrettyErgoTree.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.models 2 | 3 | import org.ergoplatform.explorer.HexString 4 | import sigmastate.PrettyPrintErgoTree 5 | import sigmastate.lang.exceptions.SerializerException 6 | import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer 7 | 8 | object PrettyErgoTree { 9 | def fromString(s: String) : Either[PrettyErgoTreeError, ErgoTreeHuman] = { 10 | HexString.fromString[Either[Throwable, *]](s) match { 11 | case Left(_) => Left(PrettyErgoTreeError.BadEncoding) 12 | case Right(hexString) => fromHexString(hexString) 13 | } 14 | } 15 | 16 | def fromHexString(h: HexString): Either[PrettyErgoTreeError, ErgoTreeHuman] = { 17 | try { 18 | val ergoTree = DefaultSerializer.deserializeErgoTree(h.bytes) 19 | ergoTree.root match { 20 | case Left(_) => Left(PrettyErgoTreeError.UnparsedErgoTree) 21 | case Right(value) => 22 | val script = PrettyPrintErgoTree.prettyPrint(value, width = 160) 23 | val constants = ergoTree.constants.zipWithIndex.map { case (c, i) => s"$i: ${c.value}" }.mkString("\n") 24 | Right(ErgoTreeHuman(constants, script)) 25 | } 26 | } catch { 27 | case se: SerializerException => Left(PrettyErgoTreeError.DeserializeException(se.message)) 28 | } 29 | } 30 | } 31 | 32 | sealed trait PrettyErgoTreeError 33 | object PrettyErgoTreeError { 34 | case object BadEncoding extends PrettyErgoTreeError 35 | case class DeserializeException(msg: String) extends PrettyErgoTreeError 36 | case object UnparsedErgoTree extends PrettyErgoTreeError 37 | } -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/models/TokenAmount.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.models 2 | 3 | import derevo.circe.{decoder, encoder} 4 | import derevo.derive 5 | import org.ergoplatform.explorer.{TokenId, TokenType} 6 | import org.ergoplatform.explorer.db.models.aggregates.AggregatedAsset 7 | import sttp.tapir.{Schema, Validator} 8 | 9 | @derive(encoder, decoder) 10 | final case class TokenAmount( 11 | tokenId: TokenId, 12 | amount: Long, 13 | decimals: Int, 14 | name: Option[String], 15 | tokenType: Option[TokenType] 16 | ) 17 | 18 | object TokenAmount { 19 | 20 | def apply(a: AggregatedAsset): TokenAmount = 21 | TokenAmount(a.tokenId, a.totalAmount, a.decimals.getOrElse(0), a.name, a.`type`) 22 | 23 | implicit val schema: Schema[TokenAmount] = 24 | Schema 25 | .derived[TokenAmount] 26 | .modify(_.tokenId)(_.description("Token ID")) 27 | .modify(_.amount)(_.description("Token amount")) 28 | .modify(_.decimals)(_.description("Number of decimals")) 29 | .modify(_.name)(_.description("Token name")) 30 | .modify(_.tokenType)(_.description("Asset type (token standard)")) 31 | 32 | implicit val validator: Validator[TokenAmount] = schema.validator 33 | } 34 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/models/TokenInfo.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.models 2 | 3 | import derevo.circe.{decoder, encoder} 4 | import derevo.derive 5 | import org.ergoplatform.explorer.db.models.Token 6 | import org.ergoplatform.explorer.{BoxId, TokenId, TokenType} 7 | import sttp.tapir.{Schema, Validator} 8 | 9 | @derive(encoder, decoder) 10 | final case class TokenInfo( 11 | id: TokenId, 12 | boxId: BoxId, 13 | emissionAmount: Long, 14 | name: Option[String], 15 | description: Option[String], 16 | `type`: Option[TokenType], 17 | decimals: Option[Int] 18 | ) 19 | 20 | object TokenInfo { 21 | 22 | def apply(token: Token): TokenInfo = 23 | TokenInfo( 24 | token.id, 25 | token.boxId, 26 | token.emissionAmount, 27 | token.name, 28 | token.description, 29 | token.`type`, 30 | token.decimals 31 | ) 32 | 33 | implicit val schema: Schema[TokenInfo] = 34 | Schema 35 | .derived[TokenInfo] 36 | .modify(_.id)(_.description("ID of the asset")) 37 | .modify(_.boxId)(_.description("Box ID this asset was issued by")) 38 | .modify(_.emissionAmount)(_.description("Number of decimal places")) 39 | .modify(_.name)(_.description("Name of the asset")) 40 | .modify(_.description)(_.description("Description of the asset")) 41 | .modify(_.`type`)(_.description("Asset type (token standard)")) 42 | .modify(_.decimals)(_.description("Number of decimal places")) 43 | 44 | implicit val validator: Validator[TokenInfo] = schema.validator 45 | } 46 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/models/TotalBalance.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.models 2 | 3 | import derevo.circe.{decoder, encoder} 4 | import derevo.derive 5 | import sttp.tapir.{Schema, Validator} 6 | 7 | @derive(encoder, decoder) 8 | final case class TotalBalance(confirmed: Balance, unconfirmed: Balance) 9 | 10 | object TotalBalance { 11 | 12 | implicit val schema: Schema[TotalBalance] = 13 | Schema 14 | .derived[TotalBalance] 15 | .modify(_.confirmed)(_.description("Confirmed balance")) 16 | .modify(_.unconfirmed)(_.description("Unconfirmed balance")) 17 | 18 | implicit val validator: Validator[TotalBalance] = schema.validator 19 | } 20 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/routes/EpochsRoutes.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.routes 2 | 3 | import cats.effect.{Concurrent, ContextShift, Timer} 4 | import cats.syntax.semigroupk._ 5 | import io.chrisdavenport.log4cats.Logger 6 | import org.ergoplatform.explorer.http.api.ApiErr 7 | import org.ergoplatform.explorer.http.api.algebra.AdaptThrowable.AdaptThrowableEitherT 8 | import org.ergoplatform.explorer.http.api.syntax.adaptThrowable._ 9 | import org.ergoplatform.explorer.http.api.syntax.routes._ 10 | import org.ergoplatform.explorer.http.api.v1.defs.EpochsEndpointDefs 11 | import org.ergoplatform.explorer.http.api.v1.services.Epochs 12 | import org.http4s.HttpRoutes 13 | import sttp.tapir.server.http4s._ 14 | 15 | final class EpochsRoutes[ 16 | F[_]: Concurrent: ContextShift: Timer: AdaptThrowableEitherT[*[_], ApiErr] 17 | ](epochs: Epochs[F])(implicit opts: Http4sServerOptions[F, F]) { 18 | 19 | val defs = new EpochsEndpointDefs 20 | 21 | val routes: HttpRoutes[F] = getEpochInfoR 22 | 23 | private def interpreter = Http4sServerInterpreter(opts) 24 | 25 | private def getEpochInfoR: HttpRoutes[F] = 26 | interpreter.toRoutes(defs.getEpochInfoDef) { _ => 27 | epochs 28 | .getLastEpoch 29 | .adaptThrowable 30 | .orNotFound(s"Data about last epoch is missing") 31 | .value 32 | } 33 | } 34 | 35 | object EpochsRoutes { 36 | 37 | def apply[F[_]: Concurrent: ContextShift: Timer: Logger](epochs: Epochs[F])(implicit opts: Http4sServerOptions[F, F]): HttpRoutes[F] = 38 | new EpochsRoutes[F](epochs).routes 39 | } 40 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/services/Assets.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.services 2 | 3 | import cats.effect.Sync 4 | import cats.{FlatMap, Monad} 5 | import fs2.Stream 6 | import mouse.anyf._ 7 | import org.ergoplatform.explorer.CRaise 8 | import org.ergoplatform.explorer.Err.RequestProcessingErr.InconsistentDbData 9 | import org.ergoplatform.explorer.db.Trans 10 | import org.ergoplatform.explorer.db.algebra.LiftConnectionIO 11 | import org.ergoplatform.explorer.db.repositories._ 12 | import org.ergoplatform.explorer.http.api.models.{Items, Paging} 13 | import org.ergoplatform.explorer.http.api.v1.models.AssetInfo 14 | import tofu.syntax.monadic._ 15 | 16 | /** A service providing an access to the assets data. 17 | */ 18 | trait Assets[F[_]] { 19 | 20 | /** Get all assets matching a given `query`. 21 | */ 22 | def getAllLike(idSubstring: String, paging: Paging): F[Items[AssetInfo]] 23 | } 24 | 25 | object Assets { 26 | 27 | def apply[ 28 | F[_]: Sync, 29 | D[_]: LiftConnectionIO: CRaise[*[_], InconsistentDbData]: Monad 30 | ](trans: D Trans F): F[Assets[F]] = 31 | AssetRepo[F, D].map(new Live(_)(trans)) 32 | 33 | final private class Live[ 34 | F[_]: FlatMap, 35 | D[_]: CRaise[*[_], InconsistentDbData]: Monad 36 | ](assetRepo: AssetRepo[D, Stream])(trans: D Trans F) 37 | extends Assets[F] { 38 | 39 | def getAllLike(idSubstring: String, paging: Paging): F[Items[AssetInfo]] = 40 | assetRepo 41 | .countAllLike(idSubstring) 42 | .flatMap { total => 43 | assetRepo 44 | .getAllLike(idSubstring, paging.offset, paging.limit) 45 | .map(_.map(AssetInfo(_))) 46 | .map(Items(_, total)) 47 | } 48 | .thrushK(trans.xa) 49 | } 50 | } -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/http/api/v1/services/Epochs.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.http.api.v1.services 2 | 3 | import cats.Functor 4 | import cats.effect.Sync 5 | import mouse.anyf._ 6 | import org.ergoplatform.explorer.db.Trans 7 | import org.ergoplatform.explorer.db.algebra.LiftConnectionIO 8 | import org.ergoplatform.explorer.db.repositories.EpochInfoRepo 9 | import org.ergoplatform.explorer.http.api.v1.models.EpochInfo 10 | import tofu.syntax.monadic._ 11 | import tofu.syntax.foption._ 12 | 13 | trait Epochs[F[_]] { 14 | 15 | /** Get data about last epoch. 16 | */ 17 | def getLastEpoch: F[Option[EpochInfo]] 18 | } 19 | 20 | object Epochs { 21 | 22 | def apply[F[_]: Sync, D[_]: Functor: LiftConnectionIO](trans: D Trans F): F[Epochs[F]] = 23 | EpochInfoRepo[F, D].map(new Live[F, D](_)(trans)) 24 | 25 | final private class Live[F[_], D[_]: Functor: LiftConnectionIO](epochInfoRepo: EpochInfoRepo[D])(trans: D Trans F) 26 | extends Epochs[F] { 27 | 28 | def getLastEpoch: F[Option[EpochInfo]] = 29 | epochInfoRepo.getLastEpoch.mapIn(EpochInfo(_)) ||> trans.xa 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/settings/ApiSettings.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.settings 2 | 3 | final case class ApiSettings( 4 | http: HttpSettings, 5 | db: DbSettings, 6 | protocol: ProtocolSettings, 7 | utxCache: UtxCacheSettings, 8 | redis: Option[RedisSettings], 9 | redisCache: RedisSettings, 10 | service: ServiceSettings, 11 | requests: RequestsSettings 12 | ) 13 | 14 | object ApiSettings extends SettingCompanion[ApiSettings] 15 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/settings/HttpSettings.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.settings 2 | 3 | final case class HttpSettings(host: String, port: Int) 4 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/settings/RequestsSettings.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.settings 2 | 3 | final case class RequestsSettings( 4 | maxEntitiesPerRequest: Int, 5 | maxEntitiesPerHeavyRequest: Int, 6 | maxBlocksPerRequest: Int 7 | ) 8 | -------------------------------------------------------------------------------- /modules/explorer-api/src/main/scala/org/ergoplatform/explorer/settings/ServiceSettings.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.settings 2 | 3 | final case class ServiceSettings(chunkSize: Int) 4 | -------------------------------------------------------------------------------- /modules/explorer-api/src/test/scala/org/ergoplatform/explorer/testContainers/RedisTest.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.testContainers 2 | 3 | import com.dimafeng.testcontainers.{ForAllTestContainer, GenericContainer} 4 | import org.scalatest.TestSuite 5 | import org.testcontainers.containers.wait.strategy.Wait 6 | 7 | trait RedisTest extends ForAllTestContainer { 8 | self: TestSuite => 9 | 10 | val redisTestPort = 6379 11 | 12 | override val container: GenericContainer = 13 | GenericContainer("redis:5.0.3", exposedPorts = Seq(redisTestPort), waitStrategy = Wait.forListeningPort()) 14 | 15 | } 16 | -------------------------------------------------------------------------------- /modules/explorer-api/src/test/scala/org/ergoplatform/explorer/v1/services/constants.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.v1.services 2 | 3 | object constants { 4 | val SenderAddressString = "3WzSdM7NrjDJswpu2ThfhWvVM1mKJhgnGNieWYcGVsYp3AoirgR5" 5 | val ReceiverAddressString = "3Wx99DApJTpUTPZDhYEerbqWfa9MvuuVJehAFVeepnZMzAN3dfYW" 6 | } 7 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/BuildFrom.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer 2 | 3 | import cats.{Applicative, FlatMap, Monad} 4 | import shapeless.{::, Generic, HList, HNil} 5 | import tofu.syntax.monadic._ 6 | 7 | trait BuildFrom[F[_], A, B] { 8 | 9 | def apply(a: A): F[B] 10 | } 11 | 12 | object BuildFrom { 13 | 14 | def instance[F[_], A, B](f: A => F[B]): BuildFrom[F, A, B] = 15 | (a: A) => f(a) 16 | 17 | def pure[F[_]: Applicative, A, B](f: A => B): BuildFrom[F, A, B] = 18 | (a: A) => f(a).pure 19 | 20 | implicit def extractHNil[F[_]: Applicative, A]: BuildFrom[F, A, HNil] = 21 | instance(_ => HNil.pure.widen[HNil]) 22 | 23 | implicit def extractHList[F[_]: FlatMap, A, H, T <: HList](implicit 24 | hExtract: BuildFrom[F, A, H], 25 | tExtract: BuildFrom[F, A, T] 26 | ): BuildFrom[F, A, H :: T] = 27 | instance { a => 28 | hExtract(a) >>= (h => tExtract(a).map(h :: _)) 29 | } 30 | 31 | implicit def instance[F[_], A, B, R](implicit 32 | F: Monad[F], 33 | gen: Generic.Aux[B, R], 34 | extract: BuildFrom[F, A, R] 35 | ): BuildFrom[F, A, B] = 36 | instance(a => extract(a).map(gen.from)) 37 | 38 | object syntax { 39 | 40 | implicit final class BuildFromOps[A](private val a: A) extends AnyVal { 41 | def intoF[F[_], B](implicit ev: BuildFrom[F, A, B]): F[B] = ev(a) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/RegisterId.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer 2 | 3 | import cats.syntax.either._ 4 | import doobie.util.{Get, Put} 5 | import enumeratum.{CirceEnum, Enum, EnumEntry} 6 | import io.circe.{KeyDecoder, KeyEncoder} 7 | import sttp.tapir.codec.enumeratum._ 8 | 9 | sealed abstract class RegisterId extends EnumEntry 10 | 11 | object RegisterId extends Enum[RegisterId] with CirceEnum[RegisterId] { 12 | 13 | case object R0 extends RegisterId 14 | case object R1 extends RegisterId 15 | case object R2 extends RegisterId 16 | case object R3 extends RegisterId 17 | case object R4 extends RegisterId 18 | case object R5 extends RegisterId 19 | case object R6 extends RegisterId 20 | case object R7 extends RegisterId 21 | case object R8 extends RegisterId 22 | case object R9 extends RegisterId 23 | 24 | val values = findValues 25 | 26 | implicit val keyDecoder: KeyDecoder[RegisterId] = withNameOption 27 | implicit val keyEncoder: KeyEncoder[RegisterId] = _.entryName 28 | 29 | implicit val get: Get[RegisterId] = 30 | Get[String].temap(s => withNameEither(s).leftMap(_ => s"No such RegisterId [$s]")) 31 | 32 | implicit val put: Put[RegisterId] = 33 | Put[String].contramap[RegisterId](_.entryName) 34 | } 35 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/cache/Redis.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.cache 2 | 3 | import cats.effect.{Concurrent, ContextShift, Resource} 4 | import cats.syntax.functor._ 5 | import dev.profunktor.redis4cats.RedisCommands 6 | import dev.profunktor.redis4cats.{Redis => RedisI} 7 | import dev.profunktor.redis4cats.connection.{RedisClient, RedisURI} 8 | import dev.profunktor.redis4cats.data.RedisCodec 9 | import dev.profunktor.redis4cats.effect.Log 10 | import dev.profunktor.redis4cats 11 | import io.chrisdavenport.log4cats.slf4j.Slf4jLogger 12 | import org.ergoplatform.explorer.settings.RedisSettings 13 | import org.ergoplatform.explorer.cache.redisInstances._ 14 | 15 | object Redis { 16 | 17 | /** Create new Redis client 18 | */ 19 | def apply[F[_]: Concurrent: ContextShift]( 20 | settings: RedisSettings 21 | ): Resource[F, RedisCommands[F, String, String]] = 22 | for { 23 | implicit0(log: Log[F]) <- Resource.eval(Slf4jLogger.create.map(logInstance(_))) 24 | uri <- Resource.eval(RedisURI.make[F](settings.url)) 25 | client <- RedisClient[F].fromUri(uri) 26 | cmd <- RedisI[F].fromClient(client, RedisCodec.Utf8) 27 | } yield cmd 28 | } 29 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/cache/redisInstances.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.cache 2 | 3 | import dev.profunktor.redis4cats.effect.Log 4 | import io.chrisdavenport.log4cats.Logger 5 | 6 | object redisInstances { 7 | 8 | implicit def logInstance[F[_]](implicit logger: Logger[F]): Log[F] = 9 | new Log[F] { 10 | def info(msg: => String): F[Unit] = logger.info(msg) 11 | def error(msg: => String): F[Unit] = logger.error(msg) 12 | def debug(msg: => String): F[Unit] = logger.debug(msg) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/DoobieTrans.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db 2 | 3 | import cats.effect.{Async, Blocker, ContextShift, Resource, Sync} 4 | import doobie.hikari.HikariTransactor 5 | import doobie.util.ExecutionContexts 6 | import org.ergoplatform.explorer.settings.DbSettings 7 | 8 | object DoobieTrans { 9 | 10 | def apply[F[_]: Async: ContextShift]( 11 | poolName: String, 12 | settings: DbSettings 13 | ): Resource[F, HikariTransactor[F]] = 14 | for { 15 | cp <- ExecutionContexts.fixedThreadPool(size = settings.cpSize) 16 | blocker <- Blocker[F] 17 | xa <- HikariTransactor.newHikariTransactor[F]( 18 | driverClassName = "org.postgresql.Driver", 19 | settings.url, 20 | settings.user, 21 | settings.pass, 22 | cp, 23 | blocker 24 | ) 25 | _ <- Resource.eval(configure(xa)(poolName, settings.cpSize)) 26 | } yield xa 27 | 28 | private def configure[F[_]: Sync]( 29 | xa: HikariTransactor[F] 30 | )(name: String, maxPoolSize: Int): F[Unit] = 31 | xa.configure { c => 32 | Sync[F].delay { 33 | c.setAutoCommit(false) 34 | c.setPoolName(name) 35 | c.setMaxLifetime(600000) 36 | c.setIdleTimeout(30000) 37 | c.setMaximumPoolSize(maxPoolSize) 38 | c.setMinimumIdle(math.max(2, maxPoolSize / 2)) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/Trans.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db 2 | 3 | import cats.effect.Bracket 4 | import cats.{~>, Monad} 5 | import doobie.free.connection.ConnectionIO 6 | import doobie.util.transactor.Transactor 7 | import fs2.Stream 8 | 9 | final case class Trans[D[_], F[_]](xa: D ~> F, xas: Stream[D, *] ~> Stream[F, *]) 10 | 11 | object Trans { 12 | 13 | def fromDoobie[F[_]: Monad: Bracket[*[_], Throwable]]( 14 | xa: Transactor[F] 15 | ): ConnectionIO Trans F = 16 | Trans(xa.trans, xa.transP) 17 | } 18 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/algebra/LiftConnectionIO.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.algebra 2 | 3 | import cats.~> 4 | import doobie.free.connection.ConnectionIO 5 | import simulacrum.typeclass 6 | 7 | /** A type class allowing to lift [[ConnectionIO]] into some effect `F[_]`. 8 | */ 9 | trait LiftConnectionIO[F[_]] { 10 | 11 | def liftConnectionIO[A](ca: ConnectionIO[A]): F[A] 12 | 13 | // see: https://github.com/typelevel/kind-projector#polymorphic-lambda-values 14 | def liftConnectionIOK: ConnectionIO ~> F = λ[ConnectionIO ~> F](liftConnectionIO(_)) 15 | } 16 | 17 | object LiftConnectionIO { 18 | 19 | def apply[F[_]](implicit ev: LiftConnectionIO[F]): LiftConnectionIO[F] = ev 20 | 21 | implicit val CIOLiftConnectionIO: LiftConnectionIO[ConnectionIO] = 22 | new LiftConnectionIO[ConnectionIO] { 23 | def liftConnectionIO[A](ca: ConnectionIO[A]): ConnectionIO[A] = ca 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/doobieInstances.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db 2 | 3 | import doobie.Meta 4 | import doobie.util.{Read, Write} 5 | import io.circe.Json 6 | import io.circe.parser.parse 7 | import org.postgresql.util.PGobject 8 | 9 | object doobieInstances { 10 | 11 | implicit val bigIntRead: Read[BigInt] = 12 | Read[BigDecimal].map(_.toBigInt()) 13 | 14 | implicit val bigIntWrite: Write[BigInt] = 15 | Write[BigDecimal].contramap(x => BigDecimal(x.bigInteger)) 16 | 17 | implicit val JsonMeta: Meta[Json] = 18 | Meta.Advanced 19 | .other[PGobject]("json") 20 | .imap[Json](a => parse(a.getValue).right.getOrElse(Json.Null))(mkPgJson) 21 | 22 | private def mkPgJson(a: Json) = { 23 | val o = new PGobject 24 | o.setType("json") 25 | o.setValue(a.noSpaces) 26 | o 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/AdProof.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models 2 | 3 | import org.ergoplatform.explorer.{BlockId, HexString} 4 | 5 | /** Represents `node_ad_proofs` table. 6 | */ 7 | final case class AdProof( 8 | headerId: BlockId, 9 | proofBytes: HexString, // serialized and hex-encoded AVL+ tree path 10 | digest: HexString // hex-encoded tree root hash 11 | ) 12 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/AnyOutput.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models 2 | 3 | import io.circe.Json 4 | import org.ergoplatform.explorer._ 5 | 6 | final case class AnyOutput( 7 | boxId: BoxId, 8 | txId: TxId, 9 | headerId: Option[BlockId], 10 | value: Long, 11 | creationHeight: Int, 12 | settlementHeight: Option[Int], 13 | index: Int, 14 | globalIndex: Option[Long], 15 | ergoTree: HexString, 16 | ergoTreeTemplateHash: ErgoTreeTemplateHash, 17 | address: Address, 18 | additionalRegisters: Json, 19 | timestamp: Option[Long], 20 | mainChain: Option[Boolean], 21 | spendingTxId: Option[TxId] 22 | ) 23 | 24 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/Asset.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models 2 | 3 | import org.ergoplatform.explorer.{BlockId, BoxId, TokenId} 4 | 5 | /** Represents `node_assets` table. 6 | */ 7 | final case class Asset( 8 | tokenId: TokenId, 9 | boxId: BoxId, 10 | headerId: BlockId, 11 | index: Int, 12 | amount: Long 13 | ) 14 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/BlockExtension.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models 2 | 3 | import io.circe.magnolia.derivation.decoder.semiauto.deriveMagnoliaDecoder 4 | import io.circe.magnolia.derivation.encoder.semiauto.deriveMagnoliaEncoder 5 | import io.circe.{Codec, Json} 6 | import org.ergoplatform.explorer.{BlockId, HexString} 7 | import sttp.tapir.{Schema, SchemaType} 8 | 9 | /** Represents `node_extensions` table. 10 | */ 11 | final case class BlockExtension( 12 | headerId: BlockId, 13 | digest: HexString, 14 | fields: Json // arbitrary key->value dictionary 15 | ) 16 | 17 | object BlockExtension { 18 | 19 | implicit val codec: Codec[BlockExtension] = Codec.from(deriveMagnoliaDecoder, deriveMagnoliaEncoder) 20 | 21 | implicit def schema: Schema[BlockExtension] = Schema.derived[BlockExtension] 22 | 23 | implicit private def fieldsSchema: Schema[Json] = Schema.string[Json] 24 | } 25 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/BlockStats.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models 2 | 3 | import org.ergoplatform.explorer.{Address, BlockId} 4 | 5 | /** Represents `blocks_info` table. 6 | * Containing main fields from protocol header and full-block stats. 7 | */ 8 | final case class BlockStats( 9 | headerId: BlockId, 10 | timestamp: Long, 11 | height: Int, 12 | difficulty: BigInt, 13 | blockSize: Int, // block size (bytes) 14 | blockCoins: Long, // total amount of nERGs in the block 15 | blockMiningTime: Option[Long], // block mining time 16 | txsCount: Int, // number of txs in the block 17 | txsSize: Int, // total size of all transactions in this block (bytes) 18 | minerAddress: Address, 19 | minerReward: Long, // total amount of nERGs miner received from coinbase 20 | minerRevenue: Long, // total amount of nERGs miner received as a reward (coinbase + fee) 21 | blockFee: Long, // total amount of transaction fee in the block (nERG) 22 | blockChainTotalSize: Long, // cumulative blockchain size including this block 23 | totalTxsCount: Long, // total number of txs in all blocks in the chain 24 | totalCoinsIssued: Long, // amount of nERGs issued in the block 25 | totalMiningTime: Long, // mining time of all the blocks in the chain 26 | totalFees: Long, // total amount of nERGs all miners received as a fee 27 | totalMinersReward: Long, // total amount of nERGs all miners received as a reward for all time 28 | totalCoinsInTxs: Long, // total amount of nERGs in all blocks 29 | maxTxGix: Long, // Global index of the last transaction in the block 30 | maxBoxGix: Long, // Global index of the last output in the last transaction in the block 31 | mainChain: Boolean 32 | ) 33 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/BlockedToken.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models 2 | 3 | import org.ergoplatform.explorer.TokenId 4 | 5 | final case class BlockedToken( 6 | tokenId: TokenId, 7 | tokenName: String 8 | ) 9 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/BoxRegister.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models 2 | 3 | import org.ergoplatform.explorer._ 4 | 5 | final case class BoxRegister( 6 | id: RegisterId, 7 | boxId: BoxId, 8 | sigmaType: SigmaType, 9 | rawValue: HexString, 10 | renderedValue: String 11 | ) 12 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/DataInput.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models 2 | 3 | import org.ergoplatform.explorer.{BlockId, BoxId, TxId} 4 | 5 | /** Represents `node_data_inputs` table. 6 | */ 7 | final case class DataInput( 8 | boxId: BoxId, 9 | txId: TxId, 10 | headerId: BlockId, 11 | index: Int, // index of the input in the transaction 12 | mainChain: Boolean // chain status, `true` if this input resides in main chain. 13 | ) 14 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/EpochParameters.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models 2 | 3 | /** Represents `epochs_parameters` table. 4 | */ 5 | final case class EpochParameters( 6 | id: Int, 7 | height: Int, 8 | storageFeeFactor: Int, 9 | minValuePerByte: Int, 10 | maxBlockSize: Int, 11 | maxBlockCost: Int, 12 | blockVersion: Byte, 13 | tokenAccessCost: Int, 14 | inputCost: Int, 15 | dataInputCost: Int, 16 | outputCost: Int 17 | ) 18 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/GenuineToken.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models 2 | 3 | import org.ergoplatform.explorer.TokenId 4 | 5 | final case class GenuineToken( 6 | id: TokenId, 7 | tokenName: String, 8 | uniqueName: Boolean, 9 | issuer: Option[String] 10 | ) 11 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/Header.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models 2 | 3 | import org.ergoplatform.explorer.{BlockId, HexString} 4 | 5 | /** Represents `node_headers` table. 6 | */ 7 | final case class Header( 8 | id: BlockId, 9 | parentId: BlockId, 10 | version: Byte, 11 | height: Int, 12 | nBits: Long, 13 | difficulty: BigDecimal, 14 | timestamp: Long, 15 | stateRoot: HexString, 16 | adProofsRoot: HexString, 17 | transactionsRoot: HexString, 18 | extensionHash: HexString, 19 | minerPk: HexString, 20 | w: HexString, // PoW one time PK 21 | n: HexString, // PoW nonce 22 | d: String, // PoW distance 23 | votes: String, // hex-encoded votes for a soft-fork and parameters 24 | mainChain: Boolean // chain status, `true` if this header resides in main chain. 25 | ) 26 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/Input.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models 2 | 3 | import io.circe.Json 4 | import org.ergoplatform.explorer.{BlockId, BoxId, HexString, TxId} 5 | 6 | /** Represents `node_inputs` table. 7 | */ 8 | final case class Input( 9 | boxId: BoxId, 10 | txId: TxId, 11 | headerId: BlockId, 12 | proofBytes: Option[HexString], // serialized and hex-encoded cryptographic proof 13 | extension: Json, // arbitrary key-value dictionary 14 | index: Int, // index of the input in the transaction 15 | mainChain: Boolean // chain status, `true` if this input resides in main chain. 16 | ) 17 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/Miner.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models 2 | 3 | import org.ergoplatform.explorer.Address 4 | 5 | /** Represents `known_miners` table. 6 | */ 7 | final case class Miner(address: Address, name: String) 8 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/Output.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models 2 | 3 | import io.circe.Json 4 | import org.ergoplatform.explorer._ 5 | 6 | /** Represents `node_outputs` table. 7 | */ 8 | final case class Output( 9 | boxId: BoxId, 10 | txId: TxId, 11 | headerId: BlockId, 12 | value: Long, // amount of nanoERG in thee corresponding box 13 | creationHeight: Int, // the height this output was created 14 | settlementHeight: Int, // the height this output got fixed in blockchain 15 | index: Int, // index of the output in the transaction 16 | globalIndex: Long, 17 | ergoTree: HexString, // serialized and hex-encoded ErgoTree 18 | ergoTreeTemplateHash: ErgoTreeTemplateHash, // hash of serialized and hex-encoded ErgoTree template 19 | address: Address, // an address derived from ergoTree 20 | additionalRegisters: Json, // arbitrary key-value dictionary 21 | timestamp: Long, // time output appeared in the blockchain 22 | mainChain: Boolean // chain status, `true` if this output resides in main chain 23 | ) 24 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/ScriptConstant.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models 2 | 3 | import org.ergoplatform.explorer.{BoxId, HexString, SigmaType} 4 | 5 | final case class ScriptConstant( 6 | index: Int, 7 | boxId: BoxId, 8 | sigmaType: SigmaType, 9 | serializedValue: HexString, 10 | renderedValue: String 11 | ) 12 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/Token.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models 2 | 3 | import org.ergoplatform.explorer.{BoxId, TokenId, TokenType} 4 | 5 | final case class Token( 6 | id: TokenId, 7 | boxId: BoxId, 8 | emissionAmount: Long, 9 | name: Option[String], 10 | description: Option[String], 11 | `type`: Option[TokenType], 12 | decimals: Option[Int] 13 | ) 14 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/Transaction.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models 2 | 3 | import org.ergoplatform.explorer.{BlockId, TxId} 4 | 5 | /** Represents `node_transactions` table. 6 | */ 7 | final case class Transaction( 8 | id: TxId, 9 | headerId: BlockId, 10 | inclusionHeight: Int, 11 | isCoinbase: Boolean, 12 | timestamp: Long, // approx time output appeared in the blockchain 13 | size: Int, // transaction size in bytes 14 | index: Int, // index of a transaction inside a block 15 | globalIndex: Long, 16 | mainChain: Boolean 17 | ) { 18 | 19 | def numConfirmations(bestHeight: Int): Int = bestHeight - inclusionHeight + 1 20 | } 21 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/UAsset.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models 2 | 3 | import org.ergoplatform.explorer.{BoxId, TokenId} 4 | 5 | /** Represents `node_u_assets` table (Unconfirmed Asset, which is a part of Unconfirmed Transaction). 6 | */ 7 | final case class UAsset( 8 | tokenId: TokenId, 9 | boxId: BoxId, 10 | index: Int, 11 | amount: Long 12 | ) 13 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/UDataInput.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models 2 | 3 | import org.ergoplatform.explorer.{BoxId, TxId} 4 | 5 | /** Represents `node_u_data_inputs` table (Unconfirmed Data Input, which is a part of Unconfirmed Transaction). 6 | */ 7 | final case class UDataInput( 8 | boxId: BoxId, 9 | txId: TxId, 10 | index: Int // index of the input in the transaction 11 | ) 12 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/UInput.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models 2 | 3 | import io.circe.Json 4 | import org.ergoplatform.explorer.{BoxId, HexString, TxId} 5 | 6 | /** Represents `node_u_inputs` table (Unconfirmed Input, which is a part of Unconfirmed Transaction). 7 | */ 8 | final case class UInput( 9 | boxId: BoxId, 10 | txId: TxId, 11 | index: Int, // index of the input in the transaction 12 | proofBytes: Option[HexString], // serialized and hex-encoded cryptographic proof 13 | extension: Json // arbitrary key-value dictionary 14 | ) 15 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/UOutput.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models 2 | 3 | import io.circe.Json 4 | import org.ergoplatform.explorer._ 5 | 6 | /** Represents `node_u_outputs` table (Unconfirmed Output, which is a part of Unconfirmed Transaction). 7 | */ 8 | final case class UOutput( 9 | boxId: BoxId, 10 | txId: TxId, 11 | value: Long, // amount of nanoERG in thee corresponding box 12 | creationHeight: Int, // the height this output was created 13 | index: Int, // index of the output in the transaction 14 | ergoTree: HexString, // serialized and hex-encoded ErgoTree 15 | ergoTreeTemplateHash: ErgoTreeTemplateHash, // hash of serialized and hex-encoded ErgoTree template 16 | address: Address, // an address derived from ergoTree (if possible) 17 | additionalRegisters: Json // arbitrary key-value dictionary 18 | ) 19 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/UTransaction.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models 2 | 3 | import org.ergoplatform.explorer.TxId 4 | 5 | /** Represents `node_u_transactions` table (Unconfirmed Transaction). 6 | */ 7 | final case class UTransaction( 8 | id: TxId, 9 | creationTimestamp: Long, // approx time transaction was created (appeared in mempool) 10 | size: Int // transaction size in bytes 11 | ) 12 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/aggregates/AggregatedAsset.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models.aggregates 2 | 3 | import org.ergoplatform.explorer.{TokenId, TokenType} 4 | 5 | final case class AggregatedAsset( 6 | tokenId: TokenId, 7 | totalAmount: Long, 8 | name: Option[String], 9 | decimals: Option[Int], 10 | `type`: Option[TokenType] 11 | ) 12 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/aggregates/AnyAsset.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models.aggregates 2 | 3 | import org.ergoplatform.explorer.{BlockId, BoxId, TokenId, TokenType} 4 | 5 | final case class AnyAsset( 6 | tokenId: TokenId, 7 | boxId: BoxId, 8 | headerId: Option[BlockId], 9 | index: Int, 10 | amount: Long, 11 | name: Option[String], 12 | decimals: Option[Int], 13 | `type`: Option[TokenType] 14 | ) 15 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/aggregates/BlockSize.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models.aggregates 2 | 3 | import org.ergoplatform.explorer.BlockId 4 | 5 | case class BlockSize(headerId: BlockId, size: Int) 6 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/aggregates/ExtendedAsset.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models.aggregates 2 | 3 | import org.ergoplatform.explorer.{BlockId, BoxId, TokenId, TokenType} 4 | 5 | /** Asset entity enriched with name and decimals num of the asset. 6 | */ 7 | final case class ExtendedAsset( 8 | tokenId: TokenId, 9 | boxId: BoxId, 10 | headerId: BlockId, 11 | index: Int, 12 | amount: Long, 13 | name: Option[String], 14 | decimals: Option[Int], 15 | `type`: Option[TokenType] 16 | ) 17 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/aggregates/ExtendedBlockInfo.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models.aggregates 2 | 3 | import org.ergoplatform.explorer.db.models.BlockStats 4 | 5 | final case class ExtendedBlockInfo(blockVersion: Byte, blockInfo: BlockStats, minerNameOpt: Option[String]) 6 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/aggregates/ExtendedDataInput.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models.aggregates 2 | 3 | import org.ergoplatform.explorer.db.models.DataInput 4 | import org.ergoplatform.explorer.{Address, TxId} 5 | 6 | /** DataInput entity enriched with a data from corresponding output. 7 | */ 8 | final case class ExtendedDataInput( 9 | input: DataInput, 10 | value: Option[Long], 11 | outputTxId: Option[TxId], 12 | outputIndex: Option[Int], 13 | address: Option[Address] 14 | ) 15 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/aggregates/ExtendedInput.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models.aggregates 2 | 3 | import org.ergoplatform.explorer.db.models.Input 4 | import org.ergoplatform.explorer.{Address, TxId} 5 | 6 | /** Input entity enriched with a data from corresponding output. 7 | */ 8 | final case class ExtendedInput( 9 | input: Input, 10 | value: Option[Long], 11 | outputTxId: Option[TxId], 12 | outputIndex: Option[Int], 13 | address: Option[Address] 14 | ) 15 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/aggregates/ExtendedOutput.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models.aggregates 2 | 3 | import org.ergoplatform.explorer.TxId 4 | import org.ergoplatform.explorer.db.models.Output 5 | 6 | /** Output entity enriched with a data from corresponding transaction. 7 | */ 8 | final case class ExtendedOutput( 9 | output: Output, 10 | spentByOpt: Option[TxId] 11 | ) 12 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/aggregates/ExtendedUAsset.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models.aggregates 2 | 3 | import org.ergoplatform.explorer.{BoxId, TokenId, TokenType} 4 | 5 | final case class ExtendedUAsset( 6 | tokenId: TokenId, 7 | boxId: BoxId, 8 | index: Int, 9 | amount: Long, 10 | name: Option[String], 11 | decimals: Option[Int], 12 | `type`: Option[TokenType] 13 | ) 14 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/aggregates/ExtendedUDataInput.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models.aggregates 2 | 3 | import io.circe.Json 4 | import org.ergoplatform.explorer.db.models.UDataInput 5 | import org.ergoplatform.explorer.{Address, BlockId, ErgoTree, TxId} 6 | 7 | /** Unconfirmed input entity enriched with a data from corresponding output. 8 | */ 9 | final case class ExtendedUDataInput( 10 | input: UDataInput, 11 | value: Long, 12 | outputTxId: TxId, 13 | outputBlockId: Option[BlockId], 14 | outputIndex: Int, 15 | ergoTree: ErgoTree, 16 | address: Address, 17 | additionalRegisters: Json 18 | ) 19 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/aggregates/ExtendedUInput.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models.aggregates 2 | 3 | import io.circe.Json 4 | import org.ergoplatform.explorer.db.models.UInput 5 | import org.ergoplatform.explorer.{Address, BlockId, ErgoTree, TxId} 6 | 7 | /** Unconfirmed input entity enriched with a data from corresponding output. 8 | */ 9 | final case class ExtendedUInput( 10 | input: UInput, 11 | value: Long, 12 | outputTxId: TxId, 13 | outputBlockId: Option[BlockId], 14 | outputIndex: Int, 15 | ergoTree: ErgoTree, 16 | address: Address, 17 | additionalRegisters: Json 18 | ) 19 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/aggregates/ExtendedUOutput.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models.aggregates 2 | 3 | import org.ergoplatform.explorer._ 4 | import org.ergoplatform.explorer.db.models.UOutput 5 | 6 | /** Represents `node_u_outputs` table (Unconfirmed Output, which is a part of Unconfirmed Transaction). 7 | */ 8 | final case class ExtendedUOutput( 9 | output: UOutput, 10 | spendingTxId: Option[TxId] 11 | ) 12 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/aggregates/FullDataInput.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models.aggregates 2 | 3 | import io.circe.Json 4 | import org.ergoplatform.explorer.db.models.DataInput 5 | import org.ergoplatform.explorer.{Address, BlockId, HexString, TxId} 6 | 7 | final case class FullDataInput( 8 | input: DataInput, 9 | outputHeaderId: BlockId, 10 | outputTxId: TxId, 11 | value: Long, // amount of nanoERG in thee corresponding box 12 | outputIndex: Int, // index of the output in the transaction 13 | ergoTree: HexString, // serialized and hex-encoded ErgoTree 14 | address: Address, // an address derived from ergoTree 15 | additionalRegisters: Json 16 | ) 17 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/aggregates/FullInput.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models.aggregates 2 | 3 | import io.circe.Json 4 | import org.ergoplatform.explorer.db.models.Input 5 | import org.ergoplatform.explorer.{Address, BlockId, HexString, TxId} 6 | 7 | /** Input entity enriched with a data from corresponding output. 8 | */ 9 | final case class FullInput( 10 | input: Input, 11 | outputHeaderId: BlockId, 12 | outputTxId: TxId, 13 | value: Long, // amount of nanoERG in thee corresponding box 14 | outputIndex: Int, // index of the output in the transaction 15 | outputGlobalIndex: Long, 16 | outputCreatedAt: Int, 17 | outputSettledAt: Int, 18 | ergoTree: HexString, // serialized and hex-encoded ErgoTree 19 | address: Address, // an address derived from ergoTree 20 | additionalRegisters: Json 21 | ) 22 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/aggregates/MinerStats.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models.aggregates 2 | 3 | import org.ergoplatform.explorer.Address 4 | 5 | final case class MinerStats( 6 | minerAddress: Address, 7 | totalDifficulties: BigDecimal, 8 | totalTime: Long, 9 | blocksMined: Int, 10 | minerName: Option[String] 11 | ) { 12 | def verboseName: String = 13 | minerName.getOrElse(minerAddress.unwrapped.takeRight(8)) 14 | } 15 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/models/aggregates/TimePoint.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models.aggregates 2 | 3 | final case class TimePoint[A](ts: Long, value: A, dummy: String) 4 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/queries/AdProofQuerySet.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.queries 2 | 3 | import doobie.LogHandler 4 | import doobie.implicits._ 5 | import doobie.refined.implicits._ 6 | import doobie.util.query.Query0 7 | import org.ergoplatform.explorer.BlockId 8 | import org.ergoplatform.explorer.db.models.AdProof 9 | 10 | /** A set of queries for doobie implementation of [AdProofRepo]. 11 | */ 12 | object AdProofQuerySet extends QuerySet { 13 | 14 | val tableName: String = "node_ad_proofs" 15 | 16 | val fields: List[String] = List( 17 | "header_id", 18 | "proof_bytes", 19 | "digest" 20 | ) 21 | 22 | def getByHeaderId(headerId: BlockId)(implicit lh: LogHandler): Query0[AdProof] = 23 | sql"select header_id, proof_bytes, digest from node_ad_proofs where header_id = $headerId" 24 | .query[AdProof] 25 | } 26 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/queries/BlockExtensionQuerySet.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.queries 2 | 3 | import doobie.LogHandler 4 | import doobie.implicits._ 5 | import doobie.refined.implicits._ 6 | import doobie.util.query.Query0 7 | import org.ergoplatform.explorer.BlockId 8 | import org.ergoplatform.explorer.db.models.BlockExtension 9 | 10 | /** A set of queries for doobie implementation of [BlockExtensionRepo]. 11 | */ 12 | object BlockExtensionQuerySet extends QuerySet { 13 | 14 | import org.ergoplatform.explorer.db.doobieInstances._ 15 | 16 | val tableName: String = "node_extensions" 17 | 18 | val fields: List[String] = List( 19 | "header_id", 20 | "digest", 21 | "fields" 22 | ) 23 | 24 | def getByHeaderId(headerId: BlockId)(implicit lh: LogHandler): Query0[BlockExtension] = 25 | sql"select * from node_extensions where header_id = $headerId" 26 | .query[BlockExtension] 27 | } 28 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/queries/BlockedTokenQuerySet.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.queries 2 | 3 | import doobie.implicits._ 4 | import doobie.util.fragment.Fragment 5 | import doobie.util.log.LogHandler 6 | import doobie.util.query.Query0 7 | import org.ergoplatform.explorer.TokenId 8 | import org.ergoplatform.explorer.db.models.BlockedToken 9 | 10 | object BlockedTokenQuerySet extends QuerySet { 11 | 12 | /** Name of the table according to a database schema. 13 | */ 14 | override val tableName: String = "blocked_tokens" 15 | 16 | /** Table column names listing according to a database schema. 17 | */ 18 | override val fields: List[String] = List( 19 | "token_id", 20 | "token_name" 21 | ) 22 | 23 | def get(id: TokenId): Query0[BlockedToken] = 24 | sql""" 25 | |select gt.token_id, gt.token_name from blocked_tokens gt 26 | |where gt.token_id = $id 27 | |""".stripMargin.query[BlockedToken] 28 | 29 | def get(name: String): Query0[BlockedToken] = 30 | sql""" 31 | |select gt.token_id, gt.token_name from blocked_tokens gt 32 | |where LOWER(gt.token_name) = LOWER($name) 33 | |""".stripMargin.query[BlockedToken] 34 | 35 | def getAll(offset: Int, limit: Int)(implicit 36 | lh: LogHandler 37 | ): Query0[BlockedToken] = { 38 | val q = 39 | sql""" 40 | |select gt.token_id, gt.token_name from blocked_tokens gt 41 | |""".stripMargin 42 | val offsetLimitFr = Fragment.const(s"offset $offset limit $limit") 43 | (q ++ offsetLimitFr).query[BlockedToken] 44 | } 45 | 46 | def countAll()(implicit lh: LogHandler): Query0[Int] = { 47 | val q = sql"select count(*) from blocked_tokens" 48 | q.query[Int] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/queries/BoxRegisterQuerySet.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.queries 2 | 3 | object BoxRegisterQuerySet extends QuerySet { 4 | 5 | val tableName: String = "box_registers" 6 | 7 | val fields: List[String] = List( 8 | "id", 9 | "box_id", 10 | "value_type", 11 | "serialized_value", 12 | "rendered_value" 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/queries/EpochParametersQuerySet.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.queries 2 | 3 | import doobie.util.log.LogHandler 4 | import doobie.util.query.Query0 5 | import org.ergoplatform.explorer.db.models.EpochParameters 6 | import doobie.LogHandler 7 | import doobie.implicits._ 8 | import org.ergoplatform.ErgoLikeContext.Height 9 | 10 | object EpochParametersQuerySet extends QuerySet { 11 | 12 | /** Name of the table according to a database schema. 13 | */ 14 | override val tableName: String = "epochs_parameters" 15 | /** Table column names listing according to a database schema. 16 | */ 17 | override val fields: List[String] = List( 18 | "id", 19 | "height", 20 | "storage_fee_factor", 21 | "min_value_per_byte", 22 | "max_block_size", 23 | "max_block_cost", 24 | "block_version", 25 | "token_access_cost", 26 | "input_cost", 27 | "data_input_cost", 28 | "output_cost" 29 | ) 30 | 31 | def getById(id: Int)(implicit lh: LogHandler): Query0[EpochParameters] = 32 | sql"select * from epochs_parameters where id = $id".query[EpochParameters] 33 | 34 | def getByHeight(height: Height)(implicit lh: LogHandler): Query0[EpochParameters] = 35 | sql"select * from epochs_parameters where height = $height".query[EpochParameters] 36 | 37 | def getLastHeight(implicit lh: LogHandler): Query0[Int] = 38 | sql"select height from epochs_parameters order by height desc limit 1".query[Int] 39 | 40 | def getLastEpoch(implicit lh: LogHandler): Query0[EpochParameters] = 41 | sql"select * from epochs_parameters order by id desc limit 1".query[EpochParameters] 42 | } 43 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/queries/QuerySet.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.queries 2 | 3 | import cats.implicits._ 4 | import doobie.implicits._ 5 | import doobie.util.{Read, Write} 6 | import doobie.{ConnectionIO, Update} 7 | 8 | /** Database table access operations layer. 9 | */ 10 | trait QuerySet { 11 | 12 | /** Name of the table according to a database schema. 13 | */ 14 | val tableName: String 15 | 16 | /** Table column names listing according to a database schema. 17 | */ 18 | val fields: List[String] 19 | 20 | def insert[M: Read: Write](m: M): ConnectionIO[M] = 21 | insert.withUniqueGeneratedKeys[M](fields: _*)(m) 22 | 23 | def insertNoConflict[M: Read: Write](m: M): ConnectionIO[Int] = 24 | insertNoConflict.run(m) 25 | 26 | def insertMany[M: Read: Write](xs: List[M]): ConnectionIO[List[M]] = 27 | insert.updateManyWithGeneratedKeys[M](fields: _*)(xs).compile.to(List) 28 | 29 | def insertManyNoConflict[M: Read: Write](xs: List[M]): ConnectionIO[Int] = 30 | insertNoConflict.updateMany(xs) 31 | 32 | private def insert[M: Write]: Update[M] = 33 | Update[M](s"insert into $tableName ($fieldsString) values ($holdersString)") 34 | 35 | private def insertNoConflict[M: Write]: Update[M] = 36 | Update[M](s"insert into $tableName ($fieldsString) values ($holdersString) on conflict do nothing") 37 | 38 | private def fieldsString: String = 39 | fields.mkString(", ") 40 | 41 | private def holdersString: String = 42 | fields.map(_ => "?").mkString(", ") 43 | } 44 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/queries/ScriptConstantsQuerySet.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.queries 2 | 3 | object ScriptConstantsQuerySet extends QuerySet { 4 | 5 | val tableName: String = "script_constants" 6 | 7 | val fields: List[String] = List( 8 | "index", 9 | "box_id", 10 | "value_type", 11 | "serialized_value", 12 | "rendered_value" 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/queries/StatsQuerySet.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.queries 2 | 3 | import doobie.Query0 4 | import doobie.implicits._ 5 | import doobie.util.log.LogHandler 6 | 7 | object StatsQuerySet { 8 | 9 | def countUniqueAddrs(implicit lh: LogHandler): Query0[Long] = 10 | sql""" 11 | |select count(distinct address) from node_outputs 12 | |""".stripMargin.query 13 | } 14 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/repositories/AdProofRepo.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.repositories 2 | 3 | import cats.effect.Sync 4 | import cats.syntax.functor._ 5 | import doobie.LogHandler 6 | import doobie.free.implicits._ 7 | import doobie.refined.implicits._ 8 | import org.ergoplatform.explorer.BlockId 9 | import org.ergoplatform.explorer.db.DoobieLogHandler 10 | import org.ergoplatform.explorer.db.algebra.LiftConnectionIO 11 | import org.ergoplatform.explorer.db.models.AdProof 12 | import org.ergoplatform.explorer.db.syntax.liftConnectionIO._ 13 | 14 | /** [[AdProof]] data access operations. 15 | */ 16 | trait AdProofRepo[D[_]] { 17 | 18 | /** Put a given `proof` to persistence. 19 | */ 20 | def insert(proof: AdProof): D[Unit] 21 | 22 | /** Get proof related to a header with a given `headerId`. 23 | */ 24 | def getByHeaderId(headerId: BlockId): D[Option[AdProof]] 25 | } 26 | 27 | object AdProofRepo { 28 | 29 | def apply[F[_]: Sync, D[_]: LiftConnectionIO]: F[AdProofRepo[D]] = 30 | DoobieLogHandler.create[F].map { implicit lh => 31 | new Live[D] 32 | } 33 | 34 | final private class Live[D[_]: LiftConnectionIO](implicit lh: LogHandler) extends AdProofRepo[D] { 35 | 36 | import org.ergoplatform.explorer.db.queries.{AdProofQuerySet => QS} 37 | 38 | def insert(proof: AdProof): D[Unit] = 39 | QS.insertNoConflict(proof).void.liftConnectionIO 40 | 41 | def getByHeaderId(headerId: BlockId): D[Option[AdProof]] = 42 | QS.getByHeaderId(headerId).option.liftConnectionIO 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/repositories/BlockExtensionRepo.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.repositories 2 | 3 | import cats.effect.Sync 4 | import cats.implicits._ 5 | import doobie.free.implicits._ 6 | import doobie.refined.implicits._ 7 | import doobie.util.log.LogHandler 8 | import org.ergoplatform.explorer.BlockId 9 | import org.ergoplatform.explorer.db.DoobieLogHandler 10 | import org.ergoplatform.explorer.db.algebra.LiftConnectionIO 11 | import org.ergoplatform.explorer.db.models.BlockExtension 12 | import org.ergoplatform.explorer.db.syntax.liftConnectionIO._ 13 | import org.ergoplatform.explorer.db.doobieInstances._ 14 | 15 | /** [[BlockExtension]] data access operations. 16 | */ 17 | trait BlockExtensionRepo[D[_]] { 18 | 19 | /** Put a given `extension` to persistence. 20 | */ 21 | def insert(extension: BlockExtension): D[Unit] 22 | 23 | /** Get extension related to a given `headerId`. 24 | */ 25 | def getByHeaderId(headerId: BlockId): D[Option[BlockExtension]] 26 | } 27 | 28 | object BlockExtensionRepo { 29 | 30 | def apply[F[_]: Sync, D[_]: LiftConnectionIO]: F[BlockExtensionRepo[D]] = 31 | DoobieLogHandler.create[F].map { implicit lh => 32 | new Live[D] 33 | } 34 | 35 | final private class Live[D[_]: LiftConnectionIO](implicit lh: LogHandler) 36 | extends BlockExtensionRepo[D] { 37 | 38 | import org.ergoplatform.explorer.db.queries.{BlockExtensionQuerySet => QS} 39 | 40 | def insert(extension: BlockExtension): D[Unit] = 41 | QS.insertNoConflict(extension).void.liftConnectionIO 42 | 43 | def getByHeaderId(headerId: BlockId): D[Option[BlockExtension]] = 44 | QS.getByHeaderId(headerId).option.liftConnectionIO 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/repositories/BoxRegisterRepo.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.repositories 2 | 3 | import cats.effect.Sync 4 | import doobie.free.implicits._ 5 | import doobie.refined.implicits._ 6 | import doobie.util.log.LogHandler 7 | import org.ergoplatform.explorer.db.DoobieLogHandler 8 | import org.ergoplatform.explorer.db.algebra.LiftConnectionIO 9 | import org.ergoplatform.explorer.db.models.BoxRegister 10 | import org.ergoplatform.explorer.db.syntax.liftConnectionIO._ 11 | import org.ergoplatform.explorer.db.doobieInstances._ 12 | import tofu.syntax.monadic._ 13 | 14 | trait BoxRegisterRepo[D[_]] { 15 | 16 | /** Put a given `register` to persistence. 17 | */ 18 | def insert(register: BoxRegister): D[Unit] 19 | 20 | /** Persist a given list of `registers`. 21 | */ 22 | def insertMany(registers: List[BoxRegister]): D[Unit] 23 | } 24 | 25 | object BoxRegisterRepo { 26 | 27 | def apply[F[_]: Sync, D[_]: LiftConnectionIO]: F[BoxRegisterRepo[D]] = 28 | DoobieLogHandler.create[F].map { implicit lh => 29 | new Live[D] 30 | } 31 | 32 | final private class Live[D[_]: LiftConnectionIO](implicit lh: LogHandler) extends BoxRegisterRepo[D] { 33 | 34 | import org.ergoplatform.explorer.db.queries.{BoxRegisterQuerySet => QS} 35 | 36 | def insert(register: BoxRegister): D[Unit] = 37 | QS.insertNoConflict(register).void.liftConnectionIO 38 | 39 | def insertMany(registers: List[BoxRegister]): D[Unit] = 40 | QS.insertManyNoConflict(registers).void.liftConnectionIO 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/repositories/ScriptConstantsRepo.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.repositories 2 | 3 | import cats.effect.Sync 4 | import doobie.free.implicits._ 5 | import doobie.refined.implicits._ 6 | import doobie.util.log.LogHandler 7 | import org.ergoplatform.explorer.db.DoobieLogHandler 8 | import org.ergoplatform.explorer.db.algebra.LiftConnectionIO 9 | import org.ergoplatform.explorer.db.models.{BoxRegister, ScriptConstant} 10 | import org.ergoplatform.explorer.db.syntax.liftConnectionIO._ 11 | import org.ergoplatform.explorer.db.doobieInstances._ 12 | import tofu.syntax.monadic._ 13 | 14 | trait ScriptConstantsRepo[D[_]] { 15 | 16 | /** Put a given `register` to persistence. 17 | */ 18 | def insert(const: ScriptConstant): D[Unit] 19 | 20 | /** Persist a given list of `registers`. 21 | */ 22 | def insertMany(consts: List[ScriptConstant]): D[Unit] 23 | } 24 | 25 | object ScriptConstantsRepo { 26 | 27 | def apply[F[_]: Sync, D[_]: LiftConnectionIO]: F[ScriptConstantsRepo[D]] = 28 | DoobieLogHandler.create[F].map { implicit lh => 29 | new Live[D] 30 | } 31 | 32 | final private class Live[D[_]: LiftConnectionIO](implicit lh: LogHandler) extends ScriptConstantsRepo[D] { 33 | 34 | import org.ergoplatform.explorer.db.queries.{ScriptConstantsQuerySet => QS} 35 | 36 | def insert(const: ScriptConstant): D[Unit] = 37 | QS.insertNoConflict(const).void.liftConnectionIO 38 | 39 | def insertMany(consts: List[ScriptConstant]): D[Unit] = 40 | QS.insertManyNoConflict(consts).void.liftConnectionIO 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/repositories/StatsRepo.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.repositories 2 | 3 | import cats.effect.Sync 4 | import cats.tagless.syntax.functorK._ 5 | import derevo.derive 6 | import doobie.ConnectionIO 7 | import doobie.util.log.LogHandler 8 | import org.ergoplatform.explorer.db.DoobieLogHandler 9 | import org.ergoplatform.explorer.db.algebra.LiftConnectionIO 10 | import tofu.higherKind.derived.representableK 11 | import tofu.syntax.monadic._ 12 | 13 | @derive(representableK) 14 | trait StatsRepo[D[_]] { 15 | def countUniqueAddrs: D[Long] 16 | } 17 | 18 | object StatsRepo { 19 | 20 | def apply[F[_]: Sync, D[_]: LiftConnectionIO]: F[StatsRepo[D]] = 21 | DoobieLogHandler.create[F].map { implicit lh => 22 | (new Live).mapK(LiftConnectionIO[D].liftConnectionIOK) 23 | } 24 | 25 | final class Live(implicit lh: LogHandler) extends StatsRepo[ConnectionIO] { 26 | 27 | import org.ergoplatform.explorer.db.queries.{StatsQuerySet => QS} 28 | 29 | def countUniqueAddrs: ConnectionIO[Long] = QS.countUniqueAddrs.unique 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/repositories/bundles/UtxRepoBundle.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.repositories.bundles 2 | 3 | import cats.Monad 4 | import cats.effect.Concurrent 5 | import cats.syntax.traverse._ 6 | import dev.profunktor.redis4cats.RedisCommands 7 | import org.ergoplatform.explorer.cache.repositories.ErgoLikeTransactionRepo 8 | import org.ergoplatform.explorer.db.algebra.LiftConnectionIO 9 | import org.ergoplatform.explorer.db.repositories 10 | import org.ergoplatform.explorer.db.repositories._ 11 | import org.ergoplatform.explorer.settings.UtxCacheSettings 12 | import tofu.syntax.monadic._ 13 | 14 | final case class UtxRepoBundle[F[_], D[_], S[_[_], _]]( 15 | txs: UTransactionRepo[D, S], 16 | inputs: UInputRepo[D, S], 17 | dataInputs: UDataInputRepo[D, S], 18 | outputs: UOutputRepo[D, S], 19 | confirmedOutputs: OutputRepo[D, S], 20 | assets: UAssetRepo[D], 21 | confirmedAssets: AssetRepo[D, S], 22 | ergoTxRepo: Option[ErgoLikeTransactionRepo[F, S]] 23 | ) 24 | 25 | object UtxRepoBundle { 26 | 27 | def apply[F[_]: Concurrent, D[_]: Monad: LiftConnectionIO]( 28 | utxCacheSettings: UtxCacheSettings, 29 | redis: Option[RedisCommands[F, String, String]] 30 | ): F[UtxRepoBundle[F, D, fs2.Stream]] = 31 | redis.map(ErgoLikeTransactionRepo[F](utxCacheSettings, _)).sequence.flatMap { etxRepo => 32 | ( 33 | UTransactionRepo[F, D], 34 | UInputRepo[F, D], 35 | UDataInputRepo[F, D], 36 | UOutputRepo[F, D], 37 | OutputRepo[F, D], 38 | UAssetRepo[F, D], 39 | AssetRepo[F, D] 40 | ).mapN(UtxRepoBundle(_, _, _, _, _, _, _, etxRepo)) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/syntax/LiftConnectionIOSyntax.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.syntax 2 | 3 | import doobie.free.connection.ConnectionIO 4 | import org.ergoplatform.explorer.db.algebra.LiftConnectionIO 5 | 6 | trait LiftConnectionIOSyntax { 7 | 8 | implicit final def toLiftConnectionIOOps[A]( 9 | ca: ConnectionIO[A] 10 | ): LiftConnectionIOOps[A] = 11 | new LiftConnectionIOOps(ca) 12 | } 13 | 14 | final private[syntax] class LiftConnectionIOOps[A](private val ca: ConnectionIO[A]) 15 | extends AnyVal { 16 | 17 | def liftConnectionIO[F[_]: LiftConnectionIO]: F[A] = 18 | LiftConnectionIO[F].liftConnectionIO(ca) 19 | } 20 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/db/syntax/package.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db 2 | 3 | package object syntax { 4 | 5 | object liftConnectionIO extends LiftConnectionIOSyntax 6 | } 7 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/protocol/TxValidation.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.protocol 2 | 3 | import org.ergoplatform.ErgoLikeTransaction 4 | import org.ergoplatform.explorer.protocol.TxValidation.RuleViolation 5 | 6 | trait TxValidation { 7 | 8 | def validate(tx: ErgoLikeTransaction): List[RuleViolation] 9 | } 10 | 11 | object TxValidation { 12 | 13 | type RuleViolation = String 14 | 15 | object PartialSemanticValidation extends TxValidation { 16 | 17 | def validate(tx: ErgoLikeTransaction): List[RuleViolation] = { 18 | val validErgs = 19 | if (tx.outputs.forall(bx => bx.value > 0L)) None 20 | else Some("nanoERG amounts must be positive") 21 | val validTokens = 22 | if (tx.outputs.forall(bx => bx.additionalTokens.forall { case (_, amt) => amt > 0L })) None 23 | else Some("Token amounts must be positive") 24 | List(validErgs, validTokens).flatten 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/protocol/blocks.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.protocol 2 | 3 | import scorex.util.encode.Base16 4 | 5 | object blocks { 6 | 7 | @inline def epochOf(height: Int): Int = height / constants.EpochLength 8 | 9 | @inline def expandVotes(votesHex: String): (Byte, Byte, Byte) = { 10 | val defaultVotes = (0: Byte, 0: Byte, 0: Byte) 11 | val paramsQty = 3 12 | Base16 13 | .decode(votesHex) 14 | .map { 15 | case votes if votes.length == paramsQty => (votes(0): Byte, votes(1): Byte, votes(2): Byte) 16 | case _ => defaultVotes 17 | } 18 | .getOrElse(defaultVotes) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/protocol/constants.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.protocol 2 | 3 | import org.ergoplatform.ErgoScriptPredef 4 | import scorex.util.encode.Base16 5 | import sigmastate.basics.BcDlogGroup 6 | import sigmastate.interpreter.CryptoConstants 7 | import sigmastate.interpreter.CryptoConstants.EcPointType 8 | 9 | // Ergo protocol constants. See: https://github.com/ergoplatform/ergo/blob/master/src/main/scala/org/ergoplatform/settings/Constants.scala 10 | object constants { 11 | 12 | val PreGenesisHeight = 0 13 | 14 | val GenesisHeight: Int = PreGenesisHeight + 1 15 | 16 | val PublicKeyLength = 33 17 | 18 | val EpochLength = 1024 19 | 20 | val MinerRewardDelta = 720 21 | 22 | val TeamTreasuryThreshold = 67500000000L 23 | 24 | val group: BcDlogGroup[EcPointType] = CryptoConstants.dlogGroup 25 | 26 | val FeePropositionScriptHex: String = { 27 | val script = ErgoScriptPredef.feeProposition(MinerRewardDelta) 28 | Base16.encode(script.bytes) 29 | } 30 | 31 | val CoinsInOneErgo: Long = 1000000000L 32 | 33 | val ErgoDecimalPlacesNum: Int = 9 34 | 35 | val Eip27UpperPoint = 15 * CoinsInOneErgo 36 | val Eip27DefaultReEmission = 12 * CoinsInOneErgo 37 | val Eip27LowerPoint = 3 * CoinsInOneErgo 38 | val Eip27ResidualEmission = 3 * CoinsInOneErgo 39 | 40 | val MainnetEip27ActivationHeight = 777217 41 | val TestnetEip27ActivationHeight = 188001 42 | } 43 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/protocol/models/ApiAdProof.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.protocol.models 2 | 3 | import derevo.circe.decoder 4 | import derevo.derive 5 | import io.circe.refined._ 6 | import org.ergoplatform.explorer.{BlockId, HexString} 7 | 8 | /** A model mirroring AdProof entity from Ergo node REST API. 9 | * See `BlockADProofs` in https://github.com/ergoplatform/ergo/blob/master/src/main/resources/api/openapi.yaml 10 | */ 11 | @derive(decoder) 12 | final case class ApiAdProof( 13 | headerId: BlockId, 14 | proofBytes: HexString, 15 | digest: HexString 16 | ) 17 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/protocol/models/ApiAsset.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.protocol.models 2 | 3 | import derevo.circe.decoder 4 | import derevo.derive 5 | import org.ergoplatform.explorer.TokenId 6 | 7 | /** A model mirroring Asset entity from Ergo node REST API. 8 | * See `Asset` in https://github.com/ergoplatform/ergo/blob/master/src/main/resources/api/openapi.yaml 9 | */ 10 | @derive(decoder) 11 | final case class ApiAsset( 12 | tokenId: TokenId, 13 | amount: Long 14 | ) 15 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/protocol/models/ApiBlockExtension.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.protocol.models 2 | 3 | import derevo.circe.decoder 4 | import derevo.derive 5 | import io.circe.Json 6 | import io.circe.refined._ 7 | import org.ergoplatform.explorer.{HexString, BlockId} 8 | 9 | /** A model mirroring Extension entity from Ergo node REST API. 10 | * See `Extension` in https://github.com/ergoplatform/ergo/blob/master/src/main/resources/api/openapi.yaml 11 | */ 12 | @derive(decoder) 13 | final case class ApiBlockExtension( 14 | headerId: BlockId, 15 | digest: HexString, 16 | fields: Json 17 | ) 18 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/protocol/models/ApiBlockTransactions.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.protocol.models 2 | 3 | import derevo.circe.decoder 4 | import derevo.derive 5 | import org.ergoplatform.explorer.BlockId 6 | 7 | /** A model mirroring BlockTransactions entity from Ergo node REST API. 8 | * See `BlockTransactions` in https://github.com/ergoplatform/ergo/blob/master/src/main/resources/api/openapi.yaml 9 | */ 10 | @derive(decoder) 11 | final case class ApiBlockTransactions( 12 | headerId: BlockId, 13 | transactions: List[ApiTransaction] 14 | ) 15 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/protocol/models/ApiDataInput.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.protocol.models 2 | 3 | import io.circe.Decoder 4 | import org.ergoplatform.explorer.BoxId 5 | 6 | /** A model mirroring ErgoTransactionDataInput entity from Ergo node REST API. 7 | * See `ErgoTransactionDataInput` in `https://github.com/ergoplatform/ergo/blob/master/src/main/resources/api/openapi.yaml` 8 | */ 9 | final case class ApiDataInput(boxId: BoxId) 10 | 11 | object ApiDataInput { 12 | implicit val decoder: Decoder[ApiDataInput] = _.downField("boxId").as[BoxId].map(ApiDataInput(_)) 13 | } 14 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/protocol/models/ApiDifficulty.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.protocol.models 2 | 3 | import cats.syntax.either._ 4 | import io.circe.Decoder 5 | 6 | /** Wrapper for difficulty value in order to avoid 7 | * manually importing implicit decoder for it. 8 | */ 9 | final case class ApiDifficulty(value: BigDecimal) 10 | 11 | object ApiDifficulty { 12 | 13 | implicit val decoder: Decoder[ApiDifficulty] = 14 | Decoder.decodeString.emap { str => 15 | Either 16 | .catchNonFatal { 17 | val bInt = BigDecimal(str) 18 | ApiDifficulty(bInt) 19 | } 20 | .leftMap(_ => "Difficulty") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/protocol/models/ApiFullBlock.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.protocol.models 2 | 3 | import io.circe.{Decoder, HCursor} 4 | 5 | /** A model mirroring FullBlock entity from Ergo node REST API. 6 | * See `FullBlock` in https://github.com/ergoplatform/ergo/blob/master/src/main/resources/api/openapi.yaml 7 | */ 8 | final case class ApiFullBlock( 9 | header: ApiHeader, 10 | transactions: ApiBlockTransactions, 11 | extension: ApiBlockExtension, 12 | adProofs: Option[ApiAdProof], 13 | size: Int 14 | ) 15 | 16 | object ApiFullBlock { 17 | 18 | implicit val decoder: Decoder[ApiFullBlock] = { c: HCursor => 19 | for { 20 | header <- c.downField("header").as[ApiHeader] 21 | transactions <- c.downField("blockTransactions").as[ApiBlockTransactions] 22 | extension <- c.downField("extension").as[ApiBlockExtension] 23 | adProofs <- c.downField("adProofs").as[ApiAdProof] match { 24 | case Left(_) => Right(None) 25 | case Right(proofs) => Right(Some(proofs)) 26 | } 27 | size <- c.downField("size").as[Int] 28 | } yield ApiFullBlock(header, transactions, extension, adProofs, size) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/protocol/models/ApiInput.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.protocol.models 2 | 3 | import derevo.circe.decoder 4 | import derevo.derive 5 | import org.ergoplatform.explorer.BoxId 6 | 7 | /** A model mirroring ErgoTransactionInput entity from Ergo node REST API. 8 | * See `ErgoTransactionInput` in https://github.com/ergoplatform/ergo/blob/master/src/main/resources/api/openapi.yaml 9 | */ 10 | @derive(decoder) 11 | final case class ApiInput(boxId: BoxId, spendingProof: ApiSpendingProof) 12 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/protocol/models/ApiNodeInfo.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.protocol.models 2 | 3 | import derevo.circe.{decoder, encoder} 4 | import derevo.derive 5 | import org.ergoplatform.explorer.{BlockId, HexString} 6 | 7 | /** A model mirroring NodeInfo entity from Ergo node REST API. 8 | * See `NodeInfo` in https://github.com/ergoplatform/ergo/blob/master/src/main/resources/api/openapi.yaml 9 | */ 10 | @derive(encoder, decoder) 11 | final case class ApiNodeInfo( 12 | currentTime: Long, 13 | name: String, 14 | stateType: String, 15 | difficulty: Long, 16 | bestFullHeaderId: BlockId, 17 | bestHeaderId: BlockId, 18 | peersCount: Int, 19 | unconfirmedCount: Int, 20 | appVersion: String, 21 | stateRoot: HexString, 22 | previousFullHeaderId: BlockId, 23 | fullHeight: Int, 24 | headersHeight: Int, 25 | stateVersion: HexString, 26 | launchTime: Long, 27 | parameters: ApiNodeInfoEpochParameters, 28 | isMining: Boolean 29 | ) 30 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/protocol/models/ApiNodeInfoEpochParameters.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.protocol.models 2 | 3 | import derevo.circe.{decoder, encoder} 4 | import derevo.derive 5 | 6 | @derive(encoder, decoder) 7 | final case class ApiNodeInfoEpochParameters( 8 | height: Int, 9 | storageFeeFactor: Int, 10 | minValuePerByte: Int, 11 | maxBlockSize: Int, 12 | maxBlockCost: Int, 13 | blockVersion: Byte, 14 | tokenAccessCost: Int, 15 | inputCost: Int, 16 | dataInputCost: Int, 17 | outputCost: Int 18 | ) 19 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/protocol/models/ApiOutput.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.protocol.models 2 | 3 | import derevo.circe.decoder 4 | import derevo.derive 5 | import io.circe.refined._ 6 | import org.ergoplatform.explorer.{BoxId, HexString, RegisterId} 7 | 8 | /** A model mirroring ErgoTransactionOutput entity from Ergo node REST API. 9 | * See `ErgoTransactionOutput` in https://github.com/ergoplatform/ergo/blob/master/src/main/resources/api/openapi.yaml 10 | */ 11 | @derive(decoder) 12 | final case class ApiOutput( 13 | boxId: BoxId, 14 | value: Long, 15 | creationHeight: Int, 16 | ergoTree: HexString, 17 | assets: List[ApiAsset], 18 | additionalRegisters: Map[RegisterId, HexString] 19 | ) 20 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/protocol/models/ApiPowSolutions.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.protocol.models 2 | 3 | import io.circe.refined._ 4 | import io.circe.{Decoder, HCursor} 5 | import org.ergoplatform.explorer.HexString 6 | 7 | /** A model mirroring PowSolutions entity from Ergo node REST API. 8 | * See `PowSolutions` in https://github.com/ergoplatform/ergo/blob/master/src/main/resources/api/openapi.yaml 9 | */ 10 | final case class ApiPowSolutions(pk: HexString, w: HexString, n: HexString, d: String) 11 | 12 | object ApiPowSolutions { 13 | 14 | implicit val jsonDecoder: Decoder[ApiPowSolutions] = { c: HCursor => 15 | for { 16 | pk <- c.downField("pk").as[HexString] 17 | w <- c.downField("w").as[HexString] 18 | n <- c.downField("n").as[HexString] 19 | d <- c.downField("d").as[BigInt] 20 | } yield ApiPowSolutions(pk, w, n, d.toString()) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/protocol/models/ApiSpendingProof.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.protocol.models 2 | 3 | import cats.instances.either._ 4 | import cats.syntax.option._ 5 | import io.circe.refined._ 6 | import io.circe.{Decoder, DecodingFailure, HCursor, Json} 7 | import org.ergoplatform.explorer.HexString 8 | 9 | /** A model mirroring SpendingProof entity from Ergo node REST API. 10 | * See `SpendingProof` in https://github.com/ergoplatform/ergo/blob/master/src/main/resources/api/openapi.yaml 11 | */ 12 | final case class ApiSpendingProof(proofBytes: Option[HexString], extension: Json) 13 | 14 | object ApiSpendingProof { 15 | 16 | implicit val decoder: Decoder[ApiSpendingProof] = { c: HCursor => 17 | for { 18 | // here decoding of refined type field value has to be handled manually as node API 19 | // may return an empty string (instead of `null`) which fails the refinement. 20 | proofBytes <- c.downField("proofBytes").as[String].flatMap { s => 21 | // todo: Simplify when node API is improved. 22 | HexString.fromString[Either[Throwable, *]](s) match { 23 | case Left(_) => Right[DecodingFailure, Option[HexString]](none) 24 | case r @ Right(_) => 25 | r.asInstanceOf[Decoder.Result[HexString]].map(_.some) 26 | } 27 | } 28 | extension <- c.downField("extension").as[Json] 29 | } yield ApiSpendingProof(proofBytes, extension) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/protocol/models/ApiTransaction.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.protocol.models 2 | 3 | import cats.data.NonEmptyList 4 | import derevo.circe.decoder 5 | import derevo.derive 6 | import org.ergoplatform.explorer.TxId 7 | 8 | /** A model mirroring ErgoTransaction entity from Ergo node REST API. 9 | * See `ErgoTransaction` in https://github.com/ergoplatform/ergo/blob/master/src/main/resources/api/openapi.yaml 10 | */ 11 | @derive(decoder) 12 | final case class ApiTransaction( 13 | id: TxId, 14 | inputs: NonEmptyList[ApiInput], 15 | dataInputs: List[ApiDataInput], 16 | outputs: NonEmptyList[ApiOutput], 17 | size: Int 18 | ) 19 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/protocol/models/ExpandedRegister.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.protocol.models 2 | 3 | import derevo.circe.{decoder, encoder} 4 | import derevo.derive 5 | import org.ergoplatform.explorer.{HexString, SigmaType} 6 | 7 | @derive(encoder, decoder) 8 | final case class ExpandedRegister( 9 | serializedValue: HexString, 10 | sigmaType: Option[SigmaType], 11 | renderedValue: Option[String] 12 | ) 13 | 14 | @derive(encoder, decoder) 15 | final case class ExpandedLegacyRegister( 16 | rawValue: HexString, 17 | valueType: String, 18 | decodedValue: String 19 | ) 20 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/protocol/models/RegisterValue.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.protocol.models 2 | 3 | import org.ergoplatform.explorer.SigmaType 4 | 5 | final case class RegisterValue(sigmaType: SigmaType, value: String) 6 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/protocol/models/TokenPropsEip4.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.protocol.models 2 | 3 | final case class TokenPropsEip4( 4 | name: String, 5 | description: String, 6 | decimals: Int 7 | ) 8 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/protocol/registers.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.protocol 2 | 3 | import io.circe.Json 4 | import io.circe.syntax._ 5 | import org.ergoplatform.explorer.protocol.models.{ExpandedLegacyRegister, ExpandedRegister, RegisterValue} 6 | import org.ergoplatform.explorer.{HexString, RegisterId} 7 | 8 | import scala.util.Try 9 | 10 | object registers { 11 | 12 | /** Expand registers into `register_id -> expanded_register` form. 13 | */ 14 | @inline def expand(registers: Map[RegisterId, HexString]): Map[RegisterId, ExpandedRegister] = { 15 | val expanded = 16 | for { 17 | (idSig, serializedValue) <- registers.toList 18 | rv = RegistersParser[Try].parseAny(serializedValue).toOption 19 | } yield idSig -> ExpandedRegister(serializedValue, rv.map(_.sigmaType), rv.map(_.value)) 20 | expanded.toMap 21 | } 22 | 23 | /** Convolve registers into `register_id -> raw_value` form. 24 | */ 25 | @inline def convolveJson(expanded: Json): Json = 26 | expanded 27 | .as[Map[RegisterId, ExpandedRegister]] 28 | .map(_.mapValues(_.serializedValue).asJson) 29 | .fold( 30 | _ => 31 | expanded 32 | .as[Map[RegisterId, ExpandedLegacyRegister]] 33 | .map(_.mapValues(_.rawValue).asJson) 34 | .fold(_ => expanded, identity), 35 | identity 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/services/NodesPool.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.services 2 | 3 | import cats.Monad 4 | import cats.data.NonEmptyList 5 | import cats.effect.Sync 6 | import cats.effect.concurrent.Ref 7 | import org.ergoplatform.explorer.UrlString 8 | import tofu.syntax.monadic._ 9 | 10 | trait NodesPool[F[_]] { 11 | 12 | def getAll: F[NonEmptyList[UrlString]] 13 | 14 | def getBest: F[UrlString] 15 | 16 | def setBest(best: UrlString): F[Unit] 17 | 18 | def rotate: F[Unit] 19 | } 20 | 21 | object NodesPool { 22 | 23 | def apply[F[_]: Sync](nodes: NonEmptyList[UrlString]): F[NodesPool[F]] = 24 | Ref.of(nodes).map(new Live(_)) 25 | 26 | final class Live[F[_]: Monad](poolRef: Ref[F, NonEmptyList[UrlString]]) extends NodesPool[F] { 27 | 28 | def getAll: F[NonEmptyList[UrlString]] = poolRef.get 29 | 30 | def getBest: F[UrlString] = poolRef.get map (_.head) 31 | 32 | def setBest(best: UrlString): F[Unit] = 33 | poolRef.update(p0 => NonEmptyList.of(best, p0.filterNot(_ == best): _*)) 34 | 35 | def rotate: F[Unit] = 36 | poolRef.update(p0 => NonEmptyList.ofInitLast(p0.tail, p0.head)) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/services/TxResponse.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.services 2 | 3 | import derevo.circe.decoder 4 | import derevo.derive 5 | import org.ergoplatform.explorer.TxId 6 | 7 | /** Node txId response model. 8 | */ 9 | @derive(decoder) 10 | final case class TxResponse(id: TxId) 11 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/settings/DbSettings.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.settings 2 | 3 | import cats.effect.Sync 4 | import pureconfig.ConfigSource 5 | import pureconfig.module.catseffect._ 6 | import pureconfig.generic.auto._ 7 | 8 | /** Database credentials and settings. 9 | */ 10 | final case class DbSettings(url: String, user: String, pass: String, cpSize: Int) 11 | 12 | object DbSettings { 13 | 14 | def load[F[_]: Sync](pathOpt: Option[String]): F[DbSettings] = 15 | pathOpt 16 | .map(ConfigSource.file) 17 | .getOrElse(ConfigSource.default) 18 | .loadF[F, DbSettings] 19 | } 20 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/settings/NetworkSettings.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.settings 2 | 3 | import cats.data.NonEmptyList 4 | import org.ergoplatform.explorer.UrlString 5 | 6 | final case class NetworkSettings(masterNodes: NonEmptyList[UrlString], selfCheckIntervalRequests: Int) 7 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/settings/ProtocolSettings.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.settings 2 | 3 | import eu.timepit.refined.api.Refined 4 | import eu.timepit.refined.string._ 5 | import org.ergoplatform.ErgoAddressEncoder 6 | import org.ergoplatform.explorer.Address 7 | import org.ergoplatform.explorer.protocol.{Emission, ReemissionSettings} 8 | import org.ergoplatform.mining.emission.EmissionRules 9 | import org.ergoplatform.settings.MonetarySettings 10 | 11 | final case class ProtocolSettings( 12 | networkPrefix: String Refined ValidByte, 13 | genesisAddress: Address, 14 | monetary: MonetarySettings 15 | ) { 16 | 17 | val emission = new Emission(monetary, ReemissionSettings()) 18 | 19 | val addressEncoder: ErgoAddressEncoder = 20 | ErgoAddressEncoder(networkPrefix.value.toByte) 21 | } 22 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/settings/RedisSettings.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.settings 2 | 3 | final case class RedisSettings(url: String) 4 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/settings/SettingCompanion.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.settings 2 | 3 | import cats.effect.Sync 4 | import pureconfig.module.catseffect._ 5 | import pureconfig.{ConfigReader, ConfigSource} 6 | 7 | import scala.reflect.ClassTag 8 | 9 | trait SettingCompanion[T] { 10 | 11 | def load[F[_]: Sync](pathOpt: Option[String])(implicit 12 | r: ConfigReader[T], 13 | ct: ClassTag[T] 14 | ): F[T] = 15 | pathOpt 16 | .map(ConfigSource.file(_).withFallback(ConfigSource.default)) 17 | .getOrElse(ConfigSource.default) 18 | .loadF[F, T] 19 | } 20 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/settings/UtxCacheSettings.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.settings 2 | 3 | import scala.concurrent.duration.FiniteDuration 4 | 5 | final case class UtxCacheSettings(transactionTtl: FiniteDuration) 6 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/settings/pureConfigInstances.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.settings 2 | 3 | import cats.syntax.either._ 4 | import cats.syntax.list._ 5 | import cats.data.NonEmptyList 6 | import eu.timepit.refined.api.{Refined, Validate} 7 | import eu.timepit.refined._ 8 | import pureconfig.ConfigReader 9 | import pureconfig.error.CannotConvert 10 | 11 | object pureConfigInstances { 12 | 13 | implicit def configReaderForRefined[A: ConfigReader, P]( 14 | implicit v: Validate[A, P] 15 | ): ConfigReader[A Refined P] = 16 | ConfigReader[A].emap { a => 17 | refineV[P](a).leftMap(r => CannotConvert(a.toString, s"Refined", r)) 18 | } 19 | 20 | implicit def nelReader[A: ConfigReader]: ConfigReader[NonEmptyList[A]] = 21 | implicitly[ConfigReader[List[A]]].emap { list => 22 | list.toNel.toRight(CannotConvert(list.toString, s"NonEmptyList", "List is empty")) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/syntax/StreamEffectSyntax.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.syntax 2 | 3 | trait StreamEffectSyntax { 4 | 5 | implicit final def toStreamEffectOps[F[_], A](fa: F[A]): StreamEffectOps[F, A] = 6 | new StreamEffectOps(fa) 7 | } 8 | 9 | final class StreamEffectOps[F[_], A](fa: F[A]) { 10 | 11 | def asStream: fs2.Stream[F, A] = fs2.Stream.eval(fa) 12 | } 13 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/syntax/StreamOptionOps.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.syntax 2 | 3 | import fs2.Stream 4 | 5 | trait StreamOptionSyntax { 6 | 7 | implicit final def toStreamOptionOps[A](oa: Option[A]): StreamOptionOps[A] = 8 | new StreamOptionOps[A](oa) 9 | } 10 | 11 | final class StreamOptionOps[A](oa: Option[A]) { 12 | 13 | def toStream: fs2.Stream[fs2.Pure, A] = 14 | oa.fold[fs2.Stream[fs2.Pure, A]](Stream.empty)(Stream.emit) 15 | } 16 | -------------------------------------------------------------------------------- /modules/explorer-core/src/main/scala/org/ergoplatform/explorer/syntax/package.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer 2 | 3 | package object syntax { 4 | 5 | object stream extends StreamEffectSyntax with StreamOptionSyntax 6 | } 7 | -------------------------------------------------------------------------------- /modules/explorer-core/src/test/scala/org/ergoplatform/explorer/CatsInstances.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer 2 | 3 | import cats.effect.{ContextShift, IO, Timer} 4 | import doobie.util.ExecutionContexts 5 | 6 | trait CatsInstances { 7 | 8 | implicit val cs: ContextShift[IO] = IO.contextShift(ExecutionContexts.synchronous) 9 | 10 | implicit val ioTimer: Timer[IO] = IO.timer(ExecutionContexts.synchronous) 11 | } 12 | -------------------------------------------------------------------------------- /modules/explorer-core/src/test/scala/org/ergoplatform/explorer/MainNetConfiguration.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer 2 | 3 | import cats.data.NonEmptyList 4 | import cats.instances.try_._ 5 | import eu.timepit.refined.api.Refined 6 | import eu.timepit.refined.refineMV 7 | import eu.timepit.refined.string.ValidByte 8 | import org.ergoplatform.explorer.settings.{DbSettings, ProtocolSettings} 9 | import org.ergoplatform.settings.MonetarySettings 10 | 11 | import scala.util.Try 12 | 13 | trait MainNetConfiguration { 14 | 15 | val GenesisAddress: Address = 16 | Address.fromString[Try]( 17 | "2Z4YBkDsDvQj8BX7xiySFewjitqp2ge9c99jfes2whbtKitZTxdBYqbrVZUvZvKv6aqn9by4kp3LE1c26LCyosFnVnm6b6U1JYv" + 18 | "WpYmL2ZnixJbXLjWAWuBThV1D6dLpqZJYQHYDznJCk49g5TUiS4q8khpag2aNmHwREV7JSsypHdHLgJT7MGaw51aJfNubyzSK" + 19 | "xZ4AJXFS27EfXwyCLzW1K6GVqwkJtCoPvrcLqmqwacAWJPkmh78nke9H4oT88XmSbRt2n9aWZjosiZCafZ4osUDxmZcc5QVEe" + 20 | "TWn8drSraY3eFKe8Mu9MSCcVU" 21 | ).get 22 | val MainNetPrefix: Refined[String, ValidByte] = refineMV[ValidByte]("16") 23 | 24 | val monetarySettings = MonetarySettings() 25 | val protocolSettings = ProtocolSettings(MainNetPrefix, GenesisAddress, monetarySettings) 26 | 27 | val mainnetNodes = 28 | NonEmptyList.one(UrlString.fromString[Try]("http://139.59.29.87:9053").get) 29 | val dbSettings = DbSettings("", "", "", 1) 30 | } 31 | -------------------------------------------------------------------------------- /modules/explorer-core/src/test/scala/org/ergoplatform/explorer/SigmaTypeSpec.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer 2 | 3 | import cats.data.NonEmptyList 4 | import org.ergoplatform.explorer.SigmaType.SimpleKindSigmaType._ 5 | import org.ergoplatform.explorer.SigmaType.{SCollection, STupleN} 6 | 7 | import org.scalatest._ 8 | import flatspec._ 9 | import matchers._ 10 | 11 | class SigmaTypeSpec extends AnyFlatSpec with should.Matchers { 12 | 13 | "SigmaType parse method" should "parse primitive type signature" in { 14 | val sig = "SInt" 15 | SigmaType.parse(sig) should be(Some(SInt)) 16 | } 17 | 18 | it should "parse HKT signature" in { 19 | val sig = "Coll[SInt]" 20 | SigmaType.parse(sig) should be(Some(SCollection(SInt))) 21 | } 22 | 23 | it should "parse Nested HKT signature" in { 24 | val sig = "Coll[(SInt,SLong)]" 25 | SigmaType.parse(sig) should be(Some(SCollection(STupleN(NonEmptyList.of(SInt, SLong))))) 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /modules/explorer-core/src/test/scala/org/ergoplatform/explorer/db/repositories/AdProofRepoSpec.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.repositories 2 | 3 | import cats.Functor 4 | import cats.effect.IO 5 | import doobie.free.connection.ConnectionIO 6 | import doobie.implicits._ 7 | import org.ergoplatform.explorer.testSyntax.runConnectionIO._ 8 | import org.ergoplatform.explorer.db.algebra.LiftConnectionIO 9 | import org.ergoplatform.explorer.db.{repositories, RealDbTest} 10 | 11 | import org.scalatest._ 12 | import flatspec._ 13 | import matchers._ 14 | 15 | class AdProofRepoSpec extends AnyFlatSpec with should.Matchers with RealDbTest { 16 | 17 | import org.ergoplatform.explorer.commonGenerators._ 18 | import org.ergoplatform.explorer.db.models.generators._ 19 | 20 | "AdProofRepo" should "insert AdProof & get AdProof by HeaderId" in { 21 | withLiveRepos[ConnectionIO] { (headerRepo, repo) => 22 | forSingleInstance(adProofWithHeaderGen) { case (header, proof) => 23 | headerRepo.insert(header).runWithIO 24 | repo.getByHeaderId(proof.headerId).runWithIO should be(None) 25 | repo.insert(proof).runWithIO 26 | repo.getByHeaderId(proof.headerId).runWithIO should be(Some(proof)) 27 | } 28 | } 29 | } 30 | 31 | private def withLiveRepos[D[_]: LiftConnectionIO: Functor]( 32 | body: (HeaderRepo[D, fs2.Stream], AdProofRepo[D]) => Any 33 | ): Any = 34 | body( 35 | repositories.HeaderRepo[IO, D].unsafeRunSync(), 36 | repositories.AdProofRepo[IO, D].unsafeRunSync() 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /modules/explorer-core/src/test/scala/org/ergoplatform/explorer/db/repositories/BlockExtensionRepoSpec.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.repositories 2 | 3 | import cats.effect.{IO, Sync} 4 | import doobie.free.connection.ConnectionIO 5 | import org.ergoplatform.explorer.db.algebra.LiftConnectionIO 6 | import org.ergoplatform.explorer.testSyntax.runConnectionIO._ 7 | import org.ergoplatform.explorer.db.{repositories, RealDbTest} 8 | 9 | import org.scalatest._ 10 | import flatspec._ 11 | import matchers._ 12 | 13 | class BlockExtensionRepoSpec extends AnyFlatSpec with should.Matchers with RealDbTest { 14 | 15 | import org.ergoplatform.explorer.commonGenerators._ 16 | import org.ergoplatform.explorer.db.models.generators._ 17 | 18 | "BlockExtensionRepo" should "insert/getByHeaderId" in { 19 | withLiveRepos[ConnectionIO] { (headerRepo, repo) => 20 | forSingleInstance(blockExtensionWithHeaderGen) { case (header, extension) => 21 | headerRepo.insert(header).runWithIO() 22 | repo.getByHeaderId(extension.headerId).runWithIO() should be(None) 23 | repo.insert(extension).runWithIO() 24 | repo.getByHeaderId(extension.headerId).runWithIO() should be(Some(extension)) 25 | } 26 | } 27 | } 28 | 29 | private def withLiveRepos[D[_]: LiftConnectionIO: Sync]( 30 | body: (HeaderRepo[D, fs2.Stream], BlockExtensionRepo[D]) => Any 31 | ): Any = 32 | body( 33 | repositories.HeaderRepo[IO, D].unsafeRunSync(), 34 | repositories.BlockExtensionRepo[IO, D].unsafeRunSync() 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /modules/explorer-core/src/test/scala/org/ergoplatform/explorer/db/repositories/BlockedTokenRepoSpec.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.repositories 2 | 3 | import cats.effect.{IO, Sync} 4 | import doobie.free.connection.ConnectionIO 5 | import org.ergoplatform.explorer.testSyntax.runConnectionIO._ 6 | import org.ergoplatform.explorer.db.{repositories, RealDbTest} 7 | import org.ergoplatform.explorer.db.algebra.LiftConnectionIO 8 | import org.scalatest.flatspec.AnyFlatSpec 9 | import org.scalatest.matchers.should 10 | 11 | class BlockedTokenRepoSpec extends AnyFlatSpec with should.Matchers with RealDbTest { 12 | import BlockedTokenRepoSpec._ 13 | import org.ergoplatform.explorer.commonGenerators._ 14 | import org.ergoplatform.explorer.db.models.generators._ 15 | 16 | "BlockedTokenRepo" should "insert & get blocked token" in { 17 | withBlockedTokenRepo[ConnectionIO] { repo => 18 | forSingleInstance(blockedTokenGen) { blockedToken => 19 | repo.get(blockedToken.tokenId).runWithIO() should be(None) 20 | repo.insert(blockedToken).runWithIO() 21 | repo.get(blockedToken.tokenId).runWithIO() should be(Some(blockedToken)) 22 | } 23 | } 24 | } 25 | } 26 | 27 | object BlockedTokenRepoSpec { 28 | 29 | private def withBlockedTokenRepo[D[_]: LiftConnectionIO: Sync]( 30 | body: BlockedTokenRepo[D] => Any 31 | ): Any = 32 | body(repositories.BlockedTokenRepo[IO, D].unsafeRunSync()) 33 | } 34 | -------------------------------------------------------------------------------- /modules/explorer-core/src/test/scala/org/ergoplatform/explorer/protocol/EmissionSpec.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.protocol 2 | 3 | import org.ergoplatform.settings.MonetarySettings 4 | import org.scalatest.flatspec.AnyFlatSpec 5 | import org.scalatest.matchers.should 6 | 7 | class EmissionSpec extends AnyFlatSpec with should.Matchers { 8 | val emission = new Emission(MonetarySettings(), ReemissionSettings()) 9 | "Emission.issuedCoinsAfterHeight" should "compute correct total supply" in { 10 | println((1L to 1119273L).map(h => emission.emissionAt(h)).sum) 11 | println(emission.issuedCoinsAfterHeight(8000000)) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /modules/explorer-core/src/test/scala/org/ergoplatform/explorer/services/nodeApiGenerator.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.services 2 | 3 | import org.ergoplatform.explorer.commonGenerators 4 | import org.ergoplatform.explorer.protocol.models.{ApiNodeInfo, ApiNodeInfoEpochParameters} 5 | import org.scalacheck.Gen 6 | 7 | object nodeApiGenerator { 8 | 9 | def generateNodeInfo: Gen[ApiNodeInfo] = { 10 | for { 11 | id <- commonGenerators.idGen 12 | hex <- commonGenerators.hexStringRGen 13 | } yield ApiNodeInfo( 14 | 1L, 15 | "testNode", 16 | "stateType", 17 | 1L, 18 | id, 19 | id, 20 | 1, 21 | 1, 22 | "appVersion", 23 | hex, 24 | id, 25 | 1, 26 | 1, 27 | hex, 28 | 1L, 29 | ApiNodeInfoEpochParameters( 30 | 1, 1, 1, 1, 1, 1: Byte, 1, 1, 1, 1 31 | ), 32 | isMining = false 33 | ) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /modules/explorer-core/src/test/scala/org/ergoplatform/explorer/testConstants.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer 2 | 3 | import cats.instances.try_._ 4 | 5 | import scala.util.Try 6 | 7 | object testConstants { 8 | 9 | val MainNetMinerPk: HexString = HexString 10 | .fromString[Try]( 11 | "0377d854c54490abc6c565d8e548d5fc92a6a6c2f4415ed96f0c340ece92e1ed2f" 12 | ) 13 | .get 14 | } 15 | -------------------------------------------------------------------------------- /modules/explorer-core/src/test/scala/org/ergoplatform/explorer/testSyntax/RunConnectionIOSyntax.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.testSyntax 2 | 3 | import cats.effect.IO 4 | import doobie.free.connection.ConnectionIO 5 | import doobie.implicits._ 6 | import doobie.util.transactor.Transactor 7 | 8 | trait RunConnectionIOSyntax { 9 | 10 | implicit def toRunConnectionIOOps[A](ca: ConnectionIO[A]): RunConnectionIOOps[A] = 11 | new RunConnectionIOOps[A](ca) 12 | } 13 | 14 | private[testSyntax] final class RunConnectionIOOps[A](private val ca: ConnectionIO[A]) extends AnyVal { 15 | 16 | def runWithIO()(implicit xa: Transactor[IO]): A = 17 | ca.transact(xa).unsafeRunSync() 18 | } 19 | -------------------------------------------------------------------------------- /modules/explorer-core/src/test/scala/org/ergoplatform/explorer/testSyntax/package.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer 2 | 3 | package object testSyntax { 4 | 5 | object runConnectionIO extends RunConnectionIOSyntax 6 | } 7 | -------------------------------------------------------------------------------- /modules/migrator/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-jre-slim as builder 2 | RUN apt-get update && \ 3 | apt-get install -y --no-install-recommends apt-transport-https apt-utils bc dirmngr gnupg && \ 4 | echo "deb https://repo.scala-sbt.org/scalasbt/debian /" | tee -a /etc/apt/sources.list.d/sbt.list && \ 5 | apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2EE0EA64E40A89B84B2DF73499E82A75642AC823 && \ 6 | apt-get update && \ 7 | apt-get upgrade -y && \ 8 | apt-get install -y --no-install-recommends sbt 9 | COPY . /explorer-backend 10 | WORKDIR /explorer-backend 11 | RUN sbt migrator/assembly 12 | RUN mv `find . -name Migrator-assembly-*.jar` /migrator.jar 13 | CMD ["/usr/bin/java", "-jar", "/migrator.jar"] 14 | 15 | FROM openjdk:8-jre-slim 16 | COPY --from=builder /migrator.jar /migrator.jar 17 | ENTRYPOINT java -jar /migrator.jar $0 $1 -------------------------------------------------------------------------------- /modules/migrator/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | db.url = "jdbc:postgresql://localhost:5432/explorer" 2 | db.user = "postgres" 3 | db.pass = "1234" 4 | db.cp-size = 8 5 | 6 | processing.batch-size = 2000 7 | processing.interval = 0.5s 8 | processing.offset = 0 9 | processing.update-schema = false 10 | 11 | network-prefix = 0 12 | -------------------------------------------------------------------------------- /modules/migrator/src/main/scala/org/ergoplatform/explorer/migration/configs/MigrationConfig.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.migration.configs 2 | 3 | import eu.timepit.refined.api.Refined 4 | import eu.timepit.refined.string.ValidByte 5 | import org.ergoplatform.ErgoAddressEncoder 6 | import org.ergoplatform.explorer.settings.{DbSettings, SettingCompanion} 7 | 8 | final case class MigrationConfig( 9 | db: DbSettings, 10 | processing: ProcessingConfig, 11 | networkPrefix: String Refined ValidByte 12 | ) { 13 | 14 | val addressEncoder: ErgoAddressEncoder = 15 | ErgoAddressEncoder(networkPrefix.value.toByte) 16 | } 17 | 18 | object MigrationConfig extends SettingCompanion[MigrationConfig] 19 | -------------------------------------------------------------------------------- /modules/migrator/src/main/scala/org/ergoplatform/explorer/migration/configs/ProcessingConfig.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.migration.configs 2 | 3 | import scala.concurrent.duration.FiniteDuration 4 | 5 | final case class ProcessingConfig(batchSize: Int, interval: FiniteDuration, offset: Int, updateSchema: Boolean) 6 | -------------------------------------------------------------------------------- /modules/migrator/src/main/scala/org/ergoplatform/explorer/migration/migrations/BlockedTokenMigration.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.migration.migrations 2 | 3 | import cats.Parallel 4 | import cats.effect.{IO, Timer} 5 | import doobie.implicits._ 6 | import doobie.util.transactor.Transactor 7 | import doobie.{ConnectionIO, Update} 8 | import io.chrisdavenport.log4cats.Logger 9 | import io.chrisdavenport.log4cats.slf4j.Slf4jLogger 10 | import org.ergoplatform.explorer.migration.configs.ProcessingConfig 11 | import tofu.syntax.monadic._ 12 | 13 | final class BlockedTokenMigration( 14 | conf: ProcessingConfig, 15 | xa: Transactor[IO], 16 | log: Logger[IO] 17 | ) { 18 | def run: IO[Unit] = updateSchema() 19 | 20 | def updateSchema(): IO[Unit] = { 21 | val txn = recreateBlockedTokensTable 22 | log.info("[updating DB schema]") >> txn.transact(xa) 23 | } 24 | 25 | def recreateBlockedTokensTable: ConnectionIO[Unit] = 26 | sql""" 27 | |create table if not exists blocked_tokens 28 | |( 29 | | token_id VARCHAR(64) PRIMARY KEY, 30 | | token_name VARCHAR NOT NULL 31 | |) 32 | |""".stripMargin.update.run.void 33 | } 34 | 35 | object BlockedTokenMigration { 36 | 37 | def apply( 38 | conf: ProcessingConfig, 39 | xa: Transactor[IO] 40 | )(implicit timer: Timer[IO], par: Parallel[IO]): IO[Unit] = 41 | for { 42 | logger <- Slf4jLogger.create[IO] 43 | _ <- new BlockedTokenMigration(conf, xa, logger).run 44 | } yield () 45 | } 46 | -------------------------------------------------------------------------------- /modules/migrator/src/main/scala/org/ergoplatform/explorer/migration/migrations/GenuineTokenMigration.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.migration.migrations 2 | 3 | import cats.Parallel 4 | import cats.effect.{IO, Timer} 5 | import doobie.implicits._ 6 | import doobie.util.transactor.Transactor 7 | import doobie.{ConnectionIO, Update} 8 | import io.chrisdavenport.log4cats.Logger 9 | import io.chrisdavenport.log4cats.slf4j.Slf4jLogger 10 | import org.ergoplatform.explorer.migration.configs.ProcessingConfig 11 | import tofu.syntax.monadic._ 12 | 13 | final class GenuineTokenMigration( 14 | conf: ProcessingConfig, 15 | xa: Transactor[IO], 16 | log: Logger[IO] 17 | ) { 18 | def run: IO[Unit] = updateSchema() 19 | 20 | def updateSchema(): IO[Unit] = { 21 | val txn = recreateGenuineTokensTable 22 | log.info("[updating DB schema]") >> txn.transact(xa) 23 | } 24 | 25 | def recreateGenuineTokensTable: ConnectionIO[Unit] = 26 | sql""" 27 | |create table if not exists genuine_tokens 28 | |( 29 | | token_id VARCHAR(64) PRIMARY KEY, 30 | | token_name VARCHAR NOT NULL, 31 | | unique_name BOOLEAN NOT NULL, 32 | | issuer VARCHAR 33 | |) 34 | |""".stripMargin.update.run.void 35 | } 36 | 37 | object GenuineTokenMigration { 38 | 39 | def apply( 40 | conf: ProcessingConfig, 41 | xa: Transactor[IO] 42 | )(implicit timer: Timer[IO], par: Parallel[IO]): IO[Unit] = 43 | for { 44 | logger <- Slf4jLogger.create[IO] 45 | _ <- new GenuineTokenMigration(conf, xa, logger).run 46 | } yield () 47 | } 48 | -------------------------------------------------------------------------------- /modules/utx-broadcaster/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-jre-slim as builder 2 | RUN apt-get update && \ 3 | apt-get install -y --no-install-recommends apt-transport-https apt-utils bc dirmngr gnupg && \ 4 | echo "deb https://repo.scala-sbt.org/scalasbt/debian /" | tee -a /etc/apt/sources.list.d/sbt.list && \ 5 | apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2EE0EA64E40A89B84B2DF73499E82A75642AC823 && \ 6 | apt-get update && \ 7 | apt-get upgrade -y && \ 8 | apt-get install -y --no-install-recommends sbt 9 | COPY . /explorer-backend 10 | WORKDIR /explorer-backend 11 | RUN sbt utx-broadcaster/assembly 12 | RUN mv `find . -name UtxBroadcaster-assembly-*.jar` /utx-broadcaster.jar 13 | CMD ["/usr/bin/java", "-jar", "/utx-broadcaster.jar"] 14 | 15 | FROM openjdk:8-jre-slim 16 | COPY --from=builder /utx-broadcaster.jar /utx-broadcaster.jar 17 | ENTRYPOINT java -jar /utx-broadcaster.jar $0 -------------------------------------------------------------------------------- /modules/utx-broadcaster/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | tick-interval = 15s 2 | network { 3 | master-nodes = ["http://213.239.193.208:9053"] 4 | self-check-interval-requests = 8 5 | } 6 | redis.url = "redis://localhost:6379" 7 | utx-cache.transaction-ttl = 48h -------------------------------------------------------------------------------- /modules/utx-broadcaster/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %white(%d{HH:mm:ss.SSS}) %highlight(%-5level) %cyan(%logger{50}) - %msg %n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /modules/utx-broadcaster/src/main/scala/org/ergoplatform/explorer/broadcaster/Application.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.broadcaster 2 | 3 | import cats.effect.{ExitCode, Resource} 4 | import cats.syntax.functor._ 5 | import io.chrisdavenport.log4cats.slf4j.Slf4jLogger 6 | import monix.eval.{Task, TaskApp} 7 | import org.ergoplatform.explorer.cache.Redis 8 | import org.ergoplatform.explorer.services.ErgoNetwork 9 | import org.ergoplatform.explorer.settings.UtxBroadcasterSettings 10 | import org.ergoplatform.explorer.settings.pureConfigInstances._ 11 | import org.http4s.blaze.client.BlazeClientBuilder 12 | import pureconfig.generic.auto._ 13 | 14 | import scala.concurrent.ExecutionContext.global 15 | 16 | /** A service broadcasting new transactions to the network. 17 | */ 18 | object Application extends TaskApp { 19 | 20 | def run(args: List[String]): Task[ExitCode] = 21 | resources(args.headOption).use { 22 | case (logger, settings, client, redis) => 23 | logger.info("Starting UtxBroadcaster service ..") >> 24 | ErgoNetwork[Task](client, settings.network) 25 | .flatMap { ns => 26 | UtxBroadcaster[Task](settings, ns, redis) 27 | .flatMap(_.run.compile.drain) 28 | .as(ExitCode.Success) 29 | } 30 | .guarantee(logger.info("Stopping UtxBroadcaster service ..")) 31 | } 32 | 33 | private def resources(configPathOpt: Option[String]) = 34 | for { 35 | logger <- Resource.eval(Slf4jLogger.create) 36 | settings <- Resource.eval(UtxBroadcasterSettings.load(configPathOpt)) 37 | client <- BlazeClientBuilder[Task](global).resource 38 | redis <- Redis[Task](settings.redis) 39 | } yield (logger, settings, client, redis) 40 | } 41 | -------------------------------------------------------------------------------- /modules/utx-broadcaster/src/main/scala/org/ergoplatform/explorer/settings/UtxBroadcasterSettings.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.settings 2 | 3 | import scala.concurrent.duration.FiniteDuration 4 | 5 | final case class UtxBroadcasterSettings( 6 | network: NetworkSettings, 7 | tickInterval: FiniteDuration, 8 | redis: RedisSettings, 9 | utxCache: UtxCacheSettings 10 | ) 11 | 12 | object UtxBroadcasterSettings extends SettingCompanion[UtxBroadcasterSettings] 13 | -------------------------------------------------------------------------------- /modules/utx-tracker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-jre-slim as builder 2 | RUN apt-get update && \ 3 | apt-get install -y --no-install-recommends apt-transport-https apt-utils bc dirmngr gnupg && \ 4 | echo "deb https://repo.scala-sbt.org/scalasbt/debian /" | tee -a /etc/apt/sources.list.d/sbt.list && \ 5 | apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2EE0EA64E40A89B84B2DF73499E82A75642AC823 && \ 6 | apt-get update && \ 7 | apt-get upgrade -y && \ 8 | apt-get install -y --no-install-recommends sbt 9 | COPY . /explorer-backend 10 | WORKDIR /explorer-backend 11 | RUN sbt utx-tracker/assembly 12 | RUN mv `find . -name UtxTracker-assembly-*.jar` /utx-tracker.jar 13 | CMD ["/usr/bin/java", "-jar", "/utx-tracker.jar"] 14 | 15 | FROM openjdk:8-jre-slim 16 | COPY --from=builder /utx-tracker.jar /utx-tracker.jar 17 | ENTRYPOINT java -jar /utx-tracker.jar $0 -------------------------------------------------------------------------------- /modules/utx-tracker/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | poll-interval = 15s 2 | network { 3 | master-nodes = ["http://213.239.193.208:9053"] 4 | self-check-interval-requests = 8 5 | } 6 | 7 | db.url = "jdbc:postgresql://localhost:5432/explorer" 8 | db.user = "postgres" 9 | db.pass = "1234" 10 | db.cp-size = 8 11 | 12 | protocol { 13 | network-prefix = 0 14 | genesis-address = "2Z4YBkDsDvQj8BX7xiySFewjitqp2ge9c99jfes2whbtKitZTxdBYqbrVZUvZvKv6aqn9by4kp3LE1c26LCyosFnVnm6b6U1JYvWpYmL2ZnixJbXLjWAWuBThV1D6dLpqZJYQHYDznJCk49g5TUiS4q8khpag2aNmHwREV7JSsypHdHLgJT7MGaw51aJfNubyzSKxZ4AJXFS27EfXwyCLzW1K6GVqwkJtCoPvrcLqmqwacAWJPkmh78nke9H4oT88XmSbRt2n9aWZjosiZCafZ4osUDxmZcc5QVEeTWn8drSraY3eFKe8Mu9MSCcVU" 15 | 16 | # Monetary config for chain 17 | monetary { 18 | # number of blocks reward won't change (2 years) 19 | fixed-rate-period = 525600 20 | # number of coins issued every block during fixedRatePeriod (75 Ergo) 21 | fixed-rate = 75000000000 22 | # Part of coins issued, that is going to the foundation during fixedRatePeriod (7.5 Ergo) 23 | founders-initial-reward = 7500000000 24 | # number of blocks between reward reduction (90 days) 25 | epoch-length = 64800 26 | # number of coins reward decrease every epochs (3 Ergo) 27 | one-epoch-reduction = 3000000000 28 | # delay between the block mined and a time, when the reward can be spend. ~ 1 day. 29 | miner-reward-delay = 720 30 | } 31 | } -------------------------------------------------------------------------------- /modules/utx-tracker/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %white(%d{HH:mm:ss.SSS}) %highlight(%-5level) %cyan(%logger{50}) - %msg %n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /modules/utx-tracker/src/main/scala/org/ergoplatform/explorer/db/models/aggregates/FlatUTransaction.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.db.models.aggregates 2 | 3 | import org.ergoplatform.explorer.db.models._ 4 | 5 | final case class FlatUTransaction( 6 | tx: UTransaction, 7 | inputs: List[UInput], 8 | dataInputs: List[UDataInput], 9 | outputs: List[UOutput], 10 | assets: List[UAsset] 11 | ) 12 | -------------------------------------------------------------------------------- /modules/utx-tracker/src/main/scala/org/ergoplatform/explorer/settings/UtxTrackerSettings.scala: -------------------------------------------------------------------------------- 1 | package org.ergoplatform.explorer.settings 2 | 3 | import scala.concurrent.duration.FiniteDuration 4 | 5 | final case class UtxTrackerSettings( 6 | pollInterval: FiniteDuration, 7 | network: NetworkSettings, 8 | db: DbSettings, 9 | protocol: ProtocolSettings 10 | ) 11 | 12 | object UtxTrackerSettings extends SettingCompanion[UtxTrackerSettings] 13 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.5.5 -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.10") 2 | -------------------------------------------------------------------------------- /project/utils.scala: -------------------------------------------------------------------------------- 1 | import sbt.Keys.{moduleName, name} 2 | import sbt.{file, Project} 3 | 4 | object utils { 5 | 6 | def mkModule(id: String, description: String): Project = 7 | Project(id, file(s"modules/$id")) 8 | .settings( 9 | moduleName := id, 10 | name := description 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /setup/config/chain-grabber.application.conf: -------------------------------------------------------------------------------- 1 | poll-interval = 15s 2 | write-orphans = true 3 | 4 | network { 5 | master-nodes = ["http://195.201.82.115:9052"] 6 | self-check-interval-requests = 8 7 | } 8 | 9 | db.url = "--" 10 | db.user = "--" 11 | db.pass = "--" 12 | db.cp-size = 8 13 | 14 | protocol { 15 | network-prefix = 16 16 | genesis-address = "AfYgQf5PappexKq8Vpig4vwEuZLjrq7gV97BWBVcKymTYqRzCoJLE9cDBpGHvtAAkAgQf8Yyv7NQUjSphKSjYxk3dB3W8VXzHzz5MuCcNbqqKHnMDZAa6dbHH1uyMScq5rXPLFD5P8MWkD5FGE6RbHKrKjANcr6QZHcBpppdjh9r5nra4c7dsCgULFZfWYTaYqHpx646BUHhhp8jDCHzzF33G8XfgKYo93ABqmdqagbYRzrqCgPHv5kxRmFt7Y99z26VQTgXoEmXJ2aRu6LoB59rKN47JxWGos27D79kKzJRiyYNEVzXU8MYCxtAwV" 17 | 18 | # Monetary config for chain 19 | monetary { 20 | # number of blocks reward won't change (2 years) 21 | fixed-rate-period = 525600 22 | # number of coins issued every block during fixedRatePeriod (75 Ergo) 23 | fixed-rate = 75000000000 24 | # Part of coins issued, that is going to the foundation during fixedRatePeriod (7.5 Ergo) 25 | founders-initial-reward = 7500000000 26 | # number of blocks between reward reduction (90 days) 27 | epoch-length = 64800 28 | # number of coins reward decrease every epochs (3 Ergo) 29 | one-epoch-reduction = 3000000000 30 | # delay between the block mined and a time, when the reward can be spend. ~ 1 day. 31 | miner-reward-delay = 720 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /setup/config/config.md: -------------------------------------------------------------------------------- 1 | ## Ergo TestNet Config 2 | 3 | TestNet configuration for explorer backend modules 4 | 5 | ```app.conf 6 | #TestNet Master Node 7 | network.master-nodes = ["http://195.201.82.115:9052"] 8 | 9 | #TestNet DB 10 | To be granted access to TestNet DB ask @ilya oskin on the community disocrd :) 11 | 12 | #TestNet Protocol 13 | protocol.network-prefix = 16 14 | protocol.genesis-address = "AfYgQf5PappexKq8Vpig4vwEuZLjrq7gV97BWBVcKymTYqRzCoJLE9cDBpGHvtAAkAgQf8Yyv7NQUjSphKSjYxk3dB3W8VXzHzz5MuCcNbqqKHnMDZAa6dbHH1uyMScq5rXPLFD5P8MWkD5FGE6RbHKrKjANcr6QZHcBpppdjh9r5nra4c7dsCgULFZfWYTaYqHpx646BUHhhp8jDCHzzF33G8XfgKYo93ABqmdqagbYRzrqCgPHv5kxRmFt7Y99z26VQTgXoEmXJ2aRu6LoB59rKN47JxWGos27D79kKzJRiyYNEVzXU8MYCxtAwV" 15 | ``` -------------------------------------------------------------------------------- /setup/config/explorer-api.application.conf: -------------------------------------------------------------------------------- 1 | http.port = 8080 2 | http.host = "0.0.0.0" 3 | 4 | db.url = "--" 5 | db.user = "--" 6 | db.pass = "--" 7 | db.cp-size = 8 8 | 9 | redis.url = "redis://localhost:6379" 10 | 11 | utx-cache.transaction-ttl = 48h 12 | 13 | requests.max-entities-per-request = 500 14 | requests.max-entities-per-heavy-request = 100 15 | requests.max-blocks-per-request = 1536 16 | service.chunk-size = 100 17 | 18 | protocol { 19 | network-prefix = 16 20 | genesis-address = "AfYgQf5PappexKq8Vpig4vwEuZLjrq7gV97BWBVcKymTYqRzCoJLE9cDBpGHvtAAkAgQf8Yyv7NQUjSphKSjYxk3dB3W8VXzHzz5MuCcNbqqKHnMDZAa6dbHH1uyMScq5rXPLFD5P8MWkD5FGE6RbHKrKjANcr6QZHcBpppdjh9r5nra4c7dsCgULFZfWYTaYqHpx646BUHhhp8jDCHzzF33G8XfgKYo93ABqmdqagbYRzrqCgPHv5kxRmFt7Y99z26VQTgXoEmXJ2aRu6LoB59rKN47JxWGos27D79kKzJRiyYNEVzXU8MYCxtAwV" 21 | # Monetary config for chain 22 | monetary { 23 | # number of blocks reward won't change (2 years) 24 | fixed-rate-period = 525600 25 | # number of coins issued every block during fixedRatePeriod (75 Ergo) 26 | fixed-rate = 75000000000 27 | # Part of coins issued, that is going to the foundation during fixedRatePeriod (7.5 Ergo) 28 | founders-initial-reward = 7500000000 29 | # number of blocks between reward reduction (90 days) 30 | epoch-length = 64800 31 | # number of coins reward decrease every epochs (3 Ergo) 32 | one-epoch-reduction = 3000000000 33 | # delay between the block mined and a time, when the reward can be spend. ~ 1 day. 34 | miner-reward-delay = 720 35 | } 36 | } -------------------------------------------------------------------------------- /setup/config/utx-broadcaster.application.conf: -------------------------------------------------------------------------------- 1 | tick-interval = 15s 2 | network { 3 | master-nodes = ["http://195.201.82.115:9052"] 4 | self-check-interval-requests = 8 5 | } 6 | redis.url = "redis://localhost:6379" 7 | utx-cache.transaction-ttl = 48h -------------------------------------------------------------------------------- /setup/config/utx-tracker.application.conf: -------------------------------------------------------------------------------- 1 | poll-interval = 15s 2 | network { 3 | master-nodes = ["http://195.201.82.115:9052"] 4 | self-check-interval-requests = 8 5 | } 6 | 7 | db.url = "--" 8 | db.user = "--" 9 | db.pass = "--" 10 | db.cp-size = 8 11 | 12 | protocol { 13 | network-prefix = 16 14 | genesis-address = "AfYgQf5PappexKq8Vpig4vwEuZLjrq7gV97BWBVcKymTYqRzCoJLE9cDBpGHvtAAkAgQf8Yyv7NQUjSphKSjYxk3dB3W8VXzHzz5MuCcNbqqKHnMDZAa6dbHH1uyMScq5rXPLFD5P8MWkD5FGE6RbHKrKjANcr6QZHcBpppdjh9r5nra4c7dsCgULFZfWYTaYqHpx646BUHhhp8jDCHzzF33G8XfgKYo93ABqmdqagbYRzrqCgPHv5kxRmFt7Y99z26VQTgXoEmXJ2aRu6LoB59rKN47JxWGos27D79kKzJRiyYNEVzXU8MYCxtAwV" 15 | 16 | # Monetary config for chain 17 | monetary { 18 | # number of blocks reward won't change (2 years) 19 | fixed-rate-period = 525600 20 | # number of coins issued every block during fixedRatePeriod (75 Ergo) 21 | fixed-rate = 75000000000 22 | # Part of coins issued, that is going to the foundation during fixedRatePeriod (7.5 Ergo) 23 | founders-initial-reward = 7500000000 24 | # number of blocks between reward reduction (90 days) 25 | epoch-length = 64800 26 | # number of coins reward decrease every epochs (3 Ergo) 27 | one-epoch-reduction = 3000000000 28 | # delay between the block mined and a time, when the reward can be spend. ~ 1 day. 29 | miner-reward-delay = 720 30 | } 31 | } -------------------------------------------------------------------------------- /setup/docker-clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker rm -f $(docker ps -aq) -------------------------------------------------------------------------------- /setup/psql.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "================== Help for psql =========================" 3 | echo "\\dt : Describe the current database" 4 | echo "\\d [table] : Describe a table" 5 | echo "\\c : Connect to a database" 6 | echo "\\h : help with SQL commands" 7 | echo "\\? : help with psql commands" 8 | echo "\\q : quit" 9 | echo "==================================================================" 10 | docker exec -it explorer-postgres psql -U postgres -d explorer 11 | -------------------------------------------------------------------------------- /setup/setup.md: -------------------------------------------------------------------------------- 1 | # Ergo Blockchain Explorer 2 | 3 | ## Quick Start 4 | 5 | 1. Install [Docker](https://www.docker.com/docker-mac) 6 | 2. In project Directory navigate to `/setup` 7 | 3. Run `docker-compose build` to setup project modules, redis & postgres database 8 | 4. Run `docker-compose up` to start all modules 9 | 10 | ## Notes 11 | 1. To start a single module run `docker-compose up `, start module dependencies with same command 12 | 2. The above configuration will get your local ergo explorer application pointing at `Mainnet` 13 | 3. To get started on `TestNet` edit configuration files in `/src/main/resources/application.conf` to match testnet config in `setup/config/config.md` -------------------------------------------------------------------------------- /setup/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "staring services :)" 4 | docker compose up --------------------------------------------------------------------------------