main 5a2cd546471a cached
290 files
924.1 KB
257.1k tokens
4 symbols
1 requests
Download .txt
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)
Download .txt
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
Download .txt
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.

Copied to clipboard!