diff --git a/.eslintrc.js b/.eslintrc.js index b2df7577..b2a64a01 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,52 +1,52 @@ const namingConventions = [ { selector: 'default', - format: ['camelCase'] + format: ['camelCase'], }, { selector: 'variable', modifiers: ['const'], - format: ['camelCase', 'UPPER_CASE'] + format: ['camelCase', 'UPPER_CASE'], }, { selector: 'variable', modifiers: ['unused'], leadingUnderscore: 'require', - format: ['camelCase'] + format: ['camelCase'], }, { selector: 'variable', modifiers: ['unused', 'destructured'], leadingUnderscore: 'allow', - format: ['camelCase'] + format: ['camelCase'], }, { selector: 'property', - format: ['camelCase', 'UPPER_CASE'] + format: ['camelCase', 'UPPER_CASE'], }, { selector: 'parameter', modifiers: ['unused'], leadingUnderscore: 'require', - format: ['camelCase'] + format: ['camelCase'], }, { selector: 'typeLike', - format: ['PascalCase'] + format: ['PascalCase'], }, { selector: 'enumMember', - format: ['UPPER_CASE'] + format: ['UPPER_CASE'], }, { selector: 'objectLiteralProperty', modifiers: ['requiresQuotes'], - format: null + format: null, }, { selector: 'import', format: ['camelCase', 'PascalCase', 'snake_case', 'UPPER_CASE'], - } + }, ] module.exports = { @@ -54,24 +54,25 @@ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { ecmaVersion: 2021, - sourceType: 'module' + sourceType: 'module', }, env: { es6: true, - node: true + node: true, }, ignorePatterns: [ '.idea', 'build', 'data', 'node_modules', - 'package-lock.json' + 'package-lock.json', ], plugins: [ '@typescript-eslint', 'filenames', 'import', - 'promise' + 'promise', + 'prettier', ], extends: [ 'eslint:all', @@ -80,14 +81,13 @@ module.exports = { 'plugin:import/errors', 'plugin:import/warnings', 'plugin:import/typescript', - 'plugin:promise/recommended' + 'plugin:promise/recommended', + 'plugin:prettier/recommended', ], rules: { // http://eslint.org/docs/rules/ 'array-bracket-newline': ['error', 'consistent'], 'array-element-newline': 'off', - 'arrow-parens': ['error', 'as-needed', { requireForBlockBody: true }], - 'brace-style': ['error', 'stroustrup', { allowSingleLine: true }], 'callback-return': ['error', ['callback', 'cb', 'done']], 'class-methods-use-this': 'off', 'consistent-return': 'off', @@ -99,30 +99,28 @@ module.exports = { 'function-paren-newline': 'off', 'id-length': 'off', 'implicit-arrow-linebreak': 'off', - 'indent': ['error', 4, { - SwitchCase: 1, - // workaround until https://github.com/typescript-eslint/typescript-eslint/issues/1824 is fixed - ignoredNodes: [ - 'ClassBody.body > PropertyDefinition[decorators.length > 0] > .key' - ] - }], 'init-declarations': 'off', 'line-comment-position': 'off', - 'lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }], - 'max-len': ['error', { code: 120, ignoreStrings: true, ignoreTemplateLiterals: true }], + 'lines-between-class-members': [ + 'error', + 'always', + { exceptAfterSingleLine: true }, + ], + 'max-len': [ + 'error', + { code: 120, ignoreStrings: true, ignoreTemplateLiterals: true }, + ], 'max-lines': 'off', 'max-lines-per-function': 'off', - 'max-params': ['error', { max: 5 }], + 'max-params': ['error', { max: 15 }], 'max-statements': 'off', 'multiline-comment-style': 'off', 'multiline-ternary': ['error', 'always-multiline'], 'new-cap': ['error', { capIsNew: false }], 'newline-after-var': ['error', 'always'], - 'newline-per-chained-call': ['error', { ignoreChainWithDepth: 6 }], 'no-await-in-loop': 'error', 'no-confusing-arrow': 'off', 'no-console': 'off', - 'no-extra-parens': 'off', 'no-inline-comments': 'off', 'no-invalid-this': 'off', 'no-magic-numbers': 'off', @@ -134,7 +132,6 @@ module.exports = { 'no-prototype-builtins': 'error', 'no-shadow': 'off', 'no-sync': 'off', - 'no-template-curly-in-string': 'error', 'no-ternary': 'off', 'no-trailing-spaces': 'error', 'no-undefined': 'off', @@ -142,49 +139,62 @@ module.exports = { 'no-unused-vars': 'off', 'no-use-before-define': 'off', 'no-warning-comments': 'off', - 'object-curly-newline': ['error', { - ObjectExpression: { multiline: true, consistent: true }, - ObjectPattern: { multiline: true, consistent: true }, - ImportDeclaration: { multiline: true }, - ExportDeclaration: { multiline: true, consistent: true, minProperties: 3 } - }], - 'object-curly-spacing': ['error', 'always'], 'object-property-newline': 'off', 'one-var': ['error', { uninitialized: 'always', initialized: 'never' }], 'padded-blocks': ['error', 'never'], 'padding-line-between-statements': [ 'error', { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, - { blankLine: 'any', prev: ['const', 'let', 'var'], next: ['const', 'let', 'var'] }, - { blankLine: 'always', prev: '*', next: 'return' } + { + blankLine: 'any', + prev: ['const', 'let', 'var'], + next: ['const', 'let', 'var'], + }, + { blankLine: 'always', prev: '*', next: 'return' }, ], 'prefer-named-capture-group': 'off', 'require-atomic-updates': 'off', - 'quote-props': ['error', 'consistent-as-needed'], - 'quotes': ['error', 'single', { avoidEscape: true }], + quotes: ['error', 'single', { avoidEscape: true }], 'require-jsdoc': 'off', 'require-unicode-regexp': 'off', - 'semi': ['error', 'never'], + semi: ['error', 'never'], 'sort-imports': 'off', 'sort-keys': 'off', - 'strict': 'off', + strict: 'off', // https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#supported-rules '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'error', - '@typescript-eslint/member-delimiter-style': ['error', { multiline: { delimiter: 'none' } }], + '@typescript-eslint/explicit-module-boundary-types': 'warn', + '@typescript-eslint/member-delimiter-style': [ + 'error', + { multiline: { delimiter: 'none' } }, + ], '@typescript-eslint/naming-convention': ['error', ...namingConventions], - '@typescript-eslint/no-extra-parens': ['error', 'all', { returnAssign: false, nestedBinaryExpressions: false }], '@typescript-eslint/no-shadow': 'error', - '@typescript-eslint/no-unused-vars': ['error', { ignoreRestSiblings: true, varsIgnorePattern: '^_+$' }], + '@typescript-eslint/no-unused-vars': [ + 'warn', + { + args: 'all', + argsIgnorePattern: '^_', + caughtErrors: 'all', + caughtErrorsIgnorePattern: '^_', + destructuredArrayIgnorePattern: '^_', + varsIgnorePattern: '^_', + ignoreRestSiblings: true, + }, + ], '@typescript-eslint/no-use-before-define': 'error', - '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-explicit-any': 'off', // https://github.com/benmosher/eslint-plugin-import/tree/master/docs/rules 'import/default': 'error', 'import/export': 'error', 'import/exports-last': 'off', - 'import/extensions': ['error', 'never', { json: 'always', scss: 'always' }], + 'import/extensions': [ + 'error', + 'never', + { json: 'always', scss: 'always' }, + ], 'import/first': 'error', 'import/group-exports': 'off', 'import/max-dependencies': ['error', { max: 25 }], @@ -195,17 +205,17 @@ module.exports = { 'import/no-amd': 'error', 'import/no-anonymous-default-export': 'off', 'import/no-commonjs': 'error', - 'import/no-cycle': 'error', + 'import/no-cycle': 'warn', 'import/no-default-export': 'off', 'import/no-deprecated': 'error', 'import/no-duplicates': 'error', 'import/no-dynamic-require': 'error', - 'import/no-extraneous-dependencies': ['error', { - devDependencies: [ - '**/*.test.ts', - 'test/**/*.ts' - ] - }], + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: ['**/*.test.ts', 'test/**/*.ts'], + }, + ], 'import/no-internal-modules': 'off', 'import/no-mutable-exports': 'off', 'import/no-named-as-default': 'off', @@ -216,27 +226,41 @@ module.exports = { 'import/no-self-import': 'error', 'import/no-unassigned-import': 'off', 'import/no-unresolved': 'error', - 'import/no-restricted-paths': ['error', { - zones: [ - { target: './src', from: './e2e' } - ] - }], - 'import/order': ['error', { - 'pathGroups': ['.', '..', '../..', '../../..', '../../../..'].map(p => ({ - pattern: `${p}/sentry`, - group: 'internal', - position: 'before' - })), - 'groups': [['builtin', 'external'], ['internal', 'parent', 'type'], ['sibling', 'index']], - 'newlines-between': 'always' - }], + 'import/no-restricted-paths': [ + 'error', + { + zones: [{ target: './src', from: './e2e' }], + }, + ], + 'import/order': [ + 'error', + { + pathGroups: ['.', '..', '../..', '../../..', '../../../..'].map( + (p) => ({ + pattern: `${p}/sentry`, + group: 'internal', + position: 'before', + }), + ), + groups: [ + ['builtin', 'external'], + ['internal', 'parent', 'type'], + ['sibling', 'index'], + ], + 'newlines-between': 'always', + }, + ], 'import/no-unused-modules': 'error', 'import/no-useless-path-segments': ['error', { noUselessIndex: true }], 'import/prefer-default-export': 'off', 'import/unambiguous': 'error', // https://github.com/selaux/eslint-plugin-filenames#rules - 'filenames/match-regex': ['error', '^[a-z0-9-]+(\\.(d|test|e2e|fixture|schema|request|response))?$', true], + 'filenames/match-regex': [ + 'error', + '^[a-z0-9-]+(\\.(d|test|e2e|fixture|schema|request|response))?$', + true, + ], 'filenames/match-exported': ['error', 'kebab'], 'filenames/no-index': 'off', @@ -253,7 +277,7 @@ module.exports = { 'promise/no-return-wrap': 'error', 'promise/param-names': 'error', 'promise/prefer-await-to-callbacks': 'error', - 'promise/prefer-await-to-then': 'error' + 'promise/prefer-await-to-then': 'error', }, overrides: [ { @@ -262,50 +286,54 @@ module.exports = { 'filenames/match-regex': 'off', 'import/no-commonjs': 'off', 'import/unambiguous': 'off', - '@typescript-eslint/naming-convention': 'off' - } + '@typescript-eslint/naming-convention': 'off', + }, }, { files: ['*.config.ts'], rules: { - 'filenames/match-exported': 'off' - } + 'filenames/match-exported': 'off', + }, }, { files: ['*.d.ts'], rules: { - 'import/unambiguous': 'off' - } + 'import/unambiguous': 'off', + }, }, { files: ['src/db/entities/**/*.ts'], rules: { - 'import/no-cycle': 'off' - } + 'import/no-cycle': 'off', + }, }, { files: ['src/db/columns/**/*.ts'], rules: { - '@typescript-eslint/naming-convention': ['error', ...namingConventions, { - selector: 'variable', - modifiers: ['exported'], - format: ['PascalCase'] - }] - } + '@typescript-eslint/naming-convention': [ + 'error', + ...namingConventions, + { + selector: 'variable', + modifiers: ['exported'], + format: ['PascalCase'], + }, + ], + }, }, { files: ['src/index.ts', 'src/main/*/index.ts'], rules: { - 'promise/prefer-await-to-callbacks': 'off' - } + 'promise/prefer-await-to-callbacks': 'off', + }, }, { files: ['src/main/**/*.ts'], rules: { 'max-depth': ['error', { max: 6 }], 'no-continue': 'off', - 'no-await-in-loop': 'off' - } - } - ] + 'no-await-in-loop': 'off', + }, + }, + ], } diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 897f695a..051e08e3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,17 +1,17 @@ version: 2 updates: - - package-ecosystem: "github-actions" - directory: "/" + - package-ecosystem: 'github-actions' + directory: '/' schedule: - interval: "daily" - - package-ecosystem: "npm" - directory: "/" + interval: 'daily' + - package-ecosystem: 'npm' + directory: '/' schedule: - interval: "daily" + interval: 'daily' groups: sentry: patterns: - - "*sentry*" + - '*sentry*' eslint: patterns: - - "*eslint*" + - '*eslint*' diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 5a90b444..8f20bdd9 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -1,55 +1,53 @@ name: cicd on: - push: - branches: [ "*" ] - pull_request: - branches: [ "main" ] + push: + branches: ['*'] + pull_request: + branches: ['main'] jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/install + - run: pnpm run build - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/install - - run: pnpm run build + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/install + - run: pnpm run lint - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/install - - run: pnpm run lint - - release: - if: github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/install - - run: pnpm run build - - run: pnpm install --frozen-lockfile --ignore-scripts --prod - - uses: docker/metadata-action@v5 - id: metadata - with: - images: runmymind/fleetbot - tags: | - type=schedule - type=ref,event=branch - type=ref,event=tag - type=ref,event=pr - type=sha - type=raw,value=latest,enable={{is_default_branch}} - - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - uses: int128/kaniko-action@v1 - with: - push: true - file: ./docker/Dockerfile - tags: ${{ steps.metadata.outputs.tags }} - labels: ${{ steps.metadata.outputs.labels }} - cache: true - cache-repository: runmymind/fleetbot/cache + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/install + - run: pnpm run build + - run: pnpm install --frozen-lockfile --ignore-scripts --prod + - uses: docker/metadata-action@v5 + id: metadata + with: + images: runmymind/fleetbot + tags: | + type=schedule + type=ref,event=branch + type=ref,event=tag + type=ref,event=pr + type=sha + type=raw,value=latest,enable={{is_default_branch}} + - uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - uses: int128/kaniko-action@v1 + with: + push: true + file: ./docker/Dockerfile + tags: ${{ steps.metadata.outputs.tags }} + labels: ${{ steps.metadata.outputs.labels }} + cache: true + cache-repository: runmymind/fleetbot/cache diff --git a/.gitignore b/.gitignore index 8acf6f0c..59102c7f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,5 @@ /node_modules .env* !.env.example -.prettierrc *.iml diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..ad4ed129 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "semi": false, + "singleQuote": true, + "trailingComma": "all", + "endOfLine": "auto" +} diff --git a/.yamllint.yml b/.yamllint.yml index f5e13cf4..f8b33ff9 100644 --- a/.yamllint.yml +++ b/.yamllint.yml @@ -1,24 +1,24 @@ extends: default ignore: | - node_modules + node_modules rules: - braces: - min-spaces-inside: 1 - max-spaces-inside: 1 - brackets: - min-spaces-inside: 1 - max-spaces-inside: 1 - document-start: disable - document-end: - present: false - empty-lines: - max: 1 - max-start: 0 - max-end: 0 - empty-values: enable - line-length: disable - octal-values: enable - truthy: - level: error + braces: + min-spaces-inside: 1 + max-spaces-inside: 1 + brackets: + min-spaces-inside: 1 + max-spaces-inside: 1 + document-start: disable + document-end: + present: false + empty-lines: + max: 1 + max-start: 0 + max-end: 0 + empty-values: enable + line-length: disable + octal-values: enable + truthy: + level: error diff --git a/README.md b/README.md index 81d8c062..cf13b75f 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ + ## About The Project fleetbot is an automatic fleet refill service bot for Star Atlas SCORE, @@ -54,21 +55,25 @@ an early stage mini-game in the Star Atlas universe. `fleetbot` contains of four core components: ### resource manager + The resource manager makes sure that `fleetbot` always has enough `R4`. (FOOD, TOOL, FUEL and AMMO). Basically it calculates how much is needed for a given time period based on users and their burn. As soon as `fleetbot` runs low on a particular resource, it uses the official Star Atlas marketplace to restock them. ### bookkeeper + The bookkeeper manages user's accounts, handles deposits and withdrawals and keeps track of individual balances. Since all user's funds are kept in one wallet, this needs to be baked by a persistence layer to store additional information which can not efficiently be read from the blockchain itself. ### refill service + The most crucial part for `fleetbot` users. Checks your fleets and refills them as soon as they run out of resources. Ships aren't getting full refills by default, but only what is really needed. This leaves the option for the user to manually add resources as well. ### telegram bot + To interact with `fleetbot`, telegram can be used. See telegram section further down for more information.

(back to top)

@@ -80,6 +85,7 @@ To interact with `fleetbot`, telegram can be used. See telegram section further

(back to top)

+ ## Usage `fleetbot` can be used in several ways, depending on user's preference. To be fully aware of what the bot is actually doing @@ -90,24 +96,27 @@ everyone, there are more convenient solutions available as well. To compile and run the source code, the following prerequisites are needed: -* node environment -* empty postgres database -* telegram bot -* unused solana wallet +- node environment +- empty postgres database +- telegram bot +- unused solana wallet See `.env.example` for required information. Create a copy (`.env.`) and configure the environment. 1. clone repo - ```sh - git clone https://github.com/mindrunner/fleetbot.git - ``` + + ```sh + git clone https://github.com/mindrunner/fleetbot.git + ``` 2. install dependencies + ```sh npm install ``` 3. run database migrations + ```sh npm run typeorm migration:run ``` @@ -123,6 +132,7 @@ An official docker image is available and automatically build from `main` branch to use `docker-compose`. Example compose file: + ``` version: '3.5' @@ -155,7 +165,9 @@ For convenience, there is an officially hosted `fleetbot` available at `https:// See the following section on how to interact with the bot on telegram. + ## Telegram + Interaction with `fleetbot` is possible with telegram. The bot has a `/help` command which explains all important commands. Usage should be pretty straight forward as follows: @@ -209,18 +221,18 @@ Commands for verified users:

(back to top)

- + ## Roadmap -- [] Migrate to SPL-Token 0.3.x interface +- [] Migrate to SPL-Token 0.3.x interface See the [open issues](https://github.com/mindrunner/fleetbot/issues) for a full list of proposed features (and known issues).

(back to top)

- + ## Contributing Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. @@ -237,34 +249,40 @@ Don't forget to give the project a star! Thanks again!

(back to top)

+ ## Disclaimer Before using this service or code, do your own research about crypto-world, metaverses, wallets and all the potential danger in this universe. -- `fleetbot` is not responsible for any loss of funds -- `fleetbot` will never DM you -- `fleetbot` will never ask you for seed phrases or private keys +- `fleetbot` is not responsible for any loss of funds +- `fleetbot` will never DM you +- `fleetbot` will never ask you for seed phrases or private keys

(back to top)

+ ## License + Distributed under the MIT License. See `LICENSE` for more information.

(back to top)

+ ## Donations + `fleetbot` accepts donations in `SOL`, `POLIS` or `USDC` at the same addresses it operates: -* fleetbot.sol -* ANDqa82T21G1RXwxpbf9v7jZXXfFSeezaUf7MJGTH6BZ -

(back to top)

+- fleetbot.sol +- ANDqa82T21G1RXwxpbf9v7jZXXfFSeezaUf7MJGTH6BZ +

(back to top)

+ [contributors-shield]: https://img.shields.io/github/contributors/mindrunner/fleetbot.svg?style=for-the-badge [contributors-url]: https://github.com/mindrunner/fleetbot/graphs/contributors [forks-shield]: https://img.shields.io/github/forks/mindrunner/fleetbot.svg?style=for-the-badge @@ -275,7 +293,5 @@ Distributed under the MIT License. See `LICENSE` for more information. [issues-url]: https://github.com/mindrunner/fleetbot/issues [license-shield]: https://img.shields.io/github/license/mindrunner/fleetbot.svg?style=for-the-badge [license-url]: https://github.com/mindrunner/fleetbot/blob/master/LICENSE - [node.js]: https://img.shields.io/badge/node.js-000000?style=for-the-badge&logo=nextdotjs&logoColor=white [Node-url]: https://nodejs.org - diff --git a/docker/scripts/basedbot.sh b/docker/scripts/basedbot.sh new file mode 100755 index 00000000..0544938c --- /dev/null +++ b/docker/scripts/basedbot.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +node main/basedbot diff --git a/package.json b/package.json index e70ebff6..79de906b 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "build": "tsc", "build:docker": "docker build -t fleetbot -f docker/app/Dockerfile .", "db:migrate": "npm run typeorm -- migration:run", - "lint": "eslint --ext .ts --ext .js .", + "lint": "eslint --ext .ts .", "lint:docker": "docker run --rm -i hadolint/hadolint < cicd/docker/app/Dockerfile", "lint:yaml": "yamllint .", "clean": "rm -rf build", @@ -30,22 +30,23 @@ "@sentry/integrations": "^7.114.0", "@sentry/node": "^8.11.0", "@sentry/tracing": "^7.114.0", - "@solana/spl-token": "^0.4.6", + "@solana/spl-token": "^0.4.8", "@solana/web3.js": "^1.95.3", "@staratlas/atlas-prime": "^0.13.1", "@staratlas/cargo": "^1.1.0", - "@staratlas/points": "^1.0.4", - "@staratlas/claim-stake": "^0.11.5", + "@staratlas/claim-stake": "^0.11.6", "@staratlas/crafting": "^1.1.0", - "@staratlas/data-source": "^0.7.4", + "@staratlas/data-source": "^0.8.0", "@staratlas/factory": "^0.7.0", "@staratlas/player-profile": "^0.9.1", + "@staratlas/points": "^1.0.5", "@staratlas/profile-faction": "^0.4.1", - "@staratlas/sage": "^1.0.2", + "@staratlas/sage": "^1.4.0", "big.js": "^6.2.1", "bip39": "^3.1.0", "bn.js": "^5.2.1", "bs58": "^5.0.0", + "chance": "^1.1.12", "cron": "^3.1.7", "dayjs": "^1.11.11", "dotenv": "^16.4.5", @@ -55,11 +56,13 @@ "superagent": "^9.0.2", "telegraf": "^4.16.3", "typeorm": "^0.3.20", + "undici": "^6.19.2", "winston": "^3.13.0" }, "devDependencies": { "@types/big.js": "^6.2.2", "@types/bn.js": "^5.1.5", + "@types/chance": "^1.1.6", "@types/bs58": "^4.0.4", "@types/pg": "^8.11.6", "@types/superagent": "^8.1.7", @@ -69,9 +72,12 @@ "eslint-plugin-filenames": "^1.3.2", "eslint-plugin-import": "^2.29.1", "eslint-plugin-promise": "^6.2.0", + "eslint-plugin-prettier": "^5.1.3", + "eslint-config-prettier": "^9.1.0", + "prettier": "^3.2.5", "ts-jest": "^29.1.5", "ts-node-dev": "^2.0.0", "typescript": "^5.4.5" }, - "packageManager": "pnpm@9.1.0" + "packageManager": "pnpm@9.4.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 197c38a1..2afaa465 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,8 +24,8 @@ importers: specifier: ^7.114.0 version: 7.114.0 '@solana/spl-token': - specifier: ^0.4.6 - version: 0.4.6(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) + specifier: ^0.4.8 + version: 0.4.8(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) '@solana/web3.js': specifier: ^1.95.3 version: 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -36,29 +36,29 @@ importers: specifier: ^1.1.0 version: 1.1.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) '@staratlas/claim-stake': - specifier: ^0.11.5 - version: 0.11.5(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) + specifier: ^0.11.6 + version: 0.11.6(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) '@staratlas/crafting': specifier: ^1.1.0 version: 1.1.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) '@staratlas/data-source': - specifier: ^0.7.4 - version: 0.7.4(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) + specifier: ^0.8.0 + version: 0.8.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) '@staratlas/factory': specifier: ^0.7.0 - version: 0.7.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) + version: 0.7.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) '@staratlas/player-profile': specifier: ^0.9.1 version: 0.9.1(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) '@staratlas/points': - specifier: ^1.0.4 - version: 1.0.4(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) + specifier: ^1.0.5 + version: 1.1.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) '@staratlas/profile-faction': specifier: ^0.4.1 version: 0.4.1(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) '@staratlas/sage': - specifier: ^1.0.2 - version: 1.0.2(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) + specifier: ^1.4.0 + version: 1.6.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) big.js: specifier: ^6.2.1 version: 6.2.1 @@ -71,6 +71,9 @@ importers: bs58: specifier: ^5.0.0 version: 5.0.0 + chance: + specifier: ^1.1.12 + version: 1.1.12 cron: specifier: ^3.1.7 version: 3.1.7 @@ -98,6 +101,9 @@ importers: typeorm: specifier: ^0.3.20 version: 0.3.20(pg@8.11.5)(ts-node@10.9.2(@types/node@20.12.11)(typescript@5.4.5)) + undici: + specifier: ^6.19.2 + version: 6.19.8 winston: specifier: ^3.13.0 version: 3.13.0 @@ -111,6 +117,9 @@ importers: '@types/bs58': specifier: ^4.0.4 version: 4.0.4 + '@types/chance': + specifier: ^1.1.6 + version: 1.1.6 '@types/pg': specifier: ^8.11.6 version: 8.11.6 @@ -126,15 +135,24 @@ importers: eslint: specifier: ^8.57.0 version: 8.57.0 + eslint-config-prettier: + specifier: ^9.1.0 + version: 9.1.0(eslint@8.57.0) eslint-plugin-filenames: specifier: ^1.3.2 version: 1.3.2(eslint@8.57.0) eslint-plugin-import: specifier: ^2.29.1 version: 2.29.1(@typescript-eslint/parser@7.13.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0) + eslint-plugin-prettier: + specifier: ^5.1.3 + version: 5.1.3(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.3.3) eslint-plugin-promise: specifier: ^6.2.0 version: 6.2.0(eslint@8.57.0) + prettier: + specifier: ^3.2.5 + version: 3.3.3 ts-jest: specifier: ^29.1.5 version: 29.1.5(@babel/core@7.24.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.5))(jest@29.7.0(@types/node@20.12.11)(ts-node@10.9.2(@types/node@20.12.11)(typescript@5.4.5)))(typescript@5.4.5) @@ -361,6 +379,15 @@ packages: resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@ethereumjs/rlp@4.0.1': + resolution: {integrity: sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==} + engines: {node: '>=14'} + hasBin: true + + '@ethereumjs/util@8.1.0': + resolution: {integrity: sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA==} + engines: {node: '>=14'} + '@faker-js/faker@8.4.1': resolution: {integrity: sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0, npm: '>=6.14.13'} @@ -477,8 +504,110 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@noble/curves@1.4.0': - resolution: {integrity: sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==} + '@metaplex-foundation/beet-solana@0.4.1': + resolution: {integrity: sha512-/6o32FNUtwK8tjhotrvU/vorP7umBuRFvBZrC6XCk51aKidBHe5LPVPA5AjGPbV3oftMfRuXPNd9yAGeEqeCDQ==} + + '@metaplex-foundation/beet@0.7.2': + resolution: {integrity: sha512-K+g3WhyFxKPc0xIvcIjNyV1eaTVJTiuaHZpig7Xx0MuYRMoJLLvhLTnUXhFdR5Tu2l2QSyKwfyXDgZlzhULqFg==} + + '@metaplex-foundation/digital-asset-standard-api@1.0.4': + resolution: {integrity: sha512-YSYyMnIoKNykDZTXsSCeiIOJ7NT5Ke2pzghXDsinRwHvwIZWv+zY5kJQBvTglAzYlt/GaI+noAhUZXXmSbp07A==} + peerDependencies: + '@metaplex-foundation/umi': '>= 0.8.2 < 1' + + '@metaplex-foundation/mpl-bubblegum@4.2.1': + resolution: {integrity: sha512-r9kHrVmkzJApbXwd7cmJyO0mAV3qsJaTjv5ks6PUT1Bzjj9QCvlJYg2UYQJLUTcrY5TjE9wXLpwUqNgllXH/Cw==} + peerDependencies: + '@metaplex-foundation/umi': '>= 0.8.9 < 1' + + '@metaplex-foundation/mpl-token-metadata@3.2.1': + resolution: {integrity: sha512-26W1NhQwDWmLOg/pBRYut7x/vEs/5kFS2sWVEY5/X0f2jJOLhnd4NaZQcq+5u+XZsXvm1jq2AtrRGPNK43oqWQ==} + peerDependencies: + '@metaplex-foundation/umi': '>= 0.8.2 < 1' + + '@metaplex-foundation/mpl-toolbox@0.9.4': + resolution: {integrity: sha512-fd6JxfoLbj/MM8FG2x91KYVy1U6AjBQw4qjt7+Da3trzQaWnSaYHDcYRG/53xqfvZ9qofY1T2t53GXPlD87lnQ==} + peerDependencies: + '@metaplex-foundation/umi': '>= 0.8.2 < 1' + + '@metaplex-foundation/umi-bundle-defaults@0.9.2': + resolution: {integrity: sha512-kV3tfvgvRjVP1p9OFOtH+ibOtN9omVJSwKr0We4/9r45e5LTj+32su0V/rixZUkG1EZzzOYBsxhtIE0kIw/Hrw==} + peerDependencies: + '@metaplex-foundation/umi': ^0.9.2 + '@solana/web3.js': ^1.72.0 + + '@metaplex-foundation/umi-downloader-http@0.9.2': + resolution: {integrity: sha512-tzPT9hBwenzTzAQg07rmsrqZfgguAXELbcJrsYMoASp5VqWFXYIP00g94KET6XLjWUXH4P1J2zoa6hGennPXHA==} + peerDependencies: + '@metaplex-foundation/umi': ^0.9.2 + + '@metaplex-foundation/umi-eddsa-web3js@0.9.2': + resolution: {integrity: sha512-hhPCxXbYIp4BC4z9gK78sXpWLkNSrfv4ndhF5ruAkdIp7GcRVYKj0QnOUO6lGYGiIkNlw20yoTwOe1CT//OfTQ==} + peerDependencies: + '@metaplex-foundation/umi': ^0.9.2 + '@solana/web3.js': ^1.72.0 + + '@metaplex-foundation/umi-http-fetch@0.9.2': + resolution: {integrity: sha512-YCZuBu24T9ZzEDe4+w12LEZm/fO9pkyViZufGgASC5NX93814Lvf6Ssjn/hZzjfA7CvZbvLFbmujc6CV3Q/m9Q==} + peerDependencies: + '@metaplex-foundation/umi': ^0.9.2 + + '@metaplex-foundation/umi-options@0.8.9': + resolution: {integrity: sha512-jSQ61sZMPSAk/TXn8v8fPqtz3x8d0/blVZXLLbpVbo2/T5XobiI6/MfmlUosAjAUaQl6bHRF8aIIqZEFkJiy4A==} + + '@metaplex-foundation/umi-program-repository@0.9.2': + resolution: {integrity: sha512-g3+FPqXEmYsBa8eETtUE2gb2Oe3mqac0z3/Ur1TvAg5TtIy3mzRzOy/nza+sgzejnfcxcVg835rmpBaxpBnjDA==} + peerDependencies: + '@metaplex-foundation/umi': ^0.9.2 + + '@metaplex-foundation/umi-public-keys@0.8.9': + resolution: {integrity: sha512-CxMzN7dgVGOq9OcNCJe2casKUpJ3RmTVoOvDFyeoTQuK+vkZ1YSSahbqC1iGuHEtKTLSjtWjKvUU6O7zWFTw3Q==} + + '@metaplex-foundation/umi-rpc-chunk-get-accounts@0.9.2': + resolution: {integrity: sha512-YRwVf6xH0jPBAUgMhEPi+UbjioAeqTXmjsN2TnmQCPAmHbrHrMRj0rlWYwFLWAgkmoxazYrXP9lqOFRrfOGAEA==} + peerDependencies: + '@metaplex-foundation/umi': ^0.9.2 + + '@metaplex-foundation/umi-rpc-web3js@0.9.2': + resolution: {integrity: sha512-MqcsBz8B4wGl6jxsf2Jo/rAEpYReU9VCSR15QSjhvADHMmdFxCIZCCAgE+gDE2Vuanfl437VhOcP3g5Uw8C16Q==} + peerDependencies: + '@metaplex-foundation/umi': ^0.9.2 + '@solana/web3.js': ^1.72.0 + + '@metaplex-foundation/umi-serializer-data-view@0.9.2': + resolution: {integrity: sha512-5vGptadJxUxvUcyrwFZxXlEc6Q7AYySBesizCtrBFUY8w8PnF2vzmS45CP1MLySEATNH6T9mD4Rs0tLb87iQyA==} + peerDependencies: + '@metaplex-foundation/umi': ^0.9.2 + + '@metaplex-foundation/umi-serializers-core@0.8.9': + resolution: {integrity: sha512-WT82tkiYJ0Qmscp7uTj1Hz6aWQPETwaKLAENAUN5DeWghkuBKtuxyBKVvEOuoXerJSdhiAk0e8DWA4cxcTTQ/w==} + + '@metaplex-foundation/umi-serializers-encodings@0.8.9': + resolution: {integrity: sha512-N3VWLDTJ0bzzMKcJDL08U3FaqRmwlN79FyE4BHj6bbAaJ9LEHjDQ9RJijZyWqTm0jE7I750fU7Ow5EZL38Xi6Q==} + + '@metaplex-foundation/umi-serializers-numbers@0.8.9': + resolution: {integrity: sha512-NtBf1fnVNQJHFQjLFzRu2i9GGnigb9hOm/Gfrk628d0q0tRJB7BOM3bs5C61VAs7kJs4yd+pDNVAERJkknQ7Lg==} + + '@metaplex-foundation/umi-serializers@0.9.0': + resolution: {integrity: sha512-hAOW9Djl4w4ioKeR4erDZl5IG4iJdP0xA19ZomdaCbMhYAAmG/FEs5khh0uT2mq53/MnzWcXSUPoO8WBN4Q+Vg==} + + '@metaplex-foundation/umi-transaction-factory-web3js@0.9.2': + resolution: {integrity: sha512-fR1Kf21uylMFd1Smkltmj4jTNxhqSWf416owsJ+T+cvJi2VCOcOwq/3UFzOrpz78fA0RhsajKYKj0HYsRnQI1g==} + peerDependencies: + '@metaplex-foundation/umi': ^0.9.2 + '@solana/web3.js': ^1.72.0 + + '@metaplex-foundation/umi-web3js-adapters@0.9.2': + resolution: {integrity: sha512-RQqUTtHYY9fmEMnq7s3Hiv/81flGaoI0ZVVoafnFVaQLnxU6QBKxtboRZHk43XtD9CiFh5f9izrMJX7iK7KlOA==} + peerDependencies: + '@metaplex-foundation/umi': ^0.9.2 + '@solana/web3.js': ^1.72.0 + + '@metaplex-foundation/umi@0.9.2': + resolution: {integrity: sha512-9i4Acm4pruQfJcpRrc2EauPBwkfDN0I9QTvJyZocIlKgoZwD6A6wH0PViH1AjOVG5CQCd1YI3tJd5XjYE1ElBw==} + + '@noble/curves@1.4.2': + resolution: {integrity: sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==} '@noble/curves@1.6.0': resolution: {integrity: sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==} @@ -692,6 +821,10 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@pkgr/core@0.1.1': + resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@prisma/instrumentation@5.15.0': resolution: {integrity: sha512-fCWOOOajTKOUEp43gRmBqwt6oN9bPJcLiloi2OG/2ED0N5z62Cuza6FDrlm3SJHQAXYlXqLE0HLdEE5WcUkOzg==} @@ -701,6 +834,15 @@ packages: peerDependencies: '@solana/web3.js': ^1.2.0 + '@scure/base@1.1.8': + resolution: {integrity: sha512-6CyAclxj3Nb0XT7GHK6K4zK6k2xJm6E4Ft0Ohjt4WgegiFUHEtFb2CGzmPmGBwoIhrLsqNLYfLr04Y1GePrzZg==} + + '@scure/bip32@1.4.0': + resolution: {integrity: sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==} + + '@scure/bip39@1.3.0': + resolution: {integrity: sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==} + '@sentry-internal/tracing@7.114.0': resolution: {integrity: sha512-dOuvfJN7G+3YqLlUY4HIjyWHaRP8vbOgF+OsE5w2l7ZEn1rMAaUbPntAR8AF9GBA6j2zWNoSo8e7GjbJxVofSg==} engines: {node: '>=8'} @@ -829,11 +971,11 @@ packages: peerDependencies: typescript: '>=5' - '@solana/spl-token-group@0.0.4': - resolution: {integrity: sha512-7+80nrEMdUKlK37V6kOe024+T7J4nNss0F8LQ9OOPYdWCCfJmsGUzVx2W3oeizZR4IHM6N4yC9v1Xqwc3BTPWw==} + '@solana/spl-account-compression@0.2.0': + resolution: {integrity: sha512-nHpa+hTUpjLdV9x4LXlp7k0WIkr8kUGjY/SPh+vuTUy4SEIIDjrGJ6/B0hUdd8+mFfrq2x4j/tgJvPsm4K5AJw==} engines: {node: '>=16'} peerDependencies: - '@solana/web3.js': ^1.91.6 + '@solana/web3.js': ^1.50.1 '@solana/spl-token-group@0.0.5': resolution: {integrity: sha512-CLJnWEcdoUBpQJfx9WEbX3h6nTdNiUzswfFdkABUik7HVwSNA98u5AYvBVK2H93d9PGMOHAak2lHW9xr+zAJGQ==} @@ -853,12 +995,6 @@ packages: peerDependencies: '@solana/web3.js': ^1.88.0 - '@solana/spl-token@0.4.6': - resolution: {integrity: sha512-1nCnUqfHVtdguFciVWaY/RKcQz1IF4b31jnKgAmjU9QVN1q7dRUkTEWJZgTYIEtsULjVnC9jRqlhgGN39WbKKA==} - engines: {node: '>=16'} - peerDependencies: - '@solana/web3.js': ^1.91.6 - '@solana/spl-token@0.4.8': resolution: {integrity: sha512-RO0JD9vPRi4LsAbMUdNbDJ5/cv2z11MGhtAvFeRzT4+hAGE/FUzRi0tkkWtuCfSIU3twC6CtmAihRp/+XXjWsA==} engines: {node: '>=16'} @@ -885,17 +1021,17 @@ packages: '@staratlas/cargo@1.1.0': resolution: {integrity: sha512-7td0gxru4dkygO+xv+RQs9bkizKj6+blAMRSPP5ozOZ9QQ35HwCBIDJXB6Kk8sfVcq2JOHiWyQtyjcAD6GHArQ==} - '@staratlas/claim-stake@0.11.5': - resolution: {integrity: sha512-tp/SDJnG5N0emgidAf7gjdlBNR/ZasJo7uaDnk82Ja9ANr9FKsjRB4tGjUWQV3BV4fVni3VH2Xw3591pc2OYzw==} + '@staratlas/claim-stake@0.11.6': + resolution: {integrity: sha512-AEaMKAOQCntFZRxGTw0i+C3+S8MzTY+CJlgFPhb/T9QLAHO5CIby2g4P6cb8v5xaJIW5Z8Lh+nvn1+THX8fRXw==} '@staratlas/crafting@1.1.0': resolution: {integrity: sha512-lxvdGBZatVBL63o1EHcUAxk1k1jHwEv6dZsulpgXpsOfnBP7Btzt8Ky1knBD7HvM/gBQplxMKopolRGFCQekXQ==} - '@staratlas/data-source@0.7.4': - resolution: {integrity: sha512-DY4fGLJjKbn/c+h+PdbLHjg8r3skDvnj3ctJQpk4LsNKFYR7zRq/0x+H24WB53DgZbGelxcJIdhsY6178732TA==} + '@staratlas/crew@0.7.0': + resolution: {integrity: sha512-tr8RDDs1waOSVwVwR9WlD6Z6iDvRHprqQu4XQmSSy6oy5Sv2FNofFWENsC0dmysUl/n0OLzKfT8/Q4D3hKRlTA==} - '@staratlas/data-source@0.7.5': - resolution: {integrity: sha512-LtaCpV6G3X3r7pSUlkr6pDsm/B+90pKtryr4qnZRttJ7vguadSC7xzlQJ5ubEdhK3b1Xs8UFLBnEHpvzkJk9ow==} + '@staratlas/data-source@0.7.7': + resolution: {integrity: sha512-DQfR/a9MXr1Gds/YTR9RylQZlx5gbCe4NjynQ/45je3rnnblNfhma0WoHrSHz/r/lWwJwN7yRwSJkfaDW8QhuQ==} '@staratlas/data-source@0.8.0': resolution: {integrity: sha512-fUwoQDwGcBPXN+s4foGT2XItK4XJh2C4UCBN7HlQM1pO3NQfOxGW7tLrvP67ZMyE4ltrwN3bA+GoUub9vj33vA==} @@ -909,17 +1045,23 @@ packages: '@staratlas/player-profile@0.9.1': resolution: {integrity: sha512-bqPXn6fGl+gg5zZBEK4+/Cv9oriVzrNOTW1Z+6TMFSB/vMsE51DsdyN7BWMkvwfBg2XdHfL9VntlU8j8OYD8jQ==} - '@staratlas/points@1.0.4': - resolution: {integrity: sha512-/pPjOZc6DTwps3uvTDHN3UUEZPPktbbPGn6yl4KJbpuj08G39Gd+n7HZYI4pLUUxJrq3ktVBO2+FC1YYUSro/w==} + '@staratlas/points-store@1.1.0': + resolution: {integrity: sha512-Rrbsnku5flephKSNkb6OYkUxGWmQAsHH76ZQKISBViXSPcQkpZ3lIYaqYaztEDx6rXSY1J5ftkJEAnxMVtkJ4A==} + + '@staratlas/points@1.1.0': + resolution: {integrity: sha512-9XyG5i3xz4Om6Blq/HxYqnGGAnklyCLlokIXpLAxYWEU53JT7ldhLiXCgKuA8n/aWi0Y3YCOnmPuuQ5JwD+cZw==} '@staratlas/profile-faction@0.4.1': resolution: {integrity: sha512-RaE7ZqX7VyPZBMpZm0sblFPykGhBr4IrtMsxVtgbU2hgtZk0rqXWA8vv8hR55fEtmweGuon06JPnGQfLBz4/yQ==} + '@staratlas/profile-faction@0.6.0': + resolution: {integrity: sha512-OLENOQ12cG12g8xkmZsf6oFvtCkTlsI/ljGGwpOzic0iCD282bydVStJ//N0nRxBfDBGYCyRI9TBkR3uF69D2g==} + '@staratlas/profile-vault@0.9.1': resolution: {integrity: sha512-nY6t1VTI/kRd/bQLSEeRTqwJ6QWRAC1du6vFN6zufJQyumuuZpmevKNZF1DkrSjZqhe3/2HgyR/ud7d62KGFxg==} - '@staratlas/sage@1.0.2': - resolution: {integrity: sha512-PWz8eUflXvk14+I+fCupnEAgsuQ2UAkgi32laZ/s3JqINnQOE/Dhpa9DPSgqas/5J5/fti5FS1JacmqpU2DcdQ==} + '@staratlas/sage@1.6.0': + resolution: {integrity: sha512-fwaPyxs3Rr1dLR1GjMV0nnqG/L68Paa1E26h9AxXEf/Lx+im5x5u+jsc2m7/uwEVMo53ThPt+2ToIaK4kdgAAQ==} '@swc/helpers@0.5.13': resolution: {integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==} @@ -966,6 +1108,9 @@ packages: '@types/bs58@4.0.4': resolution: {integrity: sha512-0IEpMFXXQi2zXaXl9GJ3sRwQo0uEkD+yFOv+FnAU5lkPtcu6h61xb7jc2CFPEZ5BUOaiP13ThuGc9HD4R8lR5g==} + '@types/chance@1.1.6': + resolution: {integrity: sha512-V+pm3stv1Mvz8fSKJJod6CglNGVqEQ6OyuqitoDkWywEODM/eJd1eSuIp9xt6DrX8BWZ2eDSIzbw1tPCUTvGbQ==} + '@types/connect@3.4.36': resolution: {integrity: sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==} @@ -1011,6 +1156,9 @@ packages: '@types/keygrip@1.0.6': resolution: {integrity: sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==} + '@types/keyv@3.1.4': + resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} + '@types/koa-compose@3.2.8': resolution: {integrity: sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==} @@ -1059,6 +1207,9 @@ packages: '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/responselike@1.0.3': + resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} + '@types/send@0.17.4': resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} @@ -1167,6 +1318,9 @@ packages: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} + abs@1.3.14: + resolution: {integrity: sha512-PrS26IzwKLWwuURpiKl8wRmJ2KdR/azaVrLEBWG/TALwT20Y7qjtYp1qcMLHA4206hBHY5phv3w4pjf9NPv4Vw==} + acorn-import-assertions@1.9.0: resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} peerDependencies: @@ -1226,6 +1380,9 @@ packages: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} + ansicolors@0.3.2: + resolution: {integrity: sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==} + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -1277,6 +1434,9 @@ packages: asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + assert@2.1.0: + resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==} + async@3.2.5: resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} @@ -1350,6 +1510,9 @@ packages: bip39@3.1.0: resolution: {integrity: sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==} + bn.js@4.11.6: + resolution: {integrity: sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==} + bn.js@5.2.1: resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} @@ -1406,6 +1569,9 @@ packages: resolution: {integrity: sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA==} engines: {node: '>=4.5'} + buffer-reverse@1.0.1: + resolution: {integrity: sha512-M87YIUBsZ6N924W57vDwT/aOu8hw7ZgdByz6ijksLjmHJELBASmYTTlNHRgjE+pTsT9oJXGaDSgqqwfdHotDUg==} + buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} @@ -1440,6 +1606,10 @@ packages: caniuse-lite@1.0.30001617: resolution: {integrity: sha512-mLyjzNI9I+Pix8zwcrpxEbGlfqOkF9kM3ptzmKNw5tizSyYwMe+nGLTqMK9cO+0E+Bh6TsBxNAaHWEM8xwSsmA==} + capture-stack-trace@1.0.2: + resolution: {integrity: sha512-X/WM2UQs6VMHUtjUDnZTRI+i1crWteJySFzr9UpGoQa4WQffXVTTXuekjl7TjZRlcF2XfjgITT0HxZ9RnxeT0w==} + engines: {node: '>=0.10.0'} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -1452,6 +1622,9 @@ packages: resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + chance@1.1.12: + resolution: {integrity: sha512-vVBIGQVnwtUG+SYe0ge+3MvF78cvSpuCOEUJr7sVEk2vSBuMW6OXNJjSzdtzrlxNUEaoqH2GBd5Y/+18BEB01Q==} + char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} @@ -1534,6 +1707,13 @@ packages: cookiejar@2.1.4: resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + create-error-class@3.0.2: + resolution: {integrity: sha512-gYTKKexFO3kh200H1Nit76sRwRtOY32vQd3jpAQKpLtZqyNsSQNfI4N7o3eP2wUjV35pTWKRYqFUDBvUha/Pkw==} + engines: {node: '>=0.10.0'} + create-hash@1.2.0: resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==} @@ -1562,6 +1742,9 @@ packages: resolution: {integrity: sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg==} engines: {node: '>=8'} + crypto-js@4.2.0: + resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + css-select@4.3.0: resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} @@ -1609,6 +1792,10 @@ packages: babel-plugin-macros: optional: true + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -1616,6 +1803,9 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} + deffy@2.2.4: + resolution: {integrity: sha512-pLc9lsbsWjr6RxmJ2OLyvm+9l4j1yK69h+TML/gUit/t3vTijpkNGh8LioaJYTGO7F25m6HZndADcUOo2PsiUg==} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -1682,6 +1872,9 @@ packages: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} + duplexer2@0.1.4: + resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + dynamic-dedupe@0.3.0: resolution: {integrity: sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==} @@ -1710,6 +1903,9 @@ packages: entities@2.2.0: resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + err@1.1.1: + resolution: {integrity: sha512-N97Ybd2jJHVQ+Ft3Q5+C2gM3kgygkdeQmEqbN2z15UTVyyEsIwLA1VK39O1DHEJhXbwIFcJLqm6iARNhFANcQA==} + error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -1762,6 +1958,12 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + eslint-config-prettier@9.1.0: + resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} @@ -1801,6 +2003,20 @@ packages: '@typescript-eslint/parser': optional: true + eslint-plugin-prettier@5.1.3: + resolution: {integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '*' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + eslint-plugin-promise@6.2.0: resolution: {integrity: sha512-QmAqwizauvnKOlifxyDj2ObfULpHQawlg/zQdgEixur9vl0CvZGv/LCJV2rtj3210QCoeGBzVMfMXqGAOr/4fA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1845,6 +2061,16 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + ethereum-bloom-filters@1.2.0: + resolution: {integrity: sha512-28hyiE7HVsWubqhpVLVmZXFd4ITeHi+BUu05o9isf0GUpMtzBUi+8/gFrGaGYzvGAJQmJ3JKj77Mk9G98T84rA==} + + ethereum-cryptography@2.2.1: + resolution: {integrity: sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==} + + ethjs-unit@0.1.6: + resolution: {integrity: sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==} + engines: {node: '>=6.5.0', npm: '>=3'} + event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -1855,6 +2081,9 @@ packages: eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + exec-limiter@3.2.13: + resolution: {integrity: sha512-86Ri699bwiHZVBzTzNj8gspqAhCPchg70zPVWIh3qzUOA1pUMcb272Em3LPk8AE0mS95B9yMJhtqF8vFJAn0dA==} + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -1874,6 +2103,9 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} @@ -1965,6 +2197,9 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + function.name@1.0.13: + resolution: {integrity: sha512-mVrqdoy5npWZyoXl4DxCeuVF6delDcQjVS9aPdvLYlBxtMTZDR2B5GVEQEoM1jJyspCqg3C0v4ABkLE7tp9xFA==} + function.prototype.name@1.1.6: resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} engines: {node: '>= 0.4'} @@ -1996,6 +2231,18 @@ packages: resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} + git-package-json@1.4.10: + resolution: {integrity: sha512-DRAcvbzd2SxGK7w8OgYfvKqhFliT5keX0lmSmVdgScgf1kkl5tbbo7Pam6uYoCa1liOiipKxQZG8quCtGWl/fA==} + + git-source@1.1.10: + resolution: {integrity: sha512-XZZ7ZgnLL35oLgM/xjnLYgtlKlxJG0FohC1kWDvGkU7s1VKGXK0pFF/g1itQEwQ3D+uTQzBnzPi8XbqOv7Wc1Q==} + + git-up@1.2.1: + resolution: {integrity: sha512-SRVN3rOLACva8imc7BFrB6ts5iISWKH1/h/1Z+JZYoUI7UVQM7gQqk4M2yxUENbq2jUUT09NEND5xwP1i7Ktlw==} + + git-url-parse@5.0.1: + resolution: {integrity: sha512-4uSiOgrryNEMBX+gTWogenYRUh2j1D+95STTSEF2RCTgLkfJikl8c7BGr0Bn274hwuxTsbS2/FQ5pVS9FoXegQ==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -2032,12 +2279,19 @@ packages: gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + got@5.7.1: + resolution: {integrity: sha512-1qd54GLxvVgzuidFmw9ze9umxS3rzhdBH6Wt6BTYrTQUXTN01vGGYXwzLzYLowNx8HBH3/c7kRyvx90fh13i7Q==} + engines: {node: '>=0.10.0 <7'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + gry@5.0.8: + resolution: {integrity: sha512-meq9ZjYVpLzZh3ojhTg7IMad9grGsx6rUUKHLqPnhLXzJkRQvEL2U3tQpS5/WentYTtHtxkT3Ew/mb10D6F6/g==} + has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} @@ -2079,6 +2333,9 @@ packages: highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + hosted-git-info@2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -2134,10 +2391,17 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + internal-slot@1.0.7: resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} + is-arguments@1.1.1: + resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} + engines: {node: '>= 0.4'} + is-array-buffer@3.0.4: resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} engines: {node: '>= 0.4'} @@ -2186,10 +2450,22 @@ packages: resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} engines: {node: '>=6'} + is-generator-function@1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-hex-prefixed@1.0.0: + resolution: {integrity: sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA==} + engines: {node: '>=6.5.0', npm: '>=3'} + + is-nan@1.3.2: + resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} + engines: {node: '>= 0.4'} + is-negative-zero@2.0.3: resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} @@ -2206,14 +2482,29 @@ packages: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} + is-redirect@1.0.0: + resolution: {integrity: sha512-cr/SlUEe5zOGmzvj9bUyC4LVvkNVAXu4GytXLNMr1pny+a65MpQ9IJzFHD5vi7FyJgb4qt27+eS3TuQnqB+RQw==} + engines: {node: '>=0.10.0'} + is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} + is-retry-allowed@1.2.0: + resolution: {integrity: sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==} + engines: {node: '>=0.10.0'} + is-shared-array-buffer@1.0.3: resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} engines: {node: '>= 0.4'} + is-ssh@1.4.0: + resolution: {integrity: sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==} + + is-stream@1.1.0: + resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} + engines: {node: '>=0.10.0'} + is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -2233,6 +2524,9 @@ packages: is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} @@ -2268,6 +2562,9 @@ packages: resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} engines: {node: '>=8'} + iterate-object@1.3.4: + resolution: {integrity: sha512-4dG1D1x/7g8PwHS9aK6QV5V94+ZvyP4+d19qDv43EzImmrndysIl4prmJ1hWWIGCqrZHyaHBm6BSEWHOLnpoNw==} + jackspeak@2.3.6: resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} engines: {node: '>=14'} @@ -2409,6 +2706,9 @@ packages: js-sha256@0.9.0: resolution: {integrity: sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==} + js-sha3@0.8.0: + resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2474,6 +2774,9 @@ packages: lie@3.1.1: resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==} + limit-it@3.2.10: + resolution: {integrity: sha512-T0NK99pHnkimldr1WUqvbGV1oWDku/xC9J/OqzJFsV1jeOS6Bwl8W7vkeQIBqwiON9dTALws+rX/XPMQqWerDQ==} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -2516,6 +2819,10 @@ packages: lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + lowercase-keys@1.0.1: + resolution: {integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==} + engines: {node: '>=0.10.0'} + lru-cache@10.2.2: resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} engines: {node: 14 || >=16.14} @@ -2547,10 +2854,17 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + merkletreejs@0.3.11: + resolution: {integrity: sha512-LJKTl4iVNTndhL+3Uz/tfkjD0klIWsHlUzgtuNnNrsf7bAlXR30m+xYB7lHr5Z/l6e/yAIsr26Dabx6Buo4VGQ==} + engines: {node: '>= 7.6.0'} + methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} + micro-ftch@0.3.1: + resolution: {integrity: sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==} + micromatch@4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} @@ -2652,6 +2966,16 @@ packages: node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + node-status-codes@1.0.0: + resolution: {integrity: sha512-1cBMgRxdMWE8KeWCqk2RIOrvUb0XCwYfEsY5/y2NlXyq4Y/RumnOZvTj4Nbr77+Vb2C+kyBoRTdkNOS8L3d/aQ==} + engines: {node: '>=0.10.0'} + + noop6@1.0.9: + resolution: {integrity: sha512-DB3Hwyd89dPr5HqEPg3YHjzvwh/mCqizC1zZ8vyofqc+TQRyPDnT4wgXXbLGF4z9YAzwwTLi8pNLhGqcbSjgkA==} + + normalize-package-data@2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -2663,6 +2987,16 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + number-to-bn@1.7.0: + resolution: {integrity: sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig==} + engines: {node: '>=6.5.0', npm: '>=3'} + + oargv@3.4.10: + resolution: {integrity: sha512-SXaMANv9sr7S/dP0vj0+Ybipa47UE1ntTWQ2rpPRhC6Bsvfl+Jg03Xif7jfL0sWKOYWK8oPjcZ5eJ82t8AP/8g==} + + obj-def@1.0.9: + resolution: {integrity: sha512-bQ4ya3VYD6FAA1+s6mEhaURRHSmw4+sKaXE6UyXZ1XDYc5D+c7look25dFdydmLd18epUegh398gdDkMUZI9xg==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2670,6 +3004,10 @@ packages: object-inspect@1.13.1: resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} + object-is@1.1.6: + resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} + engines: {node: '>= 0.4'} + object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} @@ -2696,6 +3034,9 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + one-by-one@3.2.8: + resolution: {integrity: sha512-HR/pSzZdm46Xqj58K+Bu64kMbSTw8/u77AwWvV+rprO/OsuR++pPlkUJn+SmwqBGRgHKwSKQ974V3uls7crIeQ==} + one-time@1.0.0: resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} @@ -2711,6 +3052,10 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -2735,6 +3080,17 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + package-json-path@1.0.9: + resolution: {integrity: sha512-uNu7f6Ef7tQHZRnkyVnCtzdSYVN9uBtge/sG7wzcUaawFWkPYUq67iXxRGrQSg/q0tzxIB8jSyIYUKjG2Jn//A==} + + package-json@2.4.0: + resolution: {integrity: sha512-PRg65iXMTt/uK8Rfh5zvzkUbfAPitF17YaCY+IbHsYgksiLvtzWWTUildHth3mVaZ7871OJ7gtP4LBRBlmAdXg==} + engines: {node: '>=0.10.0'} + + package.json@2.0.1: + resolution: {integrity: sha512-pSxZ6XR5yEawRN2ekxx9IKgPN5uNAYco7MCPxtBEWMKO3UKWa1X2CtQMzMgloeGj2g2o6cue3Sb5iPkByIJqlw==} + deprecated: Use pkg.json instead. + pako@2.1.0: resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} @@ -2742,10 +3098,17 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-json@2.2.0: + resolution: {integrity: sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==} + engines: {node: '>=0.10.0'} + parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse-url@1.3.11: + resolution: {integrity: sha512-1wj9nkgH/5EboDxLwaTMGJh3oH3f+Gue+aGdh631oCqoSBpokzmMmOldvOeBPtB8GJBYJbaF93KPzlkU+Y1ksg==} + parse5-htmlparser2-tree-adapter@6.0.1: resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} @@ -2827,6 +3190,14 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + pinkie-promise@2.0.1: + resolution: {integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==} + engines: {node: '>=0.10.0'} + + pinkie@2.0.4: + resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==} + engines: {node: '>=0.10.0'} + pirates@4.0.6: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} @@ -2878,6 +3249,19 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prepend-http@1.0.4: + resolution: {integrity: sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==} + engines: {node: '>=0.10.0'} + + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + + prettier@3.3.3: + resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} + engines: {node: '>=14'} + hasBin: true + pretty-error@4.0.0: resolution: {integrity: sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==} @@ -2885,10 +3269,19 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} + protocols@1.4.8: + resolution: {integrity: sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg==} + + protocols@2.0.1: + resolution: {integrity: sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==} + proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} @@ -2906,9 +3299,29 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + r-json@1.3.0: + resolution: {integrity: sha512-xesd+RHCpymPCYd9DvDvUr1w1IieSChkqYF1EpuAYrvCfLXji9NP36DvyYZJZZB5soVDvZ0WUtBoZaU1g5Yt9A==} + + r-package-json@1.0.9: + resolution: {integrity: sha512-G4Vpf1KImWmmPFGdtWQTU0L9zk0SjqEC4qs/jE7AQ+Ylmr5kizMzGeC4wnHp5+ijPqNN+2ZPpvyjVNdN1CDVcg==} + + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + read-all-stream@3.1.0: + resolution: {integrity: sha512-DI1drPHbmBcUDWrJ7ull/F2Qb8HkwBncVx8/RpKYFSIACYaVRQReISYPdZz/mt1y1+qMCOrfReTopERmaxtP6w==} + engines: {node: '>=0.10.0'} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -2927,6 +3340,13 @@ packages: resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} engines: {node: '>= 0.4'} + registry-auth-token@3.4.0: + resolution: {integrity: sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==} + + registry-url@3.1.0: + resolution: {integrity: sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==} + engines: {node: '>=0.10.0'} + renderkid@3.0.0: resolution: {integrity: sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==} @@ -2985,6 +3405,9 @@ packages: resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} engines: {node: '>=0.4'} + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -3003,6 +3426,10 @@ packages: resolution: {integrity: sha512-jLYV0DORrzY3xaz/S9ydJL6Iz7essZeAfnAavsJ+zsJGZ1MOnsS52yRjU3uF3pJa/lla7+wisp//fxOwOH8SKQ==} engines: {node: '>= 0.10'} + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -3056,6 +3483,9 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + sliced@1.0.1: + resolution: {integrity: sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==} + snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} @@ -3069,6 +3499,18 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-license-ids@3.0.20: + resolution: {integrity: sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==} + split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} @@ -3106,6 +3548,9 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} @@ -3129,6 +3574,10 @@ packages: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} + strip-hex-prefix@1.0.0: + resolution: {integrity: sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A==} + engines: {node: '>=6.5.0', npm: '>=3'} + strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} @@ -3164,6 +3613,10 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + synckit@0.8.8: + resolution: {integrity: sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==} + engines: {node: ^14.18.0 || >=16.0.0} + telegraf@4.16.3: resolution: {integrity: sha512-yjEu2NwkHlXu0OARWoNhJlIjX09dRktiMQFsM678BAH/PEPVwctzL67+tvXqLCRQQvm3SDtki2saGO9hLlz68w==} engines: {node: ^12.20.0 || >=14.13.1} @@ -3192,6 +3645,14 @@ packages: through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + timed-out@3.1.3: + resolution: {integrity: sha512-3RB4qgvPkxF/FGPnrzaWLhW1rxNK2sdH0mFjbhxkfTR6QXvcM3EtYm9L44UrhODZrZ+yhDXeMncLqi8QXn2MJg==} + engines: {node: '>=0.10.0'} + + tmp@0.0.28: + resolution: {integrity: sha512-c2mmfiBmND6SOVxzogm1oda0OJ1HZVIk/5n26N59dDTh80MUeavpiCls4PGAdkX1PFkKokLpcf7prSjCeXLsJg==} + engines: {node: '>=0.4.0'} + tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} @@ -3213,6 +3674,10 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true + treeify@1.1.0: + resolution: {integrity: sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==} + engines: {node: '>=0.6'} + triple-beam@1.4.1: resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} engines: {node: '>= 14.0.0'} @@ -3374,17 +3839,34 @@ packages: typeorm-aurora-data-api-driver: optional: true + typescript-collections@1.3.3: + resolution: {integrity: sha512-7sI4e/bZijOzyURng88oOFZCISQPTHozfE2sUu5AviFYk5QV7fYGb6YiDl+vKjF/pICA354JImBImL9XJWUvdQ==} + typescript@5.4.5: resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} engines: {node: '>=14.17'} hasBin: true + typpy@2.3.13: + resolution: {integrity: sha512-vOxIcQz9sxHi+rT09SJ5aDgVgrPppQjwnnayTrMye1ODaU8gIZTDM19t9TxmEElbMihx2Nq/0/b/MtyKfayRqA==} + + ul@5.2.15: + resolution: {integrity: sha512-svLEUy8xSCip5IWnsRa0UOg+2zP0Wsj4qlbjTmX6GJSmvKMHADBuHOm1dpNkWqWPIGuVSqzUkV3Cris5JrlTRQ==} + unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici@6.19.8: + resolution: {integrity: sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==} + engines: {node: '>=18.17'} + + unzip-response@1.0.2: + resolution: {integrity: sha512-pwCcjjhEcpW45JZIySExBHYv5Y9EeL2OIGEfrSKp2dMUFGFv4CpvZkwJbVge8OvGH2BNNtJBx67DuKuJhf+N5Q==} + engines: {node: '>=0.10'} + update-browserslist-db@1.0.15: resolution: {integrity: sha512-K9HWH62x3/EalU1U6sjSZiylm9C8tgq2mSvshZpqc7QE69RaA2qjhkW2HlNA0tFpEbtyFz7HTqbSdN4MSwUodA==} hasBin: true @@ -3394,13 +3876,23 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + url-parse-lax@1.0.0: + resolution: {integrity: sha512-BVA4lR5PIviy2PMseNd2jbFQ+jwSwQGdJejf5ctd1rEXt0Ypd7yanUK9+lYechVlN5VaTJGsu2U/3MDDu6KgBA==} + engines: {node: '>=0.10.0'} + utf-8-validate@5.0.10: resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==} engines: {node: '>=6.14.2'} + utf8@3.0.0: + resolution: {integrity: sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==} + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + util@0.12.5: + resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + utila@0.4.0: resolution: {integrity: sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==} @@ -3419,9 +3911,19 @@ packages: resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} engines: {node: '>=10.12.0'} + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + + w-json@1.3.10: + resolution: {integrity: sha512-XadVyw0xE+oZ5FGApXsdswv96rOhStzKqL53uSe5UaTadABGkWIg1+DTx8kiZ/VqTZTBneoL0l65RcPe4W3ecw==} + walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + web3-utils@1.10.4: + resolution: {integrity: sha512-tsu8FiKJLk2PzhDl9fXbGUWTkkVXYhtTA+SmEFkKft+9BgwLxfCRpU96sWv7ICC8zixBNd3JURVoiR3dUXgP8A==} + engines: {node: '>=8.0.0'} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -3799,6 +4301,14 @@ snapshots: '@eslint/js@8.57.0': {} + '@ethereumjs/rlp@4.0.1': {} + + '@ethereumjs/util@8.1.0': + dependencies: + '@ethereumjs/rlp': 4.0.1 + ethereum-cryptography: 2.2.1 + micro-ftch: 0.3.1 + '@faker-js/faker@8.4.1': {} '@humanwhocodes/config-array@0.11.14': @@ -4016,67 +4526,204 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 - '@noble/curves@1.4.0': + '@metaplex-foundation/beet-solana@0.4.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)': dependencies: - '@noble/hashes': 1.4.0 + '@metaplex-foundation/beet': 0.7.2 + '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) + bs58: 5.0.0 + debug: 4.3.4 + transitivePeerDependencies: + - bufferutil + - encoding + - supports-color + - utf-8-validate - '@noble/curves@1.6.0': + '@metaplex-foundation/beet@0.7.2': dependencies: - '@noble/hashes': 1.5.0 + ansicolors: 0.3.2 + assert: 2.1.0 + bn.js: 5.2.1 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color - '@noble/hashes@1.4.0': {} + '@metaplex-foundation/digital-asset-standard-api@1.0.4(@metaplex-foundation/umi@0.9.2)': + dependencies: + '@metaplex-foundation/umi': 0.9.2 + package.json: 2.0.1 - '@noble/hashes@1.5.0': {} + '@metaplex-foundation/mpl-bubblegum@4.2.1(@metaplex-foundation/umi@0.9.2)': + dependencies: + '@metaplex-foundation/digital-asset-standard-api': 1.0.4(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/mpl-token-metadata': 3.2.1(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/mpl-toolbox': 0.9.4(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi': 0.9.2 + '@noble/hashes': 1.5.0 + merkletreejs: 0.3.11 - '@nodelib/fs.scandir@2.1.5': + '@metaplex-foundation/mpl-token-metadata@3.2.1(@metaplex-foundation/umi@0.9.2)': dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 + '@metaplex-foundation/mpl-toolbox': 0.9.4(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi': 0.9.2 - '@nodelib/fs.stat@2.0.5': {} + '@metaplex-foundation/mpl-toolbox@0.9.4(@metaplex-foundation/umi@0.9.2)': + dependencies: + '@metaplex-foundation/umi': 0.9.2 - '@nodelib/fs.walk@1.2.8': + '@metaplex-foundation/umi-bundle-defaults@0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))': dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.17.1 + '@metaplex-foundation/umi': 0.9.2 + '@metaplex-foundation/umi-downloader-http': 0.9.2(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi-eddsa-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + '@metaplex-foundation/umi-http-fetch': 0.9.2(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi-program-repository': 0.9.2(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi-rpc-chunk-get-accounts': 0.9.2(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi-rpc-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + '@metaplex-foundation/umi-serializer-data-view': 0.9.2(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi-transaction-factory-web3js': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - encoding - '@opentelemetry/api-logs@0.51.1': + '@metaplex-foundation/umi-downloader-http@0.9.2(@metaplex-foundation/umi@0.9.2)': dependencies: - '@opentelemetry/api': 1.9.0 + '@metaplex-foundation/umi': 0.9.2 - '@opentelemetry/api-logs@0.52.0': + '@metaplex-foundation/umi-eddsa-web3js@0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))': dependencies: - '@opentelemetry/api': 1.9.0 + '@metaplex-foundation/umi': 0.9.2 + '@metaplex-foundation/umi-web3js-adapters': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + '@noble/curves': 1.6.0 + '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@opentelemetry/api-logs@0.52.1': + '@metaplex-foundation/umi-http-fetch@0.9.2(@metaplex-foundation/umi@0.9.2)': dependencies: - '@opentelemetry/api': 1.9.0 + '@metaplex-foundation/umi': 0.9.2 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding - '@opentelemetry/api@1.9.0': {} + '@metaplex-foundation/umi-options@0.8.9': {} - '@opentelemetry/context-async-hooks@1.25.1(@opentelemetry/api@1.9.0)': + '@metaplex-foundation/umi-program-repository@0.9.2(@metaplex-foundation/umi@0.9.2)': dependencies: - '@opentelemetry/api': 1.9.0 + '@metaplex-foundation/umi': 0.9.2 - '@opentelemetry/core@1.25.0(@opentelemetry/api@1.9.0)': + '@metaplex-foundation/umi-public-keys@0.8.9': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/semantic-conventions': 1.25.0 + '@metaplex-foundation/umi-serializers-encodings': 0.8.9 - '@opentelemetry/core@1.25.1(@opentelemetry/api@1.9.0)': + '@metaplex-foundation/umi-rpc-chunk-get-accounts@0.9.2(@metaplex-foundation/umi@0.9.2)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/semantic-conventions': 1.25.1 + '@metaplex-foundation/umi': 0.9.2 - '@opentelemetry/instrumentation-connect@0.37.0(@opentelemetry/api@1.9.0)': + '@metaplex-foundation/umi-rpc-web3js@0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.25.1 - '@types/connect': 3.4.36 - transitivePeerDependencies: - - supports-color + '@metaplex-foundation/umi': 0.9.2 + '@metaplex-foundation/umi-web3js-adapters': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) + + '@metaplex-foundation/umi-serializer-data-view@0.9.2(@metaplex-foundation/umi@0.9.2)': + dependencies: + '@metaplex-foundation/umi': 0.9.2 + + '@metaplex-foundation/umi-serializers-core@0.8.9': {} + + '@metaplex-foundation/umi-serializers-encodings@0.8.9': + dependencies: + '@metaplex-foundation/umi-serializers-core': 0.8.9 + + '@metaplex-foundation/umi-serializers-numbers@0.8.9': + dependencies: + '@metaplex-foundation/umi-serializers-core': 0.8.9 + + '@metaplex-foundation/umi-serializers@0.9.0': + dependencies: + '@metaplex-foundation/umi-options': 0.8.9 + '@metaplex-foundation/umi-public-keys': 0.8.9 + '@metaplex-foundation/umi-serializers-core': 0.8.9 + '@metaplex-foundation/umi-serializers-encodings': 0.8.9 + '@metaplex-foundation/umi-serializers-numbers': 0.8.9 + + '@metaplex-foundation/umi-transaction-factory-web3js@0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))': + dependencies: + '@metaplex-foundation/umi': 0.9.2 + '@metaplex-foundation/umi-web3js-adapters': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) + + '@metaplex-foundation/umi-web3js-adapters@0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))': + dependencies: + '@metaplex-foundation/umi': 0.9.2 + '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) + buffer: 6.0.3 + + '@metaplex-foundation/umi@0.9.2': + dependencies: + '@metaplex-foundation/umi-options': 0.8.9 + '@metaplex-foundation/umi-public-keys': 0.8.9 + '@metaplex-foundation/umi-serializers': 0.9.0 + + '@noble/curves@1.4.2': + dependencies: + '@noble/hashes': 1.4.0 + + '@noble/curves@1.6.0': + dependencies: + '@noble/hashes': 1.5.0 + + '@noble/hashes@1.4.0': {} + + '@noble/hashes@1.5.0': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@opentelemetry/api-logs@0.51.1': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/api-logs@0.52.0': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/api-logs@0.52.1': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/api@1.9.0': {} + + '@opentelemetry/context-async-hooks@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/core@1.25.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.25.0 + + '@opentelemetry/core@1.25.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.25.1 + + '@opentelemetry/instrumentation-connect@0.37.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.25.1 + '@types/connect': 3.4.36 + transitivePeerDependencies: + - supports-color '@opentelemetry/instrumentation-express@0.40.1(@opentelemetry/api@1.9.0)': dependencies: @@ -4288,6 +4935,8 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@pkgr/core@0.1.1': {} + '@prisma/instrumentation@5.15.0': dependencies: '@opentelemetry/api': 1.9.0 @@ -4302,6 +4951,19 @@ snapshots: bn.js: 5.2.1 buffer-layout: 1.2.2 + '@scure/base@1.1.8': {} + + '@scure/bip32@1.4.0': + dependencies: + '@noble/curves': 1.4.2 + '@noble/hashes': 1.4.0 + '@scure/base': 1.1.8 + + '@scure/bip39@1.3.0': + dependencies: + '@noble/hashes': 1.4.0 + '@scure/base': 1.1.8 + '@sentry-internal/tracing@7.114.0': dependencies: '@sentry/core': 7.114.0 @@ -4507,13 +5169,20 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder - '@solana/spl-token-group@0.0.4(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)': + '@solana/spl-account-compression@0.2.0(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(utf-8-validate@5.0.10)': dependencies: - '@solana/codecs': 2.0.0-preview.2(fastestsmallesttextencoderdecoder@1.0.22) - '@solana/spl-type-length-value': 0.1.0 + '@metaplex-foundation/beet': 0.7.2 + '@metaplex-foundation/beet-solana': 0.4.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) + bn.js: 5.2.1 + borsh: 0.7.0 + js-sha3: 0.8.0 + typescript-collections: 1.3.3 transitivePeerDependencies: - - fastestsmallesttextencoderdecoder + - bufferutil + - encoding + - supports-color + - utf-8-validate '@solana/spl-token-group@0.0.5(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)': dependencies: @@ -4545,20 +5214,6 @@ snapshots: - fastestsmallesttextencoderdecoder - utf-8-validate - '@solana/spl-token@0.4.6(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10)': - dependencies: - '@solana/buffer-layout': 4.0.1 - '@solana/buffer-layout-utils': 0.2.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@solana/spl-token-group': 0.0.4(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22) - '@solana/spl-token-metadata': 0.1.4(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22) - '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) - buffer: 6.0.3 - transitivePeerDependencies: - - bufferutil - - encoding - - fastestsmallesttextencoderdecoder - - utf-8-validate - '@solana/spl-token@0.4.8(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10)': dependencies: '@solana/buffer-layout': 4.0.1 @@ -4629,7 +5284,7 @@ snapshots: '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) '@staratlas/anchor': 0.25.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@staratlas/data-source': 0.7.4(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) + '@staratlas/data-source': 0.7.7(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) '@staratlas/player-profile': 0.9.1(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) '@staratlas/profile-vault': 0.9.1(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) '@types/node': 20.12.8 @@ -4657,12 +5312,12 @@ snapshots: - typescript - utf-8-validate - '@staratlas/claim-stake@0.11.5(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10)': + '@staratlas/claim-stake@0.11.6(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10)': dependencies: '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) '@staratlas/anchor': 0.25.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@staratlas/data-source': 0.7.5(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) + '@staratlas/data-source': 0.7.7(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - encoding @@ -4685,23 +5340,30 @@ snapshots: - typescript - utf-8-validate - '@staratlas/data-source@0.7.4(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10)': + '@staratlas/crew@0.7.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10)': dependencies: - '@noble/curves': 1.4.0 - '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) + '@metaplex-foundation/mpl-bubblegum': 4.2.1(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/mpl-token-metadata': 3.2.1(@metaplex-foundation/umi@0.9.2) + '@metaplex-foundation/umi': 0.9.2 + '@metaplex-foundation/umi-bundle-defaults': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + '@metaplex-foundation/umi-web3js-adapters': 0.9.2(@metaplex-foundation/umi@0.9.2)(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + '@project-serum/anchor': '@staratlas/anchor@0.25.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)' + '@solana/spl-account-compression': 0.2.0(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@solana/spl-token': 0.4.8(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) '@staratlas/anchor': 0.25.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - bs58: 5.0.0 - camelcase: 7.0.1 - lodash: 4.17.21 - neverthrow: 6.2.1 + '@staratlas/data-source': 0.8.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) + '@staratlas/player-profile': 0.11.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) + '@staratlas/profile-faction': 0.6.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - encoding - fastestsmallesttextencoderdecoder + - supports-color + - typescript - utf-8-validate - '@staratlas/data-source@0.7.5(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10)': + '@staratlas/data-source@0.7.7(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10)': dependencies: '@noble/curves': 1.6.0 '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) @@ -4734,10 +5396,10 @@ snapshots: - typescript - utf-8-validate - '@staratlas/factory@0.7.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10)': + '@staratlas/factory@0.7.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10)': dependencies: '@coral-xyz/anchor': 0.29.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@solana/spl-token': 0.4.6(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) + '@solana/spl-token': 0.4.8(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) big.js: 6.2.1 lodash: 4.17.21 @@ -4747,6 +5409,7 @@ snapshots: - bufferutil - encoding - fastestsmallesttextencoderdecoder + - typescript - utf-8-validate '@staratlas/player-profile@0.11.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10)': @@ -4765,24 +5428,41 @@ snapshots: dependencies: '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) '@staratlas/anchor': 0.25.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@staratlas/data-source': 0.7.4(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) + '@staratlas/data-source': 0.7.7(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - encoding - fastestsmallesttextencoderdecoder - utf-8-validate - '@staratlas/points@1.0.4(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10)': + '@staratlas/points-store@1.1.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10)': dependencies: - '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) + '@solana/spl-token': 0.4.8(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) '@staratlas/anchor': 0.25.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@staratlas/data-source': 0.7.5(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) - '@staratlas/player-profile': 0.9.1(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) + '@staratlas/data-source': 0.8.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) + '@staratlas/player-profile': 0.11.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) + '@staratlas/points': 1.1.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) + '@staratlas/profile-faction': 0.6.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - encoding - fastestsmallesttextencoderdecoder + - typescript + - utf-8-validate + + '@staratlas/points@1.1.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10)': + dependencies: + '@solana/spl-token': 0.4.8(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) + '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@staratlas/anchor': 0.25.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@staratlas/data-source': 0.8.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) + '@staratlas/player-profile': 0.11.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bufferutil + - encoding + - fastestsmallesttextencoderdecoder + - typescript - utf-8-validate '@staratlas/profile-faction@0.4.1(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10)': @@ -4790,7 +5470,7 @@ snapshots: '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) '@staratlas/anchor': 0.25.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@staratlas/data-source': 0.7.4(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) + '@staratlas/data-source': 0.7.7(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) '@staratlas/player-profile': 0.9.1(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil @@ -4798,12 +5478,26 @@ snapshots: - fastestsmallesttextencoderdecoder - utf-8-validate + '@staratlas/profile-faction@0.6.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10)': + dependencies: + '@solana/spl-token': 0.4.8(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) + '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@staratlas/anchor': 0.25.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@staratlas/data-source': 0.8.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) + '@staratlas/player-profile': 0.11.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bufferutil + - encoding + - fastestsmallesttextencoderdecoder + - typescript + - utf-8-validate + '@staratlas/profile-vault@0.9.1(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10)': dependencies: '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) '@staratlas/anchor': 0.25.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@staratlas/data-source': 0.7.5(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) + '@staratlas/data-source': 0.7.7(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) '@staratlas/player-profile': 0.9.1(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil @@ -4811,23 +5505,26 @@ snapshots: - fastestsmallesttextencoderdecoder - utf-8-validate - '@staratlas/sage@1.0.2(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10)': + '@staratlas/sage@1.6.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10)': dependencies: - '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) + '@solana/spl-token': 0.4.8(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) '@staratlas/anchor': 0.25.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) '@staratlas/cargo': 1.1.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) '@staratlas/crafting': 1.1.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) - '@staratlas/data-source': 0.7.4(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) - '@staratlas/player-profile': 0.9.1(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) - '@staratlas/points': 1.0.4(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) - '@staratlas/profile-faction': 0.4.1(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) + '@staratlas/crew': 0.7.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) + '@staratlas/data-source': 0.8.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) + '@staratlas/player-profile': 0.11.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) + '@staratlas/points': 1.1.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) + '@staratlas/points-store': 1.1.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) + '@staratlas/profile-faction': 0.6.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.4.5)(utf-8-validate@5.0.10) '@types/lodash': 4.17.0 lodash: 4.17.21 transitivePeerDependencies: - bufferutil - encoding - fastestsmallesttextencoderdecoder + - supports-color - typescript - utf-8-validate @@ -4886,6 +5583,8 @@ snapshots: '@types/node': 20.12.8 base-x: 3.0.9 + '@types/chance@1.1.6': {} + '@types/connect@3.4.36': dependencies: '@types/node': 20.12.11 @@ -4941,6 +5640,10 @@ snapshots: '@types/keygrip@1.0.6': {} + '@types/keyv@3.1.4': + dependencies: + '@types/node': 20.12.11 + '@types/koa-compose@3.2.8': dependencies: '@types/koa': 2.14.0 @@ -5002,6 +5705,10 @@ snapshots: '@types/range-parser@1.2.7': {} + '@types/responselike@1.0.3': + dependencies: + '@types/node': 20.12.11 + '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 @@ -5137,6 +5844,10 @@ snapshots: dependencies: event-target-shim: 5.0.1 + abs@1.3.14: + dependencies: + ul: 5.2.15 + acorn-import-assertions@1.9.0(acorn@8.11.3): dependencies: acorn: 8.11.3 @@ -5185,6 +5896,8 @@ snapshots: ansi-styles@6.2.1: {} + ansicolors@0.3.2: {} + any-promise@1.3.0: {} anymatch@3.1.3: @@ -5254,6 +5967,14 @@ snapshots: asap@2.0.6: {} + assert@2.1.0: + dependencies: + call-bind: 1.0.7 + is-nan: 1.3.2 + object-is: 1.1.6 + object.assign: 4.1.5 + util: 0.12.5 + async@3.2.5: {} asynckit@0.4.0: {} @@ -5352,6 +6073,8 @@ snapshots: dependencies: '@noble/hashes': 1.4.0 + bn.js@4.11.6: {} + bn.js@5.2.1: {} boolbase@1.0.0: {} @@ -5415,6 +6138,8 @@ snapshots: buffer-layout@1.2.2: {} + buffer-reverse@1.0.1: {} + buffer@6.0.3: dependencies: base64-js: 1.5.1 @@ -5445,6 +6170,8 @@ snapshots: caniuse-lite@1.0.30001617: {} + capture-stack-trace@1.0.2: {} + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -5458,6 +6185,8 @@ snapshots: chalk@5.3.0: {} + chance@1.1.12: {} + char-regex@1.0.2: {} chokidar@3.6.0: @@ -5549,6 +6278,12 @@ snapshots: cookiejar@2.1.4: {} + core-util-is@1.0.3: {} + + create-error-class@3.0.2: + dependencies: + capture-stack-trace: 1.0.2 + create-hash@1.2.0: dependencies: cipher-base: 1.0.4 @@ -5602,6 +6337,8 @@ snapshots: crypto-hash@1.3.0: {} + crypto-js@4.2.0: {} + css-select@4.3.0: dependencies: boolbase: 1.0.0 @@ -5642,10 +6379,16 @@ snapshots: dedent@1.5.3: {} + deep-extend@0.6.0: {} + deep-is@0.1.4: {} deepmerge@4.3.1: {} + deffy@2.2.4: + dependencies: + typpy: 2.3.13 + define-data-property@1.1.4: dependencies: es-define-property: 1.0.0 @@ -5714,6 +6457,10 @@ snapshots: dotenv@16.4.5: {} + duplexer2@0.1.4: + dependencies: + readable-stream: 2.3.8 + dynamic-dedupe@0.3.0: dependencies: xtend: 4.0.2 @@ -5737,6 +6484,10 @@ snapshots: entities@2.2.0: {} + err@1.1.1: + dependencies: + typpy: 2.3.13 + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 @@ -5830,6 +6581,10 @@ snapshots: escape-string-regexp@4.0.0: {} + eslint-config-prettier@9.1.0(eslint@8.57.0): + dependencies: + eslint: 8.57.0 + eslint-import-resolver-node@0.3.9: dependencies: debug: 3.2.7 @@ -5883,6 +6638,15 @@ snapshots: - eslint-import-resolver-webpack - supports-color + eslint-plugin-prettier@5.1.3(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.3.3): + dependencies: + eslint: 8.57.0 + prettier: 3.3.3 + prettier-linter-helpers: 1.0.0 + synckit: 0.8.8 + optionalDependencies: + eslint-config-prettier: 9.1.0(eslint@8.57.0) + eslint-plugin-promise@6.2.0(eslint@8.57.0): dependencies: eslint: 8.57.0 @@ -5957,12 +6721,33 @@ snapshots: esutils@2.0.3: {} + ethereum-bloom-filters@1.2.0: + dependencies: + '@noble/hashes': 1.5.0 + + ethereum-cryptography@2.2.1: + dependencies: + '@noble/curves': 1.4.2 + '@noble/hashes': 1.4.0 + '@scure/bip32': 1.4.0 + '@scure/bip39': 1.3.0 + + ethjs-unit@0.1.6: + dependencies: + bn.js: 4.11.6 + number-to-bn: 1.7.0 + event-target-shim@5.0.1: {} eventemitter3@4.0.7: {} eventemitter3@5.0.1: {} + exec-limiter@3.2.13: + dependencies: + limit-it: 3.2.10 + typpy: 2.3.13 + execa@5.1.1: dependencies: cross-spawn: 7.0.3 @@ -5989,6 +6774,8 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-diff@1.3.0: {} + fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -6077,6 +6864,10 @@ snapshots: function-bind@1.1.2: {} + function.name@1.0.13: + dependencies: + noop6: 1.0.9 + function.prototype.name@1.1.6: dependencies: call-bind: 1.0.7 @@ -6108,6 +6899,31 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.2.4 + git-package-json@1.4.10: + dependencies: + deffy: 2.2.4 + err: 1.1.1 + gry: 5.0.8 + normalize-package-data: 2.5.0 + oargv: 3.4.10 + one-by-one: 3.2.8 + r-json: 1.3.0 + r-package-json: 1.0.9 + tmp: 0.0.28 + + git-source@1.1.10: + dependencies: + git-url-parse: 5.0.1 + + git-up@1.2.1: + dependencies: + is-ssh: 1.4.0 + parse-url: 1.3.11 + + git-url-parse@5.0.1: + dependencies: + git-up: 1.2.1 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -6157,10 +6973,37 @@ snapshots: dependencies: get-intrinsic: 1.2.4 + got@5.7.1: + dependencies: + '@types/keyv': 3.1.4 + '@types/responselike': 1.0.3 + create-error-class: 3.0.2 + duplexer2: 0.1.4 + is-redirect: 1.0.0 + is-retry-allowed: 1.2.0 + is-stream: 1.1.0 + lowercase-keys: 1.0.1 + node-status-codes: 1.0.0 + object-assign: 4.1.1 + parse-json: 2.2.0 + pinkie-promise: 2.0.1 + read-all-stream: 3.1.0 + readable-stream: 2.3.8 + timed-out: 3.1.3 + unzip-response: 1.0.2 + url-parse-lax: 1.0.0 + graceful-fs@4.2.11: {} graphemer@1.4.0: {} + gry@5.0.8: + dependencies: + abs: 1.3.14 + exec-limiter: 3.2.13 + one-by-one: 3.2.8 + ul: 5.2.15 + has-bigints@1.0.2: {} has-flag@3.0.0: {} @@ -6193,6 +7036,8 @@ snapshots: highlight.js@10.7.3: {} + hosted-git-info@2.8.9: {} + html-escaper@2.0.2: {} htmlparser2@6.1.0: @@ -6262,12 +7107,19 @@ snapshots: inherits@2.0.4: {} + ini@1.3.8: {} + internal-slot@1.0.7: dependencies: es-errors: 1.3.0 hasown: 2.0.2 side-channel: 1.0.6 + is-arguments@1.1.1: + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + is-array-buffer@3.0.4: dependencies: call-bind: 1.0.7 @@ -6310,10 +7162,21 @@ snapshots: is-generator-fn@2.1.0: {} + is-generator-function@1.0.10: + dependencies: + has-tostringtag: 1.0.2 + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-hex-prefixed@1.0.0: {} + + is-nan@1.3.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + is-negative-zero@2.0.3: {} is-number-object@1.0.7: @@ -6324,15 +7187,25 @@ snapshots: is-path-inside@3.0.3: {} + is-redirect@1.0.0: {} + is-regex@1.1.4: dependencies: call-bind: 1.0.7 has-tostringtag: 1.0.2 + is-retry-allowed@1.2.0: {} + is-shared-array-buffer@1.0.3: dependencies: call-bind: 1.0.7 + is-ssh@1.4.0: + dependencies: + protocols: 2.0.1 + + is-stream@1.1.0: {} + is-stream@2.0.1: {} is-string@1.0.7: @@ -6351,6 +7224,8 @@ snapshots: dependencies: call-bind: 1.0.7 + isarray@1.0.0: {} + isarray@2.0.5: {} isexe@2.0.0: {} @@ -6400,6 +7275,8 @@ snapshots: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 + iterate-object@1.3.4: {} + jackspeak@2.3.6: dependencies: '@isaacs/cliui': 8.0.2 @@ -6735,6 +7612,8 @@ snapshots: js-sha256@0.9.0: {} + js-sha3@0.8.0: {} + js-tokens@4.0.0: {} js-yaml@3.14.1: @@ -6785,6 +7664,10 @@ snapshots: dependencies: immediate: 3.0.6 + limit-it@3.2.10: + dependencies: + typpy: 2.3.13 + lines-and-columns@1.2.4: {} localforage@1.10.0: @@ -6826,6 +7709,8 @@ snapshots: dependencies: tslib: 2.6.2 + lowercase-keys@1.0.1: {} + lru-cache@10.2.2: {} lru-cache@5.1.1: @@ -6854,8 +7739,18 @@ snapshots: merge2@1.4.1: {} + merkletreejs@0.3.11: + dependencies: + bignumber.js: 9.1.2 + buffer-reverse: 1.0.1 + crypto-js: 4.2.0 + treeify: 1.1.0 + web3-utils: 1.10.4 + methods@1.1.2: {} + micro-ftch@0.3.1: {} + micromatch@4.0.5: dependencies: braces: 3.0.2 @@ -6929,6 +7824,17 @@ snapshots: node-releases@2.0.14: {} + node-status-codes@1.0.0: {} + + noop6@1.0.9: {} + + normalize-package-data@2.5.0: + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.8 + semver: 5.7.2 + validate-npm-package-license: 3.0.4 + normalize-path@3.0.0: {} npm-run-path@4.0.1: @@ -6939,10 +7845,29 @@ snapshots: dependencies: boolbase: 1.0.0 + number-to-bn@1.7.0: + dependencies: + bn.js: 4.11.6 + strip-hex-prefix: 1.0.0 + + oargv@3.4.10: + dependencies: + iterate-object: 1.3.4 + ul: 5.2.15 + + obj-def@1.0.9: + dependencies: + deffy: 2.2.4 + object-assign@4.1.1: {} object-inspect@1.13.1: {} + object-is@1.1.6: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + object-keys@1.1.1: {} object.assign@4.1.5: @@ -6977,6 +7902,11 @@ snapshots: dependencies: wrappy: 1.0.2 + one-by-one@3.2.8: + dependencies: + obj-def: 1.0.9 + sliced: 1.0.1 + one-time@1.0.0: dependencies: fn.name: 1.1.0 @@ -7003,6 +7933,8 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + os-tmpdir@1.0.2: {} + p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -7023,12 +7955,33 @@ snapshots: p-try@2.2.0: {} + package-json-path@1.0.9: + dependencies: + abs: 1.3.14 + + package-json@2.4.0: + dependencies: + got: 5.7.1 + registry-auth-token: 3.4.0 + registry-url: 3.1.0 + semver: 5.7.2 + + package.json@2.0.1: + dependencies: + git-package-json: 1.4.10 + git-source: 1.1.10 + package-json: 2.4.0 + pako@2.1.0: {} parent-module@1.0.1: dependencies: callsites: 3.1.0 + parse-json@2.2.0: + dependencies: + error-ex: 1.3.2 + parse-json@5.2.0: dependencies: '@babel/code-frame': 7.24.2 @@ -7036,6 +7989,11 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse-url@1.3.11: + dependencies: + is-ssh: 1.4.0 + protocols: 1.4.8 + parse5-htmlparser2-tree-adapter@6.0.1: dependencies: parse5: 6.0.1 @@ -7110,6 +8068,12 @@ snapshots: picomatch@2.3.1: {} + pinkie-promise@2.0.1: + dependencies: + pinkie: 2.0.4 + + pinkie@2.0.4: {} + pirates@4.0.6: {} pkg-dir@4.2.0: @@ -7142,6 +8106,14 @@ snapshots: prelude-ls@1.2.1: {} + prepend-http@1.0.4: {} + + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + + prettier@3.3.3: {} + pretty-error@4.0.0: dependencies: lodash: 4.17.21 @@ -7153,11 +8125,17 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 + process-nextick-args@2.0.1: {} + prompts@2.4.2: dependencies: kleur: 3.0.3 sisteransi: 1.0.5 + protocols@1.4.8: {} + + protocols@2.0.1: {} + proxy-from-env@1.1.0: {} punycode@2.3.1: {} @@ -7170,8 +8148,43 @@ snapshots: queue-microtask@1.2.3: {} + r-json@1.3.0: + dependencies: + w-json: 1.3.10 + + r-package-json@1.0.9: + dependencies: + package-json-path: 1.0.9 + r-json: 1.3.0 + + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + react-is@18.3.1: {} + read-all-stream@3.1.0: + dependencies: + pinkie-promise: 2.0.1 + readable-stream: 2.3.8 + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + readable-stream@3.6.2: dependencies: inherits: 2.0.4 @@ -7193,6 +8206,15 @@ snapshots: es-errors: 1.3.0 set-function-name: 2.0.2 + registry-auth-token@3.4.0: + dependencies: + rc: 1.2.8 + safe-buffer: 5.2.1 + + registry-url@3.1.0: + dependencies: + rc: 1.2.8 + renderkid@3.0.0: dependencies: css-select: 4.3.0 @@ -7266,6 +8288,8 @@ snapshots: has-symbols: 1.0.3 isarray: 2.0.5 + safe-buffer@5.1.2: {} + safe-buffer@5.2.1: {} safe-compare@1.1.4: @@ -7282,6 +8306,8 @@ snapshots: sandwich-stream@2.0.2: {} + semver@5.7.2: {} + semver@6.3.1: {} semver@7.6.2: {} @@ -7334,6 +8360,8 @@ snapshots: slash@3.0.0: {} + sliced@1.0.1: {} + snake-case@3.0.4: dependencies: dot-case: 3.0.4 @@ -7351,6 +8379,20 @@ snapshots: source-map@0.6.1: {} + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.20 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.20 + + spdx-license-ids@3.0.20: {} + split2@4.2.0: {} sprintf-js@1.0.3: {} @@ -7397,6 +8439,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.0.0 + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 @@ -7415,6 +8461,10 @@ snapshots: strip-final-newline@2.0.0: {} + strip-hex-prefix@1.0.0: + dependencies: + is-hex-prefixed: 1.0.0 + strip-json-comments@2.0.1: {} strip-json-comments@3.1.1: {} @@ -7451,6 +8501,11 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + synckit@0.8.8: + dependencies: + '@pkgr/core': 0.1.1 + tslib: 2.6.2 + telegraf@4.16.3: dependencies: '@telegraf/types': 7.1.0 @@ -7487,6 +8542,12 @@ snapshots: through@2.3.8: {} + timed-out@3.1.3: {} + + tmp@0.0.28: + dependencies: + os-tmpdir: 1.0.2 + tmpl@1.0.5: {} to-fast-properties@2.0.0: {} @@ -7501,6 +8562,8 @@ snapshots: tree-kill@1.2.2: {} + treeify@1.1.0: {} + triple-beam@1.4.1: {} ts-api-utils@1.3.0(typescript@5.4.5): @@ -7644,8 +8707,19 @@ snapshots: transitivePeerDependencies: - supports-color + typescript-collections@1.3.3: {} + typescript@5.4.5: {} + typpy@2.3.13: + dependencies: + function.name: 1.0.13 + + ul@5.2.15: + dependencies: + deffy: 2.2.4 + typpy: 2.3.13 + unbox-primitive@1.0.2: dependencies: call-bind: 1.0.7 @@ -7655,6 +8729,10 @@ snapshots: undici-types@5.26.5: {} + undici@6.19.8: {} + + unzip-response@1.0.2: {} + update-browserslist-db@1.0.15(browserslist@4.23.0): dependencies: browserslist: 4.23.0 @@ -7665,13 +8743,27 @@ snapshots: dependencies: punycode: 2.3.1 + url-parse-lax@1.0.0: + dependencies: + prepend-http: 1.0.4 + utf-8-validate@5.0.10: dependencies: node-gyp-build: 4.8.1 optional: true + utf8@3.0.0: {} + util-deprecate@1.0.2: {} + util@0.12.5: + dependencies: + inherits: 2.0.4 + is-arguments: 1.1.1 + is-generator-function: 1.0.10 + is-typed-array: 1.1.13 + which-typed-array: 1.1.15 + utila@0.4.0: {} uuid@8.3.2: {} @@ -7686,10 +8778,28 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + + w-json@1.3.10: {} + walker@1.0.8: dependencies: makeerror: 1.0.12 + web3-utils@1.10.4: + dependencies: + '@ethereumjs/util': 8.1.0 + bn.js: 5.2.1 + ethereum-bloom-filters: 1.2.0 + ethereum-cryptography: 2.2.1 + ethjs-unit: 0.1.6 + number-to-bn: 1.7.0 + randombytes: 2.1.0 + utf8: 3.0.0 + webidl-conversions@3.0.1: {} whatwg-url@5.0.0: diff --git a/src/config/config.ts b/src/config/config.ts index 96122668..c72a5a5c 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -15,6 +15,7 @@ export interface Config { user: { keyMode: string mnemonic: string + pubKey: string secretKey: number[] walletId: number address1: string @@ -52,19 +53,23 @@ export const config: Config = { app: { version: env.get('APP_VERSION'), logLevel: env.get('LOG_LEVEL'), - quickstart: env.get('QUICKSTART') === 'true' + quickstart: env.get('QUICKSTART') === 'true', }, bot: { telegramToken: env.get('TELEGRAM_TOKEN'), - owner: env.get('BOT_OWNER') + owner: env.get('BOT_OWNER'), }, user: { keyMode: env.get('KEY_MODE'), - secretKey: env.get('SECRET_KEY').split(',').map(s => Number(s)), + secretKey: env + .get('SECRET_KEY') + .split(',') + .map((s) => Number(s)), + pubKey: env.get('PUBKEY'), mnemonic: env.get('MNEMONIC'), walletId: Number(env.get('WALLET_ID')), address1: env.get('BOT_ADDRESS_1'), - address2: env.get('BOT_ADDRESS_2') + address2: env.get('BOT_ADDRESS_2'), }, db: { host: env.get('DATABASE_HOST'), @@ -72,7 +77,7 @@ export const config: Config = { username: env.get('DATABASE_USER'), password: env.get('DATABASE_PASSWORD'), database: env.get('DATABASE_NAME'), - logging: env.get('DATABASE_LOGGING') as LoggerOptions + logging: env.get('DATABASE_LOGGING') as LoggerOptions, }, sol: { rpcEndpoint: env.get('RPC_ENDPOINT'), @@ -83,11 +88,11 @@ export const config: Config = { toolMint: env.get('TOOL_MINT'), foodMint: env.get('FOOD_MINT'), fuelMint: env.get('FUEL_MINT'), - ammoMint: env.get('AMMO_MINT') + ammoMint: env.get('AMMO_MINT'), }, cron: { refillInterval: env.get('REFILL_INTERVAL'), bookkeeperInterval: env.get('BOOKKEEPER_INTERVAL'), - resourceInterval: env.get('RESOURCE_INTERVAL') - } + resourceInterval: env.get('RESOURCE_INTERVAL'), + }, } diff --git a/src/dayjs.ts b/src/dayjs.ts index 3a4c2a38..90c48db8 100644 --- a/src/dayjs.ts +++ b/src/dayjs.ts @@ -1,7 +1,7 @@ import dayjs from 'dayjs' +import advancedFormat from 'dayjs/plugin/advancedFormat' import customParseFormat from 'dayjs/plugin/customParseFormat' import duration from 'dayjs/plugin/duration' -import advancedFormat from 'dayjs/plugin/advancedFormat' import minMax from 'dayjs/plugin/minMax' import relativeTime from 'dayjs/plugin/relativeTime' import utc from 'dayjs/plugin/utc' @@ -9,6 +9,8 @@ import utc from 'dayjs/plugin/utc' export { Dayjs } from 'dayjs' export { Duration } from 'dayjs/plugin/duration' +export const now = (): dayjs.Dayjs => dayjs() + dayjs.extend(customParseFormat) dayjs.extend(advancedFormat) dayjs.extend(duration) diff --git a/src/db/columns/relation-id.ts b/src/db/columns/relation-id.ts index 3ec56419..f9c5a347 100644 --- a/src/db/columns/relation-id.ts +++ b/src/db/columns/relation-id.ts @@ -1,3 +1,5 @@ import { Column, ColumnOptions } from 'typeorm' -export const RelationIdColumn = (options: ColumnOptions = {}): ReturnType => Column(options) +export const RelationIdColumn = ( + options: ColumnOptions = {}, +): ReturnType => Column(options) diff --git a/src/db/db-config.ts b/src/db/db-config.ts index ebf2c1ab..303174d2 100644 --- a/src/db/db-config.ts +++ b/src/db/db-config.ts @@ -9,7 +9,9 @@ const dbConfig: DataSourceOptions = { synchronize: false, entities: [`${__dirname}/entities/**/!(*test).{ts,js}`], migrations: [`${__dirname}/migrations/**/*.{ts,js}`], - logging: ((config.db.logging || 'all') as string).split(',').map(l => l.trim()) as LoggerOptions + logging: ((config.db.logging || 'all') as string) + .split(',') + .map((l) => l.trim()) as LoggerOptions, } export default dbConfig diff --git a/src/db/db.ts b/src/db/db.ts index 121262b4..b5ab2aaa 100644 --- a/src/db/db.ts +++ b/src/db/db.ts @@ -11,7 +11,10 @@ export let dataSource: DataSource export const connect = async (logging?: LoggerOptions): Promise => { logger.info(`Attempting db connection for ${config.db.database}`) - dataSource = new DataSource({ ...dbConfig, logging: logging || dbConfig.logging }) + dataSource = new DataSource({ + ...dbConfig, + logging: logging || dbConfig.logging, + }) await dataSource.initialize() diff --git a/src/db/entities/bonus.ts b/src/db/entities/bonus.ts index 63c75e6c..8438b51e 100644 --- a/src/db/entities/bonus.ts +++ b/src/db/entities/bonus.ts @@ -1,4 +1,10 @@ -import { BaseEntity, Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm' +import { + BaseEntity, + Column, + Entity, + ManyToOne, + PrimaryGeneratedColumn, +} from 'typeorm' import { RelationIdColumn } from '../columns' @@ -21,6 +27,8 @@ export class Bonus extends BaseEntity { @RelationIdColumn() walletPublicKey: Wallet['publicKey'] - @ManyToOne(() => Wallet, wallet => wallet.transactions, { onDelete: 'CASCADE' }) + @ManyToOne(() => Wallet, (wallet) => wallet.transactions, { + onDelete: 'CASCADE', + }) wallet: Wallet } diff --git a/src/db/entities/refill.ts b/src/db/entities/refill.ts index ae24a449..2b4e1eda 100644 --- a/src/db/entities/refill.ts +++ b/src/db/entities/refill.ts @@ -42,6 +42,8 @@ export class Refill extends BaseEntity { @RelationIdColumn() walletPublicKey: Wallet['publicKey'] - @ManyToOne(() => Wallet, wallet => wallet.refills, { onDelete: 'CASCADE' }) + @ManyToOne(() => Wallet, (wallet) => wallet.refills, { + onDelete: 'CASCADE', + }) wallet: Wallet } diff --git a/src/db/entities/ship-info.ts b/src/db/entities/ship-info.ts index 2a052ccd..ebf43c25 100644 --- a/src/db/entities/ship-info.ts +++ b/src/db/entities/ship-info.ts @@ -1,4 +1,11 @@ -import { BaseEntity, Column, CreateDateColumn, Entity, PrimaryColumn, UpdateDateColumn } from 'typeorm' +import { + BaseEntity, + Column, + CreateDateColumn, + Entity, + PrimaryColumn, + UpdateDateColumn, +} from 'typeorm' @Entity() export class ShipInfo extends BaseEntity { diff --git a/src/db/entities/transaction.ts b/src/db/entities/transaction.ts index 4bdc2c37..4e65de9c 100644 --- a/src/db/entities/transaction.ts +++ b/src/db/entities/transaction.ts @@ -1,4 +1,12 @@ -import { BaseEntity, Column, Entity, Index, ManyToOne, PrimaryGeneratedColumn, Unique } from 'typeorm' +import { + BaseEntity, + Column, + Entity, + Index, + ManyToOne, + PrimaryGeneratedColumn, + Unique, +} from 'typeorm' import { RelationIdColumn } from '../columns' @@ -29,6 +37,8 @@ export class Transaction extends BaseEntity { @RelationIdColumn() walletPublicKey: Wallet['publicKey'] - @ManyToOne(() => Wallet, wallet => wallet.transactions, { onDelete: 'CASCADE' }) + @ManyToOne(() => Wallet, (wallet) => wallet.transactions, { + onDelete: 'CASCADE', + }) wallet: Wallet } diff --git a/src/db/entities/wallet.ts b/src/db/entities/wallet.ts index 84811a94..fe2d8064 100644 --- a/src/db/entities/wallet.ts +++ b/src/db/entities/wallet.ts @@ -1,5 +1,14 @@ import Big from 'big.js' -import { BaseEntity, Column, CreateDateColumn, Entity, Index, OneToMany, PrimaryColumn, UpdateDateColumn } from 'typeorm' +import { + BaseEntity, + Column, + CreateDateColumn, + Entity, + Index, + OneToMany, + PrimaryColumn, + UpdateDateColumn, +} from 'typeorm' import { Bonus } from './bonus' import { Refill } from './refill' @@ -46,24 +55,39 @@ export class Wallet extends BaseEntity { @Column({ default: true }) notify: boolean - @OneToMany(() => Transaction, transaction => transaction.wallet) + @OneToMany(() => Transaction, (transaction) => transaction.wallet) transactions: Promise - @OneToMany(() => Bonus, bonus => bonus.wallet) + @OneToMany(() => Bonus, (bonus) => bonus.wallet) bonuses: Promise - @OneToMany(() => Refill, refill => refill.wallet) + @OneToMany(() => Refill, (refill) => refill.wallet) refills: Promise - async getBalance (): Promise { - const deposit = (await this.transactions).reduce((acc, cur) => acc.add(cur.amount), Big(0)) - const bonus = (await this.bonuses).reduce((acc, cur) => acc.add(cur.amount), Big(0)) - const spent = (await this.refills).reduce((acc, cur) => acc.add(cur.price), Big(0)) - - return deposit.add(bonus).minus(spent).minus(await this.totalTipped()) + async getBalance(): Promise { + const deposit = (await this.transactions).reduce( + (acc, cur) => acc.add(cur.amount), + Big(0), + ) + const bonus = (await this.bonuses).reduce( + (acc, cur) => acc.add(cur.amount), + Big(0), + ) + const spent = (await this.refills).reduce( + (acc, cur) => acc.add(cur.price), + Big(0), + ) + + return deposit + .add(bonus) + .minus(spent) + .minus(await this.totalTipped()) } - async totalTipped (): Promise { - return (await this.refills).reduce((acc, cur) => acc.add(Big(cur.price).mul(cur.tip)), Big(0)) + async totalTipped(): Promise { + return (await this.refills).reduce( + (acc, cur) => acc.add(Big(cur.price).mul(cur.tip)), + Big(0), + ) } } diff --git a/src/db/migrations/1665261222505-init.ts b/src/db/migrations/1665261222505-init.ts index b2fad742..ad95d31a 100644 --- a/src/db/migrations/1665261222505-init.ts +++ b/src/db/migrations/1665261222505-init.ts @@ -1,19 +1,37 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class Init1665261222505 implements MigrationInterface { - public async up (queryRunner: QueryRunner): Promise { - await queryRunner.query('CREATE TABLE "refill" ("signature" character varying NOT NULL, "time" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "fleet" character varying NOT NULL, "price" double precision NOT NULL, "food" integer NOT NULL, "tool" integer NOT NULL, "fuel" integer NOT NULL, "ammo" integer NOT NULL, "walletPublicKey" character varying, CONSTRAINT "PK_3e9de3152e2613e44bee3cc366a" PRIMARY KEY ("signature"))') - await queryRunner.query('CREATE TABLE "transaction" ("signature" character varying NOT NULL, "time" TIMESTAMP WITH TIME ZONE NOT NULL, "amount" double precision NOT NULL, "walletPublicKey" character varying, CONSTRAINT "PK_504a5a02c8957257be1e0c49510" PRIMARY KEY ("signature"))') - await queryRunner.query('CREATE TABLE "wallet" ("publicKey" character varying NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "nextRefill" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), CONSTRAINT "PK_ee618336017c495b4b88f6694e3" PRIMARY KEY ("publicKey"))') - await queryRunner.query('CREATE INDEX "IDX_8aacaefa0dd21764082066fb86" ON "wallet" ("nextRefill") ') - await queryRunner.query('ALTER TABLE "refill" ADD CONSTRAINT "FK_13d26ed77e9d9fd183eceb62599" FOREIGN KEY ("walletPublicKey") REFERENCES "wallet"("publicKey") ON DELETE CASCADE ON UPDATE NO ACTION') - await queryRunner.query('ALTER TABLE "transaction" ADD CONSTRAINT "FK_0c2fe605e9898819004cd7f2113" FOREIGN KEY ("walletPublicKey") REFERENCES "wallet"("publicKey") ON DELETE CASCADE ON UPDATE NO ACTION') + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'CREATE TABLE "refill" ("signature" character varying NOT NULL, "time" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "fleet" character varying NOT NULL, "price" double precision NOT NULL, "food" integer NOT NULL, "tool" integer NOT NULL, "fuel" integer NOT NULL, "ammo" integer NOT NULL, "walletPublicKey" character varying, CONSTRAINT "PK_3e9de3152e2613e44bee3cc366a" PRIMARY KEY ("signature"))', + ) + await queryRunner.query( + 'CREATE TABLE "transaction" ("signature" character varying NOT NULL, "time" TIMESTAMP WITH TIME ZONE NOT NULL, "amount" double precision NOT NULL, "walletPublicKey" character varying, CONSTRAINT "PK_504a5a02c8957257be1e0c49510" PRIMARY KEY ("signature"))', + ) + await queryRunner.query( + 'CREATE TABLE "wallet" ("publicKey" character varying NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "nextRefill" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), CONSTRAINT "PK_ee618336017c495b4b88f6694e3" PRIMARY KEY ("publicKey"))', + ) + await queryRunner.query( + 'CREATE INDEX "IDX_8aacaefa0dd21764082066fb86" ON "wallet" ("nextRefill") ', + ) + await queryRunner.query( + 'ALTER TABLE "refill" ADD CONSTRAINT "FK_13d26ed77e9d9fd183eceb62599" FOREIGN KEY ("walletPublicKey") REFERENCES "wallet"("publicKey") ON DELETE CASCADE ON UPDATE NO ACTION', + ) + await queryRunner.query( + 'ALTER TABLE "transaction" ADD CONSTRAINT "FK_0c2fe605e9898819004cd7f2113" FOREIGN KEY ("walletPublicKey") REFERENCES "wallet"("publicKey") ON DELETE CASCADE ON UPDATE NO ACTION', + ) } - public async down (queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "transaction" DROP CONSTRAINT "FK_0c2fe605e9898819004cd7f2113"') - await queryRunner.query('ALTER TABLE "refill" DROP CONSTRAINT "FK_13d26ed77e9d9fd183eceb62599"') - await queryRunner.query('DROP INDEX "public"."IDX_8aacaefa0dd21764082066fb86"') + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "transaction" DROP CONSTRAINT "FK_0c2fe605e9898819004cd7f2113"', + ) + await queryRunner.query( + 'ALTER TABLE "refill" DROP CONSTRAINT "FK_13d26ed77e9d9fd183eceb62599"', + ) + await queryRunner.query( + 'DROP INDEX "public"."IDX_8aacaefa0dd21764082066fb86"', + ) await queryRunner.query('DROP TABLE "wallet"') await queryRunner.query('DROP TABLE "transaction"') await queryRunner.query('DROP TABLE "refill"') diff --git a/src/db/migrations/1665312398768-refill.ts b/src/db/migrations/1665312398768-refill.ts index 6523b9ba..533dde36 100644 --- a/src/db/migrations/1665312398768-refill.ts +++ b/src/db/migrations/1665312398768-refill.ts @@ -1,15 +1,27 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class Refill1665312398768 implements MigrationInterface { - public async up (queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "refill" DROP CONSTRAINT "FK_13d26ed77e9d9fd183eceb62599"') - await queryRunner.query('ALTER TABLE "refill" ALTER COLUMN "walletPublicKey" SET NOT NULL') - await queryRunner.query('ALTER TABLE "refill" ADD CONSTRAINT "FK_13d26ed77e9d9fd183eceb62599" FOREIGN KEY ("walletPublicKey") REFERENCES "wallet"("publicKey") ON DELETE CASCADE ON UPDATE NO ACTION') + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "refill" DROP CONSTRAINT "FK_13d26ed77e9d9fd183eceb62599"', + ) + await queryRunner.query( + 'ALTER TABLE "refill" ALTER COLUMN "walletPublicKey" SET NOT NULL', + ) + await queryRunner.query( + 'ALTER TABLE "refill" ADD CONSTRAINT "FK_13d26ed77e9d9fd183eceb62599" FOREIGN KEY ("walletPublicKey") REFERENCES "wallet"("publicKey") ON DELETE CASCADE ON UPDATE NO ACTION', + ) } - public async down (queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "refill" DROP CONSTRAINT "FK_13d26ed77e9d9fd183eceb62599"') - await queryRunner.query('ALTER TABLE "refill" ALTER COLUMN "walletPublicKey" DROP NOT NULL') - await queryRunner.query('ALTER TABLE "refill" ADD CONSTRAINT "FK_13d26ed77e9d9fd183eceb62599" FOREIGN KEY ("walletPublicKey") REFERENCES "wallet"("publicKey") ON DELETE CASCADE ON UPDATE NO ACTION') + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "refill" DROP CONSTRAINT "FK_13d26ed77e9d9fd183eceb62599"', + ) + await queryRunner.query( + 'ALTER TABLE "refill" ALTER COLUMN "walletPublicKey" DROP NOT NULL', + ) + await queryRunner.query( + 'ALTER TABLE "refill" ADD CONSTRAINT "FK_13d26ed77e9d9fd183eceb62599" FOREIGN KEY ("walletPublicKey") REFERENCES "wallet"("publicKey") ON DELETE CASCADE ON UPDATE NO ACTION', + ) } } diff --git a/src/db/migrations/1665312398769-shipname.ts b/src/db/migrations/1665312398769-shipname.ts index 93026cba..28107835 100644 --- a/src/db/migrations/1665312398769-shipname.ts +++ b/src/db/migrations/1665312398769-shipname.ts @@ -1,11 +1,13 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class Shipname1665312398769 implements MigrationInterface { - public async up (queryRunner: QueryRunner): Promise { - await queryRunner.query('CREATE TABLE "ship_info" ("mint" character varying NOT NULL, "name" character varying NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), CONSTRAINT "PK_9a25e31a5bdbb88e239433c7898" PRIMARY KEY ("mint"))') + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'CREATE TABLE "ship_info" ("mint" character varying NOT NULL, "name" character varying NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), CONSTRAINT "PK_9a25e31a5bdbb88e239433c7898" PRIMARY KEY ("mint"))', + ) } - public async down (queryRunner: QueryRunner): Promise { + public async down(queryRunner: QueryRunner): Promise { await queryRunner.query('DROP TABLE "ship_info"') } } diff --git a/src/db/migrations/1665395111187-tips.ts b/src/db/migrations/1665395111187-tips.ts index 503e32b9..130d4974 100644 --- a/src/db/migrations/1665395111187-tips.ts +++ b/src/db/migrations/1665395111187-tips.ts @@ -1,12 +1,16 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class Tips1665395111187 implements MigrationInterface { - public async up (queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "refill" ADD "tip" double precision NOT NULL DEFAULT \'0.15\'') - await queryRunner.query('ALTER TABLE "wallet" ADD "tip" double precision NOT NULL DEFAULT \'0.15\'') + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "refill" ADD "tip" double precision NOT NULL DEFAULT \'0.15\'', + ) + await queryRunner.query( + 'ALTER TABLE "wallet" ADD "tip" double precision NOT NULL DEFAULT \'0.15\'', + ) } - public async down (queryRunner: QueryRunner): Promise { + public async down(queryRunner: QueryRunner): Promise { await queryRunner.query('ALTER TABLE "wallet" DROP COLUMN "tip"') await queryRunner.query('ALTER TABLE "refill" DROP COLUMN "tip"') } diff --git a/src/db/migrations/1665595883444-registration.ts b/src/db/migrations/1665595883444-registration.ts index 326fbc7e..30163621 100644 --- a/src/db/migrations/1665595883444-registration.ts +++ b/src/db/migrations/1665595883444-registration.ts @@ -1,18 +1,28 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class Registration1665595883444 implements MigrationInterface { - public async up (queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "wallet" ADD "nick" character varying') - await queryRunner.query('ALTER TABLE "wallet" ADD "authed" boolean NOT NULL DEFAULT false') - await queryRunner.query('ALTER TABLE "wallet" ADD "authTxAmount" double precision') - await queryRunner.query('ALTER TABLE "wallet" ADD "authExpire" TIMESTAMP WITH TIME ZONE') + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "wallet" ADD "nick" character varying', + ) + await queryRunner.query( + 'ALTER TABLE "wallet" ADD "authed" boolean NOT NULL DEFAULT false', + ) + await queryRunner.query( + 'ALTER TABLE "wallet" ADD "authTxAmount" double precision', + ) + await queryRunner.query( + 'ALTER TABLE "wallet" ADD "authExpire" TIMESTAMP WITH TIME ZONE', + ) await queryRunner.query('ALTER TABLE "wallet" ADD "telegramId" integer') } - public async down (queryRunner: QueryRunner): Promise { + public async down(queryRunner: QueryRunner): Promise { await queryRunner.query('ALTER TABLE "wallet" DROP COLUMN "telegramId"') await queryRunner.query('ALTER TABLE "wallet" DROP COLUMN "authExpire"') - await queryRunner.query('ALTER TABLE "wallet" DROP COLUMN "authTxAmount"') + await queryRunner.query( + 'ALTER TABLE "wallet" DROP COLUMN "authTxAmount"', + ) await queryRunner.query('ALTER TABLE "wallet" DROP COLUMN "authed"') await queryRunner.query('ALTER TABLE "wallet" DROP COLUMN "nick"') } diff --git a/src/db/migrations/1665601528905-imagename.ts b/src/db/migrations/1665601528905-imagename.ts index 4e0c4f7d..78d27442 100644 --- a/src/db/migrations/1665601528905-imagename.ts +++ b/src/db/migrations/1665601528905-imagename.ts @@ -1,11 +1,15 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class Imagename1665601528905 implements MigrationInterface { - public async up (queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "ship_info" ADD "imageName" character varying NOT NULL') + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "ship_info" ADD "imageName" character varying NOT NULL', + ) } - public async down (queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "ship_info" DROP COLUMN "imageName"') + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "ship_info" DROP COLUMN "imageName"', + ) } } diff --git a/src/db/migrations/1665620874572-disable-wallet.ts b/src/db/migrations/1665620874572-disable-wallet.ts index 5523beb0..e26861fd 100644 --- a/src/db/migrations/1665620874572-disable-wallet.ts +++ b/src/db/migrations/1665620874572-disable-wallet.ts @@ -1,11 +1,13 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class DisableWallet1665620874572 implements MigrationInterface { - public async up (queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "wallet" ADD "enabled" boolean NOT NULL DEFAULT true') + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "wallet" ADD "enabled" boolean NOT NULL DEFAULT true', + ) } - public async down (queryRunner: QueryRunner): Promise { + public async down(queryRunner: QueryRunner): Promise { await queryRunner.query('ALTER TABLE "wallet" DROP COLUMN "enabled"') } } diff --git a/src/db/migrations/1665623724921-index.ts b/src/db/migrations/1665623724921-index.ts index d86896f4..818b2e10 100644 --- a/src/db/migrations/1665623724921-index.ts +++ b/src/db/migrations/1665623724921-index.ts @@ -1,13 +1,21 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class Index1665623724921 implements MigrationInterface { - public async up (queryRunner: QueryRunner): Promise { - await queryRunner.query('CREATE INDEX "IDX_1332d7bfbb2ff3d47e92d7ab49" ON "wallet" ("telegramId") ') - await queryRunner.query('CREATE INDEX "IDX_33a938fb6968e2895273fda726" ON "wallet" ("enabled") ') + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'CREATE INDEX "IDX_1332d7bfbb2ff3d47e92d7ab49" ON "wallet" ("telegramId") ', + ) + await queryRunner.query( + 'CREATE INDEX "IDX_33a938fb6968e2895273fda726" ON "wallet" ("enabled") ', + ) } - public async down (queryRunner: QueryRunner): Promise { - await queryRunner.query('DROP INDEX "public"."IDX_33a938fb6968e2895273fda726"') - await queryRunner.query('DROP INDEX "public"."IDX_1332d7bfbb2ff3d47e92d7ab49"') + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'DROP INDEX "public"."IDX_33a938fb6968e2895273fda726"', + ) + await queryRunner.query( + 'DROP INDEX "public"."IDX_1332d7bfbb2ff3d47e92d7ab49"', + ) } } diff --git a/src/db/migrations/1665681627046-notify.ts b/src/db/migrations/1665681627046-notify.ts index 058cace5..c6036d53 100644 --- a/src/db/migrations/1665681627046-notify.ts +++ b/src/db/migrations/1665681627046-notify.ts @@ -1,11 +1,13 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class Notify1665681627046 implements MigrationInterface { - public async up (queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "wallet" ADD "notify" boolean NOT NULL DEFAULT true') + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "wallet" ADD "notify" boolean NOT NULL DEFAULT true', + ) } - public async down (queryRunner: QueryRunner): Promise { + public async down(queryRunner: QueryRunner): Promise { await queryRunner.query('ALTER TABLE "wallet" DROP COLUMN "notify"') } } diff --git a/src/db/migrations/1665694120757-tx-relation-id.ts b/src/db/migrations/1665694120757-tx-relation-id.ts index ae56e378..e309610e 100644 --- a/src/db/migrations/1665694120757-tx-relation-id.ts +++ b/src/db/migrations/1665694120757-tx-relation-id.ts @@ -1,15 +1,27 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class TxRelationId1665694120757 implements MigrationInterface { - public async up (queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "transaction" DROP CONSTRAINT "FK_0c2fe605e9898819004cd7f2113"') - await queryRunner.query('ALTER TABLE "transaction" ALTER COLUMN "walletPublicKey" SET NOT NULL') - await queryRunner.query('ALTER TABLE "transaction" ADD CONSTRAINT "FK_0c2fe605e9898819004cd7f2113" FOREIGN KEY ("walletPublicKey") REFERENCES "wallet"("publicKey") ON DELETE CASCADE ON UPDATE NO ACTION') + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "transaction" DROP CONSTRAINT "FK_0c2fe605e9898819004cd7f2113"', + ) + await queryRunner.query( + 'ALTER TABLE "transaction" ALTER COLUMN "walletPublicKey" SET NOT NULL', + ) + await queryRunner.query( + 'ALTER TABLE "transaction" ADD CONSTRAINT "FK_0c2fe605e9898819004cd7f2113" FOREIGN KEY ("walletPublicKey") REFERENCES "wallet"("publicKey") ON DELETE CASCADE ON UPDATE NO ACTION', + ) } - public async down (queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "transaction" DROP CONSTRAINT "FK_0c2fe605e9898819004cd7f2113"') - await queryRunner.query('ALTER TABLE "transaction" ALTER COLUMN "walletPublicKey" DROP NOT NULL') - await queryRunner.query('ALTER TABLE "transaction" ADD CONSTRAINT "FK_0c2fe605e9898819004cd7f2113" FOREIGN KEY ("walletPublicKey") REFERENCES "wallet"("publicKey") ON DELETE CASCADE ON UPDATE NO ACTION') + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "transaction" DROP CONSTRAINT "FK_0c2fe605e9898819004cd7f2113"', + ) + await queryRunner.query( + 'ALTER TABLE "transaction" ALTER COLUMN "walletPublicKey" DROP NOT NULL', + ) + await queryRunner.query( + 'ALTER TABLE "transaction" ADD CONSTRAINT "FK_0c2fe605e9898819004cd7f2113" FOREIGN KEY ("walletPublicKey") REFERENCES "wallet"("publicKey") ON DELETE CASCADE ON UPDATE NO ACTION', + ) } } diff --git a/src/db/migrations/1668120405025-balance.ts b/src/db/migrations/1668120405025-balance.ts index 9b5158af..b5e0bf8a 100644 --- a/src/db/migrations/1668120405025-balance.ts +++ b/src/db/migrations/1668120405025-balance.ts @@ -1,13 +1,19 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class Balance1668120405025 implements MigrationInterface { - public async up (queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "refill" ADD "preBalance" double precision NOT NULL DEFAULT \'0\'') - await queryRunner.query('ALTER TABLE "refill" ADD "postBalance" double precision NOT NULL DEFAULT \'0\'') + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "refill" ADD "preBalance" double precision NOT NULL DEFAULT \'0\'', + ) + await queryRunner.query( + 'ALTER TABLE "refill" ADD "postBalance" double precision NOT NULL DEFAULT \'0\'', + ) } - public async down (queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "refill" DROP COLUMN "postBalance"') + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "refill" DROP COLUMN "postBalance"', + ) await queryRunner.query('ALTER TABLE "refill" DROP COLUMN "preBalance"') } } diff --git a/src/db/migrations/1675021579415-telegram-id-bigint.ts b/src/db/migrations/1675021579415-telegram-id-bigint.ts index 35271ec5..270961e7 100644 --- a/src/db/migrations/1675021579415-telegram-id-bigint.ts +++ b/src/db/migrations/1675021579415-telegram-id-bigint.ts @@ -1,11 +1,15 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class TelegramIdBigint1675021579415 implements MigrationInterface { - public async up (queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "wallet" ALTER COLUMN "telegramId" SET DATA TYPE bigint') + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "wallet" ALTER COLUMN "telegramId" SET DATA TYPE bigint', + ) } - public async down (queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "wallet" ALTER COLUMN "telegramId" SET DATA TYPE integer') + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "wallet" ALTER COLUMN "telegramId" SET DATA TYPE integer', + ) } } diff --git a/src/db/migrations/1681496450836-r4-support.ts b/src/db/migrations/1681496450836-r4-support.ts index f6737e8a..2d96659d 100644 --- a/src/db/migrations/1681496450836-r4-support.ts +++ b/src/db/migrations/1681496450836-r4-support.ts @@ -3,17 +3,33 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class R4Support1681496450836 implements MigrationInterface { name = 'R4Support1681496450836' - public async up (queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "transaction" ADD "originalAmount" double precision') - await queryRunner.query('ALTER TABLE "transaction" ADD "resource" character varying') - await queryRunner.query('UPDATE "transaction" SET "originalAmount" = "amount"') - await queryRunner.query('UPDATE "transaction" SET "resource" = \'ATLAS\'') - await queryRunner.query('ALTER TABLE "transaction" ALTER COLUMN "originalAmount" SET NOT NULL') - await queryRunner.query('ALTER TABLE "transaction" ALTER COLUMN "resource" SET NOT NULL') + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "transaction" ADD "originalAmount" double precision', + ) + await queryRunner.query( + 'ALTER TABLE "transaction" ADD "resource" character varying', + ) + await queryRunner.query( + 'UPDATE "transaction" SET "originalAmount" = "amount"', + ) + await queryRunner.query( + 'UPDATE "transaction" SET "resource" = \'ATLAS\'', + ) + await queryRunner.query( + 'ALTER TABLE "transaction" ALTER COLUMN "originalAmount" SET NOT NULL', + ) + await queryRunner.query( + 'ALTER TABLE "transaction" ALTER COLUMN "resource" SET NOT NULL', + ) } - public async down (queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "transaction" DROP COLUMN "resource"') - await queryRunner.query('ALTER TABLE "transaction" DROP COLUMN "originalAmount"') + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "transaction" DROP COLUMN "resource"', + ) + await queryRunner.query( + 'ALTER TABLE "transaction" DROP COLUMN "originalAmount"', + ) } } diff --git a/src/db/migrations/1685807496144-multi-resource-tx-support.ts b/src/db/migrations/1685807496144-multi-resource-tx-support.ts index b54e7711..e4c4a422 100644 --- a/src/db/migrations/1685807496144-multi-resource-tx-support.ts +++ b/src/db/migrations/1685807496144-multi-resource-tx-support.ts @@ -3,23 +3,49 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class MultiResourceTxSupport1685807496144 implements MigrationInterface { name = 'MultiResourceTxSupport1685807496144' - public async up (queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "transaction" ADD "id" uuid NOT NULL default gen_random_uuid()') - await queryRunner.query('ALTER TABLE "transaction" DROP CONSTRAINT "PK_504a5a02c8957257be1e0c49510"') - await queryRunner.query('ALTER TABLE "transaction" ADD CONSTRAINT "PK_1607c7f366fba66c851dce79058" PRIMARY KEY ("signature", "id")') - await queryRunner.query('ALTER TABLE "transaction" DROP CONSTRAINT "PK_1607c7f366fba66c851dce79058"') - await queryRunner.query('ALTER TABLE "transaction" ADD CONSTRAINT "PK_89eadb93a89810556e1cbcd6ab9" PRIMARY KEY ("id")') - await queryRunner.query('CREATE INDEX "IDX_504a5a02c8957257be1e0c4951" ON "transaction" ("signature") ') - await queryRunner.query('ALTER TABLE "transaction" ADD CONSTRAINT "UQ_6720d355d907ad611603f0a3dc6" UNIQUE ("signature", "resource")') + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "transaction" ADD "id" uuid NOT NULL default gen_random_uuid()', + ) + await queryRunner.query( + 'ALTER TABLE "transaction" DROP CONSTRAINT "PK_504a5a02c8957257be1e0c49510"', + ) + await queryRunner.query( + 'ALTER TABLE "transaction" ADD CONSTRAINT "PK_1607c7f366fba66c851dce79058" PRIMARY KEY ("signature", "id")', + ) + await queryRunner.query( + 'ALTER TABLE "transaction" DROP CONSTRAINT "PK_1607c7f366fba66c851dce79058"', + ) + await queryRunner.query( + 'ALTER TABLE "transaction" ADD CONSTRAINT "PK_89eadb93a89810556e1cbcd6ab9" PRIMARY KEY ("id")', + ) + await queryRunner.query( + 'CREATE INDEX "IDX_504a5a02c8957257be1e0c4951" ON "transaction" ("signature") ', + ) + await queryRunner.query( + 'ALTER TABLE "transaction" ADD CONSTRAINT "UQ_6720d355d907ad611603f0a3dc6" UNIQUE ("signature", "resource")', + ) } - public async down (queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "transaction" DROP CONSTRAINT "UQ_6720d355d907ad611603f0a3dc6"') - await queryRunner.query('DROP INDEX "public"."IDX_504a5a02c8957257be1e0c4951"') - await queryRunner.query('ALTER TABLE "transaction" DROP CONSTRAINT "PK_89eadb93a89810556e1cbcd6ab9"') - await queryRunner.query('ALTER TABLE "transaction" ADD CONSTRAINT "PK_1607c7f366fba66c851dce79058" PRIMARY KEY ("signature", "id")') - await queryRunner.query('ALTER TABLE "transaction" DROP CONSTRAINT "PK_1607c7f366fba66c851dce79058"') - await queryRunner.query('ALTER TABLE "transaction" ADD CONSTRAINT "PK_504a5a02c8957257be1e0c49510" PRIMARY KEY ("signature")') + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "transaction" DROP CONSTRAINT "UQ_6720d355d907ad611603f0a3dc6"', + ) + await queryRunner.query( + 'DROP INDEX "public"."IDX_504a5a02c8957257be1e0c4951"', + ) + await queryRunner.query( + 'ALTER TABLE "transaction" DROP CONSTRAINT "PK_89eadb93a89810556e1cbcd6ab9"', + ) + await queryRunner.query( + 'ALTER TABLE "transaction" ADD CONSTRAINT "PK_1607c7f366fba66c851dce79058" PRIMARY KEY ("signature", "id")', + ) + await queryRunner.query( + 'ALTER TABLE "transaction" DROP CONSTRAINT "PK_1607c7f366fba66c851dce79058"', + ) + await queryRunner.query( + 'ALTER TABLE "transaction" ADD CONSTRAINT "PK_504a5a02c8957257be1e0c49510" PRIMARY KEY ("signature")', + ) await queryRunner.query('ALTER TABLE "transaction" DROP COLUMN "id"') } } diff --git a/src/db/migrations/1689091973427-tx-cache.ts b/src/db/migrations/1689091973427-tx-cache.ts index 7d526f8c..2ac6ae77 100644 --- a/src/db/migrations/1689091973427-tx-cache.ts +++ b/src/db/migrations/1689091973427-tx-cache.ts @@ -1,13 +1,19 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class TxCache1689091973427 implements MigrationInterface { - public async up (queryRunner: QueryRunner): Promise { - await queryRunner.query('CREATE TABLE "tx_cache" ("id" character varying NOT NULL, "tx" jsonb NOT NULL, CONSTRAINT "PK_a48880d12cdce61b512e5c20c02" PRIMARY KEY ("id"))') - await queryRunner.query('ALTER TABLE "transaction" ALTER COLUMN "id" DROP DEFAULT') + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'CREATE TABLE "tx_cache" ("id" character varying NOT NULL, "tx" jsonb NOT NULL, CONSTRAINT "PK_a48880d12cdce61b512e5c20c02" PRIMARY KEY ("id"))', + ) + await queryRunner.query( + 'ALTER TABLE "transaction" ALTER COLUMN "id" DROP DEFAULT', + ) } - public async down (queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "transaction" ALTER COLUMN "id" SET DEFAULT uuid_generate_v4()') + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "transaction" ALTER COLUMN "id" SET DEFAULT uuid_generate_v4()', + ) await queryRunner.query('DROP TABLE "tx_cache"') } } diff --git a/src/db/migrations/1689092742010-auto-generated-id-column.ts b/src/db/migrations/1689092742010-auto-generated-id-column.ts index a37d5a4b..6aeab3cf 100644 --- a/src/db/migrations/1689092742010-auto-generated-id-column.ts +++ b/src/db/migrations/1689092742010-auto-generated-id-column.ts @@ -1,11 +1,15 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class AutoGeneratedIdColumn1689092742010 implements MigrationInterface { - public async up (queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "transaction" ALTER COLUMN "id" SET DEFAULT uuid_generate_v4()') + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "transaction" ALTER COLUMN "id" SET DEFAULT uuid_generate_v4()', + ) } - public async down (queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "transaction" ALTER COLUMN "id" DROP DEFAULT') + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "transaction" ALTER COLUMN "id" DROP DEFAULT', + ) } } diff --git a/src/db/migrations/1689511318618-bonuses.ts b/src/db/migrations/1689511318618-bonuses.ts index 936b039e..2049c1bf 100644 --- a/src/db/migrations/1689511318618-bonuses.ts +++ b/src/db/migrations/1689511318618-bonuses.ts @@ -1,13 +1,19 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class Bonuses1689511318618 implements MigrationInterface { - public async up (queryRunner: QueryRunner): Promise { - await queryRunner.query('CREATE TABLE "bonus" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "time" TIMESTAMP WITH TIME ZONE NOT NULL, "amount" double precision NOT NULL, "walletPublicKey" character varying NOT NULL, CONSTRAINT "PK_885c9ca672f42874b1a5cb4d9e7" PRIMARY KEY ("id"))') - await queryRunner.query('ALTER TABLE "bonus" ADD CONSTRAINT "FK_81fa518bdf061dcbe81b60ccb94" FOREIGN KEY ("walletPublicKey") REFERENCES "wallet"("publicKey") ON DELETE CASCADE ON UPDATE NO ACTION') + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'CREATE TABLE "bonus" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "time" TIMESTAMP WITH TIME ZONE NOT NULL, "amount" double precision NOT NULL, "walletPublicKey" character varying NOT NULL, CONSTRAINT "PK_885c9ca672f42874b1a5cb4d9e7" PRIMARY KEY ("id"))', + ) + await queryRunner.query( + 'ALTER TABLE "bonus" ADD CONSTRAINT "FK_81fa518bdf061dcbe81b60ccb94" FOREIGN KEY ("walletPublicKey") REFERENCES "wallet"("publicKey") ON DELETE CASCADE ON UPDATE NO ACTION', + ) } - public async down (queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "bonus" DROP CONSTRAINT "FK_81fa518bdf061dcbe81b60ccb94"') + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "bonus" DROP CONSTRAINT "FK_81fa518bdf061dcbe81b60ccb94"', + ) await queryRunner.query('DROP TABLE "bonus"') } } diff --git a/src/db/migrations/1689512138737-bonuses-reason.ts b/src/db/migrations/1689512138737-bonuses-reason.ts index 978fc71f..2f33f11c 100644 --- a/src/db/migrations/1689512138737-bonuses-reason.ts +++ b/src/db/migrations/1689512138737-bonuses-reason.ts @@ -1,11 +1,13 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class BonusesReason1689512138737 implements MigrationInterface { - public async up (queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "bonus" ADD "reason" character varying NOT NULL') + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "bonus" ADD "reason" character varying NOT NULL', + ) } - public async down (queryRunner: QueryRunner): Promise { + public async down(queryRunner: QueryRunner): Promise { await queryRunner.query('ALTER TABLE "bonus" DROP COLUMN "reason"') } } diff --git a/src/lib/check-atlas-transactions.ts b/src/lib/check-atlas-transactions.ts index 513bd6fa..f6cf5926 100644 --- a/src/lib/check-atlas-transactions.ts +++ b/src/lib/check-atlas-transactions.ts @@ -1,7 +1,14 @@ // eslint-disable-next-line import/named import { getAssociatedTokenAddressSync } from '@solana/spl-token' // eslint-disable-next-line import/named -import { ParsedInstruction, ParsedTransactionWithMeta, SignaturesForAddressOptions } from '@solana/web3.js' +import { + // eslint-disable-next-line import/named + ParsedInstruction, + // eslint-disable-next-line import/named + ParsedTransactionWithMeta, + // eslint-disable-next-line import/named + SignaturesForAddressOptions, +} from '@solana/web3.js' import dayjs from '../dayjs' import { Transaction } from '../db/entities' @@ -11,82 +18,135 @@ import { keyPair, resource } from '../service/wallet' import { ensureWallet } from './ensure-wallet' -export const checkAtlasTransactions = async (options?: SignaturesForAddressOptions): Promise => { +export const checkAtlasTransactions = async ( + options?: SignaturesForAddressOptions, +): Promise => { const atlasTokenAccount = getAssociatedTokenAddressSync( resource.atlas, keyPair.publicKey, - true + true, + ) + const signatureList = await connection.getSignaturesForAddress( + atlasTokenAccount, + options, ) - const signatureList = await connection.getSignaturesForAddress(atlasTokenAccount, options) - const transactionList : ParsedTransactionWithMeta[] = [] + const transactionList: ParsedTransactionWithMeta[] = [] for (const signature of signatureList) { // https://docs.solana.com/developing/versioned-transactions#max-supported-transaction-version const parsedSignature = // eslint-disable-next-line no-await-in-loop - await connection.getParsedTransaction(signature.signature, { maxSupportedTransactionVersion: 0 }) + await connection.getParsedTransaction(signature.signature, { + maxSupportedTransactionVersion: 0, + }) if (parsedSignature) { transactionList.push(parsedSignature) } } - const txList: ParsedTransactionWithMeta[] = - transactionList.filter((tx): tx is ParsedTransactionWithMeta => tx !== null) + const txList: ParsedTransactionWithMeta[] = transactionList.filter( + (tx): tx is ParsedTransactionWithMeta => tx !== null, + ) - const transferList = txList.filter(tx => tx.meta?.postTokenBalances?.length === 2) + const transferList = txList.filter( + (tx) => tx.meta?.postTokenBalances?.length === 2, + ) await Promise.all( - transferList.map(tx => tx.transaction.message.instructions.map(async (instr) => { - const instruction: ParsedInstruction = instr as ParsedInstruction - - if (instruction.program === 'spl-token' && instruction.parsed.info.mint === resource.atlas.toString()) { - const { info } = instruction.parsed - - const sender = info.authority ?? info.multisigAuthority - const amount = info.tokenAmount.uiAmount - const blockTime = tx.blockTime || 0 - const time = dayjs.unix(blockTime).toDate() - const [signature] = tx.transaction.signatures - - const transaction = await Transaction.findOneBy({ signature }) - const log = transaction ? logger.debug : logger.info - - if (sender === keyPair.publicKey.toString()) { - const receiver = tx.meta?.postTokenBalances?.filter( - tb => tb.owner?.toString() !== keyPair.publicKey.toString())[0].owner as string - - log(`${receiver} -${amount} ATLAS ${dayjs.duration(dayjs().diff(time)).humanize(false)} ago`) - const wallet = await ensureWallet(receiver) - - const tr = await Transaction.findOneBy({ signature, resource: 'ATLAS' }) - - if (!tr) { - await Transaction.create({ wallet, amount: -amount, signature, time, originalAmount: amount, resource: 'ATLAS' }).save() - } - } - else { - log(`${sender} +${amount} ATLAS ${dayjs.duration(dayjs().diff(time)).humanize(false)} ago`) - - const wallet = await ensureWallet(sender) - - if (wallet.telegramId && !wallet.authed && dayjs().isBefore(wallet.authExpire) && !transaction) { - if (wallet.authTxAmount === amount) { - wallet.authed = true - - logger.info(`Successfully assigned ${wallet.telegramId} to ${wallet.publicKey}`) + transferList.map((tx) => + tx.transaction.message.instructions.map(async (instr) => { + const instruction: ParsedInstruction = + instr as ParsedInstruction + + if ( + instruction.program === 'spl-token' && + instruction.parsed.info.mint === resource.atlas.toString() + ) { + const { info } = instruction.parsed + + const sender = info.authority ?? info.multisigAuthority + const amount = info.tokenAmount.uiAmount + const blockTime = tx.blockTime || 0 + const time = dayjs.unix(blockTime).toDate() + const [signature] = tx.transaction.signatures + + const transaction = await Transaction.findOneBy({ + signature, + }) + const log = transaction ? logger.debug : logger.info + + if (sender === keyPair.publicKey.toString()) { + const receiver = tx.meta?.postTokenBalances?.filter( + (tb) => + tb.owner?.toString() !== + keyPair.publicKey.toString(), + )[0].owner as string + + log( + `${receiver} -${amount} ATLAS ${dayjs.duration(dayjs().diff(time)).humanize(false)} ago`, + ) + const wallet = await ensureWallet(receiver) + + const tr = await Transaction.findOneBy({ + signature, + resource: 'ATLAS', + }) + + if (!tr) { + await Transaction.create({ + wallet, + amount: -amount, + signature, + time, + originalAmount: amount, + resource: 'ATLAS', + }).save() } - else { - logger.warn(`Amount mismatch, got ${amount}, expected ${wallet.authTxAmount}`) + } else { + log( + `${sender} +${amount} ATLAS ${dayjs.duration(dayjs().diff(time)).humanize(false)} ago`, + ) + + const wallet = await ensureWallet(sender) + + if ( + wallet.telegramId && + !wallet.authed && + dayjs().isBefore(wallet.authExpire) && + !transaction + ) { + if (wallet.authTxAmount === amount) { + wallet.authed = true + + logger.info( + `Successfully assigned ${wallet.telegramId} to ${wallet.publicKey}`, + ) + } else { + logger.warn( + `Amount mismatch, got ${amount}, expected ${wallet.authTxAmount}`, + ) + } + } + const tr = await Transaction.findOneBy({ + signature, + resource: 'ATLAS', + }) + + if (!tr) { + await Transaction.create({ + wallet, + amount, + signature, + time, + originalAmount: amount, + resource: 'ATLAS', + }).save() } - } - const tr = await Transaction.findOneBy({ signature, resource: 'ATLAS' }) - - if (!tr) { - await Transaction.create({ wallet, amount, signature, time, originalAmount: amount, resource: 'ATLAS' }).save() } } - } - }))) + }), + ), + ) } diff --git a/src/lib/check-r4-transactions.ts b/src/lib/check-r4-transactions.ts index b7cd45cd..61bd0ba0 100644 --- a/src/lib/check-r4-transactions.ts +++ b/src/lib/check-r4-transactions.ts @@ -1,6 +1,12 @@ import { getAssociatedTokenAddressSync } from '@solana/spl-token' // eslint-disable-next-line import/named -import { ParsedInstruction, PublicKey, SignaturesForAddressOptions } from '@solana/web3.js' +import { + // eslint-disable-next-line import/named + ParsedInstruction, + PublicKey, + // eslint-disable-next-line import/named + SignaturesForAddressOptions, +} from '@solana/web3.js' import Big from 'big.js' import dayjs from '../dayjs' @@ -14,7 +20,7 @@ import { keyPair } from '../service/wallet' import { ensureWallet } from './ensure-wallet' import { getPrice } from './get-price' -type ResourceName = 'food' | 'tool' | 'fuel' | 'ammo'; +type ResourceName = 'food' | 'tool' | 'fuel' | 'ammo' export const checkR4Transactions = async ( wallet: Wallet, @@ -106,7 +112,7 @@ export const checkR4Transactions = async ( // eslint-disable-next-line max-depth if (sender === keyPair.publicKey.toString()) { const receiver = tx.meta?.postTokenBalances?.filter( - tb => + (tb) => tb.owner?.toString() !== keyPair.publicKey.toString(), )[0].owner as string @@ -128,8 +134,7 @@ export const checkR4Transactions = async ( time, }).save() } - } - else { + } else { log( `${sender} +${originalAmount} ${resourceName.toUpperCase()} worth ${price.toFixed(AD)} ATLAS ${dayjs.duration(dayjs().diff(time)).humanize(false)} ago`, ) diff --git a/src/lib/check-transactions.ts b/src/lib/check-transactions.ts index 8c44ba43..595cd083 100644 --- a/src/lib/check-transactions.ts +++ b/src/lib/check-transactions.ts @@ -3,7 +3,11 @@ import { LessThan, MoreThan } from 'typeorm' import dayjs from '../dayjs' import { Transaction, Wallet } from '../db/entities' import { logger } from '../logger' -import { getBalanceAtlas, getResourceBalances, getResourcePrices } from '../service/gm' +import { + getBalanceAtlas, + getResourceBalances, + getResourcePrices, +} from '../service/gm' import { AD } from '../service/sol' import { keyPair, resource } from '../service/wallet' @@ -16,26 +20,44 @@ export const checkTransactions = async (): Promise => { const resources = await getResourceBalances(keyPair.publicKey) logger.info(`ATLAS balance: ${atlasBalance.toFixed(AD)}`) - logger.info(`FOOD balance: ${resources.food} (${resources.food.mul(prices.food).toFixed(AD)} ATLAS)`) - logger.info(`TOOL balance: ${resources.tool} (${resources.tool.mul(prices.tool).toFixed(AD)} ATLAS)`) - logger.info(`FUEL balance: ${resources.fuel} (${resources.fuel.mul(prices.fuel).toFixed(AD)} ATLAS)`) - logger.info(`AMMO balance: ${resources.ammo} (${resources.ammo.mul(prices.ammo).toFixed(AD)} ATLAS)`) + logger.info( + `FOOD balance: ${resources.food} (${resources.food.mul(prices.food).toFixed(AD)} ATLAS)`, + ) + logger.info( + `TOOL balance: ${resources.tool} (${resources.tool.mul(prices.tool).toFixed(AD)} ATLAS)`, + ) + logger.info( + `FUEL balance: ${resources.fuel} (${resources.fuel.mul(prices.fuel).toFixed(AD)} ATLAS)`, + ) + logger.info( + `AMMO balance: ${resources.ammo} (${resources.ammo.mul(prices.ammo).toFixed(AD)} ATLAS)`, + ) - const total = atlasBalance.add( - resources.food.mul(prices.food)).add( - resources.ammo.mul(prices.ammo)).add( - resources.fuel.mul(prices.fuel)).add( - resources.tool.mul(prices.tool)) + const total = atlasBalance + .add(resources.food.mul(prices.food)) + .add(resources.ammo.mul(prices.ammo)) + .add(resources.fuel.mul(prices.fuel)) + .add(resources.tool.mul(prices.tool)) const [[lastInTx], [lastOutTx]] = await Promise.all([ - Transaction.find({ where: { amount: MoreThan(0) }, order: { time: 'DESC' }, take: 1 }), - Transaction.find({ where: { amount: LessThan(0) }, order: { time: 'DESC' }, take: 1 }) + Transaction.find({ + where: { amount: MoreThan(0) }, + order: { time: 'DESC' }, + take: 1, + }), + Transaction.find({ + where: { amount: LessThan(0) }, + order: { time: 'DESC' }, + take: 1, + }), ]) let getSigOptions if (lastInTx && lastOutTx) { - const until = dayjs(lastInTx.time).isBefore(lastOutTx.time) ? lastInTx.signature : lastOutTx.signature + const until = dayjs(lastInTx.time).isBefore(lastOutTx.time) + ? lastInTx.signature + : lastOutTx.signature getSigOptions = { until } } @@ -48,12 +70,36 @@ export const checkTransactions = async (): Promise => { for (const wallet of wallets) { // eslint-disable-next-line no-await-in-loop - await checkR4Transactions(wallet, 'tool', resource.tool, prices, getSigOptions) + await checkR4Transactions( + wallet, + 'tool', + resource.tool, + prices, + getSigOptions, + ) // eslint-disable-next-line no-await-in-loop - await checkR4Transactions(wallet, 'ammo', resource.ammo, prices, getSigOptions) + await checkR4Transactions( + wallet, + 'ammo', + resource.ammo, + prices, + getSigOptions, + ) // eslint-disable-next-line no-await-in-loop - await checkR4Transactions(wallet, 'food', resource.food, prices, getSigOptions) + await checkR4Transactions( + wallet, + 'food', + resource.food, + prices, + getSigOptions, + ) // eslint-disable-next-line no-await-in-loop - await checkR4Transactions(wallet, 'fuel', resource.fuel, prices, getSigOptions) + await checkR4Transactions( + wallet, + 'fuel', + resource.fuel, + prices, + getSigOptions, + ) } } diff --git a/src/lib/const/max.ts b/src/lib/const/max.ts index 2d2a33f6..3fd992bd 100644 --- a/src/lib/const/max.ts +++ b/src/lib/const/max.ts @@ -1,3 +1,3 @@ import Big from 'big.js' -export const max = (a: Big, b: Big): Big => a.gte(b) ? a : b +export const max = (a: Big, b: Big): Big => (a.gte(b) ? a : b) diff --git a/src/lib/ensure-wallet.ts b/src/lib/ensure-wallet.ts index 2ca5ea7d..fe5b907c 100644 --- a/src/lib/ensure-wallet.ts +++ b/src/lib/ensure-wallet.ts @@ -1,7 +1,10 @@ import { Wallet } from '../db/entities' export const ensureWallet = async (publicKey: string): Promise => { - await Wallet.upsert({ publicKey: publicKey.toString() }, { conflictPaths: ['publicKey'], skipUpdateIfNoValuesChanged: true }) + await Wallet.upsert( + { publicKey: publicKey.toString() }, + { conflictPaths: ['publicKey'], skipUpdateIfNoValuesChanged: true }, + ) return Wallet.findOneOrFail({ where: { publicKey: publicKey.toString() } }) } diff --git a/src/lib/fleet-depletion-info.ts b/src/lib/fleet-depletion-info.ts index fbc8ee39..0fb2e132 100644 --- a/src/lib/fleet-depletion-info.ts +++ b/src/lib/fleet-depletion-info.ts @@ -9,18 +9,25 @@ type FleetDepletionInfo = { human: string } -export const fleetDepletionInfo = async (shipStakingInfo: ShipStakingInfo): Promise => { - const info = await getScoreVarsShipInfo(connection, fleetProgram, shipStakingInfo.shipMint) +export const fleetDepletionInfo = async ( + shipStakingInfo: ShipStakingInfo, +): Promise => { + const info = await getScoreVarsShipInfo( + connection, + fleetProgram, + shipStakingInfo.shipMint, + ) const remainingResources = getFleetRemainingResources(info, shipStakingInfo) const secondsLeft = Math.min( remainingResources.food.secondsLeft, remainingResources.ammo.secondsLeft, remainingResources.fuel.secondsLeft, - remainingResources.tool.secondsLeft) + remainingResources.tool.secondsLeft, + ) return { seconds: secondsLeft, - human: dayjs.duration(secondsLeft, 'seconds').humanize(false) + human: dayjs.duration(secondsLeft, 'seconds').humanize(false), } } diff --git a/src/lib/get-price.ts b/src/lib/get-price.ts index 0616bf88..76b79bc5 100644 --- a/src/lib/get-price.ts +++ b/src/lib/get-price.ts @@ -10,5 +10,8 @@ export const getPrice = (amount: Amounts, price?: Amounts): Big => { const totalAmmoPrice = amount.ammo.mul(p.ammo) const totalToolPrice = amount.tool.mul(p.tool) - return totalFuelPrice.add(totalFoodPrice).add(totalAmmoPrice).add(totalToolPrice) + return totalFuelPrice + .add(totalFoodPrice) + .add(totalAmmoPrice) + .add(totalToolPrice) } diff --git a/src/lib/index.ts b/src/lib/index.ts index 654edf69..99c73a72 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,9 +1,9 @@ -export * from './const' -export * from './telegram' export * from './check-transactions' +export * from './const' export * from './fleet-depletion-info' export * from './get-price' -export * from './refill-strategy' export * from './refill' export * from './refill-player' +export * from './refill-strategy' export * from './stock-resources' +export * from './telegram' diff --git a/src/lib/refill-player.ts b/src/lib/refill-player.ts index 5a8061be..35f93d04 100644 --- a/src/lib/refill-player.ts +++ b/src/lib/refill-player.ts @@ -15,9 +15,18 @@ import { fleetDepletionInfo } from './fleet-depletion-info' import { RefillStrategy } from './refill-strategy' import { getShipName } from './stock-resources' -export const refillPlayer = async (player: PublicKey, strategy: RefillStrategy): Promise => { - const wallet = await Wallet.findOneByOrFail({ publicKey: player.toString() }) - const shipStakingInfos = await getAllFleetsForUserPublicKey(connection, player, fleetProgram) +export const refillPlayer = async ( + player: PublicKey, + strategy: RefillStrategy, +): Promise => { + const wallet = await Wallet.findOneByOrFail({ + publicKey: player.toString(), + }) + const shipStakingInfos = await getAllFleetsForUserPublicKey( + connection, + player, + fleetProgram, + ) if (shipStakingInfos.length === 0) { logger.warn(`${player.toString()} has no staked ships`) @@ -25,7 +34,13 @@ export const refillPlayer = async (player: PublicKey, strategy: RefillStrategy): return [] } - await Promise.all(shipStakingInfos.map(async shipStakingInfo => logger.info(`${player.toString()} ${await getShipName(shipStakingInfo)} depleting in ${(await fleetDepletionInfo(shipStakingInfo)).human}`))) + await Promise.all( + shipStakingInfos.map(async (shipStakingInfo) => + logger.info( + `${player.toString()} ${await getShipName(shipStakingInfo)} depleting in ${(await fleetDepletionInfo(shipStakingInfo)).human}`, + ), + ), + ) const refills: FleetRefill[] = await strategy(shipStakingInfos) @@ -33,61 +48,95 @@ export const refillPlayer = async (player: PublicKey, strategy: RefillStrategy): const playerBalance = await wallet.getBalance() if (playerBalance.lt(totalCost)) { - logger.warn(`${player.toString()} credits insufficient! Has ${playerBalance.toFixed(AD)}, need ${totalCost.toFixed(AD)}`) - await Wallet.update({ publicKey: wallet.publicKey }, { nextRefill: dayjs().add(1, 'day').toDate() }) + logger.warn( + `${player.toString()} credits insufficient! Has ${playerBalance.toFixed(AD)}, need ${totalCost.toFixed(AD)}`, + ) + await Wallet.update( + { publicKey: wallet.publicKey }, + { nextRefill: dayjs().add(1, 'day').toDate() }, + ) return [] } - const fleetRefills = await Promise.all(refills - .filter(refill => - refill.amount.tool.gt(0) || - refill.amount.fuel.gt(0) || - refill.amount.ammo.gt(0) || - refill.amount.food.gt(0)) - .map(async (refill) => { - const shipName = await getShipName(refill.shipStakingInfo) - - logger.warn(`${player.toString()}: Refilling ${JSON.stringify(refill.amount)} for ${shipName} costs ${refill.price.toFixed(AD)} ATLAS`) - - try { - const tx = await refillFleet(player, refill.shipStakingInfo, refill.amount) - - return Refill.create({ - signature: tx, - walletPublicKey: wallet.publicKey, - fleet: shipName, - preBalance: playerBalance.toNumber(), - postBalance: playerBalance.sub(refill.price).toNumber(), - tip: wallet.tip, - price: refill.price.toNumber(), - food: refill.amount.food.toNumber(), - tool: refill.amount.tool.toNumber(), - fuel: refill.amount.fuel.toNumber(), - ammo: refill.amount.ammo.toNumber() - }).save() - } - catch (e) { - Sentry.captureException(e) - logger.error(`Error refilling fleet: ${(e as Error).message}`) - - return null - } - })) - - const depletionInfos = await Promise.all(shipStakingInfos.map(async (shipStakingInfo) => { - const depletionInfo = await fleetDepletionInfo(shipStakingInfo) - - logger.info(`${player.toString()} ${await getShipName(shipStakingInfo)} depleting in ${depletionInfo.human}`) - - return depletionInfo - })) - - const secondsLeft = depletionInfos.reduce((acc, cur) => Math.min(cur.seconds, acc), Number.MAX_SAFE_INTEGER) - - const nextRefill = dayjs().add(Big(secondsLeft).minus(Big(secondsLeft).div(4)).toNumber(), 'second').toDate() + const fleetRefills = await Promise.all( + refills + .filter( + (refill) => + refill.amount.tool.gt(0) || + refill.amount.fuel.gt(0) || + refill.amount.ammo.gt(0) || + refill.amount.food.gt(0), + ) + .map(async (refill) => { + const shipName = await getShipName(refill.shipStakingInfo) + + logger.warn( + `${player.toString()}: Refilling ${JSON.stringify(refill.amount)} for ${shipName} costs ${refill.price.toFixed(AD)} ATLAS`, + ) + + try { + const tx = await refillFleet( + player, + refill.shipStakingInfo, + refill.amount, + ) + + return Promise.all( + tx.map((signature) => { + return Refill.create({ + signature, + walletPublicKey: wallet.publicKey, + fleet: shipName, + preBalance: playerBalance.toNumber(), + postBalance: playerBalance + .sub(refill.price) + .toNumber(), + tip: wallet.tip, + price: refill.price.toNumber(), + food: refill.amount.food.toNumber(), + tool: refill.amount.tool.toNumber(), + fuel: refill.amount.fuel.toNumber(), + ammo: refill.amount.ammo.toNumber(), + }).save() + }), + ) + } catch (e) { + Sentry.captureException(e) + logger.error( + `Error refilling fleet: ${(e as Error).message}`, + ) + + return null + } + }), + ) + + const depletionInfos = await Promise.all( + shipStakingInfos.map(async (shipStakingInfo) => { + const depletionInfo = await fleetDepletionInfo(shipStakingInfo) + + logger.info( + `${player.toString()} ${await getShipName(shipStakingInfo)} depleting in ${depletionInfo.human}`, + ) + + return depletionInfo + }), + ) + + const secondsLeft = depletionInfos.reduce( + (acc, cur) => Math.min(cur.seconds, acc), + Number.MAX_SAFE_INTEGER, + ) + + const nextRefill = dayjs() + .add( + Big(secondsLeft).minus(Big(secondsLeft).div(4)).toNumber(), + 'second', + ) + .toDate() await Wallet.update({ publicKey: wallet.publicKey }, { nextRefill }) - return fleetRefills.filter((f): f is Refill => f !== null) + return fleetRefills.filter((f): f is Refill[] => f !== null).flat() } diff --git a/src/lib/refill-strategy/full-refill-strategy.ts b/src/lib/refill-strategy/full-refill-strategy.ts index b1c5d9a1..876547a4 100644 --- a/src/lib/refill-strategy/full-refill-strategy.ts +++ b/src/lib/refill-strategy/full-refill-strategy.ts @@ -9,30 +9,39 @@ import { getPrice } from '../get-price' import { RefillStrategy } from './refill-strategy' -export const fullRefillStrategy: RefillStrategy = shipStakingInfos => - Promise.all(shipStakingInfos.map(async (shipStakingInfo) => { - const info = await getScoreVarsShipInfo(connection, fleetProgram, shipStakingInfo.shipMint) +export const fullRefillStrategy: RefillStrategy = (shipStakingInfos) => + Promise.all( + shipStakingInfos.map(async (shipStakingInfo) => { + const info = await getScoreVarsShipInfo( + connection, + fleetProgram, + shipStakingInfo.shipMint, + ) - const remainingResources = getFleetRemainingResources(info, shipStakingInfo) + const remainingResources = getFleetRemainingResources( + info, + shipStakingInfo, + ) - const amount: Amounts = { - food: Big(remainingResources.food.maxUnits) - .minus(remainingResources.food.unitsLeft) - .round(0, Big.roundDown), - ammo: Big(remainingResources.ammo.maxUnits) - .minus(remainingResources.ammo.unitsLeft) - .round(0, Big.roundDown), - fuel: Big(remainingResources.fuel.maxUnits) - .minus(remainingResources.fuel.unitsLeft) - .round(0, Big.roundDown), - tool: Big(remainingResources.tool.maxUnits) - .minus(remainingResources.tool.unitsLeft) - .round(0, Big.roundDown) - } + const amount: Amounts = { + food: Big(remainingResources.food.maxUnits) + .minus(remainingResources.food.unitsLeft) + .round(0, Big.roundDown), + ammo: Big(remainingResources.ammo.maxUnits) + .minus(remainingResources.ammo.unitsLeft) + .round(0, Big.roundDown), + fuel: Big(remainingResources.fuel.maxUnits) + .minus(remainingResources.fuel.unitsLeft) + .round(0, Big.roundDown), + tool: Big(remainingResources.tool.maxUnits) + .minus(remainingResources.tool.unitsLeft) + .round(0, Big.roundDown), + } - return { - shipStakingInfo, - amount, - price: getPrice(amount) - } as FleetRefill - })) + return { + shipStakingInfo, + amount, + price: getPrice(amount), + } as FleetRefill + }), + ) diff --git a/src/lib/refill-strategy/index.ts b/src/lib/refill-strategy/index.ts index 12b87712..3bb6f852 100644 --- a/src/lib/refill-strategy/index.ts +++ b/src/lib/refill-strategy/index.ts @@ -1,3 +1,3 @@ -export * from './refill-strategy' -export * from './optimal-refill-strategy' export * from './full-refill-strategy' +export * from './optimal-refill-strategy' +export * from './refill-strategy' diff --git a/src/lib/refill-strategy/optimal-refill-strategy.ts b/src/lib/refill-strategy/optimal-refill-strategy.ts index 2a1f7718..deaabc34 100644 --- a/src/lib/refill-strategy/optimal-refill-strategy.ts +++ b/src/lib/refill-strategy/optimal-refill-strategy.ts @@ -9,60 +9,82 @@ import { getPrice } from '../get-price' import { RefillStrategy } from './refill-strategy' -export const optimalRefillStrategy: RefillStrategy = async (shipStakingInfos) => { - const targets = await Promise.all(shipStakingInfos.map(async (shipStakingInfo) => { - const info = await getScoreVarsShipInfo(connection, fleetProgram, shipStakingInfo.shipMint) - const remainingResources = getFleetRemainingResources(info, shipStakingInfo) - - return Math.min( - remainingResources.food.maxSeconds, - remainingResources.ammo.maxSeconds, - remainingResources.fuel.maxSeconds, - remainingResources.tool.maxSeconds - ) - })) - const target = targets.reduce((acc, cur) => Math.min(acc, cur), Number.MAX_SAFE_INTEGER) +export const optimalRefillStrategy: RefillStrategy = async ( + shipStakingInfos, +) => { + const targets = await Promise.all( + shipStakingInfos.map(async (shipStakingInfo) => { + const info = await getScoreVarsShipInfo( + connection, + fleetProgram, + shipStakingInfo.shipMint, + ) + const remainingResources = getFleetRemainingResources( + info, + shipStakingInfo, + ) - return Promise.all(shipStakingInfos.map(async (shipStakingInfo) => { - const info = await getScoreVarsShipInfo(connection, fleetProgram, shipStakingInfo.shipMint) - const remainingResources = getFleetRemainingResources(info, shipStakingInfo) + return Math.min( + remainingResources.food.maxSeconds, + remainingResources.ammo.maxSeconds, + remainingResources.fuel.maxSeconds, + remainingResources.tool.maxSeconds, + ) + }), + ) + const target = targets.reduce( + (acc, cur) => Math.min(acc, cur), + Number.MAX_SAFE_INTEGER, + ) - const amount: Amounts = { - food: max( - Big(target) - .minus(remainingResources.food.secondsLeft) - .mul(remainingResources.food.burnRatePerFleet) - .round(), - Big(0) - ), - ammo: max( - Big(target) - .minus(remainingResources.ammo.secondsLeft) - .mul(remainingResources.ammo.burnRatePerFleet) - .round(), - Big(0) - ), - fuel: max( - Big(target) - .minus(remainingResources.fuel.secondsLeft) - .mul(remainingResources.fuel.burnRatePerFleet) - .round(), - Big(0) - ), - tool: max( - Big(target) - .minus(remainingResources.tool.secondsLeft) - .mul(remainingResources.tool.burnRatePerFleet) - .round(), - Big(0) + return Promise.all( + shipStakingInfos.map(async (shipStakingInfo) => { + const info = await getScoreVarsShipInfo( + connection, + fleetProgram, + shipStakingInfo.shipMint, + ) + const remainingResources = getFleetRemainingResources( + info, + shipStakingInfo, ) - } + const amount: Amounts = { + food: max( + Big(target) + .minus(remainingResources.food.secondsLeft) + .mul(remainingResources.food.burnRatePerFleet) + .round(), + Big(0), + ), + ammo: max( + Big(target) + .minus(remainingResources.ammo.secondsLeft) + .mul(remainingResources.ammo.burnRatePerFleet) + .round(), + Big(0), + ), + fuel: max( + Big(target) + .minus(remainingResources.fuel.secondsLeft) + .mul(remainingResources.fuel.burnRatePerFleet) + .round(), + Big(0), + ), + tool: max( + Big(target) + .minus(remainingResources.tool.secondsLeft) + .mul(remainingResources.tool.burnRatePerFleet) + .round(), + Big(0), + ), + } - return { - shipStakingInfo, - amount, - price: getPrice(amount) - } as FleetRefill - })) + return { + shipStakingInfo, + amount, + price: getPrice(amount), + } as FleetRefill + }), + ) } diff --git a/src/lib/refill-strategy/refill-strategy.ts b/src/lib/refill-strategy/refill-strategy.ts index 73f9c031..2299df9e 100644 --- a/src/lib/refill-strategy/refill-strategy.ts +++ b/src/lib/refill-strategy/refill-strategy.ts @@ -2,4 +2,6 @@ import { ShipStakingInfo } from '@staratlas/factory' import { FleetRefill } from '../const' -export type RefillStrategy = (shipStakingInfos: ShipStakingInfo[]) => Promise +export type RefillStrategy = ( + shipStakingInfos: ShipStakingInfo[], +) => Promise diff --git a/src/lib/refill.ts b/src/lib/refill.ts index c37dae42..c2e6f33a 100644 --- a/src/lib/refill.ts +++ b/src/lib/refill.ts @@ -19,27 +19,41 @@ export const refill = async (): Promise => { /* eslint-disable no-await-in-loop */ for (const player of players) { if (dayjs().isAfter(player.nextRefill)) { - await refillPlayer(new PublicKey(player.publicKey), optimalRefillStrategy) + await refillPlayer( + new PublicKey(player.publicKey), + optimalRefillStrategy, + ) } - const fleets = await getAllFleetsForUserPublicKey(connection, new PublicKey(player.publicKey), fleetProgram) + const fleets = await getAllFleetsForUserPublicKey( + connection, + new PublicKey(player.publicKey), + fleetProgram, + ) const burnRate = await getDailyBurnRate(fleets) const price = getResourcePrices() const balance = await player.getBalance() - const burnPerDay = - max(burnRate.food.mul(price.food).add( - burnRate.fuel.mul(price.fuel)).add( - burnRate.tool.mul(price.tool)).add( - burnRate.fuel.mul(price.ammo)), Big(0.00000001)) + const burnPerDay = max( + burnRate.food + .mul(price.food) + .add(burnRate.fuel.mul(price.fuel)) + .add(burnRate.tool.mul(price.tool)) + .add(burnRate.fuel.mul(price.ammo)), + Big(0.00000001), + ) const balanceTime = balance.div(burnPerDay) await player.reload() logger.info('-----------------------------------------------------') - logger.info(`${player.publicKey} next refill in ${dayjs.duration(dayjs().diff(player.nextRefill, 'second'), 'second').humanize()}`) - logger.info(`Balance: ${balance.toFixed(AD)} ATLAS / Burn ${burnPerDay.toFixed(AD)} ATLAS per day / Credit for ${dayjs.duration(balanceTime.toNumber(), 'day').humanize(false)}`) + logger.info( + `${player.publicKey} next refill in ${dayjs.duration(dayjs().diff(player.nextRefill, 'second'), 'second').humanize()}`, + ) + logger.info( + `Balance: ${balance.toFixed(AD)} ATLAS / Burn ${burnPerDay.toFixed(AD)} ATLAS per day / Credit for ${dayjs.duration(balanceTime.toNumber(), 'day').humanize(false)}`, + ) logger.info(`Total Tipped: ${(await player.totalTipped()).toFixed(AD)}`) logger.info('-----------------------------------------------------') } diff --git a/src/lib/stock-resources.ts b/src/lib/stock-resources.ts index 94d2bdbc..bec91e1a 100644 --- a/src/lib/stock-resources.ts +++ b/src/lib/stock-resources.ts @@ -1,7 +1,12 @@ // eslint-disable-next-line import/named import { AnchorProvider, Idl, Program } from '@coral-xyz/anchor' import { Connection, PublicKey } from '@solana/web3.js' -import { getScoreIDL, getScoreVarsShipInfo, getShipStakingAccount, ShipStakingInfo } from '@staratlas/factory' +import { + ShipStakingInfo, + getScoreIDL, + getScoreVarsShipInfo, + getShipStakingAccount, +} from '@staratlas/factory' import Big from 'big.js' import superagent from 'superagent' @@ -10,7 +15,11 @@ import { ShipInfo, Wallet } from '../db/entities' import { logger } from '../logger' import { getFleetRemainingResources, getTimePass } from '../service/fleet' import { Amounts } from '../service/fleet/const' -import { buyResources, getResourceBalances, getResourcePrices } from '../service/gm' +import { + buyResources, + getResourceBalances, + getResourcePrices, +} from '../service/gm' import { AD, connection, fleetProgram } from '../service/sol' import { keyPair } from '../service/wallet' @@ -25,7 +34,7 @@ import { keyPair } from '../service/wallet' const getAllFleetsForUserPublicKey = async ( conn: Connection, playerPublicKey: PublicKey, - programId: PublicKey + programId: PublicKey, ): Promise => { const idl = getScoreIDL(programId) // @ts-expect-error 123 @@ -41,7 +50,7 @@ const getAllFleetsForUserPublicKey = async ( const [playerShipStakingAccount] = await getShipStakingAccount( programId, ship.account.shipMint as PublicKey, - playerPublicKey + playerPublicKey, ) playerShipStakingAccounts.push(playerShipStakingAccount) @@ -57,33 +66,37 @@ const getAllFleetsForUserPublicKey = async ( if (fleet) { playerFleets.push(fleet) } - } - catch (e) { + } catch (e) { logger.error(e) } } - logger.info(`Found ${playerFleets.length} fleets for ${playerPublicKey.toString()}`) + logger.info( + `Found ${playerFleets.length} fleets for ${playerPublicKey.toString()}`, + ) return playerFleets } -export const getShipName = async (shipStakingInfo: ShipStakingInfo): Promise => { +export const getShipName = async ( + shipStakingInfo: ShipStakingInfo, +): Promise => { const mint = shipStakingInfo.shipMint.toString() let shipInfo = await ShipInfo.findOneBy({ mint }) if (!shipInfo) { try { - const shipNftInfo = await superagent.get(`https://galaxy.staratlas.com/nfts/${mint}`) + const shipNftInfo = await superagent.get( + `https://galaxy.staratlas.com/nfts/${mint}`, + ) const urlSplit = shipNftInfo.body.image.slice(0, -4).split('/') const imageName = urlSplit[urlSplit.length - 1] shipInfo = await ShipInfo.create({ mint, name: shipNftInfo.body.name, - imageName + imageName, }).save() - } - catch { + } catch { return 'n/a' } } @@ -91,99 +104,162 @@ export const getShipName = async (shipStakingInfo: ShipStakingInfo): Promise => { - const fleetInfos = await Promise.all(fleets.map(async (fleet) => { - const shipInfo = await getScoreVarsShipInfo( - connection, - fleetProgram, - fleet.shipMint - ) - - return { - info: shipInfo, - fleet - } - })) - - return fleetInfos.reduce((sum, fleetInfo) => { - const { fleet } = fleetInfo - const { info } = fleetInfo - const timePass = getTimePass(fleet) - const pendingReward = Number(fleet.shipQuantityInEscrow) * - (Number(fleet.totalTimeStaked) - Number(fleet.stakedTimePaid) + timePass) * - Number(info.rewardRatePerSecond) - - return sum.add(pendingReward) - }, Big(0)).div(100000000) +export const getPendingRewards = async ( + fleets: ShipStakingInfo[], +): Promise => { + const fleetInfos = await Promise.all( + fleets.map(async (fleet) => { + const shipInfo = await getScoreVarsShipInfo( + connection, + fleetProgram, + fleet.shipMint, + ) + + return { + info: shipInfo, + fleet, + } + }), + ) + + return fleetInfos + .reduce((sum, fleetInfo) => { + const { fleet } = fleetInfo + const { info } = fleetInfo + const timePass = getTimePass(fleet) + const pendingReward = + Number(fleet.shipQuantityInEscrow) * + (Number(fleet.totalTimeStaked) - + Number(fleet.stakedTimePaid) + + timePass) * + Number(info.rewardRatePerSecond) + + return sum.add(pendingReward) + }, Big(0)) + .div(100000000) } -export const getDailyBurnRate = async (fleets: ShipStakingInfo[]): Promise => { +export const getDailyBurnRate = async ( + fleets: ShipStakingInfo[], +): Promise => { const dayInSeconds = 86400 const resourcePerDay: Amounts = { food: Big(0), tool: Big(0), ammo: Big(0), - fuel: Big(0) + fuel: Big(0), } - await Promise.all(fleets.map(async (shipStakingInfo) => { - const info = await getScoreVarsShipInfo(connection, fleetProgram, shipStakingInfo.shipMint) - const remaining = getFleetRemainingResources(info, shipStakingInfo) - - resourcePerDay.food = resourcePerDay.food.add(Big(remaining.food.burnRatePerFleet).mul(dayInSeconds)) - resourcePerDay.tool = resourcePerDay.tool.add(Big(remaining.tool.burnRatePerFleet).mul(dayInSeconds)) - resourcePerDay.ammo = resourcePerDay.ammo.add(Big(remaining.ammo.burnRatePerFleet).mul(dayInSeconds)) - resourcePerDay.fuel = resourcePerDay.fuel.add(Big(remaining.fuel.burnRatePerFleet).mul(dayInSeconds)) - })) + await Promise.all( + fleets.map(async (shipStakingInfo) => { + const info = await getScoreVarsShipInfo( + connection, + fleetProgram, + shipStakingInfo.shipMint, + ) + const remaining = getFleetRemainingResources(info, shipStakingInfo) + + resourcePerDay.food = resourcePerDay.food.add( + Big(remaining.food.burnRatePerFleet).mul(dayInSeconds), + ) + resourcePerDay.tool = resourcePerDay.tool.add( + Big(remaining.tool.burnRatePerFleet).mul(dayInSeconds), + ) + resourcePerDay.ammo = resourcePerDay.ammo.add( + Big(remaining.ammo.burnRatePerFleet).mul(dayInSeconds), + ) + resourcePerDay.fuel = resourcePerDay.fuel.add( + Big(remaining.fuel.burnRatePerFleet).mul(dayInSeconds), + ) + }), + ) return resourcePerDay } const logStats = (balance: Amounts, dailyBurn: Amounts) => { - logger.info(`TOOL balance: ${balance.tool}, burning ${dailyBurn.tool.toFixed(0)} per day, last for ${dailyBurn.tool.eq(0) ? 0 : dayjs.duration(balance.tool.div(dailyBurn.tool).toNumber(), 'day').humanize()}`) - logger.info(`AMMO balance: ${balance.ammo}, burning ${dailyBurn.ammo.toFixed(0)} per day, last for ${dailyBurn.ammo.eq(0) ? 0 : dayjs.duration(balance.ammo.div(dailyBurn.ammo).toNumber(), 'day').humanize()}`) - logger.info(`FOOD balance: ${balance.food}, burning ${dailyBurn.food.toFixed(0)} per day, last for ${dailyBurn.food.eq(0) ? 0 : dayjs.duration(balance.food.div(dailyBurn.food).toNumber(), 'day').humanize()}`) - logger.info(`FUEL balance: ${balance.fuel}, burning ${dailyBurn.fuel.toFixed(0)} per day, last for ${dailyBurn.fuel.eq(0) ? 0 : dayjs.duration(balance.fuel.div(dailyBurn.fuel).toNumber(), 'day').humanize()}`) + logger.info( + `TOOL balance: ${balance.tool}, burning ${dailyBurn.tool.toFixed(0)} per day, last for ${dailyBurn.tool.eq(0) ? 0 : dayjs.duration(balance.tool.div(dailyBurn.tool).toNumber(), 'day').humanize()}`, + ) + logger.info( + `AMMO balance: ${balance.ammo}, burning ${dailyBurn.ammo.toFixed(0)} per day, last for ${dailyBurn.ammo.eq(0) ? 0 : dayjs.duration(balance.ammo.div(dailyBurn.ammo).toNumber(), 'day').humanize()}`, + ) + logger.info( + `FOOD balance: ${balance.food}, burning ${dailyBurn.food.toFixed(0)} per day, last for ${dailyBurn.food.eq(0) ? 0 : dayjs.duration(balance.food.div(dailyBurn.food).toNumber(), 'day').humanize()}`, + ) + logger.info( + `FUEL balance: ${balance.fuel}, burning ${dailyBurn.fuel.toFixed(0)} per day, last for ${dailyBurn.fuel.eq(0) ? 0 : dayjs.duration(balance.fuel.div(dailyBurn.fuel).toNumber(), 'day').humanize()}`, + ) } export const stockResources = async (): Promise => { const wallets = await Wallet.findBy({ enabled: true }) - const dailyBurn = (await Promise.all(wallets.map(async wallet => - getDailyBurnRate( - await getAllFleetsForUserPublicKey(connection, new PublicKey(wallet.publicKey), fleetProgram))) - ) ) .reduce((acc, cur) => ({ - tool: acc.tool.add(cur.tool), - ammo: acc.ammo.add(cur.ammo), - food: acc.food.add(cur.food), - fuel: acc.fuel.add(cur.fuel) - }), { ammo: Big(0), food: Big(0), fuel: Big(0), tool: Big(0) } as Amounts) + const dailyBurn = ( + await Promise.all( + wallets.map(async (wallet) => + getDailyBurnRate( + await getAllFleetsForUserPublicKey( + connection, + new PublicKey(wallet.publicKey), + fleetProgram, + ), + ), + ), + ) + ).reduce( + (acc, cur) => ({ + tool: acc.tool.add(cur.tool), + ammo: acc.ammo.add(cur.ammo), + food: acc.food.add(cur.food), + fuel: acc.fuel.add(cur.fuel), + }), + { ammo: Big(0), food: Big(0), fuel: Big(0), tool: Big(0) } as Amounts, + ) const balance = await getResourceBalances(keyPair.publicKey) logStats(balance, dailyBurn) const amount: Amounts = { - food: balance.food.lt(dailyBurn.food.mul(14)) ? dailyBurn.food.mul(21) : Big(0), - ammo: balance.ammo.lt(dailyBurn.ammo.mul(14)) ? dailyBurn.ammo.mul(21) : Big(0), - fuel: balance.fuel.lt(dailyBurn.fuel.mul(14)) ? dailyBurn.fuel.mul(21) : Big(0), - tool: balance.tool.lt(dailyBurn.tool.mul(14)) ? dailyBurn.tool.mul(21) : Big(0) + food: balance.food.lt(dailyBurn.food.mul(14)) + ? dailyBurn.food.mul(21) + : Big(0), + ammo: balance.ammo.lt(dailyBurn.ammo.mul(14)) + ? dailyBurn.ammo.mul(21) + : Big(0), + fuel: balance.fuel.lt(dailyBurn.fuel.mul(14)) + ? dailyBurn.fuel.mul(21) + : Big(0), + tool: balance.tool.lt(dailyBurn.tool.mul(14)) + ? dailyBurn.tool.mul(21) + : Big(0), } - if (amount.food.gt(0) || amount.ammo.gt(0) || amount.fuel.gt(0) || amount.tool.gt(0)) { + if ( + amount.food.gt(0) || + amount.ammo.gt(0) || + amount.fuel.gt(0) || + amount.tool.gt(0) + ) { const price = getResourcePrices() const totalFuelPrice = amount.fuel.mul(price.fuel) const totalFoodPrice = amount.food.mul(price.food) const totalAmmoPrice = amount.ammo.mul(price.ammo) const totalToolPrice = amount.tool.mul(price.tool) - const totalPrice = totalFuelPrice.add(totalFoodPrice).add(totalAmmoPrice).add(totalToolPrice) + const totalPrice = totalFuelPrice + .add(totalFoodPrice) + .add(totalAmmoPrice) + .add(totalToolPrice) - logger.info(`Buying Resources...${JSON.stringify(amount)} for ${totalPrice.toFixed(AD)} ATLAS`) + logger.info( + `Buying Resources...${JSON.stringify(amount)} for ${totalPrice.toFixed(AD)} ATLAS`, + ) const txs = await buyResources(amount) - txs.forEach(tx => logger.info(tx)) + txs.forEach((tx) => logger.info(tx)) logStats(await getResourceBalances(keyPair.publicKey), dailyBurn) } } diff --git a/src/lib/telegram/commands/disable-notify.ts b/src/lib/telegram/commands/disable-notify.ts index 7ef59abb..9a68f58f 100644 --- a/src/lib/telegram/commands/disable-notify.ts +++ b/src/lib/telegram/commands/disable-notify.ts @@ -14,7 +14,9 @@ export const disableNotify = (bot: Telegraf): void => { ctx.user.notify = false await ctx.user.save() - await ctx.reply('I will stay silent unless there is something urgent happening.') + await ctx.reply( + 'I will stay silent unless there is something urgent happening.', + ) }) }) } diff --git a/src/lib/telegram/commands/logout.ts b/src/lib/telegram/commands/logout.ts index 3aa1d11b..4d5d4532 100644 --- a/src/lib/telegram/commands/logout.ts +++ b/src/lib/telegram/commands/logout.ts @@ -14,7 +14,9 @@ export const logout = (bot: Telegraf): void => { } if (ctx.user.enabled) { - await ctx.reply('Your wallet is currently enabled, toggle with /disable command') + await ctx.reply( + 'Your wallet is currently enabled, toggle with /disable command', + ) return } diff --git a/src/lib/telegram/commands/meme/kitten.ts b/src/lib/telegram/commands/meme/kitten.ts index 9413c78c..beaebe86 100644 --- a/src/lib/telegram/commands/meme/kitten.ts +++ b/src/lib/telegram/commands/meme/kitten.ts @@ -11,8 +11,7 @@ export const kitten = (bot: Telegraf): void => { try { await ctx.replyWithPhoto(`https://placekitten.com/${x}/${x}`) - } - catch (e: any) { + } catch (e: any) { logger.error(`Cannot send Photo: ${e.message}`) await ctx.reply('Meow!') } diff --git a/src/lib/telegram/commands/meme/porn.ts b/src/lib/telegram/commands/meme/porn.ts index 2e6333eb..0c2ba0ea 100644 --- a/src/lib/telegram/commands/meme/porn.ts +++ b/src/lib/telegram/commands/meme/porn.ts @@ -18,8 +18,7 @@ export const porn = (bot: Telegraf): void => { const imageUrl = `https://storage.googleapis.com/nft-assets/items/${imageName}.jpg` await ctx.replyWithPhoto(imageUrl) - } - catch (e: any) { + } catch (e: any) { logger.error(`Cannot send Photo: ${e.message}`) await ctx.reply(':-*') } diff --git a/src/lib/telegram/commands/meme/wen.ts b/src/lib/telegram/commands/meme/wen.ts index 507b06ac..43c0e305 100644 --- a/src/lib/telegram/commands/meme/wen.ts +++ b/src/lib/telegram/commands/meme/wen.ts @@ -7,9 +7,10 @@ export const wen = (bot: Telegraf): void => { bot.command(['wen'], async (ctx) => { await ctx.persistentChatAction('upload_photo', async () => { try { - await ctx.replyWithAnimation('http://2damnfunny.com/wp-content/uploads/2013/06/Very-Thoon-Husky-Dog-Meme-Gif.gif') - } - catch (e: any) { + await ctx.replyWithAnimation( + 'http://2damnfunny.com/wp-content/uploads/2013/06/Very-Thoon-Husky-Dog-Meme-Gif.gif', + ) + } catch (e: any) { logger.error(`Cannot send Photo: ${e.message}`) await ctx.reply('Thoon!') } diff --git a/src/lib/telegram/commands/refill.ts b/src/lib/telegram/commands/refill.ts index 350c20ea..fd0aa004 100644 --- a/src/lib/telegram/commands/refill.ts +++ b/src/lib/telegram/commands/refill.ts @@ -3,7 +3,10 @@ import { Telegraf } from 'telegraf' import { AD } from '../../../service/sol' import { refillPlayer } from '../../refill-player' -import { fullRefillStrategy, optimalRefillStrategy } from '../../refill-strategy' +import { + fullRefillStrategy, + optimalRefillStrategy, +} from '../../refill-strategy' import { ContextMessageUpdate } from '../context-message-update' import { unauthorized } from '../response' @@ -17,20 +20,35 @@ export const refill = (bot: Telegraf): void => { } if (!ctx.user.enabled) { - await ctx.reply('Your wallet is currently disabled, toggle with /enable command') + await ctx.reply( + 'Your wallet is currently disabled, toggle with /enable command', + ) return } - const strategy = ctx.params.length === 1 && ctx.params[0] === 'full' ? fullRefillStrategy : optimalRefillStrategy - const strategyName = ctx.params.length === 1 && ctx.params[0] === 'full' ? 'full' : 'optimal' + const strategy = + ctx.params.length === 1 && ctx.params[0] === 'full' + ? fullRefillStrategy + : optimalRefillStrategy + const strategyName = + ctx.params.length === 1 && ctx.params[0] === 'full' + ? 'full' + : 'optimal' - await ctx.reply('Just saying... There should not be any reason to do this now, but as you wish, I am going to refill your fleets, ser!') - await ctx.reply(`Using ${strategyName} refill strategy. Give me a moment please...`) - const userRefills = await refillPlayer(new PublicKey(ctx.user.publicKey), strategy) + await ctx.reply( + 'Just saying... There should not be any reason to do this now, but as you wish, I am going to refill your fleets, ser!', + ) + await ctx.reply( + `Using ${strategyName} refill strategy. Give me a moment please...`, + ) + const userRefills = await refillPlayer( + new PublicKey(ctx.user.publicKey), + strategy, + ) for (const userRefill of userRefills) { - // eslint-disable-next-line no-await-in-loop + // eslint-disable-next-line no-await-in-loop await ctx.replyWithHTML(` Signature: click Time: ${userRefill.time.toLocaleDateString()} ${userRefill.time.toLocaleTimeString()} @@ -39,12 +57,13 @@ export const refill = (bot: Telegraf): void => { Tool: ${userRefill.tool} Fuel: ${userRefill.fuel} Ammo ${userRefill.ammo} -Price: ${userRefill.price.toFixed(AD)} ATLAS` - ) +Price: ${userRefill.price.toFixed(AD)} ATLAS`) } if (strategyName === 'optimal') { - await ctx.reply('Pro Tip: Trigger a full refill with \'/refill full\'') + await ctx.reply( + "Pro Tip: Trigger a full refill with '/refill full'", + ) } }) }) diff --git a/src/lib/telegram/commands/refills.ts b/src/lib/telegram/commands/refills.ts index 09e3c492..66c13167 100644 --- a/src/lib/telegram/commands/refills.ts +++ b/src/lib/telegram/commands/refills.ts @@ -15,7 +15,9 @@ export const refills = (bot: Telegraf): void => { } if (!ctx.user.enabled) { - await ctx.reply('Your wallet is currently disabled, toggle with /enable command') + await ctx.reply( + 'Your wallet is currently disabled, toggle with /enable command', + ) return } @@ -23,10 +25,14 @@ export const refills = (bot: Telegraf): void => { const take = 10 await ctx.reply(`Showing the last ${take} refills`) - const userRefills = await Refill.find({ where: { walletPublicKey: ctx.user.publicKey }, take, order: { time: 'DESC' } }) + const userRefills = await Refill.find({ + where: { walletPublicKey: ctx.user.publicKey }, + take, + order: { time: 'DESC' }, + }) for (const refill of userRefills) { - // eslint-disable-next-line no-await-in-loop + // eslint-disable-next-line no-await-in-loop await ctx.replyWithHTML(` Signature: click Time: ${refill.time.toLocaleDateString()} ${refill.time.toLocaleTimeString()} @@ -35,8 +41,7 @@ export const refills = (bot: Telegraf): void => { Tool: ${refill.tool} Fuel: ${refill.fuel} Ammo ${refill.ammo} -Price: ${refill.price.toFixed(AD)} ATLAS` - ) +Price: ${refill.price.toFixed(AD)} ATLAS`) } }) }) diff --git a/src/lib/telegram/commands/stats.ts b/src/lib/telegram/commands/stats.ts index 08cf0c6e..d843383e 100644 --- a/src/lib/telegram/commands/stats.ts +++ b/src/lib/telegram/commands/stats.ts @@ -21,7 +21,9 @@ export const stats = (bot: Telegraf): void => { } if (!ctx.user.enabled) { - await ctx.reply('Your wallet is currently disabled, toggle with /enable command') + await ctx.reply( + 'Your wallet is currently disabled, toggle with /enable command', + ) return } @@ -34,40 +36,74 @@ export const stats = (bot: Telegraf): void => { const fleets = await getAllFleetsForUserPublicKey( connection, new PublicKey(player.publicKey), - fleetProgram) - const [burnRate, price, playerBalance, pendingRewards] = await Promise.all([ - getDailyBurnRate(fleets), - getResourcePrices(), - player.getBalance(), - getPendingRewards(fleets) - ]) - const burnPerDay = - burnRate.food.mul(price.food).add( - burnRate.fuel.mul(price.fuel)).add( - burnRate.tool.mul(price.tool)).add( - burnRate.fuel.mul(price.ammo)) - const balanceTime = burnPerDay.gt(0) ? playerBalance.div(burnPerDay) : Big(0) - - const diffToNextRefill = dayjs().diff(player.nextRefill, 'second') - - const todayRefills = await Refill.findBy({ walletPublicKey: player.publicKey, time: MoreThanOrEqual(dayjs().startOf('day').toDate()) }) - const burnToday = todayRefills.reduce((curr, acc) => curr.add(acc.price), Big(0)) + fleetProgram, + ) + const [burnRate, price, playerBalance, pendingRewards] = + await Promise.all([ + getDailyBurnRate(fleets), + getResourcePrices(), + player.getBalance(), + getPendingRewards(fleets), + ]) + const burnPerDay = burnRate.food + .mul(price.food) + .add(burnRate.fuel.mul(price.fuel)) + .add(burnRate.tool.mul(price.tool)) + .add(burnRate.fuel.mul(price.ammo)) + const balanceTime = burnPerDay.gt(0) + ? playerBalance.div(burnPerDay) + : Big(0) + + const diffToNextRefill = dayjs().diff( + player.nextRefill, + 'second', + ) + + const todayRefills = await Refill.findBy({ + walletPublicKey: player.publicKey, + time: MoreThanOrEqual(dayjs().startOf('day').toDate()), + }) + const burnToday = todayRefills.reduce( + (curr, acc) => curr.add(acc.price), + Big(0), + ) const yesterdayRefills = await Refill.findBy({ - walletPublicKey: player.publicKey, time: Between( + walletPublicKey: player.publicKey, + time: Between( dayjs().subtract(1, 'day').startOf('day').toDate(), - dayjs().subtract(1, 'day').endOf('day').toDate() - ) + dayjs().subtract(1, 'day').endOf('day').toDate(), + ), }) - const burnYesterday = yesterdayRefills.reduce((curr, acc) => curr.add(acc.price), Big(0)) + const burnYesterday = yesterdayRefills.reduce( + (curr, acc) => curr.add(acc.price), + Big(0), + ) - const refills7d = await Refill.findBy({ walletPublicKey: player.publicKey, time: MoreThanOrEqual(dayjs().subtract(7, 'day').startOf('day').toDate()) }) - const burn7d = refills7d.reduce((curr, acc) => curr.add(acc.price), Big(0)) + const refills7d = await Refill.findBy({ + walletPublicKey: player.publicKey, + time: MoreThanOrEqual( + dayjs().subtract(7, 'day').startOf('day').toDate(), + ), + }) + const burn7d = refills7d.reduce( + (curr, acc) => curr.add(acc.price), + Big(0), + ) - const refills30d = await Refill.findBy({ walletPublicKey: player.publicKey, time: MoreThanOrEqual(dayjs().subtract(30, 'day').startOf('day').toDate()) }) - const burn30d = refills30d.reduce((curr, acc) => curr.add(acc.price), Big(0)) + const refills30d = await Refill.findBy({ + walletPublicKey: player.publicKey, + time: MoreThanOrEqual( + dayjs().subtract(30, 'day').startOf('day').toDate(), + ), + }) + const burn30d = refills30d.reduce( + (curr, acc) => curr.add(acc.price), + Big(0), + ) - const avg = (b: Big[]) => b.reduce((c, a) => c.add(a), Big(0)).div(b.length) + const avg = (b: Big[]) => + b.reduce((c, a) => c.add(a), Big(0)).div(b.length) const burnAvg7 = burn7d.div(7) const burnAvg30 = burn30d.div(30) @@ -76,7 +112,8 @@ export const stats = (bot: Telegraf): void => { const drift = burnAvg.sub(burnPerDay) - await ctx.replyWithMarkdownV2(` + await ctx.replyWithMarkdownV2( + ` *Next refill in:* ${dayjs.duration(diffToNextRefill, 'second').humanize()} *Balance:* ${playerBalance.toFixed(AD)} ATLAS *Burn \\(estimate\\):* ${burnPerDay.toFixed(AD)} ATLAS per day @@ -87,7 +124,7 @@ export const stats = (bot: Telegraf): void => { *Drift:* ${drift.toFixed(AD).toString().replace('-', '\\-')} ATLAS *Pending Rewards:* ${pendingRewards.toFixed(AD)} ATLAS *Credit for:* ${dayjs.duration(balanceTime.toNumber(), 'day').humanize(false)} -*Current Tips:* ${player.tip * 100} %`.replace(/\./g, '\\.') +*Current Tips:* ${player.tip * 100} %`.replace(/\./g, '\\.'), ) } }) diff --git a/src/lib/telegram/commands/support.ts b/src/lib/telegram/commands/support.ts index 425008e6..2dcf95ee 100644 --- a/src/lib/telegram/commands/support.ts +++ b/src/lib/telegram/commands/support.ts @@ -6,7 +6,9 @@ import { ContextMessageUpdate } from '../context-message-update' export const support = (bot: Telegraf): void => { bot.command(['support'], async (ctx) => { await ctx.persistentChatAction('typing', async () => { - await ctx.reply(`I really don't know why you would need that, but just in case you want to talk to a human, please contact ${config.bot.owner}`) + await ctx.reply( + `I really don't know why you would need that, but just in case you want to talk to a human, please contact ${config.bot.owner}`, + ) }) }) } diff --git a/src/lib/telegram/commands/tip.ts b/src/lib/telegram/commands/tip.ts index 7efbeb12..8240be1f 100644 --- a/src/lib/telegram/commands/tip.ts +++ b/src/lib/telegram/commands/tip.ts @@ -14,7 +14,9 @@ export const tip = (bot: Telegraf): void => { } if (ctx.params.length !== 1) { - await ctx.reply(`Tip is currently set to ${Big(ctx.user.tip).mul(100).toFixed()} %`) + await ctx.reply( + `Tip is currently set to ${Big(ctx.user.tip).mul(100).toFixed()} %`, + ) await ctx.reply('To update setting, use /tip {percentage}') return @@ -26,14 +28,15 @@ export const tip = (bot: Telegraf): void => { try { ctx.user.tip = Big(tipPercent).div(100).abs().toNumber() await ctx.user.save() - } - catch { + } catch { await ctx.reply('Tip amount must be be a positive number!') return } - await ctx.reply(`Tip set to ${Big(ctx.user.tip).mul(100).toFixed()} %`) + await ctx.reply( + `Tip set to ${Big(ctx.user.tip).mul(100).toFixed()} %`, + ) }) }) } diff --git a/src/lib/telegram/commands/transactions.ts b/src/lib/telegram/commands/transactions.ts index 45c6dda0..215020fd 100644 --- a/src/lib/telegram/commands/transactions.ts +++ b/src/lib/telegram/commands/transactions.ts @@ -15,7 +15,9 @@ export const transactions = (bot: Telegraf): void => { } if (!ctx.user.enabled) { - await ctx.reply('Your wallet is currently disabled, toggle with /enable command') + await ctx.reply( + 'Your wallet is currently disabled, toggle with /enable command', + ) return } @@ -23,15 +25,18 @@ export const transactions = (bot: Telegraf): void => { const take = 10 await ctx.reply(`Showing the last ${take} transactions`) - const userTransactions = await Transaction.find({ where: { walletPublicKey: ctx.user.publicKey }, take, order: { time: 'DESC' } }) + const userTransactions = await Transaction.find({ + where: { walletPublicKey: ctx.user.publicKey }, + take, + order: { time: 'DESC' }, + }) for (const transaction of userTransactions) { // eslint-disable-next-line no-await-in-loop await ctx.replyWithHTML(` Signature: click Time: ${transaction.time.toLocaleDateString()} ${transaction.time.toLocaleTimeString()} -Amount: ${transaction.amount.toFixed(AD)} ATLAS` - ) +Amount: ${transaction.amount.toFixed(AD)} ATLAS`) } }) }) diff --git a/src/lib/telegram/commands/verify.ts b/src/lib/telegram/commands/verify.ts index 8f3c5372..5b27b721 100644 --- a/src/lib/telegram/commands/verify.ts +++ b/src/lib/telegram/commands/verify.ts @@ -4,7 +4,12 @@ import { Telegraf } from 'telegraf' import { Wallet } from '../../../db/entities' import { ContextMessageUpdate } from '../context-message-update' -import { alreadyRegistered, authPending, unknownWallet, wrongParamCount } from '../response' +import { + alreadyRegistered, + authPending, + unknownWallet, + wrongParamCount, +} from '../response' export const verify = (bot: Telegraf): void => { bot.command(['verify'], async (ctx) => { @@ -40,13 +45,21 @@ export const verify = (bot: Telegraf): void => { return } - ctx.reply('Alright! To verify this wallet belongs to you, I need you to send a small amount of ATLAS to me. Don\'t worry. I will add this to your balance of course.') + ctx.reply( + "Alright! To verify this wallet belongs to you, I need you to send a small amount of ATLAS to me. Don't worry. I will add this to your balance of course.", + ) wallet.telegramId = ctx.from.id wallet.authExpire = dayjs().add(1, 'hour').toDate() - wallet.authTxAmount = faker.datatype.number({ min: 0.1, max: 0.999, precision: 0.001 }) + wallet.authTxAmount = faker.datatype.number({ + min: 0.1, + max: 0.999, + precision: 0.001, + }) await wallet.save() await authPending(ctx, wallet) - ctx.reply('This may take a moment. You can check the status with /verify command') + ctx.reply( + 'This may take a moment. You can check the status with /verify command', + ) }) }) } diff --git a/src/lib/telegram/commands/withdraw.ts b/src/lib/telegram/commands/withdraw.ts index c3c3ed0d..8f7c5dd6 100644 --- a/src/lib/telegram/commands/withdraw.ts +++ b/src/lib/telegram/commands/withdraw.ts @@ -26,21 +26,41 @@ export const withdraw = (bot: Telegraf): void => { const wallet = ctx.user const userBalance = await wallet.getBalance() - const withdrawAmount = ctx.params[0] === 'all' ? userBalance : Big(ctx.params[0]).abs() + const withdrawAmount = + ctx.params[0] === 'all' ? userBalance : Big(ctx.params[0]).abs() if (withdrawAmount.gt(userBalance)) { - await ctx.reply(`Amount ${withdrawAmount.toFixed(AD)} exceeds user balance ${userBalance.toFixed(AD)}`) + await ctx.reply( + `Amount ${withdrawAmount.toFixed(AD)} exceeds user balance ${userBalance.toFixed(AD)}`, + ) return } - await ctx.reply(`Sending ${withdrawAmount} ATLAS to ${ctx.user.publicKey}`) - const signature = await sendAtlas(new PublicKey(ctx.user.publicKey), withdrawAmount.toNumber()) + await ctx.reply( + `Sending ${withdrawAmount} ATLAS to ${ctx.user.publicKey}`, + ) + const signatures = await sendAtlas( + new PublicKey(ctx.user.publicKey), + withdrawAmount.toNumber(), + ) - await ctx.reply(`https://solscan.io/tx/${signature}`) const amount = -withdrawAmount - await Transaction.create({ wallet, amount, signature, time: dayjs().toDate(), originalAmount: amount, resource: 'ATLAS' }).save() + await Promise.all( + signatures.map(async (signature) => { + await ctx.reply(`https://solscan.io/tx/${signature}`) + + return Transaction.create({ + wallet, + amount, + signature, + time: dayjs().toDate(), + originalAmount: amount, + resource: 'ATLAS', + }).save() + }), + ) }) }) } diff --git a/src/lib/telegram/middleware/auth.ts b/src/lib/telegram/middleware/auth.ts index 85846bdd..27d26a41 100644 --- a/src/lib/telegram/middleware/auth.ts +++ b/src/lib/telegram/middleware/auth.ts @@ -3,7 +3,10 @@ import { Middleware } from 'telegraf' import { Wallet } from '../../../db/entities' import { ContextMessageUpdate } from '../context-message-update' -export const auth: Middleware = async (ctx: ContextMessageUpdate, next: any) => { +export const auth: Middleware = async ( + ctx: ContextMessageUpdate, + next: any, +) => { if (ctx.from?.id) { const wallet = await Wallet.findOneBy({ telegramId: ctx.from.id }) diff --git a/src/lib/telegram/middleware/params.ts b/src/lib/telegram/middleware/params.ts index d66b42dd..9fb3e81c 100644 --- a/src/lib/telegram/middleware/params.ts +++ b/src/lib/telegram/middleware/params.ts @@ -2,7 +2,10 @@ import { Middleware } from 'telegraf' import { ContextMessageUpdate } from '../context-message-update' -export const params: Middleware = (ctx: ContextMessageUpdate, next: any) => { +export const params: Middleware = ( + ctx: ContextMessageUpdate, + next: any, +) => { if (ctx.message && 'text' in ctx.message) { ctx.params = ctx.message.text.split(' ').splice(1) } diff --git a/src/lib/telegram/response/already-registered.ts b/src/lib/telegram/response/already-registered.ts index 99b9b289..8706542b 100644 --- a/src/lib/telegram/response/already-registered.ts +++ b/src/lib/telegram/response/already-registered.ts @@ -2,6 +2,9 @@ import { Message } from 'telegraf/types' import { ContextMessageUpdate } from '../context-message-update' -export const alreadyRegistered = (ctx: ContextMessageUpdate): Promise => ctx.reply(` +export const alreadyRegistered = ( + ctx: ContextMessageUpdate, +): Promise => + ctx.reply(` Looks like you are already registered and ready to go! `) diff --git a/src/lib/telegram/response/auth-pending.ts b/src/lib/telegram/response/auth-pending.ts index e0a7395c..10e41965 100644 --- a/src/lib/telegram/response/auth-pending.ts +++ b/src/lib/telegram/response/auth-pending.ts @@ -4,7 +4,10 @@ import dayjs from '../../../dayjs' import { Wallet } from '../../../db/entities' import { ContextMessageUpdate } from '../context-message-update' -export const authPending = (ctx: ContextMessageUpdate, wallet: Wallet): Promise => { +export const authPending = ( + ctx: ContextMessageUpdate, + wallet: Wallet, +): Promise => { const authExpire = dayjs().diff(wallet.authExpire, 'second') return ctx.reply(` diff --git a/src/lib/telegram/response/index.ts b/src/lib/telegram/response/index.ts index 3ee03bb0..ace3b11f 100644 --- a/src/lib/telegram/response/index.ts +++ b/src/lib/telegram/response/index.ts @@ -1,5 +1,5 @@ -export * from './unauthorized' export * from './already-registered' +export * from './auth-pending' +export * from './unauthorized' export * from './unknown-wallet' export * from './wrong-param-count' -export * from './auth-pending' diff --git a/src/lib/telegram/response/unauthorized.ts b/src/lib/telegram/response/unauthorized.ts index ede5ef41..6ef48867 100644 --- a/src/lib/telegram/response/unauthorized.ts +++ b/src/lib/telegram/response/unauthorized.ts @@ -2,6 +2,9 @@ import { Message } from 'telegraf/types' import { ContextMessageUpdate } from '../context-message-update' -export const unauthorized = (ctx: ContextMessageUpdate): Promise => ctx.reply(` +export const unauthorized = ( + ctx: ContextMessageUpdate, +): Promise => + ctx.reply(` Huh, who are you? Please authorize first! `) diff --git a/src/lib/telegram/response/unknown-wallet.ts b/src/lib/telegram/response/unknown-wallet.ts index 69ad3fe6..bdc4f310 100644 --- a/src/lib/telegram/response/unknown-wallet.ts +++ b/src/lib/telegram/response/unknown-wallet.ts @@ -2,6 +2,9 @@ import { Message } from 'telegraf/types' import { ContextMessageUpdate } from '../context-message-update' -export const unknownWallet = (ctx: ContextMessageUpdate): Promise => ctx.reply(` +export const unknownWallet = ( + ctx: ContextMessageUpdate, +): Promise => + ctx.reply(` Could not find wallet. Please send some ATLAS first! `) diff --git a/src/lib/telegram/response/wrong-param-count.ts b/src/lib/telegram/response/wrong-param-count.ts index 7e44269c..72ae4e2e 100644 --- a/src/lib/telegram/response/wrong-param-count.ts +++ b/src/lib/telegram/response/wrong-param-count.ts @@ -2,5 +2,7 @@ import { Message } from 'telegraf/types' import { ContextMessageUpdate } from '../context-message-update' -export const wrongParamCount = - (ctx: ContextMessageUpdate, message: string): Promise => ctx.reply(message) +export const wrongParamCount = ( + ctx: ContextMessageUpdate, + message: string, +): Promise => ctx.reply(message) diff --git a/src/lib/telegram/telegram-bot.ts b/src/lib/telegram/telegram-bot.ts index d3563b55..7a7eae3d 100644 --- a/src/lib/telegram/telegram-bot.ts +++ b/src/lib/telegram/telegram-bot.ts @@ -7,7 +7,10 @@ import { ContextMessageUpdate } from './context-message-update' import { auth } from './middleware' import { params } from './middleware/params' -const telegramBot: Telegraf = new Telegraf(config.bot.telegramToken, { handlerTimeout: 360_000 }) +const telegramBot: Telegraf = new Telegraf( + config.bot.telegramToken, + { handlerTimeout: 360_000 }, +) telegramBot.use(auth) telegramBot.use(params) diff --git a/src/logger.ts b/src/logger.ts index e2d7425d..32c8b875 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -5,18 +5,21 @@ import { config } from './config' const prettyError = new PrettyError() .skipNodeFiles() - .skip((traceLine: Record): boolean => traceLine.packageName !== '[current]') + .skip( + (traceLine: Record): boolean => + traceLine.packageName !== '[current]', + ) .appendStyle({ 'pretty-error': { - marginLeft: 0 + marginLeft: 0, }, 'pretty-error > trace': { - marginTop: 0 + marginTop: 0, }, 'pretty-error > trace > item': { bullet: '', - marginBottom: 0 - } + marginBottom: 0, + }, }) const prettyErrorFormat = winston.format((info) => { @@ -24,25 +27,22 @@ const prettyErrorFormat = winston.format((info) => { return { ...info, message: prettyError.render(info), - stack: undefined + stack: undefined, } } return info }) -const format = - winston.format.combine( - winston.format.colorize(), - winston.format.errors({ stack: true }), - prettyErrorFormat(), - winston.format.simple() - ) +const format = winston.format.combine( + winston.format.colorize(), + winston.format.errors({ stack: true }), + prettyErrorFormat(), + winston.format.simple(), +) export const logger = winston.createLogger({ level: config.app?.logLevel || 'info', format, - transports: [ - new winston.transports.Console() - ] + transports: [new winston.transports.Console()], }) diff --git a/src/main/basedbot/basedbot.ts b/src/main/basedbot/basedbot.ts new file mode 100644 index 00000000..525362a3 --- /dev/null +++ b/src/main/basedbot/basedbot.ts @@ -0,0 +1,286 @@ +import { + getAssociatedTokenAddressSync, + TOKEN_PROGRAM_ID, +} from '@solana/spl-token' +import { PublicKey } from '@solana/web3.js' +import { + getParsedTokenAccountsByOwner, + ixReturnsToIxs, +} from '@staratlas/data-source' +import { + Fleet, + Game, + getCleanPodsByStarbasePlayerAccounts, + getPodCleanupInstructions, + Starbase, +} from '@staratlas/sage' +import BN from 'bn.js' + +import { Sentry } from '../../sentry' + +import { logger } from '../../logger' +import { sleep } from '../../service/sleep' +import { connection } from '../../service/sol' +import { sendAndConfirmInstructions } from '../../service/sol/send-and-confirm-tx' +import { keyPair } from '../../service/wallet' + +import { getFleetStrategy } from './fleet-strategies/get-fleet-strategy' +import { StrategyConfig } from './fleet-strategies/strategy-config' +import { createInfoStrategy } from './fsm/info' +import { programs } from './lib/programs' +import { createFleet, FleetShip } from './lib/sage/act/create-fleet' +import { depositCargo } from './lib/sage/act/deposit-cargo' +import { ensureShips } from './lib/sage/act/deposit-ship' +import { getCargoStatsDefinition } from './lib/sage/state/cargo-stats-definition' +import { sageGame } from './lib/sage/state/game' +import { settleFleet } from './lib/sage/state/settle-fleet' +import { getStarbasePlayer } from './lib/sage/state/starbase-player' +import { getPlayerContext, Player } from './lib/sage/state/user-account' +import { + FleetInfo, + getFleetInfo, + getUserFleets, +} from './lib/sage/state/user-fleets' +import { getMapContext, WorldMap } from './lib/sage/state/world-map' +// eslint-disable-next-line import/max-dependencies +import { getName } from './lib/sage/util' + +// eslint-disable-next-line require-await +export const create = async (): Promise => { + logger.info('Starting basedbot...') +} + +// eslint-disable-next-line require-await +export const stop = async (): Promise => { + logger.info('Stopping basedbot') +} + +type BotConfig = { + player: Player + map: WorldMap + fleetStrategies: StrategyConfig +} + +const applyStrategy = ( + fleetInfo: FleetInfo, + config: StrategyConfig, +): Promise => { + const { strategy } = config.match(fleetInfo.fleetName, config.map) + + if (!strategy) { + logger.warn( + `No strategy for fleet: ${fleetInfo.fleetName}. Using Info Strategy...`, + ) + + return createInfoStrategy().apply(fleetInfo) + } + + return strategy.apply(fleetInfo) +} + +export const getTokenBalance = async ( + account: PublicKey, + mint: PublicKey, +): Promise => { + const allTokenAccounts = await getParsedTokenAccountsByOwner( + connection, + account, + TOKEN_PROGRAM_ID, + ) + + const sourceTokenAccount = getAssociatedTokenAddressSync( + mint, + account, + true, + ) + const [mintTokenAccount] = allTokenAccounts.filter((it) => + it.address.equals(sourceTokenAccount), + ) + + if (!mintTokenAccount) { + logger.warn('Token account not found, assuming empty balance.') + } + + return new BN(mintTokenAccount ? mintTokenAccount.amount.toString() : 0) +} + +const importR4 = async (player: Player, game: Game): Promise => { + await Promise.all( + [ + game.data.mints.food, + game.data.mints.ammo, + game.data.mints.fuel, + game.data.mints.repairKit, + ].map(async (mint) => { + const amountAtOrigin = await getTokenBalance( + player.signer.publicKey(), + mint, + ) + + if (amountAtOrigin.gtn(0)) { + logger.info( + `Importing R4 for ${mint.toBase58()}: ${amountAtOrigin}`, + ) + + await depositCargo( + player, + game, + player.homeStarbase, + mint, + amountAtOrigin, + ) + } + }), + ) +} + +const ensureFleets = async ( + player: Player, + game: Game, + fleets: Array, + fleetStrategies: StrategyConfig, +): Promise => { + const existingFleets = fleets.map(getName) + const wantedFleets = Array.from(fleetStrategies.map.keys()) + + const neededFleets = wantedFleets.filter((f) => !existingFleets.includes(f)) + + if (neededFleets.length > 0) { + logger.info('Creating fleets:', neededFleets) + } + + const neededShips = new Map() + + neededFleets.forEach((fleetName) => { + const fleetStrategy = fleetStrategies.map.get(fleetName)! + + fleetStrategy.fleet?.forEach((fleetShip) => { + const curr = neededShips.get(fleetShip.shipMint.toBase58()) ?? 0 + + neededShips.set( + fleetShip.shipMint.toBase58(), + curr + fleetShip.count, + ) + }) + }) + + const shipMints = Array.from(neededShips.keys()) + .map((mint) => [ + { + count: neededShips.get(mint) ?? 0, + shipMint: new PublicKey(mint), + } as FleetShip, + ]) + .flat() + + await ensureShips(player, game, player.homeStarbase, shipMints) + + await Promise.all( + neededFleets.map((fleetName) => { + const fleetStrategy = fleetStrategies.map.get(fleetName)! + + if (!fleetStrategy.fleet) { + logger.info('Cannot ensure fleet without config.') + + return Promise.resolve() + } + + return createFleet( + player, + game, + player.homeStarbase, + fleetStrategy.fleet!, + fleetName, + ) + }), + ) +} + +const cleanupPods = async (player: Player, game: Game, starbase: Starbase) => { + const starbasePlayer = await getStarbasePlayer(player, starbase, programs) + const podCleanup = await getCleanPodsByStarbasePlayerAccounts( + connection, + programs.cargo, + starbasePlayer.key, + ) + const [cargoStatsDefinition] = await getCargoStatsDefinition() + + if (!podCleanup) { + logger.info('Nothing to Clean up') + + return + } + + const ixs = getPodCleanupInstructions( + podCleanup, + programs.sage, + programs.cargo, + starbasePlayer.key, + starbase.key, + player.profile.key, + player.profileFaction.key, + cargoStatsDefinition.key, + game.key, + game.data.gameState, + player.signer, + 0, + ) + + await sendAndConfirmInstructions(await ixReturnsToIxs(ixs, player.signer)) +} + +const basedbot = async (botConfig: BotConfig) => { + logger.info( + '-------------------------------------------------------------------------------------', + ) + const { player, map } = botConfig + const [fleets, game] = await Promise.all([ + getUserFleets(player), + sageGame(), + ]) + const fleetInfos = await Promise.all( + fleets.map((f) => getFleetInfo(f, player, map)), + ) + + await cleanupPods(player, game, player.homeStarbase) + + await Promise.all([ + importR4(player, game), + ensureFleets(player, game, fleets, botConfig.fleetStrategies), + ]) + + await Promise.all( + fleetInfos.map((fleetInfo) => settleFleet(fleetInfo, player, game)), + ) + await Promise.all( + fleetInfos.map((fleetInfo) => + applyStrategy(fleetInfo, botConfig.fleetStrategies), + ), + ) + logger.info( + '-------------------------------------------------------------------------------------', + ) +} + +export const start = async (): Promise => { + const player = await getPlayerContext(keyPair.publicKey, keyPair) + const game = await sageGame() + const map = await getMapContext(game) + const fleetStrategies = getFleetStrategy(map, player, game) + + // eslint-disable-next-line no-constant-condition + while (true) { + try { + await basedbot({ + player, + map, + fleetStrategies, + }) + } catch (e) { + Sentry.captureException(e) + logger.error(e) + } finally { + await sleep(10000) + } + } +} diff --git a/src/main/basedbot/fleet-strategies/atlasnet-fc-strategy.ts b/src/main/basedbot/fleet-strategies/atlasnet-fc-strategy.ts new file mode 100644 index 00000000..f4527be7 --- /dev/null +++ b/src/main/basedbot/fleet-strategies/atlasnet-fc-strategy.ts @@ -0,0 +1,133 @@ +import { Game } from '@staratlas/sage' +import { Chance } from 'chance' + +import { mine } from '../fsm/configs/mine/mine' +import { createInfoStrategy } from '../fsm/info' +import { createMiningStrategy } from '../fsm/mine' +import { createTransportStrategy, transport } from '../fsm/transport' +import { FleetShips } from '../lib/sage/act/create-fleet' +import { Calico, Ogrika, Pearce, ships } from '../lib/sage/ships' +import { Player } from '../lib/sage/state/user-account' +import { WorldMap } from '../lib/sage/state/world-map' +import { + Faction, + galaxySectorsData, + SectorInfo, +} from '../lib/util/galaxy-sectors-data' + +import { nameMapMatcher } from './name-map-matcher' +import { makeStrategyMap, StrategyConfig, StrategyMap } from './strategy-config' + +export const randomIntFromInterval = (min: number, max: number): number => { + return Math.floor(Math.random() * (max - min + 1) + min) +} + +const getRandomFleetForFaction = (faction: Faction): FleetShips => { + switch (faction) { + case Faction.MUD: + return [ + { + shipMint: ships[Pearce.D9].mint, + count: randomIntFromInterval(1, 2), + }, + { + shipMint: ships[Pearce.F4].mint, + count: randomIntFromInterval(2, 3), + }, + ] + case Faction.ONI: + return [ + { + shipMint: ships[Ogrika.Niruch].mint, + count: randomIntFromInterval(1, 2), + }, + { + shipMint: ships[Ogrika.Sunpaa].mint, + count: randomIntFromInterval(2, 4), + }, + ] + case Faction.UST: + return [ + { + shipMint: ships[Calico.Guardian].mint, + count: randomIntFromInterval(1, 2), + }, + { + shipMint: ships[Calico.Evac].mint, + count: randomIntFromInterval(2, 4), + }, + ] + default: + throw new Error('Unknown Faction') + } +} + +const getRandomFleetName = (chance: Chance.Chance, maxLen: number): string => { + const getName = () => `${chance.animal()} Fleet` + + let name = getName() + + while (name.length > maxLen) { + name = getName() + } + + return name +} + +const randomSector = (chance: Chance.Chance, sectors: Array) => + sectors[chance.integer({ min: 0, max: sectors.length - 1 })].coordinates + +export const atlasnetFcStrategy = + (count: number) => + ( + map: WorldMap, + player: Player, + game: Game, + seed: string = 'basedbot', + ): StrategyConfig => { + const strategyMap: StrategyMap = makeStrategyMap() + const chance = new Chance(seed) + const sectors = galaxySectorsData() + .filter((sector) => sector.closestFaction === player.faction) + .sort((a, b) => a.name.localeCompare(b.name)) + + for (let i = 0; i < count; i++) { + const home = randomSector(chance, sectors) + const target = randomSector(chance, sectors) + + strategyMap.set(getRandomFleetName(chance, 32), { + fleet: getRandomFleetForFaction(player.faction), + strategy: createMiningStrategy( + mine(map, home, target), + player, + game, + ), + }) + // No transport fleet needed if mining fleet uses CSS as home base. + if (!home.equals(player.homeCoordinates)) { + strategyMap.set(getRandomFleetName(chance, 32), { + fleet: getRandomFleetForFaction(player.faction), + strategy: createTransportStrategy( + transport( + map, + player.homeCoordinates, + home, + new Set([ + game.data.mints.fuel, + game.data.mints.ammo, + game.data.mints.food, + game.data.mints.repairKit, + ]), + ), + player, + game, + ), + }) + } + } + + return { + match: nameMapMatcher(createInfoStrategy()), + map: strategyMap, + } + } diff --git a/src/main/basedbot/fleet-strategies/atlasnet-lu-strategy.ts b/src/main/basedbot/fleet-strategies/atlasnet-lu-strategy.ts new file mode 100644 index 00000000..8ba68694 --- /dev/null +++ b/src/main/basedbot/fleet-strategies/atlasnet-lu-strategy.ts @@ -0,0 +1,400 @@ +import { Game } from '@staratlas/sage' + +import { mineBiomass } from '../fsm/configs/mine/mine-biomass' +import { mineCarbon } from '../fsm/configs/mine/mine-carbon' +import { mineConfig } from '../fsm/configs/mine/mine-config' +import { mineCopperOre } from '../fsm/configs/mine/mine-copper-ore' +import { mineHydrogen } from '../fsm/configs/mine/mine-hydrogen' +import { mineIronOre } from '../fsm/configs/mine/mine-iron-ore' +import { mineLumanite } from '../fsm/configs/mine/mine-lumanite' +import { mineNitrogen } from '../fsm/configs/mine/mine-nitrogen' +import { mineRochinol } from '../fsm/configs/mine/mine-rochinol' +import { mineSilicia } from '../fsm/configs/mine/mine-silicia' +import { mineTitaniumOre } from '../fsm/configs/mine/mine-titanium-ore' +import { createInfoStrategy } from '../fsm/info' +import { createMiningStrategy } from '../fsm/mine' +import { Player } from '../lib/sage/state/user-account' +import { mineableByCoordinates, WorldMap } from '../lib/sage/state/world-map' +import { Coordinates } from '../lib/util/coordinates' + +import { nameMapMatcher } from './name-map-matcher' +import { StrategyConfig } from './strategy-config' + +export const atlasnetLuStrategy = ( + map: WorldMap, + player: Player, + game: Game, +): StrategyConfig => { + return { + match: nameMapMatcher(createInfoStrategy()), + map: new Map([ + [ + 'Armadillo Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineBiomass(map), + player, + game, + ), + }, + ], + [ + 'Barnacle Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineConfig({ + homeBase: Coordinates.fromNumber(-40, 30), + targetBase: Coordinates.fromNumber(-19, 40), + resource: mineableByCoordinates( + map, + Coordinates.fromNumber(-19, 40), + ) + .values() + .next().value, + }), + player, + game, + ), + }, + ], + [ + 'Cobra Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineConfig({ + homeBase: Coordinates.fromNumber(-40, 30), + targetBase: Coordinates.fromNumber(-18, 23), + resource: mineableByCoordinates( + map, + Coordinates.fromNumber(-18, 23), + ) + .values() + .next().value, + }), + player, + game, + ), + }, + ], + [ + 'Falcon Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineCarbon(map), + player, + game, + ), + }, + ], + [ + 'Geoffroys Cat Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineNitrogen(map), + player, + game, + ), + }, + ], + [ + 'Gerbils Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineSilicia(map), + player, + game, + ), + }, + ], + [ + 'Grasshopper Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineLumanite(map), + player, + game, + ), + }, + ], + [ + 'Guanaco Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineCopperOre(map), + player, + game, + ), + }, + ], + [ + 'King Cobra Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineIronOre(map), + player, + game, + ), + }, + ], + [ + 'Pacific Sardine Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineHydrogen(map), + player, + game, + ), + }, + ], + [ + 'Porpoise Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineRochinol(map), + player, + game, + ), + }, + ], + [ + 'Rabbit Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineHydrogen(map), + player, + game, + ), + }, + ], + [ + 'Smalltooth Sawfish Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineTitaniumOre(map), + player, + game, + ), + }, + ], + [ + 'Sugar Gliders Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineIronOre(map), + player, + game, + ), + }, + ], + [ + 'Turkey Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineHydrogen(map), + player, + game, + ), + }, + ], + + [ + 'Aardwolf Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineBiomass(map), + player, + game, + ), + }, + ], + [ + 'Antelope Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineConfig({ + homeBase: Coordinates.fromNumber(-40, 30), + targetBase: Coordinates.fromNumber(-19, 40), + resource: mineableByCoordinates( + map, + Coordinates.fromNumber(-19, 40), + ) + .values() + .next().value, + }), + player, + game, + ), + }, + ], + [ + 'Boa Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineConfig({ + homeBase: Coordinates.fromNumber(-40, 30), + targetBase: Coordinates.fromNumber(-18, 23), + resource: mineableByCoordinates( + map, + Coordinates.fromNumber(-18, 23), + ) + .values() + .next().value, + }), + player, + game, + ), + }, + ], + [ + 'Chinchillas Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineCarbon(map), + player, + game, + ), + }, + ], + [ + 'Fathead Sculpin Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineNitrogen(map), + player, + game, + ), + }, + ], + [ + 'Giant Tortoise Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineSilicia(map), + player, + game, + ), + }, + ], + [ + 'Kultarr Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineLumanite(map), + player, + game, + ), + }, + ], + [ + 'Leopard Seal Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineCopperOre(map), + player, + game, + ), + }, + ], + [ + 'Pangolin Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineIronOre(map), + player, + game, + ), + }, + ], + [ + 'Rhinoceros Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineHydrogen(map), + player, + game, + ), + }, + ], + [ + 'Snow Leopard Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineRochinol(map), + player, + game, + ), + }, + ], + [ + 'Southern White Faced Owl Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineHydrogen(map), + player, + game, + ), + }, + ], + [ + 'Turkeys Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineTitaniumOre(map), + player, + game, + ), + }, + ], + [ + 'Zebra Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineIronOre(map), + player, + game, + ), + }, + ], + [ + 'Guinea Fowl Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineHydrogen(map), + player, + game, + ), + }, + ], + ]), + } +} diff --git a/src/main/basedbot/fleet-strategies/atlasnet-qt-strategy.ts b/src/main/basedbot/fleet-strategies/atlasnet-qt-strategy.ts new file mode 100644 index 00000000..e7f98968 --- /dev/null +++ b/src/main/basedbot/fleet-strategies/atlasnet-qt-strategy.ts @@ -0,0 +1,149 @@ +import { Game } from '@staratlas/sage' +import { Chance } from 'chance' + +import { mine } from '../fsm/configs/mine/mine' +import { createInfoStrategy } from '../fsm/info' +import { createMiningStrategy } from '../fsm/mine' +import { createTransportStrategy, transport } from '../fsm/transport' +import { FleetShips } from '../lib/sage/act/create-fleet' +import { Calico, Ogrika, Pearce, ships } from '../lib/sage/ships' +import { Player } from '../lib/sage/state/user-account' +import { WorldMap } from '../lib/sage/state/world-map' +import { + Faction, + galaxySectorsData, + SectorInfo, +} from '../lib/util/galaxy-sectors-data' + +import { nameMapMatcher } from './name-map-matcher' +import { makeStrategyMap, StrategyConfig, StrategyMap } from './strategy-config' + +export const randomIntFromInterval = (min: number, max: number): number => { + return Math.floor(Math.random() * (max - min + 1) + min) +} + +const getRandomFleetForFaction = (faction: Faction): FleetShips => { + switch (faction) { + case Faction.MUD: + return [ + { + shipMint: ships[Pearce.D9].mint, + count: randomIntFromInterval(1, 2), + }, + { + shipMint: ships[Pearce.F4].mint, + count: randomIntFromInterval(2, 3), + }, + ] + case Faction.ONI: + return [ + { + shipMint: ships[Ogrika.Niruch].mint, + count: randomIntFromInterval(1, 2), + }, + { + shipMint: ships[Ogrika.Sunpaa].mint, + count: randomIntFromInterval(2, 4), + }, + ] + case Faction.UST: + return [ + { + shipMint: ships[Calico.Guardian].mint, + count: randomIntFromInterval(1, 2), + }, + { + shipMint: ships[Calico.Evac].mint, + count: randomIntFromInterval(2, 4), + }, + ] + default: + throw new Error('Unknown Faction') + } +} + +const getRandomFleetName = (chance: Chance.Chance, maxLen: number): string => { + const getName = () => `${chance.animal()} Fleet` + + let name = getName() + + while (name.length > maxLen) { + name = getName() + } + + return name +} + +const randomSector = (chance: Chance.Chance, sectors: Array) => + sectors[chance.integer({ min: 0, max: sectors.length - 1 })].coordinates + +export const atlasnetQtStrategy = + (count: number) => + ( + map: WorldMap, + player: Player, + game: Game, + seed: string = 'basedbot', + ): StrategyConfig => { + const strategyMap: StrategyMap = makeStrategyMap() + const chance = new Chance(seed) + const sectors = galaxySectorsData() + .filter((sector) => sector.closestFaction === player.faction) + .filter((sector) => + [ + 'MUD-2', + 'MUD-3', + 'MUD-4', + 'MUD-5', + 'ONI-2', + 'ONI-3', + 'ONI-4', + 'ONI-5', + 'UST-2', + 'UST-3', + 'UST-4', + 'UST-5', + ].includes(sector.name), + ) + .sort((a, b) => a.name.localeCompare(b.name)) + + for (let i = 0; i < count; i++) { + const home = randomSector(chance, sectors) + const target = randomSector(chance, sectors) + + strategyMap.set(getRandomFleetName(chance, 32), { + fleet: getRandomFleetForFaction(player.faction), + strategy: createMiningStrategy( + mine(map, home, target), + player, + game, + ), + }) + // No transport fleet needed if mining fleet uses CSS as home base. + if (!home.equals(player.homeCoordinates)) { + strategyMap.set(getRandomFleetName(chance, 32), { + fleet: getRandomFleetForFaction(player.faction), + strategy: createTransportStrategy( + transport( + map, + player.homeCoordinates, + home, + new Set([ + game.data.mints.fuel, + game.data.mints.ammo, + game.data.mints.food, + game.data.mints.repairKit, + ]), + ), + player, + game, + ), + }) + } + } + + return { + match: nameMapMatcher(createInfoStrategy()), + map: strategyMap, + } + } diff --git a/src/main/basedbot/fleet-strategies/destruct-all-strategy.ts b/src/main/basedbot/fleet-strategies/destruct-all-strategy.ts new file mode 100644 index 00000000..29ed0b2c --- /dev/null +++ b/src/main/basedbot/fleet-strategies/destruct-all-strategy.ts @@ -0,0 +1,21 @@ +import { Game } from '@staratlas/sage' + +import { createDestructStrategy, destructConfig } from '../fsm/destruct' +import { Player } from '../lib/sage/state/user-account' +import { WorldMap } from '../lib/sage/state/world-map' + +import { nameMapMatcher } from './name-map-matcher' +import { makeStrategyMap, StrategyConfig } from './strategy-config' + +export const destructAllStrategy = ( + worldMap: WorldMap, + player: Player, + game: Game, +): StrategyConfig => { + return { + match: nameMapMatcher( + createDestructStrategy(destructConfig({ worldMap }), player, game), + ), + map: makeStrategyMap(), + } +} diff --git a/src/main/basedbot/fleet-strategies/disband-all-strategy.ts b/src/main/basedbot/fleet-strategies/disband-all-strategy.ts new file mode 100644 index 00000000..496d2b9d --- /dev/null +++ b/src/main/basedbot/fleet-strategies/disband-all-strategy.ts @@ -0,0 +1,26 @@ +import { Game } from '@staratlas/sage' + +import { disbandConfig } from '../fsm/configs/disband-config' +import { createDisbandStrategy } from '../fsm/disband' +import { Player } from '../lib/sage/state/user-account' +import { WorldMap } from '../lib/sage/state/world-map' + +import { nameMapMatcher } from './name-map-matcher' +import { makeStrategyMap, StrategyConfig } from './strategy-config' + +export const disbandAllStrategy = ( + worldMap: WorldMap, + player: Player, + game: Game, +): StrategyConfig => { + return { + match: nameMapMatcher( + createDisbandStrategy( + disbandConfig({ worldMap, homeBase: player.homeCoordinates }), + player, + game, + ), + ), + map: makeStrategyMap(), + } +} diff --git a/src/main/basedbot/fleet-strategies/get-fleet-strategy.ts b/src/main/basedbot/fleet-strategies/get-fleet-strategy.ts new file mode 100644 index 00000000..34ae08d1 --- /dev/null +++ b/src/main/basedbot/fleet-strategies/get-fleet-strategy.ts @@ -0,0 +1,37 @@ +import { Game } from '@staratlas/sage' + +import { Player } from '../lib/sage/state/user-account' +import { WorldMap } from '../lib/sage/state/world-map' + +import { atlasnetFcStrategy } from './atlasnet-fc-strategy' +import { atlasnetLuStrategy } from './atlasnet-lu-strategy' +import { atlasnetQtStrategy } from './atlasnet-qt-strategy' +import { mainnetLuStrategy } from './mainnet-lu-strategy' +import { StrategyConfig } from './strategy-config' + +export const getFleetStrategy = ( + map: WorldMap, + player: Player, + game: Game, +): StrategyConfig => { + switch (player.publicKey.toString()) { + case 'k49Y5xwN7Nyi19TqDR4zbCFuAt8kgy6qMaJ6Kj1wHrn': + return atlasnetLuStrategy(map, player, game) + case 'AePY3wEoUFcFuXeUU9X26YK6tNKQMZovBgvY54LK2B8N': + return mainnetLuStrategy(map, player, game) + case 'CgHvzwGbwWv3CwLTvEgeqSKeD8EwMdTfiiCG3dFrKVVC': + return atlasnetFcStrategy(150)(map, player, game, 'mud') + // return destructAllStrategy(map, player, game) + case '9KBrgWVjsmdZ3YEjcsa3wrbbJREgZgS7vDbgoz2aHaNm': + return atlasnetFcStrategy(150)(map, player, game, 'ustur') + // return destructAllStrategy(map, player, game) + case 'FUwHSqujzcPD44SDZYJXuk73NbkEyYQwcLMioHhpjbx2': + return atlasnetFcStrategy(150)(map, player, game, 'oni') + // return destructAllStrategy(map, player, game) + case '34ghznSJCYEMrS1aC55UYZZUuxfuurA9441aKnigmYyz': + return atlasnetQtStrategy(1)(map, player, game, 'le.local') + // return destructAllStrategy(map, player, game) + default: + throw new Error('Unknown strategy') + } +} diff --git a/src/main/basedbot/fleet-strategies/mainnet-lu-strategy.ts b/src/main/basedbot/fleet-strategies/mainnet-lu-strategy.ts new file mode 100644 index 00000000..4ba3d211 --- /dev/null +++ b/src/main/basedbot/fleet-strategies/mainnet-lu-strategy.ts @@ -0,0 +1,33 @@ +import { Game } from '@staratlas/sage' + +import { mineHydrogen } from '../fsm/configs/mine/mine-hydrogen' +import { createInfoStrategy } from '../fsm/info' +import { createMiningStrategy } from '../fsm/mine' +import { Player } from '../lib/sage/state/user-account' +import { WorldMap } from '../lib/sage/state/world-map' + +import { nameMapMatcher } from './name-map-matcher' +import { StrategyConfig } from './strategy-config' + +export const mainnetLuStrategy = ( + worldMap: WorldMap, + player: Player, + game: Game, +): StrategyConfig => { + return { + match: nameMapMatcher(createInfoStrategy()), + map: new Map([ + [ + 'Red Ruffed Lemur Fleet', + { + fleet: null, + strategy: createMiningStrategy( + mineHydrogen(worldMap), + player, + game, + ), + }, + ], + ]), + } +} diff --git a/src/main/basedbot/fleet-strategies/name-map-matcher.ts b/src/main/basedbot/fleet-strategies/name-map-matcher.ts new file mode 100644 index 00000000..dfd07fea --- /dev/null +++ b/src/main/basedbot/fleet-strategies/name-map-matcher.ts @@ -0,0 +1,14 @@ +import { Strategy } from '../fsm/strategy' + +import { FleetStrategy, StrategyMap } from './strategy-config' + +export const nameMapMatcher = + (fallback: Strategy) => + (key: string, strategyMap: StrategyMap): FleetStrategy => { + return ( + strategyMap.get(key) || { + strategy: fallback, + fleet: null, + } + ) + } diff --git a/src/main/basedbot/fleet-strategies/number-map-matcher.ts b/src/main/basedbot/fleet-strategies/number-map-matcher.ts new file mode 100644 index 00000000..6d0223b0 --- /dev/null +++ b/src/main/basedbot/fleet-strategies/number-map-matcher.ts @@ -0,0 +1,20 @@ +import { Strategy } from '../fsm/strategy' + +import { FleetStrategy, StrategyMap } from './strategy-config' + +export const numberMapMatcher = + (fallback: Strategy) => + (key: string, strategyMap: StrategyMap): FleetStrategy => { + const fallbackFleetStrategy = { + strategy: fallback, + fleet: null, + } + const extractNumbers = (str: string): Array | undefined => + str.match(/\d+/g)?.map(Number) + const [index] = extractNumbers(key) ?? [] + const mapKey = Array.from(strategyMap.keys()).find(extractNumbers) + + if (!index || !mapKey) return fallbackFleetStrategy + + return strategyMap.get(mapKey) || fallbackFleetStrategy + } diff --git a/src/main/basedbot/fleet-strategies/strategy-config.ts b/src/main/basedbot/fleet-strategies/strategy-config.ts new file mode 100644 index 00000000..8b168526 --- /dev/null +++ b/src/main/basedbot/fleet-strategies/strategy-config.ts @@ -0,0 +1,16 @@ +import { Strategy } from '../fsm/strategy' +import { FleetShips } from '../lib/sage/act/create-fleet' + +export type FleetStrategy = { + fleet: FleetShips | null + strategy: Strategy +} +export type StrategyMap = Map +export const makeStrategyMap = (): StrategyMap => + new Map() +export type MapMatcher = (key: string, map: StrategyMap) => FleetStrategy + +export type StrategyConfig = { + map: StrategyMap + match: MapMatcher +} diff --git a/src/main/basedbot/fsm/configs/disband-config.ts b/src/main/basedbot/fsm/configs/disband-config.ts new file mode 100644 index 00000000..0b0f2bd7 --- /dev/null +++ b/src/main/basedbot/fsm/configs/disband-config.ts @@ -0,0 +1,20 @@ +import { WarpMode } from '../../lib/sage/act/move' +import { WorldMap } from '../../lib/sage/state/world-map' +import { Coordinates } from '../../lib/util/coordinates' + +export type DisbandConfig = { + worldMap: WorldMap + homeBase: Coordinates + warpMode: WarpMode +} + +export const disbandConfig = ( + config: Partial & { + worldMap: WorldMap + homeBase: Coordinates + }, +): DisbandConfig => ({ + worldMap: config.worldMap, + homeBase: config.homeBase, + warpMode: config.warpMode || 'auto', +}) diff --git a/src/main/basedbot/fsm/configs/mine/mine-biomass.ts b/src/main/basedbot/fsm/configs/mine/mine-biomass.ts new file mode 100644 index 00000000..8e8abe99 --- /dev/null +++ b/src/main/basedbot/fsm/configs/mine/mine-biomass.ts @@ -0,0 +1,16 @@ +import { + mineableByCoordinates, + WorldMap, +} from '../../../lib/sage/state/world-map' +import { Coordinates } from '../../../lib/util/coordinates' + +import { MineConfig, mineConfig } from './mine-config' + +export const mineBiomass = (map: WorldMap): MineConfig => + mineConfig({ + homeBase: Coordinates.fromNumber(-40, 30), + targetBase: Coordinates.fromNumber(-42, 35), + resource: mineableByCoordinates(map, Coordinates.fromNumber(-42, 35)) + .values() + .next().value, + }) diff --git a/src/main/basedbot/fsm/configs/mine/mine-carbon.ts b/src/main/basedbot/fsm/configs/mine/mine-carbon.ts new file mode 100644 index 00000000..9870950b --- /dev/null +++ b/src/main/basedbot/fsm/configs/mine/mine-carbon.ts @@ -0,0 +1,16 @@ +import { + mineableByCoordinates, + WorldMap, +} from '../../../lib/sage/state/world-map' +import { Coordinates } from '../../../lib/util/coordinates' + +import { MineConfig, mineConfig } from './mine-config' + +export const mineCarbon = (map: WorldMap): MineConfig => + mineConfig({ + homeBase: Coordinates.fromNumber(-40, 30), + targetBase: Coordinates.fromNumber(-30, 30), + resource: mineableByCoordinates(map, Coordinates.fromNumber(-30, 30)) + .values() + .next().value, + }) diff --git a/src/main/basedbot/fsm/configs/mine/mine-config.ts b/src/main/basedbot/fsm/configs/mine/mine-config.ts new file mode 100644 index 00000000..ab77cbe0 --- /dev/null +++ b/src/main/basedbot/fsm/configs/mine/mine-config.ts @@ -0,0 +1,23 @@ +import { WarpMode } from '../../../lib/sage/act/move' +import { Mineable } from '../../../lib/sage/state/world-map' +import { Coordinates } from '../../../lib/util/coordinates' + +export type MineConfig = { + homeBase: Coordinates + targetBase: Coordinates + resource: Mineable + warpMode: WarpMode +} + +export const mineConfig = ( + config: Partial & { + homeBase: Coordinates + targetBase: Coordinates + resource: Mineable + }, +): MineConfig => ({ + homeBase: config.homeBase, + targetBase: config.targetBase, + resource: config.resource, + warpMode: config.warpMode || 'auto', +}) diff --git a/src/main/basedbot/fsm/configs/mine/mine-copper-ore.ts b/src/main/basedbot/fsm/configs/mine/mine-copper-ore.ts new file mode 100644 index 00000000..91d01cd8 --- /dev/null +++ b/src/main/basedbot/fsm/configs/mine/mine-copper-ore.ts @@ -0,0 +1,16 @@ +import { + mineableByCoordinates, + WorldMap, +} from '../../../lib/sage/state/world-map' +import { Coordinates } from '../../../lib/util/coordinates' + +import { MineConfig, mineConfig } from './mine-config' + +export const mineCopperOre = (map: WorldMap): MineConfig => + mineConfig({ + homeBase: Coordinates.fromNumber(-40, 30), + targetBase: Coordinates.fromNumber(-47, 30), + resource: mineableByCoordinates(map, Coordinates.fromNumber(-47, 30)) + .values() + .next().value, + }) diff --git a/src/main/basedbot/fsm/configs/mine/mine-diamond.ts b/src/main/basedbot/fsm/configs/mine/mine-diamond.ts new file mode 100644 index 00000000..efda752d --- /dev/null +++ b/src/main/basedbot/fsm/configs/mine/mine-diamond.ts @@ -0,0 +1,16 @@ +import { + mineableByCoordinates, + WorldMap, +} from '../../../lib/sage/state/world-map' +import { Coordinates } from '../../../lib/util/coordinates' + +import { MineConfig, mineConfig } from './mine-config' + +export const mineDiamond = (map: WorldMap): MineConfig => + mineConfig({ + homeBase: Coordinates.fromNumber(-40, 30), + targetBase: Coordinates.fromNumber(-16, 0), + resource: mineableByCoordinates(map, Coordinates.fromNumber(-16, 0)) + .values() + .next().value, + }) diff --git a/src/main/basedbot/fsm/configs/mine/mine-hydrogen.ts b/src/main/basedbot/fsm/configs/mine/mine-hydrogen.ts new file mode 100644 index 00000000..f05555ae --- /dev/null +++ b/src/main/basedbot/fsm/configs/mine/mine-hydrogen.ts @@ -0,0 +1,16 @@ +import { + mineableByCoordinates, + WorldMap, +} from '../../../lib/sage/state/world-map' +import { Coordinates } from '../../../lib/util/coordinates' + +import { MineConfig, mineConfig } from './mine-config' + +export const mineHydrogen = (map: WorldMap): MineConfig => + mineConfig({ + homeBase: Coordinates.fromNumber(-40, 30), + targetBase: Coordinates.fromNumber(-40, 30), + resource: mineableByCoordinates(map, Coordinates.fromNumber(-40, 30)) + .values() + .next().value, + }) diff --git a/src/main/basedbot/fsm/configs/mine/mine-iron-ore.ts b/src/main/basedbot/fsm/configs/mine/mine-iron-ore.ts new file mode 100644 index 00000000..02c1dcac --- /dev/null +++ b/src/main/basedbot/fsm/configs/mine/mine-iron-ore.ts @@ -0,0 +1,16 @@ +import { + mineableByCoordinates, + WorldMap, +} from '../../../lib/sage/state/world-map' +import { Coordinates } from '../../../lib/util/coordinates' + +import { MineConfig, mineConfig } from './mine-config' + +export const mineIronOre = (map: WorldMap): MineConfig => + mineConfig({ + homeBase: Coordinates.fromNumber(-40, 30), + targetBase: Coordinates.fromNumber(-38, 25), + resource: mineableByCoordinates(map, Coordinates.fromNumber(-38, 25)) + .values() + .next().value, + }) diff --git a/src/main/basedbot/fsm/configs/mine/mine-lumanite.ts b/src/main/basedbot/fsm/configs/mine/mine-lumanite.ts new file mode 100644 index 00000000..dbefb537 --- /dev/null +++ b/src/main/basedbot/fsm/configs/mine/mine-lumanite.ts @@ -0,0 +1,16 @@ +import { + mineableByCoordinates, + WorldMap, +} from '../../../lib/sage/state/world-map' +import { Coordinates } from '../../../lib/util/coordinates' + +import { MineConfig, mineConfig } from './mine-config' + +export const mineLumanite = (map: WorldMap): MineConfig => + mineConfig({ + homeBase: Coordinates.fromNumber(-40, 30), + targetBase: Coordinates.fromNumber(-23, 4), + resource: mineableByCoordinates(map, Coordinates.fromNumber(-23, 4)) + .values() + .next().value, + }) diff --git a/src/main/basedbot/fsm/configs/mine/mine-nitrogen.ts b/src/main/basedbot/fsm/configs/mine/mine-nitrogen.ts new file mode 100644 index 00000000..151aaa94 --- /dev/null +++ b/src/main/basedbot/fsm/configs/mine/mine-nitrogen.ts @@ -0,0 +1,16 @@ +import { + mineableByCoordinates, + WorldMap, +} from '../../../lib/sage/state/world-map' +import { Coordinates } from '../../../lib/util/coordinates' + +import { MineConfig, mineConfig } from './mine-config' + +export const mineNitrogen = (map: WorldMap): MineConfig => + mineConfig({ + homeBase: Coordinates.fromNumber(-40, 30), + targetBase: Coordinates.fromNumber(-45, 15), + resource: mineableByCoordinates(map, Coordinates.fromNumber(-45, 15)) + .values() + .next().value, + }) diff --git a/src/main/basedbot/fsm/configs/mine/mine-rochinol.ts b/src/main/basedbot/fsm/configs/mine/mine-rochinol.ts new file mode 100644 index 00000000..3afd1581 --- /dev/null +++ b/src/main/basedbot/fsm/configs/mine/mine-rochinol.ts @@ -0,0 +1,16 @@ +import { + mineableByCoordinates, + WorldMap, +} from '../../../lib/sage/state/world-map' +import { Coordinates } from '../../../lib/util/coordinates' + +import { MineConfig, mineConfig } from './mine-config' + +export const mineRochinol = (map: WorldMap): MineConfig => + mineConfig({ + homeBase: Coordinates.fromNumber(-40, 30), + targetBase: Coordinates.fromNumber(0, 16), + resource: mineableByCoordinates(map, Coordinates.fromNumber(0, 16)) + .values() + .next().value, + }) diff --git a/src/main/basedbot/fsm/configs/mine/mine-silicia.ts b/src/main/basedbot/fsm/configs/mine/mine-silicia.ts new file mode 100644 index 00000000..d14f626b --- /dev/null +++ b/src/main/basedbot/fsm/configs/mine/mine-silicia.ts @@ -0,0 +1,16 @@ +import { + mineableByCoordinates, + WorldMap, +} from '../../../lib/sage/state/world-map' +import { Coordinates } from '../../../lib/util/coordinates' + +import { MineConfig, mineConfig } from './mine-config' + +export const mineSilicia = (map: WorldMap): MineConfig => + mineConfig({ + homeBase: Coordinates.fromNumber(-40, 30), + targetBase: Coordinates.fromNumber(-22, 32), + resource: mineableByCoordinates(map, Coordinates.fromNumber(-22, 32)) + .values() + .next().value, + }) diff --git a/src/main/basedbot/fsm/configs/mine/mine-titanium-ore.ts b/src/main/basedbot/fsm/configs/mine/mine-titanium-ore.ts new file mode 100644 index 00000000..cea72d41 --- /dev/null +++ b/src/main/basedbot/fsm/configs/mine/mine-titanium-ore.ts @@ -0,0 +1,16 @@ +import { + mineableByCoordinates, + WorldMap, +} from '../../../lib/sage/state/world-map' +import { Coordinates } from '../../../lib/util/coordinates' + +import { MineConfig, mineConfig } from './mine-config' + +export const mineTitaniumOre = (map: WorldMap): MineConfig => + mineConfig({ + homeBase: Coordinates.fromNumber(-40, 30), + targetBase: Coordinates.fromNumber(-8, 35), + resource: mineableByCoordinates(map, Coordinates.fromNumber(-8, 35)) + .values() + .next().value, + }) diff --git a/src/main/basedbot/fsm/configs/mine/mine.ts b/src/main/basedbot/fsm/configs/mine/mine.ts new file mode 100644 index 00000000..863698bb --- /dev/null +++ b/src/main/basedbot/fsm/configs/mine/mine.ts @@ -0,0 +1,18 @@ +import { + mineableByCoordinates, + WorldMap, +} from '../../../lib/sage/state/world-map' +import { Coordinates } from '../../../lib/util/coordinates' + +import { mineConfig, MineConfig } from './mine-config' + +export const mine = ( + map: WorldMap, + homeBase: Coordinates, + targetBase: Coordinates, +): MineConfig => + mineConfig({ + homeBase, + targetBase, + resource: mineableByCoordinates(map, targetBase).values().next().value, + }) diff --git a/src/main/basedbot/fsm/destruct.ts b/src/main/basedbot/fsm/destruct.ts new file mode 100644 index 00000000..f9abddcc --- /dev/null +++ b/src/main/basedbot/fsm/destruct.ts @@ -0,0 +1,176 @@ +import { Game } from '@staratlas/sage' +import dayjs from 'dayjs' + +import { now } from '../../../dayjs' +import { logger } from '../../../logger' +import { disbandFleet } from '../lib/sage/act/disband-fleet' +import { dock } from '../lib/sage/act/dock' +import { endMine } from '../lib/sage/act/end-mine' +import { endMove } from '../lib/sage/act/end-move' +import { selfDestruct } from '../lib/sage/act/self-destruct' +import { stopSubwarp } from '../lib/sage/act/stop-subwarp' +import { undock } from '../lib/sage/act/undock' +import { starbaseByCoordinates } from '../lib/sage/state/starbase-by-coordinates' +import { Player } from '../lib/sage/state/user-account' +import { FleetInfo } from '../lib/sage/state/user-fleets' +import { mineableByCoordinates, WorldMap } from '../lib/sage/state/world-map' +import { getName } from '../lib/sage/util' + +import { DisbandConfig } from './configs/disband-config' +import { Strategy } from './strategy' + +// eslint-disable-next-line complexity +const transition = async ( + fleetInfo: FleetInfo, + player: Player, + game: Game, + config: DestructConfig, +): Promise => { + const currentStarbase = await starbaseByCoordinates(fleetInfo.location) + const { fleetName, location } = fleetInfo + const homeBase = player.homeCoordinates + const isAtHomeBase = homeBase.equals(location) + + switch (fleetInfo.fleetState.type) { + case 'Idle': { + logger.info( + `${fleetName} is idle at ${fleetInfo.fleetState.data.sector} [Starbase: ${currentStarbase ? getName(currentStarbase) : 'N/A'}]`, + ) + + if (isAtHomeBase) { + logger.info(`${fleetName} is at home base, docking to disband`) + + return dock(fleetInfo, location, player, game) + } + + return selfDestruct(fleetInfo, player, game) + } + case 'StarbaseLoadingBay': { + logger.info( + `${fleetInfo.fleetName} is in the loading bay at ${getName(fleetInfo.fleetState.data.starbase)}`, + ) + + if (isAtHomeBase) { + logger.info( + `${fleetInfo.fleetName} is at home base, disbanding...`, + ) + + return disbandFleet( + player, + game, + player.homeStarbase, + fleetInfo, + ) + } + logger.info( + `${fleetInfo.fleetName} is at ${location}, undocking...`, + ) + + return undock(fleetInfo.fleet, fleetInfo.location, player, game) + } + case 'MoveWarp': { + const { fromSector, toSector, warpFinish } = + fleetInfo.fleetState.data + + if (!homeBase.equals(toSector)) { + logger.info(`Stopping fleet ${fleetInfo.fleetName}`) + + return endMove(fleetInfo, player, game) + } + + if (warpFinish.isBefore(now())) { + logger.info( + `${fleetInfo.fleetName} has arrived at ${fleetInfo.fleetState.data.toSector}`, + ) + } else { + logger.info( + `${fleetInfo.fleetName} warping from ${fromSector} to ${toSector}. Arrival in ${dayjs.duration(warpFinish.diff(now())).humanize(false)}. Current Position: ${fleetInfo.location}`, + ) + } + break + } + case 'MoveSubwarp': { + const { fromSector, toSector, arrivalTime } = + fleetInfo.fleetState.data + + if (!homeBase.equals(toSector)) { + logger.info(`Stopping fleet ${fleetInfo.fleetName}`) + + return stopSubwarp(fleetInfo, player, game) + } + + if (arrivalTime.isBefore(now())) { + logger.info( + `${fleetInfo.fleetName} has arrived at ${fleetInfo.fleetState.data.toSector}`, + ) + } else { + logger.info( + `${fleetInfo.fleetName} subwarping from ${fromSector} to ${toSector}. Arrival in ${dayjs.duration(arrivalTime.diff(now())).humanize(false)}. Current Position: ${fleetInfo.location}`, + ) + } + break + } + case 'MineAsteroid': { + const { mineItem, end, amountMined } = fleetInfo.fleetState.data + + if (end.isBefore(now())) { + logger.info( + `${fleetInfo.fleetName} has finished mining ${getName(mineItem)} for ${amountMined}`, + ) + } + + logger.info( + `${fleetInfo.fleetName} mining ${getName(mineItem)} for ${amountMined}. Ending...`, + ) + const resource = mineableByCoordinates( + config.worldMap, + fleetInfo.location, + ) + .values() + .next().value + + return endMine(fleetInfo, player, game, resource) + } + case 'Respawn': { + const { destructionTime, ETA } = fleetInfo.fleetState.data + + if (ETA.isBefore(now())) { + logger.info(`${fleetInfo.fleetName} has respawned`) + } else { + logger.info( + `${fleetInfo.fleetName} respawning at ${fleetInfo.fleetState.data.sector}. ETA: ${dayjs.duration(ETA.diff(now())).humanize(false)}. Destruction time: ${destructionTime}`, + ) + } + break + } + default: + logger.info( + `${fleetInfo.fleetName} is ${fleetInfo.fleetState.type}`, + ) + + return Promise.resolve() + } +} + +export type DestructConfig = { + worldMap: WorldMap +} + +export const destructConfig = ( + config: Partial & { + worldMap: WorldMap + }, +): DestructConfig => ({ + worldMap: config.worldMap, +}) + +export const createDestructStrategy = ( + config: DestructConfig, + player: Player, + game: Game, +): Strategy => { + return { + apply: (fleetInfo: FleetInfo): Promise => + transition(fleetInfo, player, game, config), + } +} diff --git a/src/main/basedbot/fsm/disband.ts b/src/main/basedbot/fsm/disband.ts new file mode 100644 index 00000000..0fca98be --- /dev/null +++ b/src/main/basedbot/fsm/disband.ts @@ -0,0 +1,180 @@ +// eslint-disable-next-line filenames/match-regex +import { Game } from '@staratlas/sage' +import dayjs from 'dayjs' + +import { now } from '../../../dayjs' +import { logger } from '../../../logger' +import { disbandFleet } from '../lib/sage/act/disband-fleet' +import { dock } from '../lib/sage/act/dock' +import { endMine } from '../lib/sage/act/end-mine' +import { endMove } from '../lib/sage/act/end-move' +import { move } from '../lib/sage/act/move' +import { selfDestruct } from '../lib/sage/act/self-destruct' +import { stopSubwarp } from '../lib/sage/act/stop-subwarp' +import { undock } from '../lib/sage/act/undock' +import { starbaseByCoordinates } from '../lib/sage/state/starbase-by-coordinates' +import { Player } from '../lib/sage/state/user-account' +import { FleetInfo } from '../lib/sage/state/user-fleets' +import { mineableByCoordinates } from '../lib/sage/state/world-map' +import { getName } from '../lib/sage/util' + +import { DisbandConfig } from './configs/disband-config' +import { Strategy } from './strategy' + +// eslint-disable-next-line complexity +const transition = async ( + fleetInfo: FleetInfo, + player: Player, + game: Game, + config: DisbandConfig, +): Promise => { + const cargoLevelFuel = fleetInfo.cargoLevels.fuel + const currentStarbase = await starbaseByCoordinates(fleetInfo.location) + const { fleetName, location } = fleetInfo + const { homeBase, warpMode } = config + const isAtHomeBase = homeBase.equals(location) + + switch (fleetInfo.fleetState.type) { + case 'Idle': { + logger.info( + `${fleetName} is idle at ${fleetInfo.fleetState.data.sector} [Starbase: ${currentStarbase ? getName(currentStarbase) : 'N/A'}]`, + ) + + if (!currentStarbase && cargoLevelFuel < 1) { + logger.warn( + `${fleetName} is out of fuel and not at a starbase, need self destruction`, + ) + + return selfDestruct(fleetInfo, player, game) + } + if (isAtHomeBase) { + logger.info(`${fleetName} is at home base, docking to disband`) + + return dock(fleetInfo, location, player, game) + } + + logger.info(`${fleetName} is at ${location} warping home`) + + return move(fleetInfo, homeBase, player, game, warpMode) + } + case 'StarbaseLoadingBay': { + logger.info( + `${fleetInfo.fleetName} is in the loading bay at ${getName(fleetInfo.fleetState.data.starbase)}`, + ) + + if (isAtHomeBase) { + logger.info( + `${fleetInfo.fleetName} is at home base, disbanding...`, + ) + + return disbandFleet( + player, + game, + player.homeStarbase, + fleetInfo, + ) + } + logger.info( + `${fleetInfo.fleetName} is at ${location}, undocking...`, + ) + + return undock(fleetInfo.fleet, fleetInfo.location, player, game) + } + case 'MoveWarp': { + const { fromSector, toSector, warpFinish } = + fleetInfo.fleetState.data + + if (!homeBase.equals(toSector)) { + logger.info( + `Wrong direction, stopping fleet ${fleetInfo.fleetName}`, + ) + + return endMove(fleetInfo, player, game) + } + + if (warpFinish.isBefore(now())) { + logger.info( + `${fleetInfo.fleetName} has arrived at ${fleetInfo.fleetState.data.toSector}`, + ) + } else { + logger.info( + `${fleetInfo.fleetName} warping from ${fromSector} to ${toSector}. Arrival in ${dayjs.duration(warpFinish.diff(now())).humanize(false)}. Current Position: ${fleetInfo.location}`, + ) + } + break + } + case 'MoveSubwarp': { + const { fromSector, toSector, arrivalTime } = + fleetInfo.fleetState.data + + if (!homeBase.equals(toSector)) { + logger.info( + `Wrong direction, stopping fleet ${fleetInfo.fleetName}`, + ) + + return stopSubwarp(fleetInfo, player, game) + } + + if (arrivalTime.isBefore(now())) { + logger.info( + `${fleetInfo.fleetName} has arrived at ${fleetInfo.fleetState.data.toSector}`, + ) + } else { + logger.info( + `${fleetInfo.fleetName} subwarping from ${fromSector} to ${toSector}. Arrival in ${dayjs.duration(arrivalTime.diff(now())).humanize(false)}. Current Position: ${fleetInfo.location}`, + ) + } + break + } + case 'MineAsteroid': { + const { mineItem, end, amountMined } = fleetInfo.fleetState.data + + if (end.isBefore(now())) { + logger.info( + `${fleetInfo.fleetName} has finished mining ${getName(mineItem)} for ${amountMined}`, + ) + } + + logger.info( + `${fleetInfo.fleetName} mining ${getName(mineItem)} for ${amountMined}. Ending...`, + ) + const resource = mineableByCoordinates( + config.worldMap, + fleetInfo.location, + ) + .values() + .next().value + + return endMine(fleetInfo, player, game, resource) + } + case 'Respawn': { + const { destructionTime, ETA } = fleetInfo.fleetState.data + + if (ETA.isBefore(now())) { + logger.info(`${fleetInfo.fleetName} has respawned`) + } else { + logger.info( + `${fleetInfo.fleetName} respawning at ${fleetInfo.fleetState.data.sector}. ETA: ${dayjs.duration(ETA.diff(now())).humanize(false)}. Destruction time: ${destructionTime}`, + ) + } + break + } + default: + logger.info( + `${fleetInfo.fleetName} is ${fleetInfo.fleetState.type}`, + ) + + return Promise.resolve() + } +} + +export const createDisbandStrategy = ( + config: DisbandConfig, + player: Player, + game: Game, +): Strategy => { + return { + apply: (fleetInfo: FleetInfo): Promise => + transition(fleetInfo, player, game, config), + } +} diff --git a/src/main/basedbot/fsm/info.ts b/src/main/basedbot/fsm/info.ts new file mode 100644 index 00000000..acb69cb0 --- /dev/null +++ b/src/main/basedbot/fsm/info.ts @@ -0,0 +1,86 @@ +import dayjs from 'dayjs' + +import { now } from '../../../dayjs' +import { logger } from '../../../logger' +import { planetsByCoordinates } from '../lib/sage/state/planets-by-coordinates' +import { starbaseByCoordinates } from '../lib/sage/state/starbase-by-coordinates' +import { FleetInfo } from '../lib/sage/state/user-fleets' +import { getName } from '../lib/sage/util' + +import { Strategy } from './strategy' + +const transition = async (fleetInfo: FleetInfo): Promise => { + switch (fleetInfo.fleetState.type) { + case 'Idle': { + const baseStation = await starbaseByCoordinates(fleetInfo.location) + const planets = await planetsByCoordinates(fleetInfo.location) + + logger.info( + `${fleetInfo.fleetName} is idle at ${fleetInfo.fleetState.data.sector} [BaseStation: ${baseStation ? getName(baseStation) : 'N/A'} / Planets: ${planets.length}]`, + ) + break + } + case 'StarbaseLoadingBay': + logger.info( + `${fleetInfo.fleetName} is in the loading bay at ${getName(fleetInfo.fleetState.data.starbase)}`, + ) + break + case 'MoveWarp': { + const { fromSector, toSector, warpFinish } = + fleetInfo.fleetState.data + + if (warpFinish.isBefore(now())) { + logger.info( + `${fleetInfo.fleetName} has arrived at ${fleetInfo.fleetState.data.toSector}`, + ) + } else { + logger.info( + `${fleetInfo.fleetName} warping from ${fromSector} to ${toSector}. Arrival in ${dayjs.duration(warpFinish.diff(now())).humanize(false)}. Current Position: ${fleetInfo.location}`, + ) + } + break + } + case 'MoveSubwarp': { + const { fromSector, toSector, arrivalTime } = + fleetInfo.fleetState.data + + if (arrivalTime.isBefore(now())) { + logger.info( + `${fleetInfo.fleetName} has arrived at ${fleetInfo.fleetState.data.toSector}`, + ) + } else { + logger.info( + `${fleetInfo.fleetName} subwarping from ${fromSector} to ${toSector}. Arrival in ${dayjs.duration(arrivalTime.diff(now())).humanize(false)}. Current Position: ${fleetInfo.location}`, + ) + } + break + } + case 'MineAsteroid': { + const { mineItem, end, amountMined, endReason } = + fleetInfo.fleetState.data + + if (end.isBefore(now())) { + logger.info( + `${fleetInfo.fleetName} has finished mining ${getName(mineItem)} for ${amountMined}`, + ) + } else { + const log = endReason === 'FULL' ? logger.info : logger.warn + + log( + `${fleetInfo.fleetName} mining ${getName(mineItem)} for ${amountMined}. Time remaining: ${dayjs.duration(end.diff(now())).humanize(false)} until ${endReason}`, + ) + } + break + } + default: + logger.info( + `${fleetInfo.fleetName} is ${fleetInfo.fleetState.type}`, + ) + } + + return Promise.resolve() +} + +export const createInfoStrategy = (): Strategy => ({ + apply: (fleetInfo: FleetInfo): Promise => transition(fleetInfo), +}) diff --git a/src/main/basedbot/fsm/mine.ts b/src/main/basedbot/fsm/mine.ts new file mode 100644 index 00000000..e201f2cb --- /dev/null +++ b/src/main/basedbot/fsm/mine.ts @@ -0,0 +1,276 @@ +import { Game } from '@staratlas/sage' +import dayjs from 'dayjs' + +import { now } from '../../../dayjs' +import { logger } from '../../../logger' +import { dock } from '../lib/sage/act/dock' +import { endMine } from '../lib/sage/act/end-mine' +import { loadCargo } from '../lib/sage/act/load-cargo' +import { mine } from '../lib/sage/act/mine' +import { move } from '../lib/sage/act/move' +import { selfDestruct } from '../lib/sage/act/self-destruct' +import { undock } from '../lib/sage/act/undock' +import { unloadAllCargo } from '../lib/sage/act/unload-all-cargo' +import { starbaseByCoordinates } from '../lib/sage/state/starbase-by-coordinates' +import { Player } from '../lib/sage/state/user-account' +import { FleetInfo } from '../lib/sage/state/user-fleets' +import { getName } from '../lib/sage/util' + +import { MineConfig } from './configs/mine/mine-config' +import { Strategy } from './strategy' + +// eslint-disable-next-line complexity +const transition = async ( + fleetInfo: FleetInfo, + player: Player, + game: Game, + config: MineConfig, +): Promise => { + const cargoLoad = player.cargoTypes + .filter((ct) => !ct.data.mint.equals(game.data.mints.food)) + .reduce((acc, cargoType) => { + const load = + fleetInfo.cargoLevels.cargo.get( + cargoType.data.mint.toBase58(), + ) ?? 0 + + return acc + load + }, 0) + + const { cargoCapacity } = fleetInfo.cargoStats + const cargoLevelFood = fleetInfo.cargoLevels.food + const cargoLevelAmmo = fleetInfo.cargoLevels.ammo + const cargoLevelFuel = fleetInfo.cargoLevels.fuel + const desiredFood = cargoCapacity / 20 + const toLoad = desiredFood - cargoLevelFood + const hasEnoughFood = toLoad <= 10 + const hasEnoughAmmo = + cargoLevelAmmo >= fleetInfo.cargoStats.ammoCapacity - 100 + const hasEnoughFuel = + cargoLevelFuel >= fleetInfo.cargoStats.fuelCapacity - 100 + const hasCargo = cargoLoad > 0 + const currentStarbase = await starbaseByCoordinates(fleetInfo.location) + const { fleetName, location } = fleetInfo + const { homeBase, targetBase, resource, warpMode } = config + const resourceName = getName(resource.mineItem) + const isAtHomeBase = homeBase.equals(location) + const isAtTargetBase = targetBase.equals(location) + const isSameBase = homeBase.equals(targetBase) + + logger.info( + `${fleetName} is mining ${getName(config.resource.mineItem)} resources from ${config.targetBase} to ${config.homeBase}`, + ) + + switch (fleetInfo.fleetState.type) { + case 'Idle': { + logger.info( + `${fleetName} is idle at ${fleetInfo.fleetState.data.sector} [Starbase: ${currentStarbase ? getName(currentStarbase) : 'N/A'}]`, + ) + + if (!currentStarbase && cargoLevelFuel < 1) { + logger.warn( + `${fleetName} is out of fuel and not at a starbase, need self destruction`, + ) + + return selfDestruct(fleetInfo, player, game) + } + if (isAtHomeBase) { + logger.info(`${fleetName} is at home base`) + if (hasCargo) { + logger.info( + `${fleetName} has ${cargoLoad} ${resourceName}, docking to unload`, + ) + + return dock(fleetInfo, location, player, game) + } + if (!hasEnoughFood || !hasEnoughFuel || !hasEnoughAmmo) { + logger.info( + `${fleetName} doesn't have enough resources, docking to resupply`, + ) + + return dock(fleetInfo, location, player, game) + } + if (isSameBase) { + logger.info(`${fleetName} is at home/target base, mining`) + + return mine(fleetInfo, player, game, resource) + } + logger.info( + `${fleetName} is at home base, moving to target base`, + ) + + return move(fleetInfo, targetBase, player, game, warpMode) + } + + if (isAtTargetBase && !isSameBase) { + logger.info(`${fleetName} is at target base`) + if (hasCargo) { + logger.info( + `${fleetName} has ${cargoLoad} ${resourceName}, returning home`, + ) + + return move(fleetInfo, homeBase, player, game, warpMode) + } + if (hasEnoughFood) { + logger.info(`${fleetName} has enough food, mining`) + + return mine(fleetInfo, player, game, resource) + } + logger.info( + `${fleetName} doesn't have enough food, returning home`, + ) + + return move(fleetInfo, homeBase, player, game, warpMode) + } + + logger.info(`${fleetName} is at ${location}`) + + return move( + fleetInfo, + hasCargo || !hasEnoughFood ? homeBase : targetBase, + player, + game, + warpMode, + ) + } + case 'StarbaseLoadingBay': { + logger.info( + `${fleetInfo.fleetName} is in the loading bay at ${getName(fleetInfo.fleetState.data.starbase)}`, + ) + + if (hasCargo) { + logger.info( + `${fleetInfo.fleetName} has ${cargoLoad} cargo, unloading`, + ) + + return unloadAllCargo( + fleetInfo, + fleetInfo.location, + player, + game, + ) + } + + if (!hasEnoughFuel) { + logger.info(`${fleetInfo.fleetName} is refueling`) + + return loadCargo( + fleetInfo, + player, + game, + game.data.mints.fuel, + fleetInfo.cargoStats.fuelCapacity - cargoLevelFuel, + ) + } + + if (!hasEnoughAmmo) { + logger.info(`${fleetInfo.fleetName} is rearming`) + + return loadCargo( + fleetInfo, + player, + game, + game.data.mints.ammo, + fleetInfo.cargoStats.ammoCapacity - cargoLevelAmmo, + ) + } + + if (!hasEnoughFood) { + logger.info( + `${fleetInfo.fleetName} is loading ${desiredFood - cargoLevelFood} food`, + ) + + return loadCargo( + fleetInfo, + player, + game, + game.data.mints.food, + toLoad, + ) + } + + return undock(fleetInfo.fleet, fleetInfo.location, player, game) + } + case 'MoveWarp': { + const { fromSector, toSector, warpFinish } = + fleetInfo.fleetState.data + + if (warpFinish.isBefore(now())) { + logger.info( + `${fleetInfo.fleetName} has arrived at ${fleetInfo.fleetState.data.toSector}`, + ) + } else { + logger.info( + `${fleetInfo.fleetName} warping from ${fromSector} to ${toSector}. Arrival in ${dayjs.duration(warpFinish.diff(now())).humanize(false)}. Current Position: ${fleetInfo.location}`, + ) + } + break + } + case 'MoveSubwarp': { + const { fromSector, toSector, arrivalTime } = + fleetInfo.fleetState.data + + if (arrivalTime.isBefore(now())) { + logger.info( + `${fleetInfo.fleetName} has arrived at ${fleetInfo.fleetState.data.toSector}`, + ) + } else { + logger.info( + `${fleetInfo.fleetName} subwarping from ${fromSector} to ${toSector}. Arrival in ${dayjs.duration(arrivalTime.diff(now())).humanize(false)}. Current Position: ${fleetInfo.location}`, + ) + } + break + } + case 'MineAsteroid': { + const { mineItem, end, amountMined, endReason } = + fleetInfo.fleetState.data + + if (end.isBefore(now())) { + logger.info( + `${fleetInfo.fleetName} has finished mining ${getName(mineItem)} for ${amountMined}`, + ) + + return endMine(fleetInfo, player, game, config.resource) + } + + const log = endReason === 'FULL' ? logger.info : logger.warn + + log( + `${fleetInfo.fleetName} mining ${getName(mineItem)} for ${amountMined}. Time remaining: ${dayjs.duration(end.diff(now())).humanize(false)} until ${endReason}`, + ) + + break + } + case 'Respawn': { + const { destructionTime, ETA } = fleetInfo.fleetState.data + + if (ETA.isBefore(now())) { + logger.info(`${fleetInfo.fleetName} has respawned`) + } else { + logger.info( + `${fleetInfo.fleetName} respawning at ${fleetInfo.fleetState.data.sector}. ETA: ${dayjs.duration(ETA.diff(now())).humanize(false)}. Destruction time: ${destructionTime}`, + ) + } + break + } + default: + logger.info( + `${fleetInfo.fleetName} is ${fleetInfo.fleetState.type}`, + ) + + return Promise.resolve() + } +} + +export const createMiningStrategy = ( + miningConfig: MineConfig, + player: Player, + game: Game, +): Strategy => { + const config = miningConfig + + return { + apply: (fleetInfo: FleetInfo): Promise => + transition(fleetInfo, player, game, config), + } +} diff --git a/src/main/basedbot/fsm/strategy.ts b/src/main/basedbot/fsm/strategy.ts new file mode 100644 index 00000000..5d6350de --- /dev/null +++ b/src/main/basedbot/fsm/strategy.ts @@ -0,0 +1,7 @@ +import { FleetInfo } from '../lib/sage/state/user-fleets' + +export type Strategy = { + apply: (fleetInfo: FleetInfo) => Promise +} + +export const noop = (): Promise => Promise.resolve() diff --git a/src/main/basedbot/fsm/transport.ts b/src/main/basedbot/fsm/transport.ts new file mode 100644 index 00000000..7ff514fa --- /dev/null +++ b/src/main/basedbot/fsm/transport.ts @@ -0,0 +1,329 @@ +import { Game } from '@staratlas/sage' +import dayjs from 'dayjs' + +import { now } from '../../../dayjs' +import { logger } from '../../../logger' +import { Resource } from '../../../service/wallet' +import { getTokenBalance } from '../basedbot' +import { dock } from '../lib/sage/act/dock' +import { loadCargo } from '../lib/sage/act/load-cargo' +import { move, WarpMode } from '../lib/sage/act/move' +import { selfDestruct } from '../lib/sage/act/self-destruct' +import { undock } from '../lib/sage/act/undock' +import { getHold, unloadCargo } from '../lib/sage/act/unload-cargo' +import { starbaseByCoordinates } from '../lib/sage/state/starbase-by-coordinates' +import { Player } from '../lib/sage/state/user-account' +import { FleetInfo } from '../lib/sage/state/user-fleets' +import { WorldMap } from '../lib/sage/state/world-map' +import { getName } from '../lib/sage/util' +import { Coordinates } from '../lib/util/coordinates' + +import { Strategy } from './strategy' + +// eslint-disable-next-line complexity +const transition = async ( + fleetInfo: FleetInfo, + player: Player, + game: Game, + config: TransportConfig, +): Promise => { + const cargoLoad = player.cargoTypes.reduce((acc, cargoType) => { + const load = + fleetInfo.cargoLevels.cargo.get(cargoType.data.mint.toBase58()) ?? 0 + + return acc + load + }, 0) + + const { cargoCapacity } = fleetInfo.cargoStats + const cargoLevelFuel = fleetInfo.cargoLevels.fuel + const cargoLevelAmmo = fleetInfo.cargoLevels.ammo + const fuelReserve = fleetInfo.cargoStats.fuelCapacity / 10 + const ammoReserve = 0 + const hasEnoughFuel = cargoLevelFuel >= fuelReserve + const hasEnoughAmmo = cargoLevelAmmo >= ammoReserve + const hasCargo = cargoLoad > 0 + const currentStarbase = await starbaseByCoordinates(fleetInfo.location) + const { fleetName, location } = fleetInfo + const { homeBase, targetBase, resources, warpMode } = config + const isAtHomeBase = homeBase.equals(location) + const isAtTargetBase = targetBase.equals(location) + const isSameBase = homeBase.equals(targetBase) + + logger.info( + `${fleetName} is transporting ${config.resources.size} resources from ${config.homeBase} to ${config.targetBase}`, + ) + + switch (fleetInfo.fleetState.type) { + case 'Idle': { + logger.info( + `${fleetName} is idle at ${fleetInfo.fleetState.data.sector} [Starbase: ${currentStarbase ? getName(currentStarbase) : 'N/A'}]`, + ) + + if (!currentStarbase && cargoLevelFuel < 1) { + logger.warn( + `${fleetName} is out of fuel and not at a starbase, need self destruction`, + ) + + return selfDestruct(fleetInfo, player, game) + } + if (isSameBase) { + logger.warn( + `${fleetName} is configured as transport fleet with home and target being the same. Idling....`, + ) + + return Promise.resolve() + } + if (isAtHomeBase) { + logger.info(`${fleetName} is at home base`) + if (hasEnoughAmmo && hasEnoughFuel && hasCargo) { + logger.info('Ready to go! Moving to target base') + + return move(fleetInfo, targetBase, player, game, warpMode) + } + logger.info(`${fleetName} is docking to resupply`) + + return dock(fleetInfo, location, player, game) + } + + if (isAtTargetBase) { + logger.info(`${fleetName} is at target base`) + + if (!hasCargo) { + logger.info('Ready to go! Moving to home base') + + return move(fleetInfo, homeBase, player, game, warpMode) + } + + logger.info( + `${fleetName} has ${cargoLoad} cargo, docking to unload.`, + ) + + return dock(fleetInfo, location, player, game) + } + + logger.warn(`${fleetName} doesn't know what to do`) + + return Promise.resolve() + } + + case 'StarbaseLoadingBay': { + logger.info( + `${fleetInfo.fleetName} is in the loading bay at ${getName(fleetInfo.fleetState.data.starbase)}`, + ) + + if (isAtHomeBase) { + if (!hasEnoughFuel) { + logger.info(`${fleetInfo.fleetName} is refueling`) + + await loadCargo( + fleetInfo, + player, + game, + game.data.mints.fuel, + fleetInfo.cargoStats.fuelCapacity - cargoLevelFuel, + ) + } + if (!hasEnoughAmmo) { + logger.info(`${fleetInfo.fleetName} is rearming`) + + await loadCargo( + fleetInfo, + player, + game, + game.data.mints.ammo, + fleetInfo.cargoStats.ammoCapacity - cargoLevelAmmo, + ) + } + + if (!hasCargo) { + logger.info(`Loading ${Array.from(resources).length} cargo`) + const cargoResources = Array.from(resources).filter( + (resource) => + !resource.equals(game.data.mints.ammo) && + !resource.equals(game.data.mints.fuel), + ) + + await Promise.all( + cargoResources.map((resource) => { + const count = Math.floor( + cargoCapacity / + Array.from(cargoResources).length, + ) + + logger.info( + `Loading ${count} ${resource.toBase58()}`, + ) + + return loadCargo( + fleetInfo, + player, + game, + resource, + count, + ) + }), + ) + } + + logger.info(`${fleetName} is undocking...`) + + return undock(fleetInfo.fleet, fleetInfo.location, player, game) + } + + if (isAtTargetBase) { + if (hasCargo) { + logger.info( + `Unloading ${Array.from(resources).length} cargo`, + ) + + await Promise.all( + Array.from(resources).map(async (resource) => { + const fleetCargoPod = getHold( + resource, + game, + fleetInfo, + ) + const amount = await getTokenBalance( + fleetCargoPod, + resource, + ) + + logger.info( + `Unloading ${amount} ${resource.toBase58()}`, + ) + + return unloadCargo( + fleetInfo, + player, + game, + resource, + amount, + ) + }), + ) + } + + if (!hasEnoughFuel) { + logger.info(`${fleetInfo.fleetName} is refueling`) + + await loadCargo( + fleetInfo, + player, + game, + game.data.mints.fuel, + fuelReserve - cargoLevelFuel, + ) + } + } + + logger.info(`${fleetName} is undocking for take off`) + + return undock(fleetInfo.fleet, fleetInfo.location, player, game) + } + case 'MoveWarp': { + const { fromSector, toSector, warpFinish } = + fleetInfo.fleetState.data + + if (warpFinish.isBefore(now())) { + logger.info( + `${fleetInfo.fleetName} has arrived at ${fleetInfo.fleetState.data.toSector}`, + ) + } else { + logger.info( + `${fleetInfo.fleetName} warping from ${fromSector} to ${toSector}. Arrival in ${dayjs.duration(warpFinish.diff(now())).humanize(false)}. Current Position: ${fleetInfo.location}`, + ) + } + break + } + case 'MoveSubwarp': { + const { fromSector, toSector, arrivalTime } = + fleetInfo.fleetState.data + + if (arrivalTime.isBefore(now())) { + logger.info( + `${fleetInfo.fleetName} has arrived at ${fleetInfo.fleetState.data.toSector}`, + ) + } else { + logger.info( + `${fleetInfo.fleetName} subwarping from ${fromSector} to ${toSector}. Arrival in ${dayjs.duration(arrivalTime.diff(now())).humanize(false)}. Current Position: ${fleetInfo.location}`, + ) + } + break + } + case 'MineAsteroid': { + //TODO: Gather 'Mineable' in order to call `endMine` + // return endMine(fleetInfo, player, game, config.resource) + logger.warn( + `${fleetInfo.fleetName} is currently mining, need to end mine manually.`, + ) + + return Promise.resolve() + } + case 'Respawn': { + const { destructionTime, ETA } = fleetInfo.fleetState.data + + if (ETA.isBefore(now())) { + logger.info(`${fleetInfo.fleetName} has respawned`) + } else { + logger.info( + `${fleetInfo.fleetName} respawning at ${fleetInfo.fleetState.data.sector}. ETA: ${dayjs.duration(ETA.diff(now())).humanize(false)}. Destruction time: ${destructionTime}`, + ) + } + break + } + default: + logger.info( + `${fleetInfo.fleetName} is ${fleetInfo.fleetState.type}`, + ) + + return Promise.resolve() + } +} + +export type TransportConfig = { + map: WorldMap + homeBase: Coordinates + targetBase: Coordinates + resources: Set + warpMode: WarpMode +} + +export const transportConfig = ( + config: Partial & { + map: WorldMap + homeBase: Coordinates + targetBase: Coordinates + resources: Set + }, +): TransportConfig => ({ + map: config.map, + homeBase: config.homeBase, + targetBase: config.targetBase, + resources: config.resources, + warpMode: config.warpMode || 'auto', +}) + +export const transport = ( + map: WorldMap, + homeBase: Coordinates, + targetBase: Coordinates, + resources: Set, +): TransportConfig => + transportConfig({ + map, + homeBase, + targetBase, + resources, + warpMode: 'subwarp', + }) + +export const createTransportStrategy = ( + config: TransportConfig, + player: Player, + game: Game, +): Strategy => { + return { + apply: (fleetInfo: FleetInfo): Promise => + transition(fleetInfo, player, game, config), + } +} diff --git a/src/main/basedbot/index.ts b/src/main/basedbot/index.ts new file mode 100644 index 00000000..6a5c0809 --- /dev/null +++ b/src/main/basedbot/index.ts @@ -0,0 +1,66 @@ +import { Sentry } from '../../sentry' // import this as early as possible to catch early startup errors + +import { logger } from '../../logger' + +import * as app from './basedbot' + +const stop = async (signal?: NodeJS.Signals) => { + logger.info(`Shutting down${signal ? ` (${signal})` : ''}`) + + try { + await app.stop() + } catch (error) { + Sentry.captureException(error) + logger.error('Close failed') + logger.error((error as Error).stack) + + process.exitCode = 1 + } + process.exit() +} + +const start = async () => { + try { + await app.create() + await app.start() + } catch (error) { + Sentry.captureException(error) + logger.error((error as Error).stack) + + process.exitCode = 1 + await stop() + } +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +process.on( + 'unhandledRejection', + async (reason: any | null | undefined, _promise: Promise) => { + logger.error('Unhandled rejection') + + if (reason) { + const { message }: { message: string } = reason + + if (message.includes('Event listener')) { + return + } + logger.error(message) + } + + Sentry.captureException(reason) + await stop() + }, +) + +process.on('uncaughtException', async (error) => { + Sentry.captureException(error) + logger.error('Uncaught exception') + logger.error(error.stack) + + await stop() +}) + +process.on('SIGINT', stop) +process.on('SIGTERM', stop) + +start() diff --git a/src/main/basedbot/lib/fleet-state/fleet-state.ts b/src/main/basedbot/lib/fleet-state/fleet-state.ts new file mode 100644 index 00000000..f260717b --- /dev/null +++ b/src/main/basedbot/lib/fleet-state/fleet-state.ts @@ -0,0 +1,286 @@ +import { CargoStats, Fleet, MiscStats } from '@staratlas/sage' +import Big from 'big.js' +import BN from 'bn.js' + +import { now } from '../../../../dayjs' +import { FleetCargo } from '../sage/state/fleet-cargo' +import { planetByKey } from '../sage/state/planet-by-key' +import { starbaseByKey } from '../sage/state/starbase-by-key' +import { WorldMap } from '../sage/state/world-map' +import { Coordinates } from '../util/coordinates' + +import { transformSector } from './transform/transform-sector' +import { transformTime } from './transform/transform-time' +import { isIdleData } from './type-guard/idle' +import { isMineAsteroidData } from './type-guard/mine-asteroid' +import { isMoveSubWarpData } from './type-guard/move-sub-warp' +import { isMoveWarpData } from './type-guard/move-warp' +import { isRespawnData } from './type-guard/respawn' +import { isStarbaseLoadingBayData } from './type-guard/starbase-loading-bay' +import { + EndReason, + FleetState, + FleetStateType, + RawMineAsteroidData, +} from './types' + +const toBig = (bn: BN): Big => new Big(bn.toString()) +const toBN = (bigInt: Big): BN => new BN(bigInt.toString()) + +const calculateCurrentPosition = ( + startPos: Coordinates, + destPos: Coordinates, + startTime: BN, + endTime: BN, + currentTime: BN, +): Coordinates => { + if (currentTime.gte(endTime)) { + return destPos + } else if (currentTime.lte(startTime)) { + return startPos + } + + const totalTime = toBig(endTime.sub(startTime)) + const elapsedTime = toBig(currentTime.sub(startTime)) + const ratio = elapsedTime.div(totalTime) + + const xDifference = toBig(destPos.xBN.sub(startPos.xBN)) + const yDifference = toBig(destPos.yBN.sub(startPos.yBN)) + + const xt = toBig(startPos.xBN).add(xDifference.mul(ratio)) + const yt = toBig(startPos.yBN).add(yDifference.mul(ratio)) + + return Coordinates.fromBN(toBN(xt.round(0, 1)), toBN(yt.round(0, 1))) +} + +type MiningStats = { + startTime: BN + endTime: BN + cargoLevel: number + miningRate: number + amountMined: number + ammoRequired: number + foodRequired: number + ammoConsumptionRate: number + foodConsumptionRate: number + ammoLevel: number + foodLevel: number + endReason: EndReason + maxMiningDuration: number + isMining: boolean +} + +const getMiningStats = ( + fleet: Fleet, + cargoLevels: FleetCargo, + mineAsteroidData: RawMineAsteroidData, +): MiningStats => { + const cargoStats = fleet.data.stats.cargoStats as unknown as CargoStats + const { + miningRate, + cargoCapacity, + foodConsumptionRate, + ammoConsumptionRate, + } = cargoStats + + const startTime = mineAsteroidData.start + + let cargoLevel = 0 + + for (const [_, value] of cargoLevels.cargo) { + cargoLevel += value + } + + const cargoSpace = cargoCapacity - cargoLevel + + const miningRatePerSecond = miningRate / 10000 + const ammoConsumptionRatePerSecond = ammoConsumptionRate / 10000 + const foodConsumptionRatePerSecond = foodConsumptionRate / 10000 + + const durationToFull = cargoSpace / miningRatePerSecond + const durationToammoDepletion = + cargoLevels.ammo / ammoConsumptionRatePerSecond + const durationToFoodDepletion = + cargoLevels.food / foodConsumptionRatePerSecond + + const maxMiningDuration = Math.min( + durationToFull, + durationToammoDepletion, + durationToFoodDepletion, + ) + + const endReason = + maxMiningDuration === durationToFull + ? 'FULL' + : maxMiningDuration === durationToammoDepletion + ? 'AMMO' + : 'FOOD' + + const n = new BN(now().unix()) + const miningDuration = n.sub(startTime).toNumber() + + const realMiningDuration = Math.min(miningDuration, maxMiningDuration) + + const amountMined = miningRatePerSecond * realMiningDuration + const ammoConsumed = ammoConsumptionRatePerSecond * realMiningDuration + const foodConsumed = foodConsumptionRatePerSecond * realMiningDuration + + return { + startTime, + endTime: startTime.add(new BN(maxMiningDuration)), + cargoLevel, + miningRate, + amountMined, + ammoRequired: ammoConsumed, + foodRequired: foodConsumed, + ammoConsumptionRate, + foodConsumptionRate, + ammoLevel: cargoLevels.ammo - ammoConsumed, + foodLevel: cargoLevels.food - foodConsumed, + endReason, + maxMiningDuration, + isMining: miningDuration < maxMiningDuration, + } +} + +export const getFleetState = async ( + fleet: Fleet, + map: WorldMap, + cargoLevels: FleetCargo, +): Promise => { + const fleetStateKeys = Object.keys(fleet.state) as Array + const miscStats = fleet.data.stats.miscStats as unknown as MiscStats + + if (fleetStateKeys.length === 0) { + throw new Error('Fleet state is empty') + } + + const [type] = fleetStateKeys + const data = fleet.state[type as keyof typeof fleet.state] + + if (!data) { + throw new Error('Data is empty') + } + const warpCooldownExpiry = transformTime(fleet.data.warpCooldownExpiresAt) + const scanCoolDownExpiry = transformTime(fleet.data.scanCooldownExpiresAt) + + const baseData = { + warpCooldownExpiry, + scanCoolDownExpiry, + warpCooldown: warpCooldownExpiry.isAfter(now()), + scanCooldown: scanCoolDownExpiry.isAfter(now()), + } + + switch (type) { + case 'Idle': + if (isIdleData(data)) { + return { + type, + data: { + sector: transformSector(data.sector), + ...baseData, + }, + } + } + break + case 'StarbaseLoadingBay': + if (isStarbaseLoadingBayData(data)) { + const starbase = await starbaseByKey(data.starbase) + + return { + type, + data: { + starbase, + lastUpdate: data.lastUpdate, + sector: transformSector(starbase.data.sector), + ...baseData, + }, + } + } + break + case 'MineAsteroid': + if (isMineAsteroidData(data)) { + const planet = await planetByKey(data.asteroid) + + const miningStats = getMiningStats(fleet, cargoLevels, data) + + return { + type, + data: { + sector: transformSector(planet.data.sector), + lastUpdate: transformTime(data.lastUpdate), + amountMined: new BN(miningStats.amountMined), + asteroid: data.asteroid, + end: transformTime(miningStats.endTime), + resource: data.resource, + mineItem: map.mineItems.get(data.resource.toBase58())!, + start: transformTime(data.start), + endReason: miningStats.endReason, + ...baseData, + }, + } + } + break + case 'MoveWarp': + if (isMoveWarpData(data)) { + return { + type, + data: { + fromSector: transformSector(data.fromSector), + toSector: transformSector(data.toSector), + warpStart: transformTime(data.warpStart), + warpFinish: transformTime(data.warpFinish), + sector: calculateCurrentPosition( + transformSector(data.fromSector), + transformSector(data.toSector), + data.warpStart, + data.warpFinish, + new BN(now().unix()), + ), + ...baseData, + }, + } + } + break + case 'MoveSubwarp': + if (isMoveSubWarpData(data)) { + return { + type, + data: { + fromSector: transformSector(data.fromSector), + toSector: transformSector(data.toSector), + departureTime: transformTime(data.departureTime), + arrivalTime: transformTime(data.arrivalTime), + fuelExpenditure: data.fuelExpenditure, + lastUpdate: transformTime(data.lastUpdate), + sector: calculateCurrentPosition( + transformSector(data.fromSector), + transformSector(data.toSector), + data.departureTime, + data.arrivalTime, + new BN(now().unix()), + ), + ...baseData, + }, + } + } + break + case 'Respawn': + if (isRespawnData(data)) { + return { + type, + data: { + sector: transformSector(data.sector), + destructionTime: transformTime(data.start), + ETA: transformTime( + data.start.add(new BN(miscStats.respawnTime)), + ), + ...baseData, + }, + } + } + break + } + + throw new Error('Data does not match expected type for the fleet state') +} diff --git a/src/main/basedbot/lib/fleet-state/transform/transform-sector.ts b/src/main/basedbot/lib/fleet-state/transform/transform-sector.ts new file mode 100644 index 00000000..787a70f2 --- /dev/null +++ b/src/main/basedbot/lib/fleet-state/transform/transform-sector.ts @@ -0,0 +1,6 @@ +import BN from 'bn.js' + +import { Coordinates } from '../../util/coordinates' + +export const transformSector = (sector: BN[]): Coordinates => + Coordinates.fromBN(sector[0], sector[1]) diff --git a/src/main/basedbot/lib/fleet-state/transform/transform-time.ts b/src/main/basedbot/lib/fleet-state/transform/transform-time.ts new file mode 100644 index 00000000..6673f693 --- /dev/null +++ b/src/main/basedbot/lib/fleet-state/transform/transform-time.ts @@ -0,0 +1,6 @@ +import BN from 'bn.js' + +import dayjs from '../../../../../dayjs' + +export const transformTime = (time: BN): dayjs.Dayjs => + dayjs.unix(time.toNumber()) diff --git a/src/main/basedbot/lib/fleet-state/type-guard/idle.ts b/src/main/basedbot/lib/fleet-state/type-guard/idle.ts new file mode 100644 index 00000000..7bc25312 --- /dev/null +++ b/src/main/basedbot/lib/fleet-state/type-guard/idle.ts @@ -0,0 +1,12 @@ +import BN from 'bn.js' + +import { RawIdleData } from '../types' + +export const isIdleData = (data: unknown): data is RawIdleData => + data !== undefined && + data instanceof Object && + 'sector' in data && + Array.isArray(data.sector) && + data.sector.length === 2 && + data.sector[0] instanceof BN && + data.sector[1] instanceof BN diff --git a/src/main/basedbot/lib/fleet-state/type-guard/mine-asteroid.ts b/src/main/basedbot/lib/fleet-state/type-guard/mine-asteroid.ts new file mode 100644 index 00000000..28923611 --- /dev/null +++ b/src/main/basedbot/lib/fleet-state/type-guard/mine-asteroid.ts @@ -0,0 +1,22 @@ +import { PublicKey } from '@solana/web3.js' +import BN from 'bn.js' + +import { RawMineAsteroidData } from '../types' + +export const isMineAsteroidData = ( + data: unknown, +): data is RawMineAsteroidData => + data !== undefined && + data instanceof Object && + 'asteroid' in data && + 'resource' in data && + 'start' in data && + 'end' in data && + 'amountMined' in data && + 'lastUpdate' in data && + data.asteroid instanceof PublicKey && + data.resource instanceof PublicKey && + data.start instanceof BN && + data.end instanceof BN && + data.amountMined instanceof BN && + data.lastUpdate instanceof BN diff --git a/src/main/basedbot/lib/fleet-state/type-guard/move-sub-warp.ts b/src/main/basedbot/lib/fleet-state/type-guard/move-sub-warp.ts new file mode 100644 index 00000000..e1385a2a --- /dev/null +++ b/src/main/basedbot/lib/fleet-state/type-guard/move-sub-warp.ts @@ -0,0 +1,18 @@ +import BN from 'bn.js' + +import { RawMoveSubwarpData } from '../types' + +// TODO: Add all the fields that are required to be present in the data +export const isMoveSubWarpData = (data: unknown): data is RawMoveSubwarpData => + data !== undefined && + data instanceof Object && + 'fromSector' in data && + 'toSector' in data && + Array.isArray(data.fromSector) && + data.fromSector.length === 2 && + data.fromSector[0] instanceof BN && + data.fromSector[1] instanceof BN && + Array.isArray(data.toSector) && + data.toSector.length === 2 && + data.toSector[0] instanceof BN && + data.toSector[1] instanceof BN diff --git a/src/main/basedbot/lib/fleet-state/type-guard/move-warp.ts b/src/main/basedbot/lib/fleet-state/type-guard/move-warp.ts new file mode 100644 index 00000000..d4ebc23a --- /dev/null +++ b/src/main/basedbot/lib/fleet-state/type-guard/move-warp.ts @@ -0,0 +1,18 @@ +import BN from 'bn.js' + +import { RawMoveWarpData } from '../types' + +// TODO: Add all the fields that are required to be present in the data +export const isMoveWarpData = (data: unknown): data is RawMoveWarpData => + data !== undefined && + data instanceof Object && + 'fromSector' in data && + 'toSector' in data && + Array.isArray(data.fromSector) && + data.fromSector.length === 2 && + data.fromSector[0] instanceof BN && + data.fromSector[1] instanceof BN && + Array.isArray(data.toSector) && + data.toSector.length === 2 && + data.toSector[0] instanceof BN && + data.toSector[1] instanceof BN diff --git a/src/main/basedbot/lib/fleet-state/type-guard/respawn.ts b/src/main/basedbot/lib/fleet-state/type-guard/respawn.ts new file mode 100644 index 00000000..0a5a9291 --- /dev/null +++ b/src/main/basedbot/lib/fleet-state/type-guard/respawn.ts @@ -0,0 +1,12 @@ +import BN from 'bn.js' + +import { RawRespawnData } from '../types' + +export const isRespawnData = (data: unknown): data is RawRespawnData => + data !== undefined && + data instanceof Object && + 'sector' in data && + Array.isArray(data.sector) && + data.sector.length === 2 && + data.sector[0] instanceof BN && + data.sector[1] instanceof BN diff --git a/src/main/basedbot/lib/fleet-state/type-guard/starbase-loading-bay.ts b/src/main/basedbot/lib/fleet-state/type-guard/starbase-loading-bay.ts new file mode 100644 index 00000000..f13a9457 --- /dev/null +++ b/src/main/basedbot/lib/fleet-state/type-guard/starbase-loading-bay.ts @@ -0,0 +1,14 @@ +import { PublicKey } from '@solana/web3.js' +import BN from 'bn.js' + +import { RawStarbaseLoadingBayData } from '../types' + +export const isStarbaseLoadingBayData = ( + data: unknown, +): data is RawStarbaseLoadingBayData => + data !== undefined && + data instanceof Object && + 'starbase' in data && + 'lastUpdate' in data && + data.starbase instanceof PublicKey && + data.lastUpdate instanceof BN diff --git a/src/main/basedbot/lib/fleet-state/types.ts b/src/main/basedbot/lib/fleet-state/types.ts new file mode 100644 index 00000000..ce9bffd4 --- /dev/null +++ b/src/main/basedbot/lib/fleet-state/types.ts @@ -0,0 +1,119 @@ +import { PublicKey } from '@solana/web3.js' +import { MineItem, Starbase } from '@staratlas/sage' +import BN from 'bn.js' + +import dayjs from '../../../../dayjs' +import { Coordinates } from '../util/coordinates' + +export type EndReason = 'FULL' | 'AMMO' | 'FOOD' +type BaseData = { + sector: Coordinates + warpCooldownExpiry: dayjs.Dayjs + scanCoolDownExpiry: dayjs.Dayjs + warpCooldown: boolean + scanCooldown: boolean +} + +// Raw data types for incoming data +export type RawIdleData = { + sector: [BN, BN] +} + +export type IdleData = BaseData + +export type RawStarbaseLoadingBayData = { starbase: PublicKey; lastUpdate: BN } +export type StarbaseLoadingBayData = { + starbase: Starbase + lastUpdate: BN +} & BaseData + +export type RawMineAsteroidData = { + asteroid: PublicKey + resource: PublicKey + start: BN + end: BN + amountMined: BN + lastUpdate: BN + sector: BN[] +} +export type MineAsteroidData = { + asteroid: PublicKey + mineItem: MineItem + resource: PublicKey + start: dayjs.Dayjs + end: dayjs.Dayjs + amountMined: BN + lastUpdate: dayjs.Dayjs + endReason: EndReason +} & BaseData + +export type RawMoveWarpData = { + fromSector: BN[] + toSector: BN[] + warpStart: BN + warpFinish: BN +} +export type MoveWarpData = { + fromSector: Coordinates + toSector: Coordinates + warpStart: dayjs.Dayjs + warpFinish: dayjs.Dayjs +} & BaseData + +export type RawMoveSubwarpData = { + fromSector: BN[] + toSector: BN[] + currentSector: BN[] + departureTime: BN + arrivalTime: BN + fuelExpenditure: BN + lastUpdate: BN +} +export type MoveSubwarpData = { + fromSector: Coordinates + toSector: Coordinates + departureTime: dayjs.Dayjs + arrivalTime: dayjs.Dayjs + fuelExpenditure: BN + lastUpdate: dayjs.Dayjs +} & BaseData + +export type RawRespawnData = { + sector: [BN, BN] + start: BN +} +export type RespawnData = { + destructionTime: dayjs.Dayjs + ETA: dayjs.Dayjs +} & BaseData +export type StarbaseUpgradeData = BaseData +export type StarbaseRepairData = BaseData + +export type FleetStateType = + | 'StarbaseLoadingBay' + | 'Idle' + | 'MineAsteroid' + | 'MoveWarp' + | 'MoveSubwarp' + | 'Respawn' + | 'StarbaseUpgrade' + | 'StarbaseRepair' +/* eslint-disable @typescript-eslint/naming-convention */ +export type FleetStateDataMap = { + StarbaseLoadingBay: StarbaseLoadingBayData + Idle: IdleData + MineAsteroid: MineAsteroidData + MoveWarp: MoveWarpData + MoveSubwarp: MoveSubwarpData + Respawn: RespawnData + StarbaseUpgrade: StarbaseUpgradeData + StarbaseRepair: StarbaseRepairData +} +/* eslint-enable @typescript-eslint/naming-convention */ + +export type FleetState = { + [K in FleetStateType]: { + type: K + data: FleetStateDataMap[K] + } +}[FleetStateType] diff --git a/src/main/basedbot/lib/programs.ts b/src/main/basedbot/lib/programs.ts new file mode 100644 index 00000000..cb6d7387 --- /dev/null +++ b/src/main/basedbot/lib/programs.ts @@ -0,0 +1,95 @@ +// eslint-disable-next-line import/named +import { Idl } from '@coral-xyz/anchor' +import { PublicKey } from '@solana/web3.js' +import { CargoProgram } from '@staratlas/cargo' +import { Cargo } from '@staratlas/cargo/dist/src/idl/cargo' +import { CraftingProgram } from '@staratlas/crafting' +import { Crafting } from '@staratlas/crafting/dist/src/idl/crafting' +import { ProgramMethods } from '@staratlas/data-source' +import { PlayerProfileProgram } from '@staratlas/player-profile' +import { PlayerProfile } from '@staratlas/player-profile/dist/src/idl/player_profile' +import { PointsProgram } from '@staratlas/points' +import { Points } from '@staratlas/points/dist/src/idl/points' +import { ProfileFactionProgram } from '@staratlas/profile-faction' +import { ProfileFaction } from '@staratlas/profile-faction/dist/src/idl/profile_faction' +import { SageProgram } from '@staratlas/sage' +import { Sage } from '@staratlas/sage/dist/src/idl/sage' + +import { config } from '../../../config' +import { anchorProvider } from '../../../service/sol/anchor' + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-expect-error +export type StarAtlasProgram = ProgramMethods + +export const xpCategoryIds = config.sol.rpcEndpoint.includes('atlasnet') + ? { + dataRunningXpCategory: 'DXPsKQPMyaDtunxDWqiKTGWbQga3Wihck8zb8iSLATJQ', + councilRankXpCategory: 'CRXPW3csNpkEYU5U4DUp6Ln6aEEWq4PSUAwV8v6Ygcqg', + pilotingXpCategory: 'PXPfCZwu5Vuuj6aFdEUAXbxudDGeXVktTo6imwhZ5nC', + miningXpCategory: 'MXPkuZz7yXvqdEB8pGtyNknqhxbCzJNQzqixoEiW4Q7', + craftingXpCategory: 'CXPukKpixXCFPrfQmEUGR9VqnDvkUsKfPPLfdd4sKSH8', + loyalityCategory: 'LPpdwMuXRuGMz298EMbNcUioaARN8CUU6dA2qyq46g8', + } + : { + dataRunningXpCategory: 'DataJpxFgHhzwu4zYJeHCnAv21YqWtanEBphNxXBHdEY', + councilRankXpCategory: 'XPneyd1Wvoay3aAa24QiKyPjs8SUbZnGg5xvpKvTgN9', + pilotingXpCategory: 'PiLotBQoUBUvKxMrrQbuR3qDhqgwLJctWsXj3uR7fGs', + miningXpCategory: 'MineMBxARiRdMh7s1wdStSK4Ns3YfnLjBfvF5ZCnzuw', + craftingXpCategory: 'CraftndAV62acibnaW7TiwEYwu8MmJZBdyrfyN54nre7', + loyalityCategory: '', + } + +const programIds = config.sol.rpcEndpoint.includes('atlasnet') + ? { + sage: 'SaGeXGRK85wYGfDqnVDw2bZ61hpdYPFLn1HiRdnRhoC', + profile: 'PprofUW1pURCnMW2si88GWPXEEK3Bvh9Tksy8WtnoYJ', + cargo: 'CArGoi989iv3VL3xArrJXmYYDNhjwCX5ey5sY5KKwMG', + profileFaction: 'pFACzkX2eSpAjDyEohD6i3VRJvREtH9ynbtM1DwVFsj', + crafting: 'CRAFtUSjCW74gQtCS6LyJH33rhhVhdPhZxbPegE4Qwfq', + points: 'PointJfvuHi8DgGsPCy97EaZkQ6NvpghAAVkuquLf3w', + } + : { + sage: 'SAGE2HAwep459SNq61LHvjxPk4pLPEJLoMETef7f7EE', + profile: 'pprofELXjL5Kck7Jn5hCpwAL82DpTkSYBENzahVtbc9', + cargo: 'Cargo2VNTPPTi9c1vq1Jw5d3BWUNr18MjRtSupAghKEk', + profileFaction: 'pFACSRuobDmvfMKq1bAzwj27t6d2GJhSCHb1VcfnRmq', + crafting: 'CRAFT2RPXPJWCEix4WpJST3E7NLf79GTqZUL75wngXo5', + points: 'Point2iBvz7j5TMVef8nEgpmz4pDr7tU7v3RjAfkQbM', + } + +export type StarAtlasPrograms = { + sage: StarAtlasProgram + points: StarAtlasProgram + playerProfile: StarAtlasProgram + cargo: StarAtlasProgram + profileFaction: StarAtlasProgram + crafting: StarAtlasProgram +} + +export const programs: StarAtlasPrograms = { + sage: SageProgram.buildProgram( + new PublicKey(programIds.sage), + anchorProvider, + ), + points: PointsProgram.buildProgram( + new PublicKey(programIds.points), + anchorProvider, + ), + playerProfile: PlayerProfileProgram.buildProgram( + new PublicKey(programIds.profile), + anchorProvider, + ), + cargo: CargoProgram.buildProgram( + new PublicKey(programIds.cargo), + anchorProvider, + ), + profileFaction: ProfileFactionProgram.buildProgram( + new PublicKey(programIds.profileFaction), + anchorProvider, + ), + crafting: CraftingProgram.buildProgram( + new PublicKey(programIds.crafting), + anchorProvider, + ), +} diff --git a/src/main/basedbot/lib/sage/act/create-fleet.ts b/src/main/basedbot/lib/sage/act/create-fleet.ts new file mode 100644 index 00000000..8a59cc70 --- /dev/null +++ b/src/main/basedbot/lib/sage/act/create-fleet.ts @@ -0,0 +1,142 @@ +import { PublicKey } from '@solana/web3.js' +import { InstructionReturn, ixReturnsToIxs } from '@staratlas/data-source' +import { + Game, + Ship, + Starbase, + StarbasePlayer, + WrappedShipEscrow, +} from '@staratlas/sage' + +import { logger } from '../../../../../logger' +import { sendAndConfirmInstructions } from '../../../../../service/sol/send-and-confirm-tx' +import { programs } from '../../programs' +import { addShipToFleetIx } from '../ix/add-ship-to-fleet' +import { createFleetIx } from '../ix/create-fleet' +import { getCargoStatsDefinition } from '../state/cargo-stats-definition' +import { getShipByMint, getStarbasePlayer } from '../state/starbase-player' +import { Player } from '../state/user-account' + +export type FleetShips = Array +export type FleetShip = { + shipMint: PublicKey + count: number +} + +const getShipEscrowIndex = ( + starbasePlayer: StarbasePlayer, + shipKey: PublicKey, +) => { + const pred = (key: PublicKey) => (v: WrappedShipEscrow) => + v.ship.equals(key) + const index = starbasePlayer.wrappedShipEscrows.findIndex(pred(shipKey)) + + if (index === -1) { + throw new Error('Ship not found') + } + + return index +} + +type ShipMintMap = { + mint: PublicKey + ship: Ship +} + +export const createFleet = async ( + player: Player, + game: Game, + starbase: Starbase, + fleetShips: FleetShips, + name: string, +): Promise => { + const instructions: InstructionReturn[] = [] + + const shipMints = ( + await Promise.all( + fleetShips.map(async (fleetShip) => { + return { + mint: fleetShip.shipMint, + ship: await getShipByMint( + fleetShip.shipMint, + game, + programs, + ), + } as ShipMintMap + }), + ) + ).reduce( + (acc, curr) => acc.set(curr.mint.toBase58(), curr.ship), + new Map(), + ) + + const [starbasePlayer, [cargoStatsDefinition]] = await Promise.all([ + getStarbasePlayer(player, starbase, programs), + getCargoStatsDefinition(), + ]) + + const [head, ...tail] = fleetShips.sort( + (a, b) => + getShipEscrowIndex( + starbasePlayer, + shipMints.get(a.shipMint.toBase58())!.key, + ) - + getShipEscrowIndex( + starbasePlayer, + shipMints.get(b.shipMint.toBase58())!.key, + ), + ) + + const shipKey = shipMints.get(head.shipMint.toBase58())?.key + + if (!shipKey) throw new Error('No ship found') + + const escrowIndex = getShipEscrowIndex(starbasePlayer, shipKey) + + logger.info(`Escrow index ${escrowIndex} for ${head.shipMint.toBase58()}`) + + const createFleetReturn = createFleetIx( + player, + game, + starbase, + starbasePlayer, + programs, + shipKey, + cargoStatsDefinition.key, + head.count, + name, + escrowIndex, + ) + + instructions.push(createFleetReturn.instructions) + + for (const fleetShip of tail) { + const shipKey2 = shipMints.get(fleetShip.shipMint.toBase58())?.key + + if (!shipKey2) throw new Error('No ship found') + + const escrowIndex2 = getShipEscrowIndex(starbasePlayer, shipKey2) + + logger.info( + `Escrow index ${escrowIndex2} for ${fleetShip.shipMint.toBase58()}`, + ) + + instructions.push( + addShipToFleetIx( + player, + game, + starbase, + starbasePlayer, + programs, + createFleetReturn.fleetKey[0], + shipKey2, + fleetShip.count, + escrowIndex2, + ), + ) + } + + await sendAndConfirmInstructions( + await ixReturnsToIxs(instructions, player.signer), + ) +} diff --git a/src/main/basedbot/lib/sage/act/deposit-cargo.ts b/src/main/basedbot/lib/sage/act/deposit-cargo.ts new file mode 100644 index 00000000..deda30f5 --- /dev/null +++ b/src/main/basedbot/lib/sage/act/deposit-cargo.ts @@ -0,0 +1,83 @@ +import { getAssociatedTokenAddressSync } from '@solana/spl-token' +import { PublicKey } from '@solana/web3.js' +import { + createAssociatedTokenAccountIdempotent, + InstructionReturn, + ixReturnsToIxs, +} from '@staratlas/data-source' +import { Game, Starbase } from '@staratlas/sage' +import BN from 'bn.js' + +import { sendAndConfirmInstructions } from '../../../../../service/sol/send-and-confirm-tx' +import { getTokenBalance } from '../../../basedbot' +import { programs } from '../../programs' +import { depositCargoIx } from '../ix/import-cargo' +import { getCargoType } from '../state/cargo-types' +import { + getCargoPodsForStarbasePlayer, + getStarbasePlayer, +} from '../state/starbase-player' +import { Player } from '../state/user-account' + +export const depositCargo = async ( + player: Player, + game: Game, + starbase: Starbase, + mint: PublicKey, + amount: BN, + // eslint-disable-next-line max-params +): Promise => { + const instructions: InstructionReturn[] = [] + + const starbasePlayer = await getStarbasePlayer(player, starbase, programs) + + const sourceTokenAccount = getAssociatedTokenAddressSync( + mint, + player.signer.publicKey(), + ) + + const cargoPodTo = await getCargoPodsForStarbasePlayer( + starbasePlayer, + programs, + ) + const destinationTokenAccount = getAssociatedTokenAddressSync( + mint, + cargoPodTo.key, + true, + ) + + instructions.push( + createAssociatedTokenAccountIdempotent(mint, cargoPodTo.key, true) + .instructions, + ) + + const cargoType = getCargoType(player.cargoTypes, game, mint) + + const amountAtOrigin = await getTokenBalance( + player.signer.publicKey(), + mint, + ) + + if (amountAtOrigin.lt(new BN(amount))) { + throw new Error('Not enough cargo available at origin') + } + + instructions.push( + depositCargoIx( + player, + game, + starbase, + starbasePlayer, + cargoPodTo.key, + sourceTokenAccount, + destinationTokenAccount, + cargoType.key, + programs, + amount, + ), + ) + + await sendAndConfirmInstructions( + await ixReturnsToIxs(instructions, player.signer), + ) +} diff --git a/src/main/basedbot/lib/sage/act/deposit-ship.ts b/src/main/basedbot/lib/sage/act/deposit-ship.ts new file mode 100644 index 00000000..ad2ab502 --- /dev/null +++ b/src/main/basedbot/lib/sage/act/deposit-ship.ts @@ -0,0 +1,112 @@ +import { getAssociatedTokenAddressSync } from '@solana/spl-token' +import { + createAssociatedTokenAccountIdempotent, + InstructionReturn, + ixReturnsToIxs, +} from '@staratlas/data-source' +import { + Game, + SagePlayerProfile, + Ship, + Starbase, + WrappedShipEscrow, +} from '@staratlas/sage' +import BN from 'bn.js' + +import { connection } from '../../../../../service/sol' +import { sendAndConfirmInstructions } from '../../../../../service/sol/send-and-confirm-tx' +import { programs } from '../../programs' +import { addShipEscrowIx } from '../ix/add-ship-escrow' +import { getShipByMint, getStarbasePlayer } from '../state/starbase-player' +import { Player } from '../state/user-account' + +import { FleetShips } from './create-fleet' + +export const depositShip = async ( + player: Player, + game: Game, + starbase: Starbase, + ship: Ship, + amount: BN, + // eslint-disable-next-line max-params,require-await +): Promise => { + const instructions: InstructionReturn[] = [] + + const { mint } = ship.data + const starbasePlayer = await getStarbasePlayer(player, starbase, programs) + + const sourceTokenAccount = getAssociatedTokenAddressSync( + mint, + player.signer.publicKey(), + ) + + const [sagePlayerProfile] = SagePlayerProfile.findAddress( + programs.sage, + player.profile.key, + game.key, + ) + const shipEscrowTokenAccountResult = createAssociatedTokenAccountIdempotent( + mint, + sagePlayerProfile, + true, + ) + + instructions.push(shipEscrowTokenAccountResult.instructions) + + const info = await connection.getTokenAccountBalance(sourceTokenAccount) + + if (info.value.uiAmount === null) throw new Error('No balance found') + + const amountAtOrigin = new BN(info.value.uiAmount) + + if (amountAtOrigin.lt(new BN(amount))) { + throw new Error('Not enough ships available at origin') + } + + const pred = (v: WrappedShipEscrow) => v.ship.equals(ship.key) + // const shipEscrow = starbasePlayer.wrappedShipEscrows.find(pred) + const index = starbasePlayer.wrappedShipEscrows.findIndex(pred) + + instructions.push( + addShipEscrowIx( + player, + game, + starbase, + starbasePlayer, + sagePlayerProfile, + programs, + sourceTokenAccount, + ship.key, + shipEscrowTokenAccountResult.address, + amount, + index === -1 ? null : index, + ), + ) + + await sendAndConfirmInstructions( + await ixReturnsToIxs(instructions, player.signer), + ) +} +export const ensureShips = async ( + player: Player, + game: Game, + starbase: Starbase, + fleetShips: FleetShips, +): Promise => { + const starbasePlayer = await getStarbasePlayer(player, starbase, programs) + + for (const fleetShip of fleetShips) { + const desiredAmount = new BN(fleetShip.count) + + const ship = await getShipByMint(fleetShip.shipMint, game, programs) + const pred = (v: WrappedShipEscrow) => v.ship.equals(ship.key) + const shipEscrow = starbasePlayer.wrappedShipEscrows.find(pred) + const needed = shipEscrow + ? desiredAmount.sub(shipEscrow.amount) + : desiredAmount + + if (needed.gt(new BN(0))) { + await depositShip(player, game, starbase, ship, needed) + } + } +} diff --git a/src/main/basedbot/lib/sage/act/disband-fleet.ts b/src/main/basedbot/lib/sage/act/disband-fleet.ts new file mode 100644 index 00000000..38cb7a05 --- /dev/null +++ b/src/main/basedbot/lib/sage/act/disband-fleet.ts @@ -0,0 +1,87 @@ +import { InstructionReturn, ixReturnsToIxs } from '@staratlas/data-source' +import { Game, Starbase, WrappedShipEscrow } from '@staratlas/sage' + +import dayjs from '../../../../../dayjs' +import { logger } from '../../../../../logger' +import { sendAndConfirmInstructions } from '../../../../../service/sol/send-and-confirm-tx' +import { programs } from '../../programs' +import { closeDisbandedFleetIx } from '../ix/close-disbanded-fleet' +import { disbandFleetIx } from '../ix/disband-fleet' +import { disbandedFleetToEscrowIx } from '../ix/disbanded-fleet-to-escrow' +import { getFleetShips } from '../state/get-fleet-ships' +import { getStarbasePlayer } from '../state/starbase-player' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' +import { getName } from '../util' + +export const disbandFleet = async ( + player: Player, + game: Game, + starbase: Starbase, + fleetInfo: FleetInfo, +): Promise => { + const ixs: InstructionReturn[] = [] + const starbasePlayer = await getStarbasePlayer(player, starbase, programs) + const { fleet } = fleetInfo + + if (fleetInfo.fleetState.data.warpCooldown) { + const timeLeft = dayjs.duration( + dayjs().diff(fleetInfo.fleetState.data.warpCooldownExpiry), + ) + + logger.warn( + `Fleet is on warp cooldown, cannot disband. Retry in: ${timeLeft.humanize()}`, + ) + + return + } + + const { disbandedFleetKey, instructions } = disbandFleetIx( + player, + game, + starbase, + starbasePlayer, + programs, + fleet, + ) + + ixs.push(instructions) + + const [fleetShips] = await getFleetShips(fleet) + + for (const fleetShipInfo of fleetShips.fleetShips) { + const pred = (v: WrappedShipEscrow) => v.ship.equals(fleetShipInfo.ship) + // const shipEscrow = starbasePlayer.wrappedShipEscrows.find(pred) + const shipEscrowIndex = + starbasePlayer.wrappedShipEscrows.findIndex(pred) + + ixs.push( + disbandedFleetToEscrowIx( + player, + game, + starbase, + starbasePlayer, + programs, + shipEscrowIndex === -1 ? null : shipEscrowIndex, + disbandedFleetKey[0], + fleet.data.fleetShips, + fleetShipInfo.ship, + fleetShipInfo.amount, + ), + ) + } + + ixs.push( + closeDisbandedFleetIx( + player, + programs, + disbandedFleetKey[0], + fleet.data.fleetShips, + ), + ) + console.log( + `Added ${ixs.length} ixs for disbanding fleet ${getName(fleet)}`, + ) + + await sendAndConfirmInstructions(await ixReturnsToIxs(ixs, player.signer)) +} diff --git a/src/main/basedbot/lib/sage/act/dock.ts b/src/main/basedbot/lib/sage/act/dock.ts new file mode 100644 index 00000000..9966e117 --- /dev/null +++ b/src/main/basedbot/lib/sage/act/dock.ts @@ -0,0 +1,38 @@ +import { ixReturnsToIxs } from '@staratlas/data-source' +import { Game } from '@staratlas/sage' + +import { sendAndConfirmInstructions } from '../../../../../service/sol/send-and-confirm-tx' +import { programs } from '../../programs' +import { Coordinates } from '../../util/coordinates' +import { dockIx } from '../ix/dock' +import { starbaseByCoordinates } from '../state/starbase-by-coordinates' +import { getStarbasePlayer } from '../state/starbase-player' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' + +export const dock = async ( + fleetInfo: FleetInfo, + coordinates: Coordinates, + player: Player, + game: Game, +): Promise => { + const starbase = await starbaseByCoordinates(coordinates) + + if (!starbase) { + throw new Error(`No starbase found at ${coordinates}`) + } + const starbasePlayer = await getStarbasePlayer(player, starbase, programs) + + const ix = dockIx( + fleetInfo, + player, + game, + starbase, + starbasePlayer, + programs, + ) + + const instructions = await ixReturnsToIxs(ix, player.signer) + + await sendAndConfirmInstructions(instructions) +} diff --git a/src/main/basedbot/lib/sage/act/end-mine.ts b/src/main/basedbot/lib/sage/act/end-mine.ts new file mode 100644 index 00000000..670b7370 --- /dev/null +++ b/src/main/basedbot/lib/sage/act/end-mine.ts @@ -0,0 +1,99 @@ +import { + createAssociatedTokenAccountIdempotent, + ixReturnsToIxs, +} from '@staratlas/data-source' +import { Game } from '@staratlas/sage' + +import { logger } from '../../../../../logger' +import { sendAndConfirmInstructions } from '../../../../../service/sol/send-and-confirm-tx' +import { programs } from '../../programs' +import { miningHandlerIx } from '../ix/fleet-state-handler' +import { stopMiningIx } from '../ix/stop-mining' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' +import { Mineable } from '../state/world-map' + +export const endMine = async ( + fleetInfo: FleetInfo, + player: Player, + game: Game, + mineable: Mineable, +): Promise => { + const { fleet } = fleetInfo + + if (!fleet.state.MineAsteroid) { + logger.warn('Fleet is not mining, cannot End Mine') + + return + } + + const [ + foodToken, + ammoToken, + resourceFromToken, + resourceToToken, + fuelToken, + ] = [ + createAssociatedTokenAccountIdempotent( + game.data.mints.food, + fleet.data.cargoHold, + true, + ), + createAssociatedTokenAccountIdempotent( + game.data.mints.ammo, + fleet.data.ammoBank, + true, + ), + createAssociatedTokenAccountIdempotent( + mineable.mineItem.data.mint, + mineable.resource.data.mineItem, + true, + ), + createAssociatedTokenAccountIdempotent( + mineable.mineItem.data.mint, + fleet.data.cargoHold, + true, + ), + createAssociatedTokenAccountIdempotent( + game.data.mints.fuel, + fleet.data.fuelTank, + true, + ), + ] + + await ixReturnsToIxs( + [ + foodToken.instructions, + ammoToken.instructions, + resourceFromToken.instructions, + resourceToToken.instructions, + miningHandlerIx( + fleetInfo, + player, + mineable, + foodToken.address, + ammoToken.address, + resourceFromToken.address, + resourceToToken.address, + programs, + game, + ), + ], + player.signer, + ).then(sendAndConfirmInstructions) + + await ixReturnsToIxs( + [ + fuelToken.instructions, + stopMiningIx( + fleetInfo, + player, + game, + mineable, + fuelToken.address, + programs, + ), + ], + player.signer, + ).then(sendAndConfirmInstructions) +} diff --git a/src/main/basedbot/lib/sage/act/end-move.ts b/src/main/basedbot/lib/sage/act/end-move.ts new file mode 100644 index 00000000..97aa5d55 --- /dev/null +++ b/src/main/basedbot/lib/sage/act/end-move.ts @@ -0,0 +1,43 @@ +import { + createAssociatedTokenAccountIdempotent, + ixReturnsToIxs, +} from '@staratlas/data-source' +import { Game } from '@staratlas/sage' + +import { logger } from '../../../../../logger' +import { sendAndConfirmInstructions } from '../../../../../service/sol/send-and-confirm-tx' +import { programs } from '../../programs' +import { movementSubwarpHandlerIx } from '../ix/movement-subwarp-handler' +import { stopWarpIx } from '../ix/stop-warp' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' + +export const endMove = async ( + fleetInfo: FleetInfo, + player: Player, + game: Game, +): Promise => { + const { fleet } = fleetInfo + + if (!fleet.state.MoveWarp && !fleet.state.MoveSubwarp) { + logger.warn('Fleet is not moving, cannot End Move') + + return + } + const fuelTokenAccount = createAssociatedTokenAccountIdempotent( + game.data.mints.fuel, + fleet.data.fuelTank, + true, + ) + + const ix = ( + fleet.state.MoveSubwarp ? movementSubwarpHandlerIx : stopWarpIx + )(fleetInfo, player, game, fuelTokenAccount.address, programs) + + const instructions = await ixReturnsToIxs( + [fuelTokenAccount.instructions, ix], + player.signer, + ) + + await sendAndConfirmInstructions(instructions) +} diff --git a/src/main/basedbot/lib/sage/act/exit-respawn.ts b/src/main/basedbot/lib/sage/act/exit-respawn.ts new file mode 100644 index 00000000..bc71b570 --- /dev/null +++ b/src/main/basedbot/lib/sage/act/exit-respawn.ts @@ -0,0 +1,77 @@ +import { getAssociatedTokenAddressSync } from '@solana/spl-token' +import { PublicKey } from '@solana/web3.js' +import { InstructionReturn, ixReturnsToIxs } from '@staratlas/data-source' +import { Game, Starbase } from '@staratlas/sage' + +import { logger } from '../../../../../logger' +import { connection } from '../../../../../service/sol' +import { sendAndConfirmInstructions } from '../../../../../service/sol/send-and-confirm-tx' +import { programs } from '../../programs' +import { exitRespawnIx } from '../ix/exit-respawn' +import { forceDropFleetCargoIx } from '../ix/force-drop-fleet-cargo' +import { getCargoStatsDefinition } from '../state/cargo-stats-definition' +import { getCargoType } from '../state/cargo-types' +import { getStarbasePlayer } from '../state/starbase-player' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' + +import { getFleetCargoHold } from './load-cargo' + +export const exitRespawn = async ( + fleetInfo: FleetInfo, + starbase: Starbase, + player: Player, + game: Game, +): Promise => { + const { fleet } = fleetInfo + + if (!fleet.state.Respawn) { + logger.warn('Fleet is not respawning, cannot Exit Respawn') + + return + } + + const starbasePlayer = await getStarbasePlayer(player, starbase, programs) + + const [cargoStatsDefinition] = await getCargoStatsDefinition() + + const ixs: Array = [] + + const cargoMints = player.cargoTypes.map((ct) => ct.data.mint) + + for (const key of cargoMints) { + const mint = new PublicKey(key) + const cargoType = getCargoType(player.cargoTypes, game, mint) + + const cargoPod = getFleetCargoHold(mint, game, fleetInfo) + const tokenFrom = getAssociatedTokenAddressSync(mint, cargoPod, true) + + const accountInfo = await connection.getAccountInfo(tokenFrom) + + if (accountInfo) { + ixs.push( + forceDropFleetCargoIx( + fleetInfo, + game, + cargoStatsDefinition, + cargoPod, + cargoType.key, + tokenFrom, + mint, + programs, + ), + ) + } + } + ixs.push( + exitRespawnIx( + fleetInfo, + player, + game, + starbase, + starbasePlayer, + programs, + ), + ) + await ixReturnsToIxs(ixs, player.signer).then(sendAndConfirmInstructions) +} diff --git a/src/main/basedbot/lib/sage/act/load-cargo.ts b/src/main/basedbot/lib/sage/act/load-cargo.ts new file mode 100644 index 00000000..3b5ee5b7 --- /dev/null +++ b/src/main/basedbot/lib/sage/act/load-cargo.ts @@ -0,0 +1,113 @@ +import { getAssociatedTokenAddressSync } from '@solana/spl-token' +import { PublicKey } from '@solana/web3.js' +import { + createAssociatedTokenAccountIdempotent, + ixReturnsToIxs, +} from '@staratlas/data-source' +import { Game } from '@staratlas/sage' +import BN from 'bn.js' + +import { logger } from '../../../../../logger' +import { sendAndConfirmInstructions } from '../../../../../service/sol/send-and-confirm-tx' +import { getTokenBalance } from '../../../basedbot' +import { programs } from '../../programs' +import { loadCargoIx } from '../ix/load-cargo' +import { getCargoType } from '../state/cargo-types' +import { starbaseByCoordinates } from '../state/starbase-by-coordinates' +import { + getCargoPodsForStarbasePlayer, + getStarbasePlayer, +} from '../state/starbase-player' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' +import { getName } from '../util' + +export const getFleetCargoHold = ( + mint: PublicKey, + game: Game, + fleetInfo: FleetInfo, +): PublicKey => { + switch (mint.toBase58()) { + case game.data.mints.fuel.toBase58(): + return fleetInfo.fleet.data.fuelTank + case game.data.mints.ammo.toBase58(): + return fleetInfo.fleet.data.ammoBank + default: + return fleetInfo.fleet.data.cargoHold + } +} + +export const loadCargo = async ( + fleetInfo: FleetInfo, + player: Player, + game: Game, + mint: PublicKey, + amount: number, + // eslint-disable-next-line max-params +): Promise => { + const starbase = await starbaseByCoordinates(fleetInfo.location) + + const hold = getFleetCargoHold(mint, game, fleetInfo) + + if (!starbase) { + throw new Error(`No starbase found at ${fleetInfo.location}`) + } + + const cargoType = getCargoType(player.cargoTypes, game, mint) + const fleetCargoTokenResult = createAssociatedTokenAccountIdempotent( + mint, + hold, + true, + ) + + const starbasePlayer = await getStarbasePlayer(player, starbase, programs) + const cargoPodFrom = await getCargoPodsForStarbasePlayer( + starbasePlayer, + programs, + ) + + const cargoTokenAccountAddress = getAssociatedTokenAddressSync( + mint, + cargoPodFrom.key, + true, + ) + + const cargoAmountAtOrigin = await getTokenBalance(cargoPodFrom.key, mint) + const toLoad = cargoAmountAtOrigin.lt(new BN(amount)) + ? cargoAmountAtOrigin + : new BN(amount) + + if (toLoad.eq(new BN(0))) { + logger.warn(`No ${mint} available at ${getName(starbase)}...`) + + return + } + if (cargoAmountAtOrigin.lt(new BN(amount))) { + logger.warn( + `Not enough cargo available at origin Starbase, loading ${cargoAmountAtOrigin} instead of ${amount}`, + ) + } + + const ix = loadCargoIx( + fleetInfo, + player, + game, + starbase, + starbasePlayer, + cargoPodFrom.key, + hold, + cargoTokenAccountAddress, + fleetCargoTokenResult.address, + mint, + cargoType.key, + programs, + toLoad, + ) + + const instructions = await ixReturnsToIxs( + [fleetCargoTokenResult.instructions, ix], + player.signer, + ) + + await sendAndConfirmInstructions(instructions) +} diff --git a/src/main/basedbot/lib/sage/act/mine.ts b/src/main/basedbot/lib/sage/act/mine.ts new file mode 100644 index 00000000..e75e3bcb --- /dev/null +++ b/src/main/basedbot/lib/sage/act/mine.ts @@ -0,0 +1,74 @@ +import { + createAssociatedTokenAccountIdempotent, + ixReturnsToIxs, +} from '@staratlas/data-source' +import { Game } from '@staratlas/sage' + +import { logger } from '../../../../../logger' +import { sendAndConfirmInstructions } from '../../../../../service/sol/send-and-confirm-tx' +import { programs } from '../../programs' +import { startMiningIx } from '../ix/start-mining' +import { getStarbasePlayer } from '../state/starbase-player' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' +import { Mineable } from '../state/world-map' + +import { undock } from './undock' + +export const mine = async ( + fleetInfo: FleetInfo, + player: Player, + game: Game, + mineable: Mineable, +): Promise => { + const { fleet } = fleetInfo + + // TOOD: Check fuel cost for mining + + if (fleet.state.MineAsteroid) { + logger.warn('Fleet is already mining') + + return + } + + if (fleet.state.StarbaseLoadingBay) { + logger.info( + `${fleetInfo.fleetName} is in the loading bay at ${fleet.state.StarbaseLoadingBay.starbase}, undocking...`, + ) + + await undock(fleet, fleetInfo.location, player, game) + } + + if (fleet.state.MoveSubwarp || fleet.state.MoveWarp) { + logger.info(`${fleetInfo.fleetName} is moving, cannot mine`) + + return + } + const starbasePlayer = await getStarbasePlayer( + player, + mineable.starbase, + programs, + ) + const fuelTokenAccount = createAssociatedTokenAccountIdempotent( + game.data.mints.fuel, + fleet.data.fuelTank, + true, + ) + + const ix = startMiningIx( + fleetInfo, + player, + game, + mineable, + starbasePlayer, + fuelTokenAccount.address, + programs, + ) + + const instructions = await ixReturnsToIxs( + [fuelTokenAccount.instructions, ix], + player.signer, + ) + + await sendAndConfirmInstructions(instructions) +} diff --git a/src/main/basedbot/lib/sage/act/move.ts b/src/main/basedbot/lib/sage/act/move.ts new file mode 100644 index 00000000..3f40318c --- /dev/null +++ b/src/main/basedbot/lib/sage/act/move.ts @@ -0,0 +1,89 @@ +import { + createAssociatedTokenAccountIdempotent, + ixReturnsToIxs, +} from '@staratlas/data-source' +import { Game } from '@staratlas/sage' + +import dayjs from '../../../../../dayjs' +import { logger } from '../../../../../logger' +import { sendAndConfirmInstructions } from '../../../../../service/sol/send-and-confirm-tx' +import { programs } from '../../programs' +import { Coordinates } from '../../util/coordinates' +import { subWarpIx } from '../ix/subwarp' +import { warpIx } from '../ix/warp' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' + +import { undock } from './undock' + +export type WarpMode = 'warp' | 'subwarp' | 'auto' + +export const move = async ( + fleetInfo: FleetInfo, + coordinates: Coordinates, + player: Player, + game: Game, + warpMode: WarpMode = 'auto', +): Promise => { + const { fleet } = fleetInfo + + if (fleet.state.MoveWarp || fleet.state.MoveSubwarp) { + logger.warn('Fleet is already moving') + + return + } + + if (fleet.state.StarbaseLoadingBay) { + logger.info( + `${fleetInfo.fleetName} is in the loading bay at ${fleet.state.StarbaseLoadingBay.starbase}, undocking...`, + ) + + await undock(fleet, fleetInfo.location, player, game) + } + + if (fleet.state.MineAsteroid) { + logger.info(`${fleetInfo.fleetName} is mining an asteroid, cannot move`) + + return + } + + const { maxWarpDistance } = fleetInfo.movementStats + const desiredDistance = fleetInfo.location.distanceFrom(coordinates) * 100 + + const canWarp = desiredDistance <= maxWarpDistance + const warp = warpMode === 'warp' || (warpMode === 'auto' && canWarp) + + if (warp && fleetInfo.fleetState.data.warpCooldown) { + const timeLeft = dayjs.duration( + dayjs().diff(fleetInfo.fleetState.data.warpCooldownExpiry), + ) + + logger.warn( + `Fleet is on warp cooldown, cannot warp. Retry in: ${timeLeft.humanize()}`, + ) + + return + } + + const fuelTokenAccount = createAssociatedTokenAccountIdempotent( + game.data.mints.fuel, + fleet.data.fuelTank, + true, + ) + + const ix = (warp ? warpIx : subWarpIx)( + fleetInfo, + coordinates, + fuelTokenAccount.address, + player, + game, + programs, + ) + + const instructions = await ixReturnsToIxs( + [fuelTokenAccount.instructions, ix], + player.signer, + ) + + await sendAndConfirmInstructions(instructions) +} diff --git a/src/main/basedbot/lib/sage/act/rearm.ts b/src/main/basedbot/lib/sage/act/rearm.ts new file mode 100644 index 00000000..08d93c9d --- /dev/null +++ b/src/main/basedbot/lib/sage/act/rearm.ts @@ -0,0 +1,89 @@ +import { + createAssociatedTokenAccountIdempotent, + getParsedTokenAccountsByOwner, + ixReturnsToIxs, +} from '@staratlas/data-source' +import { Game } from '@staratlas/sage' +import BN from 'bn.js' + +import { connection } from '../../../../../service/sol' +import { sendAndConfirmInstructions } from '../../../../../service/sol/send-and-confirm-tx' +import { programs } from '../../programs' +import { Coordinates } from '../../util/coordinates' +import { loadCargoIx } from '../ix/load-cargo' +import { getCargoType } from '../state/cargo-types' +import { starbaseByCoordinates } from '../state/starbase-by-coordinates' +import { + getCargoPodsForStarbasePlayer, + getStarbasePlayer, +} from '../state/starbase-player' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' + +export const rearm = async ( + fleetInfo: FleetInfo, + coordinates: Coordinates, + player: Player, + game: Game, +): Promise => { + const starbase = await starbaseByCoordinates(coordinates) + + if (!starbase) { + throw new Error(`No starbase found at ${coordinates}`) + } + + const cargoType = getCargoType( + player.cargoTypes, + game, + game.data.mints.ammo, + ) + const fleetFuelTokenResult = createAssociatedTokenAccountIdempotent( + game.data.mints.ammo, + fleetInfo.fleet.data.ammoBank, + true, + ) + + const starbasePlayer = await getStarbasePlayer(player, starbase, programs) + const cargoPodFrom = await getCargoPodsForStarbasePlayer( + starbasePlayer, + programs, + ) + + const starbaseTokenAccounts = await getParsedTokenAccountsByOwner( + connection, + cargoPodFrom.key, + ) + + const currentAmmo = fleetInfo.cargoLevels.ammo + const maxAmmo = fleetInfo.cargoStats.ammoCapacity + const ammoNeeded = maxAmmo - currentAmmo + + console.log( + `Current Ammo: ${currentAmmo}, Max Ammo: ${maxAmmo}, Ammo Needed: ${ammoNeeded}`, + ) + + // TODO: Check if starbase has enough ammo balance + + const ix = loadCargoIx( + fleetInfo, + player, + game, + starbase, + starbasePlayer, + cargoPodFrom.key, + fleetInfo.fleet.data.ammoBank, + starbaseTokenAccounts[0].address, + fleetFuelTokenResult.address, + game.data.mints.ammo, + cargoType.key, + programs, + new BN(ammoNeeded), + ) + + const instructions = await ixReturnsToIxs( + [fleetFuelTokenResult.instructions, ix], + player.signer, + ) + + await sendAndConfirmInstructions(instructions) +} diff --git a/src/main/basedbot/lib/sage/act/refuel.ts b/src/main/basedbot/lib/sage/act/refuel.ts new file mode 100644 index 00000000..124e098c --- /dev/null +++ b/src/main/basedbot/lib/sage/act/refuel.ts @@ -0,0 +1,89 @@ +import { + createAssociatedTokenAccountIdempotent, + getParsedTokenAccountsByOwner, + ixReturnsToIxs, +} from '@staratlas/data-source' +import { Game } from '@staratlas/sage' +import BN from 'bn.js' + +import { connection } from '../../../../../service/sol' +import { sendAndConfirmInstructions } from '../../../../../service/sol/send-and-confirm-tx' +import { programs } from '../../programs' +import { Coordinates } from '../../util/coordinates' +import { loadCargoIx } from '../ix/load-cargo' +import { getCargoType } from '../state/cargo-types' +import { starbaseByCoordinates } from '../state/starbase-by-coordinates' +import { + getCargoPodsForStarbasePlayer, + getStarbasePlayer, +} from '../state/starbase-player' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' + +export const refuel = async ( + fleetInfo: FleetInfo, + coordinates: Coordinates, + player: Player, + game: Game, +): Promise => { + const starbase = await starbaseByCoordinates(coordinates) + + if (!starbase) { + throw new Error(`No starbase found at ${coordinates}`) + } + + const cargoType = getCargoType( + player.cargoTypes, + game, + game.data.mints.fuel, + ) + const fleetFuelTokenResult = createAssociatedTokenAccountIdempotent( + game.data.mints.fuel, + fleetInfo.fleet.data.fuelTank, + true, + ) + + const starbasePlayer = await getStarbasePlayer(player, starbase, programs) + const cargoPodFrom = await getCargoPodsForStarbasePlayer( + starbasePlayer, + programs, + ) + + const starbaseTokenAccounts = await getParsedTokenAccountsByOwner( + connection, + cargoPodFrom.key, + ) + + const currentFuel = fleetInfo.cargoLevels.fuel + const maxFuel = fleetInfo.cargoStats.fuelCapacity + const fuelNeeded = maxFuel - currentFuel + + console.log( + `Current Fuel: ${currentFuel}, Max Fuel: ${maxFuel}, Fuel Needed: ${fuelNeeded}`, + ) + + // TODO: Check if starbase has enough ammo balance + + const ix = loadCargoIx( + fleetInfo, + player, + game, + starbase, + starbasePlayer, + cargoPodFrom.key, + fleetInfo.fleet.data.fuelTank, + starbaseTokenAccounts[0].address, + fleetFuelTokenResult.address, + game.data.mints.fuel, + cargoType.key, + programs, + new BN(fuelNeeded), + ) + + const instructions = await ixReturnsToIxs( + [fleetFuelTokenResult.instructions, ix], + player.signer, + ) + + await sendAndConfirmInstructions(instructions) +} diff --git a/src/main/basedbot/lib/sage/act/self-destruct.ts b/src/main/basedbot/lib/sage/act/self-destruct.ts new file mode 100644 index 00000000..808d93f4 --- /dev/null +++ b/src/main/basedbot/lib/sage/act/self-destruct.ts @@ -0,0 +1,34 @@ +import { getAssociatedTokenAddressSync } from '@solana/spl-token' +import { ixReturnsToIxs } from '@staratlas/data-source' +import { Game } from '@staratlas/sage' + +import { logger } from '../../../../../logger' +import { sendAndConfirmInstructions } from '../../../../../service/sol/send-and-confirm-tx' +import { programs } from '../../programs' +import { idleToRespawnIx } from '../ix/idle-to-respawn' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' + +export const selfDestruct = async ( + fleetInfo: FleetInfo, + player: Player, + game: Game, +): Promise => { + const { fleet } = fleetInfo + + // TODO: Also support self-destruct for mining fleets + if (!fleet.state.Idle) { + logger.warn('Only Idle Fleets can self destruct') + + return + } + const atlasTokenFrom = getAssociatedTokenAddressSync( + game.data.mints.atlas, + player.signer.publicKey(), + ) + + await ixReturnsToIxs( + idleToRespawnIx(player, game, fleet, atlasTokenFrom, programs), + player.signer, + ).then(sendAndConfirmInstructions) +} diff --git a/src/main/basedbot/lib/sage/act/stop-subwarp.ts b/src/main/basedbot/lib/sage/act/stop-subwarp.ts new file mode 100644 index 00000000..a03b6cec --- /dev/null +++ b/src/main/basedbot/lib/sage/act/stop-subwarp.ts @@ -0,0 +1,52 @@ +import { + createAssociatedTokenAccountIdempotent, + ixReturnsToIxs, +} from '@staratlas/data-source' +import { Game } from '@staratlas/sage' + +import { logger } from '../../../../../logger' +import { sendAndConfirmInstructions } from '../../../../../service/sol/send-and-confirm-tx' +import { programs } from '../../programs' +import { stopSubWarpIx } from '../ix/stop-subwarp' +import { getCargoStatsDefinition } from '../state/cargo-stats-definition' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' + +import { endMove } from './end-move' + +export const stopSubwarp = async ( + fleetInfo: FleetInfo, + player: Player, + game: Game, +): Promise => { + const { fleet } = fleetInfo + + if (!fleet.state.MoveSubwarp) { + logger.warn('Fleet is not subwarping, cannot End Subwarp') + + return + } + + const fuelToken = createAssociatedTokenAccountIdempotent( + game.data.mints.fuel, + fleet.data.fuelTank, + true, + ) + + await ixReturnsToIxs( + [ + fuelToken.instructions, + stopSubWarpIx( + fleetInfo, + player, + game, + (await getCargoStatsDefinition())[0], + fuelToken.address, + game.data.mints.fuel, + programs, + ), + ], + player.signer, + ).then(sendAndConfirmInstructions) + await endMove(fleetInfo, player, game) +} diff --git a/src/main/basedbot/lib/sage/act/undock.ts b/src/main/basedbot/lib/sage/act/undock.ts new file mode 100644 index 00000000..249137f4 --- /dev/null +++ b/src/main/basedbot/lib/sage/act/undock.ts @@ -0,0 +1,41 @@ +import { ixReturnsToIxs } from '@staratlas/data-source' +import { Fleet, Game } from '@staratlas/sage' + +import { sendAndConfirmInstructions } from '../../../../../service/sol/send-and-confirm-tx' +import { programs } from '../../programs' +import { Coordinates } from '../../util/coordinates' +import { starbaseByCoordinates } from '../state/starbase-by-coordinates' +import { getStarbasePlayer } from '../state/starbase-player' +import { Player } from '../state/user-account' + +export const undock = async ( + fleet: Fleet, + coordinates: Coordinates, + player: Player, + game: Game, +): Promise => { + const starbase = await starbaseByCoordinates(coordinates) + + if (!starbase) { + throw new Error(`No starbase found at ${coordinates}`) + } + const { sage } = programs + const starbasePlayer = await getStarbasePlayer(player, starbase, programs) + + const ix = Fleet.loadingBayToIdle( + sage, + player.signer, + player.profile.key, + player.profileFaction.key, + fleet.key, + starbase.key, + starbasePlayer.key, + game.key, + game.data.gameState, + player.keyIndex, + ) + + const instructions = await ixReturnsToIxs(ix, player.signer) + + await sendAndConfirmInstructions(instructions) +} diff --git a/src/main/basedbot/lib/sage/act/unload-all-cargo.ts b/src/main/basedbot/lib/sage/act/unload-all-cargo.ts new file mode 100644 index 00000000..234646f0 --- /dev/null +++ b/src/main/basedbot/lib/sage/act/unload-all-cargo.ts @@ -0,0 +1,94 @@ +import { + createAssociatedTokenAccountIdempotent, + getParsedTokenAccountsByOwner, + InstructionReturn, + ixReturnsToIxs, +} from '@staratlas/data-source' +import { Game } from '@staratlas/sage' +import BN from 'bn.js' + +import { connection } from '../../../../../service/sol' +import { sendAndConfirmInstructions } from '../../../../../service/sol/send-and-confirm-tx' +import { programs } from '../../programs' +import { Coordinates } from '../../util/coordinates' +import { unloadCargoIx } from '../ix/unload-cargo' +import { getCargoType } from '../state/cargo-types' +import { starbaseByCoordinates } from '../state/starbase-by-coordinates' +import { + getCargoPodsForStarbasePlayer, + getStarbasePlayer, +} from '../state/starbase-player' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' + +export const unloadAllCargo = async ( + fleetInfo: FleetInfo, + coordinates: Coordinates, + player: Player, + game: Game, +): Promise => { + const starbase = await starbaseByCoordinates(coordinates) + + const hold = fleetInfo.fleet.data.cargoHold + + if (!starbase) { + throw new Error(`No starbase found at ${coordinates}`) + } + const starbasePlayer = await getStarbasePlayer(player, starbase, programs) + const cargoPodTo = await getCargoPodsForStarbasePlayer( + starbasePlayer, + programs, + ) + + const fleetTokenAccounts = await getParsedTokenAccountsByOwner( + connection, + hold, + ) + + const tokenAddresses: string[] = [] + const withdrawInstructions: InstructionReturn[] = [] + + for (let i = 0; i < fleetTokenAccounts.length; i++) { + const fleetTokenAccount = fleetTokenAccounts[i] + const tokenToResult = createAssociatedTokenAccountIdempotent( + fleetTokenAccount.mint, + (await getCargoPodsForStarbasePlayer(starbasePlayer, programs)).key, + true, + ) + + if (!tokenAddresses.includes(tokenToResult.address.toBase58())) { + tokenAddresses.push(tokenToResult.address.toBase58()) + withdrawInstructions.push(tokenToResult.instructions) + } + + const cargoType = getCargoType( + player.cargoTypes, + game, + fleetTokenAccount.mint, + ) + + withdrawInstructions.push( + unloadCargoIx( + fleetInfo, + player, + game, + starbase, + starbasePlayer, + fleetInfo.fleet.data.cargoHold, + cargoPodTo.key, + fleetTokenAccount.address, + tokenToResult.address, + fleetTokenAccount.mint, + cargoType.key, + programs, + new BN(fleetTokenAccount.delegatedAmount.toString()), + ), + ) + } + const instructions = await ixReturnsToIxs( + withdrawInstructions, + player.signer, + ) + + await sendAndConfirmInstructions(instructions) +} diff --git a/src/main/basedbot/lib/sage/act/unload-cargo.ts b/src/main/basedbot/lib/sage/act/unload-cargo.ts new file mode 100644 index 00000000..75cae3ed --- /dev/null +++ b/src/main/basedbot/lib/sage/act/unload-cargo.ts @@ -0,0 +1,108 @@ +import { getAssociatedTokenAddressSync } from '@solana/spl-token' +import { PublicKey } from '@solana/web3.js' +import { + createAssociatedTokenAccountIdempotent, + ixReturnsToIxs, +} from '@staratlas/data-source' +import { Game } from '@staratlas/sage' +import BN from 'bn.js' + +import { logger } from '../../../../../logger' +import { sendAndConfirmInstructions } from '../../../../../service/sol/send-and-confirm-tx' +import { getTokenBalance } from '../../../basedbot' +import { programs } from '../../programs' +import { unloadCargoIx } from '../ix/unload-cargo' +import { getCargoType } from '../state/cargo-types' +import { starbaseByCoordinates } from '../state/starbase-by-coordinates' +import { + getCargoPodsForStarbasePlayer, + getStarbasePlayer, +} from '../state/starbase-player' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' + +export const getHold = ( + mint: PublicKey, + game: Game, + fleetInfo: FleetInfo, +): PublicKey => { + switch (mint.toBase58()) { + case game.data.mints.fuel.toBase58(): + return fleetInfo.fleet.data.fuelTank + case game.data.mints.ammo.toBase58(): + return fleetInfo.fleet.data.ammoBank + default: + return fleetInfo.fleet.data.cargoHold + } +} + +export const unloadCargo = async ( + fleetInfo: FleetInfo, + player: Player, + game: Game, + mint: PublicKey, + amount: BN, +): Promise => { + const starbase = await starbaseByCoordinates(fleetInfo.location) + + if (!starbase) { + throw new Error(`No starbase found at ${fleetInfo.location}`) + } + + const cargoType = getCargoType(player.cargoTypes, game, mint) + const fleetCargoPod = getHold(mint, game, fleetInfo) + + const starbasePlayer = await getStarbasePlayer(player, starbase, programs) + const cargoPodTo = await getCargoPodsForStarbasePlayer( + starbasePlayer, + programs, + ) + + const cargoPodTokenAccountAddress = getAssociatedTokenAddressSync( + mint, + cargoPodTo.key, + true, + ) + const cargoFleetTokenAccountAddress = getAssociatedTokenAddressSync( + mint, + fleetCargoPod, + true, + ) + const cargoPodTokenResult = createAssociatedTokenAccountIdempotent( + mint, + cargoPodTo.key, + true, + ) + + const fuelAmountAtOrigin = await getTokenBalance(fleetCargoPod, mint) + + if (fuelAmountAtOrigin.lt(amount)) { + logger.warn( + `Requested ${amount.toNumber()} cargo to unload. can only unload ${fuelAmountAtOrigin.toNumber()}`, + ) + } + const toUnload = fuelAmountAtOrigin.lt(amount) ? fuelAmountAtOrigin : amount + + const ix = unloadCargoIx( + fleetInfo, + player, + game, + starbase, + starbasePlayer, + fleetCargoPod, + cargoPodTo.key, + cargoFleetTokenAccountAddress, + cargoPodTokenAccountAddress, + mint, + cargoType.key, + programs, + toUnload, + ) + + const instructions = await ixReturnsToIxs( + [cargoPodTokenResult.instructions, ix], + player.signer, + ) + + await sendAndConfirmInstructions(instructions) +} diff --git a/src/main/basedbot/lib/sage/ix/add-ship-escrow.ts b/src/main/basedbot/lib/sage/ix/add-ship-escrow.ts new file mode 100644 index 00000000..2eeb6f5d --- /dev/null +++ b/src/main/basedbot/lib/sage/ix/add-ship-escrow.ts @@ -0,0 +1,45 @@ +import { PublicKey } from '@solana/web3.js' +import { InstructionReturn } from '@staratlas/data-source' +import { + Game, + SagePlayerProfile, + Starbase, + StarbasePlayer, +} from '@staratlas/sage' +import BN from 'bn.js' + +import { StarAtlasPrograms } from '../../programs' +import { Player } from '../state/user-account' + +export const addShipEscrowIx = ( + player: Player, + game: Game, + starbase: Starbase, + starbasePlayer: StarbasePlayer, + sagePlayerProfile: PublicKey, + programs: StarAtlasPrograms, + originTokenAccount: PublicKey, + ship: PublicKey, + shipEscrowTokenAccount: PublicKey, + shipAmount: BN, + escrowIndex: number | null, + // eslint-disable-next-line max-params +): InstructionReturn => + SagePlayerProfile.addShipEscrow( + programs.sage, + player.profile.key, + player.profileFaction.key, + sagePlayerProfile, + player.signer, + originTokenAccount, + ship, + shipEscrowTokenAccount, + starbasePlayer.key, + starbase.key, + game.key, + game.data.gameState, + { + shipAmount, + index: escrowIndex, + }, + ) diff --git a/src/main/basedbot/lib/sage/ix/add-ship-to-fleet.ts b/src/main/basedbot/lib/sage/ix/add-ship-to-fleet.ts new file mode 100644 index 00000000..bf2cbedc --- /dev/null +++ b/src/main/basedbot/lib/sage/ix/add-ship-to-fleet.ts @@ -0,0 +1,36 @@ +import { PublicKey } from '@solana/web3.js' +import { InstructionReturn } from '@staratlas/data-source' +import { Fleet, Game, Starbase, StarbasePlayer } from '@staratlas/sage' + +import { StarAtlasPrograms } from '../../programs' +import { Player } from '../state/user-account' + +export const addShipToFleetIx = ( + player: Player, + game: Game, + starbase: Starbase, + starbasePlayer: StarbasePlayer, + programs: StarAtlasPrograms, + fleet: PublicKey, + ship: PublicKey, + shipAmount: number, + shipEscrowIndex: number, +): InstructionReturn => + Fleet.addShipToFleet( + programs.sage, + player.signer, + player.profile.key, + player.profileFaction.key, + fleet, + ship, + starbasePlayer.key, + starbase.key, + game.key, + game.data.gameState, + { + shipAmount, + shipEscrowIndex, + keyIndex: 0, + fleetShipInfoIndex: null, + }, + ) diff --git a/src/main/basedbot/lib/sage/ix/close-disbanded-fleet.ts b/src/main/basedbot/lib/sage/ix/close-disbanded-fleet.ts new file mode 100644 index 00000000..520a3778 --- /dev/null +++ b/src/main/basedbot/lib/sage/ix/close-disbanded-fleet.ts @@ -0,0 +1,22 @@ +import { PublicKey } from '@solana/web3.js' +import { InstructionReturn } from '@staratlas/data-source' +import { DisbandedFleet } from '@staratlas/sage' + +import { StarAtlasPrograms } from '../../programs' +import { Player } from '../state/user-account' + +export const closeDisbandedFleetIx = ( + player: Player, + programs: StarAtlasPrograms, + disbandedFleetKey: PublicKey, + fleetShipsKey: PublicKey, +): InstructionReturn => + DisbandedFleet.closeDisbandedFleet( + programs.sage, + player.signer, + player.profile.key, + 'funder', + disbandedFleetKey, + fleetShipsKey, + { keyIndex: 0 }, + ) diff --git a/src/main/basedbot/lib/sage/ix/create-fleet.ts b/src/main/basedbot/lib/sage/ix/create-fleet.ts new file mode 100644 index 00000000..fa6c62d9 --- /dev/null +++ b/src/main/basedbot/lib/sage/ix/create-fleet.ts @@ -0,0 +1,47 @@ +import { PublicKey } from '@solana/web3.js' +import { InstructionReturn, stringToByteArray } from '@staratlas/data-source' +import { Fleet, Game, Starbase, StarbasePlayer } from '@staratlas/sage' + +import { StarAtlasPrograms } from '../../programs' +import { Player } from '../state/user-account' + +type CreateFleetReturn = { + fleetKey: [PublicKey, number] + cargoHoldKey: [PublicKey, number] + fuelTankKey: [PublicKey, number] + ammoBankKey: [PublicKey, number] + instructions: InstructionReturn +} + +export const createFleetIx = ( + player: Player, + game: Game, + starbase: Starbase, + starbasePlayer: StarbasePlayer, + programs: StarAtlasPrograms, + ship: PublicKey, + cargoStatsDefinition: PublicKey, + shipAmount: number, + name: string, + shipEscrowIndex: number, + // eslint-disable-next-line max-params +): CreateFleetReturn => + Fleet.createFleet( + programs.sage, + programs.cargo, + player.signer, + player.profile.key, + player.profileFaction.key, + ship, + starbasePlayer.key, + starbase.key, + game.key, + game.data.gameState, + cargoStatsDefinition, + { + shipAmount, + fleetLabel: stringToByteArray(name, 32), + shipEscrowIndex, + keyIndex: 0, + }, + ) diff --git a/src/main/basedbot/lib/sage/ix/disband-fleet.ts b/src/main/basedbot/lib/sage/ix/disband-fleet.ts new file mode 100644 index 00000000..61762bda --- /dev/null +++ b/src/main/basedbot/lib/sage/ix/disband-fleet.ts @@ -0,0 +1,36 @@ +import { PublicKey } from '@solana/web3.js' +import { InstructionReturn } from '@staratlas/data-source' +import { Fleet, Game, Starbase, StarbasePlayer } from '@staratlas/sage' + +import { StarAtlasPrograms } from '../../programs' +import { Player } from '../state/user-account' + +type DisbandFleetReturn = { + disbandedFleetKey: [PublicKey, number] + instructions: InstructionReturn +} + +export const disbandFleetIx = ( + player: Player, + game: Game, + starbase: Starbase, + starbasePlayer: StarbasePlayer, + programs: StarAtlasPrograms, + fleet: Fleet, + // eslint-disable-next-line max-params +): DisbandFleetReturn => + Fleet.disbandFleet( + programs.sage, + programs.cargo, + player.signer, + player.profile.key, + player.profileFaction.key, + fleet, + starbasePlayer.key, + starbase.key, + game.key, + game.data.gameState, + { + keyIndex: 0, + }, + ) diff --git a/src/main/basedbot/lib/sage/ix/disbanded-fleet-to-escrow.ts b/src/main/basedbot/lib/sage/ix/disbanded-fleet-to-escrow.ts new file mode 100644 index 00000000..e006023f --- /dev/null +++ b/src/main/basedbot/lib/sage/ix/disbanded-fleet-to-escrow.ts @@ -0,0 +1,39 @@ +import { PublicKey } from '@solana/web3.js' +import { InstructionReturn } from '@staratlas/data-source' +import { DisbandedFleet, Game, Starbase, StarbasePlayer } from '@staratlas/sage' +import BN from 'bn.js' + +import { StarAtlasPrograms } from '../../programs' +import { Player } from '../state/user-account' + +export const disbandedFleetToEscrowIx = ( + player: Player, + game: Game, + starbase: Starbase, + starbasePlayer: StarbasePlayer, + programs: StarAtlasPrograms, + shipEscrowIndex: number | null, + disbandedFleet: PublicKey, + fleetShips: PublicKey, + shipKey: PublicKey, + shipAmount: BN, +): InstructionReturn => + DisbandedFleet.disbandedFleetToEscrow( + programs.sage, + player.signer, + player.profile.key, + player.profileFaction.key, + disbandedFleet, + fleetShips, + shipKey, + starbasePlayer.key, + starbase.key, + game.key, + game.data.gameState, + { + fleetShipInfoIndex: 0, + keyIndex: 0, + shipAmount: shipAmount.toNumber(), + shipEscrowIndex, + }, + ) diff --git a/src/main/basedbot/lib/sage/ix/dock.ts b/src/main/basedbot/lib/sage/ix/dock.ts new file mode 100644 index 00000000..4d327a5d --- /dev/null +++ b/src/main/basedbot/lib/sage/ix/dock.ts @@ -0,0 +1,27 @@ +import { InstructionReturn } from '@staratlas/data-source' +import { Fleet, Game, Starbase, StarbasePlayer } from '@staratlas/sage' + +import { StarAtlasPrograms } from '../../programs' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' + +export const dockIx = ( + fleetInfo: FleetInfo, + player: Player, + game: Game, + starbase: Starbase, + starbasePlayer: StarbasePlayer, + programs: StarAtlasPrograms, +): InstructionReturn => + Fleet.idleToLoadingBay( + programs.sage, + player.signer, + player.profile.key, + player.profileFaction.key, + fleetInfo.fleet.key, + starbase.key, + starbasePlayer.key, + game.key, + game.data.gameState, + player.keyIndex, + ) diff --git a/src/main/basedbot/lib/sage/ix/exit-respawn.ts b/src/main/basedbot/lib/sage/ix/exit-respawn.ts new file mode 100644 index 00000000..b65ea741 --- /dev/null +++ b/src/main/basedbot/lib/sage/ix/exit-respawn.ts @@ -0,0 +1,32 @@ +import { InstructionReturn } from '@staratlas/data-source' +import { Fleet, Game, Starbase, StarbasePlayer } from '@staratlas/sage' + +import { StarAtlasPrograms } from '../../programs' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' + +export const exitRespawnIx = ( + fleetInfo: FleetInfo, + player: Player, + game: Game, + starbase: Starbase, + starbasePlayer: StarbasePlayer, + programs: StarAtlasPrograms, +): InstructionReturn => + Fleet.respawnToLoadingBay( + programs.sage, + player.signer, + player.profile.key, + player.profileFaction.key, + fleetInfo.fleet.key, + starbase.key, + starbasePlayer.key, + fleetInfo.fleet.data.cargoHold, + fleetInfo.fleet.data.fuelTank, + fleetInfo.fleet.data.ammoBank, + game.key, + game.data.gameState, + { + keyIndex: 0, + }, + ) diff --git a/src/main/basedbot/lib/sage/ix/fleet-state-handler.ts b/src/main/basedbot/lib/sage/ix/fleet-state-handler.ts new file mode 100644 index 00000000..c7678036 --- /dev/null +++ b/src/main/basedbot/lib/sage/ix/fleet-state-handler.ts @@ -0,0 +1,45 @@ +import { PublicKey } from '@solana/web3.js' +import { InstructionReturn } from '@staratlas/data-source' +import { Fleet, Game } from '@staratlas/sage' + +import { StarAtlasPrograms } from '../../programs' +import { getCargoType } from '../state/cargo-types' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' +import { Mineable } from '../state/world-map' + +export const miningHandlerIx = ( + fleetInfo: FleetInfo, + player: Player, + mineable: Mineable, + foodTokenFrom: PublicKey, + ammoTokenFrom: PublicKey, + resourceTokenFrom: PublicKey, + resourceTokenTo: PublicKey, + programs: StarAtlasPrograms, + game: Game, + // eslint-disable-next-line max-params +): InstructionReturn => + Fleet.asteroidMiningHandler( + programs.sage, + programs.cargo, + fleetInfo.fleet.key, + mineable.starbase.key, + mineable.mineItem.key, + mineable.resource.key, + mineable.planet.key, + fleetInfo.fleet.data.cargoHold, + fleetInfo.fleet.data.ammoBank, + player.foodCargoType.key, + player.ammoCargoType.key, + getCargoType(player.cargoTypes, game, mineable.mineItem.data.mint).key, + game.data.cargo.statsDefinition, + game.data.gameState, + game.key, + foodTokenFrom, + ammoTokenFrom, + resourceTokenFrom, + resourceTokenTo, + game.data.mints.food, + game.data.mints.ammo, + ) diff --git a/src/main/basedbot/lib/sage/ix/force-drop-fleet-cargo.ts b/src/main/basedbot/lib/sage/ix/force-drop-fleet-cargo.ts new file mode 100644 index 00000000..8f8e46e5 --- /dev/null +++ b/src/main/basedbot/lib/sage/ix/force-drop-fleet-cargo.ts @@ -0,0 +1,29 @@ +import { PublicKey } from '@solana/web3.js' +import { CargoStatsDefinition } from '@staratlas/cargo' +import { InstructionReturn } from '@staratlas/data-source' +import { Fleet, Game } from '@staratlas/sage' + +import { StarAtlasPrograms } from '../../programs' +import { FleetInfo } from '../state/user-fleets' + +export const forceDropFleetCargoIx = ( + fleetInfo: FleetInfo, + game: Game, + cargoStatsDefinition: CargoStatsDefinition, + cargoPod: PublicKey, + cargoType: PublicKey, + tokenFrom: PublicKey, + tokenMint: PublicKey, + programs: StarAtlasPrograms, +): InstructionReturn => + Fleet.forceDropFleetCargo( + programs.sage, + programs.cargo, + fleetInfo.fleet.key, + cargoPod, + cargoType, + cargoStatsDefinition.key, + game.key, + tokenFrom, + tokenMint, + ) diff --git a/src/main/basedbot/lib/sage/ix/idle-to-respawn.ts b/src/main/basedbot/lib/sage/ix/idle-to-respawn.ts new file mode 100644 index 00000000..2bfafea1 --- /dev/null +++ b/src/main/basedbot/lib/sage/ix/idle-to-respawn.ts @@ -0,0 +1,26 @@ +import { PublicKey } from '@solana/web3.js' +import { InstructionReturn } from '@staratlas/data-source' +import { Fleet, Game } from '@staratlas/sage' + +import { StarAtlasPrograms } from '../../programs' +import { Player } from '../state/user-account' + +export const idleToRespawnIx = ( + player: Player, + game: Game, + fleet: Fleet, + atlasTokenFrom: PublicKey, + programs: StarAtlasPrograms, +): InstructionReturn => + Fleet.idleToRespawn( + programs.sage, + player.signer, + player.profile.key, + player.profileFaction.key, + fleet.key, + atlasTokenFrom, + game.data.vaults.atlas, + game.data.gameState, + game.key, + { keyIndex: 0 }, + ) diff --git a/src/main/basedbot/lib/sage/ix/import-cargo.ts b/src/main/basedbot/lib/sage/ix/import-cargo.ts new file mode 100644 index 00000000..606f6cac --- /dev/null +++ b/src/main/basedbot/lib/sage/ix/import-cargo.ts @@ -0,0 +1,41 @@ +import { PublicKey } from '@solana/web3.js' +import { InstructionReturn } from '@staratlas/data-source' +import { Game, Starbase, StarbasePlayer } from '@staratlas/sage' +import BN from 'bn.js' + +import { StarAtlasPrograms } from '../../programs' +import { Player } from '../state/user-account' + +export const depositCargoIx = ( + player: Player, + game: Game, + starbase: Starbase, + starbasePlayer: StarbasePlayer, + cargoPodTo: PublicKey, + tokenFrom: PublicKey, + tokenTo: PublicKey, + cargoType: PublicKey, + programs: StarAtlasPrograms, + amount: BN, + // eslint-disable-next-line max-params +): InstructionReturn => + StarbasePlayer.depositCargoToGame( + programs.sage, + programs.cargo, + starbasePlayer.key, + player.signer, + player.profile.key, + player.profileFaction.key, + starbase.key, + cargoPodTo, + cargoType, + game.data.cargo.statsDefinition, + tokenFrom, + tokenTo, + game.key, + game.data.gameState, + { + amount, + keyIndex: 0, + }, + ) diff --git a/src/main/basedbot/lib/sage/ix/load-cargo.ts b/src/main/basedbot/lib/sage/ix/load-cargo.ts new file mode 100644 index 00000000..24f64fed --- /dev/null +++ b/src/main/basedbot/lib/sage/ix/load-cargo.ts @@ -0,0 +1,49 @@ +import { PublicKey } from '@solana/web3.js' +import { InstructionReturn } from '@staratlas/data-source' +import { Fleet, Game, Starbase, StarbasePlayer } from '@staratlas/sage' +import BN from 'bn.js' + +import { StarAtlasPrograms } from '../../programs' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' + +export const loadCargoIx = ( + fleetInfo: FleetInfo, + player: Player, + game: Game, + starbase: Starbase, + starbasePlayer: StarbasePlayer, + cargoPodFrom: PublicKey, + cargoPodTo: PublicKey, + tokenFrom: PublicKey, + tokenTo: PublicKey, + tokenMint: PublicKey, + cargoType: PublicKey, + programs: StarAtlasPrograms, + amount: BN, + // eslint-disable-next-line max-params +): InstructionReturn => + Fleet.depositCargoToFleet( + programs.sage, + programs.cargo, + player.signer, + player.profile.key, + player.profileFaction.key, + 'funder', + starbase.key, + starbasePlayer.key, + fleetInfo.fleet.key, + cargoPodFrom, + cargoPodTo, + cargoType, + game.data.cargo.statsDefinition, + tokenFrom, + tokenTo, + tokenMint, + game.key, + game.data.gameState, + { + amount, + keyIndex: 0, + }, + ) diff --git a/src/main/basedbot/lib/sage/ix/movement-subwarp-handler.ts b/src/main/basedbot/lib/sage/ix/movement-subwarp-handler.ts new file mode 100644 index 00000000..760d4326 --- /dev/null +++ b/src/main/basedbot/lib/sage/ix/movement-subwarp-handler.ts @@ -0,0 +1,34 @@ +import { PublicKey } from '@solana/web3.js' +import { InstructionReturn } from '@staratlas/data-source' +import { Fleet, Game } from '@staratlas/sage' + +import { StarAtlasPrograms } from '../../programs' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' + +export const movementSubwarpHandlerIx = ( + fleetInfo: FleetInfo, + player: Player, + game: Game, + fuelTokenAccount: PublicKey, + programs: StarAtlasPrograms, +): InstructionReturn => + Fleet.movementSubwarpHandler( + programs.sage, + programs.cargo, + programs.points, + player.profile.key, + fleetInfo.fleet.key, + fleetInfo.fleet.data.fuelTank, + player.fuelCargoType.key, + game.data.cargo.statsDefinition, + fuelTokenAccount, + game.data.mints.fuel, + player.xpAccounts.piloting.userPointsAccount, + player.xpAccounts.piloting.pointsCategory, + player.xpAccounts.piloting.pointsModifierAccount, + player.xpAccounts.councilRank.userPointsAccount, + player.xpAccounts.councilRank.pointsCategory, + player.xpAccounts.councilRank.pointsModifierAccount, + game.key, + ) diff --git a/src/main/basedbot/lib/sage/ix/start-mining.ts b/src/main/basedbot/lib/sage/ix/start-mining.ts new file mode 100644 index 00000000..a3d064bf --- /dev/null +++ b/src/main/basedbot/lib/sage/ix/start-mining.ts @@ -0,0 +1,37 @@ +import { PublicKey } from '@solana/web3.js' +import { InstructionReturn } from '@staratlas/data-source' +import { Fleet, Game, StarbasePlayer } from '@staratlas/sage' + +import { StarAtlasPrograms } from '../../programs' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' +import { Mineable } from '../state/world-map' + +export const startMiningIx = ( + fleetInfo: FleetInfo, + player: Player, + game: Game, + mineable: Mineable, + starbasePlayer: StarbasePlayer, + fuelTokenAccount: PublicKey, + programs: StarAtlasPrograms, + // eslint-disable-next-line max-params +): InstructionReturn => + Fleet.startMiningAsteroid( + programs.sage, + player.signer, + player.profile.key, + player.profileFaction.key, + fleetInfo.fleet.key, + mineable.starbase.key, + starbasePlayer.key, + mineable.mineItem.key, + mineable.resource.key, + mineable.planet.key, + game.data.gameState, + game.key, + fuelTokenAccount, + { + keyIndex: player.keyIndex, + }, + ) diff --git a/src/main/basedbot/lib/sage/ix/stop-mining.ts b/src/main/basedbot/lib/sage/ix/stop-mining.ts new file mode 100644 index 00000000..5efb726c --- /dev/null +++ b/src/main/basedbot/lib/sage/ix/stop-mining.ts @@ -0,0 +1,49 @@ +import { PublicKey } from '@solana/web3.js' +import { InstructionReturn } from '@staratlas/data-source' +import { Fleet, Game } from '@staratlas/sage' + +import { StarAtlasPrograms } from '../../programs' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' +import { Mineable } from '../state/world-map' + +export const stopMiningIx = ( + fleetInfo: FleetInfo, + player: Player, + game: Game, + mineable: Mineable, + fuelTokenAccount: PublicKey, + programs: StarAtlasPrograms, + // eslint-disable-next-line max-params +): InstructionReturn => + Fleet.stopMiningAsteroid( + programs.sage, + programs.cargo, + programs.points, + player.signer, + player.profile.key, + player.profileFaction.key, + fleetInfo.fleet.key, + mineable.mineItem.key, + mineable.resource.key, + mineable.planet.key, + fleetInfo.fleet.data.fuelTank, + player.fuelCargoType.key, + game.data.cargo.statsDefinition, + player.xpAccounts.mining.userPointsAccount, + player.xpAccounts.mining.pointsCategory, + player.xpAccounts.mining.pointsModifierAccount, + player.xpAccounts.piloting.userPointsAccount, + player.xpAccounts.piloting.pointsCategory, + player.xpAccounts.piloting.pointsModifierAccount, + player.xpAccounts.councilRank.userPointsAccount, + player.xpAccounts.councilRank.pointsCategory, + player.xpAccounts.councilRank.pointsModifierAccount, + game.data.gameState, + game.key, + fuelTokenAccount, + game.data.mints.fuel, + { + keyIndex: player.keyIndex, + }, + ) diff --git a/src/main/basedbot/lib/sage/ix/stop-subwarp.ts b/src/main/basedbot/lib/sage/ix/stop-subwarp.ts new file mode 100644 index 00000000..280a7bd5 --- /dev/null +++ b/src/main/basedbot/lib/sage/ix/stop-subwarp.ts @@ -0,0 +1,43 @@ +import { PublicKey } from '@solana/web3.js' +import { CargoStatsDefinition } from '@staratlas/cargo' +import { InstructionReturn } from '@staratlas/data-source' +import { Fleet, Game } from '@staratlas/sage' + +import { StarAtlasPrograms } from '../../programs' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' + +export const stopSubWarpIx = ( + fleetInfo: FleetInfo, + player: Player, + game: Game, + cargoStatsDefinition: CargoStatsDefinition, + fuelTokenAccount: PublicKey, + fuelTokenMint: PublicKey, + programs: StarAtlasPrograms, +): InstructionReturn => + Fleet.stopSubwarp( + programs.sage, + programs.cargo, + programs.points, + player.signer, + player.profile.key, + player.profileFaction.key, + fleetInfo.fleet.key, + fleetInfo.fleet.data.fuelTank, + player.fuelCargoType.key, + cargoStatsDefinition.key, + fuelTokenAccount, + fuelTokenMint, + player.xpAccounts.piloting.userPointsAccount, + player.xpAccounts.piloting.pointsCategory, + player.xpAccounts.piloting.pointsModifierAccount, + player.xpAccounts.councilRank.userPointsAccount, + player.xpAccounts.councilRank.pointsCategory, + player.xpAccounts.councilRank.pointsModifierAccount, + game.key, + game.data.gameState, + { + keyIndex: 0, + }, + ) diff --git a/src/main/basedbot/lib/sage/ix/stop-warp.ts b/src/main/basedbot/lib/sage/ix/stop-warp.ts new file mode 100644 index 00000000..865d9bcc --- /dev/null +++ b/src/main/basedbot/lib/sage/ix/stop-warp.ts @@ -0,0 +1,28 @@ +import { PublicKey } from '@solana/web3.js' +import { InstructionReturn } from '@staratlas/data-source' +import { Fleet, Game } from '@staratlas/sage' + +import { StarAtlasPrograms } from '../../programs' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' + +export const stopWarpIx = ( + fleetInfo: FleetInfo, + player: Player, + game: Game, + _fuelTokenAccount: PublicKey, + programs: StarAtlasPrograms, +): InstructionReturn => + Fleet.moveWarpHandler( + programs.sage, + programs.points, + player.profile.key, + fleetInfo.fleet.key, + player.xpAccounts.piloting.userPointsAccount, + player.xpAccounts.piloting.pointsCategory, + player.xpAccounts.piloting.pointsModifierAccount, + player.xpAccounts.councilRank.userPointsAccount, + player.xpAccounts.councilRank.pointsCategory, + player.xpAccounts.councilRank.pointsModifierAccount, + game.key, + ) diff --git a/src/main/basedbot/lib/sage/ix/subwarp.ts b/src/main/basedbot/lib/sage/ix/subwarp.ts new file mode 100644 index 00000000..073e4979 --- /dev/null +++ b/src/main/basedbot/lib/sage/ix/subwarp.ts @@ -0,0 +1,30 @@ +import { PublicKey } from '@solana/web3.js' +import { InstructionReturn } from '@staratlas/data-source' +import { Fleet, Game } from '@staratlas/sage' + +import { StarAtlasPrograms } from '../../programs' +import { Coordinates } from '../../util/coordinates' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' + +export const subWarpIx = ( + fleetInfo: FleetInfo, + coordinates: Coordinates, + _fuelTokenAccount: PublicKey, + player: Player, + game: Game, + programs: StarAtlasPrograms, +): InstructionReturn => + Fleet.startSubwarp( + programs.sage, + player.signer, + player.profile.key, + player.profileFaction.key, + fleetInfo.fleet.key, + game.key, + game.data.gameState, + { + toSector: coordinates.toArray(), + keyIndex: player.keyIndex, + }, + ) diff --git a/src/main/basedbot/lib/sage/ix/undock.ts b/src/main/basedbot/lib/sage/ix/undock.ts new file mode 100644 index 00000000..2ef715bd --- /dev/null +++ b/src/main/basedbot/lib/sage/ix/undock.ts @@ -0,0 +1,27 @@ +import { InstructionReturn } from '@staratlas/data-source' +import { Fleet, Game, Starbase, StarbasePlayer } from '@staratlas/sage' + +import { StarAtlasPrograms } from '../../programs' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' + +export const undockIx = ( + fleetInfo: FleetInfo, + player: Player, + game: Game, + starbase: Starbase, + starbasePlayer: StarbasePlayer, + programs: StarAtlasPrograms, +): InstructionReturn => + Fleet.loadingBayToIdle( + programs.sage, + player.signer, + player.profile.key, + player.profileFaction.key, + fleetInfo.fleet.key, + starbase.key, + starbasePlayer.key, + game.key, + game.data.gameState, + player.keyIndex, + ) diff --git a/src/main/basedbot/lib/sage/ix/unload-cargo.ts b/src/main/basedbot/lib/sage/ix/unload-cargo.ts new file mode 100644 index 00000000..efb30b2c --- /dev/null +++ b/src/main/basedbot/lib/sage/ix/unload-cargo.ts @@ -0,0 +1,49 @@ +import { PublicKey } from '@solana/web3.js' +import { InstructionReturn } from '@staratlas/data-source' +import { Fleet, Game, Starbase, StarbasePlayer } from '@staratlas/sage' +import BN from 'bn.js' + +import { StarAtlasPrograms } from '../../programs' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' + +export const unloadCargoIx = ( + fleetInfo: FleetInfo, + player: Player, + game: Game, + starbase: Starbase, + starbasePlayer: StarbasePlayer, + cargoPodFrom: PublicKey, + cargoPodTo: PublicKey, + tokenFrom: PublicKey, + tokenTo: PublicKey, + tokenMint: PublicKey, + cargoType: PublicKey, + programs: StarAtlasPrograms, + amount: BN, + // eslint-disable-next-line max-params +): InstructionReturn => + Fleet.withdrawCargoFromFleet( + programs.sage, + programs.cargo, + player.signer, + 'funder', + player.profile.key, + player.profileFaction.key, + starbase.key, + starbasePlayer.key, + fleetInfo.fleet.key, + cargoPodFrom, + cargoPodTo, + cargoType, + game.data.cargo.statsDefinition, + tokenFrom, + tokenTo, + tokenMint, + game.key, + game.data.gameState, + { + amount, + keyIndex: 0, + }, + ) diff --git a/src/main/basedbot/lib/sage/ix/warp.ts b/src/main/basedbot/lib/sage/ix/warp.ts new file mode 100644 index 00000000..8e828ba2 --- /dev/null +++ b/src/main/basedbot/lib/sage/ix/warp.ts @@ -0,0 +1,36 @@ +import { PublicKey } from '@solana/web3.js' +import { InstructionReturn } from '@staratlas/data-source' +import { Fleet, Game } from '@staratlas/sage' + +import { StarAtlasPrograms } from '../../programs' +import { Coordinates } from '../../util/coordinates' +import { Player } from '../state/user-account' +import { FleetInfo } from '../state/user-fleets' + +export const warpIx = ( + fleetInfo: FleetInfo, + coordinates: Coordinates, + fuelTokenAccount: PublicKey, + player: Player, + game: Game, + programs: StarAtlasPrograms, +): InstructionReturn => + Fleet.warpToCoordinate( + programs.sage, + player.signer, + player.profile.key, + player.profileFaction.key, + fleetInfo.fleet.key, + fleetInfo.fleet.data.fuelTank, + player.fuelCargoType.key, + game.data.cargo.statsDefinition, + fuelTokenAccount, + game.data.mints.fuel, + game.data.gameState, + game.key, + programs.cargo, + { + toSector: coordinates.toArray(), + keyIndex: player.keyIndex, + }, + ) diff --git a/src/main/basedbot/lib/sage/ships.ts b/src/main/basedbot/lib/sage/ships.ts new file mode 100644 index 00000000..a98a7a81 --- /dev/null +++ b/src/main/basedbot/lib/sage/ships.ts @@ -0,0 +1,354 @@ +import { PublicKey } from '@solana/web3.js' + +/* eslint-disable @typescript-eslint/naming-convention */ +export enum VZUS { + Opod = 'VZUSOP', + Ambwe = 'VZUSAM', + Ballad = 'VZUSBA', + Solos = 'VZUSSO', +} + +export enum Pearce { + X5 = 'PX5', + F4 = 'PF4', + R8 = 'PR8', + C11 = 'PC11', + X6 = 'PX6', + X4 = 'PX4', + C9 = 'PC9', + R6 = 'PR6', + D9 = 'PD9', + T1 = 'T1TAN', +} + +export enum Ogrika { + Thripid = 'OGKATP', + JodAsteris = 'OGKAJA', + Mik = 'OGKAMK', + Tursik = 'OGKATU', + Sunpaa = 'OGKASP', + Niruch = 'OGKANR', + Ruch = 'OGKARU', +} + +export enum Opal { + JetJet = 'OPALJJ', + Jet = 'OPALJ', + Bitboat = 'OPALBB', + RayFam = 'OPALRF', +} + +export enum Fimbul { + Airbike = 'FBLAIR', + Lowbie = 'FBLLOW', + Mamba = 'FBLMAM', + MambaEX = 'FBLMEX', + Sledbarge = 'FBLSLE', +} + +export enum FimbulECOS { + Greenader = 'FBLEGR', + TreeArrow = 'FBLETR', + Bombarella = 'FBLEBO', + Unibomba = 'FBLEUN', + Superphoenix = 'SUPER', +} + +export enum FimbulBYOS { + Earp = 'FBLBEA', + Packlite = 'FBLBPL', + Tankship = 'FBLBTA', + Butch = 'FBLBBU', +} + +export enum Rainbow { + Chi = 'CHI', + Om = 'OM', + Arc = 'ARC', +} + +export enum Calico { + Evac = 'CALEV', + Guardian = 'CALG', + CompaktHero = 'CALCH', + Medtech = 'CALMED', + AtsEnforcer = 'CALATS', + Scud = 'CALSCD', + Maxhog = 'CALMAX', +} + +export enum Tufa { + Feist = 'TUFAFE', +} + +export enum Busan { + ThrillOfLife = 'THRILL', + MaidenHeart = 'HEART', + TheLastStand = 'STAND', +} + +export enum Armstrong { + Tip = 'IMP1', + Tap = 'IMP2', + IMP = 'IMP3', +} + +export enum DevShip { + Test = 'Test', +} + +export type ShipSymbol = + | VZUS + | Pearce + | Ogrika + | Opal + | Fimbul + | FimbulECOS + | FimbulBYOS + | Rainbow + | Calico + | Tufa + | Busan + | Armstrong + | DevShip + +interface ShipData { + name: string + mint: PublicKey +} + +interface ShipMap { + [key: string]: ShipData +} + +export const ships: ShipMap = { + [DevShip.Test]: { + name: 'A Test Ship', + mint: new PublicKey('testyj8KfG3VhmvyXALbQ9riiEG9UbWZ7xKQmkDwJDA'), + }, + [Pearce.X4]: { + name: 'Pearce X4', + mint: new PublicKey('CWvsu7kmgCDhi1bWBqA9N4NKkhpDV6heTb5T3we54zrt'), + }, + [Opal.JetJet]: { + name: 'Opal Jetjet', + mint: new PublicKey('5o635z1vCTUxQF5qVgsbSVB3EKMThGA1Y6giyrhTpLJj'), + }, + [Rainbow.Om]: { + name: 'Rainbow Om', + mint: new PublicKey('7feVdv745gGxZYKb5JEe84EGCJ6nGyMYtPng2wQRfrdh'), + }, + [Tufa.Feist]: { + name: 'Tufa Feist', + mint: new PublicKey('2K7rLcoTsx2pS5KV1LW6wmRdu7GKxUn9azx6iUXY87fS'), + }, + [FimbulECOS.Greenader]: { + name: 'Fimbul ECOS Greenader', + mint: new PublicKey('2bkoL87tqVzgwGcRjqm5vk3RGjd5Mpay81dx88huU4e5'), + }, + [FimbulECOS.Bombarella]: { + name: 'Fimbul ECOS Bombarella', + mint: new PublicKey('32K2GbxgmMWAvdztdY96NYD7eucpk95rbK2wVgh2vFRo'), + }, + [FimbulBYOS.Tankship]: { + name: 'Fimbul BYOS Tankship', + mint: new PublicKey('7pW9NHhuHCP2rBhwE23B6v5bwrfmvGmgg1LgPSVpTxZv'), + }, + [FimbulBYOS.Butch]: { + name: 'Fimbul BYOS Butch', + mint: new PublicKey('BBUPJXjgwBhHXrsQoLw4oCk2DrVR8sFYjCo7MjNjH2NE'), + }, + [Fimbul.Mamba]: { + name: 'Fimbul Mamba', + mint: new PublicKey('En3uuAwxMFFKH382Xkr24LVKjs8e25NZXUV3FBkjnmuX'), + }, + [Pearce.X5]: { + name: 'Pearce X5', + mint: new PublicKey('7CBrGuHSpRvLgQMwA2Vbe9ra9nsg2K9FUDH3TpMmjFdA'), + }, + [Ogrika.Thripid]: { + name: 'Ogrika Thripid', + mint: new PublicKey('8Tt2ec8HKmwqr1wxKVwizmcvdJQos2B42faDyyinjFti'), + }, + [Ogrika.Sunpaa]: { + name: 'Ogrika Sunpaa', + mint: new PublicKey('8mQpeQjw8zqxFDKV3iHdkec7DMh4uJD52kHESitKXs2f'), + }, + [VZUS.Opod]: { + name: 'VZUS Opod', + mint: new PublicKey('4YRhx9VQR4TvscsAcvUjJZPjiT6vAjAQDrDo1pJR5RVF'), + }, + [FimbulBYOS.Packlite]: { + name: 'Fimbul BYOS Packlite', + mint: new PublicKey('8BA19uTEFGPLHYHaGhJBd3y4rJtZgsA37HFDoPZ9wgzv'), + }, + [Calico.Guardian]: { + name: 'Calico Guardian', + mint: new PublicKey('EyUcVag89Lx7rpJLZctPYwHiSJpk8TM411bqgpeKcS5n'), + }, + [Calico.CompaktHero]: { + name: 'Calico Compakt Hero', + mint: new PublicKey('9jTjPB8rGNEBC5HzM49ZfqN1F8xQLUeuRVNeh3DFGHbh'), + }, + [Opal.Jet]: { + name: 'Opal Jet', + mint: new PublicKey('5tyJwmNjzHusouZxsaMjSkLna9VeeJkg2FRaDXLRFQSk'), + }, + [Pearce.C11]: { + name: 'Pearce C11', + mint: new PublicKey('6whMv9uPMG3mPXcCgDaqFg6TaN4KZM1m3BmjGJXJRvco'), + }, + [Pearce.F4]: { + name: 'Pearce F4', + mint: new PublicKey('HUxaWGNGgZSEVpB2Epgx2zYHgi7KxP8atApkmdnsng2s'), + }, + [VZUS.Ambwe]: { + name: 'VZUS Ambwe', + mint: new PublicKey('Dwf5wFNSuwtKC7xtvjGMpXPbRGqAf6WNmgKxXSNSqtaz'), + }, + [Pearce.X6]: { + name: 'Pearce X6', + mint: new PublicKey('B8XeKhrKTKQTPePk87u6tA4KFd8oHzu1VqvbAuaDvLNv'), + }, + [Pearce.R8]: { + name: 'Pearce R8', + mint: new PublicKey('9RGak4MwF9hH72ma42yuytDZsusEQQY57h9Ud9Zao1wY'), + }, + [Pearce.C9]: { + name: 'Pearce C9', + mint: new PublicKey('G3NxTo8zMGMmDiADrWwpRdKz7Kbo2o5LgjCm4tKt2gxR'), + }, + [Ogrika.JodAsteris]: { + name: 'Ogrika Jod Asteris', + mint: new PublicKey('6dUPLNJ6M1oorUyoH4QvHAdTQp4kViVwCNLUvnS9EHyN'), + }, + [FimbulECOS.TreeArrow]: { + name: 'Fimbul ECOS Treearrow', + mint: new PublicKey('rcm1idAXHDYtcjtm56Q1HgFfJ2fnH9rUzEQg1WC74Ar'), + }, + [Rainbow.Chi]: { + name: 'Rainbow Chi', + mint: new PublicKey('Ag97kh8ZNQp9aEB4ETX9kDsXDnBzrjXQccH42xdRnWkP'), + }, + [FimbulBYOS.Earp]: { + name: 'Fimbul BYOS Earp', + mint: new PublicKey('Da6xRFv8W3JhQzmzYYACbxiADwdr3yDcwrBqo4WgjLBW'), + }, + [Calico.Evac]: { + name: 'Calico Evac', + mint: new PublicKey('yMr4mABoBhzV948WzFuVDZPMdzJe8uL42v3RE5cdFqm'), + }, + [Fimbul.Airbike]: { + name: 'Fimbul Airbike', + mint: new PublicKey('6N7zoDykqkbf4354jhHb6oD7zxqv6BZNtE77MyX99CUQ'), + }, + [Ogrika.Mik]: { + name: 'Ogrika Mik', + mint: new PublicKey('HLAKintjFoH6dSwVDVV2a99tUV2r6CVR7hpKpaWUB6PU'), + }, + [FimbulECOS.Unibomba]: { + name: 'Fimbul ECOS Unibomba', + mint: new PublicKey('5Uu2jCmS9mVBC7RyR9eMVDp79C9cocq6R31yCEYXt9zG'), + }, + [Rainbow.Arc]: { + name: 'Rainbow Arc', + mint: new PublicKey('7BAvPzwjf6Y8hZpaweT4dobBT5nEy95HJJci3P6E3C8Y'), + }, + [Fimbul.Lowbie]: { + name: 'Fimbul Lowbie', + mint: new PublicKey('68A55jmwJaiDULknycr7kRNi11XNyKSgotK6pVzJsSLe'), + }, + [VZUS.Ballad]: { + name: 'VZUS Ballad', + mint: new PublicKey('9TTFMGM32UHvuoKnrFL5ECbG7vcYnwyhbT4gJAy56LNA'), + }, + [Busan.ThrillOfLife]: { + name: 'Busan Thrill of Life', + mint: new PublicKey('ETadnNjotJA759Dn3SYa2ZN9TB6NF6hndF6ciajZVA4Y'), + }, + [Opal.Bitboat]: { + name: 'Opal Bitboat', + mint: new PublicKey('Dni9ZeMpsUY825XvBdTLdjGLKCXuj2z1JYE769yrnu4L'), + }, + [Calico.Medtech]: { + name: 'Calico Medtech', + mint: new PublicKey('EVyhcpWDbjEqUUadpv22WNxUx2z5kHwiYwoTvKiDhsVH'), + }, + [Calico.AtsEnforcer]: { + name: 'Calico ATS Enforcer', + mint: new PublicKey('GAANgsxxkPMQ5rwP8qbXh3HifjFkBYM7MVciAsNBeL2S'), + }, + [Pearce.R6]: { + name: 'Pearce R6', + mint: new PublicKey('427BaYEFGERXin2fxuRFGyUDzMoZYA9KTr9w7jZqRKqk'), + }, + [Busan.MaidenHeart]: { + name: 'Busan Maiden Heart', + mint: new PublicKey('BA4SZjCZoPzPJb9oa7W8eFJbYDpYtD54ZAKPxE3GTWXM'), + }, + [Ogrika.Tursik]: { + name: 'Ogrika Tursic', + mint: new PublicKey('EQYL2jczThWKRnydEExa4aYCKkXG7gAq3g8d7x85TQCR'), + }, + [Pearce.D9]: { + name: 'Pearce D9', + mint: new PublicKey('6sPspaPk1UJDRPAASWhnwc8sghToVXBZAXskmHZG8M5v'), + }, + [Pearce.T1]: { + name: 'Pearce T1', + mint: new PublicKey('HQayhtSnytPSTpb3LCJrxkECTDbxJZH1LKwPk9jRKRLf'), + }, + [Busan.TheLastStand]: { + name: 'Busan The Last Stand mk. VIII', + mint: new PublicKey('9tGU2Mvtvvr2n7Fjmw3zbsdr5YrfGbLtPxR31bi5hTA4'), + }, + [FimbulECOS.Superphoenix]: { + name: 'Fimbul ECOS Superphoenix', + mint: new PublicKey('BXtMBhvfeskw2YQAa4uB5WTBWZBhFn8imwQ24HHzjrPG'), + }, + [Ogrika.Niruch]: { + name: 'Ogrika Niruch', + mint: new PublicKey('A94GocS3UK23zKVuwRqgcuyEbpuaNuWB5dK8fsiw6VJG'), + }, + [Ogrika.Ruch]: { + name: 'Ogrika Ruch', + mint: new PublicKey('ruCHVupnE23CQzJ47STdKqLU2jVdFQ9FHEXj7Zvw8hJ'), + }, + [Calico.Scud]: { + name: 'Calico Scud', + mint: new PublicKey('29uLEnHrLnuAJgrDsM66x4uhzwSi6w7bTsYjTGSf1KRk'), + }, + [Calico.Maxhog]: { + name: 'Calico Maxhog', + mint: new PublicKey('6NeyGNP8tKX6V629kQNCa7Xxu8VKQRbGkMfy5477eKRs'), + }, + [VZUS.Solos]: { + name: 'VZUS Solos', + mint: new PublicKey('DXRALgzwdJbKHatQLEyK59tq2sBTuvHPxmKamcrDnKSx'), + }, + [Opal.RayFam]: { + name: 'Opal Rayfam', + mint: new PublicKey('6xS4TduL4hBsPS2ko21ynFqHf3BSAG5yq8MmV8sQoojU'), + }, + [Fimbul.MambaEX]: { + name: 'Fimbul Mamba EX', + mint: new PublicKey('ErMXUtK3jiNMkxBh3Wd1pxXpNREdjp5w8LyofBp5PAvx'), + }, + [Fimbul.Sledbarge]: { + name: 'Fimbul Sledbarge', + mint: new PublicKey('SLEDCrZ1tA6H21ez1uhxmK1jVYCyzCvDAUEbJxGm9qe'), + }, + [Armstrong.Tip]: { + name: 'Armstrong IMP Tip', + mint: new PublicKey('1MP1J8CN5d1roJjsktiUbcTmHAeDYakfQ7eSRDLYmbG'), + }, + [Armstrong.Tap]: { + name: 'Armstrong IMP Tap', + mint: new PublicKey('1MP2S5WNNFzf1MRLacPdAf8TVoebNtuuEm15xWmnpa7'), + }, + [Armstrong.IMP]: { + name: 'Armstrong IMP', + mint: new PublicKey('1MP3RpP21TVoEWVnvNuG1piq32AnWtsxzd2fykS8yJe'), + }, +} +/* eslint-enable @typescript-eslint/naming-convention */ diff --git a/src/main/basedbot/lib/sage/state/cargo-stats-definition.ts b/src/main/basedbot/lib/sage/state/cargo-stats-definition.ts new file mode 100644 index 00000000..d70c426d --- /dev/null +++ b/src/main/basedbot/lib/sage/state/cargo-stats-definition.ts @@ -0,0 +1,19 @@ +import { CargoStatsDefinition } from '@staratlas/cargo' +import { readAllFromRPC } from '@staratlas/data-source' + +import { connection } from '../../../../../service/sol' +import { programs } from '../../programs' + +export const getCargoStatsDefinition = async (): Promise< + Array +> => { + const cargoTypesAccountData = await readAllFromRPC( + connection, + programs.cargo, + CargoStatsDefinition, + ) + + return cargoTypesAccountData + .filter((f) => f.type === 'ok' && 'data' in f) + .map((f) => (f as any).data) +} diff --git a/src/main/basedbot/lib/sage/state/cargo-types.ts b/src/main/basedbot/lib/sage/state/cargo-types.ts new file mode 100644 index 00000000..9c2d8722 --- /dev/null +++ b/src/main/basedbot/lib/sage/state/cargo-types.ts @@ -0,0 +1,37 @@ +import { PublicKey } from '@solana/web3.js' +import { CargoType } from '@staratlas/cargo' +import { readAllFromRPC } from '@staratlas/data-source' +import { Game } from '@staratlas/sage' + +import { connection } from '../../../../../service/sol' +import { programs } from '../../programs' + +export const getCargoTypes = async (): Promise> => { + const cargoTypesAccountData = await readAllFromRPC( + connection, + programs.cargo, + CargoType, + ) + + return cargoTypesAccountData + .filter((f) => f.type === 'ok' && 'data' in f) + .map((f) => (f as any).data) +} + +export const getCargoType = ( + cargoTypes: Array, + game: Game, + mint: PublicKey, +): CargoType => { + const cargoType = cargoTypes.find( + (ct) => + game.data.cargo.statsDefinition.equals(ct.data.statsDefinition) && + mint.equals(ct.data.mint), + ) + + if (!cargoType) { + throw new Error(`Cargo type not found for mint ${mint}.`) + } + + return cargoType +} diff --git a/src/main/basedbot/lib/sage/state/fleet-cargo.ts b/src/main/basedbot/lib/sage/state/fleet-cargo.ts new file mode 100644 index 00000000..bb95632d --- /dev/null +++ b/src/main/basedbot/lib/sage/state/fleet-cargo.ts @@ -0,0 +1,136 @@ +import { + getAssociatedTokenAddressSync, + TOKEN_PROGRAM_ID, +} from '@solana/spl-token' +import { PublicKey } from '@solana/web3.js' +import { + createAssociatedTokenAccountIdempotent, + ixReturnsToIxs, +} from '@staratlas/data-source' +import { Fleet } from '@staratlas/sage' +import BN from 'bn.js' +import bs58 from 'bs58' + +import { logger } from '../../../../../logger' +import { connection } from '../../../../../service/sol' +import { sendAndConfirmInstructions } from '../../../../../service/sol/send-and-confirm-tx' + +import { sageGame } from './game' +import { Player } from './user-account' + +type PartialTokenAccount = { + amount: number | null + tokenAccount: string + mint: string + owner: string + delegate: string | null + delegatedAmount: number +} + +const getTokenAccountForKey = async ( + key: PublicKey, +): Promise => { + const tokenAccounts = await connection.getTokenAccountsByOwner(key, { + programId: TOKEN_PROGRAM_ID, + }) + + const result: PartialTokenAccount[] = [] + + for (const tokenAccount of tokenAccounts.value) { + const accountKey = tokenAccount.pubkey.toBase58() + const accountData = tokenAccount.account.data + + result.push({ + amount: new BN(accountData.slice(64, 72), 'le').toNumber(), + mint: bs58.encode(accountData.slice(0, 32)), + owner: bs58.encode(accountData.slice(32, 64)), + tokenAccount: accountKey, + delegate: bs58.encode(accountData.slice(76, 108)), + delegatedAmount: new BN( + accountData.slice(121, 129), + 'le', + ).toNumber(), + }) + } + + return result +} + +const getBalance = async ( + mint: PublicKey, + bank: PublicKey, + player: Player, +): Promise => { + const tokenAccount = getAssociatedTokenAddressSync(mint, bank, true) + + try { + const balance = await connection.getTokenAccountBalance(tokenAccount) + + return balance.value.uiAmount ?? 0 + } catch (e) { + if ((e as Error).message.includes('could not find account')) { + logger.warn( + `No balance found for ${mint.toBase58()} at ${bank.toBase58()} creating new account`, + ) + const fleetFuelTokenResult = createAssociatedTokenAccountIdempotent( + mint, + bank, + true, + ) + + await sendAndConfirmInstructions( + await ixReturnsToIxs( + fleetFuelTokenResult.instructions, + player.signer, + ), + ) + + return 0 + } + } + + return 0 +} + +const getFleetCargoBalances = async ( + fleet: Fleet, +): Promise> => { + const cargoHoldBalances = await getTokenAccountForKey( + new PublicKey(fleet.data.cargoHold), + ) + + return new Map( + cargoHoldBalances.map((tokenAccount) => [ + tokenAccount.mint, + tokenAccount.delegatedAmount, + ]), + ) +} + +export type FleetCargo = { + ammo: number + cargo: Map + food: number + fuel: number + toolkit: number +} + +export const getFleetCargoBalance = async ( + fleet: Fleet, + player: Player, +): Promise => { + const game = await sageGame() + const [ammo, fuel, cargo] = await Promise.all([ + getBalance(game.data.mints.ammo, fleet.data.ammoBank, player), + getBalance(game.data.mints.fuel, fleet.data.fuelTank, player), + getFleetCargoBalances(fleet), + ]) + + return { + ammo, + cargo, + food: cargo.get(game.data.mints.food.toBase58()) ?? 0, + fuel, + toolkit: cargo.get(game.data.mints.repairKit.toBase58()) ?? 0, + } +} diff --git a/src/main/basedbot/lib/sage/state/game.ts b/src/main/basedbot/lib/sage/state/game.ts new file mode 100644 index 00000000..117180e2 --- /dev/null +++ b/src/main/basedbot/lib/sage/state/game.ts @@ -0,0 +1,21 @@ +import { readAllFromRPC } from '@staratlas/data-source' +import { Game } from '@staratlas/sage' + +import { connection } from '../../../../../service/sol' +import { programs } from '../../programs' + +export const sageGame = async (): Promise => { + const [game] = await readAllFromRPC( + connection, + programs.sage, + Game, + 'processed', + [], + ) + + if (game.type === 'error') { + throw new Error('Error reading game account') + } + + return game.data +} diff --git a/src/main/basedbot/lib/sage/state/get-fleet-ships.ts b/src/main/basedbot/lib/sage/state/get-fleet-ships.ts new file mode 100644 index 00000000..3ab865cb --- /dev/null +++ b/src/main/basedbot/lib/sage/state/get-fleet-ships.ts @@ -0,0 +1,28 @@ +import { readAllFromRPC } from '@staratlas/data-source' +import { Fleet, FleetShips } from '@staratlas/sage' + +import { connection } from '../../../../../service/sol' +import { programs } from '../../programs' + +export const getFleetShips = async ( + fleet: Fleet, +): Promise> => { + const resources = await readAllFromRPC( + connection, + programs.sage, + FleetShips, + 'processed', + [ + { + memcmp: { + offset: 8 + 1, + bytes: fleet.key.toBase58(), + }, + }, + ], + ) + + return resources + .filter((p) => p.type === 'ok' && 'data' in p) + .map((p) => (p as any).data) +} diff --git a/src/main/basedbot/lib/sage/state/mine-items.ts b/src/main/basedbot/lib/sage/state/mine-items.ts new file mode 100644 index 00000000..0f8de96d --- /dev/null +++ b/src/main/basedbot/lib/sage/state/mine-items.ts @@ -0,0 +1,26 @@ +import { readAllFromRPC } from '@staratlas/data-source' +import { Game, MineItem } from '@staratlas/sage' + +import { connection } from '../../../../../service/sol' +import { programs } from '../../programs' + +export const getMineItems = async (game: Game): Promise> => { + const mineItems = await readAllFromRPC( + connection, + programs.sage, + MineItem, + 'processed', + [ + { + memcmp: { + offset: 8 + 1, + bytes: game.key.toBase58(), + }, + }, + ], + ) + + return mineItems + .filter((p) => p.type === 'ok' && 'data' in p) + .map((p) => (p as any).data) +} diff --git a/src/main/basedbot/lib/sage/state/planet-by-key.ts b/src/main/basedbot/lib/sage/state/planet-by-key.ts new file mode 100644 index 00000000..59caadad --- /dev/null +++ b/src/main/basedbot/lib/sage/state/planet-by-key.ts @@ -0,0 +1,26 @@ +import { PublicKey } from '@solana/web3.js' +import { readFromRPC } from '@staratlas/data-source' +import { Planet } from '@staratlas/sage' + +import { connection } from '../../../../../service/sol' +import { programs } from '../../programs' + +export const planetByKey = async (key: PublicKey): Promise => { + const planet = await readFromRPC( + connection, + programs.sage, + key, + Planet, + 'processed', + ) + + if (!planet) { + throw new Error('no planet found') + } + + if (planet.type === 'error') { + throw new Error('Error reading planet account') + } + + return planet.data +} diff --git a/src/main/basedbot/lib/sage/state/planets-by-coordinates.ts b/src/main/basedbot/lib/sage/state/planets-by-coordinates.ts new file mode 100644 index 00000000..578d5c7b --- /dev/null +++ b/src/main/basedbot/lib/sage/state/planets-by-coordinates.ts @@ -0,0 +1,35 @@ +import { readAllFromRPC } from '@staratlas/data-source' +import { Planet } from '@staratlas/sage' + +import { connection } from '../../../../../service/sol' +import { programs } from '../../programs' +import { Coordinates } from '../../util/coordinates' + +export const planetsByCoordinates = async ( + coordinates: Coordinates, +): Promise> => { + const planets = await readAllFromRPC( + connection, + programs.sage, + Planet, + 'processed', + [ + { + memcmp: { + offset: 105, + bytes: coordinates.xB58, + }, + }, + { + memcmp: { + offset: 113, + bytes: coordinates.yB58, + }, + }, + ], + ) + + return planets + .filter((p) => p.type === 'ok' && 'data' in p) + .map((p) => (p as any).data) +} diff --git a/src/main/basedbot/lib/sage/state/planets.ts b/src/main/basedbot/lib/sage/state/planets.ts new file mode 100644 index 00000000..301597cf --- /dev/null +++ b/src/main/basedbot/lib/sage/state/planets.ts @@ -0,0 +1,26 @@ +import { readAllFromRPC } from '@staratlas/data-source' +import { Game, Planet } from '@staratlas/sage' + +import { connection } from '../../../../../service/sol' +import { programs } from '../../programs' + +export const getPlanets = async (game: Game): Promise> => { + const planets = await readAllFromRPC( + connection, + programs.sage, + Planet, + 'processed', + [ + { + memcmp: { + offset: 8 + 1 + 64, + bytes: game.key.toBase58(), + }, + }, + ], + ) + + return planets + .filter((p) => p.type === 'ok' && 'data' in p) + .map((p) => (p as any).data) +} diff --git a/src/main/basedbot/lib/sage/state/resources.ts b/src/main/basedbot/lib/sage/state/resources.ts new file mode 100644 index 00000000..0ea8be43 --- /dev/null +++ b/src/main/basedbot/lib/sage/state/resources.ts @@ -0,0 +1,26 @@ +import { readAllFromRPC } from '@staratlas/data-source' +import { Game, Resource } from '@staratlas/sage' + +import { connection } from '../../../../../service/sol' +import { programs } from '../../programs' + +export const getResources = async (game: Game): Promise> => { + const resources = await readAllFromRPC( + connection, + programs.sage, + Resource, + 'processed', + [ + { + memcmp: { + offset: 8 + 1, + bytes: game.key.toBase58(), + }, + }, + ], + ) + + return resources + .filter((p) => p.type === 'ok' && 'data' in p) + .map((p) => (p as any).data) +} diff --git a/src/main/basedbot/lib/sage/state/settle-fleet.ts b/src/main/basedbot/lib/sage/state/settle-fleet.ts new file mode 100644 index 00000000..c94a5cd4 --- /dev/null +++ b/src/main/basedbot/lib/sage/state/settle-fleet.ts @@ -0,0 +1,46 @@ +import { Game } from '@staratlas/sage' + +import { now } from '../../../../../dayjs' +import { logger } from '../../../../../logger' +import { endMove } from '../act/end-move' +import { exitRespawn } from '../act/exit-respawn' + +import { Player } from './user-account' +import { FleetInfo } from './user-fleets' + +export const settleFleet = async ( + fleetInfo: FleetInfo, + player: Player, + game: Game, +): Promise => { + switch (fleetInfo.fleetState.type) { + case 'MoveWarp': { + const { warpFinish } = fleetInfo.fleetState.data + + if (warpFinish.isBefore(now())) { + await endMove(fleetInfo, player, game) + } + break + } + case 'MoveSubwarp': { + const { arrivalTime } = fleetInfo.fleetState.data + + if (arrivalTime.isBefore(now())) { + await endMove(fleetInfo, player, game) + } + break + } + case 'Respawn': { + const { ETA } = fleetInfo.fleetState.data + + if (ETA.isBefore(now())) { + await exitRespawn(fleetInfo, player.homeStarbase, player, game) + } + break + } + default: + logger.debug( + `${fleetInfo.fleetName} is ${fleetInfo.fleetState.type}`, + ) + } +} diff --git a/src/main/basedbot/lib/sage/state/show-fleet-cargo-info.ts b/src/main/basedbot/lib/sage/state/show-fleet-cargo-info.ts new file mode 100644 index 00000000..3a14cb06 --- /dev/null +++ b/src/main/basedbot/lib/sage/state/show-fleet-cargo-info.ts @@ -0,0 +1,16 @@ +import { logger } from '../../../../../logger' + +import { FleetInfo } from './user-fleets' + +export const showFleetCargoInfo = (fleetInfo: FleetInfo): void => { + const { ammo, cargo, food, fuel, toolkit } = fleetInfo.cargoLevels + + logger.info(`${fleetInfo.fleetName} cargo levels:`) + logger.info(`Ammo: ${ammo}`) + logger.info(`Food: ${food}`) + logger.info(`Fuel: ${fuel}`) + logger.info(`Toolkit: ${toolkit}`) + for (const [mint, amount] of cargo.entries()) { + logger.info(`${mint}: ${amount}`) + } +} diff --git a/src/main/basedbot/lib/sage/state/show-fleet-info.ts b/src/main/basedbot/lib/sage/state/show-fleet-info.ts new file mode 100644 index 00000000..4ab1ca94 --- /dev/null +++ b/src/main/basedbot/lib/sage/state/show-fleet-info.ts @@ -0,0 +1,77 @@ +import dayjs, { now } from '../../../../../dayjs' +import { logger } from '../../../../../logger' +import { getName } from '../util' + +import { planetsByCoordinates } from './planets-by-coordinates' +import { starbaseByCoordinates } from './starbase-by-coordinates' +import { FleetInfo } from './user-fleets' + +export const showFleetInfo = async (fleetInfo: FleetInfo): Promise => { + switch (fleetInfo.fleetState.type) { + case 'Idle': { + const baseStation = await starbaseByCoordinates(fleetInfo.location) + const planets = await planetsByCoordinates(fleetInfo.location) + + logger.info( + `${fleetInfo.fleetName} is idle at ${fleetInfo.fleetState.data.sector} [BaseStation: ${baseStation ? getName(baseStation) : 'N/A'} / Planets: ${planets.length}]`, + ) + break + } + case 'StarbaseLoadingBay': + logger.info( + `${fleetInfo.fleetName} is in the loading bay at ${getName(fleetInfo.fleetState.data.starbase)}`, + ) + break + case 'MoveWarp': { + const { fromSector, toSector, warpFinish } = + fleetInfo.fleetState.data + + if (warpFinish.isBefore(now())) { + logger.info( + `${fleetInfo.fleetName} has arrived at ${fleetInfo.fleetState.data.toSector}`, + ) + } else { + logger.info( + `${fleetInfo.fleetName} warping from ${fromSector} to ${toSector}. Arrival in ${dayjs.duration(warpFinish.diff(now())).humanize(false)}. Current Position: ${fleetInfo.location}`, + ) + } + break + } + case 'MoveSubwarp': { + const { fromSector, toSector, arrivalTime } = + fleetInfo.fleetState.data + + if (arrivalTime.isBefore(now())) { + logger.info( + `${fleetInfo.fleetName} has arrived at ${fleetInfo.fleetState.data.toSector}`, + ) + } else { + logger.info( + `${fleetInfo.fleetName} subwarping from ${fromSector} to ${toSector}. Arrival in ${dayjs.duration(arrivalTime.diff(now())).humanize(false)}. Current Position: ${fleetInfo.location}`, + ) + } + break + } + case 'MineAsteroid': { + const { mineItem, end, amountMined, endReason } = + fleetInfo.fleetState.data + + if (end.isBefore(now())) { + logger.info( + `${fleetInfo.fleetName} has finished mining ${getName(mineItem)} for ${amountMined}`, + ) + } else { + const log = endReason === 'FULL' ? logger.info : logger.warn + + log( + `${fleetInfo.fleetName} mining ${getName(mineItem)} for ${amountMined}. Time remaining: ${dayjs.duration(end.diff(now())).humanize(false)} until ${endReason}`, + ) + } + break + } + default: + logger.info( + `${fleetInfo.fleetName} is ${fleetInfo.fleetState.type}`, + ) + } +} diff --git a/src/main/basedbot/lib/sage/state/starbase-by-coordinates.ts b/src/main/basedbot/lib/sage/state/starbase-by-coordinates.ts new file mode 100644 index 00000000..30f594c8 --- /dev/null +++ b/src/main/basedbot/lib/sage/state/starbase-by-coordinates.ts @@ -0,0 +1,41 @@ +import { readAllFromRPC } from '@staratlas/data-source' +import { Starbase } from '@staratlas/sage' + +import { connection } from '../../../../../service/sol' +import { programs } from '../../programs' +import { Coordinates } from '../../util/coordinates' + +export const starbaseByCoordinates = async ( + coordinates: Coordinates, +): Promise => { + const [starbase] = await readAllFromRPC( + connection, + programs.sage, + Starbase, + 'processed', + [ + { + memcmp: { + offset: 41, + bytes: coordinates.xB58, + }, + }, + { + memcmp: { + offset: 49, + bytes: coordinates.yB58, + }, + }, + ], + ) + + if (!starbase) { + return null + } + + if (starbase.type === 'error') { + throw new Error('Error reading starbase account') + } + + return starbase.data +} diff --git a/src/main/basedbot/lib/sage/state/starbase-by-key.ts b/src/main/basedbot/lib/sage/state/starbase-by-key.ts new file mode 100644 index 00000000..66ad0af1 --- /dev/null +++ b/src/main/basedbot/lib/sage/state/starbase-by-key.ts @@ -0,0 +1,26 @@ +import { PublicKey } from '@solana/web3.js' +import { readFromRPC } from '@staratlas/data-source' +import { Starbase } from '@staratlas/sage' + +import { connection } from '../../../../../service/sol' +import { programs } from '../../programs' + +export const starbaseByKey = async (key: PublicKey): Promise => { + const starbase = await readFromRPC( + connection, + programs.sage, + key, + Starbase, + 'processed', + ) + + if (!starbase) { + throw new Error('no starbase found') + } + + if (starbase.type === 'error') { + throw new Error('Error reading starbase account') + } + + return starbase.data +} diff --git a/src/main/basedbot/lib/sage/state/starbase-player.ts b/src/main/basedbot/lib/sage/state/starbase-player.ts new file mode 100644 index 00000000..1855a01b --- /dev/null +++ b/src/main/basedbot/lib/sage/state/starbase-player.ts @@ -0,0 +1,174 @@ +import { Keypair, PublicKey } from '@solana/web3.js' +import { CargoPod } from '@staratlas/cargo' +import { ixReturnsToIxs, readAllFromRPC } from '@staratlas/data-source' +import { + Game, + SagePlayerProfile, + Ship, + Starbase, + StarbasePlayer, +} from '@staratlas/sage' + +import { logger } from '../../../../../logger' +import { connection } from '../../../../../service/sol' +import { sendAndConfirmInstructions } from '../../../../../service/sol/send-and-confirm-tx' +import { StarAtlasPrograms } from '../../programs' + +import { sageGame } from './game' +import { Player } from './user-account' + +export const getCargoPodsForStarbasePlayer = async ( + starbasePlayer: StarbasePlayer, + programs: StarAtlasPrograms, +): Promise => { + const [cargoPod] = await readAllFromRPC( + connection, + programs.cargo, + CargoPod, + 'processed', + [ + { + memcmp: { + offset: 8 + 1 + 32, + bytes: starbasePlayer.key.toBase58(), + }, + }, + ], + ) + + if (!cargoPod) { + throw new Error('Error reading cargo pods') + } + + if (cargoPod.type === 'error') { + throw new Error('Error reading cargoPods account') + } + + return cargoPod.data +} + +export const getShipByMint = async ( + mint: PublicKey, + game: Game, + programs: StarAtlasPrograms, +): Promise => { + const [ship] = await readAllFromRPC( + connection, + programs.sage, + Ship, + 'processed', + [ + { + memcmp: { + offset: 8 + 1, + bytes: game.key.toString(), + }, + }, + { + memcmp: { + offset: 8 + 1 + 32, + bytes: mint.toString(), + }, + }, + ], + ) + + if (!ship) { + throw new Error('Error reading ship') + } + + if (ship.type === 'error') { + throw new Error('Error reading ship account') + } + + return ship.data +} + +export const getStarbasePlayer = async ( + player: Player, + starbase: Starbase, + programs: StarAtlasPrograms, +): Promise => { + const [starbasePlayer] = await readAllFromRPC( + connection, + programs.sage, + StarbasePlayer, + 'processed', + [ + { + memcmp: { + offset: 9, + bytes: player.profile.key.toBase58(), + }, + }, + { + memcmp: { + offset: 73, + bytes: starbase.key.toBase58(), + }, + }, + ], + ) + + const game = await sageGame() + + if (!starbasePlayer) { + const [sageProfileAddress] = SagePlayerProfile.findAddress( + programs.sage, + player.profile.key, + game.key, + ) + const [starbasePlayerAddress] = StarbasePlayer.findAddress( + programs.sage, + starbase.key, + sageProfileAddress, + starbase.data.seqId, + ) + + const instructionReturns = [ + StarbasePlayer.registerStarbasePlayer( + programs.sage, + player.profileFaction.key, + sageProfileAddress, + starbase.key, + game.key, + game.data.gameState, + starbase.data.seqId, + ), + StarbasePlayer.createCargoPod( + programs.sage, + programs.cargo, + starbasePlayerAddress, + player.signer, + player.profile.key, + player.profileFaction.key, + starbase.key, + game.data.cargo.statsDefinition, + game.key, + game.data.gameState, + { + keyIndex: 0, + podSeeds: Array.from( + Keypair.generate().publicKey.toBuffer(), + ), + }, + ), + ] + + logger.warn('Starbase player not found, creating', { + player: player.profile.key.toBase58(), + starbase: starbase.key.toBase58(), + }) + await sendAndConfirmInstructions( + await ixReturnsToIxs(instructionReturns, player.signer), + ) + + return getStarbasePlayer(player, starbase, programs) + } + + if (starbasePlayer.type === 'error') { + throw new Error('Error reading starbasePlayer account') + } + + return starbasePlayer.data +} diff --git a/src/main/basedbot/lib/sage/state/starbases.ts b/src/main/basedbot/lib/sage/state/starbases.ts new file mode 100644 index 00000000..a516bf69 --- /dev/null +++ b/src/main/basedbot/lib/sage/state/starbases.ts @@ -0,0 +1,26 @@ +import { readAllFromRPC } from '@staratlas/data-source' +import { Game, Starbase } from '@staratlas/sage' + +import { connection } from '../../../../../service/sol' +import { programs } from '../../programs' + +export const getStarbases = async (game: Game): Promise> => { + const starbases = await readAllFromRPC( + connection, + programs.sage, + Starbase, + 'processed', + [ + { + memcmp: { + offset: 8 + 1, + bytes: game.key.toBase58(), + }, + }, + ], + ) + + return starbases + .filter((p) => p.type === 'ok' && 'data' in p) + .map((p) => (p as any).data) +} diff --git a/src/main/basedbot/lib/sage/state/user-account.ts b/src/main/basedbot/lib/sage/state/user-account.ts new file mode 100644 index 00000000..b3b24283 --- /dev/null +++ b/src/main/basedbot/lib/sage/state/user-account.ts @@ -0,0 +1,178 @@ +import { Keypair, PublicKey } from '@solana/web3.js' +import { CargoType } from '@staratlas/cargo' +import { + AsyncSigner, + keypairToAsyncSigner, + readAllFromRPC, +} from '@staratlas/data-source' +import { PlayerProfile } from '@staratlas/player-profile' +import { UserPoints } from '@staratlas/points' +import { ProfileFactionAccount } from '@staratlas/profile-faction' +import { SagePointsCategory, Starbase } from '@staratlas/sage' + +import { connection } from '../../../../../service/sol' +import { programs } from '../../programs' +import { Coordinates } from '../../util/coordinates' +import { Faction, galaxySectorsData } from '../../util/galaxy-sectors-data' + +import { getCargoType, getCargoTypes } from './cargo-types' +import { sageGame } from './game' +import { starbaseByCoordinates } from './starbase-by-coordinates' + +export type XpAccounts = { + councilRank: XpAccount + dataRunning: XpAccount + piloting: XpAccount + mining: XpAccount + crafting: XpAccount +} + +export type XpAccount = { + userPointsAccount: PublicKey + pointsCategory: PublicKey + pointsModifierAccount: PublicKey +} + +export type Player = { + publicKey: PublicKey + keyIndex: number + profile: PlayerProfile + profileFaction: ProfileFactionAccount + faction: Faction + xpAccounts: XpAccounts + signer: AsyncSigner + homeStarbase: Starbase + homeCoordinates: Coordinates + cargoTypes: Array + fuelCargoType: CargoType + foodCargoType: CargoType + ammoCargoType: CargoType +} + +const getXpAccount = ( + playerProfile: PublicKey, + pointsCategory: SagePointsCategory, +): XpAccount => { + const pointsCategoryKey = pointsCategory.category + const pointsModifierAccount = pointsCategory.modifier + const [userPointsAccount] = UserPoints.findAddress( + programs.points, + pointsCategoryKey, + playerProfile, + ) + + return { + userPointsAccount, + pointsModifierAccount, + pointsCategory: pointsCategoryKey, + } +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const getKeyIndex = (_: PlayerProfile): number => 0 + +export const getPlayerContext = async ( + user: PublicKey, + signer: Keypair, +): Promise => { + const myProfiles = await readAllFromRPC( + connection, + programs.playerProfile, + PlayerProfile, + 'processed', + [ + { + memcmp: { + offset: PlayerProfile.MIN_DATA_SIZE + 2, + bytes: user.toBase58(), + }, + }, + ], + ) + + // TODO: only support one profile for now + + const [profile] = myProfiles + + if (!profile) { + throw new Error('no player profile found') + } + + if (profile.type === 'error') { + throw new Error('Error reading account') + } + + const keyIndex = getKeyIndex(profile.data) + + const [profileFaction] = await readAllFromRPC( + connection, + programs.profileFaction as any, + ProfileFactionAccount, + 'processed', + [ + { + memcmp: { + offset: 9, + bytes: profile.key.toBase58(), + }, + }, + ], + ) + + if (profileFaction.type === 'error') { + throw new Error('Error reading faction account') + } + const game = await sageGame() + + const xpAccounts = { + councilRank: getXpAccount( + profile.key, + game.data.points.councilRankXpCategory, + ), + dataRunning: getXpAccount( + profile.key, + game.data.points.dataRunningXpCategory, + ), + piloting: getXpAccount(profile.key, game.data.points.pilotXpCategory), + mining: getXpAccount(profile.key, game.data.points.miningXpCategory), + crafting: getXpAccount( + profile.key, + game.data.points.craftingXpCategory, + ), + } + + const cargoTypes = await getCargoTypes() + + const homeCoordinates = galaxySectorsData() + .filter( + (sector) => + sector.closestFaction === profileFaction.data.data.faction, + ) + .find((sector) => sector.name.includes('CSS'))?.coordinates + + if (!homeCoordinates) { + throw new Error('No home coordinates found') + } + + const homeStarbase = await starbaseByCoordinates(homeCoordinates) + + if (!homeStarbase) { + throw new Error('No home starbase found') + } + + return { + publicKey: user, + profile: profile.data, + profileFaction: profileFaction.data, + faction: profileFaction.data.data.faction, + keyIndex, + xpAccounts, + signer: keypairToAsyncSigner(signer), + homeCoordinates, + cargoTypes, + homeStarbase, + fuelCargoType: getCargoType(cargoTypes, game, game.data.mints.fuel), + foodCargoType: getCargoType(cargoTypes, game, game.data.mints.food), + ammoCargoType: getCargoType(cargoTypes, game, game.data.mints.ammo), + } +} diff --git a/src/main/basedbot/lib/sage/state/user-fleets.ts b/src/main/basedbot/lib/sage/state/user-fleets.ts new file mode 100644 index 00000000..eee6be31 --- /dev/null +++ b/src/main/basedbot/lib/sage/state/user-fleets.ts @@ -0,0 +1,119 @@ +import { readAllFromRPC } from '@staratlas/data-source' +import { Fleet } from '@staratlas/sage' +import BN from 'bn.js' + +import { connection } from '../../../../../service/sol' +import { getFleetState } from '../../fleet-state/fleet-state' +import { FleetState } from '../../fleet-state/types' +import { programs } from '../../programs' +import { Coordinates } from '../../util/coordinates' +import { getName } from '../util' + +import { FleetCargo, getFleetCargoBalance } from './fleet-cargo' +import { Player } from './user-account' +import { WorldMap } from './world-map' + +type ShipCounts = { + total: number + updated: number + xxs: number + xs: number + s: number + m: number + l: number + capital: number + commander: number + titan: number +} + +type MovementStats = { + subwarpSpeed: number + warpSpeed: number + maxWarpDistance: number + warpCooldown: number + subwarpFuelConsumptionRate: number + warpFuelConsumptionRate: number + planetExitFuelAmount: number +} + +type CargoStats = { + cargoCapacity: number + fuelCapacity: number + ammoCapacity: number + ammoConsumptionRate: number + foodConsumptionRate: number + miningRate: number + upgradeRate: number +} + +type MiscStats = { + crew: number + respawnTime: number + scanCooldown: number + scanRepairKitAmount: number +} + +export type FleetInfo = { + fleet: Fleet + location: Coordinates + fleetName: string + shipCounts: ShipCounts + warpCooldownExpiresAt: BN + scanCooldownExpiresAt: BN + movementStats: MovementStats + cargoStats: CargoStats + miscStats: MiscStats + fleetState: FleetState + cargoLevels: FleetCargo +} + +export const getUserFleets = async (player: Player): Promise> => { + const fleets = await readAllFromRPC( + connection, + programs.sage, + Fleet, + 'processed', + [ + { + memcmp: { + offset: 8 + 1 + 32, // 8 (discriminator) + 1 (version) + 32 (gameId) + bytes: player.profile.key.toBase58(), + }, + }, + ], + ) + + return fleets + .filter((f) => f.type === 'ok' && 'data' in f) + .map((f) => (f as any).data) +} + +export const getFleetInfo = async ( + fleet: Fleet, + player: Player, + map: WorldMap, +): Promise => { + const cargoLevels = await getFleetCargoBalance(fleet, player) + const fleetState = await getFleetState(fleet, map, cargoLevels) + const shipCounts = fleet.data.shipCounts as unknown as ShipCounts + const movementStats = fleet.data.stats + .movementStats as unknown as MovementStats + const cargoStats = fleet.data.stats.cargoStats as unknown as CargoStats + const miscStats = fleet.data.stats.miscStats as unknown as MiscStats + const location = fleetState.data.sector + const fleetName = getName(fleet) + + return { + fleet, + location, + miscStats, + movementStats, + cargoStats, + fleetName, + shipCounts, + fleetState, + warpCooldownExpiresAt: fleet.data.warpCooldownExpiresAt, + scanCooldownExpiresAt: fleet.data.scanCooldownExpiresAt, + cargoLevels, + } +} diff --git a/src/main/basedbot/lib/sage/state/world-map.ts b/src/main/basedbot/lib/sage/state/world-map.ts new file mode 100644 index 00000000..c966de85 --- /dev/null +++ b/src/main/basedbot/lib/sage/state/world-map.ts @@ -0,0 +1,125 @@ +import { Game, MineItem, Planet, Resource, Starbase } from '@staratlas/sage' + +import { logger } from '../../../../../logger' +import { transformSector } from '../../fleet-state/transform/transform-sector' +import { Coordinates } from '../../util/coordinates' + +import { getMineItems } from './mine-items' +import { getPlanets } from './planets' +import { getResources } from './resources' +import { getStarbases } from './starbases' + +export type PlanetId = string +export type ResourceId = string +export type StarbaseId = string + +export type WorldMap = { + starbases: Array + planets: Map> + mineItems: Map + resources: Map> +} + +export type Mineable = { + starbase: Starbase + planet: Planet + resource: Resource + mineItem: MineItem +} + +export const planetsByStarbase = ( + planets: Map>, + starbase: Starbase, +): Set => planets.get(starbase.key.toBase58()) ?? new Set() + +export const resourcesByPlanet = ( + resources: Map>, + planet: Planet, +): Set => resources.get(planet.key.toBase58()) ?? new Set() + +export const mineItemByResource = ( + mineItems: Map, + resource: Resource, +): MineItem | undefined => mineItems.get(resource.key.toBase58()) + +export const mineableByCoordinates = ( + map: WorldMap, + coordinates: Coordinates, +): Set => { + const starbase = map.starbases.find((s) => + transformSector(s.data.sector).equals(coordinates), + ) + + if (!starbase) { + logger.warn(`No starbase found at ${coordinates}`) + + return new Set() + } + const planets = planetsByStarbase(map.planets, starbase) + const minables = new Set() + + planets.forEach((planet) => { + const resources = resourcesByPlanet(map.resources, planet) + + resources.forEach((resource) => { + const mineItem = mineItemByResource(map.mineItems, resource) + + if (mineItem) { + minables.add({ + starbase, + planet, + resource, + mineItem, + }) + } + }) + }) + + return minables +} + +export const getMapContext = async (game: Game): Promise => { + const [starbases, pl, mI, res] = await Promise.all([ + getStarbases(game), + getPlanets(game), + getMineItems(game), + getResources(game), + ]) + + const planets = new Map>() + const resources = new Map>() + const mineItems = new Map() + + starbases.forEach((s) => { + const location = transformSector(s.data.sector) + const planetSet = planets.get(s.key.toBase58()) ?? new Set() + + pl.filter((p) => + transformSector(p.data.sector).equals(location), + ).forEach((p) => { + planetSet.add(p) + }) + + planets.set(s.key.toBase58(), planetSet) + }) + + res.forEach((r) => { + const resourceSet = + resources.get(r.data.location.toBase58()) ?? new Set() + + mineItems.set( + r.key.toBase58(), + mI.find((m) => m.key.equals(r.data.mineItem))!, + ) + + resourceSet.add(r) + resources.set(r.data.location.toBase58(), resourceSet) + }) + + return { + starbases, + planets, + mineItems, + resources, + } +} diff --git a/src/main/basedbot/lib/sage/util.ts b/src/main/basedbot/lib/sage/util.ts new file mode 100644 index 00000000..7ba34323 --- /dev/null +++ b/src/main/basedbot/lib/sage/util.ts @@ -0,0 +1,16 @@ +import { byteArrayToString } from '@staratlas/data-source' +import { Fleet, MineItem, Planet, Starbase } from '@staratlas/sage' + +export const getName = (item: Fleet | Starbase | Planet | MineItem): string => { + if (!item?.data) { + return 'N/A' + } + + const name = 'name' in item.data ? item.data.name : undefined + const fleetLabel = + 'fleetLabel' in item.data ? item.data.fleetLabel : undefined + + const dataToConvert = name ?? fleetLabel + + return dataToConvert ? byteArrayToString(dataToConvert) : 'N/A' +} diff --git a/src/main/basedbot/lib/util/coordinates.ts b/src/main/basedbot/lib/util/coordinates.ts new file mode 100644 index 00000000..d89dc53f --- /dev/null +++ b/src/main/basedbot/lib/util/coordinates.ts @@ -0,0 +1,72 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable no-underscore-dangle */ + +import BN from 'bn.js' +import bs58 from 'bs58' + +export class Coordinates { + private readonly _x: BN + private readonly _y: BN + + public static fromString = (str: string): Coordinates => { + const [x, y] = str.split(',') + + return new Coordinates(new BN(x, 10), new BN(y, 10)) + } + + public static fromBN = (x: BN, y: BN): Coordinates => new Coordinates(x, y) + + public static fromNumber = (x: number, y: number): Coordinates => + new Coordinates(x, y) + + private constructor(x: BN | number, y: BN | number) { + this._x = typeof x === 'number' ? new BN(x, 10) : x + this._y = typeof y === 'number' ? new BN(y, 10) : y + // logger.debug('Coordinates', { x: this._x.toNumber(), y: this._y.toNumber() }) + } + + private static toB58 = (bn: BN): string => + bs58.encode(bn.toTwos(64).toArrayLike(Buffer, 'le', 8)) + + get xBN(): BN { + return this._x + } + + get yBN(): BN { + return this._y + } + + get xB58(): string { + return Coordinates.toB58(this._x) + } + + get yB58(): string { + return Coordinates.toB58(this._y) + } + + get x(): number { + return this._x.toNumber() + } + + get y(): number { + return this._y.toNumber() + } + + public distanceFrom = (other: Coordinates): number => { + const x = this._x.sub(other._x) + const y = this._y.sub(other._y) + + return Math.sqrt(x.mul(x).add(y.mul(y)).toNumber()) + } + + public equals = (other: Coordinates): boolean => + this._x.eq(other._x) && this._y.eq(other._y) + + public toString = (): string => + `${this._x.toNumber()},${this._y.toNumber()}` + + public toArray = (): [BN, BN] => [this._x, this._y] +} + +/* eslint-enable @typescript-eslint/naming-convention */ +/* eslint-enable no-underscore-dangle */ diff --git a/src/main/basedbot/lib/util/galaxy-sectors-data.ts b/src/main/basedbot/lib/util/galaxy-sectors-data.ts new file mode 100644 index 00000000..354737ef --- /dev/null +++ b/src/main/basedbot/lib/util/galaxy-sectors-data.ts @@ -0,0 +1,273 @@ +import { Coordinates } from './coordinates' + +export enum Faction { + MUD = 1, + ONI = 2, + UST = 3, +} + +export interface SectorInfo { + name: string + closestFaction: Faction + coordinates: Coordinates +} + +export const galaxySectorsData = (): SectorInfo[] => { + return [ + { + name: 'MUD CSS', + closestFaction: Faction.MUD, + coordinates: Coordinates.fromNumber(0, -39), + }, + { + name: 'MUD-2', + closestFaction: Faction.MUD, + coordinates: Coordinates.fromNumber(2, -34), + }, + { + name: 'MUD-3', + closestFaction: Faction.MUD, + coordinates: Coordinates.fromNumber(10, -41), + }, + { + name: 'MUD-4', + closestFaction: Faction.MUD, + coordinates: Coordinates.fromNumber(-2, -44), + }, + { + name: 'MUD-5', + closestFaction: Faction.MUD, + coordinates: Coordinates.fromNumber(-10, -37), + }, + { + name: 'ONI CSS', + closestFaction: Faction.ONI, + coordinates: Coordinates.fromNumber(-40, 30), + }, + { + name: 'ONI-2', + closestFaction: Faction.ONI, + coordinates: Coordinates.fromNumber(-42, 35), + }, + { + name: 'ONI-3', + closestFaction: Faction.ONI, + coordinates: Coordinates.fromNumber(-30, 30), + }, + { + name: 'ONI-4', + closestFaction: Faction.ONI, + coordinates: Coordinates.fromNumber(-38, 25), + }, + { + name: 'ONI-5', + closestFaction: Faction.ONI, + coordinates: Coordinates.fromNumber(-47, 30), + }, + { + name: 'Ustur CSS', + closestFaction: Faction.UST, + coordinates: Coordinates.fromNumber(40, 30), + }, + { + name: 'UST-2', + closestFaction: Faction.UST, + coordinates: Coordinates.fromNumber(42, 35), + }, + { + name: 'UST-3', + closestFaction: Faction.UST, + coordinates: Coordinates.fromNumber(48, 32), + }, + { + name: 'UST-4', + closestFaction: Faction.UST, + coordinates: Coordinates.fromNumber(38, 25), + }, + { + name: 'UST-5', + closestFaction: Faction.UST, + coordinates: Coordinates.fromNumber(30, 28), + }, + { + name: 'MRZ-1', + closestFaction: Faction.MUD, + coordinates: Coordinates.fromNumber(-15, -33), + }, + { + name: 'MRZ-2', + closestFaction: Faction.MUD, + coordinates: Coordinates.fromNumber(12, -31), + }, + { + name: 'MRZ-3', + closestFaction: Faction.MUD, + coordinates: Coordinates.fromNumber(-22, -25), + }, + { + name: 'MRZ-4', + closestFaction: Faction.MUD, + coordinates: Coordinates.fromNumber(-8, -24), + }, + { + name: 'MRZ-5', + closestFaction: Faction.MUD, + coordinates: Coordinates.fromNumber(2, -23), + }, + { + name: 'MRZ-6', + closestFaction: Faction.MUD, + coordinates: Coordinates.fromNumber(11, -16), + }, + { + name: 'MRZ-7', + closestFaction: Faction.MUD, + coordinates: Coordinates.fromNumber(21, -26), + }, + { + name: 'MRZ-8', + closestFaction: Faction.MUD, + coordinates: Coordinates.fromNumber(-30, -16), + }, + { + name: 'MRZ-9', + closestFaction: Faction.MUD, + coordinates: Coordinates.fromNumber(-14, -16), + }, + { + name: 'MRZ-10', + closestFaction: Faction.MUD, + coordinates: Coordinates.fromNumber(23, -12), + }, + { + name: 'MRZ-11', + closestFaction: Faction.MUD, + coordinates: Coordinates.fromNumber(31, -19), + }, + { + name: 'MRZ-12', + closestFaction: Faction.MUD, + coordinates: Coordinates.fromNumber(-16, 0), + }, + { + name: 'MRZ-13', + closestFaction: Faction.ONI, + coordinates: Coordinates.fromNumber(-36, -7), + }, + { + name: 'MRZ-14', + closestFaction: Faction.ONI, + coordinates: Coordinates.fromNumber(-23, 4), + }, + { + name: 'MRZ-15', + closestFaction: Faction.UST, + coordinates: Coordinates.fromNumber(22, 5), + }, + { + name: 'MRZ-16', + closestFaction: Faction.UST, + coordinates: Coordinates.fromNumber(39, -1), + }, + { + name: 'MRZ-17', + closestFaction: Faction.UST, + coordinates: Coordinates.fromNumber(16, -5), + }, + { + name: 'MRZ-18', + closestFaction: Faction.ONI, + coordinates: Coordinates.fromNumber(-40, 3), + }, + { + name: 'MRZ-19', + closestFaction: Faction.ONI, + coordinates: Coordinates.fromNumber(-35, 12), + }, + { + name: 'MRZ-20', + closestFaction: Faction.ONI, + coordinates: Coordinates.fromNumber(-25, 15), + }, + { + name: 'MRZ-21', + closestFaction: Faction.UST, + coordinates: Coordinates.fromNumber(25, 14), + }, + { + name: 'MRZ-22', + closestFaction: Faction.UST, + coordinates: Coordinates.fromNumber(35, 16), + }, + { + name: 'MRZ-23', + closestFaction: Faction.UST, + coordinates: Coordinates.fromNumber(44, 10), + }, + { + name: 'MRZ-24', + closestFaction: Faction.ONI, + coordinates: Coordinates.fromNumber(-45, 15), + }, + { + name: 'MRZ-25', + closestFaction: Faction.ONI, + coordinates: Coordinates.fromNumber(-18, 23), + }, + { + name: 'MRZ-26', + closestFaction: Faction.ONI, + coordinates: Coordinates.fromNumber(-9, 24), + }, + { + name: 'MRZ-27', + closestFaction: Faction.UST, + coordinates: Coordinates.fromNumber(2, 26), + }, + { + name: 'MRZ-28', + closestFaction: Faction.UST, + coordinates: Coordinates.fromNumber(17, 21), + }, + { + name: 'MRZ-29', + closestFaction: Faction.ONI, + coordinates: Coordinates.fromNumber(-22, 32), + }, + { + name: 'MRZ-30', + closestFaction: Faction.ONI, + coordinates: Coordinates.fromNumber(-19, 40), + }, + { + name: 'MRZ-31', + closestFaction: Faction.ONI, + coordinates: Coordinates.fromNumber(-8, 35), + }, + { + name: 'MRZ-32', + closestFaction: Faction.UST, + coordinates: Coordinates.fromNumber(5, 44), + }, + { + name: 'MRZ-33', + closestFaction: Faction.UST, + coordinates: Coordinates.fromNumber(13, 37), + }, + { + name: 'MRZ-34', + closestFaction: Faction.UST, + coordinates: Coordinates.fromNumber(22, 31), + }, + { + name: 'MRZ-35', + closestFaction: Faction.UST, + coordinates: Coordinates.fromNumber(49, 20), + }, + { + name: 'MRZ-36', + closestFaction: Faction.ONI, + coordinates: Coordinates.fromNumber(0, 16), + }, + ] +} diff --git a/src/main/fleetbot/fleetbot.ts b/src/main/fleetbot/fleetbot.ts index ca99058a..ac0193ca 100644 --- a/src/main/fleetbot/fleetbot.ts +++ b/src/main/fleetbot/fleetbot.ts @@ -2,7 +2,12 @@ import { CronJob } from 'cron' import { config } from '../../config' import * as db from '../../db' -import { checkTransactions, refill, stockResources, telegramBot } from '../../lib' +import { + checkTransactions, + refill, + stockResources, + telegramBot, +} from '../../lib' import { logger } from '../../logger' import { initOrderBook } from '../../service/gm' @@ -28,8 +33,7 @@ export const stop = async (): Promise => { refillCronJob.stop() } await db.close() - } - catch (e) { + } catch (e) { logger.error(e) } } @@ -38,18 +42,26 @@ export const start = async (): Promise => { await initOrderBook() // https://github.com/telegraf/telegraf/issues/1749 // eslint-disable-next-line promise/prefer-await-to-then - telegramBot.launch().catch(e => logger.error(e)) + telegramBot.launch().catch((e) => logger.error(e)) if (config.app.quickstart) { await stockResources() await checkTransactions() await refill() } - resourcesCronJob = CronJob.from({ cronTime: config.cron.resourceInterval, onTick: stockResources, start: true }) - refillCronJob = CronJob.from({ cronTime: config.cron.refillInterval, onTick: refill, start: true }) + resourcesCronJob = CronJob.from({ + cronTime: config.cron.resourceInterval, + onTick: stockResources, + start: true, + }) + refillCronJob = CronJob.from({ + cronTime: config.cron.refillInterval, + onTick: refill, + start: true, + }) transactionCronJob = CronJob.from({ cronTime: config.cron.bookkeeperInterval, onTick: checkTransactions, - start: true + start: true, }) } diff --git a/src/main/fleetbot/index.ts b/src/main/fleetbot/index.ts index 29f89488..c756e97a 100644 --- a/src/main/fleetbot/index.ts +++ b/src/main/fleetbot/index.ts @@ -9,8 +9,7 @@ const stop = async (signal?: NodeJS.Signals) => { try { await app.stop() - } - catch (error) { + } catch (error) { Sentry.captureException(error) logger.error('Close failed') logger.error((error as Error).stack) @@ -24,8 +23,7 @@ const start = async () => { try { await app.create() await app.start() - } - catch (error) { + } catch (error) { Sentry.captureException(error) logger.error((error as Error).stack) @@ -35,21 +33,24 @@ const start = async () => { } // eslint-disable-next-line @typescript-eslint/no-unused-vars -process.on('unhandledRejection', async (reason: any | null | undefined, _promise: Promise) => { - logger.error('Unhandled rejection') +process.on( + 'unhandledRejection', + async (reason: any | null | undefined, _promise: Promise) => { + logger.error('Unhandled rejection') - if (reason) { - const { message }: { message: string } = reason + if (reason) { + const { message }: { message: string } = reason - if (message.includes('Event listener')) { - return + if (message.includes('Event listener')) { + return + } + logger.error(message) } - logger.error(message) - } - Sentry.captureException(reason) - await stop() -}) + Sentry.captureException(reason) + await stop() + }, +) process.on('uncaughtException', async (error) => { Sentry.captureException(error) diff --git a/src/sentry.ts b/src/sentry.ts index b7311d68..c285d7f5 100644 --- a/src/sentry.ts +++ b/src/sentry.ts @@ -1,5 +1,8 @@ -import { extraErrorDataIntegration, rewriteFramesIntegration } from '@sentry/node' import * as Sentry from '@sentry/node' +import { + extraErrorDataIntegration, + rewriteFramesIntegration, +} from '@sentry/node' import '@sentry/tracing' Sentry.init({ @@ -8,11 +11,11 @@ Sentry.init({ release: process.env.APP_VERSION, environment: process.env.APP_ENVIRONMENT, normalizeDepth: 10, - integrations (integrations) { + integrations(integrations) { return integrations .concat(extraErrorDataIntegration) .concat(rewriteFramesIntegration({ root: process.cwd() })) - } + }, }) export { Sentry } diff --git a/src/service/fleet/get-remaining-details.ts b/src/service/fleet/get-remaining-details.ts index aacb348c..7cbe1958 100644 --- a/src/service/fleet/get-remaining-details.ts +++ b/src/service/fleet/get-remaining-details.ts @@ -25,45 +25,57 @@ export const getTimePass = (fleet: ShipStakingInfo): number => { return now - tripStart } -export const getRemainFoodSec = (fleet: ShipStakingInfo, tp: number | undefined = undefined): number => { +export const getRemainFoodSec = ( + fleet: ShipStakingInfo, + tp: number | undefined = undefined, +): number => { const timePass = tp === undefined ? getTimePass(fleet) : tp return fleet.foodCurrentCapacity.toNumber() - timePass } -export const getRemainArmsSec = (fleet: ShipStakingInfo, tp: number | undefined = undefined): number => { +export const getRemainArmsSec = ( + fleet: ShipStakingInfo, + tp: number | undefined = undefined, +): number => { const timePass = tp === undefined ? getTimePass(fleet) : tp return fleet.armsCurrentCapacity.toNumber() - timePass } -export const getRemainFuelSec = (fleet: ShipStakingInfo, tp: number | undefined = undefined): number => { +export const getRemainFuelSec = ( + fleet: ShipStakingInfo, + tp: number | undefined = undefined, +): number => { const timePass = tp === undefined ? getTimePass(fleet) : tp return fleet.fuelCurrentCapacity.toNumber() - timePass } -export const getRemainHealthSec = (fleet: ShipStakingInfo, tp: number | undefined = undefined): number => { +export const getRemainHealthSec = ( + fleet: ShipStakingInfo, + tp: number | undefined = undefined, +): number => { const timePass = tp === undefined ? getTimePass(fleet) : tp return fleet.healthCurrentCapacity.toNumber() - timePass } -export const timePassSinceLastAction = (fleet: ShipStakingInfo) : number => { +export const timePassSinceLastAction = (fleet: ShipStakingInfo): number => { let timePassSinceStart = getTimePass(fleet) const [foodRemainSec, armsRemainSec, fuelRemainSec, healthRemainSec] = [ getRemainFoodSec(fleet), getRemainArmsSec(fleet), getRemainFuelSec(fleet), - getRemainHealthSec(fleet) + getRemainHealthSec(fleet), ] const depletionTime = Math.min( foodRemainSec, armsRemainSec, fuelRemainSec, - healthRemainSec + healthRemainSec, ) if (depletionTime < 0) { @@ -73,123 +85,191 @@ export const timePassSinceLastAction = (fleet: ShipStakingInfo) : number => { return timePassSinceStart } -export const getRemainFoodDetails = - (shipInfo: ScoreVarsShipInfo, fleet: ShipStakingInfo, timePassSinceStart: number): Stats => { - const secondsLeft = getRemainFoodSec(fleet, timePassSinceStart) - const unitsBurnRate = 1 / (shipInfo.millisecondsToBurnOneFood / 1000) // Per Second - const burnRatePerFleet = 1 / - ((shipInfo.millisecondsToBurnOneFood / fleet.shipQuantityInEscrow.toNumber()) / 1000) - const unitsBurnt = unitsBurnRate * timePassSinceStart * fleet.shipQuantityInEscrow.toNumber() - const unitsLeft = unitsBurnRate * secondsLeft * fleet.shipQuantityInEscrow.toNumber() - const unitsLeftPct = unitsLeft / (shipInfo.foodMaxReserve * fleet.shipQuantityInEscrow.toNumber()) - const totalSeconds = fleet.foodCurrentCapacity.toNumber() - const maxSeconds = shipInfo.foodMaxReserve * fleet.shipQuantityInEscrow.toNumber() * - (shipInfo.millisecondsToBurnOneFood / 1000 / fleet.shipQuantityInEscrow.toNumber()) - const maxUnits = shipInfo.foodMaxReserve * fleet.shipQuantityInEscrow.toNumber() - - return { - unitsBurnt, - unitsLeftPct, - unitsLeft, - secondsLeft: Math.max(0, secondsLeft), - totalSeconds, - maxSeconds, - maxUnits, - burnRatePerShip: unitsBurnRate, - burnRatePerFleet - } +export const getRemainFoodDetails = ( + shipInfo: ScoreVarsShipInfo, + fleet: ShipStakingInfo, + timePassSinceStart: number, +): Stats => { + const secondsLeft = getRemainFoodSec(fleet, timePassSinceStart) + const unitsBurnRate = 1 / (shipInfo.millisecondsToBurnOneFood / 1000) // Per Second + const burnRatePerFleet = + 1 / + (shipInfo.millisecondsToBurnOneFood / + fleet.shipQuantityInEscrow.toNumber() / + 1000) + const unitsBurnt = + unitsBurnRate * + timePassSinceStart * + fleet.shipQuantityInEscrow.toNumber() + const unitsLeft = + unitsBurnRate * secondsLeft * fleet.shipQuantityInEscrow.toNumber() + const unitsLeftPct = + unitsLeft / + (shipInfo.foodMaxReserve * fleet.shipQuantityInEscrow.toNumber()) + const totalSeconds = fleet.foodCurrentCapacity.toNumber() + const maxSeconds = + shipInfo.foodMaxReserve * + fleet.shipQuantityInEscrow.toNumber() * + (shipInfo.millisecondsToBurnOneFood / + 1000 / + fleet.shipQuantityInEscrow.toNumber()) + const maxUnits = + shipInfo.foodMaxReserve * fleet.shipQuantityInEscrow.toNumber() + + return { + unitsBurnt, + unitsLeftPct, + unitsLeft, + secondsLeft: Math.max(0, secondsLeft), + totalSeconds, + maxSeconds, + maxUnits, + burnRatePerShip: unitsBurnRate, + burnRatePerFleet, } +} -export const getRemainArmsDetails = - (shipInfo: ScoreVarsShipInfo, fleet: ShipStakingInfo, timePassSinceStart: number): Stats => { - const secondsLeft = getRemainArmsSec(fleet, timePassSinceStart) - const unitsBurnRate = 1 / (shipInfo.millisecondsToBurnOneArms / 1000) // Per Second - const unitsBurnt = unitsBurnRate * timePassSinceStart * fleet.shipQuantityInEscrow.toNumber() - const burnRatePerFleet = 1 / - ((shipInfo.millisecondsToBurnOneArms / 1000) / fleet.shipQuantityInEscrow.toNumber()) - const unitsLeft = unitsBurnRate * secondsLeft * fleet.shipQuantityInEscrow.toNumber() - const unitsLeftPct = unitsLeft / (shipInfo.armsMaxReserve * fleet.shipQuantityInEscrow.toNumber()) - const maxSeconds = shipInfo.armsMaxReserve * fleet.shipQuantityInEscrow.toNumber() * - (shipInfo.millisecondsToBurnOneArms / 1000 / fleet.shipQuantityInEscrow.toNumber()) - const totalSeconds = fleet.armsCurrentCapacity.toNumber() - const maxUnits = shipInfo.armsMaxReserve * fleet.shipQuantityInEscrow.toNumber() - - return { - unitsBurnt, - unitsLeftPct, - unitsLeft, - secondsLeft: Math.max(0, secondsLeft), - totalSeconds, - maxSeconds, - maxUnits, - burnRatePerShip: unitsBurnRate, - burnRatePerFleet - } +export const getRemainArmsDetails = ( + shipInfo: ScoreVarsShipInfo, + fleet: ShipStakingInfo, + timePassSinceStart: number, +): Stats => { + const secondsLeft = getRemainArmsSec(fleet, timePassSinceStart) + const unitsBurnRate = 1 / (shipInfo.millisecondsToBurnOneArms / 1000) // Per Second + const unitsBurnt = + unitsBurnRate * + timePassSinceStart * + fleet.shipQuantityInEscrow.toNumber() + const burnRatePerFleet = + 1 / + (shipInfo.millisecondsToBurnOneArms / + 1000 / + fleet.shipQuantityInEscrow.toNumber()) + const unitsLeft = + unitsBurnRate * secondsLeft * fleet.shipQuantityInEscrow.toNumber() + const unitsLeftPct = + unitsLeft / + (shipInfo.armsMaxReserve * fleet.shipQuantityInEscrow.toNumber()) + const maxSeconds = + shipInfo.armsMaxReserve * + fleet.shipQuantityInEscrow.toNumber() * + (shipInfo.millisecondsToBurnOneArms / + 1000 / + fleet.shipQuantityInEscrow.toNumber()) + const totalSeconds = fleet.armsCurrentCapacity.toNumber() + const maxUnits = + shipInfo.armsMaxReserve * fleet.shipQuantityInEscrow.toNumber() + + return { + unitsBurnt, + unitsLeftPct, + unitsLeft, + secondsLeft: Math.max(0, secondsLeft), + totalSeconds, + maxSeconds, + maxUnits, + burnRatePerShip: unitsBurnRate, + burnRatePerFleet, } +} -export const getRemainFuelDetails = - (shipInfo: ScoreVarsShipInfo, fleet: ShipStakingInfo, timePassSinceStart: number): Stats => { - const secondsLeft = getRemainFuelSec(fleet, timePassSinceStart) - const unitsBurnRate = 1 / (shipInfo.millisecondsToBurnOneFuel / 1000) // Per Second - const unitsBurnt = unitsBurnRate * timePassSinceStart * fleet.shipQuantityInEscrow.toNumber() - const burnRatePerFleet = 1 / - ((shipInfo.millisecondsToBurnOneFuel / fleet.shipQuantityInEscrow.toNumber()) / 1000) - const unitsLeft = unitsBurnRate * secondsLeft * fleet.shipQuantityInEscrow.toNumber() - const unitsLeftPct = unitsLeft / (shipInfo.fuelMaxReserve * fleet.shipQuantityInEscrow.toNumber()) - const totalSeconds = fleet.fuelCurrentCapacity.toNumber() - const maxSeconds = shipInfo.fuelMaxReserve * - fleet.shipQuantityInEscrow.toNumber() * (shipInfo.millisecondsToBurnOneFuel / - 1000 / fleet.shipQuantityInEscrow.toNumber()) - const maxUnits = shipInfo.fuelMaxReserve * fleet.shipQuantityInEscrow.toNumber() - - return { - unitsBurnt, - unitsLeftPct, - unitsLeft, - secondsLeft: Math.max(0, secondsLeft), - totalSeconds, - maxSeconds, - maxUnits, - burnRatePerShip: unitsBurnRate, - burnRatePerFleet - } +export const getRemainFuelDetails = ( + shipInfo: ScoreVarsShipInfo, + fleet: ShipStakingInfo, + timePassSinceStart: number, +): Stats => { + const secondsLeft = getRemainFuelSec(fleet, timePassSinceStart) + const unitsBurnRate = 1 / (shipInfo.millisecondsToBurnOneFuel / 1000) // Per Second + const unitsBurnt = + unitsBurnRate * + timePassSinceStart * + fleet.shipQuantityInEscrow.toNumber() + const burnRatePerFleet = + 1 / + (shipInfo.millisecondsToBurnOneFuel / + fleet.shipQuantityInEscrow.toNumber() / + 1000) + const unitsLeft = + unitsBurnRate * secondsLeft * fleet.shipQuantityInEscrow.toNumber() + const unitsLeftPct = + unitsLeft / + (shipInfo.fuelMaxReserve * fleet.shipQuantityInEscrow.toNumber()) + const totalSeconds = fleet.fuelCurrentCapacity.toNumber() + const maxSeconds = + shipInfo.fuelMaxReserve * + fleet.shipQuantityInEscrow.toNumber() * + (shipInfo.millisecondsToBurnOneFuel / + 1000 / + fleet.shipQuantityInEscrow.toNumber()) + const maxUnits = + shipInfo.fuelMaxReserve * fleet.shipQuantityInEscrow.toNumber() + + return { + unitsBurnt, + unitsLeftPct, + unitsLeft, + secondsLeft: Math.max(0, secondsLeft), + totalSeconds, + maxSeconds, + maxUnits, + burnRatePerShip: unitsBurnRate, + burnRatePerFleet, } +} -export const getRemainHealthDetails = - (shipInfo: ScoreVarsShipInfo, fleet: ShipStakingInfo, timePassSinceStart: number): Stats => { - const unitsLeftPct = - (fleet.healthCurrentCapacity.toNumber() - timePassSinceStart) / fleet.healthCurrentCapacity.toNumber() - const secondsLeft = getRemainHealthSec(fleet, timePassSinceStart) - const unitsBurnRate = 1 / (shipInfo.millisecondsToBurnOneToolkit / 1000) - const burnRatePerFleet = 1 / - ((shipInfo.millisecondsToBurnOneToolkit / fleet.shipQuantityInEscrow.toNumber()) / 1000) - const unitsLeft = secondsLeft / - (shipInfo.millisecondsToBurnOneToolkit / 1000 / fleet.shipQuantityInEscrow.toNumber()) - const totalSeconds = fleet.healthCurrentCapacity.toNumber() - const maxSeconds = shipInfo.toolkitMaxReserve * fleet.shipQuantityInEscrow.toNumber() * - (shipInfo.millisecondsToBurnOneToolkit / 1000 / fleet.shipQuantityInEscrow.toNumber()) - const maxUnits = shipInfo.toolkitMaxReserve * fleet.shipQuantityInEscrow.toNumber() - - return { - unitsBurnt: 0, - unitsLeftPct, - secondsLeft: Math.max(0, secondsLeft), - totalSeconds, - maxSeconds, - maxUnits, - unitsLeft, - burnRatePerShip: unitsBurnRate, - burnRatePerFleet - } +export const getRemainHealthDetails = ( + shipInfo: ScoreVarsShipInfo, + fleet: ShipStakingInfo, + timePassSinceStart: number, +): Stats => { + const unitsLeftPct = + (fleet.healthCurrentCapacity.toNumber() - timePassSinceStart) / + fleet.healthCurrentCapacity.toNumber() + const secondsLeft = getRemainHealthSec(fleet, timePassSinceStart) + const unitsBurnRate = 1 / (shipInfo.millisecondsToBurnOneToolkit / 1000) + const burnRatePerFleet = + 1 / + (shipInfo.millisecondsToBurnOneToolkit / + fleet.shipQuantityInEscrow.toNumber() / + 1000) + const unitsLeft = + secondsLeft / + (shipInfo.millisecondsToBurnOneToolkit / + 1000 / + fleet.shipQuantityInEscrow.toNumber()) + const totalSeconds = fleet.healthCurrentCapacity.toNumber() + const maxSeconds = + shipInfo.toolkitMaxReserve * + fleet.shipQuantityInEscrow.toNumber() * + (shipInfo.millisecondsToBurnOneToolkit / + 1000 / + fleet.shipQuantityInEscrow.toNumber()) + const maxUnits = + shipInfo.toolkitMaxReserve * fleet.shipQuantityInEscrow.toNumber() + + return { + unitsBurnt: 0, + unitsLeftPct, + secondsLeft: Math.max(0, secondsLeft), + totalSeconds, + maxSeconds, + maxUnits, + unitsLeft, + burnRatePerShip: unitsBurnRate, + burnRatePerFleet, } +} -export const getFleetRemainingResources = (shipInfo: ScoreVarsShipInfo, fleet: ShipStakingInfo): ResourceStats => { +export const getFleetRemainingResources = ( + shipInfo: ScoreVarsShipInfo, + fleet: ShipStakingInfo, +): ResourceStats => { const timePassSinceStart = timePassSinceLastAction(fleet) return { food: getRemainFoodDetails(shipInfo, fleet, timePassSinceStart), ammo: getRemainArmsDetails(shipInfo, fleet, timePassSinceStart), fuel: getRemainFuelDetails(shipInfo, fleet, timePassSinceStart), - tool: getRemainHealthDetails(shipInfo, fleet, timePassSinceStart) + tool: getRemainHealthDetails(shipInfo, fleet, timePassSinceStart), } } diff --git a/src/service/fleet/refill-fleet.ts b/src/service/fleet/refill-fleet.ts index 28789beb..e44cf840 100644 --- a/src/service/fleet/refill-fleet.ts +++ b/src/service/fleet/refill-fleet.ts @@ -1,5 +1,11 @@ import { PublicKey, TransactionInstruction } from '@solana/web3.js' -import { createRearmInstruction, createRefeedInstruction, createRefuelInstruction, createRepairInstruction, ShipStakingInfo } from '@staratlas/factory' +import { + ShipStakingInfo, + createRearmInstruction, + createRefeedInstruction, + createRefuelInstruction, + createRepairInstruction, +} from '@staratlas/factory' import { connection, fleetProgram, getAccount } from '../sol' import { sendAndConfirmInstructions } from '../sol/send-and-confirm-tx' @@ -7,13 +13,18 @@ import { keyPair, resource } from '../wallet' import { Amounts } from './const' -export const refillFleet = async (player: PublicKey, fleetUnit: ShipStakingInfo, amounts: Amounts): Promise => { - const [foodAccount, fuelAccount, ammoAccount, toolAccount] = await Promise.all([ - getAccount(keyPair.publicKey, resource.food), - getAccount(keyPair.publicKey, resource.fuel), - getAccount(keyPair.publicKey, resource.ammo), - getAccount(keyPair.publicKey, resource.tool) - ]) +export const refillFleet = async ( + player: PublicKey, + fleetUnit: ShipStakingInfo, + amounts: Amounts, +): Promise => { + const [foodAccount, fuelAccount, ammoAccount, toolAccount] = + await Promise.all([ + getAccount(keyPair.publicKey, resource.food), + getAccount(keyPair.publicKey, resource.fuel), + getAccount(keyPair.publicKey, resource.ammo), + getAccount(keyPair.publicKey, resource.tool), + ]) const instructions: TransactionInstruction[] = [] @@ -28,52 +39,59 @@ export const refillFleet = async (player: PublicKey, fleetUnit: ShipStakingInfo, fleetUnit.shipMint, resource.food, foodAccount, - fleetProgram) - ) + fleetProgram, + ), + ), ) } if (amounts.fuel.gt(0)) { instructions.push( new TransactionInstruction( - await createRefuelInstruction(connection, + await createRefuelInstruction( + connection, keyPair.publicKey, player, amounts.fuel.toNumber(), fleetUnit.shipMint, resource.fuel, fuelAccount, - fleetProgram) - ) + fleetProgram, + ), + ), ) } if (amounts.ammo.gt(0)) { instructions.push( new TransactionInstruction( - await createRearmInstruction(connection, + await createRearmInstruction( + connection, keyPair.publicKey, player, amounts.ammo.toNumber(), fleetUnit.shipMint, resource.ammo, ammoAccount, - fleetProgram) - ) + fleetProgram, + ), + ), ) } if (amounts.tool.gt(0)) { instructions.push( new TransactionInstruction( - await createRepairInstruction(connection, + await createRepairInstruction( + connection, keyPair.publicKey, player, amounts.tool.toNumber(), fleetUnit.shipMint, resource.tool, toolAccount, - fleetProgram) - ) + fleetProgram, + ), + ), ) } - return await sendAndConfirmInstructions(instructions) + return sendAndConfirmInstructions(instructions) } diff --git a/src/service/gm/market.ts b/src/service/gm/market.ts index 080f56a6..65cda71f 100644 --- a/src/service/gm/market.ts +++ b/src/service/gm/market.ts @@ -1,6 +1,10 @@ -import { createTransferCheckedInstruction, getOrCreateAssociatedTokenAccount } from '@solana/spl-token' +import { + createTransferCheckedInstruction, + getAssociatedTokenAddressSync, + getOrCreateAssociatedTokenAccount, +} from '@solana/spl-token' import { Keypair, PublicKey } from '@solana/web3.js' -import { getAssociatedTokenAddress, GmClientService, GmOrderbookService, Order } from '@staratlas/factory' +import { GmClientService, GmOrderbookService, Order } from '@staratlas/factory' import Big from 'big.js' import { Sentry } from '../../sentry' @@ -17,9 +21,12 @@ const gmOrderbookService = new GmOrderbookService(connection, marketProgram) const orderSorter = (a: Order, b: Order) => a.price.sub(b.price).toNumber() export const getMarketPrice = (res: PublicKey): Big => { - const orders = - gmOrderbookService.getSellOrdersByCurrencyAndItem(resource.atlas.toString(), res.toString()) - .sort(orderSorter) + const orders = gmOrderbookService + .getSellOrdersByCurrencyAndItem( + resource.atlas.toString(), + res.toString(), + ) + .sort(orderSorter) const [order] = orders @@ -30,16 +37,20 @@ export const getResourcePrices = (): Amounts => ({ food: getMarketPrice(resource.food), tool: getMarketPrice(resource.tool), ammo: getMarketPrice(resource.ammo), - fuel: getMarketPrice(resource.fuel) + fuel: getMarketPrice(resource.fuel), }) export const getBalanceAtlas = async (pubKey: PublicKey): Promise => { try { - const balance = await getOrCreateAssociatedTokenAccount(connection, new Keypair(), resource.atlas, pubKey) + const balance = await getOrCreateAssociatedTokenAccount( + connection, + new Keypair(), + resource.atlas, + pubKey, + ) return Big(Number(balance.amount)).div(100000000) - } - catch (e) { + } catch (e) { Sentry.captureException(e) logger.error(e) @@ -47,33 +58,51 @@ export const getBalanceAtlas = async (pubKey: PublicKey): Promise => { } } -export const sendAtlas = async (receiver: PublicKey, amount: number): Promise => { - const instructions = [ createTransferCheckedInstruction( - await getAssociatedTokenAddress(keyPair.publicKey, resource.atlas), - resource.atlas, - await getAssociatedTokenAddress(receiver, resource.atlas), - keyPair.publicKey, - Big(amount).mul(100000000).toNumber(), - 8, - [], - )] - - return await sendAndConfirmInstructions(instructions) +export const sendAtlas = ( + receiver: PublicKey, + amount: number, +): Promise => { + const instructions = [ + createTransferCheckedInstruction( + getAssociatedTokenAddressSync( + resource.atlas, + keyPair.publicKey, + true, + ), + resource.atlas, + getAssociatedTokenAddressSync(resource.atlas, receiver, true), + keyPair.publicKey, + Big(amount).mul(100000000).toNumber(), + 8, + [], + ), + ] + + return sendAndConfirmInstructions(instructions) } -export const getBalanceMarket = async (pubKey: PublicKey, res: PublicKey): Promise => { - const balance = await getOrCreateAssociatedTokenAccount(connection, new Keypair(), res, pubKey) +export const getBalanceMarket = async ( + pubKey: PublicKey, + res: PublicKey, +): Promise => { + const balance = await getOrCreateAssociatedTokenAccount( + connection, + new Keypair(), + res, + pubKey, + ) return Big(Number(balance.amount)) } -export const getResourceBalances = async (player: PublicKey): Promise => { +export const getResourceBalances = async ( + player: PublicKey, +): Promise => { const [tool, food, ammo, fuel] = await Promise.all([ getBalanceMarket(player, resource.tool), getBalanceMarket(player, resource.food), getBalanceMarket(player, resource.ammo), - getBalanceMarket(player, resource.fuel) - + getBalanceMarket(player, resource.fuel), ]) return { food, tool, ammo, fuel } @@ -83,10 +112,16 @@ export const initOrderBook = async (): Promise => { await gmOrderbookService.initialize() } -export const buyResource = async (res: PublicKey, amount: Big): Promise => { - const orders = - gmOrderbookService.getSellOrdersByCurrencyAndItem(resource.atlas.toString(), res.toString()) - .sort(orderSorter) +export const buyResource = async ( + res: PublicKey, + amount: Big, +): Promise => { + const orders = gmOrderbookService + .getSellOrdersByCurrencyAndItem( + resource.atlas.toString(), + res.toString(), + ) + .sort(orderSorter) const [order] = orders @@ -95,20 +130,30 @@ export const buyResource = async (res: PublicKey, amount: Big): Promise order, keyPair.publicKey, amount.round().toNumber(), - marketProgram + marketProgram, ) logger.info(`Buying ${amount.toFixed(0)} ${res} for ${order.uiPrice} each`) - return await sendAndConfirmInstructions(exchangeTx.transaction.instructions) + return sendAndConfirmInstructions(exchangeTx.transaction.instructions) } export const buyResources = async (amount: Amounts): Promise => { - const res = await Promise.all([ - amount.food.gt(0) ? buyResource(resource.food, amount.food) : Promise.resolve(''), - amount.ammo.gt(0) ? buyResource(resource.ammo, amount.ammo) : Promise.resolve(''), - amount.fuel.gt(0) ? buyResource(resource.fuel, amount.fuel) : Promise.resolve(''), - amount.tool.gt(0) ? buyResource(resource.tool, amount.tool) : Promise.resolve('') - ]) - - return res.filter(r => r !== '') + const res = ( + await Promise.all([ + amount.food.gt(0) + ? buyResource(resource.food, amount.food) + : Promise.resolve(''), + amount.ammo.gt(0) + ? buyResource(resource.ammo, amount.ammo) + : Promise.resolve(''), + amount.fuel.gt(0) + ? buyResource(resource.fuel, amount.fuel) + : Promise.resolve(''), + amount.tool.gt(0) + ? buyResource(resource.tool, amount.tool) + : Promise.resolve(''), + ]) + ).flat() + + return res.filter((r) => r !== '') } diff --git a/src/service/sleep.ts b/src/service/sleep.ts new file mode 100644 index 00000000..b78a9934 --- /dev/null +++ b/src/service/sleep.ts @@ -0,0 +1,6 @@ +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const sleep = (ms: number) => + // eslint-disable-next-line promise/avoid-new + new Promise((resolve) => { + setTimeout(resolve, ms) + }) diff --git a/src/service/sol/anchor.ts b/src/service/sol/anchor.ts new file mode 100644 index 00000000..d6dfbb19 --- /dev/null +++ b/src/service/sol/anchor.ts @@ -0,0 +1,12 @@ +// eslint-disable-next-line import/named +import { AnchorProvider, Wallet } from '@coral-xyz/anchor' + +import { keyPair } from '../wallet' + +import { connection } from './const' + +export const anchorProvider = new AnchorProvider( + connection, + new Wallet(keyPair), + {}, +) diff --git a/src/service/sol/const/connection.ts b/src/service/sol/const/connection.ts index 0fff534d..90b79e6f 100644 --- a/src/service/sol/const/connection.ts +++ b/src/service/sol/const/connection.ts @@ -1,8 +1,15 @@ import { Connection } from '@solana/web3.js' import { config } from '../../../config' +import { fetchWithRetries } from '../undici-retry' export const connection = new Connection(config.sol.rpcEndpoint, { wsEndpoint: config.sol.wsEndpoint, commitment: 'confirmed', + fetch: ( + input: RequestInfo | URL, + init?: RequestInit, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + ): Promise => fetchWithRetries(input, init, 5), }) diff --git a/src/service/sol/get-account.ts b/src/service/sol/get-account.ts index e072e56d..5cf8d234 100644 --- a/src/service/sol/get-account.ts +++ b/src/service/sol/get-account.ts @@ -5,15 +5,19 @@ import { Resource } from '../wallet' const resourceAccounts: Map = new Map() -export const getAccount = async (player: PublicKey, resource: Resource): Promise => { +export const getAccount = async ( + player: PublicKey, + resource: Resource, +): Promise => { if (!resourceAccounts.get(resource.toString())) { - const ret = await PublicKey.findProgramAddress([ - player.toBuffer(), - TOKEN_PROGRAM_ID.toBuffer(), - resource.toBuffer() - ], new PublicKey( - 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' - )) + const ret = await PublicKey.findProgramAddress( + [ + player.toBuffer(), + TOKEN_PROGRAM_ID.toBuffer(), + resource.toBuffer(), + ], + new PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'), + ) resourceAccounts.set(resource.toString(), ret[0]) } diff --git a/src/service/sol/priority-fee/compute-unit-instruction.ts b/src/service/sol/priority-fee/compute-unit-instruction.ts index 81fb60a1..458a0c92 100644 --- a/src/service/sol/priority-fee/compute-unit-instruction.ts +++ b/src/service/sol/priority-fee/compute-unit-instruction.ts @@ -1,4 +1,11 @@ -import { AddressLookupTableAccount, ComputeBudgetProgram, PublicKey, TransactionInstruction, TransactionMessage, VersionedTransaction } from '@solana/web3.js' +import { + AddressLookupTableAccount, + ComputeBudgetProgram, + PublicKey, + TransactionInstruction, + TransactionMessage, + VersionedTransaction, +} from '@solana/web3.js' import { logger } from '../../../logger' import { keyPair } from '../../wallet' @@ -7,24 +14,24 @@ import { connection } from '../const' const getSimulationUnits = async ( instructions: TransactionInstruction[], payer: PublicKey, - lookupTables: AddressLookupTableAccount[] + lookupTables: AddressLookupTableAccount[], ): Promise => { const testInstructions = [ ComputeBudgetProgram.setComputeUnitLimit({ units: 1_400_000 }), - ...instructions + ...instructions, ] const testVersionedTxn = new VersionedTransaction( new TransactionMessage({ instructions: testInstructions, payerKey: payer, - recentBlockhash: PublicKey.default.toString() - }).compileToV0Message(lookupTables) + recentBlockhash: PublicKey.default.toString(), + }).compileToV0Message(lookupTables), ) const simulation = await connection.simulateTransaction(testVersionedTxn, { replaceRecentBlockhash: true, - sigVerify: false + sigVerify: false, }) if (simulation.value.err) { @@ -34,11 +41,14 @@ const getSimulationUnits = async ( return simulation.value.unitsConsumed } -export const createComputeUnitInstruction = - async (instructions: TransactionInstruction[]): Promise => { - const units = await getSimulationUnits(instructions, keyPair.publicKey, []) +export const createComputeUnitInstruction = async ( + instructions: TransactionInstruction[], +): Promise => { + const units = await getSimulationUnits(instructions, keyPair.publicKey, []) - logger.info(`Esitmated Compute Units: ${units}`) + logger.debug(`Esitmated Compute Units: ${units}`) - return ComputeBudgetProgram.setComputeUnitLimit({ units: units ? units + 500 : 200_000 }) - } + return ComputeBudgetProgram.setComputeUnitLimit({ + units: units ? units + 500 : 200_000, + }) +} diff --git a/src/service/sol/priority-fee/priority-fee-instruction.ts b/src/service/sol/priority-fee/priority-fee-instruction.ts index af098e3f..f7706a50 100644 --- a/src/service/sol/priority-fee/priority-fee-instruction.ts +++ b/src/service/sol/priority-fee/priority-fee-instruction.ts @@ -3,12 +3,19 @@ import { ComputeBudgetProgram, TransactionInstruction } from '@solana/web3.js' import { logger } from '../../../logger' import { connection } from '../const' -export const createPriorityFeeInstruction = async (): Promise => { - const recentPriorityFees = await connection.getRecentPrioritizationFees() +export const createPriorityFeeInstruction = + async (): Promise => { + const recentPriorityFees = + await connection.getRecentPrioritizationFees() - const maxPriorityFee = Math.max(...recentPriorityFees.map(fee => fee.prioritizationFee.valueOf())) + const maxPriorityFee = Math.max( + 0, + ...recentPriorityFees.map((fee) => fee.prioritizationFee.valueOf()), + ) - logger.info(`Estimated priority fee: ${maxPriorityFee}`) + logger.debug(`Estimated priority fee: ${maxPriorityFee}`) - return ComputeBudgetProgram.setComputeUnitPrice({ microLamports: maxPriorityFee }) -} + return ComputeBudgetProgram.setComputeUnitPrice({ + microLamports: maxPriorityFee, + }) + } diff --git a/src/service/sol/send-and-confirm-tx.ts b/src/service/sol/send-and-confirm-tx.ts index b34d6cf9..0c44f04c 100644 --- a/src/service/sol/send-and-confirm-tx.ts +++ b/src/service/sol/send-and-confirm-tx.ts @@ -1,4 +1,9 @@ -import { TransactionInstruction, TransactionMessage, VersionedTransaction } from '@solana/web3.js' +import { + PublicKey, + TransactionInstruction, + TransactionMessage, + VersionedTransaction, +} from '@solana/web3.js' import { logger } from '../../logger' import { keyPair } from '../wallet' @@ -7,28 +12,40 @@ import { connection } from './const' import { createComputeUnitInstruction } from './priority-fee/compute-unit-instruction' import { createPriorityFeeInstruction } from './priority-fee/priority-fee-instruction' +// Constants for Solana transaction size limits +const MAX_TRANSACTION_SIZE = 1232 // Maximum size of a transaction in bytes +const TRANSACTION_HEADER_SIZE = 100 // Approximate size of transaction header, adjust if needed +const SIGNATURE_SIZE = 64 // Size of a signature in bytes + const sleep = (ms: number) => // eslint-disable-next-line promise/avoid-new new Promise((resolve) => { setTimeout(resolve, ms) }) -type Blockhash = string; +type Blockhash = string type BlockhashWithExpiryBlockHeight = Readonly<{ blockhash: Blockhash lastValidBlockHeight: number -}>; +}> const confirmTx = async (txId: string): Promise => { const res = await connection.getSignatureStatus(txId) - logger.debug(`Signature: ${txId} with status: ${JSON.stringify(res)}`) + // logger.debug(`Signature: ${txId} with status: ${JSON.stringify(res)}`) if (res?.value && 'confirmationStatus' in res.value) { - if (res.value.confirmationStatus === 'finalized' || res.value.confirmationStatus === 'confirmed' || res.value.confirmationStatus === 'processed') { - logger.info(`Transaction ${res.value.confirmationStatus}: ${txId} with status: ${res.value.confirmationStatus}`) + if ( + res.value.confirmationStatus === 'finalized' || + res.value.confirmationStatus === 'confirmed' || + res.value.confirmationStatus === 'processed' + ) { + const log = res.value.err ? logger.warn : logger.info + + // log(`Transaction ${res.value.confirmationStatus}: ${txId} with status: ${res.value.confirmationStatus}`) + log(`Signature: ${txId} with status: ${JSON.stringify(res)}`) - logger.info(`https://solscan.io/tx/${txId}`) + // logger.info(`https://solscan.io/tx/${txId}`) return txId } @@ -38,9 +55,9 @@ const confirmTx = async (txId: string): Promise => { export const sendAndConfirmTx = async ( transaction: VersionedTransaction, - latestBlockHash?: BlockhashWithExpiryBlockHeight + latestBlockHash?: BlockhashWithExpiryBlockHeight, ): Promise => { - const blockHash = latestBlockHash ?? await connection.getLatestBlockhash() + const blockHash = latestBlockHash ?? (await connection.getLatestBlockhash()) const blockheight = await connection.getBlockHeight() let txId: string | undefined @@ -48,11 +65,19 @@ export const sendAndConfirmTx = async ( /* eslint-disable no-await-in-loop */ while (blockheight <= blockHash.lastValidBlockHeight) { try { - txId = await connection.sendRawTransaction(transaction.serialize()) - } - catch (e) { + txId = await connection.sendRawTransaction( + transaction.serialize(), + { skipPreflight: true }, + ) + } catch (e) { const message = (e as any).message as string + const logs = (e as any).logs as string[] + + logs.filter((log) => log.includes('AnchorError')).forEach((log) => { + logger.error(log) + }) + if (message.includes('has already been processed') && txId) { await confirmTx(txId) @@ -65,8 +90,7 @@ export const sendAndConfirmTx = async ( await confirmTx(txId) return txId - } - catch (e) { + } catch (_e) { await sleep(500) } } @@ -75,48 +99,121 @@ export const sendAndConfirmTx = async ( throw new Error(`Transaction ${txId} failed to confirm`) } -const createAndSignTransaction = - (instructions: TransactionInstruction[], blockhash: Blockhash): VersionedTransaction => { - const messageV0 = new TransactionMessage({ - payerKey: keyPair.publicKey, - recentBlockhash: blockhash, - instructions - }).compileToV0Message() - const transaction = new VersionedTransaction(messageV0) +const createAndSignTransaction = ( + instructions: TransactionInstruction[], + blockhash: Blockhash, +): VersionedTransaction => { + const messageV0 = new TransactionMessage({ + payerKey: keyPair.publicKey, + recentBlockhash: blockhash, + instructions, + }).compileToV0Message() + const transaction = new VersionedTransaction(messageV0) - transaction.sign([keyPair, keyPair]) + transaction.sign([keyPair, keyPair]) - return transaction - } - -export const sendAndConfirmInstructions = async (instructions: TransactionInstruction[]): Promise => { - const maxRetries = 10 + return transaction +} - /* eslint-disable no-await-in-loop */ - for(let i = 0; i < maxRetries; ++i) { - const [latestBlockHash, priorityFeeInstruction, computeUnitsInstruction] = await Promise.all([ - connection.getLatestBlockhash(), - createPriorityFeeInstruction(), - createComputeUnitInstruction(instructions) - ]) +const getInstructionSize = (instructions: TransactionInstruction[]): number => { + const messageV0 = new TransactionMessage({ + payerKey: keyPair.publicKey, + recentBlockhash: PublicKey.default.toBase58(), + instructions, + }).compileToV0Message() - const txInstructions = [ - computeUnitsInstruction, - priorityFeeInstruction, - ...instructions - ] + // Serialize the message and return its length + return new VersionedTransaction(messageV0).serialize().byteLength + // return messageV0.serialize().length +} +const getOptimalInstructionChunk = ( + instructions: TransactionInstruction[], + maxSize: number, +): TransactionInstruction[] => { + for (let i = 0; i < instructions.length; ++i) { + const instructionSize = getInstructionSize(instructions.slice(0, i + 1)) + + logger.debug( + `Transaction with ${i + 1} instructions has size ${instructionSize}`, + ) + + if (instructionSize > maxSize) { + return instructions.slice(0, i) + } + } - const transaction = createAndSignTransaction(txInstructions, latestBlockHash.blockhash) + return instructions +} - try { - return await sendAndConfirmTx(transaction, latestBlockHash) - } - catch (e) { - const message = (e as any).message as string +export const sendAndConfirmInstructions = async ( + instructionArray: TransactionInstruction[], +): Promise => { + const maxRetries = 10 + let instructions = instructionArray + const results: string[] = [] + + while (instructions.length > 0) { + const availableSize = + MAX_TRANSACTION_SIZE - TRANSACTION_HEADER_SIZE - SIGNATURE_SIZE + + const chunk = getOptimalInstructionChunk(instructions, availableSize) + + /* eslint-disable no-await-in-loop */ + for (let i = 0; i < maxRetries; ++i) { + const [ + latestBlockHash, + priorityFeeInstruction, + computeUnitsInstruction, + ] = await Promise.all([ + connection.getLatestBlockhash(), + createPriorityFeeInstruction(), + createComputeUnitInstruction(chunk), + ]) + + const txInstructions = [ + computeUnitsInstruction, + priorityFeeInstruction, + ...chunk, + ] + + const transaction = createAndSignTransaction( + txInstructions, + latestBlockHash.blockhash, + ) + + const rawTransaction = transaction.serialize() + + if (rawTransaction.length > MAX_TRANSACTION_SIZE) { + throw new Error( + `Transaction too large: ${rawTransaction.length} bytes`, + ) + } - logger.error(`Transaction failed: ${message}, retrying... (${i + 1}/${maxRetries})`) + try { + const result = await sendAndConfirmTx( + transaction, + latestBlockHash, + ) + + results.push(result) + instructions = instructions.slice(chunk.length) + break // Exit retry loop if successful + } catch (e) { + const message = (e as any).message as string + + logger.error( + `Transaction failed: ${message}, retrying... (${i + 1}/${maxRetries})`, + ) + + if (i === maxRetries - 1) { + throw new Error( + `Transaction failed after ${maxRetries} attempts`, + ) + } + } } + /* eslint-enable no-await-in-loop */ } - /* eslint-enable no-await-in-loop */ - throw new Error(`Transaction failed after ${maxRetries} attempts`) + + return results } diff --git a/src/service/sol/undici-retry.ts b/src/service/sol/undici-retry.ts new file mode 100644 index 00000000..3051552e --- /dev/null +++ b/src/service/sol/undici-retry.ts @@ -0,0 +1,43 @@ +import { + Agent, + RequestInfo, + RequestInit, + Response, + fetch, + setGlobalDispatcher, +} from 'undici' + +setGlobalDispatcher(new Agent({ connections: 100, connectTimeout: 30000 })) + +export const fetchWithRetries = async ( + input: URL | RequestInfo, + init: RequestInit = {}, + retryAttempts = 0, +): Promise => { + let attempt = 0 + + init.headers ||= { 'Content-Type': 'application/json' } + + while (attempt < retryAttempts) { + try { + // eslint-disable-next-line no-await-in-loop + const response = await fetch(input, init) + + if (response.status === 502) { + console.log('Retrying due to 502') + attempt++ + // eslint-disable-next-line no-await-in-loop,promise/avoid-new,no-loop-func + await new Promise((resolve) => { + setTimeout(resolve, 100 * attempt) + }) + } else { + return response + } + } catch (e) { + console.log(`Retrying due to error ${e}`, e) + attempt++ + } + } + + throw new Error('Max retries reached') +} diff --git a/src/service/wallet/init-keypair.ts b/src/service/wallet/init-keypair.ts index 18393ba9..29d3c3bd 100644 --- a/src/service/wallet/init-keypair.ts +++ b/src/service/wallet/init-keypair.ts @@ -1,26 +1,53 @@ -import { Keypair } from '@solana/web3.js' +import { Keypair, PublicKey } from '@solana/web3.js' import { mnemonicToSeedSync } from 'bip39' import { derivePath } from 'ed25519-hd-key' import { config } from '../../config' import { logger } from '../../logger' -const initKeypairBySecretKey = (key: number[]): Keypair => { +const initKeypairBySecretKey = (key: number[], pubKey: PublicKey): Keypair => { const keypair = Keypair.fromSecretKey(new Uint8Array(key)) - logger.info(`key => ${keypair.publicKey.toBase58()}`) + if (keypair.publicKey.equals(pubKey)) { + logger.info(`Found keypair for ${pubKey.toBase58()}`) - return keypair + return keypair + } + + throw new Error('PubKey does not match Private key') } -const initKeypairByMnemonic = (mnemonic: string, accountNumber: number): Keypair => { +const initKeypairByMnemonic = ( + mnemonic: string, + pubKey: PublicKey, +): Keypair => { const seed = mnemonicToSeedSync(mnemonic, '') - const path = `m/44'/501'/${accountNumber}'/0'` - const keypair = Keypair.fromSeed(derivePath(path, seed.toString('hex')).key) - logger.debug(`${path} => ${keypair.publicKey.toBase58()}`) + for (let i = 0; i < 1000; ++i) { + const path = `m/44'/501'/${i}'/0'` + const keypair = Keypair.fromSeed( + derivePath(path, seed.toString('hex')).key, + ) + + logger.debug(`${path} => ${keypair.publicKey.toBase58()}`) + + if (keypair.publicKey.equals(pubKey)) { + logger.info(`Found keypair for ${pubKey.toBase58()} at ${path}`) + + return keypair + } + } - return keypair + throw new Error('PubKey not found in derivation Path') } -export const keyPair = config.user.keyMode === 'mnemonic' ? initKeypairByMnemonic(config.user.mnemonic, config.user.walletId) : initKeypairBySecretKey(config.user.secretKey) +export const keyPair = + config.user.keyMode === 'mnemonic' + ? initKeypairByMnemonic( + config.user.mnemonic, + new PublicKey(config.user.pubKey), + ) + : initKeypairBySecretKey( + config.user.secretKey, + new PublicKey(config.user.pubKey), + ) diff --git a/src/service/wallet/resource.ts b/src/service/wallet/resource.ts index 0c12d20a..2a0b863e 100644 --- a/src/service/wallet/resource.ts +++ b/src/service/wallet/resource.ts @@ -9,5 +9,5 @@ export const resource = { food: new PublicKey(config.sol.foodMint), fuel: new PublicKey(config.sol.fuelMint), ammo: new PublicKey(config.sol.ammoMint), - tool: new PublicKey(config.sol.toolMint) + tool: new PublicKey(config.sol.toolMint), } diff --git a/tsconfig.json b/tsconfig.json index f836bb0b..ff5414b0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,7 @@ "es2018", "es2019", "es2020", - "es2021", + "es2021" ], "outDir": "./build/app", "sourceMap": true,