├── .npmignore ├── src ├── shared │ ├── index.ts │ ├── constants.ts │ ├── types.ts │ └── utils.ts ├── packages │ ├── index.ts │ └── tools.ts ├── core │ ├── index.ts │ ├── lib │ │ └── abi.ts │ ├── query.ts │ ├── axiom.ts │ └── internalConfig.ts ├── v2 │ ├── index.ts │ ├── query │ │ ├── index.ts │ │ ├── gasCalc.ts │ │ ├── queryV2.ts │ │ ├── dataSubquery │ │ │ ├── utils.ts │ │ │ ├── build.ts │ │ │ ├── configLimitManager.ts │ │ │ ├── validate.ts │ │ │ └── subqueryBuilder.ts │ │ └── queryBuilderV2.ts │ ├── types.ts │ └── constants.ts ├── index.ts └── version.ts ├── env.example ├── .prettierrc ├── jest.config.js ├── .gitignore ├── scripts ├── preTsc.js └── postTsc.js ├── tsconfig.json ├── .github └── workflows │ └── test.yml ├── LICENSE ├── package.json ├── test └── unit │ ├── v2 │ ├── crosschain.test.ts │ ├── queryBuilderOptions.test.ts │ ├── basicInitialization.test.ts │ ├── buildDataQueryStandalone.test.ts │ ├── calculatorCalldataGas.test.ts │ ├── validate.test.ts │ ├── dataSubqueryBuilders.test.ts │ ├── dataQueryCapacitySdk.test.ts │ ├── buildQueryNoCallback.test.ts │ ├── buildComputeQueryWithDataQuery.test.ts │ ├── queryParams.test.ts │ └── buildAll.test.ts │ └── misc │ └── utils.test.ts └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | -------------------------------------------------------------------------------- /src/packages/index.ts: -------------------------------------------------------------------------------- 1 | export * from './tools'; 2 | -------------------------------------------------------------------------------- /src/core/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | AxiomSdkCore 3 | } from './axiom'; 4 | -------------------------------------------------------------------------------- /src/v2/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./query"; 2 | export * from "./types"; 3 | -------------------------------------------------------------------------------- /env.example: -------------------------------------------------------------------------------- 1 | PROVIDER_URI= 2 | PROVIDER_URI_GOERLI= 3 | PRIVATE_KEY= 4 | PRIVATE_KEY_GOERLI= 5 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './core'; 2 | export * from './shared'; 3 | export * from './v2'; 4 | export * from './packages'; 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "trailingComma": "all", 7 | "bracketSpacing": true 8 | } 9 | -------------------------------------------------------------------------------- /src/version.ts: -------------------------------------------------------------------------------- 1 | // This is an autogenerated file. It should match the version number in package.json. 2 | // Do not modify this file directly. 3 | 4 | export const SDK_VERSION = "2.3.8"; -------------------------------------------------------------------------------- /src/v2/query/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./dataSubquery/subqueryBuilder"; 2 | export * from "./dataSubquery/utils"; 3 | 4 | export type { QueryV2 } from "./queryV2"; 5 | export type { QueryBuilderV2 } from "./queryBuilderV2"; 6 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | require('dotenv').config({ 3 | path: '.env.local' 4 | }); 5 | 6 | module.exports = { 7 | preset: 'ts-jest', 8 | testEnvironment: 'node', 9 | modulePathIgnorePatterns: ["/dist/"], 10 | }; -------------------------------------------------------------------------------- /src/core/lib/abi.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import AxiomV2QueryAbi from "./abi/AxiomV2Query.json"; 3 | 4 | export function getAxiomQueryAbiForVersion( 5 | version: string 6 | ): ethers.InterfaceAbi { 7 | switch (version) { 8 | case "v2": 9 | return AxiomV2QueryAbi.abi; 10 | default: 11 | throw new Error(`Version ${version} does not have a Query ABI`); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/v2/query/gasCalc.ts: -------------------------------------------------------------------------------- 1 | import { ByteStringReader } from "@axiom-crypto/tools"; 2 | 3 | export function calculateCalldataGas(hexString: string): number { 4 | let gas = 0; 5 | const reader = new ByteStringReader(hexString); 6 | while (reader.getNumBytesRemaining() > 0) { 7 | const byte = reader.readBytes(1); 8 | if (byte === "0x00") { 9 | gas += 4; 10 | } else { 11 | gas += 16; 12 | } 13 | } 14 | 15 | return gas; 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | dist 14 | 15 | # misc 16 | .DS_Store 17 | *.pem 18 | debug/ 19 | 20 | # debug 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | test/debug 25 | 26 | # local env files 27 | .env*.local 28 | 29 | # typescript 30 | *.tsbuildinfo 31 | -------------------------------------------------------------------------------- /src/core/query.ts: -------------------------------------------------------------------------------- 1 | import { InternalConfig } from "./internalConfig"; 2 | 3 | export abstract class Query { 4 | protected readonly config: InternalConfig; 5 | 6 | constructor(config: InternalConfig) { 7 | this.config = config; 8 | } 9 | 10 | new(...a: any): any { 11 | throw new Error( 12 | "Typecast Query object to the appropriate version to use this method. Example:\n\n" + 13 | "const axiom = new AxiomSdkCore(config)\n" + 14 | "const aq = axiom.query as QueryV2;\n" + 15 | "const query = aq.new();" 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /scripts/preTsc.js: -------------------------------------------------------------------------------- 1 | // Functions that are to be run before the typescript compiler runs 2 | 3 | const fs = require('fs'); 4 | const packageJson = require('../package.json'); 5 | 6 | // Copies the version number from package.json to src/version.ts 7 | function copyVersion() { 8 | const version = packageJson.version; 9 | const versionFile = `// This is an autogenerated file. It should match the version number in package.json. 10 | // Do not modify this file directly. 11 | 12 | export const SDK_VERSION = "${version}";` 13 | fs.writeFileSync('./src/version.ts', versionFile); 14 | } 15 | 16 | copyVersion(); -------------------------------------------------------------------------------- /scripts/postTsc.js: -------------------------------------------------------------------------------- 1 | // Functions that are to be run after the typescript compiler runs 2 | 3 | const fs = require("fs"); 4 | const packageJson = require("../package.json"); 5 | 6 | // Copies a modified version of package.json to the /dist folder 7 | function copyPackageJson() { 8 | let packageJsonCopy = { ...packageJson }; 9 | // packageJsonCopy.name = "@axiom-crypto/core-rc"; 10 | delete packageJsonCopy.scripts; 11 | delete packageJsonCopy.devDependencies; 12 | delete packageJsonCopy.publishConfig; 13 | fs.writeFileSync("./dist/package.json", JSON.stringify(packageJsonCopy, null, 2)); 14 | } 15 | 16 | function copyReadme() { 17 | fs.copyFileSync("./README.md", "./dist/README.md"); 18 | } 19 | 20 | copyPackageJson(); 21 | copyReadme(); 22 | -------------------------------------------------------------------------------- /src/packages/tools.ts: -------------------------------------------------------------------------------- 1 | export { 2 | decodeQuery, 3 | decodeFullQueryV2, 4 | encodeQueryV2, 5 | encodeFullQueryV2, 6 | bytes32, 7 | getEventSchema, 8 | getFunctionSelector, 9 | getFunctionSignature, 10 | getByteLength, 11 | packSlot, 12 | unpackSlot, 13 | checkFitsInSlot, 14 | getSlotForMapping, 15 | getSlotForArray, 16 | getRawTransaction, 17 | getRawReceipt, 18 | getFullBlock, 19 | getAccountData, 20 | getTxHash, 21 | getBlockNumberAndTxIdx, 22 | formatDataRlp, 23 | objectToRlp, 24 | rlpEncodeBlockHeader, 25 | rlpEncodeTransaction, 26 | ByteStringReader, 27 | IpfsClient, 28 | PinataIpfsClient, 29 | QuicknodeIpfsClient, 30 | IpfsResult, 31 | ByteLengths, 32 | MaxSizes, 33 | } from "@axiom-crypto/tools"; 34 | -------------------------------------------------------------------------------- /src/core/axiom.ts: -------------------------------------------------------------------------------- 1 | import { InternalConfig } from './internalConfig'; 2 | import { AxiomSdkCoreConfig } from '../shared/types'; 3 | import { Query } from './query'; 4 | import { QueryV2 } from '../v2/query/queryV2'; 5 | 6 | export class AxiomSdkCore { 7 | /** 8 | * Axiom configuration parameters 9 | */ 10 | readonly config: InternalConfig; 11 | 12 | /** 13 | * Functions that handle querying the Axiom Query database 14 | */ 15 | readonly query: Query; 16 | 17 | constructor(config: AxiomSdkCoreConfig, overrides?: any) { 18 | this.config = new InternalConfig(config, overrides); 19 | 20 | switch (this.config.version) { 21 | case "v2": 22 | this.query = new QueryV2(this.config); 23 | break; 24 | default: 25 | throw new Error(`Invalid version detected: ${this.config.version}`) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "CommonJS", 5 | "moduleResolution": "node", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "esModuleInterop": true, 9 | "resolveJsonModule": true, 10 | "importHelpers": true, 11 | "lib": [ 12 | "es2020", 13 | "es6" 14 | ], 15 | "preserveSymlinks": true, 16 | "preserveWatchOutput": true, 17 | "pretty": false, 18 | "strict": true, 19 | "sourceMap": true, 20 | "skipLibCheck": true, 21 | "outDir": "dist", 22 | "types": [ 23 | "node", 24 | "jest" 25 | ] 26 | }, 27 | "include": [ 28 | "src/**/*.ts", 29 | "src/**/*.json" 30 | ], 31 | "exclude": [ 32 | "dist", 33 | "node_modules", 34 | "scripts", 35 | "test" 36 | ], 37 | "ts-node": { 38 | "esm": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - v2 8 | - rc1 9 | - rc2 10 | pull_request: 11 | 12 | jobs: 13 | build: 14 | name: Jest Testing 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout Code 19 | uses: actions/checkout@v3 20 | 21 | - name: Use Node.js 18.x 22 | uses: actions/setup-node@v2 23 | with: 24 | node-version: 18.x 25 | 26 | - name: Install Dependencies 27 | run: npm install 28 | 29 | - name: Run Unit Tests 30 | run: | 31 | export PRIVATE_KEY=${{ secrets.PRIVATE_KEY }} 32 | export PRIVATE_KEY_SEPOLIA=${{ secrets.PRIVATE_KEY_SEPOLIA }} 33 | export PROVIDER_URI=${{ secrets.PROVIDER_URI }} 34 | export PROVIDER_URI_GOERLI=${{ secrets.PROVIDER_URI_GOERLI }} 35 | export PROVIDER_URI_SEPOLIA=${{ secrets.PROVIDER_URI_SEPOLIA }} 36 | npm test test/unit 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Intrinsic Technologies, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/v2/query/queryV2.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AxiomV2Callback, 3 | AxiomV2ComputeQuery, 4 | } from "@axiom-crypto/tools"; 5 | import { InternalConfig } from "../../core/internalConfig"; 6 | import { Query } from "../../core/query"; 7 | import { 8 | AxiomV2QueryOptions, 9 | UnbuiltSubquery, 10 | } from "../types"; 11 | import { QueryBuilderV2 } from './queryBuilderV2'; 12 | 13 | export class QueryV2 extends Query { 14 | /** 15 | * @param config Axiom internal configuration parameters 16 | */ 17 | constructor(config: InternalConfig) { 18 | super(config); 19 | } 20 | 21 | /** 22 | * Creates a new `QueryBuilderV2`` instance 23 | * @param dataQuery (optional) An array of data subqueries to be included 24 | * @param computeQuery (optional) A compute query 25 | * @param callback (optional) The function on a contract to the called with the Query results 26 | * @param options (optional) Additional options for the Query 27 | * @returns 28 | */ 29 | new( 30 | dataQuery?: UnbuiltSubquery[], 31 | computeQuery?: AxiomV2ComputeQuery, 32 | callback?: AxiomV2Callback, 33 | options?: AxiomV2QueryOptions, 34 | ): QueryBuilderV2 { 35 | return new QueryBuilderV2(this.config, dataQuery, computeQuery, callback, options); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/shared/constants.ts: -------------------------------------------------------------------------------- 1 | export const SharedConstants = Object.freeze({ 2 | EIP2930_BLOCK: 12244000, 3 | EIP1559_BLOCK: 12965000, 4 | }); 5 | 6 | export const Versions = ["v2"]; 7 | 8 | export type VersionsType = (typeof Versions)[number]; 9 | 10 | export const ContractEvents = Object.freeze({ 11 | QueryInitiatedOnchain: "QueryInitiatedOnchain", 12 | QueryFulfilled: "QueryFulfilled", 13 | }); 14 | 15 | /// Update constants using the same nested object structure as the versionData variable. 16 | /// Pass the updateObject in as an override when initializing Axiom. 17 | export function updateConstants( 18 | versionData: any, 19 | version: string, 20 | updateObject: any 21 | ): any { 22 | const versionUpdateObject = { 23 | [version]: updateObject, 24 | }; 25 | return updateConstantsRecursive({ ...versionData }, versionUpdateObject); 26 | } 27 | 28 | function updateConstantsRecursive(versionMem: any, updateMem: any): any { 29 | const keys = Object.keys(updateMem); 30 | for (const key of keys) { 31 | if (versionMem[key] === undefined) { 32 | console.log("versionData does not have key", key); 33 | continue; 34 | } 35 | if (typeof updateMem[key] !== "object") { 36 | versionMem[key] = updateMem[key]; 37 | continue; 38 | } 39 | updateConstantsRecursive(versionMem[key], updateMem[key]); 40 | } 41 | return versionMem; 42 | } 43 | 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@axiom-crypto/core", 3 | "version": "2.3.8", 4 | "description": "SDK to build on top of Axiom, the ZK Coprocessor for Ethereum.", 5 | "author": "Intrinsic Technologies", 6 | "license": "MIT", 7 | "main": "index.js", 8 | "types": "index.d.ts", 9 | "scripts": { 10 | "build": "ts-node scripts/preTsc.js && rm -rf ./dist/* && tsc && ts-node scripts/postTsc.js", 11 | "build:staging": "ts-node scripts/preTsc.js && rm -rf ./dist/* && ENV=staging tsc && ts-node scripts/postTsc.js", 12 | "build:prod": "ts-node scripts/preTsc.js && rm -rf ./dist/* && ENV=prod tsc && ts-node scripts/postTsc.js", 13 | "test": "jest", 14 | "test:unit": "jest test/unit", 15 | "test:integration": "jest --runInBand test/integration", 16 | "test:integration:mock": "MOCK=true jest --runInBand test/integration", 17 | "dev": "ts-node src/index.ts" 18 | }, 19 | "publishConfig": { 20 | "directory": "dist" 21 | }, 22 | "keywords": ["axiom", "ethereum", "zero knowledge", "zk", "coprocessor", "crypto"], 23 | "devDependencies": { 24 | "@swc/cli": "^0.1.62", 25 | "@swc/core": "^1.3.96", 26 | "@swc/jest": "^0.2.29", 27 | "@types/jest": "^29.5.8", 28 | "@types/node": "^18.18.9", 29 | "dotenv": "^16.3.1", 30 | "jest": "^29.7.0", 31 | "prettier": "^3.0.3", 32 | "ts-jest": "^29.1.1", 33 | "ts-node": "^10.9.1", 34 | "tslib": "^2.6.2", 35 | "typescript": "^5.2.2" 36 | }, 37 | "dependencies": { 38 | "@axiom-crypto/tools": "2.1.0", 39 | "axios": "^1.6.1", 40 | "bs58": "^5.0.0", 41 | "ethers": "^6.8.1", 42 | "merkletreejs": "^0.3.11" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/shared/types.ts: -------------------------------------------------------------------------------- 1 | export interface AxiomSdkCoreConfig { 2 | /** 3 | * Axiom API key (required) 4 | */ 5 | apiKey?: string; 6 | 7 | /** 8 | * Full provider URI (https:// or wss://) from service such as Infura 9 | * or Alchemy (required) 10 | */ 11 | providerUri: string; 12 | 13 | /** 14 | * Provider URI for the target chain 15 | */ 16 | targetProviderUri?: string; 17 | 18 | /** 19 | * Backup provider URIs 20 | */ 21 | backupProviderUri?: string; 22 | backupTargetProviderUri?: string; 23 | 24 | /** 25 | * The chain ID to use 26 | * (default: 1 (mainnet))) 27 | */ 28 | chainId?: number | string | BigInt; 29 | targetChainId?: number | string | BigInt; 30 | 31 | /** 32 | * Axiom contract version number that we're targeting 33 | * (default: latest version) 34 | */ 35 | version?: string; 36 | 37 | /** 38 | * Default timeout in milliseconds for Axiom API calls 39 | * (default: 10000) 40 | */ 41 | timeoutMs?: number; 42 | 43 | /** 44 | * Optional private key used for signing transactions 45 | */ 46 | privateKey?: string; 47 | 48 | /** 49 | * Sets usage of mock prover and database for testing 50 | */ 51 | mock?: boolean; 52 | } 53 | 54 | export interface BlockHashWitness { 55 | /** 56 | * The block number of the block containing the transaction 57 | */ 58 | blockNumber: number; 59 | 60 | /** 61 | * The claimed block hash of the block 62 | */ 63 | claimedBlockHash: string; 64 | 65 | /** 66 | * The hash of the previous group of blocks 67 | */ 68 | prevHash: string; 69 | 70 | /** 71 | * The number of transactions included in this group 72 | */ 73 | numFinal: number; 74 | 75 | /** 76 | * Merkle inclusion proof of this blockhash in the group of blocks 77 | */ 78 | merkleProof: string[]; 79 | } 80 | -------------------------------------------------------------------------------- /src/v2/query/dataSubquery/utils.ts: -------------------------------------------------------------------------------- 1 | import { DataSubqueryType } from "@axiom-crypto/tools"; 2 | 3 | export function getUnbuiltSubqueryTypeFromKeys(keys: string[]): DataSubqueryType { 4 | switch (keys.join(",")) { 5 | case ["blockNumber", "fieldIdx"].join(","): 6 | return DataSubqueryType.Header; 7 | case ["blockNumber", "addr", "fieldIdx"].join(","): 8 | return DataSubqueryType.Account; 9 | case ["blockNumber", "addr", "slot"].join(","): 10 | return DataSubqueryType.Storage; 11 | case ["txHash", "fieldOrCalldataIdx"].join(","): 12 | return DataSubqueryType.Transaction; 13 | case ["txHash", "fieldOrLogIdx", "topicOrDataOrAddressIdx", "eventSchema"].join(","): 14 | return DataSubqueryType.Receipt; 15 | case ["blockNumber", "addr", "mappingSlot", "mappingDepth", "keys"].join(","): 16 | return DataSubqueryType.SolidityNestedMapping; 17 | default: 18 | throw new Error(`Could not infer subquery type from keys ${keys}`); 19 | } 20 | } 21 | 22 | export function getBuiltSubqueryTypeFromKeys(keys: string[]): DataSubqueryType { 23 | switch (keys.join(",")) { 24 | case ["blockNumber", "fieldIdx"].join(","): 25 | return DataSubqueryType.Header; 26 | case ["blockNumber", "addr", "fieldIdx"].join(","): 27 | return DataSubqueryType.Account; 28 | case ["blockNumber", "addr", "slot"].join(","): 29 | return DataSubqueryType.Storage; 30 | case ["blockNumber", "txIdx", "fieldOrCalldataIdx"].join(","): 31 | return DataSubqueryType.Transaction; 32 | case ["blockNumber", "txIdx", "fieldOrLogIdx", "topicOrDataOrAddressIdx", "eventSchema"].join(","): 33 | return DataSubqueryType.Receipt; 34 | case ["blockNumber", "addr", "mappingSlot", "mappingDepth", "keys"].join(","): 35 | return DataSubqueryType.SolidityNestedMapping; 36 | default: 37 | throw new Error(`Could not infer subquery type from keys ${keys}`); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/unit/v2/crosschain.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AxiomSdkCore, 3 | AxiomSdkCoreConfig, 4 | AxiomV2Callback, 5 | HeaderField, 6 | QueryV2, 7 | bytes32, 8 | } from "../../../src"; 9 | 10 | // Test coverage areas: 11 | // - Crosschain 12 | 13 | describe("Crosschain", () => { 14 | const callback: AxiomV2Callback = { 15 | target: "0x41a7a901ef58d383801272d2408276d96973550d", 16 | extraData: bytes32("0xbbd0d3671093a36d6e3b608a7e3b1fdc96da1116"), 17 | }; 18 | 19 | test("Build a query with a different target chain", async () => { 20 | const config: AxiomSdkCoreConfig = { 21 | providerUri: process.env.PROVIDER_URI as string, 22 | targetChainId: 5, 23 | targetProviderUri: process.env.PROVIDER_URI_GOERLI as string, 24 | version: "v2", 25 | }; 26 | const axiom = new AxiomSdkCore(config); 27 | const blockNumber = 18200000; 28 | const dataQueryReq = [ 29 | { 30 | blockNumber: blockNumber, 31 | fieldIdx: HeaderField.GasUsed, 32 | }, 33 | { 34 | blockNumber: blockNumber + 100, 35 | fieldIdx: HeaderField.GasUsed, 36 | }, 37 | ]; 38 | const query = (axiom.query as QueryV2).new(); 39 | query.append(dataQueryReq); 40 | query.setCallback(callback); 41 | 42 | await query.build(); 43 | const builtQuery = query.getBuiltQuery(); 44 | if (!builtQuery) { 45 | throw new Error("Query is not built"); 46 | } 47 | 48 | expect(builtQuery.sourceChainId).toEqual("1"); 49 | expect(builtQuery.targetChainId).toEqual("5"); 50 | expect(builtQuery.queryHash).toEqual("0xda1933a884934070a870d18243ec2f1a7efa869966c4cf52d03b179c998a4825"); 51 | expect(builtQuery.dataQueryHash).toEqual("0xfaaac492509be62a2026a769d31140ee49e4b662e56c95251b8ca6ccace0e91b"); 52 | expect(builtQuery.dataQuery).toEqual("0x0000000000000001000200010115b5c00000000a00010115b6240000000a"); 53 | expect(builtQuery.computeQuery.k).toEqual(0); 54 | expect(builtQuery.computeQuery.vkey.length).toEqual(0); 55 | expect(builtQuery.computeQuery.vkey).toEqual([]); 56 | expect(builtQuery.computeQuery.computeProof).toEqual("0x00"); 57 | }); 58 | }); -------------------------------------------------------------------------------- /src/shared/utils.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | 3 | export function concatHexStrings(...args: string[]) { 4 | return `0x${args.map((s) => { 5 | if (s.substring(0, 2) === '0x') { 6 | return s.substring(2, s.length) 7 | } else { 8 | return s; 9 | } 10 | }).join('')}`; 11 | } 12 | 13 | export function sortBlockNumber(a: number, b: number) { 14 | return a - b; 15 | }; 16 | 17 | export function sortAddress(a?: string, b?: string) { 18 | if (a === undefined && b === undefined) { 19 | return 0; 20 | } else if (a === undefined) { 21 | return -1; 22 | } else if (b === undefined) { 23 | return 1; 24 | } 25 | return parseInt(a, 16) - parseInt(b, 16); 26 | }; 27 | 28 | export function sortSlot( 29 | a?: ethers.BigNumberish, 30 | b?: ethers.BigNumberish 31 | ) { 32 | if (a === undefined && b === undefined) { 33 | return 0; 34 | } else if (a === undefined) { 35 | return -1; 36 | } else if (b === undefined) { 37 | return 1; 38 | } 39 | return parseInt(a.toString(), 16) - parseInt(b.toString(), 16); 40 | }; 41 | 42 | export function deepSort( 43 | arr: any[], 44 | keys: string[], 45 | sortFns: ((a: any, b: any) => number)[], 46 | ) { 47 | return arr.sort((a, b) => { 48 | let result = 0; 49 | for (let i = 0; i < keys.length; i++) { 50 | result = sortFns[i](a[keys[i]], b[keys[i]]); 51 | if (result !== 0) { 52 | return result; 53 | } 54 | } 55 | return result; 56 | }); 57 | } 58 | 59 | export function sortByNumber(a: number, b: number) { 60 | return a - b; 61 | } 62 | 63 | export function sortByHex(a: string, b: string) { 64 | return parseInt(a, 16) - parseInt(b, 16); 65 | } 66 | 67 | // Deep copy any object with nested objects. Will not deep copy functions inside the object. 68 | export function deepCopyObject(obj: any): any { 69 | return JSON.parse(JSON.stringify(obj)); 70 | } 71 | 72 | export function fillArray(length: number, value: string) { 73 | return Array(length).fill(value); 74 | } 75 | 76 | export function resizeArray(arr: string[], size: number, defaultValue: string) { 77 | if (arr.length < size) { 78 | return arr.concat(Array(size - arr.length).fill(defaultValue)); 79 | } 80 | return arr.slice(0, size); 81 | }; -------------------------------------------------------------------------------- /test/unit/misc/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getEventSchema, 3 | getFunctionSelector, 4 | getFunctionSignature, 5 | } from "@axiom-crypto/tools"; 6 | 7 | describe("Utils", () => { 8 | test("Get a function signature", () => { 9 | let signature = getFunctionSignature("transfer(address,uint256)"); 10 | expect(signature).toEqual("transfer(address,uint256)"); 11 | signature = getFunctionSignature("transfer(address to, uint256 amt)"); 12 | expect(signature).toEqual("transfer(address,uint256)"); 13 | signature = getFunctionSignature("transfer (address to, uint256 amt) public"); 14 | expect(signature).toEqual("transfer(address,uint256)"); 15 | signature = getFunctionSignature("transfer (address payable to, bytes32 aHash, uint gas)"); 16 | expect(signature).toEqual("transfer(address,bytes32,uint256)"); 17 | }); 18 | 19 | test("Calculate function selector", () => { 20 | let selector = getFunctionSelector("transfer", ["address","uint256"]); 21 | expect(selector).toEqual("0xa9059cbb"); 22 | selector = getFunctionSelector("transfer", ["address","uint"]); 23 | expect(selector).toEqual("0xa9059cbb"); 24 | selector = getFunctionSelector("transfer", ["address"," uint256"]); 25 | expect(selector).toEqual("0xa9059cbb"); 26 | selector = getFunctionSelector("validate", ["address"]); 27 | expect(selector).toEqual("0x207c64fb"); 28 | selector = getFunctionSelector("validate(address)"); 29 | expect(selector).toEqual("0x207c64fb"); 30 | selector = getFunctionSelector("transfer(address,uint256)"); 31 | expect(selector).toEqual("0xa9059cbb"); 32 | selector = getFunctionSelector("transfer(address,uint)"); 33 | expect(selector).toEqual("0xa9059cbb"); 34 | }); 35 | 36 | test("Calculate event schema", () => { 37 | let schema = getEventSchema("Transfer", ["address","address","uint256"]); 38 | expect(schema).toEqual("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"); 39 | schema = getEventSchema("Transfer(address,address,uint256)"); 40 | expect(schema).toEqual("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"); 41 | schema = getEventSchema("Transfer (index_topic_1 address from, index_topic_2 address to, uint256 tokens)"); 42 | expect(schema).toEqual("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"); 43 | schema = getEventSchema("Transfer (index_topic_1 address from, index_topic_2 address to, uint256 tokens)View Source"); 44 | expect(schema).toEqual("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"); 45 | }); 46 | }); -------------------------------------------------------------------------------- /src/v2/types.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AxiomV2Callback, 3 | AxiomV2ComputeQuery, 4 | AxiomV2DataQuery, 5 | AxiomV2FeeData, 6 | } from "@axiom-crypto/tools"; 7 | 8 | export { 9 | AxiomV2Callback, 10 | AxiomV2ComputeQuery, 11 | AxiomV2DataQuery, 12 | AxiomV2FeeData, 13 | DataSubquery, 14 | HeaderSubquery, 15 | AccountSubquery, 16 | StorageSubquery, 17 | TxSubquery, 18 | ReceiptSubquery, 19 | Subquery, 20 | SolidityNestedMappingSubquery, 21 | BeaconValidatorSubquery, 22 | DataSubqueryType, 23 | HeaderField, 24 | AccountField, 25 | TxField, 26 | ReceiptField, 27 | AxiomV2CircuitConstant, 28 | AxiomV2FieldConstant, 29 | } from "@axiom-crypto/tools"; 30 | 31 | export interface AxiomV2QueryOptions { 32 | maxFeePerGas?: string; 33 | callbackGasLimit?: number; 34 | overrideAxiomQueryFee?: string; 35 | refundee?: string; 36 | } 37 | 38 | export interface BuiltQueryV2 { 39 | sourceChainId: string; 40 | targetChainId: string; 41 | queryHash: string; 42 | dataQuery: string; 43 | dataQueryHash: string; 44 | dataQueryStruct: AxiomV2DataQuery; 45 | computeQuery: AxiomV2ComputeQuery; 46 | querySchema: string; 47 | callback: AxiomV2Callback; 48 | userSalt: string; 49 | feeData: AxiomV2FeeData; 50 | refundee: string; 51 | } 52 | 53 | export interface DataSubqueryCount { 54 | total: number; 55 | header: number; 56 | account: number; 57 | storage: number; 58 | transaction: number; 59 | receipt: number; 60 | solidityNestedMapping: number; 61 | } 62 | 63 | export interface UnbuiltSubquery {} 64 | 65 | export interface UnbuiltHeaderSubquery extends UnbuiltSubquery { 66 | blockNumber: number; 67 | fieldIdx: number; 68 | } 69 | 70 | export interface UnbuiltAccountSubquery extends UnbuiltSubquery { 71 | blockNumber: number; 72 | addr: string; 73 | fieldIdx: number; 74 | } 75 | 76 | export interface UnbuiltStorageSubquery extends UnbuiltSubquery { 77 | blockNumber: number; 78 | addr: string; 79 | slot: string; 80 | } 81 | 82 | export interface UnbuiltTxSubquery extends UnbuiltSubquery { 83 | txHash: string; 84 | fieldOrCalldataIdx: number; 85 | } 86 | 87 | export interface UnbuiltReceiptSubquery extends UnbuiltSubquery { 88 | txHash: string; 89 | fieldOrLogIdx: number; 90 | topicOrDataOrAddressIdx: number; 91 | eventSchema: string; 92 | } 93 | 94 | export interface UnbuiltSolidityNestedMappingSubquery extends UnbuiltSubquery { 95 | blockNumber: number; 96 | addr: string; 97 | mappingSlot: string; 98 | mappingDepth: number; 99 | keys: string[]; 100 | } 101 | 102 | export interface UnbuiltBeaconValidatorSubquery extends UnbuiltSubquery { 103 | // WIP 104 | } 105 | -------------------------------------------------------------------------------- /test/unit/v2/queryBuilderOptions.test.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { AxiomSdkCore, AxiomSdkCoreConfig, HeaderField, QueryV2, buildHeaderSubquery } from "../../../src"; 3 | import { ConstantsV2 } from "../../../src/v2/constants"; 4 | 5 | // Test coverage areas: 6 | // - QueryBuilderV2 options 7 | 8 | describe("QueryBuilderV2 Options", () => { 9 | const config: AxiomSdkCoreConfig = { 10 | providerUri: process.env.PROVIDER_URI as string, 11 | privateKey: process.env.PRIVATE_KEY as string, 12 | chainId: 1, 13 | version: "v2", 14 | }; 15 | const axiom = new AxiomSdkCore(config); 16 | 17 | const wallet = new ethers.Wallet( 18 | process.env.PRIVATE_KEY as string, 19 | new ethers.JsonRpcProvider(process.env.PROVIDER_URI as string), 20 | ); 21 | const blockNumber = 18300000; 22 | 23 | test("set maxFeePerGas", async () => { 24 | const query = (axiom.query as QueryV2).new(); 25 | const headerSubquery = buildHeaderSubquery(blockNumber).field(HeaderField.Timestamp); 26 | query.appendDataSubquery(headerSubquery); 27 | query.setOptions({ 28 | maxFeePerGas: "1000000000000", 29 | }); 30 | const builtQuery = await query.build(); 31 | expect(builtQuery.feeData.maxFeePerGas).toEqual("1000000000000"); 32 | expect(builtQuery.feeData.callbackGasLimit).toEqual(ConstantsV2.DefaultCallbackGasLimit); 33 | expect(builtQuery.feeData.overrideAxiomQueryFee).toEqual("0"); 34 | expect(builtQuery.refundee).toEqual(await wallet.getAddress()); 35 | }); 36 | 37 | test("set callbackGasLimit", async () => { 38 | const query = (axiom.query as QueryV2).new(); 39 | const headerSubquery = buildHeaderSubquery(blockNumber).field(HeaderField.Timestamp); 40 | query.appendDataSubquery(headerSubquery); 41 | query.setOptions({ 42 | callbackGasLimit: 10000, 43 | }); 44 | const builtQuery = await query.build(); 45 | expect(builtQuery.feeData.maxFeePerGas).toEqual(ConstantsV2.DefaultMaxFeePerGasWei); 46 | expect(builtQuery.feeData.callbackGasLimit).toEqual(10000); 47 | expect(builtQuery.feeData.overrideAxiomQueryFee).toEqual("0"); 48 | expect(builtQuery.refundee).toEqual(await wallet.getAddress()); 49 | }); 50 | 51 | test("set refundee", async () => { 52 | const query = (axiom.query as QueryV2).new(); 53 | const headerSubquery = buildHeaderSubquery(blockNumber).field(HeaderField.Timestamp); 54 | query.appendDataSubquery(headerSubquery); 55 | query.setOptions({ 56 | refundee: "0xe76a90E3069c9d86e666DcC687e76fcecf4429cF", 57 | }); 58 | const builtQuery = await query.build(); 59 | expect(builtQuery.feeData.maxFeePerGas).toEqual(ConstantsV2.DefaultMaxFeePerGasWei); 60 | expect(builtQuery.feeData.callbackGasLimit).toEqual(ConstantsV2.DefaultCallbackGasLimit); 61 | expect(builtQuery.feeData.overrideAxiomQueryFee).toEqual("0"); 62 | expect(builtQuery.refundee).toEqual("0xe76a90E3069c9d86e666DcC687e76fcecf4429cF"); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /src/v2/constants.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { AxiomV2Callback, AxiomV2ComputeQuery, DataSubqueryCount } from "./types"; 3 | 4 | export enum SubqueryConfig { 5 | Default, // Catch-all for anything below `Large` size 6 | AllLarge, 7 | AllMax, 8 | } 9 | 10 | export enum TxSizeCategory { 11 | Default, 12 | Large, 13 | Max, 14 | } 15 | 16 | export enum ReceiptSizeCategory { 17 | Default, 18 | Medium, 19 | Large, 20 | Max, 21 | } 22 | 23 | export const ConstantsV2 = Object.freeze({ 24 | // Default values for options 25 | DefaultMaxFeePerGasWei: "25000000000", 26 | DefaultCallbackGasLimit: 100000, 27 | DefaultOverrideAxiomQueryFee: "0", 28 | 29 | // Fallback values if contract value cannot be read 30 | FallbackProofVerificationGas: 420000n, 31 | FallbackAxiomQueryFeeWei: 3000000000000000n, 32 | 33 | // Schema hashes 34 | QueryInitiatedOnchainSchema: "0xb72b05c090ac4ae9ec18b7e708d597093716f98567026726f6f5d9f172316178", 35 | QueryInitiatedWithIpfsDataSchema: "0xf3a2958f23705cbc6bbc0922c0af3c82b76d93e8acc5c17ef86736cf4563fb85", 36 | 37 | // Subquery limits 38 | UserMaxTotalSubqueries: 128, 39 | MaxSameSubqueryType: 128, 40 | SubqueryConfigs: { 41 | [SubqueryConfig.Default]: { 42 | MaxTxSubqueries: 128, 43 | MaxReceiptSubqueries: 128, 44 | }, 45 | [SubqueryConfig.AllLarge]: { 46 | MaxTxSubqueries: 16, 47 | MaxReceiptSubqueries: 16, 48 | }, 49 | [SubqueryConfig.AllMax]: { 50 | MaxTxSubqueries: 4, 51 | MaxReceiptSubqueries: 1, 52 | }, 53 | }, 54 | 55 | // Tx categorization 56 | TxSizeCategory: { 57 | Default: { 58 | MaxDataLen: 8192, 59 | MaxAccessListRlpLen: 4096, 60 | }, 61 | Large: { 62 | MaxDataLen: 32768, 63 | MaxAccessListRlpLen: 16384, 64 | }, 65 | Max: { 66 | MaxDataLen: 330000, 67 | MaxAccessListRlpLen: 131072, 68 | }, 69 | }, 70 | 71 | // Receipt categorization 72 | ReceiptSizeCategory: { 73 | Default: { 74 | MaxLogDataLen: 800, 75 | MaxNumLogs: 20, 76 | }, 77 | Medium: { 78 | MaxLogDataLen: 1024, 79 | MaxNumLogs: 80, 80 | }, 81 | Large: { 82 | MaxLogDataLen: 2048, 83 | MaxNumLogs: 80, 84 | }, 85 | Max: { 86 | MaxLogDataLen: 1024, 87 | MaxNumLogs: 400, 88 | }, 89 | }, 90 | 91 | // Default empty objects 92 | EmptyComputeQueryObject: { 93 | k: 0, 94 | resultLen: 0, 95 | vkey: [] as string[], 96 | computeProof: "0x00", 97 | } as AxiomV2ComputeQuery, 98 | EmptyCallbackObject: { 99 | target: ethers.ZeroAddress, 100 | extraData: ethers.ZeroHash, 101 | } as AxiomV2Callback, 102 | EmptyDataSubqueryCount: { 103 | total: 0, 104 | header: 0, 105 | account: 0, 106 | storage: 0, 107 | transaction: 0, 108 | receipt: 0, 109 | solidityNestedMapping: 0, 110 | } as DataSubqueryCount, 111 | 112 | // Various constants 113 | Bytes32Max: "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 114 | }); 115 | -------------------------------------------------------------------------------- /test/unit/v2/basicInitialization.test.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { AxiomSdkCore, AxiomSdkCoreConfig, AxiomV2ComputeQuery, DataSubquery, QueryV2 } from "../../../src"; 3 | import { Versions } from "../../../src/shared/constants"; 4 | 5 | // Test coverage areas: 6 | // - Basic initialization 7 | // - DataQuery 8 | // - ComputeQuery 9 | // - Callback 10 | 11 | describe("Basic Initialization", () => { 12 | const BLOCK_NUMBER = 15537394; 13 | const WETH_ADDR = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; 14 | const WETH_WHALE = "0x2E15D7AA0650dE1009710FDd45C3468d75AE1392"; 15 | 16 | test("should initialize without an API key", () => { 17 | const config: AxiomSdkCoreConfig = { 18 | providerUri: process.env.PROVIDER_URI as string, 19 | version: "v2", 20 | }; 21 | const ax = new AxiomSdkCore(config); 22 | expect(typeof ax).toEqual("object"); 23 | }); 24 | 25 | test("should initialize AxiomV2", () => { 26 | const config: AxiomSdkCoreConfig = { 27 | providerUri: process.env.PROVIDER_URI as string, 28 | version: "v2", 29 | }; 30 | const ax = new AxiomSdkCore(config); 31 | 32 | expect(typeof ax).toEqual("object"); 33 | expect(typeof ax.query).toEqual("object"); 34 | }); 35 | 36 | test("should fail on invalid version number", () => { 37 | const config: AxiomSdkCoreConfig = { 38 | providerUri: process.env.PROVIDER_URI as string, 39 | version: "v0.3", 40 | }; 41 | expect(() => new AxiomSdkCore(config)).toThrowError("Invalid version number. Valid versions are: " + Versions.join(", ")); 42 | }); 43 | 44 | test("should set targetChainId to the same as (source) chainId", () => { 45 | const config: AxiomSdkCoreConfig = { 46 | providerUri: process.env.PROVIDER_URI as string, 47 | version: "v2", 48 | }; 49 | const ax = new AxiomSdkCore(config); 50 | expect(ax.config.chainId).toEqual(ax.config.targetChainId); 51 | }); 52 | 53 | test("should set targetProviderUri to the same as (source) providerUri", () => { 54 | const config: AxiomSdkCoreConfig = { 55 | providerUri: process.env.PROVIDER_URI as string, 56 | version: "v2", 57 | }; 58 | const ax = new AxiomSdkCore(config); 59 | expect(ax.config.providerUri).toEqual(ax.config.targetProviderUri); 60 | }); 61 | 62 | test("should fail if targetChainId is set while targetProviderUri is not set", () => { 63 | const config: AxiomSdkCoreConfig = { 64 | providerUri: process.env.PROVIDER_URI as string, 65 | targetChainId: 5, 66 | version: "v2", 67 | }; 68 | expect(() => new AxiomSdkCore(config)).toThrow(); 69 | }); 70 | 71 | test("should fail if targetProviderUri is set while targetChainId is not set", () => { 72 | const config: AxiomSdkCoreConfig = { 73 | providerUri: process.env.PROVIDER_URI as string, 74 | targetProviderUri: process.env.PROVIDER_URI as string, 75 | version: "v2", 76 | }; 77 | expect(() => new AxiomSdkCore(config)).toThrow(); 78 | }); 79 | 80 | test("should set targetChainId and targetProviderUri", () => { 81 | const config: AxiomSdkCoreConfig = { 82 | providerUri: process.env.PROVIDER_URI as string, 83 | targetChainId: 5, 84 | targetProviderUri: process.env.PROVIDER_URI_GOERLI as string, 85 | version: "v2", 86 | }; 87 | const ax = new AxiomSdkCore(config); 88 | expect(ax.config.chainId).toEqual(1n); 89 | expect(ax.config.targetChainId).toEqual(5n); 90 | expect(ax.config.providerUri).toEqual(process.env.PROVIDER_URI as string); 91 | expect(ax.config.targetProviderUri).toEqual(process.env.PROVIDER_URI_GOERLI as string); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /test/unit/v2/buildDataQueryStandalone.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AccountField, 3 | AxiomSdkCore, 4 | AxiomSdkCoreConfig, 5 | AxiomV2Callback, 6 | AxiomV2ComputeQuery, 7 | HeaderField, 8 | QueryV2, 9 | ReceiptField, 10 | TxField, 11 | buildAccountSubquery, 12 | buildHeaderSubquery, 13 | buildReceiptSubquery, 14 | buildSolidityNestedMappingSubquery, 15 | buildStorageSubquery, 16 | buildTxSubquery, 17 | bytes32, 18 | } from "../../../src"; 19 | 20 | // Test coverage areas: 21 | // - DataQuery 22 | // - Callback 23 | 24 | describe("Build DataQuery Standalone", () => { 25 | const WETH_ADDR = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; 26 | const WETH_WHALE = "0x2E15D7AA0650dE1009710FDd45C3468d75AE1392"; 27 | const WSOL_ADDR = "0xd31a59c85ae9d8edefec411d448f90841571b89c"; 28 | const UNI_V3_FACTORY_ADDR = "0x1F98431c8aD98523631AE4a59f267346ea31F984"; 29 | 30 | const config: AxiomSdkCoreConfig = { 31 | providerUri: process.env.PROVIDER_URI as string, 32 | privateKey: process.env.PRIVATE_KEY as string, 33 | chainId: "1", 34 | version: "v2", 35 | }; 36 | const axiom = new AxiomSdkCore(config); 37 | 38 | const callback: AxiomV2Callback = { 39 | target: "0x41a7a901ef58d383801272d2408276d96973550d", 40 | extraData: bytes32("0xbbd0d3671093a36d6e3b608a7e3b1fdc96da1116"), 41 | }; 42 | 43 | test("simple dataQuery", async () => { 44 | const blockNumber = 18200000; 45 | const dataQueryReq = [ 46 | { 47 | blockNumber: blockNumber, 48 | fieldIdx: HeaderField.GasUsed, 49 | }, 50 | { 51 | blockNumber: blockNumber + 100, 52 | fieldIdx: HeaderField.GasUsed, 53 | }, 54 | ]; 55 | const query = (axiom.query as QueryV2).new(); 56 | query.append(dataQueryReq); 57 | query.setCallback(callback); 58 | 59 | const builtQuery = await query.build(); 60 | if (builtQuery === undefined) { 61 | throw new Error("builtQuery is undefined"); 62 | } 63 | 64 | expect(builtQuery.queryHash).toEqual("0xda1933a884934070a870d18243ec2f1a7efa869966c4cf52d03b179c998a4825"); 65 | expect(builtQuery.dataQueryHash).toEqual("0xfaaac492509be62a2026a769d31140ee49e4b662e56c95251b8ca6ccace0e91b"); 66 | expect(builtQuery.dataQuery).toEqual("0x0000000000000001000200010115b5c00000000a00010115b6240000000a"); 67 | expect(builtQuery.computeQuery.k).toEqual(0); 68 | expect(builtQuery.computeQuery.vkey.length).toEqual(0); 69 | expect(builtQuery.computeQuery.vkey).toEqual([]); 70 | expect(builtQuery.computeQuery.computeProof).toEqual("0x00"); 71 | }); 72 | 73 | test("simple dataQuery with all types", async () => { 74 | const blockNumber = 17000000; 75 | const txHash = "0xc94a955a2f8c48dc4f14f4183aff4b23aede06ff7fcd7888b18cb407a707fa74"; 76 | 77 | const query = (axiom.query as QueryV2).new(); 78 | 79 | const headerSubquery = buildHeaderSubquery(blockNumber).field(HeaderField.GasLimit); 80 | query.appendDataSubquery(headerSubquery); 81 | 82 | const accountSubquery = buildAccountSubquery(blockNumber).address(WETH_WHALE).field(AccountField.Balance); 83 | query.appendDataSubquery(accountSubquery); 84 | 85 | const storageSubquery = buildStorageSubquery(blockNumber).address(WETH_ADDR).slot(0); 86 | query.appendDataSubquery(storageSubquery); 87 | 88 | const txSubquery = buildTxSubquery(txHash).field(TxField.Nonce); 89 | query.appendDataSubquery(txSubquery); 90 | 91 | const receiptSubquery = buildReceiptSubquery(txHash).field(ReceiptField.Status); 92 | query.appendDataSubquery(receiptSubquery); 93 | 94 | const mappingSubquery = buildSolidityNestedMappingSubquery(blockNumber) 95 | .address(UNI_V3_FACTORY_ADDR) 96 | .mappingSlot(5) 97 | .keys([WETH_ADDR, WSOL_ADDR, 10000]); 98 | query.appendDataSubquery(mappingSubquery); 99 | query.setCallback(callback); 100 | 101 | const builtQuery = await query.build(); 102 | if (builtQuery === undefined) { 103 | throw new Error("builtQuery is undefined"); 104 | } 105 | }); 106 | }); 107 | -------------------------------------------------------------------------------- /src/v2/query/dataSubquery/build.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { bytes32, getBlockNumberAndTxIdx, encodeDataQuery, AxiomV2DataQuery } from "@axiom-crypto/tools"; 3 | import { 4 | DataSubquery, 5 | DataSubqueryType, 6 | UnbuiltAccountSubquery, 7 | UnbuiltHeaderSubquery, 8 | UnbuiltReceiptSubquery, 9 | UnbuiltSolidityNestedMappingSubquery, 10 | UnbuiltStorageSubquery, 11 | UnbuiltSubquery, 12 | UnbuiltTxSubquery, 13 | } from "../../types"; 14 | import { getUnbuiltSubqueryTypeFromKeys } from "./utils"; 15 | 16 | /** 17 | * Builds UnbuiltSubquery[] into DataSubquery[] 18 | */ 19 | export async function buildDataSubqueries( 20 | provider: ethers.JsonRpcProvider, 21 | subqueries: UnbuiltSubquery[], 22 | ): Promise { 23 | let dataSubqueries: DataSubquery[] = []; 24 | for (const subquery of subqueries) { 25 | const type = getUnbuiltSubqueryTypeFromKeys(Object.keys(subquery)); 26 | let dataSubquery = await buildDataSubquery(provider, subquery, type); 27 | dataSubqueries.push(dataSubquery); 28 | } 29 | return dataSubqueries; 30 | } 31 | 32 | export function encodeBuilderDataQuery(chainId: number | string | BigInt, allSubqueries: DataSubquery[]): string { 33 | return encodeDataQuery(chainId, allSubqueries); 34 | } 35 | 36 | export function buildDataQuery(chainId: number | string | BigInt, allSubqueries: DataSubquery[]): AxiomV2DataQuery { 37 | const sourceChainId = chainId.toString(); 38 | return { 39 | sourceChainId, 40 | subqueries: allSubqueries, 41 | }; 42 | } 43 | 44 | export async function buildDataSubquery( 45 | provider: ethers.JsonRpcProvider, 46 | subquery: UnbuiltSubquery, 47 | type: DataSubqueryType, 48 | ): Promise { 49 | switch (type) { 50 | case DataSubqueryType.Header: 51 | return buildDataSubqueryHeader(subquery as UnbuiltHeaderSubquery); 52 | case DataSubqueryType.Account: 53 | return buildDataSubqueryAccount(subquery as UnbuiltAccountSubquery); 54 | case DataSubqueryType.Storage: 55 | return buildDataSubqueryStorage(subquery as UnbuiltStorageSubquery); 56 | case DataSubqueryType.Transaction: 57 | return buildDataSubqueryTx(provider, subquery as UnbuiltTxSubquery); 58 | case DataSubqueryType.Receipt: 59 | return buildDataSubqueryReceipt(provider, subquery as UnbuiltReceiptSubquery); 60 | case DataSubqueryType.SolidityNestedMapping: 61 | return buildDataSubquerySolidityNestedMapping(subquery as UnbuiltSolidityNestedMappingSubquery); 62 | default: 63 | throw new Error(`Invalid data subquery type: ${type}`); 64 | } 65 | } 66 | 67 | async function buildDataSubqueryHeader(subquery: UnbuiltHeaderSubquery): Promise { 68 | return { 69 | type: DataSubqueryType.Header, 70 | subqueryData: { 71 | blockNumber: subquery.blockNumber, 72 | fieldIdx: subquery.fieldIdx, 73 | }, 74 | }; 75 | } 76 | 77 | async function buildDataSubqueryAccount(subquery: UnbuiltAccountSubquery): Promise { 78 | return { 79 | type: DataSubqueryType.Account, 80 | subqueryData: { 81 | blockNumber: subquery.blockNumber, 82 | addr: subquery.addr.toLowerCase(), 83 | fieldIdx: subquery.fieldIdx, 84 | }, 85 | }; 86 | } 87 | 88 | async function buildDataSubqueryStorage(subquery: UnbuiltStorageSubquery): Promise { 89 | return { 90 | type: DataSubqueryType.Storage, 91 | subqueryData: { 92 | blockNumber: subquery.blockNumber, 93 | addr: subquery.addr.toLowerCase(), 94 | slot: subquery.slot, 95 | }, 96 | }; 97 | } 98 | 99 | async function buildDataSubqueryTx( 100 | provider: ethers.JsonRpcProvider, 101 | subquery: UnbuiltTxSubquery, 102 | ): Promise { 103 | const { blockNumber, txIdx } = await getBlockNumberAndTxIdx(provider, subquery.txHash); 104 | return { 105 | type: DataSubqueryType.Transaction, 106 | subqueryData: { 107 | blockNumber, 108 | txIdx, 109 | fieldOrCalldataIdx: subquery.fieldOrCalldataIdx, 110 | }, 111 | }; 112 | } 113 | 114 | async function buildDataSubqueryReceipt( 115 | provider: ethers.JsonRpcProvider, 116 | subquery: UnbuiltReceiptSubquery, 117 | ): Promise { 118 | const { blockNumber, txIdx } = await getBlockNumberAndTxIdx(provider, subquery.txHash); 119 | return { 120 | type: DataSubqueryType.Receipt, 121 | subqueryData: { 122 | blockNumber, 123 | txIdx, 124 | fieldOrLogIdx: subquery.fieldOrLogIdx, 125 | topicOrDataOrAddressIdx: subquery.topicOrDataOrAddressIdx, 126 | eventSchema: subquery.eventSchema.toLowerCase(), 127 | }, 128 | }; 129 | } 130 | 131 | async function buildDataSubquerySolidityNestedMapping( 132 | subquery: UnbuiltSolidityNestedMappingSubquery, 133 | ): Promise { 134 | return { 135 | type: DataSubqueryType.SolidityNestedMapping, 136 | subqueryData: { 137 | blockNumber: subquery.blockNumber, 138 | addr: subquery.addr.toLowerCase(), 139 | mappingSlot: subquery.mappingSlot, 140 | mappingDepth: subquery.mappingDepth, 141 | keys: subquery.keys.map((key) => bytes32(key.toLowerCase())), 142 | }, 143 | }; 144 | } 145 | -------------------------------------------------------------------------------- /src/core/internalConfig.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { Versions, updateConstants } from "../shared/constants"; 3 | import { AxiomSdkCoreConfig } from "../shared/types"; 4 | 5 | export class InternalConfig { 6 | /** 7 | * Axiom API key 8 | */ 9 | readonly apiKey: string; 10 | 11 | /** 12 | * https URL for provider 13 | */ 14 | readonly providerUri: string; 15 | readonly targetProviderUri: string; 16 | 17 | /** 18 | * The Chain ID to use for the Axiom SDK 19 | */ 20 | readonly chainId: BigInt; 21 | readonly targetChainId: BigInt; 22 | 23 | /** 24 | * Axiom contract version number to use 25 | */ 26 | readonly version: string; 27 | 28 | /** 29 | * Default timeout for Axiom API calls 30 | */ 31 | readonly timeoutMs: number; 32 | 33 | /** 34 | * Sets usage of mock prover and database for testing 35 | */ 36 | readonly mock: boolean; 37 | 38 | /** 39 | * Provider to use 40 | */ 41 | readonly provider: ethers.JsonRpcProvider; 42 | 43 | /** 44 | * Stored version data tree 45 | */ 46 | readonly versionData: any; 47 | 48 | /** 49 | * Optional private key used for signing transactions 50 | */ 51 | readonly privateKey?: string; 52 | 53 | /** 54 | * Signer to use (if privateKey provided) 55 | */ 56 | readonly signer?: ethers.Wallet; 57 | 58 | constructor(config: AxiomSdkCoreConfig, overrides?: any) { 59 | this.apiKey = config.apiKey ?? "no-api-key"; 60 | 61 | this.validateTargetChainIdAndProviderUri(config); 62 | 63 | config = this.handleProviderUri(config); 64 | this.providerUri = this.parseProviderUri(config.providerUri); 65 | this.targetProviderUri = this.parseProviderUri(config.targetProviderUri ?? ""); 66 | 67 | config = this.handleChainId(config); 68 | this.chainId = this.parseChainId(config.chainId); 69 | this.targetChainId = this.parseChainId(config.targetChainId); 70 | 71 | this.version = this.parseVersion(config.version); 72 | this.timeoutMs = config.timeoutMs ?? 10000; 73 | this.mock = this.parseMock(config.mock, this.chainId); 74 | 75 | this.provider = new ethers.JsonRpcProvider(this.providerUri); 76 | 77 | if (config.privateKey !== undefined && config.privateKey !== "") { 78 | this.signer = new ethers.Wallet(config.privateKey, this.provider) 79 | } 80 | } 81 | 82 | getConstants(): any { 83 | return this.versionData[this.version]; 84 | } 85 | 86 | private validateTargetChainIdAndProviderUri(config: AxiomSdkCoreConfig): void { 87 | // If targetChainId is set, targetProviderUri must also be set, and vice versa 88 | if (config.targetChainId !== undefined && config.targetProviderUri === undefined) { 89 | throw new Error("`targetProviderUri` is required when `targetChainId` is set"); 90 | } 91 | if (config.targetChainId === undefined && config.targetProviderUri !== undefined) { 92 | throw new Error("`targetChainId` is required when `targetProviderUri` is set"); 93 | } 94 | } 95 | 96 | private handleProviderUri(config: AxiomSdkCoreConfig): AxiomSdkCoreConfig { 97 | if (config.providerUri === undefined || config.providerUri === "") { 98 | throw new Error("`providerUri` is required in AxiomSdkCoreConfig"); 99 | } 100 | if (config.targetProviderUri === undefined || config.targetProviderUri === "") { 101 | config.targetProviderUri = config.providerUri; 102 | } 103 | return config; 104 | } 105 | 106 | private handleChainId(config: AxiomSdkCoreConfig): AxiomSdkCoreConfig { 107 | if (config.chainId === undefined) { 108 | config.chainId = 1; 109 | } 110 | if (config.targetChainId === undefined) { 111 | config.targetChainId = config.chainId; 112 | } 113 | return config; 114 | } 115 | 116 | private parseProviderUri(providerUri: string): string { 117 | if ( 118 | providerUri.startsWith("http://") || 119 | providerUri.startsWith("https://") 120 | ) { 121 | return providerUri; 122 | } else if (providerUri.startsWith("wss://")) { 123 | throw new Error("Websockets is not yet supported"); 124 | } else { 125 | throw new Error( 126 | "Invalid provider URI: URI must start with http://, https://, or wss://" 127 | ); 128 | } 129 | } 130 | 131 | private parseChainId(chainId?: number | string | BigInt): BigInt { 132 | if (chainId === undefined) { 133 | return BigInt(1); 134 | } 135 | return BigInt(chainId.valueOf()); 136 | } 137 | 138 | private parseVersion(version?: string): string { 139 | if (version === undefined) { 140 | return Versions[Versions.length - 1]; 141 | } 142 | 143 | let parsedVersion = version.toLowerCase(); 144 | if (!parsedVersion.startsWith("v")) { 145 | parsedVersion = `v${parsedVersion}`; 146 | } 147 | parsedVersion = parsedVersion.replace(/\./g, "_") as string; 148 | 149 | if (Versions.includes(parsedVersion)) { 150 | return parsedVersion; 151 | } 152 | throw new Error( 153 | "Invalid version number. Valid versions are: " + Versions.join(", ") 154 | ); 155 | } 156 | 157 | private parseMock(mock: boolean | undefined, chainId: BigInt): boolean { 158 | if (mock === undefined) { 159 | return false; 160 | } 161 | if (chainId === 1n) { 162 | return false; 163 | } 164 | return mock; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /test/unit/v2/calculatorCalldataGas.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AxiomSdkCore, 3 | AxiomSdkCoreConfig, 4 | HeaderField, 5 | QueryV2, 6 | buildHeaderSubquery, 7 | buildSolidityNestedMappingSubquery, 8 | } from "../../../src"; 9 | import { calculateCalldataGas } from "../../../src/v2/query/gasCalc"; 10 | 11 | // Test coverage areas: 12 | // - Calldata gas calculator 13 | 14 | describe("Calldata Gas Calculator", () => { 15 | const config: AxiomSdkCoreConfig = { 16 | privateKey: process.env.PRIVATE_KEY_GOERLI as string, 17 | providerUri: process.env.PROVIDER_URI_GOERLI as string, 18 | version: "v2", 19 | chainId: 5, 20 | }; 21 | const axiom = new AxiomSdkCore(config); 22 | 23 | test("basic calculator test", () => { 24 | const gas = calculateCalldataGas("0x123456789000"); 25 | expect(gas).toEqual(84); 26 | }); 27 | 28 | test("large dataQuery gas test", async () => { 29 | const WETH_ADDR = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; 30 | const WETH_WHALE = "0x2E15D7AA0650dE1009710FDd45C3468d75AE1392"; 31 | const WSOL_ADDR = "0xd31a59c85ae9d8edefec411d448f90841571b89c"; 32 | const UNI_V3_FACTORY_ADDR = "0x1F98431c8aD98523631AE4a59f267346ea31F984"; 33 | 34 | const query = (axiom.query as QueryV2).new(); 35 | for (let i = 0; i < 32; i++) { 36 | const sq = buildSolidityNestedMappingSubquery(18000000) 37 | .address(UNI_V3_FACTORY_ADDR) 38 | .mappingSlot(3) 39 | .keys([WETH_ADDR, WSOL_ADDR, 10000, 5000]); 40 | query.appendDataSubquery(sq); 41 | } 42 | const builtQuery = await query.build(); 43 | 44 | // Calculate calldata gas for just the mapping subqueries 45 | const gas = calculateCalldataGas(builtQuery.dataQuery); 46 | expect(gas).toEqual(51264); 47 | }); 48 | 49 | test("arbitrary gas cost", () => { 50 | const oldVkey = "0x0001000009000100000004010000010080000000000000000000000000000000c562ed60c110f4e0ca53d33b0e1fe0d1fc10db6d997001813ecd409d378f220cc04b25057d0bddf35d4542077516abb76445b8e745a457e3ccc1bf9aac2ba406977facb333a6cae3726c695dceea3b6a2a4ddce29428b70ddd989ea1fe60014f25443655c0b66da8a310f273dd57db7843cd3800fa5d24c415c3e13b5f035a6b000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000080c28c31d22d8f204e81b8fc714de26d87c1d4c756632cad0668655fd09220762a2f3064cf682958accb92639a8a276e8d491b49c5f2d1557fe686e8805641f21922e4c62aacfc240ed0553bfad00122ba8c7627c870c739f3f818584e066a8b1f841485e0a9f109688bdc4f5ff851d9e2e44833ae573456742c1237322e93854279a62f1cc2f1440cc9fdcd534b612a49da4b6139bbed8cf53a26f4568ac3f567e40c0e4521b32cf17ce45eee625a7e525481b412984310e1fb44eef5a34ab34cf4d1a7e36933bfb413c2f451097e4cd1ab67e8a4cb0a1bdac2d05284e48be45e"; 51 | const newVkey = "0x0001000009000100000014030000010080000000000000000000000000000000c60fa9c24296296126123ba55b4a1cdf65d078c61351a288f7fe424e48f6f81bc04b25057d0bddf35d4542077516abb76445b8e745a457e3ccc1bf9aac2ba406977facb333a6cae3726c695dceea3b6a2a4ddce29428b70ddd989ea1fe60014f25443655c0b66da8a310f273dd57db7843cd3800fa5d24c415c3e13b5f035a6b0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000080c28c31d22d8f204e81b8fc714de26d87c1d4c756632cad0668655fd09220762ac97e680c26aa7578d2a619a036f989a54d69901c0dfebe50e4550c91e77aff6622e4c62aacfc240ed0553bfad00122ba8c7627c870c739f3f818584e066a8b1f841485e0a9f109688bdc4f5ff851d9e2e44833ae573456742c1237322e93854279a62f1cc2f1440cc9fdcd534b612a49da4b6139bbed8cf53a26f4568ac3f567e40c0e4521b32cf17ce45eee625a7e525481b412984310e1fb44eef5a34ab34c62ee0fe9aa8394ac79f7db54f83e432a353c7f066c180a9e7a7a37d8d1a19621797cae9e008e1a1c6179a2669e046910e331c74b4e57daa2e643d7890a9ffe42ce5a4cc0f54b73ff0bcedb8c86a757af5e46284d55d21880e1a0a5fb244b0261e55f1e07004fb3e7beed33d7e81056d3aa999d1dd24a44f9aa260809521c156e3fa7d77341d8aa211ce29a49741679c8f72e90b0390993d99c6d8c0aaac11d57b76b2ff2659b4946451a50ff328d54bc43c0b63aa8918a58c07a4341317335060492eec333e48f59fe81084a4736040410107225455b86fa305be40e8a87fb081e80637e88a5119875eb33158d940b2e9d329d7b789a255cb0046b30b104fd10197bad10d254ed326b9f05b266b708e494b3c0d51e1029cc45cc733dddec406d0bfd7893d41759553d6bf679e019ebf724d06bf4c90a06290c16e94e31fe6e2882f323b6df6e395c26e47adac9c17a3e523baac0032760248f6cba2d26b3a606a038b067a84e5393fc5a765d129ba9623dbd01440dd62cf50bc20778c64d3e11d43319b40899edee653a91cdf3e54cc87b8a872e8c60228a51494bfd095c856ba7dfc2ccf04eeb0022d8c1d6c4afb79c908f5badd91a6ca58b4a9683edac8b2c0b239c177ecb6c67c8c45eee7e7398e12e96f7e42c80e2638960a2b367a026016c4e6756b257b8be2ca2553d710b966c469d7f976e4d3694f9bb86300db5886468609c0ad2b8ca67724c13e56a430669835624483db35d2217c517d74f9b864a0df9bbba485cc4c479ef23f0934510a5d86445a424ad80f5c68c62590b8f244da2d4a115c90c5ffa24d12d55d16890778e67b5085bc11ec9c15257c8ddffe025"; 52 | const oldGas = calculateCalldataGas(oldVkey); 53 | const newGas = calculateCalldataGas(newVkey); 54 | expect(oldGas).toBeLessThan(newGas); 55 | }) 56 | }); 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Axiom SDK Core 2 | 3 | > ⚠️ Warning: This repository has been deprecated. The general functionality of this repo has been merged into [axiom-sdk-client](https://github.com/axiom-crypto/axiom-sdk-client). 4 | 5 | [Axiom](https://axiom.xyz) allows smart contracts to compute over the entire history of Ethereum. Smart contracts can query Axiom on-chain, and Axiom results are verified on-chain in ZK and delivered to the target smart contract via callback. 6 | 7 | # Getting started with Axiom V2 8 | 9 | Please see the [Quickstart](https://docs.axiom.xyz/introduction/quickstart) for the fastest way to get started with Axiom. For more info, see the [Docs](https://docs.axiom.xyz). 10 | 11 | # Examples 12 | 13 | See various end-to-end examples in the [Examples V2 repo](https://github.com/axiom-crypto/examples-v2). 14 | 15 | # Manually using Axiom SDK Core 16 | 17 | > ⚠️ **WARNING**: It is not recommended that you use Axiom SDK Core manually unless you know what you're doing! 18 | 19 | Create an `Axiom` instance and a `QueryV2` instance from it (we will use Goerli testnet in this example): 20 | 21 | ```typescript 22 | const config: AxiomConfig = { 23 | privateKey: process.env.PRIVATE_KEY_GOERLI as string, 24 | providerUri: process.env.PROVIDER_URI_GOERLI as string, 25 | version: "v2", 26 | chainId: 5, // Goerli 27 | mock: true, 28 | }; 29 | const axiom = new Axiom(config); 30 | const query = (axiom.query as QueryV2).new(); 31 | ``` 32 | 33 | ## Building a Query 34 | 35 | There are two ways to build a query. We'll cover both of them here. 36 | 37 | ```typescript 38 | // Some constants used below 39 | const BLOCK_NUM = 9500000; 40 | const WETH_ADDR = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; 41 | const WSOL_ADDR = "0xd31a59c85ae9d8edefec411d448f90841571b89c"; 42 | const WETH_WHALE = "0x2E15D7AA0650dE1009710FDd45C3468d75AE1392"; 43 | const UNI_V3_FACTORY_ADDR = "0x1F98431c8aD98523631AE4a59f267346ea31F984"; 44 | ``` 45 | 46 | ### Building a Query: Appending method 47 | 48 | ```typescript 49 | // Appends a subquery where we look for the gas used at block BLOCK_NUM 50 | const headerSubquery = buildHeaderSubquery(BLOCK_NUM) 51 | .field(HeaderField.GasUsed); 52 | query.appendDataSubquery(headerSubquery); 53 | 54 | // Appends a receipt subquery for transaction 55 | // 0x0a126c0e009e19af335e964de0cea513098c9efe290c269dee77ca9f10838e7b in which we look at 56 | // log index 4 (Transfer (index_topic_1 address from, index_topic_2 address to, uint256 value)) 57 | // and the second topic (indexed address to) of that log event 58 | const txHash = "0x0a126c0e009e19af335e964de0cea513098c9efe290c269dee77ca9f10838e7b"; 59 | const swapEventSchema = getEventSchema( 60 | "Swap(address,uint256,uint256,uint256,uint256,address)" 61 | ); 62 | const receiptSubquery = buildReceiptSubquery(txHash) 63 | .log(4) // event 64 | .topic(0) // event schema 65 | .eventSchema(swapEventSchema); 66 | query.appendDataSubquery(receiptSubquery); 67 | 68 | // slot 5: mapping(address => mapping(address => mapping(uint24 => address))) public override getPool; 69 | const mappingSubquery = buildSolidityNestedMappingSubquery(BLOCK_NUM) 70 | .address(UNI_V3_FACTORY_ADDR) 71 | .mappingSlot(5) 72 | .keys([ 73 | WETH_ADDR, 74 | WSOL_ADDR, 75 | 10000, 76 | ]); 77 | query.appendDataSubquery(mappingSubquery); 78 | 79 | const callbackQuery = { 80 | target: WETH_ADDR, 81 | extraData: ethers.solidityPacked(["address"], [WETH_WHALE]), 82 | }; 83 | 84 | query.setCallback(callbackQuery); 85 | await query.build(); 86 | ``` 87 | 88 | ### Building a Query: Passing in objects 89 | 90 | ```typescript 91 | const dataQuery = [ 92 | { 93 | blockNumber: BLOCK_NUM, 94 | fieldIdx: HeaderField.GasLimit, 95 | }, 96 | { 97 | blockNumber: BLOCK_NUM + 1, 98 | fieldIdx: HeaderField.StateRoot, 99 | }, 100 | { 101 | blockNumber: BLOCK_NUM, 102 | addr: WETH_WHALE, 103 | fieldIdx: AccountField.Nonce, 104 | }, 105 | { 106 | txHash: "0x47082a4eaba054312c652a21c6d75a44095b8be43c60bdaeffad03d38a8b1602", 107 | fieldOrLogIdx: ReceiptField.CumulativeGas, 108 | topicOrDataOrAddressIdx: 0, 109 | eventSchema: ethers.ZeroHash, 110 | }, 111 | ]; 112 | 113 | const callbackQuery = { 114 | target: WETH_ADDR, 115 | extraData: ethers.solidityPacked(["address"], [WETH_WHALE]), 116 | }; 117 | 118 | const query = (axiom.query as QueryV2).new( 119 | dataQuery, 120 | undefined, // no computeQuery 121 | callbackQuery, 122 | ); 123 | ``` 124 | 125 | ## Validating a Query 126 | 127 | Validation will write to `console.error` for any errors detected in each of the fields (it will not throw an error or return early) and will return `false` if there is any error detected in any of the `Query` fields. 128 | 129 | ```typescript 130 | const isValid = await query.validate(); 131 | if (!isValid) { 132 | throw new Error("Query validation failed."); 133 | } 134 | ``` 135 | 136 | ## Submitting a Query 137 | 138 | Once a `Query` has been built, it can be submitted via two methods: On-chain or via IPFS. 139 | 140 | ### Submitting a Query: On-chain 141 | 142 | ```typescript 143 | // ensure you've already called `await query.build()` 144 | const payment = await query.calculateFee(); 145 | const queryId = await query.sendOnchainQuery( 146 | payment, 147 | (receipt: ethers.ContractTransactionReceipt) => { 148 | // You can do something here once you've received the transaction receipt 149 | console.log("receipt", receipt); 150 | } 151 | ); 152 | console.log("queryId", queryId); 153 | ``` 154 | 155 | ### Submitting a Query: IPFS 156 | 157 | // WIP: will be supported soon 158 | 159 | ```typescript 160 | // ensure you've already called `await query.build()` 161 | const payment = await query.calculateFee(); 162 | const queryId = await query.sendQueryWithIpfs( 163 | payment, 164 | (receipt: ethers.ContractTransactionReceipt) => { 165 | // You can do something here once you've received the transaction receipt 166 | console.log("receipt", receipt); 167 | } 168 | ); 169 | console.log("queryId", queryId); 170 | ``` 171 | -------------------------------------------------------------------------------- /src/v2/query/dataSubquery/configLimitManager.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getNumBytes, 3 | objectToRlp, 4 | } from "@axiom-crypto/tools"; 5 | import { 6 | ReceiptSizeCategory, 7 | SubqueryConfig, 8 | TxSizeCategory, 9 | ConstantsV2 10 | } from "../../constants"; 11 | 12 | // ConfigLimitManager handles the logic for determining the maximum number of subqueries 13 | // of transaction and receipt types by getting the size of various parameters of the 14 | // Transaction and Receipt. Global parameters for all subqueries are still enforced by 15 | // QueryBuliderV2. 16 | export class ConfigLimitManager { 17 | globalConfig: SubqueryConfig = SubqueryConfig.Default; 18 | txSizeCategory: TxSizeCategory = TxSizeCategory.Default 19 | receiptSizeCategory: ReceiptSizeCategory = ReceiptSizeCategory.Default; 20 | numTxSubqueries: number = 0; 21 | numReceiptSubqueries: number = 0; 22 | 23 | constructor() {} 24 | 25 | processTx(tx: any) { 26 | // Check total data length 27 | const txDataLen = getNumBytes(tx.input); 28 | 29 | // Check access list length 30 | let aclNumBytesRlp = 0; 31 | if (tx.type === "0x1" || tx.type === "0x2") { 32 | const accessListRlp = objectToRlp(tx.accessList ?? {}); 33 | aclNumBytesRlp = getNumBytes(accessListRlp); 34 | } 35 | 36 | // Validate max bounds 37 | if (txDataLen > ConstantsV2.TxSizeCategory.Max.MaxDataLen) { 38 | throw new Error(`Transaction data length (${txDataLen} bytes) exceeds supported maximum (${ConstantsV2.TxSizeCategory.Max.MaxDataLen} bytes)`); 39 | } 40 | if (aclNumBytesRlp > ConstantsV2.TxSizeCategory.Max.MaxAccessListRlpLen) { 41 | throw new Error(`Transaction access list length (${aclNumBytesRlp} bytes) exceeds supported maximum (${ConstantsV2.TxSizeCategory.Max.MaxAccessListRlpLen} bytes)`); 42 | } 43 | 44 | // Check the size category for this tx 45 | let thisTxSize = TxSizeCategory.Default; 46 | if ( 47 | txDataLen > ConstantsV2.TxSizeCategory.Large.MaxDataLen || 48 | aclNumBytesRlp > ConstantsV2.TxSizeCategory.Large.MaxAccessListRlpLen 49 | ) { 50 | thisTxSize = TxSizeCategory.Max; 51 | } else if ( 52 | txDataLen > ConstantsV2.TxSizeCategory.Default.MaxDataLen || 53 | aclNumBytesRlp > ConstantsV2.TxSizeCategory.Default.MaxAccessListRlpLen 54 | ) { 55 | thisTxSize = TxSizeCategory.Large; 56 | } 57 | 58 | // Set size category if it's greater than the current size category 59 | if (thisTxSize > this.txSizeCategory) { 60 | this.txSizeCategory = thisTxSize; 61 | } 62 | 63 | // Increment txSubqueries 64 | this.numTxSubqueries++; 65 | 66 | // Update Global config size based on txSizeCategory if size is smaller 67 | let thisTxGlobalSize = SubqueryConfig.Default; 68 | switch (this.txSizeCategory) { 69 | case TxSizeCategory.Large: 70 | thisTxGlobalSize = SubqueryConfig.AllLarge; 71 | break; 72 | case TxSizeCategory.Max: 73 | this.globalConfig = SubqueryConfig.AllMax; 74 | break; 75 | default: 76 | break; 77 | } 78 | if (thisTxGlobalSize > this.globalConfig) { 79 | this.globalConfig = thisTxGlobalSize; 80 | } 81 | 82 | // Check the subquery count 83 | const config = ConstantsV2.SubqueryConfigs[this.globalConfig as keyof typeof ConstantsV2.SubqueryConfigs]; 84 | if (this.numTxSubqueries > config.MaxTxSubqueries) { 85 | throw new Error(`Exceeded maximum number of tx subqueries (${this.numTxSubqueries}) for config: ${this.globalConfig}`); 86 | } 87 | if (this.numReceiptSubqueries > config.MaxReceiptSubqueries) { 88 | throw new Error(`Exceeded maximum number of receipt subqueries (${this.numReceiptSubqueries}) for config: ${this.globalConfig}`); 89 | } 90 | } 91 | 92 | processReceipt(rc: any) { 93 | // Get num logs 94 | const numLogs = rc.logs.length; 95 | 96 | // Get max log data length 97 | let maxLogDataLen = 0; 98 | for (const log of rc.logs) { 99 | const logDataLen = getNumBytes(log.data); 100 | if (logDataLen > maxLogDataLen) { 101 | maxLogDataLen = logDataLen; 102 | } 103 | } 104 | 105 | // Validate max bounds 106 | if ( 107 | !( 108 | (maxLogDataLen <= ConstantsV2.ReceiptSizeCategory.Large.MaxLogDataLen 109 | && numLogs <= ConstantsV2.ReceiptSizeCategory.Large.MaxNumLogs) || 110 | (maxLogDataLen <= ConstantsV2.ReceiptSizeCategory.Max.MaxLogDataLen 111 | && numLogs <= ConstantsV2.ReceiptSizeCategory.Max.MaxNumLogs) 112 | ) 113 | ) { 114 | throw new Error(`Receipt size (${maxLogDataLen} bytes, ${numLogs} logs) exceeds either Large or Max config categories`); 115 | } 116 | 117 | // Check the size category for this receipt 118 | let thisLogDataLenSize = ReceiptSizeCategory.Default; 119 | if (maxLogDataLen > ConstantsV2.ReceiptSizeCategory.Medium.MaxLogDataLen) { 120 | thisLogDataLenSize = ReceiptSizeCategory.Large; 121 | } else if (maxLogDataLen > ConstantsV2.ReceiptSizeCategory.Default.MaxLogDataLen) { 122 | thisLogDataLenSize = ReceiptSizeCategory.Medium; 123 | } 124 | let thisNumLogsSize = ReceiptSizeCategory.Default; 125 | if (numLogs > ConstantsV2.ReceiptSizeCategory.Large.MaxNumLogs) { 126 | thisNumLogsSize = ReceiptSizeCategory.Max; 127 | } else if (numLogs > ConstantsV2.ReceiptSizeCategory.Default.MaxNumLogs) { 128 | thisNumLogsSize = ReceiptSizeCategory.Medium; 129 | } 130 | const thisReceiptSize = Math.max(thisLogDataLenSize, thisNumLogsSize); 131 | 132 | // Set size category if it's greater than the current size category 133 | if (thisReceiptSize > this.receiptSizeCategory) { 134 | this.receiptSizeCategory = thisReceiptSize; 135 | } 136 | 137 | // Increment receiptSubqueries 138 | this.numReceiptSubqueries++; 139 | 140 | // Update Global config size based on receiptSizeCategory if size is smaller 141 | let thisReceiptGlobalSize = SubqueryConfig.Default; 142 | switch (this.receiptSizeCategory) { 143 | case ReceiptSizeCategory.Large: 144 | thisReceiptGlobalSize = SubqueryConfig.AllLarge; 145 | break; 146 | case ReceiptSizeCategory.Max: 147 | this.globalConfig = SubqueryConfig.AllMax; 148 | break; 149 | default: 150 | break; 151 | } 152 | if (thisReceiptGlobalSize > this.globalConfig) { 153 | this.globalConfig = thisReceiptGlobalSize; 154 | } 155 | 156 | // Check the subquery count 157 | const config = ConstantsV2.SubqueryConfigs[this.globalConfig as keyof typeof ConstantsV2.SubqueryConfigs]; 158 | if (this.numReceiptSubqueries > config.MaxReceiptSubqueries) { 159 | throw new Error(`Exceeded maximum number of receipt subqueries (${this.numReceiptSubqueries}) for config: ${this.globalConfig}`); 160 | } 161 | if (this.numTxSubqueries > config.MaxTxSubqueries) { 162 | throw new Error(`Exceeded maximum number of tx subqueries (${this.numTxSubqueries}) for config: ${this.globalConfig}`); 163 | } 164 | } 165 | 166 | getGlobalConfig(): SubqueryConfig { 167 | return this.globalConfig; 168 | } 169 | 170 | getTxSizeCategory(): TxSizeCategory { 171 | return this.txSizeCategory; 172 | } 173 | 174 | getReceiptSizeCategory(): ReceiptSizeCategory { 175 | return this.receiptSizeCategory; 176 | } 177 | } -------------------------------------------------------------------------------- /test/unit/v2/validate.test.ts: -------------------------------------------------------------------------------- 1 | import { getSlotForMapping, HeaderField, AccountField, TxField } from "@axiom-crypto/tools"; 2 | import { 3 | AxiomSdkCore, 4 | AxiomSdkCoreConfig, 5 | QueryV2, 6 | buildAccountSubquery, 7 | buildHeaderSubquery, 8 | buildReceiptSubquery, 9 | buildSolidityNestedMappingSubquery, 10 | buildStorageSubquery, 11 | buildTxSubquery, 12 | } from "../../../src"; 13 | import { ethers } from "ethers"; 14 | 15 | describe("Query Validation Tests", () => { 16 | const WETH_ADDR = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; 17 | const WSOL_ADDR = "0xd31a59c85ae9d8edefec411d448f90841571b89c"; 18 | const WETH_WHALE = "0x2E15D7AA0650dE1009710FDd45C3468d75AE1392"; 19 | const UNI_V3_FACTORY_ADDR = "0x1F98431c8aD98523631AE4a59f267346ea31F984"; 20 | 21 | const provider = new ethers.JsonRpcProvider(process.env.PROVIDER_URI as string); 22 | 23 | const config: AxiomSdkCoreConfig = { 24 | providerUri: process.env.PROVIDER_URI as string, 25 | version: "v2", 26 | chainId: 1, 27 | }; 28 | const axiom = new AxiomSdkCore(config); 29 | const aq = axiom.query as QueryV2; 30 | 31 | const axiomSepolia = new AxiomSdkCore({ 32 | providerUri: process.env.PROVIDER_URI_SEPOLIA as string, 33 | version: "v2", 34 | chainId: "11155111", 35 | }); 36 | const aqSep = axiomSepolia.query as QueryV2; 37 | 38 | test("Validate pass: Header subquery", async () => { 39 | const query = aq.new(); 40 | const subquery = buildHeaderSubquery(17000000).field(HeaderField.GasUsed); 41 | query.appendDataSubquery(subquery); 42 | const isValid = await query.validate(); 43 | expect(isValid).toEqual(true); 44 | }); 45 | 46 | test("Validate pass: Account subquery", async () => { 47 | const query = aq.new(); 48 | const subquery = buildAccountSubquery(18000000) 49 | .address(WETH_WHALE) 50 | .field(AccountField.Balance); 51 | query.appendDataSubquery(subquery); 52 | const isValid = await query.validate(); 53 | expect(isValid).toEqual(true); 54 | }); 55 | 56 | test("Validate pass: Storage subquery", async () => { 57 | const query = aq.new(); 58 | const slot = getSlotForMapping("3", "address", WETH_WHALE); 59 | const subquery = buildStorageSubquery(18000000) 60 | .address(WETH_ADDR) 61 | .slot(slot); 62 | query.appendDataSubquery(subquery); 63 | const isValid = await query.validate(); 64 | expect(isValid).toEqual(true); 65 | }); 66 | 67 | test("Validate pass: Tx subquery", async () => { 68 | const query = aq.new(); 69 | 70 | const txHash = "0x8d2e6cbd7cf1f88ee174600f31b79382e0028e239bb1af8301ba6fc782758bc6"; 71 | const subquery = buildTxSubquery(txHash).field(TxField.To); 72 | query.appendDataSubquery(subquery); 73 | const isValid = await query.validate(); 74 | expect(isValid).toEqual(true); 75 | }); 76 | 77 | test("Validate pass: Tx subquery calldata", async () => { 78 | const query = aq.new(); 79 | 80 | const txHash = "0x192bc136b4637e0c31dc80b7c4e8cd63328c7c411ba8574af1841ed2c4a6dd80"; 81 | const subquery = buildTxSubquery(txHash).calldata(0); 82 | query.appendDataSubquery(subquery); 83 | const isValid = await query.validate(); 84 | expect(isValid).toEqual(true); 85 | }); 86 | 87 | test("Validate pass: Larger Tx subquery contractData", async () => { 88 | const query = aq.new(); 89 | 90 | const txHash = "0xc9ef13429be1a3f44c75af95c4e2ac2083a3469e2751a42a04fcdace94ff98a5"; 91 | const subquery = buildTxSubquery(txHash).contractData(0); 92 | query.appendDataSubquery(subquery); 93 | const isValid = await query.validate(); 94 | expect(isValid).toEqual(true); 95 | }); 96 | 97 | test("Validate pass: Receipt subquery", async () => { 98 | const query = aq.new(); 99 | 100 | const txHash = "0x8d2e6cbd7cf1f88ee174600f31b79382e0028e239bb1af8301ba6fc782758bc6"; 101 | const subquery = buildReceiptSubquery(txHash) 102 | .log(0) 103 | .topic(1) 104 | .eventSchema("Transfer(address,address,uint256)"); 105 | query.appendDataSubquery(subquery); 106 | const isValid = await query.validate(); 107 | expect(isValid).toEqual(true); 108 | }); 109 | 110 | test("Validate pass: Solidity nested mapping subquery", async () => { 111 | const query = aq.new(); 112 | const subquery = buildSolidityNestedMappingSubquery(17000000) 113 | .address(UNI_V3_FACTORY_ADDR) 114 | .mappingSlot(5) 115 | .keys([WETH_ADDR, WSOL_ADDR, 10000]); 116 | query.appendDataSubquery(subquery); 117 | const isValid = await query.validate(); 118 | expect(isValid).toEqual(true); 119 | }); 120 | 121 | test("Validate fail: Header subquery", async () => { 122 | const query = aq.new(); 123 | const test = () => { 124 | const subquery = buildHeaderSubquery("0x480aa3cf46a1813d543e169314d56831aa002d932444723fee6b9e31d01f8c28").field( 125 | HeaderField.Miner, 126 | ); 127 | query.appendDataSubquery(subquery); 128 | }; 129 | expect(test).toThrow(); 130 | }); 131 | 132 | test("Validate pass: empty Callback combinations", async () => { 133 | const query = aq.new(); 134 | const subquery = buildSolidityNestedMappingSubquery(17000000) 135 | .address(UNI_V3_FACTORY_ADDR) 136 | .mappingSlot(5) 137 | .keys([WETH_ADDR, WSOL_ADDR, 10000]); 138 | query.appendDataSubquery(subquery); 139 | 140 | query.setCallback({ 141 | target: UNI_V3_FACTORY_ADDR, 142 | extraData: ethers.ZeroHash, 143 | }); 144 | let isValid = await query.validate(); 145 | expect(isValid).toEqual(true); 146 | 147 | query.setCallback({ 148 | target: UNI_V3_FACTORY_ADDR, 149 | extraData: "", 150 | }); 151 | isValid = await query.validate(); 152 | expect(isValid).toEqual(true); 153 | 154 | query.setCallback({ 155 | target: UNI_V3_FACTORY_ADDR, 156 | extraData: "0x", 157 | }); 158 | isValid = await query.validate(); 159 | expect(isValid).toEqual(true); 160 | }); 161 | 162 | test("Validate fail: invalid Callback combinations", async () => { 163 | const query = aq.new(); 164 | 165 | query.setCallback({ 166 | target: "", 167 | extraData: "", 168 | }); 169 | let isValid = await query.validate(); 170 | expect(isValid).toEqual(false); 171 | 172 | query.setCallback({ 173 | target: ethers.ZeroAddress, 174 | extraData: "", 175 | }); 176 | isValid = await query.validate(); 177 | 178 | query.setCallback({ 179 | target: UNI_V3_FACTORY_ADDR, 180 | extraData: "0x1234", 181 | }); 182 | isValid = await query.validate(); 183 | expect(isValid).toEqual(false); 184 | }); 185 | 186 | test("Validate fail: type 3 tx subquery", async () => { 187 | const sepoliaTransactions = [ 188 | // type 3 189 | "0x8fd091f4b5b1b17431110afa99fbd9cabdabecb92a1315afa458fc3dcb91efde", 190 | "0x95ea8f5b10f8ac9f48943ac32014705a10c76d54551391f1ed34c72c6c28fa83", 191 | "0x48c6fcfd6cbc753938d486cb33711b63d4330b48371a7919648c9e1506d6b6e9", 192 | "0xbdb6eb8982db0695f6685840d01667d9c7beb5140b96e6af38c346c6a0de2edf", 193 | "0xaeac36b485d4c6672f6d7337ab8015b0d4483724151dfda88214c0e4fd675542", 194 | ]; 195 | 196 | for (const txHash of sepoliaTransactions) { 197 | const query = aqSep.new(); 198 | const subquery = buildTxSubquery(txHash).field(TxField.To); 199 | query.appendDataSubquery(subquery); 200 | const isValid = await query.validate(); 201 | expect(isValid).toEqual(false); 202 | } 203 | }); 204 | }); 205 | -------------------------------------------------------------------------------- /src/v2/query/dataSubquery/validate.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { 3 | AccountField, 4 | BeaconValidatorSubquery, 5 | HeaderField, 6 | ReceiptField, 7 | TxField, 8 | getBlockNumberAndTxIdx, 9 | getNumBytes, 10 | AxiomV2FieldConstant, 11 | getAccountFieldValue, 12 | getHeaderFieldValue, 13 | getReceiptFieldValue, 14 | getSolidityNestedMappingValue, 15 | getStorageFieldValue, 16 | getTxFieldValue, 17 | getRawTransaction, 18 | getRawReceipt, 19 | } from "@axiom-crypto/tools"; 20 | import { 21 | UnbuiltAccountSubquery, 22 | UnbuiltHeaderSubquery, 23 | UnbuiltReceiptSubquery, 24 | UnbuiltSolidityNestedMappingSubquery, 25 | UnbuiltStorageSubquery, 26 | UnbuiltTxSubquery, 27 | } from "../../types"; 28 | import { ConfigLimitManager } from "./configLimitManager"; 29 | 30 | export async function validateHeaderSubquery( 31 | provider: ethers.JsonRpcProvider, 32 | subquery: UnbuiltHeaderSubquery, 33 | ): Promise { 34 | if ( 35 | (subquery.fieldIdx > HeaderField.WithdrawalsRoot && subquery.fieldIdx < AxiomV2FieldConstant.Header.HashFieldIdx) || 36 | (subquery.fieldIdx > AxiomV2FieldConstant.Header.ExtraDataLenFieldIdx && 37 | subquery.fieldIdx < AxiomV2FieldConstant.Header.LogsBloomFieldIdxOffset) || 38 | subquery.fieldIdx >= AxiomV2FieldConstant.Header.LogsBloomFieldIdxOffset + 8 39 | ) { 40 | console.error(`Invalid header field index: ${subquery.fieldIdx}`); 41 | return false; 42 | } 43 | const latestBlock = await provider.getBlock("latest"); 44 | if (latestBlock === null) { 45 | throw new Error("Failed to get latest block; check your internet connection or provider RPC"); 46 | } 47 | if (subquery.blockNumber > latestBlock.number) { 48 | console.warn(`Block number ${subquery.blockNumber} is in the future`); 49 | } 50 | 51 | const value = await getHeaderFieldValue(provider, subquery, console); 52 | if (value === null) { 53 | console.error(`Header subquery ${JSON.stringify(subquery)} returned null`); 54 | return false; 55 | } 56 | return true; 57 | } 58 | 59 | export async function validateAccountSubquery( 60 | provider: ethers.JsonRpcProvider, 61 | subquery: UnbuiltAccountSubquery, 62 | ): Promise { 63 | if (subquery.fieldIdx > AccountField.CodeHash) { 64 | console.error(`Invalid account field index: ${subquery.fieldIdx}`); 65 | return false; 66 | } 67 | const latestBlock = await provider.getBlock("latest"); 68 | if (latestBlock === null) { 69 | throw new Error("Failed to get latest block; check your internet connection or provider RPC"); 70 | } 71 | if (subquery.blockNumber > latestBlock.number) { 72 | console.warn(`Block number ${subquery.blockNumber} is in the future`); 73 | } 74 | 75 | const value = await getAccountFieldValue(provider, subquery, console); 76 | if (value === null) { 77 | console.error(`Account subquery ${JSON.stringify(subquery)} returned null`); 78 | return false; 79 | } 80 | return true; 81 | } 82 | 83 | export async function validateStorageSubquery( 84 | provider: ethers.JsonRpcProvider, 85 | subquery: UnbuiltStorageSubquery, 86 | ): Promise { 87 | const latestBlock = await provider.getBlock("latest"); 88 | if (latestBlock === null) { 89 | throw new Error("Failed to get latest block; check your internet connection or provider RPC"); 90 | } 91 | if (subquery.blockNumber > latestBlock.number) { 92 | console.warn(`Block number ${subquery.blockNumber} is in the future`); 93 | } 94 | 95 | const value = await getStorageFieldValue(provider, subquery, console); 96 | if (value === null) { 97 | console.error(`Storage subquery ${JSON.stringify(subquery)} returned null`); 98 | return false; 99 | } 100 | return true; 101 | } 102 | 103 | export async function validateTxSubquery( 104 | provider: ethers.JsonRpcProvider, 105 | subquery: UnbuiltTxSubquery, 106 | configLimitManager: ConfigLimitManager, 107 | ): Promise { 108 | if ( 109 | (subquery.fieldOrCalldataIdx > TxField.s && subquery.fieldOrCalldataIdx < AxiomV2FieldConstant.Tx.TxTypeFieldIdx) || 110 | (subquery.fieldOrCalldataIdx > AxiomV2FieldConstant.Tx.CalldataHashFieldIdx && 111 | subquery.fieldOrCalldataIdx < AxiomV2FieldConstant.Tx.CalldataIdxOffset) 112 | ) { 113 | console.error(`Invalid tx field/calldata index: ${subquery.fieldOrCalldataIdx}`); 114 | return false; 115 | } 116 | 117 | const tx = await getRawTransaction(provider, subquery.txHash); 118 | if (!tx) { 119 | console.error(`Unable to get transaction from txHash: ${subquery.txHash}`); 120 | return false; 121 | } 122 | if (tx.blockNumber === undefined || tx.transactionIndex === undefined) { 123 | console.error("Unable to get blockNumber or txIdx from supplied txHash"); 124 | return false; 125 | } 126 | const blockNumber = Number(tx.blockNumber); 127 | const txIdx = Number(tx.transactionIndex); 128 | 129 | configLimitManager.processTx(tx); 130 | 131 | const value = await getTxFieldValue( 132 | provider, 133 | { 134 | blockNumber, 135 | txIdx, 136 | fieldOrCalldataIdx: subquery.fieldOrCalldataIdx, 137 | }, 138 | console, 139 | tx, 140 | ); 141 | if (value === null) { 142 | console.error(`Tx subquery ${JSON.stringify(subquery)} returned null`); 143 | return false; 144 | } 145 | return true; 146 | } 147 | 148 | export async function validateReceiptSubquery( 149 | provider: ethers.JsonRpcProvider, 150 | subquery: UnbuiltReceiptSubquery, 151 | configLimitManager: ConfigLimitManager, 152 | ): Promise { 153 | if ( 154 | (subquery.fieldOrLogIdx > ReceiptField.CumulativeGas && 155 | subquery.fieldOrLogIdx < AxiomV2FieldConstant.Receipt.AddressIdx) || 156 | (subquery.fieldOrLogIdx > AxiomV2FieldConstant.Receipt.TxIndexFieldIdx && 157 | subquery.fieldOrLogIdx < AxiomV2FieldConstant.Receipt.LogIdxOffset) 158 | ) { 159 | console.error(`Invalid receipt field/log index: ${subquery.fieldOrLogIdx}`); 160 | return false; 161 | } 162 | if (subquery.fieldOrLogIdx >= AxiomV2FieldConstant.Receipt.LogIdxOffset) { 163 | if (!ethers.isBytesLike(subquery.eventSchema) || getNumBytes(subquery.eventSchema) !== 32) { 164 | console.error(`Must define event schema when using log index: ${subquery.eventSchema}`); 165 | return false; 166 | } 167 | } 168 | if ( 169 | subquery.topicOrDataOrAddressIdx > 4 && 170 | subquery.topicOrDataOrAddressIdx < AxiomV2FieldConstant.Receipt.LogIdxOffset && 171 | subquery.topicOrDataOrAddressIdx !== AxiomV2FieldConstant.Receipt.AddressIdx 172 | ) { 173 | console.error(`Invalid receipt topic/data/address index index: ${subquery.topicOrDataOrAddressIdx}`); 174 | return false; 175 | } 176 | 177 | const rc = await getRawReceipt(provider, subquery.txHash); 178 | if (!rc) { 179 | console.error(`Unable to get receipt from txHash: ${subquery.txHash}`); 180 | return false; 181 | } 182 | 183 | if (rc.blockNumber === undefined || rc.transactionIndex === undefined) { 184 | console.error("Unable to get blockNumber or txIdx from supplied txHash"); 185 | return false; 186 | } 187 | const blockNumber = Number(rc.blockNumber); 188 | const txIdx = Number(rc.transactionIndex); 189 | 190 | configLimitManager.processReceipt(rc); 191 | 192 | const value = await getReceiptFieldValue( 193 | provider, 194 | { 195 | blockNumber, 196 | txIdx, 197 | fieldOrLogIdx: subquery.fieldOrLogIdx, 198 | topicOrDataOrAddressIdx: subquery.topicOrDataOrAddressIdx, 199 | eventSchema: subquery.eventSchema, 200 | }, 201 | console, 202 | rc, 203 | ); 204 | if (value === null) { 205 | console.error(`Receipt subquery ${JSON.stringify(subquery)} returned null`); 206 | return false; 207 | } 208 | return true; 209 | } 210 | 211 | export async function validateSolidityNestedMappingSubquery( 212 | provider: ethers.JsonRpcProvider, 213 | subquery: UnbuiltSolidityNestedMappingSubquery, 214 | ): Promise { 215 | if (subquery.keys.length !== subquery.mappingDepth) { 216 | console.error( 217 | `Nested mapping keys length ${subquery.keys.length} does not match mapping depth ${subquery.mappingDepth}`, 218 | ); 219 | return false; 220 | } 221 | const latestBlock = await provider.getBlock("latest"); 222 | if (latestBlock === null) { 223 | throw new Error("Failed to get latest block; check your internet connection or provider RPC"); 224 | } 225 | if (subquery.blockNumber > latestBlock.number) { 226 | console.warn(`Block number ${subquery.blockNumber} is in the future`); 227 | } 228 | for (const key of subquery.keys) { 229 | if (!ethers.isBytesLike(key)) { 230 | console.error(`Invalid nested mapping key: ${key} (must be bytes-like)`); 231 | return false; 232 | } 233 | } 234 | 235 | const value = await getSolidityNestedMappingValue(provider, subquery, console); 236 | if (value === null) { 237 | console.error(`Solidity nested mapping subquery ${JSON.stringify(subquery)} returned null`); 238 | return false; 239 | } 240 | return true; 241 | } 242 | 243 | export async function validateBeaconSubquery( 244 | provider: ethers.JsonRpcProvider, 245 | subquery: BeaconValidatorSubquery, 246 | ): Promise { 247 | // WIP 248 | return true; 249 | } 250 | -------------------------------------------------------------------------------- /test/unit/v2/dataSubqueryBuilders.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AccountField, 3 | HeaderField, 4 | TxField, 5 | getBlockNumberAndTxIdx, 6 | bytes32, 7 | AxiomV2FieldConstant, 8 | TxSubquery, 9 | ReceiptSubquery, 10 | } from "@axiom-crypto/tools"; 11 | import { 12 | AxiomSdkCore, 13 | AxiomSdkCoreConfig, 14 | QueryV2, 15 | buildAccountSubquery, 16 | buildHeaderSubquery, 17 | buildReceiptSubquery, 18 | buildTxSubquery, 19 | buildStorageSubquery, 20 | buildSolidityNestedMappingSubquery, 21 | UnbuiltHeaderSubquery, 22 | UnbuiltAccountSubquery, 23 | UnbuiltTxSubquery, 24 | UnbuiltReceiptSubquery, 25 | UnbuiltSolidityNestedMappingSubquery, 26 | UnbuiltStorageSubquery, 27 | } from "../../../src"; 28 | import { ethers } from "ethers"; 29 | 30 | // Test coverage areas: 31 | // - DataQuery subquery builders 32 | // - DataQuery subquery types 33 | 34 | describe("Data Subquery Builders", () => { 35 | const WETH_ADDR = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".toLowerCase(); 36 | const WETH_WHALE = "0x2E15D7AA0650dE1009710FDd45C3468d75AE1392".toLowerCase(); 37 | 38 | const provider = new ethers.JsonRpcProvider(process.env.PROVIDER_URI as string); 39 | 40 | const config: AxiomSdkCoreConfig = { 41 | providerUri: process.env.PROVIDER_URI as string, 42 | privateKey: process.env.PRIVATE_KEY as string, 43 | chainId: 1, 44 | version: "v2", 45 | }; 46 | const axiom = new AxiomSdkCore(config); 47 | 48 | const blockNumber = 18000000; 49 | 50 | test("Build and append a header subquery", () => { 51 | const query = (axiom.query as QueryV2).new(); 52 | const headerSubquery: UnbuiltHeaderSubquery = buildHeaderSubquery(blockNumber).field(HeaderField.GasUsed); 53 | query.appendDataSubquery(headerSubquery); 54 | const dataQuery = query.getDataQuery(); 55 | 56 | const subquery = dataQuery?.[0] as UnbuiltHeaderSubquery; 57 | expect(subquery?.blockNumber).toEqual(blockNumber); 58 | expect(subquery?.fieldIdx).toEqual(HeaderField.GasUsed); 59 | }); 60 | 61 | test("Build and append a header logsBloom subquery", () => { 62 | const query = (axiom.query as QueryV2).new(); 63 | const headerSubquery: UnbuiltHeaderSubquery = buildHeaderSubquery(blockNumber).logsBloom(2); 64 | query.appendDataSubquery(headerSubquery); 65 | const dataQuery = query.getDataQuery(); 66 | 67 | const subquery = dataQuery?.[0] as UnbuiltHeaderSubquery; 68 | expect(subquery?.blockNumber).toEqual(blockNumber); 69 | expect(subquery?.fieldIdx).toEqual(AxiomV2FieldConstant.Header.LogsBloomFieldIdxOffset + 2); 70 | }); 71 | 72 | test("Build and append an account subquery", () => { 73 | const query = (axiom.query as QueryV2).new(); 74 | const accountSubquery: UnbuiltAccountSubquery = buildAccountSubquery(blockNumber) 75 | .address(WETH_WHALE) 76 | .field(AccountField.Balance); 77 | query.appendDataSubquery(accountSubquery); 78 | const dataQuery = query.getDataQuery(); 79 | 80 | const subquery = dataQuery?.[0] as UnbuiltAccountSubquery; 81 | expect(subquery?.blockNumber).toEqual(blockNumber); 82 | expect(subquery?.addr).toEqual(WETH_WHALE); 83 | expect(subquery?.fieldIdx).toEqual(AccountField.Balance); 84 | }); 85 | 86 | test("Build and append a storage subquery", () => { 87 | const query = (axiom.query as QueryV2).new(); 88 | const storageSubquery: UnbuiltStorageSubquery = buildStorageSubquery(blockNumber).address(WETH_ADDR).slot(1); 89 | query.appendDataSubquery(storageSubquery); 90 | const dataQuery = query.getDataQuery(); 91 | 92 | const subquery = dataQuery?.[0] as UnbuiltStorageSubquery; 93 | expect(subquery?.blockNumber).toEqual(blockNumber); 94 | expect(subquery?.addr).toEqual(WETH_ADDR); 95 | expect(subquery?.slot).toEqual(bytes32(1)); 96 | }); 97 | 98 | test("Build and append a tx subquery", async () => { 99 | const query = (axiom.query as QueryV2).new(); 100 | 101 | const txHash = "0x8d2e6cbd7cf1f88ee174600f31b79382e0028e239bb1af8301ba6fc782758bc6"; 102 | const { blockNumber, txIdx } = await getBlockNumberAndTxIdx(provider, txHash); 103 | if (blockNumber === null || txIdx === null) { 104 | throw new Error("Failed to get block number and tx idx"); 105 | } 106 | 107 | const txSubquery: UnbuiltTxSubquery = buildTxSubquery(txHash).field(TxField.MaxPriorityFeePerGas); 108 | query.appendDataSubquery(txSubquery); 109 | const dataQuery = query.getDataQuery(); 110 | 111 | // Check the unbuilt subquery 112 | const subquery = dataQuery?.[0] as UnbuiltTxSubquery; 113 | expect(subquery?.txHash).toEqual(txHash); 114 | expect(subquery?.fieldOrCalldataIdx).toEqual(2); 115 | 116 | // Build the Query and validate the built subquery 117 | const built = await query.build(); 118 | const builtSubquery = built.dataQueryStruct.subqueries?.[0].subqueryData as TxSubquery; 119 | expect(builtSubquery?.blockNumber).toEqual(blockNumber); 120 | expect(builtSubquery?.txIdx).toEqual(txIdx); 121 | expect(builtSubquery?.fieldOrCalldataIdx).toEqual(2); 122 | }); 123 | 124 | test("Build and append a receipt subquery", async () => { 125 | const query = (axiom.query as QueryV2).new(); 126 | 127 | const txHash = "0x8d2e6cbd7cf1f88ee174600f31b79382e0028e239bb1af8301ba6fc782758bc6"; 128 | const { blockNumber, txIdx } = await getBlockNumberAndTxIdx(provider, txHash); 129 | if (blockNumber === null || txIdx === null) { 130 | throw new Error("Failed to get block number and tx idx"); 131 | } 132 | 133 | const receiptSubquery: UnbuiltReceiptSubquery = buildReceiptSubquery(txHash) 134 | .log(0) 135 | .topic(1) 136 | .eventSchema("Transfer (address from, address to, uint256 value)"); 137 | query.appendDataSubquery(receiptSubquery); 138 | const dataQuery = query.getDataQuery(); 139 | 140 | // Check the unbuilt subquery 141 | const subquery = dataQuery?.[0] as UnbuiltReceiptSubquery; 142 | expect(subquery?.txHash).toEqual(txHash); 143 | expect(subquery?.fieldOrLogIdx).toEqual(100); 144 | expect(subquery?.topicOrDataOrAddressIdx).toEqual(1); 145 | expect(subquery?.eventSchema).toEqual("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"); 146 | 147 | // Build the Query and validate the built subquery 148 | const built = await query.build(); 149 | const builtSubquery = built.dataQueryStruct.subqueries?.[0].subqueryData as ReceiptSubquery; 150 | expect(builtSubquery?.blockNumber).toEqual(blockNumber); 151 | expect(builtSubquery?.txIdx).toEqual(txIdx); 152 | expect(builtSubquery?.fieldOrLogIdx).toEqual(100); 153 | expect(builtSubquery?.topicOrDataOrAddressIdx).toEqual(1); 154 | expect(builtSubquery?.eventSchema).toEqual("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"); 155 | }); 156 | 157 | test("Build and append a receipt log address subquery", async () => { 158 | const query = (axiom.query as QueryV2).new(); 159 | 160 | const txHash = "0x8d2e6cbd7cf1f88ee174600f31b79382e0028e239bb1af8301ba6fc782758bc6"; 161 | const { blockNumber, txIdx } = await getBlockNumberAndTxIdx(provider, txHash); 162 | if (blockNumber === null || txIdx === null) { 163 | throw new Error("Failed to get block number and tx idx"); 164 | } 165 | 166 | const receiptSubquery: UnbuiltReceiptSubquery = buildReceiptSubquery(txHash).log(0).address(); 167 | query.appendDataSubquery(receiptSubquery); 168 | const dataQuery = query.getDataQuery(); 169 | 170 | // Check the unbuilt subquery 171 | const subquery = dataQuery?.[0] as UnbuiltReceiptSubquery; 172 | expect(subquery?.txHash).toEqual(txHash); 173 | expect(subquery?.fieldOrLogIdx).toEqual(100); 174 | expect(subquery?.topicOrDataOrAddressIdx).toEqual(50); 175 | expect(subquery?.eventSchema).toEqual(bytes32(0)); 176 | 177 | // Build the Query and validate the built subquery 178 | const built = await query.build(); 179 | const builtSubquery = built.dataQueryStruct.subqueries?.[0].subqueryData as ReceiptSubquery; 180 | expect(builtSubquery?.blockNumber).toEqual(blockNumber); 181 | expect(builtSubquery?.txIdx).toEqual(txIdx); 182 | expect(builtSubquery?.fieldOrLogIdx).toEqual(100); 183 | expect(builtSubquery?.topicOrDataOrAddressIdx).toEqual(50); 184 | expect(builtSubquery?.eventSchema).toEqual(bytes32(0)); 185 | }); 186 | 187 | test("Build and append a receipt logsBloom subquery", async () => { 188 | const query = (axiom.query as QueryV2).new(); 189 | 190 | const txHash = "0x8d2e6cbd7cf1f88ee174600f31b79382e0028e239bb1af8301ba6fc782758bc6"; 191 | const { blockNumber, txIdx } = await getBlockNumberAndTxIdx(provider, txHash); 192 | 193 | const receiptSubquery: UnbuiltReceiptSubquery = buildReceiptSubquery(txHash).logsBloom(2); 194 | query.appendDataSubquery(receiptSubquery); 195 | const dataQuery = query.getDataQuery(); 196 | 197 | // Check the unbuilt subquery 198 | const subquery = dataQuery?.[0] as UnbuiltReceiptSubquery; 199 | expect(subquery?.txHash).toEqual(txHash); 200 | expect(subquery?.fieldOrLogIdx).toEqual(AxiomV2FieldConstant.Receipt.LogsBloomIdxOffset + 2); 201 | 202 | // Build the Query and validate the built subquery 203 | const built = await query.build(); 204 | const builtSubquery = built.dataQueryStruct.subqueries?.[0].subqueryData as ReceiptSubquery; 205 | expect(builtSubquery?.blockNumber).toEqual(blockNumber); 206 | expect(builtSubquery?.txIdx).toEqual(txIdx); 207 | expect(builtSubquery?.fieldOrLogIdx).toEqual(AxiomV2FieldConstant.Receipt.LogsBloomIdxOffset + 2); 208 | expect(builtSubquery?.topicOrDataOrAddressIdx).toEqual(0); 209 | expect(builtSubquery?.eventSchema).toEqual(bytes32(0)); 210 | }); 211 | 212 | test("Build and append a nested mapping subquery", () => { 213 | const query = (axiom.query as QueryV2).new(); 214 | const nestedMappingSubquery: UnbuiltSolidityNestedMappingSubquery = buildSolidityNestedMappingSubquery(blockNumber) 215 | .address(WETH_ADDR) 216 | .mappingSlot(0) 217 | .keys([WETH_ADDR, WETH_WHALE, 100000]); 218 | query.appendDataSubquery(nestedMappingSubquery); 219 | const dataQuery = query.getDataQuery(); 220 | 221 | const subquery = dataQuery?.[0] as UnbuiltSolidityNestedMappingSubquery; 222 | expect(subquery?.blockNumber).toEqual(blockNumber); 223 | expect(subquery?.addr).toEqual(WETH_ADDR); 224 | expect(subquery?.mappingSlot).toEqual(bytes32(0)); 225 | expect(subquery?.mappingDepth).toEqual(3); 226 | expect(subquery?.keys).toEqual([bytes32(WETH_ADDR), bytes32(WETH_WHALE), bytes32(100000)]); 227 | }); 228 | }); 229 | -------------------------------------------------------------------------------- /test/unit/v2/dataQueryCapacitySdk.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AccountField, 3 | AxiomSdkCore, 4 | AxiomSdkCoreConfig, 5 | HeaderField, 6 | QueryV2, 7 | ReceiptField, 8 | TxField, 9 | buildAccountSubquery, 10 | buildHeaderSubquery, 11 | buildReceiptSubquery, 12 | buildSolidityNestedMappingSubquery, 13 | buildStorageSubquery, 14 | buildTxSubquery, 15 | } from "../../../src"; 16 | import { ConstantsV2 } from "../../../src/v2/constants"; 17 | 18 | // Test coverage areas: 19 | // - DataQuery capacity 20 | // - Appending subqueries 21 | 22 | describe("DataQuery Capacity (SDK-enforced)", () => { 23 | const WETH_ADDR = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; 24 | const WETH_WHALE = "0x2E15D7AA0650dE1009710FDd45C3468d75AE1392"; 25 | const WSOL_ADDR = "0xd31a59c85ae9d8edefec411d448f90841571b89c"; 26 | const UNI_V3_FACTORY_ADDR = "0x1F98431c8aD98523631AE4a59f267346ea31F984"; 27 | 28 | // mainnet block 18500000 txIdx [0,33) 29 | const validMainnetTxHashes = [ 30 | "0x122f25a52c76682fb0e6b904b3b666e6a9bf5008af0d4c3b474d4e3010c4d5f9", 31 | "0xb0c21cf68a743788c10d759845db5b922c8ac3971b0583d169174f6e2da086f5", 32 | "0xb3fb7ac99ecd6a981a08d94665d08b8303e6b907c33c6562b8226f2ee64e15c5", 33 | "0x35a39c1d1c8ac6f6e8c175de0cdfcb1e9ed7ee4303f2e562f86d2290d93215ba", 34 | "0x1fafb1edf72f30a5db7835421d0869d155d9a4cf53d077ca18b7ec572febdcf8", 35 | "0x080281b5ccd4c6d3210ab08806bdf27cad889e61ed526a6e0636cdc00aa74705", 36 | "0x17c26492b5211cb4fa038f2960319e8bd6e90f9c1e9731534e841d09c4e5c2df", 37 | "0xef9ac5c744f9e1d6c07d02c11c8a2ae5acccf4bb1fc05608d6b025bca2f6b1f5", 38 | "0xd89a3851ceb10019d1613a623e68c187292c05a12d70d8c28c312b2153999c8e", 39 | "0x7e8b9c439e8dca9e660ffa071ccf24dd6a12e20e8d7ad31d50bb059b031c0975", 40 | "0xdd5b8c85fd74861566d38da30a58deef0dcb6a0c7e7b4eeff28102dd227786ff", 41 | "0xd5f40ce15676c81c160f3da5dd09398957a2c6918e32f377c767cdab1bb5bf93", 42 | "0xba4345f28b7c714a478b989a2474cee11a6062d6b9f838cc27bec77c46767bcd", 43 | "0xc94a955a2f8c48dc4f14f4183aff4b23aede06ff7fcd7888b18cb407a707fa74", 44 | "0xb9a9682b520feeaf099b6643dfae9b6432263da5e7469a01276aa39064509031", 45 | "0x741fb87be70e45db2d82d7f5cdbb2e7ef39fb19b5ef4ab544c09b9c83a67c7b7", 46 | "0x1f34e4e287336fa22b3bb950c7933aac122a5add167468a9681265faa963ff40", 47 | "0x2e8e7d7e17a076b0b69dbc188049d02ee983cc066356274cc8f424fa0edc26fd", 48 | "0x87f77e87290836e5174bf340c3a44d78cd033845d32617cdd55bf12136b17233", 49 | "0x5e6e43d3310878616d60b7760f9bd39f0aedcb2c65134738fb5354b9eb35be95", 50 | "0x7355606b0ca4f85467d5c56ea1ae3fcfa2d4f9238cf20bee7a71ada6a889dcd8", 51 | "0x4506010aa2ce63e97a141bacb9e10147b3eeeae53e98a5ef0475c3320c0ad466", 52 | "0x4cbae4bac1cf87cf8f32ada1727950be149ef294a2166310d3f67e574c03c300", 53 | "0xb1724fd30f5d5a77b2199fceac48ce60df66f074d59dcccdb12bcef56b9e4e3e", 54 | "0xc02472f7ef14a9c960defe6ecac170bcb98c24829cc75027c63d7659e9262464", 55 | "0xec2e654d06498ed0f47d68762e862059e3f8971c92b189aee92538db604e4605", 56 | "0x4f46e2c0d19ce1bb1e0cb50bf0e884b13b50f13bdbd92eddcd1676af599545f6", 57 | "0x73a028b6c5a1babdb4081df839b9a7a3bf7c0ada4df14060c670ab25eef89851", 58 | "0x6c9bd68a38d01dc374fc57053b0b9c1737f2611f00254446d5ca3db9dac740b5", 59 | "0x161944cb3d51e7d531ff0f45bcba612dd04a0973cd38e219fc85bbc061e0ab4f", 60 | "0x16b844a564c78386f62ad934474243a9ec97c171cb3bd3080757f677fadea788", 61 | "0xd85e411ae03daa7cf11352795b05a2b1c6bba1cb4144284f510ea379481994a1", 62 | // "0x079fe983f70c4c176e2b15d0fa4392c5a30fc535055d10fca31003cb48037ba0", // 33 63 | ]; 64 | console.log(validMainnetTxHashes.length); 65 | 66 | const config: AxiomSdkCoreConfig = { 67 | privateKey: process.env.PRIVATE_KEY as string, 68 | providerUri: process.env.PROVIDER_URI as string, 69 | version: "v2", 70 | }; 71 | const axiom = new AxiomSdkCore(config); 72 | 73 | test(`Append ${ConstantsV2.MaxSameSubqueryType} Header subqueries`, () => { 74 | const blockNumber = 18000000; 75 | const query = (axiom.query as QueryV2).new(); 76 | for (let i = 0; i < ConstantsV2.MaxSameSubqueryType; i++) { 77 | const subquery = buildHeaderSubquery(blockNumber + i).field(HeaderField.Nonce); 78 | query.appendDataSubquery(subquery); 79 | } 80 | }); 81 | 82 | test(`Append ${ConstantsV2.MaxSameSubqueryType + 1} Header subqueries fail`, () => { 83 | const testFn = () => { 84 | const blockNumber = 18000000; 85 | const query = (axiom.query as QueryV2).new(); 86 | for (let i = 0; i < ConstantsV2.MaxSameSubqueryType + 1; i++) { 87 | const subquery = buildHeaderSubquery(blockNumber + i).field(HeaderField.Nonce); 88 | query.appendDataSubquery(subquery); 89 | } 90 | }; 91 | expect(testFn).toThrow(); 92 | }); 93 | 94 | test(`Append ${ConstantsV2.MaxSameSubqueryType} Account subqueries`, () => { 95 | const blockNumber = 18000000; 96 | const query = (axiom.query as QueryV2).new(); 97 | for (let i = 0; i < ConstantsV2.MaxSameSubqueryType; i++) { 98 | const subquery = buildAccountSubquery(blockNumber + i) 99 | .address(WETH_WHALE) 100 | .field(AccountField.Balance); 101 | query.appendDataSubquery(subquery); 102 | } 103 | }); 104 | 105 | test(`Append ${ConstantsV2.MaxSameSubqueryType + 1} Account subqueries fail`, () => { 106 | const testFn = () => { 107 | const blockNumber = 18000000; 108 | const query = (axiom.query as QueryV2).new(); 109 | for (let i = 0; i < ConstantsV2.MaxSameSubqueryType + 1; i++) { 110 | const subquery = buildAccountSubquery(blockNumber + i) 111 | .address(WETH_WHALE) 112 | .field(AccountField.Balance); 113 | query.appendDataSubquery(subquery); 114 | } 115 | }; 116 | expect(testFn).toThrow(); 117 | }); 118 | 119 | test(`Append ${ConstantsV2.MaxSameSubqueryType} Storage subqueries`, () => { 120 | const blockNumber = 18000000; 121 | const query = (axiom.query as QueryV2).new(); 122 | for (let i = 0; i < ConstantsV2.MaxSameSubqueryType; i++) { 123 | const subquery = buildStorageSubquery(blockNumber + i) 124 | .address(WETH_ADDR) 125 | .slot(0); 126 | query.appendDataSubquery(subquery); 127 | } 128 | }); 129 | 130 | test(`Append ${ConstantsV2.MaxSameSubqueryType + 1} Storage subqueries fail`, () => { 131 | const testFn = () => { 132 | const blockNumber = 18000000; 133 | const query = (axiom.query as QueryV2).new(); 134 | for (let i = 0; i < ConstantsV2.MaxSameSubqueryType + 1; i++) { 135 | const subquery = buildStorageSubquery(blockNumber + i) 136 | .address(WETH_ADDR) 137 | .slot(0); 138 | query.appendDataSubquery(subquery); 139 | } 140 | }; 141 | expect(testFn).toThrow(); 142 | }); 143 | 144 | test(`Append ${ConstantsV2.MaxSameSubqueryType} Solidity Nested Mapping subqueries`, () => { 145 | const blockNumber = 18000000; 146 | const query = (axiom.query as QueryV2).new(); 147 | for (let i = 0; i < ConstantsV2.MaxSameSubqueryType; i++) { 148 | const mapping = buildSolidityNestedMappingSubquery(blockNumber + i) 149 | .address(UNI_V3_FACTORY_ADDR) 150 | .mappingSlot(5) 151 | .keys([WETH_ADDR, WSOL_ADDR, 10000]); 152 | query.appendDataSubquery(mapping); 153 | } 154 | }); 155 | 156 | test(`Append ${ConstantsV2.MaxSameSubqueryType + 1} Solidity Nested Mapping subqueries fail`, () => { 157 | const testFn = () => { 158 | const blockNumber = 18000000; 159 | const query = (axiom.query as QueryV2).new(); 160 | for (let i = 0; i < ConstantsV2.MaxSameSubqueryType + 1; i++) { 161 | const mapping = buildSolidityNestedMappingSubquery(blockNumber + i) 162 | .address(UNI_V3_FACTORY_ADDR) 163 | .mappingSlot(5) 164 | .keys([WETH_ADDR, WSOL_ADDR, 10000]); 165 | query.appendDataSubquery(mapping); 166 | } 167 | }; 168 | expect(testFn).toThrow(); 169 | }); 170 | 171 | test(`Append 43 Account + 43 Storage + 42 Nested Mapping subqueries`, () => { 172 | const blockNumber = 18000000; 173 | const query = (axiom.query as QueryV2).new(); 174 | for (let i = 0; i < 43; i++) { 175 | const accountSubquery = buildAccountSubquery(blockNumber + i) 176 | .address(WETH_WHALE) 177 | .field(AccountField.Balance); 178 | query.appendDataSubquery(accountSubquery); 179 | const account = buildStorageSubquery(blockNumber + i) 180 | .address(WETH_ADDR) 181 | .slot(0); 182 | query.appendDataSubquery(account); 183 | if (i === 42) { 184 | continue; 185 | } 186 | const mapping = buildSolidityNestedMappingSubquery(blockNumber + i) 187 | .address(UNI_V3_FACTORY_ADDR) 188 | .mappingSlot(5) 189 | .keys([WETH_ADDR, WSOL_ADDR, 10000]); 190 | query.appendDataSubquery(mapping); 191 | } 192 | }); 193 | 194 | test(`Append 43 Account + 43 Storage + 43 Nested Mapping subqueries fail`, () => { 195 | const testFn = () => { 196 | const blockNumber = 18000000; 197 | const query = (axiom.query as QueryV2).new(); 198 | for (let i = 0; i < 43; i++) { 199 | const accountSubquery = buildAccountSubquery(blockNumber + i) 200 | .address(WETH_WHALE) 201 | .field(AccountField.Balance); 202 | query.appendDataSubquery(accountSubquery); 203 | const account = buildStorageSubquery(blockNumber + i) 204 | .address(WETH_ADDR) 205 | .slot(0); 206 | query.appendDataSubquery(account); 207 | const mapping = buildSolidityNestedMappingSubquery(blockNumber + i) 208 | .address(UNI_V3_FACTORY_ADDR) 209 | .mappingSlot(5) 210 | .keys([WETH_ADDR, WSOL_ADDR, 10000]); 211 | query.appendDataSubquery(mapping); 212 | } 213 | }; 214 | expect(testFn).toThrow(); 215 | }); 216 | 217 | test(`Append ${ConstantsV2.MaxSameSubqueryType} Tx subqueries`, () => { 218 | const query = (axiom.query as QueryV2).new(); 219 | const txHashes = validMainnetTxHashes; 220 | for (let i = 0; i < validMainnetTxHashes.length; i++) { 221 | query.appendDataSubquery(buildTxSubquery(txHashes[i]).field(TxField.To)); 222 | query.appendDataSubquery(buildTxSubquery(txHashes[i]).field(TxField.ChainId)); 223 | query.appendDataSubquery(buildTxSubquery(txHashes[i]).field(TxField.GasPrice)); 224 | query.appendDataSubquery(buildTxSubquery(txHashes[i]).field(TxField.Nonce)); 225 | } 226 | }); 227 | 228 | test(`Append ${ConstantsV2.MaxSameSubqueryType + 1} Tx subqueries fail`, () => { 229 | const query = (axiom.query as QueryV2).new(); 230 | const txHashes = validMainnetTxHashes; 231 | for (let i = 0; i < validMainnetTxHashes.length; i++) { 232 | query.appendDataSubquery(buildTxSubquery(txHashes[i]).field(TxField.To)); 233 | query.appendDataSubquery(buildTxSubquery(txHashes[i]).field(TxField.ChainId)); 234 | query.appendDataSubquery(buildTxSubquery(txHashes[i]).field(TxField.GasPrice)); 235 | query.appendDataSubquery(buildTxSubquery(txHashes[i]).field(TxField.Nonce)); 236 | } 237 | const oneMore = () => { 238 | query.appendDataSubquery(buildTxSubquery(txHashes[0]).field(TxField.To)); 239 | }; 240 | expect(oneMore).toThrow(); 241 | }); 242 | 243 | test(`Append ${ConstantsV2.MaxSameSubqueryType} Receipt subqueries`, () => { 244 | const query = (axiom.query as QueryV2).new(); 245 | const txHashes = validMainnetTxHashes 246 | for (let i = 0; i < validMainnetTxHashes.length; i++) { 247 | query.appendDataSubquery(buildReceiptSubquery(txHashes[i]).field(ReceiptField.Status)); 248 | query.appendDataSubquery(buildReceiptSubquery(txHashes[i]).field(ReceiptField.LogsBloom)); 249 | query.appendDataSubquery(buildReceiptSubquery(txHashes[i]).field(ReceiptField.Logs)); 250 | query.appendDataSubquery(buildReceiptSubquery(txHashes[i]).field(ReceiptField.CumulativeGas)); 251 | } 252 | }); 253 | 254 | test(`Append ${ConstantsV2.MaxSameSubqueryType + 1} Receipt subqueries fail`, () => { 255 | const testFn = () => { 256 | const query = (axiom.query as QueryV2).new(); 257 | const txHashes = validMainnetTxHashes.slice(0, ConstantsV2.MaxSameSubqueryType + 1); 258 | for (let i = 0; i < ConstantsV2.MaxSameSubqueryType + 1; i++) { 259 | const subquery = buildReceiptSubquery(txHashes[i]).field(ReceiptField.Status); 260 | query.appendDataSubquery(subquery); 261 | } 262 | }; 263 | expect(testFn).toThrow(); 264 | }); 265 | 266 | test(`Append ${ConstantsV2.UserMaxTotalSubqueries} subqueries`, () => { 267 | const blockNumber = 18000000; 268 | const txHashes = validMainnetTxHashes; 269 | 270 | const query = (axiom.query as QueryV2).new(); 271 | for (let i = 0; i < ConstantsV2.MaxSameSubqueryType / 4; i++) { 272 | const headerSubquery = buildHeaderSubquery(blockNumber + i).field(HeaderField.Nonce); 273 | query.appendDataSubquery(headerSubquery); 274 | 275 | const accountSubquery = buildAccountSubquery(blockNumber + i) 276 | .address(WETH_WHALE) 277 | .field(AccountField.Balance); 278 | query.appendDataSubquery(accountSubquery); 279 | 280 | const txSubquery = buildTxSubquery(txHashes[i]).field(TxField.To); 281 | query.appendDataSubquery(txSubquery); 282 | 283 | const receiptSubquery = buildReceiptSubquery(txHashes[i]).field(ReceiptField.Status); 284 | query.appendDataSubquery(receiptSubquery); 285 | } 286 | }); 287 | }); 288 | -------------------------------------------------------------------------------- /test/unit/v2/buildQueryNoCallback.test.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { AxiomSdkCore, AxiomSdkCoreConfig, AxiomV2Callback, AxiomV2ComputeQuery, HeaderField, QueryV2, bytes32 } from "../../../src"; 3 | 4 | // Test coverage areas: 5 | // - DataQuery 6 | // - ComputeQuery 7 | // - No Callback 8 | 9 | describe("Build a Query with no Callback", () => { 10 | const config: AxiomSdkCoreConfig = { 11 | providerUri: process.env.PROVIDER_URI as string, 12 | privateKey: process.env.PRIVATE_KEY as string, 13 | chainId: 1, 14 | version: "v2", 15 | }; 16 | const axiom = new AxiomSdkCore(config); 17 | 18 | test("simple computeQuery with no Callback", async () => { 19 | /** Code 20 | // x^2 + y 21 | const x_2 = mul(x, x); 22 | const val = add(x_2, y); 23 | addToCallback(val) 24 | */ 25 | 26 | /** Inputs 27 | { 28 | "x": 10, 29 | "y": 50 30 | } 31 | */ 32 | 33 | const computeQuery: AxiomV2ComputeQuery = { 34 | k: 13, 35 | resultLen: 1, 36 | vkey: [ 37 | "0x4caffbbdc5d29b00b3e97df7fb169baadaff9443ea3ecfd62231541d2752e003", 38 | "0xc04b25057d0bddf35d4542077516abb76445b8e745a457e3ccc1bf9aac2ba406", 39 | "0x6f4cebf257ebe7a319dd5dedcd41085d35b11afe7254a55d5f33366084983a61", 40 | "0xce36e621969a1e33547280e28d8158537284eb5b5d284ede4dc81d1e69ef2e28", 41 | "0x0000000000000000000000000000000000000000000000000000000000000080", 42 | "0x0000000000000000000000000000000000000000000000000000000000000080", 43 | "0x0000000000000000000000000000000000000000000000000000000000000080", 44 | "0xceb30489e39186b75336db29c5e0ce27e0c7480fc5a8ee071fc8642283700e42", 45 | "0x4570a761b654d0422dba9042e1c2130284cca09e659d46b69739c99697ff4068", 46 | "0x22e4c62aacfc240ed0553bfad00122ba8c7627c870c739f3f818584e066a8b1f", 47 | "0x841485e0a9f109688bdc4f5ff851d9e2e44833ae573456742c1237322e938542", 48 | "0x79a62f1cc2f1440cc9fdcd534b612a49da4b6139bbed8cf53a26f4568ac3f567", 49 | "0x63bf6c5c2a2c6a568d6399d6658a320511dda5caf1dc5a84af03472a552ee510", 50 | "0x1f3d22024e669b91ee856e925fcbe0b6da642b57b348823bf7ac26266b3e695d", 51 | ], 52 | computeProof: 53 | "0x00000000000000000000000000000000000000000000000000000000000000960000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ca0d58f038fa1dea93aac4ae41d157f0894c4d857468a9da2d8999721f5691209442ccfd19f64af56d033662e58c382db23a6693a591a22e2706a9a7527c1f041e4ca2d69d9aec9cd4203ce4cf181dda917e93db04afd78953f872112797c710b38aa8b8c774c4f56bf471ac233b27f87791647e18d70be18f28570a4c948548a137f0d62b925e4f0f0c8b2c8de36c2c1c54b63061619cb391dc117d72dcea16ec2f0173413814f94fd7ab61bf134aab9cf7bf453d246d6a25746576377e8806eccf2692cab5ffd4d443e9ac325a479dcd51b72b9123f39a7ac77d94333469629987d1f0209effc6d3f71b9ed1622bc1e6fc68c575725eaf2bca1ae3674ee44ca652f57b0da21ecb9413d083cdeabe8e52d255177621112c3b3a22daf8eec7074c6e4ec115313e94a935e632ac5226a232abfb0910b3e5ff8a6ea58855028948d093f98691bac135ab827597dd3291bd7372298c4cc028af1b710972776f106637331c92b893f3db7a85c480f26a7083fe3052ec48c1360b42929ef950e643279fbb17dbef72b8e87321b8e8b911383a0913ecd1789960428aafd24d7939d45148e89f457178c188b065cf233f69a26d0195e8e9853e973fdfd3015612bae12b0f18ffa2e18550a7195beff3d684841ecf3cb869ba9668fda32514d83fcc801bbd72d06f730773bae66310f62c6eac2f58d6a88826adb520ef0c9b87ee7c371c40b6f73f27226ab90f0594860c30d892d4082925d83d8a7cfe885b743e49481a1b685dcefe3d8029e70d875b9b4902ac0a82895175881cd0a354090c0b1c9903465eb370ed07dbd3eba1367ec17095ea20278a6db3055f45b85be5862ceaa92b0cf0c3cb2f8f6df24a598f1b45c0f60a4308b44ce630c3e97c385aa664756500d6ea8cd7069d2b9fb8c0b389a9b325cca515a0e3a0498ca8ce67ef920358b12959761f8d863b39ee4e8036e816a110d1f4637f65c68d5ef3a9ba50f059aec10e39e9652b6594d85c86dd9b98889d7b854e5e3a4674cb54d66dff547409165f235637f037c8dc693649eebf8e6cede4474a92e3ac4091cc0eea855f0790ed8e06bffc7ddd79222f3423209826f6c99279f0f709a04a58de67d9ae12aeb1bc38261907ab51f8f4025cded6cfa1d54acccf85a39834721865723a9560dca880310a1444588fa6fa6cf1caa6d5282e0a401861cf4d7dbc60449086014e076f673c2f7eb23a6204f697d6209bca33b52453de1841c683390ac3f03eedcdfb1b92941f0c6c1e92c687955f343eec30f71cf78d8ff1ee4c38d44c19acd01e5fd910682391b066e657a08ef28362f40f918963b4228d657280688dafab61b4063c760d1ed8562c1b061850b49054f0816858a574bd79bb8d592423ea1d7f02b617b01e06327e6c6f120c69866bf81203ec2ebb274e118b44e87fe2e30dd77882de784b11243f41f5200ede478cb14c5c24c117fa987e149ccb67b7a7f758e26250158c20c515702aa9c28af2ea51cc9e135d6d635b3ddc259d5f5713a5615333f277a805b452afe6ec4205b927793ab473fc3fa40631d9d9cf365067a4c78906b7119b19e66f13936c69f4db1db9005b89187657772eae05d81530375d9def9fcff1450a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005ed4d210ce92739ea5af0066601fcc2a43a9ae8ccc8a49f1008940b5066c2326f419c6da9fbd9481ca099e405aa18403a4188d21832ee58a39975d6a9a49ea1bba8d1de4811547f6e3aa1d684c0c0cd57443729dbcf201ac20704a553d0b6b20a9cdb94058c8deb216792b9ef0ca51a26c54ca23e460b2b191ce6af8784bff2f90fc0a4ec281dd95beee516d3e15a04fb873038b949c44db2fb0ee943f6bfa1d4f2ff763d865adf55a29b37bee0f6ad8754fde6999125b6a779056d4bdf95b13ac41d86d841adec49bf27a7414b88074f27a975ee36d5f647f7dcbcc580df20e4d7e040bbb2c6c2a6dc5f18737259dee588657cbbe225ebd66fac66ca3386d16a9bef9c4664733a2619a50be2ac5a8f4ce7f272b4686526681415e79ff3baf0881d52948b3fc7d0b5d413d44a1cb985d4557ff651042fd2c8180fe9528b9cb1f400da2686d602ebbe2e24d6d6b0c9e12d6ba41c1db556ee1ecce44fe8513e0104353210ebba3f83cfd38b84509c47245cfa4544189949997fe36e96fcb68b10495d6632a553da9f1f1dec8251018793d44e30a30bf874fc56abeb9facdd04a25a1bd9fe412949086db288d3326b80b48e4d85ec44de11a8767032c1569c472141f44f80531c0becccbf5791f65ef4d21a6529a908b7e5386f5c9169d20082813684ee79e7ce7ddc0497301321f5fbda44ecaac3c157cf00c28d432411528f405c8ee72147fcd0ab094b58914163c875cca43bd1724ee1b448137e5afbe38bf24be5c46aa67200f1e19a9cd4c9abfa91af60f54751220c189206d64c314fe1622d827025d39024eb329090c2b496490306a52f721bb8e41f41266262b899c420ea10ef2211821bbcbcc3ae427053b97d3a6a3d3133893f373adab42c8d8e20a0beb5d12246bc590c4424fddcc8511c594fdcfa23fa55ed2a89af28947fc9e5f1f081992a5f470977f7c7e38ceaa69cc5d43ee604cd5bf6c4a5a861960f3c10e1c306c1e0999a0ac0b6e565e6bc205de0d807e7b3a686d03b559b040c0cd332518b1289a02a889400211e1e84f038faac5966e5d927f025c3ed0b0149644646f11d91d524604313e3382d86310325d000e462fb5705776ad3dd10ced1d3889f829370e0aa9669d80239514dad97bbfdd5bde7e5d74d341d45b9827cccf1ce57d0d", 54 | }; 55 | 56 | const query = (axiom.query as QueryV2).new(); 57 | query.setComputeQuery(computeQuery); 58 | const builtQuery = await query.build(); 59 | 60 | expect(builtQuery.computeQuery).toEqual(computeQuery); 61 | expect(builtQuery.dataQueryStruct.subqueries).toEqual([]); 62 | expect(builtQuery.callback).toEqual({ 63 | target: ethers.ZeroAddress, 64 | extraData: ethers.ZeroHash, 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /src/v2/query/dataSubquery/subqueryBuilder.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers" 2 | import { 3 | AccountField, 4 | ReceiptField, 5 | TxField, 6 | bytes32, 7 | validateAddress, 8 | validateBytes32, 9 | validateSize, 10 | getFieldIdxHeaderLogsBloomIdx, 11 | getFieldIdxReceiptLogAddress, 12 | getFieldIdxReceiptBlockNumber, 13 | getFieldIdxReceiptDataIdx, 14 | getFieldIdxReceiptLogIdx, 15 | getFieldIdxReceiptLogsBloomIdx, 16 | getFieldIdxReceiptTopicIdx, 17 | getFieldIdxReceiptTxIndex, 18 | getFieldIdxReceiptTxType, 19 | getFieldIdxTxBlockNumber, 20 | getFieldIdxTxCalldataHash, 21 | getFieldIdxTxCalldataIdx, 22 | getFieldIdxTxContractDataIdx, 23 | getFieldIdxTxFunctionSelector, 24 | getFieldIdxTxIndex, 25 | getFieldIdxTxType, 26 | HeaderField, 27 | getEventSchema, 28 | } from "@axiom-crypto/tools" 29 | import { 30 | UnbuiltAccountSubquery, 31 | UnbuiltHeaderSubquery, 32 | UnbuiltReceiptSubquery, 33 | UnbuiltSolidityNestedMappingSubquery, 34 | UnbuiltStorageSubquery, 35 | UnbuiltTxSubquery 36 | } from "../../types"; 37 | 38 | /** 39 | * Builder for a Header data subquery 40 | * @param blockNumber Block number to query 41 | */ 42 | export const buildHeaderSubquery = (blockNumber: number | string | BigInt) => { 43 | validateSize(blockNumber, "uint32"); 44 | const blockNumberNum = Number(blockNumber.toString()); 45 | 46 | /** 47 | * End of the builder chain for a Header subquery. Specifies the HeaderField to query. 48 | * @param field HeaderField to query 49 | * @returns UnbuiltHeaderSubquery struct 50 | */ 51 | const field = (field: HeaderField): UnbuiltHeaderSubquery => { 52 | return { 53 | blockNumber: blockNumberNum, 54 | fieldIdx: field, 55 | } 56 | } 57 | 58 | /** 59 | * End of the builder chain for a Header subquery. Specifies the logs bloom index to query. 60 | * @param logsBloomIdx Logs Bloom index (bytes as bytes32 array) to query 61 | * @returns UnbuiltHeaderSubquery struct 62 | */ 63 | const logsBloom = (logsBloomIdx: number): UnbuiltHeaderSubquery => { 64 | if (logsBloomIdx < 0 || logsBloomIdx >= 8) { 65 | throw new Error("logsBloomIdx range is [0,8)"); 66 | } 67 | return { 68 | blockNumber: blockNumberNum, 69 | fieldIdx: getFieldIdxHeaderLogsBloomIdx(logsBloomIdx), 70 | } 71 | } 72 | 73 | return Object.freeze({ 74 | field, 75 | logsBloom, 76 | }); 77 | } 78 | 79 | /** 80 | * Builder for an Account data subquery 81 | * @param blockNumber Block number to query 82 | */ 83 | export const buildAccountSubquery = (blockNumber: number | string | BigInt) => { 84 | validateSize(blockNumber, "uint32"); 85 | 86 | /** 87 | * Continues building an Account subquery. Specifies the address to query. 88 | */ 89 | const blockNumberNum = Number(blockNumber.toString()); 90 | const address = (address: string) => { 91 | validateAddress(address); 92 | 93 | /** 94 | * End of the builder chain for an Account subquery. Specifies the AccountField to 95 | * query. 96 | * @param field AccountField to query 97 | * @returns UnbuiltAccountSubquery struct 98 | */ 99 | const field = (field: AccountField): UnbuiltAccountSubquery => { 100 | return { 101 | blockNumber: blockNumberNum, 102 | addr: address, 103 | fieldIdx: field, 104 | } 105 | } 106 | 107 | return Object.freeze({ 108 | field, 109 | }); 110 | } 111 | 112 | return Object.freeze({ 113 | address, 114 | }); 115 | } 116 | 117 | /** 118 | * Builder for a Storage data subquery 119 | * @param blockNumber Block number to query 120 | */ 121 | export const buildStorageSubquery = (blockNumber: number | string | BigInt) => { 122 | validateSize(blockNumber, "uint32"); 123 | const blockNumberNum = Number(blockNumber.toString()); 124 | 125 | /** 126 | * Continues building a Storage subquery. Specifies the address to query. 127 | * @param address Address to query 128 | */ 129 | const address = (address: string) => { 130 | validateAddress(address); 131 | 132 | /** 133 | * End of the builder chain for a Storage subquery. Specifies the storage slot to 134 | * query. 135 | * @param slot Storage slot to query 136 | * @returns UnbuiltStorageSubquery struct 137 | */ 138 | const slot = (slot: number | string | BigInt): UnbuiltStorageSubquery => { 139 | validateSize(slot, "uint256"); 140 | const slotStr = bytes32(slot.toString()); 141 | return { 142 | blockNumber: blockNumberNum, 143 | addr: address, 144 | slot: slotStr, 145 | } 146 | } 147 | 148 | return Object.freeze({ 149 | slot, 150 | }); 151 | } 152 | 153 | return Object.freeze({ 154 | address, 155 | }); 156 | } 157 | 158 | /** 159 | * Builder for a Transaction data subquery 160 | * @param txHash Transaction hash to query 161 | */ 162 | export const buildTxSubquery = ( 163 | txHash: string, 164 | ) => { 165 | validateBytes32(txHash); 166 | 167 | /** 168 | * End of builder chain for a Transaction subquery. 169 | * @param field The TxField to query 170 | * @returns UnbuiltTxSubquery struct 171 | */ 172 | const field = (field: TxField): UnbuiltTxSubquery => { 173 | return { 174 | txHash, 175 | fieldOrCalldataIdx: field, 176 | } 177 | } 178 | 179 | /** 180 | * End of the builder chain for a Transaction subquery. Specifies the calldata data index 181 | * (as an array of bytes32 after the 4-byte function selector) to query. 182 | * @param dataIdx Calldata index (bytes as bytes32 array) to query 183 | * @returns UnbuiltTxSubquery struct 184 | */ 185 | const calldata = (dataIdx: number | string | BigInt): UnbuiltTxSubquery => { 186 | validateSize(dataIdx, "uint32"); 187 | const dataIdxNum = Number(dataIdx.toString()); 188 | return { 189 | txHash, 190 | fieldOrCalldataIdx: getFieldIdxTxCalldataIdx(dataIdxNum), 191 | } 192 | } 193 | 194 | /** 195 | * End of the builder chain for a Transaction subquery. Specifies the contract data index 196 | * (as an array of bytes32) to query. 197 | * @param dataIdx Contract data index (bytes as bytes32 array) to query 198 | * @returns UnbuiltTxSubquery struct 199 | */ 200 | const contractData = (dataIdx: number | string | BigInt): UnbuiltTxSubquery => { 201 | validateSize(dataIdx, "uint32"); 202 | const dataIdxNum = Number(dataIdx.toString()); 203 | return { 204 | txHash, 205 | fieldOrCalldataIdx: getFieldIdxTxContractDataIdx(dataIdxNum), 206 | } 207 | } 208 | 209 | /** 210 | * End of the builder chain for a Transaction subquery. Queries the transaction type. 211 | * @returns UnbuiltTxSubquery struct 212 | */ 213 | const txType = (): UnbuiltTxSubquery => { 214 | return { 215 | txHash, 216 | fieldOrCalldataIdx: getFieldIdxTxType(), 217 | } 218 | } 219 | 220 | /** 221 | * End of the builder chain for a Transaction subquery. Queries the block number. 222 | * @returns UnbuiltTxSubquery struct 223 | */ 224 | const blockNumber = (): UnbuiltTxSubquery => { 225 | return { 226 | txHash, 227 | fieldOrCalldataIdx: getFieldIdxTxBlockNumber(), 228 | } 229 | } 230 | 231 | /** 232 | * End of the builder chain for a Transaction subquery. Queries the transaction index. 233 | * @returns UnbuiltTxSubquery struct 234 | */ 235 | const txIndex = (): UnbuiltTxSubquery => { 236 | return { 237 | txHash, 238 | fieldOrCalldataIdx: getFieldIdxTxIndex(), 239 | } 240 | } 241 | 242 | /** 243 | * End of the builder chain for a Transaction subquery. Queries the function selector. 244 | * @returns UnbuiltTxSubquery struct 245 | */ 246 | const functionSelector = (): UnbuiltTxSubquery => { 247 | return { 248 | txHash, 249 | fieldOrCalldataIdx: getFieldIdxTxFunctionSelector(), 250 | } 251 | } 252 | 253 | /** 254 | * End of the builder chain for a Transaction subquery. Queries the keccak256 hash of 255 | * the transaction's calldata. 256 | * @returns UnbuiltTxSubquery struct 257 | */ 258 | const calldataHash = (): UnbuiltTxSubquery => { 259 | return { 260 | txHash, 261 | fieldOrCalldataIdx: getFieldIdxTxCalldataHash(), 262 | } 263 | } 264 | 265 | return Object.freeze({ 266 | field, 267 | calldata, 268 | contractData, 269 | txType, 270 | blockNumber, 271 | txIndex, 272 | functionSelector, 273 | calldataHash, 274 | }); 275 | } 276 | 277 | /** 278 | * Builder for a Receipt data subquery 279 | * @param txHash Transaction hash to query 280 | */ 281 | export const buildReceiptSubquery = ( 282 | txHash: string, 283 | ) => { 284 | validateBytes32(txHash); 285 | 286 | /** 287 | * End of the builder chain for a Receipt subquery. Specifies the ReceiptField to query. 288 | * @param field 289 | * @returns UnbuiltReceiptSubquery struct 290 | */ 291 | const field = (field: ReceiptField): UnbuiltReceiptSubquery => { 292 | return { 293 | txHash, 294 | fieldOrLogIdx: field, 295 | topicOrDataOrAddressIdx: 0, 296 | eventSchema: ethers.ZeroHash, 297 | } 298 | } 299 | 300 | /** 301 | * End of the builder chain for a Receipt subquery. Specifies the logs bloom index to query. 302 | * @param logsBloomIdx Logs Bloom index (bytes as bytes32 array) to query 303 | * @returns UnbuiltReceiptSubquery struct 304 | */ 305 | const logsBloom = (logsBloomIdx: number): UnbuiltReceiptSubquery => { 306 | if (logsBloomIdx < 0 || logsBloomIdx >= 8) { 307 | throw new Error("logsBloomIdx range is [0,8)"); 308 | } 309 | return { 310 | txHash, 311 | fieldOrLogIdx: getFieldIdxReceiptLogsBloomIdx(logsBloomIdx), 312 | topicOrDataOrAddressIdx: 0, 313 | eventSchema: ethers.ZeroHash, 314 | } 315 | } 316 | 317 | /** 318 | * Continues building a Receipt subquery for a log (event) index. 319 | * @param logIdx Index of the log event to query 320 | */ 321 | const log = (logIdx: number) => { 322 | validateSize(logIdx, "uint32"); 323 | logIdx = getFieldIdxReceiptLogIdx(logIdx); 324 | 325 | /** 326 | * Continues building a Receipt subquery. Specifies the topic index of the 327 | * log to query. 328 | * @param topicIdx Index of the topic to query 329 | */ 330 | const topic = (topicIdx: number) => { 331 | validateSize(topicIdx, "uint32"); 332 | 333 | /** 334 | * End of the builder chain for a Receipt subquery. Specifies the event schema of the log. 335 | * @param eventSchema Bytes32 event schema 336 | * @returns UnbuiltReceiptSubquery struct 337 | */ 338 | const eventSchema = (eventSchema: string): UnbuiltReceiptSubquery => { 339 | eventSchema = eventSchema.startsWith("0x") ? eventSchema : getEventSchema(eventSchema); 340 | validateBytes32(eventSchema); 341 | return { 342 | txHash, 343 | fieldOrLogIdx: logIdx, 344 | topicOrDataOrAddressIdx: getFieldIdxReceiptTopicIdx(topicIdx), 345 | eventSchema, 346 | } 347 | } 348 | 349 | return Object.freeze({ 350 | eventSchema, 351 | }); 352 | } 353 | 354 | /** 355 | * Continues building a Receipt subquery. Specifies the data index of the 356 | * log to query. 357 | * @param dataIdx Index of the data to query (bytes as bytes32 array) 358 | */ 359 | const data = (dataIdx: number) => { 360 | validateSize(dataIdx, "uint32"); 361 | 362 | /** 363 | * End of the builder chain for a Receipt subquery. Specifies the event schema of the log. 364 | * @param eventSchema Bytes32 event schema 365 | * @returns UnbuiltReceiptSubquery struct 366 | */ 367 | const eventSchema = (eventSchema: string): UnbuiltReceiptSubquery => { 368 | eventSchema = eventSchema.startsWith("0x") ? eventSchema : getEventSchema(eventSchema); 369 | validateBytes32(eventSchema); 370 | 371 | return { 372 | txHash, 373 | fieldOrLogIdx: logIdx, 374 | topicOrDataOrAddressIdx: getFieldIdxReceiptDataIdx(dataIdx), 375 | eventSchema, 376 | } 377 | } 378 | 379 | return Object.freeze({ 380 | eventSchema, 381 | }); 382 | } 383 | 384 | /** 385 | * End of the builder chain for a Receipt subquery. Specifies querying the address 386 | * of the log event. 387 | * @returns UnbuiltReceiptSubquery struct 388 | */ 389 | const address = (): UnbuiltReceiptSubquery => { 390 | return { 391 | txHash, 392 | fieldOrLogIdx: logIdx, 393 | topicOrDataOrAddressIdx: getFieldIdxReceiptLogAddress(), 394 | eventSchema: ethers.ZeroHash, 395 | } 396 | } 397 | 398 | return Object.freeze({ 399 | topic, 400 | data, 401 | address, 402 | }); 403 | } 404 | 405 | /** 406 | * End of the builder chain for a Receipt subquery. Queries the transaction type. 407 | * @returns UnbuiltReceiptSubquery struct 408 | */ 409 | const txType = (): UnbuiltReceiptSubquery => { 410 | return { 411 | txHash, 412 | fieldOrLogIdx: getFieldIdxReceiptTxType(), 413 | topicOrDataOrAddressIdx: 0, 414 | eventSchema: ethers.ZeroHash, 415 | } 416 | } 417 | 418 | /** 419 | * End of the builder chain for a Receipt subquery. Queries the block number. 420 | * @returns UnbuiltReceiptSubquery struct 421 | */ 422 | const blockNumber = (): UnbuiltReceiptSubquery => { 423 | return { 424 | txHash, 425 | fieldOrLogIdx: getFieldIdxReceiptBlockNumber(), 426 | topicOrDataOrAddressIdx: 0, 427 | eventSchema: ethers.ZeroHash, 428 | } 429 | } 430 | 431 | /** 432 | * End of the builder chain for a Receipt subquery. Queries the transaction index. 433 | * @returns UnbuiltReceiptSubquery struct 434 | */ 435 | const txIndex = (): UnbuiltReceiptSubquery => { 436 | return { 437 | txHash, 438 | fieldOrLogIdx: getFieldIdxReceiptTxIndex(), 439 | topicOrDataOrAddressIdx: 0, 440 | eventSchema: ethers.ZeroHash, 441 | } 442 | } 443 | 444 | return Object.freeze({ 445 | field, 446 | logsBloom, 447 | log, 448 | txType, 449 | blockNumber, 450 | txIndex, 451 | }); 452 | } 453 | 454 | /** 455 | * Builder for a Solidity Nested Mapping data subquery 456 | * @param blockNumber Block number to query 457 | */ 458 | export const buildSolidityNestedMappingSubquery = (blockNumber: number | string | BigInt) => { 459 | validateSize(blockNumber, "uint32"); 460 | const blockNumberNum = Number(blockNumber.toString()); 461 | 462 | /** 463 | * Continues building a Solidity Nested Mapping subquery. Specifies the contract address 464 | * to query. 465 | * @param address Contract address to query 466 | */ 467 | const address = (address: string) => { 468 | validateAddress(address); 469 | 470 | /** 471 | * Continues building a Solidity Nested Mapping subquery. Specifies the slot of the 472 | * mapping in the contract. 473 | * @param mappingSlot Slot of the mapping in the contract to query. 474 | */ 475 | const mappingSlot = (mappingSlot: number | string | BigInt) => { 476 | validateSize(mappingSlot, "uint256"); 477 | const mappingSlotStr = bytes32(mappingSlot.toString()); 478 | 479 | /** 480 | * End of the builder chain for a Solidity Nested Mapping subquery. Specifies an array 481 | * of keys for the nested mapping. Max nested mappinng depth supported is 4. 482 | * @param keys An array of keys for the nested mapping (max depth 4). 483 | * @returns UnbuiltSolidityNestedMappingSubquery struct 484 | */ 485 | const keys = (keys: (number | string | BigInt)[]): UnbuiltSolidityNestedMappingSubquery => { 486 | if (keys.length > 4) { 487 | throw new Error("Max mapping depth supported is 4"); 488 | } 489 | const keysStr = keys.map(k => bytes32(k.toString())); 490 | 491 | return { 492 | blockNumber: blockNumberNum, 493 | addr: address, 494 | mappingSlot: mappingSlotStr, 495 | mappingDepth: keys.length, 496 | keys: keysStr, 497 | } 498 | } 499 | 500 | return Object.freeze({ 501 | keys, 502 | }); 503 | } 504 | 505 | return Object.freeze({ 506 | mappingSlot, 507 | }); 508 | } 509 | 510 | return Object.freeze({ 511 | address, 512 | }); 513 | } 514 | -------------------------------------------------------------------------------- /test/unit/v2/buildComputeQueryWithDataQuery.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AxiomSdkCore, 3 | AxiomSdkCoreConfig, 4 | AxiomV2Callback, 5 | AxiomV2ComputeQuery, 6 | AxiomV2DataQuery, 7 | QueryV2, 8 | bytes32, 9 | } from "../../../src"; 10 | 11 | // Test coverage areas: 12 | // - DataQuery 13 | // - Setting a built DataQuery 14 | // - ComputeQuery 15 | // - Callback 16 | 17 | describe("Build ComputeQuery with DataQuery", () => { 18 | const config: AxiomSdkCoreConfig = { 19 | providerUri: process.env.PROVIDER_URI as string, 20 | privateKey: process.env.PRIVATE_KEY as string, 21 | chainId: 1, 22 | version: "v2", 23 | }; 24 | const axiom = new AxiomSdkCore(config); 25 | 26 | const callback: AxiomV2Callback = { 27 | target: "0x41a7a901ef58d383801272d2408276d96973550d", 28 | extraData: bytes32("0xbbd0d3671093a36d6e3b608a7e3b1fdc96da1116"), 29 | }; 30 | 31 | test("simple computeQuery with dataQuery", async () => { 32 | const computeQuery: AxiomV2ComputeQuery = { 33 | k: 13, 34 | resultLen: 1, 35 | vkey: [ 36 | "0x83b88c6080be442679432e6c5634a3e3a7a26051a3b2581fba85dba0973fca20", 37 | "0xc04b25057d0bddf35d4542077516abb76445b8e745a457e3ccc1bf9aac2ba406", 38 | "0xa471542dc1c798279c6e094f7fae5174d83d5bd4f419d39f38a18a6aadadef23", 39 | "0xa17889e08418a09cecdac9afac9ddb4d839a56cc50205cd8df90ab459f53e900", 40 | "0x0000000000000000000000000000000000000000000000000000000000000080", 41 | "0x0000000000000000000000000000000000000000000000000000000000000080", 42 | "0x0000000000000000000000000000000000000000000000000000000000000080", 43 | "0xdaa121f99b66245770900bec7f7df67ba081c1ea1ec4e85de531e5efcb05dc2b", 44 | "0x72e95e6a67298de4d3da26492ee511e5b88295db678f739edb226e7947d38d0b", 45 | "0x22e4c62aacfc240ed0553bfad00122ba8c7627c870c739f3f818584e066a8b1f", 46 | "0x841485e0a9f109688bdc4f5ff851d9e2e44833ae573456742c1237322e938542", 47 | "0x79a62f1cc2f1440cc9fdcd534b612a49da4b6139bbed8cf53a26f4568ac3f567", 48 | "0x1c5846bb7c78a94b984ed2e0296334dd93085bdb3cbe3e69f89772ba64fa102c", 49 | "0xb9681242289c63756173eed28ce8ff44a71fe1fcf683bd07fcd70fdaede5b769", 50 | ], 51 | computeProof: 52 | "0x0000000000000000000000000000000000000000000000008713ff58e11eda2c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b742e3f91720ab2f43ece6b297314a246a28e8a37718433aee65e2452a1ebc57f4ca1a1e173855eaa5a9fbb74f96343f20e26d3d3ead44407e2e762f51ec4d0ed9b0aef7e5779df902223b4830ed0d12a02b950758107e35e5518d878cbff12da8658a104bf75f18aa7e3addaa8f4eaed64a353795f6e79b5faa6a633c3af06c2fb71ba568306ab02ca08f5a3a781b41f88696d30919d6bb1f849332d89f180065d76a0f0cbd417968fec77aee71be48054fcb4e6558be00fdce9a27a94aad593889074e456d9c1604b89eace24329a1e70091eaa7de84f0b5003719d5f4a7426599d89656cc63b4fe5889cbef9822b8bc72b13f5c17ce053168c479122de0024d54935ef42eacea4bcd71212dd3df7915258bb6b7e10b4fcba0cda608359a60e25ce4db9a4781e37fd89aa45b72b837bfedf2270abe6a71e39eb8a35bdbad1e967b1bc90fa8acee63c07191105168642f1d8b155342f1e0496ce9da38eade6bf41ba260e495de52802e49fa3cdc8df28a35b17c9d70420952e37034abfac36d7ab7dd54e0eccab23dcc53c12d0477046f6232c6e2b969eb38d432277ec24270e971c3d634f961848bbc9ae1a2e3c240056d3cd6f05cce571a417eeaf01c446fd30244b86c099a4c33d65f7b584152a3af87506b76c7ca75d4c1026becbf293003fbf082a70a5f59c8e04d4ff7ff6765c15881ece1da60dfd929e32c755239230bf43fc4550991af513d27dd6d6eaaf6722815574f6dfda59d2ccacf21f366037c7d3e718e28bc938f2f0bcc531106a6c080b1668af4cd43a1e78b6be2826b084788029be51d8e6a2c28aa3ff6a40a8cfad83258c9de474b927e9ab690760d159d29f54bcd509d436a9b5f9b823210ebc7270ed35a847e6a69a2c0144cb34d22cb4188b51dd1f066e2b81a8ca6b0eb71a63d70dc58cd6d6052c399afc220bd211595f31aa97ff14841347122e0339220f5d54e53fbf1637c9e06623279d27f11f855dc69c96c75d5c6eae7405d2818a9c3d23d278a98370d051cc807998374170c6b334eec536d0908e778e05f09dad697e6600b65664d452d95eccb96cfc1056dba89a96e878acf151abe100ba0bb9b5d312f78f71593b3362439beca96fb04d9e81ddf083b8d8d27761815149917c987b2f20d33fac83382bc3bbb06429c2c0fecd6ce7dc5dec284247b52df9c40eeda443cd2251f03dd7c273861a3416a102b62251dc9e7c2e5a31af17cf61e42ca536353041ff1e5c8250de0e4e3abbf268b59c095f2ac631d4d8096372bca388ab6d8a0fb65dede8377c79e8b7f7d900740e401f41adcbc17099e7aa8d460a5b83c4fad9778e47962049c22fdb3aaf51cff60d8b2f8f334918a30decf3e037a7a8c0e82f59164bdb64e39adfa8a63db052c82399a620c80d95bafcb87979a4de501948c0f1d3484f10c1a5ec4062e7f034043b6bc02b2e3b8827173e885cb1acbd835742c7cd7efcb96e4eddbf7831d1c1a7db7eb87c2949a9fdeccca48a072dcad163472f202312772eee02a1c3fd012d6b993f8a9f3f0948057f2cde182945fbf1c953fb6413a099180312c78da471289c0fa64c7c0228d79d7e432cfd00ef50973db40566a71221c1a0b89e6084526000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de97f1ddfb2158546e5e03072fecbbe020f0dbd3de37507743e2fa0ebad01f1c90dabcf0e87726d1c855b8740ba4bd58e976a816d798c06a76eca992b5694f2e340984db397c67b1cff836cfe8511334d80b313b5b167849ac88bf68c7f9f11126a1dd52e4a44dbc34111734e572e47a6fcf395f1af6bc1ecd4513588038f51a5bdfc30a8b7eeb2bad8c6d353f405b65ca3644935ee6ce7b451420f6d7821c15c026098859e9e7ef0802201bb1d18cec3628d18858e307d8fc450cc487c3ed0e2ea995450e7d315de27bbe2dc3c984de54c4124fc5bfa316bab6782119610327f30bea752bc651ef7566c0a0b63019f80bba217203c82c9c81d9acb0ea60c01efddb1b4256395523642a5bc7d1b26a39326b7ae354a60bea41b514e8f1282312d26a6f35c969fa352d94a336118edd4cf01d9b84abdc817814d57cea99ac322ba025020705565ef4e356580bcaef735b1012bca412100c3a635d78a92683a12bfe47f0e03a21b89394bfb6a871da24eac0d61c38c7f3a0bb9dd80e2785065b157561e9f1c36d3f8002913bc37b2cda34d988af0154ce83536ba9b65093e61522927bc3c36119ca9d7cea8b57fda0957121358965c20f317ce227d816a411cd1daf43d3e16180dab1536b66944e96640f45b5583d4c351f72c126a02b995b102f1e4bc4a2d03b064cc6fdf6d8b0a919387002f22acfd6b6abb8676de7f4c8a607848a771aa2bea3f4d01d740a111e86282be89c83bf3ab7723fad017a453e6b07ac92550f2773027a09daf2c71030dce29042b7fa9d0e79a634b2990e49121510653a0d3a6939258ea5727164cc3ac9c8056391c0f3dae76a8eb6af86ee0c022a6abaf06d576ec79280cf5fdab040eb85b0bf89b0201b7cc39661671fe6f17b23b07a252e1da57576842a4c751ba7ed0a1e353e42acc64d97be8207ce9257d00ec9735011e00a5edba5dbbd8d7124911639526b0563911179c7bb6765745c6b0a10f7e06499deb91e71b5d52213b973cd761105e5950009e8a96b75c721f89c2f0e7e87d280431249edbaabe885198349651032396cb30bb20942d700abb8da2c0df5c2d5361dc487452cd3d7d5ea5da9a6b2fcceb8b49a4b84be63caf4fdce0e4e13218cbcdca3bc55a05981a169890efc7c8906500140c9c51aa75490d7c41e", 53 | }; 54 | const dataQuery: AxiomV2DataQuery = { 55 | sourceChainId: "5", 56 | subqueries: [ 57 | { 58 | subqueryData: { blockNumber: 9900000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 59 | type: 2, 60 | }, 61 | { 62 | subqueryData: { blockNumber: 9899000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 63 | type: 2, 64 | }, 65 | { 66 | subqueryData: { blockNumber: 9898000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 67 | type: 2, 68 | }, 69 | { 70 | subqueryData: { blockNumber: 9897000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 71 | type: 2, 72 | }, 73 | { 74 | subqueryData: { blockNumber: 9896000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 75 | type: 2, 76 | }, 77 | { 78 | subqueryData: { blockNumber: 9895000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 79 | type: 2, 80 | }, 81 | { 82 | subqueryData: { blockNumber: 9894000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 83 | type: 2, 84 | }, 85 | { 86 | subqueryData: { blockNumber: 9893000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 87 | type: 2, 88 | }, 89 | { 90 | subqueryData: { blockNumber: 9892000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 91 | type: 2, 92 | }, 93 | { 94 | subqueryData: { blockNumber: 9891000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 95 | type: 2, 96 | }, 97 | { 98 | subqueryData: { blockNumber: 9890000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 99 | type: 2, 100 | }, 101 | { 102 | subqueryData: { blockNumber: 9889000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 103 | type: 2, 104 | }, 105 | { 106 | subqueryData: { blockNumber: 9888000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 107 | type: 2, 108 | }, 109 | { 110 | subqueryData: { blockNumber: 9887000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 111 | type: 2, 112 | }, 113 | { 114 | subqueryData: { blockNumber: 9886000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 115 | type: 2, 116 | }, 117 | { 118 | subqueryData: { blockNumber: 9885000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 119 | type: 2, 120 | }, 121 | { 122 | subqueryData: { blockNumber: 9884000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 123 | type: 2, 124 | }, 125 | { 126 | subqueryData: { blockNumber: 9883000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 127 | type: 2, 128 | }, 129 | { 130 | subqueryData: { blockNumber: 9882000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 131 | type: 2, 132 | }, 133 | { 134 | subqueryData: { blockNumber: 9881000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 135 | type: 2, 136 | }, 137 | { 138 | subqueryData: { blockNumber: 9880000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 139 | type: 2, 140 | }, 141 | { 142 | subqueryData: { blockNumber: 9879000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 143 | type: 2, 144 | }, 145 | { 146 | subqueryData: { blockNumber: 9878000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 147 | type: 2, 148 | }, 149 | { 150 | subqueryData: { blockNumber: 9877000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 151 | type: 2, 152 | }, 153 | { 154 | subqueryData: { blockNumber: 9876000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 155 | type: 2, 156 | }, 157 | ], 158 | }; 159 | const query = (axiom.query as QueryV2).new(); 160 | query.setBuiltDataQuery(dataQuery); 161 | query.setComputeQuery(computeQuery); 162 | query.setCallback(callback); 163 | await query.build(); 164 | }); 165 | 166 | test("build a query with no DataQuery or ComputeQuery", async () => { 167 | const testFn = async () => { 168 | const query = (axiom.query as QueryV2).new(); 169 | query.setCallback(callback); 170 | await query.build(); 171 | }; 172 | expect(testFn).rejects.toThrow(); 173 | }); 174 | }); 175 | -------------------------------------------------------------------------------- /test/unit/v2/queryParams.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AxiomSdkCore, 3 | AxiomSdkCoreConfig, 4 | AxiomV2Callback, 5 | AxiomV2ComputeQuery, 6 | AxiomV2DataQuery, 7 | HeaderField, 8 | QueryV2, 9 | bytes32, 10 | } from "../../../src"; 11 | 12 | // Test coverage areas: 13 | // - QueryID 14 | // - QuerySchema 15 | 16 | describe("Query ID and Schema calculation", () => { 17 | const config: AxiomSdkCoreConfig = { 18 | providerUri: process.env.PROVIDER_URI as string, 19 | privateKey: process.env.PRIVATE_KEY as string, 20 | chainId: 1, 21 | version: "v2", 22 | }; 23 | const axiom = new AxiomSdkCore(config); 24 | 25 | const callback: AxiomV2Callback = { 26 | target: "0x41a7a901ef58d383801272d2408276d96973550d", 27 | extraData: bytes32("0xbbd0d3671093a36d6e3b608a7e3b1fdc96da1116"), 28 | }; 29 | 30 | test("get query ID and querySchema for simple computeQuery with dataQuery", async () => { 31 | const computeQuery: AxiomV2ComputeQuery = { 32 | k: 13, 33 | resultLen: 1, 34 | vkey: [ 35 | "0x83b88c6080be442679432e6c5634a3e3a7a26051a3b2581fba85dba0973fca20", 36 | "0xc04b25057d0bddf35d4542077516abb76445b8e745a457e3ccc1bf9aac2ba406", 37 | "0xa471542dc1c798279c6e094f7fae5174d83d5bd4f419d39f38a18a6aadadef23", 38 | "0xa17889e08418a09cecdac9afac9ddb4d839a56cc50205cd8df90ab459f53e900", 39 | "0x0000000000000000000000000000000000000000000000000000000000000080", 40 | "0x0000000000000000000000000000000000000000000000000000000000000080", 41 | "0x0000000000000000000000000000000000000000000000000000000000000080", 42 | "0xdaa121f99b66245770900bec7f7df67ba081c1ea1ec4e85de531e5efcb05dc2b", 43 | "0x72e95e6a67298de4d3da26492ee511e5b88295db678f739edb226e7947d38d0b", 44 | "0x22e4c62aacfc240ed0553bfad00122ba8c7627c870c739f3f818584e066a8b1f", 45 | "0x841485e0a9f109688bdc4f5ff851d9e2e44833ae573456742c1237322e938542", 46 | "0x79a62f1cc2f1440cc9fdcd534b612a49da4b6139bbed8cf53a26f4568ac3f567", 47 | "0x1c5846bb7c78a94b984ed2e0296334dd93085bdb3cbe3e69f89772ba64fa102c", 48 | "0xb9681242289c63756173eed28ce8ff44a71fe1fcf683bd07fcd70fdaede5b769", 49 | ], 50 | computeProof: 51 | "0x0000000000000000000000000000000000000000000000008713ff58e11eda2c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b742e3f91720ab2f43ece6b297314a246a28e8a37718433aee65e2452a1ebc57f4ca1a1e173855eaa5a9fbb74f96343f20e26d3d3ead44407e2e762f51ec4d0ed9b0aef7e5779df902223b4830ed0d12a02b950758107e35e5518d878cbff12da8658a104bf75f18aa7e3addaa8f4eaed64a353795f6e79b5faa6a633c3af06c2fb71ba568306ab02ca08f5a3a781b41f88696d30919d6bb1f849332d89f180065d76a0f0cbd417968fec77aee71be48054fcb4e6558be00fdce9a27a94aad593889074e456d9c1604b89eace24329a1e70091eaa7de84f0b5003719d5f4a7426599d89656cc63b4fe5889cbef9822b8bc72b13f5c17ce053168c479122de0024d54935ef42eacea4bcd71212dd3df7915258bb6b7e10b4fcba0cda608359a60e25ce4db9a4781e37fd89aa45b72b837bfedf2270abe6a71e39eb8a35bdbad1e967b1bc90fa8acee63c07191105168642f1d8b155342f1e0496ce9da38eade6bf41ba260e495de52802e49fa3cdc8df28a35b17c9d70420952e37034abfac36d7ab7dd54e0eccab23dcc53c12d0477046f6232c6e2b969eb38d432277ec24270e971c3d634f961848bbc9ae1a2e3c240056d3cd6f05cce571a417eeaf01c446fd30244b86c099a4c33d65f7b584152a3af87506b76c7ca75d4c1026becbf293003fbf082a70a5f59c8e04d4ff7ff6765c15881ece1da60dfd929e32c755239230bf43fc4550991af513d27dd6d6eaaf6722815574f6dfda59d2ccacf21f366037c7d3e718e28bc938f2f0bcc531106a6c080b1668af4cd43a1e78b6be2826b084788029be51d8e6a2c28aa3ff6a40a8cfad83258c9de474b927e9ab690760d159d29f54bcd509d436a9b5f9b823210ebc7270ed35a847e6a69a2c0144cb34d22cb4188b51dd1f066e2b81a8ca6b0eb71a63d70dc58cd6d6052c399afc220bd211595f31aa97ff14841347122e0339220f5d54e53fbf1637c9e06623279d27f11f855dc69c96c75d5c6eae7405d2818a9c3d23d278a98370d051cc807998374170c6b334eec536d0908e778e05f09dad697e6600b65664d452d95eccb96cfc1056dba89a96e878acf151abe100ba0bb9b5d312f78f71593b3362439beca96fb04d9e81ddf083b8d8d27761815149917c987b2f20d33fac83382bc3bbb06429c2c0fecd6ce7dc5dec284247b52df9c40eeda443cd2251f03dd7c273861a3416a102b62251dc9e7c2e5a31af17cf61e42ca536353041ff1e5c8250de0e4e3abbf268b59c095f2ac631d4d8096372bca388ab6d8a0fb65dede8377c79e8b7f7d900740e401f41adcbc17099e7aa8d460a5b83c4fad9778e47962049c22fdb3aaf51cff60d8b2f8f334918a30decf3e037a7a8c0e82f59164bdb64e39adfa8a63db052c82399a620c80d95bafcb87979a4de501948c0f1d3484f10c1a5ec4062e7f034043b6bc02b2e3b8827173e885cb1acbd835742c7cd7efcb96e4eddbf7831d1c1a7db7eb87c2949a9fdeccca48a072dcad163472f202312772eee02a1c3fd012d6b993f8a9f3f0948057f2cde182945fbf1c953fb6413a099180312c78da471289c0fa64c7c0228d79d7e432cfd00ef50973db40566a71221c1a0b89e6084526000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de97f1ddfb2158546e5e03072fecbbe020f0dbd3de37507743e2fa0ebad01f1c90dabcf0e87726d1c855b8740ba4bd58e976a816d798c06a76eca992b5694f2e340984db397c67b1cff836cfe8511334d80b313b5b167849ac88bf68c7f9f11126a1dd52e4a44dbc34111734e572e47a6fcf395f1af6bc1ecd4513588038f51a5bdfc30a8b7eeb2bad8c6d353f405b65ca3644935ee6ce7b451420f6d7821c15c026098859e9e7ef0802201bb1d18cec3628d18858e307d8fc450cc487c3ed0e2ea995450e7d315de27bbe2dc3c984de54c4124fc5bfa316bab6782119610327f30bea752bc651ef7566c0a0b63019f80bba217203c82c9c81d9acb0ea60c01efddb1b4256395523642a5bc7d1b26a39326b7ae354a60bea41b514e8f1282312d26a6f35c969fa352d94a336118edd4cf01d9b84abdc817814d57cea99ac322ba025020705565ef4e356580bcaef735b1012bca412100c3a635d78a92683a12bfe47f0e03a21b89394bfb6a871da24eac0d61c38c7f3a0bb9dd80e2785065b157561e9f1c36d3f8002913bc37b2cda34d988af0154ce83536ba9b65093e61522927bc3c36119ca9d7cea8b57fda0957121358965c20f317ce227d816a411cd1daf43d3e16180dab1536b66944e96640f45b5583d4c351f72c126a02b995b102f1e4bc4a2d03b064cc6fdf6d8b0a919387002f22acfd6b6abb8676de7f4c8a607848a771aa2bea3f4d01d740a111e86282be89c83bf3ab7723fad017a453e6b07ac92550f2773027a09daf2c71030dce29042b7fa9d0e79a634b2990e49121510653a0d3a6939258ea5727164cc3ac9c8056391c0f3dae76a8eb6af86ee0c022a6abaf06d576ec79280cf5fdab040eb85b0bf89b0201b7cc39661671fe6f17b23b07a252e1da57576842a4c751ba7ed0a1e353e42acc64d97be8207ce9257d00ec9735011e00a5edba5dbbd8d7124911639526b0563911179c7bb6765745c6b0a10f7e06499deb91e71b5d52213b973cd761105e5950009e8a96b75c721f89c2f0e7e87d280431249edbaabe885198349651032396cb30bb20942d700abb8da2c0df5c2d5361dc487452cd3d7d5ea5da9a6b2fcceb8b49a4b84be63caf4fdce0e4e13218cbcdca3bc55a05981a169890efc7c8906500140c9c51aa75490d7c41e", 52 | }; 53 | const dataQuery: AxiomV2DataQuery = { 54 | sourceChainId: "5", 55 | subqueries: [ 56 | { 57 | subqueryData: { blockNumber: 9900000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 58 | type: 2, 59 | }, 60 | { 61 | subqueryData: { blockNumber: 9899000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 62 | type: 2, 63 | }, 64 | { 65 | subqueryData: { blockNumber: 9898000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 66 | type: 2, 67 | }, 68 | { 69 | subqueryData: { blockNumber: 9897000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 70 | type: 2, 71 | }, 72 | { 73 | subqueryData: { blockNumber: 9896000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 74 | type: 2, 75 | }, 76 | { 77 | subqueryData: { blockNumber: 9895000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 78 | type: 2, 79 | }, 80 | { 81 | subqueryData: { blockNumber: 9894000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 82 | type: 2, 83 | }, 84 | { 85 | subqueryData: { blockNumber: 9893000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 86 | type: 2, 87 | }, 88 | { 89 | subqueryData: { blockNumber: 9892000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 90 | type: 2, 91 | }, 92 | { 93 | subqueryData: { blockNumber: 9891000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 94 | type: 2, 95 | }, 96 | { 97 | subqueryData: { blockNumber: 9890000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 98 | type: 2, 99 | }, 100 | { 101 | subqueryData: { blockNumber: 9889000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 102 | type: 2, 103 | }, 104 | { 105 | subqueryData: { blockNumber: 9888000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 106 | type: 2, 107 | }, 108 | { 109 | subqueryData: { blockNumber: 9887000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 110 | type: 2, 111 | }, 112 | { 113 | subqueryData: { blockNumber: 9886000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 114 | type: 2, 115 | }, 116 | { 117 | subqueryData: { blockNumber: 9885000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 118 | type: 2, 119 | }, 120 | { 121 | subqueryData: { blockNumber: 9884000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 122 | type: 2, 123 | }, 124 | { 125 | subqueryData: { blockNumber: 9883000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 126 | type: 2, 127 | }, 128 | { 129 | subqueryData: { blockNumber: 9882000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 130 | type: 2, 131 | }, 132 | { 133 | subqueryData: { blockNumber: 9881000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 134 | type: 2, 135 | }, 136 | { 137 | subqueryData: { blockNumber: 9880000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 138 | type: 2, 139 | }, 140 | { 141 | subqueryData: { blockNumber: 9879000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 142 | type: 2, 143 | }, 144 | { 145 | subqueryData: { blockNumber: 9878000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 146 | type: 2, 147 | }, 148 | { 149 | subqueryData: { blockNumber: 9877000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 150 | type: 2, 151 | }, 152 | { 153 | subqueryData: { blockNumber: 9876000, addr: "0xb392448932f6ef430555631f765df0dfae34eff3", fieldIdx: 1 }, 154 | type: 2, 155 | }, 156 | ], 157 | }; 158 | const query = (axiom.query as QueryV2).new(); 159 | query.setBuiltDataQuery(dataQuery); 160 | query.setComputeQuery(computeQuery); 161 | query.setCallback(callback); 162 | const builtQuery = await query.build(); 163 | if (builtQuery === undefined) { 164 | throw new Error("builtQuery is undefined"); 165 | } 166 | builtQuery.userSalt = bytes32(1); // lock the salt value for consistent results 167 | 168 | const querySchema = builtQuery.querySchema; 169 | expect(querySchema).toEqual("0x412efc8f4184ff6cb59c65113d3e64ddfdc521b3dd083bd076aecec735fb6e98"); 170 | 171 | const queryId = await query.getQueryId(); 172 | expect(queryId).toEqual("26331238880531630939427649691123836670567064478799277856427570787260492145586"); 173 | }); 174 | 175 | test("queryId should change with different caller", async () => { 176 | const blockNumber = 18400000; 177 | const dataQueryReq = [ 178 | { 179 | blockNumber: blockNumber, 180 | fieldIdx: HeaderField.GasUsed, 181 | }, 182 | { 183 | blockNumber: blockNumber + 1000, 184 | fieldIdx: HeaderField.GasUsed, 185 | }, 186 | ]; 187 | const query = (axiom.query as QueryV2).new(); 188 | query.append(dataQueryReq); 189 | query.setCallback(callback); 190 | 191 | await query.build(); 192 | const builtQuery = query.getBuiltQuery(); 193 | if (builtQuery === undefined) { 194 | throw new Error("builtQuery is undefined"); 195 | } 196 | builtQuery.userSalt = bytes32(1); // lock the salt value for consistent results 197 | 198 | let queryId = await query.getQueryId(); 199 | expect(queryId).toEqual("34105472793833197573956594097263260213057846125309585781735597677991434057572"); 200 | 201 | queryId = await query.getQueryId("0x41a7a901ef58d383801272d2408276d96973550d"); 202 | expect(queryId).toEqual("7120525174168517755499793234294755392835908142367641295038978869119154112223"); 203 | }); 204 | }); 205 | -------------------------------------------------------------------------------- /test/unit/v2/buildAll.test.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { 3 | AccountField, 4 | AccountSubquery, 5 | AxiomSdkCore, 6 | AxiomSdkCoreConfig, 7 | AxiomV2ComputeQuery, 8 | AxiomV2QueryOptions, 9 | QueryV2, 10 | ReceiptField, 11 | UnbuiltAccountSubquery, 12 | } from "../../../src"; 13 | import { resizeArray } from "../../../src/shared/utils"; 14 | 15 | // Test coverage areas: 16 | // - DataQuery 17 | // - ComputeQuery 18 | // - Callback 19 | // - Options 20 | 21 | describe("Build Query w/ ComputeQuery, DataQuery, Callback, and Options set (core test)", () => { 22 | const BLOCK_NUMBER = 15537394; 23 | const WETH_ADDR = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; 24 | const WETH_WHALE = "0x2E15D7AA0650dE1009710FDd45C3468d75AE1392"; 25 | const WSOL_ADDR = "0xd31a59c85ae9d8edefec411d448f90841571b89c"; 26 | const UNI_V3_FACTORY_ADDR = "0x1F98431c8aD98523631AE4a59f267346ea31F984"; 27 | 28 | const provider = new ethers.JsonRpcProvider(process.env.PROVIDER_URI); 29 | 30 | const computeQuery: AxiomV2ComputeQuery = { 31 | k: 13, 32 | resultLen: 1, 33 | vkey: [ 34 | "0x4caffbbdc5d29b00b3e97df7fb169baadaff9443ea3ecfd62231541d2752e003", 35 | "0xc04b25057d0bddf35d4542077516abb76445b8e745a457e3ccc1bf9aac2ba406", 36 | "0x6f4cebf257ebe7a319dd5dedcd41085d35b11afe7254a55d5f33366084983a61", 37 | "0xce36e621969a1e33547280e28d8158537284eb5b5d284ede4dc81d1e69ef2e28", 38 | "0x0000000000000000000000000000000000000000000000000000000000000080", 39 | "0x0000000000000000000000000000000000000000000000000000000000000080", 40 | "0x0000000000000000000000000000000000000000000000000000000000000080", 41 | "0xceb30489e39186b75336db29c5e0ce27e0c7480fc5a8ee071fc8642283700e42", 42 | "0x4570a761b654d0422dba9042e1c2130284cca09e659d46b69739c99697ff4068", 43 | "0x22e4c62aacfc240ed0553bfad00122ba8c7627c870c739f3f818584e066a8b1f", 44 | "0x841485e0a9f109688bdc4f5ff851d9e2e44833ae573456742c1237322e938542", 45 | "0x79a62f1cc2f1440cc9fdcd534b612a49da4b6139bbed8cf53a26f4568ac3f567", 46 | "0x63bf6c5c2a2c6a568d6399d6658a320511dda5caf1dc5a84af03472a552ee510", 47 | "0x1f3d22024e669b91ee856e925fcbe0b6da642b57b348823bf7ac26266b3e695d", 48 | ], 49 | computeProof: 50 | "0x00000000000000000000000000000000000000000000000000000000000000960000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ca0d58f038fa1dea93aac4ae41d157f0894c4d857468a9da2d8999721f5691209442ccfd19f64af56d033662e58c382db23a6693a591a22e2706a9a7527c1f041e4ca2d69d9aec9cd4203ce4cf181dda917e93db04afd78953f872112797c710b38aa8b8c774c4f56bf471ac233b27f87791647e18d70be18f28570a4c948548a137f0d62b925e4f0f0c8b2c8de36c2c1c54b63061619cb391dc117d72dcea16ec2f0173413814f94fd7ab61bf134aab9cf7bf453d246d6a25746576377e8806eccf2692cab5ffd4d443e9ac325a479dcd51b72b9123f39a7ac77d94333469629987d1f0209effc6d3f71b9ed1622bc1e6fc68c575725eaf2bca1ae3674ee44ca652f57b0da21ecb9413d083cdeabe8e52d255177621112c3b3a22daf8eec7074c6e4ec115313e94a935e632ac5226a232abfb0910b3e5ff8a6ea58855028948d093f98691bac135ab827597dd3291bd7372298c4cc028af1b710972776f106637331c92b893f3db7a85c480f26a7083fe3052ec48c1360b42929ef950e643279fbb17dbef72b8e87321b8e8b911383a0913ecd1789960428aafd24d7939d45148e89f457178c188b065cf233f69a26d0195e8e9853e973fdfd3015612bae12b0f18ffa2e18550a7195beff3d684841ecf3cb869ba9668fda32514d83fcc801bbd72d06f730773bae66310f62c6eac2f58d6a88826adb520ef0c9b87ee7c371c40b6f73f27226ab90f0594860c30d892d4082925d83d8a7cfe885b743e49481a1b685dcefe3d8029e70d875b9b4902ac0a82895175881cd0a354090c0b1c9903465eb370ed07dbd3eba1367ec17095ea20278a6db3055f45b85be5862ceaa92b0cf0c3cb2f8f6df24a598f1b45c0f60a4308b44ce630c3e97c385aa664756500d6ea8cd7069d2b9fb8c0b389a9b325cca515a0e3a0498ca8ce67ef920358b12959761f8d863b39ee4e8036e816a110d1f4637f65c68d5ef3a9ba50f059aec10e39e9652b6594d85c86dd9b98889d7b854e5e3a4674cb54d66dff547409165f235637f037c8dc693649eebf8e6cede4474a92e3ac4091cc0eea855f0790ed8e06bffc7ddd79222f3423209826f6c99279f0f709a04a58de67d9ae12aeb1bc38261907ab51f8f4025cded6cfa1d54acccf85a39834721865723a9560dca880310a1444588fa6fa6cf1caa6d5282e0a401861cf4d7dbc60449086014e076f673c2f7eb23a6204f697d6209bca33b52453de1841c683390ac3f03eedcdfb1b92941f0c6c1e92c687955f343eec30f71cf78d8ff1ee4c38d44c19acd01e5fd910682391b066e657a08ef28362f40f918963b4228d657280688dafab61b4063c760d1ed8562c1b061850b49054f0816858a574bd79bb8d592423ea1d7f02b617b01e06327e6c6f120c69866bf81203ec2ebb274e118b44e87fe2e30dd77882de784b11243f41f5200ede478cb14c5c24c117fa987e149ccb67b7a7f758e26250158c20c515702aa9c28af2ea51cc9e135d6d635b3ddc259d5f5713a5615333f277a805b452afe6ec4205b927793ab473fc3fa40631d9d9cf365067a4c78906b7119b19e66f13936c69f4db1db9005b89187657772eae05d81530375d9def9fcff1450a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005ed4d210ce92739ea5af0066601fcc2a43a9ae8ccc8a49f1008940b5066c2326f419c6da9fbd9481ca099e405aa18403a4188d21832ee58a39975d6a9a49ea1bba8d1de4811547f6e3aa1d684c0c0cd57443729dbcf201ac20704a553d0b6b20a9cdb94058c8deb216792b9ef0ca51a26c54ca23e460b2b191ce6af8784bff2f90fc0a4ec281dd95beee516d3e15a04fb873038b949c44db2fb0ee943f6bfa1d4f2ff763d865adf55a29b37bee0f6ad8754fde6999125b6a779056d4bdf95b13ac41d86d841adec49bf27a7414b88074f27a975ee36d5f647f7dcbcc580df20e4d7e040bbb2c6c2a6dc5f18737259dee588657cbbe225ebd66fac66ca3386d16a9bef9c4664733a2619a50be2ac5a8f4ce7f272b4686526681415e79ff3baf0881d52948b3fc7d0b5d413d44a1cb985d4557ff651042fd2c8180fe9528b9cb1f400da2686d602ebbe2e24d6d6b0c9e12d6ba41c1db556ee1ecce44fe8513e0104353210ebba3f83cfd38b84509c47245cfa4544189949997fe36e96fcb68b10495d6632a553da9f1f1dec8251018793d44e30a30bf874fc56abeb9facdd04a25a1bd9fe412949086db288d3326b80b48e4d85ec44de11a8767032c1569c472141f44f80531c0becccbf5791f65ef4d21a6529a908b7e5386f5c9169d20082813684ee79e7ce7ddc0497301321f5fbda44ecaac3c157cf00c28d432411528f405c8ee72147fcd0ab094b58914163c875cca43bd1724ee1b448137e5afbe38bf24be5c46aa67200f1e19a9cd4c9abfa91af60f54751220c189206d64c314fe1622d827025d39024eb329090c2b496490306a52f721bb8e41f41266262b899c420ea10ef2211821bbcbcc3ae427053b97d3a6a3d3133893f373adab42c8d8e20a0beb5d12246bc590c4424fddcc8511c594fdcfa23fa55ed2a89af28947fc9e5f1f081992a5f470977f7c7e38ceaa69cc5d43ee604cd5bf6c4a5a861960f3c10e1c306c1e0999a0ac0b6e565e6bc205de0d807e7b3a686d03b559b040c0cd332518b1289a02a889400211e1e84f038faac5966e5d927f025c3ed0b0149644646f11d91d524604313e3382d86310325d000e462fb5705776ad3dd10ced1d3889f829370e0aa9669d80239514dad97bbfdd5bde7e5d74d341d45b9827cccf1ce57d0d", 51 | }; 52 | 53 | test("should initialize with private key; build QueryV2 with dataQuery, computeQuery, and callback", async () => { 54 | const config: AxiomSdkCoreConfig = { 55 | privateKey: process.env.PRIVATE_KEY as string, 56 | providerUri: process.env.PROVIDER_URI as string, 57 | version: "v2", 58 | }; 59 | const axiom = new AxiomSdkCore(config); 60 | 61 | const dataQueryReq = [ 62 | { 63 | blockNumber: BLOCK_NUMBER, 64 | fieldIdx: 0, 65 | }, 66 | { 67 | blockNumber: BLOCK_NUMBER + 1, 68 | fieldIdx: 1, 69 | }, 70 | { 71 | blockNumber: BLOCK_NUMBER, 72 | addr: WETH_WHALE, 73 | fieldIdx: AccountField.Nonce, 74 | }, 75 | { 76 | // blockNumber: 17975259, 77 | // txIdx: 34, 78 | txHash: "0x47082a4eaba054312c652a21c6d75a44095b8be43c60bdaeffad03d38a8b1602", 79 | fieldOrLogIdx: ReceiptField.CumulativeGas, 80 | topicOrDataOrAddressIdx: 10, 81 | eventSchema: ethers.ZeroHash, 82 | }, 83 | ]; 84 | const computeQueryReq: AxiomV2ComputeQuery = { 85 | k: 14, 86 | vkey: computeQuery.vkey, 87 | computeProof: computeQuery.computeProof, 88 | }; 89 | const callbackQuery = { 90 | target: WETH_ADDR, 91 | extraData: ethers.solidityPacked(["address"], [WETH_WHALE]), 92 | }; 93 | const options: AxiomV2QueryOptions = { 94 | maxFeePerGas: BigInt(100000000).toString(), 95 | }; 96 | const query = (axiom.query as QueryV2).new(dataQueryReq, computeQueryReq, callbackQuery, options); 97 | 98 | const unbiltDq = query.getDataQuery(); 99 | expect((unbiltDq?.[2] as UnbuiltAccountSubquery).addr).toEqual(WETH_WHALE); 100 | await query.build(); 101 | const builtQuery = query.getBuiltQuery(); 102 | if (builtQuery === undefined) { 103 | throw new Error("builtQuery is undefined"); 104 | } 105 | 106 | const builtDq = builtQuery.dataQueryStruct.subqueries; 107 | expect((builtDq?.[2].subqueryData as AccountSubquery).addr).toEqual(WETH_WHALE.toLowerCase()); 108 | expect(builtQuery.queryHash).toEqual("0xf83d8c1a06ca57e6ac62998276ce1f4e091a28f93e8edcefb34682c30c34450c"); 109 | expect(builtQuery.dataQueryHash).toEqual("0xa23b68c6445bf7850bfdb0921bef5c55d43bd6362133b1f3929dd7b8103502dd"); 110 | expect(builtQuery.dataQuery).toEqual( 111 | "0x00000000000000010004000100ed14f200000000000100ed14f300000001000200ed14f22e15d7aa0650de1009710fdd45c3468d75ae1392000000000005011247db0022000000020000000a0000000000000000000000000000000000000000000000000000000000000000", 112 | ); 113 | expect(builtQuery.computeQuery.k).toEqual(computeQueryReq.k); 114 | expect(builtQuery.computeQuery.resultLen).toEqual(4); 115 | expect(builtQuery.computeQuery.vkey.length).toEqual(computeQueryReq.vkey.length); 116 | expect(builtQuery.computeQuery.vkey).toEqual( 117 | resizeArray(computeQueryReq.vkey, computeQueryReq.vkey.length, ethers.ZeroHash), 118 | ); 119 | expect(builtQuery.computeQuery.computeProof).toEqual(computeQuery.computeProof); 120 | expect(builtQuery.callback).toEqual({ 121 | target: WETH_ADDR.toLowerCase(), 122 | extraData: "0x2e15d7aa0650de1009710fdd45c3468d75ae1392", 123 | }); 124 | }); 125 | 126 | test("should initialize without private key; build QueryV2 with dataQuery, computeQuery, and callback", async () => { 127 | const config: AxiomSdkCoreConfig = { 128 | providerUri: process.env.PROVIDER_URI as string, 129 | version: "v2", 130 | }; 131 | const axiom = new AxiomSdkCore(config); 132 | 133 | const dataQueryReq = [ 134 | { 135 | blockNumber: BLOCK_NUMBER, 136 | fieldIdx: 0, 137 | }, 138 | { 139 | blockNumber: BLOCK_NUMBER + 1, 140 | fieldIdx: 1, 141 | }, 142 | { 143 | blockNumber: BLOCK_NUMBER, 144 | addr: WETH_WHALE, 145 | fieldIdx: AccountField.Nonce, 146 | }, 147 | { 148 | // blockNumber: 17975259, 149 | // txIdx: 34, 150 | txHash: "0x47082a4eaba054312c652a21c6d75a44095b8be43c60bdaeffad03d38a8b1602", 151 | fieldOrLogIdx: ReceiptField.CumulativeGas, 152 | topicOrDataOrAddressIdx: 10, 153 | eventSchema: ethers.ZeroHash, 154 | }, 155 | ]; 156 | const computeQueryReq: AxiomV2ComputeQuery = { 157 | k: 14, 158 | vkey: computeQuery.vkey, 159 | computeProof: computeQuery.computeProof, 160 | }; 161 | const callbackQuery = { 162 | target: WETH_ADDR, 163 | extraData: ethers.solidityPacked(["address"], [WETH_WHALE]), 164 | }; 165 | const options: AxiomV2QueryOptions = { 166 | maxFeePerGas: BigInt(100000000).toString(), 167 | }; 168 | const query = (axiom.query as QueryV2).new(dataQueryReq, computeQueryReq, callbackQuery, options); 169 | 170 | const unbiltDq = query.getDataQuery(); 171 | expect((unbiltDq?.[2] as UnbuiltAccountSubquery).addr).toEqual(WETH_WHALE); 172 | await query.build(); 173 | const builtQuery = query.getBuiltQuery(); 174 | if (builtQuery === undefined) { 175 | throw new Error("builtQuery is undefined"); 176 | } 177 | 178 | const builtDq = builtQuery.dataQueryStruct.subqueries; 179 | expect((builtDq?.[2].subqueryData as AccountSubquery).addr).toEqual(WETH_WHALE.toLowerCase()); 180 | expect(builtQuery.queryHash).toEqual("0xf83d8c1a06ca57e6ac62998276ce1f4e091a28f93e8edcefb34682c30c34450c"); 181 | expect(builtQuery.dataQueryHash).toEqual("0xa23b68c6445bf7850bfdb0921bef5c55d43bd6362133b1f3929dd7b8103502dd"); 182 | expect(builtQuery.dataQuery).toEqual( 183 | "0x00000000000000010004000100ed14f200000000000100ed14f300000001000200ed14f22e15d7aa0650de1009710fdd45c3468d75ae1392000000000005011247db0022000000020000000a0000000000000000000000000000000000000000000000000000000000000000", 184 | ); 185 | expect(builtQuery.computeQuery.k).toEqual(computeQueryReq.k); 186 | expect(builtQuery.computeQuery.resultLen).toEqual(4); 187 | expect(builtQuery.computeQuery.vkey.length).toEqual(computeQueryReq.vkey.length); 188 | expect(builtQuery.computeQuery.vkey).toEqual( 189 | resizeArray(computeQueryReq.vkey, computeQueryReq.vkey.length, ethers.ZeroHash), 190 | ); 191 | expect(builtQuery.computeQuery.computeProof).toEqual(computeQuery.computeProof); 192 | expect(builtQuery.callback).toEqual({ 193 | target: WETH_ADDR.toLowerCase(), 194 | extraData: "0x2e15d7aa0650de1009710fdd45c3468d75ae1392", 195 | }); 196 | }); 197 | }); 198 | -------------------------------------------------------------------------------- /src/v2/query/queryBuilderV2.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { 3 | AxiomV2Callback, 4 | AxiomV2ComputeQuery, 5 | DataSubqueryType, 6 | bytes32, 7 | encodeQueryV2, 8 | getQuerySchemaHash, 9 | getQueryHashV2, 10 | getDataQueryHashFromSubqueries, 11 | AxiomV2CircuitConstant, 12 | getQueryId, 13 | getCallbackHash, 14 | AxiomV2DataQuery, 15 | encodeDataQuery, 16 | encodeComputeQuery, 17 | encodeCallback, 18 | AxiomV2FeeData, 19 | encodeFeeData, 20 | } from "@axiom-crypto/tools"; 21 | import { InternalConfig } from "../../core/internalConfig"; 22 | import { 23 | BuiltQueryV2, 24 | AxiomV2QueryOptions, 25 | UnbuiltSubquery, 26 | UnbuiltHeaderSubquery, 27 | UnbuiltAccountSubquery, 28 | UnbuiltStorageSubquery, 29 | UnbuiltTxSubquery, 30 | UnbuiltReceiptSubquery, 31 | UnbuiltSolidityNestedMappingSubquery, 32 | UnbuiltBeaconValidatorSubquery, 33 | DataSubqueryCount, 34 | } from "../types"; 35 | import { ConstantsV2 } from "../constants"; 36 | import { 37 | validateAccountSubquery, 38 | validateHeaderSubquery, 39 | validateStorageSubquery, 40 | validateTxSubquery, 41 | validateReceiptSubquery, 42 | validateSolidityNestedMappingSubquery, 43 | validateBeaconSubquery, 44 | } from "./dataSubquery/validate"; 45 | import { getUnbuiltSubqueryTypeFromKeys } from "./dataSubquery/utils"; 46 | import { buildDataQuery, buildDataSubqueries, encodeBuilderDataQuery } from "./dataSubquery/build"; 47 | import { deepCopyObject } from "../../shared/utils"; 48 | import { ConfigLimitManager } from "./dataSubquery/configLimitManager"; 49 | 50 | export class QueryBuilderV2 { 51 | protected readonly config: InternalConfig; 52 | private builtQuery?: BuiltQueryV2; 53 | private builtDataQuery?: AxiomV2DataQuery; 54 | private dataQuery?: UnbuiltSubquery[]; 55 | private computeQuery?: AxiomV2ComputeQuery; 56 | private callback?: AxiomV2Callback; 57 | private options: AxiomV2QueryOptions; 58 | private dataSubqueryCount: DataSubqueryCount; 59 | 60 | constructor( 61 | config: InternalConfig, 62 | dataQuery?: UnbuiltSubquery[], 63 | computeQuery?: AxiomV2ComputeQuery, 64 | callback?: AxiomV2Callback, 65 | options?: AxiomV2QueryOptions, 66 | ) { 67 | this.config = config; 68 | 69 | this.options = this.setOptions(options ?? {}); 70 | this.dataSubqueryCount = deepCopyObject(ConstantsV2.EmptyDataSubqueryCount); 71 | 72 | if (dataQuery !== undefined) { 73 | this.append(dataQuery); 74 | } 75 | 76 | if (computeQuery !== undefined) { 77 | this.computeQuery = this.handleComputeQueryRequest(computeQuery); 78 | } 79 | 80 | if (callback !== undefined) { 81 | this.callback = this.handleCallback(callback); 82 | } 83 | } 84 | 85 | /** 86 | * Gets the current set of unbuilt data subqueries 87 | * @returns Array of unbuilt data subqueries 88 | */ 89 | getDataQuery(): UnbuiltSubquery[] | undefined { 90 | return this.dataQuery; 91 | } 92 | 93 | /** 94 | * Gets the current compute query 95 | * @returns The current compute query 96 | */ 97 | getComputeQuery(): AxiomV2ComputeQuery | undefined { 98 | return this.computeQuery; 99 | } 100 | 101 | /** 102 | * Gets the callback information 103 | * @returns The current callback information 104 | */ 105 | getCallback(): AxiomV2Callback | undefined { 106 | return this.callback; 107 | } 108 | 109 | /** 110 | * Gets the current Query options 111 | * @returns The current Query options 112 | */ 113 | getOptions(): AxiomV2QueryOptions { 114 | return this.options; 115 | } 116 | 117 | /** 118 | * Gets the current count of each type of data subquery 119 | * @returns Subquery counts 120 | */ 121 | getDataSubqueryCount(): DataSubqueryCount { 122 | return this.dataSubqueryCount; 123 | } 124 | 125 | /** 126 | * Gets the built Query. Built Query resets if any data is changed. 127 | * @returns The built Query; undefined if Query has not been built yet 128 | */ 129 | getBuiltQuery(): BuiltQueryV2 | undefined { 130 | return this.builtQuery; 131 | } 132 | 133 | /** 134 | * Gets the hash of the querySchema of the computeQuery 135 | * @returns Query schema hash 136 | */ 137 | getQuerySchema(): string { 138 | return getQuerySchemaHash( 139 | this.computeQuery?.k ?? 0, 140 | this.computeQuery?.resultLen ?? this.getDefaultResultLen(), 141 | this.computeQuery?.vkey ?? [], 142 | ); 143 | } 144 | 145 | /** 146 | * Gets the hash of the data query 147 | * @returns Data query hash 148 | */ 149 | getDataQueryHash(): string { 150 | if (this.builtQuery === undefined) { 151 | throw new Error( 152 | "Query must first be built with `.build()` before getting data query hash. If Query is modified after building, you will need to run `.build()` again.", 153 | ); 154 | } 155 | return getDataQueryHashFromSubqueries(this.config.chainId.toString(), this.builtQuery.dataQueryStruct.subqueries); 156 | } 157 | 158 | getQueryHash(): string { 159 | if (this.builtQuery === undefined) { 160 | throw new Error( 161 | "Query must first be built with `.build()` before getting query hash. If Query is modified after building, you will need to run `.build()` again.", 162 | ); 163 | } 164 | const computeQuery = this.computeQuery ?? deepCopyObject(ConstantsV2.EmptyComputeQueryObject); 165 | return getQueryHashV2(this.config.chainId.toString(), this.getDataQueryHash(), computeQuery); 166 | } 167 | 168 | setDataQuery(dataQuery: UnbuiltSubquery[]) { 169 | this.unsetBuiltQuery(); 170 | this.dataQuery = undefined; 171 | this.resetSubqueryCount(); 172 | this.append(dataQuery); 173 | } 174 | 175 | setComputeQuery(computeQuery: AxiomV2ComputeQuery) { 176 | this.unsetBuiltQuery(); 177 | this.computeQuery = this.handleComputeQueryRequest(computeQuery); 178 | } 179 | 180 | setCallback(callback: AxiomV2Callback) { 181 | this.unsetBuiltQuery(); 182 | this.callback = this.handleCallback(callback); 183 | } 184 | 185 | setOptions(options: AxiomV2QueryOptions): AxiomV2QueryOptions { 186 | this.unsetBuiltQuery(); 187 | this.options = { 188 | maxFeePerGas: options?.maxFeePerGas ?? ConstantsV2.DefaultMaxFeePerGasWei, 189 | callbackGasLimit: options?.callbackGasLimit ?? ConstantsV2.DefaultCallbackGasLimit, 190 | overrideAxiomQueryFee: options?.overrideAxiomQueryFee ?? ConstantsV2.DefaultOverrideAxiomQueryFee, 191 | refundee: options?.refundee, 192 | }; 193 | return this.options; 194 | } 195 | 196 | /** 197 | * Append a `UnbuiltSubquery[]` object to the current dataQuery 198 | * @param dataQuery A `UnbuiltSubquery[]` object to append 199 | */ 200 | append(dataSubqueries: UnbuiltSubquery[], skipValidate?: boolean): void { 201 | this.unsetBuiltQuery(); 202 | 203 | if (this.dataQuery === undefined) { 204 | this.dataQuery = [] as UnbuiltSubquery[]; 205 | } 206 | 207 | if (this.dataQuery?.length + dataSubqueries.length > ConstantsV2.UserMaxTotalSubqueries) { 208 | throw new Error(`Cannot add more than ${ConstantsV2.UserMaxTotalSubqueries} subqueries`); 209 | } 210 | 211 | for (const subquery of dataSubqueries) { 212 | const type = getUnbuiltSubqueryTypeFromKeys(Object.keys(subquery)); 213 | this.updateSubqueryCount(type, skipValidate); 214 | } 215 | 216 | // Append new dataSubqueries to existing dataQuery 217 | this.dataQuery = [...(this.dataQuery ?? []), ...dataSubqueries]; 218 | } 219 | 220 | /** 221 | * Appends a single subquery to the current dataQuery 222 | * @param dataSubquery The data of the subquery to append 223 | * @param type (optional) The type of subquery to append. If not provided, the type will be 224 | * inferred from the keys of the subquery. 225 | */ 226 | appendDataSubquery(dataSubquery: UnbuiltSubquery): void { 227 | this.append([dataSubquery]); 228 | } 229 | 230 | /** 231 | * Appends a built DataQuery. This is used when receiving a DataQuery from a ComputeQuery. 232 | * Setting this will take precedence over setting any UnbuiltSubqueries via `append()`. 233 | */ 234 | setBuiltDataQuery(dataQuery: AxiomV2DataQuery, skipValidate?: boolean): void { 235 | this.resetSubqueryCount(); 236 | for (const subquery of dataQuery.subqueries) { 237 | this.updateSubqueryCount(subquery.type, skipValidate); 238 | } 239 | this.builtDataQuery = dataQuery; 240 | } 241 | 242 | /** 243 | * Queries the required subquery data and builds the entire Query object into the format 244 | * that is required by the backend/ZK circuit 245 | * @param validate (optional) Runs validation on the Query before attempting to build it 246 | * @returns A built Query object 247 | */ 248 | async build(validate?: boolean): Promise { 249 | if (validate === true) { 250 | const valid = await this.validate(); 251 | if (!valid) { 252 | throw new Error("Query validation failed"); 253 | } 254 | } 255 | 256 | // Check if Query can be built: needs at least a dataQuery or computeQuery 257 | let validDataQuery = true; 258 | if (this.builtDataQuery === undefined && (this.dataQuery === undefined || this.dataQuery.length === 0)) { 259 | validDataQuery = false; 260 | } 261 | let validComputeQuery = true; 262 | if (this.computeQuery === undefined || this.computeQuery.k === 0) { 263 | validComputeQuery = false; 264 | } 265 | if (!validDataQuery && !validComputeQuery) { 266 | throw new Error("Cannot build Query without either a data query or a compute query"); 267 | } 268 | 269 | // Handle Data Query 270 | let dataQuery, dataQueryHash, dataQueryStruct; 271 | if (this.builtDataQuery === undefined) { 272 | // Parse and get fetch appropriate data for all data subqueries 273 | const builtDataSubqueries = await buildDataSubqueries(this.config.provider, this.dataQuery ?? []); 274 | 275 | // Encode & build data query 276 | dataQuery = encodeBuilderDataQuery(this.config.chainId, builtDataSubqueries); 277 | dataQueryHash = getDataQueryHashFromSubqueries(this.config.chainId.toString(), builtDataSubqueries); 278 | dataQueryStruct = buildDataQuery(this.config.chainId, builtDataSubqueries); 279 | } else { 280 | dataQuery = encodeDataQuery(this.builtDataQuery.sourceChainId, this.builtDataQuery.subqueries); 281 | dataQueryHash = getDataQueryHashFromSubqueries(this.builtDataQuery.sourceChainId, this.builtDataQuery.subqueries); 282 | dataQueryStruct = deepCopyObject(this.builtDataQuery); 283 | } 284 | 285 | // Handle compute query 286 | let defaultResultLen = this.getDefaultResultLen(); 287 | let computeQuery: AxiomV2ComputeQuery = { 288 | k: 0, 289 | resultLen: defaultResultLen, 290 | vkey: [] as string[], 291 | computeProof: "0x00", 292 | }; 293 | if (this.computeQuery !== undefined) { 294 | computeQuery.k = this.computeQuery.k; 295 | computeQuery.resultLen = this.computeQuery?.resultLen ?? defaultResultLen; 296 | computeQuery.vkey = this.computeQuery.vkey; 297 | computeQuery.computeProof = this.computeQuery.computeProof; 298 | } 299 | 300 | const querySchema = getQuerySchemaHash( 301 | computeQuery.k, 302 | computeQuery.resultLen ?? defaultResultLen, 303 | computeQuery.vkey, 304 | ); 305 | 306 | // Get the hash of the full Query 307 | const queryHash = getQueryHashV2(this.config.chainId.toString(), dataQueryHash, computeQuery); 308 | 309 | // Handle callback 310 | const callback = { 311 | target: this.callback?.target ?? ethers.ZeroAddress, 312 | extraData: this.callback?.extraData ?? ethers.ZeroHash, 313 | }; 314 | 315 | // FeeData 316 | const feeData: AxiomV2FeeData = { 317 | maxFeePerGas: this.options.maxFeePerGas!, 318 | callbackGasLimit: this.options.callbackGasLimit!, 319 | overrideAxiomQueryFee: this.options.overrideAxiomQueryFee!, 320 | }; 321 | 322 | // Get the refundee address 323 | const caller = await this.config.signer?.getAddress(); 324 | const refundee = this.options?.refundee ?? caller ?? ""; 325 | 326 | // Calculate a salt 327 | const userSalt = this.calculateUserSalt(); 328 | 329 | this.builtQuery = { 330 | sourceChainId: this.config.chainId.toString(), 331 | targetChainId: this.config.targetChainId.toString(), 332 | queryHash, 333 | dataQuery, 334 | dataQueryHash, 335 | dataQueryStruct, 336 | computeQuery, 337 | querySchema, 338 | callback, 339 | feeData, 340 | userSalt, 341 | refundee, 342 | }; 343 | 344 | return this.builtQuery; 345 | } 346 | 347 | /** 348 | * @returns {boolean} Whether the query is valid or not 349 | */ 350 | async validate(): Promise { 351 | // Check if data subqueries are valid 352 | const data = await this.validateDataSubqueries(); 353 | 354 | // Check if compute query is valid 355 | const compute = await this.validateComputeQuery(); 356 | 357 | // Check if callback is valid 358 | const callback = await this.validateCallback(); 359 | 360 | return data && compute && callback; 361 | } 362 | 363 | /** 364 | * Gets a queryId for a built Query (requires `privateKey` to be set in AxiomSdkCoreConfig) 365 | * @returns uint256 queryId 366 | */ 367 | async getQueryId(caller?: string): Promise { 368 | if (this.builtQuery === undefined) { 369 | throw new Error("Must query with `build()` first before getting queryId"); 370 | } 371 | 372 | // Get required queryId params 373 | if (caller === undefined) { 374 | if (this.config.signer === undefined) { 375 | throw new Error("Unable to get signer; ensure you have set `privateKey` in AxiomSdkCoreConfig"); 376 | } 377 | const callerAddr = await this.config.signer?.getAddress(); 378 | if (callerAddr === "") { 379 | throw new Error("Unable to get signer address; ensure you have set `privateKey` in AxiomSdkCoreConfig"); 380 | } 381 | caller = callerAddr; 382 | } 383 | const targetChainId = this.builtQuery.targetChainId; 384 | const refundee = this.options?.refundee ?? caller; 385 | const salt = this.builtQuery.userSalt; 386 | const queryHash = this.builtQuery.queryHash; 387 | const callbackHash = getCallbackHash(this.builtQuery.callback.target, this.builtQuery.callback.extraData); 388 | 389 | // Calculate the queryId 390 | const queryId = getQueryId(targetChainId, caller, salt, queryHash, callbackHash, refundee); 391 | return BigInt(queryId).toString(); 392 | } 393 | 394 | private unsetBuiltQuery() { 395 | // Reset built query if any data is changed 396 | this.builtQuery = undefined; 397 | } 398 | 399 | private calculateUserSalt(): string { 400 | return ethers.hexlify(ethers.randomBytes(32)); 401 | } 402 | 403 | private getDefaultResultLen(): number { 404 | return Math.min(this.dataQuery?.length ?? 0, AxiomV2CircuitConstant.UserMaxOutputs); 405 | } 406 | 407 | private handleComputeQueryRequest(computeQuery: AxiomV2ComputeQuery) { 408 | computeQuery.resultLen = computeQuery.resultLen ?? this.getDefaultResultLen(); 409 | computeQuery.vkey = computeQuery.vkey.map((x: string) => bytes32(x)); 410 | return computeQuery; 411 | } 412 | 413 | private handleCallback(callback: AxiomV2Callback): AxiomV2Callback { 414 | callback.target = callback.target.toLowerCase(); 415 | callback.extraData = callback.extraData.toLowerCase(); 416 | return callback; 417 | } 418 | 419 | private async validateDataSubqueries(): Promise { 420 | if (this.dataQuery === undefined || this.dataQuery.length === 0) { 421 | return true; 422 | } 423 | const provider = this.config.provider; 424 | let validQuery = true; 425 | const configLimitManager = new ConfigLimitManager(); 426 | 427 | for (const subquery of this.dataQuery) { 428 | const type = getUnbuiltSubqueryTypeFromKeys(Object.keys(subquery)); 429 | switch (type) { 430 | case DataSubqueryType.Header: 431 | validQuery = validQuery && (await validateHeaderSubquery(provider, subquery as UnbuiltHeaderSubquery)); 432 | break; 433 | case DataSubqueryType.Account: 434 | validQuery = validQuery && (await validateAccountSubquery(provider, subquery as UnbuiltAccountSubquery)); 435 | break; 436 | case DataSubqueryType.Storage: 437 | validQuery = validQuery && (await validateStorageSubquery(provider, subquery as UnbuiltStorageSubquery)); 438 | break; 439 | case DataSubqueryType.Transaction: 440 | validQuery = 441 | validQuery && (await validateTxSubquery(provider, subquery as UnbuiltTxSubquery, configLimitManager)); 442 | break; 443 | case DataSubqueryType.Receipt: 444 | validQuery = 445 | validQuery && 446 | (await validateReceiptSubquery(provider, subquery as UnbuiltReceiptSubquery, configLimitManager)); 447 | break; 448 | case DataSubqueryType.SolidityNestedMapping: 449 | validQuery = 450 | validQuery && 451 | (await validateSolidityNestedMappingSubquery(provider, subquery as UnbuiltSolidityNestedMappingSubquery)); 452 | break; 453 | case DataSubqueryType.BeaconValidator: 454 | validQuery = 455 | validQuery && (await validateBeaconSubquery(provider, subquery as UnbuiltBeaconValidatorSubquery)); 456 | break; 457 | default: 458 | throw new Error(`Invalid subquery type: ${type}`); 459 | } 460 | } 461 | return validQuery; 462 | } 463 | 464 | private async validateComputeQuery(): Promise { 465 | if (this.computeQuery === undefined) { 466 | return true; 467 | } 468 | let valid = true; 469 | 470 | // Check resultLen 471 | if ( 472 | this.computeQuery.resultLen !== undefined && 473 | this.computeQuery.resultLen > AxiomV2CircuitConstant.UserMaxOutputs 474 | ) { 475 | console.warn(`Callback resultLen is greater than maxOutputs (${AxiomV2CircuitConstant.UserMaxOutputs})`); 476 | valid = false; 477 | } 478 | 479 | // Check that vkey and computeProof are not zero if k is nonzero 480 | if (this.computeQuery.k !== 0) { 481 | if (this.computeQuery.vkey.length === 0) { 482 | console.warn("Compute query vkey is empty"); 483 | valid = false; 484 | } 485 | if (this.computeQuery.computeProof.length === 0) { 486 | console.warn("Compute query computeProof is empty"); 487 | valid = false; 488 | } 489 | } 490 | 491 | return valid; 492 | } 493 | 494 | private async validateCallback(): Promise { 495 | if (this.callback === undefined) { 496 | return true; 497 | } 498 | let valid = true; 499 | 500 | let target = this.callback.target; 501 | if (target === undefined || target === "" || target === ethers.ZeroAddress) { 502 | console.warn("Callback target is empty"); 503 | valid = false; 504 | } 505 | 506 | let extraData = this.callback.extraData; 507 | if (extraData === undefined) { 508 | console.warn("Callback extraData is undefined"); 509 | valid = false; 510 | } else { 511 | // Check if extra data is bytes32-aligned 512 | if (extraData.startsWith("0x")) { 513 | extraData = extraData.slice(2); 514 | } 515 | if (extraData.length % 64 !== 0) { 516 | console.warn( 517 | "Callback extraData is not bytes32-aligned; EVM will automatically right-append zeros to data that is not a multiple of 32 bytes, which is probably not what you want.", 518 | ); 519 | valid = false; 520 | } 521 | } 522 | 523 | return valid; 524 | } 525 | 526 | private resetSubqueryCount() { 527 | this.dataSubqueryCount = deepCopyObject(ConstantsV2.EmptyDataSubqueryCount); 528 | } 529 | 530 | private updateSubqueryCount(type: DataSubqueryType, skipValidate?: boolean) { 531 | this.dataSubqueryCount.total++; 532 | if (skipValidate) return; 533 | if (this.dataSubqueryCount.total > ConstantsV2.UserMaxTotalSubqueries) { 534 | throw new Error(`Cannot add more than ${ConstantsV2.UserMaxTotalSubqueries} subqueries`); 535 | } 536 | switch (type) { 537 | case DataSubqueryType.Header: 538 | this.dataSubqueryCount.header++; 539 | if (this.dataSubqueryCount.header > ConstantsV2.MaxSameSubqueryType) { 540 | throw new Error(`Cannot add more than ${ConstantsV2.MaxSameSubqueryType} Header subqueries`); 541 | } 542 | break; 543 | case DataSubqueryType.Account: 544 | this.dataSubqueryCount.account++; 545 | if (this.dataSubqueryCount.account > ConstantsV2.MaxSameSubqueryType) { 546 | throw new Error( 547 | `Cannot add more than ${ConstantsV2.MaxSameSubqueryType} Account + Storage + Nested Mapping subqueries`, 548 | ); 549 | } 550 | break; 551 | case DataSubqueryType.Storage: 552 | this.dataSubqueryCount.storage++; 553 | if (this.dataSubqueryCount.storage > ConstantsV2.MaxSameSubqueryType) { 554 | throw new Error( 555 | `Cannot add more than ${ConstantsV2.MaxSameSubqueryType} Account + Storage + Nested Mapping subqueries`, 556 | ); 557 | } 558 | break; 559 | case DataSubqueryType.Transaction: 560 | this.dataSubqueryCount.transaction++; 561 | if (this.dataSubqueryCount.transaction > ConstantsV2.MaxSameSubqueryType) { 562 | throw new Error(`Cannot add more than ${ConstantsV2.MaxSameSubqueryType} Transaction subqueries`); 563 | } 564 | break; 565 | case DataSubqueryType.Receipt: 566 | this.dataSubqueryCount.receipt++; 567 | if (this.dataSubqueryCount.receipt > ConstantsV2.MaxSameSubqueryType) { 568 | throw new Error(`Cannot add more than ${ConstantsV2.MaxSameSubqueryType} Receipt subqueries`); 569 | } 570 | break; 571 | case DataSubqueryType.SolidityNestedMapping: 572 | this.dataSubqueryCount.solidityNestedMapping++; 573 | if (this.dataSubqueryCount.solidityNestedMapping > ConstantsV2.MaxSameSubqueryType) { 574 | throw new Error( 575 | `Cannot add more than ${ConstantsV2.MaxSameSubqueryType} Account + Storage + Nested Mapping subqueries`, 576 | ); 577 | } 578 | break; 579 | default: 580 | throw new Error(`Unknown subquery type: ${type}`); 581 | } 582 | } 583 | } 584 | --------------------------------------------------------------------------------