├── .dockerignore
├── .editorconfig
├── .env-example
├── .eslintignore
├── .eslintrc.json
├── .gitattributes
├── .github
└── workflows
│ ├── docker.yml
│ ├── install.yml
│ ├── node.js.yml
│ ├── pages.yml
│ └── shellcheck.yml
├── .gitignore
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── LICENSE
├── README.md
├── RELEASE.md
├── apps
├── sps-validator-ui
│ ├── .eslintrc.json
│ ├── Dockerfile
│ ├── index.html
│ ├── nginx
│ │ └── nginx.conf
│ ├── openapi.yaml
│ ├── postcss.config.js
│ ├── project.json
│ ├── public
│ │ └── favicon.ico
│ ├── src
│ │ ├── app
│ │ │ ├── app.tsx
│ │ │ ├── components
│ │ │ │ ├── AuthorizedAccountWrapper.tsx
│ │ │ │ ├── InfoTooltip.tsx
│ │ │ │ ├── LocaleNumber.tsx
│ │ │ │ ├── Table.tsx
│ │ │ │ ├── ValidatorName.tsx
│ │ │ │ ├── ValidatorStatsTable.tsx
│ │ │ │ ├── ValidatorVotesTable.tsx
│ │ │ │ └── layout
│ │ │ │ │ ├── Navbar.tsx
│ │ │ │ │ ├── Sidebar.tsx
│ │ │ │ │ └── index.ts
│ │ │ ├── context
│ │ │ │ └── MetricsContext.tsx
│ │ │ ├── hooks
│ │ │ │ ├── LocalStorage.ts
│ │ │ │ ├── Promise.ts
│ │ │ │ └── index.ts
│ │ │ ├── pages
│ │ │ │ ├── AccountBalances.tsx
│ │ │ │ ├── AccountVotes.tsx
│ │ │ │ ├── Home.tsx
│ │ │ │ ├── ManageValidatorNode.tsx
│ │ │ │ ├── ManageVotes.tsx
│ │ │ │ ├── Settings.tsx
│ │ │ │ ├── TokenBalances.tsx
│ │ │ │ ├── ValidatorNodes.tsx
│ │ │ │ └── block-explorer
│ │ │ │ │ ├── Account.tsx
│ │ │ │ │ ├── Block.tsx
│ │ │ │ │ ├── BlockExplorer.tsx
│ │ │ │ │ ├── Chips.tsx
│ │ │ │ │ ├── OmniBox.tsx
│ │ │ │ │ ├── Transaction.tsx
│ │ │ │ │ └── utils.ts
│ │ │ └── services
│ │ │ │ ├── TxLookupService.ts
│ │ │ │ ├── hive
│ │ │ │ ├── core
│ │ │ │ │ └── Hive.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── keychain.d.ts
│ │ │ │ └── services
│ │ │ │ │ └── HiveService.ts
│ │ │ │ └── openapi
│ │ │ │ ├── core
│ │ │ │ ├── ApiError.ts
│ │ │ │ ├── ApiRequestOptions.ts
│ │ │ │ ├── ApiResult.ts
│ │ │ │ ├── CancelablePromise.ts
│ │ │ │ ├── OpenAPI.ts
│ │ │ │ └── request.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── models
│ │ │ │ ├── Account.ts
│ │ │ │ ├── Balance.ts
│ │ │ │ ├── Balances.ts
│ │ │ │ ├── BalancesCount.ts
│ │ │ │ ├── Block.ts
│ │ │ │ ├── NoPriceAtPoint.ts
│ │ │ │ ├── PoolSettings.ts
│ │ │ │ ├── PriceAtPoint.ts
│ │ │ │ ├── Status.ts
│ │ │ │ ├── TokenSupply.ts
│ │ │ │ ├── TokenTransferTransaction.ts
│ │ │ │ ├── TokenTransferTransactions.ts
│ │ │ │ ├── Transaction.ts
│ │ │ │ ├── TransitionStatus.ts
│ │ │ │ ├── TransitionStatuses.ts
│ │ │ │ ├── Validator.ts
│ │ │ │ ├── ValidatorConfig.ts
│ │ │ │ ├── ValidatorVote.ts
│ │ │ │ ├── ValidatorVotes.ts
│ │ │ │ └── Validators.ts
│ │ │ │ └── services
│ │ │ │ └── DefaultService.ts
│ │ ├── assets
│ │ │ └── .gitkeep
│ │ ├── main.tsx
│ │ └── styles.css
│ ├── tailwind.config.js
│ ├── tsconfig.app.json
│ ├── tsconfig.json
│ ├── tsconfig.spec.json
│ └── vite.config.ts
└── sps-validator
│ ├── .eslintrc.js
│ ├── Dockerfile
│ ├── jest.config.ts
│ ├── project.json
│ ├── src
│ ├── __tests__
│ │ ├── action-fixture.ts
│ │ ├── db-helpers.ts
│ │ ├── disposable.ts
│ │ ├── fake-db.ts
│ │ ├── fixture.ts
│ │ ├── process-op.ts
│ │ ├── shop
│ │ │ ├── bonus.json
│ │ │ ├── hardcoded-shop.ts
│ │ │ └── license_tranches
│ │ │ │ ├── presale.json
│ │ │ │ ├── tranche_1.json
│ │ │ │ ├── tranche_2.json
│ │ │ │ ├── tranche_3.json
│ │ │ │ ├── tranche_4.json
│ │ │ │ ├── tranche_5.json
│ │ │ │ ├── tranche_6.json
│ │ │ │ └── tranche_7.json
│ │ ├── structure.sql
│ │ └── test-composition-root.ts
│ ├── express.d.ts
│ ├── jest.d.ts
│ ├── main.ts
│ ├── plugins
│ │ └── kill_plugin.ts
│ └── sps
│ │ ├── actions
│ │ ├── account
│ │ │ ├── set_authority.test.ts
│ │ │ └── set_authority.ts
│ │ ├── burn.ts
│ │ ├── config_update.test.ts
│ │ ├── config_update.ts
│ │ ├── index.ts
│ │ ├── missed_blocks.ts
│ │ ├── pools
│ │ │ ├── add.ts
│ │ │ ├── claim.ts
│ │ │ ├── disable.ts
│ │ │ ├── pool.test.ts
│ │ │ └── update.ts
│ │ ├── promises
│ │ │ ├── cancel_promise.test.ts
│ │ │ ├── cancel_promise.ts
│ │ │ ├── complete_promise.test.ts
│ │ │ ├── complete_promise.ts
│ │ │ ├── create_promise.test.ts
│ │ │ ├── create_promise.ts
│ │ │ ├── expire_promises.test.ts
│ │ │ ├── expire_promises.ts
│ │ │ ├── fulfill_promise.test.ts
│ │ │ ├── fulfill_promise.ts
│ │ │ ├── fulfill_promise_multi.test.ts
│ │ │ ├── fulfill_promises.ts
│ │ │ ├── reverse_promise.test.ts
│ │ │ └── reverse_promise.ts
│ │ ├── schema.test.ts
│ │ ├── schema.ts
│ │ ├── test_action.ts
│ │ ├── tokens
│ │ │ ├── burn.test.ts
│ │ │ ├── burn.ts
│ │ │ ├── cancel_unstake_tokens.test.ts
│ │ │ ├── cancel_unstake_tokens.ts
│ │ │ ├── claim_staking_rewards.test.ts
│ │ │ ├── claim_staking_rewards.ts
│ │ │ ├── delegate_tokens.test.ts
│ │ │ ├── delegate_tokens.ts
│ │ │ ├── mint_tokens.ts
│ │ │ ├── return_tokens.test.ts
│ │ │ ├── return_tokens.ts
│ │ │ ├── shop_purchase.test.ts
│ │ │ ├── shop_purchase.ts
│ │ │ ├── stake_tokens.test.ts
│ │ │ ├── stake_tokens.ts
│ │ │ ├── stake_tokens_multi.ts
│ │ │ ├── token_award.test.ts
│ │ │ ├── token_award.ts
│ │ │ ├── token_transfer.test.ts
│ │ │ ├── token_transfer.ts
│ │ │ ├── token_transfer_multi.test.ts
│ │ │ ├── token_transfer_multi.ts
│ │ │ ├── token_unstaking.test.ts
│ │ │ ├── token_unstaking.ts
│ │ │ ├── undelegate_tokens.test.ts
│ │ │ ├── undelegate_tokens.ts
│ │ │ ├── undelegate_tokens_multi.test.ts
│ │ │ ├── undelegate_tokens_multi.ts
│ │ │ ├── unstake_tokens.test.ts
│ │ │ └── unstake_tokens.ts
│ │ ├── transition.test.ts
│ │ ├── transitions
│ │ │ ├── fix_vote_weight.test.ts
│ │ │ └── fix_vote_weight.ts
│ │ ├── utils.ts
│ │ └── validator
│ │ │ ├── activate_license.test.ts
│ │ │ ├── activate_license.ts
│ │ │ ├── approve_validator.test.ts
│ │ │ ├── approve_validator.ts
│ │ │ ├── check_in_validator.test.ts
│ │ │ ├── check_in_validator.ts
│ │ │ ├── deactivate_license.test.ts
│ │ │ ├── deactivate_license.ts
│ │ │ ├── expire_check_ins.test.ts
│ │ │ ├── expire_check_ins.ts
│ │ │ ├── price_feed.test.ts
│ │ │ ├── price_feed.ts
│ │ │ ├── unapprove_validator.test.ts
│ │ │ ├── unapprove_validator.ts
│ │ │ ├── update_missed_blocks.ts
│ │ │ ├── update_validator.test.ts
│ │ │ ├── update_validator.ts
│ │ │ ├── validate_block.test.ts
│ │ │ └── validate_block.ts
│ │ ├── api
│ │ ├── api.test.ts
│ │ ├── health.test.ts
│ │ ├── index.ts
│ │ ├── middleware.test.ts
│ │ ├── sps.ts
│ │ └── transition.ts
│ │ ├── bookkeeping.ts
│ │ ├── composition-root.ts
│ │ ├── config.test.ts
│ │ ├── config.ts
│ │ ├── convict-config.ts
│ │ ├── db
│ │ └── db.test.ts
│ │ ├── entities
│ │ ├── block.test.ts
│ │ ├── block.ts
│ │ ├── claims.ts
│ │ ├── custom_json_id.test.ts
│ │ ├── operation.ts
│ │ ├── promises
│ │ │ └── promise.ts
│ │ ├── tables.ts
│ │ ├── tokens
│ │ │ ├── active_delegations.ts
│ │ │ ├── balance.test.ts
│ │ │ ├── balance.ts
│ │ │ ├── balance_history.ts
│ │ │ ├── eth.ts
│ │ │ ├── hive_engine.ts
│ │ │ ├── sps.abi.json
│ │ │ ├── staking_rewards.test.ts
│ │ │ ├── staking_rewards.ts
│ │ │ ├── token_unstaking.ts
│ │ │ └── types
│ │ │ │ └── ethers-contracts
│ │ │ │ ├── SpsAbi.ts
│ │ │ │ ├── common.ts
│ │ │ │ ├── factories
│ │ │ │ ├── SpsAbi__factory.ts
│ │ │ │ └── index.ts
│ │ │ │ └── index.ts
│ │ └── validator
│ │ │ ├── validator.ts
│ │ │ ├── validator_check_in.ts
│ │ │ └── validator_vote.ts
│ │ ├── entry-point.ts
│ │ ├── features
│ │ ├── delegation
│ │ │ ├── delegation_manager.ts
│ │ │ ├── delegation_promises.ts
│ │ │ └── index.ts
│ │ ├── price_feed
│ │ │ ├── config.ts
│ │ │ ├── external-feeds.ts
│ │ │ ├── index.ts
│ │ │ ├── plugin.ts
│ │ │ └── price-feed.ts
│ │ ├── promises
│ │ │ ├── index.ts
│ │ │ └── promse-manager.ts
│ │ ├── tokens
│ │ │ ├── index.ts
│ │ │ ├── supported-tokens.ts
│ │ │ └── virtual-tokens.ts
│ │ ├── transition
│ │ │ ├── index.ts
│ │ │ └── transitions.ts
│ │ └── validator
│ │ │ ├── config.ts
│ │ │ ├── index.ts
│ │ │ ├── license-manager.ts
│ │ │ └── license-plugin.ts
│ │ ├── hive-stream.ts
│ │ ├── hive.ts
│ │ ├── jest.global-db.ts
│ │ ├── jest.global-setup.ts
│ │ ├── jest.global-teardown.ts
│ │ ├── jest.setup.ts
│ │ ├── logger.ts
│ │ ├── manual-disposable.ts
│ │ ├── pool-manager.ts
│ │ ├── pools.ts
│ │ ├── primer.ts
│ │ ├── processor.ts
│ │ ├── repositories
│ │ └── transactions.test.ts
│ │ ├── shop.test.ts
│ │ ├── snapshot.ts
│ │ ├── socket.test.ts
│ │ ├── socket.ts
│ │ ├── startup.test.ts
│ │ ├── sync.ts
│ │ ├── utilities
│ │ ├── price_feed.test.ts
│ │ ├── snapshot.test.ts
│ │ └── validator-shop.ts
│ │ └── validator-shop.ts
│ ├── tsconfig.app.json
│ ├── tsconfig.json
│ └── tsconfig.spec.json
├── atom
├── .eslintrc.json
├── LICENSE
├── jest.config.js
├── package.json
├── project.json
├── src
│ ├── atom.ts
│ ├── changeHandler.ts
│ ├── deref.ts
│ ├── dispose.ts
│ ├── error-messages.ts
│ ├── getValidator.ts
│ ├── index.ts
│ ├── internal-state.ts
│ ├── internal-types.ts
│ ├── prettyPrint.ts
│ ├── set.ts
│ ├── setValidator.ts
│ ├── swap.ts
│ ├── test
│ │ ├── atom.spec.ts
│ │ ├── changeHandler.spec.ts
│ │ ├── deref.spec.tsx
│ │ ├── set.spec.tsx
│ │ ├── swap.spec.tsx
│ │ └── validator.spec.ts
│ └── throwIfNotAtom.ts
├── tsconfig.json
├── tsconfig.lib.json
└── tsconfig.spec.json
├── docker-compose.yml
├── install.sh
├── jest.config.ts
├── jest.preset.js
├── monad
├── .eslintrc.json
├── .gitignore
├── jest.config.ts
├── package.json
├── project.json
├── src
│ ├── lib.ts
│ ├── result.test.ts
│ └── result.ts
├── tsconfig.json
├── tsconfig.lib.json
└── tsconfig.spec.json
├── npmrc.docker
├── nx.json
├── package-lock.json
├── package.json
├── postgres
├── Dockerfile
├── Dockerfile-alpine-no-work
└── setup-preload.sh
├── run.ps1
├── run.sh
├── scripts
├── check_fork.js
├── ci-lazy-regenerate-structure.sh
├── ci-regenerate-structure.sh
├── create-snapshot.sh
├── create-structural-dump.sh
├── db_restore.sql
├── new_db_config.sql
├── prebake.sh
└── validator_schema.sql
├── sqitch
├── Dockerfile
├── README.md
├── deploy
│ ├── active_delegations.sql
│ ├── appschema.sql
│ ├── hive_account_authority.sql
│ ├── partitions.sql
│ ├── post-snapshot-restore-function.sql
│ ├── post-snapshot-restore-partition-prep.sql
│ ├── pre-snapshot-restore-function.sql
│ ├── pre-snapshot.sql
│ ├── price_feed.sql
│ ├── promises.sql
│ ├── running-validator-reward-pool.sql
│ ├── snapshot-function-v2.sql
│ ├── snapshot-function.sql
│ ├── snapshot-tables.sql
│ ├── snapshot.sql
│ ├── transaction_players_perf.sql
│ ├── validator_api_url.sql
│ └── validators.sql
├── docker-entrypoint.sh
├── extract-snapshot.sh
├── init-db.sh
├── revert
│ ├── active_delegations.sql
│ ├── appschema.sql
│ ├── hive_account_authority.sql
│ ├── partitions.sql
│ ├── post-snapshot-restore-function.sql
│ ├── post-snapshot-restore-partition-prep.sql
│ ├── pre-snapshot-restore-function.sql
│ ├── pre-snapshot.sql
│ ├── price_feed.sql
│ ├── promises.sql
│ ├── running-validator-reward-pool.sql
│ ├── snapshot-function-v2.sql
│ ├── snapshot-function.sql
│ ├── snapshot-tables.sql
│ ├── snapshot.sql
│ ├── transaction_players_perf.sql
│ ├── validator_api_url.sql
│ └── validators.sql
├── sqitch-data.plan
├── sqitch.conf
├── sqitch.plan
├── staggered-deploy.sh
└── verify
│ ├── active_delegations.sql
│ ├── appschema.sql
│ ├── hive_account_authority.sql
│ ├── partitions.sql
│ ├── post-snapshot-restore-function.sql
│ ├── post-snapshot-restore-partition-prep.sql
│ ├── pre-snapshot-restore-function.sql
│ ├── pre-snapshot.sql
│ ├── price_feed.sql
│ ├── promises.sql
│ ├── reward_delegations.sql
│ ├── running-validator-reward-pool.sql
│ ├── snapshot-function-v2.sql
│ ├── snapshot-function.sql
│ ├── snapshot-tables.sql
│ ├── snapshot.sql
│ ├── transaction_players_perf.sql
│ ├── validator_api_url.sql
│ └── validators.sql
├── tsconfig.base.json
├── validator
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── jest.config.js
├── licenses
│ ├── LICENSE-APACHE-2.0.md
│ ├── LICENSE-BSD-3-CLAUSE.md
│ └── LICENSE-CC-BY-SA-4.0.txt
├── npmrc.docker
├── package.json
├── project.json
├── src
│ ├── actions
│ │ ├── ActionConstructor.ts
│ │ ├── action.ts
│ │ ├── admin_action.ts
│ │ ├── authority.ts
│ │ ├── index.ts
│ │ ├── schema.test.ts
│ │ ├── schema.ts
│ │ ├── test_action.ts
│ │ ├── transition.ts
│ │ └── virtual.ts
│ ├── api
│ │ ├── activator.ts
│ │ ├── health.ts
│ │ ├── index.ts
│ │ └── middleware.ts
│ ├── config
│ │ ├── index.ts
│ │ ├── pools.test.ts
│ │ ├── pools.ts
│ │ ├── staking.ts
│ │ └── updater.ts
│ ├── db
│ │ ├── Block.ts
│ │ ├── columns.ts
│ │ ├── config
│ │ │ ├── bigint-counts.d.ts
│ │ │ └── mapping.ts
│ │ ├── tables.ts
│ │ └── transaction.ts
│ ├── entities
│ │ ├── account
│ │ │ └── hive_account.ts
│ │ ├── block.ts
│ │ ├── bookkeeping.test.ts
│ │ ├── bookkeeping.ts
│ │ ├── claims.ts
│ │ ├── errors.ts
│ │ ├── event_log.ts
│ │ ├── operation.ts
│ │ ├── promises
│ │ │ └── promise.ts
│ │ ├── tokens
│ │ │ ├── active_delegations.ts
│ │ │ ├── balance.ts
│ │ │ ├── balance_history.ts
│ │ │ ├── price_history.ts
│ │ │ ├── staking_rewards.ts
│ │ │ ├── token_transfer.ts
│ │ │ └── token_unstaking.ts
│ │ └── validator
│ │ │ ├── types.ts
│ │ │ ├── validator.ts
│ │ │ ├── validator_vote.ts
│ │ │ └── validator_vote_history.ts
│ ├── entry-point.ts
│ ├── features
│ │ ├── delegation
│ │ │ ├── delegation_manager.ts
│ │ │ ├── delegation_promises.ts
│ │ │ └── index.ts
│ │ ├── promises
│ │ │ ├── index.ts
│ │ │ ├── promise_handler.ts
│ │ │ └── promise_manager.ts
│ │ └── tokens
│ │ │ ├── balance_manager.ts
│ │ │ └── staking_manager.ts
│ ├── hive.ts
│ ├── lib.ts
│ ├── libs
│ │ ├── acl
│ │ │ └── admin.ts
│ │ ├── async-queue.ts
│ │ ├── guards.ts
│ │ ├── head-block-observer.ts
│ │ ├── hive-stream.ts
│ │ ├── mint.ts
│ │ ├── plugin.test.ts
│ │ ├── plugin.ts
│ │ ├── pool.ts
│ │ ├── rng-iterator.ts
│ │ ├── routing
│ │ │ ├── decorators.test.ts
│ │ │ ├── decorators.ts
│ │ │ ├── index.ts
│ │ │ ├── precompute.test.ts
│ │ │ ├── precompute.ts
│ │ │ └── routing.test.ts
│ │ ├── shop
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ └── strict-enum.ts
│ ├── plugins
│ │ └── SimpleLogPlugin.ts
│ ├── processor.ts
│ ├── repositories
│ │ └── transactions
│ │ │ ├── Transaction.ts
│ │ │ └── index.ts
│ ├── socket.ts
│ ├── sync
│ │ ├── index.ts
│ │ └── type.ts
│ ├── utilities
│ │ ├── accounts.ts
│ │ ├── action_type.ts
│ │ ├── block_num.test.ts
│ │ ├── block_num.ts
│ │ ├── cache.test.ts
│ │ ├── cache.ts
│ │ ├── config.test.ts
│ │ ├── config.ts
│ │ ├── convict.d.ts
│ │ ├── date.ts
│ │ ├── dependency-injection.ts
│ │ ├── derive.test.ts
│ │ ├── derive.ts
│ │ ├── entry-options.ts
│ │ ├── express.d.ts
│ │ ├── guards.ts
│ │ ├── mint_manager.ts
│ │ ├── pool_manager.ts
│ │ ├── price_feed.ts
│ │ ├── primer.ts
│ │ ├── snapshot.ts
│ │ ├── stop.ts
│ │ ├── token_support.test.ts
│ │ ├── token_support.ts
│ │ └── traits.ts
│ └── utils.ts
├── tsconfig.json
├── tsconfig.lib.json
└── tsconfig.spec.json
└── vitest.workspace.ts
/.dockerignore:
--------------------------------------------------------------------------------
1 | dist
2 | coverage
3 | README.md
4 | .nvmrc
5 | .gitignore
6 | .eslintrc.js
7 | .eslintrc.json
8 | jest.config.js
9 | .prettierrc
10 | !bridge/sqitch/
11 | doc
12 | node_modules
13 | *.test.ts
14 | *.spec.ts
15 | tsconfig.lib.json
16 | tests
17 | Dockerfile
18 | .env
19 | .env.example
20 |
21 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 4
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
15 | [package.json]
16 | indent_size = 2
17 |
18 | [*.yml]
19 | indent_size = 2
20 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "plugins": ["@nx"],
4 | "extends": ["plugin:prettier/recommended"],
5 | "overrides": [
6 | {
7 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
8 | "rules": {
9 | "@nx/enforce-module-boundaries": [
10 | "error",
11 | {
12 | "enforceBuildableLibDependency": true,
13 | "allow": [],
14 | "depConstraints": [
15 | {
16 | "sourceTag": "*",
17 | "onlyDependOnLibsWithTags": ["*"]
18 | }
19 | ]
20 | }
21 | ]
22 | }
23 | },
24 | {
25 | "files": ["*.ts", "*.tsx"],
26 | "extends": ["plugin:@nx/typescript"],
27 | "rules": {
28 | "@typescript-eslint/no-extra-semi": "error",
29 | "no-extra-semi": "off"
30 | }
31 | },
32 | {
33 | "files": ["*.js", "*.jsx"],
34 | "extends": ["plugin:@nx/javascript"],
35 | "rules": {
36 | "@typescript-eslint/no-extra-semi": "error",
37 | "no-extra-semi": "off"
38 | }
39 | }
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Linux/OSX specific files that are only run on Windows in WSL
5 | *.sh text eol=lf
6 |
--------------------------------------------------------------------------------
/.github/workflows/docker.yml:
--------------------------------------------------------------------------------
1 |
2 | name: Docker Push
3 |
4 | on:
5 | workflow_dispatch:
6 | release:
7 | types: [published]
8 |
9 | env:
10 | REGISTRY: ghcr.io
11 | IMAGE_NAME: ${{ github.repository }}
12 |
13 | jobs:
14 | build:
15 | runs-on: ubuntu-latest
16 | permissions:
17 | contents: read
18 | packages: write
19 | attestations: write
20 | id-token: write
21 | steps:
22 | - name: Checkout
23 | uses: actions/checkout@v4
24 |
25 | - name: Log in to the Container registry
26 | uses: docker/login-action@v3
27 | with:
28 | registry: ${{ env.REGISTRY }}
29 | username: ${{ github.actor }}
30 | password: ${{ secrets.GITHUB_TOKEN }}
31 |
32 | - name: Extract metadata (tags, labels) for Docker
33 | id: meta
34 | uses: docker/metadata-action@v5
35 | with:
36 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
37 |
38 | - name: Build and push Docker image
39 | id: push
40 | uses: docker/build-push-action@v6
41 | with:
42 | context: .
43 | file: ./apps/sps-validator/Dockerfile
44 | tags: ${{ steps.meta.outputs.tags }}
45 | annotations: ${{ steps.meta.outputs.annotations }}
46 | labels: ${{ steps.meta.outputs.labels }}
47 | push: true
48 |
49 | - name: Generate artifact attestation
50 | uses: actions/attest-build-provenance@v2
51 | with:
52 | subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
53 | subject-digest: ${{ steps.push.outputs.digest }}
54 | push-to-registry: true
55 |
--------------------------------------------------------------------------------
/.github/workflows/install.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # This workflow will test the install scripts
3 |
4 | name: Install CI
5 |
6 | on:
7 | push:
8 | branches:
9 | - master
10 | - release-*
11 | pull_request:
12 | branches:
13 | - master
14 | - release-*
15 |
16 | jobs:
17 | build:
18 | permissions:
19 | checks: write
20 | contents: read
21 | pull-requests: write
22 | packages: read
23 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
24 | strategy:
25 | matrix:
26 | os: [ubuntu-latest, macos-14, macos-14-large]
27 | runs-on: ubuntu-latest
28 | steps:
29 | - uses: actions/checkout@v4
30 | with:
31 | fetch-depth: 0
32 |
33 | - name: 'Copy .env-example to .env'
34 | run: cp .env-example .env
35 |
36 | - name: 'Run build'
37 | run: ./run.sh build
38 |
39 | - name: 'Start validator'
40 | run: |
41 | # check for "Blocks to head" in the logs
42 | ./run.sh start validator-silent
43 | ./run.sh logs | tee validator.log & sleep 10
44 | grep -q "Processed block" validator.log
45 | # check return code
46 | if [ $? -ne 0 ]; then
47 | echo "Validator failed to start"
48 | exit 1
49 | fi
50 | ./run.sh stop
51 |
--------------------------------------------------------------------------------
/.github/workflows/pages.yml:
--------------------------------------------------------------------------------
1 | name: Pages
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | build:
8 | concurrency: ci-${{ github.ref }}
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout
12 | uses: actions/checkout@v3
13 |
14 | - name: Install and Build
15 | run: |
16 | npm ci --silent
17 | npx nx build sps-validator-ui --configuration=production
18 | cp dist/apps/sps-validator-ui/index.html dist/apps/sps-validator-ui/404.html
19 | env:
20 | BASE_URL: /SPS-Validator/
21 | VALIDATOR_PREFIX: sm_
22 | VALIDATOR_API_URL: https://splinterlands-validator-api.splinterlands.com
23 |
24 | - name: Upload static files as artifact
25 | id: deployment
26 | uses: actions/upload-pages-artifact@v3 # or specific "vX.X.X" version tag for this action
27 | with:
28 | path: dist/apps/sps-validator-ui/
29 |
30 | deploy:
31 | environment:
32 | name: github-pages
33 | url: ${{ steps.deployment.outputs.page_url }}
34 | runs-on: ubuntu-latest
35 | needs: build
36 | permissions:
37 | pages: write
38 | id-token: write
39 | steps:
40 | - name: Deploy to GitHub Pages
41 | id: deployment
42 | uses: actions/deploy-pages@v4
43 |
--------------------------------------------------------------------------------
/.github/workflows/shellcheck.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: 'Shellcheck CI'
3 |
4 | on:
5 | push:
6 | branches:
7 | - master
8 | - release-*
9 | pull_request:
10 | branches:
11 | - master
12 | - release-*
13 |
14 | jobs:
15 | shellcheck:
16 | name: Shellcheck
17 | runs-on: ubuntu-latest
18 | steps:
19 | - uses: actions/checkout@v4
20 | - name: Run ShellCheck
21 | uses: ludeeus/action-shellcheck@master
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # MacOS Stuff
2 | .DS_Store
3 | /snapshot.sql
4 | /snapshot.env
5 | /sqitch/downloaded-snapshot.zip
6 | node_modules
7 | /.vscode/
8 | /dist/
9 | .env
10 | config.ini
11 | /sqitch/validator-data-latest.zip
12 | .nx
13 | coverage
14 | tmp
15 | snapshot.sql
16 | snapshot.zip
17 | link.txt
18 | vite.config.*.timestamp*
19 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v23.3.0
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Add files here to ignore them from prettier formatting
2 |
3 | /dist
4 | /coverage
5 |
6 | /.nx/cache
7 | /.nx/workspace-data
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all",
4 | "arrowParens": "always",
5 | "tabWidth": 4,
6 | "printWidth": 180
7 | }
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Matt Rosen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/RELEASE.md:
--------------------------------------------------------------------------------
1 | ## Creating Releases
2 |
3 | When master branch is ready to be released:
4 | - Update the `VERSION=` variable in install.sh to `v{version}`.
5 | - Update the VERSION variable in the docker-compose files validator service
6 | - Create a new branch named `release-{version}` from the previous release branch.
7 | - Create a PR from master to the new branch and review the changes. The tests will run.
8 | - Create a new release on the new branch, the tag should match the `VERSION=` variable you set (`v{version}`)
9 | - Force create/push a new tag, `vlatest`, pointed to the new branch (`git tag -f vlatest v{version}^{}`).
10 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["plugin:@nx/react", "../../.eslintrc.json"],
3 | "ignorePatterns": ["!**/*"],
4 | "overrides": [
5 | {
6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7 | "rules": {}
8 | },
9 | {
10 | "files": ["*.ts", "*.tsx"],
11 | "rules": {}
12 | },
13 | {
14 | "files": ["*.js", "*.jsx"],
15 | "rules": {}
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:23.3-alpine3.20 AS build
2 |
3 | WORKDIR /app
4 |
5 | ENV PATH /app/node_modules/.bin:$PATH
6 |
7 | COPY package.json package-lock.json ./
8 | RUN npm ci --silent
9 |
10 | COPY . ./
11 |
12 | ARG VALIDATOR_API_URL "http://localhost:3333/"
13 | ARG VALIDATOR_PREFIX "sm_"
14 |
15 | ENV VALIDATOR_API_URL=$VALIDATOR_API_URL
16 | ENV VALIDATOR_PREFIX=$VALIDATOR_PREFIX
17 |
18 | RUN npx nx build sps-validator-ui --configuration=production
19 |
20 | FROM nginx:stable-alpine
21 | COPY --from=build /app/dist/apps/sps-validator-ui/ /usr/share/nginx/html
22 | COPY /apps/sps-validator-ui/nginx/nginx.conf /etc/nginx/conf.d/default.conf
23 |
24 | EXPOSE 80
25 | CMD ["nginx", "-g", "daemon off;"]
26 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SPS Validator Network
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/nginx/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 |
4 | location / {
5 | root /usr/share/nginx/html;
6 | index index.html index.htm;
7 | try_files $uri $uri/ /index.html;
8 | }
9 |
10 | error_page 404 /index.html;
11 | error_page 500 502 503 504 /50x.html;
12 |
13 | location = /50x.html {
14 | root /usr/share/nginx/html;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/postcss.config.js:
--------------------------------------------------------------------------------
1 | const { join } = require('path');
2 |
3 | // Note: If you use library-specific PostCSS/Tailwind configuration then you should remove the `postcssConfig` build
4 | // option from your application's configuration (i.e. project.json).
5 | //
6 | // See: https://nx.dev/guides/using-tailwind-css-in-react#step-4:-applying-configuration-to-libraries
7 |
8 | module.exports = {
9 | plugins: {
10 | tailwindcss: {
11 | config: join(__dirname, 'tailwind.config.js'),
12 | },
13 | autoprefixer: {},
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheSPSDAO/SPS-Validator/8007f3fe78dc19a9cacc6101c23e14d8790d621b/apps/sps-validator-ui/public/favicon.ico
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/components/InfoTooltip.tsx:
--------------------------------------------------------------------------------
1 | import { InformationCircleIcon } from '@heroicons/react/24/solid';
2 | import { Tooltip } from '@material-tailwind/react';
3 | import React from 'react';
4 |
5 | export type InfoTooltipProps = {
6 | text: string;
7 | className?: string;
8 | icon?: React.ReactNode;
9 | };
10 |
11 | export function InfoTooltip(props: InfoTooltipProps) {
12 | const icon = props.icon ?? ;
13 | return (
14 |
15 | {icon}
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/components/LocaleNumber.tsx:
--------------------------------------------------------------------------------
1 | export function localeNumber(value: number | string, precision = 3) {
2 | const coerced = typeof value === 'string' ? Number(value) : value;
3 | const number = Number((Number.isNaN(coerced) ? 0 : coerced).toFixed(precision)).toLocaleString();
4 | return number;
5 | }
6 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/components/ValidatorName.tsx:
--------------------------------------------------------------------------------
1 | import { Link } from 'react-router-dom';
2 | import { Validator } from '../services/openapi';
3 |
4 | export type ValidatorNameProps = Pick & {
5 | link_to_validator?: boolean;
6 | };
7 |
8 | export function ValidatorName({ account_name, api_url, post_url, link_to_validator }: ValidatorNameProps) {
9 | return (
10 |
11 | {link_to_validator && (
12 |
13 | {account_name}
14 |
15 | )}
16 | {!link_to_validator && {account_name}} (
17 | {api_url && (
18 |
19 | api
20 |
21 | )}
22 | {!api_url && no api}
23 | {' | '}
24 | {post_url && (
25 |
26 | peakd post
27 |
28 | )}
29 | {!post_url && no peakd post})
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/components/layout/Sidebar.tsx:
--------------------------------------------------------------------------------
1 | import { List } from '@material-tailwind/react';
2 | import { ReactNode } from 'react';
3 |
4 | export type AppSidebarProps = {
5 | isMobileOpen?: boolean;
6 | children: ReactNode;
7 | };
8 |
9 | export function AppSidebar(props: AppSidebarProps) {
10 | return (
11 |
16 | {props.children}
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/components/layout/index.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheSPSDAO/SPS-Validator/8007f3fe78dc19a9cacc6101c23e14d8790d621b/apps/sps-validator-ui/src/app/components/layout/index.ts
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/hooks/LocalStorage.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | export function getLocalStorageValue(key: string, defaultValue: T) {
4 | const stored = localStorage.getItem(key);
5 | const parsed = stored !== null ? JSON.parse(stored) : null;
6 | return parsed ?? defaultValue;
7 | }
8 |
9 | export const useLocalStorage = (key: string, defaultValue: T) => {
10 | const [value, setValue] = useState(() => {
11 | return getLocalStorageValue(key, defaultValue);
12 | });
13 |
14 | useEffect(() => {
15 | localStorage.setItem(key, JSON.stringify(value));
16 | }, [key, value]);
17 |
18 | return [value, setValue] as const;
19 | };
20 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export { useLocalStorage, getLocalStorageValue } from './LocalStorage';
2 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/pages/block-explorer/utils.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export function formatBlockTime(block_time?: string) {
4 | if (!block_time) {
5 | return 'unknown';
6 | }
7 | const date = new Date(block_time);
8 | const secondsAgo = Math.floor((Date.now() - date.getTime()) / 1000);
9 | if (secondsAgo < 60) {
10 | return `${secondsAgo} seconds ago`;
11 | }
12 | return date.toLocaleString();
13 | }
14 |
15 | export function listItemClickHandler(onClick: () => void) {
16 | return (event: React.MouseEvent) => {
17 | const target = event.target as HTMLElement;
18 | if (!target.classList.contains('outer-list-item')) {
19 | return;
20 | }
21 | onClick();
22 | };
23 | }
24 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/TxLookupService.ts:
--------------------------------------------------------------------------------
1 | import { Transaction } from './openapi';
2 | import { DefaultService } from './openapi/services/DefaultService';
3 |
4 | export class TxLookupService {
5 | static async waitForTx(txId: string, timeout = 30000): Promise {
6 | const start = Date.now();
7 | while (Date.now() - start < timeout) {
8 | try {
9 | return await DefaultService.getTransaction(txId);
10 | } catch (e) {
11 | await new Promise((resolve) => setTimeout(resolve, 1000));
12 | }
13 | }
14 | throw new Error('Timeout waiting for transaction. The transaction may still be processing. Refresh this page in a few minutes and try again.');
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/hive/core/Hive.ts:
--------------------------------------------------------------------------------
1 | type Config = {
2 | PREFIX: string;
3 | ACCOUNT: string | null;
4 | };
5 |
6 | export const Hive: Config = {
7 | PREFIX: 'dev-sm_',
8 | ACCOUNT: null,
9 | };
10 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/hive/index.ts:
--------------------------------------------------------------------------------
1 | export { HiveService } from "./services/HiveService";
2 | export { Hive } from "./core/Hive";
3 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/openapi/core/ApiError.ts:
--------------------------------------------------------------------------------
1 | /* generated using openapi-typescript-codegen -- do not edit */
2 | /* istanbul ignore file */
3 | /* tslint:disable */
4 | /* eslint-disable */
5 | import type { ApiRequestOptions } from './ApiRequestOptions';
6 | import type { ApiResult } from './ApiResult';
7 |
8 | export class ApiError extends Error {
9 | public readonly url: string;
10 | public readonly status: number;
11 | public readonly statusText: string;
12 | public readonly body: any;
13 | public readonly request: ApiRequestOptions;
14 |
15 | constructor(request: ApiRequestOptions, response: ApiResult, message: string) {
16 | super(message);
17 |
18 | this.name = 'ApiError';
19 | this.url = response.url;
20 | this.status = response.status;
21 | this.statusText = response.statusText;
22 | this.body = response.body;
23 | this.request = request;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/openapi/core/ApiRequestOptions.ts:
--------------------------------------------------------------------------------
1 | /* generated using openapi-typescript-codegen -- do not edit */
2 | /* istanbul ignore file */
3 | /* tslint:disable */
4 | /* eslint-disable */
5 | export type ApiRequestOptions = {
6 | readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH';
7 | readonly url: string;
8 | readonly path?: Record;
9 | readonly cookies?: Record;
10 | readonly headers?: Record;
11 | readonly query?: Record;
12 | readonly formData?: Record;
13 | readonly body?: any;
14 | readonly mediaType?: string;
15 | readonly responseHeader?: string;
16 | readonly errors?: Record;
17 | };
18 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/openapi/core/ApiResult.ts:
--------------------------------------------------------------------------------
1 | /* generated using openapi-typescript-codegen -- do not edit */
2 | /* istanbul ignore file */
3 | /* tslint:disable */
4 | /* eslint-disable */
5 | export type ApiResult = {
6 | readonly url: string;
7 | readonly ok: boolean;
8 | readonly status: number;
9 | readonly statusText: string;
10 | readonly body: any;
11 | };
12 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/openapi/core/OpenAPI.ts:
--------------------------------------------------------------------------------
1 | /* generated using openapi-typescript-codegen -- do not edit */
2 | /* istanbul ignore file */
3 | /* tslint:disable */
4 | /* eslint-disable */
5 | import type { ApiRequestOptions } from './ApiRequestOptions';
6 |
7 | type Resolver = (options: ApiRequestOptions) => Promise;
8 | type Headers = Record;
9 |
10 | export type OpenAPIConfig = {
11 | BASE: string;
12 | VERSION: string;
13 | WITH_CREDENTIALS: boolean;
14 | CREDENTIALS: 'include' | 'omit' | 'same-origin';
15 | TOKEN?: string | Resolver | undefined;
16 | USERNAME?: string | Resolver | undefined;
17 | PASSWORD?: string | Resolver | undefined;
18 | HEADERS?: Headers | Resolver | undefined;
19 | ENCODE_PATH?: ((path: string) => string) | undefined;
20 | };
21 |
22 | export const OpenAPI: OpenAPIConfig = {
23 | BASE: 'http://localhost:3333',
24 | VERSION: '1.0.0',
25 | WITH_CREDENTIALS: false,
26 | CREDENTIALS: 'include',
27 | TOKEN: undefined,
28 | USERNAME: undefined,
29 | PASSWORD: undefined,
30 | HEADERS: undefined,
31 | ENCODE_PATH: undefined,
32 | };
33 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/openapi/models/Account.ts:
--------------------------------------------------------------------------------
1 | /* generated using openapi-typescript-codegen -- do not edit */
2 | /* istanbul ignore file */
3 | /* tslint:disable */
4 | /* eslint-disable */
5 | export type Account = {
6 | name: string;
7 | authority?: Record;
8 | };
9 |
10 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/openapi/models/Balance.ts:
--------------------------------------------------------------------------------
1 | /* generated using openapi-typescript-codegen -- do not edit */
2 | /* istanbul ignore file */
3 | /* tslint:disable */
4 | /* eslint-disable */
5 | export type Balance = {
6 | player: string;
7 | token: string;
8 | balance: number;
9 | };
10 |
11 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/openapi/models/Balances.ts:
--------------------------------------------------------------------------------
1 | /* generated using openapi-typescript-codegen -- do not edit */
2 | /* istanbul ignore file */
3 | /* tslint:disable */
4 | /* eslint-disable */
5 | import type { Balance } from './Balance';
6 | export type Balances = Array;
7 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/openapi/models/BalancesCount.ts:
--------------------------------------------------------------------------------
1 | /* generated using openapi-typescript-codegen -- do not edit */
2 | /* istanbul ignore file */
3 | /* tslint:disable */
4 | /* eslint-disable */
5 | import type { Balance } from './Balance';
6 | export type BalancesCount = {
7 | count?: number;
8 | balances?: Array;
9 | };
10 |
11 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/openapi/models/Block.ts:
--------------------------------------------------------------------------------
1 | /* generated using openapi-typescript-codegen -- do not edit */
2 | /* istanbul ignore file */
3 | /* tslint:disable */
4 | /* eslint-disable */
5 | export type Block = {
6 | block_num?: number;
7 | block_time?: string;
8 | block_id?: string;
9 | prev_block_id?: string;
10 | l2_block_id?: string;
11 | validator?: string;
12 | validation_tx?: string;
13 | };
14 |
15 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/openapi/models/NoPriceAtPoint.ts:
--------------------------------------------------------------------------------
1 | /* generated using openapi-typescript-codegen -- do not edit */
2 | /* istanbul ignore file */
3 | /* tslint:disable */
4 | /* eslint-disable */
5 | export type NoPriceAtPoint = {
6 | token: string;
7 | block_time: string;
8 | };
9 |
10 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/openapi/models/PoolSettings.ts:
--------------------------------------------------------------------------------
1 | /* generated using openapi-typescript-codegen -- do not edit */
2 | /* istanbul ignore file */
3 | /* tslint:disable */
4 | /* eslint-disable */
5 | export type PoolSettings = {
6 | acc_tokens_per_share: number;
7 | tokens_per_block: number;
8 | start_block: number;
9 | last_reward_block: number;
10 | };
11 |
12 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/openapi/models/PriceAtPoint.ts:
--------------------------------------------------------------------------------
1 | /* generated using openapi-typescript-codegen -- do not edit */
2 | /* istanbul ignore file */
3 | /* tslint:disable */
4 | /* eslint-disable */
5 | import type { NoPriceAtPoint } from './NoPriceAtPoint';
6 | export type PriceAtPoint = (NoPriceAtPoint & {
7 | price: number;
8 | });
9 |
10 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/openapi/models/Status.ts:
--------------------------------------------------------------------------------
1 | /* generated using openapi-typescript-codegen -- do not edit */
2 | /* istanbul ignore file */
3 | /* tslint:disable */
4 | /* eslint-disable */
5 | export type Status = {
6 | status: Status.status;
7 | last_block: number;
8 | };
9 | export namespace Status {
10 | export enum status {
11 | RUNNING = 'running',
12 | }
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/openapi/models/TokenSupply.ts:
--------------------------------------------------------------------------------
1 | /* generated using openapi-typescript-codegen -- do not edit */
2 | /* istanbul ignore file */
3 | /* tslint:disable */
4 | /* eslint-disable */
5 | export type TokenSupply = Record;
6 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/openapi/models/TokenTransferTransaction.ts:
--------------------------------------------------------------------------------
1 | /* generated using openapi-typescript-codegen -- do not edit */
2 | /* istanbul ignore file */
3 | /* tslint:disable */
4 | /* eslint-disable */
5 | export type TokenTransferTransaction = {
6 | id: string;
7 | success: boolean;
8 | from: string;
9 | to: string;
10 | qty: number;
11 | token: string;
12 | memo: string;
13 | error?: {
14 | message: string;
15 | code: number;
16 | };
17 | };
18 |
19 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/openapi/models/TokenTransferTransactions.ts:
--------------------------------------------------------------------------------
1 | /* generated using openapi-typescript-codegen -- do not edit */
2 | /* istanbul ignore file */
3 | /* tslint:disable */
4 | /* eslint-disable */
5 | import type { TokenTransferTransaction } from './TokenTransferTransaction';
6 | export type TokenTransferTransactions = Array;
7 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/openapi/models/Transaction.ts:
--------------------------------------------------------------------------------
1 | /* generated using openapi-typescript-codegen -- do not edit */
2 | /* istanbul ignore file */
3 | /* tslint:disable */
4 | /* eslint-disable */
5 | export type Transaction = {
6 | id: string;
7 | block_id: string;
8 | prev_block_id: string;
9 | block_num: number;
10 | type: string;
11 | player: string;
12 | data?: string;
13 | success?: boolean;
14 | error?: string;
15 | created_date?: string;
16 | result?: string;
17 | index: number;
18 | };
19 |
20 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/openapi/models/TransitionStatus.ts:
--------------------------------------------------------------------------------
1 | /* generated using openapi-typescript-codegen -- do not edit */
2 | /* istanbul ignore file */
3 | /* tslint:disable */
4 | /* eslint-disable */
5 | export type TransitionStatus = {
6 | transition: string;
7 | block_num: number;
8 | blocks_until: number;
9 | transitioned: boolean;
10 | description: string;
11 | };
12 |
13 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/openapi/models/TransitionStatuses.ts:
--------------------------------------------------------------------------------
1 | /* generated using openapi-typescript-codegen -- do not edit */
2 | /* istanbul ignore file */
3 | /* tslint:disable */
4 | /* eslint-disable */
5 | import type { TransitionStatus } from './TransitionStatus';
6 | export type TransitionStatuses = {
7 | block_num?: number;
8 | transition_points?: Array;
9 | };
10 |
11 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/openapi/models/Validator.ts:
--------------------------------------------------------------------------------
1 | /* generated using openapi-typescript-codegen -- do not edit */
2 | /* istanbul ignore file */
3 | /* tslint:disable */
4 | /* eslint-disable */
5 | export type Validator = {
6 | account_name: string;
7 | reward_account: string;
8 | last_version: string;
9 | is_active: boolean;
10 | post_url: string | null;
11 | api_url: string | null;
12 | total_votes: number;
13 | missed_blocks: number;
14 | };
15 |
16 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/openapi/models/ValidatorConfig.ts:
--------------------------------------------------------------------------------
1 | /* generated using openapi-typescript-codegen -- do not edit */
2 | /* istanbul ignore file */
3 | /* tslint:disable */
4 | /* eslint-disable */
5 | export type ValidatorConfig = {
6 | reward_start_block: number;
7 | paused_until_block: number;
8 | tokens_per_block: number;
9 | reward_token: string;
10 | min_validators: number;
11 | reduction_blocks: number;
12 | max_block_age: number;
13 | reduction_pct: number;
14 | max_votes: number;
15 | num_top_validators: number;
16 | };
17 |
18 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/openapi/models/ValidatorVote.ts:
--------------------------------------------------------------------------------
1 | /* generated using openapi-typescript-codegen -- do not edit */
2 | /* istanbul ignore file */
3 | /* tslint:disable */
4 | /* eslint-disable */
5 | export type ValidatorVote = {
6 | voter: string;
7 | validator: string;
8 | vote_weight: number;
9 | };
10 |
11 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/openapi/models/ValidatorVotes.ts:
--------------------------------------------------------------------------------
1 | /* generated using openapi-typescript-codegen -- do not edit */
2 | /* istanbul ignore file */
3 | /* tslint:disable */
4 | /* eslint-disable */
5 | import type { ValidatorVote } from './ValidatorVote';
6 | export type ValidatorVotes = Array;
7 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/app/services/openapi/models/Validators.ts:
--------------------------------------------------------------------------------
1 | /* generated using openapi-typescript-codegen -- do not edit */
2 | /* istanbul ignore file */
3 | /* tslint:disable */
4 | /* eslint-disable */
5 | import type { Validator } from './Validator';
6 | export type Validators = {
7 | validators?: Array;
8 | count?: number;
9 | };
10 |
11 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheSPSDAO/SPS-Validator/8007f3fe78dc19a9cacc6101c23e14d8790d621b/apps/sps-validator-ui/src/assets/.gitkeep
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react';
2 | import { BrowserRouter } from 'react-router-dom';
3 | import * as ReactDOM from 'react-dom/client';
4 | import { ThemeProvider } from '@material-tailwind/react';
5 | import App from './app/app';
6 | import { OpenAPI } from './app/services/openapi';
7 | import { getLocalStorageValue } from './app/hooks';
8 | import { Hive } from './app/services/hive';
9 |
10 | OpenAPI.BASE = getLocalStorageValue('api.url', import.meta.env.VALIDATOR_API_URL || 'http://localhost:3333');
11 | OpenAPI.WITH_CREDENTIALS = false;
12 | Hive.PREFIX = getLocalStorageValue('hive.prefix', import.meta.env.VALIDATOR_PREFIX || 'sm_');
13 | Hive.ACCOUNT = getLocalStorageValue('hive.account', '');
14 |
15 | const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
16 |
17 | root.render(
18 |
19 |
20 |
21 |
22 |
23 |
24 | ,
25 | );
26 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/src/styles.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 | /* You can add global styles to this file, and also import other style files */
5 |
6 | /** hack to get scrollbars working on dialogs */
7 | div:has(.dialog) {
8 | overflow-y: auto;
9 | }
10 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/tailwind.config.js:
--------------------------------------------------------------------------------
1 | const { createGlobPatternsForDependencies } = require('@nx/react/tailwind');
2 | const withMT = require('@material-tailwind/react/utils/withMT');
3 | const { join } = require('path');
4 |
5 | /** @type {import('tailwindcss').Config} */
6 | const config = {
7 | content: [join(__dirname, '{src,pages,components,app}/**/*!(*.stories|*.spec).{ts,tsx,html}'), ...createGlobPatternsForDependencies(__dirname)],
8 | theme: {
9 | extend: {},
10 | },
11 | darkMode: 'class',
12 | plugins: [],
13 | };
14 |
15 | /** @type {import('tailwindcss').Config} */
16 | module.exports = withMT(config);
17 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "types": ["node", "@nx/react/typings/cssmodule.d.ts", "@nx/react/typings/image.d.ts", "vite/client"]
6 | },
7 | "exclude": [
8 | "src/**/*.spec.ts",
9 | "src/**/*.test.ts",
10 | "src/**/*.spec.tsx",
11 | "src/**/*.test.tsx",
12 | "src/**/*.spec.js",
13 | "src/**/*.test.js",
14 | "src/**/*.spec.jsx",
15 | "src/**/*.test.jsx",
16 | "vite.config.ts",
17 | "vite.config.mts",
18 | "vitest.config.ts",
19 | "vitest.config.mts"
20 | ],
21 | "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
22 | }
23 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "jsx": "react-jsx",
4 | "lib": ["dom"],
5 | "module": "ES2022",
6 | "allowJs": false,
7 | "esModuleInterop": false,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "types": ["vite/client", "vitest"]
11 | },
12 | "files": [],
13 | "include": [],
14 | "references": [
15 | {
16 | "path": "./tsconfig.app.json"
17 | },
18 | {
19 | "path": "./tsconfig.spec.json"
20 | }
21 | ],
22 | "extends": "../../tsconfig.base.json"
23 | }
24 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest", "@nx/react/typings/cssmodule.d.ts", "@nx/react/typings/image.d.ts"]
6 | },
7 | "include": [
8 | "vite.config.ts",
9 | "vite.config.mts",
10 | "vitest.config.ts",
11 | "vitest.config.mts",
12 | "src/**/*.test.ts",
13 | "src/**/*.spec.ts",
14 | "src/**/*.test.tsx",
15 | "src/**/*.spec.tsx",
16 | "src/**/*.test.js",
17 | "src/**/*.spec.js",
18 | "src/**/*.test.jsx",
19 | "src/**/*.spec.jsx",
20 | "src/**/*.d.ts"
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/apps/sps-validator-ui/vite.config.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import { defineConfig } from 'vite';
3 | import react from '@vitejs/plugin-react';
4 | import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
5 | import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
6 |
7 | const BASE_URL = process.env.BASE_URL || '/';
8 |
9 | export default defineConfig({
10 | base: BASE_URL,
11 | root: __dirname,
12 | cacheDir: '../../node_modules/.vite/apps/sps-validator-ui',
13 | server: {
14 | port: 4200,
15 | host: 'localhost',
16 | },
17 | preview: {
18 | port: 4300,
19 | host: 'localhost',
20 | },
21 | plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
22 | envPrefix: ['VITE_', 'VALIDATOR_'],
23 | build: {
24 | outDir: '../../dist/apps/sps-validator-ui',
25 | emptyOutDir: true,
26 | reportCompressedSize: true,
27 | commonjsOptions: {
28 | transformMixedEsModules: true,
29 | },
30 | },
31 | test: {
32 | watch: false,
33 | globals: true,
34 | environment: 'jsdom',
35 | include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
36 | reporters: ['default'],
37 | coverage: {
38 | reportsDirectory: '../../coverage/apps/sps-validator-ui',
39 | provider: 'v8',
40 | },
41 | },
42 | });
43 |
--------------------------------------------------------------------------------
/apps/sps-validator/jest.config.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | export default {
3 | displayName: 'sps-validator',
4 | preset: '../../jest.preset.js',
5 | globals: {},
6 | setupFilesAfterEnv: ['/src/sps/jest.setup.ts'],
7 | globalSetup: '/src/sps/jest.global-setup.ts',
8 | globalTeardown: '/src/sps/jest.global-teardown.ts',
9 | testEnvironment: 'node',
10 | transform: {
11 | '^.+\\.[tj]s$': [
12 | 'ts-jest',
13 | {
14 | tsconfig: '/tsconfig.spec.json',
15 | },
16 | ],
17 | },
18 | moduleFileExtensions: ['ts', 'js', 'html'],
19 | coverageDirectory: '../../coverage/apps/sps-validator',
20 | };
21 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/__tests__/action-fixture.ts:
--------------------------------------------------------------------------------
1 | import { autoInjectable, inject } from 'tsyringe';
2 | import { Fixture as BaseFixture } from './fixture';
3 | import { ConfigType } from '../sps/convict-config';
4 | import { SpsConfigLoader } from '../sps/config';
5 |
6 | @autoInjectable()
7 | export class Fixture extends BaseFixture {
8 | readonly loader: SpsConfigLoader;
9 | readonly cfg: ConfigType;
10 | constructor(@inject(SpsConfigLoader) loader?: SpsConfigLoader, @inject(ConfigType) cfg?: ConfigType) {
11 | super();
12 | this.loader = loader!;
13 | this.cfg = cfg!;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/__tests__/disposable.ts:
--------------------------------------------------------------------------------
1 | // Already part of tsyringe master, unreleased to NPM though
2 |
3 | export interface Disposable {
4 | dispose(): Promise | void;
5 | }
6 | export const Disposable: unique symbol = Symbol.for('Disposable');
7 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/__tests__/fixture.ts:
--------------------------------------------------------------------------------
1 | import { autoInjectable, inject, injectAll } from 'tsyringe';
2 | import { Handle, KnexToken } from '@steem-monsters/splinterlands-validator';
3 | import { Knex } from 'knex';
4 | import { Disposable } from './disposable';
5 | import { Backup } from './fake-db';
6 | import { TestHelper } from './db-helpers';
7 | import { OpsHelper } from './process-op';
8 |
9 | @autoInjectable()
10 | export class Fixture implements Disposable, Backup {
11 | private readonly disposables: Disposable[];
12 | private readonly backup: Backup;
13 | readonly knex: Knex;
14 | readonly handle: Handle;
15 | readonly testHelper: TestHelper;
16 | readonly opsHelper: OpsHelper;
17 | constructor(
18 | @injectAll(Disposable) disposables?: Disposable[],
19 | @inject(Backup) backup?: Backup,
20 | @inject(KnexToken) knex?: Knex,
21 | @inject(Handle) handle?: Handle,
22 | @inject(TestHelper) testHelper?: TestHelper,
23 | @inject(OpsHelper) opsHelper?: OpsHelper,
24 | ) {
25 | this.disposables = disposables!;
26 | this.backup = backup!;
27 | this.knex = knex!;
28 | this.handle = handle!;
29 | this.testHelper = testHelper!;
30 | this.opsHelper = opsHelper!;
31 | }
32 |
33 | async init() {
34 | await this.backup.init();
35 | }
36 |
37 | async dispose(): Promise {
38 | for (const disposable of this.disposables) {
39 | await disposable.dispose();
40 | }
41 | }
42 |
43 | async restore() {
44 | await this.backup.restore();
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/__tests__/shop/bonus.json:
--------------------------------------------------------------------------------
1 | {
2 | "start_date": "2022-02-24T17:00:00Z",
3 | "price": [
4 | {
5 | "currency": "SPS",
6 | "amount": 1
7 | }
8 | ],
9 | "bonus": {
10 | "currency": "VOUCHER",
11 | "brackets": [{
12 | "min": 0,
13 | "ratio": 0.1
14 | }]
15 | },
16 | "token": "LICENSE",
17 | "item_details_name": "License with Bonus",
18 | "supply": "limited",
19 | "type": "token",
20 | "max": 1000000
21 | }
22 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/__tests__/shop/license_tranches/presale.json:
--------------------------------------------------------------------------------
1 | {
2 | "start_date": "2022-05-25T16:00:00Z",
3 | "price": [
4 | {
5 | "currency": "USD",
6 | "amount": 1000,
7 | "accepted_currency": "SPS"
8 | },
9 | {
10 | "currency": "VOUCHER",
11 | "amount": 500
12 | }
13 | ],
14 | "supply": "limited",
15 | "tranche_reserves": 58000,
16 | "type": "token",
17 | "token": "LICENSE",
18 | "item_details_name": "Validator License",
19 | "max": 1
20 | }
21 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/__tests__/shop/license_tranches/tranche_1.json:
--------------------------------------------------------------------------------
1 | {
2 | "start_date": "2022-05-25T16:00:00Z",
3 | "price": [
4 | {
5 | "currency": "USD",
6 | "amount": 3000,
7 | "accepted_currency": "SPS"
8 | }
9 | ],
10 | "discount": {
11 | "currency": "VOUCHER",
12 | "max_amount": 500,
13 | "discount_rate": 3
14 | },
15 | "supply": "limited",
16 | "tranche_reserves": 55000,
17 | "type": "token",
18 | "token": "LICENSE",
19 | "item_details_name": "Validator License",
20 | "max": 500
21 | }
22 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/__tests__/shop/license_tranches/tranche_2.json:
--------------------------------------------------------------------------------
1 | {
2 | "start_date": "2022-05-25T16:00:00Z",
3 | "price": [
4 | {
5 | "currency": "USD",
6 | "amount": 5000,
7 | "accepted_currency": "SPS"
8 | }
9 | ],
10 | "discount": {
11 | "currency": "VOUCHER",
12 | "max_amount": 500,
13 | "discount_rate": 5
14 | },
15 | "supply": "limited",
16 | "tranche_reserves": 50000,
17 | "type": "token",
18 | "token": "LICENSE",
19 | "item_details_name": "Validator License",
20 | "max": 500
21 | }
22 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/__tests__/shop/license_tranches/tranche_3.json:
--------------------------------------------------------------------------------
1 | {
2 | "start_date": "2022-05-25T16:00:00Z",
3 | "price": [
4 | {
5 | "currency": "USD",
6 | "amount": 7500,
7 | "accepted_currency": "SPS"
8 | }
9 | ],
10 | "discount": {
11 | "currency": "VOUCHER",
12 | "max_amount": 500,
13 | "discount_rate": 7.5
14 | },
15 | "supply": "limited",
16 | "tranche_reserves": 40000,
17 | "type": "token",
18 | "token": "LICENSE",
19 | "item_details_name": "Validator License",
20 | "max": 500
21 | }
22 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/__tests__/shop/license_tranches/tranche_4.json:
--------------------------------------------------------------------------------
1 | {
2 | "start_date": "2022-05-25T16:00:00Z",
3 | "price": [
4 | {
5 | "currency": "USD",
6 | "amount": 10000,
7 | "accepted_currency": "SPS"
8 | }
9 | ],
10 | "discount": {
11 | "currency": "VOUCHER",
12 | "max_amount": 500,
13 | "discount_rate": 10
14 | },
15 | "supply": "limited",
16 | "tranche_reserves": 30000,
17 | "type": "token",
18 | "token": "LICENSE",
19 | "item_details_name": "Validator License",
20 | "max": 500
21 | }
22 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/__tests__/shop/license_tranches/tranche_5.json:
--------------------------------------------------------------------------------
1 | {
2 | "start_date": "2022-05-25T16:00:00Z",
3 | "price": [
4 | {
5 | "currency": "USD",
6 | "amount": 15000,
7 | "accepted_currency": "SPS"
8 | }
9 | ],
10 | "discount": {
11 | "currency": "VOUCHER",
12 | "max_amount": 500,
13 | "discount_rate": 15
14 | },
15 | "supply": "limited",
16 | "tranche_reserves": 20000,
17 | "type": "token",
18 | "token": "LICENSE",
19 | "item_details_name": "Validator License",
20 | "max": 500
21 | }
22 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/__tests__/shop/license_tranches/tranche_6.json:
--------------------------------------------------------------------------------
1 | {
2 | "start_date": "2022-05-25T16:00:00Z",
3 | "price": [
4 | {
5 | "currency": "USD",
6 | "amount": 20000,
7 | "accepted_currency": "SPS"
8 | }
9 | ],
10 | "discount": {
11 | "currency": "VOUCHER",
12 | "max_amount": 500,
13 | "discount_rate": 20
14 | },
15 | "supply": "limited",
16 | "tranche_reserves": 10000,
17 | "type": "token",
18 | "token": "LICENSE",
19 | "item_details_name": "Validator License",
20 | "max": 500
21 | }
22 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/__tests__/shop/license_tranches/tranche_7.json:
--------------------------------------------------------------------------------
1 | {
2 | "start_date": "2022-05-25T16:00:00Z",
3 | "price": [
4 | {
5 | "currency": "USD",
6 | "amount": 40000,
7 | "accepted_currency": "SPS"
8 | }
9 | ],
10 | "discount": {
11 | "currency": "VOUCHER",
12 | "max_amount": 500,
13 | "discount_rate": 40
14 | },
15 | "supply": "limited",
16 | "tranche_reserves": 0,
17 | "type": "token",
18 | "token": "LICENSE",
19 | "item_details_name": "Validator License",
20 | "max": 500
21 | }
22 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/express.d.ts:
--------------------------------------------------------------------------------
1 | import 'express';
2 | import { Resolver } from '@steem-monsters/splinterlands-validator';
3 |
4 | declare module 'express-serve-static-core' {
5 | interface Request {
6 | resolver: Resolver;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/jest.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare namespace jest {
4 | interface It {
5 | dbOnly: It;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/main.ts:
--------------------------------------------------------------------------------
1 | import '@abraham/reflection';
2 | import { EntryPoint } from '@steem-monsters/splinterlands-validator';
3 | import { CompositionRoot, container } from './sps/composition-root';
4 |
5 | async function start(): Promise {
6 | try {
7 | CompositionRoot.assertValidRegistry();
8 | const ep = container.resolve(EntryPoint);
9 | await ep.preflightCheck();
10 | await ep.start();
11 | } catch (e) {
12 | console.error('Error while starting validator');
13 | console.error(e);
14 | process.exit(1);
15 | }
16 | }
17 |
18 | start();
19 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/plugins/kill_plugin.ts:
--------------------------------------------------------------------------------
1 | import { coerceToBlockNum, log, LogLevel, Plugin } from '@steem-monsters/splinterlands-validator';
2 |
3 | export class KillPlugin implements Plugin {
4 | readonly name: string = 'KillAtBlockPlugin';
5 | private readonly killBlock: number;
6 |
7 | public static isAvailable(): boolean {
8 | return coerceToBlockNum(process.env.KILL_BLOCK) !== null;
9 | }
10 |
11 | public constructor(private readonly logLevel: LogLevel = LogLevel.Warning) {
12 | this.killBlock = coerceToBlockNum(process.env.KILL_BLOCK) ?? Number.POSITIVE_INFINITY;
13 | log(`Validator configured to be killed at block ${this.killBlock}`, this.logLevel);
14 | }
15 |
16 | public async beforeBlockProcessed(blockNumber: number): Promise {
17 | if (blockNumber >= this.killBlock) {
18 | log(`Killing validator because we've reached the kill block.`, this.logLevel);
19 | process.exit(1);
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/actions/account/set_authority.ts:
--------------------------------------------------------------------------------
1 | import { OperationData, HiveAccountRepository, Action, Trx, ValidationError, ErrorType } from '@steem-monsters/splinterlands-validator';
2 | import { set_authority } from '../schema';
3 | import { MakeActionFactory, MakeRouter } from '../utils';
4 |
5 | export class SetAuthorityAction extends Action {
6 | constructor(op: OperationData, data: unknown, index: number, protected readonly hiveAccountRepository: HiveAccountRepository) {
7 | super(set_authority, op, data, index);
8 | }
9 |
10 | async validate(trx?: Trx) {
11 | const authority = {
12 | delegation: this.params.delegation,
13 | };
14 | const accounts = Object.values(authority).flatMap((accounts) => accounts);
15 | const valid_accounts = await this.hiveAccountRepository.onlyHiveAccounts(accounts, trx);
16 | if (!valid_accounts) {
17 | throw new ValidationError('One or more of the accounts are not valid Hive accounts.', this, ErrorType.AccountNotKnown);
18 | }
19 | return true;
20 | }
21 |
22 | async process(trx?: Trx) {
23 | const authority = {
24 | delegation: this.params.delegation,
25 | };
26 | const event_log = await this.hiveAccountRepository.setAuthority(this.op.account, authority, trx);
27 | return [event_log];
28 | }
29 | }
30 |
31 | const Builder = MakeActionFactory(SetAuthorityAction, HiveAccountRepository);
32 | export const Router = MakeRouter(set_authority.action_name, Builder);
33 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/actions/pools/add.ts:
--------------------------------------------------------------------------------
1 | import { AdminAction, AdminMembership, EventLog, OperationData, PoolManager, Trx } from '@steem-monsters/splinterlands-validator';
2 | import { add_pool } from '../schema';
3 | import { MakeActionFactory, MakeRouter } from '../utils';
4 |
5 | export class AddPoolAction extends AdminAction {
6 | readonly #poolManager: PoolManager;
7 | constructor(op: OperationData, data: unknown, index: number, poolManager: PoolManager, adminMembership: AdminMembership) {
8 | super(adminMembership, add_pool, op, data, index);
9 | this.#poolManager = poolManager;
10 | }
11 |
12 | async process(trx?: Trx): Promise {
13 | const added = await this.#poolManager.add(this.params, this, trx);
14 | return [added];
15 | }
16 | }
17 |
18 | const Builder = MakeActionFactory(AddPoolAction, PoolManager, AdminMembership);
19 | export const Router = MakeRouter(add_pool.action_name, Builder);
20 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/actions/pools/claim.ts:
--------------------------------------------------------------------------------
1 | import { Action, EventLog, OperationData, PoolManager, Trx } from '@steem-monsters/splinterlands-validator';
2 | import { claim_pool } from '../schema';
3 | import { MakeActionFactory, MakeRouter } from '../utils';
4 |
5 | export class ClaimPoolAction extends Action {
6 | readonly #poolManager: PoolManager;
7 | constructor(op: OperationData, data: unknown, index: number, poolManager: PoolManager) {
8 | super(claim_pool, op, data, index);
9 | this.#poolManager = poolManager;
10 | }
11 |
12 | async validate(_trx?: Trx) {
13 | return true;
14 | }
15 |
16 | process(trx?: Trx): Promise {
17 | // Assume this.params.now comes from a deterministic source, such as block time
18 | return this.#poolManager.payout(this.params.now, this, trx);
19 | }
20 | }
21 |
22 | const Builder = MakeActionFactory(ClaimPoolAction, PoolManager);
23 | export const Router = MakeRouter(claim_pool.action_name, Builder);
24 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/actions/pools/disable.ts:
--------------------------------------------------------------------------------
1 | import { OperationData, PoolManager, AdminMembership, AdminAction, EventLog, Trx } from '@steem-monsters/splinterlands-validator';
2 | import { disable_pool } from '../schema';
3 | import { MakeActionFactory, MakeRouter } from '../utils';
4 |
5 | export class DisablePoolAction extends AdminAction {
6 | readonly #poolManager: PoolManager;
7 |
8 | private static readonly maxDate = new Date(8_640_000_000_000_000);
9 |
10 | constructor(op: OperationData, data: unknown, index: number, poolManager: PoolManager, adminMembership: AdminMembership) {
11 | super(adminMembership, disable_pool, op, data, index);
12 | this.#poolManager = poolManager;
13 | }
14 |
15 | async process(trx?: Trx): Promise {
16 | const payload = { name: this.params.name, start: DisablePoolAction.maxDate };
17 | const update = await this.#poolManager.update(payload, this, trx);
18 | return [update];
19 | }
20 | }
21 |
22 | const Builder = MakeActionFactory(DisablePoolAction, PoolManager, AdminMembership);
23 | export const Router = MakeRouter(disable_pool.action_name, Builder);
24 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/actions/pools/update.ts:
--------------------------------------------------------------------------------
1 | import { OperationData, PoolManager, AdminMembership, AdminAction, Trx, EventLog } from '@steem-monsters/splinterlands-validator';
2 | import { update_pool } from '../schema';
3 | import { MakeActionFactory, MakeRouter } from '../utils';
4 |
5 | export class UpdatePoolAction extends AdminAction {
6 | readonly #poolManager: PoolManager;
7 | constructor(op: OperationData, data: unknown, index: number, poolManager: PoolManager, adminMembership: AdminMembership) {
8 | super(adminMembership, update_pool, op, data, index);
9 | this.#poolManager = poolManager;
10 | }
11 |
12 | async process(trx?: Trx): Promise {
13 | const update = await this.#poolManager.update(this.params, this, trx);
14 | return [update];
15 | }
16 | }
17 |
18 | const Builder = MakeActionFactory(UpdatePoolAction, PoolManager, AdminMembership);
19 | export const Router = MakeRouter(update_pool.action_name, Builder);
20 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/actions/promises/cancel_promise.ts:
--------------------------------------------------------------------------------
1 | import { OperationData, PromiseManager, Action, EventLog, Trx } from '@steem-monsters/splinterlands-validator';
2 | import { cancel_promise } from '../schema';
3 | import { Result } from '@steem-monsters/lib-monad';
4 | import { MakeActionFactory, MakeRouter } from '../utils';
5 |
6 | export class CancelPromiseAction extends Action {
7 | constructor(op: OperationData, data: unknown, index: number, private readonly promiseManager: PromiseManager) {
8 | super(cancel_promise, op, data, index);
9 | }
10 |
11 | async validate(trx?: Trx): Promise {
12 | const validateResult = await this.promiseManager.validateCancelPromise(
13 | {
14 | type: this.params.type,
15 | id: this.params.id,
16 | },
17 | this,
18 | trx,
19 | );
20 |
21 | if (Result.isErr(validateResult)) {
22 | throw validateResult.error;
23 | }
24 |
25 | return true;
26 | }
27 |
28 | async process(trx?: Trx): Promise {
29 | return this.promiseManager.cancelPromise(
30 | {
31 | type: this.params.type,
32 | id: this.params.id,
33 | },
34 | this,
35 | trx,
36 | );
37 | }
38 | }
39 |
40 | const Builder = MakeActionFactory(CancelPromiseAction, PromiseManager);
41 | export const Router = MakeRouter(cancel_promise.action_name, Builder);
42 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/actions/promises/complete_promise.ts:
--------------------------------------------------------------------------------
1 | import { OperationData, PromiseManager, Action, EventLog, Trx } from '@steem-monsters/splinterlands-validator';
2 | import { complete_promise } from '../schema';
3 | import { Result } from '@steem-monsters/lib-monad';
4 | import { MakeActionFactory, MakeRouter } from '../utils';
5 |
6 | export class CompletePromiseAction extends Action {
7 | constructor(op: OperationData, data: unknown, index: number, private readonly promiseManager: PromiseManager) {
8 | super(complete_promise, op, data, index);
9 | }
10 |
11 | async validate(trx?: Trx): Promise {
12 | const validateResult = await this.promiseManager.validateCompletePromise(
13 | {
14 | type: this.params.type,
15 | id: this.params.id,
16 | },
17 | this,
18 | trx,
19 | );
20 |
21 | if (Result.isErr(validateResult)) {
22 | throw validateResult.error;
23 | }
24 |
25 | return true;
26 | }
27 |
28 | async process(trx?: Trx): Promise {
29 | return this.promiseManager.completePromise(
30 | {
31 | type: this.params.type,
32 | id: this.params.id,
33 | },
34 | this,
35 | trx,
36 | );
37 | }
38 | }
39 |
40 | const Builder = MakeActionFactory(CompletePromiseAction, PromiseManager);
41 | export const Router = MakeRouter(complete_promise.action_name, Builder);
42 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/actions/promises/expire_promises.ts:
--------------------------------------------------------------------------------
1 | import { OperationData, PromiseManager, Action, EventLog, Trx } from '@steem-monsters/splinterlands-validator';
2 | import { expire_promises } from '../schema';
3 | import { MakeActionFactory, MakeRouter } from '../utils';
4 |
5 | export class ExpirePromisesAction extends Action {
6 | constructor(op: OperationData, data: unknown, index: number, private readonly promiseManager: PromiseManager) {
7 | super(expire_promises, op, data, index);
8 | }
9 |
10 | async validate(): Promise {
11 | return true;
12 | }
13 |
14 | async process(trx?: Trx): Promise {
15 | return this.promiseManager.expirePromises(this.params.now, this, trx);
16 | }
17 | }
18 |
19 | const Builder = MakeActionFactory(ExpirePromisesAction, PromiseManager);
20 | export const Router = MakeRouter(expire_promises.action_name, Builder);
21 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/actions/promises/fulfill_promise.ts:
--------------------------------------------------------------------------------
1 | import { Result } from '@steem-monsters/lib-monad';
2 | import { OperationData, PromiseManager, Action, EventLog, Trx } from '@steem-monsters/splinterlands-validator';
3 | import { fulfill_promise } from '../schema';
4 | import { MakeActionFactory, MakeRouter } from '../utils';
5 |
6 | export class FulfillPromiseAction extends Action {
7 | constructor(op: OperationData, data: unknown, index: number, private readonly promiseManager: PromiseManager) {
8 | super(fulfill_promise, op, data, index);
9 | }
10 |
11 | async validate(trx?: Trx): Promise {
12 | const validateResult = await this.promiseManager.validateFulfillPromise(
13 | {
14 | type: this.params.type,
15 | id: this.params.id,
16 | metadata: this.params.metadata,
17 | },
18 | this,
19 | trx,
20 | );
21 |
22 | if (Result.isErr(validateResult)) {
23 | throw validateResult.error;
24 | }
25 |
26 | return true;
27 | }
28 |
29 | async process(trx?: Trx): Promise {
30 | return this.promiseManager.fulfillPromise(
31 | {
32 | type: this.params.type,
33 | id: this.params.id,
34 | metadata: this.params.metadata,
35 | },
36 | this,
37 | trx,
38 | );
39 | }
40 | }
41 |
42 | const Builder = MakeActionFactory(FulfillPromiseAction, PromiseManager);
43 | export const Router = MakeRouter(fulfill_promise.action_name, Builder);
44 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/actions/promises/fulfill_promises.ts:
--------------------------------------------------------------------------------
1 | import { Result } from '@steem-monsters/lib-monad';
2 | import { OperationData, PromiseManager, Action, EventLog, Trx } from '@steem-monsters/splinterlands-validator';
3 | import { fulfill_promise_multi } from '../schema';
4 | import { MakeActionFactory, MakeRouter } from '../utils';
5 | export class FulfillPromisesAction extends Action {
6 | constructor(op: OperationData, data: unknown, index: number, private readonly promiseManager: PromiseManager) {
7 | super(fulfill_promise_multi, op, data, index);
8 | }
9 |
10 | async validate(trx?: Trx): Promise {
11 | const validateResult = await this.promiseManager.validateFulfillPromises(
12 | {
13 | type: this.params.type,
14 | ids: this.params.ids,
15 | metadata: this.params.metadata,
16 | },
17 | this,
18 | trx,
19 | );
20 |
21 | if (Result.isErr(validateResult)) {
22 | throw validateResult.error;
23 | }
24 |
25 | return true;
26 | }
27 |
28 | async process(trx?: Trx): Promise {
29 | return this.promiseManager.fulfillPromises(
30 | {
31 | type: this.params.type,
32 | ids: this.params.ids,
33 | metadata: this.params.metadata,
34 | },
35 | this,
36 | trx,
37 | );
38 | }
39 | }
40 |
41 | const Builder = MakeActionFactory(FulfillPromisesAction, PromiseManager);
42 | export const Router = MakeRouter(fulfill_promise_multi.action_name, Builder);
43 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/actions/promises/reverse_promise.ts:
--------------------------------------------------------------------------------
1 | import { Result } from '@steem-monsters/lib-monad';
2 | import { OperationData, PromiseManager, Action, EventLog, Trx } from '@steem-monsters/splinterlands-validator';
3 | import { reverse_promise } from '../schema';
4 | import { MakeActionFactory, MakeRouter } from '../utils';
5 |
6 | export class ReversePromiseAction extends Action {
7 | constructor(op: OperationData, data: unknown, index: number, private readonly promiseManager: PromiseManager) {
8 | super(reverse_promise, op, data, index);
9 | }
10 |
11 | async validate(trx?: Trx): Promise {
12 | const validateResult = await this.promiseManager.validateReversePromise(
13 | {
14 | type: this.params.type,
15 | id: this.params.id,
16 | },
17 | this,
18 | trx,
19 | );
20 |
21 | if (Result.isErr(validateResult)) {
22 | throw validateResult.error;
23 | }
24 |
25 | return true;
26 | }
27 |
28 | async process(trx?: Trx): Promise {
29 | return this.promiseManager.reversePromise(
30 | {
31 | type: this.params.type,
32 | id: this.params.id,
33 | },
34 | this,
35 | trx,
36 | );
37 | }
38 | }
39 |
40 | const Builder = MakeActionFactory(ReversePromiseAction, PromiseManager);
41 | export const Router = MakeRouter(reverse_promise.action_name, Builder);
42 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/actions/schema.test.ts:
--------------------------------------------------------------------------------
1 | import { Result } from '@steem-monsters/lib-monad';
2 | import { validate_block } from './schema';
3 |
4 | describe('schema validation', () => {
5 | it.each`
6 | schema | isValid | params
7 | ${validate_block} | ${true} | ${{ block_num: 12345, hash: 'abc123', version: 'abc' }}
8 | ${validate_block} | ${true} | ${{ block_num: 12345, hash: 'abc123', version: 'abc' }}
9 | ${validate_block} | ${true} | ${{ block_num: '12345', hash: 'abc123', version: 'abc' }}
10 | ${validate_block} | ${false} | ${{ block_num: 12345, hash: 12345, version: 'abc' }}
11 | ${validate_block} | ${false} | ${{ hash: 'abc123', version: 'abc' }}
12 | ${validate_block} | ${false} | ${{ block_num: 12345, version: 'abc' }}
13 | ${validate_block} | ${false} | ${{ block_num: '12345', hash: 'abc123' }}
14 | `(`verifies [$schema.action_name] schema requirements are [$isValid] for $params ($#) `, ({ schema, params, isValid }) => {
15 | const result = schema.validate(params);
16 | expect(Result.isOk(result)).toBe(isValid);
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/actions/test_action.ts:
--------------------------------------------------------------------------------
1 | import { inject, injectable } from 'tsyringe';
2 | import { ActionFactory, Schema, ActionRouter, autoroute, OperationData, route, TestAction } from '@steem-monsters/splinterlands-validator';
3 |
4 | @injectable()
5 | export class Builder implements ActionFactory {
6 | build(op: OperationData, data: unknown, index?: number) {
7 | return new TestAction(op, data, index);
8 | }
9 | }
10 |
11 | @injectable()
12 | @autoroute()
13 | export class Router extends ActionRouter {
14 | @route(Schema.test.action_name, { from_block: 100 })
15 | readonly builder: Builder;
16 |
17 | constructor(@inject(Builder) builder: Builder) {
18 | super();
19 | this.builder = builder;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/actions/tokens/burn.ts:
--------------------------------------------------------------------------------
1 | import { Action, BalanceRepository, ErrorType, EventLog, OperationData, Trx, ValidationError } from '@steem-monsters/splinterlands-validator';
2 | import { burn } from '../schema';
3 | import { MakeActionFactory, MakeRouter } from '../utils';
4 |
5 | export class BurnAction extends Action {
6 | constructor(op: OperationData, data: unknown, index: number, private readonly balanceRepository: BalanceRepository) {
7 | super(burn, op, data, index);
8 | }
9 |
10 | async validate(trx?: Trx) {
11 | // Check that the sender has enough tokens in their account
12 | const balance = await this.balanceRepository.getBalance(this.op.account, this.params.token, trx);
13 |
14 | if (balance < this.params.qty) {
15 | throw new ValidationError('Insufficient balance.', this, ErrorType.InsufficientBalance);
16 | }
17 |
18 | if (this.op.account !== this.params.account) {
19 | throw new ValidationError('Account mismatch balance.', this, ErrorType.MismatchedAccount);
20 | }
21 | return true;
22 | }
23 |
24 | async process(trx?: Trx): Promise {
25 | // Add the recipient to the list of players affected by this action
26 | this.players.push(this.params.to);
27 | return await this.balanceRepository.updateBalance(this, this.op.account, this.params.to, this.params.token, this.params.qty, this.action_name, trx);
28 | }
29 | }
30 |
31 | const Builder = MakeActionFactory(BurnAction, BalanceRepository);
32 | export const Router = MakeRouter(burn.action_name, Builder);
33 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/actions/tokens/claim_staking_rewards.ts:
--------------------------------------------------------------------------------
1 | import { Action, EventLog, OperationData, StakingRewardsRepository, Trx } from '@steem-monsters/splinterlands-validator';
2 | import { claim_staking_rewards } from '../schema';
3 | import { MakeActionFactory, MakeRouter } from '../utils';
4 |
5 | export class ClaimStakingRewardsAction extends Action {
6 | constructor(op: OperationData, data: unknown, index: number, private readonly stakingRewardsRepository: StakingRewardsRepository) {
7 | super(claim_staking_rewards, op, data, index);
8 | }
9 |
10 | async validate(_: Trx) {
11 | return true;
12 | }
13 |
14 | async process(trx?: Trx): Promise {
15 | const claim_results = await this.stakingRewardsRepository.claimAll(this.op.account, 0, this, trx);
16 | // TODO update validator vote weight? i don't think we have to. staked SPS doesnt change.
17 | return claim_results;
18 | }
19 | }
20 |
21 | const Builder = MakeActionFactory(ClaimStakingRewardsAction, StakingRewardsRepository);
22 | export const Router = MakeRouter(claim_staking_rewards.action_name, Builder);
23 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/actions/validator/activate_license.ts:
--------------------------------------------------------------------------------
1 | import { OperationData, Action, EventLog, Trx, ValidationError, ErrorType } from '@steem-monsters/splinterlands-validator';
2 | import { activate_license } from '../schema';
3 | import { MakeActionFactory, MakeRouter } from '../utils';
4 | import { SpsValidatorLicenseManager } from '../../features/validator';
5 |
6 | export class ActivateLicenseAction extends Action {
7 | constructor(op: OperationData, data: unknown, index: number, private readonly licenseManager: SpsValidatorLicenseManager) {
8 | super(activate_license, op, data, index);
9 | }
10 |
11 | async validate(trx?: Trx) {
12 | const { licenses } = await this.licenseManager.getLicenses(this.op.account, trx);
13 | if (licenses < this.params.qty) {
14 | throw new ValidationError('Not enough licenses', this, ErrorType.InsufficientBalance);
15 | }
16 | return true;
17 | }
18 |
19 | async process(trx?: Trx): Promise {
20 | return [...(await this.licenseManager.activateLicenses(this, this.op.account, this.params.qty, trx))];
21 | }
22 | }
23 |
24 | const Builder = MakeActionFactory(ActivateLicenseAction, SpsValidatorLicenseManager);
25 | export const Router = MakeRouter(activate_license.action_name, Builder);
26 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/actions/validator/deactivate_license.ts:
--------------------------------------------------------------------------------
1 | import { OperationData, Action, EventLog, Trx, ValidationError, ErrorType } from '@steem-monsters/splinterlands-validator';
2 | import { deactivate_license } from '../schema';
3 | import { MakeActionFactory, MakeRouter } from '../utils';
4 | import { SpsValidatorLicenseManager } from '../../features/validator';
5 |
6 | export class DeactivateLicenseAction extends Action {
7 | constructor(op: OperationData, data: unknown, index: number, private readonly licenseManager: SpsValidatorLicenseManager) {
8 | super(deactivate_license, op, data, index);
9 | }
10 |
11 | async validate(trx?: Trx) {
12 | const { activatedLicenses } = await this.licenseManager.getLicenses(this.op.account, trx);
13 | if (activatedLicenses < this.params.qty) {
14 | throw new ValidationError('Not enough activated licenses', this, ErrorType.InsufficientBalance);
15 | }
16 | return true;
17 | }
18 |
19 | async process(trx?: Trx): Promise {
20 | return [...(await this.licenseManager.deactivateLicenses(this, this.op.account, this.params.qty, trx))];
21 | }
22 | }
23 |
24 | const Builder = MakeActionFactory(DeactivateLicenseAction, SpsValidatorLicenseManager);
25 | export const Router = MakeRouter(deactivate_license.action_name, Builder);
26 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/actions/validator/expire_check_ins.ts:
--------------------------------------------------------------------------------
1 | import { OperationData, Action, EventLog, Trx, ValidationError, ErrorType } from '@steem-monsters/splinterlands-validator';
2 | import { expire_check_ins } from '../schema';
3 | import { MakeActionFactory, MakeRouter } from '../utils';
4 | import { SpsValidatorLicenseManager } from '../../features/validator';
5 |
6 | export class ExpireCheckInsAction extends Action {
7 | constructor(op: OperationData, data: unknown, index: number, private readonly licenseManager: SpsValidatorLicenseManager) {
8 | super(expire_check_ins, op, data, index);
9 | }
10 |
11 | async validate() {
12 | if (this.op.account !== SpsValidatorLicenseManager.LICENSE_MANAGER_ACCOUNT) {
13 | throw new ValidationError('Only the system account can expire check ins', this, ErrorType.MismatchedAccount);
14 | }
15 | return true;
16 | }
17 |
18 | async process(trx?: Trx): Promise {
19 | return [...(await this.licenseManager.expireCheckIns(this, trx))];
20 | }
21 | }
22 |
23 | const Builder = MakeActionFactory(ExpireCheckInsAction, SpsValidatorLicenseManager);
24 | export const Router = MakeRouter(expire_check_ins.action_name, Builder);
25 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/actions/validator/unapprove_validator.ts:
--------------------------------------------------------------------------------
1 | import { Action, ErrorType, EventLog, OperationData, Trx, ValidationError, ValidatorVoteRepository } from '@steem-monsters/splinterlands-validator';
2 | import { unapprove_validator } from '../schema';
3 | import { MakeActionFactory, MakeRouter } from '../utils';
4 |
5 | export class UnapproveValidatorAction extends Action {
6 | constructor(op: OperationData, data: unknown, index: number, private readonly validatorVoteRepository: ValidatorVoteRepository) {
7 | super(unapprove_validator, op, data, index);
8 | }
9 |
10 | async validate(trx?: Trx) {
11 | const votes = await this.validatorVoteRepository.lookupByVoter(this.op.account, trx);
12 | // Make sure the account is currently voting for the specified validator that they wish to unapprove
13 | if (!votes || !votes.find((v) => v.validator === this.params.account_name)) {
14 | throw new ValidationError('This account is not currently voting for the specified validator.', this, ErrorType.NoSuchValidatorVote);
15 | }
16 |
17 | return true;
18 | }
19 |
20 | async process(trx?: Trx): Promise {
21 | return await this.validatorVoteRepository.delete(this, trx);
22 | }
23 | }
24 |
25 | const Builder = MakeActionFactory(UnapproveValidatorAction, ValidatorVoteRepository);
26 | export const Router = MakeRouter(unapprove_validator.action_name, Builder);
27 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/actions/validator/update_missed_blocks.ts:
--------------------------------------------------------------------------------
1 | import { Action, EventLog, OperationData, Trx, ValidatorRepository } from '@steem-monsters/splinterlands-validator';
2 | import { update_missed_blocks } from '../schema';
3 | import { MakeActionFactory, MakeRouter } from '../utils';
4 |
5 | export class UpdateMissedBlocksAction extends Action {
6 | constructor(op: OperationData, data: unknown, index: number, private readonly validatorRepository: ValidatorRepository) {
7 | super(update_missed_blocks, op, data, index);
8 | }
9 |
10 | async validate() {
11 | return true;
12 | }
13 |
14 | async process(trx?: Trx): Promise {
15 | return [...(await this.validatorRepository.incrementMissedBlocks(this.params.account, this.params.missed_blocks, trx))];
16 | }
17 | }
18 |
19 | const Builder = MakeActionFactory(UpdateMissedBlocksAction, ValidatorRepository);
20 | export const Router = MakeRouter(update_missed_blocks.action_name, Builder);
21 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/api/transition.ts:
--------------------------------------------------------------------------------
1 | import { LastBlockCache } from '@steem-monsters/splinterlands-validator';
2 | import { Router } from 'express';
3 | import { TransitionManager } from '../features/transition';
4 |
5 | export function registerTransitionRoutes(app: Router) {
6 | app.get('/extensions/transitions', async (req, res, next) => {
7 | try {
8 | const lastBlockCache = req.resolver.resolve(LastBlockCache);
9 | const TranisitionManager = req.resolver.resolve(TransitionManager);
10 | const blockNum = lastBlockCache.value?.block_num ?? 0;
11 | const statuses = TranisitionManager.getTransitionPointsStatusesAtBlock(blockNum);
12 | res.status(200).json({
13 | block_num: blockNum,
14 | transition_points: statuses,
15 | });
16 | } catch (err) {
17 | next(err);
18 | }
19 | });
20 | }
21 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/bookkeeping.ts:
--------------------------------------------------------------------------------
1 | import { inject, singleton } from 'tsyringe';
2 | import { BookkeepingFromConfig, BookkeepingWatch } from '@steem-monsters/splinterlands-validator';
3 | import { BookkeepingDefault } from '@steem-monsters/splinterlands-validator';
4 |
5 | @singleton()
6 | export class SpsBookkeeping extends BookkeepingFromConfig {
7 | constructor(@inject(BookkeepingWatch) watcher: BookkeepingWatch) {
8 | super(watcher, BookkeepingDefault.DOLLAR_ONLY);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/entities/block.ts:
--------------------------------------------------------------------------------
1 | import { inject, injectable, singleton } from 'tsyringe';
2 | import { BlockRepository, Handle, HiveAccountRepository, LastBlockCache, SocketLike, TransactionRepository } from '@steem-monsters/splinterlands-validator';
3 |
4 | @injectable()
5 | export class SpsTransactionRepository extends TransactionRepository {
6 | public constructor(@inject(Handle) handle: Handle, @inject(SocketLike) socket: SocketLike) {
7 | super(handle, socket);
8 | }
9 | }
10 |
11 | @injectable()
12 | export class SpsBlockRepository extends BlockRepository {
13 | public constructor(@inject(Handle) handle: Handle, @inject(TransactionRepository) transactionRepository: TransactionRepository) {
14 | super(handle, transactionRepository);
15 | }
16 | }
17 |
18 | @injectable()
19 | export class SpsHiveAccountRepository extends HiveAccountRepository {
20 | constructor(@inject(Handle) handle: Handle) {
21 | super(handle);
22 | }
23 | }
24 |
25 | @singleton()
26 | export class SpsLastBlockCache extends LastBlockCache {
27 | constructor(@inject(BlockRepository) blockRepository: BlockRepository) {
28 | super(blockRepository);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/entities/claims.ts:
--------------------------------------------------------------------------------
1 | import { inject, injectable } from 'tsyringe';
2 | import { PoolClaimPayloads, PoolManager, PrefixOpts } from '@steem-monsters/splinterlands-validator';
3 |
4 | @injectable()
5 | export class SpsPoolClaimPayloads extends PoolClaimPayloads {
6 | constructor(@inject(PrefixOpts) cfg: PrefixOpts, @inject(PoolManager) poolManager: PoolManager) {
7 | super(cfg, poolManager);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/entities/operation.ts:
--------------------------------------------------------------------------------
1 | import { ActionOrBust, ConfigLoader, LookupWrapper, OperationFactory, PrefixOpts } from '@steem-monsters/splinterlands-validator';
2 | import { inject, injectable } from 'tsyringe';
3 |
4 | @injectable()
5 | export class SpsActionOrBust extends ActionOrBust {
6 | public constructor(@inject(LookupWrapper) lookupWrapper: LookupWrapper) {
7 | super(lookupWrapper);
8 | }
9 | }
10 |
11 | @injectable()
12 | export class SpsOperationFactory extends OperationFactory {
13 | public constructor(@inject(ActionOrBust) actionOrBust: ActionOrBust, @inject(ConfigLoader) configLoader: ConfigLoader, @inject(PrefixOpts) cfg: PrefixOpts) {
14 | super(actionOrBust, configLoader, cfg);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/entities/promises/promise.ts:
--------------------------------------------------------------------------------
1 | import { Handle, PromiseRepository } from '@steem-monsters/splinterlands-validator';
2 | import { inject, injectable } from 'tsyringe';
3 |
4 | @injectable()
5 | export class SpsPromiseRepository extends PromiseRepository {
6 | public constructor(@inject(Handle) handle: Handle) {
7 | super(handle);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/entities/tables.ts:
--------------------------------------------------------------------------------
1 | import { Column, Table } from '@wwwouter/typed-knex';
2 |
3 | export type ValidatorCheckInStatus = 'active' | 'inactive';
4 |
5 | @Table('validator_check_in')
6 | export class ValidatorCheckInEntity {
7 | @Column()
8 | account!: string;
9 | @Column()
10 | status!: ValidatorCheckInStatus;
11 | @Column()
12 | last_check_in_block_num!: number;
13 | @Column()
14 | last_check_in!: Date;
15 | }
16 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/entities/tokens/active_delegations.ts:
--------------------------------------------------------------------------------
1 | import { inject, injectable } from 'tsyringe';
2 | import { ActiveDelegationsRepository, BalanceRepository, Handle, TokenUnstakingRepository } from '@steem-monsters/splinterlands-validator';
3 |
4 | @injectable()
5 | export class SpsActiveDelegationsRepository extends ActiveDelegationsRepository {
6 | public constructor(
7 | @inject(Handle) handle: Handle,
8 | @inject(BalanceRepository) balanceRepository: BalanceRepository,
9 | @inject(TokenUnstakingRepository) unstakingRepository: TokenUnstakingRepository,
10 | ) {
11 | super(handle, balanceRepository, unstakingRepository);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/entities/tokens/balance_history.ts:
--------------------------------------------------------------------------------
1 | import { inject, injectable } from 'tsyringe';
2 | import { BalanceHistoryRepository, Handle, SocketLike } from '@steem-monsters/splinterlands-validator';
3 |
4 | @injectable()
5 | export class SpsBalanceHistoryRepository extends BalanceHistoryRepository {
6 | public constructor(@inject(Handle) handle: Handle, @inject(SocketLike) socket: SocketLike) {
7 | super(handle, socket);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/entities/tokens/hive_engine.ts:
--------------------------------------------------------------------------------
1 | import { HiveClient } from '@steem-monsters/splinterlands-validator';
2 | import { inject, injectable } from 'tsyringe';
3 |
4 | @injectable()
5 | export class HiveEngineRepository {
6 | constructor(@inject(HiveClient) private readonly client: HiveClient) {}
7 |
8 | async getCirculatingSupply(token: string) {
9 | const resp: { circulatingSupply: string } = await this.client.engine.contracts.findOne('tokens', 'tokens', { symbol: token });
10 | return parseFloat(resp.circulatingSupply);
11 | }
12 |
13 | async getBalance(account: string, token: string) {
14 | const resp = await this.client.engine.tokens.getAccountBalance(account, token);
15 | return parseFloat(resp.balance);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/entities/tokens/staking_rewards.ts:
--------------------------------------------------------------------------------
1 | import { inject, injectable } from 'tsyringe';
2 | import { BalanceRepository, Handle, StakingRewardsRepository, Pools, PoolWatch, PoolUpdater, StakingConfiguration, BlockRepository } from '@steem-monsters/splinterlands-validator';
3 |
4 | @injectable()
5 | export class SpsStakingRewardsRepository extends StakingRewardsRepository {
6 | constructor(
7 | @inject(Handle) handle: Handle,
8 | @inject(PoolUpdater) poolUpdater: PoolUpdater,
9 | @inject(PoolWatch) watcher: PoolWatch,
10 | @inject(BalanceRepository) balanceRepository: BalanceRepository,
11 | @inject(Pools) pools: Pools,
12 | @inject(StakingConfiguration) stakingConfiguration: StakingConfiguration,
13 | @inject(BlockRepository) blockRepository: BlockRepository,
14 | ) {
15 | super(handle, poolUpdater, watcher, balanceRepository, pools, stakingConfiguration, blockRepository);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/entities/tokens/token_unstaking.ts:
--------------------------------------------------------------------------------
1 | import { inject, injectable } from 'tsyringe';
2 | import { Handle, PrefixOpts, TokenUnstakingRepository, UnstakingWatch } from '@steem-monsters/splinterlands-validator';
3 |
4 | @injectable()
5 | export class SpsTokenUnstakingRepository extends TokenUnstakingRepository {
6 | constructor(@inject(Handle) handle: Handle, @inject(UnstakingWatch) watcher: UnstakingWatch, @inject(PrefixOpts) cfg: PrefixOpts) {
7 | super(handle, watcher, cfg);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/entities/tokens/types/ethers-contracts/factories/index.ts:
--------------------------------------------------------------------------------
1 | /* Autogenerated file. Do not edit manually. */
2 | /* tslint:disable */
3 | /* eslint-disable */
4 | export { SpsAbi__factory } from "./SpsAbi__factory";
5 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/entities/tokens/types/ethers-contracts/index.ts:
--------------------------------------------------------------------------------
1 | /* Autogenerated file. Do not edit manually. */
2 | /* tslint:disable */
3 | /* eslint-disable */
4 | export type { SpsAbi } from "./SpsAbi";
5 | export * as factories from "./factories";
6 | export { SpsAbi__factory } from "./factories/SpsAbi__factory";
7 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/entities/validator/validator.ts:
--------------------------------------------------------------------------------
1 | import { inject, injectable } from 'tsyringe';
2 | import { Handle, ValidatorRepository, ValidatorWatch } from '@steem-monsters/splinterlands-validator';
3 |
4 | @injectable()
5 | export class SpsValidatorRepository extends ValidatorRepository {
6 | public constructor(@inject(Handle) handle: Handle, @inject(ValidatorWatch) watcher: ValidatorWatch) {
7 | super(handle, watcher);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/entities/validator/validator_vote.ts:
--------------------------------------------------------------------------------
1 | import { BalanceRepository, Handle, Trx, ValidatorVoteHistoryRepository, ValidatorVoteRepository, VoteWeightCalculator } from '@steem-monsters/splinterlands-validator';
2 | import { inject, injectable } from 'tsyringe';
3 | import { TOKENS } from '../../features/tokens';
4 |
5 | @injectable()
6 | export class SpsVoteWeightCalculator implements VoteWeightCalculator {
7 | constructor(@inject(BalanceRepository) private readonly balanceRepository: BalanceRepository) {}
8 |
9 | calculateVoteWeight(account: string, trx?: Trx): Promise {
10 | return this.balanceRepository.getBalance(account, TOKENS.SPSP, trx);
11 | }
12 | }
13 |
14 | @injectable()
15 | export class SpsValidatorVoteRepository extends ValidatorVoteRepository {
16 | constructor(
17 | @inject(Handle) handle: Handle,
18 | @inject(VoteWeightCalculator) voteWeightCalculator: VoteWeightCalculator,
19 | @inject(ValidatorVoteHistoryRepository) validatorVoteHistoryRepository: ValidatorVoteHistoryRepository,
20 | ) {
21 | super(handle, voteWeightCalculator, validatorVoteHistoryRepository);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/entry-point.ts:
--------------------------------------------------------------------------------
1 | import { DependencyContainer, inject, singleton } from 'tsyringe';
2 | import { Knex } from 'knex';
3 | import { SpsDelayedSocket } from './socket';
4 | import { SpsSynchronisationConfig } from './sync';
5 | import {
6 | BlockProcessor,
7 | BlockRepository,
8 | ConditionalApiActivator,
9 | DelayedSocket,
10 | EntryOptions,
11 | EntryPoint,
12 | HiveStream,
13 | KnexToken,
14 | LastBlockCache,
15 | PluginDispatcher,
16 | Primer,
17 | Snapshot,
18 | } from '@steem-monsters/splinterlands-validator';
19 |
20 | @singleton()
21 | export class SpsEntryPoint extends EntryPoint {
22 | public constructor(
23 | @inject(Primer) primer: Primer,
24 | @inject(EntryOptions) cfg: EntryOptions,
25 | @inject(KnexToken) knex: Knex,
26 | @inject(Snapshot) snap: Snapshot,
27 | @inject(HiveStream) stream: HiveStream,
28 | @inject(SpsDelayedSocket) socket: DelayedSocket,
29 | @inject(BlockProcessor) processor: BlockProcessor,
30 | @inject(BlockRepository) blockRepository: BlockRepository,
31 | @inject(LastBlockCache) lastBlockCache: LastBlockCache,
32 | @inject(ConditionalApiActivator) activator: ConditionalApiActivator,
33 | @inject(PluginDispatcher) pluginDispatcher: PluginDispatcher,
34 | ) {
35 | super(primer, cfg, knex, snap, stream, socket, processor, blockRepository, lastBlockCache, activator, pluginDispatcher);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/features/delegation/delegation_manager.ts:
--------------------------------------------------------------------------------
1 | import { ActiveDelegationsRepository, DelegationManager, DelegationManagerOpts, HiveAccountRepository, TokenSupport } from '@steem-monsters/splinterlands-validator';
2 | import { inject, injectable } from 'tsyringe';
3 | import { SUPPORTED_TOKENS } from '../tokens';
4 |
5 | const DELEGATION_MANAGER_OPTS: DelegationManagerOpts = {
6 | // 7 days in milliseconds
7 | undelegation_cooldown_ms: 7 * 24 * 60 * 60 * 1000,
8 | system_account_whitelist: ['$SOULKEEP'],
9 | };
10 |
11 | @injectable()
12 | export class SpsDelegationManager extends DelegationManager {
13 | constructor(
14 | @inject(HiveAccountRepository) hiveAccountRepository: HiveAccountRepository,
15 | @inject(ActiveDelegationsRepository) delegationRepository: ActiveDelegationsRepository,
16 | ) {
17 | super(DELEGATION_MANAGER_OPTS, TokenSupport.wrap(SUPPORTED_TOKENS), hiveAccountRepository, delegationRepository);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/features/delegation/delegation_promises.ts:
--------------------------------------------------------------------------------
1 | import { DelegationManager, DelegationPromiseHandler, DelgationPromiseHandlerOpts } from '@steem-monsters/splinterlands-validator';
2 | import { inject, injectable } from 'tsyringe';
3 |
4 | const DELEGATION_PROMISE_HANDLER_OPTS: DelgationPromiseHandlerOpts = {
5 | delegation_promise_account: '$DELEGATION_PROMISES',
6 | };
7 |
8 | @injectable()
9 | export class SpsDelegationPromiseHandler extends DelegationPromiseHandler {
10 | constructor(@inject(DelegationManager) delegationManager: DelegationManager) {
11 | super(DELEGATION_PROMISE_HANDLER_OPTS, delegationManager);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/features/delegation/index.ts:
--------------------------------------------------------------------------------
1 | export * from './delegation_manager';
2 | export * from './delegation_promises';
3 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/features/price_feed/config.ts:
--------------------------------------------------------------------------------
1 | import { Watcher } from '@steem-monsters/splinterlands-validator';
2 | import { number, object } from 'yup';
3 |
4 | export type PriceFeedConfig = {
5 | interval_blocks: number;
6 | };
7 |
8 | export type PriceFeedWatch = Watcher<'price_feed', PriceFeedConfig>;
9 | export const PriceFeedWatch: unique symbol = Symbol('PriceFeedWatch');
10 |
11 | export const price_feed_schema = object({
12 | interval_blocks: number().required(),
13 | });
14 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/features/price_feed/index.ts:
--------------------------------------------------------------------------------
1 | export * from './plugin';
2 | export * from './external-feeds';
3 | export * from './config';
4 | export * from './price-feed';
5 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/features/promises/index.ts:
--------------------------------------------------------------------------------
1 | export * from './promse-manager';
2 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/features/promises/promse-manager.ts:
--------------------------------------------------------------------------------
1 | import { AdminMembership, DelegationPromiseHandler, PrefixOpts, PromiseHandler, PromiseManager, PromiseRepository } from '@steem-monsters/splinterlands-validator';
2 | import { inject, injectable } from 'tsyringe';
3 |
4 | function buildHandlerMap(delegationPromiseHandler: DelegationPromiseHandler): Map {
5 | const handlerMap = new Map();
6 | handlerMap.set('delegation', delegationPromiseHandler);
7 | return handlerMap;
8 | }
9 |
10 | @injectable()
11 | export class SpsPromiseManager extends PromiseManager {
12 | constructor(
13 | @inject(DelegationPromiseHandler) delegationPromiseHandler: DelegationPromiseHandler,
14 | @inject(PrefixOpts) prefixOpts: PrefixOpts,
15 | @inject(AdminMembership) adminMembership: AdminMembership,
16 | @inject(PromiseRepository) promiseRepository: PromiseRepository,
17 | ) {
18 | super(buildHandlerMap(delegationPromiseHandler), prefixOpts, adminMembership, promiseRepository);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/features/tokens/index.ts:
--------------------------------------------------------------------------------
1 | export * from './supported-tokens';
2 | export * from './virtual-tokens';
3 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/features/tokens/supported-tokens.ts:
--------------------------------------------------------------------------------
1 | export const TOKENS = {
2 | SPS: 'SPS',
3 | SPSP: 'SPSP',
4 | SPSP_IN: 'SPSP-IN',
5 | SPSP_OUT: 'SPSP-OUT',
6 | LICENSE: 'LICENSE',
7 | ACTIVATED_LICENSE: 'ACTIVATED_LICENSE',
8 | RUNNING_LICENSE: 'RUNNING_LICENSE',
9 | };
10 |
11 | export const SUPPORTED_TOKENS = {
12 | [TOKENS.SPS]: { token: TOKENS.SPS, transferable: true, awardable: true, stakes: TOKENS.SPSP },
13 | [TOKENS.SPSP]: {
14 | token: TOKENS.SPSP,
15 | transferable: false,
16 | unstakes: TOKENS.SPS,
17 | delegation: { in_token: TOKENS.SPSP_IN, out_token: TOKENS.SPSP_OUT },
18 | },
19 | [TOKENS.LICENSE]: { token: TOKENS.LICENSE, transferable: true, precision: 0 },
20 | [TOKENS.ACTIVATED_LICENSE]: { token: TOKENS.ACTIVATED_LICENSE, transferable: false, precision: 0 },
21 | [TOKENS.RUNNING_LICENSE]: { token: TOKENS.RUNNING_LICENSE, transferable: false, precision: 0 },
22 | } as const;
23 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/features/tokens/virtual-tokens.ts:
--------------------------------------------------------------------------------
1 | import { TOKENS } from './supported-tokens';
2 |
3 | export const VIRTUAL_TOKENS = {
4 | SPS_TOTAL: 'SPS_TOTAL',
5 | LICENSE_TOTAL: 'LICENSE_TOTAL',
6 | };
7 |
8 | export const VIRTUAL_TOKENS_CONFIG = {
9 | [VIRTUAL_TOKENS.SPS_TOTAL]: [TOKENS.SPS, TOKENS.SPSP],
10 | [VIRTUAL_TOKENS.LICENSE_TOTAL]: [TOKENS.LICENSE, TOKENS.ACTIVATED_LICENSE],
11 | } as const;
12 |
13 | export type VirtualTokenConfig = Record;
14 | export const VirtualTokenConfig = Symbol('VirtualTokenConfig');
15 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/features/transition/index.ts:
--------------------------------------------------------------------------------
1 | export * from './transitions';
2 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/features/validator/config.ts:
--------------------------------------------------------------------------------
1 | import { Watcher } from '@steem-monsters/splinterlands-validator';
2 | import { number, object } from 'yup';
3 |
4 | export type ValidatorCheckInConfig = {
5 | check_in_window_blocks: number;
6 | check_in_interval_blocks: number;
7 | paused_until_block: number;
8 | };
9 |
10 | export type ValidatorCheckInWatch = Watcher<'validator_check_in', ValidatorCheckInConfig>;
11 | export const ValidatorCheckInWatch: unique symbol = Symbol('ValidatorCheckInWatch');
12 |
13 | export const validator_check_in_schema = object({
14 | check_in_window_blocks: number().required(),
15 | check_in_interval_blocks: number().required(),
16 | paused_until_block: number().required(),
17 | });
18 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/features/validator/index.ts:
--------------------------------------------------------------------------------
1 | export * from './config';
2 | export * from './license-manager';
3 | export * from './license-plugin';
4 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/hive-stream.ts:
--------------------------------------------------------------------------------
1 | import { inject, injectable } from 'tsyringe';
2 | import { Client } from 'splinterlands-dhive-sl';
3 | import { HiveStream, HiveStreamOptions } from '@steem-monsters/splinterlands-validator';
4 |
5 | @injectable()
6 | export class SpsHiveStream extends HiveStream {
7 | public constructor(@inject(Client) client: Client, @inject(HiveStreamOptions) options: HiveStreamOptions) {
8 | super(client, options);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/hive.ts:
--------------------------------------------------------------------------------
1 | import { HiveClient, HiveOptions, PrefixOpts, ValidatorOpts } from '@steem-monsters/splinterlands-validator';
2 | import { inject, singleton } from 'tsyringe';
3 |
4 | @singleton()
5 | export class SpsHiveClient extends HiveClient {
6 | constructor(@inject(HiveOptions) cfg: HiveOptions, @inject(ValidatorOpts) validatorConfig: ValidatorOpts, @inject(PrefixOpts) prefixOpts: PrefixOpts) {
7 | super(cfg, validatorConfig, prefixOpts);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/jest.global-setup.ts:
--------------------------------------------------------------------------------
1 | import GlobalDb from './jest.global-db';
2 |
3 | export default async function () {
4 | const { templateDb, connectionString } = await GlobalDb.init();
5 | process.env.SPL_TEST_DB_TEMPLATE = templateDb;
6 | process.env.SPL_TEST_DB_TEMPLATE_CONNECTION_STRING = connectionString;
7 | process.env.TZ = 'UTC';
8 | }
9 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/jest.global-teardown.ts:
--------------------------------------------------------------------------------
1 | import GlobalDb from './jest.global-db';
2 |
3 | export default async function () {
4 | await GlobalDb.destroy();
5 | }
6 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/jest.setup.ts:
--------------------------------------------------------------------------------
1 | import { TestWrapper } from '../__tests__/fake-db';
2 | import { DependencyContainer } from 'tsyringe';
3 | class Lazy {
4 | private static readonly initial: unique symbol = Symbol.for('uninitialized');
5 | #value: T | typeof Lazy.initial = Lazy.initial;
6 | readonly #init: () => T;
7 |
8 | private constructor(init: () => T) {
9 | this.#init = init;
10 | }
11 |
12 | public static from(init: () => T) {
13 | return new Lazy(init);
14 | }
15 |
16 | public get value(): T {
17 | return this.#value === Lazy.initial ? (this.#value = this.#init()) : this.#value;
18 | }
19 | }
20 |
21 | const lazy_container = Lazy.from(() => {
22 | // eslint-disable-next-line @typescript-eslint/no-var-requires
23 | const { container } = require('../__tests__/test-composition-root');
24 | return container as DependencyContainer;
25 | });
26 |
27 | const t = Lazy.from(() => lazy_container.value.resolve(TestWrapper).test);
28 | Object.defineProperty(test, 'dbOnly', { get: () => t.value });
29 |
30 | // we're using testcontainers now so test timeouts need to be pretty high.
31 | jest.setTimeout(60_000 * 5);
32 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/logger.ts:
--------------------------------------------------------------------------------
1 | import { log, LogLevel } from '@steem-monsters/splinterlands-validator';
2 | import { injectable } from 'tsyringe';
3 |
4 | export type StructuredLogMessage = Record | string;
5 |
6 | export interface Logger {
7 | error(message: StructuredLogMessage): void;
8 | warning(message: StructuredLogMessage): void;
9 | info(message: StructuredLogMessage): void;
10 | debug(message: StructuredLogMessage): void;
11 | trace(message: StructuredLogMessage): void;
12 | }
13 |
14 | function structured_log_to_string(message: StructuredLogMessage): string {
15 | if (typeof message === 'string') {
16 | return message;
17 | }
18 |
19 | return JSON.stringify(message);
20 | }
21 |
22 | @injectable()
23 | export class ValidatorLogger implements Logger {
24 | public debug(message: StructuredLogMessage): void {
25 | this.log(message, LogLevel.Debug);
26 | }
27 |
28 | public error(message: StructuredLogMessage): void {
29 | this.log(message, LogLevel.Error);
30 | }
31 |
32 | public info(message: StructuredLogMessage): void {
33 | this.log(message, LogLevel.Info);
34 | }
35 |
36 | public trace(message: StructuredLogMessage): void {
37 | this.log(message, LogLevel.Debug);
38 | }
39 |
40 | public warning(message: StructuredLogMessage): void {
41 | this.log(message, LogLevel.Warning);
42 | }
43 |
44 | private log(message: StructuredLogMessage, level: LogLevel): void {
45 | log(structured_log_to_string(message), level);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/manual-disposable.ts:
--------------------------------------------------------------------------------
1 | import { Lifecycle, injectAll, scoped, Disposable } from 'tsyringe';
2 |
3 | export const ManualDisposable: unique symbol = Symbol('ManualDisposable');
4 | type ManualDisposable = Disposable;
5 |
6 | /**
7 | * A class that tracks disposables that were registered with useValue, but still need to be disposed at the end of the container's lifecycle.
8 | */
9 | @scoped(Lifecycle.ContainerScoped)
10 | export class ManualDisposer implements Disposable {
11 | constructor(@injectAll(ManualDisposable) private readonly disposables: ManualDisposable[]) {}
12 |
13 | dispose() {
14 | for (const disposable of this.disposables) {
15 | disposable.dispose();
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/pool-manager.ts:
--------------------------------------------------------------------------------
1 | import { inject, singleton } from 'tsyringe';
2 | import { BalanceRepository, PoolManager, PoolSerializer, TokenWatch } from '@steem-monsters/splinterlands-validator';
3 |
4 | @singleton()
5 | export class SpsPoolManager extends PoolManager {
6 | constructor(@inject(PoolSerializer) serializer: PoolSerializer, @inject(TokenWatch) watcher: TokenWatch, @inject(BalanceRepository) balanceRepository: BalanceRepository) {
7 | super(serializer, watcher, balanceRepository);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/pools.ts:
--------------------------------------------------------------------------------
1 | import { TOKENS } from './features/tokens';
2 |
3 | const SpsPool = {
4 | name: 'staking_rewards',
5 | reward_account: '$SPS_STAKING_REWARDS',
6 | token: TOKENS.SPS,
7 | stake: TOKENS.SPSP,
8 | } as const;
9 |
10 | const ValidatorPool = {
11 | name: 'validator_rewards',
12 | reward_account: '$REWARD_POOLS_LICENSE',
13 | token: TOKENS.SPS,
14 | stake: TOKENS.RUNNING_LICENSE,
15 | } as const;
16 |
17 | export const ValidatorPools = [SpsPool, ValidatorPool];
18 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/primer.ts:
--------------------------------------------------------------------------------
1 | import { inject, singleton } from 'tsyringe';
2 | import { SpsConfigLoader } from './config';
3 | import { Bookkeeping, LastBlockCache, Prime, Primer, RawPriceFeed } from '@steem-monsters/splinterlands-validator';
4 | import { ValidatorShop } from './utilities/validator-shop';
5 | import { ValidatorCheckInPlugin } from './features/validator/license-plugin';
6 | import { PriceFeedPlugin } from './features/price_feed';
7 |
8 | @singleton()
9 | export class SpsPrimer extends Primer {
10 | constructor(
11 | @inject(RawPriceFeed) feed: Prime,
12 | @inject(LastBlockCache) lastBlockCache: Prime,
13 | @inject(SpsConfigLoader) configLoader: Prime,
14 | @inject(ValidatorShop) shop: Prime,
15 | @inject(Bookkeeping) bookkeeping: Prime,
16 | @inject(ValidatorCheckInPlugin) checkInPlugin: Prime,
17 | @inject(PriceFeedPlugin) priceFeedPlugin: Prime,
18 | ) {
19 | super(feed, lastBlockCache, configLoader, shop, bookkeeping, checkInPlugin, priceFeedPlugin);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/socket.ts:
--------------------------------------------------------------------------------
1 | import { inject, injectable, singleton } from 'tsyringe';
2 | import { DelayedSocket, SocketLike, SocketOptions, SocketWrapper } from '@steem-monsters/splinterlands-validator';
3 |
4 | @injectable()
5 | export class SpsSocketWrapper extends SocketWrapper {
6 | public constructor(@inject(SocketOptions) cfg: SocketOptions) {
7 | super(cfg);
8 | }
9 | }
10 |
11 | @singleton()
12 | export class SpsDelayedSocket extends DelayedSocket {
13 | public constructor(@inject(SocketWrapper) socket: SocketLike) {
14 | super(socket);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/apps/sps-validator/src/sps/validator-shop.ts:
--------------------------------------------------------------------------------
1 | import { inject, singleton } from 'tsyringe';
2 | import { BalanceRepository, LastBlockCache, PriceFeedConsumer, ShopWatch } from '@steem-monsters/splinterlands-validator';
3 | import { ValidatorShop } from './utilities/validator-shop';
4 |
5 | @singleton()
6 | export class SpsValidatorShop extends ValidatorShop {
7 | constructor(
8 | @inject(LastBlockCache) lastBlockCache: LastBlockCache,
9 | @inject(BalanceRepository) balanceRepository: BalanceRepository,
10 | @inject(PriceFeedConsumer) consumer: PriceFeedConsumer,
11 | @inject(ShopWatch) watcher: ShopWatch,
12 | ) {
13 | super(lastBlockCache, balanceRepository, consumer, watcher);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/apps/sps-validator/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "types": ["node"]
6 | },
7 | "exclude": ["jest.config.ts", "src/jest.global-db.ts", "src/jest.global-setup.ts", "src/jest.global-teardown.ts", "src/jest.setup.ts", "**/*.spec.ts", "**/*.test.ts"],
8 | "include": ["src/**/*.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/apps/sps-validator/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "esModuleInterop": true,
5 | "module": "commonjs",
6 | "target": "ES2022"
7 | },
8 | "files": [],
9 | "include": [],
10 | "references": [
11 | {
12 | "path": "./tsconfig.app.json"
13 | },
14 | {
15 | "path": "./tsconfig.spec.json"
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/apps/sps-validator/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "module": "commonjs",
6 | "types": ["jest", "node"]
7 | },
8 | "include": [
9 | "jest.config.ts",
10 | "src/jest.global-db.ts",
11 | "src/jest.global-setup.ts",
12 | "src/jest.global-teardown.ts",
13 | "src/jest.setup.ts",
14 | "**/*.test.ts",
15 | "**/*.spec.ts",
16 | "**/*.d.ts"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/atom/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["../.eslintrc.json"],
3 | "ignorePatterns": ["!**/*"],
4 | "overrides": [
5 | {
6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7 | "rules": {}
8 | },
9 | {
10 | "files": ["*.ts", "*.tsx"],
11 | "rules": {}
12 | },
13 | {
14 | "files": ["*.js", "*.jsx"],
15 | "rules": {}
16 | },
17 | {
18 | "files": ["*.json"],
19 | "parser": "jsonc-eslint-parser",
20 | "rules": {
21 | "@nx/dependency-checks": "error"
22 | }
23 | }
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/atom/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Derrick Ashton Beining
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/atom/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | displayName: 'lib-atom',
3 | preset: '../jest.preset.js',
4 | setupFiles: ['@abraham/reflection'],
5 | globals: {},
6 | testEnvironment: 'node',
7 | transform: {
8 | '^.+\\.[tj]sx?$': [
9 | 'ts-jest',
10 | {
11 | tsconfig: '/tsconfig.spec.json',
12 | },
13 | ],
14 | },
15 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
16 | coverageDirectory: '../../coverage/libs/atom',
17 | };
18 |
--------------------------------------------------------------------------------
/atom/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@steem-monsters/atom",
3 | "version": "0.0.1",
4 | "dependencies": {
5 | "tslib": "^2.3.0"
6 | },
7 | "type": "commonjs",
8 | "main": "./src/index.js",
9 | "typings": "./src/index.d.ts",
10 | "private": true
11 | }
12 |
--------------------------------------------------------------------------------
/atom/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lib-atom",
3 | "$schema": "../node_modules/nx/schemas/project-schema.json",
4 | "sourceRoot": "atom/src",
5 | "projectType": "library",
6 | "targets": {
7 | "build": {
8 | "executor": "@nx/esbuild:esbuild",
9 | "outputs": ["{options.outputPath}"],
10 | "options": {
11 | "outputPath": "dist/libs/atom",
12 | "main": "atom/src/index.ts",
13 | "platform": "node",
14 | "format": ["cjs"],
15 | "tsConfig": "atom/tsconfig.lib.json"
16 | }
17 | },
18 | "lint": {
19 | "executor": "@nx/eslint:lint"
20 | },
21 | "test": {
22 | "executor": "@nx/jest:jest",
23 | "outputs": ["{workspaceRoot}/coverage/atom"],
24 | "options": {
25 | "jestConfig": "atom/jest.config.js"
26 | }
27 | }
28 | },
29 | "tags": []
30 | }
31 |
--------------------------------------------------------------------------------
/atom/src/deref.ts:
--------------------------------------------------------------------------------
1 | import { Atom } from './atom';
2 | import { _getState } from './internal-state';
3 | import { DeepImmutable } from './internal-types';
4 | import { throwIfNotAtom } from './throwIfNotAtom';
5 |
6 | /**
7 | * Dereferences (i.e. "*reads*") the current state of an [[Atom]]. The dereferenced value
8 | * should ___not___ be mutated.
9 | *
10 | * @param the type of `atom`'s inner state
11 | *
12 | * @example
13 | ```js
14 |
15 | import {Atom, deref} from '@steem-monsters/atom'
16 |
17 | const stateAtom = Atom.of({ count: 0 })
18 |
19 | deref(stateAtom) // => { count: 0 }
20 | ```
21 | */
22 | export function deref(atom: Atom): DeepImmutable {
23 | throwIfNotAtom(atom);
24 | return _getState(atom) as DeepImmutable;
25 | }
26 |
--------------------------------------------------------------------------------
/atom/src/dispose.ts:
--------------------------------------------------------------------------------
1 | import { Atom } from './atom';
2 | import { _dispose } from './internal-state';
3 | import { throwIfNotAtom } from './throwIfNotAtom';
4 |
5 | /**
6 | * Cleans up any resources this atom is using.
7 | * @param atom
8 | */
9 | export function dispose(atom: Atom): void {
10 | throwIfNotAtom(atom);
11 | _dispose(atom);
12 | }
13 |
--------------------------------------------------------------------------------
/atom/src/error-messages.ts:
--------------------------------------------------------------------------------
1 | /** @ignore */
2 | export const expectedAtomButGot = 'Expected an Atom instances, but got:';
3 |
--------------------------------------------------------------------------------
/atom/src/getValidator.ts:
--------------------------------------------------------------------------------
1 | import { Atom } from './atom';
2 | import { _getValidator } from './internal-state';
3 | import { AtomConstructorOptions } from './internal-types';
4 |
5 | import { throwIfNotAtom } from './throwIfNotAtom';
6 |
7 | /**
8 | * Gets `atom`'s validator function
9 | *
10 | * @param the type of `atom`'s inner state
11 | *
12 | * @example
13 | ```js
14 |
15 | import {Atom, deref, getValidator, swap} from '@steem-monsters/atom'
16 |
17 | const atom = Atom.of({ count: 0 }, { validator: (state) => isEven(state.count) })
18 | const validator = getValidator(atom)
19 | validator({ count: 3 }) // => false
20 | validator({ count: 2 }) // => true
21 | ```
22 | */
23 |
24 | export function getValidator(atom: Atom): NonNullable['validator']> {
25 | throwIfNotAtom(atom);
26 | return _getValidator(atom);
27 | }
28 |
--------------------------------------------------------------------------------
/atom/src/index.ts:
--------------------------------------------------------------------------------
1 | export { addChangeHandler, removeChangeHandler } from './changeHandler';
2 | export { Atom } from './atom';
3 | export * from './internal-types';
4 | export { deref } from './deref';
5 | export { getValidator } from './getValidator';
6 | export { set } from './set';
7 | export { setValidator } from './setValidator';
8 | export { swap } from './swap';
9 | export { dispose } from './dispose';
10 |
--------------------------------------------------------------------------------
/atom/src/prettyPrint.ts:
--------------------------------------------------------------------------------
1 | /** @ignore */
2 | export function prettyPrint(val: any): string {
3 | return JSON.stringify(val, null, ' ');
4 | }
5 |
--------------------------------------------------------------------------------
/atom/src/set.ts:
--------------------------------------------------------------------------------
1 | import { Atom } from './atom';
2 | import { deref } from './deref';
3 | import { _getValidator, _runChangeHandlers, _setState } from './internal-state';
4 | import { DeepImmutable } from './internal-types';
5 | import { prettyPrint } from './prettyPrint';
6 | import { throwIfNotAtom } from './throwIfNotAtom';
7 |
8 | /**
9 | * Sets `atom`s state to `nextState`.
10 | *
11 | * It is equivalent to `swap(atom, () => newState)`.
12 | *
13 | * @param the type of `atom`'s inner state
14 | * @param atom an instance of [[Atom]]
15 | * @param nextState the value to which to set the state; it should be the same type/interface as current state
16 | *
17 | * @example
18 | ```js
19 |
20 | import {Atom, deref, set} from '@steem-monsters/atom'
21 |
22 | const atom = Atom.of({ count: 0 })
23 |
24 | set(atom, { count: 100 })
25 | deref(atom) // => { count: 100 }
26 | ```
27 | */
28 |
29 | export function set(atom: Atom, nextState: S): void {
30 | throwIfNotAtom(atom);
31 | const validator = _getValidator(atom);
32 | const didValidate = validator(nextState as DeepImmutable);
33 | if (!didValidate) {
34 | const errMsg = `Attempted to set the state of\n\n${atom}\n\nwith:\n\n${prettyPrint(nextState)}\n\nbut it did not pass validator:\n${validator}\n\n`;
35 | const err = Error(errMsg);
36 | err.name = 'AtomInvalidStateError';
37 |
38 | throw err;
39 | } else {
40 | const prevState = deref(atom);
41 | _setState(atom, nextState);
42 | _runChangeHandlers(atom, prevState as S, nextState);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/atom/src/test/deref.spec.tsx:
--------------------------------------------------------------------------------
1 | import * as ErrorMsgs from '../error-messages';
2 | import { Atom, deref } from '../index';
3 |
4 | describe('deref', () => {
5 | it('is a function', () => {
6 | expect(deref).toBeInstanceOf(Function);
7 | });
8 |
9 | it('fails when called with anything other than an Atom instance', () => {
10 | const pojo: unknown = {};
11 | const arr: unknown = [];
12 | const num: unknown = 1;
13 | const str: unknown = 'hello';
14 | const bool: unknown = true;
15 | expect(() => deref(pojo as Atom)).toThrow(ErrorMsgs.expectedAtomButGot);
16 | expect(() => deref(arr as Atom)).toThrow(ErrorMsgs.expectedAtomButGot);
17 | expect(() => deref(num as Atom)).toThrow(ErrorMsgs.expectedAtomButGot);
18 | expect(() => deref(str as Atom)).toThrow(ErrorMsgs.expectedAtomButGot);
19 | expect(() => deref(bool as Atom)).toThrow(ErrorMsgs.expectedAtomButGot);
20 | });
21 |
22 | it('returns the state of the atom', () => {
23 | const a1State = { count: 1 };
24 | const a2State = { size: 1 };
25 | const a3State = [1];
26 | const a1 = Atom.of(a1State);
27 | const a2 = Atom.of(a2State);
28 | const a3 = Atom.of(a3State);
29 |
30 | expect(deref(a3)).toBe(a3State);
31 | expect(deref(a1)).toBe(a1State);
32 | expect(deref(a2)).toBe(a2State);
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/atom/src/test/set.spec.tsx:
--------------------------------------------------------------------------------
1 | import { Atom, deref, set } from '../index';
2 |
3 | describe('set function', () => {
4 | it('is a function', () => {
5 | expect(set).toBeInstanceOf(Function);
6 | });
7 |
8 | it("sets the Atom's value to the passed-in value", () => {
9 | const TEST_ATOM = Atom.of({ count: 0 });
10 | const newState = { count: 1 };
11 | set(TEST_ATOM, newState);
12 | expect(deref(TEST_ATOM)).toBe(newState);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/atom/src/throwIfNotAtom.ts:
--------------------------------------------------------------------------------
1 | import { Atom } from './atom';
2 | import * as ErrorMsgs from './error-messages';
3 | import { _getState } from './internal-state';
4 | import { prettyPrint } from './prettyPrint';
5 |
6 | /** @ignore */
7 | export function throwIfNotAtom(atom: Atom): void | never {
8 | if (!(atom instanceof Atom)) {
9 | throw TypeError(`${ErrorMsgs.expectedAtomButGot}\n\n${prettyPrint(atom)}`);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/atom/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.base.json",
3 | "compilerOptions": {
4 | "target": "ES2022",
5 | "lib": ["ES2022"],
6 | "module": "CommonJS",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitOverride": true,
10 | "noPropertyAccessFromIndexSignature": true,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true
13 | },
14 | "files": [],
15 | "include": [],
16 | "references": [
17 | {
18 | "path": "./tsconfig.lib.json"
19 | },
20 | {
21 | "path": "./tsconfig.spec.json"
22 | }
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/atom/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../dist/out-tsc",
5 | "declaration": true,
6 | "types": ["node"]
7 | },
8 | "include": ["src/**/*.ts"],
9 | "exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/atom/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../dist/out-tsc",
5 | "module": "commonjs",
6 | "types": [
7 | "jest",
8 | "node"
9 | ]
10 | },
11 | "include": [
12 | "jest.config.ts",
13 | "jest.config.js",
14 | "**/*.test.ts",
15 | "**/*.spec.ts",
16 | "**/*.test.tsx",
17 | "**/*.spec.tsx",
18 | "**/*.test.js",
19 | "**/*.spec.js",
20 | "**/*.test.jsx",
21 | "**/*.spec.jsx",
22 | "**/*.d.ts"
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/jest.config.ts:
--------------------------------------------------------------------------------
1 | import { getJestProjectsAsync } from '@nx/jest';
2 |
3 | export default async () => ({
4 | projects: await getJestProjectsAsync(),
5 | });
6 |
--------------------------------------------------------------------------------
/jest.preset.js:
--------------------------------------------------------------------------------
1 | const nxPreset = require('@nx/jest/preset').default;
2 |
3 | module.exports = {
4 | ...nxPreset,
5 | /* TODO: Update to latest Jest snapshotFormat
6 | * By default Nx has kept the older style of Jest Snapshot formats
7 | * to prevent breaking of any existing tests with snapshots.
8 | * It's recommend you update to the latest format.
9 | * You can do this by removing snapshotFormat property
10 | * and running tests with --update-snapshot flag.
11 | * Example: "nx affected --targets=test --update-snapshot"
12 | * More info: https://jestjs.io/docs/upgrading-to-jest29#snapshot-format
13 | */
14 | snapshotFormat: { escapeString: true, printBasicPrototype: true },
15 | };
16 |
--------------------------------------------------------------------------------
/monad/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["../.eslintrc.json"],
3 | "ignorePatterns": ["!**/*"],
4 | "overrides": [
5 | {
6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7 | "rules": {
8 | "@typescript-eslint/no-unused-vars": [
9 | "warn",
10 | {
11 | "argsIgnorePattern": "^_",
12 | "varsIgnorePattern": "^_"
13 | }
14 | ]
15 | }
16 | },
17 | {
18 | "files": ["*.ts", "*.tsx"],
19 | "rules": {}
20 | },
21 | {
22 | "files": ["*.js", "*.jsx"],
23 | "rules": {}
24 | }
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/monad/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # parcel-bundler cache (https://parceljs.org/)
61 | .cache
62 |
63 | # next.js build output
64 | .next
65 |
66 | # nuxt.js build output
67 | .nuxt
68 |
69 | # vuepress build output
70 | .vuepress/dist
71 |
72 | # Serverless directories
73 | .serverless
74 |
75 | # FuseBox cache
76 | .fusebox/
77 | state.json
78 | scripts/validator_data_*.sql
79 |
80 | # Intellij family
81 | .idea
82 |
83 | # MacOS stuff
84 | .DS_Store
85 |
86 | # TypeScript build folder
87 | dist
88 |
--------------------------------------------------------------------------------
/monad/jest.config.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | export default {
3 | displayName: 'lib-monad',
4 | preset: '../jest.preset.js',
5 | globals: {},
6 | roots: ['/src'],
7 | testEnvironment: 'node',
8 | transform: {
9 | '^.+\\.[tj]s$': [
10 | 'ts-jest',
11 | {
12 | tsconfig: '/tsconfig.spec.json',
13 | },
14 | ],
15 | },
16 | moduleFileExtensions: ['ts', 'js', 'html'],
17 | coverageDirectory: '../coverage/libs/lib-monad',
18 | };
19 |
--------------------------------------------------------------------------------
/monad/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@steem-monsters/lib-monad",
3 | "version": "0.1.0-prerelease2",
4 | "description": "Library for monads",
5 | "author": "Michiel Hegemans",
6 | "license": "MIT",
7 | "repository": {
8 | "type": "git",
9 | "url": "git://github.com/steem-monsters/splinterlands-validator.git"
10 | },
11 | "publishConfig": {
12 | "registry": "https://npm.pkg.github.com"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/monad/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lib-monad",
3 | "$schema": "../node_modules/nx/schemas/project-schema.json",
4 | "projectType": "library",
5 | "targets": {
6 | "build": {
7 | "executor": "@nx/esbuild:esbuild",
8 | "outputs": ["{options.outputPath}"],
9 | "options": {
10 | "outputPath": "dist/libs/monad",
11 | "tsConfig": "monad/tsconfig.lib.json",
12 | "platform": "node",
13 | "format": ["cjs"],
14 | "main": "monad/src/lib.ts",
15 | "assets": ["monad/"]
16 | }
17 | },
18 | "lint": {
19 | "executor": "@nx/eslint:lint",
20 | "outputs": ["{options.outputFile}"]
21 | },
22 | "test": {
23 | "executor": "@nx/jest:jest",
24 | "outputs": ["{workspaceRoot}/coverage/libs/lib-monad"],
25 | "options": {
26 | "jestConfig": "monad/jest.config.ts"
27 | }
28 | }
29 | },
30 | "tags": []
31 | }
32 |
--------------------------------------------------------------------------------
/monad/src/lib.ts:
--------------------------------------------------------------------------------
1 | export * from './result';
2 |
--------------------------------------------------------------------------------
/monad/src/result.test.ts:
--------------------------------------------------------------------------------
1 | import { Result } from './result';
2 |
3 | describe('Result', () => {
4 | test('isOk(ok)', () => {
5 | const result = Result.Ok(undefined);
6 |
7 | expect(Result.isOk(result)).toBeTruthy();
8 | });
9 |
10 | test('isOk(error)', () => {
11 | const result = Result.Err(undefined);
12 |
13 | expect(Result.isOk(result)).toBeFalsy();
14 | });
15 |
16 | test('isErr(error)', () => {
17 | const result = Result.Err(undefined);
18 |
19 | expect(Result.isErr(result)).toBeTruthy();
20 | });
21 |
22 | test('isErr(ok)', () => {
23 | const result = Result.Ok(undefined);
24 |
25 | expect(Result.isErr(result)).toBeFalsy();
26 | });
27 |
28 | test('Ok construction', () => {
29 | const result = Result.Ok('hello');
30 |
31 | expect(result.value).toBe('hello');
32 | });
33 |
34 | test('Err construction', () => {
35 | const result = Result.Err('hello');
36 |
37 | expect(result.error).toBe('hello');
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/monad/src/result.ts:
--------------------------------------------------------------------------------
1 | export type Ok = {
2 | status: 'ok';
3 | value: T;
4 | };
5 |
6 | export type Err = {
7 | status: 'err';
8 | error: E;
9 | };
10 |
11 | export type Result = Ok | Err;
12 |
13 | export const Result = {
14 | Ok: (value: T): Ok => ({ status: 'ok', value } as const),
15 | OkVoid: (): Ok => ({ status: 'ok', value: undefined } as const),
16 | Err: (error: E): Err =>
17 | ({
18 | status: 'err',
19 | error,
20 | } as const),
21 | isErr: (result: Result): result is Err => result.status === 'err',
22 | isOk: (result: Result): result is Ok => result.status === 'ok',
23 | } as const;
24 |
--------------------------------------------------------------------------------
/monad/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.base.json",
3 | "compilerOptions": {
4 | "target": "ES2022",
5 | "lib": ["ES2022"],
6 | "module": "commonjs",
7 | "experimentalDecorators": true,
8 | "emitDecoratorMetadata": true,
9 | "resolveJsonModule": true,
10 | "sourceMap": true,
11 | "outDir": "dist",
12 | "noEmit": true,
13 | "forceConsistentCasingInFileNames": true,
14 | "strict": true,
15 | "noImplicitAny": true,
16 | "strictNullChecks": true,
17 | "noImplicitReturns": true,
18 | "noFallthroughCasesInSwitch": true,
19 | "noImplicitOverride": true,
20 | "skipLibCheck": true
21 | },
22 | "references": [
23 | {
24 | "path": "./tsconfig.lib.json"
25 | },
26 | {
27 | "path": "./tsconfig.spec.json"
28 | }
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/monad/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "isolatedModules": true,
5 | "noEmit": false,
6 | "declaration": true,
7 | "declarationMap": true
8 | },
9 | "include": ["src/**/*.ts"],
10 | "exclude": ["src/**/*.test.ts", "src/**/*.spec.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/monad/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../dist/out-tsc",
5 | "module": "commonjs",
6 | "types": ["jest", "node"]
7 | },
8 | "include": ["jest.config.ts", "**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/npmrc.docker:
--------------------------------------------------------------------------------
1 | @steem-monsters:registry=https://npm.pkg.github.com/
2 |
--------------------------------------------------------------------------------
/postgres/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM postgres:16.8-bullseye
2 |
3 | ENV POSTGRES_DB ""
4 | ENV POSTGRES_USER ""
5 | ENV POSTGRES_PASSWORD ""
6 | ENV VALIDATOR_DB ""
7 |
8 | RUN apt-get update && apt-get -y install postgresql-16-partman
9 |
10 | COPY ./setup-preload.sh /docker-entrypoint-initdb.d/
11 |
--------------------------------------------------------------------------------
/postgres/setup-preload.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cat <> /var/lib/postgresql/data/postgresql.conf
4 | shared_preload_libraries='pg_partman_bgw'
5 | pg_partman_bgw.dbname = '$VALIDATOR_DB'
6 | EOT
7 |
--------------------------------------------------------------------------------
/scripts/ci-lazy-regenerate-structure.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | IFS=$'\n\t'
4 |
5 | SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
6 | ROOT_DIR="${SCRIPT_DIR}/.."
7 | SQITCH_DIR="${ROOT_DIR}/sqitch"
8 | VALIDATOR_DIR="${ROOT_DIR}/apps/sps-validator/src/__tests__"
9 |
10 | MOST_RECENT_MIGRATION=$(find "${SQITCH_DIR}" -type f -printf "%T+\t%p\n" | sort | tail -1 | cut -f2)
11 | TARGET="${VALIDATOR_DIR}/structure.sql"
12 | echo "$TARGET: $MOST_RECENT_MIGRATION;${SCRIPT_DIR}/ci-regenerate-structure.sh" | make -f- && exit 0
13 |
14 |
--------------------------------------------------------------------------------
/scripts/create-structural-dump.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # our version of pg_dump doesnt support excluding extensions,
4 | # so we have to exclude the extensions manually
5 | # just remove any lines that start with CREATE EXTENSION
6 | # from the dump file
7 | pg_dump --schema-only --no-owner --no-acl --disable-triggers \
8 | --no-comments --no-publications --no-security-labels \
9 | -N snapshot -N sqitch -N sqitch-data -N partman \
10 | -T '*_temp' -T 'blocks_p*' -T 'validator_transactions_p*' \
11 | -T 'validator_transaction_players_p*' -T '*_template' \
12 | --no-subscriptions --no-tablespaces \
13 | --host "${POSTGRES_HOST:-localhost}" \
14 | --username "${POSTGRES_USER:-postgres}" \
15 | "${POSTGRES_DB:-postgres}" | \
16 | # remove the CREATE EXTENSION lines
17 | sed '/^CREATE EXTENSION/d'
18 |
19 |
--------------------------------------------------------------------------------
/sqitch/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM sqitch/sqitch:v1.5.0
2 | USER root
3 | RUN apt-get -qq update && apt-get -qq install unzip && rm -rf /var/cache/apt/* /var/lib/apt/lists/*
4 | ARG snapshot
5 | RUN ["/bin/bash", "-c", ": ${snapshot:?Build argument snapshot needs to be set and not null.}"]
6 |
7 | WORKDIR /app
8 | COPY deploy ./deploy
9 | COPY verify ./verify
10 | COPY revert ./revert
11 | COPY sqitch* ./
12 | COPY *.sh ./
13 | ADD $snapshot /app/snapshot.zip
14 | ENV SNAPSHOT_ZIP=/app/snapshot.zip
15 | ENV SNAPSHOT=/app/snapshot.sql
16 |
17 | RUN chown -R sqitch /app
18 | USER sqitch
19 | ENTRYPOINT ["/bin/bash"]
20 | CMD ["./docker-entrypoint.sh"]
21 |
--------------------------------------------------------------------------------
/sqitch/README.md:
--------------------------------------------------------------------------------
1 | # Instructions for release.
2 |
3 | Add your changes using `./sqitch add` like normal. Snapshots contain the latest sqitch change they were taken with. When restoring a snapshot, we deploy up until the change from the snapshot, restore our snapshot, then apply any changes after the snapshot's change. This allows us to use older snapshots on newer versions of the validator without any issues.
4 |
5 | The snapshot restoration will call a pre and post restore function that are responsible for clearing out the snapshot tables before restore, and moving the snapshot table data into the main tables. If a database change added a new table or a new column, those functions must be updated to handle the new table/column. If an older snapshot is restored on a newer version, the pre/post snapshot functions will always be at a version that works with that snapshot.
6 |
--------------------------------------------------------------------------------
/sqitch/deploy/active_delegations.sql:
--------------------------------------------------------------------------------
1 | -- Deploy active_delegations table to pg
2 |
3 | BEGIN;
4 |
5 | CREATE TABLE IF NOT EXISTS :APP_SCHEMA.active_delegations
6 | (
7 | token character varying (20) NOT NULL,
8 | delegator character varying (50) NOT NULL,
9 | delegatee character varying (50) NOT NULL,
10 | amount numeric(15, 3) NOT NULL,
11 | last_delegation_tx character varying (100) NOT NULL,
12 | last_delegation_date timestamptz NOT NULL,
13 | last_undelegation_date timestamptz NULL,
14 | last_undelegation_tx character varying (100) NULL,
15 | CONSTRAINT active_delegations_pkey PRIMARY KEY (token, delegator, delegatee)
16 | );
17 |
18 | GRANT SELECT, INSERT, UPDATE ON TABLE :APP_SCHEMA.active_delegations TO :APP_USER;
19 |
20 | COMMIT;
21 |
--------------------------------------------------------------------------------
/sqitch/deploy/hive_account_authority.sql:
--------------------------------------------------------------------------------
1 | -- Deploy splinterlands-validator:hive_account_authority to pg
2 |
3 | BEGIN;
4 |
5 | ALTER TABLE :APP_SCHEMA.hive_accounts ADD COLUMN IF NOT EXISTS authority JSONB NOT NULL DEFAULT '{}';
6 |
7 | COMMIT;
8 |
--------------------------------------------------------------------------------
/sqitch/deploy/pre-snapshot-restore-function.sql:
--------------------------------------------------------------------------------
1 | -- Deploy splinterlands-validator:pre-snapshot-function to pg
2 |
3 | BEGIN;
4 |
5 | CREATE OR REPLACE FUNCTION snapshot.pre_snapshot_restore(p_data_schema text DEFAULT :'APP_SCHEMA'::text)
6 | RETURNS void
7 | LANGUAGE 'plpgsql'
8 | COST 100
9 | VOLATILE PARALLEL UNSAFE
10 | AS $BODY$
11 | BEGIN
12 |
13 | PERFORM set_config('search_path', regexp_replace(p_data_schema ||', public', '[^\w ,]', '', 'g'), true);
14 |
15 | RAISE NOTICE 'Data source schema: %', p_data_schema;
16 |
17 | -- Delete any pre-existing snapshot data
18 | TRUNCATE TABLE snapshot.token_unstaking;
19 | TRUNCATE TABLE snapshot.hive_accounts;
20 | TRUNCATE TABLE snapshot.balances;
21 | TRUNCATE TABLE snapshot.balance_history;
22 | TRUNCATE TABLE snapshot.staking_pool_reward_debt;
23 | TRUNCATE TABLE snapshot.validator_votes;
24 | TRUNCATE TABLE snapshot.validators;
25 | TRUNCATE TABLE snapshot.validator_vote_history;
26 | TRUNCATE TABLE snapshot.blocks;
27 | TRUNCATE TABLE snapshot.validator_transactions;
28 | TRUNCATE TABLE snapshot.validator_transaction_players;
29 | TRUNCATE TABLE snapshot.price_history;
30 | TRUNCATE TABLE snapshot.active_delegations;
31 | TRUNCATE TABLE snapshot.validator_check_in;
32 | TRUNCATE TABLE snapshot.promise;
33 | TRUNCATE TABLE snapshot.promise_history;
34 | TRUNCATE TABLE snapshot.config;
35 | END;
36 | $BODY$;
37 |
38 |
39 | COMMIT;
40 |
--------------------------------------------------------------------------------
/sqitch/deploy/pre-snapshot.sql:
--------------------------------------------------------------------------------
1 | -- Deploy splinterlands-validator:pre-snapshot-v1 to pg
2 | -- deprecated
3 |
4 | BEGIN;
5 | SET client_min_messages = warning;
6 | COMMIT;
7 |
--------------------------------------------------------------------------------
/sqitch/deploy/price_feed.sql:
--------------------------------------------------------------------------------
1 | -- Deploy splinterlands-validator:price_feed to pg
2 | -- requires: validators
3 |
4 | BEGIN;
5 | SET client_min_messages = warning;
6 |
7 | CREATE TABLE IF NOT EXISTS :APP_SCHEMA.price_history
8 | (
9 | validator character varying(50) COLLATE pg_catalog."default" NOT NULL,
10 | token character varying(20) NOT NULL,
11 | block_num integer NOT NULL,
12 | block_time timestamp without time zone NOT NULL,
13 | token_price numeric(12, 6) NOT NULL
14 | );
15 |
16 | ALTER TABLE ONLY :APP_SCHEMA.price_history
17 | ADD CONSTRAINT price_history_pkey PRIMARY KEY (validator, token);
18 |
19 | GRANT SELECT, INSERT, UPDATE ON TABLE :APP_SCHEMA.price_history TO :APP_USER;
20 |
21 | COMMIT;
22 |
--------------------------------------------------------------------------------
/sqitch/deploy/promises.sql:
--------------------------------------------------------------------------------
1 | -- Deploy splinterlands-validator:promises to pg
2 |
3 | BEGIN;
4 |
5 | CREATE TYPE :APP_SCHEMA.promise_status AS ENUM (
6 | 'open',
7 | 'fulfilled',
8 | 'completed',
9 | 'cancelled'
10 | );
11 |
12 | CREATE TABLE :APP_SCHEMA.promise (
13 | id SERIAL PRIMARY KEY,
14 | ext_id TEXT NOT NULL,
15 | -- "delegation"
16 | type TEXT NOT NULL,
17 | status :APP_SCHEMA.promise_status NOT NULL,
18 | -- { amount: 1000, token: "SPSP" }
19 | params JSONB NOT NULL,
20 | -- accounts that can complete or cancel the promise
21 | controllers TEXT[] NOT NULL,
22 | -- if someone fulfills the promise, it will automatically be cancelled if it is not completed within this interval
23 | fulfill_timeout_seconds INT,
24 | -- who fulfilled the promise
25 | fulfilled_by TEXT,
26 | -- this would be in the history table so do we need it?
27 | fulfilled_at TIMESTAMP WITHOUT TIME ZONE,
28 | fulfilled_expiration TIMESTAMP WITHOUT TIME ZONE,
29 | created_date TIMESTAMP WITHOUT TIME ZONE NOT NULL,
30 | updated_date TIMESTAMP WITHOUT TIME ZONE NOT NULL
31 | );
32 |
33 | CREATE INDEX promise_type_ext_id_idx ON :APP_SCHEMA.promise (type, ext_id);
34 |
35 | CREATE TABLE :APP_SCHEMA.promise_history (
36 | id SERIAL PRIMARY KEY,
37 | promise_id INT NOT NULL,
38 | action TEXT NOT NULL,
39 | player TEXT NOT NULL,
40 | previous_status :APP_SCHEMA.promise_status,
41 | new_status :APP_SCHEMA.promise_status NOT NULL,
42 | trx_id TEXT NOT NULL,
43 | created_date TIMESTAMP WITHOUT TIME ZONE NOT NULL
44 | );
45 |
46 | CREATE INDEX promise_history_promise_id_idx ON :APP_SCHEMA.promise_history (promise_id);
47 |
48 | COMMIT;
49 |
--------------------------------------------------------------------------------
/sqitch/deploy/running-validator-reward-pool.sql:
--------------------------------------------------------------------------------
1 | -- Deploy splinterlands-validator:running-validator-reward-pool to pg
2 | BEGIN;
3 |
4 | -- NOTE: there are no unstaking settings because you can't unstake the "running validator" token
5 | CREATE TYPE :APP_SCHEMA.validator_check_in_status AS ENUM ('active', 'inactive');
6 |
7 | CREATE TABLE :APP_SCHEMA.validator_check_in (
8 | account text NOT NULL,
9 | status :APP_SCHEMA.validator_check_in_status NOT NULL,
10 | last_check_in_block_num integer NOT NULL,
11 | last_check_in timestamp without time zone NOT NULL,
12 | PRIMARY KEY (account)
13 | );
14 |
15 | CREATE INDEX idx_validator_check_in_last_check_in_status ON :APP_SCHEMA.validator_check_in USING btree (last_check_in_block_num ASC, status);
16 |
17 | COMMIT;
18 |
--------------------------------------------------------------------------------
/sqitch/deploy/snapshot.sql:
--------------------------------------------------------------------------------
1 | -- Deploy splinterlands-validator:snapshot to pg
2 | -- requires: validators
3 | -- requires: appschema
4 |
5 | BEGIN;
6 |
7 | -- Call the pre-snapshot function to clear out any existing snapshot data
8 | SELECT snapshot.pre_snapshot_restore();
9 |
10 | \i :snapshot_file
11 |
12 | -- Call the post-snapshot function to move the snapshot data into the main tables
13 | SELECT snapshot.post_snapshot_restore();
14 |
15 | COMMIT;
16 |
--------------------------------------------------------------------------------
/sqitch/deploy/transaction_players_perf.sql:
--------------------------------------------------------------------------------
1 | -- Deploy splinterlands-validator:transaction_players_perf to pg
2 |
3 | BEGIN;
4 |
5 | ALTER TABLE :APP_SCHEMA.validator_transaction_players ADD COLUMN IF NOT EXISTS block_num integer;
6 | ALTER TABLE :APP_SCHEMA.validator_transaction_players ADD COLUMN IF NOT EXISTS is_owner boolean;
7 | ALTER TABLE :APP_SCHEMA.validator_transaction_players ADD COLUMN IF NOT EXISTS success boolean;
8 | ALTER TABLE :APP_SCHEMA.validator_transaction_players ADD COLUMN IF NOT EXISTS type character varying(100);
9 |
10 | CREATE INDEX IF NOT EXISTS validator_transaction_players_player_block_type_idx ON :APP_SCHEMA.validator_transaction_players USING btree (player ASC, block_num ASC, type ASC);
11 | CREATE INDEX IF NOT EXISTS validator_transaction_players_block_type_idx ON :APP_SCHEMA.validator_transaction_players USING btree (block_num ASC, type ASC) WHERE success AND is_owner IS TRUE;
12 |
13 | -- migrate existing data
14 | UPDATE :APP_SCHEMA.validator_transaction_players
15 | SET block_num = t.block_num, is_owner = t.player = validator_transaction_players.player, success = t.success, type = t.type
16 | FROM :APP_SCHEMA.validator_transactions t
17 | WHERE validator_transaction_players.transaction_id = t.id;
18 |
19 | ALTER TABLE :APP_SCHEMA.validator_transaction_players ALTER COLUMN block_num SET NOT NULL;
20 | ALTER TABLE :APP_SCHEMA.validator_transaction_players ALTER COLUMN is_owner SET NOT NULL;
21 | ALTER TABLE :APP_SCHEMA.validator_transaction_players ALTER COLUMN type SET NOT NULL;
22 |
23 | DROP TABLE snapshot.validator_transaction_players;
24 | CREATE TABLE snapshot.validator_transaction_players AS TABLE :APP_SCHEMA.validator_transaction_players WITH NO DATA;
25 |
26 | COMMIT;
27 |
--------------------------------------------------------------------------------
/sqitch/docker-entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | IFS=$'\n\t'
4 |
5 | SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
6 | TMP_TEMPLATE="${TMPDIR:-/tmp/}$(basename "$0").XXXXXXXXXXXX"
7 | env_file=$(mktemp "$TMP_TEMPLATE")
8 |
9 | "$SCRIPT_DIR/extract-snapshot.sh" "$SNAPSHOT_ZIP" "$env_file"
10 | "$SCRIPT_DIR/init-db.sh"
11 | # ShellCheck can't follow non-constant source. Use a directive to specify location.
12 | # shellcheck disable=SC1090
13 | source "$env_file"
14 | "$SCRIPT_DIR/staggered-deploy.sh"
15 | rm -f "$env_file"
16 |
--------------------------------------------------------------------------------
/sqitch/extract-snapshot.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | IFS=$'\n\t'
4 |
5 | SCRIPT_NAME=$0
6 | TMP_TEMPLATE="${TMPDIR:-/tmp/}$(basename "$0").XXXXXXXXXXXX"
7 |
8 | function usage {
9 | echo "usage: $SCRIPT_NAME [-h] FILE [ENV]"
10 | echo " -h display help"
11 | echo " FILE snapshot zip file"
12 | echo " [ENV] output file for parsed LAST_BLOCK, in dotenv format"
13 | echo " Required environment variables:"
14 | echo " - SNAPSHOT - where to extract the snapshot SQL file."
15 | }
16 |
17 | while getopts ":h" option; do
18 | case $option in
19 | h) # Display usage instructions
20 | usage
21 | exit;;
22 | *) # Unsupported flag
23 | usage
24 | exit 1;;
25 | esac
26 | done
27 |
28 | if [ -z ${SNAPSHOT+x} ]
29 | then
30 | echo "SNAPSHOT is unset."
31 | usage
32 | exit 1
33 | fi
34 |
35 | if [ -z ${1+x} ]
36 | then
37 | echo "FILE was not provided."
38 | usage
39 | exit 1
40 | fi
41 |
42 | FILE="$1"
43 |
44 | if [ ! -f "$FILE" ]; then
45 | echo "FILE $FILE does not seem to be a file"
46 | usage
47 | exit 1
48 | fi
49 |
50 | if [ -n "${2+x}" ]
51 | then
52 | ENV="$2"
53 | fi
54 |
55 | unzip_dir=$(mktemp -d "$TMP_TEMPLATE")
56 | unzip "$FILE" -d "$unzip_dir"
57 | snapshot_file=$(find "$unzip_dir" -maxdepth 1 -type f -iname "*.sql" | head -n 1)
58 |
59 |
60 | mv "$snapshot_file" "$SNAPSHOT"
61 | rm -r "$unzip_dir"
62 |
--------------------------------------------------------------------------------
/sqitch/init-db.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -eo pipefail
4 |
5 | CONNECTION_STRING="postgres://${PGUSER}:${PGPASSWORD}@${PGHOST}:${PGPORT}/postgres"
6 |
7 | APP_USER=${APP_USER:-$PGUSER}
8 | APP_PASSWORD=${APP_PASSWORD:-$PGPASSWORD}
9 | APP_DATABASE=${APP_DATABASE:-$PGDATABASE}
10 |
11 | # Create the users if it does not exist
12 | psql -Atx "${CONNECTION_STRING}" -c "SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = '${APP_USER}'" |
13 | grep -q 1 ||
14 | psql -v "ON_ERROR_STOP=1" -Atx "${CONNECTION_STRING}" -c "CREATE ROLE ${APP_USER} WITH LOGIN PASSWORD '${APP_PASSWORD}'" -c "GRANT ${APP_USER} TO ${PGUSER}"
15 |
16 | # Create the database if it does not exist
17 | psql -Atx "${CONNECTION_STRING}" -c "SELECT 1 FROM pg_database WHERE datname = '${APP_DATABASE}'" |
18 | grep -q 1 ||
19 | psql -v "ON_ERROR_STOP=1" -Atx "${CONNECTION_STRING}" -c "CREATE DATABASE ${APP_DATABASE} WITH OWNER ${APP_USER}"
20 |
--------------------------------------------------------------------------------
/sqitch/revert/active_delegations.sql:
--------------------------------------------------------------------------------
1 | -- Revert splinterlands-validator:active_delegations from pg
2 |
3 | BEGIN;
4 |
5 | DROP TABLE IF EXISTS :APP_SCHEMA.active_delegations;
6 |
7 | COMMIT;
8 |
--------------------------------------------------------------------------------
/sqitch/revert/appschema.sql:
--------------------------------------------------------------------------------
1 | -- Revert splinterlands-validator:appschema from pg
2 |
3 | BEGIN;
4 |
5 | DROP TABLE IF EXISTS :APP_SCHEMA.hive_accounts;
6 | DROP TABLE IF EXISTS :APP_SCHEMA.blocks;
7 | DROP TABLE IF EXISTS :APP_SCHEMA.validator_transactions;
8 | DROP TABLE IF EXISTS :APP_SCHEMA.validator_transaction_players;
9 | DROP TABLE IF EXISTS :APP_SCHEMA.token_unstaking;
10 | DROP TABLE IF EXISTS :APP_SCHEMA.staking_pool_reward_debt;
11 | DROP SEQUENCE IF EXISTS :APP_SCHEMA.item_details_id_seq;
12 | DROP TABLE IF EXISTS :APP_SCHEMA.config;
13 | DROP TABLE IF EXISTS :APP_SCHEMA.balances;
14 | DROP TABLE IF EXISTS :APP_SCHEMA.balance_history;
15 | DROP SCHEMA IF EXISTS :APP_SCHEMA;
16 |
17 | COMMIT;
18 |
--------------------------------------------------------------------------------
/sqitch/revert/hive_account_authority.sql:
--------------------------------------------------------------------------------
1 | -- Revert splinterlands-validator:hive_account_authority from pg
2 |
3 | BEGIN;
4 |
5 | ALTER TABLE :APP_SCHEMA.hive_accounts DROP COLUMN IF EXISTS authority;
6 |
7 | COMMIT;
8 |
--------------------------------------------------------------------------------
/sqitch/revert/post-snapshot-restore-function.sql:
--------------------------------------------------------------------------------
1 | -- Revert splinterlands-validator:post-snapshot-restore-function from pg
2 |
3 | BEGIN;
4 |
5 | DROP FUNCTION IF EXISTS snapshot.post_snapshot_restore(text);
6 |
7 | COMMIT;
8 |
--------------------------------------------------------------------------------
/sqitch/revert/pre-snapshot-restore-function.sql:
--------------------------------------------------------------------------------
1 | -- Revert splinterlands-validator:pre-snapshot-function from pg
2 |
3 | BEGIN;
4 |
5 | DROP FUNCTION IF EXISTS snapshot.pre_snapshot_restore(text);
6 |
7 | COMMIT;
8 |
--------------------------------------------------------------------------------
/sqitch/revert/pre-snapshot.sql:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheSPSDAO/SPS-Validator/8007f3fe78dc19a9cacc6101c23e14d8790d621b/sqitch/revert/pre-snapshot.sql
--------------------------------------------------------------------------------
/sqitch/revert/price_feed.sql:
--------------------------------------------------------------------------------
1 | -- Revert splinterlands-validator:price_feed from pg
2 |
3 | BEGIN;
4 |
5 | DROP TABLE IF EXISTS :APP_SCHEMA.price_history;
6 |
7 | COMMIT;
8 |
--------------------------------------------------------------------------------
/sqitch/revert/promises.sql:
--------------------------------------------------------------------------------
1 | -- Revert splinterlands-validator:promises from pg
2 |
3 | BEGIN;
4 |
5 | DROP TABLE :APP_SCHEMA.promise_history;
6 | DROP TABLE :APP_SCHEMA.promise;
7 | DROP TYPE :APP_SCHEMA.promise_status;
8 |
9 | COMMIT;
10 |
--------------------------------------------------------------------------------
/sqitch/revert/running-validator-reward-pool.sql:
--------------------------------------------------------------------------------
1 | -- Revert splinterlands-validator:running-validator-reward-pool from pg
2 |
3 | BEGIN;
4 |
5 | DROP TABLE :APP_SCHEMA.validator_check_in;
6 | DROP TYPE :APP_SCHEMA.validator_check_in_status;
7 |
8 | COMMIT;
9 |
--------------------------------------------------------------------------------
/sqitch/revert/snapshot-function.sql:
--------------------------------------------------------------------------------
1 | -- Revert splinterlands-validator:snapshot-function from pg
2 |
3 | BEGIN;
4 |
5 | DROP FUNCTION snapshot.freshsnapshot(boolean, text);
6 |
7 | COMMIT;
8 |
--------------------------------------------------------------------------------
/sqitch/revert/snapshot-tables.sql:
--------------------------------------------------------------------------------
1 | -- Revert splinterlands-validator:snapshot-tables from pg
2 |
3 | BEGIN;
4 |
5 | DROP TABLE IF EXISTS snapshot.validator_vote_history;
6 | DROP TABLE IF EXISTS snapshot.validator_votes;
7 | DROP TABLE IF EXISTS snapshot.validators;
8 | DROP TABLE IF EXISTS snapshot.hive_accounts;
9 | DROP TABLE IF EXISTS snapshot.blocks;
10 | DROP TABLE IF EXISTS snapshot.validator_transactions;
11 | DROP TABLE IF EXISTS snapshot.validator_transaction_players;
12 | DROP TABLE IF EXISTS snapshot.token_unstaking;
13 | DROP TABLE IF EXISTS snapshot.staking_pool_reward_debt;
14 | DROP TABLE IF EXISTS snapshot.balances;
15 | DROP TABLE IF EXISTS snapshot.balance_history;
16 | DROP TABLE IF EXISTS snapshot.price_history;
17 | DROP TABLE IF EXISTS snapshot.config;
18 | DROP TABLE IF EXISTS snapshot.active_delegations;
19 | DROP TABLE IF EXISTS snapshot.validator_check_in;
20 | DROP TABLE IF EXISTS snapshot.promise;
21 | DROP TABLE IF EXISTS snapshot.promise_history;
22 |
23 | DROP SCHEMA IF EXISTS snapshot;
24 |
25 | COMMIT;
26 |
--------------------------------------------------------------------------------
/sqitch/revert/snapshot.sql:
--------------------------------------------------------------------------------
1 | -- Revert splinterlands-validator:snapshot from pg
2 |
3 | BEGIN;
4 |
5 |
6 | TRUNCATE TABLE snapshot.balances;
7 | TRUNCATE TABLE snapshot.balance_history;
8 | TRUNCATE TABLE snapshot.hive_accounts;
9 | TRUNCATE TABLE snapshot.staking_pool_reward_debt;
10 | TRUNCATE TABLE snapshot.validator_votes;
11 | TRUNCATE TABLE snapshot.validator_vote_history;
12 | TRUNCATE TABLE snapshot.validators;
13 | TRUNCATE TABLE snapshot.blocks;
14 | TRUNCATE TABLE snapshot.token_unstaking;
15 | TRUNCATE TABLE snapshot.price_history;
16 | TRUNCATE TABLE snapshot.config;
17 | TRUNCATE TABLE snapshot.active_delegations;
18 | TRUNCATE TABLE snapshot.validator_check_in;
19 | TRUNCATE TABLE snapshot.promise;
20 | TRUNCATE TABLE snapshot.promise_history;
21 |
22 | TRUNCATE TABLE :APP_SCHEMA.balances;
23 | TRUNCATE TABLE :APP_SCHEMA.balance_history;
24 | TRUNCATE TABLE :APP_SCHEMA.hive_accounts;
25 | TRUNCATE TABLE :APP_SCHEMA.staking_pool_reward_debt;
26 | TRUNCATE TABLE :APP_SCHEMA.validator_votes;
27 | TRUNCATE TABLE :APP_SCHEMA.validator_vote_history;
28 | TRUNCATE TABLE :APP_SCHEMA.validators;
29 | TRUNCATE TABLE :APP_SCHEMA.blocks;
30 | TRUNCATE TABLE :APP_SCHEMA.token_unstaking;
31 | TRUNCATE TABLE :APP_SCHEMA.price_history;
32 | TRUNCATE TABLE :APP_SCHEMA.config;
33 | TRUNCATE TABLE :APP_SCHEMA.active_delegations;
34 | TRUNCATE TABLE :APP_SCHEMA.validator_check_in;
35 | TRUNCATE TABLE :APP_SCHEMA.promise;
36 | TRUNCATE TABLE :APP_SCHEMA.promise_history;
37 |
38 | COMMIT;
39 |
--------------------------------------------------------------------------------
/sqitch/revert/transaction_players_perf.sql:
--------------------------------------------------------------------------------
1 | -- Revert splinterlands-validator:transaction_players_perf from pg
2 |
3 | BEGIN;
4 |
5 | ALTER TABLE :APP_SCHEMA.validator_transaction_players DROP COLUMN IF EXISTS block_num;
6 | ALTER TABLE :APP_SCHEMA.validator_transaction_players DROP COLUMN IF EXISTS is_owner;
7 | ALTER TABLE :APP_SCHEMA.validator_transaction_players DROP COLUMN IF EXISTS success;
8 | ALTER TABLE :APP_SCHEMA.validator_transaction_players DROP COLUMN IF EXISTS type;
9 |
10 | DROP TABLE snapshot.validator_transaction_players;
11 | CREATE TABLE snapshot.validator_transaction_players AS TABLE :APP_SCHEMA.validator_transaction_players WITH NO DATA;
12 |
13 | COMMIT;
14 |
--------------------------------------------------------------------------------
/sqitch/revert/validators.sql:
--------------------------------------------------------------------------------
1 | -- Revert splinterlands-validator:validators from pg
2 |
3 | BEGIN;
4 |
5 | DELETE FROM :APP_SCHEMA.config WHERE group_name = 'validator' AND name = 'reduction_pct';
6 | DELETE FROM :APP_SCHEMA.config WHERE group_name = 'validator' AND name = 'reduction_blocks';
7 | DELETE FROM :APP_SCHEMA.config WHERE group_name = 'validator' AND name = 'tokens_per_block';
8 | DELETE FROM :APP_SCHEMA.config WHERE group_name = 'validator' AND name = 'reward_start_block';
9 | DELETE FROM :APP_SCHEMA.config WHERE group_name = 'validator' AND name = 'min_validators';
10 | DELETE FROM :APP_SCHEMA.config WHERE group_name = 'validator' AND name = 'max_block_age';
11 | DELETE FROM :APP_SCHEMA.config WHERE group_name = 'validator' AND name = 'max_votes';
12 |
13 | DROP TABLE IF EXISTS :APP_SCHEMA.validator_vote_history;
14 | DROP TABLE IF EXISTS :APP_SCHEMA.validator_votes;
15 | DROP TABLE IF EXISTS :APP_SCHEMA.validators;
16 |
17 | COMMIT;
18 |
--------------------------------------------------------------------------------
/sqitch/sqitch-data.plan:
--------------------------------------------------------------------------------
1 | %syntax-version=1.0.0
2 | %project=splinterlands-validator-snapshot
3 | %uri=https://github.com/steem-monsters/splinterlands-validator?ref=snapshot
4 |
5 | snapshot 2022-03-02T16:03:54Z Jelle Licht (wordempire) # Dynamic snapshot restoration
6 |
--------------------------------------------------------------------------------
/sqitch/sqitch.conf:
--------------------------------------------------------------------------------
1 | [core]
2 | engine = pg
3 | # plan_file = sqitch.plan
4 | # top_dir = .
5 | # [engine "pg"]
6 | # target = db:pg:
7 | # registry = sqitch
8 | # client = psql
9 | [deploy]
10 | verify = true
11 | [rebase]
12 | verify = true
13 | [target "sqitch-data"]
14 | uri = db:pg:
15 | plan_file = sqitch-data.plan
16 |
--------------------------------------------------------------------------------
/sqitch/verify/active_delegations.sql:
--------------------------------------------------------------------------------
1 | -- Verify splinterlands-validator:active_delegations on pg
2 |
3 | BEGIN;
4 |
5 | SELECT token,
6 | delegator,
7 | delegatee,
8 | amount,
9 | last_delegation_tx,
10 | last_delegation_date,
11 | last_undelegation_date,
12 | last_undelegation_tx
13 | FROM active_delegations
14 | WHERE FALSE;
15 |
16 | ROLLBACK;
17 |
--------------------------------------------------------------------------------
/sqitch/verify/appschema.sql:
--------------------------------------------------------------------------------
1 | -- Verify splinterlands-validator:appschema on pg
2 |
3 | BEGIN;
4 |
5 | DO
6 | $$
7 | BEGIN
8 | ASSERT (SELECT has_schema_privilege('public', 'usage'));
9 | END
10 | $$;
11 |
12 | SELECT player,
13 | token,
14 | amount,
15 | balance_start,
16 | balance_end,
17 | block_num,
18 | trx_id,
19 | type,
20 | created_date,
21 | counterparty
22 | FROM balance_history
23 | WHERE FALSE;
24 |
25 | SELECT player, token, balance
26 | FROM balances
27 | WHERE FALSE;
28 |
29 | SELECT group_name, group_type, name, index, value_type, last_updated_date, last_updated_tx
30 | FROM config
31 | WHERE FALSE;
32 |
33 | SELECT player, pool_name, reward_debt
34 | FROM staking_pool_reward_debt
35 | WHERE FALSE;
36 |
37 | SELECT player,
38 | unstake_tx,
39 | unstake_start_date,
40 | is_active,
41 | token,
42 | total_qty,
43 | next_unstake_date,
44 | total_unstaked,
45 | unstaking_periods,
46 | unstaking_interval_seconds,
47 | cancel_tx
48 | FROM token_unstaking
49 | WHERE FALSE;
50 |
51 | SELECT transaction_id, player
52 | FROM validator_transaction_players
53 | WHERE FALSE;
54 |
55 | SELECT id,
56 | block_id,
57 | prev_block_id,
58 | type,
59 | player,
60 | data,
61 | success,
62 | error,
63 | block_num,
64 | index,
65 | created_date,
66 | result
67 | FROM validator_transactions
68 | WHERE FALSE;
69 |
70 | SELECT block_num, block_id, prev_block_id, l2_block_id, block_time, validator
71 | FROM blocks
72 | WHERE FALSE;
73 |
74 | SELECT name
75 | FROM hive_accounts
76 | WHERE FALSE;
77 |
78 | ROLLBACK;
79 |
--------------------------------------------------------------------------------
/sqitch/verify/hive_account_authority.sql:
--------------------------------------------------------------------------------
1 | -- Verify splinterlands-validator:hive_account_authority on pg
2 |
3 | BEGIN;
4 |
5 | SELECT
6 | name,
7 | authority
8 | FROM
9 | :APP_SCHEMA.hive_accounts
10 | WHERE
11 | 1 = 0;
12 |
13 | ROLLBACK;
14 |
--------------------------------------------------------------------------------
/sqitch/verify/partitions.sql:
--------------------------------------------------------------------------------
1 | -- Verify splinterlands-validator:partitions on pg
2 |
3 | BEGIN;
4 |
5 | -- XXX Add verifications here.
6 |
7 | ROLLBACK;
8 |
--------------------------------------------------------------------------------
/sqitch/verify/post-snapshot-restore-function.sql:
--------------------------------------------------------------------------------
1 | -- Verify splinterlands-validator:post-snapshot-restore-function on pg
2 |
3 | BEGIN;
4 |
5 | -- XXX Add verifications here.
6 |
7 | ROLLBACK;
8 |
--------------------------------------------------------------------------------
/sqitch/verify/post-snapshot-restore-partition-prep.sql:
--------------------------------------------------------------------------------
1 | -- Verify splinterlands-validator:post-snapshot-restore-partition-prep on pg
2 |
3 | BEGIN;
4 |
5 | -- XXX Add verifications here.
6 |
7 | ROLLBACK;
8 |
--------------------------------------------------------------------------------
/sqitch/verify/pre-snapshot-restore-function.sql:
--------------------------------------------------------------------------------
1 | -- Verify splinterlands-validator:pre-snapshot-function on pg
2 |
3 | BEGIN;
4 |
5 | ROLLBACK;
6 |
--------------------------------------------------------------------------------
/sqitch/verify/pre-snapshot.sql:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheSPSDAO/SPS-Validator/8007f3fe78dc19a9cacc6101c23e14d8790d621b/sqitch/verify/pre-snapshot.sql
--------------------------------------------------------------------------------
/sqitch/verify/price_feed.sql:
--------------------------------------------------------------------------------
1 | -- Verify splinterlands-validator:price_feed on pg
2 |
3 | BEGIN;
4 |
5 | INSERT INTO :APP_SCHEMA.price_history
6 | VALUES ('$VALIDATOR', 'SPS', -7331, now(), 10);
7 |
8 | ROLLBACK;
9 |
--------------------------------------------------------------------------------
/sqitch/verify/promises.sql:
--------------------------------------------------------------------------------
1 | -- Verify splinterlands-validator:promises on pg
2 |
3 | BEGIN;
4 |
5 | SELECT
6 | id,
7 | ext_id,
8 | type,
9 | status,
10 | params,
11 | controllers,
12 | fulfill_timeout_seconds,
13 | fulfilled_by,
14 | fulfilled_at,
15 | fulfilled_expiration,
16 | created_date,
17 | updated_date
18 | FROM
19 | :APP_SCHEMA.promise
20 | WHERE FALSE;
21 |
22 | SELECT
23 | id,
24 | promise_id,
25 | action,
26 | player,
27 | previous_status,
28 | new_status,
29 | trx_id,
30 | created_date
31 | FROM
32 | :APP_SCHEMA.promise_history
33 | WHERE FALSE;
34 |
35 | ROLLBACK;
36 |
--------------------------------------------------------------------------------
/sqitch/verify/reward_delegations.sql:
--------------------------------------------------------------------------------
1 | -- Verify splinterlands-validator:reward_delegations on pg
2 |
3 | BEGIN;
4 |
5 | SELECT
6 | player,
7 | delegate_to_player,
8 | type,
9 | token,
10 | percent,
11 | trx_id,
12 | delegation_date
13 | FROM
14 | :APP_SCHEMA.reward_delegations
15 | WHERE
16 | FALSE;
17 |
18 | SELECT
19 | player,
20 | delegate_to_player,
21 | type,
22 | token,
23 | percent,
24 | trx_id,
25 | delegation_date
26 | FROM
27 | :APP_SCHEMA.reward_delegation_history
28 | WHERE
29 | FALSE;
30 |
31 |
32 | ROLLBACK;
33 |
--------------------------------------------------------------------------------
/sqitch/verify/running-validator-reward-pool.sql:
--------------------------------------------------------------------------------
1 | -- Verify splinterlands-validator:running-validator-reward-pool on pg
2 |
3 | BEGIN;
4 |
5 | SELECT
6 | account,
7 | status,
8 | last_check_in_block_num,
9 | last_check_in
10 | FROM
11 | :APP_SCHEMA.validator_check_in
12 | WHERE
13 | FALSE;
14 |
15 | ROLLBACK;
16 |
--------------------------------------------------------------------------------
/sqitch/verify/snapshot-function-v2.sql:
--------------------------------------------------------------------------------
1 | -- Verify splinterlands-validator:snapshot_function_transaction_players_update on pg
2 |
3 | BEGIN;
4 |
5 | -- XXX Add verifications here.
6 |
7 | ROLLBACK;
8 |
--------------------------------------------------------------------------------
/sqitch/verify/snapshot-function.sql:
--------------------------------------------------------------------------------
1 | -- Verify splinterlands-validator:snapshot-function on pg
2 |
3 | BEGIN;
4 |
5 | SELECT has_function_privilege('snapshot.freshsnapshot(boolean, text)', 'execute');
6 |
7 | ROLLBACK;
8 |
--------------------------------------------------------------------------------
/sqitch/verify/snapshot.sql:
--------------------------------------------------------------------------------
1 | -- Verify splinterlands-validator:snapshot on pg
2 |
3 | BEGIN;
4 |
5 | SELECT EXISTS(
6 | SELECT 1 FROM :APP_SCHEMA.config
7 | WHERE group_name='$root'
8 | AND group_type='object'
9 | AND name='admin_accounts');
10 |
11 | ROLLBACK;
12 |
--------------------------------------------------------------------------------
/sqitch/verify/transaction_players_perf.sql:
--------------------------------------------------------------------------------
1 | -- Verify splinterlands-validator:transaction_players_perf on pg
2 |
3 | BEGIN;
4 |
5 | SELECT
6 | transaction_id,
7 | player,
8 | block_num,
9 | is_owner,
10 | success,
11 | type
12 | FROM
13 | :APP_SCHEMA.validator_transaction_players
14 | WHERE FALSE;
15 |
16 | ROLLBACK;
17 |
--------------------------------------------------------------------------------
/sqitch/verify/validator_api_url.sql:
--------------------------------------------------------------------------------
1 | -- Verify splinterlands-validator:validator_api_url on pg
2 |
3 | BEGIN;
4 |
5 | SELECT account_name, is_active, post_url, total_votes, missed_blocks, api_url, last_version
6 | FROM :APP_SCHEMA.validators
7 | WHERE FALSE;
8 |
9 | SELECT account_name, is_active, post_url, total_votes, missed_blocks, api_url, last_version
10 | FROM snapshot.validators
11 | WHERE FALSE;
12 |
13 | ROLLBACK;
14 |
--------------------------------------------------------------------------------
/sqitch/verify/validators.sql:
--------------------------------------------------------------------------------
1 | -- Verify splinterlands-validator:validators on pg
2 |
3 | BEGIN;
4 |
5 | -- XXX Add verifications here.
6 | SELECT account_name, is_active, post_url, total_votes, missed_blocks
7 | FROM :APP_SCHEMA.validators
8 | WHERE FALSE;
9 |
10 | SELECT voter, validator, vote_weight
11 | FROM :APP_SCHEMA.validator_votes
12 | WHERE FALSE;
13 |
14 | SELECT transaction_id, created_date, voter, validator, is_approval, vote_weight
15 | FROM :APP_SCHEMA.validator_vote_history
16 | WHERE FALSE;
17 |
18 | ROLLBACK;
19 |
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "rootDir": ".",
5 | "sourceMap": true,
6 | "declaration": false,
7 | "moduleResolution": "node",
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "importHelpers": true,
12 | "target": "es2020",
13 | "module": "commonjs",
14 | "strict": true,
15 | "lib": ["es2020"],
16 | "skipLibCheck": true,
17 | "resolveJsonModule": true,
18 | "skipDefaultLibCheck": true,
19 | "baseUrl": ".",
20 | "paths": {
21 | "@steem-monsters/atom": ["atom/src/index.ts"],
22 | "@steem-monsters/lib-monad": ["monad/src/lib.ts"],
23 | "@steem-monsters/splinterlands-validator": ["validator/src/lib.ts"]
24 | }
25 | },
26 | "exclude": ["node_modules", "tmp"]
27 | }
28 |
--------------------------------------------------------------------------------
/validator/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # parcel-bundler cache (https://parceljs.org/)
61 | .cache
62 |
63 | # next.js build output
64 | .next
65 |
66 | # nuxt.js build output
67 | .nuxt
68 |
69 | # vuepress build output
70 | .vuepress/dist
71 |
72 | # Serverless directories
73 | .serverless
74 |
75 | # FuseBox cache
76 | .fusebox/
77 | state.json
78 | scripts/validator_data_*.sql
79 |
80 | # Intellij family
81 | .idea
82 |
83 | # MacOS stuff
84 | .DS_Store
85 |
86 | # TypeScript build folder
87 | dist
88 |
--------------------------------------------------------------------------------
/validator/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all",
4 | "arrowParens": "always",
5 | "tabWidth": 4,
6 | "printWidth": 180
7 | }
8 |
--------------------------------------------------------------------------------
/validator/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | displayName: 'lib-validator',
3 | preset: '../jest.preset.js',
4 | setupFiles: ['@abraham/reflection'],
5 | globals: {},
6 | testEnvironment: 'node',
7 | transform: {
8 | '^.+\\.[tj]sx?$': [
9 | 'ts-jest',
10 | {
11 | tsconfig: '/tsconfig.spec.json',
12 | },
13 | ],
14 | },
15 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
16 | coverageDirectory: '../../coverage/libs/validator',
17 | };
18 |
--------------------------------------------------------------------------------
/validator/licenses/LICENSE-BSD-3-CLAUSE.md:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) [year], [fullname]
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | 2. Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | 3. Neither the name of the copyright holder nor the names of its
16 | contributors may be used to endorse or promote products derived from
17 | this software without specific prior written permission.
18 |
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/validator/npmrc.docker:
--------------------------------------------------------------------------------
1 | //npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}
2 | @steem-monsters:registry=https://npm.pkg.github.com/
--------------------------------------------------------------------------------
/validator/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@steem-monsters/splinterlands-validator",
3 | "version": "0.1.0-prerelease45",
4 | "description": "The main validator block processor for Splintershards (SPS).",
5 | "author": "yabapmatt",
6 | "license": "MIT",
7 | "repository": {
8 | "type": "git",
9 | "url": "git://github.com/steem-monsters/splinterlands-validator.git"
10 | },
11 | "files": [
12 | "licenses",
13 | "src"
14 | ],
15 | "publishConfig": {
16 | "registry": "https://npm.pkg.github.com"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/validator/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "validator-lib",
3 | "$schema": "../node_modules/nx/schemas/project-schema.json",
4 | "projectType": "library",
5 | "targets": {
6 | "build": {
7 | "executor": "@nx/esbuild:esbuild",
8 | "outputs": ["{options.outputPath}"],
9 | "options": {
10 | "outputPath": "dist/libs/validator",
11 | "tsConfig": "validator/tsconfig.lib.json",
12 | "platform": "node",
13 | "format": ["cjs"],
14 | "main": "validator/src/lib.ts",
15 | "assets": ["validator/licenses/*"]
16 | }
17 | },
18 | "lint": {
19 | "executor": "@nx/eslint:lint",
20 | "outputs": ["{options.outputFile}"]
21 | },
22 | "test": {
23 | "executor": "@nx/jest:jest",
24 | "outputs": ["{workspaceRoot}/coverage/validator"],
25 | "options": {
26 | "jestConfig": "validator/jest.config.js"
27 | }
28 | }
29 | },
30 | "tags": []
31 | }
32 |
--------------------------------------------------------------------------------
/validator/src/actions/ActionConstructor.ts:
--------------------------------------------------------------------------------
1 | import { OperationData } from '../entities/operation';
2 | import { IAction } from './action';
3 | import { DeepImmutable } from '@steem-monsters/atom';
4 | import { ValidatorConfig } from '../config';
5 | import { PrecomputedMultiRouter, PrecomputedRouter } from '../libs/routing/precompute';
6 | import { IRouter, Route as GenericRoute } from '../libs/routing';
7 |
8 | export interface ActionFactory {
9 | build(op: OperationData, data: unknown, index?: number): T;
10 | }
11 |
12 | export function asActionFactory(fn: (op: OperationData, data: unknown, index?: number) => T) {
13 | return >{
14 | build: fn,
15 | };
16 | }
17 |
18 | export type Compute = DeepImmutable | undefined;
19 | export const Route = class extends GenericRoute> {};
20 | export type Route = GenericRoute>;
21 | export type RouterType = IRouter, Compute>;
22 | export class ActionRouter extends PrecomputedRouter, Compute> {}
23 | export class MultiActionRouter extends PrecomputedMultiRouter, Compute> {}
24 |
25 | export type TopRouter = IRouter, Compute>;
26 |
--------------------------------------------------------------------------------
/validator/src/actions/admin_action.ts:
--------------------------------------------------------------------------------
1 | import Action from './action';
2 | import { ErrorType, ValidationError } from '../entities/errors';
3 | import { AnyObjectSchema } from 'yup';
4 | import { OperationData } from '../entities/operation';
5 | import { Schema } from './schema';
6 | import { Trx } from '../db/tables';
7 | import { AdminMembership } from '../libs/acl/admin';
8 |
9 | // Base class for actions that can only be submitted by the game owner (Splinterlands)
10 | export default abstract class AdminAction extends Action {
11 | readonly #adminMembership: AdminMembership;
12 | protected constructor(adminMembership: AdminMembership, schema: Schema, op: OperationData, data: unknown, index?: number) {
13 | super(schema, op, data, index);
14 | this.#adminMembership = adminMembership;
15 | }
16 |
17 | async validate(_trx?: Trx) {
18 | // Check that this.op.account is in the list of admin accounts in config
19 | const isAdmin = await this.#adminMembership.isAdmin(this.op.account);
20 | if (!isAdmin) {
21 | throw new ValidationError(`Only an administrator account may perform this action.`, this, ErrorType.AdminOnly);
22 | }
23 |
24 | return true;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/validator/src/actions/authority.ts:
--------------------------------------------------------------------------------
1 | export enum AuthorityTypes {
2 | DELEGATION = 'delegation',
3 | }
4 |
--------------------------------------------------------------------------------
/validator/src/actions/index.ts:
--------------------------------------------------------------------------------
1 | import { TopRouter } from './ActionConstructor';
2 |
3 | // Extension point for DI
4 | export type TopActionRouter = TopRouter;
5 | export const TopActionRouter: unique symbol = Symbol.for('TopActionRouter');
6 |
7 | // Extension point for DI
8 | export type VirtualActionRouter = TopRouter;
9 | export const VirtualActionRouter: unique symbol = Symbol.for('VirtualActionRouter');
10 |
11 | export { default as AdminAction } from './admin_action';
12 | export * from './test_action';
13 | export { default as Action } from './action';
14 | export type { IAction } from './action';
15 |
--------------------------------------------------------------------------------
/validator/src/actions/test_action.ts:
--------------------------------------------------------------------------------
1 | import Action from './action';
2 | import { ErrorType, ValidationError } from '../entities/errors';
3 | import { EventLog, EventTypes } from '../entities/event_log';
4 | import { test } from './schema';
5 | import { OperationData } from '../entities/operation';
6 | import { Trx } from '../db/tables';
7 |
8 | export class TestAction extends Action {
9 | constructor(op: OperationData, data: unknown, index?: number) {
10 | super(test, op, data, index);
11 | }
12 |
13 | async validate(_trx?: Trx) {
14 | if (!this.params.type) throw new ValidationError('Invalid or missing type.', this, ErrorType.TestError);
15 | return true;
16 | }
17 |
18 | protected async process(_trx?: Trx): Promise {
19 | const event_log = new EventLog(EventTypes.UPDATE, { table: 'test' }, this.params.type);
20 | return [event_log];
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/validator/src/actions/transition.ts:
--------------------------------------------------------------------------------
1 | import { RouterType } from './ActionConstructor';
2 | import { ValidatorWatch } from '../config';
3 | import { TopActionRouter, VirtualActionRouter } from './index';
4 |
5 | type ConstLookup = T[keyof T];
6 |
7 | export class LookupWrapper {
8 | // TODO: Ideally the atom library supports (unique) symbols for registering change handlers.
9 | private static ChangeKeys = {
10 | normal: Symbol('router-resetter'),
11 | virtual: Symbol('virtual-router-resetter'),
12 | } as const;
13 |
14 | static computeAndWatch(router: RouterType, watcher: ValidatorWatch, key: ConstLookup | T) {
15 | router.recompute(watcher.validator);
16 | watcher.removeValidatorWatcher(key);
17 | watcher.addValidatorWatcher(key, (validator) => {
18 | router.recompute(validator);
19 | });
20 | }
21 |
22 | constructor(private readonly router: TopActionRouter, private readonly virtualRouter: VirtualActionRouter, watcher: ValidatorWatch) {
23 | // Passing never ensures we only use the const properties from ChangeKeys internally
24 | LookupWrapper.computeAndWatch(router, watcher, LookupWrapper.ChangeKeys.normal);
25 | LookupWrapper.computeAndWatch(virtualRouter, watcher, LookupWrapper.ChangeKeys.virtual);
26 | }
27 |
28 | public lookupOpsConstructor(block_num: number, action_name: string, isVirtual: boolean) {
29 | const router = isVirtual ? this.virtualRouter : this.router;
30 | return router.route(block_num, action_name);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/validator/src/actions/virtual.ts:
--------------------------------------------------------------------------------
1 | import { BlockRef } from '../entities/block';
2 | import { Trx } from '../db/tables';
3 |
4 | export type ProcessResult = [
5 | 'custom_json',
6 | {
7 | required_auths: string[];
8 | required_posting_auths: string[];
9 | id: string;
10 | json: any;
11 | },
12 | ];
13 |
14 | export interface VirtualPayloadSource {
15 | process(block: BlockRef, trx?: Trx): Promise;
16 | trx_id(block: BlockRef): string;
17 | }
18 | type WrappedPayload = { trx_id: string; payloads: ProcessResult[] };
19 | export const TopLevelVirtualPayloadSource: unique symbol = Symbol('TopLevelVirtualPayloadSource');
20 | export interface TopLevelVirtualPayloadSource {
21 | process(block: BlockRef, trx?: Trx): Promise;
22 | }
23 |
24 | //const trx_id = `sl_${this.tokenUnstakingRepository.table}_${block.block_num}`;
25 | export class BasePayloadSourceWrapper implements TopLevelVirtualPayloadSource {
26 | private readonly sources: VirtualPayloadSource[];
27 | constructor(...sources: VirtualPayloadSource[]) {
28 | this.sources = sources;
29 | }
30 |
31 | async process(block: BlockRef, trx?: Trx) {
32 | const results: Array = [];
33 | for (const source of this.sources) {
34 | results.push({ trx_id: source.trx_id(block), payloads: await source.process(block, trx) });
35 | }
36 | return results;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/validator/src/api/activator.ts:
--------------------------------------------------------------------------------
1 | import { Express } from 'express';
2 |
3 | export interface ConditionalApiActivator {
4 | perhapsEnableApi(): Express | void;
5 | }
6 | export const ConditionalApiActivator: unique symbol = Symbol('ConditionalApiActivator');
7 |
8 | export type ApiOptions = {
9 | api_port: number | null;
10 | db_block_retention: number | null;
11 | health_checker: boolean;
12 | helmetjs: boolean;
13 | version: string;
14 | };
15 |
16 | export const ApiOptions: unique symbol = Symbol('ApiOptions');
17 |
18 | export class DisabledApiActivator implements ConditionalApiActivator {
19 | perhapsEnableApi(): void {
20 | throw new Error(`Attempting to enable API while it is supposed to be disabled`);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/validator/src/api/health.ts:
--------------------------------------------------------------------------------
1 | import type { Router } from 'express';
2 | import { KnexToken } from '../db/tables';
3 | import { Knex } from 'knex';
4 |
5 | export function enableHealthChecker(app: Router) {
6 | app.get('/health-check/liveness', (_, res) => {
7 | res.status(200).send({ status: true });
8 | });
9 | app.get('/health-check/readiness', async (req, res) => {
10 | const knex = req.resolver.resolve(KnexToken);
11 | let correct: boolean;
12 | try {
13 | await knex.raw('SELECT 1');
14 | correct = true;
15 | } catch (_) {
16 | correct = false;
17 | }
18 |
19 | const msg = {
20 | name: 'SPS Validator Node',
21 | status: correct,
22 | date: new Date(),
23 | };
24 | const code = correct ? 200 : 503;
25 | res.status(code).send(msg);
26 | });
27 | }
28 |
--------------------------------------------------------------------------------
/validator/src/api/middleware.ts:
--------------------------------------------------------------------------------
1 | import { Request, Response } from 'express-serve-static-core';
2 | import { NextFunction } from 'express';
3 | import { Resolver } from '../utilities/dependency-injection';
4 |
5 | export const Middleware: unique symbol = Symbol.for('Middleware');
6 | export interface Middleware {
7 | attachResolver(c: Resolver): (req: Request, res: Response, next: NextFunction) => Promise;
8 | }
9 |
10 | /**
11 | * State is the current singleton cache(s) and their contents.
12 | * Requests will see concurrent updates, and (in case of writes) share those between requests as well.
13 | */
14 | export class SimpleMiddleware implements Middleware {
15 | attachResolver(c: Resolver) {
16 | return async (req: Request, res: Response, next: NextFunction) => {
17 | req.resolver = c;
18 | next();
19 | };
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/validator/src/config/staking.ts:
--------------------------------------------------------------------------------
1 | import { systemAccount } from '../actions/schema';
2 | import { InferType, object } from 'yup';
3 |
4 | export type StakingConfiguration = {
5 | staking_account: string; // System account
6 | };
7 |
8 | export const StakingConfiguration: unique symbol = Symbol('StakingConfiguration');
9 |
10 | const staking_configuration_schema = object({
11 | staking_account: systemAccount.required(),
12 | });
13 |
14 | type type_check = never;
15 | type _staking_configuration_check = type_check>;
16 |
17 | export const StakingConfigurationHelpers = {
18 | validate: (stakingConfiguration: StakingConfiguration) => staking_configuration_schema.isValidSync(stakingConfiguration),
19 | };
20 |
--------------------------------------------------------------------------------
/validator/src/config/updater.ts:
--------------------------------------------------------------------------------
1 | import { Trx } from '../db/tables';
2 | import { EventLog } from '../entities/event_log';
3 |
4 | export interface PoolUpdater {
5 | updateLastRewardBlock(pool_name: T, block_num: number, trx?: Trx): Promise;
6 | updateAccTokensPerShare(pool_name: T, tokens_per_share: number, trx?: Trx): Promise;
7 | updateStopBlock(pool_name: T, stop_block: number, trx?: Trx): Promise;
8 | }
9 | export const PoolUpdater: unique symbol = Symbol('PoolUpdater');
10 |
--------------------------------------------------------------------------------
/validator/src/db/Block.ts:
--------------------------------------------------------------------------------
1 | export type Block = {
2 | block_num: number;
3 | block_id: string;
4 | prev_block_id: string;
5 | l2_block_id: string;
6 | block_time: Date;
7 | validator: string | null;
8 | validation_tx: string | null;
9 | };
10 |
--------------------------------------------------------------------------------
/validator/src/db/columns.ts:
--------------------------------------------------------------------------------
1 | import { ICustomDatabaseType } from '@wwwouter/typed-knex';
2 |
3 | export class JSON implements ICustomDatabaseType {}
4 | export class JSONB implements ICustomDatabaseType {}
5 | /** internal id columns that shouldn't be exposed outside of the database */
6 | export type SerialIntKey = ICustomDatabaseType & number;
7 | export type SerialBigIntKey = ICustomDatabaseType & bigint;
8 | export type SerialKeys = SerialIntKey | SerialBigIntKey;
9 |
--------------------------------------------------------------------------------
/validator/src/db/config/bigint-counts.d.ts:
--------------------------------------------------------------------------------
1 | import * as _typedKnex from '@wwwouter/typed-knex';
2 |
3 | declare module '@wwwouter/typed-knex' {
4 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
5 | interface ITypedQueryBuilder {
6 | getCount(): Promise;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/validator/src/db/config/mapping.ts:
--------------------------------------------------------------------------------
1 | import pg from 'pg';
2 |
3 | class _ extends null {
4 | static {
5 | // TODO: Convert to const enum instead of magic constant when @types/pg ever gets added.
6 | // Convert bigserial + bigint (both with typeId = 20) to BigInt:
7 | pg.types.setTypeParser(20, BigInt);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/validator/src/entities/claims.ts:
--------------------------------------------------------------------------------
1 | import { BlockRef } from './block';
2 | import { Trx } from '../db/tables';
3 | import { ProcessResult, VirtualPayloadSource } from '../actions/virtual';
4 | import { PrefixOpts } from './operation';
5 | import { PoolManager } from '../utilities/pool_manager';
6 |
7 | export class PoolClaimPayloads implements VirtualPayloadSource {
8 | private static pool_payload_account = '$MINTING';
9 |
10 | constructor(private readonly cfg: PrefixOpts, private readonly poolManager: PoolManager) {}
11 | async process(block: BlockRef, _?: Trx): Promise {
12 | if (!this.poolManager.anyPools()) {
13 | return [];
14 | }
15 | const now = block.block_time;
16 | return [
17 | [
18 | 'custom_json',
19 | {
20 | required_auths: [PoolClaimPayloads.pool_payload_account],
21 | required_posting_auths: [],
22 | id: this.cfg.custom_json_id,
23 | json: {
24 | action: 'claim_pool',
25 | params: { now },
26 | },
27 | },
28 | ],
29 | ];
30 | }
31 |
32 | trx_id(block: BlockRef): string {
33 | return `claim_${block.block_num}@${block.block_time.getTime()}`;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/validator/src/entities/event_log.ts:
--------------------------------------------------------------------------------
1 | import { getTableName } from '@wwwouter/typed-knex';
2 | import { SerialKeys } from '../db/columns';
3 |
4 | declare type constructor = {
5 | new (...args: any[]): T;
6 | };
7 |
8 | type TableDescriptor = { table: string } | constructor;
9 |
10 | type ReturnNonSerialKeysOnly = {
11 | [K in keyof T]: T[K] extends SerialKeys ? never : K;
12 | }[keyof T];
13 | type RemoveSerialKeys = T extends string | number
14 | ? T
15 | : {
16 | [K in ReturnNonSerialKeysOnly]: T[K];
17 | };
18 |
19 | export class EventLog {
20 | public readonly object_type: string;
21 |
22 | constructor(public readonly event_type: EventTypes, object_type: TableDescriptor, public readonly data: D) {
23 | if (typeof object_type === 'function') {
24 | this.object_type = getTableName(object_type);
25 | } else {
26 | this.object_type = object_type.table;
27 | }
28 | }
29 | }
30 |
31 | export enum EventTypes {
32 | UPDATE = 'update',
33 | UPSERT = 'upsert',
34 | INSERT = 'insert',
35 | DELETE = 'delete',
36 | }
37 |
--------------------------------------------------------------------------------
/validator/src/entities/tokens/token_transfer.ts:
--------------------------------------------------------------------------------
1 | export default class TokenTransfer {
2 | public amount: number;
3 | public from_start_balance: number;
4 | public from_end_balance: number;
5 | public to_start_balance: number;
6 | public to_end_balance: number;
7 |
8 | constructor(
9 | public from: string,
10 | public to: string,
11 | amount: string | number,
12 | public token: string,
13 | from_balance: string | number,
14 | to_balance: string | number,
15 | public type: string | null,
16 | ) {
17 | this.amount = parseFloat(amount as string);
18 | this.from_start_balance = parseFloat(from_balance as string);
19 | this.from_end_balance = parseFloat(from_balance as string) + this.amount * -1;
20 | this.to_start_balance = parseFloat(to_balance as string);
21 | this.to_end_balance = parseFloat(to_balance as string) + this.amount;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/validator/src/entities/validator/types.ts:
--------------------------------------------------------------------------------
1 | import { IAction } from '../../actions/action';
2 | import { Trx } from '../../db/tables';
3 |
4 | export type ValidatorVoteEntry = {
5 | voter: string;
6 | validator: string;
7 | vote_weight: number;
8 | };
9 |
10 | export type VotingAction = IAction & { params: { account_name: string } };
11 |
12 | export interface VoteWeightCalculator {
13 | calculateVoteWeight(account: string, trx?: Trx): Promise;
14 | }
15 | export const VoteWeightCalculator: unique symbol = Symbol('VoteWeightCalculator');
16 |
--------------------------------------------------------------------------------
/validator/src/entities/validator/validator_vote_history.ts:
--------------------------------------------------------------------------------
1 | import { EventLog, EventTypes } from '../event_log';
2 | import { IAction } from '../../actions/action';
3 | import { BaseRepository, ValidatorVoteHistoryEntity, Trx, Handle } from '../../db/tables';
4 |
5 | export class ValidatorVoteHistoryRepository extends BaseRepository {
6 | constructor(handle: Handle) {
7 | super(handle);
8 | }
9 | async insert(action: IAction, is_approval: boolean, vote_weight: number, trx?: Trx): Promise {
10 | // TODO: fix this
11 | const validator = action.params?.account_name as string;
12 | const record = await this.query(ValidatorVoteHistoryEntity, trx).insertItemWithReturning({
13 | transaction_id: action.op.trx_op_id,
14 | created_date: action.op.block_time,
15 | voter: action.op.account,
16 | validator,
17 | is_approval,
18 | vote_weight: String(vote_weight),
19 | });
20 |
21 | return new EventLog(EventTypes.INSERT, ValidatorVoteHistoryEntity, record);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/validator/src/features/delegation/index.ts:
--------------------------------------------------------------------------------
1 | export * from './delegation_manager';
2 | export * from './delegation_promises';
3 |
--------------------------------------------------------------------------------
/validator/src/features/promises/index.ts:
--------------------------------------------------------------------------------
1 | export * from './promise_handler';
2 | export * from './promise_manager';
3 |
--------------------------------------------------------------------------------
/validator/src/features/tokens/balance_manager.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheSPSDAO/SPS-Validator/8007f3fe78dc19a9cacc6101c23e14d8790d621b/validator/src/features/tokens/balance_manager.ts
--------------------------------------------------------------------------------
/validator/src/features/tokens/staking_manager.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheSPSDAO/SPS-Validator/8007f3fe78dc19a9cacc6101c23e14d8790d621b/validator/src/features/tokens/staking_manager.ts
--------------------------------------------------------------------------------
/validator/src/libs/acl/admin.ts:
--------------------------------------------------------------------------------
1 | export interface AdminMembership {
2 | isAdmin(account: string): Promise;
3 | }
4 |
5 | export const AdminMembership: unique symbol = Symbol('AdminMembership');
6 |
7 | export const AdminMembershipHelpers = {
8 | fromConst: (account: string, ...accounts: string[]) => {
9 | const allAccounts = [account, ...accounts];
10 | return {
11 | async isAdmin(account: string): Promise {
12 | return allAccounts.includes(account);
13 | },
14 | };
15 | },
16 | fromFn: (fn: (account: string) => Promise) => {
17 | return {
18 | async isAdmin(account: string): Promise {
19 | return fn(account);
20 | },
21 | };
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/validator/src/libs/guards.ts:
--------------------------------------------------------------------------------
1 | export function isDefined(value: T | undefined): value is T {
2 | return value !== undefined;
3 | }
4 |
--------------------------------------------------------------------------------
/validator/src/libs/head-block-observer.ts:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from 'events';
2 | import { TypedEventEmitter } from './async-queue';
3 | import * as utils from '../utils';
4 | import { LogLevel } from '../utils';
5 |
6 | interface HeadBlockObserverEvents {
7 | updated: number;
8 | }
9 |
10 | export class HeadBlockObserver extends EventEmitter implements TypedEventEmitter {
11 | public set headBlockNum(value: number) {
12 | // I guess this could happen if the head streaming swapped to another node?
13 | if (value < this._headBlockNum) {
14 | utils.log(`Attempted to go back in time, setting headBlockNum from ${this._headBlockNum} to ${value}`, LogLevel.Error);
15 | return;
16 | } else if (value > this._headBlockNum) {
17 | this._headBlockNum = value;
18 | this.emit('updated', value);
19 | }
20 | // Ignore duplicate block numbers
21 | }
22 |
23 | public get headBlockNum(): number {
24 | return this._headBlockNum;
25 | }
26 |
27 | public constructor(private _headBlockNum: number) {
28 | super();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/validator/src/libs/plugin.test.ts:
--------------------------------------------------------------------------------
1 | import { Plugin, PluginDispatcherBuilder } from './plugin';
2 |
3 | describe('plugin', () => {
4 | let mockPlugin: Plugin;
5 |
6 | beforeAll(() => {
7 | mockPlugin = {
8 | name: 'Test Plugin',
9 | onBlockProcessed: jest.fn().mockResolvedValue(undefined),
10 | };
11 | });
12 |
13 | beforeEach(() => {
14 | (mockPlugin.onBlockProcessed as jest.Mock).mockClear();
15 | });
16 |
17 | test('Plugin gets data dispatched', () => {
18 | const dispatcher = PluginDispatcherBuilder.create().addPlugin(mockPlugin).build();
19 |
20 | dispatcher.dispatch(1, [], '', 1);
21 |
22 | expect(mockPlugin.onBlockProcessed).toBeCalledTimes(1);
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/validator/src/libs/rng-iterator.ts:
--------------------------------------------------------------------------------
1 | import type { PRNG } from 'seedrandom';
2 |
3 | export function* make_probability_iterator(probability: number, rng: PRNG): Generator {
4 | while (true) {
5 | const bool = probability > rng();
6 | yield bool;
7 | }
8 | }
9 |
10 | type Reserve = {
11 | true: number;
12 | false: number;
13 | };
14 |
15 | function constrain(v: number) {
16 | return Number.isSafeInteger(v) && v >= 0;
17 | }
18 |
19 | export function* make_dynamic_pool_iterator(reserves: Reserve, rng: PRNG): Generator {
20 | let true_left = reserves.true;
21 | let false_left = reserves.false;
22 |
23 | console.assert(constrain(true_left) && constrain(false_left), 'Either of the reserves is not a safe integer >= 0');
24 |
25 | while (true_left > 0 || false_left > 0) {
26 | const true_probability = true_left / (true_left + false_left);
27 |
28 | console.assert(true_probability >= 0, `Probability below 0: ${true_probability}`);
29 | console.assert(true_probability <= 1, `Probability above 1: ${true_probability}`);
30 |
31 | if (true_probability > rng()) {
32 | true_left -= 1;
33 | yield true;
34 | } else {
35 | false_left -= 1;
36 | yield false;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/validator/src/plugins/SimpleLogPlugin.ts:
--------------------------------------------------------------------------------
1 | import { Plugin } from '../libs/plugin';
2 | import { EventLog } from '../entities/event_log';
3 | import * as utils from '../utils';
4 | import { LogLevel } from '../utils';
5 |
6 | /**
7 | * Example plugin that logs whatever happens with the specified (or default) LogLevel.
8 | */
9 | export class SimpleLogPlugin implements Plugin {
10 | readonly name: string = 'SimpleLogPlugin';
11 |
12 | public constructor(private readonly logLevel: LogLevel = LogLevel.Info) {}
13 |
14 | async onBlockProcessed(blockNumber: number, eventLogs: EventLog[]): Promise {
15 | eventLogs.forEach((log) => {
16 | utils.log(`[${this.name}] Performed a ${log.event_type.toUpperCase()} in ${log.object_type.toUpperCase()} in block number ${blockNumber}`, this.logLevel);
17 | });
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/validator/src/repositories/transactions/Transaction.ts:
--------------------------------------------------------------------------------
1 | export type TokenTransfer = OkTokenTransfer | ErrTokenTransfer;
2 |
3 | export type Transaction = {
4 | readonly id: string;
5 | readonly block_id: string;
6 | readonly prev_block_id: string;
7 | readonly type: string;
8 | readonly player: string;
9 | readonly data: any;
10 | readonly success: boolean;
11 | readonly error: any | null;
12 | readonly block_num: number;
13 | readonly created_date: Date | null;
14 | readonly result: any | null;
15 | };
16 |
17 | type BaseTokenTransfer = {
18 | readonly id: string;
19 | readonly from: string;
20 | readonly to: string;
21 | readonly qty: number;
22 | readonly token: string;
23 | readonly memo: string;
24 | };
25 |
26 | export type OkTokenTransfer = BaseTokenTransfer & {
27 | // Discriminator
28 | readonly success: true;
29 | };
30 |
31 | export type ErrTokenTransfer = BaseTokenTransfer & {
32 | // Discriminator
33 | readonly success: false;
34 | readonly error: {
35 | readonly message: string;
36 | readonly code: number;
37 | };
38 | };
39 |
--------------------------------------------------------------------------------
/validator/src/sync/index.ts:
--------------------------------------------------------------------------------
1 | import { SynchronisationConfig, SynchronisationPoint } from './type';
2 |
3 | // This class collects all points
4 | export class CollectedSynchronisationPoint {
5 | private readonly points: SynchronisationPoint[];
6 |
7 | constructor(...points: SynchronisationPoint[]) {
8 | this.points = points;
9 | }
10 |
11 | async waitToProcessBlock(block_num: number, ...args: T): Promise {
12 | for (const point of this.points) {
13 | await point.waitToProcessBlock(block_num, ...args);
14 | }
15 | }
16 | }
17 |
18 | // This class closes over all the points
19 | export abstract class SynchronisationClosure {
20 | constructor(private readonly pointWrapper: CollectedSynchronisationPoint) {}
21 |
22 | protected abstract getConfig(): T;
23 |
24 | async waitToProcessBlock(block_num: number): Promise {
25 | const cfg = this.getConfig();
26 | await this.pointWrapper.waitToProcessBlock(block_num, ...cfg);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/validator/src/sync/type.ts:
--------------------------------------------------------------------------------
1 | export type SynchronisationConfig = unknown[]; // unknown tuple
2 | export interface SynchronisationPoint {
3 | waitToProcessBlock(block_num: number, ...args: T): Promise;
4 | }
5 |
--------------------------------------------------------------------------------
/validator/src/utilities/accounts.ts:
--------------------------------------------------------------------------------
1 | import { validateAccountName } from 'splinterlands-dhive-sl';
2 |
3 | export function isSystemAccount(value: string): boolean {
4 | return /^\$[A-Z_]*$/.test(value);
5 | }
6 |
7 | export function isHiveAccount(value: string): boolean {
8 | return validateAccountName(value).status === 'success';
9 | }
10 |
11 | export function isLiteAccount(value: string): boolean {
12 | return value.includes('_') && isHiveAccount(value.replace('_', ''));
13 | }
14 |
--------------------------------------------------------------------------------
/validator/src/utilities/action_type.ts:
--------------------------------------------------------------------------------
1 | export type ExtractParamsType = A extends { readonly params: infer T } ? T : never;
2 |
--------------------------------------------------------------------------------
/validator/src/utilities/block_num.test.ts:
--------------------------------------------------------------------------------
1 | import { coerceToBlockNum } from './block_num';
2 |
3 | describe('Utility functions', () => {
4 | it.each`
5 | value | n
6 | ${0} | ${null}
7 | ${7} | ${7}
8 | ${11.8} | ${11}
9 | ${0x80} | ${128}
10 | ${-3.14} | ${null}
11 | ${'49'} | ${49}
12 | ${'0x80'} | ${null}
13 | ${'-780'} | ${null}
14 | ${'9.9'} | ${9}
15 | ${'HEAD'} | ${null}
16 | ${'apricot'} | ${null}
17 | ${Infinity} | ${null}
18 | ${-Infinity} | ${null}
19 | ${NaN} | ${null}
20 | ${null} | ${null}
21 | ${undefined} | ${null}
22 | `(`Coercing [$value] to block number gives [$n]`, ({ value, n }) => {
23 | expect(coerceToBlockNum(value)).toBe(n);
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/validator/src/utilities/block_num.ts:
--------------------------------------------------------------------------------
1 | export function coerceToBlockNum(v: number | string | null | undefined): number | null {
2 | if (v === null || v === undefined) {
3 | return null;
4 | }
5 |
6 | let n: number;
7 | if (typeof v === 'string') {
8 | n = parseInt(v, 10);
9 | } else {
10 | n = Math.floor(v);
11 | }
12 |
13 | if (!isFinite(n) || n <= 0) {
14 | return null;
15 | }
16 |
17 | // `n` is a positive integer
18 | return n;
19 | }
20 |
--------------------------------------------------------------------------------
/validator/src/utilities/config.test.ts:
--------------------------------------------------------------------------------
1 | import { ConfigData, ConfigRepository } from './config';
2 |
3 | describe('Unparse tests', () => {
4 | test.each([
5 | [7, '7'],
6 | [new Date('2022-05-10T12:33:56.483Z'), '2022-05-10T12:33:56.483Z'],
7 | [{ mykey: 'myvalue' }, '{"mykey":"myvalue"}'],
8 | [[1, 2, 3], '[1,2,3]'],
9 | ['random-string', 'random-string'],
10 | [true, 'true'],
11 | [false, 'false'],
12 | ])('unparse_value(%s)', (value: ConfigData, expected: string) => {
13 | expect(ConfigRepository.unparse_value(value)).toBe(expected);
14 | });
15 | });
16 |
17 | describe('parse tests', () => {
18 | test.each([
19 | [7, '7', 'number'],
20 | [new Date('2022-05-10T12:33:56.483Z'), '2022-05-10T12:33:56.483Z', 'date'],
21 | [{ mykey: 'myvalue' }, '{"mykey":"myvalue"}', 'object'],
22 | [[1, 2, 3], '[1,2,3]', 'array'],
23 | ['random-string', 'random-string', 'string'],
24 | [true, 'true', 'boolean'],
25 | [false, 'false', 'boolean'],
26 | ])('%s from parse_value("%s", "%s")', (expected: ConfigData, value: string, type: string) => {
27 | expect(ConfigRepository.parse_value(value, type)).toStrictEqual(expected);
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/validator/src/utilities/date.ts:
--------------------------------------------------------------------------------
1 | export function getTotalMonths(start: Date, current: Date) {
2 | let months = (current.getFullYear() - start.getFullYear()) * 12;
3 | months += current.getMonth() - start.getMonth();
4 | return months < 0 ? 0 : months;
5 | }
6 |
7 | const DAY_IN_MILLI = 86400000;
8 |
9 | export function daysSinceEpoch(date: Date) {
10 | return Math.floor(date.getTime() / DAY_IN_MILLI);
11 | }
12 |
--------------------------------------------------------------------------------
/validator/src/utilities/dependency-injection.ts:
--------------------------------------------------------------------------------
1 | declare type constructor = {
2 | new (...args: any[]): T;
3 | };
4 |
5 | export type InjectionToken = constructor | string | symbol;
6 |
7 | export interface Resolver {
8 | resolve(token: InjectionToken): T;
9 | }
10 | export const Resolver: unique symbol = Symbol('Resolver');
11 |
12 | export interface Container {
13 | // Can't constrain provider without coupling to tsyringe
14 | register(token: InjectionToken, provider: unknown): T;
15 | }
16 | export const Container: unique symbol = Symbol('Container');
17 |
--------------------------------------------------------------------------------
/validator/src/utilities/derive.test.ts:
--------------------------------------------------------------------------------
1 | import { Atom, set, swap } from '@steem-monsters/atom';
2 | import { Quark } from './derive';
3 |
4 | test('Quark sees parent atom changes', () => {
5 | const atom = Atom.of({ count: 0 });
6 | const quark = new Quark(atom, 'count');
7 | expect(quark.deref()).toBe(0);
8 | set(atom, { count: 33 });
9 | expect(quark.deref()).toBe(33);
10 | });
11 |
12 | test('Quark can have own handler for updates', () => {
13 | const atom = Atom.of({ count: 0 });
14 | const quark = new Quark(atom, 'count');
15 | const mock = { log: (..._args: any[]) => null };
16 | const logSpy = jest.spyOn(mock, 'log');
17 | expect(quark.deref()).toBe(0);
18 | quark.addWatch('hello', (p, n) => {
19 | mock.log(p, n);
20 | });
21 | swap(atom, (s) => ({ count: s.count + 1 }));
22 | expect(quark.deref()).toBe(1);
23 | expect(logSpy).toHaveBeenCalledTimes(1);
24 | });
25 |
26 | test('Quark handler is ignored for other updates', () => {
27 | const atom = Atom.of({ count: 0, down: 99 });
28 | const quark = new Quark(atom, 'count');
29 | const mock = { log: (..._args: any[]) => null };
30 | const logSpy = jest.spyOn(mock, 'log');
31 | expect(quark.deref()).toBe(0);
32 | quark.addWatch('hello', (p, n) => {
33 | mock.log(p, n);
34 | });
35 | swap(atom, (s) => ({ ...s, down: s.down - 1 }));
36 | swap(atom, (s) => ({ ...s, down: s.down - 1 }));
37 | swap(atom, (s) => ({ ...s, count: s.count + 1 }));
38 | expect(quark.deref()).toBe(1);
39 | expect(logSpy).toHaveBeenCalledTimes(1);
40 | });
41 |
--------------------------------------------------------------------------------
/validator/src/utilities/entry-options.ts:
--------------------------------------------------------------------------------
1 | export const EntryOptions: unique symbol = Symbol('EntryOptions');
2 | export type EntryOptions = {
3 | block_processing: boolean;
4 | start_block: string | null;
5 | };
6 |
--------------------------------------------------------------------------------
/validator/src/utilities/express.d.ts:
--------------------------------------------------------------------------------
1 | import 'express';
2 | import { Resolver } from './dependency-injection';
3 |
4 | declare module 'express-serve-static-core' {
5 | interface Request {
6 | resolver: Resolver;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/validator/src/utilities/guards.ts:
--------------------------------------------------------------------------------
1 | export function isStringArray(value: unknown): value is Array {
2 | if (!Array.isArray(value)) {
3 | return false;
4 | }
5 | return value.every((v) => typeof v === 'string');
6 | }
7 |
--------------------------------------------------------------------------------
/validator/src/utilities/primer.ts:
--------------------------------------------------------------------------------
1 | import { Prime } from './traits';
2 | import { Trx } from '../db/tables';
3 |
4 | export class Primer implements Prime {
5 | private readonly primes: Prime[];
6 | constructor(...primes: Prime[]) {
7 | this.primes = primes;
8 | }
9 |
10 | async prime(trx?: Trx): Promise {
11 | for (const prime of this.primes) {
12 | await prime.prime(trx);
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/validator/src/utilities/stop.ts:
--------------------------------------------------------------------------------
1 | export interface IStop {
2 | stop(): void;
3 | readonly shouldStop: boolean;
4 | }
5 |
6 | export class Stop implements IStop {
7 | #isStopped = false;
8 | stop(): void {
9 | this.#isStopped = true;
10 | }
11 |
12 | get shouldStop() {
13 | return this.#isStopped;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/validator/src/utilities/traits.ts:
--------------------------------------------------------------------------------
1 | import { Trx } from '../db/tables';
2 |
3 | /**
4 | * A 'trait' that implies that something can create clones of itself;
5 | * These clones must have no state sharing, but may re-use injected attributes such as repositories.
6 | */
7 | export interface Cloneable> {
8 | clone(): T;
9 | }
10 |
11 | /**
12 | * A 'trait' that implies that something can be primed from persisted data in the database.
13 | */
14 | export interface Prime {
15 | prime(trx?: Trx): Promise;
16 | }
17 |
--------------------------------------------------------------------------------
/validator/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.base.json",
3 | "compilerOptions": {
4 | "target": "ES2022",
5 | "lib": ["ES2022"],
6 | "module": "commonjs",
7 | "experimentalDecorators": true,
8 | "emitDecoratorMetadata": true,
9 | "esModuleInterop": true,
10 | "resolveJsonModule": true,
11 | "sourceMap": true,
12 | "outDir": "dist",
13 | "noEmit": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "strict": true,
16 | "noImplicitAny": true,
17 | "strictNullChecks": true,
18 | "noImplicitReturns": true,
19 | "noFallthroughCasesInSwitch": true,
20 | "noImplicitOverride": true
21 | },
22 | "references": [
23 | {
24 | "path": "./tsconfig.lib.json"
25 | },
26 | {
27 | "path": "./tsconfig.spec.json"
28 | }
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/validator/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "isolatedModules": true,
5 | "noEmit": false,
6 | "declaration": true,
7 | "declarationMap": true
8 | },
9 | "include": ["src/**/*.ts"],
10 | "exclude": ["src/**/*.test.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/validator/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../dist/out-tsc",
5 | "module": "commonjs",
6 | "types": [
7 | "jest",
8 | "node"
9 | ]
10 | },
11 | "include": [
12 | "jest.config.ts",
13 | "jest.config.js",
14 | "**/*.test.ts",
15 | "**/*.spec.ts",
16 | "**/*.test.tsx",
17 | "**/*.spec.tsx",
18 | "**/*.test.js",
19 | "**/*.spec.js",
20 | "**/*.test.jsx",
21 | "**/*.spec.jsx",
22 | "**/*.d.ts"
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/vitest.workspace.ts:
--------------------------------------------------------------------------------
1 | export default ['**/*/vite.config.{ts,mts}', '**/*/vitest.config.{ts,mts}'];
2 |
--------------------------------------------------------------------------------