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