├── .editorconfig ├── .env.sample ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── pr.yml │ ├── prod_release.yml │ └── release.yml ├── .gitignore ├── .nsprc ├── .prettierignore ├── .prettierrc.js ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── LICENSE.md ├── README.md ├── commitlint.config.cjs ├── docs ├── README.md ├── code │ ├── README.md │ ├── classes │ │ ├── index.AlgorandSubscriber.md │ │ ├── types_async_event_emitter.AsyncEventEmitter.md │ │ └── types_subscription.SubscribedTransaction.md │ ├── enums │ │ └── types_subscription.BalanceChangeRole.md │ ├── interfaces │ │ ├── types_arc_28.Arc28Event.md │ │ ├── types_arc_28.Arc28EventGroup.md │ │ ├── types_arc_28.Arc28EventToProcess.md │ │ ├── types_arc_28.EmittedArc28Event.md │ │ ├── types_block.TransactionInBlock.md │ │ ├── types_subscription.AlgorandSubscriberConfig.md │ │ ├── types_subscription.BalanceChange.md │ │ ├── types_subscription.BeforePollMetadata.md │ │ ├── types_subscription.BlockMetadata.md │ │ ├── types_subscription.BlockRewards.md │ │ ├── types_subscription.BlockStateProofTracking.md │ │ ├── types_subscription.BlockUpgradeState.md │ │ ├── types_subscription.BlockUpgradeVote.md │ │ ├── types_subscription.CoreTransactionSubscriptionParams.md │ │ ├── types_subscription.NamedTransactionFilter.md │ │ ├── types_subscription.ParticipationUpdates.md │ │ ├── types_subscription.SubscriberConfigFilter.md │ │ ├── types_subscription.TransactionFilter.md │ │ ├── types_subscription.TransactionSubscriptionParams.md │ │ └── types_subscription.TransactionSubscriptionResult.md │ └── modules │ │ ├── index.md │ │ ├── types.md │ │ ├── types_arc_28.md │ │ ├── types_async_event_emitter.md │ │ ├── types_block.md │ │ └── types_subscription.md ├── subscriber.md ├── subscriptions.md └── v3-migration.md ├── eslint.config.mjs ├── examples ├── data-history-museum │ └── index.ts ├── usdc │ └── index.ts └── xgov-voting │ ├── index.ts │ ├── prisma │ ├── migrations │ │ ├── 20240619152002_init │ │ │ └── migration.sql │ │ ├── 20240619153715_tweak │ │ │ └── migration.sql │ │ ├── 20241217122304_bigint │ │ │ └── migration.sql │ │ └── migration_lock.toml │ └── schema.prisma │ └── types │ ├── voting-app-client.ts │ ├── voting-round.ts │ └── voting.arc32.json ├── package-lock.json ├── package.json ├── rollup.config.ts ├── rollup └── multi-plugin.ts ├── src ├── block.ts ├── index.ts ├── subscriber.ts ├── subscriptions.ts ├── transform.ts ├── types │ ├── arc-28.ts │ ├── async-event-emitter.ts │ ├── block.ts │ ├── index.ts │ └── subscription.ts └── utils.ts ├── tests ├── contract │ ├── .gitignore │ ├── __init__.py │ ├── build.py │ ├── client.ts │ ├── poetry.lock │ ├── poetry.toml │ ├── pyproject.toml │ └── testing_app │ │ └── contract.py ├── filterFixture.ts ├── scenarios │ ├── app-call-transactions.spec.ts │ ├── balance-changes.spec.ts │ ├── catchup-with-indexer.spec.ts │ ├── events.spec.ts │ ├── fail.spec.ts │ ├── filters.spec.ts │ ├── inner_transactions.spec.ts │ ├── multiple-filters.spec.ts │ ├── skip-sync-newest.spec.ts │ ├── subscriber.spec.ts │ ├── sync-oldest-start-now.spec.ts │ ├── sync-oldest.spec.ts │ ├── transform-appl-create-txn.spec.ts │ ├── transform-asset-config-txn.spec.ts │ ├── transform-complex-txn.spec.ts │ ├── transform-heartbeat-txn.spec.ts │ ├── transform-keyreg.spec.ts │ └── transform-stpf.spec.ts ├── setup.ts ├── subscribed-transactions.ts ├── testing-app.ts ├── transactions.ts ├── wait.ts └── watermarks.ts ├── tsconfig.build.json ├── tsconfig.dev.json ├── tsconfig.json ├── tsconfig.test.json ├── typedoc.json └── vitest.config.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | insert_final_newline = true 4 | end_of_line = lf 5 | indent_style = space 6 | indent_size = 2 7 | tab_width = 2 8 | max_line_length = 140 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | ALGOD_TOKEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 2 | ALGOD_SERVER=http://localhost 3 | ALGOD_PORT=4001 4 | KMD_PORT=4002 5 | INDEXER_TOKEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 6 | INDEXER_SERVER=http://localhost 7 | INDEXER_PORT=8980 8 | 9 | # TestNet config 10 | 11 | # AlgoNode - https://algonode.io/api/ 12 | # Useful service to target testnet and mainnet without having to run our own node 13 | ALGOD_TOKEN= 14 | ALGOD_SERVER=https://testnet-api.algonode.cloud/ 15 | ALGOD_PORT=443 16 | INDEXER_TOKEN= 17 | INDEXER_SERVER=https://testnet-idx.algonode.cloud/ 18 | INDEXER_PORT=443 19 | 20 | # MainNet config 21 | 22 | # Low-latency AlgoNode endpoints 23 | ALGOD_TOKEN= 24 | ALGOD_SERVER=https://mainnet-api.algonode.network/ 25 | ALGOD_PORT=443 26 | INDEXER_TOKEN= 27 | INDEXER_SERVER=https://mainnet-idx.algonode.network/ 28 | INDEXER_PORT=443 29 | 30 | # Example database (sqlite) 31 | DATABASE_URL=file:./data/data.db 32 | 33 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the repository to show as TypeScript rather than JS in GitHub 2 | *.js linguist-detectable=false 3 | 4 | # Treat text as lf 5 | * text=auto 6 | * eol=lf 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41C Bug report" 3 | about: Report a reproducible bug. 4 | title: '' 5 | labels: new-bug 6 | assignees: '' 7 | --- 8 | 9 | ### Subject of the issue 10 | 11 | 12 | 13 | ### Your environment 14 | 15 | 19 | 20 | ### Steps to reproduce 21 | 22 | 1. 23 | 2. 24 | 25 | ### Expected behaviour 26 | 27 | ### Actual behaviour 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F514 Feature Request" 3 | about: Suggestions for how we can improve the algorand platform. 4 | title: '' 5 | labels: new-feature-request 6 | assignees: '' 7 | --- 8 | 9 | ## Problem 10 | 11 | 12 | 13 | ## Solution 14 | 15 | 16 | 17 | ### Proposal 18 | 19 | 20 | 21 | ### Pros and Cons 22 | 23 | 24 | 25 | ## Dependencies 26 | 27 | 28 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: 'npm' 6 | directory: '/' 7 | schedule: 8 | interval: 'weekly' 9 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Proposed Changes 2 | 3 | - 4 | - 5 | - 6 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | 7 | permissions: 8 | contents: read 9 | checks: write 10 | 11 | jobs: 12 | pull_request: 13 | uses: makerxstudio/shared-config/.github/workflows/node-ci.yml@main 14 | with: 15 | node-version: 20.x 16 | working-directory: ./ 17 | run-commit-lint: true 18 | run-build: true 19 | pre-test-script: | 20 | pipx install algokit 21 | algokit localnet start 22 | npx --yes wait-on tcp:4001 -t 30000 23 | audit-script: | 24 | npm run audit 25 | check_docs: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Clone repository 29 | uses: actions/checkout@v3 30 | with: 31 | fetch-depth: 1 32 | - name: Check docs are up to date 33 | shell: bash 34 | run: | 35 | npm ci --ignore-scripts 36 | npm run generate:code-docs 37 | # Add untracked files as empty so they come up in diff 38 | git add -N . 39 | # Print changed files and error out if there are changes after generating docs 40 | git diff --exit-code --name-only 41 | -------------------------------------------------------------------------------- /.github/workflows/prod_release.yml: -------------------------------------------------------------------------------- 1 | name: Prod Publish 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | permissions: 7 | contents: write 8 | 9 | jobs: 10 | prod_release: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Clone repository 14 | uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 17 | - name: Merge main -> release 18 | uses: devmasx/merge-branch@854d3ac71ed1e9deb668e0074781b81fdd6e771f 19 | with: 20 | type: now 21 | from_branch: main 22 | target_branch: release 23 | github_token: ${{ secrets.GITHUB_TOKEN }} 24 | - name: Merge release -> main 25 | uses: devmasx/merge-branch@854d3ac71ed1e9deb668e0074781b81fdd6e771f 26 | with: 27 | type: now 28 | from_branch: release 29 | target_branch: main 30 | message: Merge release back to main to get version increment [no ci] 31 | github_token: ${{ secrets.GITHUB_TOKEN }} 32 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - release 8 | workflow_dispatch: 9 | 10 | concurrency: release 11 | 12 | permissions: 13 | contents: write 14 | issues: write 15 | checks: write 16 | 17 | jobs: 18 | ci: 19 | name: Continuous Integration 20 | uses: makerxstudio/shared-config/.github/workflows/node-ci.yml@main 21 | with: 22 | node-version: 20.x 23 | run-commit-lint: true 24 | pre-test-script: | 25 | pipx install algokit 26 | algokit localnet start 27 | npx --yes wait-on tcp:4001 -t 30000 28 | audit-script: | 29 | npm run audit 30 | 31 | check_docs: 32 | runs-on: ubuntu-latest 33 | steps: 34 | - name: Clone repository 35 | uses: actions/checkout@v3 36 | with: 37 | fetch-depth: 1 38 | - name: Check docs are up to date 39 | shell: bash 40 | run: | 41 | npm ci --ignore-scripts 42 | npm run generate:code-docs 43 | # Add untracked files as empty so they come up in diff 44 | git add -N . 45 | # Print changed files and error out if there are changes after generating docs 46 | git diff --exit-code --name-only 47 | 48 | build: 49 | name: Build 50 | uses: makerxstudio/shared-config/.github/workflows/node-build-zip.yml@main 51 | needs: 52 | - ci 53 | - check_docs 54 | with: 55 | node-version: 20.x 56 | build-path: dist 57 | artifact-name: package 58 | 59 | release: 60 | name: Release 61 | needs: build 62 | runs-on: ubuntu-latest 63 | steps: 64 | - name: Clone repository 65 | uses: actions/checkout@v3 66 | with: 67 | fetch-depth: 0 68 | 69 | # semantic-release needs node 20 70 | - name: Use Node.js 20.x 71 | uses: actions/setup-node@v3 72 | with: 73 | node-version: 20.x 74 | 75 | - name: Download built package 76 | uses: actions/download-artifact@v4 77 | with: 78 | name: package 79 | path: artifacts 80 | 81 | - name: Unzip package 82 | shell: bash 83 | run: | 84 | mkdir -p dist 85 | unzip -q "artifacts/package.zip" -d dist 86 | 87 | - name: Install dependencies to get semantic release components and plugins 88 | run: npm ci --ignore-scripts 89 | 90 | - name: 'Semantic release' 91 | run: npx semantic-release 92 | env: 93 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 94 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 95 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | # Dependency directories 11 | node_modules/ 12 | 13 | # TypeScript cache 14 | *.tsbuildinfo 15 | 16 | # Optional npm cache directory 17 | .npm 18 | 19 | # Optional eslint cache 20 | .eslintcache 21 | 22 | # Stores VSCode versions used for testing VSCode extensions 23 | .vscode-test 24 | 25 | # Editor/OS directories and files 26 | .DS_Store 27 | *.suo 28 | 29 | # Jetbrains 30 | .idea/shelf/ 31 | .idea/workspace.xml 32 | # Editor-based HTTP Client requests 33 | .idea/httpRequests/ 34 | # Datasource local storage ignored files 35 | .idea/dataSources/ 36 | .idea/dataSources.local.xml 37 | 38 | # yarn v2 39 | .yarn/cache 40 | .yarn/unplugged 41 | .yarn/build-state.yml 42 | .yarn/install-state.gz 43 | .pnp.* 44 | 45 | # Compiled code 46 | dist/ 47 | build/ 48 | 49 | # Coverage report 50 | coverage 51 | 52 | # Website & Code docs generation 53 | code-docs/ 54 | out/ 55 | 56 | # dotenv environment variable files 57 | .env 58 | .env.development.local 59 | .env.test.local 60 | .env.production.local 61 | .env.local 62 | 63 | watermark.txt 64 | examples/**/*.json 65 | !examples/**/*.arc32.json 66 | 67 | __pycache__ 68 | 69 | *.db 70 | *.db-journal 71 | -------------------------------------------------------------------------------- /.nsprc: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # don't ever format node_modules 2 | node_modules 3 | # don't lint format output (make sure it's set to your correct build folder name) 4 | dist 5 | build 6 | # don't format nyc coverage output 7 | coverage 8 | # don't format generated types 9 | **/generated/types.d.ts 10 | **/generated/types.ts 11 | # don't format ide files 12 | .idea 13 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require('@makerx/prettier-config'), 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "esbenp.prettier-vscode", 4 | "mikestead.dotenv", 5 | "EditorConfig.EditorConfig", 6 | "vitest.explorer", 7 | "dbaeumer.vscode-eslint", 8 | "Prisma.prisma" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Run examples/data-history-museum", 6 | "type": "node", 7 | "request": "launch", 8 | "runtimeExecutable": "npm", 9 | "runtimeArgs": ["run", "dhm"], 10 | "cwd": "${workspaceFolder}", 11 | "console": "integratedTerminal", 12 | "skipFiles": ["/**", "node_modules/**"], 13 | "presentation": { 14 | "hidden": false, 15 | "group": "2. Main", 16 | "order": 1 17 | } 18 | }, 19 | { 20 | "name": "Run examples/data-history-museum (loop)", 21 | "type": "node", 22 | "request": "launch", 23 | "runtimeExecutable": "npm", 24 | "runtimeArgs": ["run", "watch-dhm"], 25 | "cwd": "${workspaceFolder}", 26 | "console": "integratedTerminal", 27 | "skipFiles": ["/**", "node_modules/**"], 28 | "presentation": { 29 | "hidden": false, 30 | "group": "2. Main", 31 | "order": 1 32 | } 33 | }, 34 | { 35 | "name": "Run examples/xgov-voting", 36 | "type": "node", 37 | "request": "launch", 38 | "runtimeExecutable": "npm", 39 | "runtimeArgs": ["run", "xgov"], 40 | "cwd": "${workspaceFolder}", 41 | "console": "integratedTerminal", 42 | "skipFiles": ["/**", "node_modules/**"], 43 | "presentation": { 44 | "hidden": false, 45 | "group": "2. Main", 46 | "order": 1 47 | } 48 | }, 49 | { 50 | "name": "Run examples/xgov-voting (loop)", 51 | "type": "node", 52 | "request": "launch", 53 | "runtimeExecutable": "npm", 54 | "runtimeArgs": ["run", "watch-xgov"], 55 | "cwd": "${workspaceFolder}", 56 | "console": "integratedTerminal", 57 | "skipFiles": ["/**", "node_modules/**"], 58 | "presentation": { 59 | "hidden": false, 60 | "group": "2. Main", 61 | "order": 1 62 | } 63 | }, 64 | 65 | { 66 | "name": "Run examples/usdc (loop)", 67 | "type": "node", 68 | "request": "launch", 69 | "runtimeExecutable": "npm", 70 | "runtimeArgs": ["run", "usdc"], 71 | "cwd": "${workspaceFolder}", 72 | "console": "integratedTerminal", 73 | "skipFiles": ["/**", "node_modules/**"], 74 | "presentation": { 75 | "hidden": false, 76 | "group": "2. Main", 77 | "order": 1 78 | } 79 | } 80 | ] 81 | } 82 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.eol": "\n", 3 | "[prisma]": { 4 | "editor.defaultFormatter": "Prisma.prisma" 5 | }, 6 | "editor.formatOnSave": true, 7 | "editor.defaultFormatter": "esbenp.prettier-vscode", 8 | "editor.codeActionsOnSave": { 9 | "source.organizeImports": "explicit" 10 | }, 11 | "jest.enable": false, 12 | "vitest.enable": true, 13 | "vitest.watchOnStartup": false, 14 | "vitest.showFailMessages": true, 15 | "files.associations": { 16 | ".github/**/*.yml": "github-actions-workflow" 17 | }, 18 | "eslint.enable": true, 19 | "eslint.validate": ["typescript", "typescriptreact", "graphql"], 20 | "eslint.options": { 21 | "extensions": [".ts", ".graphql"] 22 | }, 23 | "eslint.workingDirectories": ["./graphql"], 24 | 25 | // Python 26 | "python.defaultInterpreterPath": "${workspaceFolder}/tests/contract/.venv", 27 | "python.analysis.extraPaths": ["${workspaceFolder}/tests/contract"], 28 | "[python]": { 29 | "editor.defaultFormatter": "charliermarsh.ruff" 30 | }, 31 | "python.analysis.typeCheckingMode": "basic", 32 | "ruff.enable": true, 33 | "ruff.lint.run": "onSave", 34 | "ruff.lint.args": ["--config=${workspaceFolder}/tests/contract/pyproject.toml"], 35 | "ruff.importStrategy": "fromEnvironment", 36 | "ruff.fixAll": true, //lint and fix all files in workspace 37 | "ruff.organizeImports": true, //organize imports on save 38 | "ruff.codeAction.disableRuleComment": { 39 | "enable": true 40 | }, 41 | "ruff.codeAction.fixViolation": { 42 | "enable": true 43 | }, 44 | 45 | "mypy.configFile": "tests/contract/pyproject.toml", 46 | // set to empty array to use config from project 47 | "mypy.targets": [], 48 | "mypy.runUsingActiveInterpreter": true, 49 | "mypy.enabled": true, 50 | "python.testing.unittestEnabled": false, 51 | "python.testing.pytestEnabled": false 52 | } 53 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "localstack + localnet", 6 | "command": "docker", 7 | "args": ["compose", "up", "-d"], 8 | "type": "shell", 9 | "dependsOn": ["Start AlgoKit LocalNet"], 10 | "options": { 11 | "cwd": "${workspaceFolder}" 12 | }, 13 | "problemMatcher": [] 14 | }, 15 | { 16 | "label": "Start AlgoKit LocalNet", 17 | "command": "algokit", 18 | "args": ["localnet", "start"], 19 | "type": "shell", 20 | "options": { 21 | "cwd": "${workspaceFolder}" 22 | }, 23 | "problemMatcher": [] 24 | }, 25 | { 26 | "label": "Stop AlgoKit LocalNet", 27 | "command": "algokit", 28 | "args": ["localnet", "stop"], 29 | "type": "shell", 30 | "options": { 31 | "cwd": "${workspaceFolder}" 32 | }, 33 | "problemMatcher": [] 34 | }, 35 | { 36 | "label": "Reset AlgoKit LocalNet", 37 | "command": "algokit", 38 | "args": ["localnet", "reset"], 39 | "type": "shell", 40 | "options": { 41 | "cwd": "${workspaceFolder}" 42 | }, 43 | "problemMatcher": [] 44 | }, 45 | { 46 | "label": "npm: cdk bootstrap - /infrastructure", 47 | "type": "npm", 48 | "script": "bootstrap:local", 49 | "path": "${workspaceFolder}/infrastructure", 50 | "options": { 51 | "cwd": "${workspaceFolder}/infrastructure" 52 | }, 53 | "problemMatcher": [] 54 | } 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Algorand Foundation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /commitlint.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | // Allow sentence case commit messages 5 | 'subject-case': [1, 'always', ['pascal-case', 'upper-case']], 6 | 'type-empty': [1, 'never'], 7 | 'subject-empty': [1, 'always'], 8 | 'body-leading-blank': [0, 'always'], 9 | 'body-max-line-length': [1, 'always', 200], 10 | 'header-max-length': [1, 'always', 150], 11 | 'footer-max-length': [1, 'always', 150], 12 | 'footer-max-line-length': [1, 'always', 150], 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /docs/code/README.md: -------------------------------------------------------------------------------- 1 | @algorandfoundation/algokit-subscriber 2 | 3 | # @algorandfoundation/algokit-subscriber 4 | 5 | ## Table of contents 6 | 7 | ### Modules 8 | 9 | - [index](modules/index.md) 10 | - [types](modules/types.md) 11 | - [types/arc-28](modules/types_arc_28.md) 12 | - [types/async-event-emitter](modules/types_async_event_emitter.md) 13 | - [types/block](modules/types_block.md) 14 | - [types/subscription](modules/types_subscription.md) 15 | -------------------------------------------------------------------------------- /docs/code/classes/types_async_event_emitter.AsyncEventEmitter.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / [types/async-event-emitter](../modules/types_async_event_emitter.md) / AsyncEventEmitter 2 | 3 | # Class: AsyncEventEmitter 4 | 5 | [types/async-event-emitter](../modules/types_async_event_emitter.md).AsyncEventEmitter 6 | 7 | Simple asynchronous event emitter class. 8 | 9 | **Note:** This class is not thread-safe. 10 | 11 | ## Table of contents 12 | 13 | ### Constructors 14 | 15 | - [constructor](types_async_event_emitter.AsyncEventEmitter.md#constructor) 16 | 17 | ### Properties 18 | 19 | - [listenerMap](types_async_event_emitter.AsyncEventEmitter.md#listenermap) 20 | - [listenerWrapperMap](types_async_event_emitter.AsyncEventEmitter.md#listenerwrappermap) 21 | - [off](types_async_event_emitter.AsyncEventEmitter.md#off) 22 | 23 | ### Methods 24 | 25 | - [emitAsync](types_async_event_emitter.AsyncEventEmitter.md#emitasync) 26 | - [on](types_async_event_emitter.AsyncEventEmitter.md#on) 27 | - [once](types_async_event_emitter.AsyncEventEmitter.md#once) 28 | - [removeListener](types_async_event_emitter.AsyncEventEmitter.md#removelistener) 29 | 30 | ## Constructors 31 | 32 | ### constructor 33 | 34 | • **new AsyncEventEmitter**(): [`AsyncEventEmitter`](types_async_event_emitter.AsyncEventEmitter.md) 35 | 36 | #### Returns 37 | 38 | [`AsyncEventEmitter`](types_async_event_emitter.AsyncEventEmitter.md) 39 | 40 | ## Properties 41 | 42 | ### listenerMap 43 | 44 | • `Private` **listenerMap**: `Record`\<`string` \| `symbol`, [`AsyncEventListener`](../modules/types_async_event_emitter.md#asynceventlistener)[]\> = `{}` 45 | 46 | #### Defined in 47 | 48 | [src/types/async-event-emitter.ts:12](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/async-event-emitter.ts#L12) 49 | 50 | ___ 51 | 52 | ### listenerWrapperMap 53 | 54 | • `Private` **listenerWrapperMap**: `WeakMap`\<[`AsyncEventListener`](../modules/types_async_event_emitter.md#asynceventlistener), [`AsyncEventListener`](../modules/types_async_event_emitter.md#asynceventlistener)\> 55 | 56 | #### Defined in 57 | 58 | [src/types/async-event-emitter.ts:11](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/async-event-emitter.ts#L11) 59 | 60 | ___ 61 | 62 | ### off 63 | 64 | • **off**: (`eventName`: `string` \| `symbol`, `listener`: [`AsyncEventListener`](../modules/types_async_event_emitter.md#asynceventlistener)) => [`AsyncEventEmitter`](types_async_event_emitter.AsyncEventEmitter.md) 65 | 66 | Alias for `removeListener`. 67 | 68 | #### Type declaration 69 | 70 | ▸ (`eventName`, `listener`): [`AsyncEventEmitter`](types_async_event_emitter.AsyncEventEmitter.md) 71 | 72 | ##### Parameters 73 | 74 | | Name | Type | 75 | | :------ | :------ | 76 | | `eventName` | `string` \| `symbol` | 77 | | `listener` | [`AsyncEventListener`](../modules/types_async_event_emitter.md#asynceventlistener) | 78 | 79 | ##### Returns 80 | 81 | [`AsyncEventEmitter`](types_async_event_emitter.AsyncEventEmitter.md) 82 | 83 | #### Defined in 84 | 85 | [src/types/async-event-emitter.ts:82](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/async-event-emitter.ts#L82) 86 | 87 | ## Methods 88 | 89 | ### emitAsync 90 | 91 | ▸ **emitAsync**(`eventName`, `event`): `Promise`\<`void`\> 92 | 93 | Emit an event and wait for all registered listeners to be run one-by-one 94 | in the order they were registered. 95 | 96 | #### Parameters 97 | 98 | | Name | Type | Description | 99 | | :------ | :------ | :------ | 100 | | `eventName` | `string` \| `symbol` | The name of the event | 101 | | `event` | `unknown` | The event payload | 102 | 103 | #### Returns 104 | 105 | `Promise`\<`void`\> 106 | 107 | #### Defined in 108 | 109 | [src/types/async-event-emitter.ts:21](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/async-event-emitter.ts#L21) 110 | 111 | ___ 112 | 113 | ### on 114 | 115 | ▸ **on**(`eventName`, `listener`): [`AsyncEventEmitter`](types_async_event_emitter.AsyncEventEmitter.md) 116 | 117 | Register an event listener for the given event. 118 | 119 | #### Parameters 120 | 121 | | Name | Type | Description | 122 | | :------ | :------ | :------ | 123 | | `eventName` | `string` \| `symbol` | The name of the event | 124 | | `listener` | [`AsyncEventListener`](../modules/types_async_event_emitter.md#asynceventlistener) | The listener to trigger | 125 | 126 | #### Returns 127 | 128 | [`AsyncEventEmitter`](types_async_event_emitter.AsyncEventEmitter.md) 129 | 130 | The `AsyncEventEmitter` so you can chain registrations 131 | 132 | #### Defined in 133 | 134 | [src/types/async-event-emitter.ts:33](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/async-event-emitter.ts#L33) 135 | 136 | ___ 137 | 138 | ### once 139 | 140 | ▸ **once**(`eventName`, `listener`): [`AsyncEventEmitter`](types_async_event_emitter.AsyncEventEmitter.md) 141 | 142 | Register an event listener for the given event that is only fired once. 143 | 144 | #### Parameters 145 | 146 | | Name | Type | Description | 147 | | :------ | :------ | :------ | 148 | | `eventName` | `string` \| `symbol` | The name of the event | 149 | | `listener` | [`AsyncEventListener`](../modules/types_async_event_emitter.md#asynceventlistener) | The listener to trigger | 150 | 151 | #### Returns 152 | 153 | [`AsyncEventEmitter`](types_async_event_emitter.AsyncEventEmitter.md) 154 | 155 | The `AsyncEventEmitter` so you can chain registrations 156 | 157 | #### Defined in 158 | 159 | [src/types/async-event-emitter.ts:45](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/async-event-emitter.ts#L45) 160 | 161 | ___ 162 | 163 | ### removeListener 164 | 165 | ▸ **removeListener**(`eventName`, `listener`): [`AsyncEventEmitter`](types_async_event_emitter.AsyncEventEmitter.md) 166 | 167 | Removes an event listener from the given event. 168 | 169 | #### Parameters 170 | 171 | | Name | Type | Description | 172 | | :------ | :------ | :------ | 173 | | `eventName` | `string` \| `symbol` | The name of the event | 174 | | `listener` | [`AsyncEventListener`](../modules/types_async_event_emitter.md#asynceventlistener) | The listener to remove | 175 | 176 | #### Returns 177 | 178 | [`AsyncEventEmitter`](types_async_event_emitter.AsyncEventEmitter.md) 179 | 180 | The `AsyncEventEmitter` so you can chain registrations 181 | 182 | #### Defined in 183 | 184 | [src/types/async-event-emitter.ts:63](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/async-event-emitter.ts#L63) 185 | -------------------------------------------------------------------------------- /docs/code/enums/types_subscription.BalanceChangeRole.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / [types/subscription](../modules/types_subscription.md) / BalanceChangeRole 2 | 3 | # Enumeration: BalanceChangeRole 4 | 5 | [types/subscription](../modules/types_subscription.md).BalanceChangeRole 6 | 7 | The role that an account was playing for a given balance change. 8 | 9 | ## Table of contents 10 | 11 | ### Enumeration Members 12 | 13 | - [AssetCreator](types_subscription.BalanceChangeRole.md#assetcreator) 14 | - [AssetDestroyer](types_subscription.BalanceChangeRole.md#assetdestroyer) 15 | - [CloseTo](types_subscription.BalanceChangeRole.md#closeto) 16 | - [Receiver](types_subscription.BalanceChangeRole.md#receiver) 17 | - [Sender](types_subscription.BalanceChangeRole.md#sender) 18 | 19 | ## Enumeration Members 20 | 21 | ### AssetCreator 22 | 23 | • **AssetCreator** = ``"AssetCreator"`` 24 | 25 | Account was creating an asset and holds the full asset supply 26 | 27 | #### Defined in 28 | 29 | [src/types/subscription.ts:220](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L220) 30 | 31 | ___ 32 | 33 | ### AssetDestroyer 34 | 35 | • **AssetDestroyer** = ``"AssetDestroyer"`` 36 | 37 | Account was destroying an asset and has removed the full asset supply from circulation. 38 | A balance change with this role will always have a 0 amount and use the asset manager address. 39 | 40 | #### Defined in 41 | 42 | [src/types/subscription.ts:224](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L224) 43 | 44 | ___ 45 | 46 | ### CloseTo 47 | 48 | • **CloseTo** = ``"CloseTo"`` 49 | 50 | Account was having an asset amount closed to it 51 | 52 | #### Defined in 53 | 54 | [src/types/subscription.ts:218](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L218) 55 | 56 | ___ 57 | 58 | ### Receiver 59 | 60 | • **Receiver** = ``"Receiver"`` 61 | 62 | Account was receiving a transaction 63 | 64 | #### Defined in 65 | 66 | [src/types/subscription.ts:216](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L216) 67 | 68 | ___ 69 | 70 | ### Sender 71 | 72 | • **Sender** = ``"Sender"`` 73 | 74 | Account was sending a transaction (sending asset and/or spending fee if asset `0`) 75 | 76 | #### Defined in 77 | 78 | [src/types/subscription.ts:214](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L214) 79 | -------------------------------------------------------------------------------- /docs/code/interfaces/types_arc_28.Arc28Event.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / [types/arc-28](../modules/types_arc_28.md) / Arc28Event 2 | 3 | # Interface: Arc28Event 4 | 5 | [types/arc-28](../modules/types_arc_28.md).Arc28Event 6 | 7 | The definition of metadata for an ARC-28 event per https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0028.md#event. 8 | 9 | ## Table of contents 10 | 11 | ### Properties 12 | 13 | - [args](types_arc_28.Arc28Event.md#args) 14 | - [desc](types_arc_28.Arc28Event.md#desc) 15 | - [name](types_arc_28.Arc28Event.md#name) 16 | 17 | ## Properties 18 | 19 | ### args 20 | 21 | • **args**: \{ `desc?`: `string` ; `name?`: `string` ; `type`: `string` }[] 22 | 23 | The arguments of the event, in order 24 | 25 | #### Defined in 26 | 27 | [src/types/arc-28.ts:14](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/arc-28.ts#L14) 28 | 29 | ___ 30 | 31 | ### desc 32 | 33 | • `Optional` **desc**: `string` 34 | 35 | Optional, user-friendly description for the event 36 | 37 | #### Defined in 38 | 39 | [src/types/arc-28.ts:12](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/arc-28.ts#L12) 40 | 41 | ___ 42 | 43 | ### name 44 | 45 | • **name**: `string` 46 | 47 | The name of the event 48 | 49 | #### Defined in 50 | 51 | [src/types/arc-28.ts:10](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/arc-28.ts#L10) 52 | -------------------------------------------------------------------------------- /docs/code/interfaces/types_arc_28.Arc28EventGroup.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / [types/arc-28](../modules/types_arc_28.md) / Arc28EventGroup 2 | 3 | # Interface: Arc28EventGroup 4 | 5 | [types/arc-28](../modules/types_arc_28.md).Arc28EventGroup 6 | 7 | Specifies a group of ARC-28 event definitions along with instructions for when to attempt to process the events. 8 | 9 | ## Table of contents 10 | 11 | ### Properties 12 | 13 | - [continueOnError](types_arc_28.Arc28EventGroup.md#continueonerror) 14 | - [events](types_arc_28.Arc28EventGroup.md#events) 15 | - [groupName](types_arc_28.Arc28EventGroup.md#groupname) 16 | - [processForAppIds](types_arc_28.Arc28EventGroup.md#processforappids) 17 | - [processTransaction](types_arc_28.Arc28EventGroup.md#processtransaction) 18 | 19 | ## Properties 20 | 21 | ### continueOnError 22 | 23 | • `Optional` **continueOnError**: `boolean` 24 | 25 | Whether or not to silently (with warning log) continue if an error is encountered processing the ARC-28 event data; default = false 26 | 27 | #### Defined in 28 | 29 | [src/types/arc-28.ts:55](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/arc-28.ts#L55) 30 | 31 | ___ 32 | 33 | ### events 34 | 35 | • **events**: [`Arc28Event`](types_arc_28.Arc28Event.md)[] 36 | 37 | The list of ARC-28 event definitions 38 | 39 | #### Defined in 40 | 41 | [src/types/arc-28.ts:57](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/arc-28.ts#L57) 42 | 43 | ___ 44 | 45 | ### groupName 46 | 47 | • **groupName**: `string` 48 | 49 | The name to designate for this group of events. 50 | 51 | #### Defined in 52 | 53 | [src/types/arc-28.ts:49](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/arc-28.ts#L49) 54 | 55 | ___ 56 | 57 | ### processForAppIds 58 | 59 | • `Optional` **processForAppIds**: `bigint`[] 60 | 61 | Optional list of app IDs that this event should apply to 62 | 63 | #### Defined in 64 | 65 | [src/types/arc-28.ts:51](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/arc-28.ts#L51) 66 | 67 | ___ 68 | 69 | ### processTransaction 70 | 71 | • `Optional` **processTransaction**: (`transaction`: [`SubscribedTransaction`](../classes/types_subscription.SubscribedTransaction.md)) => `boolean` 72 | 73 | Optional predicate to indicate if these ARC-28 events should be processed for the given transaction 74 | 75 | #### Type declaration 76 | 77 | ▸ (`transaction`): `boolean` 78 | 79 | ##### Parameters 80 | 81 | | Name | Type | 82 | | :------ | :------ | 83 | | `transaction` | [`SubscribedTransaction`](../classes/types_subscription.SubscribedTransaction.md) | 84 | 85 | ##### Returns 86 | 87 | `boolean` 88 | 89 | #### Defined in 90 | 91 | [src/types/arc-28.ts:53](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/arc-28.ts#L53) 92 | -------------------------------------------------------------------------------- /docs/code/interfaces/types_arc_28.Arc28EventToProcess.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / [types/arc-28](../modules/types_arc_28.md) / Arc28EventToProcess 2 | 3 | # Interface: Arc28EventToProcess 4 | 5 | [types/arc-28](../modules/types_arc_28.md).Arc28EventToProcess 6 | 7 | An ARC-28 event to be processed 8 | 9 | ## Hierarchy 10 | 11 | - **`Arc28EventToProcess`** 12 | 13 | ↳ [`EmittedArc28Event`](types_arc_28.EmittedArc28Event.md) 14 | 15 | ## Table of contents 16 | 17 | ### Properties 18 | 19 | - [eventDefinition](types_arc_28.Arc28EventToProcess.md#eventdefinition) 20 | - [eventName](types_arc_28.Arc28EventToProcess.md#eventname) 21 | - [eventPrefix](types_arc_28.Arc28EventToProcess.md#eventprefix) 22 | - [eventSignature](types_arc_28.Arc28EventToProcess.md#eventsignature) 23 | - [groupName](types_arc_28.Arc28EventToProcess.md#groupname) 24 | 25 | ## Properties 26 | 27 | ### eventDefinition 28 | 29 | • **eventDefinition**: [`Arc28Event`](types_arc_28.Arc28Event.md) 30 | 31 | The ARC-28 definition of the event 32 | 33 | #### Defined in 34 | 35 | [src/types/arc-28.ts:35](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/arc-28.ts#L35) 36 | 37 | ___ 38 | 39 | ### eventName 40 | 41 | • **eventName**: `string` 42 | 43 | The name of the ARC-28 event that was triggered 44 | 45 | #### Defined in 46 | 47 | [src/types/arc-28.ts:29](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/arc-28.ts#L29) 48 | 49 | ___ 50 | 51 | ### eventPrefix 52 | 53 | • **eventPrefix**: `string` 54 | 55 | The 4-byte hex prefix for the event 56 | 57 | #### Defined in 58 | 59 | [src/types/arc-28.ts:33](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/arc-28.ts#L33) 60 | 61 | ___ 62 | 63 | ### eventSignature 64 | 65 | • **eventSignature**: `string` 66 | 67 | The signature of the event e.g. `EventName(type1,type2)` 68 | 69 | #### Defined in 70 | 71 | [src/types/arc-28.ts:31](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/arc-28.ts#L31) 72 | 73 | ___ 74 | 75 | ### groupName 76 | 77 | • **groupName**: `string` 78 | 79 | The name of the ARC-28 event group the event belongs to 80 | 81 | #### Defined in 82 | 83 | [src/types/arc-28.ts:27](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/arc-28.ts#L27) 84 | -------------------------------------------------------------------------------- /docs/code/interfaces/types_arc_28.EmittedArc28Event.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / [types/arc-28](../modules/types_arc_28.md) / EmittedArc28Event 2 | 3 | # Interface: EmittedArc28Event 4 | 5 | [types/arc-28](../modules/types_arc_28.md).EmittedArc28Event 6 | 7 | An emitted ARC-28 event extracted from an app call log. 8 | 9 | ## Hierarchy 10 | 11 | - [`Arc28EventToProcess`](types_arc_28.Arc28EventToProcess.md) 12 | 13 | ↳ **`EmittedArc28Event`** 14 | 15 | ## Table of contents 16 | 17 | ### Properties 18 | 19 | - [args](types_arc_28.EmittedArc28Event.md#args) 20 | - [argsByName](types_arc_28.EmittedArc28Event.md#argsbyname) 21 | - [eventDefinition](types_arc_28.EmittedArc28Event.md#eventdefinition) 22 | - [eventName](types_arc_28.EmittedArc28Event.md#eventname) 23 | - [eventPrefix](types_arc_28.EmittedArc28Event.md#eventprefix) 24 | - [eventSignature](types_arc_28.EmittedArc28Event.md#eventsignature) 25 | - [groupName](types_arc_28.EmittedArc28Event.md#groupname) 26 | 27 | ## Properties 28 | 29 | ### args 30 | 31 | • **args**: `ABIValue`[] 32 | 33 | The ordered arguments extracted from the event that was emitted 34 | 35 | #### Defined in 36 | 37 | [src/types/arc-28.ts:41](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/arc-28.ts#L41) 38 | 39 | ___ 40 | 41 | ### argsByName 42 | 43 | • **argsByName**: `Record`\<`string`, `ABIValue`\> 44 | 45 | The named arguments extracted from the event that was emitted (where the arguments had a name defined) 46 | 47 | #### Defined in 48 | 49 | [src/types/arc-28.ts:43](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/arc-28.ts#L43) 50 | 51 | ___ 52 | 53 | ### eventDefinition 54 | 55 | • **eventDefinition**: [`Arc28Event`](types_arc_28.Arc28Event.md) 56 | 57 | The ARC-28 definition of the event 58 | 59 | #### Inherited from 60 | 61 | [Arc28EventToProcess](types_arc_28.Arc28EventToProcess.md).[eventDefinition](types_arc_28.Arc28EventToProcess.md#eventdefinition) 62 | 63 | #### Defined in 64 | 65 | [src/types/arc-28.ts:35](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/arc-28.ts#L35) 66 | 67 | ___ 68 | 69 | ### eventName 70 | 71 | • **eventName**: `string` 72 | 73 | The name of the ARC-28 event that was triggered 74 | 75 | #### Inherited from 76 | 77 | [Arc28EventToProcess](types_arc_28.Arc28EventToProcess.md).[eventName](types_arc_28.Arc28EventToProcess.md#eventname) 78 | 79 | #### Defined in 80 | 81 | [src/types/arc-28.ts:29](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/arc-28.ts#L29) 82 | 83 | ___ 84 | 85 | ### eventPrefix 86 | 87 | • **eventPrefix**: `string` 88 | 89 | The 4-byte hex prefix for the event 90 | 91 | #### Inherited from 92 | 93 | [Arc28EventToProcess](types_arc_28.Arc28EventToProcess.md).[eventPrefix](types_arc_28.Arc28EventToProcess.md#eventprefix) 94 | 95 | #### Defined in 96 | 97 | [src/types/arc-28.ts:33](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/arc-28.ts#L33) 98 | 99 | ___ 100 | 101 | ### eventSignature 102 | 103 | • **eventSignature**: `string` 104 | 105 | The signature of the event e.g. `EventName(type1,type2)` 106 | 107 | #### Inherited from 108 | 109 | [Arc28EventToProcess](types_arc_28.Arc28EventToProcess.md).[eventSignature](types_arc_28.Arc28EventToProcess.md#eventsignature) 110 | 111 | #### Defined in 112 | 113 | [src/types/arc-28.ts:31](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/arc-28.ts#L31) 114 | 115 | ___ 116 | 117 | ### groupName 118 | 119 | • **groupName**: `string` 120 | 121 | The name of the ARC-28 event group the event belongs to 122 | 123 | #### Inherited from 124 | 125 | [Arc28EventToProcess](types_arc_28.Arc28EventToProcess.md).[groupName](types_arc_28.Arc28EventToProcess.md#groupname) 126 | 127 | #### Defined in 128 | 129 | [src/types/arc-28.ts:27](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/arc-28.ts#L27) 130 | -------------------------------------------------------------------------------- /docs/code/interfaces/types_block.TransactionInBlock.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / [types/block](../modules/types_block.md) / TransactionInBlock 2 | 3 | # Interface: TransactionInBlock 4 | 5 | [types/block](../modules/types_block.md).TransactionInBlock 6 | 7 | The representation of all important data for a single transaction or inner transaction 8 | and its side effects within a committed block. 9 | 10 | ## Table of contents 11 | 12 | ### Properties 13 | 14 | - [assetCloseAmount](types_block.TransactionInBlock.md#assetcloseamount) 15 | - [closeAmount](types_block.TransactionInBlock.md#closeamount) 16 | - [closeRewards](types_block.TransactionInBlock.md#closerewards) 17 | - [createdAppId](types_block.TransactionInBlock.md#createdappid) 18 | - [createdAssetId](types_block.TransactionInBlock.md#createdassetid) 19 | - [genesisHash](types_block.TransactionInBlock.md#genesishash) 20 | - [genesisId](types_block.TransactionInBlock.md#genesisid) 21 | - [intraRoundOffset](types_block.TransactionInBlock.md#intraroundoffset) 22 | - [logs](types_block.TransactionInBlock.md#logs) 23 | - [parentIntraRoundOffset](types_block.TransactionInBlock.md#parentintraroundoffset) 24 | - [parentTransactionId](types_block.TransactionInBlock.md#parenttransactionid) 25 | - [receiverRewards](types_block.TransactionInBlock.md#receiverrewards) 26 | - [roundNumber](types_block.TransactionInBlock.md#roundnumber) 27 | - [roundTimestamp](types_block.TransactionInBlock.md#roundtimestamp) 28 | - [senderRewards](types_block.TransactionInBlock.md#senderrewards) 29 | - [signedTxnWithAD](types_block.TransactionInBlock.md#signedtxnwithad) 30 | - [transaction](types_block.TransactionInBlock.md#transaction) 31 | - [transactionId](types_block.TransactionInBlock.md#transactionid) 32 | 33 | ## Properties 34 | 35 | ### assetCloseAmount 36 | 37 | • `Optional` **assetCloseAmount**: `bigint` 38 | 39 | The asset close amount if the sender asset position was closed from this transaction. 40 | 41 | #### Defined in 42 | 43 | [src/types/block.ts:61](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/block.ts#L61) 44 | 45 | ___ 46 | 47 | ### closeAmount 48 | 49 | • `Optional` **closeAmount**: `bigint` 50 | 51 | The ALGO close amount if the sender account was closed from this transaction. 52 | 53 | #### Defined in 54 | 55 | [src/types/block.ts:63](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/block.ts#L63) 56 | 57 | ___ 58 | 59 | ### closeRewards 60 | 61 | • `Optional` **closeRewards**: `bigint` 62 | 63 | Rewards in microalgos applied to the close remainder to account. 64 | 65 | #### Defined in 66 | 67 | [src/types/block.ts:67](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/block.ts#L67) 68 | 69 | ___ 70 | 71 | ### createdAppId 72 | 73 | • `Optional` **createdAppId**: `bigint` 74 | 75 | The app ID if an app was created from this transaction. 76 | 77 | #### Defined in 78 | 79 | [src/types/block.ts:59](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/block.ts#L59) 80 | 81 | ___ 82 | 83 | ### createdAssetId 84 | 85 | • `Optional` **createdAssetId**: `bigint` 86 | 87 | The asset ID if an asset was created from this transaction. 88 | 89 | #### Defined in 90 | 91 | [src/types/block.ts:57](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/block.ts#L57) 92 | 93 | ___ 94 | 95 | ### genesisHash 96 | 97 | • `Optional` **genesisHash**: `Buffer` 98 | 99 | The binary genesis hash of the network the transaction is within. 100 | 101 | #### Defined in 102 | 103 | [src/types/block.ts:47](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/block.ts#L47) 104 | 105 | ___ 106 | 107 | ### genesisId 108 | 109 | • `Optional` **genesisId**: `string` 110 | 111 | The string genesis ID of the network the transaction is within. 112 | 113 | #### Defined in 114 | 115 | [src/types/block.ts:49](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/block.ts#L49) 116 | 117 | ___ 118 | 119 | ### intraRoundOffset 120 | 121 | • **intraRoundOffset**: `number` 122 | 123 | The offset of the transaction within the round including inner transactions. 124 | 125 | **`Example`** 126 | 127 | ```ts 128 | - 0 129 | - 1 130 | - 2 131 | - 3 132 | - 4 133 | - 5 134 | ``` 135 | 136 | #### Defined in 137 | 138 | [src/types/block.ts:30](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/block.ts#L30) 139 | 140 | ___ 141 | 142 | ### logs 143 | 144 | • `Optional` **logs**: `Uint8Array`[] 145 | 146 | Any logs that were issued as a result of this transaction. 147 | 148 | #### Defined in 149 | 150 | [src/types/block.ts:65](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/block.ts#L65) 151 | 152 | ___ 153 | 154 | ### parentIntraRoundOffset 155 | 156 | • `Optional` **parentIntraRoundOffset**: `number` 157 | 158 | The intra-round offset of the parent transaction if this is an inner transaction. 159 | 160 | **`Example`** 161 | 162 | ```ts 163 | - 0 164 | - 1 165 | - 1 166 | - 1 167 | - 1 168 | - 2 169 | ``` 170 | 171 | #### Defined in 172 | 173 | [src/types/block.ts:41](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/block.ts#L41) 174 | 175 | ___ 176 | 177 | ### parentTransactionId 178 | 179 | • `Optional` **parentTransactionId**: `string` 180 | 181 | The ID of the parent transaction if this is an inner transaction. 182 | 183 | #### Defined in 184 | 185 | [src/types/block.ts:45](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/block.ts#L45) 186 | 187 | ___ 188 | 189 | ### receiverRewards 190 | 191 | • `Optional` **receiverRewards**: `bigint` 192 | 193 | Rewards in microalgos applied to the receiver account. 194 | 195 | #### Defined in 196 | 197 | [src/types/block.ts:71](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/block.ts#L71) 198 | 199 | ___ 200 | 201 | ### roundNumber 202 | 203 | • **roundNumber**: `bigint` 204 | 205 | The round number of the block the transaction is within. 206 | 207 | #### Defined in 208 | 209 | [src/types/block.ts:51](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/block.ts#L51) 210 | 211 | ___ 212 | 213 | ### roundTimestamp 214 | 215 | • **roundTimestamp**: `number` 216 | 217 | The round unix timestamp of the block the transaction is within. 218 | 219 | #### Defined in 220 | 221 | [src/types/block.ts:53](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/block.ts#L53) 222 | 223 | ___ 224 | 225 | ### senderRewards 226 | 227 | • `Optional` **senderRewards**: `bigint` 228 | 229 | Rewards in microalgos applied to the sender account. 230 | 231 | #### Defined in 232 | 233 | [src/types/block.ts:69](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/block.ts#L69) 234 | 235 | ___ 236 | 237 | ### signedTxnWithAD 238 | 239 | • **signedTxnWithAD**: `SignedTxnWithAD` 240 | 241 | The signed transaction with apply data from the block 242 | 243 | #### Defined in 244 | 245 | [src/types/block.ts:10](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/block.ts#L10) 246 | 247 | ___ 248 | 249 | ### transaction 250 | 251 | • **transaction**: `Transaction` 252 | 253 | The transaction as an algosdk `Transaction` object. 254 | 255 | #### Defined in 256 | 257 | [src/types/block.ts:55](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/block.ts#L55) 258 | 259 | ___ 260 | 261 | ### transactionId 262 | 263 | • **transactionId**: `string` 264 | 265 | The transaction ID 266 | 267 | **`Example`** 268 | 269 | ```ts 270 | - W6IG6SETWKISJV4JQSS6GNZGWKYXOOLH7FT3NQM4BIFRLCOXOQHA if it's a parent transaction 271 | - W6IG6SETWKISJV4JQSS6GNZGWKYXOOLH7FT3NQM4BIFRLCOXOQHA/inner/1 if it's an inner transaction 272 | ``` 273 | 274 | #### Defined in 275 | 276 | [src/types/block.ts:19](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/block.ts#L19) 277 | -------------------------------------------------------------------------------- /docs/code/interfaces/types_subscription.AlgorandSubscriberConfig.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / [types/subscription](../modules/types_subscription.md) / AlgorandSubscriberConfig 2 | 3 | # Interface: AlgorandSubscriberConfig 4 | 5 | [types/subscription](../modules/types_subscription.md).AlgorandSubscriberConfig 6 | 7 | Configuration for an `AlgorandSubscriber`. 8 | 9 | ## Hierarchy 10 | 11 | - [`CoreTransactionSubscriptionParams`](types_subscription.CoreTransactionSubscriptionParams.md) 12 | 13 | ↳ **`AlgorandSubscriberConfig`** 14 | 15 | ## Table of contents 16 | 17 | ### Properties 18 | 19 | - [arc28Events](types_subscription.AlgorandSubscriberConfig.md#arc28events) 20 | - [filters](types_subscription.AlgorandSubscriberConfig.md#filters) 21 | - [frequencyInSeconds](types_subscription.AlgorandSubscriberConfig.md#frequencyinseconds) 22 | - [maxIndexerRoundsToSync](types_subscription.AlgorandSubscriberConfig.md#maxindexerroundstosync) 23 | - [maxRoundsToSync](types_subscription.AlgorandSubscriberConfig.md#maxroundstosync) 24 | - [syncBehaviour](types_subscription.AlgorandSubscriberConfig.md#syncbehaviour) 25 | - [waitForBlockWhenAtTip](types_subscription.AlgorandSubscriberConfig.md#waitforblockwhenattip) 26 | - [watermarkPersistence](types_subscription.AlgorandSubscriberConfig.md#watermarkpersistence) 27 | 28 | ## Properties 29 | 30 | ### arc28Events 31 | 32 | • `Optional` **arc28Events**: [`Arc28EventGroup`](types_arc_28.Arc28EventGroup.md)[] 33 | 34 | Any ARC-28 event definitions to process from app call logs 35 | 36 | #### Inherited from 37 | 38 | [CoreTransactionSubscriptionParams](types_subscription.CoreTransactionSubscriptionParams.md).[arc28Events](types_subscription.CoreTransactionSubscriptionParams.md#arc28events) 39 | 40 | #### Defined in 41 | 42 | [src/types/subscription.ts:260](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L260) 43 | 44 | ___ 45 | 46 | ### filters 47 | 48 | • **filters**: [`SubscriberConfigFilter`](types_subscription.SubscriberConfigFilter.md)\<`unknown`\>[] 49 | 50 | The set of filters to subscribe to / emit events for, along with optional data mappers. 51 | 52 | #### Overrides 53 | 54 | [CoreTransactionSubscriptionParams](types_subscription.CoreTransactionSubscriptionParams.md).[filters](types_subscription.CoreTransactionSubscriptionParams.md#filters) 55 | 56 | #### Defined in 57 | 58 | [src/types/subscription.ts:388](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L388) 59 | 60 | ___ 61 | 62 | ### frequencyInSeconds 63 | 64 | • `Optional` **frequencyInSeconds**: `number` 65 | 66 | The frequency to poll for new blocks in seconds; defaults to 1s 67 | 68 | #### Defined in 69 | 70 | [src/types/subscription.ts:390](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L390) 71 | 72 | ___ 73 | 74 | ### maxIndexerRoundsToSync 75 | 76 | • `Optional` **maxIndexerRoundsToSync**: `number` 77 | 78 | The maximum number of rounds to sync from indexer when using `syncBehaviour: 'catchup-with-indexer'. 79 | 80 | By default there is no limit and it will paginate through all of the rounds. 81 | Sometimes this can result in an incredibly long catchup time that may break the service 82 | due to execution and memory constraints, particularly for filters that result in a large number of transactions. 83 | 84 | Instead, this allows indexer catchup to be split into multiple polls, each with a transactionally consistent 85 | boundary based on the number of rounds specified here. 86 | 87 | #### Inherited from 88 | 89 | [CoreTransactionSubscriptionParams](types_subscription.CoreTransactionSubscriptionParams.md).[maxIndexerRoundsToSync](types_subscription.CoreTransactionSubscriptionParams.md#maxindexerroundstosync) 90 | 91 | #### Defined in 92 | 93 | [src/types/subscription.ts:280](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L280) 94 | 95 | ___ 96 | 97 | ### maxRoundsToSync 98 | 99 | • `Optional` **maxRoundsToSync**: `number` 100 | 101 | The maximum number of rounds to sync from algod for each subscription pull/poll. 102 | 103 | Defaults to 500. 104 | 105 | This gives you control over how many rounds you wait for at a time, 106 | your staleness tolerance when using `skip-sync-newest` or `fail`, and 107 | your catchup speed when using `sync-oldest`. 108 | 109 | #### Inherited from 110 | 111 | [CoreTransactionSubscriptionParams](types_subscription.CoreTransactionSubscriptionParams.md).[maxRoundsToSync](types_subscription.CoreTransactionSubscriptionParams.md#maxroundstosync) 112 | 113 | #### Defined in 114 | 115 | [src/types/subscription.ts:269](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L269) 116 | 117 | ___ 118 | 119 | ### syncBehaviour 120 | 121 | • **syncBehaviour**: ``"skip-sync-newest"`` \| ``"sync-oldest"`` \| ``"sync-oldest-start-now"`` \| ``"catchup-with-indexer"`` \| ``"fail"`` 122 | 123 | If the current tip of the configured Algorand blockchain is more than `maxRoundsToSync` 124 | past `watermark` then how should that be handled: 125 | * `skip-sync-newest`: Discard old blocks/transactions and sync the newest; useful 126 | for real-time notification scenarios where you don't care about history and 127 | are happy to lose old transactions. 128 | * `sync-oldest`: Sync from the oldest rounds forward `maxRoundsToSync` rounds 129 | using algod; note: this will be slow if you are starting from 0 and requires 130 | an archival node. 131 | * `sync-oldest-start-now`: Same as `sync-oldest`, but if the `watermark` is `0` 132 | then start at the current round i.e. don't sync historical records, but once 133 | subscribing starts sync everything; note: if it falls behind it requires an 134 | archival node. 135 | * `catchup-with-indexer`: Sync to round `currentRound - maxRoundsToSync + 1` 136 | using indexer (much faster than using algod for long time periods) and then 137 | use algod from there. 138 | * `fail`: Throw an error. 139 | 140 | #### Inherited from 141 | 142 | [CoreTransactionSubscriptionParams](types_subscription.CoreTransactionSubscriptionParams.md).[syncBehaviour](types_subscription.CoreTransactionSubscriptionParams.md#syncbehaviour) 143 | 144 | #### Defined in 145 | 146 | [src/types/subscription.ts:298](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L298) 147 | 148 | ___ 149 | 150 | ### waitForBlockWhenAtTip 151 | 152 | • `Optional` **waitForBlockWhenAtTip**: `boolean` 153 | 154 | Whether to wait via algod `/status/wait-for-block-after` endpoint when at the tip of the chain; reduces latency of subscription 155 | 156 | #### Defined in 157 | 158 | [src/types/subscription.ts:392](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L392) 159 | 160 | ___ 161 | 162 | ### watermarkPersistence 163 | 164 | • **watermarkPersistence**: `Object` 165 | 166 | Methods to retrieve and persist the current watermark so syncing is resilient and maintains 167 | its position in the chain 168 | 169 | #### Type declaration 170 | 171 | | Name | Type | Description | 172 | | :------ | :------ | :------ | 173 | | `get` | () => `Promise`\<`bigint`\> | - | 174 | | `set` | (`newWatermark`: `bigint`) => `Promise`\<`void`\> | - | 175 | 176 | #### Defined in 177 | 178 | [src/types/subscription.ts:395](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L395) 179 | -------------------------------------------------------------------------------- /docs/code/interfaces/types_subscription.BalanceChange.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / [types/subscription](../modules/types_subscription.md) / BalanceChange 2 | 3 | # Interface: BalanceChange 4 | 5 | [types/subscription](../modules/types_subscription.md).BalanceChange 6 | 7 | Represents a balance change effect for a transaction. 8 | 9 | ## Table of contents 10 | 11 | ### Properties 12 | 13 | - [address](types_subscription.BalanceChange.md#address) 14 | - [amount](types_subscription.BalanceChange.md#amount) 15 | - [assetId](types_subscription.BalanceChange.md#assetid) 16 | - [roles](types_subscription.BalanceChange.md#roles) 17 | 18 | ## Properties 19 | 20 | ### address 21 | 22 | • **address**: `string` 23 | 24 | The address that the balance change is for. 25 | 26 | #### Defined in 27 | 28 | [src/types/subscription.ts:202](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L202) 29 | 30 | ___ 31 | 32 | ### amount 33 | 34 | • **amount**: `bigint` 35 | 36 | The amount of the balance change in smallest divisible unit or microAlgos. 37 | 38 | #### Defined in 39 | 40 | [src/types/subscription.ts:206](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L206) 41 | 42 | ___ 43 | 44 | ### assetId 45 | 46 | • **assetId**: `bigint` 47 | 48 | The asset ID of the balance change, or 0 for Algos. 49 | 50 | #### Defined in 51 | 52 | [src/types/subscription.ts:204](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L204) 53 | 54 | ___ 55 | 56 | ### roles 57 | 58 | • **roles**: [`BalanceChangeRole`](../enums/types_subscription.BalanceChangeRole.md)[] 59 | 60 | The roles the account was playing that led to the balance change 61 | 62 | #### Defined in 63 | 64 | [src/types/subscription.ts:208](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L208) 65 | -------------------------------------------------------------------------------- /docs/code/interfaces/types_subscription.BeforePollMetadata.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / [types/subscription](../modules/types_subscription.md) / BeforePollMetadata 2 | 3 | # Interface: BeforePollMetadata 4 | 5 | [types/subscription](../modules/types_subscription.md).BeforePollMetadata 6 | 7 | Metadata about an impending subscription poll. 8 | 9 | ## Table of contents 10 | 11 | ### Properties 12 | 13 | - [currentRound](types_subscription.BeforePollMetadata.md#currentround) 14 | - [watermark](types_subscription.BeforePollMetadata.md#watermark) 15 | 16 | ## Properties 17 | 18 | ### currentRound 19 | 20 | • **currentRound**: `bigint` 21 | 22 | The current round of algod 23 | 24 | #### Defined in 25 | 26 | [src/types/subscription.ts:232](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L232) 27 | 28 | ___ 29 | 30 | ### watermark 31 | 32 | • **watermark**: `bigint` 33 | 34 | The current watermark of the subscriber 35 | 36 | #### Defined in 37 | 38 | [src/types/subscription.ts:230](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L230) 39 | -------------------------------------------------------------------------------- /docs/code/interfaces/types_subscription.BlockMetadata.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / [types/subscription](../modules/types_subscription.md) / BlockMetadata 2 | 3 | # Interface: BlockMetadata 4 | 5 | [types/subscription](../modules/types_subscription.md).BlockMetadata 6 | 7 | Metadata about a block that was retrieved from algod. 8 | 9 | ## Table of contents 10 | 11 | ### Properties 12 | 13 | - [fullTransactionCount](types_subscription.BlockMetadata.md#fulltransactioncount) 14 | - [genesisHash](types_subscription.BlockMetadata.md#genesishash) 15 | - [genesisId](types_subscription.BlockMetadata.md#genesisid) 16 | - [hash](types_subscription.BlockMetadata.md#hash) 17 | - [parentTransactionCount](types_subscription.BlockMetadata.md#parenttransactioncount) 18 | - [participationUpdates](types_subscription.BlockMetadata.md#participationupdates) 19 | - [previousBlockHash](types_subscription.BlockMetadata.md#previousblockhash) 20 | - [proposer](types_subscription.BlockMetadata.md#proposer) 21 | - [rewards](types_subscription.BlockMetadata.md#rewards) 22 | - [round](types_subscription.BlockMetadata.md#round) 23 | - [seed](types_subscription.BlockMetadata.md#seed) 24 | - [stateProofTracking](types_subscription.BlockMetadata.md#stateprooftracking) 25 | - [timestamp](types_subscription.BlockMetadata.md#timestamp) 26 | - [transactionsRoot](types_subscription.BlockMetadata.md#transactionsroot) 27 | - [transactionsRootSha256](types_subscription.BlockMetadata.md#transactionsrootsha256) 28 | - [txnCounter](types_subscription.BlockMetadata.md#txncounter) 29 | - [upgradeState](types_subscription.BlockMetadata.md#upgradestate) 30 | - [upgradeVote](types_subscription.BlockMetadata.md#upgradevote) 31 | 32 | ## Properties 33 | 34 | ### fullTransactionCount 35 | 36 | • **fullTransactionCount**: `number` 37 | 38 | Full count of transactions and inner transactions (recursively) in this block. 39 | 40 | #### Defined in 41 | 42 | [src/types/subscription.ts:53](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L53) 43 | 44 | ___ 45 | 46 | ### genesisHash 47 | 48 | • **genesisHash**: `string` 49 | 50 | The base64 genesis hash of the chain. 51 | 52 | #### Defined in 53 | 54 | [src/types/subscription.ts:43](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L43) 55 | 56 | ___ 57 | 58 | ### genesisId 59 | 60 | • **genesisId**: `string` 61 | 62 | The genesis ID of the chain. 63 | 64 | #### Defined in 65 | 66 | [src/types/subscription.ts:41](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L41) 67 | 68 | ___ 69 | 70 | ### hash 71 | 72 | • `Optional` **hash**: `string` 73 | 74 | The base64 block hash. 75 | 76 | #### Defined in 77 | 78 | [src/types/subscription.ts:35](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L35) 79 | 80 | ___ 81 | 82 | ### parentTransactionCount 83 | 84 | • **parentTransactionCount**: `number` 85 | 86 | Count of parent transactions in this block 87 | 88 | #### Defined in 89 | 90 | [src/types/subscription.ts:51](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L51) 91 | 92 | ___ 93 | 94 | ### participationUpdates 95 | 96 | • `Optional` **participationUpdates**: [`ParticipationUpdates`](types_subscription.ParticipationUpdates.md) 97 | 98 | Participation account data that needs to be checked/acted on by the network. 99 | 100 | #### Defined in 101 | 102 | [src/types/subscription.ts:68](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L68) 103 | 104 | ___ 105 | 106 | ### previousBlockHash 107 | 108 | • `Optional` **previousBlockHash**: `string` 109 | 110 | The base64 previous block hash. 111 | 112 | #### Defined in 113 | 114 | [src/types/subscription.ts:45](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L45) 115 | 116 | ___ 117 | 118 | ### proposer 119 | 120 | • `Optional` **proposer**: `string` 121 | 122 | Address of the proposer of this block 123 | 124 | #### Defined in 125 | 126 | [src/types/subscription.ts:70](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L70) 127 | 128 | ___ 129 | 130 | ### rewards 131 | 132 | • `Optional` **rewards**: [`BlockRewards`](types_subscription.BlockRewards.md) 133 | 134 | Fields relating to rewards 135 | 136 | #### Defined in 137 | 138 | [src/types/subscription.ts:49](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L49) 139 | 140 | ___ 141 | 142 | ### round 143 | 144 | • **round**: `bigint` 145 | 146 | The round of the block. 147 | 148 | #### Defined in 149 | 150 | [src/types/subscription.ts:37](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L37) 151 | 152 | ___ 153 | 154 | ### seed 155 | 156 | • **seed**: `string` 157 | 158 | The base64 seed of the block. 159 | 160 | #### Defined in 161 | 162 | [src/types/subscription.ts:47](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L47) 163 | 164 | ___ 165 | 166 | ### stateProofTracking 167 | 168 | • `Optional` **stateProofTracking**: [`BlockStateProofTracking`](types_subscription.BlockStateProofTracking.md)[] 169 | 170 | Tracks the status of state proofs. 171 | 172 | #### Defined in 173 | 174 | [src/types/subscription.ts:64](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L64) 175 | 176 | ___ 177 | 178 | ### timestamp 179 | 180 | • **timestamp**: `number` 181 | 182 | Block creation timestamp in seconds since epoch 183 | 184 | #### Defined in 185 | 186 | [src/types/subscription.ts:39](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L39) 187 | 188 | ___ 189 | 190 | ### transactionsRoot 191 | 192 | • **transactionsRoot**: `string` 193 | 194 | TransactionsRoot authenticates the set of transactions appearing in the block. More specifically, it's the root of a merkle tree whose leaves are the block's Txids, in lexicographic order. For the empty block, it's 0. Note that the TxnRoot does not authenticate the signatures on the transactions, only the transactions themselves. Two blocks with the same transactions but in a different order and with different signatures will have the same TxnRoot. 195 | Pattern : "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==\|[A-Za-z0-9+/]{3}=)?$" 196 | 197 | #### Defined in 198 | 199 | [src/types/subscription.ts:58](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L58) 200 | 201 | ___ 202 | 203 | ### transactionsRootSha256 204 | 205 | • **transactionsRootSha256**: `string` 206 | 207 | TransactionsRootSHA256 is an auxiliary TransactionRoot, built using a vector commitment instead of a merkle tree, and SHA256 hash function instead of the default SHA512_256. This commitment can be used on environments where only the SHA256 function exists. 208 | 209 | #### Defined in 210 | 211 | [src/types/subscription.ts:60](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L60) 212 | 213 | ___ 214 | 215 | ### txnCounter 216 | 217 | • **txnCounter**: `bigint` 218 | 219 | Number of the next transaction that will be committed after this block. It is 0 when no transactions have ever been committed (since TxnCounter started being supported). 220 | 221 | #### Defined in 222 | 223 | [src/types/subscription.ts:55](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L55) 224 | 225 | ___ 226 | 227 | ### upgradeState 228 | 229 | • `Optional` **upgradeState**: [`BlockUpgradeState`](types_subscription.BlockUpgradeState.md) 230 | 231 | Fields relating to a protocol upgrade. 232 | 233 | #### Defined in 234 | 235 | [src/types/subscription.ts:62](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L62) 236 | 237 | ___ 238 | 239 | ### upgradeVote 240 | 241 | • `Optional` **upgradeVote**: [`BlockUpgradeVote`](types_subscription.BlockUpgradeVote.md) 242 | 243 | Fields relating to voting for a protocol upgrade. 244 | 245 | #### Defined in 246 | 247 | [src/types/subscription.ts:66](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L66) 248 | -------------------------------------------------------------------------------- /docs/code/interfaces/types_subscription.BlockRewards.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / [types/subscription](../modules/types_subscription.md) / BlockRewards 2 | 3 | # Interface: BlockRewards 4 | 5 | [types/subscription](../modules/types_subscription.md).BlockRewards 6 | 7 | ## Table of contents 8 | 9 | ### Properties 10 | 11 | - [feeSink](types_subscription.BlockRewards.md#feesink) 12 | - [rewardsCalculationRound](types_subscription.BlockRewards.md#rewardscalculationround) 13 | - [rewardsLevel](types_subscription.BlockRewards.md#rewardslevel) 14 | - [rewardsPool](types_subscription.BlockRewards.md#rewardspool) 15 | - [rewardsRate](types_subscription.BlockRewards.md#rewardsrate) 16 | - [rewardsResidue](types_subscription.BlockRewards.md#rewardsresidue) 17 | 18 | ## Properties 19 | 20 | ### feeSink 21 | 22 | • **feeSink**: `string` 23 | 24 | FeeSink is an address that accepts transaction fees, it can only spend to the incentive pool. 25 | 26 | #### Defined in 27 | 28 | [src/types/subscription.ts:75](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L75) 29 | 30 | ___ 31 | 32 | ### rewardsCalculationRound 33 | 34 | • **rewardsCalculationRound**: `bigint` 35 | 36 | The number of leftover MicroAlgos after the distribution of rewards-rate MicroAlgos for every reward unit in the next round. 37 | 38 | #### Defined in 39 | 40 | [src/types/subscription.ts:77](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L77) 41 | 42 | ___ 43 | 44 | ### rewardsLevel 45 | 46 | • **rewardsLevel**: `bigint` 47 | 48 | How many rewards, in MicroAlgos, have been distributed to each RewardUnit of MicroAlgos since genesis. 49 | 50 | #### Defined in 51 | 52 | [src/types/subscription.ts:79](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L79) 53 | 54 | ___ 55 | 56 | ### rewardsPool 57 | 58 | • **rewardsPool**: `string` 59 | 60 | RewardsPool is an address that accepts periodic injections from the fee-sink and continually redistributes them as rewards. 61 | 62 | #### Defined in 63 | 64 | [src/types/subscription.ts:81](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L81) 65 | 66 | ___ 67 | 68 | ### rewardsRate 69 | 70 | • **rewardsRate**: `bigint` 71 | 72 | Number of new MicroAlgos added to the participation stake from rewards at the next round. 73 | 74 | #### Defined in 75 | 76 | [src/types/subscription.ts:83](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L83) 77 | 78 | ___ 79 | 80 | ### rewardsResidue 81 | 82 | • **rewardsResidue**: `bigint` 83 | 84 | Number of leftover MicroAlgos after the distribution of RewardsRate/rewardUnits MicroAlgos for every reward unit in the next round. 85 | 86 | #### Defined in 87 | 88 | [src/types/subscription.ts:85](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L85) 89 | -------------------------------------------------------------------------------- /docs/code/interfaces/types_subscription.BlockStateProofTracking.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / [types/subscription](../modules/types_subscription.md) / BlockStateProofTracking 2 | 3 | # Interface: BlockStateProofTracking 4 | 5 | [types/subscription](../modules/types_subscription.md).BlockStateProofTracking 6 | 7 | ## Table of contents 8 | 9 | ### Properties 10 | 11 | - [nextRound](types_subscription.BlockStateProofTracking.md#nextround) 12 | - [onlineTotalWeight](types_subscription.BlockStateProofTracking.md#onlinetotalweight) 13 | - [type](types_subscription.BlockStateProofTracking.md#type) 14 | - [votersCommitment](types_subscription.BlockStateProofTracking.md#voterscommitment) 15 | 16 | ## Properties 17 | 18 | ### nextRound 19 | 20 | • `Optional` **nextRound**: `bigint` 21 | 22 | (n) Next round for which we will accept a state proof transaction. 23 | 24 | #### Defined in 25 | 26 | [src/types/subscription.ts:105](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L105) 27 | 28 | ___ 29 | 30 | ### onlineTotalWeight 31 | 32 | • `Optional` **onlineTotalWeight**: `bigint` 33 | 34 | (t) The total number of microalgos held by the online accounts during the 35 | StateProof round. 36 | 37 | #### Defined in 38 | 39 | [src/types/subscription.ts:111](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L111) 40 | 41 | ___ 42 | 43 | ### type 44 | 45 | • `Optional` **type**: `number` 46 | 47 | State Proof Type. Note the raw object uses map with this as key. 48 | 49 | #### Defined in 50 | 51 | [src/types/subscription.ts:116](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L116) 52 | 53 | ___ 54 | 55 | ### votersCommitment 56 | 57 | • `Optional` **votersCommitment**: `string` 58 | 59 | (v) Root of a vector commitment containing online accounts that will help sign 60 | the proof. 61 | 62 | #### Defined in 63 | 64 | [src/types/subscription.ts:122](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L122) 65 | -------------------------------------------------------------------------------- /docs/code/interfaces/types_subscription.BlockUpgradeState.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / [types/subscription](../modules/types_subscription.md) / BlockUpgradeState 2 | 3 | # Interface: BlockUpgradeState 4 | 5 | [types/subscription](../modules/types_subscription.md).BlockUpgradeState 6 | 7 | ## Table of contents 8 | 9 | ### Properties 10 | 11 | - [currentProtocol](types_subscription.BlockUpgradeState.md#currentprotocol) 12 | - [nextProtocol](types_subscription.BlockUpgradeState.md#nextprotocol) 13 | - [nextProtocolApprovals](types_subscription.BlockUpgradeState.md#nextprotocolapprovals) 14 | - [nextProtocolSwitchOn](types_subscription.BlockUpgradeState.md#nextprotocolswitchon) 15 | - [nextProtocolVoteBefore](types_subscription.BlockUpgradeState.md#nextprotocolvotebefore) 16 | 17 | ## Properties 18 | 19 | ### currentProtocol 20 | 21 | • **currentProtocol**: `string` 22 | 23 | Current protocol version 24 | 25 | #### Defined in 26 | 27 | [src/types/subscription.ts:90](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L90) 28 | 29 | ___ 30 | 31 | ### nextProtocol 32 | 33 | • `Optional` **nextProtocol**: `string` 34 | 35 | The next proposed protocol version. 36 | 37 | #### Defined in 38 | 39 | [src/types/subscription.ts:92](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L92) 40 | 41 | ___ 42 | 43 | ### nextProtocolApprovals 44 | 45 | • `Optional` **nextProtocolApprovals**: `bigint` 46 | 47 | Number of blocks which approved the protocol upgrade. 48 | 49 | #### Defined in 50 | 51 | [src/types/subscription.ts:94](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L94) 52 | 53 | ___ 54 | 55 | ### nextProtocolSwitchOn 56 | 57 | • `Optional` **nextProtocolSwitchOn**: `bigint` 58 | 59 | Round on which the protocol upgrade will take effect. 60 | 61 | #### Defined in 62 | 63 | [src/types/subscription.ts:98](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L98) 64 | 65 | ___ 66 | 67 | ### nextProtocolVoteBefore 68 | 69 | • `Optional` **nextProtocolVoteBefore**: `bigint` 70 | 71 | Deadline round for this protocol upgrade (No votes will be consider after this round). 72 | 73 | #### Defined in 74 | 75 | [src/types/subscription.ts:96](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L96) 76 | -------------------------------------------------------------------------------- /docs/code/interfaces/types_subscription.BlockUpgradeVote.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / [types/subscription](../modules/types_subscription.md) / BlockUpgradeVote 2 | 3 | # Interface: BlockUpgradeVote 4 | 5 | [types/subscription](../modules/types_subscription.md).BlockUpgradeVote 6 | 7 | ## Table of contents 8 | 9 | ### Properties 10 | 11 | - [upgradeApprove](types_subscription.BlockUpgradeVote.md#upgradeapprove) 12 | - [upgradeDelay](types_subscription.BlockUpgradeVote.md#upgradedelay) 13 | - [upgradePropose](types_subscription.BlockUpgradeVote.md#upgradepropose) 14 | 15 | ## Properties 16 | 17 | ### upgradeApprove 18 | 19 | • `Optional` **upgradeApprove**: `boolean` 20 | 21 | (upgradeyes) Indicates a yes vote for the current proposal. 22 | 23 | #### Defined in 24 | 25 | [src/types/subscription.ts:129](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L129) 26 | 27 | ___ 28 | 29 | ### upgradeDelay 30 | 31 | • `Optional` **upgradeDelay**: `bigint` 32 | 33 | (upgradedelay) Indicates the time between acceptance and execution. 34 | 35 | #### Defined in 36 | 37 | [src/types/subscription.ts:134](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L134) 38 | 39 | ___ 40 | 41 | ### upgradePropose 42 | 43 | • `Optional` **upgradePropose**: `string` 44 | 45 | (upgradeprop) Indicates a proposed upgrade. 46 | 47 | #### Defined in 48 | 49 | [src/types/subscription.ts:139](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L139) 50 | -------------------------------------------------------------------------------- /docs/code/interfaces/types_subscription.CoreTransactionSubscriptionParams.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / [types/subscription](../modules/types_subscription.md) / CoreTransactionSubscriptionParams 2 | 3 | # Interface: CoreTransactionSubscriptionParams 4 | 5 | [types/subscription](../modules/types_subscription.md).CoreTransactionSubscriptionParams 6 | 7 | Common parameters to control a single subscription pull/poll for both `AlgorandSubscriber` and `getSubscribedTransactions`. 8 | 9 | ## Hierarchy 10 | 11 | - **`CoreTransactionSubscriptionParams`** 12 | 13 | ↳ [`TransactionSubscriptionParams`](types_subscription.TransactionSubscriptionParams.md) 14 | 15 | ↳ [`AlgorandSubscriberConfig`](types_subscription.AlgorandSubscriberConfig.md) 16 | 17 | ## Table of contents 18 | 19 | ### Properties 20 | 21 | - [arc28Events](types_subscription.CoreTransactionSubscriptionParams.md#arc28events) 22 | - [filters](types_subscription.CoreTransactionSubscriptionParams.md#filters) 23 | - [maxIndexerRoundsToSync](types_subscription.CoreTransactionSubscriptionParams.md#maxindexerroundstosync) 24 | - [maxRoundsToSync](types_subscription.CoreTransactionSubscriptionParams.md#maxroundstosync) 25 | - [syncBehaviour](types_subscription.CoreTransactionSubscriptionParams.md#syncbehaviour) 26 | 27 | ## Properties 28 | 29 | ### arc28Events 30 | 31 | • `Optional` **arc28Events**: [`Arc28EventGroup`](types_arc_28.Arc28EventGroup.md)[] 32 | 33 | Any ARC-28 event definitions to process from app call logs 34 | 35 | #### Defined in 36 | 37 | [src/types/subscription.ts:260](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L260) 38 | 39 | ___ 40 | 41 | ### filters 42 | 43 | • **filters**: [`NamedTransactionFilter`](types_subscription.NamedTransactionFilter.md)[] 44 | 45 | The filter(s) to apply to find transactions of interest. 46 | A list of filters with corresponding names. 47 | 48 | **`Example`** 49 | 50 | ```typescript 51 | filter: [{ 52 | name: 'asset-transfers', 53 | filter: { 54 | type: TransactionType.axfer, 55 | //... 56 | } 57 | }, { 58 | name: 'payments', 59 | filter: { 60 | type: TransactionType.pay, 61 | //... 62 | } 63 | }] 64 | ``` 65 | 66 | #### Defined in 67 | 68 | [src/types/subscription.ts:258](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L258) 69 | 70 | ___ 71 | 72 | ### maxIndexerRoundsToSync 73 | 74 | • `Optional` **maxIndexerRoundsToSync**: `number` 75 | 76 | The maximum number of rounds to sync from indexer when using `syncBehaviour: 'catchup-with-indexer'. 77 | 78 | By default there is no limit and it will paginate through all of the rounds. 79 | Sometimes this can result in an incredibly long catchup time that may break the service 80 | due to execution and memory constraints, particularly for filters that result in a large number of transactions. 81 | 82 | Instead, this allows indexer catchup to be split into multiple polls, each with a transactionally consistent 83 | boundary based on the number of rounds specified here. 84 | 85 | #### Defined in 86 | 87 | [src/types/subscription.ts:280](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L280) 88 | 89 | ___ 90 | 91 | ### maxRoundsToSync 92 | 93 | • `Optional` **maxRoundsToSync**: `number` 94 | 95 | The maximum number of rounds to sync from algod for each subscription pull/poll. 96 | 97 | Defaults to 500. 98 | 99 | This gives you control over how many rounds you wait for at a time, 100 | your staleness tolerance when using `skip-sync-newest` or `fail`, and 101 | your catchup speed when using `sync-oldest`. 102 | 103 | #### Defined in 104 | 105 | [src/types/subscription.ts:269](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L269) 106 | 107 | ___ 108 | 109 | ### syncBehaviour 110 | 111 | • **syncBehaviour**: ``"skip-sync-newest"`` \| ``"sync-oldest"`` \| ``"sync-oldest-start-now"`` \| ``"catchup-with-indexer"`` \| ``"fail"`` 112 | 113 | If the current tip of the configured Algorand blockchain is more than `maxRoundsToSync` 114 | past `watermark` then how should that be handled: 115 | * `skip-sync-newest`: Discard old blocks/transactions and sync the newest; useful 116 | for real-time notification scenarios where you don't care about history and 117 | are happy to lose old transactions. 118 | * `sync-oldest`: Sync from the oldest rounds forward `maxRoundsToSync` rounds 119 | using algod; note: this will be slow if you are starting from 0 and requires 120 | an archival node. 121 | * `sync-oldest-start-now`: Same as `sync-oldest`, but if the `watermark` is `0` 122 | then start at the current round i.e. don't sync historical records, but once 123 | subscribing starts sync everything; note: if it falls behind it requires an 124 | archival node. 125 | * `catchup-with-indexer`: Sync to round `currentRound - maxRoundsToSync + 1` 126 | using indexer (much faster than using algod for long time periods) and then 127 | use algod from there. 128 | * `fail`: Throw an error. 129 | 130 | #### Defined in 131 | 132 | [src/types/subscription.ts:298](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L298) 133 | -------------------------------------------------------------------------------- /docs/code/interfaces/types_subscription.NamedTransactionFilter.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / [types/subscription](../modules/types_subscription.md) / NamedTransactionFilter 2 | 3 | # Interface: NamedTransactionFilter 4 | 5 | [types/subscription](../modules/types_subscription.md).NamedTransactionFilter 6 | 7 | Specify a named filter to apply to find transactions of interest. 8 | 9 | ## Hierarchy 10 | 11 | - **`NamedTransactionFilter`** 12 | 13 | ↳ [`SubscriberConfigFilter`](types_subscription.SubscriberConfigFilter.md) 14 | 15 | ## Table of contents 16 | 17 | ### Properties 18 | 19 | - [filter](types_subscription.NamedTransactionFilter.md#filter) 20 | - [name](types_subscription.NamedTransactionFilter.md#name) 21 | 22 | ## Properties 23 | 24 | ### filter 25 | 26 | • **filter**: [`TransactionFilter`](types_subscription.TransactionFilter.md) 27 | 28 | The filter itself. 29 | 30 | #### Defined in 31 | 32 | [src/types/subscription.ts:306](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L306) 33 | 34 | ___ 35 | 36 | ### name 37 | 38 | • **name**: `string` 39 | 40 | The name to give the filter. 41 | 42 | #### Defined in 43 | 44 | [src/types/subscription.ts:304](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L304) 45 | -------------------------------------------------------------------------------- /docs/code/interfaces/types_subscription.ParticipationUpdates.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / [types/subscription](../modules/types_subscription.md) / ParticipationUpdates 2 | 3 | # Interface: ParticipationUpdates 4 | 5 | [types/subscription](../modules/types_subscription.md).ParticipationUpdates 6 | 7 | ## Table of contents 8 | 9 | ### Properties 10 | 11 | - [absentParticipationAccounts](types_subscription.ParticipationUpdates.md#absentparticipationaccounts) 12 | - [expiredParticipationAccounts](types_subscription.ParticipationUpdates.md#expiredparticipationaccounts) 13 | 14 | ## Properties 15 | 16 | ### absentParticipationAccounts 17 | 18 | • `Optional` **absentParticipationAccounts**: `string`[] 19 | 20 | (partupabs) a list of online accounts that need to be suspended. 21 | 22 | #### Defined in 23 | 24 | [src/types/subscription.ts:146](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L146) 25 | 26 | ___ 27 | 28 | ### expiredParticipationAccounts 29 | 30 | • `Optional` **expiredParticipationAccounts**: `string`[] 31 | 32 | (partupdrmv) a list of online accounts that needs to be converted to offline 33 | since their participation key expired. 34 | 35 | #### Defined in 36 | 37 | [src/types/subscription.ts:152](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L152) 38 | -------------------------------------------------------------------------------- /docs/code/interfaces/types_subscription.SubscriberConfigFilter.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / [types/subscription](../modules/types_subscription.md) / SubscriberConfigFilter 2 | 3 | # Interface: SubscriberConfigFilter\ 4 | 5 | [types/subscription](../modules/types_subscription.md).SubscriberConfigFilter 6 | 7 | A single event to subscribe to / emit. 8 | 9 | ## Type parameters 10 | 11 | | Name | 12 | | :------ | 13 | | `T` | 14 | 15 | ## Hierarchy 16 | 17 | - [`NamedTransactionFilter`](types_subscription.NamedTransactionFilter.md) 18 | 19 | ↳ **`SubscriberConfigFilter`** 20 | 21 | ## Table of contents 22 | 23 | ### Properties 24 | 25 | - [filter](types_subscription.SubscriberConfigFilter.md#filter) 26 | - [mapper](types_subscription.SubscriberConfigFilter.md#mapper) 27 | - [name](types_subscription.SubscriberConfigFilter.md#name) 28 | 29 | ## Properties 30 | 31 | ### filter 32 | 33 | • **filter**: [`TransactionFilter`](types_subscription.TransactionFilter.md) 34 | 35 | The filter itself. 36 | 37 | #### Inherited from 38 | 39 | [NamedTransactionFilter](types_subscription.NamedTransactionFilter.md).[filter](types_subscription.NamedTransactionFilter.md#filter) 40 | 41 | #### Defined in 42 | 43 | [src/types/subscription.ts:306](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L306) 44 | 45 | ___ 46 | 47 | ### mapper 48 | 49 | • `Optional` **mapper**: (`transaction`: [`SubscribedTransaction`](../classes/types_subscription.SubscribedTransaction.md)[]) => `Promise`\<`T`[]\> 50 | 51 | An optional data mapper if you want the event data to take a certain shape when subscribing to events with this filter name. 52 | 53 | If not specified, then the event will simply receive a `SubscribedTransaction`. 54 | 55 | Note: if you provide multiple filters with the same name then only the mapper of the first matching filter will be used 56 | 57 | #### Type declaration 58 | 59 | ▸ (`transaction`): `Promise`\<`T`[]\> 60 | 61 | ##### Parameters 62 | 63 | | Name | Type | 64 | | :------ | :------ | 65 | | `transaction` | [`SubscribedTransaction`](../classes/types_subscription.SubscribedTransaction.md)[] | 66 | 67 | ##### Returns 68 | 69 | `Promise`\<`T`[]\> 70 | 71 | #### Defined in 72 | 73 | [src/types/subscription.ts:411](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L411) 74 | 75 | ___ 76 | 77 | ### name 78 | 79 | • **name**: `string` 80 | 81 | The name to give the filter. 82 | 83 | #### Inherited from 84 | 85 | [NamedTransactionFilter](types_subscription.NamedTransactionFilter.md).[name](types_subscription.NamedTransactionFilter.md#name) 86 | 87 | #### Defined in 88 | 89 | [src/types/subscription.ts:304](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L304) 90 | -------------------------------------------------------------------------------- /docs/code/interfaces/types_subscription.TransactionSubscriptionParams.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / [types/subscription](../modules/types_subscription.md) / TransactionSubscriptionParams 2 | 3 | # Interface: TransactionSubscriptionParams 4 | 5 | [types/subscription](../modules/types_subscription.md).TransactionSubscriptionParams 6 | 7 | Parameters to control a single subscription pull/poll. 8 | 9 | ## Hierarchy 10 | 11 | - [`CoreTransactionSubscriptionParams`](types_subscription.CoreTransactionSubscriptionParams.md) 12 | 13 | ↳ **`TransactionSubscriptionParams`** 14 | 15 | ## Table of contents 16 | 17 | ### Properties 18 | 19 | - [arc28Events](types_subscription.TransactionSubscriptionParams.md#arc28events) 20 | - [currentRound](types_subscription.TransactionSubscriptionParams.md#currentround) 21 | - [filters](types_subscription.TransactionSubscriptionParams.md#filters) 22 | - [maxIndexerRoundsToSync](types_subscription.TransactionSubscriptionParams.md#maxindexerroundstosync) 23 | - [maxRoundsToSync](types_subscription.TransactionSubscriptionParams.md#maxroundstosync) 24 | - [syncBehaviour](types_subscription.TransactionSubscriptionParams.md#syncbehaviour) 25 | - [watermark](types_subscription.TransactionSubscriptionParams.md#watermark) 26 | 27 | ## Properties 28 | 29 | ### arc28Events 30 | 31 | • `Optional` **arc28Events**: [`Arc28EventGroup`](types_arc_28.Arc28EventGroup.md)[] 32 | 33 | Any ARC-28 event definitions to process from app call logs 34 | 35 | #### Inherited from 36 | 37 | [CoreTransactionSubscriptionParams](types_subscription.CoreTransactionSubscriptionParams.md).[arc28Events](types_subscription.CoreTransactionSubscriptionParams.md#arc28events) 38 | 39 | #### Defined in 40 | 41 | [src/types/subscription.ts:260](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L260) 42 | 43 | ___ 44 | 45 | ### currentRound 46 | 47 | • `Optional` **currentRound**: `bigint` 48 | 49 | The current tip of the configured Algorand blockchain. 50 | If not provided, it will be resolved on demand. 51 | 52 | #### Defined in 53 | 54 | [src/types/subscription.ts:382](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L382) 55 | 56 | ___ 57 | 58 | ### filters 59 | 60 | • **filters**: [`NamedTransactionFilter`](types_subscription.NamedTransactionFilter.md)[] 61 | 62 | The filter(s) to apply to find transactions of interest. 63 | A list of filters with corresponding names. 64 | 65 | **`Example`** 66 | 67 | ```typescript 68 | filter: [{ 69 | name: 'asset-transfers', 70 | filter: { 71 | type: TransactionType.axfer, 72 | //... 73 | } 74 | }, { 75 | name: 'payments', 76 | filter: { 77 | type: TransactionType.pay, 78 | //... 79 | } 80 | }] 81 | ``` 82 | 83 | #### Inherited from 84 | 85 | [CoreTransactionSubscriptionParams](types_subscription.CoreTransactionSubscriptionParams.md).[filters](types_subscription.CoreTransactionSubscriptionParams.md#filters) 86 | 87 | #### Defined in 88 | 89 | [src/types/subscription.ts:258](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L258) 90 | 91 | ___ 92 | 93 | ### maxIndexerRoundsToSync 94 | 95 | • `Optional` **maxIndexerRoundsToSync**: `number` 96 | 97 | The maximum number of rounds to sync from indexer when using `syncBehaviour: 'catchup-with-indexer'. 98 | 99 | By default there is no limit and it will paginate through all of the rounds. 100 | Sometimes this can result in an incredibly long catchup time that may break the service 101 | due to execution and memory constraints, particularly for filters that result in a large number of transactions. 102 | 103 | Instead, this allows indexer catchup to be split into multiple polls, each with a transactionally consistent 104 | boundary based on the number of rounds specified here. 105 | 106 | #### Inherited from 107 | 108 | [CoreTransactionSubscriptionParams](types_subscription.CoreTransactionSubscriptionParams.md).[maxIndexerRoundsToSync](types_subscription.CoreTransactionSubscriptionParams.md#maxindexerroundstosync) 109 | 110 | #### Defined in 111 | 112 | [src/types/subscription.ts:280](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L280) 113 | 114 | ___ 115 | 116 | ### maxRoundsToSync 117 | 118 | • `Optional` **maxRoundsToSync**: `number` 119 | 120 | The maximum number of rounds to sync from algod for each subscription pull/poll. 121 | 122 | Defaults to 500. 123 | 124 | This gives you control over how many rounds you wait for at a time, 125 | your staleness tolerance when using `skip-sync-newest` or `fail`, and 126 | your catchup speed when using `sync-oldest`. 127 | 128 | #### Inherited from 129 | 130 | [CoreTransactionSubscriptionParams](types_subscription.CoreTransactionSubscriptionParams.md).[maxRoundsToSync](types_subscription.CoreTransactionSubscriptionParams.md#maxroundstosync) 131 | 132 | #### Defined in 133 | 134 | [src/types/subscription.ts:269](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L269) 135 | 136 | ___ 137 | 138 | ### syncBehaviour 139 | 140 | • **syncBehaviour**: ``"skip-sync-newest"`` \| ``"sync-oldest"`` \| ``"sync-oldest-start-now"`` \| ``"catchup-with-indexer"`` \| ``"fail"`` 141 | 142 | If the current tip of the configured Algorand blockchain is more than `maxRoundsToSync` 143 | past `watermark` then how should that be handled: 144 | * `skip-sync-newest`: Discard old blocks/transactions and sync the newest; useful 145 | for real-time notification scenarios where you don't care about history and 146 | are happy to lose old transactions. 147 | * `sync-oldest`: Sync from the oldest rounds forward `maxRoundsToSync` rounds 148 | using algod; note: this will be slow if you are starting from 0 and requires 149 | an archival node. 150 | * `sync-oldest-start-now`: Same as `sync-oldest`, but if the `watermark` is `0` 151 | then start at the current round i.e. don't sync historical records, but once 152 | subscribing starts sync everything; note: if it falls behind it requires an 153 | archival node. 154 | * `catchup-with-indexer`: Sync to round `currentRound - maxRoundsToSync + 1` 155 | using indexer (much faster than using algod for long time periods) and then 156 | use algod from there. 157 | * `fail`: Throw an error. 158 | 159 | #### Inherited from 160 | 161 | [CoreTransactionSubscriptionParams](types_subscription.CoreTransactionSubscriptionParams.md).[syncBehaviour](types_subscription.CoreTransactionSubscriptionParams.md#syncbehaviour) 162 | 163 | #### Defined in 164 | 165 | [src/types/subscription.ts:298](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L298) 166 | 167 | ___ 168 | 169 | ### watermark 170 | 171 | • **watermark**: `bigint` 172 | 173 | The current round watermark that transactions have previously been synced to. 174 | 175 | Persist this value as you process transactions processed from this method 176 | to allow for resilient and incremental syncing. 177 | 178 | Syncing will start from `watermark + 1`. 179 | 180 | Start from 0 if you want to start from the beginning of time, noting that 181 | will be slow if `onMaxRounds` is `sync-oldest`. 182 | 183 | #### Defined in 184 | 185 | [src/types/subscription.ts:377](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L377) 186 | -------------------------------------------------------------------------------- /docs/code/interfaces/types_subscription.TransactionSubscriptionResult.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / [types/subscription](../modules/types_subscription.md) / TransactionSubscriptionResult 2 | 3 | # Interface: TransactionSubscriptionResult 4 | 5 | [types/subscription](../modules/types_subscription.md).TransactionSubscriptionResult 6 | 7 | The result of a single subscription pull/poll. 8 | 9 | ## Table of contents 10 | 11 | ### Properties 12 | 13 | - [blockMetadata](types_subscription.TransactionSubscriptionResult.md#blockmetadata) 14 | - [currentRound](types_subscription.TransactionSubscriptionResult.md#currentround) 15 | - [newWatermark](types_subscription.TransactionSubscriptionResult.md#newwatermark) 16 | - [startingWatermark](types_subscription.TransactionSubscriptionResult.md#startingwatermark) 17 | - [subscribedTransactions](types_subscription.TransactionSubscriptionResult.md#subscribedtransactions) 18 | - [syncedRoundRange](types_subscription.TransactionSubscriptionResult.md#syncedroundrange) 19 | 20 | ## Properties 21 | 22 | ### blockMetadata 23 | 24 | • `Optional` **blockMetadata**: [`BlockMetadata`](types_subscription.BlockMetadata.md)[] 25 | 26 | The metadata about any blocks that were retrieved from algod as part 27 | of the subscription poll. 28 | 29 | #### Defined in 30 | 31 | [src/types/subscription.ts:29](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L29) 32 | 33 | ___ 34 | 35 | ### currentRound 36 | 37 | • **currentRound**: `bigint` 38 | 39 | The current detected tip of the configured Algorand blockchain. 40 | 41 | #### Defined in 42 | 43 | [src/types/subscription.ts:11](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L11) 44 | 45 | ___ 46 | 47 | ### newWatermark 48 | 49 | • **newWatermark**: `bigint` 50 | 51 | The new watermark value to persist for the next call to 52 | `getSubscribedTransactions` to continue the sync. 53 | Will be equal to `syncedRoundRange[1]`. Only persist this 54 | after processing (or in the same atomic transaction as) 55 | subscribed transactions to keep it reliable. 56 | 57 | #### Defined in 58 | 59 | [src/types/subscription.ts:19](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L19) 60 | 61 | ___ 62 | 63 | ### startingWatermark 64 | 65 | • **startingWatermark**: `bigint` 66 | 67 | The watermark value that was retrieved at the start of the subscription poll. 68 | 69 | #### Defined in 70 | 71 | [src/types/subscription.ts:13](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L13) 72 | 73 | ___ 74 | 75 | ### subscribedTransactions 76 | 77 | • **subscribedTransactions**: [`SubscribedTransaction`](../classes/types_subscription.SubscribedTransaction.md)[] 78 | 79 | Any transactions that matched the given filter within 80 | the synced round range. This substantively uses the [indexer transaction 81 | format](https://dev.algorand.co/reference/rest-apis/indexer#transaction) 82 | to represent the data with some additional fields. 83 | 84 | #### Defined in 85 | 86 | [src/types/subscription.ts:25](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L25) 87 | 88 | ___ 89 | 90 | ### syncedRoundRange 91 | 92 | • **syncedRoundRange**: [startRound: bigint, endRound: bigint] 93 | 94 | The round range that was synced from/to 95 | 96 | #### Defined in 97 | 98 | [src/types/subscription.ts:9](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L9) 99 | -------------------------------------------------------------------------------- /docs/code/modules/index.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / index 2 | 3 | # Module: index 4 | 5 | ## Table of contents 6 | 7 | ### Classes 8 | 9 | - [AlgorandSubscriber](../classes/index.AlgorandSubscriber.md) 10 | 11 | ### Functions 12 | 13 | - [getSubscribedTransactions](index.md#getsubscribedtransactions) 14 | 15 | ## Functions 16 | 17 | ### getSubscribedTransactions 18 | 19 | ▸ **getSubscribedTransactions**(`subscription`, `algod`, `indexer?`): `Promise`\<[`TransactionSubscriptionResult`](../interfaces/types_subscription.TransactionSubscriptionResult.md)\> 20 | 21 | Executes a single pull/poll to subscribe to transactions on the configured Algorand 22 | blockchain for the given subscription context. 23 | 24 | #### Parameters 25 | 26 | | Name | Type | Description | 27 | | :------ | :------ | :------ | 28 | | `subscription` | [`TransactionSubscriptionParams`](../interfaces/types_subscription.TransactionSubscriptionParams.md) | The subscription context. | 29 | | `algod` | `AlgodClient` | An Algod client. | 30 | | `indexer?` | `IndexerClient` | An optional indexer client, only needed when `onMaxRounds` is `catchup-with-indexer`. | 31 | 32 | #### Returns 33 | 34 | `Promise`\<[`TransactionSubscriptionResult`](../interfaces/types_subscription.TransactionSubscriptionResult.md)\> 35 | 36 | The result of this subscription pull/poll. 37 | 38 | #### Defined in 39 | 40 | [src/subscriptions.ts:56](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/subscriptions.ts#L56) 41 | -------------------------------------------------------------------------------- /docs/code/modules/types.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / types 2 | 3 | # Module: types 4 | 5 | ## Table of contents 6 | 7 | ### References 8 | 9 | - [AlgorandSubscriberConfig](types.md#algorandsubscriberconfig) 10 | - [Arc28Event](types.md#arc28event) 11 | - [Arc28EventGroup](types.md#arc28eventgroup) 12 | - [Arc28EventToProcess](types.md#arc28eventtoprocess) 13 | - [AsyncEventEmitter](types.md#asynceventemitter) 14 | - [AsyncEventListener](types.md#asynceventlistener) 15 | - [BalanceChange](types.md#balancechange) 16 | - [BalanceChangeRole](types.md#balancechangerole) 17 | - [BeforePollMetadata](types.md#beforepollmetadata) 18 | - [BlockMetadata](types.md#blockmetadata) 19 | - [BlockRewards](types.md#blockrewards) 20 | - [BlockStateProofTracking](types.md#blockstateprooftracking) 21 | - [BlockUpgradeState](types.md#blockupgradestate) 22 | - [BlockUpgradeVote](types.md#blockupgradevote) 23 | - [CoreTransactionSubscriptionParams](types.md#coretransactionsubscriptionparams) 24 | - [EmittedArc28Event](types.md#emittedarc28event) 25 | - [ErrorListener](types.md#errorlistener) 26 | - [NamedTransactionFilter](types.md#namedtransactionfilter) 27 | - [ParticipationUpdates](types.md#participationupdates) 28 | - [SubscribedTransaction](types.md#subscribedtransaction) 29 | - [SubscriberConfigFilter](types.md#subscriberconfigfilter) 30 | - [TransactionFilter](types.md#transactionfilter) 31 | - [TransactionInBlock](types.md#transactioninblock) 32 | - [TransactionSubscriptionParams](types.md#transactionsubscriptionparams) 33 | - [TransactionSubscriptionResult](types.md#transactionsubscriptionresult) 34 | - [TypedAsyncEventListener](types.md#typedasynceventlistener) 35 | 36 | ## References 37 | 38 | ### AlgorandSubscriberConfig 39 | 40 | Re-exports [AlgorandSubscriberConfig](../interfaces/types_subscription.AlgorandSubscriberConfig.md) 41 | 42 | ___ 43 | 44 | ### Arc28Event 45 | 46 | Re-exports [Arc28Event](../interfaces/types_arc_28.Arc28Event.md) 47 | 48 | ___ 49 | 50 | ### Arc28EventGroup 51 | 52 | Re-exports [Arc28EventGroup](../interfaces/types_arc_28.Arc28EventGroup.md) 53 | 54 | ___ 55 | 56 | ### Arc28EventToProcess 57 | 58 | Re-exports [Arc28EventToProcess](../interfaces/types_arc_28.Arc28EventToProcess.md) 59 | 60 | ___ 61 | 62 | ### AsyncEventEmitter 63 | 64 | Re-exports [AsyncEventEmitter](../classes/types_async_event_emitter.AsyncEventEmitter.md) 65 | 66 | ___ 67 | 68 | ### AsyncEventListener 69 | 70 | Re-exports [AsyncEventListener](types_async_event_emitter.md#asynceventlistener) 71 | 72 | ___ 73 | 74 | ### BalanceChange 75 | 76 | Re-exports [BalanceChange](../interfaces/types_subscription.BalanceChange.md) 77 | 78 | ___ 79 | 80 | ### BalanceChangeRole 81 | 82 | Re-exports [BalanceChangeRole](../enums/types_subscription.BalanceChangeRole.md) 83 | 84 | ___ 85 | 86 | ### BeforePollMetadata 87 | 88 | Re-exports [BeforePollMetadata](../interfaces/types_subscription.BeforePollMetadata.md) 89 | 90 | ___ 91 | 92 | ### BlockMetadata 93 | 94 | Re-exports [BlockMetadata](../interfaces/types_subscription.BlockMetadata.md) 95 | 96 | ___ 97 | 98 | ### BlockRewards 99 | 100 | Re-exports [BlockRewards](../interfaces/types_subscription.BlockRewards.md) 101 | 102 | ___ 103 | 104 | ### BlockStateProofTracking 105 | 106 | Re-exports [BlockStateProofTracking](../interfaces/types_subscription.BlockStateProofTracking.md) 107 | 108 | ___ 109 | 110 | ### BlockUpgradeState 111 | 112 | Re-exports [BlockUpgradeState](../interfaces/types_subscription.BlockUpgradeState.md) 113 | 114 | ___ 115 | 116 | ### BlockUpgradeVote 117 | 118 | Re-exports [BlockUpgradeVote](../interfaces/types_subscription.BlockUpgradeVote.md) 119 | 120 | ___ 121 | 122 | ### CoreTransactionSubscriptionParams 123 | 124 | Re-exports [CoreTransactionSubscriptionParams](../interfaces/types_subscription.CoreTransactionSubscriptionParams.md) 125 | 126 | ___ 127 | 128 | ### EmittedArc28Event 129 | 130 | Re-exports [EmittedArc28Event](../interfaces/types_arc_28.EmittedArc28Event.md) 131 | 132 | ___ 133 | 134 | ### ErrorListener 135 | 136 | Re-exports [ErrorListener](types_subscription.md#errorlistener) 137 | 138 | ___ 139 | 140 | ### NamedTransactionFilter 141 | 142 | Re-exports [NamedTransactionFilter](../interfaces/types_subscription.NamedTransactionFilter.md) 143 | 144 | ___ 145 | 146 | ### ParticipationUpdates 147 | 148 | Re-exports [ParticipationUpdates](../interfaces/types_subscription.ParticipationUpdates.md) 149 | 150 | ___ 151 | 152 | ### SubscribedTransaction 153 | 154 | Re-exports [SubscribedTransaction](../classes/types_subscription.SubscribedTransaction.md) 155 | 156 | ___ 157 | 158 | ### SubscriberConfigFilter 159 | 160 | Re-exports [SubscriberConfigFilter](../interfaces/types_subscription.SubscriberConfigFilter.md) 161 | 162 | ___ 163 | 164 | ### TransactionFilter 165 | 166 | Re-exports [TransactionFilter](../interfaces/types_subscription.TransactionFilter.md) 167 | 168 | ___ 169 | 170 | ### TransactionInBlock 171 | 172 | Re-exports [TransactionInBlock](../interfaces/types_block.TransactionInBlock.md) 173 | 174 | ___ 175 | 176 | ### TransactionSubscriptionParams 177 | 178 | Re-exports [TransactionSubscriptionParams](../interfaces/types_subscription.TransactionSubscriptionParams.md) 179 | 180 | ___ 181 | 182 | ### TransactionSubscriptionResult 183 | 184 | Re-exports [TransactionSubscriptionResult](../interfaces/types_subscription.TransactionSubscriptionResult.md) 185 | 186 | ___ 187 | 188 | ### TypedAsyncEventListener 189 | 190 | Re-exports [TypedAsyncEventListener](types_subscription.md#typedasynceventlistener) 191 | -------------------------------------------------------------------------------- /docs/code/modules/types_arc_28.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / types/arc-28 2 | 3 | # Module: types/arc-28 4 | 5 | ## Table of contents 6 | 7 | ### Interfaces 8 | 9 | - [Arc28Event](../interfaces/types_arc_28.Arc28Event.md) 10 | - [Arc28EventGroup](../interfaces/types_arc_28.Arc28EventGroup.md) 11 | - [Arc28EventToProcess](../interfaces/types_arc_28.Arc28EventToProcess.md) 12 | - [EmittedArc28Event](../interfaces/types_arc_28.EmittedArc28Event.md) 13 | -------------------------------------------------------------------------------- /docs/code/modules/types_async_event_emitter.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / types/async-event-emitter 2 | 3 | # Module: types/async-event-emitter 4 | 5 | ## Table of contents 6 | 7 | ### Classes 8 | 9 | - [AsyncEventEmitter](../classes/types_async_event_emitter.AsyncEventEmitter.md) 10 | 11 | ### Type Aliases 12 | 13 | - [AsyncEventListener](types_async_event_emitter.md#asynceventlistener) 14 | 15 | ## Type Aliases 16 | 17 | ### AsyncEventListener 18 | 19 | Ƭ **AsyncEventListener**: (`event`: `unknown`, `eventName`: `string` \| `symbol`) => `Promise`\<`void`\> \| `void` 20 | 21 | An asynchronous event listener 22 | 23 | #### Type declaration 24 | 25 | ▸ (`event`, `eventName`): `Promise`\<`void`\> \| `void` 26 | 27 | ##### Parameters 28 | 29 | | Name | Type | 30 | | :------ | :------ | 31 | | `event` | `unknown` | 32 | | `eventName` | `string` \| `symbol` | 33 | 34 | ##### Returns 35 | 36 | `Promise`\<`void`\> \| `void` 37 | 38 | #### Defined in 39 | 40 | [src/types/async-event-emitter.ts:4](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/async-event-emitter.ts#L4) 41 | -------------------------------------------------------------------------------- /docs/code/modules/types_block.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / types/block 2 | 3 | # Module: types/block 4 | 5 | ## Table of contents 6 | 7 | ### Interfaces 8 | 9 | - [TransactionInBlock](../interfaces/types_block.TransactionInBlock.md) 10 | -------------------------------------------------------------------------------- /docs/code/modules/types_subscription.md: -------------------------------------------------------------------------------- 1 | [@algorandfoundation/algokit-subscriber](../README.md) / types/subscription 2 | 3 | # Module: types/subscription 4 | 5 | ## Table of contents 6 | 7 | ### Enumerations 8 | 9 | - [BalanceChangeRole](../enums/types_subscription.BalanceChangeRole.md) 10 | 11 | ### Classes 12 | 13 | - [SubscribedTransaction](../classes/types_subscription.SubscribedTransaction.md) 14 | 15 | ### Interfaces 16 | 17 | - [AlgorandSubscriberConfig](../interfaces/types_subscription.AlgorandSubscriberConfig.md) 18 | - [BalanceChange](../interfaces/types_subscription.BalanceChange.md) 19 | - [BeforePollMetadata](../interfaces/types_subscription.BeforePollMetadata.md) 20 | - [BlockMetadata](../interfaces/types_subscription.BlockMetadata.md) 21 | - [BlockRewards](../interfaces/types_subscription.BlockRewards.md) 22 | - [BlockStateProofTracking](../interfaces/types_subscription.BlockStateProofTracking.md) 23 | - [BlockUpgradeState](../interfaces/types_subscription.BlockUpgradeState.md) 24 | - [BlockUpgradeVote](../interfaces/types_subscription.BlockUpgradeVote.md) 25 | - [CoreTransactionSubscriptionParams](../interfaces/types_subscription.CoreTransactionSubscriptionParams.md) 26 | - [NamedTransactionFilter](../interfaces/types_subscription.NamedTransactionFilter.md) 27 | - [ParticipationUpdates](../interfaces/types_subscription.ParticipationUpdates.md) 28 | - [SubscriberConfigFilter](../interfaces/types_subscription.SubscriberConfigFilter.md) 29 | - [TransactionFilter](../interfaces/types_subscription.TransactionFilter.md) 30 | - [TransactionSubscriptionParams](../interfaces/types_subscription.TransactionSubscriptionParams.md) 31 | - [TransactionSubscriptionResult](../interfaces/types_subscription.TransactionSubscriptionResult.md) 32 | 33 | ### Type Aliases 34 | 35 | - [ErrorListener](types_subscription.md#errorlistener) 36 | - [TypedAsyncEventListener](types_subscription.md#typedasynceventlistener) 37 | 38 | ## Type Aliases 39 | 40 | ### ErrorListener 41 | 42 | Ƭ **ErrorListener**: (`error`: `unknown`) => `Promise`\<`void`\> \| `void` 43 | 44 | #### Type declaration 45 | 46 | ▸ (`error`): `Promise`\<`void`\> \| `void` 47 | 48 | ##### Parameters 49 | 50 | | Name | Type | 51 | | :------ | :------ | 52 | | `error` | `unknown` | 53 | 54 | ##### Returns 55 | 56 | `Promise`\<`void`\> \| `void` 57 | 58 | #### Defined in 59 | 60 | [src/types/subscription.ts:416](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L416) 61 | 62 | ___ 63 | 64 | ### TypedAsyncEventListener 65 | 66 | Ƭ **TypedAsyncEventListener**\<`T`\>: (`event`: `T`, `eventName`: `string` \| `symbol`) => `Promise`\<`void`\> \| `void` 67 | 68 | #### Type parameters 69 | 70 | | Name | 71 | | :------ | 72 | | `T` | 73 | 74 | #### Type declaration 75 | 76 | ▸ (`event`, `eventName`): `Promise`\<`void`\> \| `void` 77 | 78 | ##### Parameters 79 | 80 | | Name | Type | 81 | | :------ | :------ | 82 | | `event` | `T` | 83 | | `eventName` | `string` \| `symbol` | 84 | 85 | ##### Returns 86 | 87 | `Promise`\<`void`\> \| `void` 88 | 89 | #### Defined in 90 | 91 | [src/types/subscription.ts:414](https://github.com/algorandfoundation/algokit-subscriber-ts/blob/main/src/types/subscription.ts#L414) 92 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import eslint from '@eslint/js' 2 | import prettier from 'eslint-config-prettier' 3 | import tseslint from 'typescript-eslint' 4 | 5 | export default tseslint.config( 6 | { 7 | ignores: ['.eslint.config.mjs', 'node_modules/**', 'dist/**', 'build/**', 'coverage/**', '**/*.d.ts', '.idea/**', '.vscode/**'], 8 | }, 9 | eslint.configs.recommended, 10 | tseslint.configs.recommended, 11 | { 12 | files: ['**/*.ts'], 13 | languageOptions: { 14 | parser: tseslint.parser, 15 | parserOptions: { 16 | project: './tsconfig.json', 17 | ecmaVersion: 'latest', 18 | sourceType: 'module', 19 | }, 20 | }, 21 | rules: { 22 | 'no-console': 'warn', 23 | '@typescript-eslint/no-unused-vars': [ 24 | 'error', 25 | { ignoreRestSiblings: true, argsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_', varsIgnorePattern: '^_' }, 26 | ], 27 | '@typescript-eslint/no-unused-expressions': 'off', 28 | 'prefer-template': 'error', 29 | }, 30 | }, 31 | prettier, 32 | ) 33 | -------------------------------------------------------------------------------- /examples/data-history-museum/index.ts: -------------------------------------------------------------------------------- 1 | import { AlgorandClient } from '@algorandfoundation/algokit-utils' 2 | import { TransactionType } from 'algosdk' 3 | import fs from 'fs' 4 | import path from 'path' 5 | import { AlgorandSubscriber } from '../../src/subscriber' 6 | import { SubscribedTransaction } from '../../src/types' 7 | 8 | if (!fs.existsSync(path.join(__dirname, '..', '..', '.env')) && !process.env.ALGOD_SERVER) { 9 | // eslint-disable-next-line no-console 10 | console.error('Copy /.env.sample to /.env before starting the application.') 11 | process.exit(1) 12 | } 13 | 14 | interface DHMAsset { 15 | id: string 16 | name: string 17 | unit: string 18 | mediaUrl: string 19 | metadata: Record 20 | created: string 21 | lastModified: string 22 | } 23 | 24 | async function getDHMSubscriber() { 25 | const algorand = AlgorandClient.testNet() 26 | 27 | const subscriber = new AlgorandSubscriber( 28 | { 29 | filters: [ 30 | { 31 | name: 'dhm-asset', 32 | filter: { 33 | type: TransactionType.acfg, 34 | // Data History Museum creator accounts 35 | sender: (await algorand.client.isTestNet()) 36 | ? 'ER7AMZRPD5KDVFWTUUVOADSOWM4RQKEEV2EDYRVSA757UHXOIEKGMBQIVU' 37 | : 'EHYQCYHUC6CIWZLBX5TDTLVJ4SSVE4RRTMKFDCG4Z4Q7QSQ2XWIQPMKBPU', 38 | }, 39 | }, 40 | ], 41 | frequencyInSeconds: 5, 42 | maxRoundsToSync: 100, 43 | syncBehaviour: 'catchup-with-indexer', 44 | watermarkPersistence: { 45 | get: getLastWatermark, 46 | set: saveWatermark, 47 | }, 48 | }, 49 | algorand.client.algod, 50 | algorand.client.indexer, 51 | ) 52 | subscriber.onBatch('dhm-asset', async (events) => { 53 | // eslint-disable-next-line no-console 54 | console.log(`Received ${events.length} asset changes`) 55 | // Save all of the Data History Museum Verifiably Authentic Digital Historical Artifacts 56 | await saveDHMTransactions(events) 57 | }) 58 | return subscriber 59 | } 60 | 61 | function getArc69Metadata(t: SubscribedTransaction) { 62 | let metadata = {} 63 | try { 64 | if (t.note) { 65 | const buff = Buffer.from(t.note) 66 | if (buff.toString('base64').startsWith('ey')) { 67 | metadata = JSON.parse(buff.toString('utf-8')) 68 | } 69 | } 70 | // eslint-disable-next-line no-empty 71 | } catch (e) {} 72 | return metadata 73 | } 74 | 75 | async function saveDHMTransactions(transactions: SubscribedTransaction[]) { 76 | const assets = await getSavedTransactions('dhm-assets.json') 77 | 78 | for (const t of transactions) { 79 | if (t.createdAssetIndex) { 80 | assets.push({ 81 | id: t.createdAssetIndex.toString(), 82 | name: t.assetConfigTransaction!.params!.name!, 83 | unit: t.assetConfigTransaction!.params!.unitName!, 84 | mediaUrl: t.assetConfigTransaction!.params!.url!, 85 | metadata: getArc69Metadata(t), 86 | created: new Date(t.roundTime! * 1000).toISOString(), 87 | lastModified: new Date(t.roundTime! * 1000).toISOString(), 88 | }) 89 | } else { 90 | const asset = assets.find((a) => a.id === t.assetConfigTransaction!.assetId!.toString()) 91 | if (!asset) { 92 | // eslint-disable-next-line no-console 93 | console.error(t) 94 | throw new Error(`Unable to find existing asset data for ${t.assetConfigTransaction!.assetId}`) 95 | } 96 | if (!t.assetConfigTransaction!.params) { 97 | // Asset was deleted, remove it 98 | assets.splice(assets.indexOf(asset), 1) 99 | } else { 100 | asset!.metadata = getArc69Metadata(t) 101 | asset!.lastModified = new Date(t.roundTime! * 1000).toISOString() 102 | } 103 | } 104 | } 105 | 106 | await saveTransactions(assets, 'dhm-assets.json') 107 | } 108 | 109 | // Basic methods that persist using filesystem - for illustrative purposes only 110 | 111 | async function saveWatermark(watermark: bigint) { 112 | fs.writeFileSync(path.join(__dirname, 'watermark.txt'), watermark.toString(), { encoding: 'utf-8' }) 113 | } 114 | 115 | async function getLastWatermark(): Promise { 116 | if (!fs.existsSync(path.join(__dirname, 'watermark.txt'))) return 0n 117 | const existing = fs.readFileSync(path.join(__dirname, 'watermark.txt'), 'utf-8') 118 | // eslint-disable-next-line no-console 119 | console.log(`Found existing sync watermark in watermark.txt; syncing from ${existing}`) 120 | return BigInt(existing) 121 | } 122 | 123 | async function getSavedTransactions(fileName: string): Promise { 124 | const existing = fs.existsSync(path.join(__dirname, fileName)) 125 | ? (JSON.parse(fs.readFileSync(path.join(__dirname, fileName), 'utf-8')) as T[]) 126 | : [] 127 | return existing 128 | } 129 | 130 | async function saveTransactions(transactions: unknown[], fileName: string) { 131 | fs.writeFileSync(path.join(__dirname, fileName), asJson(transactions), { encoding: 'utf-8' }) 132 | // eslint-disable-next-line no-console 133 | console.log(`Saved ${transactions.length} transactions to ${fileName}`) 134 | } 135 | 136 | ;(async () => { 137 | const subscriber = await getDHMSubscriber() 138 | 139 | if (process.env.RUN_LOOP === 'true') { 140 | // Restart on error 141 | const maxRetries = 3 142 | let retryCount = 0 143 | subscriber.onError(async (e) => { 144 | retryCount++ 145 | if (retryCount > maxRetries) { 146 | // eslint-disable-next-line no-console 147 | console.error(e) 148 | return 149 | } 150 | // eslint-disable-next-line no-console 151 | console.log(`Error occurred, retrying in 2 seconds (${retryCount}/${maxRetries})`) 152 | await new Promise((r) => setTimeout(r, 2_000)) 153 | subscriber.start() 154 | }) 155 | 156 | subscriber.start() 157 | ;['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach((signal) => 158 | process.on(signal, () => { 159 | // eslint-disable-next-line no-console 160 | console.log(`Received ${signal}; stopping subscriber...`) 161 | subscriber.stop(signal) 162 | }), 163 | ) 164 | } else { 165 | await subscriber.pollOnce() 166 | } 167 | })().catch((e) => { 168 | // eslint-disable-next-line no-console 169 | console.error(e) 170 | }) 171 | 172 | export const asJson = (value: unknown) => JSON.stringify(value, (_, v) => (typeof v === 'bigint' ? v.toString() : v), 2) 173 | -------------------------------------------------------------------------------- /examples/usdc/index.ts: -------------------------------------------------------------------------------- 1 | import { AlgorandClient } from '@algorandfoundation/algokit-utils' 2 | import algosdk from 'algosdk' 3 | import fs from 'fs' 4 | import path from 'path' 5 | import { AlgorandSubscriber } from '../../src/subscriber' 6 | import TransactionType = algosdk.TransactionType 7 | 8 | if (!fs.existsSync(path.join(__dirname, '..', '..', '.env')) && !process.env.ALGOD_SERVER) { 9 | // eslint-disable-next-line no-console 10 | console.error('Copy /.env.sample to /.env before starting the application.') 11 | process.exit(1) 12 | } 13 | 14 | ;(async () => { 15 | const algorand = AlgorandClient.testNet() 16 | let watermark = 0n 17 | 18 | const subscriber = new AlgorandSubscriber( 19 | { 20 | filters: [ 21 | { 22 | name: 'usdc', 23 | filter: { 24 | type: TransactionType.axfer, 25 | assetId: 31566704n, // MainNet: USDC 26 | minAmount: 1_000_000n, // $1 27 | }, 28 | }, 29 | ], 30 | waitForBlockWhenAtTip: true, 31 | syncBehaviour: 'skip-sync-newest', 32 | watermarkPersistence: { 33 | get: async () => watermark, 34 | set: async (newWatermark) => { 35 | watermark = newWatermark 36 | }, 37 | }, 38 | }, 39 | algorand.client.algod, 40 | ) 41 | subscriber.on('usdc', (transfer) => { 42 | // eslint-disable-next-line no-console 43 | console.log( 44 | `${transfer.sender} sent ${transfer.assetTransferTransaction?.receiver} USDC$${Number( 45 | (transfer.assetTransferTransaction?.amount ?? 0n) / 1_000_000n, 46 | ).toFixed(2)} in transaction ${transfer.id}`, 47 | ) 48 | }) 49 | subscriber.onError((e) => { 50 | // eslint-disable-next-line no-console 51 | console.error(e) 52 | }) 53 | subscriber.start() 54 | ;['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach((signal) => 55 | process.on(signal, () => { 56 | // eslint-disable-next-line no-console 57 | console.log(`Received ${signal}; stopping subscriber...`) 58 | subscriber.stop(signal) 59 | }), 60 | ) 61 | })().catch((e) => { 62 | // eslint-disable-next-line no-console 63 | console.error(e) 64 | }) 65 | -------------------------------------------------------------------------------- /examples/xgov-voting/prisma/migrations/20240619152002_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "VotingRound" ( 3 | "id" TEXT NOT NULL PRIMARY KEY, 4 | "title" TEXT NOT NULL 5 | ); 6 | 7 | -- CreateTable 8 | CREATE TABLE "VotingRoundQuestion" ( 9 | "id" TEXT NOT NULL PRIMARY KEY, 10 | "votingRoundId" TEXT NOT NULL, 11 | "prompt" TEXT NOT NULL, 12 | CONSTRAINT "VotingRoundQuestion_votingRoundId_fkey" FOREIGN KEY ("votingRoundId") REFERENCES "VotingRound" ("id") ON DELETE RESTRICT ON UPDATE CASCADE 13 | ); 14 | 15 | -- CreateTable 16 | CREATE TABLE "VotingRoundQuestionOption" ( 17 | "id" TEXT NOT NULL PRIMARY KEY, 18 | "questionId" TEXT NOT NULL, 19 | "optionIndex" INTEGER NOT NULL, 20 | "prompt" TEXT NOT NULL, 21 | CONSTRAINT "VotingRoundQuestionOption_questionId_fkey" FOREIGN KEY ("questionId") REFERENCES "VotingRoundQuestion" ("id") ON DELETE RESTRICT ON UPDATE CASCADE 22 | ); 23 | 24 | -- CreateTable 25 | CREATE TABLE "Vote" ( 26 | "id" TEXT NOT NULL PRIMARY KEY, 27 | "castedAt" TEXT NOT NULL, 28 | "voterAddress" TEXT NOT NULL, 29 | "votingRoundId" TEXT NOT NULL, 30 | CONSTRAINT "Vote_votingRoundId_fkey" FOREIGN KEY ("votingRoundId") REFERENCES "VotingRound" ("id") ON DELETE RESTRICT ON UPDATE CASCADE 31 | ); 32 | 33 | -- CreateTable 34 | CREATE TABLE "VoteCast" ( 35 | "id" TEXT NOT NULL PRIMARY KEY, 36 | "voteId" TEXT NOT NULL, 37 | "questionOptionId" TEXT NOT NULL, 38 | "optionIndex" INTEGER NOT NULL, 39 | "voteWeight" TEXT NOT NULL, 40 | "votingRoundQuestionId" TEXT, 41 | CONSTRAINT "VoteCast_voteId_fkey" FOREIGN KEY ("voteId") REFERENCES "Vote" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, 42 | CONSTRAINT "VoteCast_questionOptionId_fkey" FOREIGN KEY ("questionOptionId") REFERENCES "VotingRoundQuestionOption" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, 43 | CONSTRAINT "VoteCast_votingRoundQuestionId_fkey" FOREIGN KEY ("votingRoundQuestionId") REFERENCES "VotingRoundQuestion" ("id") ON DELETE SET NULL ON UPDATE CASCADE 44 | ); 45 | 46 | -- CreateTable 47 | CREATE TABLE "Watermark" ( 48 | "id" TEXT NOT NULL PRIMARY KEY, 49 | "watermark" INTEGER NOT NULL, 50 | "updated" TEXT NOT NULL 51 | ); 52 | -------------------------------------------------------------------------------- /examples/xgov-voting/prisma/migrations/20240619153715_tweak/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `votingRoundQuestionId` on the `VoteCast` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- RedefineTables 8 | PRAGMA defer_foreign_keys=ON; 9 | PRAGMA foreign_keys=OFF; 10 | CREATE TABLE "new_VoteCast" ( 11 | "id" TEXT NOT NULL PRIMARY KEY, 12 | "voteId" TEXT NOT NULL, 13 | "questionOptionId" TEXT NOT NULL, 14 | "optionIndex" INTEGER NOT NULL, 15 | "voteWeight" TEXT NOT NULL, 16 | CONSTRAINT "VoteCast_voteId_fkey" FOREIGN KEY ("voteId") REFERENCES "Vote" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, 17 | CONSTRAINT "VoteCast_questionOptionId_fkey" FOREIGN KEY ("questionOptionId") REFERENCES "VotingRoundQuestionOption" ("id") ON DELETE RESTRICT ON UPDATE CASCADE 18 | ); 19 | INSERT INTO "new_VoteCast" ("id", "optionIndex", "questionOptionId", "voteId", "voteWeight") SELECT "id", "optionIndex", "questionOptionId", "voteId", "voteWeight" FROM "VoteCast"; 20 | DROP TABLE "VoteCast"; 21 | ALTER TABLE "new_VoteCast" RENAME TO "VoteCast"; 22 | PRAGMA foreign_keys=ON; 23 | PRAGMA defer_foreign_keys=OFF; 24 | -------------------------------------------------------------------------------- /examples/xgov-voting/prisma/migrations/20241217122304_bigint/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to alter the column `watermark` on the `Watermark` table. The data in that column could be lost. The data in that column will be cast from `Int` to `BigInt`. 5 | 6 | */ 7 | -- RedefineTables 8 | PRAGMA defer_foreign_keys=ON; 9 | PRAGMA foreign_keys=OFF; 10 | CREATE TABLE "new_Watermark" ( 11 | "id" TEXT NOT NULL PRIMARY KEY, 12 | "watermark" BIGINT NOT NULL, 13 | "updated" TEXT NOT NULL 14 | ); 15 | INSERT INTO "new_Watermark" ("id", "updated", "watermark") SELECT "id", "updated", "watermark" FROM "Watermark"; 16 | DROP TABLE "Watermark"; 17 | ALTER TABLE "new_Watermark" RENAME TO "Watermark"; 18 | PRAGMA foreign_keys=ON; 19 | PRAGMA defer_foreign_keys=OFF; 20 | -------------------------------------------------------------------------------- /examples/xgov-voting/prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "sqlite" -------------------------------------------------------------------------------- /examples/xgov-voting/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | } 4 | 5 | datasource db { 6 | provider = "sqlite" 7 | url = env("DATABASE_URL") 8 | } 9 | 10 | model VotingRound { 11 | // App ID of the voting round contract instance 12 | id String @id 13 | title String 14 | questions VotingRoundQuestion[] 15 | votes Vote[] 16 | } 17 | 18 | model VotingRoundQuestion { 19 | id String @id 20 | votingRoundId String 21 | votingRound VotingRound @relation(fields: [votingRoundId], references: [id]) 22 | prompt String 23 | options VotingRoundQuestionOption[] 24 | } 25 | 26 | model VotingRoundQuestionOption { 27 | id String @id 28 | questionId String 29 | question VotingRoundQuestion @relation(fields: [questionId], references: [id]) 30 | optionIndex Int 31 | prompt String 32 | VoteCast VoteCast[] 33 | } 34 | 35 | model Vote { 36 | // Transaction ID of the vote 37 | id String @id 38 | castedAt String 39 | // Account address of the voter 40 | voterAddress String 41 | castedVotes VoteCast[] 42 | votingRound VotingRound @relation(fields: [votingRoundId], references: [id]) 43 | votingRoundId String 44 | } 45 | 46 | model VoteCast { 47 | id String @id 48 | voteId String 49 | vote Vote @relation(fields: [voteId], references: [id]) 50 | questionOptionId String 51 | questionOption VotingRoundQuestionOption @relation(fields: [questionOptionId], references: [id]) 52 | optionIndex Int 53 | voteWeight String 54 | } 55 | 56 | model Watermark { 57 | id String @id 58 | watermark BigInt 59 | updated String 60 | } 61 | -------------------------------------------------------------------------------- /examples/xgov-voting/types/voting-round.ts: -------------------------------------------------------------------------------- 1 | // From: https://github.com/algorandfoundation/nft_voting_tool/blob/develop/src/dapp/src/shared/IPFSGateway.ts 2 | 3 | export enum VoteType { 4 | NO_SNAPSHOT = 0, 5 | NO_WEIGHTING = 1, 6 | WEIGHTING = 2, 7 | PARTITIONED_WEIGHTING = 3, 8 | } 9 | 10 | /** A discrete opportunity for vote casters to participate in a vote for a given context, this may consist of one or more questions */ 11 | export interface VotingRoundMetadata { 12 | id: string 13 | /** 14 | * Metadata Semantic Version 15 | */ 16 | version?: string 17 | type: VoteType 18 | title: string 19 | description?: string 20 | /** Optional URL link to more information */ 21 | informationUrl?: string 22 | /** Start of voting round as an ISO8601 string */ 23 | start: string 24 | /** End of voting round as an ISO8601 string */ 25 | end: string 26 | /** Optional quorum of participants for a valid result */ 27 | quorum?: number 28 | /** The optional IPFS content ID of the vote gating snapshot used for this voting round */ 29 | voteGatingSnapshotCid?: string 30 | /** The questions being voted on as part of the voting round */ 31 | questions: Question[] 32 | created: CreatedMetadata 33 | /** The total amount allocated for the community grants program aka xGov 34 | * this is optional for backwards compatibility 35 | */ 36 | communityGrantAllocation?: number 37 | } 38 | 39 | export interface Question { 40 | /** UUID of the question */ 41 | id: string 42 | /** The question prompt text */ 43 | prompt: string 44 | description?: string 45 | metadata?: { 46 | link?: string 47 | category?: string 48 | focus_area?: string 49 | threshold?: number 50 | ask?: number 51 | } 52 | options: Option[] 53 | } 54 | 55 | export interface Option { 56 | /** UUID of the option */ 57 | id: string 58 | /** The text description of the option */ 59 | label: string 60 | } 61 | 62 | export interface VoteGatingSnapshot { 63 | title: string 64 | /** 65 | * Snapshot Semantic Version 66 | */ 67 | version?: string 68 | /** Base 64 encoded public key corresponding to the ephemeral private key that was created to secure this snapshot */ 69 | publicKey: string 70 | created: CreatedMetadata 71 | /** The snapshot of vote gates */ 72 | snapshot: Gate[] 73 | } 74 | 75 | export interface Gate { 76 | /** Address of the account that is gated to vote */ 77 | address: string 78 | /** The vote weighting of the account that is gated to vote */ 79 | weight?: number 80 | /** Base 64 encoded signature of `{address}{weight(uint64)|string}` with the private key of this using ED25519 */ 81 | signature: string 82 | } 83 | 84 | export interface CreatedMetadata { 85 | /** When the record was created, in ISO8601 format */ 86 | at: string 87 | /** Account address of the creator */ 88 | by: string 89 | } 90 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@algorandfoundation/algokit-subscriber", 3 | "version": "1.0.0", 4 | "private": false, 5 | "scripts": { 6 | "build": "run-s build:*", 7 | "build:0-clean": "rimraf dist coverage", 8 | "build:1-compile": "rollup -c --configPlugin typescript --configImportAttributesKey with", 9 | "build:2-copy-pkg-json": "npx --yes @makerx/ts-toolkit@4.0.0-beta.22 copy-package-json --custom-sections module main type types exports", 10 | "build:3-copy-readme": "cpy README.md dist", 11 | "test": "vitest run --coverage --passWithNoTests", 12 | "test:watch": "vitest watch --coverage --passWithNoTests", 13 | "lint": "eslint \"src/**/*.ts\"", 14 | "lint:fix": "eslint \"src/**/*.ts\" --fix", 15 | "check-types": "tsc --noEmit", 16 | "audit": "better-npm-audit audit", 17 | "format": "prettier --write .", 18 | "commit-lint": "commitlint --edit -o", 19 | "semantic-release": "semantic-release", 20 | "generate:code-docs": "typedoc", 21 | "generate:contract-client": "cd tests/contract && poetry run python -m build", 22 | "pre-commit": "run-s check-types lint:fix audit format test generate:code-docs", 23 | "dhm": "ts-node-dev --project tsconfig.dev.json --transpile-only --watch .env -r dotenv/config ./examples/data-history-museum/index.ts", 24 | "watch-dhm": "cross-env RUN_LOOP=true npm run dhm", 25 | "xgov": "npx prisma migrate dev && ts-node-dev --project tsconfig.dev.json --transpile-only --watch .env -r dotenv/config ./examples/xgov-voting/index.ts", 26 | "watch-xgov": "cross-env RUN_LOOP=true npm run xgov", 27 | "usdc": "ts-node-dev --project tsconfig.dev.json --transpile-only --watch .env -r dotenv/config ./examples/usdc/index.ts" 28 | }, 29 | "prisma": { 30 | "schema": "examples/xgov-voting/prisma/schema.prisma" 31 | }, 32 | "author": "Algorand Foundation", 33 | "license": "MIT", 34 | "engines": { 35 | "node": ">=18.0" 36 | }, 37 | "type": "commonjs", 38 | "main": "index.js", 39 | "module": "index.mjs", 40 | "types": "index.d.ts", 41 | "files": [ 42 | "**/*" 43 | ], 44 | "exports": { 45 | ".": { 46 | "types": "./index.d.ts", 47 | "import": "./index.mjs", 48 | "require": "./index.js" 49 | }, 50 | "./block": { 51 | "types": "./block.d.ts", 52 | "import": "./block.mjs", 53 | "require": "./block.js" 54 | }, 55 | "./transform": { 56 | "types": "./transform.d.ts", 57 | "import": "./transform.mjs", 58 | "require": "./transform.js" 59 | }, 60 | "./types/*": { 61 | "types": "./types/*.d.ts", 62 | "import": "./types/*.mjs", 63 | "require": "./types/*.js" 64 | }, 65 | "./index.d.ts": "./index.d.ts", 66 | "./package.json": "./package.json" 67 | }, 68 | "devDependencies": { 69 | "@algorandfoundation/algokit-client-generator": "^5.0.0", 70 | "@commitlint/cli": "^19.2.1", 71 | "@commitlint/config-conventional": "^19.1.0", 72 | "@eslint/js": "^9.16.0", 73 | "@makerx/prettier-config": "^2.0.1", 74 | "@prisma/client": "^5.15.1", 75 | "@rollup/plugin-typescript": "^12.1.2", 76 | "@vitest/coverage-v8": "^2.1.8", 77 | "better-npm-audit": "^3.7.3", 78 | "conventional-changelog-conventionalcommits": "^7.0.2", 79 | "cpy-cli": "^5.0.0", 80 | "cross-env": "^7.0.3", 81 | "dotenv-cli": "^7.4.1", 82 | "eslint-config-prettier": "^9.1.0", 83 | "npm-run-all": "^4.1.5", 84 | "prettier": "3.2.5", 85 | "prisma": "^5.15.1", 86 | "rimraf": "^5.0.5", 87 | "rollup": "^4.41.1", 88 | "semantic-release": "^23.0.6", 89 | "tiny-invariant": "^1.3.3", 90 | "ts-node-dev": "^2.0.0", 91 | "typedoc": "^0.25.4", 92 | "typedoc-plugin-markdown": "^3.17.1", 93 | "typescript": "^5.4.3", 94 | "typescript-eslint": "^8.16.0", 95 | "vitest": "^2.1.9" 96 | }, 97 | "dependencies": { 98 | "js-sha512": "^0.9.0" 99 | }, 100 | "peerDependencies": { 101 | "@algorandfoundation/algokit-utils": "^9.0.0", 102 | "algosdk": "^3.0.0" 103 | }, 104 | "publishConfig": { 105 | "access": "public" 106 | }, 107 | "overrides": { 108 | "esbuild": "0.25.0" 109 | }, 110 | "release": { 111 | "branches": [ 112 | { 113 | "name": "main", 114 | "prerelease": "beta" 115 | }, 116 | { 117 | "name": "release" 118 | } 119 | ], 120 | "plugins": [ 121 | [ 122 | "@semantic-release/commit-analyzer", 123 | { 124 | "preset": "conventionalcommits", 125 | "releaseRules": [ 126 | { 127 | "type": "build", 128 | "release": "patch" 129 | }, 130 | { 131 | "type": "chore", 132 | "release": "patch" 133 | } 134 | ] 135 | } 136 | ], 137 | [ 138 | "@semantic-release/release-notes-generator", 139 | { 140 | "preset": "conventionalcommits", 141 | "presetConfig": { 142 | "types": [ 143 | { 144 | "type": "feat", 145 | "section": "Features" 146 | }, 147 | { 148 | "type": "fix", 149 | "section": "Bug Fixes" 150 | }, 151 | { 152 | "type": "build", 153 | "section": "Dependencies and Other Build Updates", 154 | "hidden": false 155 | } 156 | ] 157 | } 158 | } 159 | ], 160 | [ 161 | "@semantic-release/npm", 162 | { 163 | "pkgRoot": "dist" 164 | } 165 | ], 166 | "@semantic-release/github" 167 | ] 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /rollup.config.ts: -------------------------------------------------------------------------------- 1 | import typescript from '@rollup/plugin-typescript' 2 | import type { LogLevel, LogOrStringHandler, RollupLog } from 'rollup' 3 | import { RollupOptions } from 'rollup' 4 | import pkg from './package.json' with { type: 'json' } 5 | import { multiInput } from './rollup/multi-plugin' 6 | 7 | const config: RollupOptions = { 8 | input: ['src/index.ts', 'src/testing/index.ts', 'src/types/*.ts', '!src/types/*.spec.ts'], 9 | output: [ 10 | { 11 | dir: 'dist', 12 | format: 'cjs', 13 | entryFileNames: '[name].js', 14 | preserveModules: true, 15 | sourcemap: true, 16 | dynamicImportInCjs: false, 17 | }, 18 | { 19 | dir: 'dist', 20 | format: 'es', 21 | entryFileNames: '[name].mjs', 22 | preserveModules: true, 23 | sourcemap: true, 24 | }, 25 | ], 26 | treeshake: { 27 | moduleSideEffects: false, 28 | propertyReadSideEffects: false, 29 | }, 30 | plugins: [ 31 | typescript({ 32 | tsconfig: 'tsconfig.build.json', 33 | }), 34 | multiInput(), 35 | ], 36 | external: [...Object.keys(pkg.dependencies), ...Object.keys(pkg.peerDependencies), '@algorandfoundation/algokit-utils/types/indexer'], 37 | onLog(level: LogLevel, log: RollupLog, handler: LogOrStringHandler) { 38 | if (log.code === 'CIRCULAR_DEPENDENCY') { 39 | handler('error', log) 40 | } else { 41 | handler(level, log) 42 | } 43 | }, 44 | } 45 | 46 | export default config 47 | -------------------------------------------------------------------------------- /rollup/multi-plugin.ts: -------------------------------------------------------------------------------- 1 | import type FastGlob from 'fast-glob' 2 | import fastGlob from 'fast-glob' 3 | import path from 'path' 4 | import type { Plugin } from 'rollup' 5 | 6 | // This was taken from https://github.com/alfredosalzillo/rollup-plugin-multi-input 7 | // We maintain our copy here because rollup-plugin-multi-input has issues with exporting types 8 | 9 | const pluginName = 'rollup-plugin-multi-input' 10 | 11 | const isString = (value: unknown): value is string => typeof value === 'string' 12 | 13 | /** 14 | * default multi-input Options 15 | * */ 16 | const defaultOptions = { 17 | // `path.sep` is used for windows support 18 | relative: `src${path.sep}`, 19 | } 20 | 21 | // extract the output file name from a file name 22 | const outputFileName = (filePath: string) => filePath.replace(/\.[^/.]+$/, '').replace(/\\/g, '/') 23 | 24 | export type MultiInputOptions = { 25 | glob?: FastGlob.Options 26 | relative?: string 27 | transformOutputPath?: (path: string, fileName: string) => string 28 | } 29 | 30 | /** 31 | * multiInput is a rollup plugin to use multiple entry point and preserve the directory 32 | * structure in the dist folder 33 | * */ 34 | export const multiInput = (options: MultiInputOptions = defaultOptions): Plugin => { 35 | const { glob: globOptions, relative = defaultOptions.relative, transformOutputPath } = options 36 | return { 37 | name: pluginName, 38 | options(conf) { 39 | // flat to enable input to be a string or an array 40 | const inputs = [conf.input].flat() 41 | // separate globs inputs string from others to enable input to be a mixed array too 42 | const globs = inputs.filter(isString) 43 | const others = inputs.filter((value) => !isString(value)) 44 | const normalizedGlobs = globs.map((glob) => glob.replace(/\\/g, '/')) 45 | // get files from the globs strings and return as a Rollup entries Object 46 | const entries = fastGlob.sync(normalizedGlobs, globOptions).map((name) => { 47 | const filePath = path.relative(relative, name) 48 | const isRelative = !filePath.startsWith(`..${path.sep}`) 49 | const relativeFilePath = isRelative ? filePath : path.relative(`.${path.sep}`, name) 50 | if (transformOutputPath) { 51 | return [outputFileName(transformOutputPath(relativeFilePath, name)), name] 52 | } 53 | return [outputFileName(relativeFilePath), name] 54 | }) 55 | const input = Object.assign( 56 | {}, 57 | Object.fromEntries(entries), 58 | // add no globs input to the result 59 | ...others, 60 | ) 61 | // return the new configuration with the glob input and the non string inputs 62 | return { 63 | ...conf, 64 | input, 65 | } 66 | }, 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/block.ts: -------------------------------------------------------------------------------- 1 | import { Config } from '@algorandfoundation/algokit-utils' 2 | import algosdk from 'algosdk' 3 | import { chunkArray, range } from './utils' 4 | import Algodv2 = algosdk.Algodv2 5 | 6 | /** 7 | * Retrieves blocks in bulk (30 at a time) between the given round numbers. 8 | * @param context The blocks to retrieve 9 | * @param client The algod client 10 | * @returns The blocks 11 | */ 12 | export async function getBlocksBulk(context: { startRound: bigint; maxRound: bigint }, client: Algodv2) { 13 | // Grab 30 at a time in parallel to not overload the node 14 | const blockChunks = chunkArray(range(context.startRound, context.maxRound), 30) 15 | let blocks: algosdk.modelsv2.BlockResponse[] = [] 16 | for (const chunk of blockChunks) { 17 | Config.logger.info(`Retrieving ${chunk.length} blocks from round ${chunk[0]} via algod`) 18 | const start = +new Date() 19 | blocks = blocks.concat( 20 | await Promise.all( 21 | chunk.map(async (round) => { 22 | return await client.block(round).do() 23 | }), 24 | ), 25 | ) 26 | Config.logger.debug(`Retrieved ${chunk.length} blocks from round ${chunk[0]} via algod in ${(+new Date() - start) / 1000}s`) 27 | } 28 | return blocks 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './subscriber' 2 | export * from './subscriptions' 3 | -------------------------------------------------------------------------------- /src/types/arc-28.ts: -------------------------------------------------------------------------------- 1 | import algosdk from 'algosdk' 2 | import { SubscribedTransaction } from './subscription' 3 | import ABIValue = algosdk.ABIValue 4 | 5 | /** 6 | * The definition of metadata for an ARC-28 event per https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0028.md#event. 7 | */ 8 | export interface Arc28Event { 9 | /** The name of the event */ 10 | name: string 11 | /** Optional, user-friendly description for the event */ 12 | desc?: string 13 | /** The arguments of the event, in order */ 14 | args: Array<{ 15 | /** The type of the argument */ 16 | type: string 17 | /** Optional, user-friendly name for the argument */ 18 | name?: string 19 | /** Optional, user-friendly description for the argument */ 20 | desc?: string 21 | }> 22 | } 23 | 24 | /** An ARC-28 event to be processed */ 25 | export interface Arc28EventToProcess { 26 | /** The name of the ARC-28 event group the event belongs to */ 27 | groupName: string 28 | /** The name of the ARC-28 event that was triggered */ 29 | eventName: string 30 | /** The signature of the event e.g. `EventName(type1,type2)` */ 31 | eventSignature: string 32 | /** The 4-byte hex prefix for the event */ 33 | eventPrefix: string 34 | /** The ARC-28 definition of the event */ 35 | eventDefinition: Arc28Event 36 | } 37 | 38 | /** An emitted ARC-28 event extracted from an app call log. */ 39 | export interface EmittedArc28Event extends Arc28EventToProcess { 40 | /** The ordered arguments extracted from the event that was emitted */ 41 | args: ABIValue[] 42 | /** The named arguments extracted from the event that was emitted (where the arguments had a name defined) */ 43 | argsByName: Record 44 | } 45 | 46 | /** Specifies a group of ARC-28 event definitions along with instructions for when to attempt to process the events. */ 47 | export interface Arc28EventGroup { 48 | /** The name to designate for this group of events. */ 49 | groupName: string 50 | /** Optional list of app IDs that this event should apply to */ 51 | processForAppIds?: bigint[] 52 | /** Optional predicate to indicate if these ARC-28 events should be processed for the given transaction */ 53 | processTransaction?: (transaction: SubscribedTransaction) => boolean 54 | /** Whether or not to silently (with warning log) continue if an error is encountered processing the ARC-28 event data; default = false */ 55 | continueOnError?: boolean 56 | /** The list of ARC-28 event definitions */ 57 | events: Arc28Event[] 58 | } 59 | -------------------------------------------------------------------------------- /src/types/async-event-emitter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An asynchronous event listener 3 | */ 4 | export type AsyncEventListener = (event: unknown, eventName: string | symbol) => Promise | void 5 | 6 | /** Simple asynchronous event emitter class. 7 | * 8 | * **Note:** This class is not thread-safe. 9 | */ 10 | export class AsyncEventEmitter { 11 | private listenerWrapperMap = new WeakMap() 12 | private listenerMap: Record = {} 13 | 14 | /** 15 | * Emit an event and wait for all registered listeners to be run one-by-one 16 | * in the order they were registered. 17 | * 18 | * @param eventName The name of the event 19 | * @param event The event payload 20 | */ 21 | async emitAsync(eventName: string | symbol, event: unknown): Promise { 22 | for (const listener of this.listenerMap[eventName] ?? []) { 23 | await listener(event, eventName) 24 | } 25 | } 26 | 27 | /** 28 | * Register an event listener for the given event. 29 | * @param eventName The name of the event 30 | * @param listener The listener to trigger 31 | * @returns The `AsyncEventEmitter` so you can chain registrations 32 | */ 33 | on(eventName: string | symbol, listener: AsyncEventListener): AsyncEventEmitter { 34 | if (!this.listenerMap[eventName]) this.listenerMap[eventName] = [] 35 | this.listenerMap[eventName].push(listener) 36 | return this 37 | } 38 | 39 | /** 40 | * Register an event listener for the given event that is only fired once. 41 | * @param eventName The name of the event 42 | * @param listener The listener to trigger 43 | * @returns The `AsyncEventEmitter` so you can chain registrations 44 | */ 45 | once(eventName: string | symbol, listener: AsyncEventListener): AsyncEventEmitter { 46 | const wrappedListener: AsyncEventListener = async (event, eventName) => { 47 | try { 48 | return await listener(event, eventName) 49 | } finally { 50 | this.removeListener(eventName, wrappedListener) 51 | } 52 | } 53 | this.listenerWrapperMap.set(listener, wrappedListener) 54 | return this.on(eventName, wrappedListener) 55 | } 56 | 57 | /** 58 | * Removes an event listener from the given event. 59 | * @param eventName The name of the event 60 | * @param listener The listener to remove 61 | * @returns The `AsyncEventEmitter` so you can chain registrations 62 | */ 63 | removeListener(eventName: string | symbol, listener: AsyncEventListener): AsyncEventEmitter { 64 | const wrappedListener = this.listenerWrapperMap.get(listener) 65 | if (wrappedListener) { 66 | this.listenerWrapperMap.delete(listener) 67 | if (this.listenerMap[eventName]?.indexOf(wrappedListener) !== -1) { 68 | this.listenerMap[eventName].splice(this.listenerMap[eventName].indexOf(wrappedListener), 1) 69 | } 70 | } else { 71 | if (this.listenerMap[eventName]?.indexOf(listener) !== -1) { 72 | this.listenerMap[eventName].splice(this.listenerMap[eventName].indexOf(listener), 1) 73 | } 74 | } 75 | 76 | return this 77 | } 78 | 79 | /** 80 | * Alias for `removeListener`. 81 | */ 82 | off = this.removeListener 83 | } 84 | -------------------------------------------------------------------------------- /src/types/block.ts: -------------------------------------------------------------------------------- 1 | import algosdk from 'algosdk' 2 | 3 | /** The representation of all important data for a single transaction or inner transaction 4 | * and its side effects within a committed block. 5 | */ 6 | export interface TransactionInBlock { 7 | // Raw data 8 | 9 | /** The signed transaction with apply data from the block */ 10 | signedTxnWithAD: algosdk.SignedTxnWithAD 11 | 12 | // Processed data 13 | /** The transaction ID 14 | * 15 | * @example 16 | * - W6IG6SETWKISJV4JQSS6GNZGWKYXOOLH7FT3NQM4BIFRLCOXOQHA if it's a parent transaction 17 | * - W6IG6SETWKISJV4JQSS6GNZGWKYXOOLH7FT3NQM4BIFRLCOXOQHA/inner/1 if it's an inner transaction 18 | */ 19 | transactionId: string 20 | /** The offset of the transaction within the round including inner transactions. 21 | * 22 | * @example 23 | * - 0 24 | * - 1 25 | * - 2 26 | * - 3 27 | * - 4 28 | * - 5 29 | */ 30 | intraRoundOffset: number 31 | /** 32 | * The intra-round offset of the parent transaction if this is an inner transaction. 33 | * @example 34 | * - 0 35 | * - 1 36 | * - 1 37 | * - 1 38 | * - 1 39 | * - 2 40 | */ 41 | parentIntraRoundOffset?: number 42 | /** 43 | * The ID of the parent transaction if this is an inner transaction. 44 | */ 45 | parentTransactionId?: string 46 | /** The binary genesis hash of the network the transaction is within. */ 47 | genesisHash?: Buffer 48 | /** The string genesis ID of the network the transaction is within. */ 49 | genesisId?: string 50 | /** The round number of the block the transaction is within. */ 51 | roundNumber: bigint 52 | /** The round unix timestamp of the block the transaction is within. */ 53 | roundTimestamp: number 54 | /** The transaction as an algosdk `Transaction` object. */ 55 | transaction: algosdk.Transaction 56 | /** The asset ID if an asset was created from this transaction. */ 57 | createdAssetId?: bigint 58 | /** The app ID if an app was created from this transaction. */ 59 | createdAppId?: bigint 60 | /** The asset close amount if the sender asset position was closed from this transaction. */ 61 | assetCloseAmount?: bigint 62 | /** The ALGO close amount if the sender account was closed from this transaction. */ 63 | closeAmount?: bigint 64 | /** Any logs that were issued as a result of this transaction. */ 65 | logs?: Uint8Array[] 66 | /** Rewards in microalgos applied to the close remainder to account. */ 67 | closeRewards?: bigint 68 | /** Rewards in microalgos applied to the sender account. */ 69 | senderRewards?: bigint 70 | /** Rewards in microalgos applied to the receiver account. */ 71 | receiverRewards?: bigint 72 | } 73 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export type * from './arc-28' 2 | export type * from './async-event-emitter' 3 | export type * from './block' 4 | export type * from './subscription' 5 | export { BalanceChangeRole } from './subscription' 6 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export function chunkArray(array: T[], chunkSize: number): T[][] { 2 | const chunkedArray: T[][] = [] 3 | let i = 0 4 | 5 | while (i < array.length) { 6 | chunkedArray.push(array.slice(i, (i += chunkSize))) 7 | } 8 | 9 | return chunkedArray 10 | } 11 | 12 | export function range(start: bigint, end: bigint) { 13 | const arrayLength = Number(end - start + 1n) 14 | return [...Array(arrayLength).keys()].map((i) => BigInt(i) + start) 15 | } 16 | 17 | // https://github.com/whatwg/dom/issues/946#issuecomment-845924476 18 | export function race(promise: Promise, signal: AbortSignal) { 19 | return Promise.race([toPromise(signal), promise]) 20 | } 21 | 22 | const promises = new WeakMap>() 23 | function toPromise(signal: AbortSignal) { 24 | if (!promises.has(signal)) { 25 | promises.set( 26 | signal, 27 | new Promise((resolve) => { 28 | const propagate = () => { 29 | signal.removeEventListener('abort', propagate) 30 | resolve(new DOMException(signal.reason ?? 'Aborted', 'AbortError')) 31 | } 32 | 33 | if (signal.aborted) { 34 | propagate() 35 | } else { 36 | signal.addEventListener('abort', propagate) 37 | } 38 | }), 39 | ) 40 | } 41 | 42 | return promises.get(signal) 43 | } 44 | 45 | export async function sleep(ms: number, signal: AbortSignal) { 46 | return new Promise((resolve) => { 47 | const timeout = setTimeout(() => { 48 | resolve() 49 | signal.removeEventListener('abort', abort) 50 | }, ms) 51 | 52 | const abort = () => { 53 | clearTimeout(timeout) 54 | resolve() 55 | } 56 | 57 | signal.addEventListener('abort', abort) 58 | if (signal.aborted) { 59 | abort() 60 | } 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /tests/contract/.gitignore: -------------------------------------------------------------------------------- 1 | *.teal 2 | *.json 3 | -------------------------------------------------------------------------------- /tests/contract/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorandfoundation/algokit-subscriber-ts/c81d41406a04af63ac6a05fef0d50faf1686bfd3/tests/contract/__init__.py -------------------------------------------------------------------------------- /tests/contract/build.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import subprocess 3 | from pathlib import Path 4 | 5 | logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(levelname)-10s: %(message)s") 6 | logger = logging.getLogger(__name__) 7 | root_path = Path(__file__).parent 8 | 9 | 10 | def main() -> None: 11 | artifacts = root_path / "artifacts" 12 | 13 | app_path = root_path / "testing_app" / "contract.py" 14 | app_artifacts = artifacts / "testing_app" 15 | subprocess.run( 16 | [ 17 | "algokit", 18 | "--no-color", 19 | "compile", 20 | "python", 21 | app_path.absolute(), 22 | f"--out-dir={app_artifacts}", 23 | "--output-arc56", 24 | "--no-output-arc32", 25 | "--no-output-teal", 26 | "--no-output-source-map", 27 | ], 28 | stdout=subprocess.PIPE, 29 | stderr=subprocess.STDOUT, 30 | text=True, 31 | check=False, 32 | ) 33 | result = subprocess.run( 34 | [ 35 | "algokit", 36 | "generate", 37 | "client", 38 | app_artifacts / "TestingApp.arc56.json", 39 | "--output", 40 | "client.ts", 41 | ], 42 | stdout=subprocess.PIPE, 43 | stderr=subprocess.STDOUT, 44 | text=True, 45 | ) 46 | if result.returncode: 47 | raise Exception("Could not generate typed client") 48 | 49 | 50 | if __name__ == "__main__": 51 | main() 52 | -------------------------------------------------------------------------------- /tests/contract/poetry.toml: -------------------------------------------------------------------------------- 1 | [virtualenvs] 2 | in-project = true 3 | -------------------------------------------------------------------------------- /tests/contract/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "test-contract" 3 | version = "0.1.0" 4 | description = "Algorand test smart contract" 5 | authors = ["Rob Moore "] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.12" 9 | algokit-utils = "^2.1.2" 10 | setuptools = "^75.6.0" 11 | 12 | [tool.poetry.group.dev.dependencies] 13 | ruff = "^0.1.6" 14 | mypy = "^1.0.0" 15 | puyapy = "^4.2.0" 16 | algorand-python = "^2.5.0" 17 | 18 | [build-system] 19 | requires = ["poetry-core"] 20 | build-backend = "poetry.core.masonry.api" 21 | 22 | [tool.ruff] 23 | line-length = 120 24 | select = [ 25 | # all possible codes as of this ruff version are listed here, 26 | # ones we don't want/need are commented out to make it clear 27 | # which have been omitted on purpose vs which ones get added 28 | # in new ruff releases and should be considered for enabling 29 | "F", # pyflakes 30 | "E", "W", # pycodestyle 31 | "C90", # mccabe 32 | "I", # isort 33 | "N", # PEP8 naming 34 | "UP", # pyupgrade 35 | "YTT", # flake8-2020 36 | "ANN", # flake8-annotations 37 | # "S", # flake8-bandit 38 | # "BLE", # flake8-blind-except 39 | "FBT", # flake8-boolean-trap 40 | "B", # flake8-bugbear 41 | "A", # flake8-builtins 42 | # "COM", # flake8-commas 43 | "C4", # flake8-comprehensions 44 | "DTZ", # flake8-datetimez 45 | "T10", # flake8-debugger 46 | # "DJ", # flake8-django 47 | # "EM", # flake8-errmsg 48 | # "EXE", # flake8-executable 49 | "ISC", # flake8-implicit-str-concat 50 | "ICN", # flake8-import-conventions 51 | # "G", # flake8-logging-format 52 | # "INP", # flake8-no-pep420 53 | "PIE", # flake8-pie 54 | "T20", # flake8-print 55 | "PYI", # flake8-pyi 56 | "PT", # flake8-pytest-style 57 | "Q", # flake8-quotes 58 | "RSE", # flake8-raise 59 | "RET", # flake8-return 60 | "SLF", # flake8-self 61 | "SIM", # flake8-simplify 62 | "TID", # flake8-tidy-imports 63 | "TCH", # flake8-type-checking 64 | "ARG", # flake8-unused-arguments 65 | "PTH", # flake8-use-pathlib 66 | "ERA", # eradicate 67 | # "PD", # pandas-vet 68 | "PGH", # pygrep-hooks 69 | "PL", # pylint 70 | # "TRY", # tryceratops 71 | # "NPY", # NumPy-specific rules 72 | "RUF", # Ruff-specific rules 73 | ] 74 | ignore = [ 75 | "ANN101", # no type for self 76 | "ANN102", # no type for cls 77 | "RET505", # allow else after return 78 | "SIM108", # allow if-else in place of ternary 79 | # To avoid conflict with ruff formatter. More details on https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules 80 | "E111", # indentation is not a multiple of four 81 | "E117", # over-indented 82 | "ISC001", # single line implicit string concatenation 83 | "ISC002", # multi line implicit string concatenation 84 | "Q000", # bad quotes inline string 85 | "Q001", # bad quotes multiline string 86 | "Q002", # bad quotes docstring 87 | "Q003", # avoidable escaped quotes 88 | "W191", # indentation contains tabs 89 | ] 90 | # Exclude a variety of commonly ignored directories. 91 | exclude = [ 92 | ".direnv", 93 | ".git", 94 | ".mypy_cache", 95 | ".ruff_cache", 96 | ".venv", 97 | "__pypackages__", 98 | "_build", 99 | "build", 100 | "dist", 101 | "node_modules", 102 | "venv", 103 | "docs/sphinx", 104 | ] 105 | # Allow unused variables when underscore-prefixed. 106 | dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" 107 | # Assume Python 3.10. 108 | target-version = "py310" 109 | 110 | [tool.ruff.flake8-annotations] 111 | allow-star-arg-any = true 112 | suppress-none-returning = true 113 | 114 | [tool.mypy] 115 | files = ["src", "tests"] 116 | exclude = ["dist"] 117 | python_version = "3.10" 118 | warn_unused_ignores = true 119 | warn_redundant_casts = true 120 | warn_unused_configs = true 121 | warn_unreachable = true 122 | warn_return_any = true 123 | strict = true 124 | disallow_untyped_decorators = true 125 | disallow_any_generics = false 126 | implicit_reexport = false 127 | 128 | [[tool.mypy.overrides]] 129 | module = "approvaltests.*" 130 | ignore_missing_imports = true 131 | -------------------------------------------------------------------------------- /tests/contract/testing_app/contract.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from algopy import * 4 | 5 | 6 | class ComplexEvent(arc4.Struct): 7 | array: arc4.DynamicArray[arc4.UInt32] 8 | int: arc4.UInt64 9 | 10 | 11 | class TestingApp(ARC4Contract): 12 | value: UInt64 13 | bytes1: Bytes 14 | bytes2: Bytes 15 | int1: UInt64 16 | int2: UInt64 17 | local_bytes1: LocalState[Bytes] 18 | local_bytes2: LocalState[Bytes] 19 | local_int1: LocalState[UInt64] 20 | local_int2: LocalState[UInt64] 21 | box: BoxMap[arc4.StaticArray[arc4.Byte, Literal[4]], arc4.String] 22 | 23 | def __init__(self) -> None: 24 | self.local_int1 = LocalState(UInt64) 25 | self.local_int2 = LocalState(UInt64) 26 | self.local_bytes1 = LocalState(Bytes) 27 | self.local_bytes2 = LocalState(Bytes) 28 | 29 | self.box = BoxMap(arc4.StaticArray[arc4.Byte, Literal[4]], arc4.String, key_prefix=b"") 30 | 31 | @subroutine 32 | def authorize_creator(self) -> None: 33 | assert Txn.sender == Global.creator_address, "unauthorized" 34 | 35 | @subroutine 36 | def itoa(self, i: UInt64) -> String: 37 | if i == UInt64(0): 38 | return String("0") 39 | else: 40 | return (self.itoa(i // UInt64(10)) if (i // UInt64(10)) > UInt64(0) else String("")) + String.from_bytes( 41 | String("0123456789").bytes[i % UInt64(10)] 42 | ) 43 | 44 | @arc4.baremethod(create="require", allow_actions=["NoOp", "OptIn"]) 45 | def create(self) -> None: 46 | pass 47 | 48 | @arc4.baremethod(allow_actions=["UpdateApplication"]) 49 | def update(self) -> None: 50 | self.authorize_creator() 51 | 52 | @arc4.baremethod(allow_actions=["DeleteApplication"]) 53 | def delete(self) -> None: 54 | self.authorize_creator() 55 | 56 | @arc4.abimethod(allow_actions=["OptIn"]) 57 | def opt_in(self) -> None: 58 | pass 59 | 60 | @arc4.abimethod(readonly=True) 61 | def call_abi(self, value: String) -> String: 62 | return String("Hello, ") + value 63 | 64 | @arc4.abimethod(readonly=True) 65 | def call_abi_foreign_refs(self) -> String: 66 | return ( 67 | "App: " 68 | + self.itoa(Txn.applications(1).id) 69 | + ", Asset: " 70 | + self.itoa(Txn.assets(0).id) 71 | + ", Account: " 72 | + self.itoa(op.getbyte(Txn.accounts(0).bytes, 0)) 73 | + ":" 74 | + self.itoa(op.getbyte(Txn.accounts(0).bytes, 1)) 75 | ) 76 | 77 | @arc4.abimethod 78 | def set_global( 79 | self, int1: UInt64, int2: UInt64, bytes1: String, bytes2: arc4.StaticArray[arc4.Byte, Literal[4]] 80 | ) -> None: 81 | self.int1 = int1 82 | self.int2 = int2 83 | self.bytes1 = bytes1.bytes 84 | self.bytes2 = bytes2.bytes 85 | 86 | @arc4.abimethod 87 | def set_local( 88 | self, int1: UInt64, int2: UInt64, bytes1: String, bytes2: arc4.StaticArray[arc4.Byte, Literal[4]] 89 | ) -> None: 90 | self.local_int1[Txn.sender] = int1 91 | self.local_int2[Txn.sender] = int2 92 | self.local_bytes1[Txn.sender] = bytes1.bytes 93 | self.local_bytes2[Txn.sender] = bytes2.bytes 94 | 95 | @arc4.abimethod 96 | def issue_transfer_to_sender(self, amount: arc4.UInt64) -> None: 97 | itxn.Payment(receiver=Txn.sender, amount=amount.native).submit() 98 | 99 | @arc4.abimethod 100 | def set_box(self, name: arc4.StaticArray[arc4.Byte, Literal[4]], value: arc4.String) -> None: 101 | self.box[name] = value 102 | 103 | @arc4.abimethod(readonly=True) 104 | def error(self) -> None: 105 | assert False, "Deliberate error" # noqa: PT015, B011 106 | 107 | @arc4.abimethod 108 | def emitSwapped(self, a: arc4.UInt64, b: arc4.UInt64) -> None: 109 | arc4.emit("Swapped", a, b) 110 | 111 | @arc4.abimethod 112 | def emitSwappedTwice(self, a: arc4.UInt64, b: arc4.UInt64) -> None: 113 | arc4.emit("Swapped", a, b) 114 | arc4.emit("Swapped", b, a) 115 | 116 | @arc4.abimethod 117 | def emitComplex(self, a: arc4.UInt64, b: arc4.UInt64, array: arc4.DynamicArray[arc4.UInt32]) -> None: 118 | arc4.emit("Swapped", a, b) 119 | arc4.emit("Complex", array, b) 120 | -------------------------------------------------------------------------------- /tests/filterFixture.ts: -------------------------------------------------------------------------------- 1 | import { algorandFixture } from '@algorandfoundation/algokit-utils/testing' 2 | import { AlgorandFixture, AlgorandFixtureConfig } from '@algorandfoundation/algokit-utils/types/testing' 3 | import { SendAtomicTransactionComposerResults, SendTransactionResult } from '@algorandfoundation/algokit-utils/types/transaction' 4 | import type { Account, Transaction } from 'algosdk' 5 | import algosdk from 'algosdk' 6 | import { expect, vitest } from 'vitest' 7 | import { Arc28EventGroup, SubscribedTransaction, TransactionFilter, TransactionSubscriptionResult } from '../src/types' 8 | import { GetSubscribedTransactions, SendXTransactions } from './transactions' 9 | 10 | /** Filter out synthetic transaction. We check for 0 fee on an outer txn without a group, which should never occur naturally */ 11 | const syntheticTxnFilter = (t: SubscribedTransaction) => { 12 | return !(BigInt(t.fee) === 0n && t.parentTransactionId === undefined && t.group === undefined) 13 | } 14 | 15 | export function filterFixture(fixtureConfig?: AlgorandFixtureConfig): { 16 | localnet: AlgorandFixture 17 | systemAccount: () => Account 18 | subscribeAlgod: ( 19 | filter: TransactionFilter, 20 | result: SendTransactionResult, 21 | arc28Events?: Arc28EventGroup[], 22 | ) => Promise 23 | subscribeIndexer: ( 24 | filter: TransactionFilter, 25 | result: SendTransactionResult, 26 | arc28Events?: Arc28EventGroup[], 27 | ) => Promise 28 | subscribeAndVerify: ( 29 | filter: TransactionFilter, 30 | result: SendTransactionResult, 31 | arc28Events?: Arc28EventGroup[], 32 | ) => Promise 33 | subscribeAndVerifyFilter: ( 34 | filter: TransactionFilter, 35 | result: SendTransactionResult | SendTransactionResult[], 36 | arc28Events?: Arc28EventGroup[], 37 | ) => Promise<{ algod: TransactionSubscriptionResult; indexer: TransactionSubscriptionResult }> 38 | extractFromGroupResult: ( 39 | groupResult: Omit, 40 | index: number, 41 | ) => { 42 | transaction: Transaction 43 | confirmation: algosdk.modelsv2.PendingTransactionResponse 44 | } 45 | beforeEach: () => Promise 46 | beforeAll: () => Promise 47 | afterEach: () => Promise 48 | } { 49 | const localnet = algorandFixture(fixtureConfig) 50 | let systemAccount: Account 51 | 52 | const subscribeAlgod = async (filter: TransactionFilter, result: SendTransactionResult, arc28Events?: Arc28EventGroup[]) => { 53 | // Run the subscription 54 | const subscribed = await GetSubscribedTransactions( 55 | { 56 | roundsToSync: 1, 57 | syncBehaviour: 'sync-oldest', 58 | watermark: result.confirmation!.confirmedRound! - 1n, 59 | currentRound: result.confirmation!.confirmedRound, 60 | filters: filter, 61 | arc28Events, 62 | }, 63 | localnet.algorand, 64 | ) 65 | 66 | subscribed.subscribedTransactions = subscribed.subscribedTransactions.filter(syntheticTxnFilter) 67 | return subscribed 68 | } 69 | 70 | const subscribeIndexer = async (filter: TransactionFilter, result: SendTransactionResult, arc28Events?: Arc28EventGroup[]) => { 71 | const start = +new Date() 72 | // Ensure there is another transaction so algod subscription can process something 73 | await SendXTransactions(2, systemAccount, localnet.algorand) 74 | // Wait for indexer to catch up 75 | await localnet.context.waitForIndexerTransaction(result.transaction.txID()) 76 | const durationInSeconds = (+new Date() - start) / 1000 77 | // eslint-disable-next-line no-console 78 | console.debug(`Prepared for subscribing to indexer in ${durationInSeconds} seconds`) 79 | 80 | // Run the subscription 81 | const subscribed = await GetSubscribedTransactions( 82 | { 83 | roundsToSync: 1, 84 | syncBehaviour: 'catchup-with-indexer', 85 | watermark: (result.confirmation?.confirmedRound ?? 0n) - 1n, 86 | currentRound: (result.confirmation?.confirmedRound ?? 0n) + 1n, 87 | filters: filter, 88 | arc28Events, 89 | }, 90 | localnet.algorand, 91 | ) 92 | 93 | subscribed.subscribedTransactions = subscribed.subscribedTransactions.filter(syntheticTxnFilter) 94 | return subscribed 95 | } 96 | 97 | const subscribeAndVerify = async (filter: TransactionFilter, result: SendTransactionResult, arc28Events?: Arc28EventGroup[]) => { 98 | const subscribed = await subscribeAlgod(filter, result, arc28Events) 99 | expect(subscribed.subscribedTransactions.length).toBe(1) 100 | expect(subscribed.subscribedTransactions[0].id).toBe(result.transaction.txID()) 101 | 102 | return subscribed 103 | } 104 | 105 | const subscribeAndVerifyFilter = async ( 106 | filter: TransactionFilter, 107 | result: SendTransactionResult | SendTransactionResult[], 108 | arc28Events?: Arc28EventGroup[], 109 | ) => { 110 | const results = Array.isArray(result) ? result : [result] 111 | const [algod, indexer] = await Promise.all([ 112 | subscribeAlgod(filter, results[0], arc28Events), 113 | subscribeIndexer(filter, results[0], arc28Events), 114 | ]) 115 | 116 | expect(algod.subscribedTransactions.length).toBe(results.length) 117 | expect(algod.subscribedTransactions.map((s) => s.id)).toEqual(results.map((r) => r.transaction.txID())) 118 | 119 | expect(indexer.subscribedTransactions.length).toBe(results.length) 120 | expect(indexer.subscribedTransactions.map((s) => s.id)).toEqual(results.map((r) => r.transaction.txID())) 121 | 122 | return { algod, indexer } 123 | } 124 | 125 | const extractFromGroupResult = (groupResult: Omit, index: number) => { 126 | return { 127 | transaction: groupResult.transactions[index], 128 | confirmation: groupResult.confirmations?.[index], 129 | } 130 | } 131 | 132 | return { 133 | localnet, 134 | systemAccount: () => systemAccount, 135 | subscribeAlgod, 136 | subscribeIndexer, 137 | subscribeAndVerify, 138 | subscribeAndVerifyFilter, 139 | extractFromGroupResult, 140 | beforeEach: async () => { 141 | await localnet.beforeEach() 142 | }, 143 | beforeAll: async () => { 144 | await localnet.beforeEach() 145 | 146 | systemAccount = await localnet.context.generateAccount({ initialFunds: (100).algos() }) 147 | }, 148 | afterEach: async () => { 149 | vitest.clearAllMocks() 150 | }, 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /tests/scenarios/app-call-transactions.spec.ts: -------------------------------------------------------------------------------- 1 | import { algorandFixture } from '@algorandfoundation/algokit-utils/testing' 2 | import { afterEach, beforeEach, describe, expect, test, vitest } from 'vitest' 3 | import { app } from '../testing-app' 4 | import { GetSubscribedTransactions, SendXTransactions } from '../transactions' 5 | 6 | describe('App call transactions', () => { 7 | const localnet = algorandFixture() 8 | beforeEach(localnet.beforeEach, 10e6) 9 | afterEach(() => { 10 | vitest.clearAllMocks() 11 | }) 12 | 13 | describe('Can have an app create transaction subscribed correctly from algod', () => { 14 | test('Works for app create', async () => { 15 | const { testAccount } = localnet.context 16 | const app1 = await app({ create: true, algorand: localnet.algorand, creator: testAccount }) 17 | 18 | // Ensure there is another transaction so algod subscription can process something 19 | await SendXTransactions(1, testAccount, localnet.algorand) 20 | // Wait for indexer to catch up 21 | await localnet.context.waitForIndexer() 22 | 23 | const [algod] = await Promise.all([ 24 | GetSubscribedTransactions( 25 | { 26 | roundsToSync: 1, 27 | syncBehaviour: 'sync-oldest', 28 | watermark: (app1.result.confirmation?.confirmedRound ?? 0n) - 1n, 29 | currentRound: app1.result.confirmation?.confirmedRound ?? 0n, 30 | filters: { appCreate: true }, 31 | }, 32 | localnet.algorand, 33 | ), 34 | ]) 35 | 36 | expect(algod.subscribedTransactions.length).toBe(1) 37 | expect(algod.subscribedTransactions[0].applicationTransaction?.applicationId).toBe(0n) 38 | }) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /tests/scenarios/catchup-with-indexer.spec.ts: -------------------------------------------------------------------------------- 1 | import { algorandFixture } from '@algorandfoundation/algokit-utils/testing' 2 | import { afterEach, beforeEach, describe, expect, test, vitest } from 'vitest' 3 | import { GetSubscribedTransactionsFromSender, SendXTransactions } from '../transactions' 4 | 5 | describe('Subscribing using catchup-with-indexer', () => { 6 | const localnet = algorandFixture() 7 | beforeEach(localnet.beforeEach, 10e6) 8 | afterEach(() => { 9 | vitest.clearAllMocks() 10 | }) 11 | 12 | test('Processes start of chain to now when starting from beginning of chain', async () => { 13 | const { algorand, testAccount, generateAccount, waitForIndexerTransaction } = localnet.context 14 | // Ensure that if we are at round 0 there is a different transaction that won't be synced 15 | await SendXTransactions(1, await generateAccount({ initialFunds: (3).algos() }), algorand) 16 | const { lastTxnRound, txns } = await SendXTransactions(1, testAccount, algorand) 17 | await waitForIndexerTransaction(txns[0].transaction.txID()) 18 | 19 | const subscribed = await GetSubscribedTransactionsFromSender( 20 | { roundsToSync: 1, syncBehaviour: 'catchup-with-indexer', watermark: 0n, currentRound: lastTxnRound }, 21 | testAccount, 22 | algorand, 23 | ) 24 | 25 | expect(subscribed.currentRound).toBe(lastTxnRound) 26 | expect(subscribed.startingWatermark).toBe(0n) 27 | expect(subscribed.newWatermark).toBe(lastTxnRound) 28 | expect(subscribed.syncedRoundRange).toEqual([1n, lastTxnRound]) 29 | expect(subscribed.subscribedTransactions.length).toBe(1) 30 | expect(subscribed.subscribedTransactions[0].id).toBe(txns[0].transaction.txID()) 31 | }) 32 | 33 | test('Limits the number of synced transactions to maxIndexerRoundsToSync', async () => { 34 | const { algorand, testAccount, generateAccount, waitForIndexerTransaction } = localnet.context 35 | // Ensure that if we are at round 0 there is a different transaction that won't be synced 36 | const randomAccount = await generateAccount({ initialFunds: (3).algos() }) 37 | const { lastTxnRound: initialWatermark } = await SendXTransactions(1, randomAccount, algorand) 38 | const { txns } = await SendXTransactions(5, testAccount, algorand) 39 | const { lastTxnRound, txIds } = await SendXTransactions(1, randomAccount, algorand) 40 | await waitForIndexerTransaction(txIds[0]) 41 | const expectedNewWatermark = txns[2].confirmation!.confirmedRound! - 1n 42 | const indexerRoundsToSync = Number(expectedNewWatermark - initialWatermark) 43 | 44 | const subscribed = await GetSubscribedTransactionsFromSender( 45 | { 46 | roundsToSync: 1, 47 | indexerRoundsToSync, 48 | syncBehaviour: 'catchup-with-indexer', 49 | watermark: initialWatermark, 50 | currentRound: lastTxnRound, 51 | }, 52 | testAccount, 53 | algorand, 54 | ) 55 | 56 | expect(subscribed.currentRound).toBe(lastTxnRound) 57 | expect(subscribed.startingWatermark).toBe(initialWatermark) 58 | expect(subscribed.newWatermark).toBe(expectedNewWatermark) 59 | expect(subscribed.syncedRoundRange).toEqual([initialWatermark + 1n, expectedNewWatermark]) 60 | expect(subscribed.subscribedTransactions.length).toBe(2) 61 | expect(subscribed.subscribedTransactions[0].id).toBe(txns[0].transaction.txID()) 62 | expect(subscribed.subscribedTransactions[1].id).toBe(txns[1].transaction.txID()) 63 | }) 64 | 65 | // Same behaviour as sync-oldest 66 | test('Processes all transactions after watermark when starting from an earlier round with other transactions', async () => { 67 | const { algorand, testAccount, waitForIndexerTransaction } = localnet.context 68 | const { txns, lastTxnRound: olderTxnRound } = await SendXTransactions(2, testAccount, algorand) 69 | const { lastTxnRound: currentRound, txns: lastTxns } = await SendXTransactions(1, testAccount, algorand) 70 | await waitForIndexerTransaction(lastTxns[0].transaction.txID()) 71 | 72 | const subscribed = await GetSubscribedTransactionsFromSender( 73 | { roundsToSync: 1, syncBehaviour: 'catchup-with-indexer', watermark: olderTxnRound - 1n, currentRound }, 74 | testAccount, 75 | algorand, 76 | ) 77 | 78 | expect(subscribed.currentRound).toBe(currentRound) 79 | expect(subscribed.startingWatermark).toBe(olderTxnRound - 1n) 80 | expect(subscribed.newWatermark).toBe(currentRound) 81 | expect(subscribed.syncedRoundRange).toEqual([olderTxnRound, currentRound]) 82 | expect(subscribed.subscribedTransactions.length).toBe(2) 83 | expect(subscribed.subscribedTransactions[0].id).toBe(txns[1].transaction.txID()) 84 | expect(subscribed.subscribedTransactions[1].id).toBe(lastTxns[0].transaction.txID()) 85 | }) 86 | 87 | test('Process multiple historic transactions using indexer and blends them in with algod transaction', async () => { 88 | const { algorand, testAccount, waitForIndexerTransaction } = localnet.context 89 | const { txns, txIds, lastTxnRound } = await SendXTransactions(3, testAccount, algorand) 90 | await waitForIndexerTransaction(txIds[2]) 91 | 92 | const subscribed = await GetSubscribedTransactionsFromSender( 93 | { roundsToSync: 1, syncBehaviour: 'catchup-with-indexer', watermark: 0n, currentRound: lastTxnRound }, 94 | testAccount, 95 | algorand, 96 | ) 97 | 98 | expect(subscribed.currentRound).toBe(lastTxnRound) 99 | expect(subscribed.startingWatermark).toBe(0n) 100 | expect(subscribed.newWatermark).toBe(lastTxnRound) 101 | expect(subscribed.syncedRoundRange).toEqual([1n, lastTxnRound]) 102 | expect(subscribed.subscribedTransactions.length).toBe(3) 103 | expect(subscribed.subscribedTransactions[0].id).toBe(txns[0].transaction.txID()) 104 | expect(subscribed.subscribedTransactions[1].id).toBe(txns[1].transaction.txID()) 105 | expect(subscribed.subscribedTransactions[2].id).toBe(txns[2].transaction.txID()) 106 | }) 107 | }) 108 | -------------------------------------------------------------------------------- /tests/scenarios/fail.spec.ts: -------------------------------------------------------------------------------- 1 | import { algorandFixture } from '@algorandfoundation/algokit-utils/testing' 2 | import { afterEach, beforeEach, describe, expect, test, vitest } from 'vitest' 3 | import { GetSubscribedTransactionsFromSender, SendXTransactions } from '../transactions' 4 | 5 | describe('Subscribing using fail', () => { 6 | const localnet = algorandFixture() 7 | 8 | beforeEach(localnet.beforeEach, 10e6) 9 | afterEach(() => { 10 | vitest.clearAllMocks() 11 | }) 12 | 13 | test('Fails if too far from the tip of the chain', async () => { 14 | const { algorand, testAccount } = localnet.context 15 | const { lastTxnRound } = await SendXTransactions(2, testAccount, algorand) 16 | 17 | await expect( 18 | async () => 19 | await GetSubscribedTransactionsFromSender( 20 | { roundsToSync: 1, syncBehaviour: 'fail', watermark: 0n, currentRound: lastTxnRound }, 21 | testAccount, 22 | algorand, 23 | ), 24 | ).rejects.toThrow(`Invalid round number to subscribe from 1; current round number is ${lastTxnRound}`) 25 | }) 26 | 27 | test("Doesn't fail if not too far from the tip of the chain", async () => { 28 | const { algorand, testAccount } = localnet.context 29 | const { txns, lastTxnRound } = await SendXTransactions(2, testAccount, algorand) 30 | 31 | const subscribed = await GetSubscribedTransactionsFromSender( 32 | { roundsToSync: 1, syncBehaviour: 'fail', watermark: lastTxnRound - 1n, currentRound: lastTxnRound }, 33 | testAccount, 34 | algorand, 35 | ) 36 | 37 | expect(subscribed.currentRound).toBe(lastTxnRound) 38 | expect(subscribed.startingWatermark).toBe(lastTxnRound - 1n) 39 | expect(subscribed.newWatermark).toBe(lastTxnRound) 40 | expect(subscribed.syncedRoundRange).toEqual([lastTxnRound, lastTxnRound]) 41 | expect(subscribed.subscribedTransactions.length).toBe(1) 42 | expect(subscribed.subscribedTransactions[0].id).toBe(txns[1].transaction.txID()) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /tests/scenarios/inner_transactions.spec.ts: -------------------------------------------------------------------------------- 1 | import { algorandFixture } from '@algorandfoundation/algokit-utils/testing' 2 | import { SendAtomicTransactionComposerResults, SendTransactionResult } from '@algorandfoundation/algokit-utils/types/transaction' 3 | import { Account, TransactionType } from 'algosdk' 4 | import { afterEach, beforeAll, beforeEach, describe, expect, test, vitest } from 'vitest' 5 | import { TransactionFilter } from '../../src/types' 6 | import { app } from '../testing-app' 7 | import { GetSubscribedTransactions, SendXTransactions } from '../transactions' 8 | 9 | describe('Inner transactions', () => { 10 | const localnet = algorandFixture() 11 | let systemAccount: Account 12 | 13 | beforeAll(async () => { 14 | await localnet.beforeEach() 15 | systemAccount = await localnet.context.generateAccount({ initialFunds: (100).algos() }) 16 | }) 17 | 18 | beforeEach(localnet.beforeEach, 10e6) 19 | afterEach(() => { 20 | vitest.clearAllMocks() 21 | }) 22 | 23 | const subscribeAndVerifyFilter = async (filter: TransactionFilter, ...result: (SendTransactionResult & { id: string })[]) => { 24 | // Ensure there is another transaction so algod subscription can process something 25 | await SendXTransactions(1, systemAccount, localnet.algorand) 26 | // Wait for indexer to catch up 27 | await localnet.context.waitForIndexer() 28 | // Run the subscription twice - once that will pick up using algod and once using indexer 29 | // this allows the filtering logic for both to be tested 30 | const [algod, indexer] = await Promise.all([ 31 | GetSubscribedTransactions( 32 | { 33 | roundsToSync: 1, 34 | syncBehaviour: 'sync-oldest', 35 | watermark: (result[result.length - 1].confirmation?.confirmedRound ?? 0n) - 1n, 36 | currentRound: result[result.length - 1].confirmation?.confirmedRound, 37 | filters: filter, 38 | }, 39 | localnet.algorand, 40 | ), 41 | GetSubscribedTransactions( 42 | { 43 | roundsToSync: 1, 44 | syncBehaviour: 'catchup-with-indexer', 45 | watermark: 0n, 46 | currentRound: (result[result.length - 1].confirmation?.confirmedRound ?? 0n) + 1n, 47 | filters: filter, 48 | }, 49 | localnet.algorand, 50 | ), 51 | ]) 52 | 53 | expect(algod.subscribedTransactions.length).toBe(result.length) 54 | expect(algod.subscribedTransactions.map((t) => t.id)).toEqual(result.map((r) => r.id)) 55 | expect(indexer.subscribedTransactions.length).toBe(result.length) 56 | expect(indexer.subscribedTransactions.map((t) => t.id)).toEqual(result.map((r) => r.id)) 57 | return { algod, indexer } 58 | } 59 | 60 | const extractFromGroupResult = ( 61 | groupResult: Omit, 62 | index: number, 63 | innerTransactionIndex?: number, 64 | ) => { 65 | return { 66 | id: 67 | innerTransactionIndex !== undefined 68 | ? `${groupResult.transactions[index].txID()}/inner/${innerTransactionIndex + 1}` 69 | : groupResult.transactions[index].txID(), 70 | transaction: 71 | innerTransactionIndex !== undefined 72 | ? groupResult.confirmations![index].innerTxns![innerTransactionIndex].txn.txn 73 | : groupResult.transactions[index], 74 | confirmation: groupResult.confirmations?.[index], 75 | } 76 | } 77 | 78 | test('Is processed alongside normal transaction', async () => { 79 | const { testAccount } = localnet.context 80 | const app1 = await app({ create: true, algorand: localnet.algorand, creator: systemAccount }) 81 | 82 | const txns = await localnet.algorand 83 | .newGroup() 84 | .addPayment({ 85 | amount: (100_001).microAlgos(), 86 | receiver: app1.appClient.appAddress, 87 | sender: testAccount.addr, 88 | }) 89 | .addPayment({ 90 | amount: (1).microAlgos(), 91 | receiver: testAccount.addr, 92 | sender: testAccount.addr, 93 | }) 94 | .addAppCallMethodCall( 95 | await app1.appClient.params.issueTransferToSender({ 96 | args: { amount: 1 }, 97 | sender: testAccount.addr, 98 | staticFee: (2000).microAlgos(), 99 | }), 100 | ) 101 | .send() 102 | 103 | await subscribeAndVerifyFilter( 104 | { 105 | type: TransactionType.pay, 106 | receiver: testAccount.addr.toString(), 107 | maxAmount: 1, 108 | }, 109 | extractFromGroupResult(txns, 1), 110 | extractFromGroupResult(txns, 2, 0), 111 | ) 112 | }) 113 | }) 114 | -------------------------------------------------------------------------------- /tests/scenarios/multiple-filters.spec.ts: -------------------------------------------------------------------------------- 1 | import { algorandFixture } from '@algorandfoundation/algokit-utils/testing' 2 | import { afterEach, beforeEach, describe, expect, test, vitest } from 'vitest' 3 | import { GetSubscribedTransactionsFromSender, SendXTransactions } from '../transactions' 4 | 5 | describe('Subscribing using multiple filters', () => { 6 | const localnet = algorandFixture() 7 | 8 | beforeEach(localnet.beforeEach, 10e6) 9 | afterEach(() => { 10 | vitest.clearAllMocks() 11 | }) 12 | 13 | test('Process multiple historic transactions using indexer and blends them in with algorand transaction', async () => { 14 | const { algorand, testAccount, waitForIndexerTransaction, generateAccount } = localnet.context 15 | const senders = [ 16 | await generateAccount({ initialFunds: (5).algos() }), 17 | await generateAccount({ initialFunds: (5).algos() }), 18 | await generateAccount({ initialFunds: (5).algos() }), 19 | ] 20 | // Indexer should pick these up 21 | const { txIds: txIds1 } = await SendXTransactions(2, senders[0], algorand) 22 | const { txIds: txIds2 } = await SendXTransactions(2, senders[1], algorand) 23 | const { txIds: txIds3 } = await SendXTransactions(2, senders[2], algorand) 24 | const { lastTxnRound: postIndexerRound } = await SendXTransactions(1, testAccount, algorand) 25 | const { txIds: txIds11 } = await SendXTransactions(1, senders[0], algorand) 26 | const { txIds: txIds22 } = await SendXTransactions(1, senders[1], algorand) 27 | const { txIds: txIds33 } = await SendXTransactions(1, senders[2], algorand) 28 | const { txIds, lastTxnRound } = await SendXTransactions(1, testAccount, algorand) 29 | await waitForIndexerTransaction(txIds[0]) 30 | 31 | const subscribed = await GetSubscribedTransactionsFromSender( 32 | { 33 | roundsToSync: Number(lastTxnRound - postIndexerRound), 34 | syncBehaviour: 'catchup-with-indexer', 35 | watermark: 0n, 36 | currentRound: lastTxnRound, 37 | }, 38 | senders, 39 | algorand, 40 | ) 41 | 42 | expect(subscribed.currentRound).toBe(lastTxnRound) 43 | expect(subscribed.startingWatermark).toBe(0n) 44 | expect(subscribed.newWatermark).toBe(lastTxnRound) 45 | expect(subscribed.syncedRoundRange).toEqual([1n, lastTxnRound]) 46 | expect(subscribed.subscribedTransactions.length).toBe(9) 47 | expect(subscribed.subscribedTransactions[0].id).toBe(txIds1[0]) 48 | expect(subscribed.subscribedTransactions[1].id).toBe(txIds1[1]) 49 | expect(subscribed.subscribedTransactions[2].id).toBe(txIds2[0]) 50 | expect(subscribed.subscribedTransactions[3].id).toBe(txIds2[1]) 51 | expect(subscribed.subscribedTransactions[4].id).toBe(txIds3[0]) 52 | expect(subscribed.subscribedTransactions[5].id).toBe(txIds3[1]) 53 | expect(subscribed.subscribedTransactions[6].id).toBe(txIds11[0]) 54 | expect(subscribed.subscribedTransactions[7].id).toBe(txIds22[0]) 55 | expect(subscribed.subscribedTransactions[8].id).toBe(txIds33[0]) 56 | }) 57 | }) 58 | -------------------------------------------------------------------------------- /tests/scenarios/skip-sync-newest.spec.ts: -------------------------------------------------------------------------------- 1 | import { algorandFixture } from '@algorandfoundation/algokit-utils/testing' 2 | import { afterEach, beforeEach, describe, expect, test, vitest } from 'vitest' 3 | import { GetSubscribedTransactionsFromSender, SendXTransactions } from '../transactions' 4 | 5 | describe('Subscribing using skip-sync-newest', () => { 6 | const localnet = algorandFixture() 7 | 8 | beforeEach(localnet.beforeEach, 10e6) 9 | afterEach(() => { 10 | vitest.clearAllMocks() 11 | }) 12 | 13 | test('Only processes the latest transaction when starting from beginning of chain', async () => { 14 | const { algorand, testAccount } = localnet.context 15 | const { txns, lastTxnRound } = await SendXTransactions(2, testAccount, algorand) 16 | 17 | const subscribed = await GetSubscribedTransactionsFromSender( 18 | { roundsToSync: 1, syncBehaviour: 'skip-sync-newest', watermark: 0n, currentRound: lastTxnRound }, 19 | testAccount, 20 | algorand, 21 | ) 22 | 23 | expect(subscribed.currentRound).toBe(lastTxnRound) 24 | expect(subscribed.startingWatermark).toBe(0n) 25 | expect(subscribed.newWatermark).toBe(lastTxnRound) 26 | expect(subscribed.syncedRoundRange).toEqual([lastTxnRound, lastTxnRound]) 27 | expect(subscribed.subscribedTransactions.length).toBe(1) 28 | expect(subscribed.subscribedTransactions[0].id).toBe(txns[1].transaction.txID()) 29 | }) 30 | 31 | test('Only processes the latest transaction when starting from an earlier round with other transactions', async () => { 32 | const { algorand, testAccount } = localnet.context 33 | const { lastTxnRound: olderTxnRound } = await SendXTransactions(2, testAccount, algorand) 34 | const { txns, lastTxnRound: currentRound } = await SendXTransactions(1, testAccount, algorand) 35 | 36 | const subscribed = await GetSubscribedTransactionsFromSender( 37 | { roundsToSync: 1, syncBehaviour: 'skip-sync-newest', watermark: olderTxnRound - 1n, currentRound }, 38 | testAccount, 39 | algorand, 40 | ) 41 | 42 | expect(subscribed.currentRound).toBe(currentRound) 43 | expect(subscribed.startingWatermark).toBe(olderTxnRound - 1n) 44 | expect(subscribed.newWatermark).toBe(currentRound) 45 | expect(subscribed.syncedRoundRange).toEqual([currentRound, currentRound]) 46 | expect(subscribed.subscribedTransactions.length).toBe(1) 47 | expect(subscribed.subscribedTransactions[0].id).toBe(txns[0].transaction.txID()) 48 | }) 49 | 50 | test('Process multiple transactions', async () => { 51 | const { algorand, testAccount } = localnet.context 52 | const { txns, lastTxnRound, rounds } = await SendXTransactions(3, testAccount, algorand) 53 | 54 | const subscribed = await GetSubscribedTransactionsFromSender( 55 | { roundsToSync: Number(lastTxnRound - rounds[1]) + 1, syncBehaviour: 'skip-sync-newest', watermark: 0n, currentRound: lastTxnRound }, 56 | testAccount, 57 | algorand, 58 | ) 59 | 60 | expect(subscribed.currentRound).toBe(lastTxnRound) 61 | expect(subscribed.startingWatermark).toBe(0n) 62 | expect(subscribed.newWatermark).toBe(lastTxnRound) 63 | expect(subscribed.syncedRoundRange).toEqual([rounds[1], lastTxnRound]) 64 | expect(subscribed.subscribedTransactions.length).toBe(2) 65 | expect(subscribed.subscribedTransactions[0].id).toBe(txns[1].transaction.txID()) 66 | expect(subscribed.subscribedTransactions[1].id).toBe(txns[2].transaction.txID()) 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /tests/scenarios/sync-oldest-start-now.spec.ts: -------------------------------------------------------------------------------- 1 | import { algorandFixture } from '@algorandfoundation/algokit-utils/testing' 2 | import { afterEach, beforeEach, describe, expect, test, vitest } from 'vitest' 3 | import { GetSubscribedTransactionsFromSender, SendXTransactions } from '../transactions' 4 | 5 | describe('Subscribing using sync-oldest-start-now', () => { 6 | const localnet = algorandFixture() 7 | 8 | beforeEach(localnet.beforeEach, 10e6) 9 | afterEach(() => { 10 | vitest.clearAllMocks() 11 | }) 12 | 13 | test('Processes the current round when starting from beginning of chain', async () => { 14 | const { algorand, testAccount } = localnet.context 15 | const { txns, lastTxnRound } = await SendXTransactions(2, testAccount, algorand) 16 | 17 | const subscribed = await GetSubscribedTransactionsFromSender( 18 | { roundsToSync: 1, syncBehaviour: 'sync-oldest-start-now', watermark: 0n, currentRound: lastTxnRound }, 19 | testAccount, 20 | algorand, 21 | ) 22 | 23 | expect(subscribed.currentRound).toBe(lastTxnRound) 24 | expect(subscribed.startingWatermark).toBe(0n) 25 | expect(subscribed.newWatermark).toBe(lastTxnRound) 26 | expect(subscribed.syncedRoundRange).toEqual([lastTxnRound, lastTxnRound]) 27 | expect(subscribed.subscribedTransactions.length).toBe(1) 28 | expect(subscribed.subscribedTransactions[0].id).toBe(txns[1].transaction.txID()) 29 | }) 30 | 31 | test('Only processes the first transaction after watermark when starting from an earlier round with other transactions', async () => { 32 | const { algorand, testAccount } = localnet.context 33 | const { txns, lastTxnRound: olderTxnRound } = await SendXTransactions(2, testAccount, algorand) 34 | const { lastTxnRound: currentRound } = await SendXTransactions(1, testAccount, algorand) 35 | 36 | const subscribed = await GetSubscribedTransactionsFromSender( 37 | { roundsToSync: 1, syncBehaviour: 'sync-oldest', watermark: olderTxnRound - 1n, currentRound }, 38 | testAccount, 39 | algorand, 40 | ) 41 | 42 | expect(subscribed.currentRound).toBe(currentRound) 43 | expect(subscribed.startingWatermark).toBe(olderTxnRound - 1n) 44 | expect(subscribed.newWatermark).toBe(olderTxnRound) 45 | expect(subscribed.syncedRoundRange).toEqual([olderTxnRound, olderTxnRound]) 46 | expect(subscribed.subscribedTransactions.length).toBe(1) 47 | expect(subscribed.subscribedTransactions[0].id).toBe(txns[1].transaction.txID()) 48 | }) 49 | 50 | test('Process multiple transactions', async () => { 51 | const { algorand, testAccount } = localnet.context 52 | const { txns, lastTxnRound, rounds } = await SendXTransactions(3, testAccount, algorand) 53 | 54 | const subscribed = await GetSubscribedTransactionsFromSender( 55 | { 56 | roundsToSync: Number(rounds[1] - rounds[0]) + 1, 57 | syncBehaviour: 'sync-oldest-start-now', 58 | watermark: rounds[0] - 1n, 59 | currentRound: lastTxnRound, 60 | }, 61 | testAccount, 62 | algorand, 63 | ) 64 | 65 | expect(subscribed.currentRound).toBe(lastTxnRound) 66 | expect(subscribed.startingWatermark).toBe(rounds[0] - 1n) 67 | expect(subscribed.newWatermark).toBe(rounds[1]) 68 | expect(subscribed.syncedRoundRange).toEqual([rounds[0], rounds[1]]) 69 | expect(subscribed.subscribedTransactions.length).toBe(2) 70 | expect(subscribed.subscribedTransactions[0].id).toBe(txns[0].transaction.txID()) 71 | expect(subscribed.subscribedTransactions[1].id).toBe(txns[1].transaction.txID()) 72 | }) 73 | }) 74 | -------------------------------------------------------------------------------- /tests/scenarios/sync-oldest.spec.ts: -------------------------------------------------------------------------------- 1 | import { algorandFixture } from '@algorandfoundation/algokit-utils/testing' 2 | import { afterEach, beforeEach, describe, expect, test, vitest } from 'vitest' 3 | import { GetSubscribedTransactionsFromSender, SendXTransactions } from '../transactions' 4 | 5 | describe('Subscribing using sync-oldest', () => { 6 | const localnet = algorandFixture() 7 | 8 | beforeEach(localnet.beforeEach, 10e6) 9 | afterEach(() => { 10 | vitest.clearAllMocks() 11 | }) 12 | 13 | test('Only processes the first chain round when starting from beginning of chain', async () => { 14 | const { algorand, testAccount, generateAccount } = localnet.context 15 | // Ensure that if we are at round 0 there is a different transaction that won't be synced 16 | await SendXTransactions(1, await generateAccount({ initialFunds: (3).algos() }), algorand) 17 | const { lastTxnRound } = await SendXTransactions(1, testAccount, algorand) 18 | 19 | const subscribed = await GetSubscribedTransactionsFromSender( 20 | { roundsToSync: 1, syncBehaviour: 'sync-oldest', watermark: 0n, currentRound: lastTxnRound }, 21 | testAccount, 22 | algorand, 23 | ) 24 | 25 | expect(subscribed.currentRound).toBe(lastTxnRound) 26 | expect(subscribed.startingWatermark).toBe(0n) 27 | expect(subscribed.newWatermark).toBe(1n) 28 | expect(subscribed.syncedRoundRange).toEqual([1n, 1n]) 29 | expect(subscribed.subscribedTransactions.length).toBe(0) 30 | }) 31 | 32 | test('Only processes the first transaction after watermark when starting from an earlier round with other transactions', async () => { 33 | const { algorand, testAccount } = localnet.context 34 | const { txns, lastTxnRound: olderTxnRound } = await SendXTransactions(2, testAccount, algorand) 35 | const { lastTxnRound: currentRound } = await SendXTransactions(1, testAccount, algorand) 36 | 37 | const subscribed = await GetSubscribedTransactionsFromSender( 38 | { roundsToSync: 1, syncBehaviour: 'sync-oldest', watermark: olderTxnRound - 1n, currentRound }, 39 | testAccount, 40 | algorand, 41 | ) 42 | 43 | expect(subscribed.currentRound).toBe(currentRound) 44 | expect(subscribed.startingWatermark).toBe(olderTxnRound - 1n) 45 | expect(subscribed.newWatermark).toBe(olderTxnRound) 46 | expect(subscribed.syncedRoundRange).toEqual([olderTxnRound, olderTxnRound]) 47 | expect(subscribed.subscribedTransactions.length).toBe(1) 48 | expect(subscribed.subscribedTransactions[0].id).toBe(txns[1].transaction.txID()) 49 | }) 50 | 51 | test('Process multiple transactions', async () => { 52 | const { algorand, testAccount } = localnet.context 53 | const { txns, lastTxnRound, rounds } = await SendXTransactions(3, testAccount, algorand) 54 | 55 | const subscribed = await GetSubscribedTransactionsFromSender( 56 | { 57 | roundsToSync: Number(rounds[1] - rounds[0]) + 1, 58 | syncBehaviour: 'sync-oldest', 59 | watermark: rounds[0] - 1n, 60 | currentRound: lastTxnRound, 61 | }, 62 | testAccount, 63 | algorand, 64 | ) 65 | 66 | expect(subscribed.currentRound).toBe(lastTxnRound) 67 | expect(subscribed.startingWatermark).toBe(rounds[0] - 1n) 68 | expect(subscribed.newWatermark).toBe(rounds[1]) 69 | expect(subscribed.syncedRoundRange).toEqual([rounds[0], rounds[1]]) 70 | expect(subscribed.subscribedTransactions.length).toBe(2) 71 | expect(subscribed.subscribedTransactions[0].id).toBe(txns[0].transaction.txID()) 72 | expect(subscribed.subscribedTransactions[1].id).toBe(txns[1].transaction.txID()) 73 | }) 74 | }) 75 | -------------------------------------------------------------------------------- /tests/scenarios/transform-appl-create-txn.spec.ts: -------------------------------------------------------------------------------- 1 | import { AlgorandClient } from '@algorandfoundation/algokit-utils' 2 | import { TransactionType } from 'algosdk' 3 | import { describe, expect, it } from 'vitest' 4 | import { GetSubscribedTransactions } from '../transactions' 5 | 6 | describe('Application create transaction', () => { 7 | const txnId = 'ZCQ5OCGWV253DIN5XVJTFNWVVTOQ6PYOUI3KH7ORQISLN4PEXIGQ' 8 | const roundNumber = 31171197n 9 | const algorand = AlgorandClient.mainNet() 10 | 11 | it('Can have an app create transaction subscribed correctly from indexer', async () => { 12 | const indexerTxns = await GetSubscribedTransactions( 13 | { 14 | filters: { 15 | type: TransactionType.appl, 16 | }, 17 | roundsToSync: 1, 18 | currentRound: roundNumber + 1n, 19 | syncBehaviour: 'catchup-with-indexer', 20 | watermark: roundNumber - 1n, 21 | }, 22 | algorand, 23 | ) 24 | 25 | const txn = indexerTxns.subscribedTransactions.find((txn) => txn.id === txnId) 26 | expect(txn).toBeDefined() 27 | expect(txn!.createdApplicationIndex).toBe(1167143153n) 28 | }) 29 | 30 | it('Can have an app create transaction subscribed correctly from algod', async () => { 31 | const algodTxns = await GetSubscribedTransactions( 32 | { 33 | filters: { 34 | type: TransactionType.appl, 35 | }, 36 | roundsToSync: 1, 37 | currentRound: roundNumber + 1n, 38 | syncBehaviour: 'sync-oldest', 39 | watermark: roundNumber - 1n, 40 | }, 41 | algorand, 42 | ) 43 | 44 | const txn = algodTxns.subscribedTransactions.find((txn) => txn.id === txnId) 45 | expect(txn).toBeDefined() 46 | expect(txn!.createdApplicationIndex).toBe(1167143153n) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /tests/scenarios/transform-asset-config-txn.spec.ts: -------------------------------------------------------------------------------- 1 | import { AlgorandClient } from '@algorandfoundation/algokit-utils' 2 | import { TransactionType } from 'algosdk' 3 | import { describe, expect, it } from 'vitest' 4 | import { getSubscribedTransactionForDiff } from '../subscribed-transactions' 5 | import { GetSubscribedTransactions } from '../transactions' 6 | 7 | describe('Asset config transaction', () => { 8 | const txnId = 'QHMYTRW27O7KYP6W3SMC3HP55DN5IL2H7Z27T2YDCNFCLXOVFOJQ' 9 | const roundNumber = 44322184n 10 | const algorand = AlgorandClient.mainNet() 11 | 12 | const expectedAssetCreateData = { 13 | assetId: 0n, 14 | params: { 15 | creator: 'ATPVJYGEGP5H6GCZ4T6CG4PK7LH5OMWXHLXZHDPGO7RO6T3EHWTF6UUY6E', 16 | decimals: 6, 17 | total: 2000000000000000n, 18 | clawback: 'ATPVJYGEGP5H6GCZ4T6CG4PK7LH5OMWXHLXZHDPGO7RO6T3EHWTF6UUY6E', 19 | defaultFrozen: false, 20 | freeze: 'ATPVJYGEGP5H6GCZ4T6CG4PK7LH5OMWXHLXZHDPGO7RO6T3EHWTF6UUY6E', 21 | manager: 'ATPVJYGEGP5H6GCZ4T6CG4PK7LH5OMWXHLXZHDPGO7RO6T3EHWTF6UUY6E', 22 | name: 'Fry Node', 23 | nameB64: 'RnJ5IE5vZGU=', 24 | reserve: 'ATPVJYGEGP5H6GCZ4T6CG4PK7LH5OMWXHLXZHDPGO7RO6T3EHWTF6UUY6E', 25 | unitName: 'fNODE', 26 | unitNameB64: 'Zk5PREU=', 27 | url: 'https://frynetworks.com/', 28 | urlB64: 'aHR0cHM6Ly9mcnluZXR3b3Jrcy5jb20v', 29 | }, 30 | } 31 | 32 | it('Can have an asset create with uint name transaction subscribed correctly from indexer', async () => { 33 | const indexerTxns = await GetSubscribedTransactions( 34 | { 35 | filters: { 36 | type: TransactionType.acfg, 37 | }, 38 | roundsToSync: 1, 39 | currentRound: roundNumber + 1n, 40 | syncBehaviour: 'catchup-with-indexer', 41 | watermark: roundNumber - 1n, 42 | }, 43 | algorand, 44 | ) 45 | 46 | const txn = indexerTxns.subscribedTransactions.find((txn) => txn.id === txnId) 47 | expect(txn).toBeDefined() 48 | expect(getSubscribedTransactionForDiff(txn!).assetConfigTransaction).toMatchObject(expectedAssetCreateData) 49 | }) 50 | 51 | it('Can have an asset create with uint name transaction subscribed correctly from algod', async () => { 52 | const algodTxns = await GetSubscribedTransactions( 53 | { 54 | filters: { 55 | type: TransactionType.acfg, 56 | }, 57 | roundsToSync: 1, 58 | currentRound: roundNumber + 1n, 59 | syncBehaviour: 'sync-oldest', 60 | watermark: roundNumber - 1n, 61 | }, 62 | algorand, 63 | ) 64 | 65 | const txn = algodTxns.subscribedTransactions.find((txn) => txn.id === txnId) 66 | expect(txn).toBeDefined() 67 | expect(getSubscribedTransactionForDiff(txn!).assetConfigTransaction).toMatchObject(expectedAssetCreateData) 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /tests/scenarios/transform-heartbeat-txn.spec.ts: -------------------------------------------------------------------------------- 1 | import { AlgorandClient } from '@algorandfoundation/algokit-utils' 2 | import { TransactionType } from 'algosdk' 3 | import { describe, expect, it } from 'vitest' 4 | import { getSubscribedTransactionForDiff } from '../subscribed-transactions' 5 | import { GetSubscribedTransactions } from '../transactions' 6 | 7 | describe('Heartbeat transaction', () => { 8 | // TODO: At some point FNet will likely be torn down, once we have hb transactions on mainnet or testnet we should update this test 9 | const txnId = 'S257NEWKKXQPBRZ4KDZMTL2PBKP6QTBNXUADHB2UZELXIHBYUVAA' 10 | const roundNumber = 3811103n 11 | const algorand = AlgorandClient.fromConfig({ 12 | algodConfig: { 13 | server: 'https://fnet-api.4160.nodely.io/', 14 | port: 443, 15 | }, 16 | indexerConfig: { 17 | server: 'https://fnet-idx.4160.nodely.io/', 18 | port: 443, 19 | }, 20 | }) 21 | const expectedHeartbeatData = { 22 | hbAddress: 'OOEPDEGVG3YL2FUHUEV2LKLPQRP35AJPQL3LV3TS6UFFRLJ552PVDZJGLM', 23 | hbKeyDilution: 1001n, 24 | hbProof: { 25 | hbPk: 'SMvycKufLfsZY8Z+keR+wrpCyshVM2a7MUvgh6ufs4g=', 26 | hbPk1sig: '9csOZzjc8pgVggeK5bT0UCgQ0+f0KYwK+wTqlFFkDimyv6Iqa49ziMoorjSkOsVnsSNCJHQY8EJs9PMc2X31Dw==', 27 | hbPk2: 'Rcxu2WJtT8bu6KUdbDsiYSmA+q/HILqtQF4hImNA5N8=', 28 | hbPk2sig: 'n54z0JW489m0YzCLKsdeoSPL5JRBedbgCdj+MNQTvIT1diLCuyV6BnFXax7wEQy0Q+Q3MWNskmqncCOK6AMFCA==', 29 | hbSig: 'cq2BGIbl2d5w5Qo1ziMYZ9hasVORGwD8NsYQqnrUreD9pyqmY6HOfgdnUf7TtryNef+/RqKRJFcNPrfD7xv5CA==', 30 | }, 31 | hbSeed: 'uC0B02o31PDWHup9KzE/4lSFsOgoKq1+is6IYMK3Ptc=', 32 | hbVoteId: 'S+MIkXmZHCYzcje+tyYh3XOO1XTO7ZX/uH5rhITpKHk=', 33 | } 34 | 35 | it('Can have a hb transaction subscribed correctly from indexer', async () => { 36 | const indexerTxns = await GetSubscribedTransactions( 37 | { 38 | filters: { 39 | type: TransactionType.hb, 40 | }, 41 | roundsToSync: 1, 42 | currentRound: roundNumber + 1n, 43 | syncBehaviour: 'catchup-with-indexer', 44 | watermark: roundNumber - 1n, 45 | }, 46 | algorand, 47 | ) 48 | 49 | expect(indexerTxns.subscribedTransactions.length).toBe(1) 50 | const txn = indexerTxns.subscribedTransactions[0] 51 | expect(txn.id).toBe(txnId) 52 | expect(getSubscribedTransactionForDiff(txn).heartbeatTransaction).toMatchObject(expectedHeartbeatData) 53 | }) 54 | 55 | it('Can have a hb transaction subscribed correctly from algod', async () => { 56 | const algodTxns = await GetSubscribedTransactions( 57 | { 58 | filters: { 59 | type: TransactionType.hb, 60 | }, 61 | roundsToSync: 1, 62 | currentRound: roundNumber + 1n, 63 | syncBehaviour: 'sync-oldest', 64 | watermark: roundNumber - 1n, 65 | }, 66 | algorand, 67 | ) 68 | 69 | expect(algodTxns.subscribedTransactions.length).toBe(1) 70 | const txn = algodTxns.subscribedTransactions[0] 71 | expect(txn.id).toBe(txnId) 72 | expect(getSubscribedTransactionForDiff(txn).heartbeatTransaction).toMatchObject(expectedHeartbeatData) 73 | }) 74 | }) 75 | -------------------------------------------------------------------------------- /tests/scenarios/transform-keyreg.spec.ts: -------------------------------------------------------------------------------- 1 | import { AlgorandClient } from '@algorandfoundation/algokit-utils' 2 | import { TransactionType } from 'algosdk' 3 | import { describe, expect, it } from 'vitest' 4 | import { getSubscribedTransactionForDiff } from '../subscribed-transactions' 5 | import { GetSubscribedTransactions } from '../transactions' 6 | 7 | describe('Complex transaction with many nested inner transactions', () => { 8 | const txnId = 'LSTIW7IBLO4SFPLFAI45WAV3NPXYPX6RWPTZ5KYDL3NX2LTJFXNA' 9 | const roundNumber = 34418662n 10 | const algorand = AlgorandClient.mainNet() 11 | 12 | it('Can have a keyreg transaction subscribed correctly from indexer', async () => { 13 | const indexerTxns = await GetSubscribedTransactions( 14 | { 15 | filters: { 16 | type: TransactionType.keyreg, 17 | sender: 'HQQRVWPYAHABKCXNMZRG242Z5GWFTJMRO63HDCLF23ZWCT3IPQXIGQ2KGY', 18 | }, 19 | roundsToSync: 1, 20 | currentRound: roundNumber + 1n, 21 | syncBehaviour: 'catchup-with-indexer', 22 | watermark: roundNumber - 1n, 23 | }, 24 | algorand, 25 | ) 26 | 27 | expect(indexerTxns.subscribedTransactions.length).toBe(1) 28 | const txn = getSubscribedTransactionForDiff(indexerTxns.subscribedTransactions[0]) 29 | // https://allo.info/tx/LSTIW7IBLO4SFPLFAI45WAV3NPXYPX6RWPTZ5KYDL3NX2LTJFXNA 30 | expect(txn.id).toBe(txnId) 31 | expect(txn).toMatchInlineSnapshot(` 32 | { 33 | "balanceChanges": [ 34 | { 35 | "address": "HQQRVWPYAHABKCXNMZRG242Z5GWFTJMRO63HDCLF23ZWCT3IPQXIGQ2KGY", 36 | "amount": -1000n, 37 | "assetId": 0n, 38 | "roles": [ 39 | "Sender", 40 | ], 41 | }, 42 | ], 43 | "closeRewards": 0n, 44 | "closingAmount": 0n, 45 | "confirmedRound": 34418662n, 46 | "fee": 1000n, 47 | "filtersMatched": [ 48 | "default", 49 | ], 50 | "firstValid": 34418595n, 51 | "genesisHash": "wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=", 52 | "genesisId": "mainnet-v1.0", 53 | "id": "LSTIW7IBLO4SFPLFAI45WAV3NPXYPX6RWPTZ5KYDL3NX2LTJFXNA", 54 | "innerTxns": [], 55 | "intraRoundOffset": 54, 56 | "keyregTransaction": { 57 | "nonParticipation": false, 58 | "selectionParticipationKey": "Fsp1QLE/fXpmq5fsk/bWP8P1+H8n30bMD3X7hPdk/GU=", 59 | "stateProofKey": "Qld9eu3U/OhHohBMF4atWbKbDQB5NGO2vPl5sZ9q9yHssmrbnQIOlhujP3vaSdFXqstnzD77Z85yrlfxJFfu+g==", 60 | "voteFirstValid": 34300000n, 61 | "voteKeyDilution": 2450n, 62 | "voteLastValid": 40300000n, 63 | "voteParticipationKey": "yUR+nfHtSb2twOaprEXrnYjkhbFMBtmXW9D8x+/ROBg=", 64 | }, 65 | "lastValid": 34419595n, 66 | "receiverRewards": 0n, 67 | "roundTime": 1702579204, 68 | "sender": "HQQRVWPYAHABKCXNMZRG242Z5GWFTJMRO63HDCLF23ZWCT3IPQXIGQ2KGY", 69 | "senderRewards": 0n, 70 | "signature": { 71 | "sig": "zs+8H5J4hXmmKk36uEupgupE5Filw/xMae0ox5c7yuHM4jYVPLPBYHLOdPapguScPzuz0Lney/+V9MFrKLj9Dw==", 72 | }, 73 | "txType": "keyreg", 74 | } 75 | `) 76 | }) 77 | 78 | it('Can have an inner transaction subscribed correctly from algod', async () => { 79 | const algodTxns = await GetSubscribedTransactions( 80 | { 81 | filters: { 82 | type: TransactionType.keyreg, 83 | sender: 'HQQRVWPYAHABKCXNMZRG242Z5GWFTJMRO63HDCLF23ZWCT3IPQXIGQ2KGY', 84 | }, 85 | roundsToSync: 1, 86 | currentRound: roundNumber + 1n, 87 | syncBehaviour: 'sync-oldest', 88 | watermark: roundNumber - 1n, 89 | }, 90 | algorand, 91 | ) 92 | 93 | expect(algodTxns.subscribedTransactions.length).toBe(1) 94 | const txn = algodTxns.subscribedTransactions[0] 95 | // https://allo.info/tx/LSTIW7IBLO4SFPLFAI45WAV3NPXYPX6RWPTZ5KYDL3NX2LTJFXNA 96 | expect(txn.id).toBe(txnId) 97 | expect(getSubscribedTransactionForDiff(txn)).toMatchInlineSnapshot(` 98 | { 99 | "balanceChanges": [ 100 | { 101 | "address": "HQQRVWPYAHABKCXNMZRG242Z5GWFTJMRO63HDCLF23ZWCT3IPQXIGQ2KGY", 102 | "amount": -1000n, 103 | "assetId": 0n, 104 | "roles": [ 105 | "Sender", 106 | ], 107 | }, 108 | ], 109 | "confirmedRound": 34418662n, 110 | "fee": 1000n, 111 | "filtersMatched": [ 112 | "default", 113 | ], 114 | "firstValid": 34418595n, 115 | "genesisHash": "wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=", 116 | "genesisId": "mainnet-v1.0", 117 | "id": "LSTIW7IBLO4SFPLFAI45WAV3NPXYPX6RWPTZ5KYDL3NX2LTJFXNA", 118 | "intraRoundOffset": 54, 119 | "keyregTransaction": { 120 | "nonParticipation": false, 121 | "selectionParticipationKey": "Fsp1QLE/fXpmq5fsk/bWP8P1+H8n30bMD3X7hPdk/GU=", 122 | "stateProofKey": "Qld9eu3U/OhHohBMF4atWbKbDQB5NGO2vPl5sZ9q9yHssmrbnQIOlhujP3vaSdFXqstnzD77Z85yrlfxJFfu+g==", 123 | "voteFirstValid": 34300000n, 124 | "voteKeyDilution": 2450n, 125 | "voteLastValid": 40300000n, 126 | "voteParticipationKey": "yUR+nfHtSb2twOaprEXrnYjkhbFMBtmXW9D8x+/ROBg=", 127 | }, 128 | "lastValid": 34419595n, 129 | "note": "", 130 | "roundTime": 1702579204, 131 | "sender": "HQQRVWPYAHABKCXNMZRG242Z5GWFTJMRO63HDCLF23ZWCT3IPQXIGQ2KGY", 132 | "signature": { 133 | "sig": "zs+8H5J4hXmmKk36uEupgupE5Filw/xMae0ox5c7yuHM4jYVPLPBYHLOdPapguScPzuz0Lney/+V9MFrKLj9Dw==", 134 | }, 135 | "txType": "keyreg", 136 | } 137 | `) 138 | }) 139 | }) 140 | -------------------------------------------------------------------------------- /tests/setup.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorandfoundation/algokit-subscriber-ts/c81d41406a04af63ac6a05fef0d50faf1686bfd3/tests/setup.ts -------------------------------------------------------------------------------- /tests/subscribed-transactions.ts: -------------------------------------------------------------------------------- 1 | import { Address } from 'algosdk' 2 | import type { SubscribedTransaction } from '../src/types' 3 | 4 | // Standardise objects to make them easier to compare 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 6 | const standardiseObject = (obj: any): any => { 7 | if (obj instanceof Address) { 8 | return obj.toString() 9 | } 10 | if (obj instanceof Uint8Array) { 11 | return Buffer.from(obj).toString('base64') 12 | } 13 | if (obj instanceof Array) { 14 | return obj.map((v) => standardiseObject(v)) 15 | } 16 | if (typeof obj === 'object') { 17 | return Object.entries(obj).reduce( 18 | (acc, [key, value]) => { 19 | if (value == null) { 20 | return acc 21 | } 22 | return { 23 | ...acc, 24 | [key]: standardiseObject(value), 25 | } 26 | }, 27 | {} as Record, 28 | ) 29 | } 30 | 31 | return obj 32 | } 33 | 34 | export function getSubscribedTransactionForDiff(transaction: SubscribedTransaction): any { 35 | return standardiseObject(transaction) 36 | } 37 | -------------------------------------------------------------------------------- /tests/testing-app.ts: -------------------------------------------------------------------------------- 1 | import { AlgorandClient } from '@algorandfoundation/algokit-utils' 2 | import algosdk, { OnApplicationComplete } from 'algosdk' 3 | import { TestingAppFactory } from './contract/client' 4 | 5 | export async function app(params: { 6 | create: true 7 | algorand: AlgorandClient 8 | creator: algosdk.Account 9 | note?: string 10 | }): ReturnType 11 | export async function app(params: { 12 | create: false 13 | algorand: AlgorandClient 14 | creator: algosdk.Account 15 | note?: string 16 | }): Promise 17 | export async function app(params: { create: boolean; algorand: AlgorandClient; creator: algosdk.Account; note?: string }) { 18 | params.algorand.setSignerFromAccount(params.creator) 19 | const factory = new TestingAppFactory({ 20 | algorand: params.algorand, 21 | }) 22 | 23 | const result = await (params.create ? factory.send : factory.createTransaction).create.bare({ 24 | sender: params.creator.addr, 25 | onComplete: OnApplicationComplete.NoOpOC, 26 | note: params.note, 27 | }) 28 | 29 | return result 30 | } 31 | -------------------------------------------------------------------------------- /tests/transactions.ts: -------------------------------------------------------------------------------- 1 | import { algo, AlgorandClient } from '@algorandfoundation/algokit-utils' 2 | import { SendTransactionResult } from '@algorandfoundation/algokit-utils/types/transaction' 3 | import { Account, Transaction } from 'algosdk' 4 | import { vi } from 'vitest' 5 | import { getSubscribedTransactions } from '../src' 6 | import type { 7 | Arc28EventGroup, 8 | NamedTransactionFilter, 9 | TransactionFilter, 10 | TransactionInBlock, 11 | TransactionSubscriptionParams, 12 | } from '../src/types' 13 | 14 | export const SendXTransactions = async (x: number, account: Account, algorand: AlgorandClient) => { 15 | const txns: SendTransactionResult[] = [] 16 | for (let i = 0; i < x; i++) { 17 | algorand.setSignerFromAccount(account) 18 | txns.push( 19 | await algorand.send.payment({ 20 | sender: account.addr, 21 | receiver: account.addr, 22 | amount: algo(1), 23 | note: `txn-${i} at ${new Date().toISOString()}`, 24 | }), 25 | ) 26 | } 27 | const lastTxnRound = txns[x - 1].confirmation!.confirmedRound! 28 | 29 | return { 30 | txns, 31 | txIds: txns.map((t) => t.transaction.txID()), 32 | lastTxnRound, 33 | rounds: txns.map((t) => t.confirmation!.confirmedRound!), 34 | } 35 | } 36 | 37 | export const GetSubscribedTransactions = ( 38 | subscription: { 39 | syncBehaviour: TransactionSubscriptionParams['syncBehaviour'] 40 | roundsToSync: number 41 | indexerRoundsToSync?: number 42 | watermark?: bigint 43 | currentRound?: bigint 44 | filters: TransactionFilter | NamedTransactionFilter[] 45 | arc28Events?: Arc28EventGroup[] 46 | }, 47 | algorand: AlgorandClient, 48 | ) => { 49 | const { roundsToSync, indexerRoundsToSync, syncBehaviour, watermark, currentRound, filters, arc28Events } = subscription 50 | 51 | if (currentRound !== undefined) { 52 | const existingStatus = algorand.client.algod.status 53 | Object.assign(algorand.client.algod, { 54 | status: vi.fn().mockImplementation(() => { 55 | return { 56 | do: async () => { 57 | const status = await existingStatus.apply(algorand.client.algod).do() 58 | status.lastRound = currentRound 59 | return status 60 | }, 61 | } 62 | }), 63 | }) 64 | } 65 | 66 | return getSubscribedTransactions( 67 | { 68 | filters: Array.isArray(filters) ? filters : [{ name: 'default', filter: filters }], 69 | maxRoundsToSync: roundsToSync, 70 | maxIndexerRoundsToSync: indexerRoundsToSync, 71 | syncBehaviour: syncBehaviour, 72 | watermark: watermark ?? 0n, 73 | arc28Events, 74 | }, 75 | algorand.client.algod, 76 | algorand.client.indexer, 77 | ) 78 | } 79 | 80 | export const GetSubscribedTransactionsFromSender = ( 81 | subscription: { 82 | syncBehaviour: TransactionSubscriptionParams['syncBehaviour'] 83 | roundsToSync: number 84 | indexerRoundsToSync?: number 85 | watermark?: bigint 86 | currentRound?: bigint 87 | }, 88 | account: Account | Account[], 89 | algorand: AlgorandClient, 90 | ) => { 91 | return GetSubscribedTransactions( 92 | { 93 | ...subscription, 94 | filters: 95 | account instanceof Array 96 | ? account.map((a) => a.addr).map((a) => ({ name: a.toString(), filter: { sender: a.toString() } })) 97 | : { sender: account.addr.toString() }, 98 | }, 99 | algorand, 100 | ) 101 | } 102 | 103 | export function getTransactionInBlockForDiff(transaction: TransactionInBlock) { 104 | return { 105 | transactionId: transaction.transactionId, 106 | transaction: getTransactionForDiff(transaction.transaction), 107 | parentTransactionId: transaction.parentTransactionId, 108 | parentIntraRoundOffset: transaction.parentIntraRoundOffset, 109 | intraRoundOffset: transaction.intraRoundOffset, 110 | createdAppId: transaction.createdAppId, 111 | createdAssetId: transaction.createdAppId, 112 | assetCloseAmount: transaction.assetCloseAmount, 113 | closeAmount: transaction.closeAmount, 114 | } 115 | } 116 | 117 | export function getTransactionForDiff(transaction: Transaction) { 118 | const t = { 119 | ...transaction, 120 | applicationCall: transaction.applicationCall 121 | ? { 122 | ...transaction.applicationCall, 123 | accounts: transaction.applicationCall?.accounts?.map((a) => a.toString()), 124 | appArgs: transaction.applicationCall?.appArgs?.map((a) => Buffer.from(a).toString('base64')), 125 | approvalProgram: transaction.applicationCall?.approvalProgram 126 | ? Buffer.from(transaction.applicationCall.approvalProgram).toString('base64') 127 | : undefined, 128 | clearProgram: transaction.applicationCall?.clearProgram 129 | ? Buffer.from(transaction.applicationCall.clearProgram).toString('base64') 130 | : undefined, 131 | } 132 | : undefined, 133 | payment: transaction.payment 134 | ? { 135 | ...transaction.payment, 136 | receiver: transaction.payment?.receiver?.toString(), 137 | } 138 | : undefined, 139 | assetTransfer: transaction.assetTransfer 140 | ? { 141 | ...transaction.assetTransfer, 142 | receiver: transaction.assetTransfer?.receiver?.toString(), 143 | } 144 | : undefined, 145 | genesisHash: transaction.genesisHash ? Buffer.from(transaction.genesisHash).toString('base64') : '', 146 | group: transaction.group ? Buffer.from(transaction.group).toString('base64') : undefined, 147 | lease: transaction.lease && transaction.lease.length ? Buffer.from(transaction.lease).toString('base64') : undefined, 148 | note: transaction.note && transaction.note.length ? Buffer.from(transaction.note).toString('base64') : undefined, 149 | sender: transaction.sender.toString(), 150 | rekeyTo: transaction.rekeyTo ? transaction.rekeyTo.toString() : undefined, 151 | } 152 | 153 | return clearUndefineds(t as any) 154 | } 155 | 156 | export function clearUndefineds(object: Record) { 157 | Object.keys(object).forEach((k) => { 158 | if (object[k] === undefined) { 159 | delete object[k] 160 | } else if (typeof object[k] === 'object') { 161 | clearUndefineds(object[k] as Record) 162 | } 163 | }) 164 | return object 165 | } 166 | -------------------------------------------------------------------------------- /tests/wait.ts: -------------------------------------------------------------------------------- 1 | export function sleep(ms: number) { 2 | return new Promise((resolve) => setTimeout(resolve, ms)) 3 | } 4 | 5 | export async function waitFor(test: () => Promise | T, timeoutMs = 20 * 1000) { 6 | const endTime = Date.now() + timeoutMs 7 | let result = await test() 8 | while (!result) { 9 | if (Date.now() > endTime) { 10 | // eslint-disable-next-line no-console 11 | console.error(`Timed out`) 12 | return false 13 | } 14 | await sleep(50) 15 | result = await test() 16 | } 17 | return result 18 | } 19 | -------------------------------------------------------------------------------- /tests/watermarks.ts: -------------------------------------------------------------------------------- 1 | export const FixedWatermark = (fixedWatermark: bigint) => ({ 2 | set: async (_: bigint) => {}, 3 | get: async () => fixedWatermark, 4 | }) 5 | 6 | export const InMemoryWatermark = (get: () => bigint, set: (w: bigint) => void) => ({ 7 | set: async (w: bigint) => { 8 | set(w) 9 | }, 10 | get: async () => get(), 11 | }) 12 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src/**/*.ts"], 4 | "exclude": ["tests/**/*.ts", "examples/**/*.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "CommonJS", 5 | "target": "ES2020", 6 | "moduleResolution": "Node" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "target": "ES2020", 5 | "moduleResolution": "Bundler", 6 | "esModuleInterop": true, 7 | "types": ["node", "vitest"], 8 | "outDir": "dist", 9 | "declaration": true, 10 | "declarationMap": true, 11 | "sourceMap": true, 12 | "strict": true, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "experimentalDecorators": true, 16 | "emitDecoratorMetadata": true, 17 | "skipLibCheck": true, 18 | "forceConsistentCasingInFileNames": true, 19 | "importHelpers": true, 20 | "isolatedModules": true, 21 | "resolveJsonModule": true 22 | }, 23 | "include": ["examples/**/*.ts", "src/**/*.ts", "tests/**/*.ts", "rollup/**/*.ts", "rollup.config.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://typedoc.org/schema.json", 3 | "entryPoints": ["src/index.ts", "src/types/*.ts"], 4 | "exclude": ["src/**/*.spec.ts", "tests/**/*.*", "examples/**/*.*"], 5 | "entryPointStrategy": "expand", 6 | "out": "docs/code", 7 | "plugin": ["typedoc-plugin-markdown"], 8 | "theme": "default", 9 | "cleanOutputDir": true, 10 | "githubPages": false, 11 | "readme": "none", 12 | "entryDocument": "README.md", 13 | "gitRevision": "main", 14 | "tsconfig": "tsconfig.build.json" 15 | } 16 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | appType: 'custom', 5 | test: { 6 | include: ['**/*.spec.ts'], 7 | exclude: ['node_modules'], 8 | // Sometimes indexer catchup is slowwwww... 9 | testTimeout: 20_000, 10 | setupFiles: ['tests/setup.ts'], 11 | coverage: { 12 | include: ['src/**/*.ts'], 13 | exclude: ['src/types/*.*'], 14 | reporter: ['text', 'html'], 15 | }, 16 | typecheck: { 17 | tsconfig: 'tsconfig.test.json', 18 | }, 19 | env: { 20 | ALGOD_TOKEN: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 21 | ALGOD_SERVER: 'http://localhost', 22 | ALGOD_PORT: '4001', 23 | KMD_PORT: '4002', 24 | INDEXER_TOKEN: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 25 | INDEXER_SERVER: 'http://localhost', 26 | INDEXER_PORT: '8980', 27 | }, 28 | }, 29 | }) 30 | --------------------------------------------------------------------------------