Showing preview only (1,041K chars total). Download the full file or copy to clipboard to get everything.
Repository: leisuremeta/leisuremeta-chain
Branch: main
Commit: 5a2cd546471a
Files: 290
Total size: 924.1 KB
Directory structure:
gitextract_d4szp668/
├── .gitignore
├── .jvmopts
├── .scalafix.conf
├── .scalafmt.conf
├── README.md
├── build.sbt
├── docs/
│ ├── LeisureMeta_Chain_API.md
│ ├── api_with_example.md
│ ├── creator-dao-documentation.md
│ └── dao-voting-system-design-english.md
├── modules/
│ ├── api/
│ │ ├── src/
│ │ │ └── main/
│ │ │ └── scala/
│ │ │ └── io/
│ │ │ └── leisuremeta/
│ │ │ └── chain/
│ │ │ └── api/
│ │ │ ├── LeisureMetaChainApi.scala
│ │ │ └── model/
│ │ │ ├── Account.scala
│ │ │ ├── AccountData.scala
│ │ │ ├── AccountSignature.scala
│ │ │ ├── Block.scala
│ │ │ ├── GroupData.scala
│ │ │ ├── GroupId.scala
│ │ │ ├── NetworkId.scala
│ │ │ ├── NodeStatus.scala
│ │ │ ├── PublicKeySummary.scala
│ │ │ ├── Signed.scala
│ │ │ ├── StateRoot.scala
│ │ │ ├── Transaction.scala
│ │ │ ├── TransactionWithResult.scala
│ │ │ ├── account/
│ │ │ │ ├── EthAddress.scala
│ │ │ │ ├── ExternalChain.scala
│ │ │ │ └── ExternalChainAddress.scala
│ │ │ ├── agenda/
│ │ │ │ └── AgendaId.scala
│ │ │ ├── api_model/
│ │ │ │ ├── AccountInfo.scala
│ │ │ │ ├── ActivityInfo.scala
│ │ │ │ ├── BalanceInfo.scala
│ │ │ │ ├── BlockInfo.scala
│ │ │ │ ├── CreatorDaoInfo.scala
│ │ │ │ ├── GroupInfo.scala
│ │ │ │ ├── NftBalanceInfo.scala
│ │ │ │ ├── RewardInfo.scala
│ │ │ │ └── TxInfo.scala
│ │ │ ├── creator_dao/
│ │ │ │ ├── CreatorDaoData.scala
│ │ │ │ └── CreatorDaoId.scala
│ │ │ ├── reward/
│ │ │ │ ├── ActivityLog.scala
│ │ │ │ ├── ActivityRewardLog.scala
│ │ │ │ ├── ActivitySnapshot.scala
│ │ │ │ ├── DaoActivity.scala
│ │ │ │ ├── DaoInfo.scala
│ │ │ │ ├── OwnershipRewardLog.scala
│ │ │ │ └── OwnershipSnapshot.scala
│ │ │ ├── token/
│ │ │ │ ├── NftInfo.scala
│ │ │ │ ├── NftInfoWithPrecision.scala
│ │ │ │ ├── NftState.scala
│ │ │ │ ├── Rarity.scala
│ │ │ │ ├── SnapshotState.scala
│ │ │ │ ├── TokenDefinition.scala
│ │ │ │ ├── TokenDefinitionId.scala
│ │ │ │ ├── TokenDetail.scala
│ │ │ │ └── TokenId.scala
│ │ │ └── voting/
│ │ │ ├── Proposal.scala
│ │ │ ├── ProposalId.scala
│ │ │ └── VoteType.scala
│ │ └── tx_type.txt
│ ├── archive/
│ │ └── src/
│ │ └── main/
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ └── chain/
│ │ └── archive/
│ │ └── ArchiveMain.scala
│ ├── bulk-insert/
│ │ └── src/
│ │ └── main/
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ └── chain/
│ │ └── bulkinsert/
│ │ ├── BulkInsertMain.scala
│ │ ├── FungibleBalanceState.scala
│ │ ├── InvalidTx.scala
│ │ ├── NftBalanceState.scala
│ │ └── RecoverTx.scala
│ ├── eth-gateway/
│ │ └── src/
│ │ └── main/
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ └── chain/
│ │ └── gateway/
│ │ └── eth/
│ │ └── EthGatewayMain.scala
│ ├── eth-gateway-common/
│ │ └── src/
│ │ ├── main/
│ │ │ └── scala/
│ │ │ └── io/
│ │ │ └── leisuremeta/
│ │ │ └── chain/
│ │ │ └── gateway/
│ │ │ └── eth/
│ │ │ └── common/
│ │ │ ├── GatewayApi.scala
│ │ │ ├── GatewayConf.scala
│ │ │ ├── GatewayDecryptService.scala
│ │ │ ├── GatewayResource.scala
│ │ │ ├── GatewayServer.scala
│ │ │ ├── GatewaySimpleConf.scala
│ │ │ ├── GatewayWeb3Service.scala
│ │ │ └── client/
│ │ │ ├── GatewayApiClient.scala
│ │ │ ├── GatewayDatabaseClient.scala
│ │ │ └── GatewayKmsClient.scala
│ │ └── test/
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ └── chain/
│ │ └── gateway/
│ │ └── eth/
│ │ └── common/
│ │ └── GatewayServerTest.scala
│ ├── eth-gateway-deposit/
│ │ └── src/
│ │ └── main/
│ │ ├── resources/
│ │ │ └── application.conf.sample
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ └── chain/
│ │ └── gateway/
│ │ └── eth/
│ │ └── EthGatewayDepositMain.scala
│ ├── eth-gateway-setup/
│ │ └── src/
│ │ └── main/
│ │ ├── resources/
│ │ │ └── application.conf.sample
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ └── chain/
│ │ └── gateway/
│ │ └── eth/
│ │ └── setup/
│ │ ├── EthGatewaySetupConfig.scala
│ │ ├── EthGatewaySetupMain.scala
│ │ └── EthGatewaySetupSimpleConfig.scala
│ ├── eth-gateway-withdraw/
│ │ └── src/
│ │ └── main/
│ │ ├── resources/
│ │ │ └── application.conf.sample
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ └── chain/
│ │ └── gateway/
│ │ └── eth/
│ │ └── EthGatewayWithdrawMain.scala
│ ├── jvm-client/
│ │ └── src/
│ │ └── main/
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ └── chain/
│ │ └── jvmclient/
│ │ └── JvmClientMain.scala
│ ├── lib/
│ │ ├── js/
│ │ │ └── src/
│ │ │ └── main/
│ │ │ └── scala/
│ │ │ └── io/
│ │ │ └── leisuremeta/
│ │ │ └── chain/
│ │ │ └── lib/
│ │ │ └── crypto/
│ │ │ └── CryptoOps.scala
│ │ ├── jvm/
│ │ │ └── src/
│ │ │ └── main/
│ │ │ └── scala/
│ │ │ └── io/
│ │ │ └── leisuremeta/
│ │ │ └── chain/
│ │ │ └── lib/
│ │ │ └── crypto/
│ │ │ └── CryptoOps.scala
│ │ └── shared/
│ │ └── src/
│ │ ├── main/
│ │ │ └── scala/
│ │ │ └── io/
│ │ │ └── leisuremeta/
│ │ │ └── chain/
│ │ │ └── lib/
│ │ │ ├── application/
│ │ │ │ └── DAppState.scala
│ │ │ ├── codec/
│ │ │ │ └── byte/
│ │ │ │ ├── ByteCodec.scala
│ │ │ │ ├── ByteDecoder.scala
│ │ │ │ └── ByteEncoder.scala
│ │ │ ├── crypto/
│ │ │ │ ├── Hash.scala
│ │ │ │ ├── KeyPair.scala
│ │ │ │ ├── PublicKey.scala
│ │ │ │ ├── Recover.scala
│ │ │ │ ├── Sign.scala
│ │ │ │ └── Signature.scala
│ │ │ ├── datatype/
│ │ │ │ ├── BigNat.scala
│ │ │ │ ├── UInt256.scala
│ │ │ │ └── Utf8.scala
│ │ │ ├── failure/
│ │ │ │ └── LmChainFailure.scala
│ │ │ ├── merkle/
│ │ │ │ ├── MerkleTrie.scala
│ │ │ │ ├── MerkleTrieNode.scala
│ │ │ │ ├── MerkleTrieState.scala
│ │ │ │ ├── MerkleTrieStateDiff.scala
│ │ │ │ └── package.scala
│ │ │ └── util/
│ │ │ ├── iron/
│ │ │ │ └── package.scala
│ │ │ └── refined/
│ │ │ └── bitVector.scala
│ │ └── test/
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ └── chain/
│ │ └── lib/
│ │ ├── codec/
│ │ │ └── ByteCodecTest.scala
│ │ ├── crypto/
│ │ │ └── CryptoOpsTest.scala
│ │ ├── datatype/
│ │ │ ├── BigNatTest.scala
│ │ │ └── UInt256Test.scala
│ │ └── merkle/
│ │ ├── MerkleTrieNodeTest.scala
│ │ ├── MerkleTrieTest.scala
│ │ └── NibblesTest.scala
│ ├── lmscan-agent/
│ │ └── src/
│ │ └── main/
│ │ ├── resources/
│ │ │ └── application.conf.sample
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ └── chain/
│ │ └── lmscan/
│ │ └── agent/
│ │ ├── ScanAgentApp.scala
│ │ ├── ScanAgentConfig.scala
│ │ ├── ScanAgentMain.scala
│ │ ├── apps/
│ │ │ ├── BalanceStoreApp.scala
│ │ │ ├── NftStoreApp.scala
│ │ │ ├── NodeDataStoreApp.scala
│ │ │ └── SummaryStoreApp.scala
│ │ └── service/
│ │ ├── RequestService.scala
│ │ ├── StoreService.scala
│ │ └── store/
│ │ ├── AccountStore.scala
│ │ ├── BalanceStore.scala
│ │ ├── BlockStore.scala
│ │ ├── NftStore.scala
│ │ ├── SummaryStore.scala
│ │ └── TxStore.scala
│ ├── lmscan-backend/
│ │ ├── docs/
│ │ │ └── flyway.md
│ │ └── src/
│ │ ├── main/
│ │ │ ├── resources/
│ │ │ │ ├── application.sample.properties
│ │ │ │ └── db/
│ │ │ │ ├── dist/
│ │ │ │ │ ├── V20230116164800__Alter_ts_pgdefault.sql
│ │ │ │ │ ├── V20230116164800__Alter_ts_pgglobal.sql
│ │ │ │ │ └── V20230116164801__Create_r_playnomm.sql
│ │ │ │ ├── common/
│ │ │ │ │ ├── V20230116164802__Create_t_account.sql
│ │ │ │ │ ├── V20230116164803__Create_t_block.sql
│ │ │ │ │ ├── V20230116164805__Create_t_nft.sql
│ │ │ │ │ └── V20230116164809__Create_t_transaction.sql
│ │ │ │ ├── seed/
│ │ │ │ │ └── R__001_Seed_account.sql
│ │ │ │ └── test/
│ │ │ │ └── V20230116164801__Create_r_playnomm.sql
│ │ │ └── scala/
│ │ │ └── io/
│ │ │ └── leisuremeta/
│ │ │ └── chain/
│ │ │ └── lmscan/
│ │ │ └── backend/
│ │ │ ├── LmscanBackendMain.scala
│ │ │ ├── docs/
│ │ │ │ └── Lmscan_API.md
│ │ │ ├── entity/
│ │ │ │ ├── Account.scala
│ │ │ │ ├── AccountMapper.scala
│ │ │ │ ├── Balance.scala
│ │ │ │ ├── Block.scala
│ │ │ │ ├── CollectionInfo.scala
│ │ │ │ ├── Nft.scala
│ │ │ │ ├── NftFile.scala
│ │ │ │ ├── NftInfo.scala
│ │ │ │ ├── NftOwner.scala
│ │ │ │ ├── NftSeason.scala
│ │ │ │ ├── Summary.scala
│ │ │ │ ├── Tx.scala
│ │ │ │ ├── TxState.scala
│ │ │ │ └── Validator.scala
│ │ │ ├── repository/
│ │ │ │ ├── AccountRepository.scala
│ │ │ │ ├── BlockRepository.scala
│ │ │ │ ├── CommonQuery.scala
│ │ │ │ ├── NftFileRepository.scala
│ │ │ │ ├── NftInfoRepository.scala
│ │ │ │ ├── NftOwnerRepository.scala
│ │ │ │ ├── NftRepository.scala
│ │ │ │ ├── SummaryRepository.scala
│ │ │ │ ├── TransactionRepository.scala
│ │ │ │ └── ValidatorRepository.scala
│ │ │ └── service/
│ │ │ ├── AccountService.scala
│ │ │ ├── BlockService.scala
│ │ │ ├── NftService.scala
│ │ │ ├── SearchService.scala
│ │ │ ├── SummaryService.scala
│ │ │ ├── TransactionService.scala
│ │ │ └── ValidatorService.scala
│ │ └── test/
│ │ └── scala/
│ │ └── EmbeddedPostgreFlywayTest.scala
│ ├── lmscan-common/
│ │ ├── .js/
│ │ │ └── package.json
│ │ ├── js/
│ │ │ └── package.json
│ │ └── shared/
│ │ └── src/
│ │ └── main/
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ ├── ExplorerAPI.scala
│ │ └── model/
│ │ ├── AccountDetail.scala
│ │ ├── AccountInfo.scala
│ │ ├── ApiModel.scala
│ │ ├── BlockDetail.scala
│ │ ├── BlockInfo.scala
│ │ ├── NftActivity.scala
│ │ ├── NftDetail.scala
│ │ ├── NftFileModel.scala
│ │ ├── NftInfo.scala
│ │ ├── NftOwnerInfo.scala
│ │ ├── NftSeasonModel.scala
│ │ ├── PageNavigation.scala
│ │ ├── PageResponse.scala
│ │ ├── SearchResult.scala
│ │ ├── SummaryModel.scala
│ │ ├── TxDetail.scala
│ │ ├── TxInfo.scala
│ │ └── Validator.scala
│ ├── lmscan-frontend/
│ │ ├── .parcelrc
│ │ ├── assets/
│ │ │ ├── css/
│ │ │ │ ├── desktop.css
│ │ │ │ ├── footer.css
│ │ │ │ ├── index.html
│ │ │ │ ├── loading.css
│ │ │ │ ├── mobile.css
│ │ │ │ ├── reset.css
│ │ │ │ ├── style.css
│ │ │ │ └── tooltip.css
│ │ │ ├── index.html
│ │ │ └── load-main.js
│ │ ├── package.json
│ │ ├── project/
│ │ │ └── build.properties
│ │ ├── readme.md
│ │ └── src/
│ │ └── main/
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ └── chain/
│ │ └── lmscan/
│ │ └── frontend/
│ │ ├── LmscanFrontendApp.scala
│ │ ├── components/
│ │ │ ├── BoardView.scala
│ │ │ ├── Footer.scala
│ │ │ ├── Loader.scala
│ │ │ ├── NavBar.scala
│ │ │ ├── SearchView.scala
│ │ │ ├── common/
│ │ │ │ ├── Body.scala
│ │ │ │ ├── Head.scala
│ │ │ │ ├── Pagination.scala
│ │ │ │ └── Table.scala
│ │ │ └── detail/
│ │ │ ├── AccountDetailTable.scala
│ │ │ ├── blockDetailTable.scala
│ │ │ ├── nftDetailTable.scala
│ │ │ └── txDetailTableCommon.scala
│ │ ├── controllers/
│ │ │ ├── Model.scala
│ │ │ └── Msg.scala
│ │ ├── layouts/
│ │ │ └── DefaultLayout.scala
│ │ ├── pages/
│ │ │ ├── AccountDetailPage.scala
│ │ │ ├── AccountPage.scala
│ │ │ ├── BlockDetailPage.scala
│ │ │ ├── BlockPage.scala
│ │ │ ├── ErrorPage.scala
│ │ │ ├── MainPage.scala
│ │ │ ├── NftPage.scala
│ │ │ ├── NftTokenPage.scala
│ │ │ ├── NtfDetailPage.scala
│ │ │ ├── TxDetailPage.scala
│ │ │ ├── TxPage.scala
│ │ │ ├── VdDetailPage.scala
│ │ │ └── VdPage.scala
│ │ └── utils/
│ │ ├── Cell.scala
│ │ ├── DataProcess.scala
│ │ └── ValidData.scala
│ ├── node/
│ │ └── src/
│ │ ├── main/
│ │ │ ├── resources/
│ │ │ │ └── application.conf.sample
│ │ │ └── scala/
│ │ │ └── io/
│ │ │ └── leisuremeta/
│ │ │ └── chain/
│ │ │ └── node/
│ │ │ ├── NodeApp.scala
│ │ │ ├── NodeConfig.scala
│ │ │ ├── NodeMain.scala
│ │ │ ├── dapp/
│ │ │ │ ├── PlayNommDApp.scala
│ │ │ │ ├── PlayNommDAppFailure.scala
│ │ │ │ ├── PlayNommState.scala
│ │ │ │ └── submodule/
│ │ │ │ ├── PlayNommDAppAccount.scala
│ │ │ │ ├── PlayNommDAppAgenda.scala
│ │ │ │ ├── PlayNommDAppCreatorDao.scala
│ │ │ │ ├── PlayNommDAppGroup.scala
│ │ │ │ ├── PlayNommDAppReward.scala
│ │ │ │ ├── PlayNommDAppToken.scala
│ │ │ │ ├── PlayNommDAppVoting.scala
│ │ │ │ └── package.scala
│ │ │ ├── repository/
│ │ │ │ ├── BlockRepository.scala
│ │ │ │ ├── StateRepository.scala
│ │ │ │ └── TransactionRepository.scala
│ │ │ ├── service/
│ │ │ │ ├── BlockService.scala
│ │ │ │ ├── LocalStatusService.scala
│ │ │ │ ├── NodeInitializationService.scala
│ │ │ │ ├── RewardService.scala
│ │ │ │ ├── StateReadService.scala
│ │ │ │ └── TransactionService.scala
│ │ │ └── store/
│ │ │ ├── HashStore.scala
│ │ │ ├── KeyValueStore.scala
│ │ │ ├── SingleValueStore.scala
│ │ │ └── interpreter/
│ │ │ ├── Bag.scala
│ │ │ ├── MultiInterpreter.scala
│ │ │ ├── RedisInterpreter.scala
│ │ │ └── SwayInterpreter.scala
│ │ └── test/
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ └── chain/
│ │ └── node/
│ │ └── dapp/
│ │ └── PlayNommDAppTest.scala
│ └── node-proxy/
│ └── src/
│ └── main/
│ ├── resources/
│ │ └── migration-node.json
│ └── scala/
│ └── io/
│ └── leisuremeta/
│ └── chain/
│ └── node/
│ └── proxy/
│ ├── NodeProxyApi.scala
│ ├── NodeProxyApp.scala
│ ├── NodeProxyMain.scala
│ ├── model/
│ │ ├── NodeConfig.scala
│ │ └── TxModel.scala
│ └── service/
│ ├── InternalApiService.scala
│ ├── NodeBalancer.scala
│ ├── NodeWatchService.scala
│ └── PostTxQueue.scala
└── project/
├── Settings.scala.sample
├── build.properties
└── plugins.sbt
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.class
*.log
# config
application.conf
application.properties
# generated by scalajs
lmchain.js
dev.js
prod.js
client-fastopt-bundle.js
client-fastopt-bundle.js.map
# sway db data folder
sway
# workshheets
*.worksheet.sc
# gateway log
unsent-deposits.json
sent-deposits.logs
last-block-read.json
# npm
node_modules
# parcel
.parcel-cache
dist
dist.zip
.env
# settings
Settings.scala
# mac
.DS_Store
# archive
txs.archive
txs1.archive
# bulk-insert
invalid-txs.csv
================================================
FILE: .jvmopts
================================================
-Xms1g
-Xmx4g
================================================
FILE: .scalafix.conf
================================================
OrganizeImports {
coalesceToWildcardImportThreshold = 10 # Int.MaxValue
expandRelative = false
groupExplicitlyImportedImplicitsSeparately = false
groupedImports = Merge
groups = ["re:javax?\\.", "scala.", "cats.", "*", "hedgehog."]
importSelectorsOrder = Ascii
importsOrder = SymbolsFirst
removeUnused = false
targetDialect = Scala3
}
================================================
FILE: .scalafmt.conf
================================================
version = 3.7.3
runner.dialect = scala3
align.preset = more
rewrite.trailingCommas.style = always
newlines.beforeTypeBounds = fold
rewrite.scala3 {
convertToNewSyntax = true
removeOptionalBraces = yes
}
fileOverride {
"glob:**/*.sbt" {
runner.dialect = sbt1
}
"glob:**/**/*.sbt" {
runner.dialect = sbt1
}
"glob:**/**/**/*.sbt" {
runner.dialect = sbt1
}
}
================================================
FILE: README.md
================================================
# LeisureMetaverse Blockchain
LeisureMetaverse Blockchain is to present a practical solution to limitations of existing blockchains by combining the existing computer engineering methodology with the structure of the blockchain.
Scala is a statically typed programming language which supports both object-oriented programming and functional programming. Building a blockchain on SCALA can reduce many thread safety concerns and be suited for component-based applications that support distribution and concurrency.
LeisureMetaverse Blockchain uses a mixed data structure that combines UTXO and account. The proposed structure uses Merkle-Patricia Trie to record the UTXOs of all the accounts. It means that the blockchain has a snapshot of the latest state in the block. By comparing cryptographic signatures in the UTXO in the latest block, it is possible to verify new transaction request without synchronizing the old data.
## LM Scan
### Dev Mode
#### Run Backend
```bash
sbt ~lmscanBackend/reStart
```
#### Run ScalaJS
```bash
sbt ~lmscanFrontend/fastLinkJS
```
#### Run Frontend
```bash
cd modules/lmscan-frontend
yarn start
```
### Build Mode
#### Assembly Backend
```bash
sbt lmscanBackend/assembly
```
#### Build ScalaJS
```bash
sbt lmscanFrontend/fullLinkJS
```
#### Build Frontend
```bash
cd modules/lmscan-frontend
yarn build
```
================================================
FILE: build.sbt
================================================
val V = new {
val Scala = "3.4.3"
val ScalaGroup = "3.4"
val catsEffect = "3.5.4"
val tapir = "1.10.6"
val sttp = "3.9.6"
val circe = "0.15.0-M1"
val refined = "0.11.1"
val iron = "2.5.0"
val scodecBits = "1.1.38"
val shapeless = "3.4.1"
val fs2 = "3.10.2"
val typesafeConfig = "1.4.3"
val pureconfig = "0.17.6"
val bouncycastle = "1.70"
val sway = "0.16.2"
val lettuce = "6.3.2.RELEASE"
val jasync = "2.2.4"
val okhttp3LoggingInterceptor = "4.12.0"
val web3J = "4.9.6"
val awsSdk = "2.20.75"
val scribe = "3.13.4"
val hedgehog = "0.10.1"
val organiseImports = "0.6.0"
val munitCatsEffect = "2.0.0-RC1"
val tyrian = "0.9.0"
val scalaJavaTime = "2.3.0"
val jsSha3 = "0.8.0"
val elliptic = "6.5.4"
val typesElliptic = "6.4.12"
val pgEmbedded = "1.0.3"
val quill = "4.8.0"
val postgres = "42.7.3"
val flywayCore = "9.22.3"
val sqlite = "3.48.0.0"
val doobieVersion = "1.0.0-RC5"
val hikari = "6.2.1"
}
val Dependencies = new {
lazy val node = Seq(
libraryDependencies ++= Seq(
"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-cats" % V.tapir,
"com.softwaremill.sttp.tapir" %% "tapir-json-circe" % V.tapir,
"com.outr" %% "scribe-slf4j" % V.scribe,
"com.typesafe" % "config" % V.typesafeConfig,
("io.swaydb" %% "swaydb" % V.sway).cross(CrossVersion.for3Use2_13),
"io.lettuce" % "lettuce-core" % V.lettuce,
),
excludeDependencies ++= Seq(
"org.scala-lang.modules" % "scala-collection-compat_2.13",
"org.scala-lang.modules" % "scala-java8-compat_2.13",
),
)
lazy val jvmClient = Seq(
libraryDependencies ++= Seq(
"com.softwaremill.sttp.client3" %% "armeria-backend-cats" % V.sttp,
"com.softwaremill.sttp.tapir" %% "tapir-sttp-client" % V.tapir,
),
excludeDependencies ++= Seq(
"org.scala-lang.modules" % "scala-collection-compat_2.13",
"org.scala-lang.modules" % "scala-java8-compat_2.13",
),
)
lazy val ethGateway = Seq(
libraryDependencies ++= Seq(
"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-cats" % V.tapir,
"com.softwaremill.sttp.tapir" %% "tapir-json-circe" % V.tapir,
"com.softwaremill.sttp.client3" %% "armeria-backend-cats" % V.sttp,
"com.softwaremill.sttp.tapir" %% "tapir-sttp-client" % V.tapir,
"com.outr" %% "scribe-slf4j" % V.scribe,
"com.github.pureconfig" %% "pureconfig-core" % V.pureconfig,
"com.typesafe" % "config" % V.typesafeConfig,
"org.web3j" % "core" % V.web3J,
"org.web3j" % "contracts" % V.web3J,
"com.squareup.okhttp3" % "logging-interceptor" % V.okhttp3LoggingInterceptor,
"com.github.jasync-sql" % "jasync-mysql" % V.jasync,
"software.amazon.awssdk" % "kms" % V.awsSdk,
),
)
lazy val archive = Seq(
libraryDependencies ++= Seq(
"com.softwaremill.sttp.client3" %% "armeria-backend-cats" % V.sttp,
"com.outr" %% "scribe-slf4j" % V.scribe,
"com.typesafe" % "config" % V.typesafeConfig,
),
)
lazy val api = Seq(
libraryDependencies ++= Seq(
"org.typelevel" %%% "shapeless3-deriving" % V.shapeless,
"org.typelevel" %%% "cats-effect" % V.catsEffect,
"com.softwaremill.sttp.tapir" %%% "tapir-json-circe" % V.tapir,
"com.softwaremill.sttp.client3" %%% "core" % V.sttp,
),
)
lazy val lib = Seq(
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-effect" % V.catsEffect,
"io.circe" %%% "circe-generic" % V.circe,
"io.circe" %%% "circe-parser" % V.circe,
"io.circe" %%% "circe-refined" % V.circe,
"eu.timepit" %%% "refined" % V.refined,
"io.github.iltotore" %%% "iron" % V.iron,
"io.github.iltotore" %%% "iron-circe" % V.iron,
"org.scodec" %%% "scodec-bits" % V.scodecBits,
"org.typelevel" %%% "shapeless3-typeable" % V.shapeless,
"co.fs2" %%% "fs2-core" % V.fs2,
),
)
lazy val libJVM = Seq(
libraryDependencies ++= Seq(
"org.bouncycastle" % "bcprov-jdk15on" % V.bouncycastle,
"com.outr" %% "scribe-slf4j" % V.scribe,
),
)
lazy val libJS = Seq(
libraryDependencies ++= Seq(
"com.outr" %%% "scribe" % V.scribe,
),
Compile / npmDependencies ++= Seq(
"js-sha3" -> V.jsSha3,
"elliptic" -> V.elliptic,
"@types/elliptic" -> V.typesElliptic,
),
)
lazy val catsEffectTests = Def.settings(
libraryDependencies ++= Seq(
"org.typelevel" %% "munit-cats-effect" % V.munitCatsEffect % Test,
),
Test / fork := true,
)
lazy val tests = Def.settings(
libraryDependencies ++= Seq(
"qa.hedgehog" %%% "hedgehog-munit" % V.hedgehog % Test,
"com.opentable.components" % "otj-pg-embedded" % V.pgEmbedded % Test,
"org.flywaydb" % "flyway-core" % V.flywayCore,
),
Test / fork := true,
)
lazy val lmscanCommon = Seq(
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-effect" % V.catsEffect,
"io.circe" %%% "circe-generic" % V.circe,
"io.circe" %%% "circe-parser" % V.circe,
"io.circe" %%% "circe-refined" % V.circe,
"eu.timepit" %%% "refined" % V.refined,
"com.softwaremill.sttp.tapir" %%% "tapir-core" % V.tapir,
"com.softwaremill.sttp.tapir" %%% "tapir-json-circe" % V.tapir,
"org.scodec" %%% "scodec-bits" % V.scodecBits,
"co.fs2" %%% "fs2-core" % V.fs2,
"io.getquill" %% "quill-jasync-postgres" % V.quill,
"com.outr" %% "scribe" % V.scribe,
),
)
lazy val lmscanFrontend = Seq(
libraryDependencies ++= Seq(
"io.indigoengine" %%% "tyrian-io" % V.tyrian,
"qa.hedgehog" %%% "hedgehog-munit" % V.hedgehog % Test,
),
)
lazy val lmscanBackend = Seq(
libraryDependencies ++= Seq(
"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-cats" % V.tapir,
"com.softwaremill.sttp.client3" %% "armeria-backend-cats" % V.sttp,
"com.typesafe" % "config" % V.typesafeConfig,
"org.postgresql" % "postgresql" % V.postgres,
"com.opentable.components" % "otj-pg-embedded" % V.pgEmbedded,
),
)
lazy val lmscanAgent = Seq(
libraryDependencies ++= Seq(
"com.outr" %% "scribe-file" % V.scribe,
"org.slf4j" % "slf4j-nop" % "2.0.17",
"org.xerial" % "sqlite-jdbc" % V.sqlite,
"com.softwaremill.sttp.client3" %% "fs2" % V.sttp,
"com.github.pureconfig" %% "pureconfig-core" % V.pureconfig,
"org.tpolecat" %% "doobie-core" % V.doobieVersion,
"org.tpolecat" %% "doobie-postgres" % V.doobieVersion,
"org.tpolecat" %% "doobie-specs2" % V.doobieVersion,
"org.tpolecat" %% "doobie-hikari" % V.doobieVersion,
"com.zaxxer" % "HikariCP" % V.hikari,
),
)
lazy val nodeProxy = Seq(
libraryDependencies ++= Seq(
"com.outr" %% "scribe-slf4j" % V.scribe,
"com.typesafe" % "config" % V.typesafeConfig,
"com.softwaremill.sttp.tapir" %% "tapir-armeria-server-cats" % V.tapir,
"com.softwaremill.sttp.client3" %% "armeria-backend-cats" % V.sttp,
"io.circe" %% "circe-generic" % V.circe,
"io.circe" %% "circe-parser" % V.circe,
"io.circe" %% "circe-refined" % V.circe,
"com.squareup.okhttp3" % "logging-interceptor" % V.okhttp3LoggingInterceptor,
"org.typelevel" %% "cats-effect" % V.catsEffect,
"co.fs2" %%% "fs2-core" % V.fs2,
),
)
}
ThisBuild / organization := "org.leisuremeta"
ThisBuild / version := "0.0.1-SNAPSHOT"
ThisBuild / scalaVersion := V.Scala
ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % V.organiseImports
ThisBuild / semanticdbEnabled := true
lazy val root = (project in file("."))
.aggregate(
node,
api.jvm,
api.js,
lib.jvm,
lib.js,
archive,
bulkInsert,
ethGatewaySetup,
ethGatewayCommon,
ethGatewayDeposit,
ethGatewayWithdraw,
lmscanCommon.jvm,
lmscanCommon.js,
lmscanFrontend,
lmscanBackend,
lmscanAgent,
nodeProxy,
)
lazy val node = (project in file("modules/node"))
.settings(Dependencies.node)
.settings(Dependencies.tests)
.settings(Dependencies.catsEffectTests)
.settings(
name := "leisuremeta-chain-node",
assemblyMergeStrategy := {
case x if x `contains` "reactor/core" =>
MergeStrategy.first
case x if x `contains` "io.netty.versions.properties" =>
MergeStrategy.first
case PathList("META-INF", "native", "lib", xs @ _*) =>
MergeStrategy.first
case x if x `contains` "module-info.class" => MergeStrategy.discard
case x =>
val oldStrategy = (ThisBuild / assemblyMergeStrategy).value
oldStrategy(x)
},
)
.dependsOn(api.jvm)
lazy val ethGatewayCommon = (project in file("modules/eth-gateway-common"))
.settings(Dependencies.ethGateway)
.settings(Dependencies.catsEffectTests)
.settings(
name := "leisuremeta-chain-eth-gateway-common",
Compile / compile / wartremoverErrors ++= Warts
.allBut(Wart.Any, Wart.AsInstanceOf, Wart.Nothing, Wart.Recursion),
)
.dependsOn(api.jvm)
lazy val ethGatewaySetup = (project in file("modules/eth-gateway-setup"))
.settings(Dependencies.ethGateway)
.settings(Dependencies.catsEffectTests)
.settings(
name := "leisuremeta-chain-eth-gateway-setup",
)
.dependsOn(ethGatewayCommon)
lazy val ethGatewayDeposit = (project in file("modules/eth-gateway-deposit"))
.settings(Dependencies.ethGateway)
.settings(Dependencies.tests)
.settings(
name := "leisuremeta-chain-eth-gateway-deposit",
assemblyMergeStrategy := {
case x if x `contains` "okio.kotlin_module" => MergeStrategy.first
case x if x `contains` "io.netty.versions.properties" =>
MergeStrategy.first
case x if x `contains` "native/lib/libnetty-unix-common.a" =>
MergeStrategy.first
case x if x `contains` "module-info.class" => MergeStrategy.discard
case x =>
val oldStrategy = (ThisBuild / assemblyMergeStrategy).value
oldStrategy(x)
},
Compile / compile / wartremoverErrors ++= Warts
.allBut(
Wart.Any,
Wart.AsInstanceOf,
Wart.Nothing,
Wart.Recursion,
Wart.SeqApply,
),
)
.dependsOn(ethGatewayCommon)
lazy val ethGatewayWithdraw = (project in file("modules/eth-gateway-withdraw"))
.settings(Dependencies.ethGateway)
.settings(Dependencies.tests)
.settings(
name := "leisuremeta-chain-eth-gateway-withdraw",
assemblyMergeStrategy := {
case x if x `contains` "okio.kotlin_module" => MergeStrategy.first
case x if x `contains` "io.netty.versions.properties" =>
MergeStrategy.first
case x if x `contains` "module-info.class" => MergeStrategy.discard
case x =>
val oldStrategy = (ThisBuild / assemblyMergeStrategy).value
oldStrategy(x)
},
Compile / compile / wartremoverErrors ++= Warts
.allBut(
Wart.Any,
Wart.AsInstanceOf,
Wart.Nothing,
Wart.Recursion,
Wart.TripleQuestionMark,
),
)
.dependsOn(ethGatewayCommon)
lazy val archive = (project in file("modules/archive"))
.settings(Dependencies.archive)
.settings(Dependencies.tests)
.settings(
name := "leisuremeta-chain-archive",
Compile / run / fork := true,
assemblyMergeStrategy := {
case x if x `contains` "io.netty.versions.properties" =>
MergeStrategy.first
case PathList("META-INF", "native", "lib", xs @ _*) =>
MergeStrategy.first
case x if x `contains` "module-info.class" => MergeStrategy.discard
case x =>
val oldStrategy = (ThisBuild / assemblyMergeStrategy).value
oldStrategy(x)
},
)
.dependsOn(api.jvm)
lazy val bulkInsert = (project in file("modules/bulk-insert"))
.dependsOn(node)
.settings(
name := "leisuremeta-chain-bulk-insert",
Compile / run / fork := true,
excludeDependencies ++= Seq(
"org.scala-lang.modules" % "scala-collection-compat_2.13",
"org.scala-lang.modules" % "scala-java8-compat_2.13",
),
assemblyMergeStrategy := {
case PathList("reactor", "core", "scheduler", xs @ _*) =>
MergeStrategy.preferProject
case x if x `contains` "libnetty-unix-common.a" =>
MergeStrategy.first
case x if x `contains` "io.netty.versions.properties" =>
MergeStrategy.first
case PathList("META-INF", "native", "lib", xs @ _*) =>
MergeStrategy.first
case PathList("reactor", "core", "scheduler", xs @ _*) =>
MergeStrategy.first
case x if x `contains` "module-info.class" => MergeStrategy.discard
case x =>
val oldStrategy = (ThisBuild / assemblyMergeStrategy).value
oldStrategy(x)
},
)
lazy val jvmClient = (project in file("modules/jvm-client"))
.settings(Dependencies.jvmClient)
.dependsOn(node)
.settings(
name := "leisuremeta-chain-jvm-client",
Compile / run / fork := true,
)
lazy val api = crossProject(JSPlatform, JVMPlatform)
.crossType(CrossType.Pure)
.in(file("modules/api"))
.settings(Dependencies.api)
.settings(
scalacOptions ++= Seq(
"-Xmax-inlines:64",
),
Compile / compile / wartremoverErrors ++= Warts.allBut(Wart.NoNeedImport),
)
.dependsOn(lib)
lazy val lib = crossProject(JSPlatform, JVMPlatform)
.crossType(CrossType.Full)
.in(file("modules/lib"))
.settings(Dependencies.lib)
.settings(Dependencies.tests)
.settings(
scalacOptions ++= Seq(
"-Wconf:msg=Alphanumeric method .* is not declared infix:s",
),
Compile / compile / wartremoverErrors ++= Warts
.allBut(Wart.SeqApply, Wart.SeqUpdated),
)
.jvmSettings(Dependencies.libJVM)
.jsSettings(Dependencies.libJS)
.jsSettings(
useYarn := true,
Test / scalaJSLinkerConfig ~= {
_.withModuleKind(ModuleKind.CommonJSModule)
},
scalacOptions ++= Seq(
"-scalajs",
),
Test / fork := false,
)
.jsConfigure { project =>
project
.enablePlugins(ScalaJSBundlerPlugin)
.enablePlugins(ScalablyTypedConverterPlugin)
}
lazy val lmscanCommon = crossProject(JSPlatform, JVMPlatform)
.crossType(CrossType.Full)
.in(file("modules/lmscan-common"))
.settings(Dependencies.lmscanCommon)
.settings(Dependencies.tests)
.jvmSettings(
scalacOptions ++= Seq(
"-Xmax-inlines:64",
),
Test / fork := true,
)
.jsSettings(
useYarn := true,
scalaJSLinkerConfig ~= {
_.withModuleKind(ModuleKind.CommonJSModule)
},
scalacOptions ++= Seq(
"-scalajs",
"-Xmax-inlines:64",
),
externalNpm := {
scala.sys.process.Process("yarn", baseDirectory.value).!
baseDirectory.value
},
Test / fork := false,
// Compile / compile / wartremoverErrors ++= Warts.all,
)
.jsConfigure { project =>
project
.enablePlugins(ScalaJSBundlerPlugin)
.enablePlugins(ScalablyTypedConverterExternalNpmPlugin)
}
lazy val lmscanFrontend = (project in file("modules/lmscan-frontend"))
.enablePlugins(ScalaJSPlugin)
.enablePlugins(ScalablyTypedConverterExternalNpmPlugin)
.settings(Dependencies.lmscanFrontend)
.settings(
name := "leisuremeta-chain-lmscan-frontend",
scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) },
externalNpm := {
scala.sys.process.Process("yarn", baseDirectory.value).!
baseDirectory.value
},
scalacOptions ++= Seq(
"-scalajs",
),
)
.dependsOn(lmscanCommon.js, api.js)
lazy val lmscanBackend = (project in file("modules/lmscan-backend"))
.enablePlugins(FlywayPlugin)
.settings(Dependencies.lmscanBackend)
.settings(Dependencies.tests)
.settings(
name := "leisuremeta-chain-lmscan-backend",
assemblyMergeStrategy := {
case PathList("scala", "tools", "asm", xs @ _*) => MergeStrategy.first
case PathList("io", "getquill", xs @ _*) => MergeStrategy.first
case x if x `contains` "io.netty.versions.properties" =>
MergeStrategy.first
case x if x `contains` "scala-asm.properties" =>
MergeStrategy.first
case x if x `contains` "compiler.properties" =>
MergeStrategy.first
case x if x `contains` "native/lib/libnetty-unix-common.a" =>
MergeStrategy.first
case x if x `contains` "module-info.class" => MergeStrategy.discard
case x =>
val oldStrategy = (ThisBuild / assemblyMergeStrategy).value
oldStrategy(x)
},
)
.settings(
flywayUrl := Settings.flywaySettings.url,
flywayUser := Settings.flywaySettings.user,
flywayPassword := Settings.flywaySettings.pwd,
flywaySchemas := Settings.flywaySettings.schemas,
flywayLocations ++= Settings.flywaySettings.locations,
)
.dependsOn(lmscanCommon.jvm)
lazy val lmscanAgent = (project in file("modules/lmscan-agent"))
.settings(Dependencies.lmscanAgent)
.settings(Dependencies.tests)
.settings(Dependencies.catsEffectTests)
.settings(
Compile / run / fork := true,
)
.settings(
name := "leisuremeta-chain-lmscan-agent",
assemblyMergeStrategy := {
case PathList("scala", "tools", "asm", xs @ _*) => MergeStrategy.first
case PathList("io", "getquill", xs @ _*) => MergeStrategy.first
case x if x `contains` "libnetty-unix-common.a" =>
MergeStrategy.first
case x if x `contains` "io.netty.versions.properties" =>
MergeStrategy.first
case x if x `contains` "scala-asm.properties" =>
MergeStrategy.first
case x if x `contains` "compiler.properties" =>
MergeStrategy.first
case x if x `contains` "module-info.class" => MergeStrategy.discard
case x =>
val oldStrategy = (ThisBuild / assemblyMergeStrategy).value
oldStrategy(x)
},
)
.dependsOn(api.jvm)
.dependsOn(lmscanBackend)
lazy val nodeProxy = (project in file("modules/node-proxy"))
.settings(Dependencies.nodeProxy)
.settings(Dependencies.tests)
.settings(
name := "leisuremeta-chain-node-proxy",
assemblyMergeStrategy := {
case x if x `contains` "okio.kotlin_module" => MergeStrategy.first
case x if x `contains` "io.netty.versions.properties" =>
MergeStrategy.first
case x if x `contains` "native/lib/libnetty-unix-common.a" =>
MergeStrategy.first
case x if x `contains` "module-info.class" => MergeStrategy.discard
case x =>
val oldStrategy = (ThisBuild / assemblyMergeStrategy).value
oldStrategy(x)
},
)
.dependsOn(api.jvm)
================================================
FILE: docs/LeisureMeta_Chain_API.md
================================================
# LeisureMeta Chain API
## API with Top Priority
`GET` **/balance/{accountName}** 계정 잔고 조회
> `param` movable: 잔고의 이동 가능성 여부
>
> * 'free': 유동 자산
> * 'locked': 예치 자산
* Response: Map[TokenDefinitionID, BalanceInfo]
* Token Definition ID: 토큰 정의 ID (string)
* BalanceInfo
* Total Amount: 해당 토큰 총 금액/수량 (NFT의 경우 랜덤박스 갯수)
* Map[TxHash, Tx]: 사용하지 않은 트랜잭션 해시 목록
`GET` **/nft-balance/{accountName}** 계정 NFT 잔고 조회
> `param` *(optional)* movable: 잔고의 이동 가능성 여부
>
> * 'free': 유동 자산
> * 'locked': 예치 자산
> * 'all': 전체 자산
* Response: Map[TokenID, NftBalanceInfo]
* NftBalanceInfo
* TokenDefinitionId
* TxHash
* Tx
`GET` **/activity/account/{account}** 계정 활동내역 조회
* Response: Seq[ActivityInfo]
* ActivityInfo
* timestamp
* point
* description
* txHash
`GET` **/activity/token/{tokenId}** 토큰이 받은 활동내역 조회
* Response: Seq[ActivityInfo]
* ActivityInfo
* timestamp
* point
* description
* txHash
`GET` **/reward/{accountName}** 보상 조회
> `param` *(optional)* timestamp: 기준 시점. 없으면 가장 최근 보상. (월요일 0시 ~ 일요일 23시59분 주기)
>
> `param` *(optional)* dao-account: 마스터 다오 계정. 없으면 `DAO-M` 사용
>
> `param` *(optional)* reward-amount: 리워드 총량. 없으면 마스터 다오 계정의 현재 LM 밸런스.
* Response:
* account: 계정이름
* reward: 보상
* total: 총 보상량
* activity: 활동보상
* token: 토큰이 받은 사용자액션 보상
* rarity: 보유 토큰의 희귀도에 따른 보상
* bonus: 모더레이터인 경우 주어지는 추가 보상 총합
* point: 활동내역에 따르는 보상 포인트(1/1000 포인트 단위의 정수)
* activity: 활동 내역.
* like: 좋아요
* comment: 댓글
* share: 공유
* report: 신고
* token: 토큰이 받은 내역
* like: 좋아요
* comment: 댓글
* share: 공유
* report: 신고
* rarity: Map[String, Number] 희귀도에 따르는 포인트
* timestamp: 기준 시점
* totalNumberOfDao: 시스템에 개설된 다오 총 수
`GET` **/dao/{groupID}** 특정 그룹의 DAO 정보 조회
* Response: DaoInfo DAO 정보
* DaoInfo 현재까지 정해진 필드값들
* NumberOfModerator: 모더레이터 숫자
`GET` **/owners/{definitionID}** 특정 컬렉션 NFT들의 보유자 정보 조회
* Response:
* Map[TokenID, AccountName]
`GET` **/snapshot/ownership/** 받을 토큰 소유보상 점수 조회
> `param` *(optional)* from: 조회를 시작할 token id. 주어지지 않으면 ""
>
> `param` *(optional)* limit: 조회할 총 갯수. 디폴트값은 100
* Response: [TokenId, OwnershipSnapshot]
* OwnershipSnapshot
* account
* timestamp 기준 시점
* point 포인트. 일반적으론 해당 NFT의 Rarity 점수
* definitionId 보상받을 토큰 종류. 일반적으론 LM
* amount 보상량
`GET` **/rewarded/ownership/{tokenID}** 최근에 받은 토큰 소유보상 조회
* Response: OwnershipRewardLog
* OwnershipRewardLog
* OwnershipShapshot
* ExecuteReward TxHash
`GET` **/creator-dao/{daoID}** 특정 크리에이터 DAO 정보 조회
* Response: CreatorDaoInfo 크리에이터 DAO 정보
* CreatorDaoInfo
* id: CreatorDaoId
* name: Utf8
* description: Utf8
* founder: Account
* coordinator: Account
* moderators: Set[Account]
`GET` **/creator-dao/{daoID}/member** 특정 크리에이터 DAO의 멤버 목록 조회
> `param` *(optional)* from(Account): 기준 계정
>
> `param` *(optional)* limit: 갯수 제한. 기본값 100
>
* Response: Seq[Account] 회원 목록
`POST` **/tx** 트랜잭션 제출
* 아래의 트랜잭션 목록 참조
* Array로 한 번에 여러개의 트랜잭션 제출 가능
### Blockchain Explorer 지원용 API
`GET` **/status** 블록체인 현재상태 조회 (최신 블록 hash, 블록 number 포함)
`GET` **/block **블록 목록 조회
> `param` *(optional)* from: 찾기 시작할 블록 해시. 없으면 최신 블록
>
> `param` *(optional)* limit: 가져올 블록 갯수. 디폴트 50.
`GET` **/block/{blockHash}** 블록 상세정보 조회 (포함된 트랜잭션 해시 목록 포함)
`GET` **/tx** 특정 블록에 포함된 트랜잭션 목록 조회
> `param` block: 찾을 블록 해시
`GET` **/tx/{transactionHash}** 트랜잭션 상세정보 조회
### Response HTTP Status Codes
* 요청했을 때 해당 내용이 없는 경우: 404 Not Found
* 서명이 올바르지 않은 경우: 401 Unauthorized
* 트랜잭션이 invalid한 경우: 400 Bad Request
* 블록체인 노드 내부 오류: 500 Internal Server Error
## Transactions
* 모든 트랜잭션 공통 필드
* "networkId": 다른 네트워크에 똑같은 트랜잭션을 보내는 것을 막기 위한 필드.
* "createdAt": 트랜잭션 생성시각
* Format
* 서명주체
* Fields: 트랜잭션을 제출할 때 포함시켜야 하는 필드 목록
* *(optional)* Computed Fields: 블록에 기록될 때 노드에 의해 덧붙여지는 필드들
### Account
* CreateAccount 계정 생성
* > 사용자 서명
* Fields
* account: Account 계정 이름
* ethAddress: *(optional)* 이더리움 주소
* guardian: *(optional)* Account
* 계정에 공개키를 추가할 수 있는 권한을 가진 계정 지정. 일반적으로는 `playnomm`
* Example (private key `b229e76b742616db3ac2c5c2418f44063fcc5fcc52a08e05d4285bdb31acba06` 으로 서명한 예시)
```json
[
{
"sig" : {
"sig" : {
"v" : 28,
"r" : "495c3bcc143eea328c11b7ec55069dd4fb16c26463999f9dbc085094c3b59423",
"s" : "707a75e433abd208cfb76d4e0cdbc04b1ce2389e3a1f866348ef2e3ea5785e93"
},
"account" : "alice"
},
"value" : {
"AccountTx" : {
"CreateAccount" : {
"networkId" : 1000,
"createdAt" : "2020-05-22T09:00:00Z",
"account" : "alice",
"ethAddress" : null,
"guardian" : null
}
}
}
}
]
```
```json
["822380e575e482e829fc9f45ffd0f99f4f0987ccbec0c0a5de5fd640f42a9100"]
```
* CreateAccountWithExternalChainAddresses 외부 블록체인 주소를 가진 계정 생성
* > 사용자 서명
* Fields
* account: Account 계정 이름
* externalChainAddresses: 외부 블록체인 주소. 현재는 `eth`, `sol` 두 가지 지원.
* guardian: *(optional)* Account
* 계정에 공개키를 추가할 수 있는 권한을 가진 계정 지정. 일반적으로는 `playnomm`
* memo: *(optional)* 메모
* Example (private key `b229e76b742616db3ac2c5c2418f44063fcc5fcc52a08e05d4285bdb31acba06` 으로 서명한 예시)
```json
[
{
"sig" : {
"sig" : {
"v" : 28,
"r" : "2f7a53986a387961047566ab8d31fcdbbe6cc96529cdbfccb68fb268700f2bdf",
"s" : "56f993f2cca6a5f7410e04a5aa849c7698f1e0966d98b90638d7472cd9eb3210"
},
"account" : "alice"
},
"value" : {
"AccountTx" : {
"CreateAccountWithExternalChainAddresses" : {
"networkId" : 2021,
"createdAt" : "2023-01-11T19:01:30Z",
"account" : "bob",
"externalChainAddresses" : {
"eth" : "99f681d29754aeee1426ef991b745a4f662e620c"
},
"guardian" : "alice",
"memo" : null
}
}
}
}
```
```json
["d795dc9205ec5ecb3097fe0ca0326e6597c6ecae497a0876b8cc3737d823264a"]
```
* UpdateAccount 계정 생성
* > 사용자 서명 혹은 Guardian 서명
* Fields
* account: Account 계정 이름
* ethAddress: *(optional)* 이더리움 주소
* guardian: *(optional)* Account
* 계정에 공개키를 추가할 수 있는 권한을 가진 계정 지정. 일반적으로는 `playnomm`
* Example
```json
[
{
"sig" : {
"sig" : {
"v" : 28,
"r" : "22c14ac6fbdce52c256640f1e36851ef901ea1b5cfebc3a430283a89df99bc11",
"s" : "3474ebcc861c2d31a60d363356c4c89c196d450432b33bedadfb94d66edf2ffd"
},
"account" : "alice"
},
"value" : {
"AccountTx" : {
"UpdateAccount" : {
"networkId" : 1000,
"createdAt" : "2020-05-22T09:00:00Z",
"account" : "alice",
"ethAddress" : "0xefD277f6da7ac53e709392044AE98220Df142753",
"guardian" : null
}
}
}
}
]
```
```json
["7730dadeff5be3bfd63fdec8853d6301a5ec0e3b8c815a4d7e0ba20e8c52517d"]
```
* UpdateAccountWithExternalChainAddresses 계정 생성
* > 사용자 서명 혹은 Guardian 서명
* Fields
* account: Account 계정 이름
* externalChainAddresses: 외부 블록체인 주소. 현재는 `eth`, `sol` 두 가지 지원.
* guardian: *(optional)* Account
* 계정에 공개키를 추가할 수 있는 권한을 가진 계정 지정. 일반적으로는 `playnomm`
* memo: *(optional)* 메모
* Example
```json
[
{
"sig" : {
"sig" : {
"v" : 27,
"r" : "bf7dfb91669233e120a07707084f2b9879d9620cd297096f862d52d53fe9988d",
"s" : "10d652ba45a81ad572aeae6c25ffd2e6339e8f049d379ae24d5a947f531d5d65"
},
"account" : "alice"
},
"value" : {
"AccountTx" : {
"UpdateAccountWithExternalChainAddresses" : {
"networkId" : 2021,
"createdAt" : "2023-01-11T19:01:40Z",
"account" : "bob",
"externalChainAddresses" : {
"eth" : "99f681d29754aeee1426ef991b745a4f662e620c"
},
"guardian" : "alice",
"memo" : "bob updated"
}
}
}
}
]
```
```json
["a0e28414a97b3fabb49cf3b77757219b8e1a7205ba3cc4f5e618019b36bc38c3"]
```
* AddPublicKeySummaries 계정에 사용할 공개키요약 추가
* > 사용자 서명 혹은 Guardian 서명
* Fields
* account: Account 계정 이름
* summaries: Map[PublicKeySummary, String]
* 추가할 공개키요약과 간단한 설명
* 만약 설명이 `"permanant"` 인 경우 해당 public key summary 는 유효기간 없이 무제한 사용
* Result
* Removed: Map[PublicKeySummary, Descrption(string)]
* Example
```json
[
{
"sig" : {
"sig" : {
"v" : 27,
"r" : "816df20e4ff581fd2056689b48be73cca29e4f81977e5c42754e598757434c51",
"s" : "4e43aef8d836e79380067365cd7a4a452df5f52b73ec78463bdc7cdea2e11ca0"
},
"account" : "alice"
},
"value" : {
"AccountTx" : {
"AddPublicKeySummaries" : {
"networkId" : 1000,
"createdAt" : "2020-05-22T09:00:00Z",
"account" : "alice",
"summaries" : {
"5b6ed47b96cd913eb938b81ee3ea9e7dc9affbff" : "another key"
}
}
}
}
}
]
```
```json
["e996dcbabcf8a86208bcc8d683778f5d6b5d1b8ff950c9e60cc72b66fc619cca"]
```
* RemovePublicKeySummaries 계정에 사용할 공개키요약 삭제
* > 사용자 서명 혹은 Guardian 서명
* Fields
* Account: AccountName (string)
* Summaries: Set[PublicKeySummary]
* RemoveAccount 계정 삭제
* > 사용자 서명 혹은 Guardian 서명
* Fields
* Account: AccountName (string)
### Group
* CreateGroup 그룹 생성
* > Coordinator 서명
* Fields
* GroupID(string)
* Name: GroupName(string)
* Coordinator: AccountName(string)
* 그룹 조정자. 그룹에 계정 추가, 삭제 및 그룹 해산 권한을 가짐
* Example
```json
[
{
"sig" : {
"sig" : {
"v" : 28,
"r" : "aab6f7ccc108b8e75601c726d43270c1a60f38f830136dfe293a2633dc86a0dd",
"s" : "3cc1b610df7a421f9ae560853d5f07005a20c6ad225a00861a76e5e91aa183c0"
},
"account" : "alice"
},
"value" : {
"GroupTx" : {
"CreateGroup" : {
"networkId" : 1000,
"createdAt" : "2022-06-08T09:00:00Z",
"groupId" : "mint-group",
"name" : "mint group",
"coordinator" : "alice"
}
}
}
}
]
```
```json
["adb9440aeef2de4697774657ebbcce9c1e5b01423e0a21da90da355458400c75"]
```
* DisbandGroup 그룹 해산
* > Coordinator 서명
* Fields
* GroupID(string)
* AddAccounts 그룹에 계정 추가
* > Coordinator 서명
* Fields
* GroupID(string)
* Accounts: Set[AccountName(string)]
* Example
```json
[
{
"sig" : {
"sig" : {
"v" : 28,
"r" : "2dd00a2ebf07ff2d09d6e9bcd889ddc775c17989827e3e19b5e8d1744c021466",
"s" : "05bd60fef3d45463e22e5c157c814a7cbd1681410b67b0233c97ce7116d60729"
},
"account" : "alice"
},
"value" : {
"GroupTx" : {
"AddAccounts" : {
"networkId" : 1000,
"createdAt" : "2022-06-08T09:00:00Z",
"groupId" : "mint-group",
"accounts" : [
"alice",
"bob"
]
}
}
}
}
]
```
```json
["015a8cced717ca40a528d9518e8494961a4c4e7fde1422304b751814ed181e00"]
```
* RemoveAccounts 그룹에 계정 삭제
* > Coordinator 서명
* Fields
* GroupID(string)
* Accounts: Set[AccountName(string)]
* ReplaceCoordinator 그룹 조정자 변경
* > Coordinator 서명
* Fields
* GroupID(string)
* NewCoordinator: AccountName(string)
### Token
* DefineToken 토큰 정의. Fungible Token, NFT 공히 사용한다. (랜덤박스 포함)
* > MinterGroup에 속한 Account의 서명
* Fields
* definitionId: TokenDefinitionID(string)
* name: String
* *(optional)* Symbol(string)
* *(optional)* MinterGroup: GroupID(string) 신규토큰발행 권한을 가진 그룹
* *(optional)* NftInfo
* minter: AccountName(string)
* rarity: Map[(Rarity(string), Weight]
* *(optional)* DataUrl(string)
* *(optional)* ContentHash: uint256
* Example
```json
[
{
"sig" : {
"sig" : {
"v" : 28,
"r" : "ce2b48b7da96eef22a2b92170fb81865adb99cbcae99a2b81bb7ce9b4ba990b6",
"s" : "35a708c9ffc1b7ef4e88389255f883c96e551a404afc4627e3f6ca32a617bae6"
},
"account" : "alice"
},
"value" : {
"TokenTx" : {
"DefineToken" : {
"networkId" : 1000,
"createdAt" : "2020-05-22T09:01:00Z",
"definitionId" : "test-token",
"name" : "test-token",
"symbol" : "TT",
"minterGroup" : "mint-group",
"nftInfo" : {
"Some" : {
"value" : {
"minter" : "alice",
"rarity" : {
"LGDY" : 8,
"UNIQ" : 4,
"EPIC" : 2,
"RARE" : 1
},
"dataUrl" : "https://www.playnomm.com/data/test-token.json",
"contentHash" : "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
}
}
}
}
}
}
]
```
```json
["b0cfd8da5ef347762b60162c772148902b54abca4760fb53e3eb752f8b953664"]
```
* DefineTokenWithPrecision Precision이 있는 토큰 정의. Fungible Token, NFT 공히 사용한다. (랜덤박스 포함)
* > MinterGroup에 속한 Account의 서명
* Fields
* definitionId: TokenDefinitionID(string)
* name: String
* *(optional)* Symbol(string)
* *(optional)* MinterGroup: GroupID(string) 신규토큰발행 권한을 가진 그룹
* *(optional)* NftInfo
* minter: AccountName(string)
* rarity: Map[(Rarity(string), Weight]
* precision: int 소숫점 자릿수
* *(optional)* DataUrl(string)
* *(optional)* ContentHash: uint256
* Example
```json
[
{
"sig" : {
"sig" : {
"v" : 27,
"r" : "74a1fa40be985b0c9bcf92df0262317a336f585fa24e261780b2ab6ff89d3f6a",
"s" : "4cea4a8ad18df36a2c140366f8afee36441757921aa351fedf0b53d82307e9c2"
},
"account" : "alice"
},
"value" : {
"TokenTx" : {
"DefineTokenWithPrecision" : {
"networkId" : 2021,
"createdAt" : "2023-01-11T19:01:00Z",
"definitionId" : "nft-with-precision",
"name" : "NFT with precision",
"symbol" : "NFTWP",
"minterGroup" : "mint-group",
"nftInfo" : {
"Some" : {
"value" : {
"minter" : "alice",
"rarity" : {
"LGDY" : 100,
"UNIQ" : 66,
"EPIC" : 33,
"RARE" : 10
},
"precision" : 2,
"dataUrl" : "https://www.playnomm.com/data/nft-with-precision.json",
"contentHash" : "2475a387f22c248c5a3f09cea0ef624484431c1eaf8ffbbf98a4a27f43fabc84"
}
}
}
}
}
}
}
]
```
```json
["6d49236405972c01322db054338da2c7ab6fd9662d2a64c9bc1ab4026da9fb8f"]
```
* MintFungibleToken
* > MinterGroup에 속한 Account의 서명
* Fields
* TokenDefinitionID(string)
* Outputs: Map[AccountName, Amount]
* Example
```json
[
{
"sig" : {
"sig" : {
"v" : 28,
"r" : "76fb1b3be81101638c9ce070628db035ad7d86d3363d664da0c5afe254494e90",
"s" : "7ffb1c751fe4f5341c75341e4a51373139a7f730a56a08078ac89b6e1a77fc76"
},
"account" : "alice"
},
"value" : {
"TokenTx" : {
"MintFungibleToken" : {
"networkId" : 1000,
"createdAt" : "2020-05-22T09:01:00Z",
"definitionId" : "test-token",
"outputs" : {
"alice" : 100
}
}
}
}
}
]
```
```json
["a3f35adb3d5d08692a7350e61aaa28da992a4280ad8e558953898ef96a0051ca"]
```
* BurnFungibleToken
* MinterGroup에 속한 Account의 서명
* Fields
* definitionId: TokenDefinitionId
* amount
* Inputs: Set[Signed.TxHash]
* Result
* outputAmount
* MintNFT
* > MinterGroup에 속한 Account의 서명
* Fields
* TokenDefinitionID(string)
* TokenID(string)
* Rarity(string)
* DataUrl(string)
* ContentHash: uint256
* Output: AccountName
* Example
```json
[
{
"sig" : {
"sig" : {
"v" : 27,
"r" : "0a914259cc0e8513512ea6356fc3056efe104e84756cf23a6c1c1aff7a580613",
"s" : "71a15b331b9e7337a018b442ee978a15f0d86e71ca53d2f54a9a8ccb92646cf9"
},
"account" : "alice"
},
"value" : {
"TokenTx" : {
"MintNFT" : {
"networkId" : 1000,
"createdAt" : "2022-06-08T09:00:00Z",
"tokenDefinitionId" : "test-token",
"tokenId" : "2022061710000513118",
"rarity" : "EPIC",
"dataUrl" : "https://d3j8b1jkcxmuqq.cloudfront.net/temp/collections/TEST_NOMM4/NFT_ITEM/F7A92FB1-B29F-4E6F-BEF1-47C6A1376D68.jpg",
"contentHash" : "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"output" : "alice"
}
}
}
}
]
```
```json
["6040003b0020245ce82f352bed95dee2636442efee4e5a15ee3911c67910b657"]
```
* MintNFTWithMemo
* > MinterGroup에 속한 Account의 서명
* Fields
* TokenDefinitionID(string)
* TokenID(string)
* Rarity(string)
* DataUrl(string)
* ContentHash: uint256
* Output: AccountName
* Example
```json
[
{
"sig" : {
"sig" : {
"v" : 28,
"r" : "d1c7f699ff24b4767e3728f79b13d3d930fa1be02cb511481010fbbaecf538c0",
"s" : "298829e3f5b03d4b3f87766b655eb3632099f6ea737e5e0d02da6ba03fcd72dd"
},
"account" : "alice"
},
"value" : {
"TokenTx" : {
"MintNFTWithMemo" : {
"networkId" : 2021,
"createdAt" : "2023-01-11T19:05:00Z",
"tokenDefinitionId" : "nft-with-precision",
"tokenId" : "2022061710000513118",
"rarity" : "EPIC",
"dataUrl" : "https://d3j8b1jkcxmuqq.cloudfront.net/temp/collections/TEST_NOMM4/NFT_ITEM/F7A92FB1-B29F-4E6F-BEF1-47C6A1376D68.jpg",
"contentHash" : "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"output" : "alice",
"memo" : "Test Minting NFT #2022061710000513118"
}
}
}
}
]
```
```json
["018edc66aa45e303a2621e5a981c2a2ed5f262802498888814a1844c04b12bd3"]
```
* BurnNFT
* > 토큰 소유자 서명
* Fields
* TokenDefinitionID(string)
* Input: SignedTxHash
* UpdateNFT
* > MinterGroup 에 속한 account 서명
* Fields
* TokenDefinitionID(string)
* Example
```json
[
{
"sig": {
"sig": {
"v": 28,
"r": "1ec82ef3e977dd8e6857e6d77b7955e57bc8d7081730198372f4740c588f0c80",
"s": "65031c7011d8aceae4bfbd90049b2bb4c458050988368b3ea3017fb7402c0c03"
},
"account": "alice"
},
"value": {
"TokenTx": {
"UpdateNFT": {
"networkId": 2021,
"createdAt": "2023-01-11T19:06:00Z",
"tokenDefinitionId": "nft-with-precision",
"tokenId": "2022061710000513118",
"rarity": "EPIC",
"dataUrl": "https://d3j8b1jkcxmuqq.cloudfront.net/temp/collections/TEST_NOMM4/NFT_ITEM/F7A92FB1-B29F-4E6F-BEF1-47C6A1376D68.jpg",
"contentHash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"output": "alice",
"memo": "Test Updating NFT #2022061710000513118"
}
}
}
]
```
```json
["e4d85bd90857a9be1e363a10c6543de0a7826966378e2bdb0195572a87e7c1be"]
```
* TransferFungibleToken
* > 토큰 보유자 서명
* Fields
* TokenDefinitionID(string)
* Inputs: Set[SignedTxHash]: UTXO Hash, 모든 토큰 종류는 동일해야 함
* Outputs: Map[AccountName, Amount]
* *(optional)* Memo(string)
* Example
```json
[
{
"sig" : {
"sig" : {
"v" : 28,
"r" : "09a5f46d29bd8598f04cb6db32627aadd562e30e181135c2898594080db6aa79",
"s" : "340abd1b6618d3bbf4b586294a4f902942f597672330563a43591a14be0a6504"
},
"account" : "alice"
},
"value" : {
"TokenTx" : {
"TransferFungibleToken" : {
"networkId" : 1000,
"createdAt" : "2022-06-09T09:00:00Z",
"tokenDefinitionId" : "test-token",
"inputs" : [
"a3f35adb3d5d08692a7350e61aaa28da992a4280ad8e558953898ef96a0051ca"
],
"outputs" : {
"bob" : 10,
"alice" : 90
},
"memo" : "transfer from alice to bob"
}
}
}
}
]
```
```json
["cb3848af6eb3c006c8aa663711d5fcfa2d6b1ccdcaf9837e273a96cc5386785e"]
```
* TransferNFT
* > 토큰 보유자 서명
* Fields
* TokenDefinitionID(string)
* TokenID(string)
* Input: SignedTxHash
* Output: AccountName
* *(optional)* Memo(string)
* Example
```json
[
{
"sig" : {
"sig" : {
"v" : 27,
"r" : "c443ed5eda3d484bcda7bf77f030d3f6c20e4130d9bc4e03ca75df3074b40239",
"s" : "2e7a19f1baee2099ccbef500e7ceb03c5053957a55085ef52b21c022c43242d9"
},
"account" : "alice"
},
"value" : {
"TokenTx" : {
"TransferNFT" : {
"networkId" : 1000,
"createdAt" : "2022-06-09T09:00:00Z",
"definitionId" : "test-token",
"tokenId" : "2022061710000513118",
"input" : "6040003b0020245ce82f352bed95dee2636442efee4e5a15ee3911c67910b657",
"output" : "bob",
"memo" : null
}
}
}
}
]
```
```json
["1e46633eb70ec8ea484aeb0ef2e7916021b4fcc591712c4ce0514c63c897c6c9"]
```
* EntrustFungibleToken 토큰 위임
* > 토큰 보유자 서명
* Fields
* definitionId: TokenDefinitionId 맡길 토큰 종류
* amount: 맡길 토큰 수량
* inputs: Set[SignedTxHash] 입력에 사용할 트랜잭션 해시값.
* to: Account 위임할 계정. 일반적으로 playnomm
* Results
* remainder: Amount 자신에게 돌아오는 수량
* Example
```json
[
{
"sig" : {
"sig" : {
"v" : 27,
"r" : "8d438670820bb788f0ef7106aa55c5fa2fa9c898eaded4d92f29d3c21a99c127",
"s" : "1545783ca442a5ae2fdd347c79286a1c62256cd91ac76cb392f28dc190ac9c8a"
},
"account" : "alice"
},
"value" : {
"TokenTx" : {
"EntrustFungibleToken" : {
"networkId" : 1000,
"createdAt" : "2022-06-09T09:00:00Z",
"definitionId" : "test-token",
"amount" : 1000,
"inputs" : [
"a3f35adb3d5d08692a7350e61aaa28da992a4280ad8e558953898ef96a0051ca"
],
"to" : "alice"
}
}
}
}
]
```
```json
["45df6a88e74ea44f2d759251fed5a3c319e7cf9c37fafa7471418fec7b26acce"]
```
* EntrustNFT NFT 위임
* > NFT 보유자 서명
* Fields
* definitionId(string)
* tokenId(string)
* input: SignedTxHash
* to: Account 위임할 계정. 일반적으로 playnomm
* Example
```json
[
{
"sig" : {
"sig" : {
"v" : 27,
"r" : "05705f380f7a7fbad853094f69ff1527703476be30d2ac19f90a24a7900100c0",
"s" : "37fac4695829b188ebe3d8238259a212ba52588c4593a51ef81631ab9ab90581"
},
"account" : "alice"
},
"value" : {
"TokenTx" : {
"EntrustNFT" : {
"networkId" : 1000,
"createdAt" : "2020-06-09T09:00:00Z",
"definitionId" : "test-token",
"tokenId" : "2022061710000513118",
"input" : "6040003b0020245ce82f352bed95dee2636442efee4e5a15ee3911c67910b657",
"to" : "alice"
}
}
}
}
]
```
```json
["10cb0802f3dfc85abb502bad260120a424fc583016db84d384904c1c0a580955"]
```
* DisposeEntrustedFungibleToken 위임된 토큰 처분
* 위임받은 계정(일반적으로 playnomm) 서명
* Fields
* definitionID(string)
* inputs: Set[SignedTxHash]: EntrustFungibleToken 트랜잭션의 UTXO Hash
* outputs: Map[AccountName, Amount]
* 토큰을 받아갈 계정과 받아갈 양. 비어 있으면 전체를 원주인에게 반환한다.
* Example
```json
[
{
"sig" : {
"sig" : {
"v" : 28,
"r" : "fb6c99c0e26da04e8dc0855ea629708a17a8deabfabb5a488ba9faa001c4a31f",
"s" : "7de70d3fd15176451e46856af2dbedf05e58d7cfc0bfb0e0fac1b6d06550f5d3"
},
"account" : "alice"
},
"value" : {
"TokenTx" : {
"DisposeEntrustedFungibleToken" : {
"networkId" : 1000,
"createdAt" : "2020-06-10T09:00:00Z",
"definitionId" : "test-token",
"inputs" : [
"45df6a88e74ea44f2d759251fed5a3c319e7cf9c37fafa7471418fec7b26acce"
],
"outputs" : {
"bob" : 1000
}
}
}
}
}
]
```
```json
["377fef6a1d85707bb7d84c9b3f5f2a2e409ce57084fbb15a6b200a1237d04119"]
```
* DisposeEntrustedNFT 위임된 NFT 처분
* 위임받은 계정(일반적으로 playnomm) 서명
* Fields
* definitionID(string)
* tokenID(string)
* input: SignedTxHash
* output: Option[AccountName]
* NFT를 받아갈 계정. 없으면 원주인에게로 반환한다.
* Example
```json
[
{
"sig" : {
"sig" : {
"v" : 28,
"r" : "a03080b98925010e241783482e83a5fdfc25343406564a4e3fc4e6b2535657d3",
"s" : "1de0ede5ebeba4aea455094ac1b58fc24ad943f0a5422a93f60a4f2b8b59b982"
},
"account" : "alice"
},
"value" : {
"TokenTx" : {
"DisposeEntrustedNFT" : {
"networkId" : 1000,
"createdAt" : "2020-06-10T09:00:00Z",
"definitionId" : "test-token",
"tokenId" : "2022061710000513118",
"input" : "10cb0802f3dfc85abb502bad260120a424fc583016db84d384904c1c0a580955",
"output" : "bob"
}
}
}
}
]
```
```json
["83c783f31b95cc4a713a921ec1df0725c6675b999ba6285a70c1f777615e4281"]
```
* CreateSnapshots
* > MinterGroup에 속한 Account의 서명
* Fields
* definitionID(string)
* tokenID(string)
* definitionIds: Set[TokenDefinitionId]
* *(optional)* Memo(string)
* Example
```json
[
{
"sig" : {
"sig" : {
"v" : 27,
"r" : "2a771418871b3fcfa43a0b00821fce6d9ecec40a1cf2c5ebff4489377c7d0f01",
"s" : "640b2af02ee4a713d22d2e16e0acd2c61ee1195aa16254cc6481a926d772d866"
},
"account" : "alice"
},
"value" : {
"TokenTx" : {
"CreateSnapshots" : {
"networkId" : 2021,
"createdAt" : "2023-01-11T19:09:00Z",
"definitionIds" : [
"LM",
"nft-with-precision"
],
"memo" : "Snapshot for NFT"
}
}
}
}
]
```
```json
["e9fecfafd40e655ac761730bcbb9be524f39370ffa9a272f875275d6cdc50818"]
```
### Reward
* RegisterDao 신규 DAO 등록. Group은 미리 생성해 두어야 한다.
* > Group Coordinator 서명. 일반적으로는 `playnomm`
* Fields
* GroupId(string)
* DaoAccountName(string)
* 다오 보상 충전용 계정. 여기에 들어온 금액을 매주 정해진 룰에 따라 보상한다. Unique account이어야 한다.
* Moderators: Set[Account]
* 최초 모더레이터 목록
* Example
```json
[
{
"sig" : {
"sig" : {
"v" : 27,
"r" : "d4b2d1cfe009e0e5b6dea67779fd898a7f1718e7b1869b5b36b6daacc68e88f6",
"s" : "42d8c69e964109ceab5996abdbc59d53661904e6b56337599e9c5beebe665d51"
},
"account" : "alice"
},
"value" : {
"RewardTx" : {
"RegisterDao" : {
"networkId" : 1000,
"createdAt" : "2020-06-09T09:00:00Z",
"groupId" : "sample-dao-group-id",
"daoAccountName" : "sample-dao-group-account",
"moderators" : [
"alice"
]
}
}
}
}
]
```
```json
["dabd1e1603805080722c6397568e6fc4ef384736a2bf95bc52e0f53acd43bea3"]
```
* UpdateDao DAO 정보 업데이트. 그룹 조정자가 업데이트 권한을 갖는다.
* > Group Coordinator 서명. 일반적으로는 `playnomm`
* Fields
* GroupId(string)
* Moderators: Set[Account]
* 모더레이터 목록
* RecordActivity 활동정보 추가. 그룹 조정자가 업데이트 권한을 갖는다.
* > Group Coordinator 서명. 일반적으로는 `playnomm`
* Fields
* timestamp: 기준시점
* userActivity: Map[AccountName, Seq[DaoActivity]] 사용자활동 요약 정보
* DaoActivity 활동정보
* point 총 점수
* description 어떤 활동으로 받은 점수인지 간략한 표시
* tokenReceived: Map[TokenId, Seq[DaoActivity]] 토큰이 받은 사용자활동 요약정보
* DaoActivity 활동정보
* point 총 점수
* description 어떤 활동으로 받은 점수인지 간략한 표시
* Example
```json
[
{
"sig" : {
"sig" : {
"v" : 27,
"r" : "95aff6586d03fa7c66165d9bb49f2a2fd54650f2776c728401c664622d5e2d4c",
"s" : "2cff82c55822d3266add84ea5853dbc86cf47f24e5787080b76e58681477ba09"
},
"account" : "alice"
},
"value" : {
"RewardTx" : {
"RecordActivity" : {
"networkId" : 2021,
"createdAt" : "2023-01-10T18:01:00Z",
"timestamp" : "2023-01-09T09:00:00Z",
"userActivity" : {
"bob" : [
{
"point" : 3,
"description" : "like"
}
],
"carol" : [
{
"point" : 3,
"description" : "like"
}
]
},
"tokenReceived" : {
"text-20230109-0000" : [
{
"point" : 2,
"description" : "like"
}
],
"text-20230109-0001" : [
{
"point" : 2,
"description" : "like"
}
],
"text-20230109-0002" : [
{
"point" : 2,
"description" : "like"
}
]
}
}
}
}
}
]
```
```json
["f08043c06fa17ffaf5c86121db683f5aa879bbf0194de3cac703b0572feaa4cd"]
```
* OfferReward 보상 제공. TransferFungibleToken과 같은 형태로 보상을 실행한다
* > 보상을 보낼 계정
* Fields
* TokenDefinitionID(string)
* Inputs: Set[SignedTxHash]: UTXO Hash, 모든 토큰 종류는 동일해야 함
* Outputs: Map[AccountName, Amount]
* *(optional)* Memo(string)
* Example
```json
```
```json
```
* BuildSnapshot: 보상을 위한 스냅샷 생성. 사용자가 한 활동, 토큰이 받은 활동, 토큰 소유보상의 세 가지 스냅샷을 동시에 만든다
* > 보상 실행 주체. 일반적으로 Playnomm
* Fields
* timestamp: 보상 기준 시점. 이 시점 일주일 전부터 현재 시점까지의 자료를 모아 스냅샷을 생성한다.
* accountAmount: 계정활동 총 보상량
* tokenAmount: 토큰이 받을 총 보상량
* ownershipAmount: 토큰 보유에 따르는 총 보상량
* Example
```json
[
{
"sig" : {
"sig" : {
"v" : 28,
"r" : "004b940e651bb950350157116fbfedf5ec98eed68068cea2b666a9e2b52b9588",
"s" : "17eb8460877a7d212fac4a59caf7abf1cb96c145f5cae41a8ffce55df226f003"
},
"account" : "alice"
},
"value" : {
"RewardTx" : {
"BuildSnapshot" : {
"networkId" : 2021,
"createdAt" : "2023-01-11T18:01:00Z",
"timestamp" : "2023-01-09T09:00:00Z",
"accountAmount" : 0,
"tokenAmount" : 0,
"ownershipAmount" : 100000000000000000000000
}
}
}
}
]
```
```json
["da140a6816e9437c0583b34f64636ba9b3fca02721f2ff90b03460c061067cfa"]
```
* ExecuteOwnershipReward: 스냅샷의 자료를 기반으로 토큰 소유 보상 실행.
* 보상 실행 주체. 일반적으로 Playnomm
* Fields
* definitionId 보상에 지급할 토큰 정의 ID. 일반적으로 LM.
* inputs: Set[TxHash] 보상에 사용할 UTXO
* targets: Set[TokenId] 보상할 개별 NFT 토큰 ID
* Results
* outputs: Map[Account, Amount] 각 계정별 보상결과
* Example
```json
[
{
"sig" : {
"sig" : {
"v" : 27,
"r" : "2289a570405738a66d75c1eeae451f899cbcc3bd7fd98b4b4d5aaf807c965211",
"s" : "0364409abf9829ae5ca38b9c31ee0bcc5ce4dabcff3a5d0be180dd925ec51096"
},
"account" : "alice"
},
"value" : {
"RewardTx" : {
"ExecuteOwnershipReward" : {
"networkId" : 2021,
"createdAt" : "2023-01-11T18:01:00Z",
"inputs" : [
"270650f92f584d9dbbffb99f3a915dc908fbea28bc3dbf34b8cdbe49c4070611"
],
"targets" : [
"1234567890",
"1234567891"
]
}
}
}
}
]
```
```json
["c7824fd901b71918f10663a2990988b3a933353aebc5d1b80f39d78ce43be1ca"]
```
### AgendaTx
* SuggestSimpleAgenda 투표 의제 제안.
* > 투표 의제를 제안할 계정. 일반적으로 playNomm
* Fields
* title(string)
* votingToken: TokenDefinitionId(string) 일반적으로 LM
* voteStart: Instant
* voteEnd: Instant
* voteOption: Map[String, String]
* Example
```json
[
{
"sig" : {
"sig" : {
"v" : 27,
"r" : "dc6e9660b33fdc71b14675e7a7a888fe32e4b3bb6264a3a4e90f572518e53aa8",
"s" : "069a1c0c2c602f2342384d545aab17e5dd2629efc9f8605dead14df599b5fc96"
},
"account" : "alice"
},
"value" : {
"AgendaTx" : {
"SuggestSimpleAgenda" : {
"networkId" : 2021,
"createdAt" : "2023-01-11T18:01:00Z",
"title" : "Let the world know about LeisureMeta!",
"votingToken" : "LM",
"voteStart" : "2023-01-11T18:01:00Z",
"voteEnd" : "2023-01-12T18:01:00Z",
"voteOptions" : {
"1" : "Yes",
"2" : "No"
}
}
}
}
}
]
```
```json
["2475a387f22c248c5a3f09cea0ef624484431c1eaf8ffbbf98a4a27f43fabc84"]
```
* VoteSimpleAgenda 투표.
* > 투표하는 사용자계정
* Fields
* agendaTxHash: 투표할 SuggestSimpleAgenda 트랜잭션의 tx hash
* selectedOption: 투표내용
* Example
```json
[
{
"sig" : {
"sig" : {
"v" : 28,
"r" : "89a108a5a933a8d04486384dc90521d0ca5faba1d3a09524068c22936aa2b5ea",
"s" : "2347e77fa7d1a4f6d10712bb7c5cfb1746f0aef65825dbf03f201fe5e594ee2f"
},
"account" : "alice"
},
"value" : {
"AgendaTx" : {
"VoteSimpleAgenda" : {
"networkId" : 2021,
"createdAt" : "2023-01-11T19:01:00Z",
"agendaTxHash" : "2475a387f22c248c5a3f09cea0ef624484431c1eaf8ffbbf98a4a27f43fabc84",
"selectedOption" : "1"
}
}
}
}
]
```
```json
["07dd86c19884881e1ef037eac4553b735545c03612c5fe368a07189464ad154b"]
```
## Other API
| Method | URL | Description |
| ------ | --------------------------------- | -------------------------------- |
| `GET` | **/account/{accountName}** | 계정정보 조회 |
| `GET` | **/eth/{ethAddress}** | 이더리움 주소와 연동된 계정 조회 |
| `GET` | **/dao** | DAO 목록 조회 |
| `GET` | **/group/{groupID}** | 그룹 정보 조회 |
| `GET` | **/offering/{offeringID}** | Offering 정보 조회 |
| `GET` | **/status** | 블록체인 상태 조회 |
| `GET` | **/token-def/{definitionID}** | 토큰 정의 정보 조회 |
| `GET` | **/token/{tokenID}** | 토큰 정보 조회 |
| `GET` | **/token-hist/{txHash}** | 토큰 과거 정보 조회 |
| `GET` | **/snapshot/account/{account}** | 보상받을 활동 조회 |
| `GET` | **/snapshot/token/{tokenID}** | 보상받을 토큰 점수 조회 |
| `GET` | **/snapshot/ownership/{tokenID}** | 받을 토큰 소유보상 점수 조회 |
| `GET` | **/rewarded/account/{account}** | 최근에 받은 활동보상 조회 |
| `GET` | **/rewarded/token/{tokenID}** | 최근에 받은 토큰보상 조회 |
| `GET` | **/rewarded/ownership/{tokenID}** | 최근에 받은 토큰 소유보상 조회 |
| `GET` | **/snapshot-state/{definitionID}** | 토큰정의 스냅샷 상태 조회 |
| `GET` | **/snapshot-balance/ {Account}/{TokenDefinitionID}/{SnapshotID}** | 토큰 스냅샷 잔고 조회 |
| `GET` | **/nft-snapshot-balance/ {Account}/{TokenDefinitionID}/{SnapshotID}** | NFT 스냅샷 잔고 조회 |
## State
Merkle Trie로 관리되는 블록체인 내부 상태들. 키가 사전식으로 정렬되어 있어서 순회 가능하고, StateRoot로 요약가능하다.
### Account
* NameState: AccountName => AccountData
* AccountData
* *(optional)* ethAddress
* *(optional)* guardian (account)
* AccountKeyState: (AccountName, PublicKeySummary) => Desription
* Description에는 추가된 시각이 포함되어 있어야 함
### Group
* GroupState: GroupID => GroupInfo
* GroupInfo
* Group Name
* Coordinator
* GroupAccountState: (GroupID, AccountName) => ()
### Token
* TokenDefinitionState: TokenDefinitionID(string)=> TokenDefinition
* TokenDefinition
* TokenDefinitionID(string)
* Name(string)
* *(optional)* Symbol(string)
* *(optional)* AdminGroup: GroupId
* TotalAmount
* *(optional)* NftInfo
* Minter: AccountName(string)
* Rarity: Map[(Rarity(string), Weight)]
* DataUrl(string)
* ContentHash: uint256
* NftState: TokenID => NftState
* NftState
* TokenID
* TokenDefinitionID
* Rarity
* Weight
* CurrentOwner: Account
* RarityState: (TokenDefinitionID, Rarity, TokenID) => ()
* FungibleBalanceState: (AccountName, TokenDefinitionID, TransactionHash) => ()
* NftBalanceState: (AccountName, TokenID, TransactionHash) => ()
* EntrustFungibleBalanceState: (AccountName, AccountName, TokenDefinitionId, TransactionHash) => ()
* EntrustNftBalanceState: (AccountName, AccountName, TokenId, TransactionHash) => ()
### Reward
* DaoState: GroupID => DaoInfo
* DaoInfo
* Moderators: Set[AccountName]
* AccountActivityState: (Account, Instant) => Seq[ActivityLog]
* ActivityLog
* account 포인트를 획득한 계정
* point 총 점수
* description 묘사
* txHash 근거가 되는 RecordActivity 트랜잭션 해시값
* TokenReceivedState: (TokenId, Instant) => Seq[ActivityLog]
* AccountSnapshotState: (Account) => ActivitySnapshot
* ActivitySnapshot
* account
* from: Instant
* to: Instant
* point 총 포인트
* definitionId 보상받을 토큰 종류. 일반적으론 LM
* amount 보상량
* backlog: Set[TxHash] 해당 카운트의 근거 RecordActivity의 집합
* TokenSnapshotState: (TokenId) => ActivitySnapshot
* OwnershipSnapshotState: (TokenId) => OwnershipSnapshot
* OwnershipSnapshot
* account
* timestamp 기준 시점
* point 포인트. 일반적으론 해당 NFT의 Rarity 점수
* definitionId 보상받을 토큰 종류. 일반적으론 LM
* amount 보상량
* AccountRewardedState: (Account) => ActivityRewardLog
* ActivityRewardLog
* ActivitySnapshot
* ExecuteReward TxHash
* TokenRewardedState: (TokenId) => ActivityRewardLog
* OwnershipRewardedState: (TokenId) => OwnershipRewardLog
* OwnershipRewardLog
* OwnershipShapshot
* ExecuteReward TxHash
================================================
FILE: docs/api_with_example.md
================================================
# LeisureMeta Chain API with Example
`POST` **/txhash** 트랜잭션 해시값 계산
아직 해시값 계산 모듈을 제공하지 않으므로, 여기에 트랜잭션을 보내면 해시값을 계산해준다. 나온 해시값에 서명해서 정식으로 트랜잭션을 집어넣으면 된다.
```json
[
{
"AccountTx" : {
"CreateAccount" : {
"networkId" : 1000,
"createdAt" : "2020-05-22T09:00:00Z",
"account" : "alice",
"guardian" : null
}
}
}
]
```
```json
["396fb3ef2ecdb800126027a802e26eb2e7e1d47fee28f24287fb836cdafc6f1e"]
```
`POST`**/tx** 트랜잭션 제출
(Private Key `b229e76b742616db3ac2c5c2418f44063fcc5fcc52a08e05d4285bdb31acba06`으로 서명한 예시)
```json
[
{
"sig" : {
"sig" : {
"v" : 27,
"r" : "c0cf8bb197d5f0a562fd76200f09480f676f31970e982f65bc1efd707504ef73",
"s" : "7ad50c3987ce4a9007d093d25caaf701436824dafc6290d8e477b8f1c8b6771d"
},
"account" : "alice"
},
"value" : {
"AccountTx" : {
"CreateAccount" : {
"networkId" : 1000,
"createdAt" : "2020-05-22T09:00:00Z",
"account" : "alice",
"guardian" : null
}
}
}
}
]
```
```json
["396fb3ef2ecdb800126027a802e26eb2e7e1d47fee28f24287fb836cdafc6f1e"]
```
`GET` **/status** 노드 현재상태조회
트랜잭션 하나가 들어가서 블록이 하나 생성되었으므로 block number 값이 1이 된다.
(추후 트랜잭션이 없는 빈 블록 하나를 더 찍어서 거래완결을 표시할 예정)
```json
{
"networkId": 1000,
"genesisHash": "50f1634b0534d9eaff9bb4084b38839f710b5822a599a10c3b106a19a4315127",
"bestHash": "a9735ba3420e7d9be5f26b28b035f3141d4586e1015c237a67aa46c90a65b8ca",
"number": 1
}
```
`GET` **/block**/a9735ba3420e7d9be5f26b28b035f3141d4586e1015c237a67aa46c90a65b8ca 블록 정보 조회
best hash값을 넣어서 최신 블록의 정보를 조회한다
```json
{
"header": {
"number": 1,
"parentHash": "50f1634b0534d9eaff9bb4084b38839f710b5822a599a10c3b106a19a4315127",
"stateRoot": {
"account": {
"namesRoot": "7a3a362149605d574b2eccdf85a0bfe7ca579fd2cfa0a2c19a2b601731d5ddbd",
"keyRoot": null
}
},
"transactionsRoot": "962dfb46a6439d48efd72e1a21356911f1f5882843c76a3c2b2a2709d44b25eb",
"timestamp": "2022-05-29T18:13:54.425Z"
},
"transactionHashes": [
"396fb3ef2ecdb800126027a802e26eb2e7e1d47fee28f24287fb836cdafc6f1e"
],
"votes": [
{
"v": 28,
"r": "f6d37c7994cb9f5f84b2a100c2346d6a0aec7e48e14872096cd32a90dc3c43ec",
"s": "52ad670b041581c3d1d246fb09df36b8a1201db76f4cb2113e0759e16541be20"
}
]
}
```
`GET` **/tx**/396fb3ef2ecdb800126027a802e26eb2e7e1d47fee28f24287fb836cdafc6f1e 트랜잭션 정보 조회
블록에 기록된 트랜잭션 해시값 하나에 대한 정보를 취득한다
```json
{
"signedTx": {
"sig": {
"sig": {
"v": 27,
"r": "c0cf8bb197d5f0a562fd76200f09480f676f31970e982f65bc1efd707504ef73",
"s": "7ad50c3987ce4a9007d093d25caaf701436824dafc6290d8e477b8f1c8b6771d"
},
"account": "alice"
},
"value": {
"AccountTx": {
"CreateAccount": {
"networkId": 1000,
"createdAt": "2020-05-22T09:00:00Z",
"account": "alice",
"guardian": null
}
}
}
},
"result": null
}
```
`GET` **/account**/alice 계정정보 조회
```json
{
"guardian": null,
"publicKeySummaries": {
"99f681d29754aeee1426ef991b745a4f662e620c": {
"description": "Automatically added in account creation",
"addedAt": "2020-05-22T09:00:00Z"
}
}
}
```
================================================
FILE: docs/creator-dao-documentation.md
================================================
# Creator Dao
## DAO 정보
* id(Utf8): DAO ID
* name(Utf8): 이름
* description(Utf8): 설명
## DAO 참여자
* Founder 창립자
* 크리에이터 본인
* DAO에 관한 모든 권한
* Coordinator 시스템 관리자
* playNomm 계정으로 세팅
* DAO에 관한 모든 권한
* Moderator 일반 관리자
* 해산 제외한 나머지 관리권한
* Member 참여자
* 표결 참여
* Applicant
* 가입 신청한 사람
* 그 외
* 가입신청
## 액션
* Coordinator만 가능
* ReplaceCoordinator
* Founder, Coordinator만 가능
* DAO 개설
* 관리자 임명, 해임, DAO 해산
* Moderator 이상 가능
* DAO 정보변경
* 회원 가입, 탈퇴, 안건 발의
* Member 이상 가능
* 표결 참여
* 그 외
* Dao 가입신청
## 트랜잭션
### CreateCreatorDao (개설)
Founder나 Coordinator가 새로운 DAO를 만든다.
```json
{
"sig": {
"sig": {
"v": 27,
"r": "62d7c7ddf8bea783b8ed59906b2f5db00b9e53031d6407933d7c4a80c7157f35",
"s": "2d546c7d0f0fdf058e5bdf74b39cb2d3db34aa1dcdd6b2a76ea6504655b12b0f"
}
"account": "founder",
},
"value": {
"CreatorDaoTx": {
"CreateCreatorDao": {
"networkId": 102,
"createdAt": "2024-03-15T09:28:41.339Z",
"id": "dao_001",
"name": "Art Creators DAO",
"description": "A DAO for digital art creators",
"founder": "creator001",
"coordinator": "playnomm"
}
}
}
}
```
### UpdateCreatorDao (정보변경)
Moderator 이상 권한을 가진 사용자가 DAO 정보를 수정한다.
```json
{
"sig": {
"sig": {
"v": 27,
"r": "72d7c7ddf8bea783b8ed59906b2f5db00b9e53031d6407933d7c4a80c7157f35",
"s": "3d546c7d0f0fdf058e5bdf74b39cb2d3db34aa1dcdd6b2a76ea6504655b12b0f"
},
"account": "moderator"
},
"value": {
"CreatorDaoTx": {
"UpdateCreatorDao": {
"networkId": 102,
"createdAt": "2024-03-15T10:28:41.339Z",
"id": "dao_001",
"name": "Digital Art Creators DAO",
"description": "A DAO for digital art creators and collectors"
}
}
}
}
```
### DisbandCreatorDao (해산)
Founder나 Coordinator만 DAO를 해산할 수 있다.
```json
{
"sig": {
"sig": {
"v": 27,
"r": "82d7c7ddf8bea783b8ed59906b2f5db00b9e53031d6407933d7c4a80c7157f35",
"s": "4d546c7d0f0fdf058e5bdf74b39cb2d3db34aa1dcdd6b2a76ea6504655b12b0f"
},
"account": "founder"
},
"value": {
"CreatorDaoTx": {
"DisbandCreatorDao": {
"networkId": 102,
"createdAt": "2024-03-15T11:28:41.339Z",
"id": "dao_001"
}
}
}
}
```
### ReplaceCoordinator (코디네이터 변경)
현재 Coordinator만 새로운 Coordinator를 지정할 수 있다.
```json
{
"sig": {
"sig": {
"v": 27,
"r": "92d7c7ddf8bea783b8ed59906b2f5db00b9e53031d6407933d7c4a80c7157f35",
"s": "5d546c7d0f0fdf058e5bdf74b39cb2d3db34aa1dcdd6b2a76ea6504655b12b0f"
},
"account": "coordinator"
},
"value": {
"CreatorDaoTx": {
"ReplaceCoordinator": {
"networkId": 102,
"createdAt": "2024-03-15T12:28:41.339Z",
"id": "dao_001",
"newCoordinator": "playnomm2"
}
}
}
}
```
### AddMembers (참여자 추가)
Moderator 이상 권한을 가진 사용자가 새로운 멤버를 추가할 수 있다.
```json
{
"sig": {
"sig": {
"v": 27,
"r": "a2d7c7ddf8bea783b8ed59906b2f5db00b9e53031d6407933d7c4a80c7157f35",
"s": "6d546c7d0f0fdf058e5bdf74b39cb2d3db34aa1dcdd6b2a76ea6504655b12b0f"
},
"account": "moderator"
},
"value": {
"CreatorDaoTx": {
"AddMembers": {
"networkId": 102,
"createdAt": "2024-03-15T13:28:41.339Z",
"id": "dao_001",
"members": ["user001", "user002", "user003"]
}
}
}
}
```
### RemoveMembers (참여자 제외)
Moderator 이상 권한을 가진 사용자가 멤버를 제외할 수 있다.
```json
{
"sig": {
"sig": {
"v": 27,
"r": "b2d7c7ddf8bea783b8ed59906b2f5db00b9e53031d6407933d7c4a80c7157f35",
"s": "7d546c7d0f0fdf058e5bdf74b39cb2d3db34aa1dcdd6b2a76ea6504655b12b0f"
},
"account": "moderator",
}
},
"value": {
"CreatorDaoTx": {
"RemoveMembers": {
"networkId": 102,
"createdAt": "2024-03-15T14:28:41.339Z",
"id": "dao_001",
"members": ["user003"]
}
}
}
}
```
### PromoteModerators (관리자 임명)
Founder나 Coordinator가 일반 멤버를 Moderator로 승급시킬 수 있다.
```json
{
"sig": {
"sig": {
"v": 27,
"r": "d2d7c7ddf8bea783b8ed59906b2f5db00b9e53031d6407933d7c4a80c7157f35",
"s": "9d546c7d0f0fdf058e5bdf74b39cb2d3db34aa1dcdd6b2a76ea6504655b12b0f"
},
"account": "founder"
},
"value": {
"CreatorDaoTx": {
"PromoteModerators": {
"networkId": 102,
"createdAt": "2024-03-15T15:28:41.339Z",
"id": "dao_001",
"members": ["user001"]
}
}
}
}
```
### DemoteModerators (관리자 해임)
Founder나 Coordinator가 Moderator를 일반 멤버로 강등시킬 수 있다.
```json
{
"sig": {
"sig": {
"v": 27,
"r": "d2d7c7ddf8bea783b8ed59906b2f5db00b9e53031d6407933d7c4a80c7157f35",
"s": "9d546c7d0f0fdf058e5bdf74b39cb2d3db34aa1dcdd6b2a76ea6504655b12b0f"
},
"account": "founder"
},
"value": {
"CreatorDaoTx": {
"DemoteModerators": {
"networkId": 102,
"createdAt": "2024-03-15T16:28:41.339Z",
"id": "dao_001",
"members": ["user001"]
}
}
}
}
```
## 공통 사항
- 모든 트랜잭션에는 networkId와 createdAt을 포함한다.
- 서명은 NamedSignature 형식을 사용하고, 권한에 맞는 이름을 포함한다.
- 멤버 관련 작업(추가/제거/승급/강등)은 배열로 여러 계정을 한번에 처리할 수 있다.
================================================
FILE: docs/dao-voting-system-design-english.md
================================================
# LeisureMeta DAO Voting System Design Document
## 1. Project Background and Objective
LeisureMeta is an innovative blockchain platform that supports DAO projects composed of creators and fans. The DAO Voting system designed in this document aims to enable these DAOs to make important decisions, such as fund allocation, in a transparent and democratic manner.
LeisureMeta Chain inherits the advantages of existing DAO systems while providing the following innovative features:
1. High Scalability and Low Cost:
- Addresses the high gas fees and low throughput issues faced by existing Ethereum-based DAOs.
- Internal transactions on LeisureMeta Chain are gas-free, enabling frequent voting and decision-making in a cost-effective manner.
2. Integrated Snapshot Functionality:
- While existing solutions like ERC20Snapshot supported snapshot-based voting, LeisureMeta Chain implements this at the native level.
- Snapshots can be created and queried consistently for all tokens and NFTs on the chain, enabling voting with various assets without complex smart contracts.
3. Seamless Integration of Diverse Voting Methods:
- Supports various voting methods such as one person one vote, token-weighted, and NFT-based voting within a single system.
- Unlike existing solutions that required separate contracts or implementations for each method, LeisureMeta Chain allows easy implementation of all methods through a unified API.
4. Optimized for Creator Economy:
- Enables closer relationships between creators and fans through governance utilizing NFTs and fan tokens.
- While existing DAO systems mainly focused on finance or protocol governance, LeisureMeta is specialized in building a new economic ecosystem centered around creators.
5. Enhanced User Experience:
- All functions including voting, token trading, and NFT minting occur on a single chain, greatly improving user experience.
- Eliminates the complexity and high entry barriers of existing multi-chain solutions while utilizing the advantages of each function.
6. Flexible Scalability:
- The modular structure of LeisureMeta Chain allows for easy addition of new voting methods or governance models in the future.
- This will be a crucial advantage in the rapidly evolving Web3 ecosystem.
This DAO Voting system aims to provide a governance platform where creators and fans can easily and effectively participate by maximizing these advantages of LeisureMeta Chain. While inheriting the strengths of existing solutions, we seek to present a new model of collaboration and value creation in the Web3 era through LeisureMeta's specialized features.
## 2. System Overview
LeisureMeta's DAO Voting system has the following characteristics:
1. A blockchain-based transparent and tamper-proof voting system
2. Voting based on token holdings at a specific point in time using snapshot functionality
3. Support for three voting methods: One person one vote, Fungible Token-based, and NFT-based
4. Flexible voting group settings using Token Definition ID
5. High accessibility due to internal transactions without Gas Fees
## 3. Technical Design
### 3.1 Snapshot Functionality
LeisureMeta's snapshot feature records token holdings at a specific point in time, allowing for balance inquiries regardless of subsequent token movements.
Related APIs:
- `GET /snapshot-state/{definitionID}`: Query token definition snapshot state
- `GET /snapshot-balance/{Account}/{TokenDefinitionID}/{SnapshotID}`: Query token snapshot balance
- `GET /nft-snapshot-balance/{Account}/{TokenDefinitionID}/{SnapshotID}`: Query NFT snapshot balance
### 3.2 DAO Voting System API
#### 3.2.1 Create Vote Proposal
```json
POST /tx
{
"VotingTx": {
"CreateVoteProposal": {
"networkId": 2021,
"createdAt": "2023-06-21T18:01:00Z",
"proposalId": "PROPOSAL-2023-001",
"title": "Community Fund Usage Proposal",
"description": "Fund allocation for Creator Support Program",
"votingPower": {
"LM": 12345
},
"voteStart": "2023-06-22T00:00:00Z",
"voteEnd": "2023-06-29T23:59:59Z",
"voteType": "TOKEN_WEIGHTED",
"voteOptions": {
"1": "Approve",
"2": "Reject",
"3": "Abstain"
},
"quorum": 1000000, // Minimum participation (e.g., 1,000,000 LM)
"passThreshold": 0.66 // Approval threshold (66%)
}
}
}
```
Example of NFT-based voting:
```json
POST /tx
{
"VotingTx": {
"CreateVoteProposal": {
"networkId": 2021,
"createdAt": "2023-06-21T18:01:00Z",
"proposalId": "PROPOSAL-2023-002",
"title": "Approval for New NFT Collection Launch",
"description": "Voting for approval of a new NFT collection proposed by the community",
"votingPower": {
"NFT-COLLECTION-001": 12347,
"NFT-COLLECTION-002": 12348
},
"voteStart": "2023-06-22T00:00:00Z",
"voteEnd": "2023-06-29T23:59:59Z",
"voteType": "NFT_BASED",
"voteOptions": {
"1": "Approve",
"2": "Reject"
},
"quorum": 100, // Minimum participation (number of NFTs)
"passThresholdNumer": 51, // Approval threshold numerator(51%)
"passThresholdDemon": 100, // Approval threshold denominator(100%)
}
}
}
```
#### 3.2.2 Cast Vote
```json
POST /tx
{
"VotingTx": {
"CastVote": {
"networkId": 2021,
"createdAt": "2023-06-23T10:30:00Z",
"proposalId": "PROPOSAL-2023-001",
"selectedOption": "1"
}
}
}
```
#### 3.2.3 Tally Votes
```json
POST /tx
{
"VotingTx": {
"TallyVotes": {
"networkId": 2021,
"createdAt": "2023-06-30T00:01:00Z",
"proposalId": "PROPOSAL-2023-001"
}
}
}
```
#### 3.2.4 Query Vote Proposal
```
GET /vote-proposal/{proposalId}
```
Response:
```json
{
"proposalId": "PROPOSAL-2023-001",
"title": "Community Fund Usage Proposal",
"description": "Fund allocation for Creator Support Program",
"votingTokens": ["LM"],
"snapshotId": 12345,
"voteStart": "2023-06-22T00:00:00Z",
"voteEnd": "2023-06-29T23:59:59Z",
"voteOptions": {
"1": "Approve",
"2": "Reject",
"3": "Abstain"
},
"quorum": 1000000,
"passThreshold": 0.66,
"status": "In Progress",
"currentResults": {
"1": 3500000,
"2": 1200000,
"3": 300000
},
"totalVotes": 5000000
}
```
#### 3.2.5 Query User Voting History
```
GET /vote-history/{account}
```
Response:
```json
[
{
"proposalId": "PROPOSAL-2023-001",
"votedAt": "2023-06-23T10:30:00Z",
"selectedOption": "1"
},
// ... other voting history
]
```
### 3.3 Voting Type Implementation
Voting types are specified through the `voteType` field in `CreateVoteProposal`:
1. One person one vote (`ONE_PERSON_ONE_VOTE`): Implemented to allow only one vote per account
2. Fungible Token-based voting (`TOKEN_WEIGHTED`): Voting rights proportional to LM token holdings
3. NFT-based voting (`NFT_BASED`): Voting rights based on NFT holdings
Implementation for each voting type:
1. One person one vote:
- The `votingTokens` field is ignored.
- Each account can cast one vote if it exists at the snapshot point.
2. Fungible Token-based voting (LM token):
- "LM" is specified in the `votingTokens`.
- Voting rights are granted based on LM token holdings at the snapshot point.
3. NFT-based voting:
- The Token Definition ID of the NFT collection is specified in `votingTokens` (e.g., "NFT-COLLECTION-001").
- Voting rights are granted based on the holdings of the specified NFT collection at the snapshot point.
- Each NFT has equal weight.
### 3.4 Utilizing Token Definition ID
The `votingTokens` field in `CreateVoteProposal` specifies the tokens used for voting. For TOKEN_WEIGHTED type, LM token is used, and for NFT_BASED type, the Token Definition ID of the relevant NFT collection is used.
### 3.5 Utilizing Snapshot ID
When creating a vote proposal, `snapshotId` is specified to grant voting rights based on token holdings at a specific snapshot point. This prevents the influence of token movements during the voting period and ensures fair voting.
## 4. Security and Transparency
- Transaction signatures: All transactions must be signed with the user's private key.
- Blockchain records: All voting-related transactions are permanently recorded on the blockchain, ensuring transparency.
- Snapshot-based voting: Voting rights are granted based on token holdings at a specific point in time, preventing vote manipulation.
- API security: HTTPS is used to enhance API communication security, and appropriate authentication/authorization mechanisms are implemented.
## 5. Socioeconomic Impact
LeisureMeta's DAO Voting system is expected to have the following socioeconomic impacts:
1. Democratization of the creator economy: Strengthening the relationship between creators and fans through direct participation in decision-making
2. Transparent fund management: Ensuring transparency in fund execution through a blockchain-based voting system
3. Community-driven growth: Encouraging active participation of community members through the DAO structure
4. New utilization of digital assets: Increasing the utility of digital assets through governance participation using NFTs and tokens
5. Decentralized decision-making: Providing opportunities for more stakeholders to participate in decision-making, moving away from centralized power structures
## 6. Implementation Roadmap
1. Implement and test snapshot functionality
- Build snapshot ID generation and management system
2. Implement DAO Voting system API
- Implement transaction processing for `CreateVoteProposal`, `CastVote`, `TallyVotes`
- Implement logic for each voting type
- Implement APIs for querying vote proposals and history
3. Implement blockchain state management
- Design state structure for storing vote proposals, participation, and results
4. Develop client application
- Implement user interface
- Implement communication logic with API
5. Testing and security audit
6. Beta version release and feedback collection
7. Official version release
## 7. Legal Considerations
This system merely provides a tool for decision-making and does not directly involve fund-raising or management. However, users must comply with relevant regulations in their respective countries, and LeisureMeta will continuously review regulatory compliance through legal consultation.
Key considerations:
1. Data privacy: Protect user information and comply with relevant regulations such as GDPR
2. Securities law: Ensure token-based voting does not violate securities regulations
3. Anti-Money Laundering (AML): Consider introducing KYC procedures if necessary
4. Smart contract audit: Regularly conduct security audits to eliminate vulnerabilities
## 8. Conclusion
LeisureMeta's DAO Voting system provides a fairer and more transparent decision-making platform by utilizing snapshot IDs. By supporting three voting methods (one person one vote, LM token-weighted, and NFT-based), it offers decision-making mechanisms suitable for various situations. This will foster democratic operation and sustainable growth of the LeisureMeta ecosystem.
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/LeisureMetaChainApi.scala
================================================
package io.leisuremeta.chain
package api
import java.time.Instant
import java.util.Locale
import io.circe.KeyEncoder
import io.circe.generic.auto.*
import scodec.bits.ByteVector
import sttp.model.StatusCode
import sttp.tapir.*
import sttp.tapir.CodecFormat.TextPlain
import sttp.tapir.generic.auto.*
import sttp.tapir.json.circe.*
import lib.crypto.{Hash, Signature}
import lib.datatype.{BigNat, UInt256, UInt256BigInt, UInt256Bytes, Utf8}
import api.model.{
Account,
AccountSignature,
Block,
GroupId,
NodeStatus,
Signed,
Transaction,
TransactionWithResult,
}
import api.model.account.EthAddress
import api.model.api_model.{
AccountInfo,
ActivityInfo,
BalanceInfo,
BlockInfo,
CreatorDaoInfo,
GroupInfo,
NftBalanceInfo,
RewardInfo,
TxInfo,
}
import api.model.creator_dao.CreatorDaoId
import api.model.token.{
NftState,
SnapshotState,
TokenDefinition,
TokenDefinitionId,
TokenId,
}
import api.model.reward.{
ActivitySnapshot,
DaoInfo,
OwnershipSnapshot,
OwnershipRewardLog,
}
import api.model.voting.{ProposalId, Proposal}
import api.model.token.SnapshotState.*
import io.leisuremeta.chain.api.model.Signed.TxHash
object LeisureMetaChainApi:
given Schema[UInt256Bytes] = Schema.string
given Schema[UInt256BigInt] = Schema(SchemaType.SInteger())
given Schema[BigNat] = Schema.schemaForBigInt.map[BigNat] {
(bigint: BigInt) => BigNat.fromBigInt(bigint).toOption
} { (bignat: BigNat) => bignat.toBigInt }
given Schema[Utf8] = Schema.string
given [K: KeyEncoder, V: Schema]: Schema[Map[K, V]] =
Schema.schemaForMap[K, V](KeyEncoder[K].apply)
given [A]: Schema[Hash.Value[A]] = Schema.string
given Schema[Signature.Header] = Schema(SchemaType.SInteger())
given Schema[Transaction] = Schema.derived[Transaction]
given hashValueCodec[A]: Codec[String, Hash.Value[A], TextPlain] =
Codec.string.mapDecode { (s: String) =>
ByteVector
.fromHexDescriptive(s)
.left
.map(new Exception(_))
.flatMap(UInt256.from) match
case Left(e) => DecodeResult.Error(s, e)
case Right(v) => DecodeResult.Value(Hash.Value(v))
}(_.toUInt256Bytes.toBytes.toHex)
given bignatCodec: Codec[String, BigNat, TextPlain] =
Codec.bigInt.mapDecode { (n: BigInt) =>
BigNat.fromBigInt(n) match
case Left(e) => DecodeResult.Error(n.toString(10), new Exception(e))
case Right(v) => DecodeResult.Value(v)
}(_.toBigInt)
final case class ServerError(msg: String)
sealed trait UserError:
def msg: String
final case class Unauthorized(msg: String) extends UserError
final case class NotFound(msg: String) extends UserError
final case class BadRequest(msg: String) extends UserError
val baseEndpoint = endpoint.errorOut(
oneOf[Either[ServerError, UserError]](
oneOfVariantFromMatchType(
StatusCode.Unauthorized,
jsonBody[Right[ServerError, Unauthorized]]
.description("invalid signature"),
),
oneOfVariantFromMatchType(
StatusCode.NotFound,
jsonBody[Right[ServerError, NotFound]].description("not found"),
),
oneOfVariantFromMatchType(
StatusCode.BadRequest,
jsonBody[Right[ServerError, BadRequest]].description("bad request"),
),
oneOfVariantFromMatchType(
StatusCode.InternalServerError,
jsonBody[Left[ServerError, UserError]].description(
"internal server error",
),
),
),
)
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getTxSetEndpoint = baseEndpoint.get
.in("tx" / query[Block.BlockHash]("block"))
.out(jsonBody[Set[TxInfo]])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getTxEndpoint = baseEndpoint.get
.in("tx" / path[Signed.TxHash])
.out(jsonBody[TransactionWithResult])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val postTxEndpoint =
baseEndpoint.post
.in("tx")
.in(jsonBody[Seq[Signed.Tx]])
.out(jsonBody[Seq[Hash.Value[TransactionWithResult]]])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val postTxHashEndpoint = baseEndpoint.post
.in("txhash")
.in(jsonBody[Seq[Transaction]])
.out(jsonBody[Seq[Hash.Value[Transaction]]])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getStatusEndpoint =
baseEndpoint.get.in("status").out(jsonBody[NodeStatus])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getAccountEndpoint =
baseEndpoint.get
.in("account" / path[Account])
.out(jsonBody[AccountInfo])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getEthEndpoint =
baseEndpoint.get
.in("eth" / path[EthAddress])
.out(jsonBody[Account])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getGroupEndpoint =
baseEndpoint.get
.in("group" / path[GroupId])
.out(jsonBody[GroupInfo])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getBlockListEndpoint =
baseEndpoint.get
.in(
"block" / query[Option[Block.BlockHash]]("from")
.and(query[Option[Int]]("limit")),
)
.out(jsonBody[List[BlockInfo]])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getBlockEndpoint =
baseEndpoint.get
.in("block" / path[Block.BlockHash])
.out(jsonBody[Block])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getTokenDefinitionEndpoint =
baseEndpoint.get
.in("token-def" / path[TokenDefinitionId])
.out(jsonBody[TokenDefinition])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getBalanceEndpoint =
baseEndpoint.get
.in("balance" / path[Account].and(query[Movable]("movable")))
.out(jsonBody[Map[TokenDefinitionId, BalanceInfo]])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getNftBalanceEndpoint =
baseEndpoint.get
.in("nft-balance" / path[Account].and(query[Option[Movable]]("movable")))
.out(jsonBody[Map[TokenId, NftBalanceInfo]])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getTokenEndpoint =
baseEndpoint.get
.in("token" / path[TokenId])
.out(jsonBody[NftState])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getTokenHistoryEndpoint =
baseEndpoint.get
.in("token-hist" / path[Hash.Value[TransactionWithResult]])
.out(jsonBody[NftState])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getOwnersEndpoint =
baseEndpoint.get
.in("owners" / path[TokenDefinitionId])
.out(jsonBody[Map[TokenId, Account]])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getAccountActivityEndpoint =
baseEndpoint.get
.in("activity" / "account" / path[Account])
.out(jsonBody[Seq[ActivityInfo]])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getTokenActivityEndpoint =
baseEndpoint.get
.in("activity" / "token" / path[TokenId])
.out(jsonBody[Seq[ActivityInfo]])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getAccountSnapshotEndpoint =
baseEndpoint.get
.in("snapshot" / "account" / path[Account])
.out(jsonBody[ActivitySnapshot])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getTokenSnapshotEndpoint =
baseEndpoint.get
.in("snapshot" / "token" / path[TokenId])
.out(jsonBody[ActivitySnapshot])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getOwnershipSnapshotEndpoint =
baseEndpoint.get
.in("snapshot" / "ownership" / path[TokenId])
.out(jsonBody[OwnershipSnapshot])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getOwnershipSnapshotMapEndpoint =
baseEndpoint.get
.in {
"snapshot" / "ownership" / query[Option[TokenId]]("from")
.and(query[Option[Int]]("limit"))
}
.out(jsonBody[Map[TokenId, OwnershipSnapshot]])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getOwnershipRewardedEndpoint =
baseEndpoint.get
.in("rewarded" / "ownership" / path[TokenId])
.out(jsonBody[OwnershipRewardLog])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getRewardEndpoint =
baseEndpoint.get
.in {
"reward" / path[Account]
.and(query[Option[Instant]]("timestamp"))
.and(query[Option[Account]]("dao-account"))
.and(query[Option[BigNat]]("reward-amount"))
}
.out(jsonBody[RewardInfo])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getDaoEndpoint =
baseEndpoint.get
.in("dao" / path[GroupId])
.out(jsonBody[DaoInfo])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getSnapshotStateEndpoint =
baseEndpoint.get
.in("snapshot-state" / path[TokenDefinitionId])
.out(jsonBody[SnapshotState])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getFungibleSnapshotBalanceEndpoint =
baseEndpoint.get
.in:
"snapshot-balance"
/ path[Account]
/ path[TokenDefinitionId]
/ path[SnapshotState.SnapshotId]
.out(jsonBody[Map[Hash.Value[TransactionWithResult], BigNat]])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getNftSnapshotBalanceEndpoint =
baseEndpoint.get
.in:
"nft-snapshot-balance"
/ path[Account]
/ path[TokenDefinitionId]
/ path[SnapshotState.SnapshotId]
.out(jsonBody[Set[TokenId]])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getVoteProposalEndpoint =
baseEndpoint.get
.in("vote" / "proposal" / path[ProposalId])
.out(jsonBody[Proposal])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getAccountVotesEndpoint =
baseEndpoint.get
.in("vote" / "account" / path[ProposalId] / path[Account])
.out(jsonBody[(Utf8, BigNat)])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getVoteCountEndpoint =
baseEndpoint.get
.in("vote" / "count" / path[ProposalId])
.out(jsonBody[Map[Utf8, BigNat]])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getCreatorDaoInfoEndpoint =
baseEndpoint.get
.in("creator-dao" / path[CreatorDaoId])
.out(jsonBody[CreatorDaoInfo])
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val getCreatorDaoMemberEndpoint =
baseEndpoint.get
.in:
"creator-dao"
/ path[CreatorDaoId]
/ "member"
.and(query[Option[Account]]("from"))
.and(query[Option[Int]]("limit"))
.out(jsonBody[Seq[Account]])
enum Movable:
case Free, Locked
object Movable:
@SuppressWarnings(Array("org.wartremover.warts.ToString"))
given Codec[String, Movable, TextPlain] = Codec.string.mapDecode {
(s: String) =>
s match
case "free" => DecodeResult.Value(Movable.Free)
case "locked" => DecodeResult.Value(Movable.Locked)
case _ => DecodeResult.Error(s, new Exception(s"invalid movable: $s"))
}(_.toString.toLowerCase(Locale.ENGLISH))
@SuppressWarnings(Array("org.wartremover.warts.ToString"))
given Codec[String, Option[Movable], TextPlain] = Codec.string.mapDecode {
(s: String) =>
s match
case "free" => DecodeResult.Value(Some(Movable.Free))
case "locked" => DecodeResult.Value(Some(Movable.Locked))
case "all" => DecodeResult.Value(None)
case _ => DecodeResult.Error(s, new Exception(s"invalid movable: $s"))
}(_.fold("")(_.toString.toLowerCase(Locale.ENGLISH)))
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/Account.scala
================================================
package io.leisuremeta.chain
package api.model
import cats.Eq
import io.circe.{Decoder, Encoder, KeyDecoder, KeyEncoder}
import sttp.tapir.{Codec, DecodeResult, Schema}
import sttp.tapir.CodecFormat.TextPlain
import lib.codec.byte.{ByteDecoder, ByteEncoder}
import lib.datatype.Utf8
opaque type Account = Utf8
object Account:
def apply(value: Utf8): Account = value
extension (a: Account)
def utf8: Utf8 = a
given Encoder[Account] = Encoder.encodeString.contramap(_.utf8.value)
given Decoder[Account] = Decoder.decodeString.emap(Utf8.from(_).left.map(_.getMessage)).map(apply)
given KeyDecoder[Account] = Utf8.utf8CirceKeyDecoder
given KeyEncoder[Account] = Utf8.utf8CirceKeyEncoder
given Codec[String, Account, TextPlain] = Codec.string.mapDecode{ (s: String) =>
Utf8.from(s) match
case Left(e) => DecodeResult.Error(s, e)
case Right(a) => DecodeResult.Value(Account(a))
}(_.utf8.value)
given Schema[Account] = Schema.string
given ByteDecoder[Account] = Utf8.utf8ByteDecoder
given ByteEncoder[Account] = Utf8.utf8ByteEncoder
given Eq[Account] = Eq.fromUniversalEquals
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/AccountData.scala
================================================
package io.leisuremeta.chain
package api.model
import java.time.Instant
import account.{ExternalChain, ExternalChainAddress}
import lib.datatype.Utf8
final case class AccountData(
externalChainAddresses: Map[ExternalChain, ExternalChainAddress],
guardian: Option[Account],
lastChecked: Instant,
memo: Option[Utf8],
)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/AccountSignature.scala
================================================
package io.leisuremeta.chain
package api.model
import io.circe.{Decoder, Encoder}
import io.circe.generic.semiauto.*
import lib.crypto.Signature
final case class AccountSignature(
sig: Signature,
account: Account,
)
object AccountSignature:
given Decoder[AccountSignature] = deriveDecoder
given Encoder[AccountSignature] = deriveEncoder
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/Block.scala
================================================
package io.leisuremeta.chain
package api.model
import java.time.Instant
import cats.kernel.Eq
import scodec.bits.ByteVector
import lib.codec.byte.{ByteDecoder, ByteEncoder}
import lib.crypto.{Hash, Recover, Sign, Signature}
import lib.datatype.BigNat
import lib.merkle.MerkleTrieNode
final case class Block(
header: Block.Header,
transactionHashes: Set[Signed.TxHash],
votes: Set[Signature],
)
object Block:
type BlockHash = Hash.Value[Block]
final case class Header(
number: BigNat,
parentHash: BlockHash,
stateRoot: StateRoot,
transactionsRoot: Option[MerkleTrieNode.MerkleRoot],
timestamp: Instant,
)
object Header:
given eqHeader: Eq[Header] = Eq.fromUniversalEquals
given headerHash: Hash[Header] = Hash.build
given encoder: ByteEncoder[Header] with
def encode(header: Header): ByteVector =
ByteEncoder[BigNat].encode(header.number)
++ ByteEncoder[BlockHash].encode(header.parentHash)
++ ByteEncoder[StateRoot].encode(header.stateRoot)
++ ByteEncoder[Option[MerkleTrieNode.MerkleRoot]].encode(header.transactionsRoot)
++ ByteEncoder[Instant].encode(header.timestamp)
given decoder: ByteDecoder[Header] =
for
number <- ByteDecoder[BigNat]
parentHash <- ByteDecoder[BlockHash]
stateRoot <- ByteDecoder[StateRoot]
transactionsRoot <- ByteDecoder[Option[MerkleTrieNode.MerkleRoot]]
timestamp <- ByteDecoder[Instant]
yield Header(number, parentHash, stateRoot, transactionsRoot, timestamp)
object ops:
extension (blockHash: Hash.Value[Block])
def toHeaderHash: Hash.Value[Header] =
Hash.Value[Header](blockHash.toUInt256Bytes)
extension (headerHash: Hash.Value[Header])
def toBlockHash: Hash.Value[Block] =
Hash.Value[Block](headerHash.toUInt256Bytes)
given blockHash: Hash[Block] = Header.headerHash.contramap(_.header)
given signBlock: Sign[Block.Header] = Sign.build
given recoverBlockHeader: Recover[Block.Header] = Recover.build
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/GroupData.scala
================================================
package io.leisuremeta.chain
package api.model
import lib.datatype.Utf8
final case class GroupData(
name: Utf8,
coordinator: Account,
)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/GroupId.scala
================================================
package io.leisuremeta.chain
package api.model
import io.circe.{Decoder, Encoder}
import sttp.tapir.{Codec, DecodeResult, Schema}
import sttp.tapir.CodecFormat.TextPlain
import lib.codec.byte.{ByteDecoder, ByteEncoder}
import lib.datatype.Utf8
opaque type GroupId = Utf8
object GroupId:
def apply(utf8: Utf8): GroupId = utf8
extension (a: GroupId)
def utf8: Utf8 = a
given Encoder[GroupId] = Encoder.encodeString.contramap(_.utf8.value)
given Decoder[GroupId] = Decoder.decodeString.emap(Utf8.from(_).left.map(_.getMessage)).map(apply)
given Schema[GroupId] = Schema.string
given ByteDecoder[GroupId] = Utf8.utf8ByteDecoder.map(GroupId(_))
given ByteEncoder[GroupId] = Utf8.utf8ByteEncoder.contramap(_.utf8)
given Codec[String, GroupId, TextPlain] = Codec.string.mapDecode{ (s: String) =>
Utf8.from(s) match
case Left(e) => DecodeResult.Error(s, e)
case Right(a) => DecodeResult.Value(GroupId(a))
}(_.utf8.value)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/NetworkId.scala
================================================
package io.leisuremeta.chain
package api.model
import io.circe.{Decoder, Encoder}
import sttp.tapir.Schema
import lib.codec.byte.{ByteDecoder, ByteEncoder}
import lib.datatype.BigNat
opaque type NetworkId = BigNat
object NetworkId:
def apply(value: BigNat): NetworkId = value
given ByteDecoder[NetworkId] = BigNat.bignatByteDecoder
given ByteEncoder[NetworkId] = BigNat.bignatByteEncoder
given Decoder[NetworkId] = BigNat.bignatCirceDecoder
given Encoder[NetworkId] = BigNat.bignatCirceEncoder
given Schema[NetworkId] = Schema.schemaForBigInt.map[NetworkId] {
(bigint: BigInt) =>
BigNat.fromBigInt(bigint).toOption.map(apply)
}{(bignat: BigNat) => bignat.toBigInt}
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/NodeStatus.scala
================================================
package io.leisuremeta.chain
package api.model
import lib.datatype.BigNat
final case class NodeStatus(
networkId: NetworkId,
genesisHash: Block.BlockHash,
bestHash: Block.BlockHash,
number: BigNat,
)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/PublicKeySummary.scala
================================================
package io.leisuremeta.chain
package api.model
import java.time.Instant
import cats.Eq
import cats.syntax.eq.given
import io.circe.{Decoder, Encoder, KeyDecoder, KeyEncoder}
import scodec.bits.ByteVector
import sttp.tapir.Schema
import lib.codec.byte.{ByteDecoder, ByteEncoder}
import lib.crypto.{Hash, PublicKey}
import lib.datatype.Utf8
opaque type PublicKeySummary = ByteVector
object PublicKeySummary:
final case class Info(
description: Utf8,
addedAt: Instant,
expiresAt: Option[Instant],
)
def apply(bytes: ByteVector): Either[String, PublicKeySummary] =
Either.cond(
bytes.size === 20,
bytes,
"PublicKeySummary must be 20 bytes",
)
def fromHex(hexString: String): Either[String, PublicKeySummary] =
for
bytes <- ByteVector.fromHexDescriptive(hexString)
summary <- apply(bytes)
yield summary
def fromPublicKeyHash(hash: Hash.Value[PublicKey]): PublicKeySummary =
hash.toUInt256Bytes.toBytes takeRight 20
extension (pks: PublicKeySummary)
def toBytes: ByteVector = pks
given Eq[PublicKeySummary] = Eq.fromUniversalEquals
given Decoder[PublicKeySummary] = Decoder.decodeString.emap { (s: String) =>
val (f, b) = s `splitAt` 2
for
_ <- Either.cond(
f === "0x",
(),
s"PublicKeySummary string not starting 0x: $f",
)
summary <- fromHex(b)
yield summary
}
given Encoder[PublicKeySummary] =
Encoder.encodeString.contramap(summary => s"0x${summary.toString}")
given KeyDecoder[PublicKeySummary] = KeyDecoder.instance(fromHex(_).toOption)
given KeyEncoder[PublicKeySummary] = KeyEncoder.encodeKeyString.contramap(_.toHex)
given Schema[PublicKeySummary] = Schema.string
given ByteDecoder[PublicKeySummary] = ByteDecoder.fromFixedSizeBytes(20)(identity)
given ByteEncoder[PublicKeySummary] = (bytes: ByteVector) => bytes
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/Signed.scala
================================================
package io.leisuremeta.chain
package api.model
import scodec.bits.ByteVector
import sttp.tapir.{Codec, DecodeResult}
import sttp.tapir.CodecFormat.TextPlain
import lib.crypto.Hash
import lib.datatype.UInt256
import io.circe.{Decoder, Encoder}
final case class Signed[A](sig: AccountSignature, value: A)
object Signed:
type Tx = Signed[Transaction]
type TxHash = Hash.Value[Tx]
object TxHash{
given txHashCodec: Codec[String, TxHash, TextPlain] = Codec.string.mapDecode{ (s: String) =>
ByteVector.fromHexDescriptive(s).left.map(new Exception(_)).flatMap(UInt256.from) match
case Left(e) => DecodeResult.Error(s, e)
case Right(v) => DecodeResult.Value(Hash.Value(v))
}(_.toUInt256Bytes.toBytes.toHex)
}
given signedHash[A: Hash]: Hash[Signed[A]] = Hash[A].contramap(_.value)
given txhashDecoder: Decoder[TxHash] = Hash.Value.circeValueDecoder[Tx]
given txhashEncoder: Encoder[TxHash] = Hash.Value.circeValueEncoder[Tx]
import io.circe.generic.semiauto.*
given signedDecoder[A: Decoder]: Decoder[Signed[A]] = deriveDecoder
given signedEncoder[A: Encoder]: Encoder[Signed[A]] = deriveEncoder
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/StateRoot.scala
================================================
package io.leisuremeta.chain
package api.model
import cats.Eq
import io.circe.{Decoder, Encoder}
import sttp.tapir.*
import lib.codec.byte.{ByteDecoder, ByteEncoder}
import lib.crypto.Hash
import lib.merkle.MerkleTrieNode.MerkleRoot
opaque type StateRoot = Option[MerkleRoot]
object StateRoot:
def apply(main: Option[MerkleRoot]): StateRoot = main
val empty: StateRoot = None
extension (sr: StateRoot) def main: Option[MerkleRoot] = sr
given byteDecoder: ByteDecoder[StateRoot] =
ByteDecoder.optionByteDecoder[MerkleRoot]
given byteEncoder: ByteEncoder[StateRoot] =
ByteEncoder.optionByteEncoder[MerkleRoot]
given circeDecoder: Decoder[StateRoot] =
Decoder.decodeOption[MerkleRoot]
given circeEncoder: Encoder[StateRoot] =
Encoder.encodeOption[MerkleRoot]
given tapirSchema: Schema[StateRoot] = Schema.string
given eqStateRoot: Eq[StateRoot] = Eq.fromUniversalEquals
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/Transaction.scala
================================================
package io.leisuremeta.chain
package api.model
import java.time.Instant
import io.circe.{Decoder, Encoder}
import io.circe.generic.semiauto.*
import scodec.bits.ByteVector
import account.{EthAddress, ExternalChain, ExternalChainAddress}
//import agenda.AgendaId
import creator_dao.CreatorDaoId
import reward.DaoActivity
import voting.{ProposalId, VoteType}
import lib.crypto.{Hash, Recover, Sign}
import lib.codec.byte.{ByteDecoder, ByteEncoder}
import lib.codec.byte.ByteEncoder.ops.*
import lib.datatype.{BigNat, UInt256Bytes, Utf8}
import token.{Rarity, NftInfo, NftInfoWithPrecision, TokenDefinitionId, TokenId}
sealed trait TransactionResult
object TransactionResult:
given txResultByteEncoder: ByteEncoder[TransactionResult] =
(txr: TransactionResult) =>
txr match
case r: Transaction.AccountTx.AddPublicKeySummariesResult =>
ByteVector.fromByte(0) ++ r.toBytes
case r: Transaction.TokenTx.BurnFungibleTokenResult =>
ByteVector.fromByte(1) ++ r.toBytes
case r: Transaction.TokenTx.EntrustFungibleTokenResult =>
ByteVector.fromByte(2) ++ r.toBytes
case r: Transaction.RewardTx.ExecuteRewardResult =>
ByteVector.fromByte(3) ++ r.toBytes
case r: Transaction.RewardTx.ExecuteOwnershipRewardResult =>
ByteVector.fromByte(4) ++ r.toBytes
case r: Transaction.AgendaTx.VoteSimpleAgendaResult =>
ByteVector.fromByte(5) ++ r.toBytes
given txResultByteDecoder: ByteDecoder[TransactionResult] =
ByteDecoder.byteDecoder.flatMap {
case 0 =>
ByteDecoder[Transaction.AccountTx.AddPublicKeySummariesResult].widen
case 1 =>
ByteDecoder[Transaction.TokenTx.BurnFungibleTokenResult].widen
case 2 =>
ByteDecoder[Transaction.TokenTx.EntrustFungibleTokenResult].widen
case 3 =>
ByteDecoder[Transaction.RewardTx.ExecuteRewardResult].widen
case 4 =>
ByteDecoder[Transaction.RewardTx.ExecuteOwnershipRewardResult].widen
case 5 =>
ByteDecoder[Transaction.AgendaTx.VoteSimpleAgendaResult].widen
}
given txResultCirceEncoder: Encoder[TransactionResult] =
deriveEncoder[TransactionResult]
given txResultCirceDecoder: Decoder[TransactionResult] =
deriveDecoder[TransactionResult]
sealed trait Transaction:
def networkId: NetworkId
def createdAt: Instant
// def memo: Option[Utf8]
object Transaction:
sealed trait AccountTx extends Transaction:
def account: Account
object AccountTx:
final case class CreateAccount(
networkId: NetworkId,
createdAt: Instant,
account: Account,
ethAddress: Option[EthAddress],
guardian: Option[Account],
// memo: Option[Utf8],
) extends AccountTx
final case class CreateAccountWithExternalChainAddresses(
networkId: NetworkId,
createdAt: Instant,
account: Account,
externalChainAddresses: Map[ExternalChain, ExternalChainAddress],
guardian: Option[Account],
memo: Option[Utf8],
) extends AccountTx
final case class UpdateAccount(
networkId: NetworkId,
createdAt: Instant,
account: Account,
ethAddress: Option[EthAddress],
guardian: Option[Account],
// memo: Option[Utf8],
) extends AccountTx
final case class UpdateAccountWithExternalChainAddresses(
networkId: NetworkId,
createdAt: Instant,
account: Account,
externalChainAddresses: Map[ExternalChain, ExternalChainAddress],
guardian: Option[Account],
memo: Option[Utf8],
) extends AccountTx
final case class AddPublicKeySummaries(
networkId: NetworkId,
createdAt: Instant,
account: Account,
summaries: Map[PublicKeySummary, Utf8],
// memo: Option[Utf8],
) extends AccountTx
final case class AddPublicKeySummariesResult(
removed: Map[PublicKeySummary, Utf8],
) extends TransactionResult
// final case class RemovePublicKeySummaries(
// networkId: NetworkId,
// createdAt: Instant,
// account: Account,
// summaries: Set[PublicKeySummary],
// ) extends AccountTx
//
// final case class RemoveAccount(
// networkId: NetworkId,
// createdAt: Instant,
// account: Account,
// ) extends AccountTx
given txByteDecoder: ByteDecoder[AccountTx] = ByteDecoder[BigNat].flatMap {
bignat =>
bignat.toBigInt.toInt match
case 0 => ByteDecoder[CreateAccount].widen
case 1 => ByteDecoder[UpdateAccount].widen
case 2 => ByteDecoder[AddPublicKeySummaries].widen
// case 3 => ByteDecoder[RemovePublicKeySummaries].widen
// case 4 => ByteDecoder[RemoveAccount].widen
case 5 => ByteDecoder[CreateAccountWithExternalChainAddresses].widen
case 6 => ByteDecoder[UpdateAccountWithExternalChainAddresses].widen
}
given txByteEncoder: ByteEncoder[AccountTx] = (atx: AccountTx) =>
atx match
case tx: CreateAccount => build(0)(tx)
case tx: UpdateAccount => build(1)(tx)
case tx: AddPublicKeySummaries => build(2)(tx)
// case tx: RemovePublicKeySummaries => build(3)(tx)
// case tx: RemoveAccount => build(4)(tx)
case tx: CreateAccountWithExternalChainAddresses => build(5)(tx)
case tx: UpdateAccountWithExternalChainAddresses => build(6)(tx)
given txCirceDecoder: Decoder[AccountTx] = deriveDecoder
given txCirceEncoder: Encoder[AccountTx] = deriveEncoder
end AccountTx
sealed trait GroupTx extends Transaction
object GroupTx:
final case class CreateGroup(
networkId: NetworkId,
createdAt: Instant,
groupId: GroupId,
name: Utf8,
coordinator: Account,
// memo: Option[Utf8],
) extends GroupTx
// final case class DisbandGroup(
// networkId: NetworkId,
// createdAt: Instant,
// groupId: GroupId,
// ) extends GroupTx
final case class AddAccounts(
networkId: NetworkId,
createdAt: Instant,
groupId: GroupId,
accounts: Set[Account],
// memo: Option[Utf8],
) extends GroupTx
// final case class RemoveAccounts(
// networkId: NetworkId,
// createdAt: Instant,
// groupId: GroupId,
// accounts: Set[Account],
// ) extends GroupTx
// final case class ReplaceCoordinator(
// networkId: NetworkId,
// createdAt: Instant,
// groupId: GroupId,
// newCoordinator: Account,
// ) extends GroupTx
given txByteDecoder: ByteDecoder[GroupTx] = ByteDecoder[BigNat].flatMap {
bignat =>
bignat.toBigInt.toInt match
case 0 => ByteDecoder[CreateGroup].widen
case 2 => ByteDecoder[AddAccounts].widen
}
given txByteEncoder: ByteEncoder[GroupTx] = (atx: GroupTx) =>
atx match
case tx: CreateGroup => build(0)(tx)
case tx: AddAccounts => build(2)(tx)
given txCirceDecoder: Decoder[GroupTx] = deriveDecoder
given txCirceEncoder: Encoder[GroupTx] = deriveEncoder
end GroupTx
sealed trait TokenTx extends Transaction
object TokenTx:
final case class DefineToken(
networkId: NetworkId,
createdAt: Instant,
definitionId: TokenDefinitionId,
name: Utf8,
symbol: Option[Utf8],
minterGroup: Option[GroupId],
nftInfo: Option[NftInfo],
// memo: Option[Utf8],
) extends TokenTx
final case class DefineTokenWithPrecision(
networkId: NetworkId,
createdAt: Instant,
definitionId: TokenDefinitionId,
name: Utf8,
symbol: Option[Utf8],
minterGroup: Option[GroupId],
nftInfo: Option[NftInfoWithPrecision],
// memo: Option[Utf8],
) extends TokenTx
final case class MintFungibleToken(
networkId: NetworkId,
createdAt: Instant,
definitionId: TokenDefinitionId,
outputs: Map[Account, BigNat],
// memo: Option[Utf8],
) extends TokenTx
with FungibleBalance
final case class MintNFT(
networkId: NetworkId,
createdAt: Instant,
tokenDefinitionId: TokenDefinitionId,
tokenId: TokenId,
rarity: Rarity,
dataUrl: Utf8,
contentHash: UInt256Bytes,
output: Account,
// memo: Option[Utf8],
) extends TokenTx
with NftBalance
final case class MintNFTWithMemo(
networkId: NetworkId,
createdAt: Instant,
tokenDefinitionId: TokenDefinitionId,
tokenId: TokenId,
rarity: Rarity,
dataUrl: Utf8,
contentHash: UInt256Bytes,
output: Account,
memo: Option[Utf8],
) extends TokenTx
with NftBalance
final case class BurnFungibleToken(
networkId: NetworkId,
createdAt: Instant,
definitionId: TokenDefinitionId,
amount: BigNat,
inputs: Set[Signed.TxHash],
// memo: Option[Utf8],
) extends TokenTx
with FungibleBalance
final case class BurnFungibleTokenResult(
outputAmount: BigNat,
) extends TransactionResult
final case class BurnNFT(
networkId: NetworkId,
createdAt: Instant,
definitionId: TokenDefinitionId,
input: Signed.TxHash,
// memo: Option[Utf8],
) extends TokenTx
final case class UpdateNFT(
networkId: NetworkId,
createdAt: Instant,
tokenDefinitionId: TokenDefinitionId,
tokenId: TokenId,
rarity: Rarity,
dataUrl: Utf8,
contentHash: UInt256Bytes,
output: Account,
memo: Option[Utf8],
) extends TokenTx
final case class TransferFungibleToken(
networkId: NetworkId,
createdAt: Instant,
tokenDefinitionId: TokenDefinitionId,
inputs: Set[Signed.TxHash],
outputs: Map[Account, BigNat],
memo: Option[Utf8],
) extends TokenTx
with FungibleBalance
final case class TransferNFT(
networkId: NetworkId,
createdAt: Instant,
definitionId: TokenDefinitionId,
tokenId: TokenId,
input: Signed.TxHash,
output: Account,
memo: Option[Utf8],
) extends TokenTx
with NftBalance
final case class EntrustFungibleToken(
networkId: NetworkId,
createdAt: Instant,
definitionId: TokenDefinitionId,
amount: BigNat,
inputs: Set[Signed.TxHash],
to: Account,
// memo: Option[Utf8],
) extends TokenTx
with FungibleBalance
final case class EntrustFungibleTokenResult(
remainder: BigNat,
) extends TransactionResult
final case class EntrustNFT(
networkId: NetworkId,
createdAt: Instant,
definitionId: TokenDefinitionId,
tokenId: TokenId,
input: Signed.TxHash,
to: Account,
// memo: Option[Utf8],
) extends TokenTx
final case class DisposeEntrustedFungibleToken(
networkId: NetworkId,
createdAt: Instant,
definitionId: TokenDefinitionId,
inputs: Set[Signed.TxHash],
outputs: Map[Account, BigNat],
// memo: Option[Utf8],
) extends TokenTx
with FungibleBalance
final case class DisposeEntrustedNFT(
networkId: NetworkId,
createdAt: Instant,
definitionId: TokenDefinitionId,
tokenId: TokenId,
input: Signed.TxHash,
output: Option[Account],
// memo: Option[Utf8],
) extends TokenTx
with NftBalance
final case class CreateSnapshots(
networkId: NetworkId,
createdAt: Instant,
definitionIds: Set[TokenDefinitionId],
memo: Option[Utf8],
) extends TokenTx
given txByteDecoder: ByteDecoder[TokenTx] = ByteDecoder[BigNat].flatMap {
bignat =>
bignat.toBigInt.toInt match
case 0 => ByteDecoder[DefineToken].widen
case 1 => ByteDecoder[MintFungibleToken].widen
case 2 => ByteDecoder[MintNFT].widen
case 4 => ByteDecoder[TransferFungibleToken].widen
case 5 => ByteDecoder[TransferNFT].widen
case 6 => ByteDecoder[BurnFungibleToken].widen
case 7 => ByteDecoder[BurnNFT].widen
case 8 => ByteDecoder[EntrustFungibleToken].widen
case 9 => ByteDecoder[EntrustNFT].widen
case 10 => ByteDecoder[DisposeEntrustedFungibleToken].widen
case 11 => ByteDecoder[DisposeEntrustedNFT].widen
case 12 => ByteDecoder[DefineTokenWithPrecision].widen
case 13 => ByteDecoder[UpdateNFT].widen
case 14 => ByteDecoder[MintNFTWithMemo].widen
case 15 => ByteDecoder[CreateSnapshots].widen
}
given txByteEncoder: ByteEncoder[TokenTx] = (ttx: TokenTx) =>
ttx match
case tx: DefineToken => build(0)(tx)
case tx: MintFungibleToken => build(1)(tx)
case tx: MintNFT => build(2)(tx)
case tx: TransferFungibleToken => build(4)(tx)
case tx: TransferNFT => build(5)(tx)
case tx: BurnFungibleToken => build(6)(tx)
case tx: BurnNFT => build(7)(tx)
case tx: EntrustFungibleToken => build(8)(tx)
case tx: EntrustNFT => build(9)(tx)
case tx: DisposeEntrustedFungibleToken => build(10)(tx)
case tx: DisposeEntrustedNFT => build(11)(tx)
case tx: DefineTokenWithPrecision => build(12)(tx)
case tx: UpdateNFT => build(13)(tx)
case tx: MintNFTWithMemo => build(14)(tx)
case tx: CreateSnapshots => build(15)(tx)
given txCirceDecoder: Decoder[TokenTx] = deriveDecoder
given txCirceEncoder: Encoder[TokenTx] = deriveEncoder
end TokenTx
sealed trait RewardTx extends Transaction
object RewardTx:
final case class RegisterDao(
networkId: NetworkId,
createdAt: Instant,
groupId: GroupId,
daoAccountName: Account,
moderators: Set[Account],
// memo: Option[Utf8],
) extends RewardTx
final case class UpdateDao(
networkId: NetworkId,
createdAt: Instant,
groupId: GroupId,
moderators: Set[Account],
memo: Option[Utf8],
) extends RewardTx
final case class RecordActivity(
networkId: NetworkId,
createdAt: Instant,
timestamp: Instant,
userActivity: Map[Account, Seq[DaoActivity]],
tokenReceived: Map[TokenId, Seq[DaoActivity]],
memo: Option[Utf8],
) extends RewardTx
final case class OfferReward(
networkId: NetworkId,
createdAt: Instant,
tokenDefinitionId: TokenDefinitionId,
inputs: Set[Signed.TxHash],
outputs: Map[Account, BigNat],
memo: Option[Utf8],
) extends RewardTx
with FungibleBalance
final case class BuildSnapshot(
networkId: NetworkId,
createdAt: Instant,
timestamp: Instant,
accountAmount: BigNat,
tokenAmount: BigNat,
ownershipAmount: BigNat,
memo: Option[Utf8],
) extends RewardTx
final case class ExecuteReward(
networkId: NetworkId,
createdAt: Instant,
daoAccount: Option[Account],
memo: Option[Utf8],
) extends RewardTx
with FungibleBalance
final case class ExecuteRewardResult(
outputs: Map[Account, BigNat],
) extends TransactionResult
final case class ExecuteOwnershipReward(
networkId: NetworkId,
createdAt: Instant,
definitionId: TokenDefinitionId,
inputs: Set[Hash.Value[TransactionWithResult]],
targets: Set[TokenId],
memo: Option[Utf8],
) extends RewardTx
with FungibleBalance
final case class ExecuteOwnershipRewardResult(
outputs: Map[Account, BigNat],
) extends TransactionResult
given txByteDecoder: ByteDecoder[RewardTx] = ByteDecoder[BigNat].flatMap {
bignat =>
bignat.toBigInt.toInt match
case 0 => ByteDecoder[RegisterDao].widen
case 1 => ByteDecoder[UpdateDao].widen
case 2 => ByteDecoder[RecordActivity].widen
case 3 => ByteDecoder[OfferReward].widen
case 4 => ByteDecoder[BuildSnapshot].widen
case 6 => ByteDecoder[ExecuteReward].widen
case 9 => ByteDecoder[ExecuteOwnershipReward].widen
}
given txByteEncoder: ByteEncoder[RewardTx] = (rtx: RewardTx) =>
rtx match
case tx: RegisterDao => build(0)(tx)
case tx: UpdateDao => build(1)(tx)
case tx: RecordActivity => build(2)(tx)
case tx: OfferReward => build(3)(tx)
case tx: BuildSnapshot => build(4)(tx)
case tx: ExecuteReward => build(6)(tx)
case tx: ExecuteOwnershipReward => build(9)(tx)
given txCirceDecoder: Decoder[RewardTx] = deriveDecoder
given txCirceEncoder: Encoder[RewardTx] = deriveEncoder
end RewardTx
sealed trait AgendaTx extends Transaction
object AgendaTx:
final case class SuggestSimpleAgenda(
networkId: NetworkId,
createdAt: Instant,
title: Utf8,
votingToken: TokenDefinitionId,
voteStart: Instant,
voteEnd: Instant,
voteOptions: Map[Utf8, Utf8],
) extends AgendaTx
final case class VoteSimpleAgenda(
networkId: NetworkId,
createdAt: Instant,
agendaTxHash: Hash.Value[TransactionWithResult],
selectedOption: Utf8,
memo: Option[Utf8],
) extends AgendaTx
final case class VoteSimpleAgendaResult(
votingAmount: BigNat,
) extends TransactionResult
given txByteDecoder: ByteDecoder[AgendaTx] = ByteDecoder[BigNat].flatMap {
bignat =>
bignat.toBigInt.toInt match
case 0 => ByteDecoder[SuggestSimpleAgenda].widen
case 1 => ByteDecoder[VoteSimpleAgenda].widen
}
given txByteEncoder: ByteEncoder[AgendaTx] = (rtx: AgendaTx) =>
rtx match
case tx: SuggestSimpleAgenda => build(0)(tx)
case tx: VoteSimpleAgenda => build(1)(tx)
given txCirceDecoder: Decoder[AgendaTx] = deriveDecoder
given txCirceEncoder: Encoder[AgendaTx] = deriveEncoder
end AgendaTx
sealed trait VotingTx extends Transaction
object VotingTx:
/*
"CreateVoteProposal": {
"networkId": 2021,
"createdAt": "2023-06-21T18:01:00Z",
"proposalId": "PROPOSAL-2023-002",
"title": "Approval for New NFT Collection Launch",
"description": "Voting for approval of a new NFT collection proposed by the community",
"votingPower": {
"NFT-COLLECTION-001": 12347,
"NFT-COLLECTION-002": 12348
},
"voteStart": "2023-06-22T00:00:00Z",
"voteEnd": "2023-06-29T23:59:59Z",
"voteType": "NFT_BASED",
"voteOptions": {
"1": "Approve",
"2": "Reject"
},
"quorum": 100, // Minimum participation (number of NFTs)
"passThresholdNumer": 51, // Approval threshold numerator(51%)
"passThresholdDemon": 100, // Approval threshold denominator(100%)
}
*/
final case class CreateVoteProposal(
networkId: NetworkId,
createdAt: Instant,
proposalId: ProposalId,
title: Utf8,
description: Utf8,
votingPower: Map[TokenDefinitionId, BigNat],
voteStart: Instant,
voteEnd: Instant,
voteType: VoteType,
voteOptions: Map[Utf8, Utf8],
quorum: BigNat,
passThresholdNumer: BigNat,
passThresholdDenom: BigNat,
) extends VotingTx
/*
"CastVote": {
"networkId": 2021,
"createdAt": "2023-06-23T10:30:00Z",
"proposalId": "PROPOSAL-2023-001",
"selectedOption": "1"
}
*/
final case class CastVote(
networkId: NetworkId,
createdAt: Instant,
proposalId: ProposalId,
selectedOption: Utf8,
) extends VotingTx
/*
"TallyVotes": {
"networkId": 2021,
"createdAt": "2023-06-30T00:01:00Z",
"proposalId": "PROPOSAL-2023-001"
}
*/
final case class TallyVotes(
networkId: NetworkId,
createdAt: Instant,
proposalId: ProposalId,
) extends VotingTx
given txByteDecoder: ByteDecoder[VotingTx] = ByteDecoder[BigNat].flatMap:
bignat =>
bignat.toBigInt.toInt match
case 0 => ByteDecoder[CreateVoteProposal].widen
case 1 => ByteDecoder[CastVote].widen
case 2 => ByteDecoder[TallyVotes].widen
given txByteEncoder: ByteEncoder[VotingTx] = (vtx: VotingTx) =>
vtx match
case tx: CreateVoteProposal => build(0)(tx)
case tx: CastVote => build(1)(tx)
case tx: TallyVotes => build(2)(tx)
given txCirceDecoder: Decoder[VotingTx] = deriveDecoder
given txCirceEncoder: Encoder[VotingTx] = deriveEncoder
end VotingTx
sealed trait CreatorDaoTx extends Transaction
object CreatorDaoTx:
/*
{
"sig": {
"NamedSignature": {
"name": "founder",
"sig": {
"v": 27,
"r": "62d7c7ddf8bea783b8ed59906b2f5db00b9e53031d6407933d7c4a80c7157f35",
"s": "2d546c7d0f0fdf058e5bdf74b39cb2d3db34aa1dcdd6b2a76ea6504655b12b0f"
}
}
},
"value": {
"CreatorDaoTx": {
"CreateCreatorDao": {
"networkId": 102,
"createdAt": "2024-03-15T09:28:41.339Z",
"id": "dao_001",
"name": "Art Creators DAO",
"description": "A DAO for digital art creators",
"founder": "creator001",
"coordinator": "playnomm"
}
}
}
}
*/
final case class CreateCreatorDao(
networkId: NetworkId,
createdAt: Instant,
id: CreatorDaoId,
name: Utf8,
description: Utf8,
founder: Account,
coordinator: Account,
) extends CreatorDaoTx
/*
```json
{
"sig": {
"NamedSignature": {
"name": "moderator",
"sig": {
"v": 27,
"r": "72d7c7ddf8bea783b8ed59906b2f5db00b9e53031d6407933d7c4a80c7157f35",
"s": "3d546c7d0f0fdf058e5bdf74b39cb2d3db34aa1dcdd6b2a76ea6504655b12b0f"
}
}
},
"value": {
"CreatorDaoTx": {
"UpdateCreatorDao": {
"networkId": 102,
"createdAt": "2024-03-15T10:28:41.339Z",
"id": "dao_001",
"name": "Digital Art Creators DAO",
"description": "A DAO for digital art creators and collectors"
}
}
}
}
```
*/
final case class UpdateCreatorDao(
networkId: NetworkId,
createdAt: Instant,
id: CreatorDaoId,
name: Utf8,
description: Utf8,
) extends CreatorDaoTx
/*
```json
{
"sig": {
"NamedSignature": {
"name": "founder",
"sig": {
"v": 27,
"r": "82d7c7ddf8bea783b8ed59906b2f5db00b9e53031d6407933d7c4a80c7157f35",
"s": "4d546c7d0f0fdf058e5bdf74b39cb2d3db34aa1dcdd6b2a76ea6504655b12b0f"
}
}
},
"value": {
"CreatorDaoTx": {
"DisbandCreatorDao": {
"networkId": 102,
"createdAt": "2024-03-15T11:28:41.339Z",
"id": "dao_001"
}
}
}
}
```
*/
final case class DisbandCreatorDao(
networkId: NetworkId,
createdAt: Instant,
id: CreatorDaoId,
) extends CreatorDaoTx
final case class ReplaceCoordinator(
networkId: NetworkId,
createdAt: Instant,
id: CreatorDaoId,
newCoordinator: Account,
) extends CreatorDaoTx
final case class AddMembers(
networkId: NetworkId,
createdAt: Instant,
id: CreatorDaoId,
members: Set[Account],
) extends CreatorDaoTx
final case class RemoveMembers(
networkId: NetworkId,
createdAt: Instant,
id: CreatorDaoId,
members: Set[Account],
) extends CreatorDaoTx
final case class PromoteModerators(
networkId: NetworkId,
createdAt: Instant,
id: CreatorDaoId,
members: Set[Account],
) extends CreatorDaoTx
final case class DemoteModerators(
networkId: NetworkId,
createdAt: Instant,
id: CreatorDaoId,
members: Set[Account],
) extends CreatorDaoTx
given txByteDecoder: ByteDecoder[CreatorDaoTx] =
ByteDecoder[BigNat].flatMap: bignat =>
bignat.toBigInt.toInt match
case 0 => ByteDecoder[CreateCreatorDao].widen
case 1 => ByteDecoder[UpdateCreatorDao].widen
case 2 => ByteDecoder[DisbandCreatorDao].widen
case 3 => ByteDecoder[ReplaceCoordinator].widen
case 4 => ByteDecoder[AddMembers].widen
case 5 => ByteDecoder[RemoveMembers].widen
case 6 => ByteDecoder[PromoteModerators].widen
case 7 => ByteDecoder[DemoteModerators].widen
given txByteEncoder: ByteEncoder[CreatorDaoTx] = (cdtx: CreatorDaoTx) =>
cdtx match
case tx: CreateCreatorDao => build(0)(tx)
case tx: UpdateCreatorDao => build(1)(tx)
case tx: DisbandCreatorDao => build(2)(tx)
case tx: ReplaceCoordinator => build(3)(tx)
case tx: AddMembers => build(4)(tx)
case tx: RemoveMembers => build(5)(tx)
case tx: PromoteModerators => build(6)(tx)
case tx: DemoteModerators => build(7)(tx)
given txCirceDecoder: Decoder[CreatorDaoTx] = deriveDecoder
given txCirceEncoder: Encoder[CreatorDaoTx] = deriveEncoder
end CreatorDaoTx
private def build[A: ByteEncoder](discriminator: Long)(tx: A): ByteVector =
ByteEncoder[BigNat].encode(BigNat.unsafeFromLong(discriminator))
++ ByteEncoder[A].encode(tx)
given txByteDecoder: ByteDecoder[Transaction] = ByteDecoder[BigNat].flatMap:
bignat =>
bignat.toBigInt.toInt match
case 0 => ByteDecoder[AccountTx].widen
case 1 => ByteDecoder[GroupTx].widen
case 2 => ByteDecoder[TokenTx].widen
case 3 => ByteDecoder[RewardTx].widen
case 4 => ByteDecoder[AgendaTx].widen
case 5 => ByteDecoder[VotingTx].widen
case 6 => ByteDecoder[CreatorDaoTx].widen
given txByteEncoder: ByteEncoder[Transaction] = (tx: Transaction) =>
tx match
case tx: AccountTx => build(0)(tx)
case tx: GroupTx => build(1)(tx)
case tx: TokenTx => build(2)(tx)
case tx: RewardTx => build(3)(tx)
case tx: AgendaTx => build(4)(tx)
case tx: VotingTx => build(5)(tx)
case tx: CreatorDaoTx => build(6)(tx)
given txHash: Hash[Transaction] = Hash.build
given txSign: Sign[Transaction] = Sign.build
given txRecover: Recover[Transaction] = Recover.build
given txCirceDecoder: Decoder[Transaction] = deriveDecoder
given txCirceEncoder: Encoder[Transaction] = deriveEncoder
sealed trait FungibleBalance
sealed trait NftBalance:
def tokenId: TokenId
sealed trait DealSuggestion:
def originalSuggestion: Option[Signed.TxHash]
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/TransactionWithResult.scala
================================================
package io.leisuremeta.chain
package api.model
import lib.crypto.Hash
final case class TransactionWithResult (
signedTx: Signed.Tx,
result: Option[TransactionResult],
)
object TransactionWithResult:
@SuppressWarnings(Array("org.wartremover.warts.Overloading"))
inline def apply[Tx <: Transaction](signedTx: Signed[Tx])(
inline resultOption: Option[TransactionResult],
): TransactionWithResult =
def widenTx: Signed.Tx = Signed(signedTx.sig, signedTx.value)
import scala.compiletime.*
inline signedTx.value match
case ap: Transaction.AccountTx.AddPublicKeySummaries =>
inline resultOption match
case Some(Transaction.AccountTx.AddPublicKeySummariesResult(removed)) =>
TransactionWithResult(widenTx, resultOption)
case other =>
error("wrong result type: expected AddPublicKeySummariesResult")
case bt: Transaction.TokenTx.BurnFungibleToken =>
inline resultOption match
case Some(Transaction.TokenTx.BurnFungibleTokenResult(amount)) =>
TransactionWithResult(widenTx, resultOption)
case other =>
error("wrong result type: expected BurnFungibleTokenResult")
case bt: Transaction.TokenTx.EntrustFungibleToken =>
inline resultOption match
case Some(Transaction.TokenTx.EntrustFungibleTokenResult(amount)) =>
TransactionWithResult(widenTx, resultOption)
case other =>
error("wrong result type: expected EntrustFungibleTokenResult")
case xr: Transaction.RewardTx.ExecuteReward =>
inline resultOption match
case Some(Transaction.RewardTx.ExecuteRewardResult(outputs)) =>
TransactionWithResult(widenTx, resultOption)
case other =>
error("wrong result type: expected ExecuteRewardResult")
case _ =>
inline resultOption match
case None =>
TransactionWithResult(widenTx, resultOption)
case other =>
error(
"wrong result type: expected None but " + codeOf(resultOption),
)
given Hash[TransactionWithResult] =
Hash[Transaction].contramap(_.signedTx.value)
object ops:
extension [A](txHash: Hash.Value[A])
def toResultHashValue: Hash.Value[TransactionWithResult] =
Hash.Value[TransactionWithResult](txHash.toUInt256Bytes)
def toSignedTxHash: Hash.Value[Signed.Tx] =
Hash.Value[Signed.Tx](txHash.toUInt256Bytes)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/account/EthAddress.scala
================================================
package io.leisuremeta.chain
package api.model
package account
import io.circe.{Decoder, Encoder, KeyDecoder, KeyEncoder}
import sttp.tapir.{Codec, DecodeResult, Schema}
import sttp.tapir.CodecFormat.TextPlain
import lib.codec.byte.{ByteDecoder, ByteEncoder}
import lib.datatype.Utf8
opaque type EthAddress = Utf8
object EthAddress:
def apply(utf8: Utf8): EthAddress = utf8
extension (a: EthAddress)
def utf8: Utf8 = a
given Decoder[EthAddress] = Utf8.utf8CirceDecoder
given Encoder[EthAddress] = Utf8.utf8CirceEncoder
given Schema[EthAddress] = Schema.string
given KeyDecoder[EthAddress] = Utf8.utf8CirceKeyDecoder
given KeyEncoder[EthAddress] = Utf8.utf8CirceKeyEncoder
given ByteDecoder[EthAddress] = Utf8.utf8ByteDecoder.map(EthAddress(_))
given ByteEncoder[EthAddress] = Utf8.utf8ByteEncoder.contramap(_.utf8)
given Codec[String, EthAddress, TextPlain] = Codec.string.mapDecode{ (s: String) =>
Utf8.from(s) match
case Left(e) => DecodeResult.Error(s, e)
case Right(a) => DecodeResult.Value(EthAddress(a))
}(_.utf8.value)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/account/ExternalChain.scala
================================================
package io.leisuremeta.chain
package api.model.account
import cats.syntax.either.*
import io.circe.{Decoder, Encoder, KeyDecoder, KeyEncoder}
import sttp.tapir.{Codec, DecodeResult}//, Schema}
import sttp.tapir.CodecFormat.TextPlain
import lib.codec.byte.{ByteDecoder, ByteEncoder}
import lib.datatype.BigNat
import lib.failure.DecodingFailure
import java.util.Locale
enum ExternalChain(val name: String, val abbr: String):
case ETH extends ExternalChain("Ethereum", "eth")
case SOL extends ExternalChain("Solana", "sol")
object ExternalChain :
def fromAbbr(abbr: String): Option[ExternalChain] =
abbr.toLowerCase(Locale.US) match
case "eth" => Some(ETH)
case "sol" => Some(SOL)
case _ => None
given Decoder[ExternalChain] = Decoder.decodeString.emap:
fromAbbr(_).toRight("Unknown public chain")
given Encoder[ExternalChain] = Encoder.encodeString.contramap(_.abbr)
given KeyDecoder[ExternalChain] = KeyDecoder.instance(fromAbbr(_))
given KeyEncoder[ExternalChain] = KeyEncoder.encodeKeyString.contramap(_.abbr)
given ByteDecoder[ExternalChain] = BigNat.bignatByteDecoder.emap: (bn: BigNat) =>
bn.toBigInt.toInt match
case 0 => ExternalChain.ETH.asRight[DecodingFailure]
case 1 => ExternalChain.SOL.asRight[DecodingFailure]
case _ => DecodingFailure("Unknown public chain").asLeft[ExternalChain]
given ByteEncoder[ExternalChain] = BigNat.bignatByteEncoder.contramap:
case ExternalChain.ETH => BigNat.Zero
case ExternalChain.SOL => BigNat.One
given Codec[String, ExternalChain, TextPlain] = Codec.string
.mapDecode: abbr =>
fromAbbr(abbr) match
case Some(chain) => DecodeResult.Value(chain)
case None => DecodeResult.Error(abbr, DecodingFailure(s"Invalid public chain: $abbr"))
.apply(_.abbr)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/account/ExternalChainAddress.scala
================================================
package io.leisuremeta.chain
package api.model
package account
import io.circe.{Decoder, Encoder, KeyDecoder, KeyEncoder}
import sttp.tapir.{Codec, DecodeResult, Schema}
import sttp.tapir.CodecFormat.TextPlain
import lib.codec.byte.{ByteDecoder, ByteEncoder}
import lib.datatype.Utf8
opaque type ExternalChainAddress = Utf8
object ExternalChainAddress:
def apply(utf8: Utf8): ExternalChainAddress = utf8
extension (a: ExternalChainAddress)
def utf8: Utf8 = a
given Decoder[ExternalChainAddress] = Utf8.utf8CirceDecoder
given Encoder[ExternalChainAddress] = Utf8.utf8CirceEncoder
given Schema[ExternalChainAddress] = Schema.string
given KeyDecoder[ExternalChainAddress] = Utf8.utf8CirceKeyDecoder
given KeyEncoder[ExternalChainAddress] = Utf8.utf8CirceKeyEncoder
given ByteDecoder[ExternalChainAddress] = Utf8.utf8ByteDecoder.map(ExternalChainAddress(_))
given ByteEncoder[ExternalChainAddress] = Utf8.utf8ByteEncoder.contramap(_.utf8)
given Codec[String, ExternalChainAddress, TextPlain] = Codec.string.mapDecode{ (s: String) =>
Utf8.from(s) match
case Left(e) => DecodeResult.Error(s, e)
case Right(a) => DecodeResult.Value(ExternalChainAddress(a))
}(_.utf8.value)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/agenda/AgendaId.scala
================================================
package io.leisuremeta.chain
package api.model.agenda
import io.circe.{Decoder, Encoder, KeyDecoder, KeyEncoder}
import sttp.tapir.{Codec, DecodeResult, Schema}
import sttp.tapir.CodecFormat.TextPlain
import lib.codec.byte.{ByteDecoder, ByteEncoder}
import lib.datatype.Utf8
opaque type AgendaId = Utf8
object AgendaId:
def apply(id: Utf8): AgendaId = id
extension (id: AgendaId) def utf8: Utf8 = id
given Decoder[AgendaId] = Utf8.utf8CirceDecoder
given Encoder[AgendaId] = Utf8.utf8CirceEncoder
given Schema[AgendaId] = Schema.string
given KeyDecoder[AgendaId] = Utf8.utf8CirceKeyDecoder
given KeyEncoder[AgendaId] = Utf8.utf8CirceKeyEncoder
given ByteDecoder[AgendaId] = Utf8.utf8ByteDecoder.map(AgendaId(_))
given ByteEncoder[AgendaId] = Utf8.utf8ByteEncoder.contramap(_.utf8)
given Codec[String, AgendaId, TextPlain] = Codec.string.mapDecode{ (s: String) =>
Utf8.from(s) match
case Left(e) => DecodeResult.Error(s, e)
case Right(a) => DecodeResult.Value(AgendaId(a))
}(_.utf8.value)
given cats.Eq[AgendaId] = cats.Eq.fromUniversalEquals
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/api_model/AccountInfo.scala
================================================
package io.leisuremeta.chain
package api.model
package api_model
import account.*
import lib.datatype.Utf8
final case class AccountInfo(
externalChainAddresses: Map[ExternalChain, ExternalChainAddress],
ethAddress: Option[EthAddress],
guardian: Option[Account],
memo: Option[Utf8],
publicKeySummaries: Map[PublicKeySummary, PublicKeySummary.Info],
)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/api_model/ActivityInfo.scala
================================================
package io.leisuremeta.chain
package api.model
package api_model
import java.time.Instant
import lib.crypto.Hash
import lib.datatype.Utf8
final case class ActivityInfo(
timestamp: Instant,
point: BigInt,
description: Utf8,
txHash: Hash.Value[TransactionWithResult],
)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/api_model/BalanceInfo.scala
================================================
package io.leisuremeta.chain
package api.model
package api_model
import io.circe.generic.semiauto.*
import lib.crypto.Hash
import lib.datatype.BigNat
final case class BalanceInfo(
totalAmount: BigNat,
unused: Map[Hash.Value[TransactionWithResult], TransactionWithResult],
)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/api_model/BlockInfo.scala
================================================
package io.leisuremeta.chain
package api.model
package api_model
import java.time.Instant
import lib.datatype.BigNat
final case class BlockInfo(
blockNumber: BigNat,
timestamp: Instant,
blockHash: Block.BlockHash,
txCount: Int,
)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/api_model/CreatorDaoInfo.scala
================================================
package io.leisuremeta.chain
package api.model
package api_model
import lib.datatype.Utf8
import creator_dao.CreatorDaoId
final case class CreatorDaoInfo(
id: CreatorDaoId,
name: Utf8,
description: Utf8,
founder: Account,
coordinator: Account,
moderators: Set[Account],
)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/api_model/GroupInfo.scala
================================================
package io.leisuremeta.chain.api.model
package api_model
final case class GroupInfo(
data: GroupData,
accounts: Set[Account],
)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/api_model/NftBalanceInfo.scala
================================================
package io.leisuremeta.chain
package api.model
package api_model
import io.circe.generic.semiauto.*
import token.TokenDefinitionId
import lib.crypto.Hash
import lib.datatype.Utf8
final case class NftBalanceInfo(
tokenDefinitionId: TokenDefinitionId,
txHash: Hash.Value[TransactionWithResult],
tx: TransactionWithResult,
memo: Option[Utf8],
)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/api_model/RewardInfo.scala
================================================
package io.leisuremeta.chain
package api.model
package api_model
import java.time.Instant
import lib.datatype.BigNat
import token.Rarity
import reward.DaoActivity
final case class RewardInfo(
account: Account,
reward: RewardInfo.Reward,
point: RewardInfo.Point,
timestamp: Instant,
totalNumberOfDao: BigNat,
)
object RewardInfo:
final case class Reward(
total: BigNat,
activity: BigNat,
token: BigNat,
rarity: BigNat,
bonus: BigNat,
)
final case class Point(
activity: DaoActivity,
token: DaoActivity,
rarity: Map[Rarity, BigNat],
)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/api_model/TxInfo.scala
================================================
package io.leisuremeta.chain
package api.model
package api_model
import java.time.Instant
final case class TxInfo(
txHash: Signed.TxHash,
createdAt: Instant,
account: Account,
`type`: String,
)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/creator_dao/CreatorDaoData.scala
================================================
package io.leisuremeta.chain
package api.model
package creator_dao
import lib.datatype.Utf8
final case class CreatorDaoData(
id: CreatorDaoId,
name: Utf8,
description: Utf8,
founder: Account,
coordinator: Account,
)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/creator_dao/CreatorDaoId.scala
================================================
package io.leisuremeta.chain
package api.model
package creator_dao
import io.circe.{Decoder, Encoder, KeyDecoder, KeyEncoder}
import sttp.tapir.{Codec, DecodeResult, Schema}
import sttp.tapir.CodecFormat.TextPlain
import lib.codec.byte.{ByteDecoder, ByteEncoder}
import lib.datatype.Utf8
opaque type CreatorDaoId = Utf8
object CreatorDaoId:
def apply(utf8: Utf8): CreatorDaoId = utf8
extension (a: CreatorDaoId) def utf8: Utf8 = a
given Decoder[CreatorDaoId] = Utf8.utf8CirceDecoder
given Encoder[CreatorDaoId] = Utf8.utf8CirceEncoder
given Schema[CreatorDaoId] = Schema.string
given KeyDecoder[CreatorDaoId] = Utf8.utf8CirceKeyDecoder
given KeyEncoder[CreatorDaoId] = Utf8.utf8CirceKeyEncoder
given ByteDecoder[CreatorDaoId] = Utf8.utf8ByteDecoder.map(CreatorDaoId(_))
given ByteEncoder[CreatorDaoId] = Utf8.utf8ByteEncoder.contramap(_.utf8)
given Codec[String, CreatorDaoId, TextPlain] = Codec.string.mapDecode {
(s: String) =>
Utf8.from(s) match
case Left(e) => DecodeResult.Error(s, e)
case Right(a) => DecodeResult.Value(CreatorDaoId(a))
}(_.utf8.value)
given cats.Eq[CreatorDaoId] = cats.Eq.fromUniversalEquals
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/reward/ActivityLog.scala
================================================
package io.leisuremeta.chain
package api.model
package reward
import lib.crypto.Hash
import lib.datatype.Utf8
final case class ActivityLog(
point: BigInt,
description: Utf8,
txHash: Hash.Value[TransactionWithResult],
)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/reward/ActivityRewardLog.scala
================================================
package io.leisuremeta.chain
package api.model
package reward
import lib.crypto.Hash
final case class ActivityRewardLog(
activitySnapshot: ActivitySnapshot,
txHash: Hash.Value[TransactionWithResult],
)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/reward/ActivitySnapshot.scala
================================================
package io.leisuremeta.chain
package api.model
package reward
import java.time.Instant
import lib.datatype.BigNat
import token.TokenDefinitionId
final case class ActivitySnapshot(
account: Account,
from: Instant,
to: Instant,
point: BigInt,
definitionId: TokenDefinitionId,
amount: BigNat,
backlogs: Set[Signed.TxHash],
)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/reward/DaoActivity.scala
================================================
package io.leisuremeta.chain
package api.model.reward
import io.circe.{Decoder, Encoder}
import io.circe.generic.semiauto.*
import lib.datatype.Utf8
final case class DaoActivity(
point: BigInt,
description: Utf8,
)
object DaoActivity:
given circeDecoder: Decoder[DaoActivity] = deriveDecoder
given circeEncoder: Encoder[DaoActivity] = deriveEncoder
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/reward/DaoInfo.scala
================================================
package io.leisuremeta.chain.api.model
package reward
final case class DaoInfo(
moderators: Set[Account],
)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/reward/OwnershipRewardLog.scala
================================================
package io.leisuremeta.chain
package api.model
package reward
import lib.crypto.Hash
final case class OwnershipRewardLog(
ownershipSnapshot: OwnershipSnapshot,
txHash: Hash.Value[TransactionWithResult],
)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/reward/OwnershipSnapshot.scala
================================================
package io.leisuremeta.chain
package api.model
package reward
import java.time.Instant
import lib.datatype.BigNat
import token.TokenDefinitionId
final case class OwnershipSnapshot(
account: Account,
timestamp: Instant,
point: BigNat,
definitionId: TokenDefinitionId,
amount: BigNat,
)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/token/NftInfo.scala
================================================
package io.leisuremeta.chain
package api.model
package token
import io.circe.{Decoder, Encoder}
import io.circe.generic.semiauto.*
import lib.datatype.{BigNat, UInt256Bytes, Utf8}
final case class NftInfo(
minter: Account,
rarity: Map[Rarity, BigNat],
dataUrl: Utf8,
contentHash: UInt256Bytes,
)
object NftInfo:
given Decoder[NftInfo] = deriveDecoder
given Encoder[NftInfo] = deriveEncoder
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/token/NftInfoWithPrecision.scala
================================================
package io.leisuremeta.chain
package api.model
package token
import io.circe.{Decoder, Encoder}
import io.circe.generic.semiauto.*
import lib.datatype.{BigNat, UInt256Bytes, Utf8}
final case class NftInfoWithPrecision(
minter: Account,
rarity: Map[Rarity, BigNat],
precision: BigNat,
dataUrl: Utf8,
contentHash: UInt256Bytes,
)
object NftInfoWithPrecision:
def fromNftInfo(nftInfo: NftInfo): NftInfoWithPrecision =
NftInfoWithPrecision(
nftInfo.minter,
nftInfo.rarity,
BigNat.Zero,
nftInfo.dataUrl,
nftInfo.contentHash,
)
given Decoder[NftInfoWithPrecision] = deriveDecoder
given Encoder[NftInfoWithPrecision] = deriveEncoder
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/token/NftState.scala
================================================
package io.leisuremeta.chain
package api.model
package token
import lib.crypto.Hash
import lib.datatype.{BigNat, Utf8}
final case class NftState(
tokenId: TokenId,
tokenDefinitionId: TokenDefinitionId,
rarity: Rarity,
weight: BigNat,
currentOwner: Account,
memo: Option[Utf8],
lastUpdateTx: Hash.Value[TransactionWithResult],
previousState: Option[Hash.Value[TransactionWithResult]],
)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/token/Rarity.scala
================================================
package io.leisuremeta.chain
package api.model
package token
import io.circe.{Decoder, Encoder, KeyDecoder, KeyEncoder}
import sttp.tapir.Schema
import lib.codec.byte.{ByteDecoder, ByteEncoder}
import lib.datatype.Utf8
opaque type Rarity = Utf8
object Rarity:
def apply(value: Utf8): Rarity = value
extension (a: Rarity) def utf8: Utf8 = a
given Encoder[Rarity] = Utf8.utf8CirceEncoder
given Decoder[Rarity] = Utf8.utf8CirceDecoder
given KeyEncoder[Rarity] = Utf8.utf8CirceKeyEncoder
given KeyDecoder[Rarity] = Utf8.utf8CirceKeyDecoder
given Schema[Rarity] = Schema.string
given ByteDecoder[Rarity] = Utf8.utf8ByteDecoder.map(Rarity(_))
given ByteEncoder[Rarity] = Utf8.utf8ByteEncoder.contramap(_.utf8)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/token/SnapshotState.scala
================================================
package io.leisuremeta.chain
package api.model
package token
import java.time.Instant
import io.circe.{Decoder, Encoder}
import io.circe.generic.semiauto.*
import sttp.tapir.{Codec, DecodeResult, Schema}
import sttp.tapir.CodecFormat.TextPlain
import lib.codec.byte.{ByteDecoder, ByteEncoder}
import lib.datatype.{BigNat, Utf8}
final case class SnapshotState(
snapshotId: SnapshotState.SnapshotId,
createdAt: Instant,
txHash: Signed.TxHash,
memo: Option[Utf8],
)
object SnapshotState:
opaque type SnapshotId = BigNat
object SnapshotId:
def apply(value: BigNat): SnapshotId = value
given snapshotIdByteEncoder: ByteEncoder[SnapshotId] =
BigNat.bignatByteEncoder
given snapshotIdByteDecoder: ByteDecoder[SnapshotId] =
BigNat.bignatByteDecoder
given snapshotIdCirceDecoder: Decoder[SnapshotId] =
BigNat.bignatCirceDecoder
given snapshotIdCirceEncoder: Encoder[SnapshotId] =
BigNat.bignatCirceEncoder
given schema: Schema[SnapshotId] = Schema.schemaForBigInt
.map[BigNat]: (bigint: BigInt) =>
BigNat.fromBigInt(bigint).toOption
.apply: (bignat: BigNat) =>
bignat.toBigInt
given tapirCodec: Codec[String, SnapshotId, TextPlain] =
Codec.string
.mapDecode: (s: String) =>
BigNat.fromBigInt(BigInt(s)) match
case Right(bignat) => DecodeResult.Value(bignat)
case Left(msg) => DecodeResult.Error(s, new Exception(msg))
.apply: (bignat: BigNat) =>
bignat.toBigInt.toString(10)
val Zero: SnapshotId = BigNat.Zero
extension (id: SnapshotId)
def inc: SnapshotId = increase
def increase: SnapshotId = BigNat.add(id, BigNat.One)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/token/TokenDefinition.scala
================================================
package io.leisuremeta.chain
package api.model
package token
import lib.datatype.{BigNat, Utf8}
final case class TokenDefinition(
id: TokenDefinitionId,
name: Utf8,
symbol: Option[Utf8],
adminGroup: Option[GroupId],
totalAmount: BigNat,
nftInfo: Option[NftInfoWithPrecision],
)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/token/TokenDefinitionId.scala
================================================
package io.leisuremeta.chain
package api.model
package token
import cats.Eq
import io.circe.{Decoder, Encoder, KeyDecoder, KeyEncoder}
import sttp.tapir.{Codec, DecodeResult, Schema}
import sttp.tapir.CodecFormat.TextPlain
import lib.codec.byte.{ByteDecoder, ByteEncoder}
import lib.datatype.Utf8
opaque type TokenDefinitionId = Utf8
object TokenDefinitionId:
def apply(utf8: Utf8): TokenDefinitionId = utf8
extension (a: TokenDefinitionId)
def utf8: Utf8 = a
given Decoder[TokenDefinitionId] = Utf8.utf8CirceDecoder
given Encoder[TokenDefinitionId] = Utf8.utf8CirceEncoder
given KeyDecoder[TokenDefinitionId] = Utf8.utf8CirceKeyDecoder
given KeyEncoder[TokenDefinitionId] = Utf8.utf8CirceKeyEncoder
given Schema[TokenDefinitionId] = Schema.string
given ByteDecoder[TokenDefinitionId] = Utf8.utf8ByteDecoder.map(TokenDefinitionId(_))
given ByteEncoder[TokenDefinitionId] = Utf8.utf8ByteEncoder.contramap(_.utf8)
given Codec[String, TokenDefinitionId, TextPlain] = Codec.string.mapDecode{ (s: String) =>
Utf8.from(s) match
case Left(e) => DecodeResult.Error(s, e)
case Right(a) => DecodeResult.Value(TokenDefinitionId(a))
}(_.utf8.value)
given Eq[TokenDefinitionId] = Eq.fromUniversalEquals
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/token/TokenDetail.scala
================================================
package io.leisuremeta.chain
package api.model.token
import io.circe.{Decoder, Encoder}
import io.circe.generic.semiauto.*
import lib.datatype.BigNat
sealed trait TokenDetail
object TokenDetail:
final case class FungibleDetail(amount: BigNat) extends TokenDetail
final case class NftDetail(tokenId: TokenId) extends TokenDetail
given tokenDetailCirceEncoder: Encoder[TokenDetail] = deriveEncoder
given tokenDetailCirceDecoder: Decoder[TokenDetail] = deriveDecoder
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/token/TokenId.scala
================================================
package io.leisuremeta.chain
package api.model
package token
import io.circe.{Decoder, Encoder, KeyDecoder, KeyEncoder}
import sttp.tapir.{Codec, DecodeResult, Schema}
import sttp.tapir.CodecFormat.TextPlain
import lib.codec.byte.{ByteDecoder, ByteEncoder}
import lib.datatype.Utf8
opaque type TokenId = Utf8
object TokenId:
def apply(utf8: Utf8): TokenId = utf8
extension (a: TokenId)
def utf8: Utf8 = a
given Decoder[TokenId] = Utf8.utf8CirceDecoder
given Encoder[TokenId] = Utf8.utf8CirceEncoder
given Schema[TokenId] = Schema.string
given KeyDecoder[TokenId] = Utf8.utf8CirceKeyDecoder
given KeyEncoder[TokenId] = Utf8.utf8CirceKeyEncoder
given ByteDecoder[TokenId] = Utf8.utf8ByteDecoder.map(TokenId(_))
given ByteEncoder[TokenId] = Utf8.utf8ByteEncoder.contramap(_.utf8)
given Codec[String, TokenId, TextPlain] = Codec.string.mapDecode{ (s: String) =>
Utf8.from(s) match
case Left(e) => DecodeResult.Error(s, e)
case Right(a) => DecodeResult.Value(TokenId(a))
}(_.utf8.value)
given cats.Eq[TokenId] = cats.Eq.fromUniversalEquals
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/voting/Proposal.scala
================================================
package io.leisuremeta.chain
package api.model
package voting
import java.time.Instant
import lib.datatype.{BigNat, Utf8}
import token.TokenDefinitionId
final case class Proposal(
createdAt: Instant,
proposalId: ProposalId,
title: Utf8,
description: Utf8,
votingPower: Map[TokenDefinitionId, BigNat],
voteStart: Instant,
voteEnd: Instant,
voteType: VoteType,
voteOptions: Map[Utf8, Utf8],
quorum: BigNat,
passThresholdNumer: BigNat,
passThresholdDenom: BigNat,
isActive: Boolean,
)
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/voting/ProposalId.scala
================================================
package io.leisuremeta.chain
package api.model.voting
import cats.Eq
import io.circe.{Decoder, Encoder}
import sttp.tapir.{Codec, DecodeResult, Schema}
import sttp.tapir.CodecFormat.TextPlain
import lib.codec.byte.{ByteDecoder, ByteEncoder}
import lib.datatype.Utf8
opaque type ProposalId = Utf8
object ProposalId:
def apply(utf8: Utf8): ProposalId = utf8
extension (proposalId: ProposalId)
def value: Utf8 = proposalId
end extension
given byteEncoder: ByteEncoder[ProposalId] = Utf8.utf8ByteEncoder
given byteDecoder: ByteDecoder[ProposalId] = Utf8.utf8ByteDecoder
given circeEncoder: Encoder[ProposalId] = Utf8.utf8CirceEncoder
given circeDecoder: Decoder[ProposalId] = Utf8.utf8CirceDecoder
given eq: Eq[ProposalId] = Eq.fromUniversalEquals
given schema: Schema[ProposalId] = Schema.string
given bignatCodec: Codec[String, ProposalId, TextPlain] =
Codec.string
.mapDecode: (s: String) =>
Utf8.from(s) match
case Left(e) => DecodeResult.Error(s, e)
case Right(v) => DecodeResult.Value(ProposalId(v))
.apply: (b: ProposalId) =>
b.value.value
================================================
FILE: modules/api/src/main/scala/io/leisuremeta/chain/api/model/voting/VoteType.scala
================================================
package io.leisuremeta.chain
package api.model.voting
import scala.util.Try
import cats.Eq
import cats.syntax.either.*
import cats.syntax.eq.catsSyntaxEq
import io.circe.{Decoder, Encoder}
import lib.codec.byte.{ByteDecoder, ByteEncoder}
import lib.datatype.BigNat
import lib.failure.DecodingFailure
enum VoteType(val name: String):
case ONE_PERSON_ONE_VOTE extends VoteType("ONE_PERSON_ONE_VOTE")
case TOKEN_WEIGHTED extends VoteType("TOKEN_WEIGHTED")
case NFT_BASED extends VoteType("NFT_BASED")
object VoteType:
given eq: Eq[VoteType] = Eq.fromUniversalEquals
given circeEncoder: Encoder[VoteType] = Encoder.encodeString.contramap(_.name)
given circeDecoder: Decoder[VoteType] = Decoder.decodeString.emap: str =>
VoteType.values.find(_.name === str).toRight:
s"VoteType $str is not valid"
given byteEncoder: ByteEncoder[VoteType] =
BigNat.bignatByteEncoder.contramap: voteType =>
BigNat.unsafeFromBigInt(BigInt(voteType.ordinal))
given byteDecoder: ByteDecoder[VoteType] =
BigNat.bignatByteDecoder.emap: bignat =>
Try(VoteType.fromOrdinal(bignat.toBigInt.toInt)).toEither.leftMap: err =>
DecodingFailure:
s"VoteType $bignat is not valid: ${err.getMessage}"
================================================
FILE: modules/api/tx_type.txt
================================================
모든 트랜잭션 공통 create Transaction
Transaction
- TokenTx
// LM Token
- MintFungibleToken
- TransferFungibleToken
- EntrustFungibleToken
- DisposeEntrustedFungibleToken
- BurnFungibleToken
// NFT Token
- DefineToken
- MintNFT
- TransferNFT
- EntrustNFT
- DisposeEntrustedNFT
- BurnNFT
- AccountTx
- CreateAccount
=> insertAccount
- UpdateAccount
=> updateAccount
- AddPublicKeySummaries
- GroupTx
- CreateGroup
- AddAccounts
- RewardTx
- RegisterDao
- UpdateDao
- RecordActivity
- BuildSnapshot
- ExecuteAccountReward (Fungible)
- ExecuteTokenReward. (Fungible)
- ExecuteOwnershipReward. (Fungible)
NFT 테이블 NFT_TX 테이블 로 변경.
backend 서버에서 NFT_activities 리스트는 tx 테이블에서 조회해서 주기.
================================================
FILE: modules/archive/src/main/scala/io/leisuremeta/chain/archive/ArchiveMain.scala
================================================
package io.leisuremeta.chain
package archive
import java.nio.file.{Files, Paths, StandardOpenOption}
import java.time.Instant
import scala.concurrent.duration.*
//import scala.io.Source
import cats.Monad
import cats.data.EitherT
import cats.effect.{ExitCode, IO, IOApp}
import cats.syntax.bifunctor.*
//import cats.syntax.eq.*
//import cats.syntax.flatMap.toFlatMapOps
import cats.syntax.functor.*
import cats.syntax.traverse.*
import io.circe.generic.auto.*
import io.circe.parser.decode
//import io.circe.refined.*
import io.circe.syntax.*
import sttp.client3.*
import sttp.client3.armeria.cats.ArmeriaCatsBackend
import sttp.model.Uri
import api.model.*
import lib.crypto.{Hash, Signature}
import lib.datatype.*
final case class PBlock(
header: PHeader,
transactionHashes: Set[Signed.TxHash],
votes: Set[Signature],
)
final case class PHeader(
number: BigNat,
parentHash: Block.BlockHash,
timestamp: Instant,
)
object ArchiveMain extends IOApp:
// val baseUri = "http://test.chain.leisuremeta.io:8080"
// val baseUri = "http://localhost:7080"
val baseUri = "http://localhost:8081"
val archiveFileName = "txs1.archive"
def logTxs(contents: String): IO[Unit] = IO.blocking:
val path = Paths.get(archiveFileName)
val _ = Files.write(
path,
contents.getBytes,
StandardOpenOption.CREATE,
StandardOpenOption.WRITE,
StandardOpenOption.APPEND,
)
def get[F[_]: Monad, A: io.circe.Decoder](
backend: SttpBackend[F, Any],
)(uri: Uri): EitherT[F, String, A] = EitherT:
basicRequest
.get(uri)
.send(backend)
.map: response =>
for
body <- response.body
a <- decode[A](body).leftMap(_.getMessage())
yield a
def put[F[_]: Monad](
backend: SttpBackend[F, Any],
)(uri: Uri)(line: String): EitherT[F, String, List[Signed.TxHash]] =
EitherT:
// println(s"Req: $line")
basicRequest
.post(uri)
.body(line)
.send(backend)
.map: response =>
// println(s"Response: $response")
val result = for
body <- response.body
a <- decode[List[Signed.TxHash]](body).leftMap(_.getMessage())
yield a
result
def getTransaction[F[_]: Monad](
backend: SttpBackend[F, Any],
)(txHash: Signed.TxHash): EitherT[F, String, TransactionWithResult] =
get[F, TransactionWithResult](backend)
.apply:
uri"$baseUri/tx/${txHash.toUInt256Bytes.toBytes.toHex}"
.leftMap: msg =>
scribe.error(s"error msg: $msg")
msg
def getBlock[F[_]: Monad](
backend: SttpBackend[F, Any],
)(blockHash: Block.BlockHash): EitherT[F, String, PBlock] =
get[F, PBlock](backend):
uri"$baseUri/block/${blockHash.toUInt256Bytes.toBytes.toHex}"
def getStatus[F[_]: Monad](
backend: SttpBackend[F, Any],
): EitherT[F, String, NodeStatus] =
get[F, NodeStatus](backend)(uri"$baseUri/status")
def loop[F[_]: Monad](
backend: SttpBackend[F, Any],
)(next: Block.BlockHash, genesis: Block.BlockHash, until: BigInt, count: Long)(
run: (
BigNat,
Block.BlockHash,
Set[Signed.TxHash],
) => EitherT[F, String, Unit],
): EitherT[F, String, Long] = for
block <- getBlock[F](backend)(next)
number = block.header.number.toBigInt
_ <- EitherT.pure(scribe.info(s"block ${number}: $next"))
_ <- EitherT.cond(
number > until,
(),
s"block number $number is greater than $until",
)
_ <- run(block.header.number, next, block.transactionHashes).recover: msg =>
scribe.error(s"error msg: $msg")
()
count1 <- loop[F](backend)(block.header.parentHash, genesis, until, count + 1)(run)
yield count1
def run(args: List[String]): IO[ExitCode] =
val until = BigInt("0")
// val until = BigInt("12751183")
for _ <- ArmeriaCatsBackend
.resource[IO]:
SttpBackendOptions.Default.connectionTimeout(10.minutes)
.use: backend =>
val program = for
status <- getStatus[IO](backend)
block <- getBlock[IO](backend)(status.bestHash)
count <- loop[IO](backend)(
status.bestHash,
status.genesisHash,
until,
0,
): (blockNumber, blockHash, txSet) =>
txSet.toList
.sortBy(_.toUInt256Bytes.toHex)
.traverse: txHash =>
for
tx <- getTransaction[IO](backend)(txHash)
txString = tx.signedTx.asJson.noSpaces
_ <- EitherT.right:
logTxs:
s"$blockNumber\t${txHash.toUInt256Bytes.toHex}\t$txString\n"
yield ()
.as(())
yield println(s"total number of block: $count")
// val from = 0
// val to = 1000000
// Source.fromFile(archiveFileName).getLines.to(LazyList).zipWithIndex.take(to).drop(from).traverse{
// (line, i) =>
//// println(s"$i: $line")
// put[IO](backend)(uri"$baseUri/tx")(line).recover{
// case msg: String =>
// println(s"Error: $msg")
// println(s"Error Request: $line")
// Nil
// }
// }
program.value
yield ExitCode.Success
================================================
FILE: modules/bulk-insert/src/main/scala/io/leisuremeta/chain/bulkinsert/BulkInsertMain.scala
================================================
package io.leisuremeta.chain
package bulkinsert
import scala.io.Source
import cats.data.{EitherT, Kleisli}
import cats.effect.{Async, ExitCode, IO, IOApp, Resource}
import cats.syntax.all.*
import fs2.Stream
import io.circe.parser.decode
import scodec.bits.ByteVector
import api.model.{Block, Signed, StateRoot}
import api.model.TransactionWithResult.ops.*
import lib.crypto.{CryptoOps, KeyPair}
import lib.crypto.Hash.ops.*
import lib.crypto.Sign.ops.*
import lib.datatype.BigNat
import lib.merkle.*
import lib.merkle.MerkleTrie.NodeStore
import node.NodeConfig
import node.dapp.{PlayNommDApp, PlayNommDAppFailure, PlayNommState}
import node.repository.{BlockRepository, StateRepository, TransactionRepository}
import node.service.NodeInitializationService
def bulkInsert[F[_]
: Async: BlockRepository: TransactionRepository: StateRepository: PlayNommState: InvalidTxLogger](
config: NodeConfig,
source: Source,
from: String,
until: String,
): EitherT[F, String, Unit] = for
bestBlock <- NodeInitializationService
.initialize[F](config.genesis.timestamp)
merkleState = MerkleTrieState.fromRootOption(bestBlock.header.stateRoot.main)
indexWithTxsStream = Stream
.fromIterator[EitherT[F, String, *]](source.getLines(), 1)
.filterNot(_ === "[]")
.zipWithIndex
.evalMap: (line, index) =>
if index % 1000L === 0L then scribe.info(s"Processing line #$index")
line.split("\t").toList match
case blockNumber :: txHash :: jsonString :: Nil =>
EitherT
.fromEither[F]:
decode[Signed.Tx](jsonString)
.leftMap: e =>
scribe.error(s"Error decoding line #$blockNumber: $txHash: $jsonString: $e")
e.getMessage()
.map(tx => (blockNumber, tx))
case _ =>
scribe.error(s"Error parsing line: $line")
EitherT.leftT[F, (String, Signed.Tx)](s"Error parsing line: $line")
.groupAdjacentBy(_._1)
.dropWhile(_._1 =!= from)
.takeWhile(_._1 =!= until)
.map:
case (blockNumber, chunk) =>
(blockNumber, chunk.toList.map(_._2))
localKeyPair: KeyPair =
val privateKey = scala.sys.env
.get("LMNODE_PRIVATE_KEY")
.map(BigInt(_, 16))
.orElse(config.local.`private`)
.get
CryptoOps.fromPrivate(privateKey)
stateStream = indexWithTxsStream.evalMapAccumulate((bestBlock, merkleState)):
case ((previousBlock, ms), (blockNumber, txs)) =>
val program = for
result <- Stream
.fromIterator[EitherT[F, PlayNommDAppFailure, *]](txs.iterator, 1)
.evalMapAccumulate(ms): (ms, tx) =>
// scribe.info(s"signer: ${tx.sig.account}")
// scribe.info(s"tx: ${tx.value}")
// PlayNommDApp[F](tx)
// .run(ms)
// .map: (ms, txWithResult) =>
// (ms, Option(txWithResult))
// .recoverWith: _ =>
// RecoverTx(ms, tx)
RecoverTx(ms, tx)
.recoverWith: failure =>
PlayNommDApp[F](tx)
.run(ms)
.map: (ms, txWithResult) =>
(ms, Option(txWithResult))
.leftMap: failure2 =>
scribe.info(s"Error: $failure")
failure2
.map: result =>
if BigInt(blockNumber) % 100 === 0 then scribe.info(s"#$blockNumber: ${result._1.root}")
result
.compile
.toList
.leftMap: e =>
scribe.error(s"Error building txs #$blockNumber: $txs: $e")
e
.leftSemiflatTap: e =>
StateRepository[F]
.put(ms)
.leftMap: f =>
scribe.error(s"Fail to put state: ${f.msg}")
.value
(states, txWithResultOptions) = result.unzip
finalState = states.last
txWithResults = txWithResultOptions.flatten
txHashes = txWithResults.map(_.toHash)
txState = txs
.map(_.toHash)
.sortBy(_.toUInt256Bytes.toBytes)
.foldLeft(MerkleTrieState.empty): (state, txHash) =>
given idNodeStore: NodeStore[cats.Id] = Kleisli.pure(None)
MerkleTrie
.put[cats.Id](
txHash.toUInt256Bytes.toBytes.toNibbles,
ByteVector.empty,
)
.runS(state)
.value
.getOrElse(state)
stateRoot1 = StateRoot(finalState.root)
now = (previousBlock.header.timestamp :: txs.map(_.value.createdAt))
.maxBy(_.getEpochSecond())
blockNumber = BigNat.add(previousBlock.header.number, BigNat.One)
header = Block.Header(
number = blockNumber,
parentHash = previousBlock.toHash,
stateRoot = stateRoot1,
transactionsRoot = txState.root,
timestamp = now,
)
sig <- EitherT
.fromEither(header.toHash.signBy(localKeyPair))
.leftMap: msg =>
scribe.error(s"Fail to sign header: $msg")
PlayNommDAppFailure.internal(s"Fail to sign header: $msg")
block = Block(
header = header,
transactionHashes = txHashes.toSet.map(_.toSignedTxHash),
votes = Set(sig),
)
_ <- BlockRepository[F]
.put(block)
.leftMap: e =>
scribe.error(s"Fail to put block: $e")
PlayNommDAppFailure.internal(s"Fail to put block: ${e.msg}")
finalState1 <-
if blockNumber.toBigInt % 10000 === 0 then
StateRepository[F]
.put(finalState)
.leftMap: e =>
scribe.error(s"Fail to put state: $e")
PlayNommDAppFailure.internal:
s"Fail to put state: ${e.msg}"
.map: _ =>
MerkleTrieState.fromRootOption(finalState.root)
else EitherT.pure(finalState)
_ <- txWithResults.traverse: txWithResult =>
EitherT.liftF:
TransactionRepository[F].put(txWithResult)
yield ((block, finalState1), (blockNumber, txWithResults))
program
.leftMap: e =>
scribe.error(s"Error applying txs #$blockNumber: $txs: $e")
e.msg
result <- stateStream.last.compile.toList
finalState <- EitherT.fromOption[F](
result.headOption.flatten.map(_._1._2),
"Fail to get final state",
)
_ <- StateRepository[F]
.put(finalState)
.leftMap: e =>
scribe.error(s"Fail to put state: $e")
s"Fail to put state: ${e.msg}"
yield
scribe.info(s"Last: ${result.flatten}")
()
def fileResource[F[_]: Async](fileName: String): Resource[F, Source] =
Resource.fromAutoCloseable:
Async[F].delay(Source.fromFile(fileName))
object BulkInsertMain extends IOApp:
val from = "1"
// val from = "3513172"
val until = "100000000"
// val until = "3513173"
override def run(args: List[String]): IO[ExitCode] =
import com.typesafe.config.ConfigFactory
import node.NodeMain
import node.repository.StateRepository.given
NodeConfig
.load[IO](IO.blocking(ConfigFactory.load))
.value
.flatMap:
case Left(err) =>
IO(println(err)).as(ExitCode.Error)
case Right(config) =>
scribe.info(s"Loaded config: $config")
val program = for
source <- fileResource[IO]("txs.archive")
given BlockRepository[IO] <- NodeMain.getBlockRepo(config)
given TransactionRepository[IO] <- NodeMain.getTransactionRepo(config)
given StateRepository[IO] <- NodeMain.getStateRepo(config)
given InvalidTxLogger[IO] <- InvalidTxLogger.file[IO]:
"invalid-txs.csv"
result <- Resource.eval:
given PlayNommState[IO] = PlayNommState.build[IO]
bulkInsert[IO](config, source, from, until).value.map:
case Left(err) =>
scribe.error(s"Error: $err")
ExitCode.Error
case Right(_) =>
scribe.info(s"Done")
ExitCode.Success
yield result
program.use(IO.pure)
// fileResource[IO]("txs.archive")
// .use: source =>
// NftBalanceState.build(source).flatTap: state =>
// IO.pure:
// state.free.foreach(println)
// state.locked.foreach(println)
// FungibleBalanceState.build(source).flatTap: state =>
// IO.pure:
// state.free.foreach(println)
// state.locked.foreach(println)
// .as(ExitCode.Success)
================================================
FILE: modules/bulk-insert/src/main/scala/io/leisuremeta/chain/bulkinsert/FungibleBalanceState.scala
================================================
package io.leisuremeta.chain
package bulkinsert
import scala.io.Source
import cats.data.{EitherT}
import cats.effect.{IO, Sync}
import cats.syntax.all.*
import fs2.Stream
import io.circe.parser.decode
import api.model.{Account, Signed, Transaction}
import lib.crypto.Hash
import lib.crypto.Hash.ops.*
import lib.datatype.BigNat
final case class FungibleBalanceState(
free: Map[Account, Map[Signed.TxHash, (Signed.Tx, BigNat)]],
locked: Map[Account, Map[Signed.TxHash, (Signed.Tx, BigNat, Account)]],
):
def addFree(
account: Account,
tx: Signed.Tx,
amount: BigNat,
): FungibleBalanceState =
val txHash = tx.toHash
val accountMap = free.getOrElse(account, Map.empty)
val txMap = accountMap.updated(txHash, (tx, amount))
copy(free = free.updated(account, txMap))
def addLocked(
account: Account,
tx: Signed.Tx,
amount: BigNat,
from: Account,
): FungibleBalanceState =
val txHash = tx.toHash
val accountMap = locked.getOrElse(account, Map.empty)
val txMap = accountMap.updated(txHash, (tx, amount, from))
copy(locked = locked.updated(account, txMap))
def removeFree(
account: Account,
txHash: Signed.TxHash,
): FungibleBalanceState =
val accountMap = free.getOrElse(account, Map.empty)
val txMap = accountMap.removed(txHash)
copy(free = free.updated(account, txMap))
def removeLocked(
account: Account,
txHash: Signed.TxHash,
): FungibleBalanceState =
val accountMap = locked.getOrElse(account, Map.empty)
val txMap = accountMap.removed(txHash)
copy(locked = locked.updated(account, txMap))
object FungibleBalanceState:
def empty: FungibleBalanceState = FungibleBalanceState(Map.empty, Map.empty)
def build(
source: Source,
): IO[FungibleBalanceState] =
val indexWithTxsStream = Stream
.fromIterator[EitherT[IO, String, *]](source.getLines(), 1)
.evalMap: line =>
line.split("\t").toList match
case blockNumber :: txHash :: jsonString :: Nil =>
EitherT
.fromEither[IO]:
decode[Signed.Tx](jsonString)
.leftMap: e =>
scribe.error(s"Error decoding line #$blockNumber: $txHash: $jsonString: $e")
e.getMessage()
.map(tx => (BigInt(blockNumber).longValue, tx))
case _ =>
scribe.error(s"Error parsing line: $line")
EitherT.leftT[IO, (Long, Signed.Tx)](s"Error parsing line: $line")
.groupAdjacentBy(_._1)
.map:
case (blockNumber, chunk) =>
(blockNumber, chunk.toList.map(_._2))
//val indexWithTxsStream = Stream
// .fromIterator[EitherT[IO, String, *]](source.getLines(), 1)
// .zipWithIndex
// .filterNot(_._1 === "[]")
// .evalMap: (line, index) =>
// EitherT
// .fromEither[IO]:
// decode[Seq[Signed.Tx]](line)
// .leftMap: e =>
// scribe.error(s"Error decoding line #$index: $line: $e")
// e.getMessage()
// .map(txs => (index, txs))
def logWrongTx(
from: Account,
amount: BigInt,
tx: Signed.Tx,
inputs: Map[Signed.TxHash, BigNat],
): Unit =
println(s"$from\t$amount\t${tx.value.toHash}\t$tx")
inputs.foreach { (txHash, amount) => println(s"===> $txHash : $amount") }
val stateStream = indexWithTxsStream.evalMapAccumulate[EitherT[
IO,
String,
*,
], FungibleBalanceState, (Long, Seq[Signed.Tx])](
FungibleBalanceState.empty,
):
case (balanceState, (index, txs)) =>
// if index % 10000 === 0 then
// println(s"Index: $index")
val finalState = txs.foldLeft(balanceState): (state, tx) =>
tx.value match
case fb: Transaction.FungibleBalance =>
fb match
case mt: Transaction.TokenTx.MintFungibleToken =>
mt.outputs.foldLeft(state):
case (state, (to, amount)) =>
state.addFree(to, tx, amount)
case tt: Transaction.TokenTx.TransferFungibleToken =>
val inputList = tt.inputs.toList
val inputAmounts = inputList.map: inputTxHash =>
state.free
.get(tx.sig.account)
.getOrElse(Map.empty)
.get(inputTxHash)
.fold {
// scribe.error(s"input $inputTxHash is not exist in tx $txHash")
BigNat.Zero
}(_._2)
val inputs = inputList.zip(inputAmounts).toMap
val inputTotal = inputAmounts.fold(BigNat.Zero)(BigNat.add)
val outputTotal =
tt.outputs.map(_._2).fold(BigNat.Zero)(BigNat.add)
val remainder = inputTotal.toBigInt - outputTotal.toBigInt
if remainder < 0 then
logWrongTx(
tx.sig.account,
-remainder,
tx,
inputs,
)
val afterRemovingInput = tt.inputs.foldLeft(state):
case (state, inputTxHash) =>
state.removeFree(tx.sig.account, inputTxHash)
val afterAddingOutput =
tt.outputs.foldLeft(afterRemovingInput):
case (state, (to, amount)) =>
state.addFree(to, tx, amount)
afterAddingOutput
case bt: Transaction.TokenTx.BurnFungibleToken =>
val inputList = bt.inputs.toList
val inputAmounts = inputList.map: inputTxHash =>
state.free
.get(tx.sig.account)
.getOrElse(Map.empty)
.get(inputTxHash)
.fold {
// scribe.error(s"input $inputTxHash is not exist in tx $txHash")
BigNat.Zero
}(_._2)
val inputs = inputList.zip(inputAmounts).toMap
val inputTotal = inputAmounts.fold(BigNat.Zero)(BigNat.add)
val burnAmount = bt.amount.toBigInt
val remainder = inputTotal.toBigInt - burnAmount
if remainder < 0 then
logWrongTx(
tx.sig.account,
-remainder,
tx,
inputs,
)
val afterRemovingInput = bt.inputs.foldLeft(state):
case (state, inputTxHash) =>
state.removeFree(tx.sig.account, inputTxHash)
afterRemovingInput.addFree(
tx.sig.account,
tx,
BigNat.unsafeFromBigInt(remainder.max(0)),
)
case et: Transaction.TokenTx.EntrustFungibleToken =>
val inputList = et.inputs.toList
val inputAmounts = inputList.map: inputTxHash =>
state.free
.get(tx.sig.account)
.getOrElse(Map.empty)
.get(inputTxHash)
.fold {
// scribe.error(s"input $inputTxHash is not exist in tx $txHash")
BigNat.Zero
}(_._2)
val inputs = inputList.zip(inputAmounts).toMap
val inputTotal = inputAmounts.fold(BigNat.Zero)(BigNat.add)
val entrustAmount = et.amount.toBigInt
val remainder = inputTotal.toBigInt - entrustAmount
if remainder < 0 then
logWrongTx(
tx.sig.account,
-remainder,
tx,
inputs,
)
val afterRemovingInput = et.inputs.foldLeft(state):
case (state, inputTxHash) =>
state.removeFree(tx.sig.account, inputTxHash)
val afterAddingOutput =
afterRemovingInput
.addLocked(et.to, tx, et.amount, tx.sig.account)
.addFree(
tx.sig.account,
tx,
BigNat.unsafeFromBigInt(remainder.max(0)),
)
afterAddingOutput
case dt: Transaction.TokenTx.DisposeEntrustedFungibleToken =>
val inputList = dt.inputs.toList
val inputAmounts = inputList.map: inputTxHash =>
state.locked
.get(tx.sig.account)
.getOrElse(Map.empty)
.get(inputTxHash)
.fold {
// scribe.error(s"input $inputTxHash is not exist in tx $txHash")
BigNat.Zero
}(_._2)
val inputs = inputList.zip(inputAmounts).toMap
val inputTotal = inputAmounts.fold(BigNat.Zero)(BigNat.add)
val outputTotal =
dt.outputs.map(_._2).fold(BigNat.Zero)(BigNat.add)
val remainder = inputTotal.toBigInt - outputTotal.toBigInt
if remainder < 0 then
logWrongTx(
tx.sig.account,
-remainder,
tx,
inputs,
)
val afterRemovingInput = dt.inputs.foldLeft(state):
case (state, inputTxHash) =>
state.removeLocked(tx.sig.account, inputTxHash)
val afterAddingOutput =
dt.outputs.foldLeft(afterRemovingInput):
case (state, (to, amount)) =>
state.addFree(to, tx, amount)
afterAddingOutput
case or: Transaction.RewardTx.OfferReward =>
val inputList = or.inputs.toList
val inputAmounts = inputList.map: inputTxHash =>
state.free
.get(tx.sig.account)
.getOrElse(Map.empty)
.get(inputTxHash)
.fold {
// scribe.error(s"input $inputTxHash is not exist in tx $txHash")
BigNat.Zero
}(_._2)
val inputs = inputList.zip(inputAmounts).toMap
val inputTotal = inputAmounts.fold(BigNat.Zero)(BigNat.add)
val outputTotal =
or.outputs.map(_._2).fold(BigNat.Zero)(BigNat.add)
val remainder = inputTotal.toBigInt - outputTotal.toBigInt
if remainder < 0 then
logWrongTx(
tx.sig.account,
-remainder,
tx,
inputs,
)
val afterRemovingInput = or.inputs.foldLeft(state):
case (state, inputTxHash) =>
state.removeFree(tx.sig.account, inputTxHash)
val afterAddingOutput =
or.outputs.foldLeft(afterRemovingInput):
case (state, (to, amount)) =>
state.addFree(to, tx, amount)
afterAddingOutput
case er: Transaction.RewardTx.ExecuteReward =>
???
case er: Transaction.RewardTx.ExecuteOwnershipReward =>
???
case _ => state
EitherT.pure[IO, String]((finalState, (index, txs)))
stateStream.last.compile.toList
.map(_.headOption.flatten.get._1)
.value
.map:
case Left(err) =>
scribe.error(s"Error building balance map: $err")
FungibleBalanceState.empty
case Right(balanceState) =>
balanceState
================================================
FILE: modules/bulk-insert/src/main/scala/io/leisuremeta/chain/bulkinsert/InvalidTx.scala
================================================
package io.leisuremeta.chain
package bulkinsert
import java.time.Instant
import cats.effect.{Async, Resource}
import cats.effect.std.Console
import api.model.{Account, Transaction}
import api.model.token.{TokenId}
import lib.datatype.BigNat
import lib.crypto.Hash.ops.*
final case class InvalidTx(
signer: Account,
reason: InvalidReason,
amountToBurn: BigNat,
tx: Transaction,
wrongNftInput: Option[TokenId] = None,
createdAt: Instant,
memo: String = "",
):
def txType: String = tx.getClass.getSimpleName
enum InvalidReason:
case OutputMoreThanInput, InputAlreadyUsed, BalanceNotExist, CanceledBalance,
NoNftInfo
trait InvalidTxLogger[F[_]]:
def log(invalidTx: InvalidTx): F[Unit]
object InvalidTxLogger:
def apply[F[_]: InvalidTxLogger]: InvalidTxLogger[F] = summon
def console[F[_]: Async: Console]: InvalidTxLogger[F] =
invalidTx => Console[F].println(invalidTx)
def file[F[_]: Async](filename: String): Resource[F, InvalidTxLogger[F]] =
Resource
.make:
import java.io.{File, FileOutputStream, PrintWriter}
Async[F].delay(
new PrintWriter(new FileOutputStream(new File(filename), true)),
)
.apply: out =>
Async[F].delay:
out.flush()
out.close()
.map: out =>
case InvalidTx(signer, reason, amountToBurn, tx, wrongNftInput, createdAt, memo) =>
Async[F].delay:
val fields = Seq(
createdAt,
tx.toHash.toUInt256Bytes.toHex,
tx.getClass.getSimpleName,
signer,
reason,
amountToBurn,
wrongNftInput,
memo,
)
out.println(fields.mkString(","))
out.flush()
================================================
FILE: modules/bulk-insert/src/main/scala/io/leisuremeta/chain/bulkinsert/NftBalanceState.scala
================================================
package io.leisuremeta.chain
package bulkinsert
import scala.io.Source
import cats.data.EitherT
import cats.effect.IO
import cats.syntax.all.*
import fs2.Stream
import io.circe.parser.decode
import api.model.*
import api.model.token.*
import lib.crypto.Hash.ops.*
import lib.datatype.Utf8
final case class NftBalanceState(
free: Map[Account, Map[Signed.TxHash, (Signed.Tx, TokenId)]],
locked: Map[Account, Map[Signed.TxHash, (Signed.Tx, TokenId, Account)]],
tokenOwner: Map[TokenId, Account],
):
def addFree(
account: Account,
tx: Signed.Tx,
tokenId: TokenId,
): NftBalanceState =
val txHash = tx.toHash
val accountMap = free.getOrElse(account, Map.empty)
val txMap = accountMap.updated(txHash, (tx, tokenId))
val tokenOwner1 = tokenOwner.updated(tokenId, account)
copy(free = free.updated(account, txMap), tokenOwner = tokenOwner1)
def addLocked(
account: Account,
tx: Signed.Tx,
tokenId: TokenId,
from: Account,
): NftBalanceState =
val txHash = tx.toHash
val accountMap = locked.getOrElse(account, Map.empty)
val txMap = accountMap.updated(txHash, (tx, tokenId, from))
copy(locked = locked.updated(account, txMap))
def removeFree(
account: Account,
txHash: Signed.TxHash,
): NftBalanceState =
val accountMap = free.getOrElse(account, Map.empty)
val txMap = accountMap.removed(txHash)
val tokenOwner1 = accountMap.get(txHash)
.fold:
// println(s"No locked balance of account $account with $txHash")
tokenOwner
.apply: (_, tokenId) =>
tokenOwner.removed(tokenId)
copy(free = free.updated(account, txMap), tokenOwner = tokenOwner1)
def removeLocked(
account: Account,
txHash: Signed.TxHash,
): NftBalanceState =
val accountMap = locked.getOrElse(account, Map.empty)
val txMap = accountMap.removed(txHash)
val tokenOwner1 = accountMap.get(txHash)
.fold:
// println(s"No locked balance of account $account with $txHash")
tokenOwner
.apply: (_, tokenId, _) =>
tokenOwner.removed(tokenId)
copy(locked = locked.updated(account, txMap), tokenOwner = tokenOwner1)
object NftBalanceState:
def empty: NftBalanceState = NftBalanceState(Map.empty, Map.empty, Map.empty)
def build(
source: Source,
): IO[NftBalanceState] =
val indexWithTxsStream = Stream
.fromIterator[EitherT[IO, String, *]](source.getLines(), 1)
.evalMap: line =>
line.split("\t").toList match
case blockNumber :: txHash :: jsonString :: Nil =>
EitherT
.fromEither[IO]:
decode[Signed.Tx](jsonString)
.leftMap: e =>
scribe.error(s"Error decoding line #$blockNumber: $txHash: $jsonString: $e")
e.getMessage()
.map(tx => (BigInt(blockNumber).longValue, tx))
case _ =>
scribe.error(s"Error parsing line: $line")
EitherT.leftT[IO, (Long, Signed.Tx)](s"Error parsing line: $line")
.groupAdjacentBy(_._1)
.map:
case (blockNumber, chunk) =>
(blockNumber, chunk.toList.map(_._2))
// val indexWithTxsStream = Stream
// .fromIterator[EitherT[IO, String, *]](source.getLines(), 1)
// .zipWithIndex
// .filterNot(_._1 === "[]")
// .evalMap: (line, index) =>
// EitherT
// .fromEither[IO]:
// decode[Seq[Signed.Tx]](line)
// .leftMap: e =>
// scribe.error(s"Error decoding line #$index: $line: $e")
// e.getMessage()
// .map(txs => (index, txs))
def logWrongTx(
from: Account,
tokenId: TokenId,
tx: Signed.Tx,
): Unit =
println(s"No free NFT balance of $from with tokenId $tokenId: ${tx.toHash}: $tx")
()
def logWrongEntrustTx(
from: Account,
tokenId: TokenId,
tx: Signed.Tx,
): Unit =
println(s"No entrust NFT balance of $from with tokenId $tokenId: ${tx.toHash}: $tx")
()
val stateStream = indexWithTxsStream.evalMapAccumulate[EitherT[IO, String, *], NftBalanceState, (Long, Seq[Signed.Tx])](NftBalanceState.empty):
case (balanceState, (index, txs)) =>
val finalState = txs.foldLeft(balanceState): (state, tx) =>
tx.value match
case mn: Transaction.TokenTx.MintNFT =>
state.addFree(mn.output, tx, mn.tokenId)
case bn: Transaction.TokenTx.BurnNFT =>
state.removeFree(tx.sig.account, bn.input)
case tn: Transaction.TokenTx.TransferNFT =>
val inputOption = for
nftBalance <- state.free.get(tx.sig.account)
txAndTokenId <- nftBalance.get(tn.input)
yield txAndTokenId
inputOption match
case Some((inputTx, tokenId)) =>
state
.removeFree(tx.sig.account, tn.input)
.addFree(tn.output, tx, tokenId)
case None =>
if tx.sig.account === Account(Utf8.unsafeFrom("playnomm")) then
val currentOwnerOption =
state.free.toSeq
.flatMap: (account, map) =>
map.toSeq.map:
case (txHash, (tx, tokenId)) => (tokenId, account, txHash)
.find(_._1 === tn.tokenId)
currentOwnerOption
.fold(state): (_, owner, txHash) =>
state.removeFree(owner, txHash)
.addFree(tn.output, tx, tn.tokenId)
gitextract_d4szp668/
├── .gitignore
├── .jvmopts
├── .scalafix.conf
├── .scalafmt.conf
├── README.md
├── build.sbt
├── docs/
│ ├── LeisureMeta_Chain_API.md
│ ├── api_with_example.md
│ ├── creator-dao-documentation.md
│ └── dao-voting-system-design-english.md
├── modules/
│ ├── api/
│ │ ├── src/
│ │ │ └── main/
│ │ │ └── scala/
│ │ │ └── io/
│ │ │ └── leisuremeta/
│ │ │ └── chain/
│ │ │ └── api/
│ │ │ ├── LeisureMetaChainApi.scala
│ │ │ └── model/
│ │ │ ├── Account.scala
│ │ │ ├── AccountData.scala
│ │ │ ├── AccountSignature.scala
│ │ │ ├── Block.scala
│ │ │ ├── GroupData.scala
│ │ │ ├── GroupId.scala
│ │ │ ├── NetworkId.scala
│ │ │ ├── NodeStatus.scala
│ │ │ ├── PublicKeySummary.scala
│ │ │ ├── Signed.scala
│ │ │ ├── StateRoot.scala
│ │ │ ├── Transaction.scala
│ │ │ ├── TransactionWithResult.scala
│ │ │ ├── account/
│ │ │ │ ├── EthAddress.scala
│ │ │ │ ├── ExternalChain.scala
│ │ │ │ └── ExternalChainAddress.scala
│ │ │ ├── agenda/
│ │ │ │ └── AgendaId.scala
│ │ │ ├── api_model/
│ │ │ │ ├── AccountInfo.scala
│ │ │ │ ├── ActivityInfo.scala
│ │ │ │ ├── BalanceInfo.scala
│ │ │ │ ├── BlockInfo.scala
│ │ │ │ ├── CreatorDaoInfo.scala
│ │ │ │ ├── GroupInfo.scala
│ │ │ │ ├── NftBalanceInfo.scala
│ │ │ │ ├── RewardInfo.scala
│ │ │ │ └── TxInfo.scala
│ │ │ ├── creator_dao/
│ │ │ │ ├── CreatorDaoData.scala
│ │ │ │ └── CreatorDaoId.scala
│ │ │ ├── reward/
│ │ │ │ ├── ActivityLog.scala
│ │ │ │ ├── ActivityRewardLog.scala
│ │ │ │ ├── ActivitySnapshot.scala
│ │ │ │ ├── DaoActivity.scala
│ │ │ │ ├── DaoInfo.scala
│ │ │ │ ├── OwnershipRewardLog.scala
│ │ │ │ └── OwnershipSnapshot.scala
│ │ │ ├── token/
│ │ │ │ ├── NftInfo.scala
│ │ │ │ ├── NftInfoWithPrecision.scala
│ │ │ │ ├── NftState.scala
│ │ │ │ ├── Rarity.scala
│ │ │ │ ├── SnapshotState.scala
│ │ │ │ ├── TokenDefinition.scala
│ │ │ │ ├── TokenDefinitionId.scala
│ │ │ │ ├── TokenDetail.scala
│ │ │ │ └── TokenId.scala
│ │ │ └── voting/
│ │ │ ├── Proposal.scala
│ │ │ ├── ProposalId.scala
│ │ │ └── VoteType.scala
│ │ └── tx_type.txt
│ ├── archive/
│ │ └── src/
│ │ └── main/
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ └── chain/
│ │ └── archive/
│ │ └── ArchiveMain.scala
│ ├── bulk-insert/
│ │ └── src/
│ │ └── main/
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ └── chain/
│ │ └── bulkinsert/
│ │ ├── BulkInsertMain.scala
│ │ ├── FungibleBalanceState.scala
│ │ ├── InvalidTx.scala
│ │ ├── NftBalanceState.scala
│ │ └── RecoverTx.scala
│ ├── eth-gateway/
│ │ └── src/
│ │ └── main/
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ └── chain/
│ │ └── gateway/
│ │ └── eth/
│ │ └── EthGatewayMain.scala
│ ├── eth-gateway-common/
│ │ └── src/
│ │ ├── main/
│ │ │ └── scala/
│ │ │ └── io/
│ │ │ └── leisuremeta/
│ │ │ └── chain/
│ │ │ └── gateway/
│ │ │ └── eth/
│ │ │ └── common/
│ │ │ ├── GatewayApi.scala
│ │ │ ├── GatewayConf.scala
│ │ │ ├── GatewayDecryptService.scala
│ │ │ ├── GatewayResource.scala
│ │ │ ├── GatewayServer.scala
│ │ │ ├── GatewaySimpleConf.scala
│ │ │ ├── GatewayWeb3Service.scala
│ │ │ └── client/
│ │ │ ├── GatewayApiClient.scala
│ │ │ ├── GatewayDatabaseClient.scala
│ │ │ └── GatewayKmsClient.scala
│ │ └── test/
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ └── chain/
│ │ └── gateway/
│ │ └── eth/
│ │ └── common/
│ │ └── GatewayServerTest.scala
│ ├── eth-gateway-deposit/
│ │ └── src/
│ │ └── main/
│ │ ├── resources/
│ │ │ └── application.conf.sample
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ └── chain/
│ │ └── gateway/
│ │ └── eth/
│ │ └── EthGatewayDepositMain.scala
│ ├── eth-gateway-setup/
│ │ └── src/
│ │ └── main/
│ │ ├── resources/
│ │ │ └── application.conf.sample
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ └── chain/
│ │ └── gateway/
│ │ └── eth/
│ │ └── setup/
│ │ ├── EthGatewaySetupConfig.scala
│ │ ├── EthGatewaySetupMain.scala
│ │ └── EthGatewaySetupSimpleConfig.scala
│ ├── eth-gateway-withdraw/
│ │ └── src/
│ │ └── main/
│ │ ├── resources/
│ │ │ └── application.conf.sample
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ └── chain/
│ │ └── gateway/
│ │ └── eth/
│ │ └── EthGatewayWithdrawMain.scala
│ ├── jvm-client/
│ │ └── src/
│ │ └── main/
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ └── chain/
│ │ └── jvmclient/
│ │ └── JvmClientMain.scala
│ ├── lib/
│ │ ├── js/
│ │ │ └── src/
│ │ │ └── main/
│ │ │ └── scala/
│ │ │ └── io/
│ │ │ └── leisuremeta/
│ │ │ └── chain/
│ │ │ └── lib/
│ │ │ └── crypto/
│ │ │ └── CryptoOps.scala
│ │ ├── jvm/
│ │ │ └── src/
│ │ │ └── main/
│ │ │ └── scala/
│ │ │ └── io/
│ │ │ └── leisuremeta/
│ │ │ └── chain/
│ │ │ └── lib/
│ │ │ └── crypto/
│ │ │ └── CryptoOps.scala
│ │ └── shared/
│ │ └── src/
│ │ ├── main/
│ │ │ └── scala/
│ │ │ └── io/
│ │ │ └── leisuremeta/
│ │ │ └── chain/
│ │ │ └── lib/
│ │ │ ├── application/
│ │ │ │ └── DAppState.scala
│ │ │ ├── codec/
│ │ │ │ └── byte/
│ │ │ │ ├── ByteCodec.scala
│ │ │ │ ├── ByteDecoder.scala
│ │ │ │ └── ByteEncoder.scala
│ │ │ ├── crypto/
│ │ │ │ ├── Hash.scala
│ │ │ │ ├── KeyPair.scala
│ │ │ │ ├── PublicKey.scala
│ │ │ │ ├── Recover.scala
│ │ │ │ ├── Sign.scala
│ │ │ │ └── Signature.scala
│ │ │ ├── datatype/
│ │ │ │ ├── BigNat.scala
│ │ │ │ ├── UInt256.scala
│ │ │ │ └── Utf8.scala
│ │ │ ├── failure/
│ │ │ │ └── LmChainFailure.scala
│ │ │ ├── merkle/
│ │ │ │ ├── MerkleTrie.scala
│ │ │ │ ├── MerkleTrieNode.scala
│ │ │ │ ├── MerkleTrieState.scala
│ │ │ │ ├── MerkleTrieStateDiff.scala
│ │ │ │ └── package.scala
│ │ │ └── util/
│ │ │ ├── iron/
│ │ │ │ └── package.scala
│ │ │ └── refined/
│ │ │ └── bitVector.scala
│ │ └── test/
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ └── chain/
│ │ └── lib/
│ │ ├── codec/
│ │ │ └── ByteCodecTest.scala
│ │ ├── crypto/
│ │ │ └── CryptoOpsTest.scala
│ │ ├── datatype/
│ │ │ ├── BigNatTest.scala
│ │ │ └── UInt256Test.scala
│ │ └── merkle/
│ │ ├── MerkleTrieNodeTest.scala
│ │ ├── MerkleTrieTest.scala
│ │ └── NibblesTest.scala
│ ├── lmscan-agent/
│ │ └── src/
│ │ └── main/
│ │ ├── resources/
│ │ │ └── application.conf.sample
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ └── chain/
│ │ └── lmscan/
│ │ └── agent/
│ │ ├── ScanAgentApp.scala
│ │ ├── ScanAgentConfig.scala
│ │ ├── ScanAgentMain.scala
│ │ ├── apps/
│ │ │ ├── BalanceStoreApp.scala
│ │ │ ├── NftStoreApp.scala
│ │ │ ├── NodeDataStoreApp.scala
│ │ │ └── SummaryStoreApp.scala
│ │ └── service/
│ │ ├── RequestService.scala
│ │ ├── StoreService.scala
│ │ └── store/
│ │ ├── AccountStore.scala
│ │ ├── BalanceStore.scala
│ │ ├── BlockStore.scala
│ │ ├── NftStore.scala
│ │ ├── SummaryStore.scala
│ │ └── TxStore.scala
│ ├── lmscan-backend/
│ │ ├── docs/
│ │ │ └── flyway.md
│ │ └── src/
│ │ ├── main/
│ │ │ ├── resources/
│ │ │ │ ├── application.sample.properties
│ │ │ │ └── db/
│ │ │ │ ├── dist/
│ │ │ │ │ ├── V20230116164800__Alter_ts_pgdefault.sql
│ │ │ │ │ ├── V20230116164800__Alter_ts_pgglobal.sql
│ │ │ │ │ └── V20230116164801__Create_r_playnomm.sql
│ │ │ │ ├── common/
│ │ │ │ │ ├── V20230116164802__Create_t_account.sql
│ │ │ │ │ ├── V20230116164803__Create_t_block.sql
│ │ │ │ │ ├── V20230116164805__Create_t_nft.sql
│ │ │ │ │ └── V20230116164809__Create_t_transaction.sql
│ │ │ │ ├── seed/
│ │ │ │ │ └── R__001_Seed_account.sql
│ │ │ │ └── test/
│ │ │ │ └── V20230116164801__Create_r_playnomm.sql
│ │ │ └── scala/
│ │ │ └── io/
│ │ │ └── leisuremeta/
│ │ │ └── chain/
│ │ │ └── lmscan/
│ │ │ └── backend/
│ │ │ ├── LmscanBackendMain.scala
│ │ │ ├── docs/
│ │ │ │ └── Lmscan_API.md
│ │ │ ├── entity/
│ │ │ │ ├── Account.scala
│ │ │ │ ├── AccountMapper.scala
│ │ │ │ ├── Balance.scala
│ │ │ │ ├── Block.scala
│ │ │ │ ├── CollectionInfo.scala
│ │ │ │ ├── Nft.scala
│ │ │ │ ├── NftFile.scala
│ │ │ │ ├── NftInfo.scala
│ │ │ │ ├── NftOwner.scala
│ │ │ │ ├── NftSeason.scala
│ │ │ │ ├── Summary.scala
│ │ │ │ ├── Tx.scala
│ │ │ │ ├── TxState.scala
│ │ │ │ └── Validator.scala
│ │ │ ├── repository/
│ │ │ │ ├── AccountRepository.scala
│ │ │ │ ├── BlockRepository.scala
│ │ │ │ ├── CommonQuery.scala
│ │ │ │ ├── NftFileRepository.scala
│ │ │ │ ├── NftInfoRepository.scala
│ │ │ │ ├── NftOwnerRepository.scala
│ │ │ │ ├── NftRepository.scala
│ │ │ │ ├── SummaryRepository.scala
│ │ │ │ ├── TransactionRepository.scala
│ │ │ │ └── ValidatorRepository.scala
│ │ │ └── service/
│ │ │ ├── AccountService.scala
│ │ │ ├── BlockService.scala
│ │ │ ├── NftService.scala
│ │ │ ├── SearchService.scala
│ │ │ ├── SummaryService.scala
│ │ │ ├── TransactionService.scala
│ │ │ └── ValidatorService.scala
│ │ └── test/
│ │ └── scala/
│ │ └── EmbeddedPostgreFlywayTest.scala
│ ├── lmscan-common/
│ │ ├── .js/
│ │ │ └── package.json
│ │ ├── js/
│ │ │ └── package.json
│ │ └── shared/
│ │ └── src/
│ │ └── main/
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ ├── ExplorerAPI.scala
│ │ └── model/
│ │ ├── AccountDetail.scala
│ │ ├── AccountInfo.scala
│ │ ├── ApiModel.scala
│ │ ├── BlockDetail.scala
│ │ ├── BlockInfo.scala
│ │ ├── NftActivity.scala
│ │ ├── NftDetail.scala
│ │ ├── NftFileModel.scala
│ │ ├── NftInfo.scala
│ │ ├── NftOwnerInfo.scala
│ │ ├── NftSeasonModel.scala
│ │ ├── PageNavigation.scala
│ │ ├── PageResponse.scala
│ │ ├── SearchResult.scala
│ │ ├── SummaryModel.scala
│ │ ├── TxDetail.scala
│ │ ├── TxInfo.scala
│ │ └── Validator.scala
│ ├── lmscan-frontend/
│ │ ├── .parcelrc
│ │ ├── assets/
│ │ │ ├── css/
│ │ │ │ ├── desktop.css
│ │ │ │ ├── footer.css
│ │ │ │ ├── index.html
│ │ │ │ ├── loading.css
│ │ │ │ ├── mobile.css
│ │ │ │ ├── reset.css
│ │ │ │ ├── style.css
│ │ │ │ └── tooltip.css
│ │ │ ├── index.html
│ │ │ └── load-main.js
│ │ ├── package.json
│ │ ├── project/
│ │ │ └── build.properties
│ │ ├── readme.md
│ │ └── src/
│ │ └── main/
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ └── chain/
│ │ └── lmscan/
│ │ └── frontend/
│ │ ├── LmscanFrontendApp.scala
│ │ ├── components/
│ │ │ ├── BoardView.scala
│ │ │ ├── Footer.scala
│ │ │ ├── Loader.scala
│ │ │ ├── NavBar.scala
│ │ │ ├── SearchView.scala
│ │ │ ├── common/
│ │ │ │ ├── Body.scala
│ │ │ │ ├── Head.scala
│ │ │ │ ├── Pagination.scala
│ │ │ │ └── Table.scala
│ │ │ └── detail/
│ │ │ ├── AccountDetailTable.scala
│ │ │ ├── blockDetailTable.scala
│ │ │ ├── nftDetailTable.scala
│ │ │ └── txDetailTableCommon.scala
│ │ ├── controllers/
│ │ │ ├── Model.scala
│ │ │ └── Msg.scala
│ │ ├── layouts/
│ │ │ └── DefaultLayout.scala
│ │ ├── pages/
│ │ │ ├── AccountDetailPage.scala
│ │ │ ├── AccountPage.scala
│ │ │ ├── BlockDetailPage.scala
│ │ │ ├── BlockPage.scala
│ │ │ ├── ErrorPage.scala
│ │ │ ├── MainPage.scala
│ │ │ ├── NftPage.scala
│ │ │ ├── NftTokenPage.scala
│ │ │ ├── NtfDetailPage.scala
│ │ │ ├── TxDetailPage.scala
│ │ │ ├── TxPage.scala
│ │ │ ├── VdDetailPage.scala
│ │ │ └── VdPage.scala
│ │ └── utils/
│ │ ├── Cell.scala
│ │ ├── DataProcess.scala
│ │ └── ValidData.scala
│ ├── node/
│ │ └── src/
│ │ ├── main/
│ │ │ ├── resources/
│ │ │ │ └── application.conf.sample
│ │ │ └── scala/
│ │ │ └── io/
│ │ │ └── leisuremeta/
│ │ │ └── chain/
│ │ │ └── node/
│ │ │ ├── NodeApp.scala
│ │ │ ├── NodeConfig.scala
│ │ │ ├── NodeMain.scala
│ │ │ ├── dapp/
│ │ │ │ ├── PlayNommDApp.scala
│ │ │ │ ├── PlayNommDAppFailure.scala
│ │ │ │ ├── PlayNommState.scala
│ │ │ │ └── submodule/
│ │ │ │ ├── PlayNommDAppAccount.scala
│ │ │ │ ├── PlayNommDAppAgenda.scala
│ │ │ │ ├── PlayNommDAppCreatorDao.scala
│ │ │ │ ├── PlayNommDAppGroup.scala
│ │ │ │ ├── PlayNommDAppReward.scala
│ │ │ │ ├── PlayNommDAppToken.scala
│ │ │ │ ├── PlayNommDAppVoting.scala
│ │ │ │ └── package.scala
│ │ │ ├── repository/
│ │ │ │ ├── BlockRepository.scala
│ │ │ │ ├── StateRepository.scala
│ │ │ │ └── TransactionRepository.scala
│ │ │ ├── service/
│ │ │ │ ├── BlockService.scala
│ │ │ │ ├── LocalStatusService.scala
│ │ │ │ ├── NodeInitializationService.scala
│ │ │ │ ├── RewardService.scala
│ │ │ │ ├── StateReadService.scala
│ │ │ │ └── TransactionService.scala
│ │ │ └── store/
│ │ │ ├── HashStore.scala
│ │ │ ├── KeyValueStore.scala
│ │ │ ├── SingleValueStore.scala
│ │ │ └── interpreter/
│ │ │ ├── Bag.scala
│ │ │ ├── MultiInterpreter.scala
│ │ │ ├── RedisInterpreter.scala
│ │ │ └── SwayInterpreter.scala
│ │ └── test/
│ │ └── scala/
│ │ └── io/
│ │ └── leisuremeta/
│ │ └── chain/
│ │ └── node/
│ │ └── dapp/
│ │ └── PlayNommDAppTest.scala
│ └── node-proxy/
│ └── src/
│ └── main/
│ ├── resources/
│ │ └── migration-node.json
│ └── scala/
│ └── io/
│ └── leisuremeta/
│ └── chain/
│ └── node/
│ └── proxy/
│ ├── NodeProxyApi.scala
│ ├── NodeProxyApp.scala
│ ├── NodeProxyMain.scala
│ ├── model/
│ │ ├── NodeConfig.scala
│ │ └── TxModel.scala
│ └── service/
│ ├── InternalApiService.scala
│ ├── NodeBalancer.scala
│ ├── NodeWatchService.scala
│ └── PostTxQueue.scala
└── project/
├── Settings.scala.sample
├── build.properties
└── plugins.sbt
SYMBOL INDEX (4 symbols across 4 files) FILE: modules/lmscan-backend/src/main/resources/db/common/V20230116164802__Create_t_account.sql type public (line 5) | CREATE TABLE IF NOT EXISTS public.account FILE: modules/lmscan-backend/src/main/resources/db/common/V20230116164803__Create_t_block.sql type public (line 5) | CREATE TABLE IF NOT EXISTS public.block FILE: modules/lmscan-backend/src/main/resources/db/common/V20230116164805__Create_t_nft.sql type public (line 13) | CREATE TABLE IF NOT EXISTS public.nft FILE: modules/lmscan-backend/src/main/resources/db/common/V20230116164809__Create_t_transaction.sql type public (line 5) | CREATE TABLE IF NOT EXISTS public.transaction
Condensed preview — 290 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,024K chars).
[
{
"path": ".gitignore",
"chars": 476,
"preview": "*.class\n*.log\n\n# config\napplication.conf\napplication.properties\n\n# generated by scalajs\nlmchain.js\ndev.js\nprod.js\nclient"
},
{
"path": ".jvmopts",
"chars": 14,
"preview": "-Xms1g\n-Xmx4g\n"
},
{
"path": ".scalafix.conf",
"chars": 353,
"preview": "OrganizeImports {\n coalesceToWildcardImportThreshold = 10 # Int.MaxValue\n expandRelative = false\n groupExplicitlyImpo"
},
{
"path": ".scalafmt.conf",
"chars": 384,
"preview": "version = 3.7.3\nrunner.dialect = scala3\n\nalign.preset = more\nrewrite.trailingCommas.style = always\nnewlines.beforeTypeBo"
},
{
"path": "README.md",
"chars": 1363,
"preview": "# LeisureMetaverse Blockchain \nLeisureMetaverse Blockchain is to present a practical solution to limitations of existing"
},
{
"path": "build.sbt",
"chars": 19786,
"preview": "val V = new {\n val Scala = \"3.4.3\"\n val ScalaGroup = \"3.4\"\n\n val catsEffect = \"3.5.4\"\n val tapir = \"1.10.6"
},
{
"path": "docs/LeisureMeta_Chain_API.md",
"chars": 39784,
"preview": "# LeisureMeta Chain API\n\n\n\n## API with Top Priority\n\n`GET` **/balance/{accountName}** 계정 잔고 조회\n\n> `param` movable: 잔고의 이"
},
{
"path": "docs/api_with_example.md",
"chars": 3277,
"preview": "# LeisureMeta Chain API with Example\n\n`POST` **/txhash** 트랜잭션 해시값 계산\n\n아직 해시값 계산 모듈을 제공하지 않으므로, 여기에 트랜잭션을 보내면 해시값을 계산해준다."
},
{
"path": "docs/creator-dao-documentation.md",
"chars": 5172,
"preview": "# Creator Dao\n\n## DAO 정보\n* id(Utf8): DAO ID\n* name(Utf8): 이름\n* description(Utf8): 설명\n\n## DAO 참여자\n* Founder 창립자\n * 크리에이터"
},
{
"path": "docs/dao-voting-system-design-english.md",
"chars": 11180,
"preview": "# LeisureMeta DAO Voting System Design Document\n\n## 1. Project Background and Objective\n\nLeisureMeta is an innovative bl"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/LeisureMetaChainApi.scala",
"chars": 11571,
"preview": "package io.leisuremeta.chain\npackage api\n\nimport java.time.Instant\nimport java.util.Locale\n\nimport io.circe.KeyEncoder\ni"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/Account.scala",
"chars": 1123,
"preview": "package io.leisuremeta.chain\npackage api.model\n\nimport cats.Eq\n\nimport io.circe.{Decoder, Encoder, KeyDecoder, KeyEncode"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/AccountData.scala",
"chars": 336,
"preview": "package io.leisuremeta.chain\npackage api.model\n\nimport java.time.Instant\n\nimport account.{ExternalChain, ExternalChainAd"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/AccountSignature.scala",
"chars": 353,
"preview": "package io.leisuremeta.chain\npackage api.model\n\nimport io.circe.{Decoder, Encoder}\nimport io.circe.generic.semiauto.*\n\ni"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/Block.scala",
"chars": 2070,
"preview": "package io.leisuremeta.chain\npackage api.model\n\nimport java.time.Instant\n\nimport cats.kernel.Eq\nimport scodec.bits.ByteV"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/GroupData.scala",
"chars": 146,
"preview": "package io.leisuremeta.chain\npackage api.model\n\nimport lib.datatype.Utf8\n\nfinal case class GroupData(\n name: Utf8,\n "
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/GroupId.scala",
"chars": 958,
"preview": "package io.leisuremeta.chain\npackage api.model\n\nimport io.circe.{Decoder, Encoder}\nimport sttp.tapir.{Codec, DecodeResul"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/NetworkId.scala",
"chars": 696,
"preview": "package io.leisuremeta.chain\npackage api.model\n\nimport io.circe.{Decoder, Encoder}\nimport sttp.tapir.Schema\n\nimport lib."
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/NodeStatus.scala",
"chars": 218,
"preview": "package io.leisuremeta.chain\npackage api.model\n\nimport lib.datatype.BigNat\n\nfinal case class NodeStatus(\n networkId: "
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/PublicKeySummary.scala",
"chars": 1887,
"preview": "package io.leisuremeta.chain\npackage api.model\n\nimport java.time.Instant\n\nimport cats.Eq\nimport cats.syntax.eq.given\nimp"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/Signed.scala",
"chars": 1149,
"preview": "package io.leisuremeta.chain\npackage api.model\n\nimport scodec.bits.ByteVector\nimport sttp.tapir.{Codec, DecodeResult}\nim"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/StateRoot.scala",
"chars": 917,
"preview": "package io.leisuremeta.chain\npackage api.model\n\nimport cats.Eq\n\nimport io.circe.{Decoder, Encoder}\nimport sttp.tapir.*\n\n"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/Transaction.scala",
"chars": 27127,
"preview": "package io.leisuremeta.chain\npackage api.model\n\nimport java.time.Instant\n\nimport io.circe.{Decoder, Encoder}\nimport io.c"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/TransactionWithResult.scala",
"chars": 2502,
"preview": "package io.leisuremeta.chain\npackage api.model\n\nimport lib.crypto.Hash\n\nfinal case class TransactionWithResult (\n sig"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/account/EthAddress.scala",
"chars": 1077,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage account\n\nimport io.circe.{Decoder, Encoder, KeyDecoder, KeyEncode"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/account/ExternalChain.scala",
"chars": 1808,
"preview": "package io.leisuremeta.chain\npackage api.model.account\n\nimport cats.syntax.either.*\n\nimport io.circe.{Decoder, Encoder, "
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/account/ExternalChainAddress.scala",
"chars": 1216,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage account\n\nimport io.circe.{Decoder, Encoder, KeyDecoder, KeyEncode"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/agenda/AgendaId.scala",
"chars": 1090,
"preview": "package io.leisuremeta.chain\npackage api.model.agenda\n\nimport io.circe.{Decoder, Encoder, KeyDecoder, KeyEncoder}\nimport"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/api_model/AccountInfo.scala",
"chars": 372,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage api_model\n\nimport account.*\nimport lib.datatype.Utf8\n\nfinal case "
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/api_model/ActivityInfo.scala",
"chars": 287,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage api_model\n\nimport java.time.Instant\n\nimport lib.crypto.Hash\nimpor"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/api_model/BalanceInfo.scala",
"chars": 285,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage api_model\n\nimport io.circe.generic.semiauto.*\n\nimport lib.crypto."
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/api_model/BlockInfo.scala",
"chars": 249,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage api_model\n\nimport java.time.Instant\n\nimport lib.datatype.BigNat\n\n"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/api_model/CreatorDaoInfo.scala",
"chars": 298,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage api_model\n\nimport lib.datatype.Utf8\nimport creator_dao.CreatorDao"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/api_model/GroupInfo.scala",
"chars": 137,
"preview": "package io.leisuremeta.chain.api.model\npackage api_model\n\nfinal case class GroupInfo(\n data: GroupData,\n accounts:"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/api_model/NftBalanceInfo.scala",
"chars": 353,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage api_model\n\nimport io.circe.generic.semiauto.*\n\nimport token.Token"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/api_model/RewardInfo.scala",
"chars": 615,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage api_model\n\nimport java.time.Instant\n\nimport lib.datatype.BigNat\ni"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/api_model/TxInfo.scala",
"chars": 212,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage api_model\n\nimport java.time.Instant\n\nfinal case class TxInfo(\n "
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/creator_dao/CreatorDaoData.scala",
"chars": 238,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage creator_dao\n\nimport lib.datatype.Utf8\n\nfinal case class CreatorDa"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/creator_dao/CreatorDaoId.scala",
"chars": 1178,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage creator_dao\n\nimport io.circe.{Decoder, Encoder, KeyDecoder, KeyEn"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/reward/ActivityLog.scala",
"chars": 227,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage reward\n\nimport lib.crypto.Hash\nimport lib.datatype.Utf8\n\nfinal ca"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/reward/ActivityRewardLog.scala",
"chars": 212,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage reward\n\nimport lib.crypto.Hash\n\nfinal case class ActivityRewardLo"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/reward/ActivitySnapshot.scala",
"chars": 353,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage reward\n\nimport java.time.Instant\n\nimport lib.datatype.BigNat\nimpo"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/reward/DaoActivity.scala",
"chars": 365,
"preview": "package io.leisuremeta.chain\npackage api.model.reward\n\nimport io.circe.{Decoder, Encoder}\nimport io.circe.generic.semiau"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/reward/DaoInfo.scala",
"chars": 113,
"preview": "package io.leisuremeta.chain.api.model\npackage reward\n\nfinal case class DaoInfo(\n moderators: Set[Account],\n)\n"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/reward/OwnershipRewardLog.scala",
"chars": 215,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage reward\n\nimport lib.crypto.Hash\n\nfinal case class OwnershipRewardL"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/reward/OwnershipSnapshot.scala",
"chars": 308,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage reward\n\nimport java.time.Instant\n\nimport lib.datatype.BigNat\nimpo"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/token/NftInfo.scala",
"chars": 413,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage token\n\nimport io.circe.{Decoder, Encoder}\nimport io.circe.generic"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/token/NftInfoWithPrecision.scala",
"chars": 695,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage token\n\nimport io.circe.{Decoder, Encoder}\nimport io.circe.generic"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/token/NftState.scala",
"chars": 420,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage token\n\nimport lib.crypto.Hash\nimport lib.datatype.{BigNat, Utf8}\n"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/token/Rarity.scala",
"chars": 733,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage token\n\nimport io.circe.{Decoder, Encoder, KeyDecoder, KeyEncoder}"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/token/SnapshotState.scala",
"chars": 1718,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage token\n\nimport java.time.Instant\nimport io.circe.{Decoder, Encoder"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/token/TokenDefinition.scala",
"chars": 304,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage token\n\nimport lib.datatype.{BigNat, Utf8}\n\nfinal case class Token"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/token/TokenDefinitionId.scala",
"chars": 1245,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage token\n\nimport cats.Eq\n\nimport io.circe.{Decoder, Encoder, KeyDeco"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/token/TokenDetail.scala",
"chars": 477,
"preview": "package io.leisuremeta.chain\npackage api.model.token\n\nimport io.circe.{Decoder, Encoder}\nimport io.circe.generic.semiaut"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/token/TokenId.scala",
"chars": 1088,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage token\n\nimport io.circe.{Decoder, Encoder, KeyDecoder, KeyEncoder}"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/voting/Proposal.scala",
"chars": 536,
"preview": "package io.leisuremeta.chain\npackage api.model\npackage voting\n\nimport java.time.Instant\nimport lib.datatype.{BigNat, Utf"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/voting/ProposalId.scala",
"chars": 1130,
"preview": "package io.leisuremeta.chain\npackage api.model.voting\n\nimport cats.Eq\nimport io.circe.{Decoder, Encoder}\nimport sttp.tap"
},
{
"path": "modules/api/src/main/scala/io/leisuremeta/chain/api/model/voting/VoteType.scala",
"chars": 1259,
"preview": "package io.leisuremeta.chain\npackage api.model.voting\n\nimport scala.util.Try\n\nimport cats.Eq\nimport cats.syntax.either.*"
},
{
"path": "modules/api/tx_type.txt",
"chars": 751,
"preview": "\n모든 트랜잭션 공통 create Transaction\nTransaction\n \t- TokenTx\n\t\t// LM Token\n\t\t- MintFungibleToken\n\t\t- TransferFungibleToken\n\t\t-"
},
{
"path": "modules/archive/src/main/scala/io/leisuremeta/chain/archive/ArchiveMain.scala",
"chars": 5401,
"preview": "package io.leisuremeta.chain\npackage archive\n\nimport java.nio.file.{Files, Paths, StandardOpenOption}\nimport java.time.I"
},
{
"path": "modules/bulk-insert/src/main/scala/io/leisuremeta/chain/bulkinsert/BulkInsertMain.scala",
"chars": 8780,
"preview": "package io.leisuremeta.chain\npackage bulkinsert\n\nimport scala.io.Source\n\nimport cats.data.{EitherT, Kleisli}\nimport cats"
},
{
"path": "modules/bulk-insert/src/main/scala/io/leisuremeta/chain/bulkinsert/FungibleBalanceState.scala",
"chars": 12173,
"preview": "package io.leisuremeta.chain\npackage bulkinsert\n\nimport scala.io.Source\n\nimport cats.data.{EitherT}\nimport cats.effect.{"
},
{
"path": "modules/bulk-insert/src/main/scala/io/leisuremeta/chain/bulkinsert/InvalidTx.scala",
"chars": 1767,
"preview": "package io.leisuremeta.chain\npackage bulkinsert\n\nimport java.time.Instant\n\nimport cats.effect.{Async, Resource}\nimport c"
},
{
"path": "modules/bulk-insert/src/main/scala/io/leisuremeta/chain/bulkinsert/NftBalanceState.scala",
"chars": 9320,
"preview": "package io.leisuremeta.chain\npackage bulkinsert\n\nimport scala.io.Source\n\nimport cats.data.EitherT\nimport cats.effect.IO\n"
},
{
"path": "modules/bulk-insert/src/main/scala/io/leisuremeta/chain/bulkinsert/RecoverTx.scala",
"chars": 45629,
"preview": "package io.leisuremeta.chain\npackage bulkinsert\n\nimport java.time.temporal.ChronoUnit\n\nimport cats.Monad\nimport cats.dat"
},
{
"path": "modules/eth-gateway/src/main/scala/io/leisuremeta/chain/gateway/eth/EthGatewayMain.scala",
"chars": 0,
"preview": ""
},
{
"path": "modules/eth-gateway-common/src/main/scala/io/leisuremeta/chain/gateway/eth/common/GatewayApi.scala",
"chars": 1358,
"preview": "package io.leisuremeta.chain.gateway.eth.common\n\nimport io.circe.generic.auto.*\nimport sttp.model.StatusCode\nimport sttp"
},
{
"path": "modules/eth-gateway-common/src/main/scala/io/leisuremeta/chain/gateway/eth/common/GatewayConf.scala",
"chars": 625,
"preview": "package io.leisuremeta.chain.gateway.eth.common\n\nimport pureconfig.*\nimport pureconfig.generic.derivation.default.*\n\nfin"
},
{
"path": "modules/eth-gateway-common/src/main/scala/io/leisuremeta/chain/gateway/eth/common/GatewayDecryptService.scala",
"chars": 1778,
"preview": "package io.leisuremeta.chain.gateway.eth.common\n\nimport cats.data.EitherT\nimport cats.effect.{Async, Resource}\n\nimport s"
},
{
"path": "modules/eth-gateway-common/src/main/scala/io/leisuremeta/chain/gateway/eth/common/GatewayResource.scala",
"chars": 2619,
"preview": "package io.leisuremeta.chain.gateway.eth.common\n\nimport cats.data.EitherT\nimport cats.effect.{Async, Resource}\n\nimport o"
},
{
"path": "modules/eth-gateway-common/src/main/scala/io/leisuremeta/chain/gateway/eth/common/GatewayServer.scala",
"chars": 3838,
"preview": "package io.leisuremeta.chain.gateway.eth.common\n\nimport cats.data.EitherT\nimport cats.effect.{Async, Resource}\nimport ca"
},
{
"path": "modules/eth-gateway-common/src/main/scala/io/leisuremeta/chain/gateway/eth/common/GatewaySimpleConf.scala",
"chars": 631,
"preview": "package io.leisuremeta.chain.gateway.eth.common\n\nimport pureconfig.*\nimport pureconfig.generic.derivation.default.*\n\nfin"
},
{
"path": "modules/eth-gateway-common/src/main/scala/io/leisuremeta/chain/gateway/eth/common/GatewayWeb3Service.scala",
"chars": 664,
"preview": "package io.leisuremeta.chain.gateway.eth.common\n\nimport cats.effect.{Async, Resource}\n\nimport okhttp3.OkHttpClient\nimpor"
},
{
"path": "modules/eth-gateway-common/src/main/scala/io/leisuremeta/chain/gateway/eth/common/client/GatewayApiClient.scala",
"chars": 1454,
"preview": "package io.leisuremeta.chain.gateway.eth.common\npackage client\n\nimport cats.data.EitherT\nimport cats.effect.Async\nimport"
},
{
"path": "modules/eth-gateway-common/src/main/scala/io/leisuremeta/chain/gateway/eth/common/client/GatewayDatabaseClient.scala",
"chars": 2374,
"preview": "package io.leisuremeta.chain.gateway.eth.common.client\n\nimport scala.jdk.CollectionConverters.*\n\nimport cats.data.Either"
},
{
"path": "modules/eth-gateway-common/src/main/scala/io/leisuremeta/chain/gateway/eth/common/client/GatewayKmsClient.scala",
"chars": 1961,
"preview": "package io.leisuremeta.chain.gateway.eth.common.client\n\nimport cats.data.EitherT\nimport cats.effect.{Async, Resource}\nim"
},
{
"path": "modules/eth-gateway-common/src/test/scala/io/leisuremeta/chain/gateway/eth/common/GatewayServerTest.scala",
"chars": 196,
"preview": "package io.leisuremeta.chain.gateway.eth.common\n\nimport cats.effect.IO\n\nclass GatewayServerTest extends munit.CatsEffect"
},
{
"path": "modules/eth-gateway-deposit/src/main/resources/application.conf.sample",
"chars": 503,
"preview": "eth-chain-id = 11155111\neth-lm-contract-address = \"0x02886136510172E313932EA66FE7bDC4d4C2fcc4\"\neth-multisig-contract-add"
},
{
"path": "modules/eth-gateway-deposit/src/main/scala/io/leisuremeta/chain/gateway/eth/EthGatewayDepositMain.scala",
"chars": 16423,
"preview": "package io.leisuremeta.chain\npackage gateway.eth\n\n//import java.math.BigInteger\nimport java.nio.charset.StandardCharsets"
},
{
"path": "modules/eth-gateway-setup/src/main/resources/application.conf.sample",
"chars": 1053,
"preview": "// eth-private = \"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\"\n// lm-private = \"0123456789abcdef012"
},
{
"path": "modules/eth-gateway-setup/src/main/scala/io/leisuremeta/chain/gateway/eth/setup/EthGatewaySetupConfig.scala",
"chars": 837,
"preview": "package io.leisuremeta.chain.gateway.eth.setup\n\nimport pureconfig.*\nimport pureconfig.generic.derivation.default.*\n\nimpo"
},
{
"path": "modules/eth-gateway-setup/src/main/scala/io/leisuremeta/chain/gateway/eth/setup/EthGatewaySetupMain.scala",
"chars": 11754,
"preview": "package io.leisuremeta.chain.gateway.eth.setup\n\nimport scala.jdk.CollectionConverters.*\n//import scala.jdk.FutureConvert"
},
{
"path": "modules/eth-gateway-setup/src/main/scala/io/leisuremeta/chain/gateway/eth/setup/EthGatewaySetupSimpleConfig.scala",
"chars": 731,
"preview": "package io.leisuremeta.chain.gateway.eth.setup\n\nimport pureconfig.*\nimport pureconfig.generic.derivation.default.*\n\nimpo"
},
{
"path": "modules/eth-gateway-withdraw/src/main/resources/application.conf.sample",
"chars": 947,
"preview": "//eth-chain-id = 5777\n//eth-contract-address = \"0x02886136510172E313932EA66FE7bDC4d4C2fcc4\"\n//gateway-eth-address = \"0x3"
},
{
"path": "modules/eth-gateway-withdraw/src/main/scala/io/leisuremeta/chain/gateway/eth/EthGatewayWithdrawMain.scala",
"chars": 25425,
"preview": "package io.leisuremeta.chain\npackage gateway.eth\n\nimport java.math.{BigInteger, MathContext}\nimport java.nio.file.{Files"
},
{
"path": "modules/jvm-client/src/main/scala/io/leisuremeta/chain/jvmclient/JvmClientMain.scala",
"chars": 8769,
"preview": "package io.leisuremeta.chain\npackage jvmclient\n\nimport cats.effect.{ExitCode, IO, IOApp}\nimport cats.syntax.traverse.*\ni"
},
{
"path": "modules/lib/js/src/main/scala/io/leisuremeta/chain/lib/crypto/CryptoOps.scala",
"chars": 2746,
"preview": "package io.leisuremeta.chain.lib\npackage crypto\n\nimport scala.scalajs.js.JSConverters.*\nimport scala.scalajs.js.typedarr"
},
{
"path": "modules/lib/jvm/src/main/scala/io/leisuremeta/chain/lib/crypto/CryptoOps.scala",
"chars": 5828,
"preview": "package io.leisuremeta.chain.lib\npackage crypto\n\nimport java.math.BigInteger\nimport java.security.{KeyPairGenerator, Sec"
},
{
"path": "modules/lib/shared/src/main/scala/io/leisuremeta/chain/lib/application/DAppState.scala",
"chars": 4990,
"preview": "package io.leisuremeta.chain.lib\npackage application\n\nimport cats.Monad\nimport cats.data.{EitherT, StateT}\nimport cats.s"
},
{
"path": "modules/lib/shared/src/main/scala/io/leisuremeta/chain/lib/codec/byte/ByteCodec.scala",
"chars": 563,
"preview": "package io.leisuremeta.chain.lib\npackage codec.byte\n\nimport scodec.bits.ByteVector\nimport failure.DecodingFailure\n\ntrait"
},
{
"path": "modules/lib/shared/src/main/scala/io/leisuremeta/chain/lib/codec/byte/ByteDecoder.scala",
"chars": 8113,
"preview": "package io.leisuremeta.chain.lib\npackage codec.byte\n\nimport java.time.Instant\n\nimport scala.compiletime.{erasedValue, su"
},
{
"path": "modules/lib/shared/src/main/scala/io/leisuremeta/chain/lib/codec/byte/ByteEncoder.scala",
"chars": 3990,
"preview": "package io.leisuremeta.chain.lib\npackage codec.byte\n\nimport java.time.Instant\n\nimport scala.compiletime.{erasedValue, su"
},
{
"path": "modules/lib/shared/src/main/scala/io/leisuremeta/chain/lib/crypto/Hash.scala",
"chars": 2044,
"preview": "package io.leisuremeta.chain.lib\npackage crypto\n\nimport cats.Eq\nimport cats.Contravariant\n\nimport io.circe.{Decoder, Enc"
},
{
"path": "modules/lib/shared/src/main/scala/io/leisuremeta/chain/lib/crypto/KeyPair.scala",
"chars": 253,
"preview": "package io.leisuremeta.chain.lib\npackage crypto\n\nimport datatype.UInt256BigInt\n\nfinal case class KeyPair(privateKey: UIn"
},
{
"path": "modules/lib/shared/src/main/scala/io/leisuremeta/chain/lib/crypto/PublicKey.scala",
"chars": 1286,
"preview": "package io.leisuremeta.chain.lib\npackage crypto\n\nimport cats.implicits.given\n\nimport scodec.bits.ByteVector\n\nimport code"
},
{
"path": "modules/lib/shared/src/main/scala/io/leisuremeta/chain/lib/crypto/Recover.scala",
"chars": 713,
"preview": "package io.leisuremeta.chain.lib\npackage crypto\n\ntrait Recover[A]:\n def apply(a: A, signature: Signature)(implicit\n "
},
{
"path": "modules/lib/shared/src/main/scala/io/leisuremeta/chain/lib/crypto/Sign.scala",
"chars": 806,
"preview": "package io.leisuremeta.chain.lib.crypto\n\ntrait Sign[A]:\n def apply(a: A, keyPair: KeyPair)(implicit\n hash: Hash[A]"
},
{
"path": "modules/lib/shared/src/main/scala/io/leisuremeta/chain/lib/crypto/Signature.scala",
"chars": 1373,
"preview": "package io.leisuremeta.chain.lib\npackage crypto\n\nimport cats.syntax.either.*\n\nimport io.circe.{Decoder, Encoder}\nimport "
},
{
"path": "modules/lib/shared/src/main/scala/io/leisuremeta/chain/lib/datatype/BigNat.scala",
"chars": 3004,
"preview": "package io.leisuremeta.chain.lib\npackage datatype\n\nimport scala.math.Ordering\n\nimport cats.Eq\nimport cats.implicits.give"
},
{
"path": "modules/lib/shared/src/main/scala/io/leisuremeta/chain/lib/datatype/UInt256.scala",
"chars": 3506,
"preview": "package io.leisuremeta.chain.lib\npackage datatype\n\nimport scala.util.Try\n\nimport cats.syntax.eq.given\n\nimport io.circe.{"
},
{
"path": "modules/lib/shared/src/main/scala/io/leisuremeta/chain/lib/datatype/Utf8.scala",
"chars": 1649,
"preview": "package io.leisuremeta.chain.lib\npackage datatype\n\nimport java.nio.charset.{CharacterCodingException, StandardCharsets}\n"
},
{
"path": "modules/lib/shared/src/main/scala/io/leisuremeta/chain/lib/failure/LmChainFailure.scala",
"chars": 364,
"preview": "package io.leisuremeta.chain.lib.failure\n\nimport scala.util.control.NoStackTrace\n\nsealed trait LmChainFailure extends No"
},
{
"path": "modules/lib/shared/src/main/scala/io/leisuremeta/chain/lib/merkle/MerkleTrie.scala",
"chars": 24379,
"preview": "package io.leisuremeta.chain.lib\npackage merkle\n\nimport cats.Monad\nimport cats.data.{EitherT, Kleisli, OptionT, StateT}\n"
},
{
"path": "modules/lib/shared/src/main/scala/io/leisuremeta/chain/lib/merkle/MerkleTrieNode.scala",
"chars": 6895,
"preview": "package io.leisuremeta.chain.lib\npackage merkle\n\nimport scala.compiletime.constValue\n\nimport cats.Eq\n\nimport io.github.i"
},
{
"path": "modules/lib/shared/src/main/scala/io/leisuremeta/chain/lib/merkle/MerkleTrieState.scala",
"chars": 1190,
"preview": "package io.leisuremeta.chain.lib.merkle\n\nimport cats.syntax.eq.*\n\nimport MerkleTrieNode.MerkleRoot\n\nfinal case class Mer"
},
{
"path": "modules/lib/shared/src/main/scala/io/leisuremeta/chain/lib/merkle/MerkleTrieStateDiff.scala",
"chars": 1442,
"preview": "package io.leisuremeta.chain.lib.merkle\n\nimport MerkleTrieNode.MerkleHash\n\nopaque type MerkleTrieStateDiff = Map[MerkleH"
},
{
"path": "modules/lib/shared/src/main/scala/io/leisuremeta/chain/lib/merkle/package.scala",
"chars": 2954,
"preview": "package io.leisuremeta.chain.lib\npackage merkle\n\nimport scala.compiletime.{summonInline}\n\nimport cats.Eq\nimport cats.syn"
},
{
"path": "modules/lib/shared/src/main/scala/io/leisuremeta/chain/lib/util/iron/package.scala",
"chars": 1358,
"preview": "package io.leisuremeta.chain.lib.util.iron\n\nimport scala.compiletime.{summonInline}\n\nimport io.github.iltotore.iron.*\nim"
},
{
"path": "modules/lib/shared/src/main/scala/io/leisuremeta/chain/lib/util/refined/bitVector.scala",
"chars": 896,
"preview": "package io.leisuremeta.chain.lib.util.refined\n\nimport eu.timepit.refined.api.Validate\nimport eu.timepit.refined.collecti"
},
{
"path": "modules/lib/shared/src/test/scala/io/leisuremeta/chain/lib/codec/ByteCodecTest.scala",
"chars": 715,
"preview": "package io.leisuremeta.chain.lib\npackage codec\n\nimport hedgehog.munit.HedgehogSuite\nimport hedgehog.*\n\nimport codec.byte"
},
{
"path": "modules/lib/shared/src/test/scala/io/leisuremeta/chain/lib/crypto/CryptoOpsTest.scala",
"chars": 1685,
"preview": "package io.leisuremeta.chain.lib\npackage crypto\n\nimport hedgehog.munit.HedgehogSuite\nimport hedgehog.*\n\nimport scodec.bi"
},
{
"path": "modules/lib/shared/src/test/scala/io/leisuremeta/chain/lib/datatype/BigNatTest.scala",
"chars": 1298,
"preview": "package io.leisuremeta.chain.lib\npackage datatype\n\n//import eu.timepit.refined.auto.autoUnwrap\nimport io.circe.Decoder\n/"
},
{
"path": "modules/lib/shared/src/test/scala/io/leisuremeta/chain/lib/datatype/UInt256Test.scala",
"chars": 1530,
"preview": "package io.leisuremeta.chain.lib.datatype\n\nimport hedgehog.munit.HedgehogSuite\nimport hedgehog.*\nimport scodec.bits.Byte"
},
{
"path": "modules/lib/shared/src/test/scala/io/leisuremeta/chain/lib/merkle/MerkleTrieNodeTest.scala",
"chars": 2365,
"preview": "package io.leisuremeta.chain.lib\npackage merkle\n\nimport io.github.iltotore.iron.assume\nimport scodec.bits.ByteVector\n\nim"
},
{
"path": "modules/lib/shared/src/test/scala/io/leisuremeta/chain/lib/merkle/MerkleTrieTest.scala",
"chars": 28513,
"preview": "package io.leisuremeta.chain.lib\npackage merkle\n\nimport hedgehog.munit.HedgehogSuite\nimport hedgehog.*\nimport hedgehog.s"
},
{
"path": "modules/lib/shared/src/test/scala/io/leisuremeta/chain/lib/merkle/NibblesTest.scala",
"chars": 1130,
"preview": "package io.leisuremeta.chain.lib\npackage merkle\n\nimport scodec.bits.ByteVector\n\nimport codec.byte.{ByteDecoder, ByteEnco"
},
{
"path": "modules/lmscan-agent/src/main/resources/application.conf.sample",
"chars": 263,
"preview": "scan = \"\"\nbase = \"\"\nmarket {\n key = \"\",\n token = 1,\n}\nes {\n key = \"\",\n lm = \"\",\n addrs = [\n \"\"\n ],\n}\nremote {\n "
},
{
"path": "modules/lmscan-agent/src/main/scala/io/leisuremeta/chain/lmscan/agent/ScanAgentApp.scala",
"chars": 2645,
"preview": "package io.leisuremeta.chain\npackage lmscan.agent\n\nimport cats.effect.*\nimport cats.implicits.*\nimport cats.data.*\nimpor"
},
{
"path": "modules/lmscan-agent/src/main/scala/io/leisuremeta/chain/lmscan/agent/ScanAgentConfig.scala",
"chars": 673,
"preview": "package io.leisuremeta.chain.lmscan.agent\n\nimport pureconfig.*\nimport pureconfig.generic.derivation.default.*\n\nfinal cas"
},
{
"path": "modules/lmscan-agent/src/main/scala/io/leisuremeta/chain/lmscan/agent/ScanAgentMain.scala",
"chars": 1222,
"preview": "package io.leisuremeta.chain\npackage lmscan.agent\n\nimport cats.effect.*\nimport service.RequestService\nimport cats.data.E"
},
{
"path": "modules/lmscan-agent/src/main/scala/io/leisuremeta/chain/lmscan/agent/apps/BalanceStoreApp.scala",
"chars": 7700,
"preview": "package io.leisuremeta.chain.lmscan.agent\npackage apps\n\nimport cats.effect.*\nimport cats.effect.kernel.instances.all.*\ni"
},
{
"path": "modules/lmscan-agent/src/main/scala/io/leisuremeta/chain/lmscan/agent/apps/NftStoreApp.scala",
"chars": 5565,
"preview": "package io.leisuremeta.chain.lmscan.agent\npackage apps\n\nimport cats.effect.*\nimport cats.implicits.*\nimport io.leisureme"
},
{
"path": "modules/lmscan-agent/src/main/scala/io/leisuremeta/chain/lmscan/agent/apps/NodeDataStoreApp.scala",
"chars": 18082,
"preview": "package io.leisuremeta.chain.lmscan.agent\npackage apps\n\nimport cats.effect.*\nimport cats.effect.kernel.instances.all.*\ni"
},
{
"path": "modules/lmscan-agent/src/main/scala/io/leisuremeta/chain/lmscan/agent/apps/SummaryStoreApp.scala",
"chars": 2856,
"preview": "package io.leisuremeta.chain.lmscan.agent.apps\n\nimport cats.effect.*\nimport io.leisuremeta.chain.lmscan.agent.service.Re"
},
{
"path": "modules/lmscan-agent/src/main/scala/io/leisuremeta/chain/lmscan/agent/service/RequestService.scala",
"chars": 1769,
"preview": "package io.leisuremeta.chain.lmscan.agent.service\n\nimport sttp.client3.*\nimport cats.effect.kernel.Async\nimport io.circe"
},
{
"path": "modules/lmscan-agent/src/main/scala/io/leisuremeta/chain/lmscan/agent/service/StoreService.scala",
"chars": 1178,
"preview": "package io.leisuremeta.chain.lmscan.agent\npackage service\n\nimport doobie.util.transactor.Transactor\nimport doobie.*\nimpo"
},
{
"path": "modules/lmscan-agent/src/main/scala/io/leisuremeta/chain/lmscan/agent/service/store/AccountStore.scala",
"chars": 722,
"preview": "package io.leisuremeta.chain.lmscan.agent\npackage service\n\nimport doobie.util.transactor.Transactor\nimport doobie.*\nimpo"
},
{
"path": "modules/lmscan-agent/src/main/scala/io/leisuremeta/chain/lmscan/agent/service/store/BalanceStore.scala",
"chars": 3374,
"preview": "package io.leisuremeta.chain.lmscan.agent\npackage service\n\nimport doobie.util.transactor.Transactor\nimport doobie.*\nimpo"
},
{
"path": "modules/lmscan-agent/src/main/scala/io/leisuremeta/chain/lmscan/agent/service/store/BlockStore.scala",
"chars": 1156,
"preview": "package io.leisuremeta.chain.lmscan.agent\npackage service\n\nimport doobie.util.transactor.Transactor\nimport doobie.*\nimpo"
},
{
"path": "modules/lmscan-agent/src/main/scala/io/leisuremeta/chain/lmscan/agent/service/store/NftStore.scala",
"chars": 2143,
"preview": "package io.leisuremeta.chain.lmscan.agent\npackage service\n\nimport doobie.util.transactor.Transactor\nimport doobie.*\nimpo"
},
{
"path": "modules/lmscan-agent/src/main/scala/io/leisuremeta/chain/lmscan/agent/service/store/SummaryStore.scala",
"chars": 1243,
"preview": "package io.leisuremeta.chain.lmscan.agent\npackage service\n\nimport doobie.util.transactor.Transactor\nimport doobie.*\nimpo"
},
{
"path": "modules/lmscan-agent/src/main/scala/io/leisuremeta/chain/lmscan/agent/service/store/TxStore.scala",
"chars": 1105,
"preview": "package io.leisuremeta.chain.lmscan.agent\npackage service\n\nimport doobie.util.transactor.Transactor\nimport doobie.*\nimpo"
},
{
"path": "modules/lmscan-backend/docs/flyway.md",
"chars": 816,
"preview": "<flyway command with sbt>\n1. sbt flywayBaseline -> baselineVersion까지의 migration 제외한 현재 db의 baseline 지정 \n2. sbt flywayCle"
},
{
"path": "modules/lmscan-backend/src/main/resources/application.sample.properties",
"chars": 180,
"preview": "ctx.dataSourceClassName=org.postgresql.ds.PGSimpleDataSource\nctx.url=postgresql://...\nctx.connectTimeout=30000s\nctx.test"
},
{
"path": "modules/lmscan-backend/src/main/resources/db/\bdist/V20230116164800__Alter_ts_pgdefault.sql",
"chars": 116,
"preview": "-- Tablespace: pg_default\n\n-- DROP TABLESPACE IF EXISTS pg_default;\n\nALTER TABLESPACE pg_default OWNER TO rdsadmin;\n"
},
{
"path": "modules/lmscan-backend/src/main/resources/db/\bdist/V20230116164800__Alter_ts_pgglobal.sql",
"chars": 113,
"preview": "-- Tablespace: pg_global\n\n-- DROP TABLESPACE IF EXISTS pg_global;\n\nALTER TABLESPACE pg_global OWNER TO rdsadmin;\n"
},
{
"path": "modules/lmscan-backend/src/main/resources/db/\bdist/V20230116164801__Create_r_playnomm.sql",
"chars": 212,
"preview": "-- Role: playnomm\n-- DROP ROLE IF EXISTS playnomm;\n\nCREATE ROLE playnomm WITH\n LOGIN\n NOSUPERUSER\n INHERIT\n CREATEDB"
},
{
"path": "modules/lmscan-backend/src/main/resources/db/common/V20230116164802__Create_t_account.sql",
"chars": 409,
"preview": "-- Table: public.account\n\n-- DROP TABLE IF EXISTS public.account;\n\nCREATE TABLE IF NOT EXISTS public.account\n(\n id bi"
},
{
"path": "modules/lmscan-backend/src/main/resources/db/common/V20230116164803__Create_t_block.sql",
"chars": 561,
"preview": "-- Table: public.block\n\n-- DROP TABLE IF EXISTS public.block;\n\nCREATE TABLE IF NOT EXISTS public.block\n(\n id bigint N"
},
{
"path": "modules/lmscan-backend/src/main/resources/db/common/V20230116164805__Create_t_nft.sql",
"chars": 1062,
"preview": "-- SEQUENCE: public.nft_id_seq\n-- DROP SEQUENCE IF EXISTS public.nft_id_seq;\nCREATE SEQUENCE IF NOT EXISTS public.nft_id"
},
{
"path": "modules/lmscan-backend/src/main/resources/db/common/V20230116164809__Create_t_transaction.sql",
"chars": 837,
"preview": "-- Table: public.transaction\n\n-- DROP TABLE IF EXISTS public.transaction;\n\nCREATE TABLE IF NOT EXISTS public.transaction"
},
{
"path": "modules/lmscan-backend/src/main/resources/db/seed/R__001_Seed_account.sql",
"chars": 186,
"preview": "INSERT INTO account(id, balance, amount, created_at) \nVALUES (1, 1.1, 1.1, 20230116), \n (2, 2.2, 2.2, 20230116),"
},
{
"path": "modules/lmscan-backend/src/main/resources/db/test/V20230116164801__Create_r_playnomm.sql",
"chars": 244,
"preview": "-- Role: playnomm\n-- DROP ROLE IF EXISTS playnomm;\nDROP ROLE IF EXISTS playnomm;\nCREATE ROLE playnomm WITH\n LOGIN\n NOS"
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/LmscanBackendMain.scala",
"chars": 8679,
"preview": "package io.leisuremeta.chain.lmscan\npackage backend\n\nimport cats.effect.{ExitCode, IO, IOApp, Resource}\nimport cats.effe"
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/docs/Lmscan_API.md",
"chars": 8494,
"preview": "# Lmscan API\n\n`GET` **/tx/list** 트랜잭션(Tx) 목록 페이지 조회\n\n> `param` pageNo: 페이지 번호\n> `param` sizePerRequest: 페이지 당 출력할 레코드 갯수"
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/entity/Account.scala",
"chars": 143,
"preview": "package io.leisuremeta.chain.lmscan.backend.entity\n\nfinal case class Account(\n address: String,\n createdAt: Long,\n"
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/entity/AccountMapper.scala",
"chars": 146,
"preview": "package io.leisuremeta.chain.lmscan.backend.entity\n\nfinal case class AccountMapper(\n address: String,\n hash: Strin"
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/entity/Balance.scala",
"chars": 221,
"preview": "package io.leisuremeta.chain.lmscan.backend.entity\n\nfinal case class Balance(\n address: String,\n free: BigDecimal "
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/entity/Block.scala",
"chars": 429,
"preview": "package io.leisuremeta.chain.lmscan.backend.entity\n\nimport io.leisuremeta.chain.lmscan.common.model.BlockInfo\n\nfinal cas"
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/entity/CollectionInfo.scala",
"chars": 348,
"preview": "package io.leisuremeta.chain.lmscan.backend.entity\n\nimport java.util.Date\n\nfinal case class CollectionInfo(\n tokenDef"
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/entity/Nft.scala",
"chars": 237,
"preview": "package io.leisuremeta.chain.lmscan\npackage backend\npackage entity\n\nfinal case class Nft(\n txHash: String,\n action"
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/entity/NftFile.scala",
"chars": 352,
"preview": "package io.leisuremeta.chain.lmscan.backend.entity\n\nfinal case class NftFile(\n tokenId: String,\n tokenDefId: Strin"
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/entity/NftInfo.scala",
"chars": 278,
"preview": "package io.leisuremeta.chain.lmscan.backend.entity\n\nimport java.util.Date\n\nfinal case class NftInfo(\n season: String,"
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/entity/NftOwner.scala",
"chars": 181,
"preview": "package io.leisuremeta.chain.lmscan.backend.entity\n\nfinal case class NftOwner(\n tokenId: String = \"\",\n owner: Stri"
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/entity/NftSeason.scala",
"chars": 509,
"preview": "package io.leisuremeta.chain.lmscan.backend.entity\n\nimport io.leisuremeta.chain.lmscan.common.model.NftSeasonModel\n\nfina"
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/entity/Summary.scala",
"chars": 327,
"preview": "package io.leisuremeta.chain.lmscan.backend.entity\n\nfinal case class Summary(\n lmPrice: Double,\n blockNumber: Long"
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/entity/Tx.scala",
"chars": 284,
"preview": "package io.leisuremeta.chain.lmscan.backend.entity\n\nfinal case class Tx(\n hash: String,\n signer: String,\n txTyp"
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/entity/TxState.scala",
"chars": 181,
"preview": "package io.leisuremeta.chain.lmscan.backend.entity\n\nfinal case class TxState(\n hash: String,\n blockHash: String,\n "
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/entity/Validator.scala",
"chars": 361,
"preview": "package io.leisuremeta.chain.lmscan.backend.entity\n\nimport io.leisuremeta.chain.lmscan.common.model.NodeValidator\n\nfinal"
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/repository/AccountRepository.scala",
"chars": 1481,
"preview": "package io.leisuremeta.chain.lmscan.backend.repository\n\nimport cats.effect.kernel.Async\nimport cats.data.EitherT\nimport "
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/repository/BlockRepository.scala",
"chars": 2162,
"preview": "package io.leisuremeta.chain.lmscan.backend.repository\n\nimport io.leisuremeta.chain.lmscan.common.model.PageNavigation\ni"
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/repository/CommonQuery.scala",
"chars": 2891,
"preview": "package io.leisuremeta.chain.lmscan.backend.repository\n\nimport cats.data.EitherT\nimport cats.effect.kernel.Async\nimport "
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/repository/NftFileRepository.scala",
"chars": 526,
"preview": "package io.leisuremeta.chain.lmscan.backend.repository\n\nimport io.leisuremeta.chain.lmscan.backend.entity.NftFile\nimport"
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/repository/NftInfoRepository.scala",
"chars": 2784,
"preview": "package io.leisuremeta.chain.lmscan\npackage backend\npackage repository\n\nimport entity._\nimport common.model._\nimport cat"
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/repository/NftOwnerRepository.scala",
"chars": 530,
"preview": "package io.leisuremeta.chain.lmscan.backend.repository\n\nimport cats.effect.kernel.Async\nimport cats.data.EitherT\nimport "
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/repository/NftRepository.scala",
"chars": 1211,
"preview": "package io.leisuremeta.chain.lmscan.backend.repository\n\nimport io.leisuremeta.chain.lmscan.backend.entity.Nft\nimport io."
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/repository/SummaryRepository.scala",
"chars": 515,
"preview": "package io.leisuremeta.chain.lmscan.backend.repository\n\nimport io.leisuremeta.chain.lmscan.backend.entity.Summary\nimport"
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/repository/TransactionRepository.scala",
"chars": 3565,
"preview": "package io.leisuremeta.chain.lmscan.backend.repository\n\nimport io.leisuremeta.chain.lmscan.common.model.PageNavigation\ni"
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/repository/ValidatorRepository.scala",
"chars": 721,
"preview": "package io.leisuremeta.chain.lmscan.backend.repository\n\nimport cats.effect.kernel.Async\nimport cats.data.EitherT\nimport "
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/service/AccountService.scala",
"chars": 2552,
"preview": "package io.leisuremeta.chain.lmscan.backend.service\n\nimport cats.effect.kernel.Async\nimport cats.data.EitherT\n\nimport io"
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/service/BlockService.scala",
"chars": 2453,
"preview": "package io.leisuremeta.chain.lmscan.backend.service\n\nimport cats.effect.kernel.Async\nimport cats.data.EitherT\n\nimport io"
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/service/NftService.scala",
"chars": 2950,
"preview": "package io.leisuremeta.chain.lmscan.backend.service\n\nimport cats.effect.kernel.Async\nimport cats.data.EitherT\nimport io."
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/service/SearchService.scala",
"chars": 1411,
"preview": "package io.leisuremeta.chain.lmscan.backend.service\n\nimport io.leisuremeta.chain.lmscan.common.model._\nimport cats.data."
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/service/SummaryService.scala",
"chars": 2038,
"preview": "package io.leisuremeta.chain.lmscan.backend.service\n\nimport io.leisuremeta.chain.lmscan.common.model.SummaryModel\nimport"
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/service/TransactionService.scala",
"chars": 3056,
"preview": "package io.leisuremeta.chain.lmscan.backend.service\n\nimport io.leisuremeta.chain.lmscan.backend.entity.Tx\nimport io.leis"
},
{
"path": "modules/lmscan-backend/src/main/scala/io/leisuremeta/chain/lmscan/backend/service/ValidatorService.scala",
"chars": 1505,
"preview": "package io.leisuremeta.chain.lmscan.backend.service\n\nimport cats.effect.kernel.Async\nimport cats.data.EitherT\n\nimport io"
},
{
"path": "modules/lmscan-backend/src/test/scala/EmbeddedPostgreFlywayTest.scala",
"chars": 1989,
"preview": "import com.opentable.db.postgres.junit.EmbeddedPostgresRules\nimport com.opentable.db.postgres.embedded.FlywayPreparer\nim"
},
{
"path": "modules/lmscan-common/.js/package.json",
"chars": 55,
"preview": "{\n \"dependencies\": {\n \"typescript\": \"^4.9.4\"\n }\n}\n"
},
{
"path": "modules/lmscan-common/js/package.json",
"chars": 55,
"preview": "{\n \"dependencies\": {\n \"typescript\": \"^4.9.4\"\n }\n}\n"
},
{
"path": "modules/lmscan-common/shared/src/main/scala/io/leisuremeta/ExplorerAPI.scala",
"chars": 4167,
"preview": "package io.leisuremeta.chain.lmscan.common\n\nimport sttp.model.StatusCode\nimport sttp.tapir.*\nimport sttp.tapir.generic.a"
},
{
"path": "modules/lmscan-common/shared/src/main/scala/io/leisuremeta/model/AccountDetail.scala",
"chars": 446,
"preview": "package io.leisuremeta.chain.lmscan.common.model\n\nimport io.circe.Decoder\nimport io.circe.generic.semiauto.deriveDecoder"
},
{
"path": "modules/lmscan-common/shared/src/main/scala/io/leisuremeta/model/AccountInfo.scala",
"chars": 239,
"preview": "package io.leisuremeta.chain.lmscan.common.model\n\nimport java.time.Instant\n\nfinal case class AccountInfo(\n address: Opt"
},
{
"path": "modules/lmscan-common/shared/src/main/scala/io/leisuremeta/model/ApiModel.scala",
"chars": 65,
"preview": "package io.leisuremeta.chain.lmscan.common.model\n\ntrait ApiModel\n"
},
{
"path": "modules/lmscan-common/shared/src/main/scala/io/leisuremeta/model/BlockDetail.scala",
"chars": 495,
"preview": "package io.leisuremeta.chain.lmscan.common.model\n\nimport io.circe.Decoder\nimport io.circe.generic.semiauto.deriveDecoder"
},
{
"path": "modules/lmscan-common/shared/src/main/scala/io/leisuremeta/model/BlockInfo.scala",
"chars": 415,
"preview": "package io.leisuremeta.chain.lmscan.common.model\n\nimport io.circe.Decoder\nimport io.circe.generic.semiauto.deriveDecoder"
},
{
"path": "modules/lmscan-common/shared/src/main/scala/io/leisuremeta/model/NftActivity.scala",
"chars": 429,
"preview": "package io.leisuremeta.chain.lmscan.common.model\n\nimport io.circe.Decoder\nimport io.circe.generic.semiauto.deriveDecoder"
},
{
"path": "modules/lmscan-common/shared/src/main/scala/io/leisuremeta/model/NftDetail.scala",
"chars": 334,
"preview": "package io.leisuremeta.chain.lmscan.common.model\n\nimport io.circe.Decoder\nimport io.circe.generic.semiauto.deriveDecoder"
},
{
"path": "modules/lmscan-common/shared/src/main/scala/io/leisuremeta/model/NftFileModel.scala",
"chars": 704,
"preview": "package io.leisuremeta.chain.lmscan.common.model\n\nimport io.circe.Decoder\nimport io.circe.generic.semiauto.deriveDecoder"
},
{
"path": "modules/lmscan-common/shared/src/main/scala/io/leisuremeta/model/NftInfo.scala",
"chars": 351,
"preview": "package io.leisuremeta.chain.lmscan.common.model\n\nimport java.time.Instant\n\nfinal case class NftInfoModel(\n season: O"
},
{
"path": "modules/lmscan-common/shared/src/main/scala/io/leisuremeta/model/NftOwnerInfo.scala",
"chars": 152,
"preview": "package io.leisuremeta.chain.lmscan.common.model\n\nfinal case class NftOwnerInfo(\n owner: Option[String] = None,\n n"
},
{
"path": "modules/lmscan-common/shared/src/main/scala/io/leisuremeta/model/NftSeasonModel.scala",
"chars": 360,
"preview": "package io.leisuremeta.chain.lmscan.common.model\n\nfinal case class NftSeasonModel(\n nftName: Option[String] = None,\n "
},
{
"path": "modules/lmscan-common/shared/src/main/scala/io/leisuremeta/model/PageNavigation.scala",
"chars": 846,
"preview": "package io.leisuremeta.chain.lmscan.common.model\n\nimport sttp.tapir.EndpointIO.annotations.query\nimport io.getquill.Ord\n"
},
{
"path": "modules/lmscan-common/shared/src/main/scala/io/leisuremeta/model/PageResponse.scala",
"chars": 367,
"preview": "package io.leisuremeta.chain.lmscan.common.model\n\nfinal case class PageResponse[T](\n totalCount: Long = 0L,\n total"
},
{
"path": "modules/lmscan-common/shared/src/main/scala/io/leisuremeta/model/SearchResult.scala",
"chars": 1001,
"preview": "package io.leisuremeta.chain.lmscan.common.model\n\nimport io.circe.HCursor\nimport io.circe.Decoder\n\nenum SearchResult:\n "
},
{
"path": "modules/lmscan-common/shared/src/main/scala/io/leisuremeta/model/SummaryModel.scala",
"chars": 679,
"preview": "package io.leisuremeta.chain.lmscan.common.model\n\nfinal case class SummaryModel(\n lmPrice: Option[Double] = None,\n "
},
{
"path": "modules/lmscan-common/shared/src/main/scala/io/leisuremeta/model/TxDetail.scala",
"chars": 341,
"preview": "package io.leisuremeta.chain.lmscan.common.model\n\nimport io.circe.Decoder\nimport io.circe.generic.semiauto.deriveDecoder"
},
{
"path": "modules/lmscan-common/shared/src/main/scala/io/leisuremeta/model/TxInfo.scala",
"chars": 550,
"preview": "package io.leisuremeta.chain.lmscan.common.model\n\nimport io.circe.Decoder\nimport io.circe.generic.semiauto.deriveDecoder"
},
{
"path": "modules/lmscan-common/shared/src/main/scala/io/leisuremeta/model/Validator.scala",
"chars": 741,
"preview": "package io.leisuremeta.chain.lmscan.common.model\n\nimport io.circe.Decoder\nimport io.circe.generic.semiauto.deriveDecoder"
},
{
"path": "modules/lmscan-frontend/.parcelrc",
"chars": 185,
"preview": "{\n \"extends\": \"@parcel/config-default\",\n \"compressors\": {\n \"*.{html,css,js,svg,map}\": [\n \"...\",\n \"@parcel"
},
{
"path": "modules/lmscan-frontend/assets/css/desktop.css",
"chars": 5844,
"preview": "@charset \"utf-8\";\r\n@import url(./tooltip.css);\r\n\r\n.con-wrap {\r\n min-height: calc(100vh - 350px);\r\n}\r\n\r\n.con-wrap > * {\r"
}
]
// ... and 90 more files (download for full content)
About this extraction
This page contains the full source code of the leisuremeta/leisuremeta-chain GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 290 files (924.1 KB), approximately 257.1k tokens, and a symbol index with 4 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.