├── .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
--------------------------------------------------------------------------------