├── .changeset ├── README.md └── config.json ├── .cursor.json ├── .env.example ├── .github └── workflows │ ├── create-release.yml │ ├── dev-test-on-pr.yml │ ├── prod-test-on-pr.yml │ └── record-changeset.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── babel.config.js ├── config └── aggregator.yaml ├── docker-compose.yml ├── docs └── TIMEOUT_CONFIGURATION.md ├── eslint.config.mjs ├── examples ├── README.md ├── config.ts ├── example.ts ├── package.json ├── tsconfig.json └── yarn.lock ├── grpc_codegen ├── avs.proto ├── avs_grpc_pb.d.ts ├── avs_grpc_pb.js ├── avs_pb.d.ts └── avs_pb.js ├── jest.config.cjs ├── package.json ├── packages ├── sdk-js │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── auth.ts │ │ ├── config.ts │ │ ├── index.ts │ │ ├── models │ │ │ ├── edge.ts │ │ │ ├── execution.ts │ │ │ ├── node │ │ │ │ ├── branch.ts │ │ │ │ ├── contractRead.ts │ │ │ │ ├── contractWrite.ts │ │ │ │ ├── customCode.ts │ │ │ │ ├── ethTransfer.ts │ │ │ │ ├── factory.ts │ │ │ │ ├── filter.ts │ │ │ │ ├── graphqlQuery.ts │ │ │ │ ├── interface.ts │ │ │ │ ├── loop.ts │ │ │ │ └── restApi.ts │ │ │ ├── secret.ts │ │ │ ├── step.ts │ │ │ ├── trigger │ │ │ │ ├── block.ts │ │ │ │ ├── cron.ts │ │ │ │ ├── event.ts │ │ │ │ ├── factory.ts │ │ │ │ ├── fixedTime.ts │ │ │ │ ├── interface.ts │ │ │ │ └── manual.ts │ │ │ └── workflow.ts │ │ └── utils.ts │ └── tsconfig.json └── types │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ ├── api.ts │ ├── auth.ts │ ├── enums.ts │ ├── index.ts │ ├── node.ts │ ├── shared.ts │ ├── token.ts │ ├── trigger.ts │ └── workflow.ts │ └── tsconfig.json ├── scripts ├── README.md ├── generate-docs.js ├── generate-signed-message.js ├── prepare-package.js ├── publish-packages.js ├── publish-packages.sh ├── release.js └── run-tests-with-docker.sh ├── tests ├── core │ ├── auth.test.ts │ ├── step.test.ts │ └── wallet.test.ts ├── executions │ ├── execution.test.ts │ ├── getExecution.test.ts │ ├── getExecutions.test.ts │ ├── simulateWorkflow.test.ts │ └── stepInput.test.ts ├── integrations │ ├── getTokenMetadata.test.ts │ └── secret.test.ts ├── nodes │ ├── RestAPi.test.ts │ ├── branchNode.test.ts │ ├── contractRead.test.ts │ ├── contractWrite.test.ts │ ├── customCode.test.ts │ ├── filterNode.test.ts │ └── loopNode.test.ts ├── runImmediately.test.ts ├── templates │ ├── README.md │ ├── index.ts │ └── telegram-alert-on-transfer.test.ts ├── triggers │ ├── block.test.ts │ ├── cron.test.ts │ └── eventTrigger.test.ts ├── utils │ ├── abis.ts │ ├── envalid.ts │ ├── mocks │ │ └── api.ts │ ├── templates.ts │ ├── templates │ │ └── loopNode.ts │ └── utils.ts └── workflows │ ├── cancelWorkflow.test.ts │ ├── createWorkflow.test.ts │ ├── deleteWorkflow.test.ts │ ├── triggerWorkflow.test.ts │ └── workflow.test.ts ├── tsconfig.json ├── verify-changes.js └── yarn.lock /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.3/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": true, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [], 11 | "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { 12 | "updateInternalDependents": "always" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.cursor.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands": { 3 | "build": "cd ava-sdk-js && npm run build", 4 | "test": "cd ava-sdk-js && npx jest", 5 | "lint": "cd ava-sdk-js && npm run lint" 6 | }, 7 | "instructions": [ 8 | "The avs.proto file is in the EigenLayer-AVS directory. Always copy the `avs.proto` file to the `ava-sdk-js/grpc_codegen` folder, and run `npm run protoc-gen` to generate the protobuf files.", 9 | "For the TypeScript SDK in ava-sdk-js, use `npm run build` to build and `npx jest` to run tests." 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # The private key of the test wallet 2 | TEST_PRIVATE_KEY= 3 | 4 | # For each chain environment, create a new .env.${ENV} file and set the following variables: 5 | # AVS_API_KEY= 6 | # CHAIN_ENDPOINT= 7 | 8 | # For example, for the Ethereum mainnet, create a new .env.ethereum file and set the following variables: 9 | # AVS_API_KEY= 10 | # CHAIN_ENDPOINT= 11 | -------------------------------------------------------------------------------- /.github/workflows/create-release.yml: -------------------------------------------------------------------------------- 1 | name: Release with Changeset 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | release: 8 | name: Create Release 9 | runs-on: ubuntu-latest 10 | if: github.ref == 'refs/heads/main' 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | token: ${{ github.token }} 17 | 18 | - name: Setup Node.js 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: "20" 22 | 23 | - name: Install dependencies 24 | run: npm ci 25 | 26 | - name: Commit Changeset Version Bump 27 | id: changesets 28 | run: | 29 | git config --global user.email "github-actions[bot]@users.noreply.github.com" 30 | git config --global user.name "github-actions[bot]" 31 | npx changeset version 32 | 33 | # Print the git status to see the changes 34 | git status 35 | 36 | if [ -n "$(git status --porcelain)" ]; then 37 | NEW_VERSION=$(node -p "require('./package.json').version") 38 | git add . 39 | git commit -m "Version packages" 40 | echo "commit=true" >> $GITHUB_ENV 41 | echo "NEW_VERSION=${NEW_VERSION}" >> $GITHUB_ENV 42 | echo "::set-output name=commit::true" 43 | else 44 | echo "No changes to commit" 45 | echo "commit=false" >> $GITHUB_ENV 46 | echo "::set-output name=commit::false" 47 | fi 48 | 49 | - name: Pull Latest Changes 50 | run: | 51 | git pull origin main 52 | env: 53 | GITHUB_TOKEN: ${{ github.token }} 54 | 55 | - name: Push Version Bump to Main 56 | if: steps.changesets.outputs.commit == 'true' 57 | run: | 58 | git push origin main 59 | env: 60 | GITHUB_TOKEN: ${{ github.token }} 61 | 62 | - name: Check Version Change 63 | id: version_check 64 | env: 65 | GITHUB_TOKEN: ${{ github.token }} 66 | run: | 67 | CURRENT_VERSION=$(node -p "require('./package.json').version") 68 | LATEST_RELEASE=$(gh release list --limit 1 | cut -f1) 69 | LATEST_VERSION=${LATEST_RELEASE#v} 70 | 71 | echo "Current version: ${CURRENT_VERSION}" 72 | echo "Latest release: ${LATEST_VERSION}" 73 | 74 | if [ "$(printf '%s\n' "$LATEST_VERSION" "$CURRENT_VERSION" | sort -V | tail -n1)" = "$CURRENT_VERSION" ] && [ "$LATEST_VERSION" != "$CURRENT_VERSION" ]; then 75 | echo "Version increment detected" 76 | echo "should_release=true" >> $GITHUB_OUTPUT 77 | else 78 | echo "No version increment detected or versions are equal" 79 | echo "should_release=false" >> $GITHUB_OUTPUT 80 | exit 0 81 | fi 82 | 83 | - name: Create GitHub Release 84 | if: steps.version_check.outputs.should_release == 'true' 85 | env: 86 | GITHUB_TOKEN: ${{ github.token }} 87 | run: | 88 | CURRENT_VERSION=$(node -p "require('./package.json').version") 89 | gh release create "v${CURRENT_VERSION}" \ 90 | --title "v${CURRENT_VERSION}" \ 91 | --generate-notes 92 | -------------------------------------------------------------------------------- /.github/workflows/dev-test-on-pr.yml: -------------------------------------------------------------------------------- 1 | name: Run Dev Tests on PR 2 | 3 | on: 4 | pull_request: 5 | workflow_dispatch: 6 | inputs: 7 | docker_image_tag: 8 | description: "Docker Image Tag of avaprotocol/avs-dev" 9 | required: false 10 | default: "latest" # Or use an empty default if you want to force it from elsewhere for PRs 11 | 12 | jobs: 13 | dev-test: 14 | runs-on: ubuntu-latest 15 | environment: dev 16 | 17 | env: 18 | CHAIN_ENDPOINT: ${{ vars.CHAIN_ENDPOINT }} 19 | ENDPOINT: "localhost:2206" 20 | TEST_PRIVATE_KEY: ${{ secrets.TEST_PRIVATE_KEY }} 21 | DOCKER_IMAGE_TAG: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.docker_image_tag || 'latest' }} # Or your preferred default for PRs 22 | 23 | steps: 24 | - name: Checkout repository 25 | uses: actions/checkout@v4 26 | 27 | - name: Debug environment variables 28 | run: | 29 | echo "ENDPOINT: $ENDPOINT" 30 | echo "DOCKER_IMAGE_TAG: $DOCKER_IMAGE_TAG" 31 | 32 | - name: Check Source or Test Code Changes 33 | id: check_source_changes 34 | if: github.event_name == 'pull_request' 35 | run: | 36 | echo "PR Base SHA: ${{ github.event.pull_request.base.sha }}" 37 | echo "PR Head SHA: ${{ github.event.pull_request.head.sha }}" 38 | echo "GitHub Ref: ${{ github.ref }}" 39 | 40 | # Ensure we have the base branch 41 | git fetch origin ${{ github.base_ref }} --depth=1 42 | 43 | # Get all changed files 44 | CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }} HEAD) 45 | 46 | # Define the source code patterns we want to monitor 47 | SOURCE_PATTERN='^(packages/sdk-js/src|packages/types/src|tests/)' 48 | 49 | echo "=== Debug Information ===" 50 | echo "Base ref: ${{ github.base_ref }}" 51 | echo "Current branch: $(git branch --show-current)" 52 | echo "Git status:" 53 | git status 54 | 55 | echo "=== Changed Files ===" 56 | echo "$CHANGED_FILES" 57 | 58 | # Check if any files in our monitored directories have changed 59 | MATCHING_FILES=$(echo "$CHANGED_FILES" | grep -E "$SOURCE_PATTERN" || echo "") 60 | 61 | echo "=== Matching Files ===" 62 | echo "$MATCHING_FILES" 63 | 64 | if [ -z "$MATCHING_FILES" ]; then 65 | echo "source_changed=false" >> $GITHUB_OUTPUT 66 | echo "No changes detected in monitored directories, skipping tests" 67 | else 68 | echo "source_changed=true" >> $GITHUB_OUTPUT 69 | echo "Changes detected in monitored directories:" 70 | echo "$MATCHING_FILES" 71 | fi 72 | shell: bash 73 | 74 | - name: Run local AVS aggregator in Docker 75 | if: github.event_name == 'workflow_dispatch' || steps.check_source_changes.outputs.source_changed == 'true' 76 | run: | 77 | echo "Starting AVS aggregator with version: $DOCKER_IMAGE_TAG" 78 | docker compose up -d 79 | 80 | - name: Use Node.js 81 | if: github.event_name == 'workflow_dispatch' || steps.check_source_changes.outputs.source_changed == 'true' 82 | uses: actions/setup-node@v4 83 | with: 84 | node-version: "20.x" 85 | cache: "yarn" 86 | 87 | - name: Install dependencies 88 | if: github.event_name == 'workflow_dispatch' || steps.check_source_changes.outputs.source_changed == 'true' 89 | run: yarn install 90 | 91 | - name: Ensure the aggregator is running 92 | if: github.event_name == 'workflow_dispatch' || steps.check_source_changes.outputs.source_changed == 'true' 93 | run: | 94 | until curl --output /dev/null --silent --fail http://localhost:1323/up; do 95 | printf '.' 96 | sleep 5 97 | done 98 | 99 | - name: Retrieve AVS API Key from the aggregator 100 | if: github.event_name == 'workflow_dispatch' || steps.check_source_changes.outputs.source_changed == 'true' 101 | id: setup-env 102 | run: | 103 | echo "AVS_API_KEY=$(docker compose exec aggregator /ava create-api-key --role=admin --subject=apikey)" >> $GITHUB_ENV 104 | echo "ENDPOINT=localhost:2206" >> $GITHUB_ENV 105 | 106 | - name: Print Required Environment Variables 107 | if: github.event_name == 'workflow_dispatch' || steps.check_source_changes.outputs.source_changed == 'true' 108 | run: | 109 | echo "=== Required Variables from envalid.ts ===" 110 | echo "AVS_API_KEY: ...${AVS_API_KEY: -6}" # Show last 6 chars for security 111 | echo "CHAIN_ENDPOINT: $CHAIN_ENDPOINT" 112 | echo "TEST_PRIVATE_KEY: ...${TEST_PRIVATE_KEY: -6}" # Show last 6 chars for security 113 | echo "TEST_ENV: dev" 114 | 115 | - name: Build the SDK for local test 116 | if: github.event_name == 'workflow_dispatch' || steps.check_source_changes.outputs.source_changed == 'true' 117 | run: yarn build 118 | 119 | - name: Test Core Functionality 120 | if: github.event_name == 'workflow_dispatch' || steps.check_source_changes.outputs.source_changed == 'true' 121 | env: 122 | TEST_ENV: dev 123 | run: yarn test:core 124 | 125 | - name: Test Workflows 126 | if: github.event_name == 'workflow_dispatch' || steps.check_source_changes.outputs.source_changed == 'true' 127 | env: 128 | TEST_ENV: dev 129 | run: yarn test:workflows 130 | 131 | - name: Test Executions 132 | if: github.event_name == 'workflow_dispatch' || steps.check_source_changes.outputs.source_changed == 'true' 133 | env: 134 | TEST_ENV: dev 135 | run: yarn test:executions 136 | 137 | - name: Test Triggers 138 | if: github.event_name == 'workflow_dispatch' || steps.check_source_changes.outputs.source_changed == 'true' 139 | env: 140 | TEST_ENV: dev 141 | run: yarn test:triggers 142 | 143 | - name: Test Nodes 144 | if: github.event_name == 'workflow_dispatch' || steps.check_source_changes.outputs.source_changed == 'true' 145 | env: 146 | TEST_ENV: dev 147 | run: yarn test:nodes 148 | 149 | - name: Test Integrations 150 | if: github.event_name == 'workflow_dispatch' || steps.check_source_changes.outputs.source_changed == 'true' 151 | env: 152 | TEST_ENV: dev 153 | run: yarn test:integrations 154 | -------------------------------------------------------------------------------- /.github/workflows/prod-test-on-pr.yml: -------------------------------------------------------------------------------- 1 | name: Run Production Tests on PR 2 | 3 | on: 4 | pull_request: 5 | workflow_dispatch: 6 | inputs: 7 | environment: 8 | description: "Environment to run tests against" 9 | required: true 10 | default: "sepolia" 11 | type: choice 12 | options: 13 | - sepolia 14 | - ethereum 15 | - base 16 | - base-sepolia 17 | - soneium-minato 18 | 19 | jobs: 20 | production-test: 21 | runs-on: ubuntu-latest 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | env: [sepolia, ethereum, base, base-sepolia, soneium-minato] 26 | environment: ${{ matrix.env }} 27 | env: 28 | CHAIN_ENDPOINT: ${{ vars.CHAIN_ENDPOINT }} 29 | TEST_PRIVATE_KEY: ${{ secrets.TEST_PRIVATE_KEY }} 30 | TEST_ENV: ${{ matrix.env }} 31 | AVS_API_KEY: ${{ secrets.AVS_API_KEY }} 32 | # On production env, when test run it may not cleanup data properly for multiple reasons. 33 | # In the next run, especially on pagination test, the assumption about data is wrong. 34 | # For those kind of particular test, we want to ensure a 100% unique salt across run 35 | # One way to do so, is get this run_id from github action and use it as salt 36 | # On local dev, we just default to a hard code number in the test to isolate its. data 37 | # on local test is continously wipe out on every run so it isn't a problem like production 38 | # We may want to sweep and delete task data on those test account 39 | RUN_ID: ${{ github.run_id }} 40 | 41 | steps: 42 | - name: Checkout repository 43 | uses: actions/checkout@v4 44 | with: 45 | ref: ${{ github.event.pull_request.head.ref }} 46 | fetch-depth: 0 # Fetch all history for all branches 47 | 48 | - name: Debug environment variables 49 | run: | 50 | echo "TEST_ENV: $TEST_ENV" 51 | echo "Using environment: ${{ matrix.env }}" 52 | echo "CHAIN_ENDPOINT: $CHAIN_ENDPOINT" 53 | 54 | - name: Check Source or Test Code Changes 55 | id: check_source_changes 56 | if: github.event_name == 'pull_request' 57 | run: | 58 | echo "=== Pull Request Information ===" 59 | echo "PR Base SHA: ${{ github.event.pull_request.base.sha }}" 60 | echo "PR Head SHA: ${{ github.event.pull_request.head.sha }}" 61 | echo "PR Head Ref: ${{ github.event.pull_request.head.ref }}" 62 | echo "GitHub Ref: ${{ github.ref }}" 63 | echo "Base Branch: ${{ github.base_ref }}" 64 | 65 | # Fetch both branches 66 | git fetch origin ${{ github.base_ref }} 67 | git fetch origin ${{ github.event.pull_request.head.ref }} 68 | 69 | # Get all changed files (only names) 70 | CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }} HEAD) 71 | 72 | # Define the source code patterns we want to monitor 73 | SOURCE_PATTERN='^(packages/sdk-js/src|packages/types/src|tests/)' 74 | 75 | echo "=== Git Information ===" 76 | echo "Current branch: $(git branch --show-current)" 77 | echo "Git status:" 78 | git status 79 | 80 | echo "=== Changed Files ===" 81 | echo "$CHANGED_FILES" 82 | 83 | # Check if any files in our monitored directories have changed 84 | MATCHING_FILES=$(echo "$CHANGED_FILES" | grep -E "$SOURCE_PATTERN" || echo "") 85 | 86 | echo "=== Matching Files ===" 87 | echo "$MATCHING_FILES" 88 | 89 | if [ -z "$MATCHING_FILES" ]; then 90 | echo "source_changed=false" >> $GITHUB_OUTPUT 91 | echo "No changes detected in monitored directories, skipping tests" 92 | else 93 | echo "source_changed=true" >> $GITHUB_OUTPUT 94 | echo "Changes detected in monitored directories:" 95 | echo "$MATCHING_FILES" 96 | fi 97 | shell: bash 98 | 99 | - name: Print Required Environment Variables 100 | if: github.event_name == 'workflow_dispatch' || steps.check_source_changes.outputs.source_changed == 'true' 101 | run: | 102 | echo "=== Required Variables from envalid.ts ===" 103 | echo "AVS_API_KEY: ...${AVS_API_KEY: -6}" # Show last 6 chars for security 104 | echo "CHAIN_ENDPOINT: $CHAIN_ENDPOINT" 105 | echo "TEST_PRIVATE_KEY: ...${TEST_PRIVATE_KEY: -6}" # Show last 6 chars for security 106 | echo "TEST_ENV: $TEST_ENV" 107 | 108 | if [ -z "$AVS_API_KEY" ] || [ -z "$CHAIN_ENDPOINT" ] || [ -z "$TEST_PRIVATE_KEY" ] || [ -z "$TEST_ENV" ]; then 109 | echo "Error: One or more required environment variables are empty." >&2 110 | exit 1 111 | fi 112 | 113 | - name: Use Node.js 114 | if: github.event_name == 'workflow_dispatch' || steps.check_source_changes.outputs.source_changed == 'true' 115 | uses: actions/setup-node@v4 116 | with: 117 | node-version: "20.x" 118 | cache: "yarn" 119 | 120 | - name: Install dependencies 121 | if: github.event_name == 'workflow_dispatch' || steps.check_source_changes.outputs.source_changed == 'true' 122 | run: yarn install 123 | 124 | - name: Build the SDK 125 | if: github.event_name == 'workflow_dispatch' || steps.check_source_changes.outputs.source_changed == 'true' 126 | run: yarn build 127 | 128 | - name: Test Core Functionality 129 | if: github.event_name == 'workflow_dispatch' || steps.check_source_changes.outputs.source_changed == 'true' 130 | env: 131 | TEST_ENV: ${{ matrix.env }} 132 | run: yarn test:core 133 | 134 | - name: Test Workflows 135 | if: github.event_name == 'workflow_dispatch' || steps.check_source_changes.outputs.source_changed == 'true' 136 | env: 137 | TEST_ENV: ${{ matrix.env }} 138 | run: yarn test:workflows 139 | 140 | - name: Test Executions 141 | if: github.event_name == 'workflow_dispatch' || steps.check_source_changes.outputs.source_changed == 'true' 142 | env: 143 | TEST_ENV: ${{ matrix.env }} 144 | run: yarn test:executions 145 | 146 | - name: Test Triggers 147 | if: github.event_name == 'workflow_dispatch' || steps.check_source_changes.outputs.source_changed == 'true' 148 | env: 149 | TEST_ENV: ${{ matrix.env }} 150 | run: yarn test:triggers 151 | 152 | - name: Test Nodes 153 | if: github.event_name == 'workflow_dispatch' || steps.check_source_changes.outputs.source_changed == 'true' 154 | env: 155 | TEST_ENV: ${{ matrix.env }} 156 | run: yarn test:nodes 157 | 158 | - name: Test Integrations 159 | if: github.event_name == 'workflow_dispatch' || steps.check_source_changes.outputs.source_changed == 'true' 160 | env: 161 | TEST_ENV: ${{ matrix.env }} 162 | run: yarn test:integrations -------------------------------------------------------------------------------- /.github/workflows/record-changeset.yml: -------------------------------------------------------------------------------- 1 | name: Record Changeset 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version_type: 7 | description: "Change type" 8 | required: true 9 | type: choice 10 | options: 11 | - patch 12 | - minor 13 | - major 14 | default: "patch" 15 | 16 | jobs: 17 | changeset: 18 | name: Create Changeset 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 25 | fetch-tags: true 26 | 27 | - name: Set BASE_BRANCH 28 | run: | 29 | if [[ "${GITHUB_REF#refs/heads/}" == "staging"* ]]; then 30 | echo "BASE_BRANCH=staging" >> $GITHUB_ENV 31 | else 32 | echo "BASE_BRANCH=main" >> $GITHUB_ENV 33 | fi 34 | 35 | - name: Sync Base Branch 36 | run: | 37 | git fetch origin ${{ env.BASE_BRANCH }} 38 | git merge origin/${{ env.BASE_BRANCH }} --no-edit 39 | 40 | - name: Setup Node.js 41 | uses: actions/setup-node@v4 42 | with: 43 | node-version: "20" 44 | 45 | - name: Install Dependencies 46 | run: yarn install --frozen-lockfile 47 | 48 | - name: Run Changeset Command 49 | id: changeset 50 | env: 51 | BASE_BRANCH: ${{ env.BASE_BRANCH }} 52 | run: | 53 | echo "=== Starting Changeset Creation ===" 54 | 55 | # Get and log commit message 56 | COMMIT_MESSAGE=$(git log -1 --pretty=%B) 57 | echo "Commit Message: ${COMMIT_MESSAGE}" 58 | echo "Base Branch: ${BASE_BRANCH}" 59 | 60 | # Sanitize the commit message to remove problematic characters 61 | SANITIZED_COMMIT_MESSAGE=$(echo "${COMMIT_MESSAGE}" | tr -d '\r' | tr '\n' ' ') 62 | echo "Sanitized Commit Message: ${SANITIZED_COMMIT_MESSAGE}" 63 | 64 | echo "=== Creating Empty Changeset ===" 65 | # Create the changeset file first 66 | CHANGESET_OUTPUT=$(npx changeset add --empty) 67 | echo "Changeset Command Output: ${CHANGESET_OUTPUT}" 68 | 69 | echo "=== Extracting Filename ===" 70 | # Get the generated changeset filename 71 | CHANGESET_FILE=$(echo "$CHANGESET_OUTPUT" | grep -o '[a-z0-9\-]\+\.md') 72 | echo "Generated Changeset Filename: ${CHANGESET_FILE}" 73 | 74 | echo "=== Checking .changeset Directory ===" 75 | ls -la .changeset || echo "Directory not found!" 76 | 77 | echo "=== Writing Changeset Content ===" 78 | # Modify the changeset file to include the package 79 | cat > ".changeset/${CHANGESET_FILE}" << EOF 80 | --- 81 | "@avaprotocol/sdk-js": ${{ github.event.inputs.version_type }} 82 | --- 83 | 84 | ${SANITIZED_COMMIT_MESSAGE} 85 | EOF 86 | 87 | echo "=== Verifying Changeset File Content ===" 88 | if [ -f ".changeset/${CHANGESET_FILE}" ]; then 89 | echo "Changeset file content:" 90 | cat ".changeset/${CHANGESET_FILE}" 91 | else 92 | echo "Error: Changeset file was not created!" 93 | fi 94 | 95 | echo "=== Setting Output Variable ===" 96 | echo "changeset_file=${CHANGESET_FILE}" >> $GITHUB_OUTPUT 97 | 98 | # Add this line to set the sanitized commit message as an output 99 | echo "commit_message=${SANITIZED_COMMIT_MESSAGE}" >> $GITHUB_OUTPUT 100 | 101 | echo "=== Final Directory Check ===" 102 | # Verify changeset creation 103 | if [ ! -d ".changeset" ]; then 104 | echo "Error: .changeset directory not created" 105 | exit 1 106 | fi 107 | 108 | echo "=== Staging and Committing Changeset ===" 109 | git config --local user.email "action@github.com" 110 | git config --local user.name "GitHub Action" 111 | 112 | echo "Staging changeset file..." 113 | git add ".changeset/${CHANGESET_FILE}" 114 | git status 115 | 116 | echo "Committing changeset..." 117 | git commit -m "Add changeset for: ${COMMIT_MESSAGE} [skip ci]" 118 | 119 | echo "Verifying commit..." 120 | git log -1 --stat 121 | 122 | echo "=== Changeset Creation Complete ===" 123 | 124 | - name: Commit and Push Changes 125 | id: commit_and_push 126 | run: | 127 | git config --local user.email "action@github.com" 128 | git config --local user.name "GitHub Action" 129 | 130 | # Create and checkout new branch using changeset filename 131 | BRANCH_NAME="changeset-$(basename ${{ steps.changeset.outputs.changeset_file }} .md)" 132 | git checkout -b "$BRANCH_NAME" 133 | git push origin "$BRANCH_NAME" 134 | 135 | # Set outputs for later use 136 | echo "commit_message=${{ steps.changeset.outputs.commit_message }}" >> $GITHUB_ENV 137 | echo "branch_name=${BRANCH_NAME}" >> $GITHUB_ENV 138 | 139 | - name: Create Pull Request 140 | env: 141 | GH_TOKEN: ${{ github.token }} 142 | COMMIT_MESSAGE: ${{ steps.changeset.outputs.commit_message }} 143 | BRANCH_NAME: ${{ env.branch_name }} 144 | BASE_BRANCH: ${{ env.BASE_BRANCH }} 145 | run: | 146 | gh pr create \ 147 | --title "Add changeset for ${COMMIT_MESSAGE}" \ 148 | --body "This pull request adds a changeset for ${COMMIT_MESSAGE}. Please review." \ 149 | --base "$BASE_BRANCH" \ 150 | --head "$BRANCH_NAME" 151 | 152 | permissions: 153 | contents: write 154 | pull-requests: write 155 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directories 2 | node_modules/ 3 | jspm_packages/ 4 | package-lock.json 5 | 6 | # Build outputs 7 | build/ 8 | dist/ 9 | out/ 10 | *.tsbuildinfo 11 | 12 | # Logs 13 | logs 14 | *.log 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | # Runtime data 20 | pids 21 | *.pid 22 | *.seed 23 | *.pid.lock 24 | 25 | # Directory for instrumented libs generated by jscoverage/JSCover 26 | lib-cov 27 | 28 | # Coverage directory used by tools like istanbul 29 | coverage/ 30 | 31 | # nyc test coverage 32 | .nyc_output 33 | 34 | # IDEs and editors 35 | .idea/ 36 | .vscode/ 37 | *.swp 38 | *.swo 39 | .DS_Store 40 | 41 | # Optional npm cache directory 42 | .npm 43 | 44 | # Optional eslint cache 45 | .eslintcache 46 | 47 | # Optional REPL history 48 | .node_repl_history 49 | 50 | # Output of 'npm pack' 51 | *.tgz 52 | 53 | # Yarn Integrity file 54 | .yarn-integrity 55 | 56 | # dotenv environment variables file 57 | .env 58 | .env.* 59 | 60 | # parcel-bundler cache (https://parceljs.org/) 61 | .cache 62 | 63 | # Next.js build output 64 | .next 65 | 66 | # Nuxt.js build / generate output 67 | .nuxt 68 | 69 | # Gatsby files 70 | .cache/ 71 | public 72 | 73 | # vuepress build output 74 | .vuepress/dist 75 | 76 | # Serverless directories 77 | .serverless/ 78 | 79 | # FuseBox cache 80 | .fusebox/ 81 | 82 | # DynamoDB Local files 83 | .dynamodb/ 84 | 85 | # TypeScript cache 86 | *.tsbuildinfo 87 | 88 | # Temporary folders 89 | tmp/ 90 | temp/ 91 | 92 | # visual studio code workspace files 93 | *.code-workspace 94 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @avaprotocol/sdk-js 2 | 3 | ## 0.8.0 4 | 5 | ### Minor Changes 6 | 7 | - 6414916: Release after Vinh’s restructure 8 | 9 | ## 0.7.3 10 | 11 | ### Patch Changes 12 | 13 | - a5305c0: Revert changeset auto-commit to false 14 | - f9f8874: Create a new branch for version bump 2 15 | 16 | ## 0.7.2 17 | 18 | ### Patch Changes 19 | 20 | - d4dcc7c: authKey is no longer stored in client and added getAddresses test cases 21 | 22 | ## 0.7.1 23 | 24 | ### Patch Changes 25 | 26 | - 10801d8: Updated README.md with release instructions 27 | - ec7427c: Make sure COMMIT_MESSAGE is written into changeset 28 | - 5f9dfc3: hi there 29 | 30 | ## 0.7.0 31 | 32 | ### Minor Changes 33 | 34 | - ab2c788: hi 35 | 36 | ### Patch Changes 37 | 38 | - some summary 39 | 40 | ## 0.6.13 41 | 42 | ### Patch Changes 43 | 44 | - e2905e8: Added changeset 45 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ["@babel/preset-env", { targets: { node: "current" }, modules: "auto" }], 4 | "@babel/preset-typescript", 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /config/aggregator.yaml: -------------------------------------------------------------------------------- 1 | # Dummy unsecure test key. do not deposit real fund to here 2 | # Wallet: 0x578B110b0a7c06e66b7B1a33C39635304aaF733c 3 | ecdsa_private_key: e0502ddd5a0d05ec7b5c22614a01c8ce783810edaa98e44cc82f5fa5a819aaa9 4 | 5 | eth_rpc_url: https://holesky.gateway.tenderly.co/6PJfDJkrMGBl3f4MuyU5M 6 | eth_ws_url: wss://holesky.gateway.tenderly.co/6PJfDJkrMGBl3f4MuyU5M 7 | 8 | rpc_bind_address: "0.0.0.0:2206" 9 | http_bind_address: "0.0.0.0:8080" 10 | 11 | avs_registry_coordinator_address: 0x90c6d6f2A78d5Ce22AB8631Ddb142C03AC87De7a 12 | operator_state_retriever_address: 0xb7bb920538e038DFFEfcB55caBf713652ED2031F 13 | 14 | etherscan_url: https://sepolia.etherscan.io 15 | eigenlayer_url: https://holesky.eigenlayer.xyz 16 | 17 | environment: development 18 | db_path: /tmp/ap-avs/db 19 | 20 | # Random key for test, please do not use or copy/paste this to any prod env 21 | jwt_secret: "1b7db1c64236d92de3b3ed32e5d6bf56" 22 | 23 | # account abstraction config 24 | # Sepolia 25 | smart_wallet: 26 | eth_rpc_url: https://ethereum-sepolia.core.chainstack.com/2504cb0765f0edf6c33d99095148006f 27 | eth_ws_url: wss://ethereum-sepolia.core.chainstack.com/2504cb0765f0edf6c33d99095148006f 28 | bundler_url: https://bundler-sepolia.avaprotocol.org 29 | factory_address: 0x29adA1b5217242DEaBB142BC3b1bCfFdd56008e7 30 | entrypoint_address: 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 31 | 32 | # dummy key, cannot actually run transaction 33 | controller_private_key: e0502ddd5a0d05ec7b5c22614a01c8ce783810edaa98e44cc82f5fa5a819aaa9 34 | 35 | macros: 36 | secrets: 37 | notify_bot_token: "" 38 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | name: ava-sdk-test 2 | services: 3 | aggregator: 4 | image: avaprotocol/avs-dev:${DOCKER_IMAGE_TAG:-latest} # DOCKER_IMAGE_TAG is a specific git commit hash of https://github.com/AvaProtocol/EigenLayer-AVS. It’s used in the .github/workflows/dev-test-on-pr.yml file. 5 | command: 6 | - "aggregator" 7 | ports: 8 | - "2206:2206" 9 | - "1323:1323" 10 | volumes: 11 | - ./config:/app/config 12 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from "globals"; 2 | import pluginJs from "@eslint/js"; 3 | import tseslint from "typescript-eslint"; 4 | 5 | /** @type {import('eslint').Linter.Config[]} */ 6 | export default [ 7 | { files: ["**/*.{js,mjs,cjs,ts}"] }, 8 | { 9 | ignores: [ 10 | "**/dist/**", // Build output directories 11 | "**/grpc_codegen/**", // Generated gRPC code 12 | "**/*.d.ts", // TypeScript declaration files 13 | "**/*_pb.js", // Protocol buffer generated files 14 | "**/*_grpc_pb.js" // gRPC generated files 15 | ] 16 | }, 17 | { languageOptions: { globals: globals.node } }, 18 | pluginJs.configs.recommended, 19 | ...tseslint.configs.recommended, 20 | { 21 | rules: { 22 | "max-len": ["error", { code: 256, ignoreUrls: true }], 23 | }, 24 | }, 25 | ]; 26 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples Script 2 | 3 | This is a CLI script that can be used to interact with the SDK. It is a simple script designed to create a workflow, schedule a workflow, get the status of a workflow, list executions, manage secrets, and generate task data for a contract call method. 4 | 5 | It is not intended to be a flexible, full-featured CLI, but rather a simple script to demonstrate how to use the SDK. Feel free to customize, add your own features, or edit it to suit your needs. Pull requests are welcome to add more actions. 6 | 7 | ## Environment Setup 8 | 9 | Before running the script, you need to set up the following environment variables: 10 | 11 | - `PRIVATE_KEY`: Your private key for accessing the blockchain. This is required for signing transactions. 12 | - `ENV`: The environment in which the script will run. Options include `development`, `sepolia`, `base-sepolia`, and `ethereum`. Set this to match the desired network. 13 | 14 | You can set these variables in a `.env` file or directly in your terminal: 15 | 16 | ```bash 17 | export PRIVATE_KEY= 18 | export ENV=development 19 | ``` 20 | 21 | ## Usage 22 | 23 | 1. **Build the Packages**: The example script imports libraries defined in this repo, so build the packages first by running `yarn build` at the root folder. `node_modules` should only be in the root folder, as all node_modules are managed and shared by this yarn workspace. 24 | 25 | 2. **Run the Script**: Use `yarn start` to see all options of this command-line tool. For example, run `yarn run wallet` to list all registered smart wallets, and `yarn run create-wallet ` to create and add more smart wallets to the list. 26 | 27 | ## Example Commands 28 | 29 | ### Smart Wallets Related 30 | 31 | - Create a new smart wallet: 32 | 33 | ```bash 34 | yarn start create-wallet 1 35 | ``` 36 | 37 | - List all registered smart wallets: 38 | ```bash 39 | yarn start wallet 40 | ``` 41 | 42 | ### Workflow Related 43 | 44 | #### General Workflow Commands 45 | 46 | 1. **Setup a Workflow to Monitor a Wallet**: 47 | 48 | ```bash 49 | yarn start schedule-monitor 0xC114FB059434563DC65AC8D57e7976e3eaC534F4 50 | ``` 51 | 52 | After scheduling, set the workflow ID to an environment variable for use in other commands: 53 | 54 | ```bash 55 | export WORKFLOW_ID=01JKVASBP0E7E5XGJ949QXX0X1 56 | ``` 57 | 58 | 2. **Get Details of the Scheduled Workflow**: 59 | 60 | ```bash 61 | yarn start get $WORKFLOW_ID 62 | ``` 63 | 64 | 3. **Get All Workflows of a Smart Wallet**: 65 | Replace with your own smart wallet address: 66 | 67 | ```bash 68 | yarn start tasks 0x5Df343de7d99fd64b2479189692C1dAb8f46184a 69 | ``` 70 | 71 | 4. **Pagination Support**: 72 | Update the cursor as needed: 73 | 74 | ```bash 75 | yarn start tasks 0x5Df343de7d99fd64b2479189692C1dAb8f46184a 2 'eyJkIjoibmV4dCIsInAiOiIwMUpLTVdXQk5QOURKRVBKMTFYTVNEMkg3VyJ9' 76 | ``` 77 | 78 | 5. **Trigger the Workflow Manually**: 79 | 80 | ```bash 81 | yarn start trigger $WORKFLOW_ID '{"tx_hash": "0x74edc53c374eb0947909fd387ecf9b166b8add40528ed4f2534ece903bc70cdd", "log_index": 143, "block_number": 72121741}' 82 | ``` 83 | 84 | 6. **List All Executions of the Workflow**: 85 | ```bash 86 | yarn start executions $WORKFLOW_ID 87 | ``` 88 | 89 | #### Schedule a Cron Task 90 | 91 | Schedule a cron task that posts the result to [webhook.site](https://webhook.site) every 5 minutes. 92 | 93 | **Note**: Please check and update your smart wallet accordingly. This task requires that you pass a smart wallet address. 94 | 95 | ```bash 96 | n dist/example.js schedule-cron '0x5Afb1B1bc212C6417C575A78bf9921Cc05F6d3E2' 97 | ``` 98 | 99 | #### Schedule an Auto Sweep Task 100 | 101 | Schedule an auto sweep task where funds transferred to the smart wallet will automatically move out to the specified wallet address. 102 | 103 | ```bash 104 | n dist/example.js schedule-sweep 0x036cbd53842c5426634e7929541ec2318f3dcf7e 105 | ``` 106 | 107 | Feel free to reach out with any questions or suggestions for improvement! 108 | -------------------------------------------------------------------------------- /examples/config.ts: -------------------------------------------------------------------------------- 1 | import commandLineParser from "command-line-args"; 2 | 3 | const optionDefinitions = [ 4 | { name: "command", type: String, defaultOption: true }, 5 | { 6 | name: "avs-target", 7 | alias: "t", 8 | type: String, 9 | defaultValue: "development", 10 | }, 11 | { name: "args", type: String, multiple: true, defaultValue: [] }, // Captures extra arguments like `0` 12 | ]; 13 | 14 | const parsed = commandLineParser(optionDefinitions, { 15 | stopAtFirstUnknown: true, 16 | }); 17 | 18 | const argv = process.argv; 19 | const unknownArgsStart = argv.findIndex((arg) => arg === parsed.command) + 1; 20 | const unknownArgs = argv 21 | .slice(unknownArgsStart) 22 | .filter((arg) => !arg.startsWith("-")); 23 | 24 | export const commandArgs = { 25 | ...parsed, 26 | args: unknownArgs, 27 | }; 28 | 29 | console.log("commandArgs", commandArgs); 30 | 31 | export const env = commandArgs["avs-target"] || "development"; 32 | 33 | export const config = { 34 | // The development environment is the local environment run on your machine. It can be bring up following the instructions in this file https://github.com/AvaProtocol/EigenLayer-AVS/blob/main/docs/development.md 35 | development: { 36 | AP_AVS_RPC: "localhost:2206", 37 | TEST_TRANSFER_TOKEN: "0x2e8bdb63d09ef989a0018eeb1c47ef84e3e61f7b", 38 | TEST_TRANSFER_TO: "0xe0f7D11FD714674722d325Cd86062A5F1882E13a", 39 | ORACLE_PRICE_CONTRACT: "0x4aDC67696bA383F43DD60A9e78F2C97Fbbfc7cb1", 40 | RPC_PROVIDER: "https://sepolia.gateway.tenderly.co", 41 | FACTORY_ADDRESS: "0xB99BC2E399e06CddCF5E725c0ea341E8f0322834", 42 | }, 43 | 44 | sepolia: { 45 | AP_AVS_RPC: "aggregator-sepolia.avaprotocol.org:2206", 46 | TEST_TRANSFER_TOKEN: "0x2e8bdb63d09ef989a0018eeb1c47ef84e3e61f7b", 47 | TEST_TRANSFER_TO: "0xe0f7D11FD714674722d325Cd86062A5F1882E13a", 48 | ORACLE_PRICE_CONTRACT: "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419", 49 | RPC_PROVIDER: "https://sepolia.gateway.tenderly.co", 50 | FACTORY_ADDRESS: "0xB99BC2E399e06CddCF5E725c0ea341E8f0322834", 51 | }, 52 | 53 | "base-sepolia": { 54 | AP_AVS_RPC: "aggregator-base-sepolia.avaprotocol.org:3206", 55 | TEST_TRANSFER_TOKEN: "0x72d587b34f7d21fbc47d55fa3d2c2609d4f25698", 56 | TEST_TRANSFER_TO: "0xa5ABB97A2540E4A4756E33f93fB2D7987668396a", 57 | ORACLE_PRICE_CONTRACT: "0x4aDC67696bA383F43DD60A9e78F2C97Fbbfc7cb1", 58 | RPC_PROVIDER: "https://mainnet.gateway.tenderly.co", 59 | FACTORY_ADDRESS: "0xB99BC2E399e06CddCF5E725c0ea341E8f0322834", 60 | }, 61 | 62 | base: { 63 | AP_AVS_RPC: "aggregator-base.avaprotocol.org:3206", 64 | TEST_TRANSFER_TOKEN: "0x72d587b34f7d21fbc47d55fa3d2c2609d4f25698", 65 | TEST_TRANSFER_TO: "0xa5ABB97A2540E4A4756E33f93fB2D7987668396a", 66 | ORACLE_PRICE_CONTRACT: "0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70", 67 | RPC_PROVIDER: "https://mainnet.gateway.tenderly.co", 68 | FACTORY_ADDRESS: "0xB99BC2E399e06CddCF5E725c0ea341E8f0322834", 69 | }, 70 | 71 | ethereum: { 72 | AP_AVS_RPC: "aggregator.avaprotocol.org:2206", 73 | TEST_TRANSFER_TOKEN: "0x72d587b34f7d21fbc47d55fa3d2c2609d4f25698", 74 | TEST_TRANSFER_TO: "0xa5ABB97A2540E4A4756E33f93fB2D7987668396a", 75 | ORACLE_PRICE_CONTRACT: "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419", 76 | RPC_PROVIDER: "https://mainnet.gateway.tenderly.co", 77 | FACTORY_ADDRESS: "0xB99BC2E399e06CddCF5E725c0ea341E8f0322834", 78 | }, 79 | 80 | // TODO: Minato no longer works so we comment out in this, will add it back it eventually 81 | // minato: { 82 | // AP_AVS_RPC: "aggregator-minato.avaprotocol.org:2306", 83 | // // https://explorer-testnet.soneium.org/token/0xBA33747043d09868946978Dd935130490a083458?tab=contract 84 | // // anyone can mint this token for testing transfer it 85 | // TEST_TRANSFER_TOKEN: "0xBA33747043d09868946978Dd935130490a083458", 86 | // // Can be any arbitrary address to demonstrate that this address will receive the token above 87 | // TEST_TRANSFER_TO: "0xa5ABB97A2540E4A4756E33f93fB2D7987668396a", 88 | // ORACLE_PRICE_CONTRACT: "0x0ee7f0f7796Bd98c0E68107c42b21F5B7C13bcA9", 89 | // RPC_PROVIDER: "https://rpc.minato.soneium.org", 90 | // }, 91 | }; 92 | 93 | if (!config[env as keyof typeof config]) { 94 | throw new Error(`Environment ${env} not found`); 95 | } 96 | 97 | export function getConfig() { 98 | return config[env as keyof typeof config]; 99 | } 100 | 101 | export const currentEnv = env; 102 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ava-sdk-examples", 3 | "version": "1.0.0", 4 | "engines": { 5 | "node": ">=20.18.0", 6 | "yarn": ">=1.22.19" 7 | }, 8 | "private": true, 9 | "workspaces": [ 10 | "packages/*" 11 | ], 12 | "license": "Apache-2.0", 13 | "scripts": { 14 | "start": "ts-node example.ts", 15 | "start:sepolia": "ts-node example.ts -t sepolia", 16 | "start:base": "ts-node example.ts -t base", 17 | "start:base-sepolia": "ts-node example.ts -t base-sepolia", 18 | "start:ethereum": "ts-node example.ts -t ethereum" 19 | }, 20 | "dependencies": { 21 | "@avaprotocol/sdk-js": "file:../packages/sdk-js", 22 | "@avaprotocol/types": "file:../packages/types", 23 | "ethers": "^6.13.3", 24 | "id128": "^1.6.6", 25 | "lodash": "^4.17.21" 26 | }, 27 | "devDependencies": { 28 | "@types/node": "^22.7.7", 29 | "command-line-args": "^6.0.1", 30 | "ts-node": "^10.9.2", 31 | "typescript": "^5.4.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "./dist", 6 | "baseUrl": ".", 7 | "esModuleInterop": true, 8 | "allowImportingTsExtensions": true, 9 | "noEmit": true 10 | }, 11 | "include": ["example.ts", "config.ts"], 12 | } 13 | -------------------------------------------------------------------------------- /jest.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ["/tests"], // Points to the `tests` folder 3 | transform: { 4 | "^.+\\.(ts|tsx|js|jsx)$": "babel-jest", 5 | }, 6 | testEnvironment: "node", 7 | extensionsToTreatAsEsm: [".ts"], 8 | testMatch: ["**/?(*.)+(spec|test).[tj]s?(x)"], // Matches test files 9 | moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], 10 | moduleNameMapper: { 11 | // Map workspace packages to their TypeScript source files for testing 12 | // This ensures tests run against the latest source code instead of built/published versions 13 | "^@avaprotocol/sdk-js$": "/packages/sdk-js/src/index.ts", 14 | "^@avaprotocol/types$": "/packages/types/src/index.ts", 15 | // Legacy mappings for internal imports 16 | "^@/sdk-js/(.*)$": "/packages/sdk-js/$1", 17 | "^@/grpc_codegen/(.*)$": "/grpc_codegen/$1", 18 | "^@/types/(.*)$": "/packages/types/$1", 19 | }, 20 | setupFilesAfterEnv: ["/tests/utils/mocks/api.ts"], 21 | }; 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@avaprotocol/root", 3 | "version": "1.2.2", 4 | "engines": { 5 | "node": ">=20.18.0", 6 | "yarn": ">=1.22.19" 7 | }, 8 | "private": true, 9 | "type": "commonjs", 10 | "license": "Apache-2.0", 11 | "workspaces": [ 12 | "packages/*" 13 | ], 14 | "scripts": { 15 | "changeset": "changeset", 16 | "version": "changeset version", 17 | "proto-download": "curl -s https://raw.githubusercontent.com/AvaProtocol/EigenLayer-AVS/staging/protobuf/avs.proto > grpc_codegen/avs.proto", 18 | "protoc-gen": "grpc_tools_node_protoc --js_out=import_style=commonjs,binary:./grpc_codegen/ --grpc_out=grpc_js:./grpc_codegen/ --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --ts_out=grpc_js:./grpc_codegen --proto_path=./grpc_codegen grpc_codegen/avs.proto", 19 | "apikey-gen": "sh -c 'API_KEY=$(docker compose exec aggregator /ava create-api-key --role=admin --subject=apikey) && echo \"Generated API key for tests: $API_KEY\" && echo \"Writing to .env.test to replace the TEST_API_KEY env variable\" && sed -i \"\" -e \"s/^TEST_API_KEY=.*/TEST_API_KEY=$API_KEY/\" .env.test && echo \"Done.\"'", 20 | "build": "yarn workspaces run build", 21 | "clean": "rm -rf node_modules yarn.lock && yarn workspaces run clean", 22 | "test": "jest --config jest.config.cjs --detectOpenHandles", 23 | "test:core": "dotenv -e .env.$TEST_ENV jest tests/core/ --verbose --runInBand", 24 | "test:workflows": "dotenv -e .env.$TEST_ENV jest tests/workflows/ --verbose --runInBand", 25 | "test:executions": "dotenv -e .env.$TEST_ENV jest tests/executions/ --verbose --runInBand", 26 | "test:triggers": "dotenv -e .env.$TEST_ENV jest tests/triggers/ --verbose --runInBand", 27 | "test:nodes": "dotenv -e .env.$TEST_ENV jest tests/nodes/ --verbose --runInBand", 28 | "test:integrations": "dotenv -e .env.$TEST_ENV jest tests/integrations/ --verbose --runInBand", 29 | "test:docker": "bash ./scripts/run-tests-with-docker.sh", 30 | "build:example": "tsup examples/example.ts --format cjs,esm --watch examples/", 31 | "release": "node scripts/release.js", 32 | "publish": "node ./scripts/publish-packages.js", 33 | "publish:dry-run": "node ./scripts/publish-packages.js --dry-run", 34 | "publish:bash": "bash ./scripts/publish-packages.sh", 35 | "publish:bash:dry-run": "bash ./scripts/publish-packages.sh --dry-run", 36 | "publish:help": "bash ./scripts/publish-packages.sh --help", 37 | "lint": "eslint packages/*/src --ext .ts" 38 | }, 39 | "devDependencies": { 40 | "@avaprotocol/sdk-js": "^2.3.13-dev.1", 41 | "@avaprotocol/types": "^2.2.10", 42 | "@babel/core": "^7.26.0", 43 | "@babel/preset-env": "^7.26.0", 44 | "@babel/preset-typescript": "^7.26.0", 45 | "@changesets/cli": "^2.27.9", 46 | "@eslint/js": "^9.16.0", 47 | "@jest/globals": "^29.7.0", 48 | "@types/axios": "^0.14.4", 49 | "@types/google-protobuf": "^3.15.12", 50 | "@types/jest": "^29.5.13", 51 | "@types/lodash": "^4.17.12", 52 | "@typescript-eslint/eslint-plugin": "^8.17.0", 53 | "@typescript-eslint/parser": "^8.17.0", 54 | "axios": "^1.8.4", 55 | "babel-jest": "^29.7.0", 56 | "cross-env": "^7.0.3", 57 | "dotenv-cli": "^7.3.0", 58 | "envalid": "^8.0.0", 59 | "eslint": "^9.16.0", 60 | "globals": "^15.13.0", 61 | "grpc-tools": "^1.13.0", 62 | "grpc_tools_node_protoc_ts": "^5.3.3", 63 | "jest": "^29.7.0", 64 | "ts-jest": "^29.2.5", 65 | "tsup": "^8.0.2", 66 | "typescript": "^5.4.2", 67 | "typescript-eslint": "^8.35.0" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/sdk-js/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @avaprotocol/sdk-js 2 | 3 | ## 2.4.3 4 | 5 | ### Patch Changes 6 | 7 | - 52b44d6: Update Secret and Cancel, Delete Workflow responses 8 | - Updated dependencies [52b44d6] 9 | - @avaprotocol/types@2.2.16 10 | 11 | ## 2.4.2 12 | 13 | ### Patch Changes 14 | 15 | - 6650be6: Update Cancel and Delete operation response 16 | - Updated dependencies [6650be6] 17 | - @avaprotocol/types@2.2.15 18 | 19 | ## 2.4.1 20 | 21 | ### Patch Changes 22 | 23 | - 264c1c8: Renovated ContractRead and ContractWrite 24 | - Updated dependencies [264c1c8] 25 | - @avaprotocol/types@2.2.14 26 | 27 | ## 2.4.0 28 | 29 | ### Minor Changes 30 | 31 | - 60181d0: Added EventCondition, and unified contractAbi to string type 32 | 33 | ## 2.3.17 34 | 35 | ### Patch Changes 36 | 37 | - 0dfcf69: Added EventCondition, and unified contractAbi to string type 38 | - Updated dependencies [0dfcf69] 39 | - @avaprotocol/types@2.2.13 40 | 41 | ## 2.3.16 42 | 43 | ### Patch Changes 44 | 45 | - 27551e0: Added EventCondition to EventTrigger 46 | - Updated dependencies [27551e0] 47 | - @avaprotocol/types@2.2.12 48 | 49 | ## 2.3.15 50 | 51 | ### Patch Changes 52 | 53 | - 567e1ab: Fixed nodes data not serialized for deploy problem 54 | 55 | ## 2.3.14 56 | 57 | ### Patch Changes 58 | 59 | - ba5ec8f: Fix input is not correctly entered into eventTrigger issue 60 | 61 | ## 2.3.13 62 | 63 | ### Patch Changes 64 | 65 | - 272c26e: Fixed types package include protobuf library issue 66 | - Updated dependencies [272c26e] 67 | - @avaprotocol/types@2.2.11 68 | 69 | ## 2.3.12 70 | 71 | ### Patch Changes 72 | 73 | - 0a0c764: Use new release proces 74 | 75 | ## 2.3.11 76 | 77 | ### Patch Changes 78 | 79 | - 8ebfb1e: Update the RunNodeWithInputsResponse type dependency 80 | 81 | ## 2.3.10 82 | 83 | ### Patch Changes 84 | 85 | - 72339c5: Updated and tested loop functions; test empty data returned 86 | - Updated dependencies [72339c5] 87 | - @avaprotocol/types@2.2.9 88 | 89 | ## 2.3.9 90 | 91 | ### Patch Changes 92 | 93 | - 42b23c8: Fixed contract read Step Output;added data validity check to block and cron 94 | 95 | ## 2.3.8 96 | 97 | ### Patch Changes 98 | 99 | - 36ac928: Replace the old value getScheduleList to getSchedulesList 100 | - Updated dependencies [36ac928] 101 | - @avaprotocol/types@2.2.8 102 | 103 | ## 2.3.7 104 | 105 | ### Patch Changes 106 | 107 | - 31667f8: Fix CustomCodeLang to match enum value 108 | - Updated dependencies [31667f8] 109 | - @avaprotocol/types@2.2.7 110 | 111 | ## 2.3.6 112 | 113 | ### Patch Changes 114 | 115 | - Updated dependencies [75c7865] 116 | - @avaprotocol/types@2.2.6 117 | 118 | ## 2.3.5 119 | 120 | ### Patch Changes 121 | 122 | - b5b1eff: Convert lang of CustomCode to use protobuf value 123 | - Updated dependencies [b5b1eff] 124 | - @avaprotocol/types@2.2.5 125 | 126 | ## 2.3.4 127 | 128 | ### Patch Changes 129 | 130 | - 9c7c1e0: Updated CronTrigger’s scheduleList to schedules 131 | - Updated dependencies [9c7c1e0] 132 | - @avaprotocol/types@2.2.4 133 | 134 | ## 2.3.3 135 | 136 | ### Patch Changes 137 | 138 | - Updated dependencies [38ade5e] 139 | - @avaprotocol/types@2.2.3 140 | 141 | ## 2.3.2 142 | 143 | ### Patch Changes 144 | 145 | - Updated dependencies [2baed44] 146 | - @avaprotocol/types@2.2.2 147 | 148 | ## 2.3.1 149 | 150 | ### Patch Changes 151 | 152 | - Updated dependencies [07c05b4] 153 | - @avaprotocol/types@2.2.1 154 | 155 | ## 2.3.0 156 | 157 | ### Minor Changes 158 | 159 | - c45a2fb: Complete field name standardization and remove backward compatibility 160 | 161 | ### Patch Changes 162 | 163 | - Updated dependencies [c45a2fb] 164 | - @avaprotocol/types@2.2.0 165 | 166 | ## 2.2.0 167 | 168 | ### Minor Changes 169 | 170 | - 52a1d86: Added timeout option and strategy to the client 171 | 172 | ### Patch Changes 173 | 174 | - Updated dependencies [52a1d86] 175 | - @avaprotocol/types@2.1.0 176 | 177 | ## 2.1.1 178 | 179 | ### Patch Changes 180 | 181 | - ef05955: Updated the request interface of ContractRead 182 | 183 | ## 2.1.0 184 | 185 | ### Minor Changes 186 | 187 | - 2fe0498: Update interface of event trigger to use queries 188 | 189 | ## 2.0.4 190 | 191 | ### Patch Changes 192 | 193 | - cd87ed8: Change API response from Execution to ExecutionProps 194 | 195 | ## 2.0.3 196 | 197 | ### Patch Changes 198 | 199 | - 3bbdc95: Updated runTrigger with EventTrigger output 200 | 201 | ## 2.0.2 202 | 203 | ### Patch Changes 204 | 205 | - 8e09a20: Fix the request type of SimulateWorkflow and RunTriggerReq 206 | - Updated dependencies [8e09a20] 207 | - @avaprotocol/types@2.0.2 208 | 209 | ## 2.0.1 210 | 211 | ### Patch Changes 212 | 213 | - 45212f0: Added GetTokenMetadata function to retrieve whitelist token information 214 | - Updated dependencies [45212f0] 215 | - @avaprotocol/types@2.0.1 216 | 217 | ## 2.0.0 218 | 219 | ### Major Changes 220 | 221 | - cfbf435: Migrated type definitions for props into types package 222 | 223 | ### Patch Changes 224 | 225 | - Updated dependencies [cfbf435] 226 | - @avaprotocol/types@2.0.0 227 | 228 | ## 1.7.1 229 | 230 | ### Patch Changes 231 | 232 | - 18a2273: Fix types package exported grpc libary; rename simulateTask to simulateWorkflow 233 | - Updated dependencies [18a2273] 234 | - @avaprotocol/types@1.1.1 235 | 236 | ## 1.7.0 237 | 238 | ### Minor Changes 239 | 240 | - a7578b6: Added SimulateWorkflow function and updated protobuf 241 | 242 | ### Patch Changes 243 | 244 | - Updated dependencies [a7578b6] 245 | - @avaprotocol/types@1.1.0 246 | 247 | ## 1.6.8 248 | 249 | ### Patch Changes 250 | 251 | - 0bd6c9f: Flatten out and removed TriggerReason in Execution 252 | - Updated dependencies [0bd6c9f] 253 | - @avaprotocol/types@1.0.8 254 | 255 | ## 1.6.7 256 | 257 | ### Patch Changes 258 | 259 | - ca68e9c: Updated output data to Value type for json and runTrigger logic 260 | - Updated dependencies [ca68e9c] 261 | - @avaprotocol/types@1.0.7 262 | 263 | ## 1.6.6 264 | 265 | ### Patch Changes 266 | 267 | - 491ae98: Added runTrigger method and refactored enums 268 | - Updated dependencies [491ae98] 269 | - @avaprotocol/types@1.0.6 270 | 271 | ## 1.6.5 272 | 273 | ### Patch Changes 274 | 275 | - bda8a74: Unified the node type conversion code and values by updating protobuf 276 | - Updated dependencies [bda8a74] 277 | - @avaprotocol/types@1.0.5 278 | 279 | ## 1.6.4 280 | 281 | ### Patch Changes 282 | 283 | - fa76ec5: Moved node DataType from sdk-js to types pacakge 284 | - Updated dependencies [fa76ec5] 285 | - @avaprotocol/types@1.0.4 286 | 287 | ## 1.6.3 288 | 289 | ### Patch Changes 290 | 291 | - 6e9069f: Make type definitions more consistent 292 | - Updated dependencies [6e9069f] 293 | - @avaprotocol/types@1.0.3 294 | 295 | ## 1.6.2 296 | 297 | ### Patch Changes 298 | 299 | - b2db42f: Update getSecrets to use general pageInfo 300 | - Updated dependencies [b2db42f] 301 | - @avaprotocol/types@1.0.2 302 | 303 | ## 1.6.1 304 | 305 | ### Patch Changes 306 | 307 | - f6ba804: Added eventTrigger tests 308 | 309 | ## 1.6.0 310 | 311 | ### Minor Changes 312 | 313 | - a38f8df: Consolidated pagination response and match protobuf of AVS v1.8.2 314 | 315 | ### Patch Changes 316 | 317 | - Updated dependencies [a38f8df] 318 | - @avaprotocol/types@1.0.1 319 | 320 | ## 1.5.0 321 | 322 | ### Minor Changes 323 | 324 | - cdecf79: Get sign message from server; added wallet isHidden 325 | 326 | ### Patch Changes 327 | 328 | - Updated dependencies [cdecf79] 329 | - @avaprotocol/types@1.0.0 330 | 331 | ## 1.4.0 332 | 333 | ### Minor Changes 334 | 335 | - 5a4bad7: Added getWorkflowCount and getExecutionCount methods 336 | - 4769b3c: Added Output and Step properties to Execution response 337 | 338 | ### Patch Changes 339 | 340 | - ba9b001: Sync to the latest grpc_codegen and remove step.outputdata that caused build error 341 | 342 | ## 1.3.8 343 | 344 | ### Patch Changes 345 | 346 | - Made Client a regular export instead of default 347 | 348 | ## 1.3.7 349 | 350 | ### Patch Changes 351 | 352 | - Added Secret and TriggerReason 353 | 354 | ## 1.3.6 355 | 356 | ### Patch Changes 357 | 358 | - Fix the build file problem in 1.3.5 359 | 360 | ## 1.3.5 361 | 362 | ### Patch Changes 363 | 364 | - b04cca3: Improved secret related code and tests 365 | 366 | ## 1.3.4 367 | 368 | ### Patch Changes 369 | 370 | - Migrated getKeyRequestMessage to types; Added secret functions 371 | - Updated dependencies 372 | - @avaprotocol/types@0.9.4 373 | 374 | ## 1.3.3 375 | 376 | ### Patch Changes 377 | 378 | - update the new auth method and trigger matcher array 379 | 380 | ## 1.3.2 381 | 382 | ### Patch Changes 383 | 384 | - Updated Task.memo to Task.name and fixed a build warning in types package 385 | - Updated dependencies 386 | - @avaprotocol/types@0.9.3 387 | 388 | ## 1.3.1 389 | 390 | ### Patch Changes 391 | 392 | - Update reference to types 393 | 394 | ## 1.3.0 395 | 396 | ### Minor Changes 397 | 398 | - Initialize types package 399 | -------------------------------------------------------------------------------- /packages/sdk-js/README.md: -------------------------------------------------------------------------------- 1 | # Ava SDK for JavaScript/TypeScript 2 | 3 | `@avaprotocol/sdk-js` is a simple, type-safe wrapper around gRPC designed to simplify integration with Ava Protocol’s AVS. It enables developers to interact with Ava Protocol efficiently, whether on the client-side or server-side, and provides full TypeScript support for a seamless development experience. 4 | 5 | ## Features 6 | 7 | - Type-Safe SDK: Automatically generated TypeScript types from gRPC protocol buffers ensure type safety and reduce errors during development. 8 | - Seamless Integration: Works in both Node.js and browser environments, optimized for frameworks like Next.js. 9 | - Easy to Use: Abstracts the complexity of gRPC with a simple JavaScript/TypeScript API. 10 | - Efficient Communication: Leverages gRPC for fast, efficient communication with Ava Protocol’s AVS (Actively Validated Services). 11 | 12 | ## Installation 13 | 14 | To install `@avaprotocol/sdk-js`, run the following command: 15 | 16 | ```bash 17 | npm install @avaprotocol/sdk-js 18 | ``` 19 | 20 | Or with Yarn: 21 | 22 | ```bash 23 | yarn add @avaprotocol/sdk-js 24 | ``` 25 | 26 | ## Getting Started 27 | 28 | Here’s a quick example of how to use the SDK to get started with Ava Protocol: 29 | 30 | ```typescript 31 | import { Client } from "@avaprotocol/sdk-js"; 32 | import { getServerSession } from "next-auth"; 33 | import { isAuthKeyValid } from "./utils"; 34 | import { authOptions } from "./auth/[...nextauth]/route"; 35 | 36 | let avaClient: Client | null = null; 37 | const EXPIRED_AT = Math.floor(Date.now() / 1000) + 24 * 60 * 60; // 24 hours from now 38 | 39 | async function initializeClient() { 40 | if (avaClient) return avaClient; 41 | 42 | if (!process.env.AVS_ENDPOINT) { 43 | throw new Error("AVS_ENDPOINT is not set in environment variables."); 44 | } 45 | 46 | avaClient = new Client({ 47 | endpoint: process.env.AVS_ENDPOINT, 48 | }); 49 | 50 | return avaClient; 51 | } 52 | 53 | /** 54 | * Get the client instance, lazy initialize it if it's not initialized yet 55 | * @returns 56 | */ 57 | export async function getClient() { 58 | if (avaClient) { 59 | return avaClient; 60 | } 61 | 62 | return await initializeClient(); 63 | } 64 | 65 | /** 66 | * Get the auth key using authWithAPIKey() for a wallet address before user signs in with a wallet signature 67 | * @param walletAddress 68 | * @returns 69 | */ 70 | async function getPresignAuthKey(walletAddress: string): Promise { 71 | const client = await getClient(); 72 | 73 | // Since we almost certainly need this env variable, we throw an error if it's not set 74 | if (!process.env.AVS_API_KEY) { 75 | throw new Error("AVS_API_KEY is not set in environment variables."); 76 | } 77 | 78 | const resp = await client.authWithAPIKey( 79 | walletAddress, 80 | process.env.AVS_API_KEY, 81 | EXPIRED_AT 82 | ); 83 | return resp.authKey; 84 | } 85 | 86 | /** 87 | * Get the auth key for a wallet address 88 | * First checks if the current session contains a valid auth key 89 | * If not, it will get the auth key using authWithAPIKey() for the wallet address 90 | * @param walletAddress 91 | * @returns 92 | */ 93 | export async function getAuthKey( 94 | walletAddress: string | undefined 95 | ): Promise { 96 | const session = await getServerSession(authOptions); 97 | 98 | if (session?.user?.authKey) { 99 | if (isAuthKeyValid(session?.user?.authKey)) { 100 | return session?.user?.authKey; 101 | } else { 102 | console.warn( 103 | `AuthKey is found for ${session?.user?.walletAddress} in session, but expired. Atempting to use authWithAPIKey` 104 | ); 105 | } 106 | } 107 | 108 | if (walletAddress) { 109 | return await getPresignAuthKey(walletAddress); 110 | } 111 | 112 | return undefined; 113 | } 114 | ``` 115 | 116 | ## Contributing 117 | 118 | We welcome contributions! Feel free to submit pull requests or open issues for any bugs or feature requests. 119 | 120 | ## License 121 | 122 | This project is licensed under the Apache 2.0 License. See the LICENSE file for more details. 123 | -------------------------------------------------------------------------------- /packages/sdk-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@avaprotocol/sdk-js", 3 | "version": "2.4.3", 4 | "description": "A JavaScript/TypeScript SDK designed to simplify integration with Ava Protocol's AVS", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "files": [ 8 | "dist", 9 | "README.md", 10 | "CHANGELOG.md", 11 | "package.json" 12 | ], 13 | "exports": { 14 | ".": { 15 | "types": "./dist/index.d.ts", 16 | "import": "./dist/index.js", 17 | "require": "./dist/index.js" 18 | } 19 | }, 20 | "author": "Vinh Nguyen (https://github.com/v9n), Chris Li (https://github.com/chrisli30)", 21 | "license": "Apache-2.0", 22 | "engines": { 23 | "node": ">=20.18.0", 24 | "yarn": ">=1.22.19" 25 | }, 26 | "scripts": { 27 | "build:declarations": "tsc -b tsconfig.json", 28 | "build:js": "tsup src/index.ts --format cjs,esm", 29 | "build": "yarn build:declarations && yarn build:js", 30 | "clean": "rm -rf node_modules dist tsconfig.tsbuildinfo", 31 | "prepare": "node ../../scripts/prepare-package.js" 32 | }, 33 | "dependencies": { 34 | "@avaprotocol/types": "^2.2.16", 35 | "@grpc/grpc-js": "^1.11.3", 36 | "@grpc/proto-loader": "^0.7.13", 37 | "dotenv": "^16.4.5", 38 | "ethers": "^6.13.2", 39 | "google-protobuf": "3.19.4", 40 | "id128": "^1.6.6", 41 | "lodash": "^4.17.21" 42 | }, 43 | "devDependencies": { 44 | "@types/google-protobuf": "^3.15.12" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/sdk-js/src/auth.ts: -------------------------------------------------------------------------------- 1 | export const getKeyRequestMessage = (chainId: number, address: string, issuedAt: Date, expiredAt: Date): string => { 2 | return `Please sign the below text for ownership verification. 3 | 4 | URI: https://app.avaprotocol.org 5 | Chain ID: ${chainId} 6 | Version: 1 7 | Issued At: ${issuedAt.toISOString()} 8 | Expire At: ${expiredAt.toISOString()} 9 | Wallet: ${address}`; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /packages/sdk-js/src/config.ts: -------------------------------------------------------------------------------- 1 | import { Environment } from "@avaprotocol/types"; 2 | 3 | export const DEFAULT_JWT_EXPIRATION = 24 * 60 * 60; // 24 hours 4 | interface Config { 5 | AVS_RPC_URL: string; 6 | } 7 | 8 | // Define the configs object with typed keys 9 | const configs: Record = { 10 | development: { 11 | AVS_RPC_URL: process.env.AVS_RPC_URL || "localhost:2206", 12 | }, 13 | staging: { 14 | AVS_RPC_URL: "aggregator-holesky.avaprotocol.org:2206", 15 | }, 16 | production: { 17 | AVS_RPC_URL: "aggregator.avaprotocol.org:2206", 18 | }, 19 | }; 20 | 21 | // Function to get RPC endpoint with improved type safety 22 | export function getRpcEndpoint(env: Environment): string { 23 | return configs[env].AVS_RPC_URL; 24 | } 25 | 26 | // Export the configs only 27 | export { configs }; 28 | -------------------------------------------------------------------------------- /packages/sdk-js/src/models/edge.ts: -------------------------------------------------------------------------------- 1 | import * as avs_pb from "@/grpc_codegen/avs_pb"; 2 | import _ from "lodash"; 3 | import { EdgeProps } from "@avaprotocol/types"; 4 | 5 | class Edge implements EdgeProps { 6 | id: string; 7 | source: string; 8 | target: string; 9 | 10 | constructor(edge: EdgeProps) { 11 | this.id = edge.id; 12 | this.source = edge.source; 13 | this.target = edge.target; 14 | } 15 | 16 | static fromResponse(edge: avs_pb.TaskEdge): Edge { 17 | return new Edge(edge.toObject()); 18 | } 19 | 20 | toRequest(): avs_pb.TaskEdge { 21 | const edge = new avs_pb.TaskEdge(); 22 | edge.setId(this.id); 23 | edge.setSource(this.source); 24 | edge.setTarget(this.target); 25 | 26 | return edge; 27 | } 28 | } 29 | 30 | export default Edge; 31 | -------------------------------------------------------------------------------- /packages/sdk-js/src/models/execution.ts: -------------------------------------------------------------------------------- 1 | import * as avs_pb from "@/grpc_codegen/avs_pb"; 2 | import { TriggerTypeConverter, TriggerType, ExecutionProps, StepProps } from "@avaprotocol/types"; 3 | import Step from "./step"; 4 | 5 | 6 | 7 | class Execution implements ExecutionProps { 8 | id: string; 9 | startAt: number; 10 | endAt: number; 11 | success: boolean; 12 | error: string; 13 | steps: Step[]; 14 | 15 | constructor(props: ExecutionProps) { 16 | this.id = props.id; 17 | this.startAt = props.startAt; 18 | this.endAt = props.endAt; 19 | this.success = props.success; 20 | this.error = props.error; 21 | this.steps = props.steps.map(s => new Step(s)); 22 | } 23 | 24 | /** 25 | * Convert Execution instance to plain object (ExecutionProps) 26 | * This is useful for serialization, especially with Next.js Server Components 27 | */ 28 | toJson(): ExecutionProps { 29 | return { 30 | id: this.id, 31 | startAt: this.startAt, 32 | endAt: this.endAt, 33 | success: this.success, 34 | error: this.error, 35 | steps: this.steps.map(step => step.toJson()), 36 | }; 37 | } 38 | 39 | static fromResponse(execution: avs_pb.Execution): Execution { 40 | return new Execution({ 41 | id: execution.getId(), 42 | startAt: execution.getStartAt(), 43 | endAt: execution.getEndAt(), 44 | success: execution.getSuccess(), 45 | error: execution.getError(), 46 | steps: execution 47 | .getStepsList() 48 | .map((step) => Step.fromResponse(step)) as StepProps[], 49 | }); 50 | } 51 | 52 | // Client side does not generate the execution, so there's no toRequest() method 53 | } 54 | 55 | export default Execution; 56 | -------------------------------------------------------------------------------- /packages/sdk-js/src/models/node/branch.ts: -------------------------------------------------------------------------------- 1 | import { NodeType, BranchNodeData, BranchNodeProps, NodeProps } from "@avaprotocol/types"; 2 | import Node from "./interface"; 3 | import * as avs_pb from "@/grpc_codegen/avs_pb"; 4 | import { convertInputToProtobuf, extractInputFromProtobuf } from "../../utils"; 5 | 6 | // Required props for constructor: id, name, type and data: { conditions } 7 | 8 | 9 | class BranchNode extends Node { 10 | constructor(props: BranchNodeProps) { 11 | super({ ...props, type: NodeType.Branch, data: props.data }); 12 | } 13 | 14 | static fromResponse(raw: avs_pb.TaskNode): BranchNode { 15 | // Convert the raw object to BranchNodeProps, which should keep name and id 16 | const obj = raw.toObject() as unknown as NodeProps; 17 | const protobufData = raw.getBranch()!.toObject().config; 18 | 19 | // Convert protobuf data to our custom interface 20 | const data: BranchNodeData = { 21 | conditions: protobufData?.conditionsList?.map(condition => ({ 22 | id: condition.id, 23 | type: condition.type, 24 | expression: condition.expression, 25 | })) || [], 26 | }; 27 | 28 | // Extract input data if present 29 | const input = extractInputFromProtobuf(raw.getBranch()?.getInput()); 30 | 31 | return new BranchNode({ 32 | ...obj, 33 | type: NodeType.Branch, 34 | data: data, 35 | input: input, 36 | }); 37 | } 38 | 39 | toRequest(): avs_pb.TaskNode { 40 | const request = new avs_pb.TaskNode(); 41 | 42 | request.setId(this.id); 43 | request.setName(this.name); 44 | 45 | const node = new avs_pb.BranchNode(); 46 | const config = new avs_pb.BranchNode.Config(); 47 | 48 | if ((this.data as BranchNodeData).conditions && 49 | (this.data as BranchNodeData).conditions.length > 0) { 50 | 51 | const conditionsList = (this.data as BranchNodeData).conditions.map( 52 | (condition: any) => { 53 | const conditionObj = new avs_pb.BranchNode.Condition(); 54 | conditionObj.setId(condition.id); 55 | conditionObj.setType(condition.type); 56 | conditionObj.setExpression(condition.expression); 57 | return conditionObj; 58 | } 59 | ); 60 | 61 | config.setConditionsList(conditionsList); 62 | } 63 | 64 | node.setConfig(config); 65 | 66 | // Set input data if provided 67 | const inputValue = convertInputToProtobuf(this.input); 68 | if (inputValue) { 69 | node.setInput(inputValue); 70 | } 71 | 72 | request.setBranch(node); 73 | 74 | return request; 75 | } 76 | 77 | static fromOutputData(outputData: avs_pb.RunNodeWithInputsResp): any { 78 | const branchOutput = outputData.getBranch(); 79 | return branchOutput?.toObject() || null; 80 | } 81 | 82 | // TODO: do we need a getConditionId() to avoid exporting BranchNodeData? 83 | } 84 | export default BranchNode; 85 | -------------------------------------------------------------------------------- /packages/sdk-js/src/models/node/contractRead.ts: -------------------------------------------------------------------------------- 1 | import Node from "./interface"; 2 | import * as avs_pb from "@/grpc_codegen/avs_pb"; 3 | import { 4 | NodeType, 5 | ContractReadNodeData, 6 | ContractReadNodeProps, 7 | NodeProps, 8 | } from "@avaprotocol/types"; 9 | import { 10 | convertProtobufValueToJs, 11 | convertInputToProtobuf, 12 | extractInputFromProtobuf, 13 | } from "../../utils"; 14 | 15 | // Required props for constructor: id, name, type and data 16 | 17 | class ContractReadNode extends Node { 18 | constructor(props: ContractReadNodeProps) { 19 | super({ ...props, type: NodeType.ContractRead, data: props.data }); 20 | } 21 | 22 | static fromResponse(raw: avs_pb.TaskNode): ContractReadNode { 23 | // Convert the raw object to ContractReadNodeProps, which should keep name and id 24 | const obj = raw.toObject() as unknown as NodeProps; 25 | const protobufData = raw.getContractRead()!.getConfig()!.toObject(); 26 | 27 | // Convert protobuf data to our custom interface 28 | const data: ContractReadNodeData = { 29 | contractAddress: protobufData.contractAddress, 30 | contractAbi: protobufData.contractAbi, 31 | methodCalls: 32 | protobufData.methodCallsList?.map((call) => ({ 33 | callData: call.callData, 34 | methodName: call.methodName, 35 | applyToFields: call.applyToFieldsList || [], 36 | })) || [], 37 | }; 38 | 39 | // Extract input data from top-level TaskNode.input field (not nested ContractReadNode.input) 40 | // This matches where we set it in toRequest() and where the Go backend looks for it 41 | let input: Record | undefined = undefined; 42 | if (raw.hasInput()) { 43 | input = extractInputFromProtobuf(raw.getInput()); 44 | } 45 | 46 | return new ContractReadNode({ 47 | ...obj, 48 | type: NodeType.ContractRead, 49 | data: data, 50 | input: input, 51 | }); 52 | } 53 | 54 | toRequest(): avs_pb.TaskNode { 55 | const request = new avs_pb.TaskNode(); 56 | 57 | request.setId(this.id); 58 | request.setName(this.name); 59 | 60 | const node = new avs_pb.ContractReadNode(); 61 | 62 | const config = new avs_pb.ContractReadNode.Config(); 63 | config.setContractAddress( 64 | (this.data as ContractReadNodeData).contractAddress 65 | ); 66 | config.setContractAbi((this.data as ContractReadNodeData).contractAbi); 67 | 68 | // Handle method calls array 69 | const methodCalls = (this.data as ContractReadNodeData).methodCalls || []; 70 | methodCalls.forEach( 71 | (methodCall: { 72 | callData: string; 73 | methodName?: string; 74 | applyToFields?: string[]; 75 | }) => { 76 | const methodCallMsg = new avs_pb.ContractReadNode.MethodCall(); 77 | methodCallMsg.setCallData(methodCall.callData); 78 | if (methodCall.methodName) { 79 | methodCallMsg.setMethodName(methodCall.methodName); 80 | } 81 | if (methodCall.applyToFields) { 82 | methodCallMsg.setApplyToFieldsList(methodCall.applyToFields); 83 | } 84 | config.addMethodCalls(methodCallMsg); 85 | } 86 | ); 87 | 88 | node.setConfig(config); 89 | 90 | // Set input data on the top-level TaskNode, not the nested ContractReadNode 91 | // This matches where the Go backend's ExtractNodeInputData() looks for it 92 | const inputValue = convertInputToProtobuf(this.input); 93 | if (inputValue) { 94 | request.setInput(inputValue); 95 | } 96 | 97 | request.setContractRead(node); 98 | 99 | return request; 100 | } 101 | 102 | static fromOutputData(outputData: avs_pb.RunNodeWithInputsResp): any { 103 | const contractReadOutput = outputData.getContractRead(); 104 | if (contractReadOutput && contractReadOutput.getData()) { 105 | // The new structure uses getData() which returns a protobuf Value 106 | const data = contractReadOutput.getData(); 107 | if (data) { 108 | // Convert protobuf Value to JavaScript object 109 | const jsData = convertProtobufValueToJs(data); 110 | 111 | // The data should now be directly an array of results 112 | if (Array.isArray(jsData)) { 113 | return jsData.map((result: any) => ({ 114 | methodName: result.methodName, 115 | success: result.success, 116 | error: result.error || "", 117 | data: result.data || {}, 118 | })); 119 | } else { 120 | // Fallback for old format or unexpected structure 121 | return jsData; 122 | } 123 | } 124 | } 125 | return null; 126 | } 127 | } 128 | 129 | export default ContractReadNode; 130 | -------------------------------------------------------------------------------- /packages/sdk-js/src/models/node/contractWrite.ts: -------------------------------------------------------------------------------- 1 | import Node from "./interface"; 2 | import * as avs_pb from "@/grpc_codegen/avs_pb"; 3 | import { 4 | NodeType, 5 | ContractWriteNodeData, 6 | ContractWriteNodeProps, 7 | NodeProps, 8 | } from "@avaprotocol/types"; 9 | import { convertInputToProtobuf, extractInputFromProtobuf } from "../../utils"; 10 | import { convertProtobufValueToJs } from "../../utils"; 11 | 12 | // Required props for constructor: id, name, type and data: { config: { contractAddress, callData, contractAbi, methodCallsList? } } 13 | 14 | class ContractWriteNode extends Node { 15 | constructor(props: ContractWriteNodeProps) { 16 | super({ ...props, type: NodeType.ContractWrite, data: props.data }); 17 | } 18 | 19 | static fromResponse(raw: avs_pb.TaskNode): ContractWriteNode { 20 | // Convert the raw object to ContractWriteNodeProps, which should keep name and id 21 | const obj = raw.toObject() as unknown as NodeProps; 22 | const protobufData = raw.getContractWrite()!.getConfig()!.toObject(); 23 | 24 | // Convert protobuf data to our custom interface 25 | const data: ContractWriteNodeData = { 26 | contractAddress: protobufData.contractAddress, 27 | callData: protobufData.callData, 28 | contractAbi: protobufData.contractAbi, 29 | methodCalls: 30 | protobufData.methodCallsList?.map((call) => ({ 31 | callData: call.callData, 32 | methodName: call.methodName, 33 | })) || [], 34 | }; 35 | 36 | // Extract input data from top-level TaskNode.input field (not nested ContractWriteNode.input) 37 | // This matches where we set it in toRequest() and where the Go backend looks for it 38 | let input: Record | undefined = undefined; 39 | if (raw.hasInput()) { 40 | input = extractInputFromProtobuf(raw.getInput()); 41 | } 42 | 43 | return new ContractWriteNode({ 44 | ...obj, 45 | type: NodeType.ContractWrite, 46 | data: data, 47 | input: input, 48 | }); 49 | } 50 | 51 | toRequest(): avs_pb.TaskNode { 52 | const request = new avs_pb.TaskNode(); 53 | 54 | request.setId(this.id); 55 | request.setName(this.name); 56 | 57 | const node = new avs_pb.ContractWriteNode(); 58 | 59 | const config = new avs_pb.ContractWriteNode.Config(); 60 | config.setContractAddress( 61 | (this.data as ContractWriteNodeData).contractAddress 62 | ); 63 | config.setCallData((this.data as ContractWriteNodeData).callData); 64 | config.setContractAbi((this.data as ContractWriteNodeData).contractAbi); 65 | 66 | // Handle method calls array 67 | const methodCalls = (this.data as ContractWriteNodeData).methodCalls || []; 68 | methodCalls.forEach( 69 | (methodCall: { callData: string; methodName?: string }) => { 70 | const methodCallMsg = new avs_pb.ContractWriteNode.MethodCall(); 71 | methodCallMsg.setCallData(methodCall.callData); 72 | if (methodCall.methodName) { 73 | methodCallMsg.setMethodName(methodCall.methodName); 74 | } 75 | config.addMethodCalls(methodCallMsg); 76 | } 77 | ); 78 | 79 | node.setConfig(config); 80 | 81 | // Set input data on the top-level TaskNode, not the nested ContractWriteNode 82 | // This matches where the Go backend's ExtractNodeInputData() looks for it 83 | const inputValue = convertInputToProtobuf(this.input); 84 | if (inputValue) { 85 | request.setInput(inputValue); 86 | } 87 | 88 | request.setContractWrite(node); 89 | 90 | return request; 91 | } 92 | 93 | static fromOutputData(outputData: avs_pb.RunNodeWithInputsResp): any { 94 | const contractWriteOutput = outputData.getContractWrite(); 95 | if (!contractWriteOutput) return null; 96 | 97 | // Use the new getData() method instead of the old resultsList 98 | const data = contractWriteOutput.getData(); 99 | if (!data) return null; 100 | 101 | // Convert protobuf Value to JavaScript object 102 | const jsData = convertProtobufValueToJs(data); 103 | 104 | // Return the array directly, matching ContractRead format 105 | return Array.isArray(jsData) ? jsData : [jsData]; 106 | } 107 | } 108 | 109 | export default ContractWriteNode; 110 | -------------------------------------------------------------------------------- /packages/sdk-js/src/models/node/customCode.ts: -------------------------------------------------------------------------------- 1 | import Node from "./interface"; 2 | import * as avs_pb from "@/grpc_codegen/avs_pb"; 3 | import { 4 | NodeType, 5 | CustomCodeNodeData, 6 | CustomCodeLang, 7 | CustomCodeNodeProps, 8 | NodeProps, 9 | } from "@avaprotocol/types"; 10 | import { convertProtobufValueToJs, convertInputToProtobuf, extractInputFromProtobuf } from "../../utils"; 11 | 12 | // Required props for constructor: id, name, type and data: { lang: number, source: string } 13 | 14 | class CustomCodeNode extends Node { 15 | constructor(props: CustomCodeNodeProps) { 16 | super({ ...props, type: NodeType.CustomCode, data: props.data }); 17 | } 18 | 19 | static fromResponse(raw: avs_pb.TaskNode): CustomCodeNode { 20 | // Convert the raw object to CustomCodeNodeProps, which should keep name and id 21 | const obj = raw.toObject() as unknown as NodeProps; 22 | 23 | // Get the raw protobuf config and convert to our custom interface 24 | const rawConfig = raw.getCustomCode()!.getConfig()!.toObject(); 25 | 26 | const convertedConfig: CustomCodeNodeData = { 27 | lang: rawConfig.lang as unknown as CustomCodeLang, 28 | source: rawConfig.source, 29 | }; 30 | 31 | // Extract input data from top-level TaskNode.input field (not nested CustomCodeNode.input) 32 | // This matches where we set it in toRequest() and where the Go backend looks for it 33 | let input: Record | undefined = undefined; 34 | if (raw.hasInput()) { 35 | input = extractInputFromProtobuf(raw.getInput()); 36 | } 37 | 38 | return new CustomCodeNode({ 39 | ...obj, 40 | type: NodeType.CustomCode, 41 | data: convertedConfig, 42 | input: input, 43 | }); 44 | } 45 | 46 | toRequest(): avs_pb.TaskNode { 47 | const request = new avs_pb.TaskNode(); 48 | 49 | request.setId(this.id); 50 | request.setName(this.name); 51 | 52 | const node = new avs_pb.CustomCodeNode(); 53 | 54 | const config = new avs_pb.CustomCodeNode.Config(); 55 | 56 | // Set lang using enum value (cast to protobuf Lang type) 57 | config.setLang((this.data as CustomCodeNodeData).lang as any); 58 | config.setSource((this.data as CustomCodeNodeData).source); 59 | 60 | node.setConfig(config); 61 | 62 | // Set input data on the top-level TaskNode, not the nested CustomCodeNode 63 | // This matches where the Go backend's ExtractNodeInputData() looks for it 64 | const inputValue = convertInputToProtobuf(this.input); 65 | if (inputValue) { 66 | request.setInput(inputValue); 67 | } 68 | 69 | request.setCustomCode(node); 70 | 71 | return request; 72 | } 73 | 74 | static fromOutputData(outputData: avs_pb.RunNodeWithInputsResp): any { 75 | const customCodeOutput = outputData.getCustomCode(); 76 | if (customCodeOutput?.getData()) { 77 | // Use the modern protobuf conversion function 78 | const result = convertProtobufValueToJs(customCodeOutput.getData()); 79 | 80 | // SPECIAL FIX: Check if the result is incorrectly wrapped with a single "data" property 81 | // This handles the case where primitive values get wrapped as {"data": value} 82 | if (result && typeof result === 'object' && 83 | Object.keys(result).length === 1 && 84 | 'data' in result) { 85 | return result.data; 86 | } 87 | 88 | return result; 89 | } 90 | return null; 91 | } 92 | } 93 | 94 | export default CustomCodeNode; 95 | -------------------------------------------------------------------------------- /packages/sdk-js/src/models/node/ethTransfer.ts: -------------------------------------------------------------------------------- 1 | import Node from "./interface"; 2 | import * as avs_pb from "@/grpc_codegen/avs_pb"; 3 | import { NodeType, ETHTransferNodeData, ETHTransferNodeProps, NodeProps } from "@avaprotocol/types"; 4 | import { convertInputToProtobuf, extractInputFromProtobuf } from "../../utils"; 5 | 6 | // Required props for constructor: id, name, type and data: { destination, amount } 7 | 8 | 9 | class ETHTransferNode extends Node { 10 | constructor(props: ETHTransferNodeProps) { 11 | super({ ...props, type: NodeType.ETHTransfer, data: props.data }); 12 | } 13 | 14 | static fromResponse(raw: avs_pb.TaskNode): ETHTransferNode { 15 | // Convert the raw object to ETHTransferNodeProps, which should keep name and id 16 | const obj = raw.toObject() as unknown as NodeProps; 17 | 18 | // Extract input data if present 19 | const input = extractInputFromProtobuf(raw.getEthTransfer()?.getInput()); 20 | 21 | return new ETHTransferNode({ 22 | ...obj, 23 | type: NodeType.ETHTransfer, 24 | data: raw.getEthTransfer()!.getConfig()!.toObject() as ETHTransferNodeData, 25 | input: input, 26 | }); 27 | } 28 | 29 | toRequest(): avs_pb.TaskNode { 30 | const request = new avs_pb.TaskNode(); 31 | 32 | request.setId(this.id); 33 | request.setName(this.name); 34 | 35 | const node = new avs_pb.ETHTransferNode(); 36 | 37 | const config = new avs_pb.ETHTransferNode.Config(); 38 | config.setDestination((this.data as ETHTransferNodeData).destination); 39 | config.setAmount((this.data as ETHTransferNodeData).amount); 40 | node.setConfig(config); 41 | 42 | // Set input data if provided 43 | const inputValue = convertInputToProtobuf(this.input); 44 | if (inputValue) { 45 | node.setInput(inputValue); 46 | } 47 | 48 | request.setEthTransfer(node); 49 | 50 | return request; 51 | } 52 | 53 | static fromOutputData(outputData: avs_pb.RunNodeWithInputsResp): any { 54 | const ethTransferOutput = outputData.getEthTransfer(); 55 | return ethTransferOutput?.toObject() || null; 56 | } 57 | } 58 | 59 | export default ETHTransferNode; 60 | -------------------------------------------------------------------------------- /packages/sdk-js/src/models/node/factory.ts: -------------------------------------------------------------------------------- 1 | import * as avs_pb from "@/grpc_codegen/avs_pb"; 2 | import _ from "lodash"; 3 | import ContractWriteNode from "./contractWrite"; 4 | import CustomCodeNode from "./customCode"; 5 | import GraphQLQueryNode from "./graphqlQuery"; 6 | import Node from "./interface"; 7 | import RestAPINode from "./restApi"; 8 | import ContractReadNode from "./contractRead"; 9 | import ETHTransferNode from "./ethTransfer"; 10 | import BranchNode from "./branch"; 11 | import FilterNode from "./filter"; 12 | import LoopNode from "./loop"; 13 | import { 14 | NodeType, 15 | ContractWriteNodeData, 16 | ContractReadNodeData, 17 | BranchNodeData, 18 | ETHTransferNodeData, 19 | GraphQLQueryNodeData, 20 | RestAPINodeData, 21 | CustomCodeNodeData, 22 | FilterNodeData, 23 | LoopNodeData, 24 | NodeProps, 25 | ContractWriteNodeProps, 26 | ContractReadNodeProps, 27 | BranchNodeProps, 28 | ETHTransferNodeProps, 29 | GraphQLQueryNodeProps, 30 | RestAPINodeProps, 31 | CustomCodeNodeProps, 32 | FilterNodeProps, 33 | LoopNodeProps 34 | } from "@avaprotocol/types"; 35 | 36 | class NodeFactory { 37 | static create(props: NodeProps): Node { 38 | switch (props.type) { 39 | case NodeType.ContractWrite: 40 | return new ContractWriteNode(props as ContractWriteNodeProps); 41 | case NodeType.RestAPI: 42 | return new RestAPINode(props as RestAPINodeProps); 43 | case NodeType.CustomCode: 44 | return new CustomCodeNode(props as CustomCodeNodeProps); 45 | case NodeType.ContractRead: 46 | return new ContractReadNode(props as ContractReadNodeProps); 47 | case NodeType.ETHTransfer: 48 | return new ETHTransferNode(props as ETHTransferNodeProps); 49 | case NodeType.GraphQLQuery: 50 | return new GraphQLQueryNode(props as GraphQLQueryNodeProps); 51 | case NodeType.Branch: 52 | return new BranchNode(props as BranchNodeProps); 53 | case NodeType.Filter: 54 | return new FilterNode(props as FilterNodeProps); 55 | case NodeType.Loop: 56 | return new LoopNode(props as LoopNodeProps); 57 | case NodeType.Unspecified: 58 | throw new Error("Cannot create node with unspecified type"); 59 | default: 60 | throw new Error(`Unsupported node type: ${props.type}`); 61 | } 62 | } 63 | 64 | static createNodes(props: NodeProps[]): Node[] { 65 | return _.map(props, (node) => this.create(node)); 66 | } 67 | 68 | static fromResponse(raw: avs_pb.TaskNode): Node { 69 | switch (true) { 70 | case !!raw.getEthTransfer(): 71 | return ETHTransferNode.fromResponse(raw); 72 | case !!raw.getContractRead(): 73 | return ContractReadNode.fromResponse(raw); 74 | case !!raw.getContractWrite(): 75 | return ContractWriteNode.fromResponse(raw); 76 | case !!raw.getGraphqlQuery(): 77 | return GraphQLQueryNode.fromResponse(raw); 78 | case !!raw.getRestApi(): 79 | return RestAPINode.fromResponse(raw); 80 | case !!raw.getCustomCode(): 81 | return CustomCodeNode.fromResponse(raw); 82 | case !!raw.getBranch(): 83 | return BranchNode.fromResponse(raw); 84 | case !!raw.getFilter(): 85 | return FilterNode.fromResponse(raw); 86 | case !!raw.getLoop(): 87 | return LoopNode.fromResponse(raw); 88 | default: 89 | throw new Error(`Unsupported node type: ${raw.getName()}`); 90 | } 91 | } 92 | 93 | static fromOutputData(outputData: avs_pb.RunNodeWithInputsResp): any { 94 | // Delegate to the specific node type's fromOutputData method 95 | // This is the correct approach, similar to how TriggerFactory.fromOutputData works 96 | 97 | switch (outputData.getOutputDataCase()) { 98 | case avs_pb.RunNodeWithInputsResp.OutputDataCase.REST_API: 99 | return RestAPINode.fromOutputData(outputData); 100 | case avs_pb.RunNodeWithInputsResp.OutputDataCase.CUSTOM_CODE: 101 | return CustomCodeNode.fromOutputData(outputData); 102 | case avs_pb.RunNodeWithInputsResp.OutputDataCase.CONTRACT_READ: 103 | return ContractReadNode.fromOutputData(outputData); 104 | case avs_pb.RunNodeWithInputsResp.OutputDataCase.CONTRACT_WRITE: 105 | return ContractWriteNode.fromOutputData(outputData); 106 | case avs_pb.RunNodeWithInputsResp.OutputDataCase.ETH_TRANSFER: 107 | return ETHTransferNode.fromOutputData(outputData); 108 | case avs_pb.RunNodeWithInputsResp.OutputDataCase.GRAPHQL: 109 | return GraphQLQueryNode.fromOutputData(outputData); 110 | case avs_pb.RunNodeWithInputsResp.OutputDataCase.BRANCH: 111 | return BranchNode.fromOutputData(outputData); 112 | case avs_pb.RunNodeWithInputsResp.OutputDataCase.FILTER: 113 | return FilterNode.fromOutputData(outputData); 114 | case avs_pb.RunNodeWithInputsResp.OutputDataCase.LOOP: 115 | return LoopNode.fromOutputData(outputData); 116 | case avs_pb.RunNodeWithInputsResp.OutputDataCase.OUTPUT_DATA_NOT_SET: 117 | default: 118 | throw new Error(`Unsupported output data case: ${outputData.getOutputDataCase()}`); 119 | } 120 | } 121 | } 122 | 123 | export default NodeFactory; 124 | 125 | // Node object definitions 126 | export { 127 | Node, 128 | ContractWriteNode, 129 | ContractReadNode, 130 | BranchNode, 131 | ETHTransferNode, 132 | GraphQLQueryNode, 133 | RestAPINode, 134 | CustomCodeNode, 135 | FilterNode, 136 | LoopNode, 137 | }; 138 | 139 | // Data definitions of Node 140 | export type { 141 | ContractWriteNodeData, 142 | ContractReadNodeData, 143 | BranchNodeData, 144 | ETHTransferNodeData, 145 | GraphQLQueryNodeData, 146 | RestAPINodeData, 147 | CustomCodeNodeData, 148 | FilterNodeData, 149 | LoopNodeData, 150 | }; 151 | 152 | // Node Props definitions 153 | export type { 154 | NodeProps, 155 | ContractWriteNodeProps, 156 | ContractReadNodeProps, 157 | BranchNodeProps, 158 | ETHTransferNodeProps, 159 | GraphQLQueryNodeProps, 160 | RestAPINodeProps, 161 | CustomCodeNodeProps, 162 | FilterNodeProps, 163 | LoopNodeProps, 164 | }; 165 | -------------------------------------------------------------------------------- /packages/sdk-js/src/models/node/filter.ts: -------------------------------------------------------------------------------- 1 | import Node from "./interface"; 2 | import * as avs_pb from "@/grpc_codegen/avs_pb"; 3 | import { NodeType, FilterNodeData, FilterNodeProps, NodeProps } from "@avaprotocol/types"; 4 | import { convertInputToProtobuf, extractInputFromProtobuf } from "../../utils"; 5 | 6 | // Required props for constructor: id, name, type and data: { expression, sourceId } 7 | 8 | 9 | class FilterNode extends Node { 10 | constructor(props: FilterNodeProps) { 11 | super({ ...props, type: NodeType.Filter, data: props.data }); 12 | } 13 | 14 | static fromResponse(raw: avs_pb.TaskNode): FilterNode { 15 | // Convert the raw object to FilterNodeProps, which should keep name and id 16 | const obj = raw.toObject() as unknown as NodeProps; 17 | 18 | // Extract input data if present 19 | const input = extractInputFromProtobuf(raw.getFilter()?.getInput()); 20 | 21 | return new FilterNode({ 22 | ...obj, 23 | type: NodeType.Filter, 24 | data: raw.getFilter()!.getConfig()!.toObject() as FilterNodeData, 25 | input: input, 26 | }); 27 | } 28 | 29 | toRequest(): avs_pb.TaskNode { 30 | const request = new avs_pb.TaskNode(); 31 | 32 | request.setId(this.id); 33 | request.setName(this.name); 34 | 35 | const node = new avs_pb.FilterNode(); 36 | 37 | const config = new avs_pb.FilterNode.Config(); 38 | config.setExpression((this.data as FilterNodeData).expression); 39 | config.setSourceId((this.data as FilterNodeData).sourceId || ''); 40 | node.setConfig(config); 41 | 42 | // Set input data if provided 43 | const inputValue = convertInputToProtobuf(this.input); 44 | if (inputValue) { 45 | node.setInput(inputValue); 46 | } 47 | 48 | request.setFilter(node); 49 | return request; 50 | } 51 | 52 | static fromOutputData(outputData: avs_pb.RunNodeWithInputsResp): any { 53 | const filterOutput = outputData.getFilter(); 54 | return filterOutput?.toObject() || null; 55 | } 56 | } 57 | 58 | export default FilterNode; 59 | -------------------------------------------------------------------------------- /packages/sdk-js/src/models/node/graphqlQuery.ts: -------------------------------------------------------------------------------- 1 | import Node from "./interface"; 2 | import * as avs_pb from "@/grpc_codegen/avs_pb"; 3 | import { NodeType, GraphQLQueryNodeData, GraphQLQueryNodeProps, NodeProps } from "@avaprotocol/types"; 4 | import { convertInputToProtobuf, extractInputFromProtobuf } from "../../utils"; 5 | 6 | // Required props for constructor: id, name, type and data: { url, query, variablesMap } 7 | 8 | 9 | class GraphQLQueryNode extends Node { 10 | constructor(props: GraphQLQueryNodeProps) { 11 | super({ 12 | ...props, 13 | type: NodeType.GraphQLQuery, 14 | data: props.data, 15 | }); 16 | } 17 | 18 | static fromResponse(raw: avs_pb.TaskNode): GraphQLQueryNode { 19 | // Convert the raw object to GraphQLQueryNodeProps, which should keep name and id 20 | const obj = raw.toObject() as unknown as NodeProps; 21 | 22 | // Extract input data if present 23 | const input = extractInputFromProtobuf(raw.getGraphqlQuery()?.getInput()); 24 | 25 | return new GraphQLQueryNode({ 26 | ...obj, 27 | type: NodeType.GraphQLQuery, 28 | data: raw.getGraphqlQuery()!.getConfig()!.toObject() as GraphQLQueryNodeData, 29 | input: input, 30 | }); 31 | } 32 | 33 | toRequest(): avs_pb.TaskNode { 34 | const request = new avs_pb.TaskNode(); 35 | 36 | request.setId(this.id); 37 | request.setName(this.name); 38 | 39 | const node = new avs_pb.GraphQLQueryNode(); 40 | 41 | const config = new avs_pb.GraphQLQueryNode.Config(); 42 | config.setUrl((this.data as GraphQLQueryNodeData).url); 43 | config.setQuery((this.data as GraphQLQueryNodeData).query); 44 | 45 | if ((this.data as GraphQLQueryNodeData).variablesMap && 46 | (this.data as GraphQLQueryNodeData).variablesMap.length > 0) { 47 | const variablesMap = config.getVariablesMap(); 48 | (this.data as GraphQLQueryNodeData).variablesMap.forEach(([key, value]: [string, string]) => { 49 | variablesMap.set(key, value); 50 | }); 51 | } 52 | 53 | node.setConfig(config); 54 | 55 | // Set input data if provided 56 | const inputValue = convertInputToProtobuf(this.input); 57 | if (inputValue) { 58 | node.setInput(inputValue); 59 | } 60 | 61 | request.setGraphqlQuery(node); 62 | 63 | return request; 64 | } 65 | 66 | static fromOutputData(outputData: avs_pb.RunNodeWithInputsResp): any { 67 | const graphqlOutput = outputData.getGraphql(); 68 | return graphqlOutput?.toObject() || null; 69 | } 70 | } 71 | 72 | export default GraphQLQueryNode; 73 | -------------------------------------------------------------------------------- /packages/sdk-js/src/models/node/interface.ts: -------------------------------------------------------------------------------- 1 | import * as avs_pb from "@/grpc_codegen/avs_pb"; 2 | import * as google_protobuf_struct_pb from "google-protobuf/google/protobuf/struct_pb"; 3 | import { NodeType, NodeTypeGoConverter, NodeTypeConverter, NodeProps, NodeData } from "@avaprotocol/types"; 4 | import _ from "lodash"; 5 | import { extractInputFromProtobuf } from "../../utils"; 6 | 7 | // Function to convert TaskStatus to string 8 | export function covertNodeTypeToString( 9 | status: avs_pb.TaskNode.TaskTypeCase 10 | ): NodeType { 11 | const conversionMap: { [key in avs_pb.TaskNode.TaskTypeCase]: NodeType } = { 12 | [avs_pb.TaskNode.TaskTypeCase.ETH_TRANSFER]: NodeType.ETHTransfer, 13 | [avs_pb.TaskNode.TaskTypeCase.CONTRACT_WRITE]: NodeType.ContractWrite, 14 | [avs_pb.TaskNode.TaskTypeCase.CONTRACT_READ]: NodeType.ContractRead, 15 | [avs_pb.TaskNode.TaskTypeCase.GRAPHQL_QUERY]: NodeType.GraphQLQuery, 16 | [avs_pb.TaskNode.TaskTypeCase.REST_API]: NodeType.RestAPI, 17 | [avs_pb.TaskNode.TaskTypeCase.BRANCH]: NodeType.Branch, 18 | [avs_pb.TaskNode.TaskTypeCase.FILTER]: NodeType.Filter, 19 | [avs_pb.TaskNode.TaskTypeCase.LOOP]: NodeType.Loop, 20 | [avs_pb.TaskNode.TaskTypeCase.CUSTOM_CODE]: NodeType.CustomCode, 21 | [avs_pb.TaskNode.TaskTypeCase.TASK_TYPE_NOT_SET]: NodeType.Unspecified, 22 | }; 23 | 24 | return conversionMap[status] as NodeType; 25 | } 26 | 27 | // Option 2: Use protobuf NodeType directly 28 | export type ProtobufNodeProps = Omit< 29 | avs_pb.TaskNode.AsObject, 30 | | "ethTransfer" 31 | | "contractWrite" 32 | | "contractRead" 33 | | "graphqlDataQuery" 34 | | "restApi" 35 | | "branch" 36 | | "filter" 37 | | "loop" 38 | | "customCode" 39 | > & { 40 | // Keep the protobuf type field as-is (numeric enum) 41 | data: any; // Use any for protobuf data since it's different from our custom NodeData 42 | }; 43 | 44 | // Utility functions to work with protobuf NodeProps 45 | export const ProtobufNodePropsUtils = { 46 | // Get the Go backend string representation of the node type 47 | getGoStringType: (props: ProtobufNodeProps): string => { 48 | return NodeTypeGoConverter.toGoString(props.type); 49 | }, 50 | 51 | // Create ProtobufNodeProps from Go string type 52 | fromGoStringType: (goStringType: string, baseProps: Omit): ProtobufNodeProps => { 53 | return { 54 | ...baseProps, 55 | type: NodeTypeGoConverter.fromGoString(goStringType) 56 | }; 57 | } 58 | }; 59 | 60 | export default abstract class Node implements NodeProps { 61 | id: string; 62 | name: string; 63 | type: NodeType; 64 | data: NodeData; 65 | input?: Record; // Use JavaScript object type for internal storage 66 | 67 | constructor(props: NodeProps) { 68 | this.id = props.id; 69 | this.name = props.name; 70 | this.type = props.type; 71 | this.data = props.data; 72 | // Direct assignment - no protobuf conversion needed for user input 73 | this.input = props.input; 74 | } 75 | 76 | toRequest(): avs_pb.TaskNode { 77 | const request = new avs_pb.TaskNode(); 78 | const raw = request.serializeBinary(); 79 | const parsed = avs_pb.TaskNode.deserializeBinary(raw); 80 | if (!_.isEqual(request, parsed)) { 81 | throw new Error("Invalid request object"); 82 | } 83 | 84 | return request; 85 | } 86 | 87 | static fromResponse(raw: avs_pb.TaskNode): Node { 88 | // Convert the raw object to NodeProps, which should keep name and id 89 | const obj = raw.toObject() as unknown as NodeProps; 90 | 91 | // Extract input data using the utility function 92 | let input: google_protobuf_struct_pb.Value.AsObject | undefined = undefined; 93 | if (raw.hasInput()) { 94 | input = raw.getInput()!.toObject(); 95 | } 96 | 97 | return new (this as any)({ 98 | ...obj, 99 | input: input, 100 | }); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /packages/sdk-js/src/models/node/restApi.ts: -------------------------------------------------------------------------------- 1 | import Node from "./interface"; 2 | import * as avs_pb from "@/grpc_codegen/avs_pb"; 3 | import { 4 | NodeType, 5 | RestAPINodeData, 6 | RestAPINodeProps, 7 | NodeProps, 8 | } from "@avaprotocol/types"; 9 | import { 10 | convertProtobufValueToJs, 11 | convertInputToProtobuf, 12 | extractInputFromProtobuf, 13 | } from "../../utils"; 14 | 15 | // Required props for constructor: id, name, type and data: { url, method, headersMap, body } 16 | 17 | class RestAPINode extends Node { 18 | constructor(props: RestAPINodeProps) { 19 | super({ ...props, type: NodeType.RestAPI, data: props.data }); 20 | } 21 | 22 | static fromResponse(raw: avs_pb.TaskNode): RestAPINode { 23 | // Convert the raw object to RestAPINodeProps, which should keep name and id 24 | const obj = raw.toObject() as unknown as NodeProps; 25 | 26 | // Extract input data from top-level TaskNode.input field (not nested RestAPINode.input) 27 | // This matches where we set it in toRequest() and where the Go backend looks for it 28 | let input: Record | undefined = undefined; 29 | if (raw.hasInput()) { 30 | input = extractInputFromProtobuf(raw.getInput()); 31 | } 32 | 33 | return new RestAPINode({ 34 | ...obj, 35 | type: NodeType.RestAPI, 36 | data: raw.getRestApi()!.getConfig()!.toObject() as RestAPINodeData, 37 | input: input, // Include input data from top-level TaskNode 38 | }); 39 | } 40 | 41 | toRequest(): avs_pb.TaskNode { 42 | const request = new avs_pb.TaskNode(); 43 | 44 | request.setId(this.id); 45 | request.setName(this.name); 46 | 47 | const nodeData = new avs_pb.RestAPINode(); 48 | 49 | const config = new avs_pb.RestAPINode.Config(); 50 | config.setUrl((this.data as RestAPINodeData).url); 51 | config.setMethod((this.data as RestAPINodeData).method); 52 | config.setBody((this.data as RestAPINodeData).body || ""); 53 | 54 | if ( 55 | (this.data as RestAPINodeData).headersMap && 56 | (this.data as RestAPINodeData).headersMap.length > 0 57 | ) { 58 | const headersMap = config.getHeadersMap(); 59 | (this.data as RestAPINodeData).headersMap.forEach( 60 | ([key, value]: [string, string]) => { 61 | headersMap.set(key, value); 62 | } 63 | ); 64 | } 65 | 66 | nodeData.setConfig(config); 67 | 68 | // Use the standard utility function to convert input field to protobuf format 69 | const inputValue = convertInputToProtobuf(this.input); 70 | 71 | if (inputValue) { 72 | // Set input on the top-level TaskNode, not the nested RestAPINode 73 | // This matches where the Go backend's ExtractNodeInputData() looks for it 74 | request.setInput(inputValue); 75 | } 76 | 77 | request.setRestApi(nodeData); 78 | 79 | return request; 80 | } 81 | 82 | static fromOutputData(outputData: avs_pb.RunNodeWithInputsResp): any { 83 | const restApiOutput = outputData.getRestApi(); 84 | if (!restApiOutput) { 85 | console.log("Debug RestAPI: No restApiOutput found"); 86 | return null; 87 | } 88 | 89 | // Use convertProtobufValueToJs to get clean JavaScript objects 90 | const rawData = restApiOutput.getData(); 91 | if (rawData) { 92 | return convertProtobufValueToJs(rawData); 93 | } 94 | 95 | return restApiOutput.toObject(); 96 | } 97 | } 98 | 99 | export default RestAPINode; 100 | -------------------------------------------------------------------------------- /packages/sdk-js/src/models/secret.ts: -------------------------------------------------------------------------------- 1 | import * as avs_pb from "@/grpc_codegen/avs_pb"; 2 | import { SecretProps } from "@avaprotocol/types"; 3 | 4 | class Secret implements SecretProps { 5 | name: string; 6 | secret?: string; 7 | workflowId?: string; 8 | orgId?: string; 9 | createdAt?: number; 10 | updatedAt?: number; 11 | createdBy?: string; 12 | description?: string; 13 | 14 | constructor(props: SecretProps) { 15 | this.name = props.name; 16 | this.secret = props.secret; 17 | this.workflowId = props.workflowId; 18 | this.orgId = props.orgId; 19 | this.createdAt = props.createdAt; 20 | this.updatedAt = props.updatedAt; 21 | this.createdBy = props.createdBy; 22 | this.description = props.description; 23 | } 24 | 25 | toRequest(): avs_pb.CreateOrUpdateSecretReq { 26 | const request = new avs_pb.CreateOrUpdateSecretReq(); 27 | 28 | request.setName(this.name); 29 | if (this.secret) { 30 | request.setSecret(this.secret); 31 | } 32 | if (this.orgId) { 33 | request.setOrgId(this.orgId); 34 | } 35 | if (this.workflowId) { 36 | request.setWorkflowId(this.workflowId); 37 | } 38 | 39 | return request; 40 | } 41 | } 42 | 43 | export default Secret; 44 | -------------------------------------------------------------------------------- /packages/sdk-js/src/models/trigger/block.ts: -------------------------------------------------------------------------------- 1 | import * as avs_pb from "@/grpc_codegen/avs_pb"; 2 | import Trigger from "./interface"; 3 | import { TriggerType, BlockTriggerDataType, BlockTriggerOutput, BlockTriggerProps, TriggerProps, TriggerOutput } from "@avaprotocol/types"; 4 | import { convertInputToProtobuf, extractInputFromProtobuf } from "../../utils"; 5 | 6 | // Required props for constructor: id, name, type and data: { interval } 7 | 8 | 9 | class BlockTrigger extends Trigger { 10 | constructor(props: BlockTriggerProps) { 11 | super({ ...props, type: TriggerType.Block, data: props.data }); 12 | } 13 | 14 | toRequest(): avs_pb.TaskTrigger { 15 | const request = new avs_pb.TaskTrigger(); 16 | request.setName(this.name); 17 | request.setId(this.id); 18 | request.setType(avs_pb.TriggerType.TRIGGER_TYPE_BLOCK); 19 | 20 | if (!this.data) { 21 | throw new Error(`Trigger data is missing for block`); 22 | } 23 | 24 | const blockData = this.data as BlockTriggerDataType; 25 | 26 | // Validate interval is present and not null/undefined 27 | if (blockData.interval === null || blockData.interval === undefined) { 28 | throw new Error("Interval is required for block trigger"); 29 | } 30 | 31 | // Validate interval is greater than 0 32 | if (blockData.interval <= 0) { 33 | throw new Error("Interval must be greater than 0"); 34 | } 35 | 36 | const trigger = new avs_pb.BlockTrigger(); 37 | const config = new avs_pb.BlockTrigger.Config(); 38 | config.setInterval(blockData.interval); 39 | trigger.setConfig(config); 40 | 41 | // Use utility function to convert input field to protobuf format 42 | const inputValue = convertInputToProtobuf(this.input); 43 | if (inputValue) { 44 | trigger.setInput(inputValue); 45 | } 46 | 47 | request.setBlock(trigger); 48 | 49 | return request; 50 | } 51 | 52 | static fromResponse(raw: avs_pb.TaskTrigger): BlockTrigger { 53 | // Convert the raw object to TriggerProps, which should keep name and id 54 | const obj = raw.toObject() as unknown as TriggerProps; 55 | 56 | let data: BlockTriggerDataType = { interval: 0 }; 57 | let input: Record | undefined = undefined; 58 | 59 | if (raw.getBlock() && raw.getBlock()!.hasConfig()) { 60 | const config = raw.getBlock()!.getConfig(); 61 | 62 | if (config) { 63 | data = { 64 | interval: config.getInterval() || 0 65 | }; 66 | } 67 | 68 | // Use utility function to extract input field from protobuf format 69 | const blockTrigger = raw.getBlock()!; 70 | if (blockTrigger.hasInput()) { 71 | input = extractInputFromProtobuf(blockTrigger.getInput()); 72 | } 73 | } 74 | 75 | return new BlockTrigger({ 76 | ...obj, 77 | type: TriggerType.Block, 78 | data: data, 79 | input: input, 80 | }); 81 | } 82 | 83 | /** 84 | * Convert raw data from runNodeWithInputs response to BlockOutput format 85 | * @param rawData - The raw data from the gRPC response 86 | * @returns {BlockTriggerOutput | undefined} - The converted data 87 | */ 88 | getOutput(): BlockTriggerOutput | undefined { 89 | return this.output as BlockTriggerOutput; 90 | } 91 | 92 | /** 93 | * Extract output data from RunTriggerResp for block triggers 94 | * @param outputData - The RunTriggerResp containing block trigger output 95 | * @returns Plain JavaScript object with block trigger data 96 | */ 97 | static fromOutputData(outputData: avs_pb.RunTriggerResp): any { 98 | const blockOutput = outputData.getBlockTrigger(); 99 | return blockOutput?.toObject() || null; 100 | } 101 | } 102 | 103 | export default BlockTrigger; 104 | -------------------------------------------------------------------------------- /packages/sdk-js/src/models/trigger/cron.ts: -------------------------------------------------------------------------------- 1 | import * as avs_pb from "@/grpc_codegen/avs_pb"; 2 | import * as google_protobuf_struct_pb from "google-protobuf/google/protobuf/struct_pb"; 3 | import Trigger from "./interface"; 4 | import { TriggerType, CronTriggerDataType, CronTriggerOutput, CronTriggerProps, TriggerProps } from "@avaprotocol/types"; 5 | 6 | class CronTrigger extends Trigger { 7 | constructor(props: CronTriggerProps) { 8 | super({ ...props, type: TriggerType.Cron, data: props.data }); 9 | } 10 | 11 | toRequest(): avs_pb.TaskTrigger { 12 | const request = new avs_pb.TaskTrigger(); 13 | request.setName(this.name); 14 | request.setId(this.id); 15 | request.setType(avs_pb.TriggerType.TRIGGER_TYPE_CRON); 16 | 17 | if (!this.data) { 18 | throw new Error(`Trigger data is missing for ${this.type}`); 19 | } 20 | 21 | const cronData = this.data as CronTriggerDataType; 22 | 23 | // Validate schedules is present and not null/undefined 24 | if (cronData.schedules === null || cronData.schedules === undefined) { 25 | throw new Error("Schedules are required for cron trigger"); 26 | } 27 | 28 | // Validate schedules is not empty 29 | if (!Array.isArray(cronData.schedules) || cronData.schedules.length === 0) { 30 | throw new Error("Schedules are required for cron trigger"); 31 | } 32 | 33 | const trigger = new avs_pb.CronTrigger(); 34 | const config = new avs_pb.CronTrigger.Config(); 35 | config.setSchedulesList(cronData.schedules); 36 | trigger.setConfig(config); 37 | 38 | // ✨ NEW: Set input data if provided 39 | if (this.input) { 40 | try { 41 | const inputValue = google_protobuf_struct_pb.Value.fromJavaScript(this.input); 42 | trigger.setInput(inputValue); 43 | } catch (error) { 44 | throw new Error(`Failed to convert input data to protobuf.Value: ${error}`); 45 | } 46 | } 47 | 48 | request.setCron(trigger); 49 | 50 | return request; 51 | } 52 | 53 | static fromResponse(raw: avs_pb.TaskTrigger): CronTrigger { 54 | // Convert the raw object to TriggerProps, which should keep name and id 55 | const obj = raw.toObject() as unknown as TriggerProps; 56 | 57 | let data: CronTriggerDataType = { schedules: [] }; 58 | 59 | if (raw.getCron() && raw.getCron()!.hasConfig()) { 60 | const config = raw.getCron()!.getConfig(); 61 | 62 | if (config) { 63 | data = { 64 | schedules: config.getSchedulesList() || [] 65 | }; 66 | } 67 | } 68 | 69 | // ✨ NEW: Extract input data if present 70 | let input: google_protobuf_struct_pb.Value.AsObject | undefined; 71 | if (raw.getCron() && raw.getCron()!.hasInput()) { 72 | const inputValue = raw.getCron()!.getInput(); 73 | if (inputValue) { 74 | input = inputValue.toObject(); 75 | } 76 | } 77 | 78 | return new CronTrigger({ 79 | ...obj, 80 | type: TriggerType.Cron, 81 | data: data, 82 | input: input, // ✨ NEW: Include input data 83 | }); 84 | } 85 | 86 | /** 87 | * Convert raw data from runTrigger response to CronOutput format 88 | * @param rawData - The raw data from the gRPC response 89 | * @returns {CronTriggerOutput | undefined} - The converted data 90 | */ 91 | getOutput(): CronTriggerOutput | undefined { 92 | return this.output as CronTriggerOutput; 93 | } 94 | 95 | /** 96 | * Extract output data from RunTriggerResp for cron triggers 97 | * Updated to handle timestamp and timestamp_iso instead of epoch 98 | * @param outputData - The RunTriggerResp containing cron trigger output 99 | * @returns Plain JavaScript object with cron trigger data 100 | */ 101 | static fromOutputData(outputData: avs_pb.RunTriggerResp): any { 102 | const cronOutput = outputData.getCronTrigger(); 103 | if (!cronOutput) return null; 104 | 105 | const outputObj = cronOutput.toObject(); 106 | // The output now contains timestamp and timestampIso instead of epoch 107 | return { 108 | timestamp: outputObj.timestamp, 109 | timestampIso: outputObj.timestampIso 110 | }; 111 | } 112 | } 113 | 114 | export default CronTrigger; 115 | -------------------------------------------------------------------------------- /packages/sdk-js/src/models/trigger/factory.ts: -------------------------------------------------------------------------------- 1 | import * as avs_pb from "@/grpc_codegen/avs_pb"; 2 | import BlockTrigger from "./block"; 3 | import CronTrigger from "./cron"; 4 | import EventTrigger from "./event"; 5 | import FixedTimeTrigger from "./fixedTime"; 6 | import ManualTrigger from "./manual"; 7 | import Trigger from "./interface"; 8 | import { 9 | TriggerType, 10 | TriggerProps, 11 | BlockTriggerProps, 12 | CronTriggerProps, 13 | EventTriggerProps, 14 | FixedTimeTriggerProps, 15 | ManualTriggerProps 16 | } from "@avaprotocol/types"; 17 | import { convertProtobufValueToJs } from "../../utils"; 18 | 19 | class TriggerFactory { 20 | /** 21 | * Static factory method to create Trigger instances 22 | * @param props 23 | * @returns 24 | */ 25 | static create(props: TriggerProps): Trigger { 26 | switch (props.type) { 27 | case TriggerType.Block: 28 | return new BlockTrigger(props as BlockTriggerProps); 29 | case TriggerType.Cron: 30 | return new CronTrigger(props as CronTriggerProps); 31 | case TriggerType.Event: 32 | return new EventTrigger(props as EventTriggerProps); 33 | case TriggerType.FixedTime: 34 | return new FixedTimeTrigger(props as FixedTimeTriggerProps); 35 | case TriggerType.Manual: 36 | return new ManualTrigger(props as ManualTriggerProps); 37 | } 38 | 39 | // Add more conditions for other subclasses 40 | throw new Error("Unsupported trigger type"); 41 | } 42 | 43 | /** 44 | * Create an instance of Trigger from AVS getWorkflow or getWorkflows response 45 | * @param trigger 46 | * @returns 47 | */ 48 | static fromResponse(raw: avs_pb.TaskTrigger): Trigger { 49 | switch (true) { 50 | case !!raw.getFixedTime(): 51 | return FixedTimeTrigger.fromResponse(raw); 52 | case !!raw.getCron(): 53 | return CronTrigger.fromResponse(raw); 54 | case !!raw.getBlock(): 55 | return BlockTrigger.fromResponse(raw); 56 | case !!raw.getEvent(): 57 | return EventTrigger.fromResponse(raw); 58 | case !!raw.getManual(): 59 | return ManualTrigger.fromResponse(raw); 60 | default: 61 | throw new Error("Unknown trigger type"); 62 | } 63 | } 64 | 65 | static fromOutputData(outputData: avs_pb.RunTriggerResp): any { 66 | // Delegate to the specific trigger type's fromOutputData method 67 | switch (outputData.getOutputDataCase()) { 68 | case avs_pb.RunTriggerResp.OutputDataCase.BLOCK_TRIGGER: 69 | return BlockTrigger.fromOutputData(outputData); 70 | case avs_pb.RunTriggerResp.OutputDataCase.FIXED_TIME_TRIGGER: 71 | return FixedTimeTrigger.fromOutputData(outputData); 72 | case avs_pb.RunTriggerResp.OutputDataCase.CRON_TRIGGER: 73 | return CronTrigger.fromOutputData(outputData); 74 | case avs_pb.RunTriggerResp.OutputDataCase.EVENT_TRIGGER: 75 | return EventTrigger.fromOutputData(outputData); 76 | case avs_pb.RunTriggerResp.OutputDataCase.MANUAL_TRIGGER: 77 | return ManualTrigger.fromOutputData(outputData); 78 | case avs_pb.RunTriggerResp.OutputDataCase.OUTPUT_DATA_NOT_SET: 79 | default: 80 | return null; 81 | } 82 | } 83 | } 84 | 85 | export default TriggerFactory; 86 | 87 | // Trigger object definitions 88 | export { 89 | Trigger, 90 | BlockTrigger, 91 | CronTrigger, 92 | EventTrigger, 93 | FixedTimeTrigger, 94 | ManualTrigger, 95 | }; 96 | 97 | // Trigger Props definitions 98 | export type { 99 | TriggerProps, 100 | BlockTriggerProps, 101 | CronTriggerProps, 102 | EventTriggerProps, 103 | FixedTimeTriggerProps, 104 | ManualTriggerProps, 105 | }; 106 | -------------------------------------------------------------------------------- /packages/sdk-js/src/models/trigger/fixedTime.ts: -------------------------------------------------------------------------------- 1 | import * as avs_pb from "@/grpc_codegen/avs_pb"; 2 | import Trigger from "./interface"; 3 | import { TriggerType, FixedTimeTriggerDataType, FixedTimeTriggerOutput, FixedTimeTriggerProps, TriggerProps, TriggerOutput } from "@avaprotocol/types"; 4 | import { convertInputToProtobuf, extractInputFromProtobuf } from "../../utils"; 5 | 6 | // Required props for constructor: id, name, type and data: { epochsList } 7 | 8 | 9 | class FixedTimeTrigger extends Trigger { 10 | constructor(props: FixedTimeTriggerProps) { 11 | super({ ...props, type: TriggerType.FixedTime, data: props.data }); 12 | } 13 | 14 | toRequest(): avs_pb.TaskTrigger { 15 | const request = new avs_pb.TaskTrigger(); 16 | request.setName(this.name); 17 | request.setId(this.id); 18 | request.setType(avs_pb.TriggerType.TRIGGER_TYPE_FIXED_TIME); 19 | 20 | if (!this.data) { 21 | throw new Error(`Trigger data is missing for ${this.type}`); 22 | } 23 | 24 | const trigger = new avs_pb.FixedTimeTrigger(); 25 | const config = new avs_pb.FixedTimeTrigger.Config(); 26 | config.setEpochsList((this.data as FixedTimeTriggerDataType).epochsList || []); 27 | trigger.setConfig(config); 28 | 29 | // Convert input field to protobuf format and set on FixedTimeTrigger 30 | const inputValue = convertInputToProtobuf(this.input); 31 | if (inputValue) { 32 | trigger.setInput(inputValue); 33 | } 34 | 35 | request.setFixedTime(trigger); 36 | 37 | return request; 38 | } 39 | 40 | static fromResponse(raw: avs_pb.TaskTrigger): FixedTimeTrigger { 41 | // Convert the raw object to TriggerProps, which should keep name and id 42 | const obj = raw.toObject() as unknown as TriggerProps; 43 | 44 | let data: FixedTimeTriggerDataType = { epochsList: [] }; 45 | let input: Record | undefined = undefined; 46 | 47 | if (raw.getFixedTime() && raw.getFixedTime()!.hasConfig()) { 48 | const config = raw.getFixedTime()!.getConfig(); 49 | 50 | if (config) { 51 | data = { 52 | epochsList: config.getEpochsList() || [] 53 | }; 54 | } 55 | 56 | // Extract input data if present 57 | if (raw.getFixedTime()!.hasInput()) { 58 | input = extractInputFromProtobuf(raw.getFixedTime()!.getInput()); 59 | } 60 | } 61 | 62 | return new FixedTimeTrigger({ 63 | ...obj, 64 | type: TriggerType.FixedTime, 65 | data: data, 66 | input: input, 67 | }); 68 | } 69 | 70 | /** 71 | * Convert raw data from runTrigger response to FixedTimeOutput format 72 | * @param rawData - The raw data from the gRPC response 73 | * @returns {FixedTimeTriggerOutput | undefined} - The converted data 74 | */ 75 | getOutput(): FixedTimeTriggerOutput | undefined { 76 | return this.output as FixedTimeTriggerOutput; 77 | } 78 | 79 | /** 80 | * Extract output data from RunTriggerResp for fixed time triggers 81 | * Updated to handle timestamp and timestamp_iso instead of epoch 82 | * @param outputData - The RunTriggerResp containing fixed time trigger output 83 | * @returns Plain JavaScript object with fixed time trigger data 84 | */ 85 | static fromOutputData(outputData: avs_pb.RunTriggerResp): any { 86 | const fixedTimeOutput = outputData.getFixedTimeTrigger(); 87 | if (!fixedTimeOutput) return null; 88 | 89 | const outputObj = fixedTimeOutput.toObject(); 90 | // The output now contains timestamp and timestampIso instead of epoch 91 | return { 92 | timestamp: outputObj.timestamp, 93 | timestampIso: outputObj.timestampIso 94 | }; 95 | } 96 | } 97 | 98 | export default FixedTimeTrigger; 99 | -------------------------------------------------------------------------------- /packages/sdk-js/src/models/trigger/interface.ts: -------------------------------------------------------------------------------- 1 | import * as avs_pb from "@/grpc_codegen/avs_pb"; 2 | import * as google_protobuf_struct_pb from "google-protobuf/google/protobuf/struct_pb"; 3 | import { 4 | TriggerType, 5 | TriggerTypeGoConverter, 6 | TriggerTypeConverter, 7 | TriggerProps, 8 | TriggerData, 9 | TriggerOutput, 10 | } from "@avaprotocol/types"; 11 | import _ from "lodash"; 12 | import { extractInputFromProtobuf } from "../../utils"; 13 | 14 | export default abstract class Trigger implements TriggerProps { 15 | id: string; 16 | name: string; 17 | type: TriggerType; 18 | data: TriggerData; 19 | output?: TriggerOutput; 20 | input?: Record; 21 | 22 | /** 23 | * Create an instance of Trigger from user inputs 24 | * @param props 25 | */ 26 | constructor(props: TriggerProps) { 27 | this.id = props.id; 28 | this.name = props.name; 29 | this.type = props.type; 30 | this.data = props.data; 31 | this.output = props.output; 32 | this.input = props.input; 33 | } 34 | 35 | toRequest(): avs_pb.TaskTrigger { 36 | throw new Error("Method not implemented."); 37 | } 38 | 39 | getOutput(): TriggerOutput | undefined { 40 | return this.output; 41 | } 42 | 43 | toJson(): TriggerProps { 44 | return { 45 | id: this.id, 46 | name: this.name, 47 | type: this.type, 48 | data: this.data, 49 | output: this.output, 50 | input: this.input, 51 | }; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/sdk-js/src/models/trigger/manual.ts: -------------------------------------------------------------------------------- 1 | import * as avs_pb from "@/grpc_codegen/avs_pb"; 2 | import Trigger from "./interface"; 3 | import { convertInputToProtobuf, extractInputFromProtobuf } from "../../utils"; 4 | import { TriggerType, ManualTriggerOutput, ManualTriggerProps, TriggerProps } from "@avaprotocol/types"; 5 | 6 | 7 | 8 | class ManualTrigger extends Trigger { 9 | constructor(props: ManualTriggerProps) { 10 | super({ ...props, type: TriggerType.Manual, data: props.data || null }); 11 | } 12 | 13 | toRequest(): avs_pb.TaskTrigger { 14 | const trigger = new avs_pb.TaskTrigger(); 15 | trigger.setId(this.id); 16 | trigger.setName(this.name); 17 | trigger.setType(avs_pb.TriggerType.TRIGGER_TYPE_MANUAL); 18 | 19 | trigger.setManual(true); 20 | 21 | // Convert input field to protobuf format and set on top-level TaskTrigger 22 | // Manual triggers use the top-level input field since they don't have nested structure 23 | const inputValue = convertInputToProtobuf(this.input); 24 | if (inputValue) { 25 | trigger.setInput(inputValue); 26 | } 27 | 28 | return trigger; 29 | } 30 | 31 | static fromResponse(raw: avs_pb.TaskTrigger): ManualTrigger { 32 | const obj = raw.toObject() as unknown as TriggerProps; 33 | 34 | // Extract input from top-level TaskTrigger input field for manual triggers 35 | let input: Record | undefined = undefined; 36 | if (raw.hasInput()) { 37 | input = extractInputFromProtobuf(raw.getInput()); 38 | } 39 | 40 | return new ManualTrigger({ 41 | ...obj, 42 | type: TriggerType.Manual, 43 | data: null, // Manual triggers don't have data in the protobuf response 44 | input: input, 45 | }); 46 | } 47 | 48 | getInputVariables(): Record | null { 49 | return this.data as Record | null; 50 | } 51 | 52 | /** 53 | * Convert raw data from runTrigger response to ManualOutput format 54 | * @param rawData - The raw data from the gRPC response 55 | * @returns {ManualTriggerOutput | undefined} - The converted data 56 | */ 57 | getOutput(): ManualTriggerOutput | undefined { 58 | return this.output as ManualTriggerOutput; 59 | } 60 | 61 | /** 62 | * Extract output data from RunTriggerResp for manual triggers 63 | * @param outputData - The RunTriggerResp containing manual trigger output 64 | * @returns Plain JavaScript object with manual trigger data 65 | */ 66 | static fromOutputData(outputData: avs_pb.RunTriggerResp): any { 67 | const manualOutput = outputData.getManualTrigger(); 68 | return manualOutput?.toObject() || null; 69 | } 70 | } 71 | 72 | export default ManualTrigger; -------------------------------------------------------------------------------- /packages/sdk-js/src/models/workflow.ts: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import * as avs_pb from "@/grpc_codegen/avs_pb"; 3 | import Node from "./node/interface"; 4 | import Edge from "./edge"; 5 | import Trigger from "./trigger/interface"; 6 | import TriggerFactory from "./trigger/factory"; 7 | import NodeFactory from "./node/factory"; 8 | export const DefaultExpiredAt = -1; // TODO: explain the meaning of -1 9 | import { WorkflowStatus, WorkflowProps } from "@avaprotocol/types"; 10 | 11 | // Function to convert TaskStatus to string 12 | export function convertStatusToString( 13 | status: avs_pb.TaskStatus 14 | ): WorkflowStatus { 15 | const conversionMap: { [key in avs_pb.TaskStatus]: WorkflowStatus } = { 16 | [avs_pb.TaskStatus.ACTIVE]: WorkflowStatus.Active, 17 | [avs_pb.TaskStatus.COMPLETED]: WorkflowStatus.Completed, 18 | [avs_pb.TaskStatus.FAILED]: WorkflowStatus.Failed, 19 | [avs_pb.TaskStatus.CANCELED]: WorkflowStatus.Canceled, 20 | [avs_pb.TaskStatus.EXECUTING]: WorkflowStatus.Executing, 21 | }; 22 | 23 | return conversionMap[status] as WorkflowStatus; 24 | } 25 | 26 | class Workflow implements WorkflowProps { 27 | smartWalletAddress: string; 28 | trigger: Trigger; 29 | nodes: Node[]; 30 | edges: Edge[]; 31 | startAt: number; 32 | expiredAt: number; 33 | maxExecution: number; 34 | 35 | // Optional fields 36 | id?: string; 37 | owner?: string; 38 | name?: string; 39 | completedAt?: number; 40 | status?: WorkflowStatus; 41 | lastRanAt?: number; 42 | executionCount?: number; 43 | 44 | /** 45 | * Create an instance of Workflow from user inputs 46 | * @param props 47 | */ 48 | constructor(props: WorkflowProps) { 49 | if (!props.trigger) { 50 | throw new Error("Trigger is undefined in new Workflow()"); 51 | } 52 | 53 | this.smartWalletAddress = props.smartWalletAddress; 54 | this.trigger = props.trigger as any; 55 | this.nodes = props.nodes as any; 56 | this.edges = props.edges as any; 57 | this.startAt = props.startAt; 58 | this.expiredAt = props.expiredAt; 59 | this.maxExecution = props.maxExecution; 60 | this.executionCount = props.executionCount; 61 | 62 | // Optional fields 63 | this.id = props.id; 64 | this.owner = props.owner; 65 | this.name = props.name; 66 | this.status = props.status; 67 | this.completedAt = props.completedAt; 68 | this.lastRanAt = props.lastRanAt; 69 | } 70 | 71 | /** 72 | * Create an instance of Workflow from AVS getWorkflow response 73 | * @param res 74 | * @returns 75 | */ 76 | static fromResponse(obj: avs_pb.Task): Workflow { 77 | const trigger = TriggerFactory.fromResponse(obj.getTrigger()!); 78 | 79 | if (!trigger) { 80 | throw new Error("Trigger is undefined in fromResponse()"); 81 | } 82 | 83 | const nodes = _.map(obj.getNodesList(), (node) => 84 | NodeFactory.fromResponse(node) 85 | ); 86 | 87 | const edges = _.map(obj.getEdgesList(), (edge) => Edge.fromResponse(edge)); 88 | 89 | 90 | const workflow = new Workflow({ 91 | id: obj.getId(), 92 | owner: obj.getOwner(), 93 | smartWalletAddress: obj.getSmartWalletAddress(), 94 | trigger, 95 | nodes, 96 | edges, 97 | startAt: obj.getStartAt(), 98 | expiredAt: obj.getExpiredAt(), 99 | maxExecution: obj.getMaxExecution(), 100 | executionCount: obj.getExecutionCount(), 101 | name: obj.getName(), 102 | status: convertStatusToString(obj.getStatus()), 103 | completedAt: obj.getCompletedAt(), 104 | lastRanAt: obj.getLastRanAt(), 105 | }); 106 | 107 | return workflow; 108 | } 109 | 110 | /** 111 | * Create an instance of Workflow with only selected fields 112 | * @param obj 113 | */ 114 | static fromListResponse(obj: avs_pb.Task): Workflow { 115 | const trigger = TriggerFactory.fromResponse(obj.getTrigger()!); 116 | 117 | if (!trigger) { 118 | throw new Error("Trigger is undefined in fromListResponse()"); 119 | } 120 | 121 | return new Workflow({ 122 | id: obj.getId(), 123 | owner: obj.getOwner(), 124 | smartWalletAddress: obj.getSmartWalletAddress(), 125 | trigger: trigger, 126 | startAt: obj.getStartAt(), 127 | expiredAt: obj.getExpiredAt(), 128 | maxExecution: obj.getMaxExecution(), 129 | executionCount: obj.getExecutionCount(), 130 | nodes: _.map(obj.getNodesList(), (node) => NodeFactory.fromResponse(node)), 131 | edges: _.map(obj.getEdgesList(), (edge) => Edge.fromResponse(edge)), 132 | completedAt: obj.getCompletedAt(), 133 | status: convertStatusToString(obj.getStatus()), 134 | name: obj.getName(), 135 | lastRanAt: obj.getLastRanAt(), 136 | }); 137 | } 138 | 139 | toRequest(): avs_pb.CreateTaskReq { 140 | const request = new avs_pb.CreateTaskReq(); 141 | 142 | // TODO: add client side validation for each field 143 | request.setSmartWalletAddress(this.smartWalletAddress); 144 | 145 | request.setTrigger(this.trigger.toRequest()); 146 | 147 | // Add error handling for node serialization 148 | try { 149 | _.map(this.nodes, (node, index) => { 150 | try { 151 | const nodeRequest = node.toRequest(); 152 | request.addNodes(nodeRequest); 153 | } catch (nodeError) { 154 | console.error(`🚨 Node ${index} (${node.name}) serialization failed:`, nodeError); 155 | const errorMessage = nodeError instanceof Error ? nodeError.message : String(nodeError); 156 | throw new Error(`Node serialization failed: ${node.name} - ${errorMessage}`); 157 | } 158 | }); 159 | } catch (nodesError) { 160 | console.error('🚨 Nodes serialization failed:', nodesError); 161 | throw nodesError; 162 | } 163 | 164 | // Add error handling for edge serialization 165 | try { 166 | _.map(this.edges, (edge, index) => { 167 | try { 168 | const edgeRequest = edge.toRequest(); 169 | request.addEdges(edgeRequest); 170 | } catch (edgeError) { 171 | console.error(`🚨 Edge ${index} serialization failed:`, edgeError); 172 | const errorMessage = edgeError instanceof Error ? edgeError.message : String(edgeError); 173 | throw new Error(`Edge serialization failed: ${edge.id} - ${errorMessage}`); 174 | } 175 | }); 176 | } catch (edgesError) { 177 | console.error('🚨 Edges serialization failed:', edgesError); 178 | throw edgesError; 179 | } 180 | 181 | request.setStartAt(this.startAt); 182 | request.setExpiredAt(this.expiredAt); 183 | request.setMaxExecution(this.maxExecution); 184 | 185 | // Optional fields 186 | if (this.name) { 187 | request.setName(this.name); 188 | } 189 | 190 | // Log final summary for debugging (only errors and counts) 191 | console.log(`📤 Workflow serialization: ${this.nodes.length} nodes, ${this.edges.length} edges -> protobuf: ${request.getNodesList().length} nodes, ${request.getEdgesList().length} edges`); 192 | 193 | return request; 194 | } 195 | 196 | /** 197 | * Convert Workflow instance to plain object (WorkflowProps) 198 | * This is useful for serialization, especially with Next.js Server Components 199 | */ 200 | toJson(): WorkflowProps { 201 | return { 202 | id: this.id, 203 | owner: this.owner, 204 | smartWalletAddress: this.smartWalletAddress, 205 | trigger: this.trigger.toJson(), 206 | nodes: this.nodes as any, 207 | edges: this.edges as any, 208 | startAt: this.startAt, 209 | expiredAt: this.expiredAt, 210 | maxExecution: this.maxExecution, 211 | executionCount: this.executionCount, 212 | name: this.name, 213 | status: this.status, 214 | completedAt: this.completedAt, 215 | lastRanAt: this.lastRanAt, 216 | }; 217 | } 218 | } 219 | 220 | export default Workflow; 221 | -------------------------------------------------------------------------------- /packages/sdk-js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true, 9 | "typeRoots": ["../../node_modules/@types", "./node_modules/@types"], 10 | "types": ["google-protobuf"], 11 | "baseUrl": ".", 12 | "paths": { 13 | "@/grpc_codegen/*": ["../../grpc_codegen/*"] 14 | }, 15 | "outDir": "./dist", 16 | "rootDir": "./src", 17 | "composite": true, 18 | "declaration": true, 19 | "declarationMap": true, 20 | "declarationDir": "./dist" 21 | }, 22 | "references": [{ "path": "../types" }], 23 | "include": ["src/**/*"] 24 | } 25 | -------------------------------------------------------------------------------- /packages/types/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @avaprotocol/types 2 | 3 | ## 2.2.16 4 | 5 | ### Patch Changes 6 | 7 | - 52b44d6: Update Secret and Cancel, Delete Workflow responses 8 | 9 | ## 2.2.15 10 | 11 | ### Patch Changes 12 | 13 | - 6650be6: Update Cancel and Delete operation response 14 | 15 | ## 2.2.14 16 | 17 | ### Patch Changes 18 | 19 | - 264c1c8: Renovated ContractRead and ContractWrite 20 | 21 | ## 2.2.13 22 | 23 | ### Patch Changes 24 | 25 | - 0dfcf69: Added EventCondition, and unified contractAbi to string type 26 | 27 | ## 2.2.12 28 | 29 | ### Patch Changes 30 | 31 | - 27551e0: Added EventCondition to EventTrigger 32 | 33 | ## 2.2.11 34 | 35 | ### Patch Changes 36 | 37 | - 272c26e: Fixed types package include protobuf library issue 38 | 39 | ## 2.2.9 40 | 41 | ### Patch Changes 42 | 43 | - 72339c5: Updated and tested loop functions; test empty data returned 44 | 45 | ## 2.2.8 46 | 47 | ### Patch Changes 48 | 49 | - 36ac928: Replace the old value getScheduleList to getSchedulesList 50 | 51 | ## 2.2.7 52 | 53 | ### Patch Changes 54 | 55 | - 31667f8: Fix CustomCodeLang to match enum value 56 | 57 | ## 2.2.6 58 | 59 | ### Patch Changes 60 | 61 | - 75c7865: Update types to use correct enum value for CustomCode 62 | 63 | ## 2.2.5 64 | 65 | ### Patch Changes 66 | 67 | - b5b1eff: Convert lang of CustomCode to use protobuf value 68 | 69 | ## 2.2.4 70 | 71 | ### Patch Changes 72 | 73 | - 9c7c1e0: Updated CronTrigger’s scheduleList to schedules 74 | 75 | ## 2.2.3 76 | 77 | ### Patch Changes 78 | 79 | - 38ade5e: Update CustomCodeLange 80 | 81 | ## 2.2.2 82 | 83 | ### Patch Changes 84 | 85 | - 2baed44: pdate CustomCodeLang from Javascript to JavaScript to match server string 86 | 87 | ## 2.2.1 88 | 89 | ### Patch Changes 90 | 91 | - 07c05b4: Changed CustomCodeLang value from 0 to Javascript 92 | 93 | ## 2.2.0 94 | 95 | ### Minor Changes 96 | 97 | - c45a2fb: Complete field name standardization and remove backward compatibility 98 | 99 | ## 2.1.0 100 | 101 | ### Minor Changes 102 | 103 | - 52a1d86: Added timeout option and strategy to the client 104 | 105 | ## 2.0.2 106 | 107 | ### Patch Changes 108 | 109 | - 8e09a20: Fix the request type of SimulateWorkflow and RunTriggerReq 110 | 111 | ## 2.0.1 112 | 113 | ### Patch Changes 114 | 115 | - 45212f0: Added GetTokenMetadata function to retrieve whitelist token information 116 | 117 | ## 2.0.0 118 | 119 | ### Major Changes 120 | 121 | - cfbf435: Migrated type definitions for props into types package 122 | 123 | ## 1.1.1 124 | 125 | ### Patch Changes 126 | 127 | - 18a2273: Fix types package exported grpc libary; rename simulateTask to simulateWorkflow 128 | 129 | ## 1.1.0 130 | 131 | ### Minor Changes 132 | 133 | - a7578b6: Added SimulateWorkflow function and updated protobuf 134 | 135 | ## 1.0.8 136 | 137 | ### Patch Changes 138 | 139 | - 0bd6c9f: Flatten out and removed TriggerReason in Execution 140 | 141 | ## 1.0.7 142 | 143 | ### Patch Changes 144 | 145 | - ca68e9c: Updated output data to Value type for json and runTrigger logic 146 | 147 | ## 1.0.6 148 | 149 | ### Patch Changes 150 | 151 | - 491ae98: Added runTrigger method and refactored enums 152 | 153 | ## 1.0.5 154 | 155 | ### Patch Changes 156 | 157 | - bda8a74: Unified the node type conversion code and values by updating protobuf 158 | 159 | ## 1.0.4 160 | 161 | ### Patch Changes 162 | 163 | - fa76ec5: Moved node DataType from sdk-js to types pacakge 164 | 165 | ## 1.0.3 166 | 167 | ### Patch Changes 168 | 169 | - 6e9069f: Make type definitions more consistent 170 | 171 | ## 1.0.2 172 | 173 | ### Patch Changes 174 | 175 | - b2db42f: Update getSecrets to use general pageInfo 176 | 177 | ## 1.0.1 178 | 179 | ### Patch Changes 180 | 181 | - a38f8df: Consolidated pagination response and match protobuf of AVS v1.8.2 182 | 183 | ## 1.0.0 184 | 185 | ### Major Changes 186 | 187 | - cdecf79: Get sign message from server; added wallet isHidden 188 | 189 | ## 0.9.4 190 | 191 | ### Patch Changes 192 | 193 | - Migrated getKeyRequestMessage to types; Added secret functions 194 | 195 | ## 0.9.3 196 | 197 | ### Patch Changes 198 | 199 | - Updated Task.memo to Task.name and fixed a build warning in types package 200 | 201 | ## 0.9.2 202 | 203 | ### Patch Changes 204 | 205 | - Updated with the latest AVS data 206 | 207 | ## 0.9.1 208 | 209 | ### Patch Changes 210 | 211 | - Initialize types package 212 | -------------------------------------------------------------------------------- /packages/types/README.md: -------------------------------------------------------------------------------- 1 | # Ava SDK for JavaScript/TypeScript 2 | 3 | `@avaprotocol/types` is the TypeScript types for the `@avaprotocol/sdk-js` library and is intended to be used on the client side of a web app to interpret data. 4 | 5 | ## Installation 6 | 7 | To install `@avaprotocol/types`, run the following command: 8 | 9 | ```bash 10 | npm install @avaprotocol/types 11 | ``` 12 | 13 | Or with Yarn: 14 | 15 | ```bash 16 | yarn add @avaprotocol/types 17 | ``` 18 | 19 | ## Getting Started 20 | 21 | Here’s a quick example of how to use the types library to get started with Ava Protocol: 22 | 23 | ```typescript 24 | import { WorkflowStatus, TriggerType } from "@avaprotocol/types"; 25 | 26 | // Check conditions such as workflow status 27 | if (workflow.status === WorkflowStatus.Active) { 28 | // ... 29 | // your logic here 30 | // ... 31 | } 32 | ``` 33 | 34 | ## Contributing 35 | 36 | We welcome contributions! Feel free to submit pull requests or open issues for any bugs or feature requests. 37 | 38 | ## License 39 | 40 | This project is licensed under the Apache 2.0 License. See the LICENSE file for more details. 41 | -------------------------------------------------------------------------------- /packages/types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@avaprotocol/types", 3 | "version": "2.2.16", 4 | "description": "Type definitions for Ava Protocol's AVS", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "files": [ 8 | "dist", 9 | "README.md", 10 | "CHANGELOG.md", 11 | "package.json" 12 | ], 13 | "exports": { 14 | "types": "./dist/index.d.ts", 15 | "import": "./dist/index.js", 16 | "require": "./dist/index.js" 17 | }, 18 | "author": "Vinh Nguyen (https://github.com/v9n), Chris Li (https://github.com/chrisli30)", 19 | "license": "Apache-2.0", 20 | "engines": { 21 | "node": ">=20.18.0", 22 | "yarn": ">=1.22.19" 23 | }, 24 | "scripts": { 25 | "build:declarations": "tsc -b tsconfig.json", 26 | "build:js": "tsup src/index.ts --format cjs,esm", 27 | "build": "yarn build:declarations && yarn build:js", 28 | "clean": "rm -rf node_modules dist tsconfig.tsbuildinfo", 29 | "prepare": "node ../../scripts/prepare-package.js" 30 | }, 31 | "dependencies": { 32 | "typescript": "^5.4.2" 33 | }, 34 | "devDependencies": { 35 | "@types/google-protobuf": "^3.15.12" 36 | }, 37 | "publishConfig": { 38 | "access": "public" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/types/src/api.ts: -------------------------------------------------------------------------------- 1 | import { NodeProps } from "./node"; 2 | import { TriggerProps } from "./trigger"; 3 | import { EdgeProps } from "./workflow"; 4 | 5 | export interface GetWalletRequest { 6 | salt: string; 7 | factoryAddress?: string; 8 | } 9 | 10 | export interface ClientOption { 11 | endpoint: string; 12 | factoryAddress?: string; 13 | timeout?: TimeoutConfig; 14 | } 15 | 16 | /** 17 | * Timeout configuration options for gRPC requests 18 | */ 19 | export interface TimeoutConfig { 20 | /** Request timeout in milliseconds (default: 30000) */ 21 | timeout?: number; 22 | /** Maximum number of retry attempts (default: 3) */ 23 | retries?: number; 24 | /** Delay between retries in milliseconds (default: 1000) */ 25 | retryDelay?: number; 26 | } 27 | 28 | /** 29 | * Predefined timeout presets for common use cases 30 | */ 31 | export const TimeoutPresets = { 32 | /** 5s timeout, 2 retries, 500ms delay - for quick operations */ 33 | FAST: { timeout: 5000, retries: 2, retryDelay: 500 } as TimeoutConfig, 34 | /** 30s timeout, 3 retries, 1s delay - for normal operations */ 35 | NORMAL: { timeout: 30000, retries: 3, retryDelay: 1000 } as TimeoutConfig, 36 | /** 2min timeout, 2 retries, 2s delay - for heavy operations */ 37 | SLOW: { timeout: 120000, retries: 2, retryDelay: 2000 } as TimeoutConfig, 38 | /** 30s timeout, no retries - fail-fast for latency-sensitive operations */ 39 | NO_RETRY: { timeout: 30000, retries: 0, retryDelay: 0 } as TimeoutConfig, 40 | } as const; 41 | 42 | export interface RequestOptions { 43 | authKey?: string; 44 | timeout?: TimeoutConfig; 45 | } 46 | 47 | /** 48 | * Enhanced error with timeout context 49 | */ 50 | export interface TimeoutError extends Error { 51 | isTimeout: boolean; 52 | attemptsMade: number; 53 | methodName: string; 54 | } 55 | 56 | export interface GetExecutionsOptions extends RequestOptions { 57 | before?: string; 58 | after?: string; 59 | limit?: number; 60 | } 61 | export interface GetWorkflowsOptions extends RequestOptions { 62 | before?: string; 63 | after?: string; 64 | limit?: number; 65 | includeNodes?: boolean; 66 | includeEdges?: boolean; 67 | } 68 | export interface GetSecretsOptions extends RequestOptions { 69 | workflowId?: string; 70 | orgId?: string; 71 | before?: string; 72 | after?: string; 73 | limit?: number; 74 | // Field control options for flexible response content 75 | includeTimestamps?: boolean; // Include created_at and updated_at fields 76 | includeCreatedBy?: boolean; // Include created_by field 77 | includeDescription?: boolean; // Include description field 78 | } 79 | export interface SecretOptions extends RequestOptions { 80 | name: string; 81 | value: string; 82 | workflowId?: string; 83 | orgId?: string; 84 | } 85 | 86 | // New structured response types based on updated proto definitions 87 | export interface CreateSecretResponse { 88 | success: boolean; 89 | status: string; // "created", "already_exists", "error" 90 | message: string; 91 | createdAt?: number; // Unix timestamp when secret was created 92 | secretName: string; 93 | scope: string; // "user", "workflow", "org" 94 | } 95 | 96 | export interface UpdateSecretResponse { 97 | success: boolean; 98 | status: string; // "updated", "not_found", "error" 99 | message: string; 100 | updatedAt?: number; // Unix timestamp when secret was updated 101 | secretName: string; 102 | scope: string; // "user", "workflow", "org" 103 | } 104 | 105 | export interface DeleteSecretResponse { 106 | success: boolean; 107 | status: string; // "deleted", "not_found", "already_deleted" 108 | message: string; 109 | deletedAt?: number; // Unix timestamp when secret was deleted 110 | secretName: string; 111 | scope: string; // "user", "workflow", "org" 112 | } 113 | 114 | export interface CancelTaskResponse { 115 | success: boolean; 116 | status: string; // "cancelled", "not_found", "already_cancelled", "cannot_cancel" 117 | message: string; 118 | cancelledAt?: number; // Unix timestamp when task was cancelled 119 | id: string; 120 | previousStatus: string; // Previous status before cancellation 121 | } 122 | 123 | export interface DeleteTaskResponse { 124 | success: boolean; 125 | status: string; // "deleted", "not_found", "cannot_delete" 126 | message: string; 127 | deletedAt?: number; // Unix timestamp when task was deleted 128 | id: string; 129 | previousStatus: string; // Previous status before deletion 130 | } 131 | 132 | // New execution statistics response 133 | export interface GetExecutionStatsResponse { 134 | total: number; // Total number of executions 135 | succeeded: number; // Number of successful executions 136 | failed: number; // Number of failed executions 137 | avgExecutionTime: number; // Average execution time in milliseconds 138 | } 139 | 140 | export interface GetExecutionStatsOptions extends RequestOptions { 141 | workflowIds?: string[]; // Optional array of workflow IDs 142 | days?: number; // Number of days to look back (default: 7) 143 | } 144 | 145 | export interface RunNodeWithInputsRequest { 146 | nodeType: string; 147 | nodeConfig: Record; 148 | inputVariables?: Record; 149 | } 150 | 151 | /** 152 | * Comprehensive type for node output data that handles all possible return types 153 | * 154 | * This type represents the actual data returned by nodes after execution. 155 | * The data can be: 156 | * - Primitive values (string, number, boolean, null) - especially from CustomCode nodes 157 | * - Objects (Record) - from REST API responses, structured data 158 | * - Arrays (any[]) - from Filter nodes, loop results, etc. 159 | * - null - for empty results (protobuf limitation: cannot be undefined) 160 | * 161 | * Note: Due to protobuf limitations, undefined is not supported and will be converted to null. 162 | * This type is designed to be compatible with the existing OutputDataProps type used in workflow executions. 163 | */ 164 | export type NodeOutputData = 165 | | string 166 | | number 167 | | boolean 168 | | null 169 | | Record 170 | | any[]; 171 | 172 | /** 173 | * Comprehensive type for trigger output data that handles all possible return types 174 | * 175 | * This type represents the actual data returned by triggers after execution. 176 | * The data can be: 177 | * - Primitive values (string, number, boolean, null) - from various trigger types 178 | * - Objects (Record) - from structured trigger data 179 | * - null - for empty results (protobuf limitation: cannot be undefined) 180 | * 181 | * Note: Due to protobuf limitations, undefined is not supported and will be converted to null. 182 | * This type is designed to be compatible with the existing OutputDataProps type used in workflow executions. 183 | */ 184 | export type TriggerOutputData = 185 | | string 186 | | number 187 | | boolean 188 | | null 189 | | Record; 190 | 191 | export interface RunNodeWithInputsResponse { 192 | success: boolean; 193 | data: NodeOutputData; 194 | error?: string; 195 | executionId?: string; 196 | nodeId?: string; 197 | metadata?: { 198 | _raw?: any[]; // Array of raw method results for contract reads 199 | eval?: any; // Evaluation metadata (for consistency with eventTrigger) 200 | [key: string]: any; // Allow additional metadata fields for other node types 201 | }; 202 | } 203 | 204 | export interface RunTriggerRequest { 205 | triggerType: string; 206 | triggerConfig: Record; 207 | } 208 | 209 | export interface RunTriggerResponse { 210 | success: boolean; 211 | data: TriggerOutputData; 212 | error?: string; 213 | triggerId?: string; 214 | metadata?: string; // Optional JSON-encoded metadata for testing/debugging 215 | } 216 | 217 | export interface SimulateWorkflowRequest { 218 | trigger: TriggerProps; 219 | nodes: Array; 220 | edges: Array; 221 | inputVariables?: Record; 222 | } 223 | -------------------------------------------------------------------------------- /packages/types/src/auth.ts: -------------------------------------------------------------------------------- 1 | export type GetSignatureFormatRequest = { 2 | wallet: string; 3 | }; 4 | 5 | export interface GetSignatureFormatResponse { 6 | message: string; 7 | } 8 | 9 | // Common interface for all get authKey requests (legacy) 10 | export type GetKeyRequestMessage = { 11 | chainId: number; 12 | address: string; 13 | issuedAt: Date; 14 | expiredAt: Date; 15 | }; 16 | 17 | // Get authKey request with signature 18 | export interface GetKeyRequestSignature { 19 | message: string; 20 | signature: string; 21 | } 22 | 23 | // Get authKey request with apiKey 24 | export interface GetKeyRequestApiKey { 25 | message: string; 26 | apiKey: string; 27 | } 28 | 29 | export interface GetKeyResponse { 30 | authKey: string; 31 | } 32 | -------------------------------------------------------------------------------- /packages/types/src/enums.ts: -------------------------------------------------------------------------------- 1 | export enum WorkflowStatus { 2 | Active = "active", 3 | Completed = "completed", 4 | Failed = "failed", 5 | Canceled = "canceled", 6 | Executing = "executing", 7 | } 8 | 9 | export enum TriggerType { 10 | Unspecified = "unspecified", 11 | Manual = "manualTrigger", 12 | FixedTime = "fixedTimeTrigger", 13 | Cron = "cronTrigger", 14 | Block = "blockTrigger", 15 | Event = "eventTrigger", 16 | } 17 | 18 | export enum NodeType { 19 | Unspecified = "unspecified", 20 | ETHTransfer = "ethTransfer", 21 | ContractWrite = "contractWrite", 22 | ContractRead = "contractRead", 23 | GraphQLQuery = "graphql", 24 | RestAPI = "restApi", 25 | CustomCode = "customCode", 26 | Branch = "branch", 27 | Filter = "filter", 28 | Loop = "loop", 29 | } -------------------------------------------------------------------------------- /packages/types/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./auth"; 2 | export * from "./enums"; 3 | export * from "./node"; 4 | export * from "./token"; 5 | export * from "./trigger"; 6 | export * from "./workflow"; 7 | export * from "./api"; 8 | export * from "./shared"; 9 | 10 | // Export the new structured response types 11 | export type { 12 | CreateSecretResponse, 13 | UpdateSecretResponse, 14 | DeleteSecretResponse, 15 | CancelTaskResponse, 16 | DeleteTaskResponse, 17 | GetExecutionStatsResponse, 18 | GetExecutionStatsOptions 19 | } from "./api"; 20 | -------------------------------------------------------------------------------- /packages/types/src/node.ts: -------------------------------------------------------------------------------- 1 | import * as avs_pb from "@/grpc_codegen/avs_pb"; 2 | import { NodeType } from "./enums"; 3 | 4 | // Node DataTypes 5 | export type ETHTransferNodeData = avs_pb.ETHTransferNode.Config.AsObject; 6 | // Custom ContractWrite data type with cleaner field names 7 | export interface ContractWriteNodeData { 8 | contractAddress: string; 9 | callData: string; 10 | contractAbi: string; // Contract ABI as string 11 | methodCalls?: Array<{ 12 | callData: string; 13 | methodName?: string; 14 | }>; 15 | } 16 | 17 | // Custom ContractRead data type with cleaner field names 18 | export interface ContractReadNodeData { 19 | contractAddress: string; 20 | contractAbi: string; // Contract ABI as string 21 | methodCalls: Array<{ 22 | callData: string; 23 | methodName?: string; 24 | applyToFields?: string[]; // Fields to apply decimal formatting to 25 | }>; 26 | } 27 | 28 | // Custom CustomCode data type with cleaner field names 29 | export interface CustomCodeNodeData { 30 | lang: CustomCodeLang; 31 | source: string; 32 | } 33 | 34 | // Custom BranchNode data type with cleaner field names 35 | export interface BranchNodeData { 36 | conditions: Array<{ 37 | id: string; 38 | type: string; 39 | expression: string; 40 | }>; 41 | } 42 | 43 | export type RestAPINodeData = avs_pb.RestAPINode.Config.AsObject; 44 | export type GraphQLQueryNodeData = avs_pb.GraphQLQueryNode.Config.AsObject; 45 | export type FilterNodeData = avs_pb.FilterNode.Config.AsObject; 46 | export type LoopNodeData = avs_pb.LoopNode.Config.AsObject & { 47 | // Optional runner nodes - only one should be present 48 | restApi?: { 49 | config: avs_pb.RestAPINode.Config.AsObject; 50 | }; 51 | customCode?: { 52 | config: { 53 | lang: CustomCodeLang; 54 | source: string; 55 | }; 56 | }; 57 | ethTransfer?: { 58 | config: avs_pb.ETHTransferNode.Config.AsObject; 59 | }; 60 | contractRead?: { 61 | config: avs_pb.ContractReadNode.Config.AsObject; 62 | }; 63 | contractWrite?: { 64 | config: avs_pb.ContractWriteNode.Config.AsObject; 65 | }; 66 | graphqlDataQuery?: { 67 | config: avs_pb.GraphQLQueryNode.Config.AsObject; 68 | }; 69 | }; 70 | 71 | // Node Output Types 72 | export type RestAPINodeOutput = avs_pb.RestAPINode.Output.AsObject; 73 | 74 | // Language enum that exactly matches protobuf enum values 75 | export enum CustomCodeLang { 76 | JavaScript = 0, // avs_pb.Lang.JAVASCRIPT 77 | } 78 | 79 | export type NodeData = 80 | | ETHTransferNodeData 81 | | ContractWriteNodeData 82 | | ContractReadNodeData 83 | | GraphQLQueryNodeData 84 | | RestAPINodeData 85 | | BranchNodeData 86 | | FilterNodeData 87 | | LoopNodeData 88 | | CustomCodeNodeData; 89 | 90 | export type NodeProps = Omit< 91 | avs_pb.TaskNode.AsObject, 92 | | "ethTransfer" 93 | | "contractWrite" 94 | | "contractRead" 95 | | "graphqlDataQuery" 96 | | "restApi" 97 | | "branch" 98 | | "filter" 99 | | "loop" 100 | | "customCode" 101 | | "type" // Exclude the protobuf type field to avoid conflict 102 | | "input" // ✨ Omit the protobuf input field 103 | > & { 104 | type: NodeType; // Use our own NodeType enum 105 | data: NodeData; 106 | input?: Record; // Simplified to plain JS object - transformation handled in SDK 107 | }; 108 | 109 | export type LoopNodeProps = NodeProps & { data: LoopNodeData }; 110 | export type ContractWriteNodeProps = NodeProps & { 111 | data: ContractWriteNodeData; 112 | }; 113 | export type ContractReadNodeProps = NodeProps & { data: ContractReadNodeData }; 114 | export type ETHTransferNodeProps = NodeProps & { data: ETHTransferNodeData }; 115 | export type RestAPINodeProps = NodeProps & { data: RestAPINodeData }; 116 | export type CustomCodeNodeProps = NodeProps & { data: CustomCodeNodeData }; 117 | export type GraphQLQueryNodeProps = NodeProps & { data: GraphQLQueryNodeData }; 118 | export type BranchNodeProps = NodeProps & { data: BranchNodeData }; 119 | export type FilterNodeProps = NodeProps & { data: FilterNodeData }; 120 | export const NodeTypeConverter = { 121 | toProtobuf: (type: NodeType): avs_pb.NodeType => { 122 | switch (type) { 123 | case NodeType.ETHTransfer: 124 | return avs_pb.NodeType.NODE_TYPE_ETH_TRANSFER; 125 | case NodeType.ContractWrite: 126 | return avs_pb.NodeType.NODE_TYPE_CONTRACT_WRITE; 127 | case NodeType.ContractRead: 128 | return avs_pb.NodeType.NODE_TYPE_CONTRACT_READ; 129 | case NodeType.GraphQLQuery: 130 | return avs_pb.NodeType.NODE_TYPE_GRAPHQL_QUERY; 131 | case NodeType.RestAPI: 132 | return avs_pb.NodeType.NODE_TYPE_REST_API; 133 | case NodeType.CustomCode: 134 | return avs_pb.NodeType.NODE_TYPE_CUSTOM_CODE; 135 | case NodeType.Branch: 136 | return avs_pb.NodeType.NODE_TYPE_BRANCH; 137 | case NodeType.Filter: 138 | return avs_pb.NodeType.NODE_TYPE_FILTER; 139 | case NodeType.Loop: 140 | return avs_pb.NodeType.NODE_TYPE_LOOP; 141 | case NodeType.Unspecified: 142 | default: 143 | return avs_pb.NodeType.NODE_TYPE_UNSPECIFIED; 144 | } 145 | }, 146 | fromProtobuf: (type: avs_pb.NodeType): NodeType => { 147 | switch (type) { 148 | case avs_pb.NodeType.NODE_TYPE_ETH_TRANSFER: 149 | return NodeType.ETHTransfer; 150 | case avs_pb.NodeType.NODE_TYPE_CONTRACT_WRITE: 151 | return NodeType.ContractWrite; 152 | case avs_pb.NodeType.NODE_TYPE_CONTRACT_READ: 153 | return NodeType.ContractRead; 154 | case avs_pb.NodeType.NODE_TYPE_GRAPHQL_QUERY: 155 | return NodeType.GraphQLQuery; 156 | case avs_pb.NodeType.NODE_TYPE_REST_API: 157 | return NodeType.RestAPI; 158 | case avs_pb.NodeType.NODE_TYPE_CUSTOM_CODE: 159 | return NodeType.CustomCode; 160 | case avs_pb.NodeType.NODE_TYPE_BRANCH: 161 | return NodeType.Branch; 162 | case avs_pb.NodeType.NODE_TYPE_FILTER: 163 | return NodeType.Filter; 164 | case avs_pb.NodeType.NODE_TYPE_LOOP: 165 | return NodeType.Loop; 166 | case avs_pb.NodeType.NODE_TYPE_UNSPECIFIED: 167 | default: 168 | return NodeType.Unspecified; 169 | } 170 | }, 171 | }; 172 | export const NodeTypeGoConverter = { 173 | toGoString: (nodeType: avs_pb.NodeType): string => { 174 | switch (nodeType) { 175 | case avs_pb.NodeType.NODE_TYPE_ETH_TRANSFER: 176 | return "ethTransfer"; 177 | case avs_pb.NodeType.NODE_TYPE_CONTRACT_WRITE: 178 | return "contractWrite"; 179 | case avs_pb.NodeType.NODE_TYPE_CONTRACT_READ: 180 | return "contractRead"; 181 | case avs_pb.NodeType.NODE_TYPE_GRAPHQL_QUERY: 182 | return "graphql"; 183 | case avs_pb.NodeType.NODE_TYPE_REST_API: 184 | return "restApi"; 185 | case avs_pb.NodeType.NODE_TYPE_CUSTOM_CODE: 186 | return "customCode"; 187 | case avs_pb.NodeType.NODE_TYPE_BRANCH: 188 | return "branch"; 189 | case avs_pb.NodeType.NODE_TYPE_FILTER: 190 | return "filter"; 191 | case avs_pb.NodeType.NODE_TYPE_LOOP: 192 | return "loop"; 193 | case avs_pb.NodeType.NODE_TYPE_UNSPECIFIED: 194 | default: 195 | return "unspecified"; 196 | } 197 | }, 198 | fromGoString: (goString: string): avs_pb.NodeType => { 199 | switch (goString) { 200 | case "ethTransfer": 201 | return avs_pb.NodeType.NODE_TYPE_ETH_TRANSFER; 202 | case "contractWrite": 203 | return avs_pb.NodeType.NODE_TYPE_CONTRACT_WRITE; 204 | case "contractRead": 205 | return avs_pb.NodeType.NODE_TYPE_CONTRACT_READ; 206 | case "graphql": 207 | return avs_pb.NodeType.NODE_TYPE_GRAPHQL_QUERY; 208 | case "restApi": 209 | return avs_pb.NodeType.NODE_TYPE_REST_API; 210 | case "customCode": 211 | return avs_pb.NodeType.NODE_TYPE_CUSTOM_CODE; 212 | case "branch": 213 | return avs_pb.NodeType.NODE_TYPE_BRANCH; 214 | case "filter": 215 | return avs_pb.NodeType.NODE_TYPE_FILTER; 216 | case "loop": 217 | return avs_pb.NodeType.NODE_TYPE_LOOP; 218 | case "unspecified": 219 | default: 220 | return avs_pb.NodeType.NODE_TYPE_UNSPECIFIED; 221 | } 222 | }, 223 | }; 224 | -------------------------------------------------------------------------------- /packages/types/src/shared.ts: -------------------------------------------------------------------------------- 1 | import * as avs_pb from "@/grpc_codegen/avs_pb"; 2 | 3 | export type Environment = "production" | "development" | "staging"; 4 | export const AUTH_KEY_HEADER = "authkey"; 5 | export const DEFAULT_LIMIT = 10; 6 | 7 | export type SmartWallet = avs_pb.SmartWallet.AsObject & { 8 | totalTaskCount?: number; 9 | activeTaskCount?: number; 10 | completedTaskCount?: number; 11 | failedTaskCount?: number; 12 | canceledTaskCount?: number; 13 | }; 14 | export interface PageInfo { 15 | startCursor: string; 16 | endCursor: string; 17 | hasPreviousPage: boolean; 18 | hasNextPage: boolean; 19 | } 20 | export type SecretProps = { 21 | name: string; 22 | secret?: string; 23 | workflowId?: string; 24 | orgId?: string; 25 | createdAt?: number; 26 | updatedAt?: number; 27 | createdBy?: string; 28 | description?: string; 29 | }; 30 | 31 | export type TriggerDataProps = { 32 | type: any; 33 | blockNumber?: number; 34 | timestamp?: number; 35 | data?: any; 36 | }; 37 | -------------------------------------------------------------------------------- /packages/types/src/token.ts: -------------------------------------------------------------------------------- 1 | // Token metadata types based on avs.proto TokenMetadata message 2 | 3 | /** 4 | * Represents ERC20 token information 5 | */ 6 | export interface TokenMetadata { 7 | /** Contract address (lowercase, normalized) */ 8 | address: string; 9 | /** Token name (e.g., "USD Coin") */ 10 | name: string; 11 | /** Token symbol (e.g., "USDC") */ 12 | symbol: string; 13 | /** Number of decimal places */ 14 | decimals: number; 15 | } 16 | 17 | /** 18 | * Request for token metadata lookup 19 | */ 20 | export interface GetTokenMetadataRequest { 21 | /** Contract address to look up */ 22 | address: string; 23 | } 24 | 25 | /** 26 | * Token source types for better type safety 27 | */ 28 | export type TokenSource = "whitelist" | "rpc" | "cache" | ""; 29 | 30 | /** 31 | * Response containing token metadata 32 | */ 33 | export interface GetTokenMetadataResponse { 34 | /** Token metadata information (null if not found) */ 35 | token: TokenMetadata | null; 36 | /** Whether the token was found */ 37 | found: boolean; 38 | /** Source of data: "whitelist", "rpc", or "cache" */ 39 | source: TokenSource; 40 | } -------------------------------------------------------------------------------- /packages/types/src/trigger.ts: -------------------------------------------------------------------------------- 1 | import * as avs_pb from "@/grpc_codegen/avs_pb"; 2 | import { TriggerType } from "./enums"; 3 | 4 | // Custom CronTrigger data type with cleaner field names 5 | export interface CronTriggerDataType { 6 | schedules: string[]; 7 | } 8 | 9 | // EventCondition interface for condition-based filtering 10 | export interface EventConditionType { 11 | fieldName: string; // Event field name (e.g., "answer", "roundId") 12 | operator: string; // Comparison operator: "gt", "gte", "lt", "lte", "eq", "ne" 13 | value: string; // Value to compare against (as string, parsed based on type) 14 | fieldType: string; // Field type: "uint256", "int256", "address", "bool", "bytes32", etc. 15 | } 16 | 17 | // MethodCall interface for enhanced event data formatting 18 | export interface MethodCallType { 19 | methodName: string; // Method name (e.g., "decimals") 20 | callData: string; // Hex-encoded calldata for the method 21 | applyToFields: string[]; // Fields to apply formatting to (e.g., ["current", "answer"]) 22 | } 23 | 24 | // Custom EventTrigger data type with cleaner field names 25 | export interface EventTriggerDataType { 26 | queries: Array<{ 27 | addresses?: string[]; 28 | topics?: Array<{ 29 | values?: string[]; 30 | }>; 31 | maxEventsPerBlock?: number; 32 | contractAbi?: string; // Contract ABI as string 33 | conditions?: EventConditionType[]; // Event conditions to evaluate on decoded event data 34 | methodCalls?: MethodCallType[]; // Method calls for enhanced formatting (e.g., decimals) 35 | }>; 36 | } 37 | export type BlockTriggerDataType = avs_pb.BlockTrigger.Config.AsObject; 38 | export type FixedTimeTriggerDataType = avs_pb.FixedTimeTrigger.Config.AsObject; 39 | 40 | export type CronTriggerOutput = avs_pb.CronTrigger.Output.AsObject; 41 | export type EventTriggerOutput = avs_pb.EventTrigger.Output.AsObject; 42 | export type BlockTriggerOutput = avs_pb.BlockTrigger.Output.AsObject; 43 | export type FixedTimeTriggerOutput = avs_pb.FixedTimeTrigger.Output.AsObject; 44 | export type ManualTriggerOutput = avs_pb.ManualTrigger.Output.AsObject; 45 | 46 | export type TriggerData = 47 | | FixedTimeTriggerDataType 48 | | CronTriggerDataType 49 | | BlockTriggerDataType 50 | | EventTriggerDataType 51 | | Record 52 | | null; 53 | 54 | export type TriggerOutput = 55 | | avs_pb.FixedTimeTrigger.Output.AsObject 56 | | avs_pb.CronTrigger.Output.AsObject 57 | | avs_pb.BlockTrigger.Output.AsObject 58 | | avs_pb.EventTrigger.Output.AsObject 59 | | avs_pb.ManualTrigger.Output.AsObject 60 | | null; 61 | 62 | export type TriggerProps = Omit< 63 | avs_pb.TaskTrigger.AsObject, 64 | "manual" | "fixedTime" | "cron" | "block" | "event" | "type" | "input" 65 | > & { 66 | type: TriggerType; 67 | data: TriggerData; 68 | output?: TriggerOutput; 69 | input?: Record; 70 | }; 71 | 72 | export type CronTriggerProps = TriggerProps & { data: CronTriggerDataType }; 73 | export type EventTriggerProps = TriggerProps & { data: EventTriggerDataType }; 74 | export type ManualTriggerProps = TriggerProps & { data?: Record | null }; 75 | export type BlockTriggerProps = TriggerProps & { data: BlockTriggerDataType }; 76 | export type FixedTimeTriggerProps = TriggerProps & { data: FixedTimeTriggerDataType }; 77 | 78 | 79 | export const TriggerTypeConverter = { 80 | toProtobuf: (type: TriggerType): avs_pb.TriggerType => { 81 | switch (type) { 82 | case TriggerType.Manual: 83 | return avs_pb.TriggerType.TRIGGER_TYPE_MANUAL; 84 | case TriggerType.FixedTime: 85 | return avs_pb.TriggerType.TRIGGER_TYPE_FIXED_TIME; 86 | case TriggerType.Cron: 87 | return avs_pb.TriggerType.TRIGGER_TYPE_CRON; 88 | case TriggerType.Block: 89 | return avs_pb.TriggerType.TRIGGER_TYPE_BLOCK; 90 | case TriggerType.Event: 91 | return avs_pb.TriggerType.TRIGGER_TYPE_EVENT; 92 | case TriggerType.Unspecified: 93 | default: 94 | return avs_pb.TriggerType.TRIGGER_TYPE_UNSPECIFIED; 95 | } 96 | }, 97 | fromProtobuf: (type: avs_pb.TriggerType): TriggerType => { 98 | switch (type) { 99 | case avs_pb.TriggerType.TRIGGER_TYPE_MANUAL: 100 | return TriggerType.Manual; 101 | case avs_pb.TriggerType.TRIGGER_TYPE_FIXED_TIME: 102 | return TriggerType.FixedTime; 103 | case avs_pb.TriggerType.TRIGGER_TYPE_CRON: 104 | return TriggerType.Cron; 105 | case avs_pb.TriggerType.TRIGGER_TYPE_BLOCK: 106 | return TriggerType.Block; 107 | case avs_pb.TriggerType.TRIGGER_TYPE_EVENT: 108 | return TriggerType.Event; 109 | case avs_pb.TriggerType.TRIGGER_TYPE_UNSPECIFIED: 110 | default: 111 | return TriggerType.Unspecified; 112 | } 113 | }, 114 | }; 115 | export const TriggerTypeGoConverter = { 116 | toGoString: (triggerType: avs_pb.TriggerType): string => { 117 | switch (triggerType) { 118 | case avs_pb.TriggerType.TRIGGER_TYPE_MANUAL: 119 | return "manualTrigger"; 120 | case avs_pb.TriggerType.TRIGGER_TYPE_FIXED_TIME: 121 | return "fixedTimeTrigger"; 122 | case avs_pb.TriggerType.TRIGGER_TYPE_CRON: 123 | return "cronTrigger"; 124 | case avs_pb.TriggerType.TRIGGER_TYPE_BLOCK: 125 | return "blockTrigger"; 126 | case avs_pb.TriggerType.TRIGGER_TYPE_EVENT: 127 | return "eventTrigger"; 128 | case avs_pb.TriggerType.TRIGGER_TYPE_UNSPECIFIED: 129 | default: 130 | return "unspecified"; 131 | } 132 | }, 133 | fromGoString: (goString: string): avs_pb.TriggerType => { 134 | switch (goString) { 135 | case "manualTrigger": 136 | return avs_pb.TriggerType.TRIGGER_TYPE_MANUAL; 137 | case "fixedTimeTrigger": 138 | return avs_pb.TriggerType.TRIGGER_TYPE_FIXED_TIME; 139 | case "cronTrigger": 140 | return avs_pb.TriggerType.TRIGGER_TYPE_CRON; 141 | case "blockTrigger": 142 | return avs_pb.TriggerType.TRIGGER_TYPE_BLOCK; 143 | case "eventTrigger": 144 | return avs_pb.TriggerType.TRIGGER_TYPE_EVENT; 145 | case "unspecified": 146 | default: 147 | return avs_pb.TriggerType.TRIGGER_TYPE_UNSPECIFIED; 148 | } 149 | }, 150 | }; 151 | -------------------------------------------------------------------------------- /packages/types/src/workflow.ts: -------------------------------------------------------------------------------- 1 | import * as avs_pb from "@/grpc_codegen/avs_pb"; 2 | import { TriggerType, WorkflowStatus } from "./enums"; 3 | import { TriggerProps } from "./trigger"; 4 | import { NodeProps } from "./node"; 5 | 6 | export type WorkflowTriggerDataProps = 7 | | { type: TriggerType.FixedTime; timestamp: number; timestampIso: string } 8 | | { type: TriggerType.Cron; timestamp: number; timestampIso: string } 9 | | { 10 | type: TriggerType.Block; 11 | blockNumber: number; 12 | blockHash?: string; 13 | timestamp?: number; 14 | parentHash?: string; 15 | difficulty?: string; 16 | gasLimit?: number; 17 | gasUsed?: number; 18 | } 19 | | { 20 | type: TriggerType.Event; 21 | data?: string | Record; // JSON string or object containing event data 22 | } 23 | | { type: TriggerType.Manual; runAt?: number } 24 | | { type: TriggerType.Unspecified }; 25 | 26 | // Edge Props 27 | export type EdgeProps = avs_pb.TaskEdge.AsObject; 28 | 29 | // Output Data Props 30 | export type OutputDataProps = 31 | | avs_pb.BlockTrigger.Output.AsObject 32 | | avs_pb.FixedTimeTrigger.Output.AsObject 33 | | avs_pb.CronTrigger.Output.AsObject 34 | | avs_pb.EventTrigger.Output.AsObject 35 | | avs_pb.ManualTrigger.Output.AsObject 36 | | { blockNumber: number } // Filtered block trigger output 37 | | { timestamp: number; timestampIso: string } // Filtered time trigger output (updated from epoch) 38 | | { transactionHash: string } // ETH transfer output 39 | | avs_pb.ETHTransferNode.Output.AsObject // ETH transfer node output 40 | | avs_pb.ContractWriteNode.Output.AsObject // Contract write output 41 | | avs_pb.BranchNode.Output.AsObject // Branch output 42 | | any[] // Contract read output (array of converted protobuf values) 43 | | any // Custom code, GraphQL, REST API, Filter outputs (converted JS values) 44 | | string // Loop output 45 | | undefined; 46 | 47 | // Step Props 48 | export type StepProps = Omit< 49 | avs_pb.Execution.Step.AsObject, 50 | | "outputDataCase" 51 | | "ethTransfer" 52 | | "graphql" 53 | | "contractRead" 54 | | "contractWrite" 55 | | "customCode" 56 | | "restApi" 57 | | "branch" 58 | | "filter" 59 | | "loop" 60 | | "blockTrigger" 61 | | "fixedTimeTrigger" 62 | | "cronTrigger" 63 | | "eventTrigger" 64 | | "manualTrigger" 65 | > & { 66 | output: OutputDataProps; 67 | }; 68 | 69 | // Execution Props 70 | export type ExecutionProps = Omit & { 71 | steps: Array; 72 | }; 73 | 74 | // Workflow Props - depends on other types so defined last 75 | export type WorkflowProps = Omit< 76 | avs_pb.Task.AsObject, 77 | | "id" 78 | | "owner" 79 | | "completedAt" 80 | | "status" 81 | | "name" 82 | | "trigger" 83 | | "nodesList" 84 | | "edgesList" 85 | | "lastRanAt" 86 | | "executionCount" 87 | > & { 88 | id?: string; 89 | owner?: string; 90 | completedAt?: number; 91 | status?: WorkflowStatus; 92 | name?: string; 93 | trigger: TriggerProps; 94 | nodes: NodeProps[]; 95 | edges: EdgeProps[]; 96 | lastRanAt?: number; 97 | executionCount?: number; 98 | }; 99 | 100 | export const ExecutionStatus = avs_pb.ExecutionStatus; 101 | -------------------------------------------------------------------------------- /packages/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true, 9 | "typeRoots": ["../../node_modules/@types"], 10 | "types": ["google-protobuf"], 11 | "baseUrl": ".", 12 | "paths": { 13 | "@/grpc_codegen/*": ["../../grpc_codegen/*"] 14 | }, 15 | "outDir": "./dist", 16 | "rootDir": "./src", 17 | "composite": true, 18 | "declaration": true, 19 | "declarationMap": true, 20 | "declarationDir": "./dist" 21 | }, 22 | "include": ["src/**/*"] 23 | } 24 | -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | # Publish Scripts 2 | 3 | This directory contains automated scripts to simplify the package publishing process for the ava-sdk-js monorepo. 4 | 5 | ## Quick Start 6 | 7 | After running `yarn changeset` and `yarn changeset version` manually: 8 | 9 | ```bash 10 | # Dry run to see what would be published (recommended first) 11 | npm run publish:dry-run 12 | 13 | # Publish packages 14 | npm run publish 15 | ``` 16 | 17 | ## Available Scripts 18 | 19 | ### Package.json Scripts 20 | 21 | | Script | Description | 22 | |--------|-------------| 23 | | `npm run publish` | **Main publish command** | 24 | | `npm run publish:dry-run` | **Dry run to preview what would be published (recommended first)** | 25 | | `npm run publish:bash` | Alternative bash script version | 26 | | `npm run publish:bash:dry-run` | Bash script dry run | 27 | | `npm run publish:help` | Show help for bash script | 28 | 29 | ### Direct Script Usage 30 | 31 | ```bash 32 | # Node.js script (cross-platform) - default 33 | node ./scripts/publish-packages.js [--dry-run] [--help] 34 | 35 | # Bash script (alternative) 36 | ./scripts/publish-packages.sh [--dry-run] [--help] 37 | ``` 38 | 39 | ## What the Scripts Do 40 | 41 | 1. **Validation**: Check directory, npm auth, package versions 42 | 2. **Build**: Ensure packages are built before publishing 43 | 3. **Dependency Order**: Publish `types` package first, then `sdk-js` 44 | 4. **Dependency Refresh**: Run `yarn install` between publishes to ensure sdk-js gets the latest types version 45 | 5. **Safety Checks**: Confirm before publishing, check for existing versions 46 | 6. **Git Suggestions**: Provide git commands for tagging the release 47 | 48 | ## Publishing Process 49 | 50 | ### Manual Steps (Still Required) 51 | ```bash 52 | yarn changeset # Create changeset for your changes 53 | yarn changeset version # Bump versions and update CHANGELOGs 54 | ``` 55 | 56 | ### Automated Steps (Via Scripts) 57 | ```bash 58 | npm run publish # Handles the rest automatically 59 | ``` 60 | 61 | ## Why Dependencies Are Updated 62 | 63 | **Q: Why does the script run `yarn install` between publishing `types` and `sdk-js`?** 64 | 65 | **A:** When `sdk-js` depends on `@avaprotocol/types`, we need to ensure it uses the newly published version, not the local workspace version. The script: 66 | 67 | 1. Publishes `@avaprotocol/types@1.2.3` to npm 68 | 2. Runs `yarn install` to update `sdk-js/node_modules` with the published version 69 | 3. Publishes `@avaprotocol/sdk-js@1.2.3` with the correct dependency 70 | 71 | This prevents issues where `sdk-js` might reference an unpublished or mismatched version of `types`. 72 | 73 | ## Error Handling 74 | 75 | - **Authentication**: Script checks `npm whoami` before publishing 76 | - **Version Conflicts**: Warns if package version already exists on npm 77 | - **Build Failures**: Stops if package build fails 78 | - **Dependency Issues**: Aborts if workspace dependency update fails 79 | 80 | ## Dry Run Mode 81 | 82 | Always test first: 83 | ```bash 84 | npm run publish:dry-run 85 | ``` 86 | 87 | This will: 88 | - Show what packages would be published 89 | - Validate configurations 90 | - Check npm authentication 91 | - Skip actual publishing 92 | 93 | ## Platform Compatibility 94 | 95 | The default publish commands (`npm run publish` and `npm run publish:dry-run`) use the **Node.js script** which works on all platforms: 96 | 97 | - ✅ **Windows** (PowerShell, Command Prompt) 98 | - ✅ **macOS** (Terminal, iTerm) 99 | - ✅ **Linux** (bash, zsh, fish) 100 | - ✅ **Windows WSL** 101 | 102 | Alternative bash script is available if needed (`npm run publish:bash`). -------------------------------------------------------------------------------- /scripts/generate-docs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This script generates the SDK reference documentation based on the methods array defined at the beginning of the file. 3 | * For a generated example, please refer to https://avaprotocol.org/docs/ethereum/develop/js-sdk 4 | * 5 | * Usage: 6 | * node ./scripts/generate-docs.js 7 | */ 8 | 9 | const fs = require("fs"); 10 | 11 | const OUTPUT_DIR = "./scripts"; // The directory to output the generated documentation; setting is to ./scripts for now. 12 | const OUTPUT_FILENAME = "SDK_References.md"; 13 | 14 | const methods = [ 15 | { 16 | name: "authWithSignature", 17 | description: "Authenticate using a signature.", 18 | params: [ 19 | { name: "address", type: "string", description: "The user's address." }, 20 | { 21 | name: "signature", 22 | type: "string", 23 | description: "The signature for authentication.", 24 | }, 25 | { 26 | name: "expiredAtEpoch", 27 | type: "number", 28 | description: "The expiration epoch for the signature.", 29 | }, 30 | ], 31 | returns: { 32 | type: "Promise", 33 | description: "The authentication key response.", 34 | }, 35 | }, 36 | { 37 | name: "listSmartWallets", 38 | description: "Retrieve a list of smart wallets.", 39 | params: [ 40 | { 41 | name: "options", 42 | type: "RequestOptions", 43 | description: "Request options, including authentication.", 44 | }, 45 | ], 46 | returns: { 47 | type: "Promise", 48 | description: "A list of smart wallets associated with the user.", 49 | }, 50 | }, 51 | { 52 | name: "createWallet", 53 | description: "Create a new smart wallet.", 54 | params: [ 55 | { 56 | name: "salt", 57 | type: "string", 58 | description: "The salt value for wallet creation.", 59 | }, 60 | { 61 | name: "factoryAddress", 62 | type: "string", 63 | description: "The factory address (optional).", 64 | }, 65 | { 66 | name: "options", 67 | type: "RequestOptions", 68 | description: "Request options, including authentication.", 69 | }, 70 | ], 71 | returns: { 72 | type: "Promise", 73 | description: "Details of the created wallet.", 74 | }, 75 | }, 76 | { 77 | name: "createTask", 78 | description: "Create a new task for automation.", 79 | params: [ 80 | { 81 | name: "payload", 82 | type: "any", 83 | description: "The task payload including trigger, nodes, and edges.", 84 | }, 85 | { 86 | name: "options", 87 | type: "RequestOptions", 88 | description: "Request options, including authentication.", 89 | }, 90 | ], 91 | returns: { 92 | type: "Promise", 93 | description: "The ID of the created task.", 94 | }, 95 | }, 96 | { 97 | name: "listTasks", 98 | description: "Retrieve a list of tasks associated with a smart wallet.", 99 | params: [ 100 | { 101 | name: "address", 102 | type: "string", 103 | description: "The smart wallet address.", 104 | }, 105 | { 106 | name: "options", 107 | type: "RequestOptions", 108 | description: "Request options, including authentication.", 109 | }, 110 | ], 111 | returns: { 112 | type: "Promise", 113 | description: "A list of tasks for the specified wallet.", 114 | }, 115 | }, 116 | { 117 | name: "getTask", 118 | description: "Retrieve details of a specific task.", 119 | params: [ 120 | { 121 | name: "id", 122 | type: "string", 123 | description: "The ID of the task to retrieve.", 124 | }, 125 | { 126 | name: "options", 127 | type: "RequestOptions", 128 | description: "Request options, including authentication.", 129 | }, 130 | ], 131 | returns: { 132 | type: "Promise", 133 | description: "Details of the specified task.", 134 | }, 135 | }, 136 | { 137 | name: "cancelTask", 138 | description: "Cancel an existing task.", 139 | params: [ 140 | { 141 | name: "id", 142 | type: "string", 143 | description: "The ID of the task to cancel.", 144 | }, 145 | { 146 | name: "options", 147 | type: "RequestOptions", 148 | description: "Request options, including authentication.", 149 | }, 150 | ], 151 | returns: { 152 | type: "Promise", 153 | description: 154 | "True if the task was successfully canceled, otherwise false.", 155 | }, 156 | }, 157 | { 158 | name: "deleteTask", 159 | description: "Delete an existing task.", 160 | params: [ 161 | { 162 | name: "id", 163 | type: "string", 164 | description: "The ID of the task to delete.", 165 | }, 166 | { 167 | name: "options", 168 | type: "RequestOptions", 169 | description: "Request options, including authentication.", 170 | }, 171 | ], 172 | returns: { 173 | type: "Promise", 174 | description: 175 | "True if the task was successfully deleted, otherwise false.", 176 | }, 177 | }, 178 | ]; 179 | 180 | const generateMarkdown = (methods) => { 181 | let md = "# SDK API Documentation\n\n"; 182 | 183 | methods.forEach((method) => { 184 | md += `## ${method.name}\n\n`; 185 | md += `${method.description}\n\n`; 186 | md += "### Parameters\n\n"; 187 | if (method.params.length > 0) { 188 | md += "| Name | Type | Description |\n"; 189 | md += "|------|------|-------------|\n"; 190 | method.params.forEach((param) => { 191 | md += `| ${param.name} | ${param.type} | ${param.description} |\n`; 192 | }); 193 | } else { 194 | md += "This method has no parameters.\n"; 195 | } 196 | md += "\n### Returns\n\n"; 197 | md += `**${method.returns.type}** - ${method.returns.description}\n\n`; 198 | }); 199 | 200 | return md; 201 | }; 202 | 203 | const markdown = generateMarkdown(methods); 204 | 205 | fs.writeFileSync(`${OUTPUT_DIR}/${OUTPUT_FILENAME}`, markdown); 206 | 207 | console.log(`Documentation generated: ${OUTPUT_DIR}/${OUTPUT_FILENAME}`); 208 | -------------------------------------------------------------------------------- /scripts/generate-signed-message.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Get wallet address and generate a signed message from a private key 3 | * An environment variable TEST_MNEMONIC must be set to the mnemonic used to generate the wallet 4 | */ 5 | 6 | import { ethers } from "ethers"; 7 | import { getKeyRequestMessage } from "../dist/index.js"; 8 | 9 | const mnemonic = process.env.TEST_MNEMONIC; 10 | const expiredAtEpoch = Math.floor(Date.now() / 1000) + 24 * 60 * 60; 11 | 12 | if (!mnemonic) { 13 | throw new Error("Mnemonic is required; Please set the TEST_MNEMONIC environment variable."); 14 | } 15 | 16 | const wallet = ethers.Wallet.fromPhrase(mnemonic); 17 | 18 | console.log("Address:", wallet.address, "Expired at epoch:", expiredAtEpoch); 19 | console.log("Private key:", wallet.privateKey); 20 | 21 | // Generate the key request message 22 | const message = getKeyRequestMessage(wallet.address, expiredAtEpoch); 23 | 24 | async function main() { 25 | const signature = await wallet.signMessage(message); 26 | console.log("Message:", message); 27 | console.log("Signature:", signature); 28 | } 29 | 30 | main() 31 | .catch(console.error) 32 | .finally(() => process.exit()); 33 | -------------------------------------------------------------------------------- /scripts/prepare-package.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | /** 7 | * Script to prepare packages for publishing by replacing workspace dependencies 8 | * with actual version numbers from the workspace packages. 9 | */ 10 | 11 | function getPackageVersion(packageName) { 12 | try { 13 | const packagePath = path.join(__dirname, '..', 'packages', packageName, 'package.json'); 14 | const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8')); 15 | return packageJson.version; 16 | } catch (error) { 17 | console.error(`Error reading version for ${packageName}:`, error.message); 18 | return null; 19 | } 20 | } 21 | 22 | function updatePackageJson(packagePath) { 23 | try { 24 | const packageJsonPath = path.join(packagePath, 'package.json'); 25 | const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); 26 | let updated = false; 27 | 28 | // Update dependencies 29 | if (packageJson.dependencies) { 30 | for (const [dep, version] of Object.entries(packageJson.dependencies)) { 31 | if (version === 'workspace:*') { 32 | const actualVersion = getPackageVersion(dep.replace('@avaprotocol/', '')); 33 | if (actualVersion) { 34 | packageJson.dependencies[dep] = `^${actualVersion}`; 35 | updated = true; 36 | console.log(`Updated dependency ${dep} from workspace:* to ^${actualVersion}`); 37 | } 38 | } 39 | } 40 | } 41 | 42 | // Update devDependencies 43 | if (packageJson.devDependencies) { 44 | for (const [dep, version] of Object.entries(packageJson.devDependencies)) { 45 | if (version === 'workspace:*') { 46 | const actualVersion = getPackageVersion(dep.replace('@avaprotocol/', '')); 47 | if (actualVersion) { 48 | packageJson.devDependencies[dep] = `^${actualVersion}`; 49 | updated = true; 50 | console.log(`Updated devDependency ${dep} from workspace:* to ^${actualVersion}`); 51 | } 52 | } 53 | } 54 | } 55 | 56 | if (updated) { 57 | fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n'); 58 | console.log(`Updated package.json for ${path.basename(packagePath)}`); 59 | } else { 60 | console.log(`No workspace dependencies found in ${path.basename(packagePath)}`); 61 | } 62 | 63 | return updated; 64 | } catch (error) { 65 | console.error(`Error updating package.json for ${packagePath}:`, error.message); 66 | return false; 67 | } 68 | } 69 | 70 | // Get the package directory from command line or use current directory 71 | const packageDir = process.argv[2] || process.cwd(); 72 | const packageName = path.basename(packageDir); 73 | 74 | console.log(`Preparing package: ${packageName}`); 75 | updatePackageJson(packageDir); -------------------------------------------------------------------------------- /scripts/publish-packages.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { execSync, spawn } = require('child_process'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | 7 | // Colors for console output 8 | const colors = { 9 | reset: '\x1b[0m', 10 | red: '\x1b[31m', 11 | green: '\x1b[32m', 12 | yellow: '\x1b[33m', 13 | blue: '\x1b[34m', 14 | }; 15 | 16 | // Utility functions for colored output 17 | const log = { 18 | info: (msg) => console.log(`${colors.blue}[INFO]${colors.reset} ${msg}`), 19 | success: (msg) => console.log(`${colors.green}[SUCCESS]${colors.reset} ${msg}`), 20 | warning: (msg) => console.log(`${colors.yellow}[WARNING]${colors.reset} ${msg}`), 21 | error: (msg) => console.log(`${colors.red}[ERROR]${colors.reset} ${msg}`), 22 | }; 23 | 24 | // Helper to run commands 25 | function runCommand(command, options = {}) { 26 | try { 27 | const result = execSync(command, { 28 | encoding: 'utf8', 29 | stdio: options.silent ? 'pipe' : 'inherit', 30 | ...options 31 | }); 32 | return { success: true, output: result }; 33 | } catch (error) { 34 | return { success: false, error: error.message, output: error.stdout }; 35 | } 36 | } 37 | 38 | // Helper to check if we're in the correct directory 39 | function checkDirectory() { 40 | if (!fs.existsSync('package.json') || !fs.existsSync('packages')) { 41 | log.error('This script must be run from the root of the ava-sdk-js repository'); 42 | process.exit(1); 43 | } 44 | } 45 | 46 | // Helper to get package info 47 | function getPackageInfo(packagePath) { 48 | try { 49 | const packageJson = JSON.parse(fs.readFileSync(path.join(packagePath, 'package.json'), 'utf8')); 50 | return { 51 | name: packageJson.name, 52 | version: packageJson.version, 53 | }; 54 | } catch (error) { 55 | log.error(`Failed to read package.json from ${packagePath}: ${error.message}`); 56 | process.exit(1); 57 | } 58 | } 59 | 60 | // Helper to check if npm user is authenticated 61 | function checkNpmAuth() { 62 | log.info('Checking npm authentication...'); 63 | const result = runCommand('npm whoami', { silent: true }); 64 | 65 | log.info(`npm whoami exit code: ${result.success ? 0 : 'failed'}`); 66 | log.info(`npm whoami output: '${result.output || ''}'`); 67 | if (result.error) { 68 | log.info(`npm whoami error: '${result.error}'`); 69 | } 70 | 71 | if (!result.success) { 72 | log.error('You are not logged in to npm. Please run "npm login" first.'); 73 | log.error(`Error details: ${result.error || 'Unknown error'}`); 74 | process.exit(1); 75 | } 76 | 77 | const npmUser = result.output.trim(); 78 | if (!npmUser) { 79 | log.error('npm whoami returned empty output. Please check your npm configuration.'); 80 | process.exit(1); 81 | } 82 | 83 | log.success(`Authenticated as: ${npmUser}`); 84 | return npmUser; 85 | } 86 | 87 | // Helper to check if package version exists on npm 88 | function checkVersionExists(packageName, version) { 89 | const result = runCommand(`npm view "${packageName}@${version}" version`, { silent: true }); 90 | return result.success; 91 | } 92 | 93 | // Helper to get user input 94 | function getUserInput(question) { 95 | return new Promise((resolve) => { 96 | const readline = require('readline').createInterface({ 97 | input: process.stdin, 98 | output: process.stdout, 99 | }); 100 | 101 | readline.question(question, (answer) => { 102 | readline.close(); 103 | resolve(answer.toLowerCase()); 104 | }); 105 | }); 106 | } 107 | 108 | // Main publish function for a single package 109 | async function publishPackage(packagePath, dryRun = false) { 110 | const packageInfo = getPackageInfo(packagePath); 111 | 112 | log.info(`Publishing ${packageInfo.name}@${packageInfo.version}`); 113 | 114 | // Check if this version already exists 115 | if (checkVersionExists(packageInfo.name, packageInfo.version)) { 116 | log.warning(`${packageInfo.name}@${packageInfo.version} already exists on npm`); 117 | const answer = await getUserInput('Do you want to skip this package? (Y/n): '); 118 | if (answer !== 'n' && answer !== 'no') { 119 | log.warning(`Skipping ${packageInfo.name}`); 120 | return true; 121 | } 122 | } 123 | 124 | if (dryRun) { 125 | log.info(`[DRY RUN] Would publish ${packageInfo.name}@${packageInfo.version}`); 126 | return true; 127 | } 128 | 129 | // Build the package 130 | log.info(`Building ${packageInfo.name}...`); 131 | const buildResult = runCommand('npm run build', { cwd: packagePath }); 132 | if (!buildResult.success) { 133 | log.error(`Build failed for ${packageInfo.name}`); 134 | return false; 135 | } 136 | 137 | // Publish the package 138 | log.info(`Publishing ${packageInfo.name} to npm...`); 139 | const publishResult = runCommand('npm publish --access public', { cwd: packagePath }); 140 | 141 | if (publishResult.success) { 142 | log.success(`Successfully published ${packageInfo.name}@${packageInfo.version}`); 143 | return true; 144 | } else { 145 | log.error(`Failed to publish ${packageInfo.name}`); 146 | return false; 147 | } 148 | } 149 | 150 | // Main function 151 | async function main() { 152 | const args = process.argv.slice(2); 153 | const dryRun = args.includes('--dry-run'); 154 | const help = args.includes('--help') || args.includes('-h'); 155 | 156 | if (help) { 157 | console.log('Usage: node scripts/publish-packages.js [--dry-run] [--help]'); 158 | console.log(' --dry-run Show what would be published without actually publishing'); 159 | console.log(' --help Show this help message'); 160 | process.exit(0); 161 | } 162 | 163 | log.info('Starting package publish process...'); 164 | 165 | // Preliminary checks 166 | checkDirectory(); 167 | 168 | if (!dryRun) { 169 | checkNpmAuth(); 170 | } 171 | 172 | // Get package information 173 | const typesInfo = getPackageInfo('packages/types'); 174 | const sdkInfo = getPackageInfo('packages/sdk-js'); 175 | 176 | log.info('Packages to publish:'); 177 | console.log(` - ${typesInfo.name}@${typesInfo.version}`); 178 | console.log(` - ${sdkInfo.name}@${sdkInfo.version}`); 179 | 180 | if (dryRun) { 181 | log.warning('DRY RUN MODE - No packages will actually be published'); 182 | } else { 183 | // Confirm before publishing 184 | console.log(); 185 | const answer = await getUserInput('Do you want to proceed with publishing? (y/N): '); 186 | if (answer !== 'y' && answer !== 'yes') { 187 | log.error('Publish cancelled by user'); 188 | process.exit(1); 189 | } 190 | } 191 | 192 | // Publish packages in dependency order 193 | log.info('Publishing packages in dependency order...'); 194 | 195 | // 1. Publish types package first 196 | log.info('Step 1: Publishing types package...'); 197 | const typesSuccess = await publishPackage('packages/types', dryRun); 198 | if (!typesSuccess) { 199 | log.error('Failed to publish types package. Aborting.'); 200 | process.exit(1); 201 | } 202 | 203 | // 2. Update workspace dependencies (only if not dry run) 204 | if (!dryRun) { 205 | log.info('Step 2: Updating workspace dependencies...'); 206 | const updateResult = runCommand('yarn install'); 207 | if (!updateResult.success) { 208 | log.error('Failed to update dependencies. Aborting.'); 209 | process.exit(1); 210 | } 211 | log.success('Workspace dependencies updated'); 212 | } 213 | 214 | // 3. Publish sdk-js package 215 | log.info('Step 3: Publishing sdk-js package...'); 216 | const sdkSuccess = await publishPackage('packages/sdk-js', dryRun); 217 | if (!sdkSuccess) { 218 | log.error('Failed to publish sdk-js package'); 219 | process.exit(1); 220 | } 221 | 222 | if (dryRun) { 223 | log.success('Dry run completed successfully!'); 224 | } else { 225 | log.success('All packages published successfully!'); 226 | log.info('Published packages:'); 227 | console.log(` ✅ ${typesInfo.name}@${typesInfo.version}`); 228 | console.log(` ✅ ${sdkInfo.name}@${sdkInfo.version}`); 229 | 230 | log.info('You may want to create a git tag for this release:'); 231 | console.log(' git add .'); 232 | console.log(' git commit -m "chore: release packages"'); 233 | console.log(` git tag v${sdkInfo.version}`); 234 | console.log(' git push && git push --tags'); 235 | } 236 | } 237 | 238 | // Run the script 239 | main().catch((error) => { 240 | log.error(`Script failed: ${error.message}`); 241 | process.exit(1); 242 | }); -------------------------------------------------------------------------------- /scripts/release.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { execSync } = require('child_process'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | 7 | /** 8 | * Release script that handles workspace dependencies and publishing 9 | */ 10 | 11 | function updateWorkspaceDependencies() { 12 | console.log('Updating workspace dependencies for publishing...'); 13 | 14 | const packages = ['types', 'sdk-js']; 15 | 16 | for (const pkg of packages) { 17 | const packagePath = path.join(__dirname, '..', 'packages', pkg); 18 | const packageJsonPath = path.join(packagePath, 'package.json'); 19 | 20 | if (fs.existsSync(packageJsonPath)) { 21 | try { 22 | const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); 23 | let updated = false; 24 | 25 | // Update dependencies 26 | if (packageJson.dependencies) { 27 | for (const [dep, version] of Object.entries(packageJson.dependencies)) { 28 | if (version === 'workspace:*') { 29 | const depPackageName = dep.replace('@avaprotocol/', ''); 30 | const depPackagePath = path.join(__dirname, '..', 'packages', depPackageName, 'package.json'); 31 | 32 | if (fs.existsSync(depPackagePath)) { 33 | const depPackageJson = JSON.parse(fs.readFileSync(depPackagePath, 'utf8')); 34 | packageJson.dependencies[dep] = `^${depPackageJson.version}`; 35 | updated = true; 36 | console.log(`Updated ${dep} to ^${depPackageJson.version} in ${pkg}`); 37 | } 38 | } 39 | } 40 | } 41 | 42 | if (updated) { 43 | fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n'); 44 | } 45 | } catch (error) { 46 | console.error(`Error updating ${pkg}:`, error.message); 47 | } 48 | } 49 | } 50 | } 51 | 52 | function restoreWorkspaceDependencies() { 53 | console.log('Restoring workspace dependencies...'); 54 | 55 | const packages = ['types', 'sdk-js']; 56 | 57 | for (const pkg of packages) { 58 | const packagePath = path.join(__dirname, '..', 'packages', pkg); 59 | const packageJsonPath = path.join(packagePath, 'package.json'); 60 | 61 | if (fs.existsSync(packageJsonPath)) { 62 | try { 63 | const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); 64 | let updated = false; 65 | 66 | // Restore dependencies 67 | if (packageJson.dependencies) { 68 | for (const [dep] of Object.entries(packageJson.dependencies)) { 69 | if (dep.startsWith('@avaprotocol/')) { 70 | packageJson.dependencies[dep] = 'workspace:*'; 71 | updated = true; 72 | } 73 | } 74 | } 75 | 76 | if (updated) { 77 | fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n'); 78 | console.log(`Restored workspace dependencies in ${pkg}`); 79 | } 80 | } catch (error) { 81 | console.error(`Error restoring ${pkg}:`, error.message); 82 | } 83 | } 84 | } 85 | } 86 | 87 | function release() { 88 | try { 89 | console.log('🚀 Starting release process...'); 90 | 91 | // Check if there are any changesets 92 | const changesetPath = path.join(__dirname, '..', '.changeset'); 93 | const changesetFiles = fs.readdirSync(changesetPath).filter(file => file.endsWith('.md')); 94 | 95 | if (changesetFiles.length === 0) { 96 | console.log('No changesets found. Run "yarn changeset" to create one.'); 97 | return; 98 | } 99 | 100 | console.log(`Found ${changesetFiles.length} changeset(s)`); 101 | 102 | // Build all packages 103 | console.log('📦 Building packages...'); 104 | execSync('yarn build', { stdio: 'inherit' }); 105 | 106 | // Update workspace dependencies for publishing 107 | updateWorkspaceDependencies(); 108 | 109 | // Version packages using changesets 110 | console.log('🏷️ Versioning packages...'); 111 | execSync('yarn version', { stdio: 'inherit' }); 112 | 113 | // Publish packages 114 | console.log('📤 Publishing packages...'); 115 | execSync('yarn changeset publish', { stdio: 'inherit' }); 116 | 117 | // Restore workspace dependencies 118 | restoreWorkspaceDependencies(); 119 | 120 | console.log('✅ Release completed successfully!'); 121 | 122 | } catch (error) { 123 | console.error('❌ Release failed:', error.message); 124 | 125 | // Always try to restore workspace dependencies on failure 126 | try { 127 | restoreWorkspaceDependencies(); 128 | } catch (restoreError) { 129 | console.error('Failed to restore workspace dependencies:', restoreError.message); 130 | } 131 | 132 | process.exit(1); 133 | } 134 | } 135 | 136 | // Run the release process 137 | release(); -------------------------------------------------------------------------------- /scripts/run-tests-with-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Script to run tests with Docker container 5 | # This script replicates the GitHub Actions workflow in .github/workflows/dev-test-on-pr.yml 6 | 7 | # Colors for output 8 | GREEN='\033[0;32m' 9 | YELLOW='\033[1;33m' 10 | RED='\033[0;31m' 11 | NC='\033[0m' # No Color 12 | 13 | # Function to print status messages 14 | print_status() { 15 | echo -e "${GREEN}==>${NC} $1" 16 | } 17 | 18 | print_warning() { 19 | echo -e "${YELLOW}==>${NC} $1" 20 | } 21 | 22 | print_error() { 23 | echo -e "${RED}==>${NC} $1" 24 | } 25 | 26 | # Check if .env.test exists, create it if not 27 | if [ ! -f .env.test ]; then 28 | print_status "Creating .env.test file from .env.example" 29 | cp .env.example .env.test 30 | fi 31 | 32 | # Set CHAIN_ID and CHAIN_ENDPOINT if not already set in .env.test 33 | if ! grep -q "CHAIN_ID=" .env.test; then 34 | print_status "Adding CHAIN_ID to .env.test" 35 | echo "CHAIN_ID=11155111" >> .env.test 36 | fi 37 | 38 | if ! grep -q "CHAIN_ENDPOINT=" .env.test; then 39 | print_warning "Adding placeholder CHAIN_ENDPOINT to .env.test" 40 | echo "CHAIN_ENDPOINT=https://sepolia.infura.io/v3/your-infura-key" >> .env.test 41 | print_warning "Please update CHAIN_ENDPOINT in .env.test with a valid endpoint" 42 | fi 43 | 44 | # Step 1: Check if TEST_PRIVATE_KEY is set in the environment 45 | if [ -z "$TEST_PRIVATE_KEY" ]; then 46 | # Step 2: If not set, check the .env.test file 47 | if ! grep -q "TEST_PRIVATE_KEY=[a-zA-Z0-9]" .env.test; then 48 | print_error "TEST_PRIVATE_KEY is not set in the environment or .env.test" 49 | print_error "Please add a valid private key to the environment or .env.test" 50 | exit 1 51 | else 52 | # Load the .env.test file 53 | export $(grep -v '^#' .env.test | xargs) 54 | fi 55 | fi 56 | 57 | # Ensure config directory exists 58 | mkdir -p config 59 | 60 | # Step 3: Pull the latest Docker images 61 | print_status "Pulling the latest Docker images" 62 | docker pull avaprotocol/avs-dev:latest 63 | 64 | # Start the Docker container 65 | print_status "Starting Docker container" 66 | docker compose up -d 67 | 68 | # Wait for the container to be ready 69 | print_status "Waiting for the container to be ready" 70 | until curl --output /dev/null --silent --fail http://localhost:1323/up; do 71 | printf '.' 72 | sleep 5 73 | done 74 | echo "" 75 | 76 | # Generate API key and update .env.test 77 | print_status "Generating API key" 78 | API_KEY=$(docker compose exec aggregator /ava create-api-key --role=admin --subject=apikey) 79 | echo "Generated API key: $API_KEY" 80 | print_status "Updating TEST_API_KEY in .env.test" 81 | # Use sed with compatibility for both Linux and macOS 82 | if [[ "$OSTYPE" == "darwin"* ]]; then 83 | # macOS version 84 | sed -i '' "s/^TEST_API_KEY=.*/TEST_API_KEY=$API_KEY/" .env.test 85 | else 86 | # Linux version 87 | sed -i "s/^TEST_API_KEY=.*/TEST_API_KEY=$API_KEY/" .env.test 88 | fi 89 | 90 | # Set ENDPOINT in .env.test 91 | print_status "Setting ENDPOINT in .env.test" 92 | if [[ "$OSTYPE" == "darwin"* ]]; then 93 | # macOS version 94 | sed -i '' "s/^ENDPOINT=.*/ENDPOINT=localhost:2206/" .env.test 95 | else 96 | # Linux version 97 | sed -i "s/^ENDPOINT=.*/ENDPOINT=localhost:2206/" .env.test 98 | fi 99 | 100 | # Build the project 101 | print_status "Building the project" 102 | yarn build 103 | 104 | # Run tests 105 | if [ "$1" == "" ]; then 106 | print_status "Running all tests" 107 | yarn test 108 | else 109 | print_status "Running specific test: $1" 110 | yarn test "$1" 111 | fi 112 | 113 | # Cleanup 114 | print_status "Cleaning up" 115 | docker compose down 116 | 117 | print_status "Done!" 118 | -------------------------------------------------------------------------------- /tests/templates/README.md: -------------------------------------------------------------------------------- 1 | # Real-World Workflow Templates 2 | 3 | This folder contains comprehensive test suites based on **real client workflow data** to validate that the SDK works correctly with actual usage patterns. 4 | 5 | ## Overview 6 | 7 | Each template test file follows a consistent structure with **three test suites**: 8 | 9 | ### 1. Individual Component Testing 10 | - Tests each trigger and node in isolation using `runTrigger` and `runNodeImmediately` 11 | - Validates input field handling and data flow 12 | - Ensures components work correctly with real-world data structures 13 | 14 | ### 2. Workflow Simulation Testing 15 | - Tests the complete workflow using `simulateWorkflow` 16 | - Verifies end-to-end execution without deployment 17 | - Validates step-by-step execution and data transformation 18 | 19 | ### 3. Full Deployment and Execution Testing 20 | - Deploys the workflow to the aggregator 21 | - Triggers the workflow with real event data 22 | - Verifies complete execution cycle including database persistence 23 | 24 | ## Current Templates 25 | 26 | ### `telegram-alert-on-transfer.test.ts` 27 | **Real client use case:** Monitor USDC transfers and send Telegram alerts 28 | 29 | - **Trigger:** EventTrigger with complex input data (tokens, addresses, chainId) 30 | - **Nodes:** CustomCode (message formatting) + RestAPI (Telegram notification) 31 | - **Real Data:** Based on actual Sepolia USDC contract monitoring 32 | - **Tests:** Input field serialization, workflow persistence, event monitoring 33 | 34 | ## Running Template Tests 35 | 36 | ```bash 37 | # Run all template tests 38 | npm test -- tests/templates/ 39 | 40 | # Run specific template 41 | npm test -- tests/templates/telegram-alert-on-transfer.test.ts 42 | 43 | # Run with verbose output 44 | npm test -- tests/templates/ --verbose 45 | ``` 46 | 47 | ## Benefits 48 | 49 | ### 🔍 **Comprehensive Coverage** 50 | - Tests individual components, simulation, and full deployment 51 | - Validates both success and error scenarios 52 | - Ensures input field handling works correctly 53 | 54 | ### 🌍 **Real-World Validation** 55 | - Based on actual client usage patterns 56 | - Uses real contract addresses and data structures 57 | - Tests complex input data scenarios 58 | 59 | ### 🚀 **Regression Prevention** 60 | - Catches serialization issues (like empty nodes/edges arrays) 61 | - Validates input field persistence and retrieval 62 | - Tests event monitoring integration -------------------------------------------------------------------------------- /tests/templates/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Real-world workflow templates for comprehensive testing 3 | * 4 | * These templates are based on actual client usage patterns and help validate 5 | * that the SDK works correctly with real-world scenarios. 6 | * 7 | * Each template includes: 8 | * 1. Individual component testing (runTrigger, runNodeImmediately) 9 | * 2. Workflow simulation testing 10 | * 3. Full deployment and execution testing 11 | */ 12 | 13 | // Note: Test files don't have default exports, they are Jest test suites 14 | // Import the test files directly to run them, don't export them 15 | 16 | /** 17 | * Template categories for easy organization 18 | */ 19 | export const TemplateCategories = { 20 | ALERTS: 'alerts', 21 | DEFI: 'defi', 22 | MONITORING: 'monitoring', 23 | NOTIFICATIONS: 'notifications', 24 | } as const; 25 | 26 | /** 27 | * Template metadata for documentation and testing 28 | */ 29 | export const TemplateMetadata = { 30 | 'telegram-alert-on-transfer': { 31 | name: 'Telegram Alert on Transfer', 32 | description: 'Monitors ERC20 token transfers and sends Telegram notifications', 33 | category: TemplateCategories.ALERTS, 34 | triggers: ['EventTrigger'], 35 | nodes: ['CustomCode', 'RestAPI'], 36 | realWorldUsage: true, 37 | inputFields: { 38 | trigger: ['tokens', 'address', 'chainId', 'subType'], 39 | nodes: [] 40 | }, 41 | complexity: 'medium', 42 | testTypes: ['individual', 'simulation', 'deployment'] 43 | } 44 | } as const; 45 | 46 | /** 47 | * Helper function to get template metadata 48 | */ 49 | export function getTemplateMetadata(templateName: keyof typeof TemplateMetadata) { 50 | return TemplateMetadata[templateName]; 51 | } 52 | 53 | /** 54 | * Helper function to list all available templates 55 | */ 56 | export function listTemplates() { 57 | return Object.keys(TemplateMetadata); 58 | } 59 | 60 | /** 61 | * Helper function to get templates by category 62 | */ 63 | export function getTemplatesByCategory(category: typeof TemplateCategories[keyof typeof TemplateCategories]) { 64 | return Object.entries(TemplateMetadata) 65 | .filter(([_, metadata]) => metadata.category === category) 66 | .map(([name, _]) => name); 67 | } -------------------------------------------------------------------------------- /tests/utils/abis.ts: -------------------------------------------------------------------------------- 1 | export const factoryProxyAbi = `[ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "address", 6 | "name": "owner", 7 | "type": "address" 8 | }, 9 | { 10 | "internalType": "uint256", 11 | "name": "salt", 12 | "type": "uint256" 13 | } 14 | ], 15 | "name": "createAccount", 16 | "outputs": [ 17 | { 18 | "internalType": "address", 19 | "name": "", 20 | "type": "address" 21 | } 22 | ], 23 | "stateMutability": "nonpayable", 24 | "type": "function" 25 | }, 26 | { 27 | "inputs": [ 28 | { 29 | "internalType": "address", 30 | "name": "owner", 31 | "type": "address" 32 | }, 33 | { 34 | "internalType": "uint256", 35 | "name": "salt", 36 | "type": "uint256" 37 | } 38 | ], 39 | "name": "getAddress", 40 | "outputs": [ 41 | { 42 | "internalType": "address", 43 | "name": "", 44 | "type": "address" 45 | } 46 | ], 47 | "stateMutability": "view", 48 | "type": "function" 49 | } 50 | ]`; -------------------------------------------------------------------------------- /tests/utils/envalid.ts: -------------------------------------------------------------------------------- 1 | import { cleanEnv, str, makeValidator } from "envalid"; 2 | import dotenv from "dotenv"; 3 | 4 | // Define allowed environment values 5 | const ALLOWED_ENVIRONMENTS = [ 6 | "dev", 7 | "sepolia", 8 | "ethereum", 9 | "base-sepolia", 10 | "base", 11 | "soneium", 12 | "soneium-minato", 13 | "bsc", 14 | "bsc-testnet", 15 | ] as const; 16 | type Environment = (typeof ALLOWED_ENVIRONMENTS)[number]; 17 | 18 | // Smart wallet factory address is the same for all chain 19 | const FACTORY_ADDRESS = "0xB99BC2E399e06CddCF5E725c0ea341E8f0322834"; 20 | 21 | // Environment-specific configurations 22 | const ENV_CONFIGS = { 23 | dev: { 24 | avsEndpoint: "localhost:2206", 25 | chainId: "11155111", 26 | }, 27 | ethereum: { 28 | avsEndpoint: "aggregator.avaprotocol.org:2206", 29 | chainId: "1", 30 | }, 31 | sepolia: { 32 | avsEndpoint: "aggregator-sepolia.avaprotocol.org:2206", 33 | chainId: "11155111", 34 | }, 35 | base: { 36 | avsEndpoint: "aggregator-base.avaprotocol.org:3206", // TODO:Change to 2207 37 | chainId: "8453", 38 | }, 39 | "base-sepolia": { 40 | avsEndpoint: "aggregator-base-sepolia.avaprotocol.org:3206", // TODO:Change to 2207 41 | chainId: "84532", 42 | }, 43 | soneium: { 44 | avsEndpoint: "aggregator-soneium.avaprotocol.org:2208", 45 | chainId: "1868", 46 | }, 47 | "soneium-minato": { 48 | avsEndpoint: "aggregator-soneium-minato.avaprotocol.org:2208", 49 | chainId: "1946", 50 | }, 51 | bsc: { 52 | avsEndpoint: "aggregator-bsc.avaprotocol.org:2209", 53 | chainId: "56", 54 | }, 55 | "bsc-testnet": { 56 | avsEndpoint: "aggregator-bsc-testnet.avaprotocol.org:2209", 57 | chainId: "97", 58 | }, 59 | } as const; 60 | 61 | // 1. Load base .env file first (for common variables like TEST_PRIVATE_KEY) 62 | dotenv.config(); 63 | 64 | // 2. Then load environment-specific .env file if TEST_ENV is set 65 | if (process.env.TEST_ENV) { 66 | dotenv.config({ path: `.env.${process.env.TEST_ENV}`, override: true }); 67 | } 68 | 69 | // Define the config type 70 | type Config = { 71 | avsEndpoint: string; 72 | avsApiKey: string; 73 | chainEndpoint: string; 74 | chainId: string; 75 | walletPrivateKey: string; 76 | environment: Environment; 77 | factoryAddress: string; 78 | }; 79 | 80 | // Create a custom validator for private key 81 | const privateKeyValidator = makeValidator((privateKey) => { 82 | if (!privateKey || !privateKey.startsWith("0x") || privateKey.length !== 66) { 83 | throw new Error( 84 | "TEST_PRIVATE_KEY must be a valid Ethereum private key (0x-prefixed, 64 hex chars)" 85 | ); 86 | } 87 | return privateKey; 88 | }); 89 | 90 | // Custom validator for environment 91 | const environmentValidator = makeValidator((environment) => { 92 | if (!ALLOWED_ENVIRONMENTS.includes(environment as Environment)) { 93 | throw new Error(`Expected one of: ${ALLOWED_ENVIRONMENTS.join(", ")}`); 94 | } 95 | return environment as Environment; 96 | }); 97 | 98 | // Get the environment from command line or default to dev 99 | const env = process.env.TEST_ENV || "dev"; 100 | 101 | // Validate the environment 102 | const validatedEnv = cleanEnv( 103 | { 104 | AVS_API_KEY: process.env.AVS_API_KEY, 105 | CHAIN_ENDPOINT: process.env.CHAIN_ENDPOINT, 106 | TEST_PRIVATE_KEY: process.env.TEST_PRIVATE_KEY, 107 | TEST_ENV: env, 108 | }, 109 | { 110 | AVS_API_KEY: str({ desc: "AVS API Key" }), 111 | CHAIN_ENDPOINT: str({ desc: "Chain RPC Endpoint" }), 112 | TEST_PRIVATE_KEY: privateKeyValidator({ desc: "Wallet Private Key" }), 113 | TEST_ENV: environmentValidator({ desc: "Test environment", default: "dev" }), 114 | } 115 | ); 116 | 117 | // Get the environment-specific configuration 118 | const envConfig = ENV_CONFIGS[validatedEnv.TEST_ENV]; 119 | 120 | // Export the configuration 121 | export const getConfig = () => ({ 122 | avsEndpoint: envConfig.avsEndpoint, 123 | avsApiKey: validatedEnv.AVS_API_KEY, 124 | chainId: envConfig.chainId, 125 | chainEndpoint: validatedEnv.CHAIN_ENDPOINT, 126 | walletPrivateKey: validatedEnv.TEST_PRIVATE_KEY, 127 | factoryAddress: FACTORY_ADDRESS, 128 | environment: validatedEnv.TEST_ENV, 129 | }); 130 | 131 | // Export the environment for use in other files 132 | export const getEnvironment = () => validatedEnv.TEST_ENV; 133 | 134 | // Export the validation function for use in other files 135 | export const validateEnv = () => validatedEnv; 136 | -------------------------------------------------------------------------------- /tests/utils/mocks/api.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | // Mock responses 4 | export const mockRestApiResponse = { 5 | status: 200, 6 | data: { 7 | success: true, 8 | message: "Test successful", 9 | data: { 10 | id: "123", 11 | value: 42, 12 | }, 13 | }, 14 | }; 15 | 16 | export const mockRestApiError = { 17 | status: 400, 18 | data: { 19 | success: false, 20 | message: "Test failed", 21 | error: "Invalid request", 22 | }, 23 | }; 24 | 25 | // Mock axios for REST API calls 26 | jest.mock("axios"); 27 | const mockedAxios = axios as jest.Mocked; 28 | mockedAxios.post.mockImplementation((url: string) => { 29 | console.log(`[Mock] REST API called: ${url}`); 30 | 31 | if (url === "http://localhost:3000/api/test") { 32 | console.log("[Mock] Returning success response"); 33 | return Promise.resolve(mockRestApiResponse); 34 | } 35 | 36 | console.log("[Mock] Unknown endpoint, returning error"); 37 | return Promise.reject(new Error("Unknown API endpoint")); 38 | }); 39 | -------------------------------------------------------------------------------- /tests/utils/templates.ts: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import { 3 | Edge, 4 | NodeFactory, 5 | TriggerFactory, 6 | } from "@avaprotocol/sdk-js"; 7 | import { 8 | NodeProps, 9 | ContractWriteNodeProps, 10 | ContractReadNodeProps, 11 | ETHTransferNodeProps, 12 | RestAPINodeProps, 13 | CustomCodeNodeProps, 14 | GraphQLQueryNodeProps, 15 | BranchNodeProps, 16 | FilterNodeProps, 17 | WorkflowProps, 18 | } from "@avaprotocol/types"; 19 | import { getNextId } from "./utils"; 20 | import { NodeType, TriggerType, CustomCodeLang } from "@avaprotocol/types"; 21 | import { ethers } from "ethers"; 22 | import { factoryProxyAbi } from "./abis"; 23 | 24 | // Lazy-load configuration to handle CI/CD environments gracefully 25 | function getTestConfig() { 26 | try { 27 | const { getConfig } = require("./envalid"); 28 | return getConfig(); 29 | } catch (error) { 30 | console.warn("⚠️ Environment validation failed in templates, using mock config:", error); 31 | // Return mock config for CI/CD or when real credentials aren't available 32 | return { 33 | factoryAddress: "0x0000000000000000000000000000000000000000", 34 | }; 35 | } 36 | } 37 | 38 | export const defaultTriggerId = getNextId(); 39 | 40 | export const ethTransferNodeProps: ETHTransferNodeProps = { 41 | id: getNextId(), 42 | name: "send eth", 43 | type: NodeType.ETHTransfer, 44 | data: { 45 | destination: "0x2e8bdb63d09ef989a0018eeb1c47ef84e3e61f7b", 46 | amount: "1000000000000000000", // 1 ETH in wei (decimal string) 47 | }, 48 | }; 49 | 50 | // Write to the proxy of our factory contract 0xB99BC2E399e06CddCF5E725c0ea341E8f0322834 51 | // { 52 | // "chainId": 11155111, 53 | // "data": "0x5fbfb9cf000000000000000000000000c60e71bd0f2e6d8832fea1a2d56091c48493c7880000000000000000000000000000000000000000000000000000000000000000", 54 | // "from": "0xc60e71bd0f2e6d8832fea1a2d56091c48493c788", 55 | // "gas": "0x77258", 56 | // "gasPrice": "0x2a1f99", 57 | // "nonce": "0x8", 58 | // "to": "0xB99BC2E399e06CddCF5E725c0ea341E8f0322834" 59 | // } 60 | export const createContractWriteNodeProps = ( 61 | owner: string, 62 | salt: string 63 | ): ContractWriteNodeProps => { 64 | const config = getTestConfig(); 65 | // Encode the createAccount function call 66 | const contract = new ethers.Contract(config.factoryAddress, factoryProxyAbi); 67 | const callData = contract.interface.encodeFunctionData("createAccount", [ 68 | owner, 69 | ethers.toBigInt(salt), 70 | ]); 71 | 72 | return { 73 | id: getNextId(), 74 | name: "create account", 75 | type: NodeType.ContractWrite, 76 | data: { 77 | contractAddress: config.factoryAddress, 78 | callData, 79 | contractAbi: factoryProxyAbi, 80 | }, 81 | }; 82 | }; 83 | 84 | export const createContractReadNodeProps = ( 85 | owner: string, 86 | salt: string 87 | ): ContractReadNodeProps => { 88 | const config = getTestConfig(); 89 | // Encode the getAddress function call 90 | const contract = new ethers.Contract(config.factoryAddress, factoryProxyAbi); 91 | const callData = contract.interface.encodeFunctionData("getAddress", [ 92 | owner, 93 | ethers.toBigInt(salt), 94 | ]); 95 | 96 | return { 97 | id: getNextId(), 98 | name: "get account address", 99 | type: NodeType.ContractRead, 100 | data: { 101 | contractAddress: config.factoryAddress, 102 | contractAbi: factoryProxyAbi, 103 | methodCalls: [ 104 | { 105 | callData, 106 | methodName: "getAddress", 107 | } 108 | ], 109 | }, 110 | }; 111 | }; 112 | 113 | export const restApiNodeProps: RestAPINodeProps = { 114 | id: getNextId(), 115 | name: "rest_api_call", 116 | type: NodeType.RestAPI, 117 | data: { 118 | url: "http://localhost:3000/api/test", 119 | method: "post", 120 | body: `{"test": true}`, 121 | headersMap: [["Content-Type", "application/json"]], 122 | }, 123 | }; 124 | 125 | export const filterNodeProps: FilterNodeProps = { 126 | id: getNextId(), 127 | name: "filterNode", 128 | type: NodeType.Filter, 129 | data: { 130 | sourceId: "rest_api_call", 131 | expression: "current >= 1", 132 | }, 133 | }; 134 | 135 | const graphqlQueryNodeProps: GraphQLQueryNodeProps = { 136 | id: getNextId(), 137 | name: "graphql call", 138 | type: NodeType.GraphQLQuery, 139 | data: { 140 | url: "http://localhost:3000/graphql", 141 | query: `query TestQuery { 142 | test { 143 | id 144 | value 145 | } 146 | }`, 147 | variablesMap: [["test", "true"]], 148 | }, 149 | }; 150 | 151 | const branchNodeProps: BranchNodeProps = { 152 | id: getNextId(), 153 | name: "branch", 154 | type: NodeType.Branch, 155 | data: { 156 | conditions: [ 157 | { id: "b1", type: "if", expression: "foo >= 5" }, 158 | { id: "b2", type: "if", expression: "foo <= -1" }, 159 | { id: "b3", type: "else", expression: "" }, 160 | ], 161 | }, 162 | }; 163 | 164 | const customCodeNodeProps: CustomCodeNodeProps = { 165 | id: getNextId(), 166 | name: "custom code", 167 | type: NodeType.CustomCode, 168 | data: { 169 | lang: CustomCodeLang.JavaScript, 170 | source: "return { foo: 'bar' };", 171 | }, 172 | }; 173 | 174 | export const NodesTemplate = [ethTransferNodeProps]; 175 | 176 | // Programmatically create edges from nodes 177 | const createEdgesFromNodes = (nodes: NodeProps[]): Edge[] => { 178 | return nodes.map((node, index) => { 179 | if (index === 0) { 180 | // First edge connects trigger to first node 181 | return new Edge({ 182 | id: getNextId(), 183 | source: defaultTriggerId, 184 | target: node.id, 185 | }); 186 | } 187 | // Connect each node to the next one 188 | return new Edge({ 189 | id: getNextId(), 190 | source: nodes[index - 1].id, 191 | target: node.id, 192 | }); 193 | }); 194 | }; 195 | 196 | /** 197 | * Workflow templates 198 | */ 199 | export const createFromTemplate = ( 200 | address: string, 201 | nodes?: NodeProps[] 202 | ): WorkflowProps => { 203 | let nodesList: NodeProps[]; 204 | 205 | if (nodes === undefined) { 206 | // Use default template when nodes is not provided 207 | nodesList = NodesTemplate; 208 | } else if (nodes.length === 0) { 209 | // When empty array is explicitly passed, use a minimal no-op node 210 | // This handles cases where tests want to test triggers in isolation 211 | nodesList = [{ 212 | id: getNextId(), 213 | name: "minimal_node", 214 | type: NodeType.CustomCode, 215 | data: { 216 | lang: CustomCodeLang.JavaScript, 217 | source: "return {};", // Minimal no-op code 218 | }, 219 | } as CustomCodeNodeProps]; 220 | } else { 221 | // Use provided nodes 222 | nodesList = nodes; 223 | } 224 | 225 | const now = Date.now(); // Get current time in milliseconds 226 | 227 | return { 228 | smartWalletAddress: address, 229 | nodes: NodeFactory.createNodes(nodesList), 230 | edges: createEdgesFromNodes(nodesList), 231 | trigger: TriggerFactory.create({ 232 | id: defaultTriggerId, 233 | name: "blockTrigger", 234 | type: TriggerType.Block, 235 | data: { interval: 5 }, 236 | }), 237 | startAt: now, 238 | expiredAt: now + 3600 * 24 * 30 * 1000, // Current time + 30 days in milliseconds 239 | maxExecution: 1, 240 | } as WorkflowProps; 241 | }; 242 | 243 | const nodes = [ 244 | ethTransferNodeProps, 245 | restApiNodeProps, 246 | graphqlQueryNodeProps, 247 | branchNodeProps, 248 | customCodeNodeProps, 249 | ]; 250 | 251 | export const MultiNodeWithBranch = { 252 | nodes: NodeFactory.createNodes(nodes), 253 | edges: createEdgesFromNodes(nodes), 254 | trigger: TriggerFactory.create({ 255 | id: defaultTriggerId, 256 | name: "blockTrigger", 257 | type: TriggerType.Block, 258 | data: { interval: 5 }, 259 | }), 260 | startAt: Date.now(), 261 | expiredAt: Date.now() + 3600 * 24 * 30 * 1000, // Current time + 30 days in milliseconds 262 | name: `Test task`, 263 | maxExecution: 1, 264 | }; 265 | 266 | export const blockTriggerEvery5 = TriggerFactory.create({ 267 | id: defaultTriggerId, 268 | name: "blockTrigger", 269 | type: TriggerType.Block, 270 | data: { interval: 5 }, 271 | }); 272 | 273 | // Import Loop node templates 274 | import { 275 | loopNodeWithRestApiProps, 276 | loopNodeWithCustomCodeProps, 277 | loopNodeWithETHTransferProps, 278 | loopNodeWithContractReadProps, 279 | loopNodeWithGraphQLQueryProps, 280 | } from "./templates/loopNode"; 281 | 282 | // Export Loop node templates 283 | export { 284 | loopNodeWithRestApiProps, 285 | loopNodeWithCustomCodeProps, 286 | loopNodeWithETHTransferProps, 287 | loopNodeWithContractReadProps, 288 | loopNodeWithGraphQLQueryProps, 289 | }; 290 | -------------------------------------------------------------------------------- /tests/utils/templates/loopNode.ts: -------------------------------------------------------------------------------- 1 | import { LoopNodeProps } from "@avaprotocol/sdk-js"; 2 | import { NodeType, CustomCodeLang } from "@avaprotocol/types"; 3 | import { getNextId } from "../utils"; 4 | 5 | export const loopNodeWithRestApiProps: LoopNodeProps = { 6 | id: getNextId(), 7 | name: "loop_with_rest_api", 8 | type: NodeType.Loop, 9 | data: { 10 | sourceId: "testArray", 11 | iterVal: "item", 12 | iterKey: "index", 13 | restApi: { 14 | config: { 15 | url: "https://httpbin.org/post", 16 | method: "POST", 17 | body: JSON.stringify({ data: "{{item}}", index: "{{index}}" }), 18 | headersMap: [["Content-Type", "application/json"]], 19 | }, 20 | }, 21 | }, 22 | }; 23 | 24 | export const loopNodeWithCustomCodeProps: LoopNodeProps = { 25 | id: getNextId(), 26 | name: "loop_with_custom_code", 27 | type: NodeType.Loop, 28 | data: { 29 | sourceId: "testArray", 30 | iterVal: "item", 31 | iterKey: "index", 32 | customCode: { 33 | config: { 34 | lang: CustomCodeLang.JavaScript, 35 | source: `const result = { processedItem: item, position: index }; return result;`, 36 | }, 37 | }, 38 | }, 39 | }; 40 | 41 | export const loopNodeWithETHTransferProps: LoopNodeProps = { 42 | id: getNextId(), 43 | name: "loop_with_eth_transfer", 44 | type: NodeType.Loop, 45 | data: { 46 | sourceId: "addressArray", 47 | iterVal: "address", 48 | iterKey: "index", 49 | ethTransfer: { 50 | config: { 51 | destination: "{{address}}", 52 | amount: "1000000000000000000", // 1 ETH in wei (decimal string) 53 | }, 54 | }, 55 | }, 56 | }; 57 | 58 | export const loopNodeWithContractReadProps: LoopNodeProps = { 59 | id: getNextId(), 60 | name: "loop_with_contract_read", 61 | type: NodeType.Loop, 62 | data: { 63 | sourceId: "contractArray", 64 | iterVal: "contract", 65 | iterKey: "index", 66 | contractRead: { 67 | config: { 68 | contractAddress: "{{contract.address}}", 69 | contractAbi: "{{contract.abi}}", 70 | methodCallsList: [ 71 | { 72 | callData: "{{contract.callData}}", 73 | methodName: "{{contract.methodName}}", 74 | }, 75 | ], 76 | }, 77 | }, 78 | }, 79 | }; 80 | 81 | export const loopNodeWithGraphQLQueryProps: LoopNodeProps = { 82 | id: getNextId(), 83 | name: "loop_with_graphql_query", 84 | type: NodeType.Loop, 85 | data: { 86 | sourceId: "queryArray", 87 | iterVal: "query", 88 | iterKey: "index", 89 | graphqlDataQuery: { 90 | config: { 91 | url: "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3", 92 | query: ` 93 | query GetToken($id: String!) { 94 | token(id: $id) { 95 | id 96 | symbol 97 | name 98 | decimals 99 | } 100 | } 101 | `, 102 | variablesMap: [["id", "{{query.id}}"]], 103 | }, 104 | }, 105 | }, 106 | }; 107 | -------------------------------------------------------------------------------- /tests/workflows/cancelWorkflow.test.ts: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import { describe, beforeAll, test, expect } from "@jest/globals"; 3 | import { Client } from "@avaprotocol/sdk-js"; 4 | import { getAddress, generateSignature, SaltGlobal } from "../utils/utils"; 5 | import { createFromTemplate } from "../utils/templates"; 6 | import { WorkflowStatus } from "@avaprotocol/types"; 7 | import { getConfig } from "../utils/envalid"; 8 | 9 | // Get environment variables from envalid config 10 | const { avsEndpoint, walletPrivateKey, factoryAddress } = getConfig(); 11 | 12 | let saltIndex = SaltGlobal.CancelWorkflow * 1000; // Salt index 1,000 - 1,999 13 | 14 | describe("cancelWorkflow Tests", () => { 15 | let client: Client; 16 | 17 | beforeAll(async () => { 18 | const eoaAddress = await getAddress(walletPrivateKey); 19 | console.log("Owner wallet address:", eoaAddress); 20 | // Initialize the client with test credentials 21 | client = new Client({ 22 | endpoint: avsEndpoint, 23 | factoryAddress, 24 | }); 25 | 26 | const { message } = await client.getSignatureFormat(eoaAddress); 27 | const signature = await generateSignature(message, walletPrivateKey); 28 | const res = await client.authWithSignature({ 29 | message: message, 30 | signature: signature, 31 | }); 32 | 33 | client.setAuthKey(res.authKey); 34 | }); 35 | 36 | test("should cancel task when authenticated with signature", async () => { 37 | const wallet = await client.getWallet({ salt: _.toString(saltIndex++) }); 38 | let workflowId: string | undefined; 39 | 40 | try { 41 | const workflowProps = createFromTemplate(wallet.address); 42 | const workflow = client.createWorkflow(workflowProps); 43 | workflowId = await client.submitWorkflow(workflow); 44 | 45 | const result = await client.cancelWorkflow(workflowId); 46 | expect(result.success).toBe(true); 47 | 48 | const cancelResult = await client.getWorkflow(workflowId); 49 | expect(cancelResult.id).toEqual(workflowId); 50 | expect(cancelResult.status).toEqual(WorkflowStatus.Canceled); 51 | } finally { 52 | if (workflowId) { 53 | await client.deleteWorkflow(workflowId); 54 | } 55 | } 56 | }); 57 | 58 | test("should return error response when canceling a non-existent task", async () => { 59 | const result = await client.cancelWorkflow("non-existent-task-id"); 60 | expect(result.success).toBe(false); 61 | expect(result.status).toBe("not_found"); 62 | expect(result.message).toMatch(/task not found/i); 63 | expect(result.id).toBe("non-existent-task-id"); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /tests/workflows/deleteWorkflow.test.ts: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import { describe, beforeAll, test, expect } from "@jest/globals"; 3 | import { Client } from "@avaprotocol/sdk-js"; 4 | import { getAddress, generateSignature, SaltGlobal } from "../utils/utils"; 5 | import { createFromTemplate } from "../utils/templates"; 6 | import { getConfig } from "../utils/envalid"; 7 | 8 | // Get environment variables from envalid config 9 | const { avsEndpoint, walletPrivateKey, factoryAddress } = getConfig(); 10 | 11 | let saltIndex = SaltGlobal.DeleteWorkflow * 1000; // Salt index 3,000 - 3,999 12 | 13 | describe("deleteWorkflow Tests", () => { 14 | let client: Client; 15 | 16 | beforeAll(async () => { 17 | const eoaAddress = await getAddress(walletPrivateKey); 18 | console.log("Owner wallet address:", eoaAddress); 19 | 20 | // Initialize the client with test credentials 21 | client = new Client({ 22 | endpoint: avsEndpoint, 23 | factoryAddress, 24 | }); 25 | 26 | const { message } = await client.getSignatureFormat(eoaAddress); 27 | const signature = await generateSignature(message, walletPrivateKey); 28 | const res = await client.authWithSignature({ 29 | message: message, 30 | signature: signature, 31 | }); 32 | 33 | client.setAuthKey(res.authKey); 34 | }); 35 | 36 | test("should delete task when authenticated with signature", async () => { 37 | const wallet = await client.getWallet({ salt: _.toString(saltIndex++) }); 38 | let workflowId: string | undefined; 39 | 40 | const workflowProps = createFromTemplate(wallet.address); 41 | const workflow = client.createWorkflow(workflowProps); 42 | workflowId = await client.submitWorkflow(workflow); 43 | 44 | const result = await client.deleteWorkflow(workflowId); 45 | expect(result.success).toBe(true); 46 | 47 | const listRes = await client.getWorkflows([wallet.address]); 48 | 49 | expect(Array.isArray(listRes.items)).toBe(true); 50 | expect(listRes.items.some((task) => task.id === workflowId)).toBe(false); 51 | }); 52 | 53 | test("should return error response when deleting a non-existent task", async () => { 54 | const result = await client.deleteWorkflow("non-existent-task-id"); 55 | expect(result.success).toBe(false); 56 | expect(result.status).toBe("not_found"); 57 | expect(result.message).toMatch(/task not found/i); 58 | expect(result.id).toBe("non-existent-task-id"); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true, 9 | "typeRoots": ["node_modules/@types"], 10 | "baseUrl": ".", 11 | "paths": { 12 | "@/grpc_codegen/*": ["grpc_codegen/*"], 13 | // "@avaprotocol/sdk-js": ["packages/sdk-js/src/*"], 14 | // "@avaprotocol/types": ["packages/types/*"] 15 | } 16 | }, 17 | "include": ["grpc_codegen/**/*", "tests", "examples"], 18 | "ts-node": { 19 | "esm": true 20 | } 21 | } 22 | --------------------------------------------------------------------------------