From 6007cfc0fd4d34d197b632f29acadd6655a50490 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Thu, 13 Mar 2025 12:05:42 -0300 Subject: [PATCH 01/63] add deploy action --- .github/workflows/deploy-postgres.yml | 43 +++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/deploy-postgres.yml diff --git a/.github/workflows/deploy-postgres.yml b/.github/workflows/deploy-postgres.yml new file mode 100644 index 0000000..24f2ddf --- /dev/null +++ b/.github/workflows/deploy-postgres.yml @@ -0,0 +1,43 @@ +on: + workflow_dispatch: + inputs: + environment: + description: 'Target environment' + type: environment + required: true + +name: Deploy postgress app + +jobs: + deploy: + name: Deploy ${{ github.ref_name }} to ${{ inputs.environment }} + runs-on: ubuntu-20.04 + environment: ${{ inputs.environment }} + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup Node.js 19 + uses: actions/setup-node@v3 + with: + node-version: '19' + + - name: Install dependencies + run: npm ci + + - name: Check types + run: npm run test:tsc + + - name: Create env file + run: | + cat > env.yml << EOF + ${{ secrets.API_ENV_POSTGRES }} + EOF + + - name: Serverless deploy + uses: serverless/github-action@v3 + with: + args: deploy --stage ${{ inputs.environment }}20 --region us-west-1 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} From a446ec76325ed57f60e0dd993c78ba7d1853de9c Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Wed, 12 Mar 2025 19:03:29 -0300 Subject: [PATCH 02/63] use postgres and prisma for dag, metagraph, and actions endpoints --- env.yml | 1 + package-lock.json | 1876 ++++++++++++++-------------- package.json | 9 +- prisma/schema.prisma | 521 ++++++++ routes/actions.yml | 41 + routes/allow-spends.yml | 125 ++ routes/dag.yml | 67 + routes/metagraph.yml | 108 ++ routes/token-locks.yml | 83 ++ serverless.yml | 185 +-- src/handler.ts | 94 -- src/handlers/actionsHandler.ts | 219 ++++ src/handlers/allowSpendsHandler.ts | 117 ++ src/handlers/dagHandler.ts | 318 +++++ src/handlers/metagraphHandler.ts | 521 ++++++++ src/handlers/tokenLocksHandler.ts | 111 ++ src/http.ts | 68 - src/model/balance.ts | 12 - src/model/block.ts | 20 - src/model/currency-data.ts | 4 - src/model/currency-snapshot.ts | 36 - src/model/fee-transaction.ts | 15 - src/model/index.ts | 7 - src/model/metagraph.ts | 6 - src/model/properties.ts | 23 - src/model/snapshot.ts | 14 - src/model/transaction.ts | 30 - src/opensearch.ts | 1037 --------------- src/pagination.ts | 106 ++ src/query.ts | 237 ---- src/request-params.ts | 192 +-- src/response.ts | 160 +++ src/service.ts | 682 ---------- src/ts-extensions.ts | 97 -- test_endpoints.sh | 66 + tests/opensearch.test.ts | 67 - tests/request-params.test.ts | 81 -- tests/validation.test.ts | 98 +- 38 files changed, 3577 insertions(+), 3877 deletions(-) create mode 100644 prisma/schema.prisma create mode 100644 routes/actions.yml create mode 100644 routes/allow-spends.yml create mode 100644 routes/dag.yml create mode 100644 routes/metagraph.yml create mode 100644 routes/token-locks.yml delete mode 100644 src/handler.ts create mode 100644 src/handlers/actionsHandler.ts create mode 100644 src/handlers/allowSpendsHandler.ts create mode 100644 src/handlers/dagHandler.ts create mode 100644 src/handlers/metagraphHandler.ts create mode 100644 src/handlers/tokenLocksHandler.ts delete mode 100644 src/http.ts delete mode 100644 src/model/balance.ts delete mode 100644 src/model/block.ts delete mode 100644 src/model/currency-data.ts delete mode 100644 src/model/currency-snapshot.ts delete mode 100644 src/model/fee-transaction.ts delete mode 100644 src/model/index.ts delete mode 100644 src/model/metagraph.ts delete mode 100644 src/model/properties.ts delete mode 100644 src/model/snapshot.ts delete mode 100644 src/model/transaction.ts delete mode 100644 src/opensearch.ts create mode 100644 src/pagination.ts delete mode 100644 src/query.ts create mode 100644 src/response.ts delete mode 100644 src/service.ts delete mode 100644 src/ts-extensions.ts create mode 100644 test_endpoints.sh delete mode 100644 tests/opensearch.test.ts delete mode 100644 tests/request-params.test.ts diff --git a/env.yml b/env.yml index a6dc14e..b3c682f 100644 --- a/env.yml +++ b/env.yml @@ -1,3 +1,4 @@ default: opensearch: 'http://localhost:9200' vpc: {} + db_url: postgresql://postgres:postgres@localhost:5432/new_block_explorer?schema=public \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index ae76270..26c3f1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,12 +10,12 @@ "license": "MIT", "dependencies": { "@opensearch-project/opensearch": "^1.2.0", + "@prisma/client": "^6.2.1", "aws-lambda": "^1.0.7", "date-fns": "^2.30.0", "fp-ts": "^2.16.0", "monocle-ts": "^2.3.13", - "serverless-plugin-typescript": "^2.1.5", - "typescript": "5.1.3" + "serverless-plugin-typescript": "^2.1.5" }, "devDependencies": { "@babel/core": "^7.22.5", @@ -23,11 +23,14 @@ "@babel/preset-typescript": "^7.22.5", "@types/aws-lambda": "^8.10.117", "@types/jest": "^29.5.2", + "@types/lodash": "^4.17.16", "@types/node": "^20.3.0", "babel-jest": "^29.5.0", "jest": "^29.5.0", "prettier": "2.8.8", - "serverless-offline": "^13.3.4" + "prisma": "^6.2.1", + "serverless-offline": "^13.3.4", + "typescript": "^5.8.2" } }, "node_modules/@ampproject/remapping": { @@ -183,529 +186,479 @@ } }, "node_modules/@aws-sdk/client-lambda": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.609.0.tgz", - "integrity": "sha512-+us5UQmuZu7Qd33l9xt2TZ2TAT4LWY7QfjSCUaaR8/LRNG6af1LvKfdxSC5iYU+jjb6Yrc1FITH/IoNxbjD6NQ==", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.758.0.tgz", + "integrity": "sha512-k7L9fe0NN1v2Vhg4ofA1pb26gTdGVFdkA6XUQyElLEdcKzJzoYiQ60faNLuMPfH0zsKNvy/xKfNOD6DFZWjgEg==", "dev": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.609.0", - "@aws-sdk/client-sts": "3.609.0", - "@aws-sdk/core": "3.609.0", - "@aws-sdk/credential-provider-node": "3.609.0", - "@aws-sdk/middleware-host-header": "3.609.0", - "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.609.0", - "@aws-sdk/middleware-user-agent": "3.609.0", - "@aws-sdk/region-config-resolver": "3.609.0", - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.609.0", - "@aws-sdk/util-user-agent-browser": "3.609.0", - "@aws-sdk/util-user-agent-node": "3.609.0", - "@smithy/config-resolver": "^3.0.4", - "@smithy/core": "^2.2.4", - "@smithy/eventstream-serde-browser": "^3.0.4", - "@smithy/eventstream-serde-config-resolver": "^3.0.3", - "@smithy/eventstream-serde-node": "^3.0.4", - "@smithy/fetch-http-handler": "^3.2.0", - "@smithy/hash-node": "^3.0.3", - "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.3", - "@smithy/middleware-endpoint": "^3.0.4", - "@smithy/middleware-retry": "^3.0.7", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/node-config-provider": "^3.1.3", - "@smithy/node-http-handler": "^3.1.1", - "@smithy/protocol-http": "^4.0.3", - "@smithy/smithy-client": "^3.1.5", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.7", - "@smithy/util-defaults-mode-node": "^3.0.7", - "@smithy/util-endpoints": "^2.0.4", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", - "@smithy/util-stream": "^3.0.5", - "@smithy/util-utf8": "^3.0.0", - "@smithy/util-waiter": "^3.1.2", + "@aws-sdk/core": "3.758.0", + "@aws-sdk/credential-provider-node": "3.758.0", + "@aws-sdk/middleware-host-header": "3.734.0", + "@aws-sdk/middleware-logger": "3.734.0", + "@aws-sdk/middleware-recursion-detection": "3.734.0", + "@aws-sdk/middleware-user-agent": "3.758.0", + "@aws-sdk/region-config-resolver": "3.734.0", + "@aws-sdk/types": "3.734.0", + "@aws-sdk/util-endpoints": "3.743.0", + "@aws-sdk/util-user-agent-browser": "3.734.0", + "@aws-sdk/util-user-agent-node": "3.758.0", + "@smithy/config-resolver": "^4.0.1", + "@smithy/core": "^3.1.5", + "@smithy/eventstream-serde-browser": "^4.0.1", + "@smithy/eventstream-serde-config-resolver": "^4.0.1", + "@smithy/eventstream-serde-node": "^4.0.1", + "@smithy/fetch-http-handler": "^5.0.1", + "@smithy/hash-node": "^4.0.1", + "@smithy/invalid-dependency": "^4.0.1", + "@smithy/middleware-content-length": "^4.0.1", + "@smithy/middleware-endpoint": "^4.0.6", + "@smithy/middleware-retry": "^4.0.7", + "@smithy/middleware-serde": "^4.0.2", + "@smithy/middleware-stack": "^4.0.1", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/node-http-handler": "^4.0.3", + "@smithy/protocol-http": "^5.0.1", + "@smithy/smithy-client": "^4.1.6", + "@smithy/types": "^4.1.0", + "@smithy/url-parser": "^4.0.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.7", + "@smithy/util-defaults-mode-node": "^4.0.7", + "@smithy/util-endpoints": "^3.0.1", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-retry": "^4.0.1", + "@smithy/util-stream": "^4.1.2", + "@smithy/util-utf8": "^4.0.0", + "@smithy/util-waiter": "^4.0.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.609.0.tgz", - "integrity": "sha512-gqXGFDkIpKHCKAbeJK4aIDt3tiwJ26Rf5Tqw9JS6BYXsdMeOB8FTzqD9R+Yc1epHd8s5L94sdqXT5PapgxFZrg==", - "dev": true, - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.609.0", - "@aws-sdk/middleware-host-header": "3.609.0", - "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.609.0", - "@aws-sdk/middleware-user-agent": "3.609.0", - "@aws-sdk/region-config-resolver": "3.609.0", - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.609.0", - "@aws-sdk/util-user-agent-browser": "3.609.0", - "@aws-sdk/util-user-agent-node": "3.609.0", - "@smithy/config-resolver": "^3.0.4", - "@smithy/core": "^2.2.4", - "@smithy/fetch-http-handler": "^3.2.0", - "@smithy/hash-node": "^3.0.3", - "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.3", - "@smithy/middleware-endpoint": "^3.0.4", - "@smithy/middleware-retry": "^3.0.7", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/node-config-provider": "^3.1.3", - "@smithy/node-http-handler": "^3.1.1", - "@smithy/protocol-http": "^4.0.3", - "@smithy/smithy-client": "^3.1.5", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.7", - "@smithy/util-defaults-mode-node": "^3.0.7", - "@smithy/util-endpoints": "^2.0.4", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.609.0.tgz", - "integrity": "sha512-0bNPAyPdkWkS9EGB2A9BZDkBNrnVCBzk5lYRezoT4K3/gi9w1DTYH5tuRdwaTZdxW19U1mq7CV0YJJARKO1L9Q==", - "dev": true, - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.609.0", - "@aws-sdk/credential-provider-node": "3.609.0", - "@aws-sdk/middleware-host-header": "3.609.0", - "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.609.0", - "@aws-sdk/middleware-user-agent": "3.609.0", - "@aws-sdk/region-config-resolver": "3.609.0", - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.609.0", - "@aws-sdk/util-user-agent-browser": "3.609.0", - "@aws-sdk/util-user-agent-node": "3.609.0", - "@smithy/config-resolver": "^3.0.4", - "@smithy/core": "^2.2.4", - "@smithy/fetch-http-handler": "^3.2.0", - "@smithy/hash-node": "^3.0.3", - "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.3", - "@smithy/middleware-endpoint": "^3.0.4", - "@smithy/middleware-retry": "^3.0.7", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/node-config-provider": "^3.1.3", - "@smithy/node-http-handler": "^3.1.1", - "@smithy/protocol-http": "^4.0.3", - "@smithy/smithy-client": "^3.1.5", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.7", - "@smithy/util-defaults-mode-node": "^3.0.7", - "@smithy/util-endpoints": "^2.0.4", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.609.0" - } - }, - "node_modules/@aws-sdk/client-sts": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.609.0.tgz", - "integrity": "sha512-A0B3sDKFoFlGo8RYRjDBWHXpbgirer2bZBkCIzhSPHc1vOFHt/m2NcUoE2xnBKXJFrptL1xDkvo1P+XYp/BfcQ==", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.758.0.tgz", + "integrity": "sha512-BoGO6IIWrLyLxQG6txJw6RT2urmbtlwfggapNCrNPyYjlXpzTSJhBYjndg7TpDATFd0SXL0zm8y/tXsUXNkdYQ==", "dev": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.609.0", - "@aws-sdk/core": "3.609.0", - "@aws-sdk/credential-provider-node": "3.609.0", - "@aws-sdk/middleware-host-header": "3.609.0", - "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.609.0", - "@aws-sdk/middleware-user-agent": "3.609.0", - "@aws-sdk/region-config-resolver": "3.609.0", - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.609.0", - "@aws-sdk/util-user-agent-browser": "3.609.0", - "@aws-sdk/util-user-agent-node": "3.609.0", - "@smithy/config-resolver": "^3.0.4", - "@smithy/core": "^2.2.4", - "@smithy/fetch-http-handler": "^3.2.0", - "@smithy/hash-node": "^3.0.3", - "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.3", - "@smithy/middleware-endpoint": "^3.0.4", - "@smithy/middleware-retry": "^3.0.7", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/node-config-provider": "^3.1.3", - "@smithy/node-http-handler": "^3.1.1", - "@smithy/protocol-http": "^4.0.3", - "@smithy/smithy-client": "^3.1.5", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.7", - "@smithy/util-defaults-mode-node": "^3.0.7", - "@smithy/util-endpoints": "^2.0.4", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", - "@smithy/util-utf8": "^3.0.0", + "@aws-sdk/core": "3.758.0", + "@aws-sdk/middleware-host-header": "3.734.0", + "@aws-sdk/middleware-logger": "3.734.0", + "@aws-sdk/middleware-recursion-detection": "3.734.0", + "@aws-sdk/middleware-user-agent": "3.758.0", + "@aws-sdk/region-config-resolver": "3.734.0", + "@aws-sdk/types": "3.734.0", + "@aws-sdk/util-endpoints": "3.743.0", + "@aws-sdk/util-user-agent-browser": "3.734.0", + "@aws-sdk/util-user-agent-node": "3.758.0", + "@smithy/config-resolver": "^4.0.1", + "@smithy/core": "^3.1.5", + "@smithy/fetch-http-handler": "^5.0.1", + "@smithy/hash-node": "^4.0.1", + "@smithy/invalid-dependency": "^4.0.1", + "@smithy/middleware-content-length": "^4.0.1", + "@smithy/middleware-endpoint": "^4.0.6", + "@smithy/middleware-retry": "^4.0.7", + "@smithy/middleware-serde": "^4.0.2", + "@smithy/middleware-stack": "^4.0.1", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/node-http-handler": "^4.0.3", + "@smithy/protocol-http": "^5.0.1", + "@smithy/smithy-client": "^4.1.6", + "@smithy/types": "^4.1.0", + "@smithy/url-parser": "^4.0.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.7", + "@smithy/util-defaults-mode-node": "^4.0.7", + "@smithy/util-endpoints": "^3.0.1", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-retry": "^4.0.1", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/core": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.609.0.tgz", - "integrity": "sha512-ptqw+DTxLr01+pKjDUuo53SEDzI+7nFM3WfQaEo0yhDg8vWw8PER4sWj1Ysx67ksctnZesPUjqxd5SHbtdBxiA==", - "dev": true, - "dependencies": { - "@smithy/core": "^2.2.4", - "@smithy/protocol-http": "^4.0.3", - "@smithy/signature-v4": "^3.1.2", - "@smithy/smithy-client": "^3.1.5", - "@smithy/types": "^3.3.0", - "fast-xml-parser": "4.2.5", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.758.0.tgz", + "integrity": "sha512-0RswbdR9jt/XKemaLNuxi2gGr4xGlHyGxkTdhSQzCyUe9A9OPCoLl3rIESRguQEech+oJnbHk/wuiwHqTuP9sg==", + "dev": true, + "dependencies": { + "@aws-sdk/types": "3.734.0", + "@smithy/core": "^3.1.5", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/property-provider": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/signature-v4": "^5.0.1", + "@smithy/smithy-client": "^4.1.6", + "@smithy/types": "^4.1.0", + "@smithy/util-middleware": "^4.0.1", + "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.609.0.tgz", - "integrity": "sha512-v69ZCWcec2iuV9vLVJMa6fAb5xwkzN4jYIT8yjo2c4Ia/j976Q+TPf35Pnz5My48Xr94EFcaBazrWedF+kwfuQ==", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.758.0.tgz", + "integrity": "sha512-N27eFoRrO6MeUNumtNHDW9WOiwfd59LPXPqDrIa3kWL/s+fOKFHb9xIcF++bAwtcZnAxKkgpDCUP+INNZskE+w==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/types": "^3.3.0", + "@aws-sdk/core": "3.758.0", + "@aws-sdk/types": "3.734.0", + "@smithy/property-provider": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.609.0.tgz", - "integrity": "sha512-GQQfB9Mk4XUZwaPsk4V3w8MqleS6ApkZKVQn3vTLAKa8Y7B2Imcpe5zWbKYjDd8MPpMWjHcBGFTVlDRFP4zwSQ==", - "dev": true, - "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/fetch-http-handler": "^3.2.0", - "@smithy/node-http-handler": "^3.1.1", - "@smithy/property-provider": "^3.1.3", - "@smithy/protocol-http": "^4.0.3", - "@smithy/smithy-client": "^3.1.5", - "@smithy/types": "^3.3.0", - "@smithy/util-stream": "^3.0.5", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.758.0.tgz", + "integrity": "sha512-Xt9/U8qUCiw1hihztWkNeIR+arg6P+yda10OuCHX6kFVx3auTlU7+hCqs3UxqniGU4dguHuftf3mRpi5/GJ33Q==", + "dev": true, + "dependencies": { + "@aws-sdk/core": "3.758.0", + "@aws-sdk/types": "3.734.0", + "@smithy/fetch-http-handler": "^5.0.1", + "@smithy/node-http-handler": "^4.0.3", + "@smithy/property-provider": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/smithy-client": "^4.1.6", + "@smithy/types": "^4.1.0", + "@smithy/util-stream": "^4.1.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.609.0.tgz", - "integrity": "sha512-hwaBfXuBTv6/eAdEsDfGcteYUW6Km7lvvubbxEdxIuJNF3vswR7RMGIXaEC37hhPkTTgd3H0TONammhwZIfkog==", - "dev": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "3.609.0", - "@aws-sdk/credential-provider-http": "3.609.0", - "@aws-sdk/credential-provider-process": "3.609.0", - "@aws-sdk/credential-provider-sso": "3.609.0", - "@aws-sdk/credential-provider-web-identity": "3.609.0", - "@aws-sdk/types": "3.609.0", - "@smithy/credential-provider-imds": "^3.1.3", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.3", - "@smithy/types": "^3.3.0", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.758.0.tgz", + "integrity": "sha512-cymSKMcP5d+OsgetoIZ5QCe1wnp2Q/tq+uIxVdh9MbfdBBEnl9Ecq6dH6VlYS89sp4QKuxHxkWXVnbXU3Q19Aw==", + "dev": true, + "dependencies": { + "@aws-sdk/core": "3.758.0", + "@aws-sdk/credential-provider-env": "3.758.0", + "@aws-sdk/credential-provider-http": "3.758.0", + "@aws-sdk/credential-provider-process": "3.758.0", + "@aws-sdk/credential-provider-sso": "3.758.0", + "@aws-sdk/credential-provider-web-identity": "3.758.0", + "@aws-sdk/nested-clients": "3.758.0", + "@aws-sdk/types": "3.734.0", + "@smithy/credential-provider-imds": "^4.0.1", + "@smithy/property-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.609.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.609.0.tgz", - "integrity": "sha512-4J8/JRuqfxJDGD9jTHVCBxCvYt7/Vgj2Stlhj930mrjFPO/yRw8ilAAZxBWe0JHPX3QwepCmh4ErZe53F5ysxQ==", - "dev": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "3.609.0", - "@aws-sdk/credential-provider-http": "3.609.0", - "@aws-sdk/credential-provider-ini": "3.609.0", - "@aws-sdk/credential-provider-process": "3.609.0", - "@aws-sdk/credential-provider-sso": "3.609.0", - "@aws-sdk/credential-provider-web-identity": "3.609.0", - "@aws-sdk/types": "3.609.0", - "@smithy/credential-provider-imds": "^3.1.3", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.3", - "@smithy/types": "^3.3.0", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.758.0.tgz", + "integrity": "sha512-+DaMv63wiq7pJrhIQzZYMn4hSarKiizDoJRvyR7WGhnn0oQ/getX9Z0VNCV3i7lIFoLNTb7WMmQ9k7+z/uD5EQ==", + "dev": true, + "dependencies": { + "@aws-sdk/credential-provider-env": "3.758.0", + "@aws-sdk/credential-provider-http": "3.758.0", + "@aws-sdk/credential-provider-ini": "3.758.0", + "@aws-sdk/credential-provider-process": "3.758.0", + "@aws-sdk/credential-provider-sso": "3.758.0", + "@aws-sdk/credential-provider-web-identity": "3.758.0", + "@aws-sdk/types": "3.734.0", + "@smithy/credential-provider-imds": "^4.0.1", + "@smithy/property-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.609.0.tgz", - "integrity": "sha512-Ux35nGOSJKZWUIM3Ny0ROZ8cqPRUEkh+tR3X2o9ydEbFiLq3eMMyEnHJqx4EeUjLRchidlm4CCid9GxMe5/gdw==", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.758.0.tgz", + "integrity": "sha512-AzcY74QTPqcbXWVgjpPZ3HOmxQZYPROIBz2YINF0OQk0MhezDWV/O7Xec+K1+MPGQO3qS6EDrUUlnPLjsqieHA==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.3", - "@smithy/types": "^3.3.0", + "@aws-sdk/core": "3.758.0", + "@aws-sdk/types": "3.734.0", + "@smithy/property-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.609.0.tgz", - "integrity": "sha512-oQPGDKMMIxjvTcm86g07RPYeC7mCNk+29dPpY15ZAPRpAF7F0tircsC3wT9fHzNaKShEyK5LuI5Kg/uxsdy+Iw==", - "dev": true, - "dependencies": { - "@aws-sdk/client-sso": "3.609.0", - "@aws-sdk/token-providers": "3.609.0", - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.3", - "@smithy/types": "^3.3.0", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.758.0.tgz", + "integrity": "sha512-x0FYJqcOLUCv8GLLFDYMXRAQKGjoM+L0BG4BiHYZRDf24yQWFCAZsCQAYKo6XZYh2qznbsW6f//qpyJ5b0QVKQ==", + "dev": true, + "dependencies": { + "@aws-sdk/client-sso": "3.758.0", + "@aws-sdk/core": "3.758.0", + "@aws-sdk/token-providers": "3.758.0", + "@aws-sdk/types": "3.734.0", + "@smithy/property-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.609.0.tgz", - "integrity": "sha512-U+PG8NhlYYF45zbr1km3ROtBMYqyyj/oK8NRp++UHHeuavgrP+4wJ4wQnlEaKvJBjevfo3+dlIBcaeQ7NYejWg==", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.758.0.tgz", + "integrity": "sha512-XGguXhBqiCXMXRxcfCAVPlMbm3VyJTou79r/3mxWddHWF0XbhaQiBIbUz6vobVTD25YQRbWSmSch7VA8kI5Lrw==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/types": "^3.3.0", + "@aws-sdk/core": "3.758.0", + "@aws-sdk/nested-clients": "3.758.0", + "@aws-sdk/types": "3.734.0", + "@smithy/property-provider": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.609.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.609.0.tgz", - "integrity": "sha512-iTKfo158lc4jLDfYeZmYMIBHsn8m6zX+XB6birCSNZ/rrlzAkPbGE43CNdKfvjyWdqgLMRXF+B+OcZRvqhMXPQ==", + "version": "3.734.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.734.0.tgz", + "integrity": "sha512-LW7RRgSOHHBzWZnigNsDIzu3AiwtjeI2X66v+Wn1P1u+eXssy1+up4ZY/h+t2sU4LU36UvEf+jrZti9c6vRnFw==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/protocol-http": "^4.0.3", - "@smithy/types": "^3.3.0", + "@aws-sdk/types": "3.734.0", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz", - "integrity": "sha512-S62U2dy4jMDhDFDK5gZ4VxFdWzCtLzwbYyFZx2uvPYTECkepLUfzLic2BHg2Qvtu4QjX+oGE3P/7fwaGIsGNuQ==", + "version": "3.734.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.734.0.tgz", + "integrity": "sha512-mUMFITpJUW3LcKvFok176eI5zXAUomVtahb9IQBwLzkqFYOrMJvWAvoV4yuxrJ8TlQBG8gyEnkb9SnhZvjg67w==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/types": "^3.3.0", + "@aws-sdk/types": "3.734.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.609.0.tgz", - "integrity": "sha512-6sewsYB7/o/nbUfA99Aa/LokM+a/u4Wpm/X2o0RxOsDtSB795ObebLJe2BxY5UssbGaWkn7LswyfvrdZNXNj1w==", + "version": "3.734.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.734.0.tgz", + "integrity": "sha512-CUat2d9ITsFc2XsmeiRQO96iWpxSKYFjxvj27Hc7vo87YUHRnfMfnc8jw1EpxEwMcvBD7LsRa6vDNky6AjcrFA==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/protocol-http": "^4.0.3", - "@smithy/types": "^3.3.0", + "@aws-sdk/types": "3.734.0", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.609.0.tgz", - "integrity": "sha512-nbq7MXRmeXm4IDqh+sJRAxGPAq0OfGmGIwKvJcw66hLoG8CmhhVMZmIAEBDFr57S+YajGwnLLRt+eMI05MMeVA==", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.758.0.tgz", + "integrity": "sha512-iNyehQXtQlj69JCgfaOssgZD4HeYGOwxcaKeG6F+40cwBjTAi0+Ph1yfDwqk2qiBPIRWJ/9l2LodZbxiBqgrwg==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.609.0", - "@smithy/protocol-http": "^4.0.3", - "@smithy/types": "^3.3.0", + "@aws-sdk/core": "3.758.0", + "@aws-sdk/types": "3.734.0", + "@aws-sdk/util-endpoints": "3.743.0", + "@smithy/core": "^3.1.5", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.758.0.tgz", + "integrity": "sha512-YZ5s7PSvyF3Mt2h1EQulCG93uybprNGbBkPmVuy/HMMfbFTt4iL3SbKjxqvOZelm86epFfj7pvK7FliI2WOEcg==", + "dev": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.758.0", + "@aws-sdk/middleware-host-header": "3.734.0", + "@aws-sdk/middleware-logger": "3.734.0", + "@aws-sdk/middleware-recursion-detection": "3.734.0", + "@aws-sdk/middleware-user-agent": "3.758.0", + "@aws-sdk/region-config-resolver": "3.734.0", + "@aws-sdk/types": "3.734.0", + "@aws-sdk/util-endpoints": "3.743.0", + "@aws-sdk/util-user-agent-browser": "3.734.0", + "@aws-sdk/util-user-agent-node": "3.758.0", + "@smithy/config-resolver": "^4.0.1", + "@smithy/core": "^3.1.5", + "@smithy/fetch-http-handler": "^5.0.1", + "@smithy/hash-node": "^4.0.1", + "@smithy/invalid-dependency": "^4.0.1", + "@smithy/middleware-content-length": "^4.0.1", + "@smithy/middleware-endpoint": "^4.0.6", + "@smithy/middleware-retry": "^4.0.7", + "@smithy/middleware-serde": "^4.0.2", + "@smithy/middleware-stack": "^4.0.1", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/node-http-handler": "^4.0.3", + "@smithy/protocol-http": "^5.0.1", + "@smithy/smithy-client": "^4.1.6", + "@smithy/types": "^4.1.0", + "@smithy/url-parser": "^4.0.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.7", + "@smithy/util-defaults-mode-node": "^4.0.7", + "@smithy/util-endpoints": "^3.0.1", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-retry": "^4.0.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.609.0.tgz", - "integrity": "sha512-lMHBG8zg9GWYBc9/XVPKyuAUd7iKqfPP7z04zGta2kGNOKbUTeqmAdc1gJGku75p4kglIPlGBorOxti8DhRmKw==", + "version": "3.734.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.734.0.tgz", + "integrity": "sha512-Lvj1kPRC5IuJBr9DyJ9T9/plkh+EfKLy+12s/mykOy1JaKHDpvj+XGy2YO6YgYVOb8JFtaqloid+5COtje4JTQ==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/node-config-provider": "^3.1.3", - "@smithy/types": "^3.3.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.3", + "@aws-sdk/types": "3.734.0", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.609.0.tgz", - "integrity": "sha512-WvhW/7XSf+H7YmtiIigQxfDVZVZI7mbKikQ09YpzN7FeN3TmYib1+0tB+EE9TbICkwssjiFc71FEBEh4K9grKQ==", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.758.0.tgz", + "integrity": "sha512-ckptN1tNrIfQUaGWm/ayW1ddG+imbKN7HHhjFdS4VfItsP0QQOB0+Ov+tpgb4MoNR4JaUghMIVStjIeHN2ks1w==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.3", - "@smithy/types": "^3.3.0", + "@aws-sdk/nested-clients": "3.758.0", + "@aws-sdk/types": "3.734.0", + "@smithy/property-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.609.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/types": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", - "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "version": "3.734.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.734.0.tgz", + "integrity": "sha512-o11tSPTT70nAkGV1fN9wm/hAIiLPyWX6SuGf+9JyTp7S/rC2cFWhR26MvA69nplcjNaXVzB0f+QFrLXXjOqCrg==", "dev": true, "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.609.0.tgz", - "integrity": "sha512-Rh+3V8dOvEeE1aQmUy904DYWtLUEJ7Vf5XBPlQ6At3pBhp+zpXbsnpZzVL33c8lW1xfj6YPwtO6gOeEsl1juCQ==", + "version": "3.743.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.743.0.tgz", + "integrity": "sha512-sN1l559zrixeh5x+pttrnd0A3+r34r0tmPkJ/eaaMaAzXqsmKU/xYre9K3FNnsSS1J1k4PEfk/nHDTVUgFYjnw==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/types": "^3.3.0", - "@smithy/util-endpoints": "^2.0.4", + "@aws-sdk/types": "3.734.0", + "@smithy/types": "^4.1.0", + "@smithy/util-endpoints": "^3.0.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/util-locate-window": { - "version": "3.568.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.568.0.tgz", - "integrity": "sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==", + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.723.0.tgz", + "integrity": "sha512-Yf2CS10BqK688DRsrKI/EO6B8ff5J86NXe4C+VCysK7UOgN0l1zOTeTukZ3H8Q9tYYX3oaF1961o8vRkFm7Nmw==", "dev": true, "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.609.0.tgz", - "integrity": "sha512-fojPU+mNahzQ0YHYBsx0ZIhmMA96H+ZIZ665ObU9tl+SGdbLneVZVikGve+NmHTQwHzwkFsZYYnVKAkreJLAtA==", + "version": "3.734.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.734.0.tgz", + "integrity": "sha512-xQTCus6Q9LwUuALW+S76OL0jcWtMOVu14q+GoLnWPUM7QeUw963oQcLhF7oq0CtaLLKyl4GOUfcwc773Zmwwng==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/types": "^3.3.0", + "@aws-sdk/types": "3.734.0", + "@smithy/types": "^4.1.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.609.0.tgz", - "integrity": "sha512-DlZBwQ/HkZyf3pOWc7+wjJRk5R7x9YxHhs2szHwtv1IW30KMabjjjX0GMlGJ9LLkBHkbaaEY/w9Tkj12XRLhRg==", + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.758.0.tgz", + "integrity": "sha512-A5EZw85V6WhoKMV2hbuFRvb9NPlxEErb4HPO6/SPXYY4QrjprIzScHxikqcWv1w4J3apB1wto9LPU3IMsYtfrw==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/node-config-provider": "^3.1.3", - "@smithy/types": "^3.3.0", + "@aws-sdk/middleware-user-agent": "3.758.0", + "@aws-sdk/types": "3.734.0", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { "aws-crt": ">=1.0.0" @@ -717,12 +670,14 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", - "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.22.5" + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -768,15 +723,16 @@ } }, "node_modules/@babel/generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", - "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz", + "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" @@ -1052,18 +1008,18 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true, "engines": { "node": ">=6.9.0" @@ -1107,25 +1063,14 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "node_modules/@babel/parser": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", + "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "@babel/types": "^7.26.9" }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", - "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", - "dev": true, "bin": { "parser": "bin/babel-parser.js" }, @@ -2407,34 +2352,31 @@ } }, "node_modules/@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", + "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", - "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz", + "integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5", - "debug": "^4.1.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.9", + "@babel/parser": "^7.26.9", + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.9", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -2442,14 +2384,13 @@ } }, "node_modules/@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz", + "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -3452,14 +3393,14 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -3475,9 +3416,9 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, "engines": { "node": ">=6.0.0" @@ -3490,20 +3431,38 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true + "node_modules/@jsep-plugin/assignment": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz", + "integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==", + "dev": true, + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@jsep-plugin/regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz", + "integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==", + "dev": true, + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } }, "node_modules/@kwsites/file-exists": { "version": "1.1.1", @@ -3567,6 +3526,68 @@ "node": ">=10" } }, + "node_modules/@prisma/client": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.2.1.tgz", + "integrity": "sha512-msKY2iRLISN8t5X0Tj7hU0UWet1u0KuxSPHWuf3IRkB4J95mCvGpyQBfQ6ufcmvKNOMQSq90O2iUmJEN2e5fiA==", + "hasInstallScript": true, + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "node_modules/@prisma/debug": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.2.1.tgz", + "integrity": "sha512-0KItvt39CmQxWkEw6oW+RQMD6RZ43SJWgEUnzxN8VC9ixMysa7MzZCZf22LCK5DSooiLNf8vM3LHZm/I/Ni7bQ==", + "devOptional": true + }, + "node_modules/@prisma/engines": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.2.1.tgz", + "integrity": "sha512-lTBNLJBCxVT9iP5I7Mn6GlwqAxTpS5qMERrhebkUhtXpGVkBNd/jHnNJBZQW4kGDCKaQg/r2vlJYkzOHnAb7ZQ==", + "devOptional": true, + "hasInstallScript": true, + "dependencies": { + "@prisma/debug": "6.2.1", + "@prisma/engines-version": "6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69", + "@prisma/fetch-engine": "6.2.1", + "@prisma/get-platform": "6.2.1" + } + }, + "node_modules/@prisma/engines-version": { + "version": "6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69.tgz", + "integrity": "sha512-7tw1qs/9GWSX6qbZs4He09TOTg1ff3gYsB3ubaVNN0Pp1zLm9NC5C5MZShtkz7TyQjx7blhpknB7HwEhlG+PrQ==", + "devOptional": true + }, + "node_modules/@prisma/fetch-engine": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.2.1.tgz", + "integrity": "sha512-OO7O9d6Mrx2F9i+Gu1LW+DGXXyUFkP7OE5aj9iBfA/2jjDXEJjqa9X0ZmM9NZNo8Uo7ql6zKm6yjDcbAcRrw1A==", + "devOptional": true, + "dependencies": { + "@prisma/debug": "6.2.1", + "@prisma/engines-version": "6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69", + "@prisma/get-platform": "6.2.1" + } + }, + "node_modules/@prisma/get-platform": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.2.1.tgz", + "integrity": "sha512-zp53yvroPl5m5/gXYLz7tGCNG33bhG+JYCm74ohxOq1pPnrL47VQYFfF3RbTZ7TzGWCrR3EtoiYMywUBw7UK6Q==", + "devOptional": true, + "dependencies": { + "@prisma/debug": "6.2.1" + } + }, "node_modules/@serverless/dashboard-plugin": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/@serverless/dashboard-plugin/-/dashboard-plugin-6.2.3.tgz", @@ -3631,18 +3652,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@serverless/dashboard-plugin/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "peer": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@serverless/dashboard-plugin/node_modules/open": { "version": "7.4.2", "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", @@ -3660,13 +3669,10 @@ } }, "node_modules/@serverless/dashboard-plugin/node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "peer": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -3683,12 +3689,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/@serverless/dashboard-plugin/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "peer": true - }, "node_modules/@serverless/event-mocks": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@serverless/event-mocks/-/event-mocks-1.1.1.tgz", @@ -3700,14 +3700,14 @@ } }, "node_modules/@serverless/platform-client": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@serverless/platform-client/-/platform-client-4.3.2.tgz", - "integrity": "sha512-DAa5Z0JAZc6UfrTZLYwqoZxgAponZpFwaqd7WzzMA+loMCkYWyJNwxrAmV6cr2UUJpkko4toPZuJ3vM9Ie+NDA==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@serverless/platform-client/-/platform-client-4.5.1.tgz", + "integrity": "sha512-XltmO/029X76zi0LUFmhsnanhE2wnqH1xf+WBt5K8gumQA9LnrfwLgPxj+VA+mm6wQhy+PCp7H5SS0ZPu7F2Cw==", "peer": true, "dependencies": { "adm-zip": "^0.5.5", "archiver": "^5.3.0", - "axios": "^0.21.1", + "axios": "^1.6.2", "fast-glob": "^3.2.7", "https-proxy-agent": "^5.0.0", "ignore": "^5.1.8", @@ -3982,236 +3982,246 @@ } }, "node_modules/@smithy/abort-controller": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", - "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.1.tgz", + "integrity": "sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g==", "dev": true, "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/config-resolver": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.4.tgz", - "integrity": "sha512-VwiOk7TwXoE7NlNguV/aPq1hFH72tqkHCw8eWXbr2xHspRyyv9DLpLXhq+Ieje+NwoqXrY0xyQjPXdOE6cGcHA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.0.1.tgz", + "integrity": "sha512-Igfg8lKu3dRVkTSEm98QpZUvKEOa71jDX4vKRcvJVyRc3UgN3j7vFMf0s7xLQhYmKa8kyJGQgUJDOV5V3neVlQ==", "dev": true, "dependencies": { - "@smithy/node-config-provider": "^3.1.3", - "@smithy/types": "^3.3.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.3", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/core": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.2.5.tgz", - "integrity": "sha512-0kqyj93/Aa30TEXnnWRBetN8fDGjFF+u8cdIiMI8YS6CrUF2dLTavRfHKfWh5cL5d6s2ZNyEnLjBitdcKmkETQ==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.1.5.tgz", + "integrity": "sha512-HLclGWPkCsekQgsyzxLhCQLa8THWXtB5PxyYN+2O6nkyLt550KQKTlbV2D1/j5dNIQapAZM1+qFnpBFxZQkgCA==", "dev": true, "dependencies": { - "@smithy/middleware-endpoint": "^3.0.4", - "@smithy/middleware-retry": "^3.0.8", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/protocol-http": "^4.0.3", - "@smithy/smithy-client": "^3.1.6", - "@smithy/types": "^3.3.0", - "@smithy/util-middleware": "^3.0.3", + "@smithy/middleware-serde": "^4.0.2", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-stream": "^4.1.2", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/credential-provider-imds": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.1.3.tgz", - "integrity": "sha512-U1Yrv6hx/mRK6k8AncuI6jLUx9rn0VVSd9NPEX6pyYFBfkSkChOc/n4zUb8alHUVg83TbI4OdZVo1X0Zfj3ijA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.1.tgz", + "integrity": "sha512-l/qdInaDq1Zpznpmev/+52QomsJNZ3JkTl5yrTl02V6NBgJOQ4LY0SFw/8zsMwj3tLe8vqiIuwF6nxaEwgf6mg==", "dev": true, "dependencies": { - "@smithy/node-config-provider": "^3.1.3", - "@smithy/property-provider": "^3.1.3", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/property-provider": "^4.0.1", + "@smithy/types": "^4.1.0", + "@smithy/url-parser": "^4.0.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/eventstream-codec": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.2.tgz", - "integrity": "sha512-0mBcu49JWt4MXhrhRAlxASNy0IjDRFU+aWNDRal9OtUJvJNiwDuyKMUONSOjLjSCeGwZaE0wOErdqULer8r7yw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.0.1.tgz", + "integrity": "sha512-Q2bCAAR6zXNVtJgifsU16ZjKGqdw/DyecKNgIgi7dlqw04fqDu0mnq+JmGphqheypVc64CYq3azSuCpAdFk2+A==", "dev": true, "dependencies": { "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^3.3.0", - "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/types": "^4.1.0", + "@smithy/util-hex-encoding": "^4.0.0", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/eventstream-serde-browser": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.4.tgz", - "integrity": "sha512-Eo4anLZX6ltGJTZ5yJMc80gZPYYwBn44g0h7oFq6et+TYr5dUsTpIcDbz2evsOKIZhZ7zBoFWHtBXQ4QQeb5xA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.0.1.tgz", + "integrity": "sha512-HbIybmz5rhNg+zxKiyVAnvdM3vkzjE6ccrJ620iPL8IXcJEntd3hnBl+ktMwIy12Te/kyrSbUb8UCdnUT4QEdA==", "dev": true, "dependencies": { - "@smithy/eventstream-serde-universal": "^3.0.4", - "@smithy/types": "^3.3.0", + "@smithy/eventstream-serde-universal": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.3.tgz", - "integrity": "sha512-NVTYjOuYpGfrN/VbRQgn31x73KDLfCXCsFdad8DiIc3IcdxL+dYA9zEQPyOP7Fy2QL8CPy2WE4WCUD+ZsLNfaQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.0.1.tgz", + "integrity": "sha512-lSipaiq3rmHguHa3QFF4YcCM3VJOrY9oq2sow3qlhFY+nBSTF/nrO82MUQRPrxHQXA58J5G1UnU2WuJfi465BA==", "dev": true, "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/eventstream-serde-node": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.4.tgz", - "integrity": "sha512-mjlG0OzGAYuUpdUpflfb9zyLrBGgmQmrobNT8b42ZTsGv/J03+t24uhhtVEKG/b2jFtPIHF74Bq+VUtbzEKOKg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.0.1.tgz", + "integrity": "sha512-o4CoOI6oYGYJ4zXo34U8X9szDe3oGjmHgsMGiZM0j4vtNoT+h80TLnkUcrLZR3+E6HIxqW+G+9WHAVfl0GXK0Q==", "dev": true, "dependencies": { - "@smithy/eventstream-serde-universal": "^3.0.4", - "@smithy/types": "^3.3.0", + "@smithy/eventstream-serde-universal": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/eventstream-serde-universal": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.4.tgz", - "integrity": "sha512-Od9dv8zh3PgOD7Vj4T3HSuox16n0VG8jJIM2gvKASL6aCtcS8CfHZDWe1Ik3ZXW6xBouU+45Q5wgoliWDZiJ0A==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.0.1.tgz", + "integrity": "sha512-Z94uZp0tGJuxds3iEAZBqGU2QiaBHP4YytLUjwZWx+oUeohCsLyUm33yp4MMBmhkuPqSbQCXq5hDet6JGUgHWA==", "dev": true, "dependencies": { - "@smithy/eventstream-codec": "^3.1.2", - "@smithy/types": "^3.3.0", + "@smithy/eventstream-codec": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/fetch-http-handler": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.1.tgz", - "integrity": "sha512-0w0bgUvZmfa0vHN8a+moByhCJT07WN6AHKEhFSOLsDpnszm+5dLVv5utGaqbhOrZ/aF5x3xuPMs/oMCd+4O5xg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.1.tgz", + "integrity": "sha512-3aS+fP28urrMW2KTjb6z9iFow6jO8n3MFfineGbndvzGZit3taZhKWtTorf+Gp5RpFDDafeHlhfsGlDCXvUnJA==", "dev": true, "dependencies": { - "@smithy/protocol-http": "^4.0.3", - "@smithy/querystring-builder": "^3.0.3", - "@smithy/types": "^3.3.0", - "@smithy/util-base64": "^3.0.0", + "@smithy/protocol-http": "^5.0.1", + "@smithy/querystring-builder": "^4.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-base64": "^4.0.0", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/hash-node": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.3.tgz", - "integrity": "sha512-2ctBXpPMG+B3BtWSGNnKELJ7SH9e4TNefJS0cd2eSkOOROeBnnVBnAy9LtJ8tY4vUEoe55N4CNPxzbWvR39iBw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.1.tgz", + "integrity": "sha512-TJ6oZS+3r2Xu4emVse1YPB3Dq3d8RkZDKcPr71Nj/lJsdAP1c7oFzYqEn1IBc915TsgLl2xIJNuxCz+gLbLE0w==", "dev": true, "dependencies": { - "@smithy/types": "^3.3.0", - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", + "@smithy/types": "^4.1.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/invalid-dependency": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.3.tgz", - "integrity": "sha512-ID1eL/zpDULmHJbflb864k72/SNOZCADRc9i7Exq3RUNJw6raWUSlFEQ+3PX3EYs++bTxZB2dE9mEHTQLv61tw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.1.tgz", + "integrity": "sha512-gdudFPf4QRQ5pzj7HEnu6FhKRi61BfH/Gk5Yf6O0KiSbr1LlVhgjThcvjdu658VE6Nve8vaIWB8/fodmS1rBPQ==", "dev": true, "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/is-array-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", - "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", "dev": true, "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/middleware-content-length": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.3.tgz", - "integrity": "sha512-Dbz2bzexReYIQDWMr+gZhpwBetNXzbhnEMhYKA6urqmojO14CsXjnsoPYO8UL/xxcawn8ZsuVU61ElkLSltIUQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.1.tgz", + "integrity": "sha512-OGXo7w5EkB5pPiac7KNzVtfCW2vKBTZNuCctn++TTSOMpe6RZO/n6WEC1AxJINn3+vWLKW49uad3lo/u0WJ9oQ==", "dev": true, "dependencies": { - "@smithy/protocol-http": "^4.0.3", - "@smithy/types": "^3.3.0", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/middleware-endpoint": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.0.4.tgz", - "integrity": "sha512-whUJMEPwl3ANIbXjBXZVdJNgfV2ZU8ayln7xUM47rXL2txuenI7jQ/VFFwCzy5lCmXScjp6zYtptW5Evud8e9g==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.0.6.tgz", + "integrity": "sha512-ftpmkTHIFqgaFugcjzLZv3kzPEFsBFSnq1JsIkr2mwFzCraZVhQk2gqN51OOeRxqhbPTkRFj39Qd2V91E/mQxg==", "dev": true, "dependencies": { - "@smithy/middleware-serde": "^3.0.3", - "@smithy/node-config-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.3", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-middleware": "^3.0.3", + "@smithy/core": "^3.1.5", + "@smithy/middleware-serde": "^4.0.2", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", + "@smithy/url-parser": "^4.0.1", + "@smithy/util-middleware": "^4.0.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/middleware-retry": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.8.tgz", - "integrity": "sha512-wmIw3t6ZbeqstUFdXtStzSSltoYrcfc28ndnr0mDSMmtMSRNduNbmneA7xiE224fVFXzbf24+0oREks1u2X7Mw==", - "dev": true, - "dependencies": { - "@smithy/node-config-provider": "^3.1.3", - "@smithy/protocol-http": "^4.0.3", - "@smithy/service-error-classification": "^3.0.3", - "@smithy/smithy-client": "^3.1.6", - "@smithy/types": "^3.3.0", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.0.7.tgz", + "integrity": "sha512-58j9XbUPLkqAcV1kHzVX/kAR16GT+j7DUZJqwzsxh1jtz7G82caZiGyyFgUvogVfNTg3TeAOIJepGc8TXF4AVQ==", + "dev": true, + "dependencies": { + "@smithy/node-config-provider": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/service-error-classification": "^4.0.1", + "@smithy/smithy-client": "^4.1.6", + "@smithy/types": "^4.1.0", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-retry": "^4.0.1", "tslib": "^2.6.2", "uuid": "^9.0.1" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/middleware-retry/node_modules/uuid": { @@ -4228,401 +4238,409 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", - "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.2.tgz", + "integrity": "sha512-Sdr5lOagCn5tt+zKsaW+U2/iwr6bI9p08wOkCp6/eL6iMbgdtc2R5Ety66rf87PeohR0ExI84Txz9GYv5ou3iQ==", "dev": true, "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/middleware-stack": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", - "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.1.tgz", + "integrity": "sha512-dHwDmrtR/ln8UTHpaIavRSzeIk5+YZTBtLnKwDW3G2t6nAupCiQUvNzNoHBpik63fwUaJPtlnMzXbQrNFWssIA==", "dev": true, "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/node-config-provider": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.3.tgz", - "integrity": "sha512-rxdpAZczzholz6CYZxtqDu/aKTxATD5DAUDVj7HoEulq+pDSQVWzbg0btZDlxeFfa6bb2b5tUvgdX5+k8jUqcg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.0.1.tgz", + "integrity": "sha512-8mRTjvCtVET8+rxvmzRNRR0hH2JjV0DFOmwXPrISmTIJEfnCBugpYYGAsCj8t41qd+RB5gbheSQ/6aKZCQvFLQ==", "dev": true, "dependencies": { - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.3", - "@smithy/types": "^3.3.0", + "@smithy/property-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/node-http-handler": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.2.tgz", - "integrity": "sha512-Td3rUNI7qqtoSLTsJBtsyfoG4cF/XMFmJr6Z2dX8QNzIi6tIW6YmuyFml8mJ2cNpyWNqITKbROMOFrvQjmsOvw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.3.tgz", + "integrity": "sha512-dYCLeINNbYdvmMLtW0VdhW1biXt+PPCGazzT5ZjKw46mOtdgToQEwjqZSS9/EN8+tNs/RO0cEWG044+YZs97aA==", "dev": true, "dependencies": { - "@smithy/abort-controller": "^3.1.1", - "@smithy/protocol-http": "^4.0.3", - "@smithy/querystring-builder": "^3.0.3", - "@smithy/types": "^3.3.0", + "@smithy/abort-controller": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/querystring-builder": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/property-provider": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.3.tgz", - "integrity": "sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.1.tgz", + "integrity": "sha512-o+VRiwC2cgmk/WFV0jaETGOtX16VNPp2bSQEzu0whbReqE1BMqsP2ami2Vi3cbGVdKu1kq9gQkDAGKbt0WOHAQ==", "dev": true, "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/protocol-http": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.0.3.tgz", - "integrity": "sha512-x5jmrCWwQlx+Zv4jAtc33ijJ+vqqYN+c/ZkrnpvEe/uDas7AT7A/4Rc2CdfxgWv4WFGmEqODIrrUToPN6DDkGw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.0.1.tgz", + "integrity": "sha512-TE4cpj49jJNB/oHyh/cRVEgNZaoPaxd4vteJNB0yGidOCVR0jCw/hjPVsT8Q8FRmj8Bd3bFZt8Dh7xGCT+xMBQ==", "dev": true, "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/querystring-builder": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", - "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.1.tgz", + "integrity": "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg==", "dev": true, "dependencies": { - "@smithy/types": "^3.3.0", - "@smithy/util-uri-escape": "^3.0.0", + "@smithy/types": "^4.1.0", + "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/querystring-parser": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", - "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.1.tgz", + "integrity": "sha512-Ma2XC7VS9aV77+clSFylVUnPZRindhB7BbmYiNOdr+CHt/kZNJoPP0cd3QxCnCFyPXC4eybmyE98phEHkqZ5Jw==", "dev": true, "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/service-error-classification": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.3.tgz", - "integrity": "sha512-Jn39sSl8cim/VlkLsUhRFq/dKDnRUFlfRkvhOJaUbLBXUsLRLNf9WaxDv/z9BjuQ3A6k/qE8af1lsqcwm7+DaQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.1.tgz", + "integrity": "sha512-3JNjBfOWpj/mYfjXJHB4Txc/7E4LVq32bwzE7m28GN79+M1f76XHflUaSUkhOriprPDzev9cX/M+dEB80DNDKA==", "dev": true, "dependencies": { - "@smithy/types": "^3.3.0" + "@smithy/types": "^4.1.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.3.tgz", - "integrity": "sha512-Z8Y3+08vgoDgl4HENqNnnzSISAaGrF2RoKupoC47u2wiMp+Z8P/8mDh1CL8+8ujfi2U5naNvopSBmP/BUj8b5w==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.1.tgz", + "integrity": "sha512-hC8F6qTBbuHRI/uqDgqqi6J0R4GtEZcgrZPhFQnMhfJs3MnUTGSnR1NSJCJs5VWlMydu0kJz15M640fJlRsIOw==", "dev": true, "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/signature-v4": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-3.1.2.tgz", - "integrity": "sha512-3BcPylEsYtD0esM4Hoyml/+s7WP2LFhcM3J2AGdcL2vx9O60TtfpDOL72gjb4lU8NeRPeKAwR77YNyyGvMbuEA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.0.1.tgz", + "integrity": "sha512-nCe6fQ+ppm1bQuw5iKoeJ0MJfz2os7Ic3GBjOkLOPtavbD1ONoyE3ygjBfz2ythFWm4YnRm6OxW+8p/m9uCoIA==", "dev": true, "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "@smithy/types": "^3.3.0", - "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-uri-escape": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/smithy-client": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.1.6.tgz", - "integrity": "sha512-w9oboI661hfptr26houZ5mdKc//DMxkuOMXSaIiALqGn4bHYT9S4U69BBS6tHX4TZHgShmhcz0d6aXk7FY5soA==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.1.6.tgz", + "integrity": "sha512-UYDolNg6h2O0L+cJjtgSyKKvEKCOa/8FHYJnBobyeoeWDmNpXjwOAtw16ezyeu1ETuuLEOZbrynK0ZY1Lx9Jbw==", "dev": true, "dependencies": { - "@smithy/middleware-endpoint": "^3.0.4", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/protocol-http": "^4.0.3", - "@smithy/types": "^3.3.0", - "@smithy/util-stream": "^3.0.6", + "@smithy/core": "^3.1.5", + "@smithy/middleware-endpoint": "^4.0.6", + "@smithy/middleware-stack": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-stream": "^4.1.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/types": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", - "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", + "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", "dev": true, "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/url-parser": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", - "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.1.tgz", + "integrity": "sha512-gPXcIEUtw7VlK8f/QcruNXm7q+T5hhvGu9tl63LsJPZ27exB6dtNwvh2HIi0v7JcXJ5emBxB+CJxwaLEdJfA+g==", "dev": true, "dependencies": { - "@smithy/querystring-parser": "^3.0.3", - "@smithy/types": "^3.3.0", + "@smithy/querystring-parser": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/util-base64": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", - "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", "dev": true, "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-body-length-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", - "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", "dev": true, "dependencies": { "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/util-body-length-node": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", - "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", "dev": true, "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", - "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", "dev": true, "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", + "@smithy/is-array-buffer": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-config-provider": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", - "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", "dev": true, "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.8.tgz", - "integrity": "sha512-eLRHCvM1w3ZJkYcd60yKqM3d70dPB+071EDpf9ZGYqFed3xcm/+pWwNS/xM0JXRrjm0yAA19dWcdFN2IE/66pQ==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.7.tgz", + "integrity": "sha512-CZgDDrYHLv0RUElOsmZtAnp1pIjwDVCSuZWOPhIOBvG36RDfX1Q9+6lS61xBf+qqvHoqRjHxgINeQz47cYFC2Q==", "dev": true, "dependencies": { - "@smithy/property-provider": "^3.1.3", - "@smithy/smithy-client": "^3.1.6", - "@smithy/types": "^3.3.0", + "@smithy/property-provider": "^4.0.1", + "@smithy/smithy-client": "^4.1.6", + "@smithy/types": "^4.1.0", "bowser": "^2.11.0", "tslib": "^2.6.2" }, "engines": { - "node": ">= 10.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.8.tgz", - "integrity": "sha512-Tajvdyg5+k77j6AOrwSCZgi7KdBizqPNs3HCnFGRoxDjzh+CjPLaLrXbIRB0lsAmqYmRHIU34IogByaqvDrkBQ==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.7.tgz", + "integrity": "sha512-79fQW3hnfCdrfIi1soPbK3zmooRFnLpSx3Vxi6nUlqaaQeC5dm8plt4OTNDNqEEEDkvKghZSaoti684dQFVrGQ==", "dev": true, "dependencies": { - "@smithy/config-resolver": "^3.0.4", - "@smithy/credential-provider-imds": "^3.1.3", - "@smithy/node-config-provider": "^3.1.3", - "@smithy/property-provider": "^3.1.3", - "@smithy/smithy-client": "^3.1.6", - "@smithy/types": "^3.3.0", + "@smithy/config-resolver": "^4.0.1", + "@smithy/credential-provider-imds": "^4.0.1", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/property-provider": "^4.0.1", + "@smithy/smithy-client": "^4.1.6", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">= 10.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-endpoints": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.0.4.tgz", - "integrity": "sha512-ZAtNf+vXAsgzgRutDDiklU09ZzZiiV/nATyqde4Um4priTmasDH+eLpp3tspL0hS2dEootyFMhu1Y6Y+tzpWBQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.1.tgz", + "integrity": "sha512-zVdUENQpdtn9jbpD9SCFK4+aSiavRb9BxEtw9ZGUR1TYo6bBHbIoi7VkrFQ0/RwZlzx0wRBaRmPclj8iAoJCLA==", "dev": true, "dependencies": { - "@smithy/node-config-provider": "^3.1.3", - "@smithy/types": "^3.3.0", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-hex-encoding": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", - "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", "dev": true, "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-middleware": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", - "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.1.tgz", + "integrity": "sha512-HiLAvlcqhbzhuiOa0Lyct5IIlyIz0PQO5dnMlmQ/ubYM46dPInB+3yQGkfxsk6Q24Y0n3/JmcA1v5iEhmOF5mA==", "dev": true, "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-retry": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.3.tgz", - "integrity": "sha512-AFw+hjpbtVApzpNDhbjNG5NA3kyoMs7vx0gsgmlJF4s+yz1Zlepde7J58zpIRIsdjc+emhpAITxA88qLkPF26w==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.1.tgz", + "integrity": "sha512-WmRHqNVwn3kI3rKk1LsKcVgPBG6iLTBGC1iYOV3GQegwJ3E8yjzHytPt26VNzOWr1qu0xE03nK0Ug8S7T7oufw==", "dev": true, "dependencies": { - "@smithy/service-error-classification": "^3.0.3", - "@smithy/types": "^3.3.0", + "@smithy/service-error-classification": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-stream": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.0.6.tgz", - "integrity": "sha512-w9i//7egejAIvplX821rPWWgaiY1dxsQUw0hXX7qwa/uZ9U3zplqTQ871jWadkcVB9gFDhkPWYVZf4yfFbZ0xA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.1.2.tgz", + "integrity": "sha512-44PKEqQ303d3rlQuiDpcCcu//hV8sn+u2JBo84dWCE0rvgeiVl0IlLMagbU++o0jCWhYCsHaAt9wZuZqNe05Hw==", "dev": true, "dependencies": { - "@smithy/fetch-http-handler": "^3.2.1", - "@smithy/node-http-handler": "^3.1.2", - "@smithy/types": "^3.3.0", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", + "@smithy/fetch-http-handler": "^5.0.1", + "@smithy/node-http-handler": "^4.0.3", + "@smithy/types": "^4.1.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-uri-escape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", - "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", "dev": true, "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", - "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", "dev": true, "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-buffer-from": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-waiter": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.2.tgz", - "integrity": "sha512-4pP0EV3iTsexDx+8PPGAKCQpd/6hsQBaQhqWzU4hqKPHN5epPsxKbvUTIiYIHTxaKt6/kEaqPBpu/ufvfbrRzw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.0.2.tgz", + "integrity": "sha512-piUTHyp2Axx3p/kc2CIJkYSv0BAaheBQmbACZgQSSfWUumWNW+R1lL+H9PDBxKJkvOeEX+hKYEFiwO8xagL8AQ==", "dev": true, "dependencies": { - "@smithy/abort-controller": "^3.1.1", - "@smithy/types": "^3.3.0", + "@smithy/abort-controller": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@szmarczak/http-timer": { @@ -4770,10 +4788,9 @@ } }, "node_modules/@types/lodash": { - "version": "4.14.195", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz", - "integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==", - "peer": true + "version": "4.17.16", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.16.tgz", + "integrity": "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==" }, "node_modules/@types/minimatch": { "version": "5.1.2", @@ -4920,6 +4937,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "peer": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -5132,12 +5150,14 @@ "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" }, "node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz", + "integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==", "peer": true, "dependencies": { - "follow-redirects": "^1.14.0" + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "node_modules/babel-jest": { @@ -5567,11 +5587,11 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -5788,6 +5808,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "peer": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -5826,9 +5847,9 @@ } }, "node_modules/child-process-ext/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", "peer": true, "dependencies": { "nice-try": "^1.0.4", @@ -5851,9 +5872,9 @@ } }, "node_modules/child-process-ext/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "peer": true, "bin": { "semver": "bin/semver" @@ -6109,6 +6130,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "peer": true, "dependencies": { "color-name": "1.1.3" } @@ -6116,7 +6138,8 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "peer": true }, "node_modules/combined-stream": { "version": "1.0.8", @@ -6241,9 +6264,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "dependencies": { "path-key": "^3.1.0", @@ -6763,20 +6786,36 @@ } }, "node_modules/es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", "hasInstallScript": true, "peer": true, "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", "next-tick": "^1.1.0" }, "engines": { "node": ">=0.10" } }, + "node_modules/es5-ext/node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "peer": true, + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/es6-iterator": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", @@ -6840,6 +6879,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "peer": true, "engines": { "node": ">=0.8.0" } @@ -7032,18 +7072,18 @@ "peer": true }, "node_modules/fast-xml-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz", - "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", "dev": true, "funding": [ - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - }, { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" } ], "dependencies": { @@ -7156,9 +7196,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -7202,9 +7242,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "funding": [ { "type": "individual", @@ -7337,9 +7377,9 @@ } }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "hasInstallScript": true, "optional": true, "os": [ @@ -7547,6 +7587,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "peer": true, "engines": { "node": ">=4" } @@ -9397,26 +9438,11 @@ "node": ">=8" } }, - "node_modules/jest-snapshot/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -9436,12 +9462,6 @@ "node": ">=8" } }, - "node_modules/jest-snapshot/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/jest-util": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", @@ -9765,9 +9785,9 @@ } }, "node_modules/jose": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-5.6.3.tgz", - "integrity": "sha512-1Jh//hEEwMhNYPDDLwXHa2ePWgWiFNNUadVmguAAw2IJ6sj9mNxV5tGXJNqlMkJAybF6Lgw1mISDxTePP/187g==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", + "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", "dev": true, "funding": { "url": "https://github.com/sponsors/panva" @@ -9800,16 +9820,25 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsep": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", + "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", + "dev": true, + "engines": { + "node": ">= 10.16.0" + } + }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-buffer": { @@ -9904,16 +9933,21 @@ } }, "node_modules/jsonpath-plus": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-8.1.0.tgz", - "integrity": "sha512-qVTiuKztFGw0dGhYi3WNqvddx3/SHtyDT0xJaeyz4uP0d1tkpG+0y5uYQ4OcIo1TLAz3PE/qDOW9F0uDt3+CTw==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.3.0.tgz", + "integrity": "sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==", "dev": true, + "dependencies": { + "@jsep-plugin/assignment": "^1.3.0", + "@jsep-plugin/regex": "^1.0.4", + "jsep": "^1.4.0" + }, "bin": { "jsonpath": "bin/jsonpath-cli.js", "jsonpath-plus": "bin/jsonpath-cli.js" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, "node_modules/jsonschema": { @@ -10292,9 +10326,9 @@ } }, "node_modules/luxon": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", - "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz", + "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==", "dev": true, "engines": { "node": ">=12" @@ -10364,11 +10398,11 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -10642,26 +10676,11 @@ "node": ">=12.0" } }, - "node_modules/npm-registry-utilities/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "peer": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/npm-registry-utilities/node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "peer": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -10669,12 +10688,6 @@ "node": ">=10" } }, - "node_modules/npm-registry-utilities/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "peer": true - }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -11198,6 +11211,25 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prisma": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.2.1.tgz", + "integrity": "sha512-hhyM0H13pQleQ+br4CkzGizS5I0oInoeTw3JfLw1BRZduBSQxPILlJLwi+46wZzj9Je7ndyQEMGw/n5cN2fknA==", + "devOptional": true, + "hasInstallScript": true, + "dependencies": { + "@prisma/engines": "6.2.1" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=18.18" + }, + "optionalDependencies": { + "fsevents": "2.3.3" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -11240,6 +11272,12 @@ "node": ">= 6" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "peer": true + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -11702,9 +11740,9 @@ "peer": true }, "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -11783,15 +11821,15 @@ } }, "node_modules/serverless-offline": { - "version": "13.6.0", - "resolved": "https://registry.npmjs.org/serverless-offline/-/serverless-offline-13.6.0.tgz", - "integrity": "sha512-aSMg9TNfFjVj4QFmHqPzLwbwCSVmrpeoe96ozOXaesn2VX+4lyS+kTF1TXh4pZd5/ef9cfcma3jcqmgnShrUyA==", + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/serverless-offline/-/serverless-offline-13.9.0.tgz", + "integrity": "sha512-r/GU1FcgUbrhUzPJ5BOpni6JXhHVhxksYQH8d3S4alx4IZQo3E9Q8fT2z8Mvh+d0rfQ1Y0kHvyeKJ126rleGfg==", "dev": true, "dependencies": { - "@aws-sdk/client-lambda": "^3.509.0", + "@aws-sdk/client-lambda": "^3.636.0", "@hapi/boom": "^10.0.1", "@hapi/h2o2": "^10.0.4", - "@hapi/hapi": "^21.3.3", + "@hapi/hapi": "^21.3.10", "array-unflat-js": "^0.1.3", "boxen": "^7.1.1", "chalk": "^5.3.0", @@ -11800,16 +11838,16 @@ "fs-extra": "^11.2.0", "is-wsl": "^3.1.0", "java-invoke-local": "0.0.6", - "jose": "^5.2.1", + "jose": "^5.7.0", "js-string-escape": "^1.0.1", - "jsonpath-plus": "^8.0.0", + "jsonpath-plus": "^10.2.0", "jsonschema": "^1.4.1", "jszip": "^3.10.1", - "luxon": "^3.4.4", + "luxon": "^3.5.0", "node-schedule": "^2.1.1", "p-memoize": "^7.1.1", "velocityjs": "^2.0.6", - "ws": "^8.16.0" + "ws": "^8.18.0" }, "engines": { "node": ">=18.12.0" @@ -12166,26 +12204,11 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/serverless/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "peer": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/serverless/node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "peer": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -12217,12 +12240,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/serverless/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "peer": true - }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -12517,10 +12534,16 @@ } }, "node_modules/strnum": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ] }, "node_modules/strtok3": { "version": "6.3.0", @@ -12562,26 +12585,11 @@ "node": ">=6.4.0 <13 || >=14" } }, - "node_modules/superagent/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "peer": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/superagent/node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "peer": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -12589,16 +12597,11 @@ "node": ">=10" } }, - "node_modules/superagent/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "peer": true - }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "peer": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -12619,9 +12622,9 @@ } }, "node_modules/tar": { - "version": "6.1.15", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", - "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "peer": true, "dependencies": { "chownr": "^2.0.0", @@ -12717,15 +12720,6 @@ "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", "peer": true }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -12833,9 +12827,9 @@ } }, "node_modules/typescript": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", - "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13269,9 +13263,9 @@ } }, "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "peer": true, "engines": { "node": ">=8.3.0" diff --git a/package.json b/package.json index aa89baa..82ea4ac 100644 --- a/package.json +++ b/package.json @@ -5,12 +5,12 @@ "main": "src/handler.ts", "dependencies": { "@opensearch-project/opensearch": "^1.2.0", + "@prisma/client": "^6.2.1", "aws-lambda": "^1.0.7", "date-fns": "^2.30.0", "fp-ts": "^2.16.0", "monocle-ts": "^2.3.13", - "serverless-plugin-typescript": "^2.1.5", - "typescript": "5.1.3" + "serverless-plugin-typescript": "^2.1.5" }, "devDependencies": { "@babel/core": "^7.22.5", @@ -18,11 +18,14 @@ "@babel/preset-typescript": "^7.22.5", "@types/aws-lambda": "^8.10.117", "@types/jest": "^29.5.2", + "@types/lodash": "^4.17.16", "@types/node": "^20.3.0", "babel-jest": "^29.5.0", "jest": "^29.5.0", "prettier": "2.8.8", - "serverless-offline": "^13.3.4" + "prisma": "^6.2.1", + "serverless-offline": "^13.3.4", + "typescript": "^5.8.2" }, "scripts": { "test": "jest", diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..b578023 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,521 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +/// This table has subclasses and requires additional setup for migrations. Visit https://pris.ly/d/table-inheritance for more info. +model abstract_blocks { + hash String @id(map: "block_pkey") @db.VarChar + height BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + block_parents block_parents[] + dag_blocks dag_blocks? @relation(fields: [hash], references: [hash], map: "dag_blocks_hash_fk") + metagraph_blocks metagraph_blocks? @relation(fields: [hash], references: [hash], map: "metagraph_blocks_hash_fk") +} + +/// This table has subclasses and requires additional setup for migrations. Visit https://pris.ly/d/table-inheritance for more info. +model abstract_transactions { + hash String @id(map: "hash_pkey") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) +} + +model abstract_transactions_view { + hash String @id @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + table_name String + + dag_token_lock dag_token_locks? @relation(fields: [hash], references: [hash], map: "dag_token_locks_ref") + metagraph_token_lock metagraph_token_locks? @relation(fields: [hash], references: [hash], map: "metagraph_token_locks_ref") + + dag_token_unlock dag_token_unlocks? @relation(fields: [hash], references: [hash], map: "dag_token_unlocks_ref") + metagraph_token_unlock metagraph_token_unlocks? @relation(fields: [hash], references: [hash], map: "metagraph_token_unlocks_ref") + + dag_allow_spend dag_allow_spends? @relation(fields: [hash], references: [hash], map: "dag_allow_spend_ref") + metagraph_allow_spend metagraph_allow_spends? @relation(fields: [hash], references: [hash], map: "metagraph_allow_spend_ref") + + dag_spend_transaction dag_spend_transactions? @relation(fields: [hash], references: [hash], map: "dag_spend_transaction_ref") + metagraph_spend_transaction metagraph_spend_transactions? @relation(fields: [hash], references: [hash], map: "metagraph_spend_transaction_ref") + + metagraph_fee_transaction metagraph_fee_transactions? @relation(fields: [hash], references: [hash], map: "metagraph_fee_transaction_ref") +} + +model addresses { + address String @id(map: "address_pkey") @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + dag_allow_spend_approvers dag_allow_spend_approvers[] + dag_allow_spends_dag_allow_spends_destination_addrToaddresses dag_allow_spends[] @relation("dag_allow_spends_destination_addrToaddresses") + dag_allow_spends_dag_allow_spends_source_addrToaddresses dag_allow_spends[] @relation("dag_allow_spends_source_addrToaddresses") + dag_balance_changes dag_balance_changes[] + dag_reward_transactions dag_reward_transactions[] + dag_spend_transactions dag_spend_transactions[] + dag_token_locks dag_token_locks[] + dag_token_unlocks dag_token_unlocks[] + dag_transactions_dag_transactions_destination_addrToaddresses dag_transactions[] @relation("dag_transactions_destination_addrToaddresses") + dag_transactions_dag_transactions_source_addrToaddresses dag_transactions[] @relation("dag_transactions_source_addrToaddresses") + metagraph_allow_spend_approvers metagraph_allow_spend_approvers[] + metagraph_allow_spends_metagraph_allow_spends_destination_addrToaddresses metagraph_allow_spends[] @relation("metagraph_allow_spends_destination_addrToaddresses") + metagraph_allow_spends_metagraph_allow_spends_source_addrToaddresses metagraph_allow_spends[] @relation("metagraph_allow_spends_source_addrToaddresses") + metagraph_balance_changes metagraph_balance_changes[] + metagraph_fee_transactions_metagraph_fee_transactions_destination_addrToaddresses metagraph_fee_transactions[] @relation("metagraph_fee_transactions_destination_addrToaddresses") + metagraph_fee_transactions_metagraph_fee_transactions_source_addrToaddresses metagraph_fee_transactions[] @relation("metagraph_fee_transactions_source_addrToaddresses") + metagraph_reward_transactions metagraph_reward_transactions[] + metagraph_snapshots_metagraph_snapshots_owner_addressToaddresses metagraph_snapshots[] @relation("metagraph_snapshots_owner_addressToaddresses") + metagraph_snapshots_metagraph_snapshots_staking_addressToaddresses metagraph_snapshots[] @relation("metagraph_snapshots_staking_addressToaddresses") + metagraph_spend_transactions metagraph_spend_transactions[] + metagraph_token_locks metagraph_token_locks[] + metagraph_token_unlocks metagraph_token_unlocks[] + metagraph_transactions_metagraph_transactions_destination_addrToaddresses metagraph_transactions[] @relation("metagraph_transactions_destination_addrToaddresses") + metagraph_transactions_metagraph_transactions_source_addrToaddresses metagraph_transactions[] @relation("metagraph_transactions_source_addrToaddresses") +} + +model block_parents { + hash String @db.VarChar + parent_proof_hash String @db.VarChar + parent_height BigInt + abstract_blocks abstract_blocks @relation(fields: [hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "block_parents_block_fk") + + @@id([hash, parent_proof_hash]) +} + +model dag_allow_spend_approvers { + allow_spend_hash String @db.VarChar + approver_address String @db.VarChar + addresses addresses @relation(fields: [approver_address], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "dag_allow_spend_approvers_address_fk") + dag_allow_spends dag_allow_spends @relation(fields: [allow_spend_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "dag_allow_spends_fk") + + @@id([allow_spend_hash, approver_address], map: "dag_allow_spend_approvers_pk") +} + +model dag_allow_spend_blocks { + round_id String @unique(map: "dag_allow_spend_blocks_unique") @db.Uuid + global_snapshot_hash String @id @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + global_snapshot global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "dag_allow_spend_blocks_global_snapshot_fk") + dag_allow_spends dag_allow_spends[] +} + +model dag_allow_spends { + hash String @id(map: "dag_allow_spends_pk") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + destination_addr String @db.VarChar + fee BigInt + parent_ordinal BigInt? + parent_hash String? @db.VarChar + last_valid_epoch_progress BigInt + round_id String @db.Uuid + ordinal BigInt @unique(map: "dag_allow_spends_ordinal") + dag_allow_spend_approvers dag_allow_spend_approvers[] + dag_allow_spend_block dag_allow_spend_blocks @relation(fields: [round_id], references: [round_id], onDelete: NoAction, onUpdate: NoAction, map: "allow_spends_block_fk") + addresses_dag_allow_spends_destination_addrToaddresses addresses @relation("dag_allow_spends_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "dag_allow_spends_destination_addr_fk") + addresses_dag_allow_spends_source_addrToaddresses addresses @relation("dag_allow_spends_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "dag_allow_spends_source_addr_fk") + dag_spend_transactions dag_spend_transactions[] + abstract_transactions_view abstract_transactions_view[] +} + +model dag_balance_changes { + snapshot_hash String @db.VarChar + address String @db.VarChar + balance BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + snapshot_ordinal BigInt + addresses addresses @relation(fields: [address], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "dag_balance_change_address_fk") + global_snapshots global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "dag_balance_change_global_snapshot_fk") + + @@id([snapshot_ordinal, address], map: "dag_balance_change_pk") +} + +model dag_blocks { + hash String @id(map: "dag_block_pk") @db.VarChar + height BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + snapshot_hash String @db.VarChar + global_snapshots global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "dag_block_global_snapshot_fk") + dag_transactions dag_transactions[] + super abstract_blocks? +} + +model dag_reward_transactions { + global_snapshot_hash String @db.VarChar + destination_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + global_snapshots global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "dag_reward_transaction_global_snapshot_fk") + addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "dag_reward_transactions_destination_addr_fk") + + @@id([global_snapshot_hash, destination_addr], map: "dag_reward_transaction_pk") +} + +model dag_spend_transactions { + hash String @id(map: "dag_spend_transactions_pk") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + destination_addr String? @db.VarChar + dag_allow_spend dag_allow_spends @relation(fields: [source_addr], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "dag_spend_transactions_dag_allow_spends_fk") + addresses addresses? @relation(fields: [destination_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "dag_spend_transactions_destination_addr_fk") + abstract_transactions_view abstract_transactions_view[] +} + +model dag_token_lock_blocks { + round_id String @id(map: "dag_token_lock_blocks_pk") @db.Uuid + global_snapshot_hash String @unique(map: "dag_token_lock_blocks_unique") @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + global_snapshot global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "dag_token_lock_blocks_global_snapshot_fk") + dag_token_locks dag_token_locks[] +} + +model dag_token_locks { + hash String @id(map: "dag_token_locks_pk") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + ordinal BigInt + unlock_epoch BigInt + round_id String + dag_token_lock_block dag_token_lock_blocks @relation(fields: [round_id], references: [round_id], onDelete: NoAction, onUpdate: NoAction, map: "token_lock_block_fk") + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "dag_token_locks_source_addr_fk") + dag_token_unlock dag_token_unlocks? + abstract_transactions_view abstract_transactions_view[] + global_snapshots global_snapshots? @relation(fields: [global_snapshotsHash], references: [hash]) + global_snapshotsHash String? @db.VarChar + + @@unique([hash], map: "dag_token_locks_unique") +} + +model dag_token_unlocks { + hash String @db.VarChar + lock_reference_ordinal BigInt + lock_reference_hash String @db.VarChar + amount BigInt + address String @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + dag_token_lock dag_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "dag_token_unlocks_token_locks_fk") + addresses addresses @relation(fields: [address], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "ddag_token_unlocks_address_fk") + abstract_transactions_view abstract_transactions_view[] + + @@id([hash], map: "dag_token_unlocks_pk") + @@unique([lock_reference_hash]) + @@unique([lock_reference_ordinal]) +} + +model dag_transactions { + hash String @id(map: "dag_transaction_pk") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + destination_addr String @db.VarChar + fee BigInt + salt BigInt + parent_ordinal BigInt? + parent_hash String? @db.VarChar + ordinal BigInt + block_hash String @db.VarChar + dag_blocks dag_blocks @relation(fields: [block_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "dag_transaction_dag_block_fk") + addresses_dag_transactions_destination_addrToaddresses addresses @relation("dag_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "dag_transactions_destination_addr_fk") + addresses_dag_transactions_source_addrToaddresses addresses @relation("dag_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "dag_transactions_source_addr_fk") +} + +model global_snapshot_proofs { + id String @db.VarChar + signature String @db.VarChar + snapshot_hash String @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + global_snapshots global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "proof_global_snapshot_fk") + + @@id([snapshot_hash, id], map: "proof_pk") +} + +model global_snapshots { + hash String @id(map: "global_snapshot_pk") @db.VarChar + ordinal BigInt @unique(map: "global_snapshot_unique") + height BigInt + subheight Int + last_snapshot_hash String @db.VarChar + metagraph_snapshot_count BigInt? + epoch_progress BigInt? + version String @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + dag_allow_spend_blocks dag_allow_spend_blocks? + dag_balance_changes dag_balance_changes[] + dag_blocks dag_blocks[] + dag_reward_transactions dag_reward_transactions[] + dag_token_lock_blocks dag_token_lock_blocks? + dag_token_locks dag_token_locks[] + global_snapshot_proofs global_snapshot_proofs[] + metagraph_snapshots metagraph_snapshots[] +} + +model metagraph_allow_spend_approvers { + allow_spend_hash String @db.VarChar + approver_address String @db.VarChar + metagraph_allow_spends metagraph_allow_spends @relation(fields: [allow_spend_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "ametagraph_llow_spends_fk") + addresses addresses @relation(fields: [approver_address], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_allow_spend_approvers_address_fk") + + @@id([allow_spend_hash, approver_address], map: "metagraph_allow_spend_approvers_pk") +} + +model metagraph_allow_spend_blocks { + metagraph_id String @db.VarChar + round_id String @id @db.Uuid + metagraph_snapshot_hash String @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_allow_spend_blocks_metagraph_snapshot_fk") + metagraph_allow_spends metagraph_allow_spends[] +} + +model metagraph_allow_spends { + hash String @id(map: "metagraph_allow_spends_pk") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + destination_addr String @db.VarChar + fee BigInt + parent_ordinal BigInt? + parent_hash String? @db.VarChar + last_valid_epoch_progress BigInt + round_id String @db.Uuid + ordinal BigInt @unique(map: "metagraph_allow_spends_ordinal") + metagraph_allow_spend_approvers metagraph_allow_spend_approvers[] + metagraph_allow_spend_block metagraph_allow_spend_blocks @relation(fields: [round_id], references: [round_id], onDelete: NoAction, onUpdate: NoAction, map: "allow_spends_block_fk") + addresses_metagraph_allow_spends_destination_addrToaddresses addresses @relation("metagraph_allow_spends_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_allow_spends_destination_addr_fk") + addresses_metagraph_allow_spends_source_addrToaddresses addresses @relation("metagraph_allow_spends_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_allow_spends_source_addr_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_id_fk") + abstract_transactions_view abstract_transactions_view[] + metagraph_spend_transactions metagraph_spend_transactions[] +} + +model metagraph_balance_changes { + metagraph_id String @db.VarChar + metagraph_snapshot_hash String @db.VarChar + address String @db.VarChar + balance BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_snapshot_ordinal BigInt + addresses addresses @relation(fields: [address], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "address_fk") + metagraph_snapshots metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_balance_change_metagraph_snapshot_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_id_fk") + + @@id([metagraph_id, address, metagraph_snapshot_ordinal], map: "metagraph_balance_change_pk") +} + +model metagraph_blocks { + hash String @db.VarChar + height BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + metagraph_snapshot_hash String @db.VarChar + metagraph_snapshots metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_block_metagraph_snapshot_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_id_fk") + metagraph_transactions metagraph_transactions[] + super abstract_blocks? + + @@id([metagraph_id, hash], map: "metagraph_block_pk") + @@unique([hash], map: "hash") +} + +model metagraph_fee_transactions { + hash String @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + metagraph_snapshot_hash String @db.VarChar + destination_addr String @db.VarChar + parent_ordinal BigInt? + parent_hash String? @db.VarChar + salt BigInt? + ordinal BigInt + data_update_ref String? @db.VarChar + metagraph_snapshots metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: NoAction, onUpdate: NoAction, map: "fee_transaction_metagraph_snapshot_fk") + addresses_metagraph_fee_transactions_destination_addrToaddresses addresses @relation("metagraph_fee_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_fee_transactions_destination_addr_fk") + addresses_metagraph_fee_transactions_source_addrToaddresses addresses @relation("metagraph_fee_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_fee_transactions_source_addr_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_id_fk") + abstract_transactions_view abstract_transactions_view[] + + @@id([hash], map: "fee_transaction_pk") + @@unique([metagraph_id, ordinal], map: "fee_transaction_ordinal") +} + +model metagraph_reward_transactions { + metagraph_id String @db.VarChar + metagraph_snapshot_hash String @db.VarChar + destination_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_snapshots metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_reward_transaction_metagraph_reward_transaction_fk") + addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_reward_transactions_destination_addr_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_reward_transactions_metagraph_id_fk") + + @@id([metagraph_id, metagraph_snapshot_hash, destination_addr], map: "metagraph_reward_transaction_pk") +} + +model metagraph_snapshots { + metagraph_id String @db.VarChar + ordinal BigInt + global_snapshot_hash String? @db.VarChar + hash String @db.VarChar + height BigInt + subheight Int + last_snapshot_hash String? @db.VarChar + fee BigInt? + owner_address String? @db.VarChar + staking_address String? @db.VarChar + epoch_progress BigInt? + version String @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_allow_spend_blocks metagraph_allow_spend_blocks[] + metagraph_balance_changes metagraph_balance_changes[] + metagraph_blocks metagraph_blocks[] + metagraph_fee_transactions metagraph_fee_transactions[] + metagraph_reward_transactions metagraph_reward_transactions[] + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_id_fk") + global_snapshots global_snapshots? @relation(fields: [global_snapshot_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_snapshots_global_snapshots_fk") + addresses_metagraph_snapshots_owner_addressToaddresses addresses? @relation("metagraph_snapshots_owner_addressToaddresses", fields: [owner_address], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "owner_address_fk") + addresses_metagraph_snapshots_staking_addressToaddresses addresses? @relation("metagraph_snapshots_staking_addressToaddresses", fields: [staking_address], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "staking_address_fk") + metagraph_token_lock_blocks metagraph_token_lock_blocks[] + + @@id([metagraph_id, hash], map: "metagraph_snapshot_pk") + @@unique([metagraph_id, ordinal], map: "metagraph_snapshot_unique") +} + +model metagraph_spend_transactions { + hash String @id(map: "metagraph_spend_transactions_pk") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + destination_addr String @db.VarChar + allow_spend_ref String + metagraph_allow_spend metagraph_allow_spends @relation(fields: [allow_spend_ref], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "mg_spend_transactions_mg_allow_spends_fk") + addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_spend_transactions_destination_addr_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_spend_transactions_metagraph_id_fk") + abstract_transactions_view abstract_transactions_view[] +} + +model metagraph_token_lock_blocks { + metagraph_id String @db.VarChar + metagraph_snapshot_hash String @db.VarChar + round_id String @unique(map: "metagraph_token_lock_blocks_unique") @db.Uuid + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_token_lock_blocks_metagraph_id_fk") + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_token_lock_blocks_metagraph_snapshot_fk") + metagraph_token_locks metagraph_token_locks[] + + @@id([metagraph_id, metagraph_snapshot_hash]) + @@unique([metagraph_id, round_id]) +} + +model metagraph_token_locks { + hash String @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + ordinal BigInt + unlock_epoch BigInt + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_id_fk") + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_token_locks_source_addr_fk") + round_id String + metagraph_token_unlock metagraph_token_unlocks? + abstract_transactions_view abstract_transactions_view[] + metagraph_token_lock_block metagraph_token_lock_blocks @relation(fields: [metagraph_id, round_id], references: [metagraph_id, round_id], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_token_lock_token_lock_blocks_fk") + + @@id([hash], map: "metagraph_token_locks_pk") + @@unique([metagraph_id, ordinal], map: "metagraph_token_locks_unique") +} + +model metagraph_token_unlocks { + metagraph_id String @db.VarChar + hash String @db.VarChar + lock_reference_ordinal BigInt + lock_reference_hash String @db.VarChar + amount BigInt + address String @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + addresses addresses @relation(fields: [address], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "address_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_id_fk") + metagraph_token_lock metagraph_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_token_unlocks_token_locks_fk") + abstract_transactions_view abstract_transactions_view[] + + @@id([hash], map: "metagraph_token_unlocks_pk") + @@unique([lock_reference_hash]) + @@unique([lock_reference_ordinal]) +} + +model metagraph_transactions { + hash String @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + destination_addr String @db.VarChar + fee BigInt + salt BigInt + parent_ordinal BigInt + parent_hash String @db.VarChar + ordinal BigInt + block_hash String @db.VarChar + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_id_fk") + metagraph_blocks metagraph_blocks @relation(fields: [metagraph_id, block_hash], references: [metagraph_id, hash], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_transaction_metagraph_block_fk") + addresses_metagraph_transactions_destination_addrToaddresses addresses @relation("metagraph_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_transactions_destination_addrfk") + addresses_metagraph_transactions_source_addrToaddresses addresses @relation("metagraph_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_transactions_source_addr_fk") + + @@id([metagraph_id, hash], map: "metagraph_transaction_pk") + @@unique([metagraph_id, ordinal], map: "metagraph_transactions_ordinal") +} + +model metagraphs { + id String @id(map: "metagraph_pkey") @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_allow_spends metagraph_allow_spends[] + metagraph_balance_changes metagraph_balance_changes[] + metagraph_blocks metagraph_blocks[] + metagraph_fee_transactions metagraph_fee_transactions[] + metagraph_reward_transactions metagraph_reward_transactions[] + metagraph_snapshots metagraph_snapshots[] + metagraph_spend_transactions metagraph_spend_transactions[] + metagraph_token_lock_blocks metagraph_token_lock_blocks[] + metagraph_token_locks metagraph_token_locks[] + metagraph_token_unlocks metagraph_token_unlocks[] + metagraph_transactions metagraph_transactions[] +} diff --git a/routes/actions.yml b/routes/actions.yml new file mode 100644 index 0000000..3c37a88 --- /dev/null +++ b/routes/actions.yml @@ -0,0 +1,41 @@ + actions: + handler: src/handlers/actionsHandler.dagActions + events: + - httpApi: + path: /actions + method: GET + + globalSnapshotActions: + handler: src/handlers/actionsHandler.globalSnapshotActions + events: + - httpApi: + path: /global-snapshots/{term}/actions + method: GET + + addressActions: + handler: src/handlers/actionsHandler.dagAddressActions + events: + - httpApi: + path: /addresses/{address}/actions + method: GET + + currencyActions: + handler: src/handlers/actionsHandler.currencyActions + events: + - httpApi: + path: /currency/{metagraph_id}/actions + method: GET + + currencySnapshotActions: + handler: src/handlers/actionsHandler.currencySnapshotActions + events: + - httpApi: + path: /currency/{metagraph_id}/snapshots/{term}/actions + method: GET + + currencyAddressActions: + handler: src/handlers/actionsHandler.currencyAddressActions + events: + - httpApi: + path: /currency/{metagraph_id}/addresses/{address}/actions + method: GET diff --git a/routes/allow-spends.yml b/routes/allow-spends.yml new file mode 100644 index 0000000..36c38ae --- /dev/null +++ b/routes/allow-spends.yml @@ -0,0 +1,125 @@ +allowSpends: + handler: src/handlers/allowSpendsHandler.allowSpends + events: + - httpApi: + path: /allow-spends + method: GET + +globalSnapshotAllowSpends: + handler: src/handlers/allowSpendsHandler.globalSnapshotAllowSpends + events: + - httpApi: + path: /global-snapshots/{hash_or_ordinal}/allow-spends + method: GET + +addressAllowSpends: + handler: src/handlers/allowSpendsHandler.addressAllowSpends + events: + - httpApi: + path: /addresses/{address}/allow-spends + method: GET + +spendTransactions: + handler: src/handlers/allowSpendsHandler.spendTransactions + events: + - httpApi: + path: /spend-transactions + method: GET + +globalSnapshotSpendTransactions: + handler: src/handlers/allowSpendsHandler.globalSnapshotSpendTransactions + events: + - httpApi: + path: /global-snapshots/{hash_or_ordinal}/spend-transactions + method: GET + +addressSpendTransactions: + handler: src/handlers/allowSpendsHandler.addressSpendTransactions + events: + - httpApi: + path: /addresses/{address}/spend-transactions + method: GET + +allowSpendExpirations: + handler: src/handlers/allowSpendsHandler.allowSpendExpirations + events: + - httpApi: + path: /allow-spend-expirations + method: GET + +globalSnapshotAllowSpendExpirations: + handler: src/handlers/allowSpendsHandler.globalSnapshotAllowSpendExpirations + events: + - httpApi: + path: /global-snapshots/{hash_or_ordinal}/allow-spend-expirations + method: GET + +addressAllowSpendExpirations: + handler: src/handlers/allowSpendsHandler.addressAllowSpendExpirations + events: + - httpApi: + path: /addresses/{address}/allow-spend-expirations + method: GET + +currencyAllowSpends: + handler: src/handlers/allowSpendsHandler.currencyAllowSpends + events: + - httpApi: + path: /currency/{metagraph_id}/allow-spends + method: GET + +currencySnapshotAllowSpends: + handler: src/handlers/allowSpendsHandler.currencySnapshotAllowSpends + events: + - httpApi: + path: /currency/{metagraph_id}/snapshots/{hash_or_ordinal}/allow-spends + method: GET + +currencyAddressAllowSpends: + handler: src/handlers/allowSpendsHandler.currencyAddressAllowSpends + events: + - httpApi: + path: /currency/{metagraph_id}/addresses/{address}/allow-spends + method: GET + +currencySnapshotSpendTransactions: + handler: src/handlers/allowSpendsHandler.currencySnapshotSpendTransactions + events: + - httpApi: + path: /currency/{metagraph_id}/snapshots/{hash_or_ordinal}/spend-transactions + method: GET + +currencySpendTransactions: + handler: src/handlers/allowSpendsHandler.currencySpendTransactions + events: + - httpApi: + path: /currency/{metagraph_id}/spend-transactions + method: GET + +currencyAddressSpendTransactions: + handler: src/handlers/allowSpendsHandler.currencyAddressSpendTransactions + events: + - httpApi: + path: /currency/{metagraph_id}/addresses/{address}/spend-transactions + method: GET + +currencySnapshotAllowSpendExpirations: + handler: src/handlers/allowSpendsHandler.currencySnapshotAllowSpendExpirations + events: + - httpApi: + path: /currency/{metagraph_id}/snapshots/{hash_or_ordinal}/allow-spend-expirations + method: GET + +currencyAllowSpendExpirations: + handler: src/handlers/allowSpendsHandler.currencyAllowSpendExpirations + events: + - httpApi: + path: /currency/{metagraph_id}/allow-spend-expirations + method: GET + +currencyAddressAllowSpendExpirations: + handler: src/handlers/allowSpendsHandler.currencyAddressAllowSpendExpirations + events: + - httpApi: + path: /currency/{metagraph_id}/addresses/{address}/allow-spend-expirations + method: GET \ No newline at end of file diff --git a/routes/dag.yml b/routes/dag.yml new file mode 100644 index 0000000..a995374 --- /dev/null +++ b/routes/dag.yml @@ -0,0 +1,67 @@ + globalSnapshots: + handler: src/handlers/dagHandler.handleGlobalSnapshots + events: + - httpApi: + path: /global-snapshots + method: GET + globalSnapshot: + handler: src/handlers/dagHandler.handleGlobalSnapshot + events: + - httpApi: + path: /global-snapshots/{term} + method: GET + globalSnapshotRewards: + handler: src/handlers/dagHandler.handleGlobalSnapshotRewards + events: + - httpApi: + path: /global-snapshots/{term}/rewards + method: GET + globalSnapshotTransactions: + handler: src/handlers/dagHandler.handleGlobalSnapshotTransactions + events: + - httpApi: + path: /global-snapshots/{term}/transactions + method: GET + block: + handler: src/handlers/dagHandler.handleDagBlock + events: + - httpApi: + path: /blocks/{hash} + method: GET + transactions: + handler: src/handlers/dagHandler.handleDagTransactions + events: + - httpApi: + path: /transactions + method: GET + transaction: + handler: src/handlers/dagHandler.handleDagTransaction + events: + - httpApi: + path: /transactions/{hash} + method: GET + transactionsByAddress: + handler: src/handlers/dagHandler.handleDagTransactionsByAddress + events: + - httpApi: + path: /addresses/{address}/transactions + method: GET + transactionsBySource: + handler: src/handlers/dagHandler.handleDagTransactionsBySource + events: + - httpApi: + path: /addresses/{address}/transactions/sent + method: GET + transactionsByDestination: + handler: src/handlers/dagHandler.handleDagTransactionsByDestination + events: + - httpApi: + path: /addresses/{address}/transactions/received + method: GET + balanceByAddress: + handler: src/handlers/dagHandler.handleDagBalanceByAddress + events: + - httpApi: + path: /addresses/{address}/balance + method: GET + diff --git a/routes/metagraph.yml b/routes/metagraph.yml new file mode 100644 index 0000000..2054627 --- /dev/null +++ b/routes/metagraph.yml @@ -0,0 +1,108 @@ + currencySnapshots: + handler: src/handlers/metagraphHandler.handleCurrencySnapshots + events: + - httpApi: + path: /currency/{identifier}/snapshots + method: GET + currencySnapshotsByOwnerAddress: + handler: src/handlers/metagraphHandler.handleCurrencySnapshotsByOwnerAddress + events: + - httpApi: + path: /addresses/{address}/snapshots + method: GET + currencySnapshot: + handler: src/handlers/metagraphHandler.handleCurrencySnapshot + events: + - httpApi: + path: /currency/{identifier}/snapshots/{term} + method: GET + currencySnapshotRewards: + handler: src/handlers/metagraphHandler.handleCurrencySnapshotRewards + events: + - httpApi: + path: /currency/{identifier}/snapshots/{term}/rewards + method: GET + currencySnapshotTransactions: + handler: src/handlers/metagraphHandler.handleCurrencySnapshotTransactions + events: + - httpApi: + path: /currency/{identifier}/snapshots/{term}/transactions + method: GET + currencyBlock: + handler: src/handlers/metagraphHandler.handleCurrencyBlock + events: + - httpApi: + path: /currency/{identifier}/blocks/{hash} + method: GET + currencyTransactions: + handler: src/handlers/metagraphHandler.handleCurrencyTransactions + events: + - httpApi: + path: /currency/{identifier}/transactions + method: GET + currencyTransaction: + handler: src/handlers/metagraphHandler.handleCurrencyTransaction + events: + - httpApi: + path: /currency/{identifier}/transactions/{hash} + method: GET + currencyTransactionsByAddress: + handler: src/handlers/metagraphHandler.handleCurrencyTransactionsByAddress + events: + - httpApi: + path: /currency/{identifier}/addresses/{address}/transactions + method: GET + currencyTransactionsBySource: + handler: src/handlers/metagraphHandler.handleCurrencyTransactionsBySource + events: + - httpApi: + path: /currency/{identifier}/addresses/{address}/transactions/sent + method: GET + currencyTransactionsByDestination: + handler: src/handlers/metagraphHandler.handleCurrencyTransactionsByDestination + events: + - httpApi: + path: /currency/{identifier}/addresses/{address}/transactions/received + method: GET + currencyBalanceByAddress: + handler: src/handlers/metagraphHandler.handleCurrencyBalanceByAddress + events: + - httpApi: + path: /currency/{identifier}/addresses/{address}/balance + method: GET + currencyFeeTransaction: + handler: src/handlers/metagraphHandler.handleCurrencyFeeTransaction + events: + - httpApi: + path: /currency/{identifier}/fee-transactions/{hash} + method: GET + currencySnapshotFeeTransactions: + handler: src/handlers/metagraphHandler.handleCurrencySnapshotFeeTransactions + events: + - httpApi: + path: /currency/{identifier}/snapshots/{term}/fee-transactions + method: GET + currencyFeeTransactionsByAddress: + handler: src/handlers/metagraphHandler.handleCurrencyFeeTransactionsByAddress + events: + - httpApi: + path: /currency/{identifier}/addresses/{address}/fee-transactions + method: GET + currencyFeeTransactionsBySource: + handler: src/handlers/metagraphHandler.handleCurrencyFeeTransactionsBySource + events: + - httpApi: + path: /currency/{identifier}/addresses/{address}/fee-transactions/sent + method: GET + currencyFeeTransactionsByDestination: + handler: src/handlers/metagraphHandler.handleCurrencyFeeTransactionsByDestination + events: + - httpApi: + path: /currency/{identifier}/addresses/{address}/fee-transactions/received + method: GET + metagraphs: + handler: src/handlers/metagraphHandler.metagraphs + events: + - httpApi: + path: /currency + method: GET diff --git a/routes/token-locks.yml b/routes/token-locks.yml new file mode 100644 index 0000000..3910122 --- /dev/null +++ b/routes/token-locks.yml @@ -0,0 +1,83 @@ +tokenLocks: + handler: src/handlers/tokenLocskHandler.tokenLocks + events: + - httpApi: + path: /token-locks + method: GET + +globalSnapshotTokenLocks: + handler: src/handlers/tokenLocskHandler.globalSnapshotTokenLocks + events: + - httpApi: + path: /global-snapshots/{hash_or_ordinal}/token-locks + method: GET + +addressTokenLocks: + handler: src/handlers/tokenLocskHandler.addressTokenLocks + events: + - httpApi: + path: /addresses/{address}/token-locks + method: GET + +tokenUnlocks: + handler: src/handlers/tokenLocskHandler.tokenUnlocks + events: + - httpApi: + path: /token-unlocks + method: GET + +globalSnapshotTokenUnlocks: + handler: src/handlers/tokenLocskHandler.globalSnapshotTokenUnlocks + events: + - httpApi: + path: /global-snapshots/{hash_or_ordinal}/token-unlocks + method: GET + +addressTokenUnlocks: + handler: src/handlers/tokenLocskHandler.addressTokenUnlocks + events: + - httpApi: + path: /addresses/{address}/token-unlocks + method: GET + +currencyTokenLocks: + handler: src/handlers/tokenLocskHandler.currencyTokenLocks + events: + - httpApi: + path: /currency/{metagraph_id}/token-locks + method: GET + +currencySnapshotTokenLocks: + handler: src/handlers/tokenLocskHandler.currencySnapshotTokenLocks + events: + - httpApi: + path: /currency/{metagraph_id}/snapshots/{hash_or_ordinal}/token-locks + method: GET + +currencyAddressTokenLocks: + handler: src/handlers/tokenLocskHandler.currencyAddressTokenLocks + events: + - httpApi: + path: /currency/{metagraph_id}/addresses/{address}/token-locks + method: GET + +currencySnapshotTokenUnlocks: + handler: src/handlers/tokenLocskHandler.currencySnapshotTokenUnlocks + events: + - httpApi: + path: /currency/{metagraph_id}/snapshots/{hash_or_ordinal}/token-unlocks + method: GET + +currencyTokenUnlocks: + handler: src/handlers/tokenLocskHandler.currencyTokenUnlocks + events: + - httpApi: + path: /currency/{metagraph_id}/token-unlocks + method: GET + +currencyAddressTokenUnlocks: + handler: src/handlers/tokenLocskHandler.currencyAddressTokenUnlocks + events: + - httpApi: + path: /currency/{metagraph_id}/addresses/{address}/token-unlocks + method: GET diff --git a/serverless.yml b/serverless.yml index a4ec975..d6e8820 100644 --- a/serverless.yml +++ b/serverless.yml @@ -13,8 +13,8 @@ provider: region: us-west-1 stage: ${self:custom.stage} environment: - OPENSEARCH_NODE: ${self:custom.env.opensearch} SLS_DEBUG: '*' + DATABASE_URL: ${self:custom.env.db_url} httpApi: cors: true vpc: ${self:custom.env.vpc} @@ -22,183 +22,12 @@ provider: timeout: 10 functions: - globalSnapshots: - handler: src/handler.globalSnapshots - events: - - httpApi: - path: /global-snapshots - method: GET - globalSnapshot: - handler: src/handler.globalSnapshot - events: - - httpApi: - path: /global-snapshots/{term} - method: GET - globalSnapshotRewards: - handler: src/handler.globalSnapshotRewards - events: - - httpApi: - path: /global-snapshots/{term}/rewards - method: GET - globalSnapshotTransactions: - handler: src/handler.globalSnapshotTransactions - events: - - httpApi: - path: /global-snapshots/{term}/transactions - method: GET - block: - handler: src/handler.block - events: - - httpApi: - path: /blocks/{hash} - method: GET - transactions: - handler: src/handler.transactions - events: - - httpApi: - path: /transactions - method: GET - transaction: - handler: src/handler.transaction - events: - - httpApi: - path: /transactions/{hash} - method: GET - transactionsByAddress: - handler: src/handler.transactionsByAddress - events: - - httpApi: - path: /addresses/{address}/transactions - method: GET - transactionsBySource: - handler: src/handler.transactionsBySource - events: - - httpApi: - path: /addresses/{address}/transactions/sent - method: GET - transactionsByDestination: - handler: src/handler.transactionsByDestination - events: - - httpApi: - path: /addresses/{address}/transactions/received - method: GET - balanceByAddress: - handler: src/handler.balanceByAddress - events: - - httpApi: - path: /addresses/{address}/balance - method: GET - - - currencySnapshots: - handler: src/handler.currencySnapshots - events: - - httpApi: - path: /currency/{identifier}/snapshots - method: GET - currencySnapshotsByOwnerAddress: - handler: src/handler.currencySnapshotsByOwnerAddress - events: - - httpApi: - path: /addresses/{address}/snapshots - method: GET - currencySnapshot: - handler: src/handler.currencySnapshot - events: - - httpApi: - path: /currency/{identifier}/snapshots/{term} - method: GET - currencySnapshotRewards: - handler: src/handler.currencySnapshotRewards - events: - - httpApi: - path: /currency/{identifier}/snapshots/{term}/rewards - method: GET - currencySnapshotTransactions: - handler: src/handler.currencySnapshotTransactions - events: - - httpApi: - path: /currency/{identifier}/snapshots/{term}/transactions - method: GET - currencyBlock: - handler: src/handler.currencyBlock - events: - - httpApi: - path: /currency/{identifier}/blocks/{hash} - method: GET - currencyTransactions: - handler: src/handler.currencyTransactions - events: - - httpApi: - path: /currency/{identifier}/transactions - method: GET - currencyTransaction: - handler: src/handler.currencyTransaction - events: - - httpApi: - path: /currency/{identifier}/transactions/{hash} - method: GET - currencyTransactionsByAddress: - handler: src/handler.currencyTransactionsByAddress - events: - - httpApi: - path: /currency/{identifier}/addresses/{address}/transactions - method: GET - currencyTransactionsBySource: - handler: src/handler.currencyTransactionsBySource - events: - - httpApi: - path: /currency/{identifier}/addresses/{address}/transactions/sent - method: GET - currencyTransactionsByDestination: - handler: src/handler.currencyTransactionsByDestination - events: - - httpApi: - path: /currency/{identifier}/addresses/{address}/transactions/received - method: GET - currencyBalanceByAddress: - handler: src/handler.currencyBalanceByAddress - events: - - httpApi: - path: /currency/{identifier}/addresses/{address}/balance - method: GET - currencyFeeTransaction: - handler: src/handler.currencyFeeTransaction - events: - - httpApi: - path: /currency/{identifier}/fee-transactions/{hash} - method: GET - currencySnapshotFeeTransactions: - handler: src/handler.currencySnapshotFeeTransactions - events: - - httpApi: - path: /currency/{identifier}/snapshots/{term}/fee-transactions - method: GET - currencyFeeTransactionsByAddress: - handler: src/handler.currencyFeeTransactionsByAddress - events: - - httpApi: - path: /currency/{identifier}/addresses/{address}/fee-transactions - method: GET - currencyFeeTransactionsBySource: - handler: src/handler.currencyFeeTransactionsBySource - events: - - httpApi: - path: /currency/{identifier}/addresses/{address}/fee-transactions/sent - method: GET - currencyFeeTransactionsByDestination: - handler: src/handler.currencyFeeTransactionsByDestination - events: - - httpApi: - path: /currency/{identifier}/addresses/{address}/fee-transactions/received - method: GET - metagraphs: - handler: src/handler.metagraphs - events: - - httpApi: - path: /currency - method: GET - + - ${file(./routes/dag.yml)} + - ${file(./routes/metagraph.yml)} + #- ${file(./routes/allow-spends.yml)} + - ${file(./routes/actions.yml)} + plugins: - serverless-plugin-typescript - serverless-offline + diff --git a/src/handler.ts b/src/handler.ts deleted file mode 100644 index 5d2d4f3..0000000 --- a/src/handler.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { getClient } from "./opensearch"; -import { - getBalanceByAddress, - getBlock, - getCurrencyBalanceByAddress, - getCurrencyBlock, - getCurrencyFeeTransaction, - getCurrencyFeeTransactionsByAddress, - getCurrencyFeeTransactionsByDestination, - getCurrencyFeeTransactionsBySource, - getCurrencySnapshot, - getCurrencySnapshotFeeTransactions, - getCurrencySnapshotRewards, - getCurrencySnapshots, - getCurrencySnapshotTransactions, - getCurrencyTransaction, - getCurrencyTransactions, - getCurrencyTransactionsByAddress, - getCurrencyTransactionsByDestination, - getCurrencyTransactionsBySource, - getGlobalSnapshot, - getGlobalSnapshotRewards, - getGlobalSnapshots, - getGlobalSnapshotTransactions, - getMetagraphs, - getCurrencySnapshotsByOwnerAddress, - getTransaction, - getTransactions, - getTransactionsByAddress, - getTransactionsByDestination, - getTransactionsBySource, -} from "./service"; - -const osClient = getClient(); - -export const globalSnapshot = (event) => getGlobalSnapshot(event, osClient)(); -export const globalSnapshots = (event) => getGlobalSnapshots(event, osClient)(); -export const globalSnapshotRewards = (event) => - getGlobalSnapshotRewards(event, osClient)(); -export const globalSnapshotTransactions = (event) => - getGlobalSnapshotTransactions(event, osClient)(); -export const block = (event) => getBlock(event, osClient)(); - -export const transaction = (event) => getTransaction(event, osClient, null)(); -export const transactions = (event) => getTransactions(event, osClient)(); -export const transactionsByAddress = (event) => - getTransactionsByAddress(event, osClient)(); -export const transactionsBySource = (event) => - getTransactionsBySource(event, osClient)(); -export const transactionsByDestination = (event) => - getTransactionsByDestination(event, osClient)(); -export const balanceByAddress = (event) => - getBalanceByAddress(event, osClient)(); - -// Currency - -export const currencySnapshot = (event) => - getCurrencySnapshot(event, osClient)(); -export const currencySnapshots = (event) => - getCurrencySnapshots(event, osClient)(); -export const currencySnapshotsByOwnerAddress = (event) => - getCurrencySnapshotsByOwnerAddress(event, osClient)(); -export const currencySnapshotRewards = (event) => - getCurrencySnapshotRewards(event, osClient)(); -export const currencySnapshotTransactions = (event) => - getCurrencySnapshotTransactions(event, osClient)(); - -export const currencyBlock = (event) => getCurrencyBlock(event, osClient)(); - -export const currencyTransaction = (event) => - getCurrencyTransaction(event, osClient)(); -export const currencyTransactions = (event) => - getCurrencyTransactions(event, osClient)(); -export const currencyTransactionsByAddress = (event) => - getCurrencyTransactionsByAddress(event, osClient)(); -export const currencyTransactionsBySource = (event) => - getCurrencyTransactionsBySource(event, osClient)(); -export const currencyTransactionsByDestination = (event) => - getCurrencyTransactionsByDestination(event, osClient)(); - -export const currencyFeeTransaction = (event) => - getCurrencyFeeTransaction(event, osClient)(); -export const currencySnapshotFeeTransactions = (event) => - getCurrencySnapshotFeeTransactions(event, osClient)(); -export const currencyFeeTransactionsByDestination = (event) => - getCurrencyFeeTransactionsByDestination(event, osClient)(); -export const currencyFeeTransactionsBySource = (event) => - getCurrencyFeeTransactionsBySource(event, osClient)(); -export const currencyFeeTransactionsByAddress = (event) => - getCurrencyFeeTransactionsByAddress(event, osClient)(); -export const currencyBalanceByAddress = (event) => - getCurrencyBalanceByAddress(event, osClient)(); - -export const metagraphs = (event) => getMetagraphs(event, osClient)(); diff --git a/src/handlers/actionsHandler.ts b/src/handlers/actionsHandler.ts new file mode 100644 index 0000000..8ed5abe --- /dev/null +++ b/src/handlers/actionsHandler.ts @@ -0,0 +1,219 @@ +import { PrismaClient } from '@prisma/client'; +import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; +import { extractHashOrdinal, extractPagination } from '../request-params'; +import { paginatedQuery, fromCreatedAtOrdinalCursor, toCreatedAtOrdinalCursor } from '../pagination'; +import { respond, handleError, dagTransactionResponse } from '../response'; + +const prisma = new PrismaClient(); + + +const dagActionsTables = [ + "dag_allow_spends", + "dag_spend_transactions", + "dag_token_locks", + "dag_token_unlocks", + "dag_fee_transactions" + ]; + + const metagraphActionsTables = [ + "metagraph_allow_spends", + "metagraph_spend_transactions", + "metagraph_token_locks", + "metagraph_token_unlocks", + "metagraph_fee_transactions" + ]; + + const currencyId = (transaction) => (transaction.metagraph_token_lock?.metagraph_id ?? + transaction.metagraph_token_unlock?.metagraph_id ?? + transaction.metagraph_allow_spend?.metagraph_id ?? + transaction.metagraph_spend_transaction?.metagraph_id ?? + transaction.metagraph_fee_transaction?.metagraph_id) + + const actionResponse = (transaction) => ({ + type: transaction.table_name, + currencyId: currencyId(transaction), + hash: transaction.hash, + amount: transaction.amount, + source: transaction.source_addr, + destination: transaction.destination_addr, + unlockEpoch: transaction.last_valid_epoch_progress ?? transaction.unlock_epoch, + parentHash: transaction.lock_reference_hash ?? transaction.allow_spend_ref, + timestamp: transaction.created_at + }); + +export const actionsResponse = (ts) => ts.map(actionResponse);; + +export const dagActions = async ( + event: APIGatewayProxyEvent +): Promise => { + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { + where: { table_name: { in: dagActionsTables } }, + orderBy: { created_at: 'desc' } + }, + prisma.abstract_transactions_view.findMany, + actionsResponse + ); +}; + + +const tokenLockGlobalSnapshotCond = (filter) => ({ dag_token_lock: { dag_token_lock_block: { global_snapshot: filter }} }) +const tokenUnlockGlobalSnapshotCond = (filter) => ({ dag_token_unlock: tokenLockGlobalSnapshotCond(filter) }) +const allowSpendGlobalSnapshotCond = (filter) => ({ + dag_allow_spend: { + dag_allow_spend_block: { + global_snapshot: filter + } + } +}) +const spendTxGlobalSnapshotCond = (filter) => ({ dag_spend_transaction: allowSpendGlobalSnapshotCond(filter) }) + + +const filterByGlobalSnapshot = (filter) => ( {OR: [ + tokenLockGlobalSnapshotCond(filter), + tokenUnlockGlobalSnapshotCond(filter), + allowSpendGlobalSnapshotCond(filter), + spendTxGlobalSnapshotCond(filter) +]}) + +export const globalSnapshotActions = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { hash_or_ordinal } = event.pathParameters || {}; + const filter = extractHashOrdinal(hash_or_ordinal); + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { where: { + ...filterByGlobalSnapshot(filter), + table_name: { in: dagActionsTables } + } + , orderBy: { created_at: 'desc' } }, + prisma.abstract_transactions_view.findMany, + actionsResponse + ); + } catch (error) { + return handleError(error); + } +}; + +export const dagAddressActions = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { address } = event.pathParameters || {}; + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { where: { source_addr: address, table_name: { in: dagActionsTables } }, + orderBy: { created_at: 'desc' } }, + prisma.abstract_transactions_view.findMany, + actionsResponse + ); + } catch (error) { + return handleError(error); + } +}; + + + +const metagraphIdCond = (metagraph_id) => ({ OR: [ + { metagraph_token_lock: {metagraph_id }}, + { metagraph_token_unlock: {metagraph_id }}, + { metagraph_allow_spend: {metagraph_id }}, + { metagraph_spend_transaction: {metagraph_id }}, + { metagraph_fee_transaction: {metagraph_id }} + ]}) + +const tokenLockMetagraphSnapshotCond = (filter) => ({ metagraph_token_lock: { metagraph_token_lock_block: { metagraph_snapshot: filter} } }) +const tokenUnlockMetagraphSnapshotCond = (filter) => ({ metagraph_token_unlock: tokenLockMetagraphSnapshotCond(filter) }) +const allowSpendMetagraphSnapshotCond = (filter) => ({ + metagraph_allow_spend: { + metagraph_allow_spend_block: { + metagraph_snapshot: filter + } + } +}) +const spendTxMetagraphSnapshotCond = (filter) => ({ metagraph_spend_transaction: allowSpendMetagraphSnapshotCond(filter) }) + + +const filterByMetagraphSnapshot = (filter) => ( {OR: [ + tokenLockMetagraphSnapshotCond(filter), + tokenUnlockMetagraphSnapshotCond(filter), + allowSpendMetagraphSnapshotCond(filter), + spendTxMetagraphSnapshotCond(filter) +]}) + + +export const currencyActions = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { metagraph_id } = event.pathParameters || {}; + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { where: { + ...metagraphIdCond(metagraph_id), + table_name: { in: metagraphActionsTables } + }, + orderBy: { created_at: 'desc' } }, + prisma.abstract_transactions_view.findMany, + actionsResponse + ); + } catch (error) { + return handleError(error); + } +}; + +export const currencySnapshotActions = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { metagraph_id, hash_or_ordinal } = event.pathParameters || {}; + const filter = extractHashOrdinal(hash_or_ordinal); + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { where: { + ...metagraphIdCond(metagraph_id), + ...filterByMetagraphSnapshot(hash_or_ordinal), + table_name: { in: metagraphActionsTables } }, orderBy: { created_at: 'desc' } }, + prisma.abstract_transactions_view.findMany, + actionsResponse + ); + } catch (error) { + return handleError(error); + } +}; + +export const currencyAddressActions = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { metagraph_id, address } = event.pathParameters || {}; + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { where: {...metagraphIdCond(metagraph_id), source_addr: address, table_name: { in: metagraphActionsTables } }, orderBy: { created_at: 'desc' } }, + prisma.abstract_transactions_view.findMany, + actionsResponse + ); + } catch (error) { + return handleError(error); + } +}; diff --git a/src/handlers/allowSpendsHandler.ts b/src/handlers/allowSpendsHandler.ts new file mode 100644 index 0000000..73906be --- /dev/null +++ b/src/handlers/allowSpendsHandler.ts @@ -0,0 +1,117 @@ +import { PrismaClient } from '@prisma/client'; +import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; +import { extractHashOrdinal, extractPagination } from '../request-params'; +import { paginatedQuery, fromCreatedAtOrdinalCursor, toCreatedAtOrdinalCursor } from '../pagination'; +import { respond, handleError } from '../response'; + +const prisma = new PrismaClient(); + +export const handleAllowSpends = async ( + event: APIGatewayProxyEvent +): Promise => { + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { orderBy: { created_at: 'desc' } }, + prisma.dag_allow_spends.findMany, + respond + ); +}; + +export const handleGlobalSnapshotAllowSpends = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { hash_or_ordinal } = event.pathParameters || {}; + const filter = extractHashOrdinal(hash_or_ordinal); + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { where: { global_snapshot: filter }, orderBy: { created_at: 'desc' } }, + prisma.dag_allow_spends.findMany, + respond + ); + } catch (error) { + return handleError(error); + } +}; + +export const handleAddressAllowSpends = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { address } = event.pathParameters || {}; + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { where: { address }, orderBy: { created_at: 'desc' } }, + prisma.dag_allow_spends.findMany, + respond + ); + } catch (error) { + return handleError(error); + } +}; + +export const handleCurrencyAllowSpends = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { metagraph_id } = event.pathParameters || {}; + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { where: { metagraph_id }, orderBy: { created_at: 'desc' } }, + prisma.metagraph_allow_spends.findMany, + respond + ); + } catch (error) { + return handleError(error); + } +}; + +export const handleCurrencySnapshotAllowSpends = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { metagraph_id, hash_or_ordinal } = event.pathParameters || {}; + const filter = extractHashOrdinal(hash_or_ordinal); + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { where: { metagraph_id, snapshot: filter }, orderBy: { created_at: 'desc' } }, + prisma.metagraph_allow_spends.findMany, + respond + ); + } catch (error) { + return handleError(error); + } +}; + +export const handleCurrencyAddressAllowSpends = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { metagraph_id, address } = event.pathParameters || {}; + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { where: { metagraph_id, address }, orderBy: { created_at: 'desc' } }, + prisma.metagraph_allow_spends.findMany, + respond + ); + } catch (error) { + return handleError(error); + } +}; diff --git a/src/handlers/dagHandler.ts b/src/handlers/dagHandler.ts new file mode 100644 index 0000000..bd7583e --- /dev/null +++ b/src/handlers/dagHandler.ts @@ -0,0 +1,318 @@ +import { PrismaClient } from '@prisma/client'; +import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; +import { + extractHashOrdinal, + extractPagination, +} from '../request-params'; +import { + balanceResponse, + dagBlockResponse, + dagTransactionResponse, + dagTransactionsResponse, + globalSnapshotResponse, + globalSnapshotsResponse, + handleError, + notFoundResponse, + respond, + rewardsResponse +} from '../response'; +import { fromCreatedAtOrdinalCursor, paginatedQuery, toCreatedAtOrdinalCursor } from '../pagination'; +import { toNumber, isFinite } from "lodash"; + +const prisma = new PrismaClient(); + +const globalSnapshotExists = async (term) => { + return prisma.global_snapshots.findUnique({ + where: extractHashOrdinal(term) , + select: { hash: true }, + }) +}; + +const latestGlobalSnapshot = async () => { + return prisma.global_snapshots.findFirst({ + select: { hash: true }, + orderBy: { ordinal: 'desc'} + }) +}; + + +const globalSnapshotWhere = async (term) => { + if (term == 'latest'){ + const latestSnapshotHash = await latestGlobalSnapshot() + return { hash: latestSnapshotHash} + } else { + return extractHashOrdinal(term) + } + } + +export const handleGlobalSnapshots = async ( + event: APIGatewayProxyEvent +): Promise => { + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { + include: { dag_blocks: true }, + orderBy: { ordinal: 'desc' } + }, + prisma.global_snapshots.findMany, + globalSnapshotsResponse + ); +}; + +export const handleGlobalSnapshot = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { term } = event.pathParameters || {}; + + let snapshot; + if (term == 'latest'){ + snapshot = await prisma.global_snapshots.findFirst({ + include: { dag_blocks: true }, + orderBy: { ordinal: 'desc' } + }); + + } else { + const filter = extractHashOrdinal(term) + + snapshot = await prisma.global_snapshots.findUnique({ + where: filter, + include: { dag_blocks: true } + }); + } + + return respond(snapshot, globalSnapshotResponse); + } catch (error) { + return handleError(error); + } +}; + +export const handleGlobalSnapshotRewards = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { term } = event.pathParameters || {}; + + if (term != "latest" && !await globalSnapshotExists(term)) { + return notFoundResponse(); + } + + const toCursor = (row) => ({ + global_snapshot_hash_destination_addr: { + global_snapshot_hash: row.global_snapshot_hash, + destination_addr: row.destination_addr + } + }); + const fromCursor = (row) => ({ + global_snapshot_hash: row.global_snapshot_hash, + destination_addr: row.destination_addr + }); + + return await paginatedQuery( + extractPagination(event), + toCursor, + fromCursor, + { + where: { global_snapshots: { ...globalSnapshotWhere(term) } }, + orderBy: [{ global_snapshot_hash: 'asc' }, { destination_addr: 'asc' }] + }, + prisma.dag_reward_transactions.findMany, + rewardsResponse + ); + } catch (error) { + return handleError(error); + } +}; + +export const handleGlobalSnapshotTransactions = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { term } = event.pathParameters || {}; + + if (term != "latest" && !await globalSnapshotExists(term)) { + return notFoundResponse(); + } + + const query = { + where: { + dag_blocks: { global_snapshots: { ...globalSnapshotWhere(term) } } + }, + include: { + dag_blocks: { + select: { + global_snapshots: { select: { hash: true, ordinal: true } } + } + } + }, + orderBy: { ordinal: 'desc' } + }; + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + query, + prisma.dag_transactions.findMany, + dagTransactionsResponse + ); + } catch (error) { + return handleError(error); + } +}; + +export const handleDagBlock = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { hash } = event.pathParameters || {}; + + const block = await prisma.dag_blocks.findUnique({ + where: { hash }, + include: { + dag_transactions: { select: { hash: true } }, + global_snapshots: true, + super: { include: { block_parents: true } } + } + }); + return respond(block, dagBlockResponse); + } catch (error) { + return handleError(error); + } +}; + +const dagTtransactionsQuery = async ( + where, + event: APIGatewayProxyEvent +): Promise => { + try { + const query = { + ...where, + include: { + dag_blocks: { + include: { + global_snapshots: { select: { hash: true, ordinal: true } } + } + } + }, + orderBy: { ordinal: 'desc' } + }; + + const toCursor = (row) => ({ + ...toCreatedAtOrdinalCursor(row), + hash: row.hash + }); + + const fromCursor = (row) => ({ + ...fromCreatedAtOrdinalCursor(row), + hash: row.hash + }); + + return await paginatedQuery( + extractPagination(event), + toCursor, + fromCursor, + query, + prisma.dag_transactions.findMany, + dagTransactionsResponse + ); + } catch (error) { + return handleError(error); + } +}; + +export const handleDagTransactions = async ( + event: APIGatewayProxyEvent +): Promise => { + return dagTtransactionsQuery({}, event); +}; + +export const handleDagTransaction = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { hash } = event.pathParameters || {}; + + const transaction = await prisma.dag_transactions.findUnique({ + where: { hash }, + include: { + dag_blocks: { + include: { + global_snapshots: { select: { hash: true, ordinal: true } } + } + } + } + }); + + return respond(transaction, dagTransactionResponse); + } catch (error) { + return handleError(error); + } +}; + +export const handleDagTransactionsByAddress = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { address } = event.pathParameters || {}; + + const where = { + where: { OR: [{ source_addr: address }, { destination_addr: address }] } + }; + + return dagTtransactionsQuery(where, event); + } catch (error) { + return handleError(error); + } +}; + +export const handleDagTransactionsBySource = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { address } = event.pathParameters || {}; + + const where = { where: { source_addr: address } }; + + return dagTtransactionsQuery(where, event); + } catch (error) { + return handleError(error); + } +}; + +export const handleDagTransactionsByDestination = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { address } = event.pathParameters || {}; + + const where = { where: { destination_addr: address } }; + + return dagTtransactionsQuery(where, event); + } catch (error) { + return handleError(error); + } +}; + +export const handleDagBalanceByAddress = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { address, ordinal } = event.pathParameters || {}; + + const ordinalNbr = toNumber(ordinal); + const ordinalCondition = (isFinite(ordinalNbr)? { snapshot_ordinal: {lte: ordinalNbr}}: {}) + + const balances = await prisma.dag_balance_changes.findFirst({ + where: { address, ...ordinalCondition }, + orderBy: { snapshot_ordinal: 'desc' } + }); + + return respond(balances, balanceResponse); + } catch (error) { + return handleError(error); + } +}; + diff --git a/src/handlers/metagraphHandler.ts b/src/handlers/metagraphHandler.ts new file mode 100644 index 0000000..59612fb --- /dev/null +++ b/src/handlers/metagraphHandler.ts @@ -0,0 +1,521 @@ +import { PrismaClient } from '@prisma/client'; +import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; +import { + extractHashOrdinal, + extractPagination, +} from '../request-params'; +import { + balanceResponse, + handleError, + metagraphBlockResponse, + metagraphFeeTransactionResponse, + metagraphFeeTransactionsResponse, + metagraphSnapshotResponse, + metagraphSnapshotsResponse, + metagraphsResponse, + metagraphTransactionsResponse, + metagraphTransactionResponse, + missingParameterResponse, + notFoundResponse, + respond, + rewardsResponse +} from '../response'; +import { fromCreatedAtOrdinalCursor, paginatedQuery, toCreatedAtOrdinalCursor } from '../pagination'; +import { toNumber, isFinite } from "lodash"; + +const prisma = new PrismaClient(); + +const latestMetagraphSnapshot = async () => { + return prisma.metagraph_snapshots.findFirst({ + select: { hash: true }, + orderBy: { ordinal: 'desc'} + }) +}; + +const metagraphSnapshotWhere = async (term) => { + if (term == 'latest'){ + const latestSnapshotHash = await latestMetagraphSnapshot() + return { hash: latestSnapshotHash} + } else { + return extractHashOrdinal(term) + } + } + +const metagraphSnapshotExists = async (metagraph_id, term) => { + const filter = extractHashOrdinal(term); + + let where; + if ("ordinal" in filter) { + where = {metagraph_id_ordinal: { metagraph_id, ordinal: filter.ordinal } }; + } else { + where = { metagraph_id_hash: { metagraph_id, hash: filter.hash }}; + } + + return prisma.metagraph_snapshots.findUnique({ + where, + select: { hash: true }, + }); +}; + +export const handleCurrencySnapshots = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { identifier } = event.pathParameters || {}; + + const toCursor = (row) => ({ + ...toCreatedAtOrdinalCursor(row), + metagraph_id: row.metagraph_id, + hash: row.hash + }); + + const fromCursor = (row) => ({ + ...fromCreatedAtOrdinalCursor(row), + metagraph_id: row.metagraph_id, + hash: row.hash + }); + + return await paginatedQuery( + extractPagination(event), + toCursor, + fromCursor, + { + where: { metagraph_id: identifier }, + include: { metagraph_blocks: true }, + orderBy: { ordinal: 'desc' } + }, + prisma.metagraph_snapshots.findMany, + metagraphSnapshotsResponse + ); + } catch (error) { + return handleError(error); + } +}; + +export const handleCurrencySnapshotsByOwnerAddress = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { address } = event.pathParameters || {}; + + const toCursor = (row) => ({ + ...toCreatedAtOrdinalCursor(row), + metagraph_id: row.metagraph_id, + hash: row.hash + }); + + const fromCursor = (row) => ({ + ...fromCreatedAtOrdinalCursor(row), + metagraph_id: row.metagraph_id, + hash: row.hash + }); + + return await paginatedQuery( + extractPagination(event), + toCursor, + fromCursor, + { + where: { owner_address: address }, + include: { metagraph_blocks: true }, + orderBy: { ordinal: 'desc' } + }, + prisma.metagraph_snapshots.findMany, + metagraphSnapshotsResponse + ); + } catch (error) { + return handleError(error); + } +}; + +export const handleCurrencySnapshot = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { identifier: metagraph_id, term } = event.pathParameters || {}; + + const snapshot = await prisma.metagraph_snapshots.findFirst({ + where: { metagraph_id: metagraph_id, ...metagraphSnapshotWhere(term) }, + include: { metagraph_blocks: true }, + orderBy: { ordinal: 'desc' } + }); + + return respond(snapshot, metagraphSnapshotResponse); + } catch (error) { + return handleError(error); + } +}; + +export const handleCurrencySnapshotRewards = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { identifier: metagraph_id, term } = event.pathParameters || {}; + + if (term != "latest" && !(await metagraphSnapshotExists(metagraph_id, term))){ + return notFoundResponse(); + } + + const cursor = (row) => ({ + metagraph_id: row.metagraph_id, + hash: row.hash + }); + + return await paginatedQuery( + extractPagination(event), + cursor, + cursor, + { + where: { + metagraph_snapshots: { + metagraph_id, + ...metagraphSnapshotWhere(term) + } + }, + orderBy: [{ metagraph_id: 'asc'}, {metagraph_snapshot_hash: 'asc'}, {destination_addr: 'asc' }] + }, + prisma.metagraph_reward_transactions.findMany, + rewardsResponse + ); + } catch (error) { + return handleError(error); + } +}; + +const metagraphTransactionsQuery = async ( + baseQuery, + event: APIGatewayProxyEvent +): Promise => { + try { + const query = { + ...baseQuery, + include: { + metagraph_blocks: { + include: { + metagraph_snapshots: { select: { hash: true, ordinal: true } } + } + } + }, + orderBy: { ordinal: 'desc' } + }; + + const cursor = (row) => ({ + metagraph_id: row.metagraph_id, + hash: row.hash + }); + + return await paginatedQuery( + extractPagination(event), + cursor, + cursor, + query, + prisma.metagraph_transactions.findMany, + metagraphTransactionsResponse + ); + } catch (error) { + return handleError(error); + } +}; + +export const handleCurrencySnapshotTransactions = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { identifier: metagraph_id, term } = event.pathParameters || {}; + + if (term != "latest" && !(await metagraphSnapshotExists(metagraph_id, term))) + return notFoundResponse(); + + const where = { + where: { + metagraph_blocks: { + metagraph_snapshots: { + metagraph_id: metagraph_id, + ...metagraphSnapshotWhere(term) + } + } + } + }; + + return metagraphTransactionsQuery(where, event); + } catch (error) { + return handleError(error); + } +}; + +export const handleCurrencyBlock = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { identifier: metagraph_id, hash } = event.pathParameters || {}; + + const block = await prisma.metagraph_blocks.findUnique({ + where: { metagraph_id, hash }, + include: { + metagraph_transactions: { select: { hash: true } }, + metagraph_snapshots: true, + super: { include: { block_parents: true } } + } + }); + + return respond(block, metagraphBlockResponse); + } catch (error) { + return handleError(error); + } +}; + +export const handleCurrencyTransactions = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { identifier: metagraph_id } = event.pathParameters || {}; + + const where = { where: { metagraph_blocks: { metagraph_id } } }; + + return metagraphTransactionsQuery(where, event); + } catch (error) { + return handleError(error); + } +}; + +export const handleCurrencyTransaction = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { identifier: metagraph_id, hash } = event.pathParameters || {}; + + const transaction = await prisma.metagraph_transactions.findUnique({ + where: { metagraph_id_hash: { + metagraph_id: metagraph_id!, + hash: hash! + } + }, + include: { + metagraph_blocks: { + include: { + metagraph_snapshots: { select: { hash: true, ordinal: true } } + } + } + } + }); + + return respond(transaction, metagraphTransactionResponse); + } catch (error) { + return handleError(error); + } +}; + +export const handleCurrencyTransactionsByAddress = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { identifier: metagraph_id, address } = event.pathParameters || {}; + + const where = { + where: { metagraph_id, + OR: [{ source_addr: address }, { destination_addr: address }] + } + }; + + return metagraphTransactionsQuery(where, event); + } catch (error) { + return handleError(error); + } +}; + +export const handleCurrencyTransactionsBySource = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { identifier: metagraph_id, address } = event.pathParameters || {}; + + const where = { where: { metagraph_id, source_addr: address } }; + + return metagraphTransactionsQuery(where, event); + } catch (error) { + return handleError(error); + } +}; + +export const handleCurrencyTransactionsByDestination = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { identifier: metagraph_id, address } = event.pathParameters || {}; + + const where = { where: { metagraph_id, destination_addr: address } }; + + return metagraphTransactionsQuery(where, event); + } catch (error) { + return handleError(error); + } +}; + +export const handleCurrencyBalanceByAddress = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { identifier: metagraph_id, address, ordinal } = event.pathParameters || {}; + + const ordinalNbr = toNumber(ordinal); + const ordinalCondition = (isFinite(ordinalNbr)? {metagraph_snapshot_ordinal: { lte: ordinalNbr}}: {}) + + const balance = await prisma.metagraph_balance_changes.findFirst({ + where: { + metagraph_id, + address, + ...ordinalCondition + }, + orderBy: { metagraph_snapshot_ordinal: 'desc' } + }); + + return respond(balance, balanceResponse); + } catch (error) { + return handleError(error); + } +}; + +export const handleCurrencyFeeTransaction = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { identifier: metagraph_id, hash } = event.pathParameters || {}; + + const transaction = await prisma.metagraph_fee_transactions.findUnique({ + where: { + metagraph_id: metagraph_id!, + hash: hash! + }, + include: { + metagraph_snapshots: { select: { hash: true, ordinal: true } } + } + }); + + return respond(transaction, metagraphFeeTransactionResponse); + } catch (error) { + return handleError(error); + } +}; + +const metagraphFeeTransactionsQuery = async ( + baseQuery, + event: APIGatewayProxyEvent +): Promise => { + try { + const query = { + ...baseQuery, + include: { + metagraph_snapshots: { select: { hash: true, ordinal: true } } + }, + orderBy: { ordinal: 'desc' } + }; + + const cursor = (row) => ({ + metagraph_id: row.metagraph_id, + hash: row.hash + }); + + return await paginatedQuery( + extractPagination(event), + cursor, + cursor, + query, + prisma.metagraph_fee_transactions.findMany, + metagraphFeeTransactionsResponse + ); + } catch (error) { + return handleError(error); + } +}; + +export const handleCurrencySnapshotFeeTransactions = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { identifier: metagraph_id, term } = event.pathParameters || {}; + if (!metagraph_id || !term) + return missingParameterResponse('identifier or term'); + + const where = { + metagraph_id: metagraph_id, + ordinal: BigInt(term), + }; + + return metagraphFeeTransactionsQuery(where, event); + } catch (error) { + return handleError(error); + } +}; + +export const handleCurrencyFeeTransactionsByAddress = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { identifier: metagraph_id, address } = event.pathParameters || {}; + if (!metagraph_id || !address) + return missingParameterResponse('identifier or address'); + + const where = { + metagraph_id: metagraph_id, + OR: [{ source_addr: address }, { destination_addr: address }] + }; + + return metagraphFeeTransactionsQuery(where, event); + } catch (error) { + return handleError(error); + } +}; + +export const handleCurrencyFeeTransactionsBySource = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { identifier: metagraph_id, address } = event.pathParameters || {}; + if (!metagraph_id || !address) + return missingParameterResponse('identifier or address'); + + const where = { + metagraph_id: metagraph_id, + source_addr: address + }; + return metagraphFeeTransactionsQuery(where, event); + } catch (error) { + return handleError(error); + } +}; + +export const handleCurrencyFeeTransactionsByDestination = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { identifier: metagraph_id, address } = event.pathParameters || {}; + if (!metagraph_id || !address) + return missingParameterResponse('identifier or address'); + + const where = { + metagraph_id: metagraph_id, + destination_addr: address + }; + return metagraphFeeTransactionsQuery(where, event); + } catch (error) { + return handleError(error); + } +}; + +export const metagraphs = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const cursor = (row) => ({ id: row.id }); + + return await paginatedQuery( + extractPagination(event), + cursor, + cursor, + {}, + prisma.metagraphs.findMany, + metagraphsResponse + ); + } catch (error) { + return handleError(error); + } +}; diff --git a/src/handlers/tokenLocksHandler.ts b/src/handlers/tokenLocksHandler.ts new file mode 100644 index 0000000..e6d49eb --- /dev/null +++ b/src/handlers/tokenLocksHandler.ts @@ -0,0 +1,111 @@ +import { PrismaClient } from '@prisma/client'; +import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; +import { extractHashOrdinal, extractPagination } from '../request-params'; +import { paginatedQuery, fromCreatedAtOrdinalCursor, toCreatedAtOrdinalCursor } from '../pagination'; +import { respond, handleError, notFoundResponse } from '../response'; + +const prisma = new PrismaClient(); + +export const handleTokenLocks = async ( + event: APIGatewayProxyEvent +): Promise => { + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { orderBy: { created_at: 'desc' } }, + prisma.dag_token_locks.findMany, + respond + ); +}; + +export const handleGlobalSnapshotTokenLocks = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { hash_or_ordinal } = event.pathParameters || {}; + const filter = extractHashOrdinal(hash_or_ordinal); + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { where: { global_snapshot: filter }, orderBy: { created_at: 'desc' } }, + prisma.dag_token_locks.findMany, + respond + ); + } catch (error) { + return handleError(error); + } +}; + +export const handleAddressTokenLocks = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { address } = event.pathParameters || {}; + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { where: { address }, orderBy: { created_at: 'desc' } }, + prisma.dag_token_locks.findMany, + respond + ); + } catch (error) { + return handleError(error); + } +}; + +export const handleTokenUnlocks = async ( + event: APIGatewayProxyEvent +): Promise => { + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { orderBy: { created_at: 'desc' } }, + prisma.dag_token_unlocks.findMany, + respond + ); +}; + +export const handleGlobalSnapshotTokenUnlocks = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { hash_or_ordinal } = event.pathParameters || {}; + const filter = extractHashOrdinal(hash_or_ordinal); + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { where: { global_snapshot: filter }, orderBy: { created_at: 'desc' } }, + prisma.dag_token_unlocks.findMany, + respond + ); + } catch (error) { + return handleError(error); + } +}; + +export const handleAddressTokenUnlocks = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { address } = event.pathParameters || {}; + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { where: { address }, orderBy: { created_at: 'desc' } }, + prisma.dag_token_unlocks.findMany, + respond + ); + } catch (error) { + return handleError(error); + } +}; diff --git a/src/http.ts b/src/http.ts deleted file mode 100644 index 34fc182..0000000 --- a/src/http.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { PaginatedResult, Result } from "./opensearch"; - -const DEFAULT_HEADERS = { - "Content-Type": "application/json", - "Access-Control-Allow-Origin": "*", -}; - -export enum StatusCodes { - OK = 200, - CREATED = 201, - BAD_REQUEST = 400, - NOT_FOUND = 404, - SERVER_ERROR = 500, -} - -type ErrorCodes = - | StatusCodes.BAD_REQUEST - | StatusCodes.NOT_FOUND - | StatusCodes.SERVER_ERROR; -type SuccessCodes = Exclude; - -export class ApplicationError { - public readonly message: string; - public readonly errors: string[]; - public readonly statusCode: ErrorCodes; - - public constructor(message: string, errors: string[], status: ErrorCodes) { - this.message = message; - this.errors = errors; - this.statusCode = status; - } -} - -export class OpenSearchError extends ApplicationError { - public constructor(error: string) { - super("OpenSearch error", [error], StatusCodes.SERVER_ERROR); - } -} - -type SuccessResponse = { - statusCode: SuccessCodes; - headers: typeof DEFAULT_HEADERS; - body: string; -}; - -export const successResponse = - (statusCode: SuccessCodes) => - (result: Result | PaginatedResult): Response => { - return { - statusCode, - headers: DEFAULT_HEADERS, - body: JSON.stringify(result), - }; - }; - -type ErrorResponse = { - statusCode: ErrorCodes; - headers: typeof DEFAULT_HEADERS; - body: string; -}; - -export const errorResponse = (error: ApplicationError): Response => ({ - statusCode: error.statusCode, - headers: DEFAULT_HEADERS, - body: JSON.stringify({ message: error.message, errors: error.errors }), -}); - -export type Response = SuccessResponse | ErrorResponse; diff --git a/src/model/balance.ts b/src/model/balance.ts deleted file mode 100644 index c395728..0000000 --- a/src/model/balance.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { SnapshotHash, SnapshotOrdinal, Timestamp } from "./properties"; - -export type OpenSearchBalance = { - address: string; - balance: number; -} & SnapshotOrdinal & - SnapshotHash & - Timestamp; - -export type Balance = Pick & { - ordinal: number; -}; diff --git a/src/model/block.ts b/src/model/block.ts deleted file mode 100644 index d996111..0000000 --- a/src/model/block.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - Hash, - Height, - SnapshotHash, - SnapshotOrdinal, - Timestamp, -} from "./properties"; - -export type OpenSearchBlock = { - transactions: string[]; - parent: BlockReference[]; -} & Timestamp & - SnapshotOrdinal & - SnapshotHash & - Hash & - Height; - -export type BlockReference = Hash & Height; - -export type Block = OpenSearchBlock; diff --git a/src/model/currency-data.ts b/src/model/currency-data.ts deleted file mode 100644 index 7d87d35..0000000 --- a/src/model/currency-data.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type CurrencyData = { - data: A; - identifier: string; -}; diff --git a/src/model/currency-snapshot.ts b/src/model/currency-snapshot.ts deleted file mode 100644 index 688d9d9..0000000 --- a/src/model/currency-snapshot.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { OpenSearchSnapshot, WithoutRewards } from "./snapshot"; - -export type OpenSearchCurrencySnapshotV1 = OpenSearchSnapshot & { - fee?: number | null; - stakingAddress?: string | null; - ownerAddress?: string | null; - sizeInKB?: number | null; -}; - -export type OpenSearchCurrencySnapshot = OpenSearchCurrencySnapshotV1 & { - fee: number | null; - stakingAddress: string | null; - ownerAddress: string | null; - sizeInKB: number | null; -}; - -export const openSearchCurrencySnapshotToV2 = ( - data: - | OpenSearchCurrencySnapshotV1 - | OpenSearchCurrencySnapshot - | WithoutRewards - | WithoutRewards -): WithoutRewards => ({ - ...data, - fee: data.fee ?? null, - stakingAddress: data.stakingAddress ?? null, - ownerAddress: data.ownerAddress ?? null, - sizeInKB: data.sizeInKB ?? null, -}); - -export type CurrencySnapshot = { - fee: number | null; - stakingAddress: string | null; - ownerAddress: string | null; - sizeInKB: number | null; -}; diff --git a/src/model/fee-transaction.ts b/src/model/fee-transaction.ts deleted file mode 100644 index ed78256..0000000 --- a/src/model/fee-transaction.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Hash, SnapshotHash, SnapshotOrdinal, Timestamp } from "./properties"; -import { TransactionReference } from "./transaction"; - -export type OpenSearchFeeTransaction = { - source: string; - destination: string; - amount: number; - parent: TransactionReference; - salt: number; -} & Timestamp & - SnapshotHash & - SnapshotOrdinal & - Hash; - -export type FeeTransaction = OpenSearchFeeTransaction; diff --git a/src/model/index.ts b/src/model/index.ts deleted file mode 100644 index cfd37dd..0000000 --- a/src/model/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from "./block"; -export * from "./balance"; -export * from "./snapshot"; -export * from "./transaction"; -export * from "./properties"; -export * from "./currency-data"; -export * from "./fee-transaction"; diff --git a/src/model/metagraph.ts b/src/model/metagraph.ts deleted file mode 100644 index d1bbc07..0000000 --- a/src/model/metagraph.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type Metagraph = { - id: string; - lastSnapshotHash: string; - ownerAddress: string | null; - stakingAddress: string | null; -}; diff --git a/src/model/properties.ts b/src/model/properties.ts deleted file mode 100644 index d19d275..0000000 --- a/src/model/properties.ts +++ /dev/null @@ -1,23 +0,0 @@ -export type Timestamp = { - timestamp: string; -}; - -export type Hash = { - hash: string; -}; - -export type Ordinal = { - ordinal: number; -}; - -export type SnapshotOrdinal = { - snapshotOrdinal: number; -}; - -export type SnapshotHash = { - snapshotHash: number; -}; - -export type Height = { - height: number; -}; diff --git a/src/model/snapshot.ts b/src/model/snapshot.ts deleted file mode 100644 index 7fd6704..0000000 --- a/src/model/snapshot.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Hash, Height, Ordinal, Timestamp } from "./properties"; -import { RewardTransaction } from "./transaction"; - -export type OpenSearchSnapshot = { - subHeight: number; - lastSnapshotHash: string; - blocks: string[]; - rewards: RewardTransaction[]; -} & Timestamp & - Ordinal & - Hash & - Height; - -export type WithoutRewards = Omit; diff --git a/src/model/transaction.ts b/src/model/transaction.ts deleted file mode 100644 index 324c307..0000000 --- a/src/model/transaction.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { - Hash, - Ordinal, - SnapshotHash, - SnapshotOrdinal, - Timestamp, -} from "./properties"; - -export type OpenSearchTransaction = { - source: string; - destination: string; - amount: number; - fee: number; - parent: TransactionReference; - blockHash: string; - salt: number; - transactionOriginal: object; -} & Timestamp & - SnapshotHash & - SnapshotOrdinal & - Hash; - -export type TransactionReference = Hash & Ordinal; - -export type Transaction = Omit; - -export type RewardTransaction = { - destination: string; - amount: number; -}; diff --git a/src/opensearch.ts b/src/opensearch.ts deleted file mode 100644 index 765a409..0000000 --- a/src/opensearch.ts +++ /dev/null @@ -1,1037 +0,0 @@ -import { Client } from "@opensearch-project/opensearch"; -import { pipe } from "fp-ts/lib/function"; - -import { ApplicationError, OpenSearchError, StatusCodes } from "./http"; -import { - Balance, - Block, - CurrencyData, - FeeTransaction, - OpenSearchBalance, - OpenSearchBlock, - OpenSearchFeeTransaction, - OpenSearchSnapshot, - OpenSearchTransaction, - RewardTransaction, - Transaction, - WithoutRewards, -} from "./model"; -import { - findAll, - findOne, - getAll, - getByFieldQuery, - getDocumentQuery, - getLatestQuery, - getMultiQuery, - getSearchSince, - maxSizeLimit, - SearchDirection, - SortOption, - SortOptions, - SortOptionSince, - SortOrder, -} from "./query"; - -import { - chain, - left, - map, - of, - orElse, - right, - TaskEither, - tryCatch, -} from "fp-ts/lib/TaskEither"; -import { fromNextString, Pagination, toNextString } from "./request-params"; -import { Get, Paths } from "./ts-extensions"; -import { Metagraph } from "./model/metagraph"; -import { - CurrencySnapshot, - OpenSearchCurrencySnapshot, - openSearchCurrencySnapshotToV2, - OpenSearchCurrencySnapshotV1, -} from "./model/currency-snapshot"; - -enum OSIndex { - Snapshots = "snapshots", - Blocks = "blocks", - Transactions = "transactions", - Balances = "balances-*", - CurrencySnapshots = "currency-snapshots-*", - CurrencyBlocks = "currency-blocks", - CurrencyTransactions = "currency-transactions", - CurrencyFeeTransactions = "currency-fee-transactions", - CurrencyBalances = "currency-balances-*", -} - -export type Result = { - data: T; - meta?: {}; -}; - -export type PaginatedResult = { - data: T[]; - meta: { - next: string | null; - }; -}; - -export const getClient = (): Client => { - return new Client({ node: process.env.OPENSEARCH_NODE }); -}; - -const getResultWithNextString = - (sortOptions: SortOptions) => - ( - getData: (sortOptions: SortOptions) => TaskEither - ): TaskEither> => { - // NOTE: We exceed the limit by 1 additional element to determine if next page is empty - const plusOneSize = sortOptions.size - ? sortOptions.size + 1 - : maxSizeLimit + 1; - - return pipe( - getData({ - ...sortOptions, - size: plusOneSize, - }), - chain((plusOneData) => { - if (plusOneData.length === 0) - return left( - new ApplicationError("Not found", [], StatusCodes.NOT_FOUND) - ); - - if (plusOneData.length < plusOneSize) { - return right({ data: plusOneData, meta: { next: null } }); - } - - const data = plusOneData.slice(0, -1); - const element = data[data.length - 1]; - - const getValue = (option: SortOption | SortOptionSince) => - option.sortField.split(".").reduce((acc, n) => acc[n], element); - - return right>({ - data, - meta: { - next: toNextString({ - size: sortOptions.size, - options: sortOptions.options.map((option) => ({ - ...option, - searchSince: getValue(option), - })), - }), - }, - }); - }) - ); - }; - -const getAggregationResultWithNextString = - (sortOptions: SortOptions) => - ( - getDataWithAfterKey: ( - sortOptions: SortOptions - ) => TaskEither - ): TaskEither> => { - // NOTE: We exceed the limit by 1 additional element to determine if next page is empty - const plusOneSize = sortOptions.size - ? sortOptions.size + 1 - : maxSizeLimit + 1; - - return pipe( - getDataWithAfterKey({ - ...sortOptions, - size: plusOneSize, - }), - chain(([plusOneData, after_key]) => { - if (plusOneData.length === 0) - return left( - new ApplicationError("Not found", [], StatusCodes.NOT_FOUND) - ); - - if (plusOneData.length < plusOneSize) { - return right({ data: plusOneData, meta: { next: null } }); - } - - const data = plusOneData.slice(0, -1); - - return right>({ - data, - meta: { - next: toNextString({ - size: sortOptions.size, - options: sortOptions.options - .filter((option) => after_key[option.sortField] !== undefined) - .map((option) => ({ - ...option, - searchSince: after_key[option.sortField], - })), - }), - }, - }); - }) - ); - }; - -const getSortOptions = (pagination: Pagination) => ({ - withDefault: (def: SortOptions): SortOptions => { - if ("next" in pagination) { - const sortOptionsFromNext = fromNextString(pagination.next); - return { - ...sortOptionsFromNext, - size: pagination.size ?? sortOptionsFromNext.size, - }; - } else { - return def; - } - }, -}); - -export function getFromPath(obj: O, path: K): Get; -export function getFromPath( - obj: Record, - path: string -): unknown { - const [firstKey, ...restKeys] = path.split("."); - - if (firstKey === undefined || firstKey === "") { - return obj; - } - - const value = obj[firstKey]; - - if (value === undefined || value === null) { - return undefined; - } - - if (restKeys.length === 0) { - return value; - } - - if (typeof value !== "object") { - return undefined; - } - - return getFromPath(value as Record, restKeys.join(".")); -} - -export const findCollectionByTerm = - (os: Client) => - ( - term: OSC[keyof OSC], - fields: (keyof OSC)[], - sortOptions: SortOptions, - index: OSIndex, - currencyIdentifier: string | null - ): TaskEither> => { - const stringifyTerm = (term: OSC[keyof OSC]) => { - return term !== null && term !== undefined && typeof term !== "object" - ? (String(term) as OSC[keyof OSC]) - : ("" as OSC[keyof OSC]); - }; - - return getResultWithNextString(sortOptions)((sort) => { - const query = - fields.length === 1 - ? getByFieldQuery( - index, - fields[0], - stringifyTerm(term), - sort, - currencyIdentifier - ) - : getMultiQuery( - index, - fields, - stringifyTerm(term), - sort, - currencyIdentifier - ); - return findAll(os.search(query), currencyIdentifier); - }); - }; - -export const findSnapshotRewards = - (os: Client) => - ( - term: string, - currencyIdentifier: string | null - ): TaskEither> => - pipe( - findOne( - findSnapshotByTerm(os)(term, currencyIdentifier), - currencyIdentifier - ), - map((r) => ({ ...r, data: r.data.rewards })) - ); - -export const listMetagraphs = - (os: Client) => - ( - pagination: Pagination - ): TaskEither> => { - const { size, ...options } = pagination; - const sortOptions = getSortOptions< - CurrencyData, - Metagraph - >(pagination).withDefault({ - size, - options: [ - { - ...options, - searchDirection: SearchDirection.Before, - sortField: "identifier" as Paths< - CurrencyData - >, - }, - ], - }); - - type LastMetagraphSnapshot = CurrencyData< - Pick< - OpenSearchCurrencySnapshotV1, - "hash" | "ownerAddress" | "stakingAddress" - > - >; - - type MetagraphBucket = { - latestSnapshot: { - hits: { - hits: [{ _source: LastMetagraphSnapshot }]; - }; - }; - }; - - type MetagraphsAggregation = { - after_key: { - identifier: - | CurrencyData["identifier"] - | null; - }; - buckets: MetagraphBucket[]; - }; - - return getAggregationResultWithNextString< - Metagraph, - LastMetagraphSnapshot, - MetagraphsAggregation["after_key"] - >(sortOptions)((sort) => { - const after = sortOptions.options - .filter(isSearchSinceOption) - .map>( - (opt) => opt as SortOptionSince - ) - .reduce( - (acc, curr) => ({ - ...acc, - [curr.sortField]: curr.searchSince, - }), - {} - ); - - const query = { - size: 0, - aggs: { - metagraphs: { - composite: { - sources: [ - { - identifier: { - terms: { - field: "identifier" as Paths< - CurrencyData - >, - }, - }, - }, - ], - size: sort.size, - ...(Object.keys(after).length > 0 ? { after } : {}), - }, - aggs: { - latestSnapshot: { - top_hits: { - sort: [ - { - "data.ordinal": { - order: SortOrder.Desc, - }, - } as { - [K in Paths< - CurrencyData - >]: { - order: SortOrder; - }; - } & { [key: string]: never }, - ], - _source: { - includes: [ - "identifier", - "data.hash", - "data.ownerAddress", - "data.stakingAddress", - ] as Paths>[], - }, - size: 1, - }, - }, - }, - }, - }, - }; - - return pipe( - tryCatch( - () => - os - .search({ - index: OSIndex.CurrencySnapshots, - body: query, - }) - .then((r) => r.body.aggregations.metagraphs), - (err) => - new ApplicationError( - "OpenSearch error", - [err as string], - StatusCodes.SERVER_ERROR - ) - ), - map(({ buckets, after_key }) => { - const data = buckets - .map(({ latestSnapshot }) => latestSnapshot.hits.hits) - .map(([hit]) => hit._source) - .map( - ({ - identifier, - data: { hash, stakingAddress, ownerAddress }, - }) => ({ - id: identifier, - lastSnapshotHash: hash, - stakingAddress: stakingAddress ?? null, - ownerAddress: ownerAddress ?? null, - }) - ); - - return [data, after_key]; - }) - ); - }); - }; - -export const listSnapshots = - (os: Client) => - ( - pagination: Pagination>, - currencyIdentifier: string | null - ): TaskEither> => { - const { size, ...options } = pagination; - const sortOptions = getSortOptions, OSS>( - pagination - ).withDefault({ - size, - options: [ - { - ...options, - searchDirection: options["searchDirection"] || SearchDirection.Before, - sortField: "ordinal", - }, - ], - }); - - return getResultWithNextString(sortOptions)((sort) => - findAll( - os.search( - getAll( - currencyIdentifier ? OSIndex.CurrencySnapshots : OSIndex.Snapshots, - sort, - currencyIdentifier - ) - ), - currencyIdentifier - ) - ); - }; - -export const findCurrencySnapshotsByOwnerAddress = - (os: Client) => - ( - ownerAddress: string, - pagination: Pagination - ): TaskEither< - OpenSearchError, - PaginatedResult - > => { - const { size, ...options } = pagination; - - const sortOptions = getSortOptions(pagination).withDefault({ - size, - options: [ - { - ...options, - searchDirection: SearchDirection.Before, - sortField: "data.ordinal", - }, - { - ...options, - searchDirection: SearchDirection.Before, - sortField: "identifier", - }, - ], - }); - - return pipe( - getResultWithNextString< - CurrencyData> - >(sortOptions)((sort) => { - const query = { - index: OSIndex.CurrencySnapshots, - body: { - ...getSearchSince>(sort), - sort: sort.options.map((s) => ({ - [s.sortField]: - s.searchDirection === SearchDirection.After - ? SortOrder.Asc - : SortOrder.Desc, - })), - size: sort.size || maxSizeLimit, - _source: { - excludes: ["data.rewards"] as Paths< - CurrencyData - >[], - }, - query: { - bool: { - must: { - term: { ["data.ownerAddress"]: ownerAddress } as Record< - Paths>, - string - >, - }, - }, - }, - }, - }; - - return pipe( - tryCatch< - OpenSearchError, - CurrencyData>[] - >( - () => - os - .search(query) - .then((r) => r.body.hits.hits.map((h) => h._source)), - (err) => new OpenSearchError(err as string) - ) - ); - }), - map(({ data, ...rest }) => ({ - ...rest, - data: data.map(({ identifier, data }) => ({ - metagraphId: identifier, - ...openSearchCurrencySnapshotToV2(data), - })), - })) - ); - }; - -const exportSortOptions = ( - pagination: Pagination, - sortFields: Paths[], - findByHashFallback: () => TaskEither> -): TaskEither> => { - const { size, ...options } = pagination; - - return pagination["searchSince"] - ? (() => - pipe( - findByHashFallback(), - map((r: Result) => ({ - size, - options: sortFields.map((sortField) => ({ - sortField, - searchDirection: - options["searchDirection"] || SearchDirection.Before, - searchSince: getFromPath(r.data, sortField), - })), - })) - ))() - : of( - getSortOptions(pagination).withDefault({ - size, - options: sortFields.map((sortField) => ({ - ...options, - sortField, - searchDirection: - options["searchDirection"] || SearchDirection.Before, - })), - }) - ); -}; - -export const listTransactions = - (os: Client) => - ( - pagination: Pagination, - currencyIdentifier: string | null - ): TaskEither> => - pipe( - exportSortOptions( - pagination, - ["snapshotOrdinal", "source", "parent.ordinal"], - () => - findTransactionByHash(os)( - pagination["searchSince"], - currencyIdentifier - ) - ), - chain< - ApplicationError, - SortOptions, - PaginatedResult - >((sortOptions) => - getResultWithNextString(sortOptions)((sort) => - findAll( - os.search( - getAll( - currencyIdentifier - ? OSIndex.CurrencyTransactions - : OSIndex.Transactions, - sort, - currencyIdentifier - ) - ), - currencyIdentifier - ) - ) - ) - ); - -export const findSnapshot = - (os: Client) => - ( - term: string, - currencyIdentifier: string | null - ): TaskEither>> => { - return pipe( - findOne( - findSnapshotByTerm(os)(term, currencyIdentifier), - currencyIdentifier - ), - map((s) => { - const { rewards, ...rest } = s.data; - return { ...s, data: rest }; - }) - ); - }; - -const findSnapshotByTerm = - (os: Client) => - (term: string, currencyIdentifier: string | null) => { - const index = currencyIdentifier - ? OSIndex.CurrencySnapshots - : OSIndex.Snapshots; - - if (isLatest(term)) { - return os.search(getLatestQuery(index, currencyIdentifier)); - } - if (isOrdinal(term)) { - return os.search( - getByFieldQuery( - index, - "ordinal", - term, - { - options: [{ sortField: "ordinal" }], - size: 1, - }, - currencyIdentifier - ) - ); - } - return os.get(getDocumentQuery(index, term, currencyIdentifier)); - }; - -export const findTransactionsBySnapshot = - (os: Client) => - ( - term: string, - pagination: Pagination, - currencyIdentifier: string | null - ): TaskEither> => { - return pipe( - findSnapshot(os)(term, currencyIdentifier), - chain((s) => - pipe( - exportSortOptions( - pagination, - ["snapshotOrdinal", "source", "parent.ordinal"], - () => - findTransactionByHash(os)( - pagination["searchSince"], - currencyIdentifier - ) - ), - chain((sortOptions) => - findCollectionByTerm(os)( - s.data.ordinal, - ["snapshotOrdinal"], - sortOptions, - currencyIdentifier - ? OSIndex.CurrencyTransactions - : OSIndex.Transactions, - currencyIdentifier - ) - ), - orElse((e: ApplicationError) => - e.statusCode === StatusCodes.NOT_FOUND - ? right({ data: [], meta: { next: null } }) - : left(e) - ) - ) - ) - ); - }; - -export const findCurrencyFeeTransactionsBySnapshot = - (os: Client) => - ( - term: string, - pagination: Pagination, - currencyIdentifier: string - ): TaskEither< - ApplicationError, - PaginatedResult - > => { - return pipe( - findSnapshot(os)(term, currencyIdentifier), - chain((s) => { - return pipe( - exportSortOptions( - pagination, - ["snapshotOrdinal", "source", "parent.ordinal"], - () => - findCurrencyFeeTransactionByHash(os)( - pagination["searchSince"], - currencyIdentifier - ) - ), - chain((sortOptions) => { - return findCollectionByTerm(os)( - s.data.ordinal, - ["snapshotOrdinal"], - sortOptions, - OSIndex.CurrencyFeeTransactions, - currencyIdentifier - ); - }), - orElse((e: ApplicationError) => - e.statusCode === StatusCodes.NOT_FOUND - ? right({ data: [], meta: { next: null } }) - : left(e) - ) - ); - }) - ); - }; - -export const findTransactionsByAddress = - (os: Client) => - ( - address: string, - pagination: Pagination, - currencyIdentifier: string | null - ) => - pipe( - exportSortOptions( - pagination, - ["snapshotOrdinal", "source", "parent.ordinal"], - () => - findTransactionByHash(os)( - pagination["searchSince"], - currencyIdentifier - ) - ), - chain((sortOptions) => - findCollectionByTerm(os)( - address, - ["source", "destination"], - sortOptions, - currencyIdentifier - ? OSIndex.CurrencyTransactions - : OSIndex.Transactions, - currencyIdentifier - ) - ) - ); - -export const findCurrencyFeeTransactionsByAddress = - (os: Client) => - ( - address: string, - pagination: Pagination, - currencyIdentifier: string - ) => - pipe( - exportSortOptions( - pagination, - ["snapshotOrdinal", "source", "parent.ordinal"], - () => - findCurrencyFeeTransactionByHash(os)( - pagination["searchSince"], - currencyIdentifier - ) - ), - chain((sortOptions) => - findCollectionByTerm(os)( - address, - ["source", "destination"], - sortOptions, - OSIndex.CurrencyFeeTransactions, - currencyIdentifier - ) - ) - ); - -export const findTransactionsBySource = - (os: Client) => - ( - term: string, - pagination: Pagination, - currencyIdentifier: string | null - ): TaskEither> => - pipe( - exportSortOptions( - pagination, - ["snapshotOrdinal", "source", "parent.ordinal"], - () => - findTransactionByHash(os)( - pagination["searchSince"], - currencyIdentifier - ) - ), - chain((sortOptions) => - findCollectionByTerm(os)( - term, - ["source"], - sortOptions, - currencyIdentifier - ? OSIndex.CurrencyTransactions - : OSIndex.Transactions, - currencyIdentifier - ) - ) - ); - -export const findCurrencyFeeTransactionsBySource = - (os: Client) => - ( - address: string, - pagination: Pagination, - currencyIdentifier: string - ) => - pipe( - exportSortOptions( - pagination, - ["snapshotOrdinal", "source", "parent.ordinal"], - () => - findCurrencyFeeTransactionByHash(os)( - pagination["searchSince"], - currencyIdentifier - ) - ), - chain((sortOptions) => - findCollectionByTerm(os)( - address, - ["source"], - sortOptions, - OSIndex.CurrencyFeeTransactions, - currencyIdentifier - ) - ) - ); - -export const findTransactionsByDestination = - (os: Client) => - ( - term: string, - pagination: Pagination, - currencyIdentifier: string | null - ): TaskEither> => - pipe( - exportSortOptions( - pagination, - ["snapshotOrdinal", "source", "parent.ordinal"], - () => - findTransactionByHash(os)( - pagination["searchSince"], - currencyIdentifier - ) - ), - chain((sortOptions) => - findCollectionByTerm(os)( - term, - ["destination"], - sortOptions, - currencyIdentifier - ? OSIndex.CurrencyTransactions - : OSIndex.Transactions, - currencyIdentifier - ) - ) - ); - -export const findCurrencyFeeTransactionsByDestination = - (os: Client) => - ( - address: string, - pagination: Pagination, - currencyIdentifier: string | null - ) => - pipe( - exportSortOptions( - pagination, - ["snapshotOrdinal", "source", "parent.ordinal"], - () => - findTransactionByHash(os)( - pagination["searchSince"], - currencyIdentifier - ) - ), - chain((sortOptions) => - findCollectionByTerm(os)( - address, - ["destination"], - sortOptions, - OSIndex.CurrencyFeeTransactions, - currencyIdentifier - ) - ) - ); - -export const findTransactionByHash = - (os: Client) => - ( - hash: string, - currencyIdentifier: string | null - ): TaskEither> => - pipe( - findOne( - os.get( - getDocumentQuery( - currencyIdentifier - ? OSIndex.CurrencyTransactions - : OSIndex.Transactions, - hash, - currencyIdentifier - ) - ), - currencyIdentifier - ), - map((r) => { - const { salt, ...tx } = r.data; - return { - ...r, - data: tx, - }; - }) - ); - -export const findCurrencyFeeTransactionByHash = - (os: Client) => - ( - hash: string, - currencyIdentifier: string - ): TaskEither> => - pipe( - findOne( - os.get( - getDocumentQuery( - OSIndex.CurrencyFeeTransactions, - hash, - currencyIdentifier - ) - ), - currencyIdentifier - ) - ); - -export const findBalanceByAddress = - (os: Client) => - ( - address: string, - currencyIdentifier: string | null, - ordinal?: number - ): TaskEither> => { - const sort = { - options: [ - { - sortField: "snapshotOrdinal", - // To achieve (0, ordinal> we need to make (0, ordinal + 1) - ...(ordinal !== undefined ? { searchSince: ordinal + 1 } : {}), - searchDirection: SearchDirection.Before, - }, - ], - size: 1, - }; - - return pipe( - findOne( - os.search( - getByFieldQuery( - currencyIdentifier ? OSIndex.CurrencyBalances : OSIndex.Balances, - "address", - address, - sort, - currencyIdentifier - ) - ), - currencyIdentifier - ), - map(({ data: { snapshotOrdinal, balance, address }, meta }) => ({ - data: { ordinal: snapshotOrdinal, balance, address }, - meta, - })), - orElse((e: ApplicationError) => { - return e.statusCode === StatusCodes.NOT_FOUND - ? pipe( - findSnapshot(os)("latest", currencyIdentifier), - map((s) => ({ - data: { ordinal: s.data.ordinal, balance: 0, address }, - meta: {}, - })) - ) - : left(e); - }) - ); - }; - -export const findBlockByHash = - (os: Client) => - ( - hash: string, - currencyIdentifier: string | null - ): TaskEither> => { - return findOne( - os.get( - getDocumentQuery( - currencyIdentifier ? OSIndex.CurrencyBlocks : OSIndex.Blocks, - hash, - currencyIdentifier - ) - ), - currencyIdentifier - ); - }; - -const isOrdinal = (term: string | number): term is number => - /^\d+$/.test(term.toString()); - -const isLatest = ( - termValue: string | number | "latest" -): termValue is "latest" => termValue === "latest"; - -const isSearchSinceOption = (option: any): option is SortOptionSince => - "searchSince" in option && option.searchSince !== undefined; diff --git a/src/pagination.ts b/src/pagination.ts new file mode 100644 index 0000000..7394a5e --- /dev/null +++ b/src/pagination.ts @@ -0,0 +1,106 @@ +import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; +import { handleError, respond } from "./response"; +import { Pagination } from './request-params'; +import { toNumber, isFinite } from "lodash"; + +export const maxSizeLimit = 100; + +export enum SortOrder { + Desc = 'desc', + Asc = 'asc' +} + +export enum SearchDirection { + After = 'search_after', + Before = 'search_before' +} + + +const safeNumber = (value, defaultValue) => { + const num = toNumber(value); + return (isFinite(num) && num > 0) ? num : defaultValue; +}; + +export const toCreatedAtCursor = (row) => ({ created_at: new Date(row.created_at) }); +const fromCreatedAtCursor = (row) => ({ + created_at: row.created_at.toISOString() +}); + +export const toOrdinalCursor = (row) => ({ ordinal: BigInt('0x' + row.ordinal) }); +export const fromOrdinalCursor = (row) => ({ ordinal: row.ordinal.toString(16) }); + +export const toCreatedAtOrdinalCursor = (row) => ({ + ...toCreatedAtCursor(row), + ...toOrdinalCursor(row) +}); +export const fromCreatedAtOrdinalCursor = (row) => ({ + ...fromCreatedAtCursor(row), + ...fromOrdinalCursor(row) +}); + + +const buildPageQuery = (pagination: Pagination, nextToCursor) => { + const pageSize = safeNumber(pagination.size, maxSizeLimit); + const incrementedSize = pageSize + 1; + + if ( + pagination && + 'searchSince' in pagination && pagination.searchSince && + 'searchDirection' in pagination && pagination.searchDirection + ) { + const page = + pagination.searchDirection === SearchDirection.Before + ? -incrementedSize + : incrementedSize; + return { + take: page, + cursor: pagination.searchSince + }; + } + + if (pagination && 'next' in pagination) { + return { take: incrementedSize, cursor: nextToCursor(pagination.next) }; + } + + if (pagination && pagination.size) { + return { take: incrementedSize }; + } + + return { take: incrementedSize }; +}; + +export const paginatedQuery = async ( + pagination, + nextToCursor, + cursorToNext, + baseQuery, + findMany, + transformResponse +): Promise => { + try { + const pageQueryParams = buildPageQuery(pagination, nextToCursor); + + const pagedQuery = { + ...baseQuery, + ...(pagination ? pageQueryParams : {}) + }; + + const rawResults = await findMany(pagedQuery); + + const pageSize = pageQueryParams.take + ? pageQueryParams.take - 1 + : maxSizeLimit; + const hasNextPage = rawResults.length > pageSize; + + let result = rawResults; + let next; + if (hasNextPage) { + result = rawResults.slice(0, -1); + const last = rawResults.at(-1); + next = cursorToNext(last); + } + return respond(result, transformResponse, next); + } catch (error) { + return handleError(error); + } +}; \ No newline at end of file diff --git a/src/query.ts b/src/query.ts deleted file mode 100644 index 79730b1..0000000 --- a/src/query.ts +++ /dev/null @@ -1,237 +0,0 @@ -import { ApiResponse } from "@opensearch-project/opensearch"; -import { ApplicationError, StatusCodes } from "./http"; -import { TransportRequestPromise } from "@opensearch-project/opensearch/lib/Transport"; -import { pipe } from "fp-ts/lib/function"; -import { filterOrElse, map, TaskEither, tryCatch } from "fp-ts/lib/TaskEither"; -import { CurrencyData, Ordinal } from "./model"; -import { SearchRequest } from "@opensearch-project/opensearch/api/types"; -import { Result } from "./opensearch"; - -export enum SortOrder { - Desc = "desc", - Asc = "asc", -} - -export enum SearchDirection { - After = "search_after", - Before = "search_before", -} - -export type SortOption = { - // TODO: Use Paths - sortField: string; // path or nested path - searchDirection?: SearchDirection; -}; - -export type SortOptionSince = { - // TODO: Use Paths - sortField: string; // path or nested path - searchSince: string | number; - searchDirection?: SearchDirection; -}; - -export type SortOptions = { - options: SortOption[] | SortOptionSince[]; - size?: number; -}; - -export const maxSizeLimit = 10000; - -export const getDocumentQuery = ( - index: string, - id: string, - currencyIdentifier: string | null -) => ({ - index, - id: currencyIdentifier ? currencyIdentifier + id : id, -}); - -export const getLatestQuery = ( - index: string, - currencyIdentifier: string | null -): any => ({ - index, - body: { - size: 1, - sort: { - [currencyIdentifier ? "data.ordinal" : "ordinal"]: { - order: SortOrder.Desc, - }, - }, - ...(currencyIdentifier - ? { - query: { - bool: { must: [{ match: { identifier: currencyIdentifier } }] }, - }, - } - : {}), - }, -}); - -const isSearchSince = (options: any): options is SortOptionSince[] => - typeof options[0]?.searchSince === "string" || - typeof options[0]?.searchSince === "number"; - -export const getSearchSince = (sort: SortOptions) => { - return isSearchSince(sort.options) - ? { search_after: sort.options.map((a) => a.searchSince) } - : {}; -}; - -export const getSort = ( - sort: SortOptions, - currencyIdentifier: string | null -) => { - return sort.options.length === 0 - ? [] - : sort.options.map((s) => ({ - [currencyIdentifier ? `data.${s.sortField}` : s.sortField]: - s.searchDirection === SearchDirection.After - ? SortOrder.Asc - : SortOrder.Desc, - })); -}; - -export const getMultiQuery = ( - index: string, - fields: K[], - value: T[keyof T], - sort: SortOptions, - currencyIdentifier: string | null -): SearchRequest => ({ - index, - body: { - ...getSearchSince(sort), - size: sort.size || maxSizeLimit, - sort: getSort(sort, currencyIdentifier), - query: { - bool: { - should: fields.map((field) => ({ - term: { - [currencyIdentifier ? `data.${String(field)}` : field]: value, - }, - })), - ...(currencyIdentifier - ? { must: [{ match: { identifier: currencyIdentifier } }] } - : {}), - minimum_should_match: 1, - boost: 1.0, - }, - }, - }, -}); - -export function getByFieldQuery( - index: string, - field: K, - value: T[K], - sort: SortOptions, - currencyIdentifier: string | null -): any { - return { - index, - body: { - ...getSearchSince(sort), - size: sort.size || maxSizeLimit, - sort: getSort(sort, currencyIdentifier), - query: { - bool: { - must: [ - { - term: { - [currencyIdentifier ? `data.${String(field)}` : field]: value, - }, - }, - ...(currencyIdentifier - ? [{ term: { identifier: currencyIdentifier } }] - : []), - ], - }, - }, - }, - }; -} - -export function getAll( - index: string, - sort: SortOptions, - currencyIdentifier: string | null -): any { - return { - index, - body: { - ...getSearchSince(sort), - size: sort.size || maxSizeLimit, - sort: getSort(sort, currencyIdentifier), - query: currencyIdentifier - ? { - bool: { - must: [{ term: { identifier: currencyIdentifier } }], - }, - } - : { - match_all: {}, - }, - }, - }; -} - -export const findOne = ( - search: TransportRequestPromise, - currencyIdentifier: string | null -): TaskEither> => - pipe( - tryCatch( - () => search.then((r) => (r.body.found ? [r.body] : r.body.hits.hits)), - (err: any) => { - if (err.meta?.body?.found === false) { - return new ApplicationError("Not Found", [""], StatusCodes.NOT_FOUND); - } else { - return new ApplicationError( - "OpenSearch error", - [err as string], - StatusCodes.SERVER_ERROR - ); - } - } - ), - filterOrElse( - (hits) => hits.length > 0, - () => new ApplicationError("Not Found", [], StatusCodes.NOT_FOUND) - ), - map((hits) => ({ - data: hits[0]._source as T, - })), - map((h) => { - return isWrapped(h.data) && currencyIdentifier - ? { ...h, data: h.data.data } - : h; - }) - ); - -export const findAll = ( - search: TransportRequestPromise, - currencyIdentifier: string | null -): TaskEither => - pipe( - tryCatch( - () => - search.then((r) => { - return r.body.hits.hits; - }), - (err) => - new ApplicationError( - "OpenSearch error", - [err as string], - StatusCodes.SERVER_ERROR - ) - ), - map((hits) => { - return hits.map((hit) => hit._source as T); - }), - map((hits) => - hits.map((h) => (isWrapped(h) && currencyIdentifier ? h.data : h)) - ) - ); - -const isWrapped = (a: any): a is CurrencyData => a.identifier && a.data; diff --git a/src/request-params.ts b/src/request-params.ts index 32aa9c5..32952f1 100644 --- a/src/request-params.ts +++ b/src/request-params.ts @@ -1,21 +1,14 @@ -import { APIGatewayEvent } from "aws-lambda"; -import { ApplicationError, StatusCodes } from "./http"; -import { Lens, Optional } from "monocle-ts"; -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/Option"; -import * as R from "fp-ts/Record"; -import * as TE from "fp-ts/TaskEither"; -import { TaskEither } from "fp-ts/TaskEither"; -import { maxSizeLimit, SearchDirection, SortOptions } from "./query"; +import { APIGatewayEvent } from 'aws-lambda'; +import { maxSizeLimit, SearchDirection } from './pagination'; -export type Pagination = +export type Pagination = | { size?: number; searchDirection: SearchDirection; - searchSince: string; + searchSince: any; } | { size?: number } - | { size?: number; next: string }; + | { size?: number; next: any }; type PaginationQueryParams = { search_after?: string; @@ -24,32 +17,18 @@ type PaginationQueryParams = { next?: string; }; -const pathParams = Lens.fromNullableProp()( - "pathParameters", - {} -); -type PathParams = NonNullable< - APIGatewayEvent["pathParameters"] & { - hash?: string; - term?: string; - address?: string; +export const extractHashOrdinal = (term) => { + if (isNaN(Number(term))) { + return { hash: term } + } else { + return { ordinal: BigInt(term) } } ->; +} -const pathParamsIsNotNull = (event: APIGatewayEvent) => - TE.fromPredicate( - () => Object.keys(pathParams.get(event)).length > 0, - () => - new ApplicationError( - "Error parsing request path params", - ["Path params should not be empty"], - StatusCodes.BAD_REQUEST - ) - )(event); -export const extractPagination = ( +export const extractPagination = ( event: APIGatewayEvent -): TaskEither> => { +): Pagination => { const params = event.queryStringParameters as PaginationQueryParams; const searchBefore = params?.search_before; const searchAfter = params?.search_after; @@ -57,161 +36,60 @@ export const extractPagination = ( const next = params?.next; if (searchBefore && searchAfter) { - return TE.left( - new ApplicationError( - "search_after & search_before should be mutually exclusive", - [], - StatusCodes.BAD_REQUEST - ) + throw new Error( + 'search_after & search_before should be mutually exclusive' ); } if (params?.limit !== undefined) { if (isNaN(limit)) { - return TE.left( - new ApplicationError( - "limit must be a number", - [], - StatusCodes.BAD_REQUEST - ) + throw new Error( + 'limit must be a number' ); } if (limit < 1) { - return TE.left( - new ApplicationError( - "limit must be a positive number", - [], - StatusCodes.BAD_REQUEST - ) + throw new Error( + 'limit must be a positive number', ); } if (limit > maxSizeLimit) { - return TE.left( - new ApplicationError( + throw new Error( `limit must be lower or equal ${maxSizeLimit}`, - [], - StatusCodes.BAD_REQUEST - ) ); } } if (next && searchAfter && searchBefore) { - return TE.left( - new ApplicationError( - "next and search_after/search_before should be mutually exclusive", - [], - StatusCodes.BAD_REQUEST - ) + throw new Error( + 'next and search_after/search_before should be mutually exclusive', ); } if (next) { - return TE.right({ - next, - size: params.limit !== undefined ? limit : undefined, - }); + return { + next: fromNextString(next), + size: params.limit !== undefined ? limit : undefined + }; } - return TE.right({ + return { searchSince: searchAfter || searchBefore, searchDirection: (searchAfter && SearchDirection.After) || (searchBefore && SearchDirection.Before) || undefined, - size: limit, - }); + size: limit + }; }; -export const toNextString = (options: SortOptions): string => { - const buffer = Buffer.from(JSON.stringify(options)); - return buffer.toString("base64"); +export const toNextString = (next): string => { + const buffer = Buffer.from(JSON.stringify(next)); + return buffer.toString('base64'); }; -export const fromNextString = (next: string): SortOptions => { - const buffer = Buffer.from(next, "base64"); - return JSON.parse(buffer.toString("ascii")); +export const fromNextString = (next: string) => { + const buffer = Buffer.from(next, 'base64'); + return JSON.parse(buffer.toString('ascii')); }; - -const pathParamExists = - (pathParam: keyof Partial) => (event: APIGatewayEvent) => - pipe( - TE.of(event), - TE.chainFirst(pathParamsIsNotNull), - TE.chainFirst(() => - pipe( - pathParams - .composeOptional(Optional.fromPath()([pathParam])) - .getOption(event), - TE.fromOption( - () => - new ApplicationError( - "Error parsing request path params", - [`${pathParam} param should not be empty`], - StatusCodes.BAD_REQUEST - ) - ) - ) - ) - ); - -export class RequestParamMissingError extends ApplicationError { - constructor(param: string) { - super( - "Error parsing request path params", - [`${param} param should not be empty`], - StatusCodes.BAD_REQUEST - ); - } -} - -export const getPathParam: ( - param: K -) => (event: APIGatewayEvent) => TE.TaskEither = - (param: K) => - (event: APIGatewayEvent) => - pipe( - O.fromNullable(event.pathParameters), - O.chain((params) => - pipe( - params, - R.lookup(param), - O.chain( - O.fromPredicate( - (value): value is string => typeof value === "string" - ) - ) - ) - ), - TE.fromOption(() => new RequestParamMissingError(param)) - ); - -/** @deprecated use extractCurrencyIdentifierParam */ -export const validateCurrencyIdentifierParam = (event: APIGatewayEvent) => - pipe( - TE.of(event), - TE.chain(pathParamExists("identifier")) - ); - -/** @deprecated use extractTermParam */ -export const validateTermParam = (event: APIGatewayEvent) => - pipe( - TE.of(event), - TE.chain(pathParamExists("term")) - ); - -/** @deprecated use extractHashParam */ -export const validateHashParam = (event: APIGatewayEvent) => - pipe( - TE.of(event), - TE.chain(pathParamExists("hash")) - ); - -/** @deprecated use extractAddressParam */ -export const validateAddressParam = (event: APIGatewayEvent) => - pipe( - TE.of(event), - TE.chain(pathParamExists("address")) - ); diff --git a/src/response.ts b/src/response.ts new file mode 100644 index 0000000..c5218aa --- /dev/null +++ b/src/response.ts @@ -0,0 +1,160 @@ +import { APIGatewayProxyResult } from 'aws-lambda'; +import { toNextString } from './request-params'; + +const DEFAULT_HEADERS = { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' +}; + +export type Result = { + data: T; + meta?: {}; +}; + +export type PaginatedResult = { + data: T[]; + meta: { + next: string | null; + }; +}; + +export const globalSnapshotsResponse = (ss) => ss.map(globalSnapshotResponse); + +const commonSnapshotResponse = (snapshot, blocksProperty) => ({ + hash: snapshot.hash, + ordinal: snapshot.ordinal, + height: snapshot.height, + subHeight: snapshot.subheight, + lastSnapshotHash: snapshot.last_snapshot_hash, + blocks: snapshot[blocksProperty].map((b) => b.hash), + timestamp: snapshot.created_at +}); + +export const globalSnapshotResponse = (snapshot) => ({ + ...commonSnapshotResponse(snapshot, 'dag_blocks') +}); + +export const rewardsResponse = (rs) => rs.map(rewardResponse); + +export const rewardResponse = (reward) => ({ + destination: reward.destination_addr, + amount: reward.amount +}); + +export const dagTransactionsResponse = (ts) => ts.map(dagTransactionResponse); + +export const dagTransactionResponse = (t) => + transactionResponse(t, t.dag_blocks.global_snapshots); + +const transactionResponse = (transaction, snapshot) => ({ + hash: transaction.hash, + amount: transaction.amount, + source: transaction.source_addr, + destination: transaction.destination_addr, + fee: transaction.fee, + parent: { + hash: transaction.parent_hash, + ordinal: transaction.parent_ordinal + }, + salt: transaction.salt, + blockHash: transaction.block_hash, + snapshotHash: snapshot.hash, + snapshotOrdinal: snapshot.ordinal, + timestamp: transaction.created_at +}); + +const blockResponse = (block) => ({ + hash: block.hash, + height: block.height, + parents: block.super.block_parents.map(blockParentResponse), + timestamp: block.created_at +}); + +export const dagBlockResponse = (block) => ({ + ...blockResponse(block), + transactions: block.dag_transactions.map((tx) => tx.hash), + snapshotHash: block.global_snapshots.hash, + snapshotOrdinal: block.global_snapshots.ordinal +}); + +export const blockParentResponse = (block_parent) => ({ + hash: block_parent.parent_proof_hash, + height: block_parent.parent_height +}); + +export const balanceResponse = (balance) => ({ + ordinal: balance.snapshot_ordinal, + balance: balance.balance, + address: balance.address +}); + +export const metagraphSnapshotsResponse = (ss) => + ss.map(metagraphSnapshotResponse); +export const metagraphSnapshotResponse = (snapshot) => ({ + ...commonSnapshotResponse(snapshot, 'metagraph_blocks') +}); + +export const metagraphBlockResponse = (block) => ({ + ...blockResponse(block), + transactions: block.metagraph_transactions.map((tx) => tx.hash), + snapshotHash: block.metagraph_snapshots.hash, + snapshotOrdinal: block.metagraph_snapshots.ordinal +}); + +export const metagraphTransactionsResponse = (ts) => + ts.map(metagraphTransactionResponse); +export const metagraphTransactionResponse = (t) => + transactionResponse(t, t.metagraph_blocks.metagraph_snapshots); + +export const metagraphFeeTransactionsResponse = (ts) => + ts.map(metagraphTransactionResponse); +export const metagraphFeeTransactionResponse = (t) => + transactionResponse(t, t.metagraph_snapshots); + +export const metagraphsResponse = (mgs) => mgs.map(metagraphResponse); +export const metagraphResponse = (mg) => ({ + id: mg.id, + timestamp: mg.created_at +}); + +export const respond = (data, transform, next?) => { + if (data) { + const transformed = transform(data); + const response = next + ? { data: transformed, meta: { next: toNextString(next) } } + : { data: transformed }; + return successResponse(response); + } else return notFoundResponse(); +}; + +export const successResponse = (data: any): APIGatewayProxyResult => ({ + statusCode: 200, + headers: DEFAULT_HEADERS, + body: JSON.stringify(data, (_, v) => + typeof v === 'bigint' + ? v > Number.MAX_SAFE_INTEGER + ? v.toString() + : Number(v) + : v + ) +}); + +export const notFoundResponse = (): APIGatewayProxyResult => ({ + statusCode: 404, + body: JSON.stringify({ message: 'Not found' }) +}); + +export const missingParameterResponse = ( + param: string +): APIGatewayProxyResult => ({ + statusCode: 400, + body: JSON.stringify({ message: `Missing parameter: ${param}` }) +}); + +export const handleError = (error: any): APIGatewayProxyResult => { + console.error(error); + return { + statusCode: 500, + body: JSON.stringify({ message: 'Internal Server Error' }) + }; +}; diff --git a/src/service.ts b/src/service.ts deleted file mode 100644 index b5b285b..0000000 --- a/src/service.ts +++ /dev/null @@ -1,682 +0,0 @@ -import { Client } from "@opensearch-project/opensearch"; -import { APIGatewayEvent } from "aws-lambda"; -import * as T from "fp-ts/lib/Task"; -import * as TE from "fp-ts/lib/TaskEither"; -import { Task } from "fp-ts/lib/Task"; -import { chain, fold, map, of } from "fp-ts/lib/TaskEither"; -import { - ApplicationError, - errorResponse, - Response, - StatusCodes, - successResponse, -} from "./http"; - -import { - extractPagination, - validateTermParam, - validateAddressParam, - validateHashParam, - validateCurrencyIdentifierParam, - getPathParam, -} from "./request-params"; -import { - findBalanceByAddress, - findBlockByHash, - findCurrencyFeeTransactionByHash, - findCurrencyFeeTransactionsByAddress, - findCurrencyFeeTransactionsByDestination, - findCurrencyFeeTransactionsBySnapshot, - findCurrencyFeeTransactionsBySource, - findCurrencySnapshotsByOwnerAddress, - findSnapshot, - findSnapshotRewards, - findTransactionByHash, - findTransactionsByAddress, - findTransactionsByDestination, - findTransactionsBySnapshot, - findTransactionsBySource, - listMetagraphs, - listSnapshots, - listTransactions, -} from "./opensearch"; -import { pipe } from "fp-ts/lib/function"; -import { OpenSearchCurrencySnapshotV1 } from "./model/currency-snapshot"; - -export const getCurrencySnapshots = (event: APIGatewayEvent, os: Client) => - pipe( - of(event), - chain(validateCurrencyIdentifierParam), - chain(() => - pipe( - extractPagination(event), - map((pagination) => { - return { pagination, ...extractCurrencyIdentifier(event) }; - }) - ) - ), - chain(({ pagination, currencyIdentifier }) => - listSnapshots(os)( - pagination, - currencyIdentifier - ) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getCurrencySnapshotsByOwnerAddress = ( - event: APIGatewayEvent, - os: Client -) => - pipe( - TE.Do, - TE.bind("pagination", () => extractPagination(event)), - TE.bind("ownerAddress", () => extractAddressParam(event)), - TE.chain(({ pagination, ownerAddress }) => - findCurrencySnapshotsByOwnerAddress(os)(ownerAddress, pagination) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getGlobalSnapshots = (event: APIGatewayEvent, os: Client) => - pipe( - of(event), - chain(() => - pipe( - extractPagination(event), - map((pagination) => { - return { pagination }; - }) - ) - ), - chain(({ pagination }) => listSnapshots(os)(pagination, null)), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getGlobalSnapshot = ( - event: APIGatewayEvent, - os: Client -): Task => - pipe( - of(event), - chain(validateTermParam), - map(extractTerm), - chain(({ termName, termValue }) => findSnapshot(os)(termValue, null)), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getCurrencySnapshot = ( - event: APIGatewayEvent, - os: Client -): Task => - pipe( - of(event), - chain(validateCurrencyIdentifierParam), - chain(validateTermParam), - map((event) => ({ - ...extractTerm(event), - ...extractCurrencyIdentifier(event), - })), - chain(({ termName, termValue, currencyIdentifier }) => - findSnapshot(os)( - termValue, - currencyIdentifier - ) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getGlobalSnapshotRewards = ( - event: APIGatewayEvent, - os: Client -): Task => - pipe( - of(event), - chain(validateTermParam), - map(extractTerm), - chain(({ termName, termValue }) => - findSnapshotRewards(os)(termValue, null) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getCurrencySnapshotRewards = ( - event: APIGatewayEvent, - os: Client -): Task => - pipe( - of(event), - chain(validateCurrencyIdentifierParam), - chain(validateTermParam), - map((event) => ({ - ...extractTerm(event), - ...extractCurrencyIdentifier(event), - })), - chain(({ termName, termValue, currencyIdentifier }) => - findSnapshotRewards(os)(termValue, currencyIdentifier) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getGlobalSnapshotTransactions = ( - event: APIGatewayEvent, - os: Client -): Task => - pipe( - of(event), - chain(validateTermParam), - map(extractTerm), - chain(({ termName, termValue }) => - pipe( - extractPagination(event), - map((pagination) => ({ termName, termValue, pagination })) - ) - ), - chain(({ termName, termValue, pagination }) => - findTransactionsBySnapshot(os)(termValue, pagination, null) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getCurrencySnapshotTransactions = ( - event: APIGatewayEvent, - os: Client -): Task => - pipe( - of(event), - chain(validateTermParam), - chain(validateCurrencyIdentifierParam), - map((event) => ({ - ...extractTerm(event), - ...extractCurrencyIdentifier(event), - })), - chain(({ termName, termValue, currencyIdentifier }) => - pipe( - extractPagination(event), - map((pagination) => ({ - termName, - termValue, - pagination, - currencyIdentifier, - })) - ) - ), - chain(({ termName, termValue, pagination, currencyIdentifier }) => - findTransactionsBySnapshot(os)(termValue, pagination, currencyIdentifier) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getCurrencySnapshotFeeTransactions = ( - event: APIGatewayEvent, - os: Client -) => - pipe( - TE.Do, - TE.bind("identifier", () => extractCurrencyIdentifierParam(event)), - TE.bind("term", () => extractTermParam(event)), - TE.bind("pagination", () => extractPagination(event)), - TE.chain(({ identifier, term: { termValue }, pagination }) => { - console.log("params: ", { identifier, termValue, pagination }); - return findCurrencyFeeTransactionsBySnapshot(os)( - termValue, - pagination, - identifier - ); - }), - TE.fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getCurrencyBlock = (event: APIGatewayEvent, os: Client) => - pipe( - of(event), - chain(validateCurrencyIdentifierParam), - fold( - (reason) => T.of(errorResponse(reason)), - (validatedEvent) => getBlock(validatedEvent, os) - ) - ); - -export const getBlock = (event: APIGatewayEvent, os: Client): Task => - pipe( - of(event), - chain(validateHashParam), - map(extractHash), - map((hash) => ({ - ...hash, - ...extractCurrencyIdentifier(event), - })), - chain(({ hash, currencyIdentifier }) => - findBlockByHash(os)(hash, currencyIdentifier) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getCurrencyTransactions = (event: APIGatewayEvent, os: Client) => - pipe( - of(event), - chain(validateCurrencyIdentifierParam), - fold( - (reason) => T.of(errorResponse(reason)), - (validatedEvent) => getTransactions(validatedEvent, os) - ) - ); - -export const getTransactions = (event: APIGatewayEvent, os: Client) => - pipe( - of(event), - chain(() => - pipe( - extractPagination(event), - map((pagination) => { - return { - pagination, - ...extractCurrencyIdentifier(event), - }; - }) - ) - ), - chain(({ pagination, currencyIdentifier }) => - listTransactions(os)(pagination, currencyIdentifier) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getCurrencyTransactionsByAddress = ( - event: APIGatewayEvent, - os: Client -) => - pipe( - of(event), - chain(validateCurrencyIdentifierParam), - fold( - (reason) => T.of(errorResponse(reason)), - (validatedEvent) => getTransactionsByAddress(validatedEvent, os) - ) - ); - -export const getCurrencyFeeTransactionsByAddress = ( - event: APIGatewayEvent, - os: Client -) => - pipe( - TE.Do, - TE.bind("address", () => extractAddressParam(event)), - TE.bind("pagination", () => extractPagination(event)), - TE.bind("identifier", () => extractCurrencyIdentifierParam(event)), - TE.chain(({ address, pagination, identifier }) => - findCurrencyFeeTransactionsByAddress(os)(address, pagination, identifier) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getTransactionsByAddress = ( - event: APIGatewayEvent, - os: Client -): Task => - pipe( - of(event), - chain(validateAddressParam), - map(extractAddress), - chain(({ address }) => - pipe( - extractPagination(event), - map((pagination) => { - return { address, pagination, ...extractCurrencyIdentifier(event) }; - }) - ) - ), - chain(({ address, pagination, currencyIdentifier }) => - findTransactionsByAddress(os)(address, pagination, currencyIdentifier) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getCurrencyTransactionsBySource = ( - event: APIGatewayEvent, - os: Client -) => - pipe( - of(event), - chain(validateCurrencyIdentifierParam), - fold( - (reason) => T.of(errorResponse(reason)), - (validatedEvent) => getTransactionsBySource(validatedEvent, os) - ) - ); - -export const getCurrencyFeeTransactionsBySource = ( - event: APIGatewayEvent, - os: Client -) => - pipe( - TE.Do, - TE.bind("address", () => extractAddressParam(event)), - TE.bind("pagination", () => extractPagination(event)), - TE.bind("identifier", () => extractCurrencyIdentifierParam(event)), - TE.chain(({ address, pagination, identifier }) => - findCurrencyFeeTransactionsBySource(os)(address, pagination, identifier) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getTransactionsBySource = ( - event: APIGatewayEvent, - os: Client -): Task => - pipe( - of(event), - chain(validateAddressParam), - map(extractAddress), - chain(({ address }) => - pipe( - extractPagination(event), - map((pagination) => ({ - address, - pagination, - ...extractCurrencyIdentifier(event), - })) - ) - ), - chain(({ address, pagination, currencyIdentifier }) => - findTransactionsBySource(os)(address, pagination, currencyIdentifier) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getCurrencyTransactionsByDestination = ( - event: APIGatewayEvent, - os: Client -) => - pipe( - of(event), - chain(validateCurrencyIdentifierParam), - fold( - (reason) => T.of(errorResponse(reason)), - (validatedEvent) => getTransactionsByDestination(validatedEvent, os) - ) - ); - -export const getCurrencyFeeTransactionsByDestination = ( - event: APIGatewayEvent, - os: Client -) => - pipe( - TE.Do, - TE.bind("address", () => extractAddressParam(event)), - TE.bind("pagination", () => extractPagination(event)), - TE.bind("identifier", () => extractCurrencyIdentifierParam(event)), - TE.chain(({ address, pagination, identifier }) => - findCurrencyFeeTransactionsByDestination(os)( - address, - pagination, - identifier - ) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getTransactionsByDestination = ( - event: APIGatewayEvent, - os: Client -): Task => - pipe( - of(event), - chain(validateAddressParam), - map(extractAddress), - chain(({ address }) => - pipe( - extractPagination(event), - map((pagination) => ({ - address, - pagination, - ...extractCurrencyIdentifier(event), - })) - ) - ), - chain(({ address, pagination, currencyIdentifier }) => - findTransactionsByDestination(os)(address, pagination, currencyIdentifier) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getCurrencyBalanceByAddress = ( - event: APIGatewayEvent, - os: Client -) => - pipe( - of(event), - chain(validateCurrencyIdentifierParam), - fold( - (reason) => T.of(errorResponse(reason)), - (validatedEvent) => getBalanceByAddress(validatedEvent, os) - ) - ); - -export const getBalanceByAddress = ( - event: APIGatewayEvent, - os: Client -): Task => - pipe( - of(event), - chain(validateAddressParam), - map(extractAddressAndOrdinal), - map((params) => ({ - ...params, - ...extractCurrencyIdentifier(event), - })), - chain(({ address, ordinal, currencyIdentifier }) => - findBalanceByAddress(os)(address, currencyIdentifier, ordinal) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getCurrencyTransaction = (event: APIGatewayEvent, os: Client) => - pipe( - extractCurrencyIdentifierParam(event), - TE.fold( - (reason) => T.of(errorResponse(reason)), - (identifier) => getTransaction(event, os, identifier) - ) - ); - -export const getCurrencyFeeTransaction = (event: APIGatewayEvent, os: Client) => - pipe( - TE.Do, - TE.bind("hash", () => extractHashParam(event)), - TE.bind("identifier", () => extractCurrencyIdentifierParam(event)), - TE.chain(({ hash, identifier }) => - findCurrencyFeeTransactionByHash(os)(hash, identifier) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getMetagraphs = (event: APIGatewayEvent, os: Client) => - pipe( - TE.Do, - TE.bind("pagination", () => extractPagination(event)), - TE.chain(({ pagination }) => listMetagraphs(os)(pagination)), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getTransaction = ( - event: APIGatewayEvent, - os: Client, - currencyIdentifier: string | null -): Task => - pipe( - extractHashParam(event), - chain((hash) => findTransactionByHash(os)(hash, currencyIdentifier)), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -/** @deprecated use extractCurrencyIdentifierParam */ -const extractCurrencyIdentifier = ( - event: APIGatewayEvent -): { currencyIdentifier: string | null } => { - return { - currencyIdentifier: event.pathParameters?.identifier || null, - }; -}; - -/** @deprecated use extractHashParam */ -const extractHash = (event: APIGatewayEvent) => { - return { hash: event.pathParameters!.hash! }; -}; - -/** @deprecated use extractAddressParam */ -const extractAddress = (event: APIGatewayEvent) => { - return { address: event.pathParameters!.address! }; -}; - -/** @deprecated use extractTermParam */ -const extractTerm = (event: APIGatewayEvent) => { - if (event.pathParameters!.term == "latest") - return { - termName: "ordinal", - termValue: "latest", - }; - return { - termName: isNaN(Number(event.pathParameters!.term!)) ? "hash" : "ordinal", - termValue: event.pathParameters!.term!, - }; -}; - -/** @deprecated use extractAddressParam and extractOrdinalParam */ -const extractAddressAndOrdinal = ( - event: APIGatewayEvent -): { address: string; ordinal?: number } => { - const ordinal = Number(event.queryStringParameters?.ordinal); - return { - address: event.pathParameters!.address!, - ...(!isNaN(ordinal) ? { ordinal } : {}), - }; -}; - -export const extractCurrencyIdentifierParam = (event: APIGatewayEvent) => - pipe( - TE.of(event), - TE.chain(getPathParam("identifier")) - ); - -export const extractTermParam = (event: APIGatewayEvent) => - pipe( - TE.of(event), - TE.chain(getPathParam("term")), - TE.map((term) => { - if (term == "latest") - return { - termName: "ordinal", - termValue: "latest", - }; - return { - termName: isNaN(Number(term)) ? "hash" : "ordinal", - termValue: term, - }; - }) - ); - -export const extractHashParam = (event: APIGatewayEvent) => - pipe( - TE.of(event), - TE.chain(getPathParam("hash")) - ); - -export const extractAddressParam = (event: APIGatewayEvent) => - pipe( - TE.of(event), - TE.chain(getPathParam("address")) - ); - -export const extractNextParam = (event: APIGatewayEvent) => - pipe( - TE.of(event), - TE.map((event) => event.queryStringParameters?.next) - ); - -export const extractLimitParam = (event: APIGatewayEvent) => - pipe( - TE.of(event), - TE.chain((event) => { - const limitParam = event.queryStringParameters?.limit; - const limit = Number(limitParam); - - if (limitParam !== undefined && isNaN(limit)) { - return TE.left( - new ApplicationError( - "limit must be a number", - [], - StatusCodes.BAD_REQUEST - ) - ); - } - - return TE.right(limit || undefined); - }) - ); diff --git a/src/ts-extensions.ts b/src/ts-extensions.ts deleted file mode 100644 index 2ed014c..0000000 --- a/src/ts-extensions.ts +++ /dev/null @@ -1,97 +0,0 @@ -type FilterUndefined = T extends undefined ? never : T; -type FilterNull = T extends null ? never : T; -type FilterUndefinedAndNull = FilterUndefined>; - -type ExtractFromObject< - O extends Record, - K -> = K extends keyof O - ? O[K] - : K extends keyof FilterUndefinedAndNull - ? FilterUndefinedAndNull[K] | undefined - : undefined; - -type ExtractFromArray = any[] extends A - ? A extends readonly (infer T)[] - ? T | undefined - : undefined - : K extends keyof A - ? A[K] - : undefined; - -type GetWithArray = K extends [] - ? O - : K extends [infer Key, ...infer Rest] - ? O extends Record - ? GetWithArray, Rest> - : O extends readonly any[] - ? GetWithArray, Rest> - : undefined - : never; - -export type Path = T extends `${infer Key}.${infer Rest}` - ? [Key, ...Path] - : T extends `${infer Key}` - ? [Key] - : []; - -export type Get = GetWithArray>; - -type Join = K extends string | number - ? P extends string | number - ? `${K}${"" extends P ? "" : "."}${P}` - : never - : never; - -type Prev = [ - never, - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - ...0[] -]; - -export type Paths = [D] extends [never] - ? never - : T extends object - ? { - [K in keyof T]-?: K extends string | number - ? `${K}` | Join> - : never; - }[keyof T] - : ""; - -export type ResolvePath< - T, - P extends string -> = P extends `${infer K}.${infer Rest}` - ? K extends keyof T - ? ResolvePath - : never - : P extends keyof T - ? T[P] - : never; - -export type ResolveValues> = K extends infer P - ? P extends string - ? ResolvePath - : never - : never; diff --git a/test_endpoints.sh b/test_endpoints.sh new file mode 100644 index 0000000..4d6ad93 --- /dev/null +++ b/test_endpoints.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +# Global Snapshots +curl http://localhost:3001/global-snapshots #ok +curl http://localhost:3001/global-snapshots/695097c844c520c40577f168b85b994558024b44fd64c245154625c89fda024f #ok +curl http://localhost:3001/global-snapshots/155849 #ok +curl http://localhost:3001/global-snapshots/latest + +curl http://localhost:3001/global-snapshots/155849/rewards #ok +curl http://localhost:3001/global-snapshots/695097c844c520c40577f168b85b994558024b44fd64c245154625c89fda024f/rewards #ok +curl http://localhost:3001/global-snapshots/latest/rewards # ok + +curl http://localhost:3001/global-snapshots/390792/transactions #ok +curl http://localhost:3001/global-snapshots/latest/transactions # ok + +# Blocks +curl http://localhost:3001/blocks/9641acffb4a5276d013e49676fcea51e231c5c9737b5866f8df07de04145e374 #ok + +# Transactions +curl http://localhost:3001/transactions #ok +curl http://localhost:3001/transactions/d8b1f2dec3030dd4e9b17e63871cc4bdb2ecb1f4d8bda079c75fdfac3a685c79 #ok +curl http://localhost:3001/addresses/DAG1x6erYwDLvt3zXxZvdHaZcNVhWfFFVC1Qunjd/transactions #ok +curl http://localhost:3001/addresses/DAG1x6erYwDLvt3zXxZvdHaZcNVhWfFFVC1Qunjd/transactions/sent #ok +curl http://localhost:3001/addresses/DAG0WtbjHQu5LsZ57DcxX7kuA5DyyvG5QYG2uu9X/transactions/received #ok + +# Address Balance +curl http://localhost:3001/addresses/DAG4J3i4K87evc71ti3aEcSKx8AUR5GhPy3EysiM/balance #ok + +# Currency Snapshots +curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/snapshots #ok + +# curl http://localhost:3001/addresses/{address}/snapshots + +curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/snapshots/1c0b793cd83af5d761d71b887f068b078daf2bdab2f7f9e5fedf09c8f63dd6b8 #ok +curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/snapshots/22 #ok + +curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/snapshots/b2bd7c8ad9f2625895f7a89ca67072ed13cb5df300e0c2343ee48b6668490fa6/rewards #ok +curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/snapshots/3098/rewards #ok +curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/snapshots/latest/rewards # ok + +curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/snapshots/f960a3b8e55277afc7db968a210d66b8827d84ebf2d7ec9dfcda8389141c45eb/transactions #ok +curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/snapshots/41589/transactions #ok +curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/snapshots/latest/transactions # ok + +# Currency Blocks +curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/blocks/1383f4cdfb1d95f2585f0a0a459ae025020a726a500d920fc8cf35b8a4aeec02 #ok + +# Currency Transactions +curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/transactions +curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/transactions/0ab3b933e40e2c7619a29ddc7e4c266888a5ad1f2259a7aacbb1478986404cc2 #ok +curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/addresses/DAG7SWAF9DoD9rJ7Qqy3gNjefVfGw7HZZkwC4a4N/transactions #ok +curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/addresses/DAG7SWAF9DoD9rJ7Qqy3gNjefVfGw7HZZkwC4a4N/transactions/sent #ok +curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/addresses/DAG2fMnbEmsWhgYGhvdREVELyESKUqGNTEWf4B61/transactions/received #ok + +# Currency Balances +curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/addresses/DAG2fMnbEmsWhgYGhvdREVELyESKUqGNTEWf4B61/balance #ok + +# Currency Fee Transactions +# curl http://localhost:3001/currency/{identifier}/fee-transactions/{hash} +# curl http://localhost:3001/currency/{identifier}/snapshots/{term}/fee-transactions +# curl http://localhost:3001/currency/{identifier}/addresses/{address}/fee-transactions +# curl http://localhost:3001/currency/{identifier}/addresses/{address}/fee-transactions/sent +# curl http://localhost:3001/currency/{identifier}/addresses/{address}/fee-transactions/received + +# Metagraphs +curl http://localhost:3001/currency #ok diff --git a/tests/opensearch.test.ts b/tests/opensearch.test.ts deleted file mode 100644 index 1cca3a9..0000000 --- a/tests/opensearch.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { getFromPath } from "../src/opensearch"; - -describe("getFromPath", () => { - const testObj = { - a: 1, - b: { - c: 2, - d: { - e: 3, - f: null, - g: undefined, - }, - }, - h: [1, 2, { i: 4 }], - j: null, - k: undefined, - }; - - it("should return the value for a simple path", () => { - expect(getFromPath(testObj, "a")).toBe(1); - }); - - it("should return the value for a nested path", () => { - expect(getFromPath(testObj, "b.c")).toBe(2); - expect(getFromPath(testObj, "b.d.e")).toBe(3); - }); - - it("should return undefined for non-existent paths", () => { - expect(getFromPath(testObj, "x")).toBeUndefined(); - expect(getFromPath(testObj, "b.x")).toBeUndefined(); - expect(getFromPath(testObj, "b.c.x")).toBeUndefined(); - }); - - it("should return undefined for paths leading to null or undefined", () => { - expect(getFromPath(testObj, "b.d.f")).toBeUndefined(); - expect(getFromPath(testObj, "b.d.g")).toBeUndefined(); - expect(getFromPath(testObj, "j")).toBeUndefined(); - expect(getFromPath(testObj, "k")).toBeUndefined(); - }); - - it("should handle array access", () => { - expect(getFromPath(testObj, "h.0")).toBe(1); - expect(getFromPath(testObj, "h.2.i")).toBe(4); - }); - - it("should return undefined for out-of-bounds array access", () => { - expect(getFromPath(testObj, "h.3")).toBeUndefined(); - }); - - it("should return the entire object for an empty path", () => { - expect(getFromPath(testObj, "")).toEqual(testObj); - }); - - it("should return undefined when trying to access properties on primitives", () => { - expect(getFromPath(testObj, "a.x")).toBeUndefined(); - }); - - it("should handle complex nested structures", () => { - const complexObj = { - a: { - b: [{ c: { d: 1 } }, { c: { d: 2 } }], - }, - }; - expect(getFromPath(complexObj, "a.b.0.c.d")).toBe(1); - expect(getFromPath(complexObj, "a.b.1.c.d")).toBe(2); - }); -}); diff --git a/tests/request-params.test.ts b/tests/request-params.test.ts deleted file mode 100644 index 9e99991..0000000 --- a/tests/request-params.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { APIGatewayEvent } from "aws-lambda"; -import * as E from "fp-ts/Either"; -import { pipe } from "fp-ts/function"; -import { getPathParam, RequestParamMissingError } from "../src/request-params"; - -describe("getPathParam", () => { - const mockEvent = ( - pathParameters: Record | null - ): APIGatewayEvent => - ({ - pathParameters, - } as APIGatewayEvent); - - it("should return the correct path parameter when it exists", async () => { - const event = mockEvent({ id: "123" }); - const result = await getPathParam("id")(event)(); - - pipe( - result, - E.fold( - (error) => { - fail(`Expected Right, but got Left: ${error}`); - }, - (value) => { - expect(value).toBe("123"); - } - ) - ); - }); - - it("should return a RequestParamMissingError when pathParameters is null", async () => { - const event = mockEvent(null); - const result = await getPathParam("id")(event)(); - - pipe( - result, - E.fold( - (error) => { - expect(error).toBeInstanceOf(RequestParamMissingError); - }, - (value) => { - fail(`Expected Left, but got Right: ${value}`); - } - ) - ); - }); - - it("should return a RequestParamMissingError when the requested parameter does not exist", async () => { - const event = mockEvent({ otherId: "456" }); - const result = await getPathParam("id")(event)(); - - pipe( - result, - E.fold( - (error) => { - expect(error).toBeInstanceOf(RequestParamMissingError); - }, - (value) => { - fail(`Expected Left, but got Right: ${value}`); - } - ) - ); - }); - - it("should return a RequestParamMissingError when the parameter value is undefined", async () => { - const event = mockEvent({ id: undefined }); - const result = await getPathParam("id")(event)(); - - pipe( - result, - E.fold( - (error) => { - expect(error).toBeInstanceOf(RequestParamMissingError); - }, - (value) => { - fail(`Expected Left, but got Right: ${value}`); - } - ) - ); - }); -}); diff --git a/tests/validation.test.ts b/tests/validation.test.ts index acf840e..0d09878 100644 --- a/tests/validation.test.ts +++ b/tests/validation.test.ts @@ -1,18 +1,14 @@ -import { - extractPagination, - validateHashParam, - validateTermParam, -} from "../src/request-params"; -import { APIGatewayEvent } from "aws-lambda"; -import { Lens } from "monocle-ts"; -import { isLeft, isRight, right } from "fp-ts/lib/Either"; -import { pipe } from "fp-ts/lib/pipeable"; +import { extractPagination } from '../src/request-params'; +import { APIGatewayEvent } from 'aws-lambda'; +import { Lens } from 'monocle-ts'; +import { isLeft, isRight, right } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; const baseEvent: APIGatewayEvent = { - httpMethod: "get", + httpMethod: 'get', isBase64Encoded: false, - path: "", - resource: "", + path: '', + resource: '', body: null, headers: {}, multiValueHeaders: {}, @@ -20,15 +16,15 @@ const baseEvent: APIGatewayEvent = { queryStringParameters: null, multiValueQueryStringParameters: null, stageVariables: null, - requestContext: {} as any, + requestContext: {} as any }; -const pathParams = Lens.fromProp()("pathParameters"); -const queryParams = Lens.fromProp()("queryStringParameters"); +const pathParams = Lens.fromProp()('pathParameters'); +const queryParams = Lens.fromProp()('queryStringParameters'); const setParam = (param: string, value: string) => pathParams.modify((a) => ({ ...a, [param]: value })); -const setTerm = (term: string) => setParam("term", term); +const setTerm = (term: string) => setParam('term', term); const setSearchAfter = (search_after: string) => queryParams.modify((a) => ({ ...a, search_after })); const setSearchBefore = (search_before: string) => @@ -36,47 +32,13 @@ const setSearchBefore = (search_before: string) => const setLimit = (limit: string) => queryParams.modify((a) => ({ ...a, limit })); -describe("validateTermParam ", () => { - it("should not pass when no term in path parameter is provided", async () => { - const result = await validateTermParam(baseEvent)(); - - expect(isLeft(result)).toBe(true); - }); - - it("should pass returning event when term in path parameter is present", async () => { - const event = setTerm("123")(baseEvent); - - const result = await validateTermParam(event)(); - const expected = right(event); - - expect(result).toStrictEqual(expected); - }); -}); - -describe("validateHashParam", () => { - it("should not pass when no hash in path parameter is provided", async () => { - const result = await validateHashParam(baseEvent)(); - - expect(isLeft(result)).toBe(true); - }); - - it("should pass returning event when hash in path parameter is present", async () => { - const event = setParam("hash", "hash")(baseEvent); - - const result = await validateHashParam(event)(); - const expected = right(event); - - expect(result).toStrictEqual(expected); - }); -}); - -describe("extractPagination", () => { - it("should not pass when both search_after and search_before", async () => { +describe('extractPagination', () => { + it('should not pass when both search_after and search_before', async () => { const event = pipe( baseEvent, - setLimit("2"), - setSearchAfter("aa"), - setSearchBefore("bb") + setLimit('2'), + setSearchAfter('aa'), + setSearchBefore('bb') ); const result = await extractPagination(event)(); @@ -84,31 +46,31 @@ describe("extractPagination", () => { expect(isLeft(result)).toBe(true); }); - it("should pass when searchAfter is provided but limit not", async () => { + it('should pass when searchAfter is provided but limit not', async () => { const event = pipe( baseEvent, - setParam("address", "123"), - setSearchAfter("aa") + setParam('address', '123'), + setSearchAfter('aa') ); const result = await extractPagination(event)(); expect(isRight(result)).toBe(true); }); - it("should pass when limit is provided but searchAfter not", async () => { - const event = pipe(baseEvent, setParam("address", "123"), setLimit("12")); + it('should pass when limit is provided but searchAfter not', async () => { + const event = pipe(baseEvent, setParam('address', '123'), setLimit('12')); const result = await extractPagination(event)(); right(event); expect(isRight(result)).toBe(true); }); - it("should pass returning event when both searchAfter and limit are provided", async () => { + it('should pass returning event when both searchAfter and limit are provided', async () => { const event = pipe( baseEvent, - setParam("address", "123"), - setSearchAfter("aa"), - setLimit("2") + setParam('address', '123'), + setSearchAfter('aa'), + setLimit('2') ); const result = await extractPagination(event)(); @@ -116,12 +78,12 @@ describe("extractPagination", () => { expect(isRight(result)).toBe(true); }); - it("should pass returning event when both searchBefore and limit are provided", async () => { + it('should pass returning event when both searchBefore and limit are provided', async () => { const event = pipe( baseEvent, - setParam("address", "123"), - setSearchBefore("aa"), - setLimit("2") + setParam('address', '123'), + setSearchBefore('aa'), + setLimit('2') ); const result = await extractPagination(event)(); From 13f604510c15a567d9796c8f2439bf69d1eacdf4 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Thu, 13 Mar 2025 13:20:26 -0300 Subject: [PATCH 03/63] temporarily change stage --- .github/workflows/deploy-postgres.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-postgres.yml b/.github/workflows/deploy-postgres.yml index 24f2ddf..179fcb2 100644 --- a/.github/workflows/deploy-postgres.yml +++ b/.github/workflows/deploy-postgres.yml @@ -37,7 +37,7 @@ jobs: - name: Serverless deploy uses: serverless/github-action@v3 with: - args: deploy --stage ${{ inputs.environment }}20 --region us-west-1 + args: deploy --stage ${{ inputs.environment }} --region us-west-1 env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} From 3508b342886c71b6508a233f3403981997f2c643 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Thu, 13 Mar 2025 14:15:28 -0300 Subject: [PATCH 04/63] add prisma binary targets --- prisma/schema.prisma | 2 ++ 1 file changed, 2 insertions(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b578023..af747e8 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,7 +1,9 @@ generator client { provider = "prisma-client-js" + binaryTargets = ["native", "rhel-openssl-3.0.x"] } + datasource db { provider = "postgresql" url = env("DATABASE_URL") From b5e725ce466c1b132fe10d229fb1d0c4d192cfaf Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Thu, 27 Mar 2025 15:33:09 -0300 Subject: [PATCH 05/63] add new endpoints --- CHANGELOG.md | 21 ++ README.md | 11 +- babel.config.js | 22 +- env.yml | 4 +- prisma/schema.prisma | 292 ++++++++++++--------- routes/actions.yml | 72 +++--- routes/allow-spends.yml | 14 +- routes/dag.yml | 133 +++++----- routes/metagraph.yml | 216 ++++++++-------- routes/token-locks.yml | 24 +- serverless.yml | 14 +- src/handlers/actionsHandler.ts | 369 ++++++++++++++++++--------- src/handlers/allowSpendsHandler.ts | 396 +++++++++++++++++++++++++++-- src/handlers/dagHandler.ts | 140 +++++----- src/handlers/metagraphHandler.ts | 247 ++++++++++-------- src/handlers/tokenLocksHandler.ts | 231 ++++++++++++++--- src/pagination.ts | 53 ++-- src/request-params.ts | 41 ++- src/response.ts | 48 ++-- test_actions.sh | 53 ++++ test_allowspends.sh | 33 +++ test_endpoints.sh | 25 +- test_tokenlocks.sh | 53 ++++ tests/validation.test.ts | 82 +++--- tsconfig.json | 15 +- 25 files changed, 1744 insertions(+), 865 deletions(-) create mode 100644 test_actions.sh create mode 100644 test_allowspends.sh create mode 100644 test_tokenlocks.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index fce9700..dbd4783 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,51 +1,72 @@ # Changelog + All notable changes to this project will be documented in this file. ## [Unreleased] ## [v3.1.0] 2023-06-15 + - Fix currency snapshot rewards ## [v3.0.6] 2023-06-15 + - Currency Framework support ## [v3.0.5] 2023-03-30 + - Revert "Fix false non-zero balances" ## [v3.0.4] 2023-02-24 + - Fix deploy workflow ## [v3.0.3] 2023-02-24 + - Fix false non-zero balances ## [v3.0.2] 2022-11-17 + - Fix 'next' reference for pagination ## [v3.0.1] 2022-10-05 + - Fix returned type of balances endpoint ## [v3.0.0] 2022-09-22 + Mainnet 2.0 - upgraded block explorer ## [v2.0.4] 2021-10-15 + ### Fixed + - Fetch by address using keyword field ## [v2.0.3] 2021-06-25 + ### Changed + - Fetch checkpoint block by soe hash by default and fallback to search by base hash ## [v2.0.2] 2020-10-19 + ### Changed + - Query parameters search_after and limit can be provided independently ## [v2.0.1] 2020-10-16 + ### Added + - Pagination support + ### Changed + - Get by hash triggers document API - Max size limit when no pagination used ## [v2.0.0] 2020-10-13 + ### Added + - Changelog introduced diff --git a/README.md b/README.md index b72644d..6dd5923 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -Block Explorer -=========== +# Block Explorer ![build](https://img.shields.io/github/actions/workflow/status/Constellation-Labs/block_explorer/release.yml?label=build) ![version](https://img.shields.io/github/v/release/Constellation-Labs/block_explorer?sort=semver) @@ -15,17 +14,21 @@ Block Explorer exposes API functions to retrieve on-chain data from a tessellati 3. [Docker Desktop](https://www.docker.com/get-started/) with [Kubernetes](https://docs.docker.com/desktop/kubernetes/) enabled ### Setup local development cluster + An [opensearch](https://aws.amazon.com/what-is/opensearch/) instance is used to store and query the on-chain data. -Follow the instructions from the [snapshot streaming](https://github.com/Constellation-Labs/snapshot-streaming) repository which sets up your local tessellation development cluster along with an opensearch instance (hosted on port `4510`). +Follow the instructions from the [snapshot streaming](https://github.com/Constellation-Labs/snapshot-streaming) repository which sets up your local tessellation development cluster along with an opensearch instance (hosted on port `4510`). ### Run + Install the npm packages from the project directory: + ``` npm install ``` Start the serverless offline host to test the APIs locally: + ``` serverless offline ``` @@ -34,8 +37,8 @@ The output of this command shows an overview of the function URL's that can be c ## Unit Tests - Run the unit tests locally: + ``` npm run test ``` diff --git a/babel.config.js b/babel.config.js index 10f7d23..1f86f72 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,13 +1,13 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - }, - ], - '@babel/preset-typescript', + presets: [ + [ + "@babel/preset-env", + { + targets: { + node: "current", + }, + }, ], -}; \ No newline at end of file + "@babel/preset-typescript", + ], +}; diff --git a/env.yml b/env.yml index b3c682f..fac7483 100644 --- a/env.yml +++ b/env.yml @@ -1,4 +1,4 @@ default: - opensearch: 'http://localhost:9200' + opensearch: "http://localhost:9200" vpc: {} - db_url: postgresql://postgres:postgres@localhost:5432/new_block_explorer?schema=public \ No newline at end of file + db_url: postgresql://postgres:postgres@localhost:5432/new_block_explorer?schema=public diff --git a/prisma/schema.prisma b/prisma/schema.prisma index af747e8..e7ba046 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,9 +1,8 @@ generator client { - provider = "prisma-client-js" + provider = "prisma-client-js" binaryTargets = ["native", "rhel-openssl-3.0.x"] } - datasource db { provider = "postgresql" url = env("DATABASE_URL") @@ -11,13 +10,14 @@ datasource db { /// This table has subclasses and requires additional setup for migrations. Visit https://pris.ly/d/table-inheritance for more info. model abstract_blocks { - hash String @id(map: "block_pkey") @db.VarChar - height BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - block_parents block_parents[] - dag_blocks dag_blocks? @relation(fields: [hash], references: [hash], map: "dag_blocks_hash_fk") - metagraph_blocks metagraph_blocks? @relation(fields: [hash], references: [hash], map: "metagraph_blocks_hash_fk") + hash String @id(map: "block_pkey") @db.VarChar + height BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + + block_parents block_parents[] + dag_block dag_blocks? @relation(fields: [hash], references: [hash], map: "dag_blocks_hash_fk") + metagraph_block metagraph_blocks? @relation(fields: [hash], references: [hash], map: "metagraph_blocks_hash_fk") } /// This table has subclasses and requires additional setup for migrations. Visit https://pris.ly/d/table-inheritance for more info. @@ -49,6 +49,9 @@ model abstract_transactions_view { dag_spend_transaction dag_spend_transactions? @relation(fields: [hash], references: [hash], map: "dag_spend_transaction_ref") metagraph_spend_transaction metagraph_spend_transactions? @relation(fields: [hash], references: [hash], map: "metagraph_spend_transaction_ref") + dag_expired_spend_transaction dag_expired_spend_transactions? @relation(fields: [hash], references: [hash], map: "dag_expired_spend_transaction_ref") + metagraph_expired_spend_transaction metagraph_expired_spend_transactions? @relation(fields: [hash], references: [hash], map: "metagraph_expired_spend_transaction_ref") + metagraph_fee_transaction metagraph_fee_transactions? @relation(fields: [hash], references: [hash], map: "metagraph_fee_transaction_ref") } @@ -89,13 +92,14 @@ model block_parents { abstract_blocks abstract_blocks @relation(fields: [hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "block_parents_block_fk") @@id([hash, parent_proof_hash]) + @@index([hash]) } model dag_allow_spend_approvers { allow_spend_hash String @db.VarChar approver_address String @db.VarChar - addresses addresses @relation(fields: [approver_address], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "dag_allow_spend_approvers_address_fk") - dag_allow_spends dag_allow_spends @relation(fields: [allow_spend_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "dag_allow_spends_fk") + addresses addresses @relation(fields: [approver_address], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spend_approvers_address_fk") + dag_allow_spends dag_allow_spends @relation(fields: [allow_spend_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spends_fk") @@id([allow_spend_hash, approver_address], map: "dag_allow_spend_approvers_pk") } @@ -105,29 +109,33 @@ model dag_allow_spend_blocks { global_snapshot_hash String @id @db.VarChar created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - global_snapshot global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "dag_allow_spend_blocks_global_snapshot_fk") + global_snapshot global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spend_blocks_global_snapshot_fk") dag_allow_spends dag_allow_spends[] } model dag_allow_spends { - hash String @id(map: "dag_allow_spends_pk") @db.VarChar - source_addr String @db.VarChar + hash String @id(map: "dag_allow_spends_pk") @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - destination_addr String @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + destination_addr String @db.VarChar fee BigInt parent_ordinal BigInt? - parent_hash String? @db.VarChar + parent_hash String? @db.VarChar last_valid_epoch_progress BigInt - round_id String @db.Uuid - ordinal BigInt @unique(map: "dag_allow_spends_ordinal") + round_id String @db.Uuid + ordinal BigInt @unique(map: "dag_allow_spends_ordinal") + snapshot_hash String? @db.VarChar dag_allow_spend_approvers dag_allow_spend_approvers[] - dag_allow_spend_block dag_allow_spend_blocks @relation(fields: [round_id], references: [round_id], onDelete: NoAction, onUpdate: NoAction, map: "allow_spends_block_fk") - addresses_dag_allow_spends_destination_addrToaddresses addresses @relation("dag_allow_spends_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "dag_allow_spends_destination_addr_fk") - addresses_dag_allow_spends_source_addrToaddresses addresses @relation("dag_allow_spends_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "dag_allow_spends_source_addr_fk") + dag_allow_spend_block dag_allow_spend_blocks @relation(fields: [round_id], references: [round_id], onDelete: Cascade, onUpdate: NoAction, map: "allow_spends_block_fk") + addresses_dag_allow_spends_destination_addrToaddresses addresses @relation("dag_allow_spends_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spends_destination_addr_fk") + addresses_dag_allow_spends_source_addrToaddresses addresses @relation("dag_allow_spends_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spends_source_addr_fk") + dag_expired_spend_transactions dag_expired_spend_transactions[] dag_spend_transactions dag_spend_transactions[] abstract_transactions_view abstract_transactions_view[] + + @@index([round_id]) } model dag_balance_changes { @@ -137,10 +145,11 @@ model dag_balance_changes { created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) snapshot_ordinal BigInt - addresses addresses @relation(fields: [address], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "dag_balance_change_address_fk") - global_snapshots global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "dag_balance_change_global_snapshot_fk") + addresses addresses @relation(fields: [address], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_balance_change_address_fk") + global_snapshots global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_balance_change_global_snapshot_fk") @@id([snapshot_ordinal, address], map: "dag_balance_change_pk") + @@index([address, created_at], map: "dag_balance_changes_address_idx") } model dag_blocks { @@ -149,9 +158,11 @@ model dag_blocks { created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) snapshot_hash String @db.VarChar - global_snapshots global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "dag_block_global_snapshot_fk") + global_snapshots global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_block_global_snapshot_fk") dag_transactions dag_transactions[] super abstract_blocks? + + @@index([snapshot_hash]) } model dag_reward_transactions { @@ -160,10 +171,11 @@ model dag_reward_transactions { amount BigInt created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - global_snapshots global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "dag_reward_transaction_global_snapshot_fk") - addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "dag_reward_transactions_destination_addr_fk") + global_snapshots global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_reward_transaction_global_snapshot_fk") + addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_reward_transactions_destination_addr_fk") @@id([global_snapshot_hash, destination_addr], map: "dag_reward_transaction_pk") + @@index([global_snapshot_hash]) } model dag_spend_transactions { @@ -173,49 +185,52 @@ model dag_spend_transactions { created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) destination_addr String? @db.VarChar - dag_allow_spend dag_allow_spends @relation(fields: [source_addr], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "dag_spend_transactions_dag_allow_spends_fk") - addresses addresses? @relation(fields: [destination_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "dag_spend_transactions_destination_addr_fk") + allow_spend_ref String? @db.VarChar + snapshot_hash String? @db.VarChar + dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_dag_allow_spends_fk") + addresses addresses? @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_destination_addr_fk") abstract_transactions_view abstract_transactions_view[] } model dag_token_lock_blocks { - round_id String @id(map: "dag_token_lock_blocks_pk") @db.Uuid + round_id String @id @db.Uuid global_snapshot_hash String @unique(map: "dag_token_lock_blocks_unique") @db.VarChar created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - global_snapshot global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "dag_token_lock_blocks_global_snapshot_fk") + global_snapshot global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_lock_blocks_global_snapshot_fk") dag_token_locks dag_token_locks[] } model dag_token_locks { - hash String @id(map: "dag_token_locks_pk") @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - ordinal BigInt - unlock_epoch BigInt - round_id String - dag_token_lock_block dag_token_lock_blocks @relation(fields: [round_id], references: [round_id], onDelete: NoAction, onUpdate: NoAction, map: "token_lock_block_fk") - addresses addresses @relation(fields: [source_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "dag_token_locks_source_addr_fk") - dag_token_unlock dag_token_unlocks? - abstract_transactions_view abstract_transactions_view[] - global_snapshots global_snapshots? @relation(fields: [global_snapshotsHash], references: [hash]) - global_snapshotsHash String? @db.VarChar + hash String @id(map: "dag_token_locks_pk") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + ordinal BigInt + unlock_epoch BigInt + round_id String @db.Uuid + global_snapshot_hash String @db.VarChar + snapshot_hash String? @db.VarChar + dag_token_lock_block dag_token_lock_blocks @relation(fields: [round_id], references: [round_id], onDelete: Cascade, onUpdate: NoAction, map: "token_lock_block_fk") + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_locks_source_addr_fk") + dag_token_unlocks dag_token_unlocks? - @@unique([hash], map: "dag_token_locks_unique") + abstract_transactions_view abstract_transactions_view[] } model dag_token_unlocks { - hash String @db.VarChar - lock_reference_ordinal BigInt - lock_reference_hash String @db.VarChar - amount BigInt - address String @db.VarChar - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - dag_token_lock dag_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "dag_token_unlocks_token_locks_fk") - addresses addresses @relation(fields: [address], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "ddag_token_unlocks_address_fk") + hash String @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + lock_reference_ordinal BigInt + lock_reference_hash String @db.VarChar + snapshot_hash String? @db.VarChar + dag_token_lock dag_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_unlocks_token_locks_fk") + + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "ddag_token_unlocks_address_fk") abstract_transactions_view abstract_transactions_view[] @@id([hash], map: "dag_token_unlocks_pk") @@ -236,9 +251,10 @@ model dag_transactions { parent_hash String? @db.VarChar ordinal BigInt block_hash String @db.VarChar - dag_blocks dag_blocks @relation(fields: [block_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "dag_transaction_dag_block_fk") - addresses_dag_transactions_destination_addrToaddresses addresses @relation("dag_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "dag_transactions_destination_addr_fk") - addresses_dag_transactions_source_addrToaddresses addresses @relation("dag_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "dag_transactions_source_addr_fk") + snapshot_hash String? @db.VarChar + dag_blocks dag_blocks @relation(fields: [block_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_transaction_dag_block_fk") + addresses_dag_transactions_destination_addrToaddresses addresses @relation("dag_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_transactions_destination_addr_fk") + addresses_dag_transactions_source_addrToaddresses addresses @relation("dag_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_transactions_source_addr_fk") } model global_snapshot_proofs { @@ -247,7 +263,7 @@ model global_snapshot_proofs { snapshot_hash String @db.VarChar created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - global_snapshots global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "proof_global_snapshot_fk") + global_snapshots global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "proof_global_snapshot_fk") @@id([snapshot_hash, id], map: "proof_pk") } @@ -268,7 +284,6 @@ model global_snapshots { dag_blocks dag_blocks[] dag_reward_transactions dag_reward_transactions[] dag_token_lock_blocks dag_token_lock_blocks? - dag_token_locks dag_token_locks[] global_snapshot_proofs global_snapshot_proofs[] metagraph_snapshots metagraph_snapshots[] } @@ -276,8 +291,8 @@ model global_snapshots { model metagraph_allow_spend_approvers { allow_spend_hash String @db.VarChar approver_address String @db.VarChar - metagraph_allow_spends metagraph_allow_spends @relation(fields: [allow_spend_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "ametagraph_llow_spends_fk") - addresses addresses @relation(fields: [approver_address], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_allow_spend_approvers_address_fk") + metagraph_allow_spends metagraph_allow_spends @relation(fields: [allow_spend_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "ametagraph_llow_spends_fk") + addresses addresses @relation(fields: [approver_address], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_allow_spend_approvers_address_fk") @@id([allow_spend_hash, approver_address], map: "metagraph_allow_spend_approvers_pk") } @@ -288,31 +303,33 @@ model metagraph_allow_spend_blocks { metagraph_snapshot_hash String @db.VarChar created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_allow_spend_blocks_metagraph_snapshot_fk") + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_allow_spend_blocks_metagraph_snapshot_fk") metagraph_allow_spends metagraph_allow_spends[] } model metagraph_allow_spends { - hash String @id(map: "metagraph_allow_spends_pk") @db.VarChar - source_addr String @db.VarChar + hash String @id(map: "metagraph_allow_spends_pk") @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar - destination_addr String @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + destination_addr String @db.VarChar fee BigInt parent_ordinal BigInt? - parent_hash String? @db.VarChar + parent_hash String? @db.VarChar last_valid_epoch_progress BigInt - round_id String @db.Uuid - ordinal BigInt @unique(map: "metagraph_allow_spends_ordinal") + round_id String @db.Uuid + ordinal BigInt @unique(map: "metagraph_allow_spends_ordinal") + snapshot_hash String? @db.VarChar metagraph_allow_spend_approvers metagraph_allow_spend_approvers[] - metagraph_allow_spend_block metagraph_allow_spend_blocks @relation(fields: [round_id], references: [round_id], onDelete: NoAction, onUpdate: NoAction, map: "allow_spends_block_fk") - addresses_metagraph_allow_spends_destination_addrToaddresses addresses @relation("metagraph_allow_spends_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_allow_spends_destination_addr_fk") - addresses_metagraph_allow_spends_source_addrToaddresses addresses @relation("metagraph_allow_spends_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_allow_spends_source_addr_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_id_fk") - abstract_transactions_view abstract_transactions_view[] + metagraph_allow_spend_block metagraph_allow_spend_blocks @relation(fields: [round_id], references: [round_id], onDelete: Cascade, onUpdate: NoAction, map: "allow_spends_block_fk") + addresses_metagraph_allow_spends_destination_addrToaddresses addresses @relation("metagraph_allow_spends_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_allow_spends_destination_addr_fk") + addresses_metagraph_allow_spends_source_addrToaddresses addresses @relation("metagraph_allow_spends_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_allow_spends_source_addr_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") + metagraph_expired_spend_transactions metagraph_expired_spend_transactions[] metagraph_spend_transactions metagraph_spend_transactions[] + abstract_transactions_view abstract_transactions_view[] } model metagraph_balance_changes { @@ -323,9 +340,9 @@ model metagraph_balance_changes { created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) metagraph_snapshot_ordinal BigInt - addresses addresses @relation(fields: [address], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "address_fk") - metagraph_snapshots metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_balance_change_metagraph_snapshot_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_id_fk") + addresses addresses @relation(fields: [address], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "address_fk") + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_balance_change_metagraph_snapshot_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") @@id([metagraph_id, address, metagraph_snapshot_ordinal], map: "metagraph_balance_change_pk") } @@ -337,8 +354,8 @@ model metagraph_blocks { updated_at DateTime @default(now()) @db.Timestamp(6) metagraph_id String @db.VarChar metagraph_snapshot_hash String @db.VarChar - metagraph_snapshots metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_block_metagraph_snapshot_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_id_fk") + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_block_metagraph_snapshot_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") metagraph_transactions metagraph_transactions[] super abstract_blocks? @@ -355,19 +372,16 @@ model metagraph_fee_transactions { metagraph_id String @db.VarChar metagraph_snapshot_hash String @db.VarChar destination_addr String @db.VarChar - parent_ordinal BigInt? - parent_hash String? @db.VarChar - salt BigInt? - ordinal BigInt data_update_ref String? @db.VarChar - metagraph_snapshots metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: NoAction, onUpdate: NoAction, map: "fee_transaction_metagraph_snapshot_fk") - addresses_metagraph_fee_transactions_destination_addrToaddresses addresses @relation("metagraph_fee_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_fee_transactions_destination_addr_fk") - addresses_metagraph_fee_transactions_source_addrToaddresses addresses @relation("metagraph_fee_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_fee_transactions_source_addr_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_id_fk") + metagraph_snapshot_ordinal BigInt? + snapshot_hash String? @db.VarChar + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "fee_transaction_metagraph_snapshot_fk") + addresses_metagraph_fee_transactions_destination_addrToaddresses addresses @relation("metagraph_fee_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_fee_transactions_destination_addr_fk") + addresses_metagraph_fee_transactions_source_addrToaddresses addresses @relation("metagraph_fee_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_fee_transactions_source_addr_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") abstract_transactions_view abstract_transactions_view[] @@id([hash], map: "fee_transaction_pk") - @@unique([metagraph_id, ordinal], map: "fee_transaction_ordinal") } model metagraph_reward_transactions { @@ -377,9 +391,9 @@ model metagraph_reward_transactions { amount BigInt created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_snapshots metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_reward_transaction_metagraph_reward_transaction_fk") - addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_reward_transactions_destination_addr_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_reward_transactions_metagraph_id_fk") + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_reward_transaction_metagraph_reward_transaction_fk") + addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_reward_transactions_destination_addr_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_reward_transactions_metagraph_id_fk") @@id([metagraph_id, metagraph_snapshot_hash, destination_addr], map: "metagraph_reward_transaction_pk") } @@ -404,11 +418,11 @@ model metagraph_snapshots { metagraph_blocks metagraph_blocks[] metagraph_fee_transactions metagraph_fee_transactions[] metagraph_reward_transactions metagraph_reward_transactions[] - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_id_fk") - global_snapshots global_snapshots? @relation(fields: [global_snapshot_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_snapshots_global_snapshots_fk") - addresses_metagraph_snapshots_owner_addressToaddresses addresses? @relation("metagraph_snapshots_owner_addressToaddresses", fields: [owner_address], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "owner_address_fk") - addresses_metagraph_snapshots_staking_addressToaddresses addresses? @relation("metagraph_snapshots_staking_addressToaddresses", fields: [staking_address], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "staking_address_fk") - metagraph_token_lock_blocks metagraph_token_lock_blocks[] + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") + global_snapshots global_snapshots? @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_snapshots_global_snapshots_fk") + addresses_metagraph_snapshots_owner_addressToaddresses addresses? @relation("metagraph_snapshots_owner_addressToaddresses", fields: [owner_address], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "owner_address_fk") + addresses_metagraph_snapshots_staking_addressToaddresses addresses? @relation("metagraph_snapshots_staking_addressToaddresses", fields: [staking_address], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "staking_address_fk") + metagraph_token_lock_blocks metagraph_token_lock_blocks? @@id([metagraph_id, hash], map: "metagraph_snapshot_pk") @@unique([metagraph_id, ordinal], map: "metagraph_snapshot_unique") @@ -422,25 +436,26 @@ model metagraph_spend_transactions { updated_at DateTime @default(now()) @db.Timestamp(6) metagraph_id String @db.VarChar destination_addr String @db.VarChar - allow_spend_ref String - metagraph_allow_spend metagraph_allow_spends @relation(fields: [allow_spend_ref], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "mg_spend_transactions_mg_allow_spends_fk") - addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_spend_transactions_destination_addr_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_spend_transactions_metagraph_id_fk") + allow_spend_ref String? @db.VarChar + snapshot_hash String? @db.VarChar + metagraph_allow_spend metagraph_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "mg_spend_transactions_mg_allow_spends_fk") + addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_destination_addr_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_metagraph_id_fk") abstract_transactions_view abstract_transactions_view[] } model metagraph_token_lock_blocks { metagraph_id String @db.VarChar metagraph_snapshot_hash String @db.VarChar - round_id String @unique(map: "metagraph_token_lock_blocks_unique") @db.Uuid + round_id String @db.Uuid created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_token_lock_blocks_metagraph_id_fk") - metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_token_lock_blocks_metagraph_snapshot_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_lock_blocks_metagraph_id_fk") + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_lock_blocks_metagraph_snapshot_fk") metagraph_token_locks metagraph_token_locks[] @@id([metagraph_id, metagraph_snapshot_hash]) - @@unique([metagraph_id, round_id]) + @@unique([metagraph_id, round_id], map: "metagraph_token_lock_blocks_unique") } model metagraph_token_locks { @@ -452,29 +467,31 @@ model metagraph_token_locks { metagraph_id String @db.VarChar ordinal BigInt unlock_epoch BigInt - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_id_fk") - addresses addresses @relation(fields: [source_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_token_locks_source_addr_fk") - round_id String - metagraph_token_unlock metagraph_token_unlocks? + round_id String @db.Uuid + snapshot_hash String? @db.VarChar + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") + metagraph_token_lock_block metagraph_token_lock_blocks @relation(fields: [metagraph_id, round_id], references: [metagraph_id, round_id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_lock_token_lock_blocks_fk") + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_locks_source_addr_fk") abstract_transactions_view abstract_transactions_view[] - metagraph_token_lock_block metagraph_token_lock_blocks @relation(fields: [metagraph_id, round_id], references: [metagraph_id, round_id], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_token_lock_token_lock_blocks_fk") + metagraph_token_unlocks metagraph_token_unlocks? @@id([hash], map: "metagraph_token_locks_pk") @@unique([metagraph_id, ordinal], map: "metagraph_token_locks_unique") } model metagraph_token_unlocks { - metagraph_id String @db.VarChar hash String @db.VarChar - lock_reference_ordinal BigInt - lock_reference_hash String @db.VarChar + source_addr String @db.VarChar amount BigInt - address String @db.VarChar created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - addresses addresses @relation(fields: [address], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "address_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_id_fk") - metagraph_token_lock metagraph_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_token_unlocks_token_locks_fk") + metagraph_id String @db.VarChar + lock_reference_ordinal BigInt + lock_reference_hash String @db.VarChar + snapshot_hash String? @db.VarChar + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "address_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") + token_lock metagraph_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_token_unlocks_token_locks_fk") abstract_transactions_view abstract_transactions_view[] @@id([hash], map: "metagraph_token_unlocks_pk") @@ -496,13 +513,13 @@ model metagraph_transactions { parent_hash String @db.VarChar ordinal BigInt block_hash String @db.VarChar - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_id_fk") - metagraph_blocks metagraph_blocks @relation(fields: [metagraph_id, block_hash], references: [metagraph_id, hash], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_transaction_metagraph_block_fk") - addresses_metagraph_transactions_destination_addrToaddresses addresses @relation("metagraph_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_transactions_destination_addrfk") - addresses_metagraph_transactions_source_addrToaddresses addresses @relation("metagraph_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_transactions_source_addr_fk") + snapshot_hash String? @db.VarChar + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") + metagraph_blocks metagraph_blocks @relation(fields: [metagraph_id, block_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transaction_metagraph_block_fk") + addresses_metagraph_transactions_destination_addrToaddresses addresses @relation("metagraph_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transactions_destination_addrfk") + addresses_metagraph_transactions_source_addrToaddresses addresses @relation("metagraph_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transactions_source_addr_fk") @@id([metagraph_id, hash], map: "metagraph_transaction_pk") - @@unique([metagraph_id, ordinal], map: "metagraph_transactions_ordinal") } model metagraphs { @@ -521,3 +538,28 @@ model metagraphs { metagraph_token_unlocks metagraph_token_unlocks[] metagraph_transactions metagraph_transactions[] } + +model dag_expired_spend_transactions { + hash String @id(map: "dag_expired_spend_transactions_pk") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + allow_spend_ref String? @db.VarChar + snapshot_hash String? @db.VarChar + abstract_transactions_view abstract_transactions_view[] + dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_expired_spend_transactions_dag_allow_spends_fk") +} + +model metagraph_expired_spend_transactions { + hash String @id(map: "metagraph_expired_spend_transactions_pk") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + allow_spend_ref String? @db.VarChar + snapshot_hash String? @db.VarChar + abstract_transactions_view abstract_transactions_view[] + metagraph_allow_spend metagraph_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_expired_spend_transactions_metagraph_allow_spends_fk") +} diff --git a/routes/actions.yml b/routes/actions.yml index 3c37a88..7fbf00d 100644 --- a/routes/actions.yml +++ b/routes/actions.yml @@ -1,41 +1,41 @@ - actions: - handler: src/handlers/actionsHandler.dagActions - events: - - httpApi: - path: /actions - method: GET +actions: + handler: src/handlers/actionsHandler.dagActions + events: + - httpApi: + path: /actions + method: GET - globalSnapshotActions: - handler: src/handlers/actionsHandler.globalSnapshotActions - events: - - httpApi: - path: /global-snapshots/{term}/actions - method: GET +globalSnapshotActions: + handler: src/handlers/actionsHandler.globalSnapshotActions + events: + - httpApi: + path: /global-snapshots/{term}/actions + method: GET - addressActions: - handler: src/handlers/actionsHandler.dagAddressActions - events: - - httpApi: - path: /addresses/{address}/actions - method: GET +addressActions: + handler: src/handlers/actionsHandler.dagAddressActions + events: + - httpApi: + path: /addresses/{address}/actions + method: GET - currencyActions: - handler: src/handlers/actionsHandler.currencyActions - events: - - httpApi: - path: /currency/{metagraph_id}/actions - method: GET +currencyActions: + handler: src/handlers/actionsHandler.currencyActions + events: + - httpApi: + path: /currency/{metagraph_id}/actions + method: GET - currencySnapshotActions: - handler: src/handlers/actionsHandler.currencySnapshotActions - events: - - httpApi: - path: /currency/{metagraph_id}/snapshots/{term}/actions - method: GET +currencySnapshotActions: + handler: src/handlers/actionsHandler.currencySnapshotActions + events: + - httpApi: + path: /currency/{metagraph_id}/snapshots/{term}/actions + method: GET - currencyAddressActions: - handler: src/handlers/actionsHandler.currencyAddressActions - events: - - httpApi: - path: /currency/{metagraph_id}/addresses/{address}/actions - method: GET +currencyAddressActions: + handler: src/handlers/actionsHandler.currencyAddressActions + events: + - httpApi: + path: /currency/{metagraph_id}/addresses/{address}/actions + method: GET diff --git a/routes/allow-spends.yml b/routes/allow-spends.yml index 36c38ae..76e4037 100644 --- a/routes/allow-spends.yml +++ b/routes/allow-spends.yml @@ -103,18 +103,18 @@ currencyAddressSpendTransactions: path: /currency/{metagraph_id}/addresses/{address}/spend-transactions method: GET -currencySnapshotAllowSpendExpirations: - handler: src/handlers/allowSpendsHandler.currencySnapshotAllowSpendExpirations +currencyAllowSpendExpirations: + handler: src/handlers/allowSpendsHandler.currencyAllowSpendExpirations events: - httpApi: - path: /currency/{metagraph_id}/snapshots/{hash_or_ordinal}/allow-spend-expirations + path: /currency/{metagraph_id}/allow-spend-expirations method: GET -currencyAllowSpendExpirations: - handler: src/handlers/allowSpendsHandler.currencyAllowSpendExpirations +currencySnapshotAllowSpendExpirations: + handler: src/handlers/allowSpendsHandler.currencySnapshotAllowSpendExpirations events: - httpApi: - path: /currency/{metagraph_id}/allow-spend-expirations + path: /currency/{metagraph_id}/snapshots/{hash_or_ordinal}/allow-spend-expirations method: GET currencyAddressAllowSpendExpirations: @@ -122,4 +122,4 @@ currencyAddressAllowSpendExpirations: events: - httpApi: path: /currency/{metagraph_id}/addresses/{address}/allow-spend-expirations - method: GET \ No newline at end of file + method: GET diff --git a/routes/dag.yml b/routes/dag.yml index a995374..fd74f10 100644 --- a/routes/dag.yml +++ b/routes/dag.yml @@ -1,67 +1,66 @@ - globalSnapshots: - handler: src/handlers/dagHandler.handleGlobalSnapshots - events: - - httpApi: - path: /global-snapshots - method: GET - globalSnapshot: - handler: src/handlers/dagHandler.handleGlobalSnapshot - events: - - httpApi: - path: /global-snapshots/{term} - method: GET - globalSnapshotRewards: - handler: src/handlers/dagHandler.handleGlobalSnapshotRewards - events: - - httpApi: - path: /global-snapshots/{term}/rewards - method: GET - globalSnapshotTransactions: - handler: src/handlers/dagHandler.handleGlobalSnapshotTransactions - events: - - httpApi: - path: /global-snapshots/{term}/transactions - method: GET - block: - handler: src/handlers/dagHandler.handleDagBlock - events: - - httpApi: - path: /blocks/{hash} - method: GET - transactions: - handler: src/handlers/dagHandler.handleDagTransactions - events: - - httpApi: - path: /transactions - method: GET - transaction: - handler: src/handlers/dagHandler.handleDagTransaction - events: - - httpApi: - path: /transactions/{hash} - method: GET - transactionsByAddress: - handler: src/handlers/dagHandler.handleDagTransactionsByAddress - events: - - httpApi: - path: /addresses/{address}/transactions - method: GET - transactionsBySource: - handler: src/handlers/dagHandler.handleDagTransactionsBySource - events: - - httpApi: - path: /addresses/{address}/transactions/sent - method: GET - transactionsByDestination: - handler: src/handlers/dagHandler.handleDagTransactionsByDestination - events: - - httpApi: - path: /addresses/{address}/transactions/received - method: GET - balanceByAddress: - handler: src/handlers/dagHandler.handleDagBalanceByAddress - events: - - httpApi: - path: /addresses/{address}/balance - method: GET - +globalSnapshots: + handler: src/handlers/dagHandler.globalSnapshots + events: + - httpApi: + path: /global-snapshots + method: GET +globalSnapshot: + handler: src/handlers/dagHandler.globalSnapshot + events: + - httpApi: + path: /global-snapshots/{term} + method: GET +globalSnapshotRewards: + handler: src/handlers/dagHandler.globalSnapshotRewards + events: + - httpApi: + path: /global-snapshots/{term}/rewards + method: GET +globalSnapshotTransactions: + handler: src/handlers/dagHandler.globalSnapshotTransactions + events: + - httpApi: + path: /global-snapshots/{term}/transactions + method: GET +block: + handler: src/handlers/dagHandler.dagBlock + events: + - httpApi: + path: /blocks/{hash} + method: GET +transactions: + handler: src/handlers/dagHandler.dagTransactions + events: + - httpApi: + path: /transactions + method: GET +transaction: + handler: src/handlers/dagHandler.dagTransaction + events: + - httpApi: + path: /transactions/{hash} + method: GET +transactionsByAddress: + handler: src/handlers/dagHandler.dagTransactionsByAddress + events: + - httpApi: + path: /addresses/{address}/transactions + method: GET +transactionsBySource: + handler: src/handlers/dagHandler.dagTransactionsBySource + events: + - httpApi: + path: /addresses/{address}/transactions/sent + method: GET +transactionsByDestination: + handler: src/handlers/dagHandler.dagTransactionsByDestination + events: + - httpApi: + path: /addresses/{address}/transactions/received + method: GET +balanceByAddress: + handler: src/handlers/dagHandler.dagBalanceByAddress + events: + - httpApi: + path: /addresses/{address}/balance + method: GET diff --git a/routes/metagraph.yml b/routes/metagraph.yml index 2054627..97ef279 100644 --- a/routes/metagraph.yml +++ b/routes/metagraph.yml @@ -1,108 +1,108 @@ - currencySnapshots: - handler: src/handlers/metagraphHandler.handleCurrencySnapshots - events: - - httpApi: - path: /currency/{identifier}/snapshots - method: GET - currencySnapshotsByOwnerAddress: - handler: src/handlers/metagraphHandler.handleCurrencySnapshotsByOwnerAddress - events: - - httpApi: - path: /addresses/{address}/snapshots - method: GET - currencySnapshot: - handler: src/handlers/metagraphHandler.handleCurrencySnapshot - events: - - httpApi: - path: /currency/{identifier}/snapshots/{term} - method: GET - currencySnapshotRewards: - handler: src/handlers/metagraphHandler.handleCurrencySnapshotRewards - events: - - httpApi: - path: /currency/{identifier}/snapshots/{term}/rewards - method: GET - currencySnapshotTransactions: - handler: src/handlers/metagraphHandler.handleCurrencySnapshotTransactions - events: - - httpApi: - path: /currency/{identifier}/snapshots/{term}/transactions - method: GET - currencyBlock: - handler: src/handlers/metagraphHandler.handleCurrencyBlock - events: - - httpApi: - path: /currency/{identifier}/blocks/{hash} - method: GET - currencyTransactions: - handler: src/handlers/metagraphHandler.handleCurrencyTransactions - events: - - httpApi: - path: /currency/{identifier}/transactions - method: GET - currencyTransaction: - handler: src/handlers/metagraphHandler.handleCurrencyTransaction - events: - - httpApi: - path: /currency/{identifier}/transactions/{hash} - method: GET - currencyTransactionsByAddress: - handler: src/handlers/metagraphHandler.handleCurrencyTransactionsByAddress - events: - - httpApi: - path: /currency/{identifier}/addresses/{address}/transactions - method: GET - currencyTransactionsBySource: - handler: src/handlers/metagraphHandler.handleCurrencyTransactionsBySource - events: - - httpApi: - path: /currency/{identifier}/addresses/{address}/transactions/sent - method: GET - currencyTransactionsByDestination: - handler: src/handlers/metagraphHandler.handleCurrencyTransactionsByDestination - events: - - httpApi: - path: /currency/{identifier}/addresses/{address}/transactions/received - method: GET - currencyBalanceByAddress: - handler: src/handlers/metagraphHandler.handleCurrencyBalanceByAddress - events: - - httpApi: - path: /currency/{identifier}/addresses/{address}/balance - method: GET - currencyFeeTransaction: - handler: src/handlers/metagraphHandler.handleCurrencyFeeTransaction - events: - - httpApi: - path: /currency/{identifier}/fee-transactions/{hash} - method: GET - currencySnapshotFeeTransactions: - handler: src/handlers/metagraphHandler.handleCurrencySnapshotFeeTransactions - events: - - httpApi: - path: /currency/{identifier}/snapshots/{term}/fee-transactions - method: GET - currencyFeeTransactionsByAddress: - handler: src/handlers/metagraphHandler.handleCurrencyFeeTransactionsByAddress - events: - - httpApi: - path: /currency/{identifier}/addresses/{address}/fee-transactions - method: GET - currencyFeeTransactionsBySource: - handler: src/handlers/metagraphHandler.handleCurrencyFeeTransactionsBySource - events: - - httpApi: - path: /currency/{identifier}/addresses/{address}/fee-transactions/sent - method: GET - currencyFeeTransactionsByDestination: - handler: src/handlers/metagraphHandler.handleCurrencyFeeTransactionsByDestination - events: - - httpApi: - path: /currency/{identifier}/addresses/{address}/fee-transactions/received - method: GET - metagraphs: - handler: src/handlers/metagraphHandler.metagraphs - events: - - httpApi: - path: /currency - method: GET +currencySnapshots: + handler: src/handlers/metagraphHandler.currencySnapshots + events: + - httpApi: + path: /currency/{identifier}/snapshots + method: GET +currencySnapshotsByOwnerAddress: + handler: src/handlers/metagraphHandler.currencySnapshotsByOwnerAddress + events: + - httpApi: + path: /addresses/{address}/snapshots + method: GET +currencySnapshot: + handler: src/handlers/metagraphHandler.currencySnapshot + events: + - httpApi: + path: /currency/{identifier}/snapshots/{term} + method: GET +currencySnapshotRewards: + handler: src/handlers/metagraphHandler.currencySnapshotRewards + events: + - httpApi: + path: /currency/{identifier}/snapshots/{term}/rewards + method: GET +currencySnapshotTransactions: + handler: src/handlers/metagraphHandler.currencySnapshotTransactions + events: + - httpApi: + path: /currency/{identifier}/snapshots/{term}/transactions + method: GET +currencyBlock: + handler: src/handlers/metagraphHandler.currencyBlock + events: + - httpApi: + path: /currency/{identifier}/blocks/{hash} + method: GET +currencyTransactions: + handler: src/handlers/metagraphHandler.currencyTransactions + events: + - httpApi: + path: /currency/{identifier}/transactions + method: GET +currencyTransaction: + handler: src/handlers/metagraphHandler.currencyTransaction + events: + - httpApi: + path: /currency/{identifier}/transactions/{hash} + method: GET +currencyTransactionsByAddress: + handler: src/handlers/metagraphHandler.currencyTransactionsByAddress + events: + - httpApi: + path: /currency/{identifier}/addresses/{address}/transactions + method: GET +currencyTransactionsBySource: + handler: src/handlers/metagraphHandler.currencyTransactionsBySource + events: + - httpApi: + path: /currency/{identifier}/addresses/{address}/transactions/sent + method: GET +currencyTransactionsByDestination: + handler: src/handlers/metagraphHandler.currencyTransactionsByDestination + events: + - httpApi: + path: /currency/{identifier}/addresses/{address}/transactions/received + method: GET +currencyBalanceByAddress: + handler: src/handlers/metagraphHandler.currencyBalanceByAddress + events: + - httpApi: + path: /currency/{identifier}/addresses/{address}/balance + method: GET +currencyFeeTransaction: + handler: src/handlers/metagraphHandler.currencyFeeTransaction + events: + - httpApi: + path: /currency/{identifier}/fee-transactions/{hash} + method: GET +currencySnapshotFeeTransactions: + handler: src/handlers/metagraphHandler.currencySnapshotFeeTransactions + events: + - httpApi: + path: /currency/{identifier}/snapshots/{term}/fee-transactions + method: GET +currencyFeeTransactionsByAddress: + handler: src/handlers/metagraphHandler.currencyFeeTransactionsByAddress + events: + - httpApi: + path: /currency/{identifier}/addresses/{address}/fee-transactions + method: GET +currencyFeeTransactionsBySource: + handler: src/handlers/metagraphHandler.currencyFeeTransactionsBySource + events: + - httpApi: + path: /currency/{identifier}/addresses/{address}/fee-transactions/sent + method: GET +currencyFeeTransactionsByDestination: + handler: src/handlers/metagraphHandler.currencyFeeTransactionsByDestination + events: + - httpApi: + path: /currency/{identifier}/addresses/{address}/fee-transactions/received + method: GET +metagraphs: + handler: src/handlers/metagraphHandler.metagraphs + events: + - httpApi: + path: /currency + method: GET diff --git a/routes/token-locks.yml b/routes/token-locks.yml index 3910122..e658dfe 100644 --- a/routes/token-locks.yml +++ b/routes/token-locks.yml @@ -1,82 +1,82 @@ tokenLocks: - handler: src/handlers/tokenLocskHandler.tokenLocks + handler: src/handlers/tokenLocksHandler.tokenLocks events: - httpApi: path: /token-locks method: GET globalSnapshotTokenLocks: - handler: src/handlers/tokenLocskHandler.globalSnapshotTokenLocks + handler: src/handlers/tokenLocksHandler.globalSnapshotTokenLocks events: - httpApi: path: /global-snapshots/{hash_or_ordinal}/token-locks method: GET addressTokenLocks: - handler: src/handlers/tokenLocskHandler.addressTokenLocks + handler: src/handlers/tokenLocksHandler.addressTokenLocks events: - httpApi: path: /addresses/{address}/token-locks method: GET tokenUnlocks: - handler: src/handlers/tokenLocskHandler.tokenUnlocks + handler: src/handlers/tokenLocksHandler.tokenUnlocks events: - httpApi: path: /token-unlocks method: GET globalSnapshotTokenUnlocks: - handler: src/handlers/tokenLocskHandler.globalSnapshotTokenUnlocks + handler: src/handlers/tokenLocksHandler.globalSnapshotTokenUnlocks events: - httpApi: path: /global-snapshots/{hash_or_ordinal}/token-unlocks method: GET addressTokenUnlocks: - handler: src/handlers/tokenLocskHandler.addressTokenUnlocks + handler: src/handlers/tokenLocksHandler.addressTokenUnlocks events: - httpApi: path: /addresses/{address}/token-unlocks method: GET currencyTokenLocks: - handler: src/handlers/tokenLocskHandler.currencyTokenLocks + handler: src/handlers/tokenLocksHandler.metagraphTokenLocks events: - httpApi: path: /currency/{metagraph_id}/token-locks method: GET currencySnapshotTokenLocks: - handler: src/handlers/tokenLocskHandler.currencySnapshotTokenLocks + handler: src/handlers/tokenLocksHandler.metagraphSnapshotTokenLocks events: - httpApi: path: /currency/{metagraph_id}/snapshots/{hash_or_ordinal}/token-locks method: GET currencyAddressTokenLocks: - handler: src/handlers/tokenLocskHandler.currencyAddressTokenLocks + handler: src/handlers/tokenLocksHandler.metagraphAddressTokenLocks events: - httpApi: path: /currency/{metagraph_id}/addresses/{address}/token-locks method: GET currencySnapshotTokenUnlocks: - handler: src/handlers/tokenLocskHandler.currencySnapshotTokenUnlocks + handler: src/handlers/tokenLocksHandler.metagraphSnapshotTokenUnlocks events: - httpApi: path: /currency/{metagraph_id}/snapshots/{hash_or_ordinal}/token-unlocks method: GET currencyTokenUnlocks: - handler: src/handlers/tokenLocskHandler.currencyTokenUnlocks + handler: src/handlers/tokenLocksHandler.metagraphTokenUnlocks events: - httpApi: path: /currency/{metagraph_id}/token-unlocks method: GET currencyAddressTokenUnlocks: - handler: src/handlers/tokenLocskHandler.currencyAddressTokenUnlocks + handler: src/handlers/tokenLocksHandler.metagraphAddressTokenUnlocks events: - httpApi: path: /currency/{metagraph_id}/addresses/{address}/token-unlocks diff --git a/serverless.yml b/serverless.yml index d6e8820..9bbb833 100644 --- a/serverless.yml +++ b/serverless.yml @@ -13,7 +13,7 @@ provider: region: us-west-1 stage: ${self:custom.stage} environment: - SLS_DEBUG: '*' + SLS_DEBUG: "*" DATABASE_URL: ${self:custom.env.db_url} httpApi: cors: true @@ -22,12 +22,12 @@ provider: timeout: 10 functions: - - ${file(./routes/dag.yml)} - - ${file(./routes/metagraph.yml)} - #- ${file(./routes/allow-spends.yml)} - - ${file(./routes/actions.yml)} - + - ${file(./routes/dag.yml)} + - ${file(./routes/metagraph.yml)} + - ${file(./routes/token-locks.yml)} + - ${file(./routes/allow-spends.yml)} + - ${file(./routes/actions.yml)} + plugins: - serverless-plugin-typescript - serverless-offline - diff --git a/src/handlers/actionsHandler.ts b/src/handlers/actionsHandler.ts index 8ed5abe..ba63de9 100644 --- a/src/handlers/actionsHandler.ts +++ b/src/handlers/actionsHandler.ts @@ -1,100 +1,167 @@ -import { PrismaClient } from '@prisma/client'; -import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; -import { extractHashOrdinal, extractPagination } from '../request-params'; -import { paginatedQuery, fromCreatedAtOrdinalCursor, toCreatedAtOrdinalCursor } from '../pagination'; -import { respond, handleError, dagTransactionResponse } from '../response'; +import { PrismaClient } from "@prisma/client"; +import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; +import { extractHashOrdinal, extractPagination } from "../request-params"; +import { paginatedQuery, hashCursor } from "../pagination"; +import { handleError } from "../response"; const prisma = new PrismaClient(); +const transactionTypeMap: Record = { + AllowSpend: "allow_spends", + TokenLock: "token_locks", + TokenUnlock: "token_unlocks", + SpendTransaction: "spend_transactions", + FeeTransaction: "fee_transactions", + ExpiredSpendTransaction: "expired_spend_transactions", +}; + +const reverseTransactionTypeMap = Object.entries(transactionTypeMap).reduce( + (acc, [key, value]) => ({ ...acc, [value]: key }), + {} as Record +); + +const getTransactionType = (tableName: string): string | undefined => { + const strippedName = tableName.replace(/^(dag_|metagraph_)/, ""); // Remove prefix + return reverseTransactionTypeMap[strippedName]; +}; + +const dagTable = (name: string) => `dag_${name}`; +const metagraphTable = (name: string) => `metagraph_${name}`; + +const actionsTables = Object.values(transactionTypeMap); + +const tableFilter = (event) => { + const queryParams = event.queryStringParameters || {}; + const transactionTypes = queryParams.transactionTypes?.split(",") || []; + + return transactionTypes.length > 0 + ? transactionTypes.map((type) => transactionTypeMap[type]).filter(Boolean) + : actionsTables; +}; + +const currencyId = (transaction) => + transaction.metagraph_token_lock?.metagraph_id ?? + transaction.metagraph_token_unlock?.metagraph_id ?? + transaction.metagraph_allow_spend?.metagraph_id ?? + transaction.metagraph_spend_transaction?.metagraph_id ?? + transaction.metagraph_fee_transaction?.metagraph_id ?? + null; -const dagActionsTables = [ - "dag_allow_spends", - "dag_spend_transactions", - "dag_token_locks", - "dag_token_unlocks", - "dag_fee_transactions" - ]; - - const metagraphActionsTables = [ - "metagraph_allow_spends", - "metagraph_spend_transactions", - "metagraph_token_locks", - "metagraph_token_unlocks", - "metagraph_fee_transactions" - ]; - - const currencyId = (transaction) => (transaction.metagraph_token_lock?.metagraph_id ?? - transaction.metagraph_token_unlock?.metagraph_id ?? - transaction.metagraph_allow_spend?.metagraph_id ?? - transaction.metagraph_spend_transaction?.metagraph_id ?? - transaction.metagraph_fee_transaction?.metagraph_id) - - const actionResponse = (transaction) => ({ - type: transaction.table_name, - currencyId: currencyId(transaction), - hash: transaction.hash, - amount: transaction.amount, - source: transaction.source_addr, - destination: transaction.destination_addr, - unlockEpoch: transaction.last_valid_epoch_progress ?? transaction.unlock_epoch, - parentHash: transaction.lock_reference_hash ?? transaction.allow_spend_ref, - timestamp: transaction.created_at - }); - -export const actionsResponse = (ts) => ts.map(actionResponse);; +const actionResponse = (transaction) => ({ + type: getTransactionType(transaction.table_name), + currencyId: currencyId(transaction), + hash: transaction.hash, + amount: transaction.amount, + source: transaction.source_addr, + destination: transaction.destination_addr, + unlockEpoch: + transaction.dag_allow_spend?.last_valid_epoch_progress ?? + transaction.dag_token_lock?.unlock_epoch, + parentHash: + transaction.dag_spend_transaction?.allow_spend_ref ?? + transaction.dag_token_unlock?.lock_reference_hash, + timestamp: transaction.created_at, +}); + +export const actionsResponse = (ts) => ts.map(actionResponse); + +const dagInclude = { + dag_token_lock: { select: { unlock_epoch: true } }, + dag_allow_spend: { select: { last_valid_epoch_progress: true } }, + dag_spend_transaction: { select: { allow_spend_ref: true } }, + dag_token_unlock: { select: { lock_reference_hash: true } }, + dag_expired_spend_transaction: { select: { allow_spend_ref: true } }, +}; + +const metagraphInclude = { + metagraph_token_lock: { select: { unlock_epoch: true } }, + metagraph_allow_spend: { select: { last_valid_epoch_progress: true } }, + metagraph_spend_transaction: { select: { allow_spend_ref: true } }, + metagraph_token_unlock: { select: { lock_reference_hash: true } }, + metagraph_expired_spend_transaction: { select: { allow_spend_ref: true } }, + metagraph_fee_transaction: { select: { data_update_ref: true } }, +}; export const dagActions = async ( event: APIGatewayProxyEvent ): Promise => { + const selectedTables = tableFilter(event).map(dagTable); + return await paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, - { - where: { table_name: { in: dagActionsTables } }, - orderBy: { created_at: 'desc' } + hashCursor, + hashCursor, + { + where: { table_name: { in: selectedTables } }, + include: dagInclude, + orderBy: [{ created_at: "desc" }, { hash: "desc" }], }, prisma.abstract_transactions_view.findMany, actionsResponse ); }; - -const tokenLockGlobalSnapshotCond = (filter) => ({ dag_token_lock: { dag_token_lock_block: { global_snapshot: filter }} }) -const tokenUnlockGlobalSnapshotCond = (filter) => ({ dag_token_unlock: tokenLockGlobalSnapshotCond(filter) }) +const tokenLockGlobalSnapshotCond = (filter) => ({ + dag_token_lock: { + dag_token_lock_block: { + global_snapshot: filter, + }, + }, +}); +const tokenUnlockGlobalSnapshotCond = (filter) => ({ + dag_token_unlock: { + dag_token_lock: { + dag_token_lock_block: { + global_snapshot: filter, + }, + }, + }, +}); const allowSpendGlobalSnapshotCond = (filter) => ({ - dag_allow_spend: { + dag_allow_spend: { dag_allow_spend_block: { - global_snapshot: filter - } - } -}) -const spendTxGlobalSnapshotCond = (filter) => ({ dag_spend_transaction: allowSpendGlobalSnapshotCond(filter) }) - + global_snapshot: filter, + }, + }, +}); +const spendTxGlobalSnapshotCond = (filter) => ({ + dag_spend_transaction: allowSpendGlobalSnapshotCond(filter), +}); +const expiredSpendTxGlobalSnapshotCond = (filter) => ({ + dag_expired_spend_transaction: allowSpendGlobalSnapshotCond(filter), +}); -const filterByGlobalSnapshot = (filter) => ( {OR: [ - tokenLockGlobalSnapshotCond(filter), - tokenUnlockGlobalSnapshotCond(filter), - allowSpendGlobalSnapshotCond(filter), - spendTxGlobalSnapshotCond(filter) -]}) +const filterByGlobalSnapshot = (filter) => ({ + OR: [ + tokenLockGlobalSnapshotCond(filter), + tokenUnlockGlobalSnapshotCond(filter), + allowSpendGlobalSnapshotCond(filter), + spendTxGlobalSnapshotCond(filter), + expiredSpendTxGlobalSnapshotCond(filter), + ], +}); export const globalSnapshotActions = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { hash_or_ordinal } = event.pathParameters || {}; - const filter = extractHashOrdinal(hash_or_ordinal); + const { term } = event.pathParameters || {}; + const filter = extractHashOrdinal(term); + + const selectedTables = tableFilter(event).map(dagTable); return await paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, - { where: { - ...filterByGlobalSnapshot(filter), - table_name: { in: dagActionsTables } - } - , orderBy: { created_at: 'desc' } }, + hashCursor, + hashCursor, + { + where: { + ...filterByGlobalSnapshot(filter), + table_name: { in: selectedTables }, + }, + include: dagInclude, + orderBy: [{ created_at: "desc" }, { hash: "asc" }], + }, prisma.abstract_transactions_view.findMany, actionsResponse ); @@ -109,12 +176,29 @@ export const dagAddressActions = async ( try { const { address } = event.pathParameters || {}; + const selectedTables = tableFilter(event).map(dagTable); + return await paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, - { where: { source_addr: address, table_name: { in: dagActionsTables } }, - orderBy: { created_at: 'desc' } }, + hashCursor, + hashCursor, + { + where: { + OR: [ + { source_addr: address }, + { dag_allow_spend: { destination_addr: address } }, + { dag_spend_transaction: { destination_addr: address } }, + { + dag_expired_spend_transaction: { + dag_allow_spend: { destination_addr: address }, + }, + }, + ], + table_name: { in: selectedTables }, + }, + include: dagInclude, + orderBy: [{ created_at: "desc" }, { hash: "asc" }], + }, prisma.abstract_transactions_view.findMany, actionsResponse ); @@ -123,35 +207,56 @@ export const dagAddressActions = async ( } }; +const metagraphIdCond = (metagraph_id) => ({ + OR: [ + { metagraph_token_lock: { metagraph_id } }, + { metagraph_token_unlock: { metagraph_id } }, + { metagraph_allow_spend: { metagraph_id } }, + { metagraph_spend_transaction: { metagraph_id } }, + { metagraph_expired_spend_transaction: { metagraph_id } }, + { metagraph_fee_transaction: { metagraph_id } }, + ], +}); - -const metagraphIdCond = (metagraph_id) => ({ OR: [ - { metagraph_token_lock: {metagraph_id }}, - { metagraph_token_unlock: {metagraph_id }}, - { metagraph_allow_spend: {metagraph_id }}, - { metagraph_spend_transaction: {metagraph_id }}, - { metagraph_fee_transaction: {metagraph_id }} - ]}) - -const tokenLockMetagraphSnapshotCond = (filter) => ({ metagraph_token_lock: { metagraph_token_lock_block: { metagraph_snapshot: filter} } }) -const tokenUnlockMetagraphSnapshotCond = (filter) => ({ metagraph_token_unlock: tokenLockMetagraphSnapshotCond(filter) }) +const tokenLockMetagraphSnapshotCond = (filter) => ({ + metagraph_token_lock: { + metagraph_token_lock_block: { metagraph_snapshot: filter }, + }, +}); +const tokenUnlockMetagraphSnapshotCond = (filter) => ({ + metagraph_token_unlock: { + token_lock: { + metagraph_token_lock_block: { metagraph_snapshot: filter }, + }, + }, +}); const allowSpendMetagraphSnapshotCond = (filter) => ({ - metagraph_allow_spend: { + metagraph_allow_spend: { metagraph_allow_spend_block: { - metagraph_snapshot: filter - } - } -}) -const spendTxMetagraphSnapshotCond = (filter) => ({ metagraph_spend_transaction: allowSpendMetagraphSnapshotCond(filter) }) - - -const filterByMetagraphSnapshot = (filter) => ( {OR: [ - tokenLockMetagraphSnapshotCond(filter), - tokenUnlockMetagraphSnapshotCond(filter), - allowSpendMetagraphSnapshotCond(filter), - spendTxMetagraphSnapshotCond(filter) -]}) + metagraph_snapshot: filter, + }, + }, +}); +const spendTxMetagraphSnapshotCond = (filter) => ({ + metagraph_spend_transaction: allowSpendMetagraphSnapshotCond(filter), +}); +const expiredSpendTxMetagraphSnapshotCond = (filter) => ({ + metagraph_expired_spend_transaction: allowSpendMetagraphSnapshotCond(filter), +}); +const feeTxMetagraphSnapshotCond = (filter) => ({ + metagraph_fee_transaction: { metagraph_snapshot: filter }, +}); +const filterByMetagraphSnapshot = (filter) => ({ + OR: [ + tokenLockMetagraphSnapshotCond(filter), + tokenUnlockMetagraphSnapshotCond(filter), + allowSpendMetagraphSnapshotCond(filter), + spendTxMetagraphSnapshotCond(filter), + expiredSpendTxMetagraphSnapshotCond(filter), + feeTxMetagraphSnapshotCond(filter), + ], +}); export const currencyActions = async ( event: APIGatewayProxyEvent @@ -159,15 +264,20 @@ export const currencyActions = async ( try { const { metagraph_id } = event.pathParameters || {}; + const selectedTables = tableFilter(event).map(metagraphTable); + return await paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, - { where: { - ...metagraphIdCond(metagraph_id), - table_name: { in: metagraphActionsTables } + hashCursor, + hashCursor, + { + where: { + ...metagraphIdCond(metagraph_id), + table_name: { in: selectedTables }, + }, + include: metagraphInclude, + orderBy: [{ created_at: "desc" }, { hash: "asc" }], }, - orderBy: { created_at: 'desc' } }, prisma.abstract_transactions_view.findMany, actionsResponse ); @@ -180,17 +290,24 @@ export const currencySnapshotActions = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { metagraph_id, hash_or_ordinal } = event.pathParameters || {}; - const filter = extractHashOrdinal(hash_or_ordinal); + const { metagraph_id, term } = event.pathParameters || {}; + const filter = extractHashOrdinal(term); + + const selectedTables = tableFilter(event).map(metagraphTable); return await paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, - { where: { - ...metagraphIdCond(metagraph_id), - ...filterByMetagraphSnapshot(hash_or_ordinal), - table_name: { in: metagraphActionsTables } }, orderBy: { created_at: 'desc' } }, + hashCursor, + hashCursor, + { + where: { + ...metagraphIdCond(metagraph_id), + ...filterByMetagraphSnapshot(filter), + table_name: { in: selectedTables }, + }, + include: metagraphInclude, + orderBy: [{ created_at: "desc" }, { hash: "asc" }], + }, prisma.abstract_transactions_view.findMany, actionsResponse ); @@ -205,11 +322,31 @@ export const currencyAddressActions = async ( try { const { metagraph_id, address } = event.pathParameters || {}; + const selectedTables = tableFilter(event).map(metagraphTable); + return await paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, - { where: {...metagraphIdCond(metagraph_id), source_addr: address, table_name: { in: metagraphActionsTables } }, orderBy: { created_at: 'desc' } }, + hashCursor, + hashCursor, + { + where: { + ...metagraphIdCond(metagraph_id), + OR: [ + { source_addr: address }, + { metagraph_allow_spend: { destination_addr: address } }, + { metagraph_spend_transaction: { destination_addr: address } }, + { metagraph_fee_transaction: { destination_addr: address } }, + { + metagraph_expired_spend_transaction: { + metagraph_allow_spend: { destination_addr: address }, + }, + }, + ], + table_name: { in: selectedTables }, + }, + include: metagraphInclude, + orderBy: [{ created_at: "desc" }, { hash: "asc" }], + }, prisma.abstract_transactions_view.findMany, actionsResponse ); diff --git a/src/handlers/allowSpendsHandler.ts b/src/handlers/allowSpendsHandler.ts index 73906be..f9dd012 100644 --- a/src/handlers/allowSpendsHandler.ts +++ b/src/handlers/allowSpendsHandler.ts @@ -1,25 +1,70 @@ -import { PrismaClient } from '@prisma/client'; -import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; -import { extractHashOrdinal, extractPagination } from '../request-params'; -import { paginatedQuery, fromCreatedAtOrdinalCursor, toCreatedAtOrdinalCursor } from '../pagination'; -import { respond, handleError } from '../response'; +import { PrismaClient } from "@prisma/client"; +import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; +import { extractHashOrdinal, extractPagination } from "../request-params"; +import { + paginatedQuery, + fromCreatedAtOrdinalCursor, + toCreatedAtOrdinalCursor, +} from "../pagination"; +import { respond, handleError } from "../response"; const prisma = new PrismaClient(); -export const handleAllowSpends = async ( +const allowSpendResponse = (transaction) => ({ + currencyId: transaction.currencyId, + hash: transaction.hash, + ordinal: transaction.ordinal, + amount: transaction.amount, + source: transaction.source_addr, + destination: transaction.destination_addr, + lastValidEpochProgress: transaction.last_valid_epoch_progress, + round: transaction.round_id, + fee: transaction.fee, + snapshot: transaction.snapshot_hash, + timestamp: transaction.created_at, +}); + +const allowSpendResponses = (txs) => txs.map(allowSpendResponse); + +const spendTransactionResponse = (transaction) => ({ + currencyId: transaction.currencyId, + hash: transaction.hash, + amount: transaction.amount, + source: transaction.source_addr, + destination: transaction.destination_addr, + allowSpendRef: transaction.allow_spend_ref, + snapshot: transaction.snapshot_hash, + timestamp: transaction.created_at, +}); + +const spendTransactionResponses = (txs) => txs.map(spendTransactionResponse); + +const spendExpiredResponse = (transaction) => ({ + currencyId: transaction.currencyId, + hash: transaction.hash, + amount: transaction.amount, + source: transaction.source_addr, + allowSpendRef: transaction.allow_spend_ref, + snapshot: transaction.snapshot_hash, + timestamp: transaction.created_at, +}); + +const spendExpiredResponses = (txs) => txs.map(spendExpiredResponse); + +export const allowSpends = async ( event: APIGatewayProxyEvent ): Promise => { return await paginatedQuery( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { orderBy: { created_at: 'desc' } }, + { orderBy: { created_at: "desc" } }, prisma.dag_allow_spends.findMany, - respond + allowSpendResponses ); }; -export const handleGlobalSnapshotAllowSpends = async ( +export const globalSnapshotAllowSpends = async ( event: APIGatewayProxyEvent ): Promise => { try { @@ -30,16 +75,19 @@ export const handleGlobalSnapshotAllowSpends = async ( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { where: { global_snapshot: filter }, orderBy: { created_at: 'desc' } }, + { + where: { dag_allow_spend_block: { global_snapshot: filter } }, + orderBy: { created_at: "desc" }, + }, prisma.dag_allow_spends.findMany, - respond + allowSpendResponses ); } catch (error) { return handleError(error); } }; -export const handleAddressAllowSpends = async ( +export const addressAllowSpends = async ( event: APIGatewayProxyEvent ): Promise => { try { @@ -49,16 +97,160 @@ export const handleAddressAllowSpends = async ( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { where: { address }, orderBy: { created_at: 'desc' } }, + { + where: { + OR: [{ source_addr: address }, { destination_addr: address }], + }, + orderBy: { created_at: "desc" }, + }, prisma.dag_allow_spends.findMany, - respond + allowSpendResponses + ); + } catch (error) { + return handleError(error); + } +}; + +export const spendTransactions = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { orderBy: { created_at: "desc" } }, + prisma.dag_spend_transactions.findMany, + spendTransactionResponses + ); + } catch (error) { + return handleError(error); + } +}; + +export const globalSnapshotSpendTransactions = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { hash_or_ordinal } = event.pathParameters || {}; + const filter = extractHashOrdinal(hash_or_ordinal); + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { + where: { + dag_allow_spend: { + dag_allow_spend_block: { global_snapshot: filter }, + }, + }, + orderBy: { created_at: "desc" }, + }, + prisma.dag_spend_transactions.findMany, + spendTransactionResponses + ); + } catch (error) { + return handleError(error); + } +}; + +export const addressSpendTransactions = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { address } = event.pathParameters || {}; + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { + where: { + OR: [{ source_addr: address }, { destination_addr: address }], + }, + orderBy: { created_at: "desc" }, + }, + prisma.dag_spend_transactions.findMany, + spendTransactionResponses + ); + } catch (error) { + return handleError(error); + } +}; + +export const allowSpendExpirations = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { orderBy: { created_at: "desc" } }, + prisma.dag_expired_spend_transactions.findMany, + spendExpiredResponses + ); + } catch (error) { + return handleError(error); + } +}; + +export const globalSnapshotAllowSpendExpirations = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { hash_or_ordinal } = event.pathParameters || {}; + const filter = extractHashOrdinal(hash_or_ordinal); + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { + where: { + dag_allow_spend: { + dag_allow_spend_block: { global_snapshot: filter }, + }, + }, + orderBy: { created_at: "desc" }, + }, + prisma.dag_expired_spend_transactions.findMany, + spendExpiredResponses + ); + } catch (error) { + return handleError(error); + } +}; + +export const addressAllowSpendExpirations = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { address } = event.pathParameters || {}; + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { + where: { + OR: [ + { source_addr: address }, + { dag_allow_spend: { destination_addr: address } }, + ], + }, + orderBy: { created_at: "desc" }, + }, + prisma.dag_expired_spend_transactions.findMany, + spendExpiredResponses ); } catch (error) { return handleError(error); } }; -export const handleCurrencyAllowSpends = async ( +export const currencyAllowSpends = async ( event: APIGatewayProxyEvent ): Promise => { try { @@ -68,16 +260,16 @@ export const handleCurrencyAllowSpends = async ( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { where: { metagraph_id }, orderBy: { created_at: 'desc' } }, + { where: { metagraph_id }, orderBy: { created_at: "desc" } }, prisma.metagraph_allow_spends.findMany, - respond + allowSpendResponses ); } catch (error) { return handleError(error); } }; -export const handleCurrencySnapshotAllowSpends = async ( +export const currencySnapshotAllowSpends = async ( event: APIGatewayProxyEvent ): Promise => { try { @@ -88,16 +280,22 @@ export const handleCurrencySnapshotAllowSpends = async ( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { where: { metagraph_id, snapshot: filter }, orderBy: { created_at: 'desc' } }, + { + where: { + metagraph_id, + metagraph_allow_spend_block: { metagraph_snapshot: filter }, + }, + orderBy: { created_at: "desc" }, + }, prisma.metagraph_allow_spends.findMany, - respond + allowSpendResponses ); } catch (error) { return handleError(error); } }; -export const handleCurrencyAddressAllowSpends = async ( +export const currencyAddressAllowSpends = async ( event: APIGatewayProxyEvent ): Promise => { try { @@ -107,9 +305,161 @@ export const handleCurrencyAddressAllowSpends = async ( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { where: { metagraph_id, address }, orderBy: { created_at: 'desc' } }, + { + where: { + metagraph_id, + OR: [{ source_addr: address }, { destination_addr: address }], + }, + orderBy: { created_at: "desc" }, + }, prisma.metagraph_allow_spends.findMany, - respond + allowSpendResponses + ); + } catch (error) { + return handleError(error); + } +}; + +export const currencySpendTransactions = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { metagraph_id } = event.pathParameters || {}; + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { where: { metagraph_id }, orderBy: { created_at: "desc" } }, + prisma.metagraph_spend_transactions.findMany, + spendTransactionResponses + ); + } catch (error) { + return handleError(error); + } +}; + +export const currencySnapshotSpendTransactions = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { metagraph_id, hash_or_ordinal } = event.pathParameters || {}; + const filter = extractHashOrdinal(hash_or_ordinal); + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { + where: { + metagraph_id, + metagraph_allow_spend: { + metagraph_allow_spend_block: { metagraph_snapshot: filter }, + }, + }, + orderBy: { created_at: "desc" }, + }, + prisma.metagraph_spend_transactions.findMany, + spendTransactionResponses + ); + } catch (error) { + return handleError(error); + } +}; + +export const currencyAddressSpendTransactions = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { address } = event.pathParameters || {}; + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { + where: { + OR: [{ source_addr: address }, { destination_addr: address }], + }, + orderBy: { created_at: "desc" }, + }, + prisma.metagraph_spend_transactions.findMany, + spendExpiredResponses + ); + } catch (error) { + return handleError(error); + } +}; + +export const currencyAllowSpendExpirations = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { metagraph_id } = event.pathParameters || {}; + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { where: { metagraph_id }, orderBy: { created_at: "desc" } }, + prisma.metagraph_expired_spend_transactions.findMany, + spendExpiredResponses + ); + } catch (error) { + return handleError(error); + } +}; + +export const currencySnapshotAllowSpendExpirations = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { metagraph_id, hash_or_ordinal } = event.pathParameters || {}; + const filter = extractHashOrdinal(hash_or_ordinal); + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { + where: { + metagraph_id, + metagraph_allow_spend: { + metagraph_allow_spend_block: { metagraph_snapshot: filter }, + }, + }, + orderBy: { created_at: "desc" }, + }, + prisma.metagraph_expired_spend_transactions.findMany, + spendExpiredResponses + ); + } catch (error) { + return handleError(error); + } +}; + +export const currencyAddressAllowSpendExpirations = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { metagraph_id, address } = event.pathParameters || {}; + + return await paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { + where: { + metagraph_id, + OR: [ + { source_addr: address }, + { metagraph_allow_spend: { destination_addr: address } }, + ], + }, + orderBy: { created_at: "desc" }, + }, + prisma.metagraph_expired_spend_transactions.findMany, + spendExpiredResponses ); } catch (error) { return handleError(error); diff --git a/src/handlers/dagHandler.ts b/src/handlers/dagHandler.ts index bd7583e..037bbc1 100644 --- a/src/handlers/dagHandler.ts +++ b/src/handlers/dagHandler.ts @@ -1,9 +1,6 @@ -import { PrismaClient } from '@prisma/client'; -import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; -import { - extractHashOrdinal, - extractPagination, -} from '../request-params'; +import { PrismaClient } from "@prisma/client"; +import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; +import { extractHashOrdinal, extractPagination } from "../request-params"; import { balanceResponse, dagBlockResponse, @@ -14,38 +11,41 @@ import { handleError, notFoundResponse, respond, - rewardsResponse -} from '../response'; -import { fromCreatedAtOrdinalCursor, paginatedQuery, toCreatedAtOrdinalCursor } from '../pagination'; + rewardsResponse, +} from "../response"; +import { + fromCreatedAtOrdinalCursor, + paginatedQuery, + toCreatedAtOrdinalCursor, +} from "../pagination"; import { toNumber, isFinite } from "lodash"; const prisma = new PrismaClient(); const globalSnapshotExists = async (term) => { return prisma.global_snapshots.findUnique({ - where: extractHashOrdinal(term) , + where: extractHashOrdinal(term), select: { hash: true }, - }) + }); }; const latestGlobalSnapshot = async () => { return prisma.global_snapshots.findFirst({ select: { hash: true }, - orderBy: { ordinal: 'desc'} - }) + orderBy: { ordinal: "desc" }, + }); }; - const globalSnapshotWhere = async (term) => { - if (term == 'latest'){ - const latestSnapshotHash = await latestGlobalSnapshot() - return { hash: latestSnapshotHash} - } else { - return extractHashOrdinal(term) - } + if (term == "latest") { + const latestSnapshotHash = await latestGlobalSnapshot(); + return { hash: latestSnapshotHash }; + } else { + return extractHashOrdinal(term); } +}; -export const handleGlobalSnapshots = async ( +export const globalSnapshots = async ( event: APIGatewayProxyEvent ): Promise => { return await paginatedQuery( @@ -54,33 +54,32 @@ export const handleGlobalSnapshots = async ( fromCreatedAtOrdinalCursor, { include: { dag_blocks: true }, - orderBy: { ordinal: 'desc' } + orderBy: { ordinal: "desc" }, }, prisma.global_snapshots.findMany, globalSnapshotsResponse ); }; -export const handleGlobalSnapshot = async ( +export const globalSnapshot = async ( event: APIGatewayProxyEvent ): Promise => { try { const { term } = event.pathParameters || {}; let snapshot; - if (term == 'latest'){ + if (term == "latest") { snapshot = await prisma.global_snapshots.findFirst({ - include: { dag_blocks: true }, - orderBy: { ordinal: 'desc' } - }); - + include: { dag_blocks: true }, + orderBy: { ordinal: "desc" }, + }); } else { - const filter = extractHashOrdinal(term) + const filter = extractHashOrdinal(term); snapshot = await prisma.global_snapshots.findUnique({ where: filter, - include: { dag_blocks: true } - }); + include: { dag_blocks: true }, + }); } return respond(snapshot, globalSnapshotResponse); @@ -89,25 +88,25 @@ export const handleGlobalSnapshot = async ( } }; -export const handleGlobalSnapshotRewards = async ( +export const globalSnapshotRewards = async ( event: APIGatewayProxyEvent ): Promise => { try { const { term } = event.pathParameters || {}; - if (term != "latest" && !await globalSnapshotExists(term)) { + if (term != "latest" && !(await globalSnapshotExists(term))) { return notFoundResponse(); } const toCursor = (row) => ({ global_snapshot_hash_destination_addr: { global_snapshot_hash: row.global_snapshot_hash, - destination_addr: row.destination_addr - } + destination_addr: row.destination_addr, + }, }); const fromCursor = (row) => ({ global_snapshot_hash: row.global_snapshot_hash, - destination_addr: row.destination_addr + destination_addr: row.destination_addr, }); return await paginatedQuery( @@ -116,7 +115,7 @@ export const handleGlobalSnapshotRewards = async ( fromCursor, { where: { global_snapshots: { ...globalSnapshotWhere(term) } }, - orderBy: [{ global_snapshot_hash: 'asc' }, { destination_addr: 'asc' }] + orderBy: [{ destination_addr: "asc" }], }, prisma.dag_reward_transactions.findMany, rewardsResponse @@ -126,32 +125,32 @@ export const handleGlobalSnapshotRewards = async ( } }; -export const handleGlobalSnapshotTransactions = async ( +export const globalSnapshotTransactions = async ( event: APIGatewayProxyEvent ): Promise => { try { const { term } = event.pathParameters || {}; - if (term != "latest" && !await globalSnapshotExists(term)) { + if (term != "latest" && !(await globalSnapshotExists(term))) { return notFoundResponse(); } const query = { where: { - dag_blocks: { global_snapshots: { ...globalSnapshotWhere(term) } } + dag_blocks: { global_snapshots: { ...globalSnapshotWhere(term) } }, }, include: { dag_blocks: { select: { - global_snapshots: { select: { hash: true, ordinal: true } } - } - } + global_snapshots: { select: { hash: true, ordinal: true } }, + }, + }, }, - orderBy: { ordinal: 'desc' } + orderBy: { ordinal: "desc" }, }; return await paginatedQuery( - extractPagination(event), + extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, query, @@ -163,19 +162,19 @@ export const handleGlobalSnapshotTransactions = async ( } }; -export const handleDagBlock = async ( +export const dagBlock = async ( event: APIGatewayProxyEvent ): Promise => { try { const { hash } = event.pathParameters || {}; - + const block = await prisma.dag_blocks.findUnique({ where: { hash }, include: { dag_transactions: { select: { hash: true } }, global_snapshots: true, - super: { include: { block_parents: true } } - } + super: { include: { block_parents: true } }, + }, }); return respond(block, dagBlockResponse); } catch (error) { @@ -193,25 +192,25 @@ const dagTtransactionsQuery = async ( include: { dag_blocks: { include: { - global_snapshots: { select: { hash: true, ordinal: true } } - } - } + global_snapshots: { select: { hash: true, ordinal: true } }, + }, + }, }, - orderBy: { ordinal: 'desc' } + orderBy: { ordinal: "desc" }, }; const toCursor = (row) => ({ ...toCreatedAtOrdinalCursor(row), - hash: row.hash + hash: row.hash, }); const fromCursor = (row) => ({ ...fromCreatedAtOrdinalCursor(row), - hash: row.hash + hash: row.hash, }); return await paginatedQuery( - extractPagination(event), + extractPagination(event), toCursor, fromCursor, query, @@ -223,13 +222,13 @@ const dagTtransactionsQuery = async ( } }; -export const handleDagTransactions = async ( +export const dagTransactions = async ( event: APIGatewayProxyEvent ): Promise => { return dagTtransactionsQuery({}, event); }; -export const handleDagTransaction = async ( +export const dagTransaction = async ( event: APIGatewayProxyEvent ): Promise => { try { @@ -240,10 +239,10 @@ export const handleDagTransaction = async ( include: { dag_blocks: { include: { - global_snapshots: { select: { hash: true, ordinal: true } } - } - } - } + global_snapshots: { select: { hash: true, ordinal: true } }, + }, + }, + }, }); return respond(transaction, dagTransactionResponse); @@ -252,14 +251,14 @@ export const handleDagTransaction = async ( } }; -export const handleDagTransactionsByAddress = async ( +export const dagTransactionsByAddress = async ( event: APIGatewayProxyEvent ): Promise => { try { const { address } = event.pathParameters || {}; const where = { - where: { OR: [{ source_addr: address }, { destination_addr: address }] } + where: { OR: [{ source_addr: address }, { destination_addr: address }] }, }; return dagTtransactionsQuery(where, event); @@ -268,7 +267,7 @@ export const handleDagTransactionsByAddress = async ( } }; -export const handleDagTransactionsBySource = async ( +export const dagTransactionsBySource = async ( event: APIGatewayProxyEvent ): Promise => { try { @@ -282,7 +281,7 @@ export const handleDagTransactionsBySource = async ( } }; -export const handleDagTransactionsByDestination = async ( +export const dagTransactionsByDestination = async ( event: APIGatewayProxyEvent ): Promise => { try { @@ -296,18 +295,20 @@ export const handleDagTransactionsByDestination = async ( } }; -export const handleDagBalanceByAddress = async ( +export const dagBalanceByAddress = async ( event: APIGatewayProxyEvent ): Promise => { try { const { address, ordinal } = event.pathParameters || {}; const ordinalNbr = toNumber(ordinal); - const ordinalCondition = (isFinite(ordinalNbr)? { snapshot_ordinal: {lte: ordinalNbr}}: {}) + const ordinalCondition = isFinite(ordinalNbr) + ? { snapshot_ordinal: { lte: ordinalNbr } } + : {}; const balances = await prisma.dag_balance_changes.findFirst({ where: { address, ...ordinalCondition }, - orderBy: { snapshot_ordinal: 'desc' } + orderBy: { snapshot_ordinal: "desc" }, }); return respond(balances, balanceResponse); @@ -315,4 +316,3 @@ export const handleDagBalanceByAddress = async ( return handleError(error); } }; - diff --git a/src/handlers/metagraphHandler.ts b/src/handlers/metagraphHandler.ts index 59612fb..e6735d0 100644 --- a/src/handlers/metagraphHandler.ts +++ b/src/handlers/metagraphHandler.ts @@ -1,9 +1,6 @@ -import { PrismaClient } from '@prisma/client'; -import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; -import { - extractHashOrdinal, - extractPagination, -} from '../request-params'; +import { PrismaClient } from "@prisma/client"; +import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; +import { extractHashOrdinal, extractPagination } from "../request-params"; import { balanceResponse, handleError, @@ -18,9 +15,13 @@ import { missingParameterResponse, notFoundResponse, respond, - rewardsResponse -} from '../response'; -import { fromCreatedAtOrdinalCursor, paginatedQuery, toCreatedAtOrdinalCursor } from '../pagination'; + rewardsResponse, +} from "../response"; +import { + fromCreatedAtOrdinalCursor, + paginatedQuery, + toCreatedAtOrdinalCursor, +} from "../pagination"; import { toNumber, isFinite } from "lodash"; const prisma = new PrismaClient(); @@ -28,36 +29,36 @@ const prisma = new PrismaClient(); const latestMetagraphSnapshot = async () => { return prisma.metagraph_snapshots.findFirst({ select: { hash: true }, - orderBy: { ordinal: 'desc'} - }) + orderBy: { ordinal: "desc" }, + }); }; const metagraphSnapshotWhere = async (term) => { - if (term == 'latest'){ - const latestSnapshotHash = await latestMetagraphSnapshot() - return { hash: latestSnapshotHash} - } else { - return extractHashOrdinal(term) - } + if (term == "latest") { + const latestSnapshotHash = await latestMetagraphSnapshot(); + return { hash: latestSnapshotHash }; + } else { + return extractHashOrdinal(term); } +}; const metagraphSnapshotExists = async (metagraph_id, term) => { const filter = extractHashOrdinal(term); let where; if ("ordinal" in filter) { - where = {metagraph_id_ordinal: { metagraph_id, ordinal: filter.ordinal } }; + where = { metagraph_id_ordinal: { metagraph_id, ordinal: filter.ordinal } }; } else { - where = { metagraph_id_hash: { metagraph_id, hash: filter.hash }}; + where = { metagraph_id_hash: { metagraph_id, hash: filter.hash } }; } return prisma.metagraph_snapshots.findUnique({ where, select: { hash: true }, }); -}; +}; -export const handleCurrencySnapshots = async ( +export const currencySnapshots = async ( event: APIGatewayProxyEvent ): Promise => { try { @@ -66,23 +67,23 @@ export const handleCurrencySnapshots = async ( const toCursor = (row) => ({ ...toCreatedAtOrdinalCursor(row), metagraph_id: row.metagraph_id, - hash: row.hash + hash: row.hash, }); const fromCursor = (row) => ({ ...fromCreatedAtOrdinalCursor(row), metagraph_id: row.metagraph_id, - hash: row.hash + hash: row.hash, }); return await paginatedQuery( - extractPagination(event), + extractPagination(event), toCursor, fromCursor, { where: { metagraph_id: identifier }, include: { metagraph_blocks: true }, - orderBy: { ordinal: 'desc' } + orderBy: { ordinal: "desc" }, }, prisma.metagraph_snapshots.findMany, metagraphSnapshotsResponse @@ -92,7 +93,7 @@ export const handleCurrencySnapshots = async ( } }; -export const handleCurrencySnapshotsByOwnerAddress = async ( +export const currencySnapshotsByOwnerAddress = async ( event: APIGatewayProxyEvent ): Promise => { try { @@ -101,23 +102,23 @@ export const handleCurrencySnapshotsByOwnerAddress = async ( const toCursor = (row) => ({ ...toCreatedAtOrdinalCursor(row), metagraph_id: row.metagraph_id, - hash: row.hash + hash: row.hash, }); const fromCursor = (row) => ({ ...fromCreatedAtOrdinalCursor(row), metagraph_id: row.metagraph_id, - hash: row.hash + hash: row.hash, }); return await paginatedQuery( - extractPagination(event), + extractPagination(event), toCursor, fromCursor, { where: { owner_address: address }, include: { metagraph_blocks: true }, - orderBy: { ordinal: 'desc' } + orderBy: { ordinal: "desc" }, }, prisma.metagraph_snapshots.findMany, metagraphSnapshotsResponse @@ -127,7 +128,7 @@ export const handleCurrencySnapshotsByOwnerAddress = async ( } }; -export const handleCurrencySnapshot = async ( +export const currencySnapshot = async ( event: APIGatewayProxyEvent ): Promise => { try { @@ -136,7 +137,7 @@ export const handleCurrencySnapshot = async ( const snapshot = await prisma.metagraph_snapshots.findFirst({ where: { metagraph_id: metagraph_id, ...metagraphSnapshotWhere(term) }, include: { metagraph_blocks: true }, - orderBy: { ordinal: 'desc' } + orderBy: { ordinal: "desc" }, }); return respond(snapshot, metagraphSnapshotResponse); @@ -145,33 +146,40 @@ export const handleCurrencySnapshot = async ( } }; -export const handleCurrencySnapshotRewards = async ( +export const currencySnapshotRewards = async ( event: APIGatewayProxyEvent ): Promise => { try { const { identifier: metagraph_id, term } = event.pathParameters || {}; - - if (term != "latest" && !(await metagraphSnapshotExists(metagraph_id, term))){ + + if ( + term != "latest" && + !(await metagraphSnapshotExists(metagraph_id, term)) + ) { return notFoundResponse(); } const cursor = (row) => ({ metagraph_id: row.metagraph_id, - hash: row.hash + hash: row.hash, }); return await paginatedQuery( - extractPagination(event), + extractPagination(event), cursor, cursor, { where: { - metagraph_snapshots: { + metagraph_snapshot: { metagraph_id, - ...metagraphSnapshotWhere(term) - } + ...metagraphSnapshotWhere(term), + }, }, - orderBy: [{ metagraph_id: 'asc'}, {metagraph_snapshot_hash: 'asc'}, {destination_addr: 'asc' }] + orderBy: [ + { metagraph_id: "asc" }, + { metagraph_snapshot_hash: "asc" }, + { destination_addr: "asc" }, + ], }, prisma.metagraph_reward_transactions.findMany, rewardsResponse @@ -191,20 +199,20 @@ const metagraphTransactionsQuery = async ( include: { metagraph_blocks: { include: { - metagraph_snapshots: { select: { hash: true, ordinal: true } } - } - } + metagraph_snapshot: { select: { hash: true, ordinal: true } }, + }, + }, }, - orderBy: { ordinal: 'desc' } + orderBy: { ordinal: "desc" }, }; const cursor = (row) => ({ metagraph_id: row.metagraph_id, - hash: row.hash + hash: row.hash, }); return await paginatedQuery( - extractPagination(event), + extractPagination(event), cursor, cursor, query, @@ -216,24 +224,27 @@ const metagraphTransactionsQuery = async ( } }; -export const handleCurrencySnapshotTransactions = async ( +export const currencySnapshotTransactions = async ( event: APIGatewayProxyEvent ): Promise => { try { const { identifier: metagraph_id, term } = event.pathParameters || {}; - - if (term != "latest" && !(await metagraphSnapshotExists(metagraph_id, term))) + + if ( + term != "latest" && + !(await metagraphSnapshotExists(metagraph_id, term)) + ) return notFoundResponse(); const where = { where: { metagraph_blocks: { - metagraph_snapshots: { + metagraph_snapshot: { metagraph_id: metagraph_id, - ...metagraphSnapshotWhere(term) - } - } - } + ...metagraphSnapshotWhere(term), + }, + }, + }, }; return metagraphTransactionsQuery(where, event); @@ -242,7 +253,7 @@ export const handleCurrencySnapshotTransactions = async ( } }; -export const handleCurrencyBlock = async ( +export const currencyBlock = async ( event: APIGatewayProxyEvent ): Promise => { try { @@ -252,9 +263,9 @@ export const handleCurrencyBlock = async ( where: { metagraph_id, hash }, include: { metagraph_transactions: { select: { hash: true } }, - metagraph_snapshots: true, - super: { include: { block_parents: true } } - } + metagraph_snapshot: true, + super: { include: { block_parents: true } }, + }, }); return respond(block, metagraphBlockResponse); @@ -263,7 +274,7 @@ export const handleCurrencyBlock = async ( } }; -export const handleCurrencyTransactions = async ( +export const currencyTransactions = async ( event: APIGatewayProxyEvent ): Promise => { try { @@ -277,25 +288,26 @@ export const handleCurrencyTransactions = async ( } }; -export const handleCurrencyTransaction = async ( +export const currencyTransaction = async ( event: APIGatewayProxyEvent ): Promise => { try { const { identifier: metagraph_id, hash } = event.pathParameters || {}; const transaction = await prisma.metagraph_transactions.findUnique({ - where: { metagraph_id_hash: { - metagraph_id: metagraph_id!, - hash: hash! - } - }, + where: { + metagraph_id_hash: { + metagraph_id: metagraph_id!, + hash: hash!, + }, + }, include: { metagraph_blocks: { include: { - metagraph_snapshots: { select: { hash: true, ordinal: true } } - } - } - } + metagraph_snapshot: { select: { hash: true, ordinal: true } }, + }, + }, + }, }); return respond(transaction, metagraphTransactionResponse); @@ -304,16 +316,17 @@ export const handleCurrencyTransaction = async ( } }; -export const handleCurrencyTransactionsByAddress = async ( +export const currencyTransactionsByAddress = async ( event: APIGatewayProxyEvent ): Promise => { try { const { identifier: metagraph_id, address } = event.pathParameters || {}; const where = { - where: { metagraph_id, - OR: [{ source_addr: address }, { destination_addr: address }] - } + where: { + metagraph_id, + OR: [{ source_addr: address }, { destination_addr: address }], + }, }; return metagraphTransactionsQuery(where, event); @@ -322,12 +335,12 @@ export const handleCurrencyTransactionsByAddress = async ( } }; -export const handleCurrencyTransactionsBySource = async ( +export const currencyTransactionsBySource = async ( event: APIGatewayProxyEvent ): Promise => { try { const { identifier: metagraph_id, address } = event.pathParameters || {}; - + const where = { where: { metagraph_id, source_addr: address } }; return metagraphTransactionsQuery(where, event); @@ -336,12 +349,12 @@ export const handleCurrencyTransactionsBySource = async ( } }; -export const handleCurrencyTransactionsByDestination = async ( +export const currencyTransactionsByDestination = async ( event: APIGatewayProxyEvent ): Promise => { try { const { identifier: metagraph_id, address } = event.pathParameters || {}; - + const where = { where: { metagraph_id, destination_addr: address } }; return metagraphTransactionsQuery(where, event); @@ -350,22 +363,28 @@ export const handleCurrencyTransactionsByDestination = async ( } }; -export const handleCurrencyBalanceByAddress = async ( +export const currencyBalanceByAddress = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { identifier: metagraph_id, address, ordinal } = event.pathParameters || {}; + const { + identifier: metagraph_id, + address, + ordinal, + } = event.pathParameters || {}; const ordinalNbr = toNumber(ordinal); - const ordinalCondition = (isFinite(ordinalNbr)? {metagraph_snapshot_ordinal: { lte: ordinalNbr}}: {}) + const ordinalCondition = isFinite(ordinalNbr) + ? { metagraph_snapshot_ordinal: { lte: ordinalNbr } } + : {}; const balance = await prisma.metagraph_balance_changes.findFirst({ where: { metagraph_id, address, - ...ordinalCondition + ...ordinalCondition, }, - orderBy: { metagraph_snapshot_ordinal: 'desc' } + orderBy: { metagraph_snapshot_ordinal: "desc" }, }); return respond(balance, balanceResponse); @@ -374,20 +393,20 @@ export const handleCurrencyBalanceByAddress = async ( } }; -export const handleCurrencyFeeTransaction = async ( +export const currencyFeeTransaction = async ( event: APIGatewayProxyEvent ): Promise => { try { const { identifier: metagraph_id, hash } = event.pathParameters || {}; const transaction = await prisma.metagraph_fee_transactions.findUnique({ - where: { - metagraph_id: metagraph_id!, - hash: hash! + where: { + metagraph_id: metagraph_id!, + hash: hash!, }, include: { - metagraph_snapshots: { select: { hash: true, ordinal: true } } - } + metagraph_snapshot: { select: { hash: true, ordinal: true } }, + }, }); return respond(transaction, metagraphFeeTransactionResponse); @@ -404,18 +423,18 @@ const metagraphFeeTransactionsQuery = async ( const query = { ...baseQuery, include: { - metagraph_snapshots: { select: { hash: true, ordinal: true } } + metagraph_snapshot: { select: { hash: true, ordinal: true } }, }, - orderBy: { ordinal: 'desc' } + orderBy: { hash: "asc" }, }; const cursor = (row) => ({ metagraph_id: row.metagraph_id, - hash: row.hash + hash: row.hash, }); return await paginatedQuery( - extractPagination(event), + extractPagination(event), cursor, cursor, query, @@ -427,17 +446,19 @@ const metagraphFeeTransactionsQuery = async ( } }; -export const handleCurrencySnapshotFeeTransactions = async ( +export const currencySnapshotFeeTransactions = async ( event: APIGatewayProxyEvent ): Promise => { try { const { identifier: metagraph_id, term } = event.pathParameters || {}; if (!metagraph_id || !term) - return missingParameterResponse('identifier or term'); + return missingParameterResponse("identifier or term"); const where = { - metagraph_id: metagraph_id, - ordinal: BigInt(term), + where: { + metagraph_id: metagraph_id, + ...metagraphSnapshotWhere(term), + }, }; return metagraphFeeTransactionsQuery(where, event); @@ -446,17 +467,19 @@ export const handleCurrencySnapshotFeeTransactions = async ( } }; -export const handleCurrencyFeeTransactionsByAddress = async ( +export const currencyFeeTransactionsByAddress = async ( event: APIGatewayProxyEvent ): Promise => { try { const { identifier: metagraph_id, address } = event.pathParameters || {}; if (!metagraph_id || !address) - return missingParameterResponse('identifier or address'); + return missingParameterResponse("identifier or address"); const where = { - metagraph_id: metagraph_id, - OR: [{ source_addr: address }, { destination_addr: address }] + where: { + metagraph_id: metagraph_id, + OR: [{ source_addr: address }, { destination_addr: address }], + }, }; return metagraphFeeTransactionsQuery(where, event); @@ -465,17 +488,19 @@ export const handleCurrencyFeeTransactionsByAddress = async ( } }; -export const handleCurrencyFeeTransactionsBySource = async ( +export const currencyFeeTransactionsBySource = async ( event: APIGatewayProxyEvent ): Promise => { try { const { identifier: metagraph_id, address } = event.pathParameters || {}; if (!metagraph_id || !address) - return missingParameterResponse('identifier or address'); + return missingParameterResponse("identifier or address"); const where = { - metagraph_id: metagraph_id, - source_addr: address + where: { + metagraph_id: metagraph_id, + source_addr: address, + }, }; return metagraphFeeTransactionsQuery(where, event); } catch (error) { @@ -483,17 +508,19 @@ export const handleCurrencyFeeTransactionsBySource = async ( } }; -export const handleCurrencyFeeTransactionsByDestination = async ( +export const currencyFeeTransactionsByDestination = async ( event: APIGatewayProxyEvent ): Promise => { try { const { identifier: metagraph_id, address } = event.pathParameters || {}; if (!metagraph_id || !address) - return missingParameterResponse('identifier or address'); + return missingParameterResponse("identifier or address"); const where = { - metagraph_id: metagraph_id, - destination_addr: address + where: { + metagraph_id: metagraph_id, + destination_addr: address, + }, }; return metagraphFeeTransactionsQuery(where, event); } catch (error) { @@ -508,7 +535,7 @@ export const metagraphs = async ( const cursor = (row) => ({ id: row.id }); return await paginatedQuery( - extractPagination(event), + extractPagination(event), cursor, cursor, {}, diff --git a/src/handlers/tokenLocksHandler.ts b/src/handlers/tokenLocksHandler.ts index e6d49eb..75770a5 100644 --- a/src/handlers/tokenLocksHandler.ts +++ b/src/handlers/tokenLocksHandler.ts @@ -1,109 +1,282 @@ -import { PrismaClient } from '@prisma/client'; -import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; -import { extractHashOrdinal, extractPagination } from '../request-params'; -import { paginatedQuery, fromCreatedAtOrdinalCursor, toCreatedAtOrdinalCursor } from '../pagination'; -import { respond, handleError, notFoundResponse } from '../response'; +import { PrismaClient } from "@prisma/client"; +import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; +import { extractHashOrdinal, extractPagination } from "../request-params"; +import { + paginatedQuery, + fromCreatedAtOrdinalCursor, + toCreatedAtOrdinalCursor, +} from "../pagination"; +import { handleError } from "../response"; const prisma = new PrismaClient(); -export const handleTokenLocks = async ( +const tokenLockResponse = (transaction) => ({ + currencyId: transaction.currencyId, + hash: transaction.hash, + amount: transaction.amount, + source: transaction.source_addr, + destination: transaction.destination_addr, + unlockEpoch: transaction.unlock_epoch, + parentHash: transaction.lock_reference_hash, + timestamp: transaction.created_at, +}); + +const tokenLockResponses = (txs) => txs.map(tokenLockResponse); + +const tokenUnlockResponse = (transaction) => ({ + currencyId: transaction.currencyId, + hash: transaction.hash, + amount: transaction.amount, + source: transaction.source_addr, + timestamp: transaction.created_at, + lockOrdinal: transaction.lock_reference_ordinal, +}); + +const tokenUnlockResponses = (txs) => txs.map(tokenUnlockResponse); + +export const tokenLocks = async ( event: APIGatewayProxyEvent ): Promise => { - return await paginatedQuery( + return paginatedQuery( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { orderBy: { created_at: 'desc' } }, + { orderBy: { created_at: "desc" } }, prisma.dag_token_locks.findMany, - respond + tokenLockResponses ); }; -export const handleGlobalSnapshotTokenLocks = async ( +export const globalSnapshotTokenLocks = async ( event: APIGatewayProxyEvent ): Promise => { try { const { hash_or_ordinal } = event.pathParameters || {}; const filter = extractHashOrdinal(hash_or_ordinal); - return await paginatedQuery( + return paginatedQuery( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { where: { global_snapshot: filter }, orderBy: { created_at: 'desc' } }, + { + where: { + dag_token_lock_block: { + global_snapshot: filter, + }, + }, + orderBy: { created_at: "desc" }, + }, prisma.dag_token_locks.findMany, - respond + tokenLockResponses ); } catch (error) { return handleError(error); } }; -export const handleAddressTokenLocks = async ( +export const addressTokenLocks = async ( event: APIGatewayProxyEvent ): Promise => { try { const { address } = event.pathParameters || {}; - return await paginatedQuery( + return paginatedQuery( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { where: { address }, orderBy: { created_at: 'desc' } }, + { where: { source_addr: address }, orderBy: { created_at: "desc" } }, prisma.dag_token_locks.findMany, - respond + tokenLockResponses ); } catch (error) { return handleError(error); } }; -export const handleTokenUnlocks = async ( +export const tokenUnlocks = async ( event: APIGatewayProxyEvent ): Promise => { - return await paginatedQuery( + return paginatedQuery( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { orderBy: { created_at: 'desc' } }, + { orderBy: { created_at: "desc" } }, prisma.dag_token_unlocks.findMany, - respond + tokenUnlockResponses ); }; -export const handleGlobalSnapshotTokenUnlocks = async ( +export const globalSnapshotTokenUnlocks = async ( event: APIGatewayProxyEvent ): Promise => { try { const { hash_or_ordinal } = event.pathParameters || {}; const filter = extractHashOrdinal(hash_or_ordinal); - return await paginatedQuery( + return paginatedQuery( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { where: { global_snapshot: filter }, orderBy: { created_at: 'desc' } }, + { + where: { + dag_token_lock: { + dag_token_lock_block: { + global_snapshot: filter, + }, + }, + }, + orderBy: { created_at: "desc" }, + }, prisma.dag_token_unlocks.findMany, - respond + tokenUnlockResponses ); } catch (error) { return handleError(error); } }; -export const handleAddressTokenUnlocks = async ( +export const addressTokenUnlocks = async ( event: APIGatewayProxyEvent ): Promise => { try { const { address } = event.pathParameters || {}; - return await paginatedQuery( + return paginatedQuery( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { where: { address }, orderBy: { created_at: 'desc' } }, + { where: { source_addr: address }, orderBy: { created_at: "desc" } }, prisma.dag_token_unlocks.findMany, - respond + tokenUnlockResponses + ); + } catch (error) { + return handleError(error); + } +}; + +export const metagraphTokenLocks = async ( + event: APIGatewayProxyEvent +): Promise => { + const { identifier: metagraph_id } = event.pathParameters || {}; + + return paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { where: { metagraph_id }, orderBy: { created_at: "desc" } }, + prisma.metagraph_token_locks.findMany, + tokenLockResponses + ); +}; + +export const metagraphSnapshotTokenLocks = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { identifier: metagraph_id, hash_or_ordinal } = + event.pathParameters || {}; + const filter = extractHashOrdinal(hash_or_ordinal); + + return paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { + where: { + metagraph_id, + metagraph_token_lock_block: { metagraph_snapshot: filter }, + }, + orderBy: { created_at: "desc" }, + }, + prisma.metagraph_token_locks.findMany, + tokenLockResponses + ); + } catch (error) { + return handleError(error); + } +}; + +export const metagraphAddressTokenLocks = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { identifier: metagraph_id, address } = event.pathParameters || {}; + + return paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { + where: { metagraph_id, source_addr: address }, + orderBy: { created_at: "desc" }, + }, + prisma.metagraph_token_locks.findMany, + tokenLockResponses + ); + } catch (error) { + return handleError(error); + } +}; + +export const metagraphTokenUnlocks = async ( + event: APIGatewayProxyEvent +): Promise => { + const { identifier: metagraph_id } = event.pathParameters || {}; + return paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { where: { metagraph_id }, orderBy: { created_at: "desc" } }, + prisma.metagraph_token_unlocks.findMany, + tokenUnlockResponses + ); +}; + +export const metagraphSnapshotTokenUnlocks = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { identifier: metagraph_id, hash_or_ordinal } = + event.pathParameters || {}; + const filter = extractHashOrdinal(hash_or_ordinal); + + return paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { + where: { + metagraph_id, + token_lock: { + metagraph_token_lock_block: { metagraph_snapshot: filter }, + }, + }, + orderBy: { created_at: "desc" }, + }, + prisma.metagraph_token_unlocks.findMany, + tokenUnlockResponses + ); + } catch (error) { + return handleError(error); + } +}; + +export const metagraphAddressTokenUnlocks = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { identifier: metagraph_id, address } = event.pathParameters || {}; + + return paginatedQuery( + extractPagination(event), + toCreatedAtOrdinalCursor, + fromCreatedAtOrdinalCursor, + { + where: { metagraph_id, source_addr: address }, + orderBy: { created_at: "desc" }, + }, + prisma.metagraph_token_unlocks.findMany, + tokenUnlockResponses ); } catch (error) { return handleError(error); diff --git a/src/pagination.ts b/src/pagination.ts index 7394a5e..e4d6771 100644 --- a/src/pagination.ts +++ b/src/pagination.ts @@ -1,43 +1,49 @@ import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; import { handleError, respond } from "./response"; -import { Pagination } from './request-params'; +import { Pagination } from "./request-params"; import { toNumber, isFinite } from "lodash"; export const maxSizeLimit = 100; export enum SortOrder { - Desc = 'desc', - Asc = 'asc' + Desc = "desc", + Asc = "asc", } export enum SearchDirection { - After = 'search_after', - Before = 'search_before' + After = "search_after", + Before = "search_before", } - const safeNumber = (value, defaultValue) => { const num = toNumber(value); - return (isFinite(num) && num > 0) ? num : defaultValue; + return isFinite(num) && num > 0 ? num : defaultValue; }; -export const toCreatedAtCursor = (row) => ({ created_at: new Date(row.created_at) }); -const fromCreatedAtCursor = (row) => ({ - created_at: row.created_at.toISOString() +export const toCreatedAtCursor = (row) => ({ + created_at: new Date(row.created_at), +}); +export const fromCreatedAtCursor = (row) => ({ + created_at: row.created_at.toISOString(), }); -export const toOrdinalCursor = (row) => ({ ordinal: BigInt('0x' + row.ordinal) }); -export const fromOrdinalCursor = (row) => ({ ordinal: row.ordinal.toString(16) }); +export const toOrdinalCursor = (row) => ({ + ordinal: BigInt("0x" + row.ordinal), +}); +export const fromOrdinalCursor = (row) => ({ + ordinal: row.ordinal.toString(16), +}); export const toCreatedAtOrdinalCursor = (row) => ({ ...toCreatedAtCursor(row), - ...toOrdinalCursor(row) + ...toOrdinalCursor(row), }); export const fromCreatedAtOrdinalCursor = (row) => ({ ...fromCreatedAtCursor(row), - ...fromOrdinalCursor(row) + ...fromOrdinalCursor(row), }); +export const hashCursor = (row) => ({ hash: row.hash }); const buildPageQuery = (pagination: Pagination, nextToCursor) => { const pageSize = safeNumber(pagination.size, maxSizeLimit); @@ -45,8 +51,10 @@ const buildPageQuery = (pagination: Pagination, nextToCursor) => { if ( pagination && - 'searchSince' in pagination && pagination.searchSince && - 'searchDirection' in pagination && pagination.searchDirection + "searchSince" in pagination && + pagination.searchSince && + "searchDirection" in pagination && + pagination.searchDirection ) { const page = pagination.searchDirection === SearchDirection.Before @@ -54,17 +62,17 @@ const buildPageQuery = (pagination: Pagination, nextToCursor) => { : incrementedSize; return { take: page, - cursor: pagination.searchSince + cursor: pagination.searchSince, }; - } + } - if (pagination && 'next' in pagination) { + if (pagination && "next" in pagination) { return { take: incrementedSize, cursor: nextToCursor(pagination.next) }; } if (pagination && pagination.size) { return { take: incrementedSize }; - } + } return { take: incrementedSize }; }; @@ -82,9 +90,8 @@ export const paginatedQuery = async ( const pagedQuery = { ...baseQuery, - ...(pagination ? pageQueryParams : {}) + ...(pagination ? pageQueryParams : {}), }; - const rawResults = await findMany(pagedQuery); const pageSize = pageQueryParams.take @@ -103,4 +110,4 @@ export const paginatedQuery = async ( } catch (error) { return handleError(error); } -}; \ No newline at end of file +}; diff --git a/src/request-params.ts b/src/request-params.ts index 32952f1..1a3efc2 100644 --- a/src/request-params.ts +++ b/src/request-params.ts @@ -1,5 +1,5 @@ -import { APIGatewayEvent } from 'aws-lambda'; -import { maxSizeLimit, SearchDirection } from './pagination'; +import { APIGatewayEvent } from "aws-lambda"; +import { maxSizeLimit, SearchDirection } from "./pagination"; export type Pagination = | { @@ -19,16 +19,13 @@ type PaginationQueryParams = { export const extractHashOrdinal = (term) => { if (isNaN(Number(term))) { - return { hash: term } + return { hash: term }; } else { - return { ordinal: BigInt(term) } + return { ordinal: BigInt(term) }; } -} - +}; -export const extractPagination = ( - event: APIGatewayEvent -): Pagination => { +export const extractPagination = (event: APIGatewayEvent): Pagination => { const params = event.queryStringParameters as PaginationQueryParams; const searchBefore = params?.search_before; const searchAfter = params?.search_after; @@ -37,40 +34,34 @@ export const extractPagination = ( if (searchBefore && searchAfter) { throw new Error( - 'search_after & search_before should be mutually exclusive' + "search_after & search_before should be mutually exclusive" ); } if (params?.limit !== undefined) { if (isNaN(limit)) { - throw new Error( - 'limit must be a number' - ); + throw new Error("limit must be a number"); } if (limit < 1) { - throw new Error( - 'limit must be a positive number', - ); + throw new Error("limit must be a positive number"); } if (limit > maxSizeLimit) { - throw new Error( - `limit must be lower or equal ${maxSizeLimit}`, - ); + throw new Error(`limit must be lower or equal ${maxSizeLimit}`); } } if (next && searchAfter && searchBefore) { throw new Error( - 'next and search_after/search_before should be mutually exclusive', + "next and search_after/search_before should be mutually exclusive" ); } if (next) { return { next: fromNextString(next), - size: params.limit !== undefined ? limit : undefined + size: params.limit !== undefined ? limit : undefined, }; } @@ -80,16 +71,16 @@ export const extractPagination = ( (searchAfter && SearchDirection.After) || (searchBefore && SearchDirection.Before) || undefined, - size: limit + size: limit, }; }; export const toNextString = (next): string => { const buffer = Buffer.from(JSON.stringify(next)); - return buffer.toString('base64'); + return buffer.toString("base64"); }; export const fromNextString = (next: string) => { - const buffer = Buffer.from(next, 'base64'); - return JSON.parse(buffer.toString('ascii')); + const buffer = Buffer.from(next, "base64"); + return JSON.parse(buffer.toString("ascii")); }; diff --git a/src/response.ts b/src/response.ts index c5218aa..7bf09f7 100644 --- a/src/response.ts +++ b/src/response.ts @@ -1,9 +1,9 @@ -import { APIGatewayProxyResult } from 'aws-lambda'; -import { toNextString } from './request-params'; +import { APIGatewayProxyResult } from "aws-lambda"; +import { toNextString } from "./request-params"; const DEFAULT_HEADERS = { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': '*' + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*", }; export type Result = { @@ -27,18 +27,18 @@ const commonSnapshotResponse = (snapshot, blocksProperty) => ({ subHeight: snapshot.subheight, lastSnapshotHash: snapshot.last_snapshot_hash, blocks: snapshot[blocksProperty].map((b) => b.hash), - timestamp: snapshot.created_at + timestamp: snapshot.created_at, }); export const globalSnapshotResponse = (snapshot) => ({ - ...commonSnapshotResponse(snapshot, 'dag_blocks') + ...commonSnapshotResponse(snapshot, "dag_blocks"), }); export const rewardsResponse = (rs) => rs.map(rewardResponse); export const rewardResponse = (reward) => ({ destination: reward.destination_addr, - amount: reward.amount + amount: reward.amount, }); export const dagTransactionsResponse = (ts) => ts.map(dagTransactionResponse); @@ -54,67 +54,67 @@ const transactionResponse = (transaction, snapshot) => ({ fee: transaction.fee, parent: { hash: transaction.parent_hash, - ordinal: transaction.parent_ordinal + ordinal: transaction.parent_ordinal, }, salt: transaction.salt, blockHash: transaction.block_hash, snapshotHash: snapshot.hash, snapshotOrdinal: snapshot.ordinal, - timestamp: transaction.created_at + timestamp: transaction.created_at, }); const blockResponse = (block) => ({ hash: block.hash, height: block.height, parents: block.super.block_parents.map(blockParentResponse), - timestamp: block.created_at + timestamp: block.created_at, }); export const dagBlockResponse = (block) => ({ ...blockResponse(block), transactions: block.dag_transactions.map((tx) => tx.hash), snapshotHash: block.global_snapshots.hash, - snapshotOrdinal: block.global_snapshots.ordinal + snapshotOrdinal: block.global_snapshots.ordinal, }); export const blockParentResponse = (block_parent) => ({ hash: block_parent.parent_proof_hash, - height: block_parent.parent_height + height: block_parent.parent_height, }); export const balanceResponse = (balance) => ({ ordinal: balance.snapshot_ordinal, balance: balance.balance, - address: balance.address + address: balance.address, }); export const metagraphSnapshotsResponse = (ss) => ss.map(metagraphSnapshotResponse); export const metagraphSnapshotResponse = (snapshot) => ({ - ...commonSnapshotResponse(snapshot, 'metagraph_blocks') + ...commonSnapshotResponse(snapshot, "metagraph_blocks"), }); export const metagraphBlockResponse = (block) => ({ ...blockResponse(block), transactions: block.metagraph_transactions.map((tx) => tx.hash), - snapshotHash: block.metagraph_snapshots.hash, - snapshotOrdinal: block.metagraph_snapshots.ordinal + snapshotHash: block.metagraph_snapshot.hash, + snapshotOrdinal: block.metagraph_snapshot.ordinal, }); export const metagraphTransactionsResponse = (ts) => ts.map(metagraphTransactionResponse); export const metagraphTransactionResponse = (t) => - transactionResponse(t, t.metagraph_blocks.metagraph_snapshots); + transactionResponse(t, t.metagraph_blocks.metagraph_snapshot); export const metagraphFeeTransactionsResponse = (ts) => ts.map(metagraphTransactionResponse); export const metagraphFeeTransactionResponse = (t) => - transactionResponse(t, t.metagraph_snapshots); + transactionResponse(t, t.metagraph_snapshot); export const metagraphsResponse = (mgs) => mgs.map(metagraphResponse); export const metagraphResponse = (mg) => ({ id: mg.id, - timestamp: mg.created_at + timestamp: mg.created_at, }); export const respond = (data, transform, next?) => { @@ -131,30 +131,30 @@ export const successResponse = (data: any): APIGatewayProxyResult => ({ statusCode: 200, headers: DEFAULT_HEADERS, body: JSON.stringify(data, (_, v) => - typeof v === 'bigint' + typeof v === "bigint" ? v > Number.MAX_SAFE_INTEGER ? v.toString() : Number(v) : v - ) + ), }); export const notFoundResponse = (): APIGatewayProxyResult => ({ statusCode: 404, - body: JSON.stringify({ message: 'Not found' }) + body: JSON.stringify({ message: "Not found" }), }); export const missingParameterResponse = ( param: string ): APIGatewayProxyResult => ({ statusCode: 400, - body: JSON.stringify({ message: `Missing parameter: ${param}` }) + body: JSON.stringify({ message: `Missing parameter: ${param}` }), }); export const handleError = (error: any): APIGatewayProxyResult => { console.error(error); return { statusCode: 500, - body: JSON.stringify({ message: 'Internal Server Error' }) + body: JSON.stringify({ message: "Internal Server Error" }), }; }; diff --git a/test_actions.sh b/test_actions.sh new file mode 100644 index 0000000..eda18ea --- /dev/null +++ b/test_actions.sh @@ -0,0 +1,53 @@ +# Get all actions +curl http://localhost:3001/actions #OK +echo -e "\n" +# Get global snapshot actions for a specific term +curl http://localhost:3001/global-snapshots/618a6f6bedba2c8a84ca3513f1cf0f6fe9eafb6f7872eecf2e222216351708b6/actions +#OK +echo -e "\n" +# Get address actions for a specific address +curl http://localhost:3001/addresses/DAG04Qob76gZG7D5qZu9oeZvr8WFxs53Md383q4x/actions #OK +echo -e "\n" +# Get currency actions for a specific metagraph ID +curl http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/actions #OK +echo -e "\n" +# Get currency snapshot actions for a specific metagraph ID and term +curl http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/snapshots/74dff07f4ff96e2dc7cea2420389515e6ccbb49f2dd0115ecfa38e4e55cd666d/actions +#OK +echo -e "\n" +# Get currency address actions for a specific metagraph ID and address +curl http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/addresses/DAG5T61H6RNLR4wLueNNZo8JR7fDKuGQSZoGTVki/actions +#OK + + +# # Get all actions +# curl https://8kiu9tfle8.execute-api.us-west-1.amazonaws.com/actions #OK +# echo -e "\n" +# # Get global snapshot actions for a specific term +# curl https://8kiu9tfle8.execute-api.us-west-1.amazonaws.com/global-snapshots/0f6a37bc3b1bcd2a751af4bf384db052cef46048c4322baa303f22eda053fbc4/actions +# #OK +# echo -e "\n" +# # Get address actions for a specific address +# curl https://8kiu9tfle8.execute-api.us-west-1.amazonaws.com/addresses/DAG04Qob76gZG7D5qZu9oeZvr8WFxs53Md383q4x/actions #OK +# echo -e "\n" +# # Get currency actions for a specific metagraph ID +# curl https://8kiu9tfle8.execute-api.us-west-1.amazonaws.com/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/actions #OK +# echo -e "\n" +# # Get currency snapshot actions for a specific metagraph ID and term +# curl https://8kiu9tfle8.execute-api.us-west-1.amazonaws.com/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/snapshots/74dff07f4ff96e2dc7cea2420389515e6ccbb49f2dd0115ecfa38e4e55cd666d/actions +# #OK +# echo -e "\n" +# # Get currency address actions for a specific metagraph ID and address +# curl https://8kiu9tfle8.execute-api.us-west-1.amazonaws.com/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/addresses/DAG5T61H6RNLR4wLueNNZo8JR7fDKuGQSZoGTVki/actions +# #OK +# echo -e "\n" + + + +# curl https://8kiu9tfle8.execute-api.us-west-1.amazonaws.com/actions | jq +# curl https://8kiu9tfle8.execute-api.us-west-1.amazonaws.com/global-snapshots/3cdde2bc02d9ad3da07fe52686a51db448129d9adf3167747be77e31f951c428/actions | jq +# curl https://8kiu9tfle8.execute-api.us-west-1.amazonaws.com/global-snapshots/618a6f6bedba2c8a84ca3513f1cf0f6fe9eafb6f7872eecf2e222216351708b6/actions | jq +# curl https://8kiu9tfle8.execute-api.us-west-1.amazonaws.com/addresses/DAG04Qob76gZG7D5qZu9oeZvr8WFxs53Md383q4x/actions | jq +# curl https://8kiu9tfle8.execute-api.us-west-1.amazonaws.com/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/actions | jq +# curl https://8kiu9tfle8.execute-api.us-west-1.amazonaws.com/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/snapshots/74dff07f4ff96e2dc7cea2420389515e6ccbb49f2dd0115ecfa38e4e55cd666d/actions | jq +# curl https://8kiu9tfle8.execute-api.us-west-1.amazonaws.com/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/addresses/DAG5T61H6RNLR4wLueNNZo8JR7fDKuGQSZoGTVki/actions | jq diff --git a/test_allowspends.sh b/test_allowspends.sh new file mode 100644 index 0000000..5bb7cf9 --- /dev/null +++ b/test_allowspends.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +BASE_URL="http://localhost:3001" + +echo "General Allow-Spend Endpoints" +curl -X GET "http://localhost:3001/allow-spends" +curl -X GET "http://localhost:3001/spend-transactions" +curl -X GET "http://localhost:3001/allow-spend-expirations" + +echo "Global Snapshot Endpoints" +curl -X GET "http://localhost:3001/global-snapshots/618a6f6bedba2c8a84ca3513f1cf0f6fe9eafb6f7872eecf2e222216351708b6/allow-spends" +curl -X GET "http://localhost:3001/global-snapshots/618a6f6bedba2c8a84ca3513f1cf0f6fe9eafb6f7872eecf2e222216351708b6/spend-transactions" +curl -X GET "http://localhost:3001/global-snapshots/618a6f6bedba2c8a84ca3513f1cf0f6fe9eafb6f7872eecf2e222216351708b6/allow-spend-expirations" + +echo "Address-Specific Endpoints" +curl -X GET "http://localhost:3001/addresses/DAG04Qob76gZG7D5qZu9oeZvr8WFxs53Md383q4x/allow-spends" +curl -X GET "http://localhost:3001/addresses/DAG04Qob76gZG7D5qZu9oeZvr8WFxs53Md383q4x/spend-transactions" +curl -X GET "http://localhost:3001/addresses/DAG04Qob76gZG7D5qZu9oeZvr8WFxs53Md383q4x/allow-spend-expirations" + +echo "Currency (Metagraph) Endpoints" +curl -X GET "http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/allow-spends" +curl -X GET "http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/spend-transactions" +curl -X GET "http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/allow-spend-expirations" + +echo "Currency Snapshot Endpoints" +curl -X GET "http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/snapshots/74dff07f4ff96e2dc7cea2420389515e6ccbb49f2dd0115ecfa38e4e55cd666d/allow-spends" +curl -X GET "http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/snapshots/74dff07f4ff96e2dc7cea2420389515e6ccbb49f2dd0115ecfa38e4e55cd666d/spend-transactions" +curl -X GET "http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/snapshots/74dff07f4ff96e2dc7cea2420389515e6ccbb49f2dd0115ecfa38e4e55cd666d/allow-spend-expirations" + +echo "Currency Address-Specific Endpoints" +curl -X GET "http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/addresses/DAG04Qob76gZG7D5qZu9oeZvr8WFxs53Md383q4x/allow-spends" +curl -X GET "http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/addresses/DAG04Qob76gZG7D5qZu9oeZvr8WFxs53Md383q4x/spend-transactions" +curl -X GET "http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/addresses/DAG04Qob76gZG7D5qZu9oeZvr8WFxs53Md383q4x/allow-spend-expirations" diff --git a/test_endpoints.sh b/test_endpoints.sh index 4d6ad93..57ceb38 100644 --- a/test_endpoints.sh +++ b/test_endpoints.sh @@ -14,23 +14,21 @@ curl http://localhost:3001/global-snapshots/390792/transactions #ok curl http://localhost:3001/global-snapshots/latest/transactions # ok # Blocks -curl http://localhost:3001/blocks/9641acffb4a5276d013e49676fcea51e231c5c9737b5866f8df07de04145e374 #ok +curl http://localhost:3001/blocks/848ac19da2eb07f9dab32e7948c00b618a518157c8e6e1f12ade249a9928e026 #ok # Transactions curl http://localhost:3001/transactions #ok -curl http://localhost:3001/transactions/d8b1f2dec3030dd4e9b17e63871cc4bdb2ecb1f4d8bda079c75fdfac3a685c79 #ok -curl http://localhost:3001/addresses/DAG1x6erYwDLvt3zXxZvdHaZcNVhWfFFVC1Qunjd/transactions #ok -curl http://localhost:3001/addresses/DAG1x6erYwDLvt3zXxZvdHaZcNVhWfFFVC1Qunjd/transactions/sent #ok -curl http://localhost:3001/addresses/DAG0WtbjHQu5LsZ57DcxX7kuA5DyyvG5QYG2uu9X/transactions/received #ok +curl http://localhost:3001/transactions/09d1c22b0cf46c50ab8dd171c0a9ba4452f9d4355045f332eb424cd3ca5c821a #ok +curl http://localhost:3001/addresses/DAG56BtU1j5uCMb5f1QxZ5oxfBhpUeYucRGygfEa/transactions #ok +curl http://localhost:3001/addresses/DAG56BtU1j5uCMb5f1QxZ5oxfBhpUeYucRGygfEa/transactions/sent #ok +curl http://localhost:3001/addresses/DAG45ZLcgmQeRHY3oV2ZJACrFUjEZwqeXKSfZc75/transactions/received #ok # Address Balance -curl http://localhost:3001/addresses/DAG4J3i4K87evc71ti3aEcSKx8AUR5GhPy3EysiM/balance #ok +curl http://localhost:3001/addresses/DAG8rB813m3yq8CQX92rgtc7ThBNHFngctr3uonG/balance #ok # Currency Snapshots curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/snapshots #ok -# curl http://localhost:3001/addresses/{address}/snapshots - curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/snapshots/1c0b793cd83af5d761d71b887f068b078daf2bdab2f7f9e5fedf09c8f63dd6b8 #ok curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/snapshots/22 #ok @@ -56,11 +54,12 @@ curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/add curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/addresses/DAG2fMnbEmsWhgYGhvdREVELyESKUqGNTEWf4B61/balance #ok # Currency Fee Transactions -# curl http://localhost:3001/currency/{identifier}/fee-transactions/{hash} -# curl http://localhost:3001/currency/{identifier}/snapshots/{term}/fee-transactions -# curl http://localhost:3001/currency/{identifier}/addresses/{address}/fee-transactions -# curl http://localhost:3001/currency/{identifier}/addresses/{address}/fee-transactions/sent -# curl http://localhost:3001/currency/{identifier}/addresses/{address}/fee-transactions/received +curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/fee-transactions/MTGRPHFEEtXId00001 +curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/snapshots/74dff07f4ff96e2dc7cea2420389515e6ccbb49f2dd0115ecfa38e4e55cd666d/fee-transactions +curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/addresses/DAG5T61H6RNLR4wLueNNZo8JR7fDKuGQSZoGTVki/fee-transactions +curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/addresses/DAG6foKfPYqKMEyLccPi9jJEZNw2RsUFWEHB4NrA/fee-transactions +curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/addresses/DAG5T61H6RNLR4wLueNNZo8JR7fDKuGQSZoGTVki/fee-transactions/sent +curl http://localhost:3001/currency/DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C/addresses/DAG6foKfPYqKMEyLccPi9jJEZNw2RsUFWEHB4NrA/fee-transactions/received # Metagraphs curl http://localhost:3001/currency #ok diff --git a/test_tokenlocks.sh b/test_tokenlocks.sh new file mode 100644 index 0000000..3fd04fe --- /dev/null +++ b/test_tokenlocks.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +BASE_URL="http://localhost:3001" # Change if needed + +echo "Testing tokenLocks endpoint..." +curl http://localhost:3001/token-locks -H "Content-Type: application/json" +echo -e "\n" + +echo "Testing globalSnapshotTokenLocks endpoint..." +curl http://localhost:3001/global-snapshots/3cdde2bc02d9ad3da07fe52686a51db448129d9adf3167747be77e31f951c428/token-locks -H "Content-Type: application/json" +echo -e "\n" + +echo "Testing addressTokenLocks endpoint..." +curl http://localhost:3001/addresses/DAG5T61H6RNLR4wLueNNZo8JR7fDKuGQSZoGTVki/token-locks -H "Content-Type: application/json" +echo -e "\n" + +echo "Testing tokenUnlocks endpoint..." +curl http://localhost:3001/token-unlocks -H"Content-Type: application/json" +echo -e "\n" + +echo "Testing globalSnapshotTokenUnlocks endpoint..." +curl http://localhost:3001/global-snapshots/3cdde2bc02d9ad3da07fe52686a51db448129d9adf3167747be77e31f951c428/token-unlocks -H"Content-Type: application/json" +echo -e "\n" + +echo "Testing addressTokenUnlocks endpoint..." +curl http://localhost:3001/addresses/DAG4AqNNL2E7TWBUvQRxSPhraS2Lnr5xPM6xtVbs/token-unlocks -H"Content-Type: application/json" +echo -e "\n" + +echo "Testing currencyTokenLocks endpoint..." +curl http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/token-locks -H"Content-Type: application/json" +echo -e "\n" + +echo "Testing currencySnapshotTokenLocks endpoint..." +curl http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/snapshots/74dff07f4ff96e2dc7cea2420389515e6ccbb49f2dd0115ecfa38e4e55cd666d/token-locks -H"Content-Type: application/json" +echo -e "\n" + +echo "Testing currencyAddressTokenLocks endpoint..." +curl http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/addresses/DAG5T61H6RNLR4wLueNNZo8JR7fDKuGQSZoGTVki/token-locks -H"Content-Type: application/json" +echo -e "\n" + +echo "Testing currencySnapshotTokenUnlocks endpoint..." +curl http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/snapshots/74dff07f4ff96e2dc7cea2420389515e6ccbb49f2dd0115ecfa38e4e55cd666d/token-unlocks -H"Content-Type: application/json" +echo -e "\n" + +echo "Testing currencyTokenUnlocks endpoint..." +curl http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/token-unlocks -H"Content-Type: application/json" +echo -e "\n" + +echo "Testing currencyAddressTokenUnlocks endpoint..." +curl http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/addresses/DAG4AqNNL2E7TWBUvQRxSPhraS2Lnr5xPM6xtVbs/token-unlocks -H"Content-Type: application/json" +echo -e "\n" + +echo "All tests completed." diff --git a/tests/validation.test.ts b/tests/validation.test.ts index 0d09878..4d0c363 100644 --- a/tests/validation.test.ts +++ b/tests/validation.test.ts @@ -1,14 +1,13 @@ -import { extractPagination } from '../src/request-params'; -import { APIGatewayEvent } from 'aws-lambda'; -import { Lens } from 'monocle-ts'; -import { isLeft, isRight, right } from 'fp-ts/lib/Either'; -import { pipe } from 'fp-ts/lib/pipeable'; +import { extractPagination } from "../src/request-params"; +import { APIGatewayEvent } from "aws-lambda"; +import { Lens } from "monocle-ts"; +import { pipe } from "fp-ts/function"; const baseEvent: APIGatewayEvent = { - httpMethod: 'get', + httpMethod: "get", isBase64Encoded: false, - path: '', - resource: '', + path: "", + resource: "", body: null, headers: {}, multiValueHeaders: {}, @@ -16,15 +15,15 @@ const baseEvent: APIGatewayEvent = { queryStringParameters: null, multiValueQueryStringParameters: null, stageVariables: null, - requestContext: {} as any + requestContext: {} as any, }; -const pathParams = Lens.fromProp()('pathParameters'); -const queryParams = Lens.fromProp()('queryStringParameters'); +const pathParams = Lens.fromProp()("pathParameters"); +const queryParams = Lens.fromProp()("queryStringParameters"); const setParam = (param: string, value: string) => pathParams.modify((a) => ({ ...a, [param]: value })); -const setTerm = (term: string) => setParam('term', term); +const setTerm = (term: string) => setParam("term", term); const setSearchAfter = (search_after: string) => queryParams.modify((a) => ({ ...a, search_after })); const setSearchBefore = (search_before: string) => @@ -32,62 +31,61 @@ const setSearchBefore = (search_before: string) => const setLimit = (limit: string) => queryParams.modify((a) => ({ ...a, limit })); -describe('extractPagination', () => { - it('should not pass when both search_after and search_before', async () => { +describe("extractPagination", () => { + it("should not pass when both search_after and search_before", async () => { const event = pipe( baseEvent, - setLimit('2'), - setSearchAfter('aa'), - setSearchBefore('bb') + setLimit("2"), + setSearchAfter("aa"), + setSearchBefore("bb") ); - const result = await extractPagination(event)(); + const result = () => extractPagination(event); - expect(isLeft(result)).toBe(true); + expect(result).toThrow(Error); + expect(result).toThrow("search_after & search_before should be mutually exclusive"); }); - it('should pass when searchAfter is provided but limit not', async () => { + it("should pass when searchAfter is provided but limit not", async () => { const event = pipe( baseEvent, - setParam('address', '123'), - setSearchAfter('aa') + setParam("address", "123"), + setSearchAfter("aa") ); - const result = await extractPagination(event)(); - expect(isRight(result)).toBe(true); + const result = await extractPagination(event); + expect(result).toEqual({searchDirection: "search_after", searchSince: "aa", size: NaN}); }); - it('should pass when limit is provided but searchAfter not', async () => { - const event = pipe(baseEvent, setParam('address', '123'), setLimit('12')); + it("should pass when limit is provided but searchAfter not", async () => { + const event = pipe(baseEvent, setParam("address", "123"), setLimit("12")); - const result = await extractPagination(event)(); - right(event); - expect(isRight(result)).toBe(true); + const result = await extractPagination(event); + expect(result).toEqual({"searchDirection": undefined, "searchSince": undefined, "size": 12}); }); - it('should pass returning event when both searchAfter and limit are provided', async () => { + it("should pass returning event when both searchAfter and limit are provided", async () => { const event = pipe( baseEvent, - setParam('address', '123'), - setSearchAfter('aa'), - setLimit('2') + setParam("address", "123"), + setSearchAfter("aa"), + setLimit("2") ); - const result = await extractPagination(event)(); - right(event); - expect(isRight(result)).toBe(true); + const result = await extractPagination(event); + expect(result).toEqual({"searchDirection": "search_after", "searchSince": "aa", "size": 2}); }); - it('should pass returning event when both searchBefore and limit are provided', async () => { + it("should pass returning event when both searchBefore and limit are provided", async () => { const event = pipe( baseEvent, - setParam('address', '123'), - setSearchBefore('aa'), - setLimit('2') + setParam("address", "123"), + setSearchBefore("aa"), + setLimit("2") ); - const result = await extractPagination(event)(); + const result = await extractPagination(event); - expect(isRight(result)).toBe(true); + expect(result).toEqual({"searchDirection": "search_before", "searchSince": "aa", "size": 2}); }); }); diff --git a/tsconfig.json b/tsconfig.json index ba0b20f..ed1a4ab 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,17 +7,10 @@ "target": "es5", "outDir": ".build", "moduleResolution": "node", - "lib": [ - "es2015" - ], - "typeRoots": [ - "node_modules/@types" - ], - "types": [ - "node", - "jest" - ], - "rootDir": "./src", + "lib": ["es2015"], + "typeRoots": ["node_modules/@types"], + "types": ["node", "jest"], + "rootDir": "./src" }, "include": ["src/**/*"] } From b7ab6ad0c10ea01231ed013225978ef4f44907b4 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Tue, 1 Apr 2025 13:37:42 -0300 Subject: [PATCH 06/63] update db structure --- .github/workflows/deploy-postgres.yml | 2 +- prisma/schema.prisma | 291 +++++++++++++------------- src/handlers/actionsHandler.ts | 12 +- src/handlers/allowSpendsHandler.ts | 17 +- src/handlers/dagHandler.ts | 34 ++- src/handlers/metagraphHandler.ts | 20 +- src/handlers/tokenLocksHandler.ts | 71 ++++--- src/response.ts | 16 +- test_actions.sh | 4 +- test_allowspends.sh | 10 +- test_endpoints.sh | 8 +- test_tokenlocks.sh | 8 +- 12 files changed, 263 insertions(+), 230 deletions(-) diff --git a/.github/workflows/deploy-postgres.yml b/.github/workflows/deploy-postgres.yml index 179fcb2..90f1e82 100644 --- a/.github/workflows/deploy-postgres.yml +++ b/.github/workflows/deploy-postgres.yml @@ -11,7 +11,7 @@ name: Deploy postgress app jobs: deploy: name: Deploy ${{ github.ref_name }} to ${{ inputs.environment }} - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 environment: ${{ inputs.environment }} steps: - name: Checkout code diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e7ba046..d0e472f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -22,11 +22,11 @@ model abstract_blocks { /// This table has subclasses and requires additional setup for migrations. Visit https://pris.ly/d/table-inheritance for more info. model abstract_transactions { - hash String @id(map: "hash_pkey") @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) + hash String @id(map: "hash_pkey") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) } model abstract_transactions_view { @@ -89,7 +89,7 @@ model block_parents { hash String @db.VarChar parent_proof_hash String @db.VarChar parent_height BigInt - abstract_blocks abstract_blocks @relation(fields: [hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "block_parents_block_fk") + abstract_blocks abstract_blocks @relation(fields: [hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "block_parents_block_fk") @@id([hash, parent_proof_hash]) @@index([hash]) @@ -105,12 +105,11 @@ model dag_allow_spend_approvers { } model dag_allow_spend_blocks { - round_id String @unique(map: "dag_allow_spend_blocks_unique") @db.Uuid - global_snapshot_hash String @id @db.VarChar - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - global_snapshot global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spend_blocks_global_snapshot_fk") - dag_allow_spends dag_allow_spends[] + round_id String @unique(map: "dag_allow_spend_blocks_unique") @db.Uuid + global_snapshot_hash String @id @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + global_snapshot global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spend_blocks_global_snapshot_fk") } model dag_allow_spends { @@ -126,14 +125,14 @@ model dag_allow_spends { last_valid_epoch_progress BigInt round_id String @db.Uuid ordinal BigInt @unique(map: "dag_allow_spends_ordinal") - snapshot_hash String? @db.VarChar + snapshot_hash String @db.VarChar dag_allow_spend_approvers dag_allow_spend_approvers[] - dag_allow_spend_block dag_allow_spend_blocks @relation(fields: [round_id], references: [round_id], onDelete: Cascade, onUpdate: NoAction, map: "allow_spends_block_fk") addresses_dag_allow_spends_destination_addrToaddresses addresses @relation("dag_allow_spends_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spends_destination_addr_fk") addresses_dag_allow_spends_source_addrToaddresses addresses @relation("dag_allow_spends_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spends_source_addr_fk") dag_expired_spend_transactions dag_expired_spend_transactions[] dag_spend_transactions dag_spend_transactions[] abstract_transactions_view abstract_transactions_view[] + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spend_blocks_global_snapshot_fk") @@index([round_id]) } @@ -146,7 +145,7 @@ model dag_balance_changes { updated_at DateTime @default(now()) @db.Timestamp(6) snapshot_ordinal BigInt addresses addresses @relation(fields: [address], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_balance_change_address_fk") - global_snapshots global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_balance_change_global_snapshot_fk") + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_balance_change_global_snapshot_fk") @@id([snapshot_ordinal, address], map: "dag_balance_change_pk") @@index([address, created_at], map: "dag_balance_changes_address_idx") @@ -158,7 +157,7 @@ model dag_blocks { created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) snapshot_hash String @db.VarChar - global_snapshots global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_block_global_snapshot_fk") + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_block_global_snapshot_fk") dag_transactions dag_transactions[] super abstract_blocks? @@ -171,7 +170,7 @@ model dag_reward_transactions { amount BigInt created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - global_snapshots global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_reward_transaction_global_snapshot_fk") + global_snapshot global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_reward_transaction_global_snapshot_fk") addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_reward_transactions_destination_addr_fk") @@id([global_snapshot_hash, destination_addr], map: "dag_reward_transaction_pk") @@ -179,17 +178,17 @@ model dag_reward_transactions { } model dag_spend_transactions { - hash String @id(map: "dag_spend_transactions_pk") @db.VarChar - source_addr String @db.VarChar + hash String @id(map: "dag_spend_transactions_pk") @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - destination_addr String? @db.VarChar - allow_spend_ref String? @db.VarChar - snapshot_hash String? @db.VarChar - dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_dag_allow_spends_fk") - addresses addresses? @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_destination_addr_fk") - abstract_transactions_view abstract_transactions_view[] + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + destination_addr String? @db.VarChar + allow_spend_ref String? @db.VarChar + snapshot_hash String? @db.VarChar + dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_dag_allow_spends_fk") + addresses addresses? @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_destination_addr_fk") + abstract_transactions_view abstract_transactions_view? } model dag_token_lock_blocks { @@ -202,40 +201,38 @@ model dag_token_lock_blocks { } model dag_token_locks { - hash String @id(map: "dag_token_locks_pk") @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - ordinal BigInt - unlock_epoch BigInt - round_id String @db.Uuid - global_snapshot_hash String @db.VarChar - snapshot_hash String? @db.VarChar - dag_token_lock_block dag_token_lock_blocks @relation(fields: [round_id], references: [round_id], onDelete: Cascade, onUpdate: NoAction, map: "token_lock_block_fk") - addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_locks_source_addr_fk") - dag_token_unlocks dag_token_unlocks? + hash String @id(map: "dag_token_locks_pk") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + ordinal BigInt + unlock_epoch BigInt? + round_id String @db.Uuid + global_snapshot_hash String @db.VarChar + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_locks_source_addr_fk") + dag_token_unlocks dag_token_unlocks? + abstract_transactions_view abstract_transactions_view? + dag_token_lock_blocks dag_token_lock_blocks? @relation(fields: [round_id], references: [round_id]) + global_snapshot global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_lock_global_snapshot_fk") - abstract_transactions_view abstract_transactions_view[] + @@unique([ordinal], map: "dag_token_locks_unique") } model dag_token_unlocks { - hash String @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - lock_reference_ordinal BigInt - lock_reference_hash String @db.VarChar - snapshot_hash String? @db.VarChar - dag_token_lock dag_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_unlocks_token_locks_fk") - - addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "ddag_token_unlocks_address_fk") - abstract_transactions_view abstract_transactions_view[] - - @@id([hash], map: "dag_token_unlocks_pk") + hash String @id(map: "dag_token_unlocks_pk") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + lock_reference_hash String @db.VarChar + dag_token_lock dag_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "dag_token_unlocks_token_locks_fk") + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "ddag_token_unlocks_address_fk") + abstract_transactions_view abstract_transactions_view? + snapshot_hash String @db.VarChar + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_unlock_global_snapshot_fk") + @@unique([lock_reference_hash]) - @@unique([lock_reference_ordinal]) } model dag_transactions { @@ -251,19 +248,18 @@ model dag_transactions { parent_hash String? @db.VarChar ordinal BigInt block_hash String @db.VarChar - snapshot_hash String? @db.VarChar dag_blocks dag_blocks @relation(fields: [block_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_transaction_dag_block_fk") addresses_dag_transactions_destination_addrToaddresses addresses @relation("dag_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_transactions_destination_addr_fk") addresses_dag_transactions_source_addrToaddresses addresses @relation("dag_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_transactions_source_addr_fk") } model global_snapshot_proofs { - id String @db.VarChar - signature String @db.VarChar - snapshot_hash String @db.VarChar - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - global_snapshots global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "proof_global_snapshot_fk") + id String @db.VarChar + signature String @db.VarChar + snapshot_hash String @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "proof_global_snapshot_fk") @@id([snapshot_hash, id], map: "proof_pk") } @@ -286,6 +282,9 @@ model global_snapshots { dag_token_lock_blocks dag_token_lock_blocks? global_snapshot_proofs global_snapshot_proofs[] metagraph_snapshots metagraph_snapshots[] + dag_allow_spends dag_allow_spends[] + dag_token_locks dag_token_locks[] + dag_token_unlocks dag_token_unlocks[] } model metagraph_allow_spend_approvers { @@ -321,7 +320,7 @@ model metagraph_allow_spends { last_valid_epoch_progress BigInt round_id String @db.Uuid ordinal BigInt @unique(map: "metagraph_allow_spends_ordinal") - snapshot_hash String? @db.VarChar + snapshot_hash String @db.VarChar metagraph_allow_spend_approvers metagraph_allow_spend_approvers[] metagraph_allow_spend_block metagraph_allow_spend_blocks @relation(fields: [round_id], references: [round_id], onDelete: Cascade, onUpdate: NoAction, map: "allow_spends_block_fk") addresses_metagraph_allow_spends_destination_addrToaddresses addresses @relation("metagraph_allow_spends_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_allow_spends_destination_addr_fk") @@ -329,7 +328,8 @@ model metagraph_allow_spends { metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") metagraph_expired_spend_transactions metagraph_expired_spend_transactions[] metagraph_spend_transactions metagraph_spend_transactions[] - abstract_transactions_view abstract_transactions_view[] + abstract_transactions_view abstract_transactions_view? + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_allow_spend_blocks_metagraph_snapshot_fk") } model metagraph_balance_changes { @@ -339,12 +339,12 @@ model metagraph_balance_changes { balance BigInt created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_snapshot_ordinal BigInt + snapshot_ordinal BigInt @map("metagraph_snapshot_ordinal") addresses addresses @relation(fields: [address], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "address_fk") metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_balance_change_metagraph_snapshot_fk") metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") - @@id([metagraph_id, address, metagraph_snapshot_ordinal], map: "metagraph_balance_change_pk") + @@id([metagraph_id, address, snapshot_ordinal], map: "metagraph_balance_change_pk") } model metagraph_blocks { @@ -364,22 +364,21 @@ model metagraph_blocks { } model metagraph_fee_transactions { - hash String @db.VarChar - source_addr String @db.VarChar + hash String @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar - metagraph_snapshot_hash String @db.VarChar - destination_addr String @db.VarChar - data_update_ref String? @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + metagraph_snapshot_hash String @db.VarChar + destination_addr String @db.VarChar + data_update_ref String? @db.VarChar metagraph_snapshot_ordinal BigInt? - snapshot_hash String? @db.VarChar - metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "fee_transaction_metagraph_snapshot_fk") - addresses_metagraph_fee_transactions_destination_addrToaddresses addresses @relation("metagraph_fee_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_fee_transactions_destination_addr_fk") - addresses_metagraph_fee_transactions_source_addrToaddresses addresses @relation("metagraph_fee_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_fee_transactions_source_addr_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") - abstract_transactions_view abstract_transactions_view[] + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "fee_transaction_metagraph_snapshot_fk") + addresses_metagraph_fee_transactions_destination_addrToaddresses addresses @relation("metagraph_fee_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_fee_transactions_destination_addr_fk") + addresses_metagraph_fee_transactions_source_addrToaddresses addresses @relation("metagraph_fee_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_fee_transactions_source_addr_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") + abstract_transactions_view abstract_transactions_view? @@id([hash], map: "fee_transaction_pk") } @@ -419,80 +418,83 @@ model metagraph_snapshots { metagraph_fee_transactions metagraph_fee_transactions[] metagraph_reward_transactions metagraph_reward_transactions[] metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") - global_snapshots global_snapshots? @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_snapshots_global_snapshots_fk") + global_snapshot global_snapshots? @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_snapshots_global_snapshots_fk") addresses_metagraph_snapshots_owner_addressToaddresses addresses? @relation("metagraph_snapshots_owner_addressToaddresses", fields: [owner_address], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "owner_address_fk") addresses_metagraph_snapshots_staking_addressToaddresses addresses? @relation("metagraph_snapshots_staking_addressToaddresses", fields: [staking_address], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "staking_address_fk") metagraph_token_lock_blocks metagraph_token_lock_blocks? + metagraph_token_locks metagraph_token_locks[] + metagraph_token_unlocks metagraph_token_unlocks[] + metagraph_allow_spends metagraph_allow_spends[] @@id([metagraph_id, hash], map: "metagraph_snapshot_pk") @@unique([metagraph_id, ordinal], map: "metagraph_snapshot_unique") } model metagraph_spend_transactions { - hash String @id(map: "metagraph_spend_transactions_pk") @db.VarChar - source_addr String @db.VarChar + hash String @id(map: "metagraph_spend_transactions_pk") @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar - destination_addr String @db.VarChar - allow_spend_ref String? @db.VarChar - snapshot_hash String? @db.VarChar - metagraph_allow_spend metagraph_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "mg_spend_transactions_mg_allow_spends_fk") - addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_destination_addr_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_metagraph_id_fk") - abstract_transactions_view abstract_transactions_view[] + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + destination_addr String @db.VarChar + allow_spend_ref String? @db.VarChar + snapshot_hash String? @db.VarChar + metagraph_allow_spend metagraph_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "mg_spend_transactions_mg_allow_spends_fk") + addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_destination_addr_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_metagraph_id_fk") + abstract_transactions_view abstract_transactions_view? } model metagraph_token_lock_blocks { - metagraph_id String @db.VarChar - metagraph_snapshot_hash String @db.VarChar - round_id String @db.Uuid - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_lock_blocks_metagraph_id_fk") - metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_lock_blocks_metagraph_snapshot_fk") - metagraph_token_locks metagraph_token_locks[] + metagraph_id String @db.VarChar + metagraph_snapshot_hash String @db.VarChar + round_id String @db.Uuid + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_lock_blocks_metagraph_id_fk") + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_lock_blocks_metagraph_snapshot_fk") @@id([metagraph_id, metagraph_snapshot_hash]) @@unique([metagraph_id, round_id], map: "metagraph_token_lock_blocks_unique") } model metagraph_token_locks { - hash String @db.VarChar - source_addr String @db.VarChar + hash String @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar ordinal BigInt - unlock_epoch BigInt - round_id String @db.Uuid - snapshot_hash String? @db.VarChar - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") - metagraph_token_lock_block metagraph_token_lock_blocks @relation(fields: [metagraph_id, round_id], references: [metagraph_id, round_id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_lock_token_lock_blocks_fk") - addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_locks_source_addr_fk") - abstract_transactions_view abstract_transactions_view[] + unlock_epoch BigInt? + round_id String @db.Uuid + snapshot_hash String @db.VarChar + metagraph metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_locks_source_addr_fk") + abstract_transactions_view abstract_transactions_view? metagraph_token_unlocks metagraph_token_unlocks? + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_lock_blocks_metagraph_snapshot_fk") @@id([hash], map: "metagraph_token_locks_pk") @@unique([metagraph_id, ordinal], map: "metagraph_token_locks_unique") } model metagraph_token_unlocks { - hash String @db.VarChar - source_addr String @db.VarChar + hash String @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar lock_reference_ordinal BigInt - lock_reference_hash String @db.VarChar - snapshot_hash String? @db.VarChar - addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "address_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") - token_lock metagraph_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_token_unlocks_token_locks_fk") - abstract_transactions_view abstract_transactions_view[] + lock_reference_hash String @db.VarChar + snapshot_hash String @db.VarChar + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "address_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") + token_lock metagraph_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_token_unlocks_token_locks_fk") + abstract_transactions_view abstract_transactions_view? + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_unlock_blocks_metagraph_snapshot_fk") @@id([hash], map: "metagraph_token_unlocks_pk") @@unique([lock_reference_hash]) @@ -513,7 +515,6 @@ model metagraph_transactions { parent_hash String @db.VarChar ordinal BigInt block_hash String @db.VarChar - snapshot_hash String? @db.VarChar metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") metagraph_blocks metagraph_blocks @relation(fields: [metagraph_id, block_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transaction_metagraph_block_fk") addresses_metagraph_transactions_destination_addrToaddresses addresses @relation("metagraph_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transactions_destination_addrfk") @@ -540,26 +541,26 @@ model metagraphs { } model dag_expired_spend_transactions { - hash String @id(map: "dag_expired_spend_transactions_pk") @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - allow_spend_ref String? @db.VarChar - snapshot_hash String? @db.VarChar - abstract_transactions_view abstract_transactions_view[] - dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_expired_spend_transactions_dag_allow_spends_fk") + hash String @id(map: "dag_expired_spend_transactions_pk") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + allow_spend_ref String? @db.VarChar + snapshot_hash String? @db.VarChar + abstract_transactions_view abstract_transactions_view? + dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_expired_spend_transactions_dag_allow_spends_fk") } model metagraph_expired_spend_transactions { - hash String @id(map: "metagraph_expired_spend_transactions_pk") @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar - allow_spend_ref String? @db.VarChar - snapshot_hash String? @db.VarChar - abstract_transactions_view abstract_transactions_view[] - metagraph_allow_spend metagraph_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_expired_spend_transactions_metagraph_allow_spends_fk") + hash String @id(map: "metagraph_expired_spend_transactions_pk") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + allow_spend_ref String? @db.VarChar + snapshot_hash String? @db.VarChar + abstract_transactions_view abstract_transactions_view? + metagraph_allow_spend metagraph_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_expired_spend_transactions_metagraph_allow_spends_fk") } diff --git a/src/handlers/actionsHandler.ts b/src/handlers/actionsHandler.ts index ba63de9..36ecc07 100644 --- a/src/handlers/actionsHandler.ts +++ b/src/handlers/actionsHandler.ts @@ -103,25 +103,19 @@ export const dagActions = async ( const tokenLockGlobalSnapshotCond = (filter) => ({ dag_token_lock: { - dag_token_lock_block: { global_snapshot: filter, - }, }, }); const tokenUnlockGlobalSnapshotCond = (filter) => ({ dag_token_unlock: { dag_token_lock: { - dag_token_lock_block: { global_snapshot: filter, - }, }, }, }); const allowSpendGlobalSnapshotCond = (filter) => ({ dag_allow_spend: { - dag_allow_spend_block: { global_snapshot: filter, - }, }, }); const spendTxGlobalSnapshotCond = (filter) => ({ @@ -220,21 +214,19 @@ const metagraphIdCond = (metagraph_id) => ({ const tokenLockMetagraphSnapshotCond = (filter) => ({ metagraph_token_lock: { - metagraph_token_lock_block: { metagraph_snapshot: filter }, + metagraph_snapshot: filter, }, }); const tokenUnlockMetagraphSnapshotCond = (filter) => ({ metagraph_token_unlock: { token_lock: { - metagraph_token_lock_block: { metagraph_snapshot: filter }, + metagraph_snapshot: filter, }, }, }); const allowSpendMetagraphSnapshotCond = (filter) => ({ metagraph_allow_spend: { - metagraph_allow_spend_block: { metagraph_snapshot: filter, - }, }, }); const spendTxMetagraphSnapshotCond = (filter) => ({ diff --git a/src/handlers/allowSpendsHandler.ts b/src/handlers/allowSpendsHandler.ts index f9dd012..1e2bc88 100644 --- a/src/handlers/allowSpendsHandler.ts +++ b/src/handlers/allowSpendsHandler.ts @@ -18,9 +18,8 @@ const allowSpendResponse = (transaction) => ({ source: transaction.source_addr, destination: transaction.destination_addr, lastValidEpochProgress: transaction.last_valid_epoch_progress, - round: transaction.round_id, fee: transaction.fee, - snapshot: transaction.snapshot_hash, + snapshotHash: transaction.snapshot_hash, timestamp: transaction.created_at, }); @@ -33,7 +32,7 @@ const spendTransactionResponse = (transaction) => ({ source: transaction.source_addr, destination: transaction.destination_addr, allowSpendRef: transaction.allow_spend_ref, - snapshot: transaction.snapshot_hash, + snapshotHash: transaction.snapshot_hash, timestamp: transaction.created_at, }); @@ -45,7 +44,7 @@ const spendExpiredResponse = (transaction) => ({ amount: transaction.amount, source: transaction.source_addr, allowSpendRef: transaction.allow_spend_ref, - snapshot: transaction.snapshot_hash, + snapshotHash: transaction.snapshot_hash, timestamp: transaction.created_at, }); @@ -76,7 +75,7 @@ export const globalSnapshotAllowSpends = async ( toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, { - where: { dag_allow_spend_block: { global_snapshot: filter } }, + where: { global_snapshot: filter }, orderBy: { created_at: "desc" }, }, prisma.dag_allow_spends.findMany, @@ -141,9 +140,7 @@ export const globalSnapshotSpendTransactions = async ( fromCreatedAtOrdinalCursor, { where: { - dag_allow_spend: { - dag_allow_spend_block: { global_snapshot: filter }, - }, + dag_allow_spend: { global_snapshot: filter }, }, orderBy: { created_at: "desc" }, }, @@ -209,9 +206,7 @@ export const globalSnapshotAllowSpendExpirations = async ( fromCreatedAtOrdinalCursor, { where: { - dag_allow_spend: { - dag_allow_spend_block: { global_snapshot: filter }, - }, + dag_allow_spend: { global_snapshot: filter }, }, orderBy: { created_at: "desc" }, }, diff --git a/src/handlers/dagHandler.ts b/src/handlers/dagHandler.ts index 037bbc1..4cd0137 100644 --- a/src/handlers/dagHandler.ts +++ b/src/handlers/dagHandler.ts @@ -31,15 +31,15 @@ const globalSnapshotExists = async (term) => { const latestGlobalSnapshot = async () => { return prisma.global_snapshots.findFirst({ - select: { hash: true }, + select: { hash: true, ordinal: true }, orderBy: { ordinal: "desc" }, }); }; const globalSnapshotWhere = async (term) => { if (term == "latest") { - const latestSnapshotHash = await latestGlobalSnapshot(); - return { hash: latestSnapshotHash }; + const hash = (await latestGlobalSnapshot())?.hash; + return { hash }; } else { return extractHashOrdinal(term); } @@ -114,7 +114,7 @@ export const globalSnapshotRewards = async ( toCursor, fromCursor, { - where: { global_snapshots: { ...globalSnapshotWhere(term) } }, + where: { global_snapshot: { ...globalSnapshotWhere(term) } }, orderBy: [{ destination_addr: "asc" }], }, prisma.dag_reward_transactions.findMany, @@ -137,12 +137,12 @@ export const globalSnapshotTransactions = async ( const query = { where: { - dag_blocks: { global_snapshots: { ...globalSnapshotWhere(term) } }, + dag_blocks: { global_snapshot: { ...globalSnapshotWhere(term) } }, }, include: { dag_blocks: { select: { - global_snapshots: { select: { hash: true, ordinal: true } }, + global_snapshot: { select: { hash: true, ordinal: true } }, }, }, }, @@ -172,7 +172,7 @@ export const dagBlock = async ( where: { hash }, include: { dag_transactions: { select: { hash: true } }, - global_snapshots: true, + global_snapshot: true, super: { include: { block_parents: true } }, }, }); @@ -192,7 +192,7 @@ const dagTtransactionsQuery = async ( include: { dag_blocks: { include: { - global_snapshots: { select: { hash: true, ordinal: true } }, + global_snapshot: { select: { hash: true, ordinal: true } }, }, }, }, @@ -239,7 +239,7 @@ export const dagTransaction = async ( include: { dag_blocks: { include: { - global_snapshots: { select: { hash: true, ordinal: true } }, + global_snapshot: { select: { hash: true, ordinal: true } }, }, }, }, @@ -295,6 +295,16 @@ export const dagTransactionsByDestination = async ( } }; +const lastOrdinal = () => {}; + +const balanceOrZeroFn = async (balance, address) => { + if (balance === null) { + const snapshot_ordinal = (await latestGlobalSnapshot())?.ordinal; + return { balance: 0, address, snapshot_ordinal }; + } + return balance; +}; + export const dagBalanceByAddress = async ( event: APIGatewayProxyEvent ): Promise => { @@ -306,12 +316,14 @@ export const dagBalanceByAddress = async ( ? { snapshot_ordinal: { lte: ordinalNbr } } : {}; - const balances = await prisma.dag_balance_changes.findFirst({ + const balance = await prisma.dag_balance_changes.findFirst({ where: { address, ...ordinalCondition }, orderBy: { snapshot_ordinal: "desc" }, }); - return respond(balances, balanceResponse); + const balanceOrZero = await balanceOrZeroFn(balance, address); + + return respond(balanceOrZero, balanceResponse); } catch (error) { return handleError(error); } diff --git a/src/handlers/metagraphHandler.ts b/src/handlers/metagraphHandler.ts index e6735d0..0b0feab 100644 --- a/src/handlers/metagraphHandler.ts +++ b/src/handlers/metagraphHandler.ts @@ -28,14 +28,14 @@ const prisma = new PrismaClient(); const latestMetagraphSnapshot = async () => { return prisma.metagraph_snapshots.findFirst({ - select: { hash: true }, + select: { hash: true, ordinal: true }, orderBy: { ordinal: "desc" }, }); }; const metagraphSnapshotWhere = async (term) => { if (term == "latest") { - const latestSnapshotHash = await latestMetagraphSnapshot(); + const latestSnapshotHash = (await latestMetagraphSnapshot())?.hash; return { hash: latestSnapshotHash }; } else { return extractHashOrdinal(term); @@ -363,6 +363,14 @@ export const currencyTransactionsByDestination = async ( } }; +const balanceOrZeroFn = async (balance, address) => { + if (balance === null) { + const snapshot_ordinal = (await latestMetagraphSnapshot())?.ordinal; + return { balance: 0, address, snapshot_ordinal }; + } + return balance; +}; + export const currencyBalanceByAddress = async ( event: APIGatewayProxyEvent ): Promise => { @@ -375,7 +383,7 @@ export const currencyBalanceByAddress = async ( const ordinalNbr = toNumber(ordinal); const ordinalCondition = isFinite(ordinalNbr) - ? { metagraph_snapshot_ordinal: { lte: ordinalNbr } } + ? { snapshot_ordinal: { lte: ordinalNbr } } : {}; const balance = await prisma.metagraph_balance_changes.findFirst({ @@ -384,10 +392,12 @@ export const currencyBalanceByAddress = async ( address, ...ordinalCondition, }, - orderBy: { metagraph_snapshot_ordinal: "desc" }, + orderBy: { snapshot_ordinal: "desc" }, }); - return respond(balance, balanceResponse); + const balanceOrZero = await balanceOrZeroFn(balance, address); + + return respond(balanceOrZero, balanceResponse); } catch (error) { return handleError(error); } diff --git a/src/handlers/tokenLocksHandler.ts b/src/handlers/tokenLocksHandler.ts index 75770a5..2c192fc 100644 --- a/src/handlers/tokenLocksHandler.ts +++ b/src/handlers/tokenLocksHandler.ts @@ -17,22 +17,35 @@ const tokenLockResponse = (transaction) => ({ source: transaction.source_addr, destination: transaction.destination_addr, unlockEpoch: transaction.unlock_epoch, - parentHash: transaction.lock_reference_hash, + parentHash: transaction.parent_hash, timestamp: transaction.created_at, }); const tokenLockResponses = (txs) => txs.map(tokenLockResponse); -const tokenUnlockResponse = (transaction) => ({ +const commonTokenUnlockResponse = (transaction) => ({ currencyId: transaction.currencyId, hash: transaction.hash, amount: transaction.amount, source: transaction.source_addr, - timestamp: transaction.created_at, - lockOrdinal: transaction.lock_reference_ordinal, + tokenLockRef: transaction.lock_reference_hash, + imestamp: transaction.created_at, +}); + +const dagTokenUnlockResponse = (transaction) => ({ + ...commonTokenUnlockResponse(transaction), + globalSnapshotOrdinal: transaction.global_snapshot.ordinal, +}); + +const dagTokenUnlockResponses = (txs) => txs.map(dagTokenUnlockResponse); + +const metagraphTokenUnlockResponse = (transaction) => ({ + ...commonTokenUnlockResponse(transaction), + metagraphSnapshotOrdinal: transaction.metagraph_snapshot.ordinal, }); -const tokenUnlockResponses = (txs) => txs.map(tokenUnlockResponse); +const metagraphTokenUnlockResponses = (txs) => + txs.map(metagraphTokenUnlockResponse); export const tokenLocks = async ( event: APIGatewayProxyEvent @@ -60,9 +73,7 @@ export const globalSnapshotTokenLocks = async ( fromCreatedAtOrdinalCursor, { where: { - dag_token_lock_block: { - global_snapshot: filter, - }, + global_snapshot: filter, }, orderBy: { created_at: "desc" }, }, @@ -100,9 +111,12 @@ export const tokenUnlocks = async ( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { orderBy: { created_at: "desc" } }, + { + include: { global_snapshot: { select: { ordinal: true } } }, + orderBy: { created_at: "desc" }, + }, prisma.dag_token_unlocks.findMany, - tokenUnlockResponses + dagTokenUnlockResponses ); }; @@ -119,16 +133,13 @@ export const globalSnapshotTokenUnlocks = async ( fromCreatedAtOrdinalCursor, { where: { - dag_token_lock: { - dag_token_lock_block: { - global_snapshot: filter, - }, - }, + global_snapshot: filter, }, + include: { global_snapshot: { select: { ordinal: true } } }, orderBy: { created_at: "desc" }, }, prisma.dag_token_unlocks.findMany, - tokenUnlockResponses + dagTokenUnlockResponses ); } catch (error) { return handleError(error); @@ -145,9 +156,13 @@ export const addressTokenUnlocks = async ( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { where: { source_addr: address }, orderBy: { created_at: "desc" } }, + { + where: { source_addr: address }, + include: { global_snapshot: { select: { ordinal: true } } }, + orderBy: { created_at: "desc" }, + }, prisma.dag_token_unlocks.findMany, - tokenUnlockResponses + dagTokenUnlockResponses ); } catch (error) { return handleError(error); @@ -184,7 +199,7 @@ export const metagraphSnapshotTokenLocks = async ( { where: { metagraph_id, - metagraph_token_lock_block: { metagraph_snapshot: filter }, + metagraph_snapshot: filter, }, orderBy: { created_at: "desc" }, }, @@ -226,9 +241,13 @@ export const metagraphTokenUnlocks = async ( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { where: { metagraph_id }, orderBy: { created_at: "desc" } }, + { + where: { metagraph_id }, + include: { metagraph_snapshot: { select: { ordinal: true } } }, + orderBy: { created_at: "desc" }, + }, prisma.metagraph_token_unlocks.findMany, - tokenUnlockResponses + metagraphTokenUnlockResponses ); }; @@ -247,14 +266,13 @@ export const metagraphSnapshotTokenUnlocks = async ( { where: { metagraph_id, - token_lock: { - metagraph_token_lock_block: { metagraph_snapshot: filter }, - }, + metagraph_snapshot: filter, }, + include: { metagraph_snapshot: { select: { ordinal: true } } }, orderBy: { created_at: "desc" }, }, prisma.metagraph_token_unlocks.findMany, - tokenUnlockResponses + metagraphTokenUnlockResponses ); } catch (error) { return handleError(error); @@ -273,10 +291,11 @@ export const metagraphAddressTokenUnlocks = async ( fromCreatedAtOrdinalCursor, { where: { metagraph_id, source_addr: address }, + include: { metagraph_snapshot: { select: { ordinal: true } } }, orderBy: { created_at: "desc" }, }, prisma.metagraph_token_unlocks.findMany, - tokenUnlockResponses + metagraphTokenUnlockResponses ); } catch (error) { return handleError(error); diff --git a/src/response.ts b/src/response.ts index 7bf09f7..f9c07f8 100644 --- a/src/response.ts +++ b/src/response.ts @@ -27,6 +27,7 @@ const commonSnapshotResponse = (snapshot, blocksProperty) => ({ subHeight: snapshot.subheight, lastSnapshotHash: snapshot.last_snapshot_hash, blocks: snapshot[blocksProperty].map((b) => b.hash), + epochProgress: snapshot.epoch_progress, timestamp: snapshot.created_at, }); @@ -44,7 +45,7 @@ export const rewardResponse = (reward) => ({ export const dagTransactionsResponse = (ts) => ts.map(dagTransactionResponse); export const dagTransactionResponse = (t) => - transactionResponse(t, t.dag_blocks.global_snapshots); + transactionResponse(t, t.dag_blocks.global_snapshot); const transactionResponse = (transaction, snapshot) => ({ hash: transaction.hash, @@ -73,8 +74,8 @@ const blockResponse = (block) => ({ export const dagBlockResponse = (block) => ({ ...blockResponse(block), transactions: block.dag_transactions.map((tx) => tx.hash), - snapshotHash: block.global_snapshots.hash, - snapshotOrdinal: block.global_snapshots.ordinal, + snapshotHash: block.global_snapshot.hash, + snapshotOrdinal: block.global_snapshot.ordinal, }); export const blockParentResponse = (block_parent) => ({ @@ -141,20 +142,23 @@ export const successResponse = (data: any): APIGatewayProxyResult => ({ export const notFoundResponse = (): APIGatewayProxyResult => ({ statusCode: 404, - body: JSON.stringify({ message: "Not found" }), + body: JSON.stringify({ message: "Not found", errors: [""] }), }); export const missingParameterResponse = ( param: string ): APIGatewayProxyResult => ({ statusCode: 400, - body: JSON.stringify({ message: `Missing parameter: ${param}` }), + body: JSON.stringify({ + message: `Missing parameter: ${param}`, + errors: [""], + }), }); export const handleError = (error: any): APIGatewayProxyResult => { console.error(error); return { statusCode: 500, - body: JSON.stringify({ message: "Internal Server Error" }), + body: JSON.stringify({ message: "Internal Server Error", errors: [error] }), }; }; diff --git a/test_actions.sh b/test_actions.sh index eda18ea..9b98724 100644 --- a/test_actions.sh +++ b/test_actions.sh @@ -2,11 +2,11 @@ curl http://localhost:3001/actions #OK echo -e "\n" # Get global snapshot actions for a specific term -curl http://localhost:3001/global-snapshots/618a6f6bedba2c8a84ca3513f1cf0f6fe9eafb6f7872eecf2e222216351708b6/actions +curl http://localhost:3001/global-snapshots/80f93d4ef86dd1bf69648b5070e27d545d23ae578925198cc45dc17e02128312/actions #OK echo -e "\n" # Get address actions for a specific address -curl http://localhost:3001/addresses/DAG04Qob76gZG7D5qZu9oeZvr8WFxs53Md383q4x/actions #OK +curl http://localhost:3001/addresses/DAG77zerQ2BUVhtVgkmseihkEfLXieBBm57vqA4J/actions #OK echo -e "\n" # Get currency actions for a specific metagraph ID curl http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/actions #OK diff --git a/test_allowspends.sh b/test_allowspends.sh index 5bb7cf9..136342e 100644 --- a/test_allowspends.sh +++ b/test_allowspends.sh @@ -8,14 +8,14 @@ curl -X GET "http://localhost:3001/spend-transactions" curl -X GET "http://localhost:3001/allow-spend-expirations" echo "Global Snapshot Endpoints" -curl -X GET "http://localhost:3001/global-snapshots/618a6f6bedba2c8a84ca3513f1cf0f6fe9eafb6f7872eecf2e222216351708b6/allow-spends" +curl -X GET "http://localhost:3001/global-snapshots/80f93d4ef86dd1bf69648b5070e27d545d23ae578925198cc45dc17e02128312/allow-spends" curl -X GET "http://localhost:3001/global-snapshots/618a6f6bedba2c8a84ca3513f1cf0f6fe9eafb6f7872eecf2e222216351708b6/spend-transactions" -curl -X GET "http://localhost:3001/global-snapshots/618a6f6bedba2c8a84ca3513f1cf0f6fe9eafb6f7872eecf2e222216351708b6/allow-spend-expirations" +curl -X GET "http://localhost:3001/global-snapshots/672c3eb45ff5527a773b02f7722de762d0232d0f347cb1871ffa7226d9fdb18a/allow-spend-expirations" echo "Address-Specific Endpoints" -curl -X GET "http://localhost:3001/addresses/DAG04Qob76gZG7D5qZu9oeZvr8WFxs53Md383q4x/allow-spends" -curl -X GET "http://localhost:3001/addresses/DAG04Qob76gZG7D5qZu9oeZvr8WFxs53Md383q4x/spend-transactions" -curl -X GET "http://localhost:3001/addresses/DAG04Qob76gZG7D5qZu9oeZvr8WFxs53Md383q4x/allow-spend-expirations" +curl -X GET "http://localhost:3001/addresses/DAG77zerQ2BUVhtVgkmseihkEfLXieBBm57vqA4J/allow-spends" +curl -X GET "http://localhost:3001/addresses/DAG77zerQ2BUVhtVgkmseihkEfLXieBBm57vqA4J/spend-transactions" +curl -X GET "http://localhost:3001/addresses/DAG77zerQ2BUVhtVgkmseihkEfLXieBBm57vqA4J/allow-spend-expirations" echo "Currency (Metagraph) Endpoints" curl -X GET "http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/allow-spends" diff --git a/test_endpoints.sh b/test_endpoints.sh index 57ceb38..f7a0175 100644 --- a/test_endpoints.sh +++ b/test_endpoints.sh @@ -2,12 +2,12 @@ # Global Snapshots curl http://localhost:3001/global-snapshots #ok -curl http://localhost:3001/global-snapshots/695097c844c520c40577f168b85b994558024b44fd64c245154625c89fda024f #ok -curl http://localhost:3001/global-snapshots/155849 #ok +curl http://localhost:3001/global-snapshots/c54b8f21c967af9c956f7f2ebd81a20550a71357d805574c557e5128ec4f26c0 #ok +curl http://localhost:3001/global-snapshots/2528938 #ok curl http://localhost:3001/global-snapshots/latest -curl http://localhost:3001/global-snapshots/155849/rewards #ok -curl http://localhost:3001/global-snapshots/695097c844c520c40577f168b85b994558024b44fd64c245154625c89fda024f/rewards #ok +curl http://localhost:3001/global-snapshots/2528938/rewards #ok +curl http://localhost:3001/global-snapshots/c54b8f21c967af9c956f7f2ebd81a20550a71357d805574c557e5128ec4f26c0/rewards #ok curl http://localhost:3001/global-snapshots/latest/rewards # ok curl http://localhost:3001/global-snapshots/390792/transactions #ok diff --git a/test_tokenlocks.sh b/test_tokenlocks.sh index 3fd04fe..4c301d0 100644 --- a/test_tokenlocks.sh +++ b/test_tokenlocks.sh @@ -7,11 +7,11 @@ curl http://localhost:3001/token-locks -H "Content-Type: application/json" echo -e "\n" echo "Testing globalSnapshotTokenLocks endpoint..." -curl http://localhost:3001/global-snapshots/3cdde2bc02d9ad3da07fe52686a51db448129d9adf3167747be77e31f951c428/token-locks -H "Content-Type: application/json" +curl http://localhost:3001/global-snapshots/c3071648a319313afdecad217f196558a7893dde2bc78ee56fbb0d0a5b73700b/token-locks -H "Content-Type: application/json" echo -e "\n" echo "Testing addressTokenLocks endpoint..." -curl http://localhost:3001/addresses/DAG5T61H6RNLR4wLueNNZo8JR7fDKuGQSZoGTVki/token-locks -H "Content-Type: application/json" +curl http://localhost:3001/addresses/DAG5sz69nNwGF8ypn1yukFpg2pVJpdx5mnf1PJVc/token-locks -H "Content-Type: application/json" echo -e "\n" echo "Testing tokenUnlocks endpoint..." @@ -19,11 +19,11 @@ curl http://localhost:3001/token-unlocks -H"Content-Type: application/json" echo -e "\n" echo "Testing globalSnapshotTokenUnlocks endpoint..." -curl http://localhost:3001/global-snapshots/3cdde2bc02d9ad3da07fe52686a51db448129d9adf3167747be77e31f951c428/token-unlocks -H"Content-Type: application/json" +curl http://localhost:3001/global-snapshots/f5524dc1bff16b587732dc0dd36e6eb0e96fb067f9e0ec93b1cb9b55e8c2f526/token-unlocks -H"Content-Type: application/json" echo -e "\n" echo "Testing addressTokenUnlocks endpoint..." -curl http://localhost:3001/addresses/DAG4AqNNL2E7TWBUvQRxSPhraS2Lnr5xPM6xtVbs/token-unlocks -H"Content-Type: application/json" +curl http://localhost:3001/addresses/DAG77zerQ2BUVhtVgkmseihkEfLXieBBm57vqA4J/token-unlocks -H"Content-Type: application/json" echo -e "\n" echo "Testing currencyTokenLocks endpoint..." From ced0dc812b0f4ea843a2b9bff2055be7c2f206c7 Mon Sep 17 00:00:00 2001 From: Alex Brandes Date: Wed, 2 Apr 2025 16:52:39 -0700 Subject: [PATCH 07/63] Added endpoint tests using local postgres db --- .env | 2 + .github/workflows/pull_request.yml | 23 +- README.md | 93 ++- docker-compose.yml | 22 + env.yml | 3 +- package-lock.json | 34 +- package.json | 10 +- .../20250402214034_initial/migration.sql | 736 ++++++++++++++++++ prisma/migrations/migration_lock.toml | 3 + prisma/seed.ts | 130 ++++ tests/handlers/dagHandler.test.ts | 253 ++++++ tests/testUtils.ts | 78 ++ 12 files changed, 1328 insertions(+), 59 deletions(-) create mode 100644 .env create mode 100644 docker-compose.yml create mode 100644 prisma/migrations/20250402214034_initial/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 prisma/seed.ts create mode 100644 tests/handlers/dagHandler.test.ts create mode 100644 tests/testUtils.ts diff --git a/.env b/.env new file mode 100644 index 0000000..54444c3 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +# Test DB connection +DATABASE_URL=postgresql://postgres:postgres@localhost:5432/be_api_test_db?schema=public \ No newline at end of file diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 205347b..6ae370c 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -9,12 +9,29 @@ jobs: strategy: matrix: node-version: [19.x] + + services: + postgres: + image: postgres:15 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: be_api_test_db + ports: + - 5432:5432 + # Set health checks to wait until postgres has started + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} @@ -25,4 +42,4 @@ jobs: run: npm run test:tsc - name: Test - run: npm test -- --coverage --ci + run: npm run test diff --git a/README.md b/README.md index 6dd5923..e327fec 100644 --- a/README.md +++ b/README.md @@ -3,42 +3,97 @@ ![build](https://img.shields.io/github/actions/workflow/status/Constellation-Labs/block_explorer/release.yml?label=build) ![version](https://img.shields.io/github/v/release/Constellation-Labs/block_explorer?sort=semver) -Block Explorer exposes API functions to retrieve on-chain data from a tessellation opensearch database cluster. +The Block Explorer provides API functions to retrieve on-chain data from a Constellation Network indexer. The service uses PostgreSQL for data storage and retrieval and the Serverless framework for API deployment. -## Build and Run +## Table of Contents -### Prerequisites +- [Block Explorer](#block-explorer) + - [Table of Contents](#table-of-contents) + - [Prerequisites](#prerequisites) + - [Quick Start](#quick-start) + - [Development](#development) + - [Database Setup](#database-setup) + - [API Development](#api-development) + - [Testing](#testing) + - [API Documentation](#api-documentation) -1. [TypeScript](https://www.typescriptlang.org/id/download) -2. [Serverless Framework](https://www.serverless.com/framework/docs/getting-started/) -3. [Docker Desktop](https://www.docker.com/get-started/) with [Kubernetes](https://docs.docker.com/desktop/kubernetes/) enabled +## Prerequisites -### Setup local development cluster +- [Node.js](https://nodejs.org/) (v18 or higher recommended) +- [TypeScript](https://www.typescriptlang.org/download) +- [Docker](https://www.docker.com/get-started/) +- [Serverless Framework](https://www.serverless.com/framework/docs/getting-started/) -An [opensearch](https://aws.amazon.com/what-is/opensearch/) instance is used to store and query the on-chain data. +## Quick Start -Follow the instructions from the [snapshot streaming](https://github.com/Constellation-Labs/snapshot-streaming) repository which sets up your local tessellation development cluster along with an opensearch instance (hosted on port `4510`). +1. Clone the repository + ```bash + git clone https://github.com/Constellation-Labs/block_explorer.git + cd block_explorer + ``` -### Run +2. Install dependencies + ```bash + npm install + ``` -Install the npm packages from the project directory: +3. Start the PostgreSQL database + ```bash + npm run db:start + ``` -``` -npm install -``` +4. Run the API locally + ```bash + serverless offline + ``` + +## Development + +### Database Setup -Start the serverless offline host to test the APIs locally: +The project uses PostgreSQL for data storage. A Docker Compose configuration is provided for easy setup: +```bash +# Start the PostgreSQL container +npm run db:start + +# To stop the container when finished +npm run db:stop ``` + +The database configuration is stored in `.env` and can be customized as needed. + +### API Development + +Start the serverless offline host to test API endpoints locally: + +```bash serverless offline ``` -The output of this command shows an overview of the function URL's that can be called locally. +This will display a list of available endpoints that can be called locally. -## Unit Tests +## Testing -Run the unit tests locally: +Run tests with the following commands: -``` +```bash +# Start the database if not already running +npm run db:start + +# Run tests (this will reset and seed the database automatically) npm run test ``` + +The test suite uses Jest and automatically resets the database schema before each test run. + +## API Documentation + +API endpoints are defined in the `routes/` directory with their handlers in `src/handlers/`. + +Available endpoints include: +- DAG operations +- Metagraph information +- Token locks +- Actions +- Allow spends diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..43388bb --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + postgres: + image: postgres:15 + restart: unless-stopped + ports: + - "5432:5432" + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: be_api_test_db + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD", "pg_isready", "-U", "postgres"] + interval: 2s + timeout: 5s + retries: 10 + +volumes: + postgres_data: \ No newline at end of file diff --git a/env.yml b/env.yml index fac7483..6e12f4e 100644 --- a/env.yml +++ b/env.yml @@ -1,4 +1,3 @@ default: - opensearch: "http://localhost:9200" vpc: {} - db_url: postgresql://postgres:postgres@localhost:5432/new_block_explorer?schema=public + db_url: postgresql://postgres:postgres@localhost:5432/new_block_explorer?schema=public \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 26c3f1a..350bef1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "3.1.2", "license": "MIT", "dependencies": { - "@opensearch-project/opensearch": "^1.2.0", "@prisma/client": "^6.2.1", "aws-lambda": "^1.0.7", "date-fns": "^2.30.0", @@ -3511,21 +3510,6 @@ "node": ">= 8" } }, - "node_modules/@opensearch-project/opensearch": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@opensearch-project/opensearch/-/opensearch-1.2.0.tgz", - "integrity": "sha512-bX0aUz5e7rlY1lKz1rFrqnNbl/l1CHvvysYB2Jn+C3WNs7nL6FnQjuxLhGwyRdW9W1bFokDoOVgPMIOi/Nn9/g==", - "dependencies": { - "aws4": "^1.11.0", - "debug": "^4.3.1", - "hpagent": "^0.1.1", - "ms": "^2.1.3", - "secure-json-parse": "^2.4.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@prisma/client": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.2.1.tgz", @@ -5144,11 +5128,6 @@ "node": ">= 10.0.0" } }, - "node_modules/aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" - }, "node_modules/axios": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz", @@ -7637,11 +7616,6 @@ "node": ">=8" } }, - "node_modules/hpagent": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-0.1.2.tgz", - "integrity": "sha512-ePqFXHtSQWAFXYmj+JtOTHr84iNrII4/QRlAAPPE+zqnKy4xJo7Ie1Y4kC7AdB+LxLxSTTzBMASsEcy0q8YyvQ==" - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -10532,7 +10506,8 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "peer": true }, "node_modules/mute-stream": { "version": "0.0.8", @@ -11715,11 +11690,6 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==" }, - "node_modules/secure-json-parse": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", - "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" - }, "node_modules/seek-bzip": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", diff --git a/package.json b/package.json index 82ea4ac..171205f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,6 @@ "description": "", "main": "src/handler.ts", "dependencies": { - "@opensearch-project/opensearch": "^1.2.0", "@prisma/client": "^6.2.1", "aws-lambda": "^1.0.7", "date-fns": "^2.30.0", @@ -28,8 +27,13 @@ "typescript": "^5.8.2" }, "scripts": { - "test": "jest", + "db:start": "docker-compose up -d", + "db:stop": "docker-compose down", + "test": "npx prisma db push --force-reset && jest --no-silent", "test:tsc": "tsc --noEmit" }, - "license": "MIT" + "license": "MIT", + "prisma": { + "seed": "ts-node --transpile-only prisma/seed.ts" + } } diff --git a/prisma/migrations/20250402214034_initial/migration.sql b/prisma/migrations/20250402214034_initial/migration.sql new file mode 100644 index 0000000..c7c23a4 --- /dev/null +++ b/prisma/migrations/20250402214034_initial/migration.sql @@ -0,0 +1,736 @@ +-- CreateTable +CREATE TABLE "abstract_blocks" ( + "hash" VARCHAR NOT NULL, + "height" BIGINT NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "block_pkey" PRIMARY KEY ("hash") +); + +-- CreateTable +CREATE TABLE "abstract_transactions" ( + "hash" VARCHAR NOT NULL, + "source_addr" VARCHAR NOT NULL, + "amount" BIGINT NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "hash_pkey" PRIMARY KEY ("hash") +); + +-- CreateTable +CREATE TABLE "abstract_transactions_view" ( + "hash" VARCHAR NOT NULL, + "source_addr" VARCHAR NOT NULL, + "amount" BIGINT NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "table_name" TEXT NOT NULL, + + CONSTRAINT "abstract_transactions_view_pkey" PRIMARY KEY ("hash") +); + +-- CreateTable +CREATE TABLE "addresses" ( + "address" VARCHAR NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "address_pkey" PRIMARY KEY ("address") +); + +-- CreateTable +CREATE TABLE "block_parents" ( + "hash" VARCHAR NOT NULL, + "parent_proof_hash" VARCHAR NOT NULL, + "parent_height" BIGINT NOT NULL, + + CONSTRAINT "block_parents_pkey" PRIMARY KEY ("hash","parent_proof_hash") +); + +-- CreateTable +CREATE TABLE "dag_allow_spend_approvers" ( + "allow_spend_hash" VARCHAR NOT NULL, + "approver_address" VARCHAR NOT NULL, + + CONSTRAINT "dag_allow_spend_approvers_pk" PRIMARY KEY ("allow_spend_hash","approver_address") +); + +-- CreateTable +CREATE TABLE "dag_allow_spend_blocks" ( + "round_id" UUID NOT NULL, + "global_snapshot_hash" VARCHAR NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "dag_allow_spend_blocks_pkey" PRIMARY KEY ("global_snapshot_hash") +); + +-- CreateTable +CREATE TABLE "dag_allow_spends" ( + "hash" VARCHAR NOT NULL, + "source_addr" VARCHAR NOT NULL, + "amount" BIGINT NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "destination_addr" VARCHAR NOT NULL, + "fee" BIGINT NOT NULL, + "parent_ordinal" BIGINT, + "parent_hash" VARCHAR, + "last_valid_epoch_progress" BIGINT NOT NULL, + "round_id" UUID NOT NULL, + "ordinal" BIGINT NOT NULL, + "snapshot_hash" VARCHAR NOT NULL, + + CONSTRAINT "dag_allow_spends_pk" PRIMARY KEY ("hash") +); + +-- CreateTable +CREATE TABLE "dag_balance_changes" ( + "snapshot_hash" VARCHAR NOT NULL, + "address" VARCHAR NOT NULL, + "balance" BIGINT NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "snapshot_ordinal" BIGINT NOT NULL, + + CONSTRAINT "dag_balance_change_pk" PRIMARY KEY ("snapshot_ordinal","address") +); + +-- CreateTable +CREATE TABLE "dag_blocks" ( + "hash" VARCHAR NOT NULL, + "height" BIGINT NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "snapshot_hash" VARCHAR NOT NULL, + + CONSTRAINT "dag_block_pk" PRIMARY KEY ("hash") +); + +-- CreateTable +CREATE TABLE "dag_reward_transactions" ( + "global_snapshot_hash" VARCHAR NOT NULL, + "destination_addr" VARCHAR NOT NULL, + "amount" BIGINT NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "dag_reward_transaction_pk" PRIMARY KEY ("global_snapshot_hash","destination_addr") +); + +-- CreateTable +CREATE TABLE "dag_spend_transactions" ( + "hash" VARCHAR NOT NULL, + "source_addr" VARCHAR NOT NULL, + "amount" BIGINT NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "destination_addr" VARCHAR, + "allow_spend_ref" VARCHAR, + "snapshot_hash" VARCHAR, + + CONSTRAINT "dag_spend_transactions_pk" PRIMARY KEY ("hash") +); + +-- CreateTable +CREATE TABLE "dag_token_lock_blocks" ( + "round_id" UUID NOT NULL, + "global_snapshot_hash" VARCHAR NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "dag_token_lock_blocks_pkey" PRIMARY KEY ("round_id") +); + +-- CreateTable +CREATE TABLE "dag_token_locks" ( + "hash" VARCHAR NOT NULL, + "source_addr" VARCHAR NOT NULL, + "amount" BIGINT NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "ordinal" BIGINT NOT NULL, + "unlock_epoch" BIGINT, + "round_id" UUID NOT NULL, + "global_snapshot_hash" VARCHAR NOT NULL, + + CONSTRAINT "dag_token_locks_pk" PRIMARY KEY ("hash") +); + +-- CreateTable +CREATE TABLE "dag_token_unlocks" ( + "hash" VARCHAR NOT NULL, + "source_addr" VARCHAR NOT NULL, + "amount" BIGINT NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "lock_reference_hash" VARCHAR NOT NULL, + "snapshot_hash" VARCHAR NOT NULL, + + CONSTRAINT "dag_token_unlocks_pk" PRIMARY KEY ("hash") +); + +-- CreateTable +CREATE TABLE "dag_transactions" ( + "hash" VARCHAR NOT NULL, + "source_addr" VARCHAR NOT NULL, + "amount" BIGINT NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "destination_addr" VARCHAR NOT NULL, + "fee" BIGINT NOT NULL, + "salt" BIGINT NOT NULL, + "parent_ordinal" BIGINT, + "parent_hash" VARCHAR, + "ordinal" BIGINT NOT NULL, + "block_hash" VARCHAR NOT NULL, + + CONSTRAINT "dag_transaction_pk" PRIMARY KEY ("hash") +); + +-- CreateTable +CREATE TABLE "global_snapshot_proofs" ( + "id" VARCHAR NOT NULL, + "signature" VARCHAR NOT NULL, + "snapshot_hash" VARCHAR NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "proof_pk" PRIMARY KEY ("snapshot_hash","id") +); + +-- CreateTable +CREATE TABLE "global_snapshots" ( + "hash" VARCHAR NOT NULL, + "ordinal" BIGINT NOT NULL, + "height" BIGINT NOT NULL, + "subheight" INTEGER NOT NULL, + "last_snapshot_hash" VARCHAR NOT NULL, + "metagraph_snapshot_count" BIGINT, + "epoch_progress" BIGINT, + "version" VARCHAR NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "global_snapshot_pk" PRIMARY KEY ("hash") +); + +-- CreateTable +CREATE TABLE "metagraph_allow_spend_approvers" ( + "allow_spend_hash" VARCHAR NOT NULL, + "approver_address" VARCHAR NOT NULL, + + CONSTRAINT "metagraph_allow_spend_approvers_pk" PRIMARY KEY ("allow_spend_hash","approver_address") +); + +-- CreateTable +CREATE TABLE "metagraph_allow_spend_blocks" ( + "metagraph_id" VARCHAR NOT NULL, + "round_id" UUID NOT NULL, + "metagraph_snapshot_hash" VARCHAR NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "metagraph_allow_spend_blocks_pkey" PRIMARY KEY ("round_id") +); + +-- CreateTable +CREATE TABLE "metagraph_allow_spends" ( + "hash" VARCHAR NOT NULL, + "source_addr" VARCHAR NOT NULL, + "amount" BIGINT NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "metagraph_id" VARCHAR NOT NULL, + "destination_addr" VARCHAR NOT NULL, + "fee" BIGINT NOT NULL, + "parent_ordinal" BIGINT, + "parent_hash" VARCHAR, + "last_valid_epoch_progress" BIGINT NOT NULL, + "round_id" UUID NOT NULL, + "ordinal" BIGINT NOT NULL, + "snapshot_hash" VARCHAR NOT NULL, + + CONSTRAINT "metagraph_allow_spends_pk" PRIMARY KEY ("hash") +); + +-- CreateTable +CREATE TABLE "metagraph_balance_changes" ( + "metagraph_id" VARCHAR NOT NULL, + "metagraph_snapshot_hash" VARCHAR NOT NULL, + "address" VARCHAR NOT NULL, + "balance" BIGINT NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "metagraph_snapshot_ordinal" BIGINT NOT NULL, + + CONSTRAINT "metagraph_balance_change_pk" PRIMARY KEY ("metagraph_id","address","metagraph_snapshot_ordinal") +); + +-- CreateTable +CREATE TABLE "metagraph_blocks" ( + "hash" VARCHAR NOT NULL, + "height" BIGINT NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "metagraph_id" VARCHAR NOT NULL, + "metagraph_snapshot_hash" VARCHAR NOT NULL, + + CONSTRAINT "metagraph_block_pk" PRIMARY KEY ("metagraph_id","hash") +); + +-- CreateTable +CREATE TABLE "metagraph_fee_transactions" ( + "hash" VARCHAR NOT NULL, + "source_addr" VARCHAR NOT NULL, + "amount" BIGINT NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "metagraph_id" VARCHAR NOT NULL, + "metagraph_snapshot_hash" VARCHAR NOT NULL, + "destination_addr" VARCHAR NOT NULL, + "data_update_ref" VARCHAR, + "metagraph_snapshot_ordinal" BIGINT, + + CONSTRAINT "fee_transaction_pk" PRIMARY KEY ("hash") +); + +-- CreateTable +CREATE TABLE "metagraph_reward_transactions" ( + "metagraph_id" VARCHAR NOT NULL, + "metagraph_snapshot_hash" VARCHAR NOT NULL, + "destination_addr" VARCHAR NOT NULL, + "amount" BIGINT NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "metagraph_reward_transaction_pk" PRIMARY KEY ("metagraph_id","metagraph_snapshot_hash","destination_addr") +); + +-- CreateTable +CREATE TABLE "metagraph_snapshots" ( + "metagraph_id" VARCHAR NOT NULL, + "ordinal" BIGINT NOT NULL, + "global_snapshot_hash" VARCHAR, + "hash" VARCHAR NOT NULL, + "height" BIGINT NOT NULL, + "subheight" INTEGER NOT NULL, + "last_snapshot_hash" VARCHAR, + "fee" BIGINT, + "owner_address" VARCHAR, + "staking_address" VARCHAR, + "epoch_progress" BIGINT, + "version" VARCHAR NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "metagraph_snapshot_pk" PRIMARY KEY ("metagraph_id","hash") +); + +-- CreateTable +CREATE TABLE "metagraph_spend_transactions" ( + "hash" VARCHAR NOT NULL, + "source_addr" VARCHAR NOT NULL, + "amount" BIGINT NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "metagraph_id" VARCHAR NOT NULL, + "destination_addr" VARCHAR NOT NULL, + "allow_spend_ref" VARCHAR, + "snapshot_hash" VARCHAR, + + CONSTRAINT "metagraph_spend_transactions_pk" PRIMARY KEY ("hash") +); + +-- CreateTable +CREATE TABLE "metagraph_token_lock_blocks" ( + "metagraph_id" VARCHAR NOT NULL, + "metagraph_snapshot_hash" VARCHAR NOT NULL, + "round_id" UUID NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "metagraph_token_lock_blocks_pkey" PRIMARY KEY ("metagraph_id","metagraph_snapshot_hash") +); + +-- CreateTable +CREATE TABLE "metagraph_token_locks" ( + "hash" VARCHAR NOT NULL, + "source_addr" VARCHAR NOT NULL, + "amount" BIGINT NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "metagraph_id" VARCHAR NOT NULL, + "ordinal" BIGINT NOT NULL, + "unlock_epoch" BIGINT, + "round_id" UUID NOT NULL, + "snapshot_hash" VARCHAR NOT NULL, + + CONSTRAINT "metagraph_token_locks_pk" PRIMARY KEY ("hash") +); + +-- CreateTable +CREATE TABLE "metagraph_token_unlocks" ( + "hash" VARCHAR NOT NULL, + "source_addr" VARCHAR NOT NULL, + "amount" BIGINT NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "metagraph_id" VARCHAR NOT NULL, + "lock_reference_ordinal" BIGINT NOT NULL, + "lock_reference_hash" VARCHAR NOT NULL, + "snapshot_hash" VARCHAR NOT NULL, + + CONSTRAINT "metagraph_token_unlocks_pk" PRIMARY KEY ("hash") +); + +-- CreateTable +CREATE TABLE "metagraph_transactions" ( + "hash" VARCHAR NOT NULL, + "source_addr" VARCHAR NOT NULL, + "amount" BIGINT NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "metagraph_id" VARCHAR NOT NULL, + "destination_addr" VARCHAR NOT NULL, + "fee" BIGINT NOT NULL, + "salt" BIGINT NOT NULL, + "parent_ordinal" BIGINT NOT NULL, + "parent_hash" VARCHAR NOT NULL, + "ordinal" BIGINT NOT NULL, + "block_hash" VARCHAR NOT NULL, + + CONSTRAINT "metagraph_transaction_pk" PRIMARY KEY ("metagraph_id","hash") +); + +-- CreateTable +CREATE TABLE "metagraphs" ( + "id" VARCHAR NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "metagraph_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "dag_expired_spend_transactions" ( + "hash" VARCHAR NOT NULL, + "source_addr" VARCHAR NOT NULL, + "amount" BIGINT NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "allow_spend_ref" VARCHAR, + "snapshot_hash" VARCHAR, + + CONSTRAINT "dag_expired_spend_transactions_pk" PRIMARY KEY ("hash") +); + +-- CreateTable +CREATE TABLE "metagraph_expired_spend_transactions" ( + "hash" VARCHAR NOT NULL, + "source_addr" VARCHAR NOT NULL, + "amount" BIGINT NOT NULL, + "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "metagraph_id" VARCHAR NOT NULL, + "allow_spend_ref" VARCHAR, + "snapshot_hash" VARCHAR, + + CONSTRAINT "metagraph_expired_spend_transactions_pk" PRIMARY KEY ("hash") +); + +-- CreateIndex +CREATE INDEX "block_parents_hash_idx" ON "block_parents"("hash"); + +-- CreateIndex +CREATE UNIQUE INDEX "dag_allow_spend_blocks_unique" ON "dag_allow_spend_blocks"("round_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "dag_allow_spends_ordinal" ON "dag_allow_spends"("ordinal"); + +-- CreateIndex +CREATE INDEX "dag_allow_spends_round_id_idx" ON "dag_allow_spends"("round_id"); + +-- CreateIndex +CREATE INDEX "dag_balance_changes_address_idx" ON "dag_balance_changes"("address", "created_at"); + +-- CreateIndex +CREATE INDEX "dag_blocks_snapshot_hash_idx" ON "dag_blocks"("snapshot_hash"); + +-- CreateIndex +CREATE INDEX "dag_reward_transactions_global_snapshot_hash_idx" ON "dag_reward_transactions"("global_snapshot_hash"); + +-- CreateIndex +CREATE UNIQUE INDEX "dag_token_lock_blocks_unique" ON "dag_token_lock_blocks"("global_snapshot_hash"); + +-- CreateIndex +CREATE UNIQUE INDEX "dag_token_locks_unique" ON "dag_token_locks"("ordinal"); + +-- CreateIndex +CREATE UNIQUE INDEX "dag_token_unlocks_lock_reference_hash_key" ON "dag_token_unlocks"("lock_reference_hash"); + +-- CreateIndex +CREATE UNIQUE INDEX "global_snapshot_unique" ON "global_snapshots"("ordinal"); + +-- CreateIndex +CREATE UNIQUE INDEX "metagraph_allow_spends_ordinal" ON "metagraph_allow_spends"("ordinal"); + +-- CreateIndex +CREATE UNIQUE INDEX "hash" ON "metagraph_blocks"("hash"); + +-- CreateIndex +CREATE UNIQUE INDEX "metagraph_snapshot_unique" ON "metagraph_snapshots"("metagraph_id", "ordinal"); + +-- CreateIndex +CREATE UNIQUE INDEX "metagraph_token_lock_blocks_unique" ON "metagraph_token_lock_blocks"("metagraph_id", "round_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "metagraph_token_locks_unique" ON "metagraph_token_locks"("metagraph_id", "ordinal"); + +-- CreateIndex +CREATE UNIQUE INDEX "metagraph_token_unlocks_lock_reference_hash_key" ON "metagraph_token_unlocks"("lock_reference_hash"); + +-- CreateIndex +CREATE UNIQUE INDEX "metagraph_token_unlocks_lock_reference_ordinal_key" ON "metagraph_token_unlocks"("lock_reference_ordinal"); + +-- AddForeignKey +ALTER TABLE "abstract_blocks" ADD CONSTRAINT "dag_blocks_hash_fk" FOREIGN KEY ("hash") REFERENCES "dag_blocks"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "abstract_blocks" ADD CONSTRAINT "metagraph_blocks_hash_fk" FOREIGN KEY ("hash") REFERENCES "metagraph_blocks"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "abstract_transactions_view" ADD CONSTRAINT "dag_token_locks_ref" FOREIGN KEY ("hash") REFERENCES "dag_token_locks"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "abstract_transactions_view" ADD CONSTRAINT "metagraph_token_locks_ref" FOREIGN KEY ("hash") REFERENCES "metagraph_token_locks"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "abstract_transactions_view" ADD CONSTRAINT "dag_token_unlocks_ref" FOREIGN KEY ("hash") REFERENCES "dag_token_unlocks"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "abstract_transactions_view" ADD CONSTRAINT "metagraph_token_unlocks_ref" FOREIGN KEY ("hash") REFERENCES "metagraph_token_unlocks"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "abstract_transactions_view" ADD CONSTRAINT "dag_allow_spend_ref" FOREIGN KEY ("hash") REFERENCES "dag_allow_spends"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "abstract_transactions_view" ADD CONSTRAINT "metagraph_allow_spend_ref" FOREIGN KEY ("hash") REFERENCES "metagraph_allow_spends"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "abstract_transactions_view" ADD CONSTRAINT "dag_spend_transaction_ref" FOREIGN KEY ("hash") REFERENCES "dag_spend_transactions"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "abstract_transactions_view" ADD CONSTRAINT "metagraph_spend_transaction_ref" FOREIGN KEY ("hash") REFERENCES "metagraph_spend_transactions"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "abstract_transactions_view" ADD CONSTRAINT "dag_expired_spend_transaction_ref" FOREIGN KEY ("hash") REFERENCES "dag_expired_spend_transactions"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "abstract_transactions_view" ADD CONSTRAINT "metagraph_expired_spend_transaction_ref" FOREIGN KEY ("hash") REFERENCES "metagraph_expired_spend_transactions"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "abstract_transactions_view" ADD CONSTRAINT "metagraph_fee_transaction_ref" FOREIGN KEY ("hash") REFERENCES "metagraph_fee_transactions"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "block_parents" ADD CONSTRAINT "block_parents_block_fk" FOREIGN KEY ("hash") REFERENCES "abstract_blocks"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "dag_allow_spend_approvers" ADD CONSTRAINT "dag_allow_spend_approvers_address_fk" FOREIGN KEY ("approver_address") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "dag_allow_spend_approvers" ADD CONSTRAINT "dag_allow_spends_fk" FOREIGN KEY ("allow_spend_hash") REFERENCES "dag_allow_spends"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "dag_allow_spend_blocks" ADD CONSTRAINT "dag_allow_spend_blocks_global_snapshot_fk" FOREIGN KEY ("global_snapshot_hash") REFERENCES "global_snapshots"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "dag_allow_spends" ADD CONSTRAINT "dag_allow_spends_destination_addr_fk" FOREIGN KEY ("destination_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "dag_allow_spends" ADD CONSTRAINT "dag_allow_spends_source_addr_fk" FOREIGN KEY ("source_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "dag_allow_spends" ADD CONSTRAINT "dag_allow_spend_blocks_global_snapshot_fk" FOREIGN KEY ("snapshot_hash") REFERENCES "global_snapshots"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "dag_balance_changes" ADD CONSTRAINT "dag_balance_change_address_fk" FOREIGN KEY ("address") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "dag_balance_changes" ADD CONSTRAINT "dag_balance_change_global_snapshot_fk" FOREIGN KEY ("snapshot_hash") REFERENCES "global_snapshots"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "dag_blocks" ADD CONSTRAINT "dag_block_global_snapshot_fk" FOREIGN KEY ("snapshot_hash") REFERENCES "global_snapshots"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "dag_reward_transactions" ADD CONSTRAINT "dag_reward_transaction_global_snapshot_fk" FOREIGN KEY ("global_snapshot_hash") REFERENCES "global_snapshots"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "dag_reward_transactions" ADD CONSTRAINT "dag_reward_transactions_destination_addr_fk" FOREIGN KEY ("destination_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "dag_spend_transactions" ADD CONSTRAINT "dag_spend_transactions_dag_allow_spends_fk" FOREIGN KEY ("allow_spend_ref") REFERENCES "dag_allow_spends"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "dag_spend_transactions" ADD CONSTRAINT "dag_spend_transactions_destination_addr_fk" FOREIGN KEY ("destination_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "dag_token_lock_blocks" ADD CONSTRAINT "dag_token_lock_blocks_global_snapshot_fk" FOREIGN KEY ("global_snapshot_hash") REFERENCES "global_snapshots"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "dag_token_locks" ADD CONSTRAINT "dag_token_locks_source_addr_fk" FOREIGN KEY ("source_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "dag_token_locks" ADD CONSTRAINT "dag_token_locks_round_id_fkey" FOREIGN KEY ("round_id") REFERENCES "dag_token_lock_blocks"("round_id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "dag_token_locks" ADD CONSTRAINT "dag_token_lock_global_snapshot_fk" FOREIGN KEY ("global_snapshot_hash") REFERENCES "global_snapshots"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "dag_token_unlocks" ADD CONSTRAINT "dag_token_unlocks_token_locks_fk" FOREIGN KEY ("lock_reference_hash") REFERENCES "dag_token_locks"("hash") ON DELETE NO ACTION ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "dag_token_unlocks" ADD CONSTRAINT "ddag_token_unlocks_address_fk" FOREIGN KEY ("source_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "dag_token_unlocks" ADD CONSTRAINT "dag_token_unlock_global_snapshot_fk" FOREIGN KEY ("snapshot_hash") REFERENCES "global_snapshots"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "dag_transactions" ADD CONSTRAINT "dag_transaction_dag_block_fk" FOREIGN KEY ("block_hash") REFERENCES "dag_blocks"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "dag_transactions" ADD CONSTRAINT "dag_transactions_destination_addr_fk" FOREIGN KEY ("destination_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "dag_transactions" ADD CONSTRAINT "dag_transactions_source_addr_fk" FOREIGN KEY ("source_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "global_snapshot_proofs" ADD CONSTRAINT "proof_global_snapshot_fk" FOREIGN KEY ("snapshot_hash") REFERENCES "global_snapshots"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_allow_spend_approvers" ADD CONSTRAINT "ametagraph_llow_spends_fk" FOREIGN KEY ("allow_spend_hash") REFERENCES "metagraph_allow_spends"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_allow_spend_approvers" ADD CONSTRAINT "metagraph_allow_spend_approvers_address_fk" FOREIGN KEY ("approver_address") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_allow_spend_blocks" ADD CONSTRAINT "metagraph_allow_spend_blocks_metagraph_snapshot_fk" FOREIGN KEY ("metagraph_id", "metagraph_snapshot_hash") REFERENCES "metagraph_snapshots"("metagraph_id", "hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_allow_spends" ADD CONSTRAINT "allow_spends_block_fk" FOREIGN KEY ("round_id") REFERENCES "metagraph_allow_spend_blocks"("round_id") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_allow_spends" ADD CONSTRAINT "metagraph_allow_spends_destination_addr_fk" FOREIGN KEY ("destination_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_allow_spends" ADD CONSTRAINT "metagraph_allow_spends_source_addr_fk" FOREIGN KEY ("source_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_allow_spends" ADD CONSTRAINT "metagraph_id_fk" FOREIGN KEY ("metagraph_id") REFERENCES "metagraphs"("id") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_allow_spends" ADD CONSTRAINT "metagraph_allow_spend_blocks_metagraph_snapshot_fk" FOREIGN KEY ("metagraph_id", "snapshot_hash") REFERENCES "metagraph_snapshots"("metagraph_id", "hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_balance_changes" ADD CONSTRAINT "address_fk" FOREIGN KEY ("address") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_balance_changes" ADD CONSTRAINT "metagraph_balance_change_metagraph_snapshot_fk" FOREIGN KEY ("metagraph_id", "metagraph_snapshot_hash") REFERENCES "metagraph_snapshots"("metagraph_id", "hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_balance_changes" ADD CONSTRAINT "metagraph_id_fk" FOREIGN KEY ("metagraph_id") REFERENCES "metagraphs"("id") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_blocks" ADD CONSTRAINT "metagraph_block_metagraph_snapshot_fk" FOREIGN KEY ("metagraph_id", "metagraph_snapshot_hash") REFERENCES "metagraph_snapshots"("metagraph_id", "hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_blocks" ADD CONSTRAINT "metagraph_id_fk" FOREIGN KEY ("metagraph_id") REFERENCES "metagraphs"("id") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_fee_transactions" ADD CONSTRAINT "fee_transaction_metagraph_snapshot_fk" FOREIGN KEY ("metagraph_id", "metagraph_snapshot_hash") REFERENCES "metagraph_snapshots"("metagraph_id", "hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_fee_transactions" ADD CONSTRAINT "metagraph_fee_transactions_destination_addr_fk" FOREIGN KEY ("destination_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_fee_transactions" ADD CONSTRAINT "metagraph_fee_transactions_source_addr_fk" FOREIGN KEY ("source_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_fee_transactions" ADD CONSTRAINT "metagraph_id_fk" FOREIGN KEY ("metagraph_id") REFERENCES "metagraphs"("id") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_reward_transactions" ADD CONSTRAINT "metagraph_reward_transaction_metagraph_reward_transaction_fk" FOREIGN KEY ("metagraph_id", "metagraph_snapshot_hash") REFERENCES "metagraph_snapshots"("metagraph_id", "hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_reward_transactions" ADD CONSTRAINT "metagraph_reward_transactions_destination_addr_fk" FOREIGN KEY ("destination_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_reward_transactions" ADD CONSTRAINT "metagraph_reward_transactions_metagraph_id_fk" FOREIGN KEY ("metagraph_id") REFERENCES "metagraphs"("id") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_snapshots" ADD CONSTRAINT "metagraph_id_fk" FOREIGN KEY ("metagraph_id") REFERENCES "metagraphs"("id") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_snapshots" ADD CONSTRAINT "metagraph_snapshots_global_snapshots_fk" FOREIGN KEY ("global_snapshot_hash") REFERENCES "global_snapshots"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_snapshots" ADD CONSTRAINT "owner_address_fk" FOREIGN KEY ("owner_address") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_snapshots" ADD CONSTRAINT "staking_address_fk" FOREIGN KEY ("staking_address") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_spend_transactions" ADD CONSTRAINT "mg_spend_transactions_mg_allow_spends_fk" FOREIGN KEY ("allow_spend_ref") REFERENCES "metagraph_allow_spends"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_spend_transactions" ADD CONSTRAINT "metagraph_spend_transactions_destination_addr_fk" FOREIGN KEY ("destination_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_spend_transactions" ADD CONSTRAINT "metagraph_spend_transactions_metagraph_id_fk" FOREIGN KEY ("metagraph_id") REFERENCES "metagraphs"("id") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_token_lock_blocks" ADD CONSTRAINT "metagraph_token_lock_blocks_metagraph_id_fk" FOREIGN KEY ("metagraph_id") REFERENCES "metagraphs"("id") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_token_lock_blocks" ADD CONSTRAINT "metagraph_token_lock_blocks_metagraph_snapshot_fk" FOREIGN KEY ("metagraph_id", "metagraph_snapshot_hash") REFERENCES "metagraph_snapshots"("metagraph_id", "hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_token_locks" ADD CONSTRAINT "metagraph_id_fk" FOREIGN KEY ("metagraph_id") REFERENCES "metagraphs"("id") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_token_locks" ADD CONSTRAINT "metagraph_token_locks_source_addr_fk" FOREIGN KEY ("source_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_token_locks" ADD CONSTRAINT "metagraph_lock_blocks_metagraph_snapshot_fk" FOREIGN KEY ("metagraph_id", "snapshot_hash") REFERENCES "metagraph_snapshots"("metagraph_id", "hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_token_unlocks" ADD CONSTRAINT "address_fk" FOREIGN KEY ("source_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_token_unlocks" ADD CONSTRAINT "metagraph_id_fk" FOREIGN KEY ("metagraph_id") REFERENCES "metagraphs"("id") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_token_unlocks" ADD CONSTRAINT "metagraph_token_unlocks_token_locks_fk" FOREIGN KEY ("lock_reference_hash") REFERENCES "metagraph_token_locks"("hash") ON DELETE NO ACTION ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_token_unlocks" ADD CONSTRAINT "metagraph_unlock_blocks_metagraph_snapshot_fk" FOREIGN KEY ("metagraph_id", "snapshot_hash") REFERENCES "metagraph_snapshots"("metagraph_id", "hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_transactions" ADD CONSTRAINT "metagraph_id_fk" FOREIGN KEY ("metagraph_id") REFERENCES "metagraphs"("id") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_transactions" ADD CONSTRAINT "metagraph_transaction_metagraph_block_fk" FOREIGN KEY ("metagraph_id", "block_hash") REFERENCES "metagraph_blocks"("metagraph_id", "hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_transactions" ADD CONSTRAINT "metagraph_transactions_destination_addrfk" FOREIGN KEY ("destination_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_transactions" ADD CONSTRAINT "metagraph_transactions_source_addr_fk" FOREIGN KEY ("source_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "dag_expired_spend_transactions" ADD CONSTRAINT "dag_expired_spend_transactions_dag_allow_spends_fk" FOREIGN KEY ("allow_spend_ref") REFERENCES "dag_allow_spends"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; + +-- AddForeignKey +ALTER TABLE "metagraph_expired_spend_transactions" ADD CONSTRAINT "metagraph_expired_spend_transactions_metagraph_allow_spends_fk" FOREIGN KEY ("allow_spend_ref") REFERENCES "metagraph_allow_spends"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..648c57f --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" \ No newline at end of file diff --git a/prisma/seed.ts b/prisma/seed.ts new file mode 100644 index 0000000..385cc79 --- /dev/null +++ b/prisma/seed.ts @@ -0,0 +1,130 @@ +import { + PrismaClient, + addresses, + global_snapshots, + dag_blocks, + dag_transactions, + dag_balance_changes, +} from "@prisma/client"; + +const prisma = new PrismaClient(); + +type TestData = { + addresses: addresses[]; + global_snapshots: global_snapshots[]; + dag_blocks: dag_blocks[]; + dag_transactions: dag_transactions[]; + dag_balance_changes: dag_balance_changes[] +}; + +export async function seed() { + const testData = {} as TestData; + + testData.addresses = await prisma.addresses.createManyAndReturn({ + data: [ + { address: "DAG4bQGdnDJ5okVdsdtvJzBwQoPGjLNzN7HC1CBV" }, + { address: "DAG5zSqFVKy399PWxxv1X4TitnVn8PfLXoK7DQQM" }, + ], + }); + + testData.global_snapshots = await prisma.global_snapshots.createManyAndReturn( + { + data: [ + { + hash: + "5649b42aad4f232ee30fe3e97b473e584ddc11c6a9823e272c32c1bb48f2042b", + ordinal: 2556535, + height: 41997, + subheight: 842, + last_snapshot_hash: + "5649b42aad4f232ee30fe3e97b473e584ddc11c6a9823e272c32c1bb48f2042b", + epoch_progress: 1012463, + metagraph_snapshot_count: 0, + version: "0.0.1", + created_at: "2025-04-02T00:00:01Z", // set explicitly to avoid race condition + }, + { + hash: + "b9fdf9e18ac3225a35b75d4fe73e9ee5d307a21ac3e0bfd4c77a6eda2411a366", + ordinal: 2556536, + height: 41998, + subheight: 845, + last_snapshot_hash: + "5649b42aad4f232ee30fe3e97b473e584ddc11c6a9823e272c32c1bb48f2042b", + epoch_progress: 1012463, + metagraph_snapshot_count: 0, + version: "0.0.1", + created_at: "2025-04-02T00:00:02Z", + }, + ], + } + ); + + testData.dag_blocks = await prisma.dag_blocks.createManyAndReturn({ + data: [ + { + hash: + "16593f9f612a453c28669b86067e097990ee18742e905afa330674636ca1431c", + height: 12, + snapshot_hash: testData.global_snapshots[0].hash, + }, + ], + }); + + testData.dag_transactions = await prisma.dag_transactions.createManyAndReturn( + { + data: [ + { + hash: + "1c53bc94c735d8d6eeaddc9f5cb446e7f79144c9aa5bba9479db8dee0ec1aa4c", + source_addr: testData.addresses[0].address, + destination_addr: testData.addresses[1].address, + amount: 90790983, + fee: 200000, + salt: 1231231232, + parent_ordinal: 21337, + parent_hash: + "4a6d3aa5715e304b4b5f32d52f0c91e0909acf7c24b3ca9776324da68db2f30c", + ordinal: testData.global_snapshots[0].ordinal, + block_hash: testData.dag_blocks[0].hash, + }, + ], + } + ); + + testData.dag_balance_changes = await prisma.dag_balance_changes.createManyAndReturn( + { + data: [ + { + snapshot_hash: testData.global_snapshots[0].hash, + snapshot_ordinal: testData.global_snapshots[0].ordinal, + address: testData.dag_transactions[0].destination_addr, + balance: testData.dag_transactions[0].amount, + }, + ], + } + ); + + return testData; +} + +export async function resetDatabase() { + // Disable referential integrity temporarily if needed + await prisma.$executeRawUnsafe( + `TRUNCATE TABLE "addresses", "global_snapshots", "dag_blocks", "dag_transactions" RESTART IDENTITY CASCADE;` + ); + // Add your own models above + + console.log("Database reset complete."); +} + +if (require.main === module) { + seed() + .catch((e) => { + console.error("err:", e); + // process.exit(0); + }) + .finally(async () => { + await prisma.$disconnect(); + }); +} diff --git a/tests/handlers/dagHandler.test.ts b/tests/handlers/dagHandler.test.ts new file mode 100644 index 0000000..dd1162c --- /dev/null +++ b/tests/handlers/dagHandler.test.ts @@ -0,0 +1,253 @@ +import { APIGatewayProxyResult } from "aws-lambda"; +import * as dagHandler from "../../src/handlers/dagHandler"; +import { + createAPIGatewayEvent, + validateResponseStructure, + validatePaginatedResponse, +} from "../testUtils"; +import { PrismaClient } from "@prisma/client"; + +const prisma = new PrismaClient(); +import { seed } from "../../prisma/seed"; + +let testData, prismaClient; +const runSeedOnce = async () => { + if (testData) return testData; + + prismaClient = new PrismaClient(); + testData = await seed(); +}; + +// These tests use a real database connection, so they're integration tests +describe("DAG Handler Integration Tests", () => { + describe("globalSnapshots", () => { + it("should return a list of global snapshots", async () => { + await runSeedOnce(); + + const event = createAPIGatewayEvent({}, { limit: "10" }); + const response: APIGatewayProxyResult = await dagHandler.globalSnapshots( + event + ); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + expect(body.data.length).toBe(testData.global_snapshots.length); + + const testSnapshot = testData.global_snapshots.sort((a, b): number => { + return a.ordinal < b.ordinal ? 1 : -1; + })[0]; + + // Verify the structure of the returned data + const snapshot = body.data[0]; + expect(snapshot.ordinal).toBe(Number(testSnapshot.ordinal)); + expect(snapshot.hash).toBe(testSnapshot.hash); + expect(snapshot.epochProgress).toBe(Number(testSnapshot.epoch_progress)); + expect(snapshot.height).toBe(Number(testSnapshot.height)); + expect(snapshot.subHeight).toBe(Number(testSnapshot.subheight)); + expect(Array.isArray(snapshot.blocks)).toBe(true); + expect(snapshot.timestamp).toBeDefined(); + }); + + it("should handle pagination correctly", async () => { + await runSeedOnce(); + + const event = createAPIGatewayEvent({}, { limit: "1" }); + + const response: APIGatewayProxyResult = await dagHandler.globalSnapshots( + event + ); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + // Ensure pagination is working + expect(body.meta.next).toBeDefined(); + + // Try getting the next page + const nextEvent = createAPIGatewayEvent( + {}, + { + limit: "1", + next: body.meta.next, + } + ); + + const nextResponse = await dagHandler.globalSnapshots(nextEvent); + expect(nextResponse.statusCode).toBe(200); + + const nextBody = validatePaginatedResponse(nextResponse); + + // Ensure we got a different set of results + expect(nextBody.data[0].hash).not.toBe(body.data[0].hash); + }); + }); + + describe("globalSnapshot", () => { + it('should return the latest global snapshot when term is "latest"', async () => { + await runSeedOnce(); + + const event = createAPIGatewayEvent({ term: "latest" }); + + const response: APIGatewayProxyResult = await dagHandler.globalSnapshot( + event + ); + + expect(response.statusCode).toBe(200); + const body = validateResponseStructure(response); + + const testSnapshot = testData.global_snapshots.sort((a, b): number => { + return a.ordinal < b.ordinal ? 1 : -1; + })[0]; + + // Verify the structure of the returned data + expect(body.data.ordinal).toBe(Number(testSnapshot.ordinal)); + expect(body.data.hash).toBe(testSnapshot.hash); + expect(body.data.epochProgress).toBe(Number(testSnapshot.epoch_progress)); + expect(body.data.height).toBe(Number(testSnapshot.height)); + expect(body.data.subHeight).toBe(Number(testSnapshot.subheight)); + expect(Array.isArray(body.data.blocks)).toBe(true); + expect(body.data.timestamp).toBeDefined(); + }); + + it("should return a specific global snapshot when a valid hash is provided", async () => { + const testData = await runSeedOnce(); + + const requestedSnapshot = testData.global_snapshots[1]; + + // First get the latest global snapshot to get a valid hash + const event = createAPIGatewayEvent({ term: requestedSnapshot.hash }); + const response: APIGatewayProxyResult = await dagHandler.globalSnapshot( + event + ); + + // Assert + expect(response.statusCode).toBe(200); + const body = validateResponseStructure(response); + + // Verify we got the same snapshot + expect(body.data.hash).toBe(requestedSnapshot.hash); + expect(body.data.ordinal).toBe(Number(requestedSnapshot.ordinal)); + }); + + it("should return 404 for non-existent global snapshot", async () => { + await runSeedOnce(); + + const event = createAPIGatewayEvent({ term: "nonexistent-hash" }); + + const response: APIGatewayProxyResult = await dagHandler.globalSnapshot( + event + ); + + expect(response.statusCode).toBe(404); + }); + }); + + describe("globalSnapshotTransactions", () => { + it("should return transactions for the latest global snapshot", async () => { + const testData = await runSeedOnce(); + + const requestedSnapshot = testData.global_snapshots[1]; + + const event = createAPIGatewayEvent( + { term: requestedSnapshot.ordinal }, + { limit: "10" } + ); + + const response: APIGatewayProxyResult = await dagHandler.globalSnapshotTransactions( + event + ); + + const transactions = await prismaClient.dag_transactions.findMany({ + where: { + dag_blocks: { snapshot_hash: requestedSnapshot.hash }, + }, + }); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + expect(body.data.length).toBe(transactions.length); + + const tx = body.data[0]; + + const dbTxn = testData.dag_transactions.filter( + (_dbTxn) => _dbTxn.hash === tx.hash + )[0]; + + expect(tx.hash).toBe(dbTxn.hash); + expect(tx.source).toBe(dbTxn.source_addr); + expect(tx.destination).toBe(dbTxn.destination_addr); + expect(tx.amount).toBe(Number(dbTxn.amount)); + expect(tx.fee).toBe(Number(dbTxn.fee)); + expect(tx.snapshotHash).toBe(requestedSnapshot.hash); + expect(tx.snapshotOrdinal).toBe(Number(requestedSnapshot.ordinal)); + expect(+new Date(tx.timestamp)).toBe(+new Date(dbTxn.created_at)); + }); + }); + + describe("dagTransaction", () => { + it("should return a specific transaction by hash", async () => { + const testData = await runSeedOnce(); + + const event = createAPIGatewayEvent({ + hash: testData.dag_transactions[0].hash, + }); + + const response: APIGatewayProxyResult = await dagHandler.dagTransaction( + event + ); + + expect(response.statusCode).toBe(200); + const body = validateResponseStructure(response); + + const tx = body.data; + + const dbTxn = testData.dag_transactions.filter( + (_dbTxn) => _dbTxn.hash === tx.hash + )[0]; + + expect(tx.hash).toBe(dbTxn.hash); + expect(tx.source).toBe(dbTxn.source_addr); + expect(tx.destination).toBe(dbTxn.destination_addr); + expect(tx.amount).toBe(Number(dbTxn.amount)); + expect(tx.fee).toBe(Number(dbTxn.fee)); + expect(tx.snapshotHash).toBeDefined(); + expect(tx.snapshotOrdinal).toBeDefined(); + expect(+new Date(tx.timestamp)).toBe(+new Date(dbTxn.created_at)); + }); + + it("should return 404 for non-existent transaction", async () => { + await runSeedOnce(); + + const event = createAPIGatewayEvent({ hash: "nonexistent-hash" }); + + const response: APIGatewayProxyResult = await dagHandler.dagTransaction( + event + ); + + expect(response.statusCode).toBe(404); + }); + }); + + describe("dagBalanceByAddress", () => { + it("should return balance for a given address", async () => { + const testData = await runSeedOnce(); + + const testTxn = testData.dag_transactions[0]; + + const event = createAPIGatewayEvent({ address: testTxn.destination_addr }); + + const response: APIGatewayProxyResult = await dagHandler.dagBalanceByAddress( + event + ); + + expect(response.statusCode).toBe(200); + const body = validateResponseStructure(response)['data']; + + expect(body.balance).toBe(Number(testTxn.amount)); + expect(body.address).toBe(testTxn.destination_addr); + expect(body.ordinal).toBeDefined(); + }); + }); +}); diff --git a/tests/testUtils.ts b/tests/testUtils.ts new file mode 100644 index 0000000..db1ed93 --- /dev/null +++ b/tests/testUtils.ts @@ -0,0 +1,78 @@ +import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; + +/** + * Creates an API Gateway event object for testing handler functions + */ +export const createAPIGatewayEvent = ( + pathParameters: Record = {}, + queryStringParameters: Record = {}, + body: string | null = null +): APIGatewayProxyEvent => ({ + httpMethod: 'GET', + isBase64Encoded: false, + path: '', + resource: '', + body, + headers: {}, + multiValueHeaders: {}, + pathParameters: Object.keys(pathParameters).length ? pathParameters : null, + queryStringParameters: Object.keys(queryStringParameters).length ? queryStringParameters : null, + multiValueQueryStringParameters: null, + stageVariables: null, + requestContext: {} as any, +}); + +/** + * Helper function to parse API Gateway response body + */ +export const parseResponseBody = (response: APIGatewayProxyResult) => { + return JSON.parse(response.body); +}; + +/** + * Validates the structure of an API response + */ +export const validateResponseStructure = (response: APIGatewayProxyResult) => { + expect(response.statusCode).toBeDefined(); + expect(response.headers).toBeDefined(); + + // Check headers if they exist + if (response.headers) { + expect(response.headers['Content-Type']).toBe('application/json'); + expect(response.headers['Access-Control-Allow-Origin']).toBe('*'); + } + + expect(response.body).toBeDefined(); + + const body = parseResponseBody(response); + expect(body).toBeDefined(); + + // Success responses should have a data field + if (response.statusCode === 200) { + expect(body.data).toBeDefined(); + } + + // Error responses should have message and errors fields + if (response.statusCode >= 400) { + expect(body.message).toBeDefined(); + expect(body.errors).toBeDefined(); + } + + return body; +}; + +/** + * Validates that a paginated response has the correct structure + */ +export const validatePaginatedResponse = (response: APIGatewayProxyResult, shouldHaveNext: boolean = false) => { + const body = validateResponseStructure(response); + + expect(Array.isArray(body.data)).toBe(true); + + if (shouldHaveNext) { + expect(body.meta).toBeDefined(); + expect(body.meta.next !== undefined).toBe(true); + } + + return body; +}; From 1f82e89f34af2e9e394cd652524b238423ddc111 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Thu, 3 Apr 2025 11:48:04 -0300 Subject: [PATCH 08/63] fix typo --- src/handlers/tokenLocksHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/tokenLocksHandler.ts b/src/handlers/tokenLocksHandler.ts index 2c192fc..eeb6f19 100644 --- a/src/handlers/tokenLocksHandler.ts +++ b/src/handlers/tokenLocksHandler.ts @@ -29,7 +29,7 @@ const commonTokenUnlockResponse = (transaction) => ({ amount: transaction.amount, source: transaction.source_addr, tokenLockRef: transaction.lock_reference_hash, - imestamp: transaction.created_at, + timestamp: transaction.created_at, }); const dagTokenUnlockResponse = (transaction) => ({ From 4617dab477fdfd807f8c0d167df4e20609a31ab3 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Thu, 3 Apr 2025 16:57:30 -0300 Subject: [PATCH 09/63] update db structure --- .github/workflows/deploy-postgres.yml | 2 +- prisma/schema.prisma | 291 +++++++++++++------------- src/handlers/actionsHandler.ts | 12 +- src/handlers/allowSpendsHandler.ts | 17 +- src/handlers/dagHandler.ts | 34 ++- src/handlers/metagraphHandler.ts | 20 +- src/handlers/tokenLocksHandler.ts | 71 ++++--- src/response.ts | 16 +- test_actions.sh | 4 +- test_allowspends.sh | 10 +- test_endpoints.sh | 8 +- test_tokenlocks.sh | 8 +- 12 files changed, 263 insertions(+), 230 deletions(-) diff --git a/.github/workflows/deploy-postgres.yml b/.github/workflows/deploy-postgres.yml index 179fcb2..90f1e82 100644 --- a/.github/workflows/deploy-postgres.yml +++ b/.github/workflows/deploy-postgres.yml @@ -11,7 +11,7 @@ name: Deploy postgress app jobs: deploy: name: Deploy ${{ github.ref_name }} to ${{ inputs.environment }} - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 environment: ${{ inputs.environment }} steps: - name: Checkout code diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e7ba046..1e0aedc 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -22,11 +22,11 @@ model abstract_blocks { /// This table has subclasses and requires additional setup for migrations. Visit https://pris.ly/d/table-inheritance for more info. model abstract_transactions { - hash String @id(map: "hash_pkey") @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) + hash String @id(map: "hash_pkey") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) } model abstract_transactions_view { @@ -89,7 +89,7 @@ model block_parents { hash String @db.VarChar parent_proof_hash String @db.VarChar parent_height BigInt - abstract_blocks abstract_blocks @relation(fields: [hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "block_parents_block_fk") + abstract_blocks abstract_blocks @relation(fields: [hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "block_parents_block_fk") @@id([hash, parent_proof_hash]) @@index([hash]) @@ -105,12 +105,11 @@ model dag_allow_spend_approvers { } model dag_allow_spend_blocks { - round_id String @unique(map: "dag_allow_spend_blocks_unique") @db.Uuid - global_snapshot_hash String @id @db.VarChar - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - global_snapshot global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spend_blocks_global_snapshot_fk") - dag_allow_spends dag_allow_spends[] + round_id String @unique(map: "dag_allow_spend_blocks_unique") @db.Uuid + global_snapshot_hash String @id @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + global_snapshot global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spend_blocks_global_snapshot_fk") } model dag_allow_spends { @@ -126,14 +125,14 @@ model dag_allow_spends { last_valid_epoch_progress BigInt round_id String @db.Uuid ordinal BigInt @unique(map: "dag_allow_spends_ordinal") - snapshot_hash String? @db.VarChar + snapshot_hash String @db.VarChar dag_allow_spend_approvers dag_allow_spend_approvers[] - dag_allow_spend_block dag_allow_spend_blocks @relation(fields: [round_id], references: [round_id], onDelete: Cascade, onUpdate: NoAction, map: "allow_spends_block_fk") addresses_dag_allow_spends_destination_addrToaddresses addresses @relation("dag_allow_spends_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spends_destination_addr_fk") addresses_dag_allow_spends_source_addrToaddresses addresses @relation("dag_allow_spends_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spends_source_addr_fk") dag_expired_spend_transactions dag_expired_spend_transactions[] dag_spend_transactions dag_spend_transactions[] abstract_transactions_view abstract_transactions_view[] + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spend_blocks_global_snapshot_fk") @@index([round_id]) } @@ -146,7 +145,7 @@ model dag_balance_changes { updated_at DateTime @default(now()) @db.Timestamp(6) snapshot_ordinal BigInt addresses addresses @relation(fields: [address], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_balance_change_address_fk") - global_snapshots global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_balance_change_global_snapshot_fk") + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_balance_change_global_snapshot_fk") @@id([snapshot_ordinal, address], map: "dag_balance_change_pk") @@index([address, created_at], map: "dag_balance_changes_address_idx") @@ -158,7 +157,7 @@ model dag_blocks { created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) snapshot_hash String @db.VarChar - global_snapshots global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_block_global_snapshot_fk") + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_block_global_snapshot_fk") dag_transactions dag_transactions[] super abstract_blocks? @@ -171,7 +170,7 @@ model dag_reward_transactions { amount BigInt created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - global_snapshots global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_reward_transaction_global_snapshot_fk") + global_snapshot global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_reward_transaction_global_snapshot_fk") addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_reward_transactions_destination_addr_fk") @@id([global_snapshot_hash, destination_addr], map: "dag_reward_transaction_pk") @@ -179,17 +178,17 @@ model dag_reward_transactions { } model dag_spend_transactions { - hash String @id(map: "dag_spend_transactions_pk") @db.VarChar - source_addr String @db.VarChar + hash String @id(map: "dag_spend_transactions_pk") @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - destination_addr String? @db.VarChar - allow_spend_ref String? @db.VarChar - snapshot_hash String? @db.VarChar - dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_dag_allow_spends_fk") - addresses addresses? @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_destination_addr_fk") - abstract_transactions_view abstract_transactions_view[] + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + destination_addr String? @db.VarChar + allow_spend_ref String? @db.VarChar + snapshot_hash String? @db.VarChar + dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_dag_allow_spends_fk") + addresses addresses? @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_destination_addr_fk") + abstract_transactions_view abstract_transactions_view? } model dag_token_lock_blocks { @@ -202,40 +201,38 @@ model dag_token_lock_blocks { } model dag_token_locks { - hash String @id(map: "dag_token_locks_pk") @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - ordinal BigInt - unlock_epoch BigInt - round_id String @db.Uuid - global_snapshot_hash String @db.VarChar - snapshot_hash String? @db.VarChar - dag_token_lock_block dag_token_lock_blocks @relation(fields: [round_id], references: [round_id], onDelete: Cascade, onUpdate: NoAction, map: "token_lock_block_fk") - addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_locks_source_addr_fk") - dag_token_unlocks dag_token_unlocks? + hash String @id(map: "dag_token_locks_pk") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + ordinal BigInt + unlock_epoch BigInt? + round_id String @db.Uuid + snapshot_hash String @db.VarChar + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_locks_source_addr_fk") + dag_token_unlocks dag_token_unlocks? + abstract_transactions_view abstract_transactions_view? + dag_token_lock_blocks dag_token_lock_blocks? @relation(fields: [round_id], references: [round_id]) + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_lock_global_snapshot_fk") - abstract_transactions_view abstract_transactions_view[] + @@unique([ordinal], map: "dag_token_locks_unique") } model dag_token_unlocks { - hash String @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - lock_reference_ordinal BigInt - lock_reference_hash String @db.VarChar - snapshot_hash String? @db.VarChar - dag_token_lock dag_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_unlocks_token_locks_fk") - - addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "ddag_token_unlocks_address_fk") - abstract_transactions_view abstract_transactions_view[] - - @@id([hash], map: "dag_token_unlocks_pk") + hash String @id(map: "dag_token_unlocks_pk") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + lock_reference_hash String @db.VarChar + dag_token_lock dag_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "dag_token_unlocks_token_locks_fk") + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "ddag_token_unlocks_address_fk") + abstract_transactions_view abstract_transactions_view? + snapshot_hash String @db.VarChar + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_unlock_global_snapshot_fk") + @@unique([lock_reference_hash]) - @@unique([lock_reference_ordinal]) } model dag_transactions { @@ -251,19 +248,18 @@ model dag_transactions { parent_hash String? @db.VarChar ordinal BigInt block_hash String @db.VarChar - snapshot_hash String? @db.VarChar dag_blocks dag_blocks @relation(fields: [block_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_transaction_dag_block_fk") addresses_dag_transactions_destination_addrToaddresses addresses @relation("dag_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_transactions_destination_addr_fk") addresses_dag_transactions_source_addrToaddresses addresses @relation("dag_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_transactions_source_addr_fk") } model global_snapshot_proofs { - id String @db.VarChar - signature String @db.VarChar - snapshot_hash String @db.VarChar - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - global_snapshots global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "proof_global_snapshot_fk") + id String @db.VarChar + signature String @db.VarChar + snapshot_hash String @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "proof_global_snapshot_fk") @@id([snapshot_hash, id], map: "proof_pk") } @@ -286,6 +282,9 @@ model global_snapshots { dag_token_lock_blocks dag_token_lock_blocks? global_snapshot_proofs global_snapshot_proofs[] metagraph_snapshots metagraph_snapshots[] + dag_allow_spends dag_allow_spends[] + dag_token_locks dag_token_locks[] + dag_token_unlocks dag_token_unlocks[] } model metagraph_allow_spend_approvers { @@ -321,7 +320,7 @@ model metagraph_allow_spends { last_valid_epoch_progress BigInt round_id String @db.Uuid ordinal BigInt @unique(map: "metagraph_allow_spends_ordinal") - snapshot_hash String? @db.VarChar + snapshot_hash String @db.VarChar metagraph_allow_spend_approvers metagraph_allow_spend_approvers[] metagraph_allow_spend_block metagraph_allow_spend_blocks @relation(fields: [round_id], references: [round_id], onDelete: Cascade, onUpdate: NoAction, map: "allow_spends_block_fk") addresses_metagraph_allow_spends_destination_addrToaddresses addresses @relation("metagraph_allow_spends_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_allow_spends_destination_addr_fk") @@ -329,7 +328,8 @@ model metagraph_allow_spends { metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") metagraph_expired_spend_transactions metagraph_expired_spend_transactions[] metagraph_spend_transactions metagraph_spend_transactions[] - abstract_transactions_view abstract_transactions_view[] + abstract_transactions_view abstract_transactions_view? + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_allow_spend_blocks_metagraph_snapshot_fk") } model metagraph_balance_changes { @@ -339,12 +339,12 @@ model metagraph_balance_changes { balance BigInt created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_snapshot_ordinal BigInt + snapshot_ordinal BigInt @map("metagraph_snapshot_ordinal") addresses addresses @relation(fields: [address], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "address_fk") metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_balance_change_metagraph_snapshot_fk") metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") - @@id([metagraph_id, address, metagraph_snapshot_ordinal], map: "metagraph_balance_change_pk") + @@id([metagraph_id, address, snapshot_ordinal], map: "metagraph_balance_change_pk") } model metagraph_blocks { @@ -364,22 +364,21 @@ model metagraph_blocks { } model metagraph_fee_transactions { - hash String @db.VarChar - source_addr String @db.VarChar + hash String @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar - metagraph_snapshot_hash String @db.VarChar - destination_addr String @db.VarChar - data_update_ref String? @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + metagraph_snapshot_hash String @db.VarChar + destination_addr String @db.VarChar + data_update_ref String? @db.VarChar metagraph_snapshot_ordinal BigInt? - snapshot_hash String? @db.VarChar - metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "fee_transaction_metagraph_snapshot_fk") - addresses_metagraph_fee_transactions_destination_addrToaddresses addresses @relation("metagraph_fee_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_fee_transactions_destination_addr_fk") - addresses_metagraph_fee_transactions_source_addrToaddresses addresses @relation("metagraph_fee_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_fee_transactions_source_addr_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") - abstract_transactions_view abstract_transactions_view[] + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "fee_transaction_metagraph_snapshot_fk") + addresses_metagraph_fee_transactions_destination_addrToaddresses addresses @relation("metagraph_fee_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_fee_transactions_destination_addr_fk") + addresses_metagraph_fee_transactions_source_addrToaddresses addresses @relation("metagraph_fee_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_fee_transactions_source_addr_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") + abstract_transactions_view abstract_transactions_view? @@id([hash], map: "fee_transaction_pk") } @@ -419,80 +418,83 @@ model metagraph_snapshots { metagraph_fee_transactions metagraph_fee_transactions[] metagraph_reward_transactions metagraph_reward_transactions[] metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") - global_snapshots global_snapshots? @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_snapshots_global_snapshots_fk") + global_snapshot global_snapshots? @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_snapshots_global_snapshots_fk") addresses_metagraph_snapshots_owner_addressToaddresses addresses? @relation("metagraph_snapshots_owner_addressToaddresses", fields: [owner_address], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "owner_address_fk") addresses_metagraph_snapshots_staking_addressToaddresses addresses? @relation("metagraph_snapshots_staking_addressToaddresses", fields: [staking_address], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "staking_address_fk") metagraph_token_lock_blocks metagraph_token_lock_blocks? + metagraph_token_locks metagraph_token_locks[] + metagraph_token_unlocks metagraph_token_unlocks[] + metagraph_allow_spends metagraph_allow_spends[] @@id([metagraph_id, hash], map: "metagraph_snapshot_pk") @@unique([metagraph_id, ordinal], map: "metagraph_snapshot_unique") } model metagraph_spend_transactions { - hash String @id(map: "metagraph_spend_transactions_pk") @db.VarChar - source_addr String @db.VarChar + hash String @id(map: "metagraph_spend_transactions_pk") @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar - destination_addr String @db.VarChar - allow_spend_ref String? @db.VarChar - snapshot_hash String? @db.VarChar - metagraph_allow_spend metagraph_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "mg_spend_transactions_mg_allow_spends_fk") - addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_destination_addr_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_metagraph_id_fk") - abstract_transactions_view abstract_transactions_view[] + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + destination_addr String @db.VarChar + allow_spend_ref String? @db.VarChar + snapshot_hash String? @db.VarChar + metagraph_allow_spend metagraph_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "mg_spend_transactions_mg_allow_spends_fk") + addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_destination_addr_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_metagraph_id_fk") + abstract_transactions_view abstract_transactions_view? } model metagraph_token_lock_blocks { - metagraph_id String @db.VarChar - metagraph_snapshot_hash String @db.VarChar - round_id String @db.Uuid - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_lock_blocks_metagraph_id_fk") - metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_lock_blocks_metagraph_snapshot_fk") - metagraph_token_locks metagraph_token_locks[] + metagraph_id String @db.VarChar + metagraph_snapshot_hash String @db.VarChar + round_id String @db.Uuid + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_lock_blocks_metagraph_id_fk") + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_lock_blocks_metagraph_snapshot_fk") @@id([metagraph_id, metagraph_snapshot_hash]) @@unique([metagraph_id, round_id], map: "metagraph_token_lock_blocks_unique") } model metagraph_token_locks { - hash String @db.VarChar - source_addr String @db.VarChar + hash String @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar ordinal BigInt - unlock_epoch BigInt - round_id String @db.Uuid - snapshot_hash String? @db.VarChar - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") - metagraph_token_lock_block metagraph_token_lock_blocks @relation(fields: [metagraph_id, round_id], references: [metagraph_id, round_id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_lock_token_lock_blocks_fk") - addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_locks_source_addr_fk") - abstract_transactions_view abstract_transactions_view[] + unlock_epoch BigInt? + round_id String @db.Uuid + snapshot_hash String @db.VarChar + metagraph metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_locks_source_addr_fk") + abstract_transactions_view abstract_transactions_view? metagraph_token_unlocks metagraph_token_unlocks? + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_lock_blocks_metagraph_snapshot_fk") @@id([hash], map: "metagraph_token_locks_pk") @@unique([metagraph_id, ordinal], map: "metagraph_token_locks_unique") } model metagraph_token_unlocks { - hash String @db.VarChar - source_addr String @db.VarChar + hash String @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar lock_reference_ordinal BigInt - lock_reference_hash String @db.VarChar - snapshot_hash String? @db.VarChar - addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "address_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") - token_lock metagraph_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_token_unlocks_token_locks_fk") - abstract_transactions_view abstract_transactions_view[] + lock_reference_hash String @db.VarChar + snapshot_hash String @db.VarChar + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "address_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") + token_lock metagraph_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_token_unlocks_token_locks_fk") + abstract_transactions_view abstract_transactions_view? + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_unlock_blocks_metagraph_snapshot_fk") @@id([hash], map: "metagraph_token_unlocks_pk") @@unique([lock_reference_hash]) @@ -513,7 +515,6 @@ model metagraph_transactions { parent_hash String @db.VarChar ordinal BigInt block_hash String @db.VarChar - snapshot_hash String? @db.VarChar metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") metagraph_blocks metagraph_blocks @relation(fields: [metagraph_id, block_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transaction_metagraph_block_fk") addresses_metagraph_transactions_destination_addrToaddresses addresses @relation("metagraph_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transactions_destination_addrfk") @@ -540,26 +541,26 @@ model metagraphs { } model dag_expired_spend_transactions { - hash String @id(map: "dag_expired_spend_transactions_pk") @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - allow_spend_ref String? @db.VarChar - snapshot_hash String? @db.VarChar - abstract_transactions_view abstract_transactions_view[] - dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_expired_spend_transactions_dag_allow_spends_fk") + hash String @id(map: "dag_expired_spend_transactions_pk") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + allow_spend_ref String? @db.VarChar + snapshot_hash String? @db.VarChar + abstract_transactions_view abstract_transactions_view? + dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_expired_spend_transactions_dag_allow_spends_fk") } model metagraph_expired_spend_transactions { - hash String @id(map: "metagraph_expired_spend_transactions_pk") @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar - allow_spend_ref String? @db.VarChar - snapshot_hash String? @db.VarChar - abstract_transactions_view abstract_transactions_view[] - metagraph_allow_spend metagraph_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_expired_spend_transactions_metagraph_allow_spends_fk") + hash String @id(map: "metagraph_expired_spend_transactions_pk") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + allow_spend_ref String? @db.VarChar + snapshot_hash String? @db.VarChar + abstract_transactions_view abstract_transactions_view? + metagraph_allow_spend metagraph_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_expired_spend_transactions_metagraph_allow_spends_fk") } diff --git a/src/handlers/actionsHandler.ts b/src/handlers/actionsHandler.ts index ba63de9..36ecc07 100644 --- a/src/handlers/actionsHandler.ts +++ b/src/handlers/actionsHandler.ts @@ -103,25 +103,19 @@ export const dagActions = async ( const tokenLockGlobalSnapshotCond = (filter) => ({ dag_token_lock: { - dag_token_lock_block: { global_snapshot: filter, - }, }, }); const tokenUnlockGlobalSnapshotCond = (filter) => ({ dag_token_unlock: { dag_token_lock: { - dag_token_lock_block: { global_snapshot: filter, - }, }, }, }); const allowSpendGlobalSnapshotCond = (filter) => ({ dag_allow_spend: { - dag_allow_spend_block: { global_snapshot: filter, - }, }, }); const spendTxGlobalSnapshotCond = (filter) => ({ @@ -220,21 +214,19 @@ const metagraphIdCond = (metagraph_id) => ({ const tokenLockMetagraphSnapshotCond = (filter) => ({ metagraph_token_lock: { - metagraph_token_lock_block: { metagraph_snapshot: filter }, + metagraph_snapshot: filter, }, }); const tokenUnlockMetagraphSnapshotCond = (filter) => ({ metagraph_token_unlock: { token_lock: { - metagraph_token_lock_block: { metagraph_snapshot: filter }, + metagraph_snapshot: filter, }, }, }); const allowSpendMetagraphSnapshotCond = (filter) => ({ metagraph_allow_spend: { - metagraph_allow_spend_block: { metagraph_snapshot: filter, - }, }, }); const spendTxMetagraphSnapshotCond = (filter) => ({ diff --git a/src/handlers/allowSpendsHandler.ts b/src/handlers/allowSpendsHandler.ts index f9dd012..1e2bc88 100644 --- a/src/handlers/allowSpendsHandler.ts +++ b/src/handlers/allowSpendsHandler.ts @@ -18,9 +18,8 @@ const allowSpendResponse = (transaction) => ({ source: transaction.source_addr, destination: transaction.destination_addr, lastValidEpochProgress: transaction.last_valid_epoch_progress, - round: transaction.round_id, fee: transaction.fee, - snapshot: transaction.snapshot_hash, + snapshotHash: transaction.snapshot_hash, timestamp: transaction.created_at, }); @@ -33,7 +32,7 @@ const spendTransactionResponse = (transaction) => ({ source: transaction.source_addr, destination: transaction.destination_addr, allowSpendRef: transaction.allow_spend_ref, - snapshot: transaction.snapshot_hash, + snapshotHash: transaction.snapshot_hash, timestamp: transaction.created_at, }); @@ -45,7 +44,7 @@ const spendExpiredResponse = (transaction) => ({ amount: transaction.amount, source: transaction.source_addr, allowSpendRef: transaction.allow_spend_ref, - snapshot: transaction.snapshot_hash, + snapshotHash: transaction.snapshot_hash, timestamp: transaction.created_at, }); @@ -76,7 +75,7 @@ export const globalSnapshotAllowSpends = async ( toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, { - where: { dag_allow_spend_block: { global_snapshot: filter } }, + where: { global_snapshot: filter }, orderBy: { created_at: "desc" }, }, prisma.dag_allow_spends.findMany, @@ -141,9 +140,7 @@ export const globalSnapshotSpendTransactions = async ( fromCreatedAtOrdinalCursor, { where: { - dag_allow_spend: { - dag_allow_spend_block: { global_snapshot: filter }, - }, + dag_allow_spend: { global_snapshot: filter }, }, orderBy: { created_at: "desc" }, }, @@ -209,9 +206,7 @@ export const globalSnapshotAllowSpendExpirations = async ( fromCreatedAtOrdinalCursor, { where: { - dag_allow_spend: { - dag_allow_spend_block: { global_snapshot: filter }, - }, + dag_allow_spend: { global_snapshot: filter }, }, orderBy: { created_at: "desc" }, }, diff --git a/src/handlers/dagHandler.ts b/src/handlers/dagHandler.ts index 037bbc1..4cd0137 100644 --- a/src/handlers/dagHandler.ts +++ b/src/handlers/dagHandler.ts @@ -31,15 +31,15 @@ const globalSnapshotExists = async (term) => { const latestGlobalSnapshot = async () => { return prisma.global_snapshots.findFirst({ - select: { hash: true }, + select: { hash: true, ordinal: true }, orderBy: { ordinal: "desc" }, }); }; const globalSnapshotWhere = async (term) => { if (term == "latest") { - const latestSnapshotHash = await latestGlobalSnapshot(); - return { hash: latestSnapshotHash }; + const hash = (await latestGlobalSnapshot())?.hash; + return { hash }; } else { return extractHashOrdinal(term); } @@ -114,7 +114,7 @@ export const globalSnapshotRewards = async ( toCursor, fromCursor, { - where: { global_snapshots: { ...globalSnapshotWhere(term) } }, + where: { global_snapshot: { ...globalSnapshotWhere(term) } }, orderBy: [{ destination_addr: "asc" }], }, prisma.dag_reward_transactions.findMany, @@ -137,12 +137,12 @@ export const globalSnapshotTransactions = async ( const query = { where: { - dag_blocks: { global_snapshots: { ...globalSnapshotWhere(term) } }, + dag_blocks: { global_snapshot: { ...globalSnapshotWhere(term) } }, }, include: { dag_blocks: { select: { - global_snapshots: { select: { hash: true, ordinal: true } }, + global_snapshot: { select: { hash: true, ordinal: true } }, }, }, }, @@ -172,7 +172,7 @@ export const dagBlock = async ( where: { hash }, include: { dag_transactions: { select: { hash: true } }, - global_snapshots: true, + global_snapshot: true, super: { include: { block_parents: true } }, }, }); @@ -192,7 +192,7 @@ const dagTtransactionsQuery = async ( include: { dag_blocks: { include: { - global_snapshots: { select: { hash: true, ordinal: true } }, + global_snapshot: { select: { hash: true, ordinal: true } }, }, }, }, @@ -239,7 +239,7 @@ export const dagTransaction = async ( include: { dag_blocks: { include: { - global_snapshots: { select: { hash: true, ordinal: true } }, + global_snapshot: { select: { hash: true, ordinal: true } }, }, }, }, @@ -295,6 +295,16 @@ export const dagTransactionsByDestination = async ( } }; +const lastOrdinal = () => {}; + +const balanceOrZeroFn = async (balance, address) => { + if (balance === null) { + const snapshot_ordinal = (await latestGlobalSnapshot())?.ordinal; + return { balance: 0, address, snapshot_ordinal }; + } + return balance; +}; + export const dagBalanceByAddress = async ( event: APIGatewayProxyEvent ): Promise => { @@ -306,12 +316,14 @@ export const dagBalanceByAddress = async ( ? { snapshot_ordinal: { lte: ordinalNbr } } : {}; - const balances = await prisma.dag_balance_changes.findFirst({ + const balance = await prisma.dag_balance_changes.findFirst({ where: { address, ...ordinalCondition }, orderBy: { snapshot_ordinal: "desc" }, }); - return respond(balances, balanceResponse); + const balanceOrZero = await balanceOrZeroFn(balance, address); + + return respond(balanceOrZero, balanceResponse); } catch (error) { return handleError(error); } diff --git a/src/handlers/metagraphHandler.ts b/src/handlers/metagraphHandler.ts index e6735d0..0b0feab 100644 --- a/src/handlers/metagraphHandler.ts +++ b/src/handlers/metagraphHandler.ts @@ -28,14 +28,14 @@ const prisma = new PrismaClient(); const latestMetagraphSnapshot = async () => { return prisma.metagraph_snapshots.findFirst({ - select: { hash: true }, + select: { hash: true, ordinal: true }, orderBy: { ordinal: "desc" }, }); }; const metagraphSnapshotWhere = async (term) => { if (term == "latest") { - const latestSnapshotHash = await latestMetagraphSnapshot(); + const latestSnapshotHash = (await latestMetagraphSnapshot())?.hash; return { hash: latestSnapshotHash }; } else { return extractHashOrdinal(term); @@ -363,6 +363,14 @@ export const currencyTransactionsByDestination = async ( } }; +const balanceOrZeroFn = async (balance, address) => { + if (balance === null) { + const snapshot_ordinal = (await latestMetagraphSnapshot())?.ordinal; + return { balance: 0, address, snapshot_ordinal }; + } + return balance; +}; + export const currencyBalanceByAddress = async ( event: APIGatewayProxyEvent ): Promise => { @@ -375,7 +383,7 @@ export const currencyBalanceByAddress = async ( const ordinalNbr = toNumber(ordinal); const ordinalCondition = isFinite(ordinalNbr) - ? { metagraph_snapshot_ordinal: { lte: ordinalNbr } } + ? { snapshot_ordinal: { lte: ordinalNbr } } : {}; const balance = await prisma.metagraph_balance_changes.findFirst({ @@ -384,10 +392,12 @@ export const currencyBalanceByAddress = async ( address, ...ordinalCondition, }, - orderBy: { metagraph_snapshot_ordinal: "desc" }, + orderBy: { snapshot_ordinal: "desc" }, }); - return respond(balance, balanceResponse); + const balanceOrZero = await balanceOrZeroFn(balance, address); + + return respond(balanceOrZero, balanceResponse); } catch (error) { return handleError(error); } diff --git a/src/handlers/tokenLocksHandler.ts b/src/handlers/tokenLocksHandler.ts index 75770a5..2c192fc 100644 --- a/src/handlers/tokenLocksHandler.ts +++ b/src/handlers/tokenLocksHandler.ts @@ -17,22 +17,35 @@ const tokenLockResponse = (transaction) => ({ source: transaction.source_addr, destination: transaction.destination_addr, unlockEpoch: transaction.unlock_epoch, - parentHash: transaction.lock_reference_hash, + parentHash: transaction.parent_hash, timestamp: transaction.created_at, }); const tokenLockResponses = (txs) => txs.map(tokenLockResponse); -const tokenUnlockResponse = (transaction) => ({ +const commonTokenUnlockResponse = (transaction) => ({ currencyId: transaction.currencyId, hash: transaction.hash, amount: transaction.amount, source: transaction.source_addr, - timestamp: transaction.created_at, - lockOrdinal: transaction.lock_reference_ordinal, + tokenLockRef: transaction.lock_reference_hash, + imestamp: transaction.created_at, +}); + +const dagTokenUnlockResponse = (transaction) => ({ + ...commonTokenUnlockResponse(transaction), + globalSnapshotOrdinal: transaction.global_snapshot.ordinal, +}); + +const dagTokenUnlockResponses = (txs) => txs.map(dagTokenUnlockResponse); + +const metagraphTokenUnlockResponse = (transaction) => ({ + ...commonTokenUnlockResponse(transaction), + metagraphSnapshotOrdinal: transaction.metagraph_snapshot.ordinal, }); -const tokenUnlockResponses = (txs) => txs.map(tokenUnlockResponse); +const metagraphTokenUnlockResponses = (txs) => + txs.map(metagraphTokenUnlockResponse); export const tokenLocks = async ( event: APIGatewayProxyEvent @@ -60,9 +73,7 @@ export const globalSnapshotTokenLocks = async ( fromCreatedAtOrdinalCursor, { where: { - dag_token_lock_block: { - global_snapshot: filter, - }, + global_snapshot: filter, }, orderBy: { created_at: "desc" }, }, @@ -100,9 +111,12 @@ export const tokenUnlocks = async ( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { orderBy: { created_at: "desc" } }, + { + include: { global_snapshot: { select: { ordinal: true } } }, + orderBy: { created_at: "desc" }, + }, prisma.dag_token_unlocks.findMany, - tokenUnlockResponses + dagTokenUnlockResponses ); }; @@ -119,16 +133,13 @@ export const globalSnapshotTokenUnlocks = async ( fromCreatedAtOrdinalCursor, { where: { - dag_token_lock: { - dag_token_lock_block: { - global_snapshot: filter, - }, - }, + global_snapshot: filter, }, + include: { global_snapshot: { select: { ordinal: true } } }, orderBy: { created_at: "desc" }, }, prisma.dag_token_unlocks.findMany, - tokenUnlockResponses + dagTokenUnlockResponses ); } catch (error) { return handleError(error); @@ -145,9 +156,13 @@ export const addressTokenUnlocks = async ( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { where: { source_addr: address }, orderBy: { created_at: "desc" } }, + { + where: { source_addr: address }, + include: { global_snapshot: { select: { ordinal: true } } }, + orderBy: { created_at: "desc" }, + }, prisma.dag_token_unlocks.findMany, - tokenUnlockResponses + dagTokenUnlockResponses ); } catch (error) { return handleError(error); @@ -184,7 +199,7 @@ export const metagraphSnapshotTokenLocks = async ( { where: { metagraph_id, - metagraph_token_lock_block: { metagraph_snapshot: filter }, + metagraph_snapshot: filter, }, orderBy: { created_at: "desc" }, }, @@ -226,9 +241,13 @@ export const metagraphTokenUnlocks = async ( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { where: { metagraph_id }, orderBy: { created_at: "desc" } }, + { + where: { metagraph_id }, + include: { metagraph_snapshot: { select: { ordinal: true } } }, + orderBy: { created_at: "desc" }, + }, prisma.metagraph_token_unlocks.findMany, - tokenUnlockResponses + metagraphTokenUnlockResponses ); }; @@ -247,14 +266,13 @@ export const metagraphSnapshotTokenUnlocks = async ( { where: { metagraph_id, - token_lock: { - metagraph_token_lock_block: { metagraph_snapshot: filter }, - }, + metagraph_snapshot: filter, }, + include: { metagraph_snapshot: { select: { ordinal: true } } }, orderBy: { created_at: "desc" }, }, prisma.metagraph_token_unlocks.findMany, - tokenUnlockResponses + metagraphTokenUnlockResponses ); } catch (error) { return handleError(error); @@ -273,10 +291,11 @@ export const metagraphAddressTokenUnlocks = async ( fromCreatedAtOrdinalCursor, { where: { metagraph_id, source_addr: address }, + include: { metagraph_snapshot: { select: { ordinal: true } } }, orderBy: { created_at: "desc" }, }, prisma.metagraph_token_unlocks.findMany, - tokenUnlockResponses + metagraphTokenUnlockResponses ); } catch (error) { return handleError(error); diff --git a/src/response.ts b/src/response.ts index 7bf09f7..f9c07f8 100644 --- a/src/response.ts +++ b/src/response.ts @@ -27,6 +27,7 @@ const commonSnapshotResponse = (snapshot, blocksProperty) => ({ subHeight: snapshot.subheight, lastSnapshotHash: snapshot.last_snapshot_hash, blocks: snapshot[blocksProperty].map((b) => b.hash), + epochProgress: snapshot.epoch_progress, timestamp: snapshot.created_at, }); @@ -44,7 +45,7 @@ export const rewardResponse = (reward) => ({ export const dagTransactionsResponse = (ts) => ts.map(dagTransactionResponse); export const dagTransactionResponse = (t) => - transactionResponse(t, t.dag_blocks.global_snapshots); + transactionResponse(t, t.dag_blocks.global_snapshot); const transactionResponse = (transaction, snapshot) => ({ hash: transaction.hash, @@ -73,8 +74,8 @@ const blockResponse = (block) => ({ export const dagBlockResponse = (block) => ({ ...blockResponse(block), transactions: block.dag_transactions.map((tx) => tx.hash), - snapshotHash: block.global_snapshots.hash, - snapshotOrdinal: block.global_snapshots.ordinal, + snapshotHash: block.global_snapshot.hash, + snapshotOrdinal: block.global_snapshot.ordinal, }); export const blockParentResponse = (block_parent) => ({ @@ -141,20 +142,23 @@ export const successResponse = (data: any): APIGatewayProxyResult => ({ export const notFoundResponse = (): APIGatewayProxyResult => ({ statusCode: 404, - body: JSON.stringify({ message: "Not found" }), + body: JSON.stringify({ message: "Not found", errors: [""] }), }); export const missingParameterResponse = ( param: string ): APIGatewayProxyResult => ({ statusCode: 400, - body: JSON.stringify({ message: `Missing parameter: ${param}` }), + body: JSON.stringify({ + message: `Missing parameter: ${param}`, + errors: [""], + }), }); export const handleError = (error: any): APIGatewayProxyResult => { console.error(error); return { statusCode: 500, - body: JSON.stringify({ message: "Internal Server Error" }), + body: JSON.stringify({ message: "Internal Server Error", errors: [error] }), }; }; diff --git a/test_actions.sh b/test_actions.sh index eda18ea..9b98724 100644 --- a/test_actions.sh +++ b/test_actions.sh @@ -2,11 +2,11 @@ curl http://localhost:3001/actions #OK echo -e "\n" # Get global snapshot actions for a specific term -curl http://localhost:3001/global-snapshots/618a6f6bedba2c8a84ca3513f1cf0f6fe9eafb6f7872eecf2e222216351708b6/actions +curl http://localhost:3001/global-snapshots/80f93d4ef86dd1bf69648b5070e27d545d23ae578925198cc45dc17e02128312/actions #OK echo -e "\n" # Get address actions for a specific address -curl http://localhost:3001/addresses/DAG04Qob76gZG7D5qZu9oeZvr8WFxs53Md383q4x/actions #OK +curl http://localhost:3001/addresses/DAG77zerQ2BUVhtVgkmseihkEfLXieBBm57vqA4J/actions #OK echo -e "\n" # Get currency actions for a specific metagraph ID curl http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/actions #OK diff --git a/test_allowspends.sh b/test_allowspends.sh index 5bb7cf9..136342e 100644 --- a/test_allowspends.sh +++ b/test_allowspends.sh @@ -8,14 +8,14 @@ curl -X GET "http://localhost:3001/spend-transactions" curl -X GET "http://localhost:3001/allow-spend-expirations" echo "Global Snapshot Endpoints" -curl -X GET "http://localhost:3001/global-snapshots/618a6f6bedba2c8a84ca3513f1cf0f6fe9eafb6f7872eecf2e222216351708b6/allow-spends" +curl -X GET "http://localhost:3001/global-snapshots/80f93d4ef86dd1bf69648b5070e27d545d23ae578925198cc45dc17e02128312/allow-spends" curl -X GET "http://localhost:3001/global-snapshots/618a6f6bedba2c8a84ca3513f1cf0f6fe9eafb6f7872eecf2e222216351708b6/spend-transactions" -curl -X GET "http://localhost:3001/global-snapshots/618a6f6bedba2c8a84ca3513f1cf0f6fe9eafb6f7872eecf2e222216351708b6/allow-spend-expirations" +curl -X GET "http://localhost:3001/global-snapshots/672c3eb45ff5527a773b02f7722de762d0232d0f347cb1871ffa7226d9fdb18a/allow-spend-expirations" echo "Address-Specific Endpoints" -curl -X GET "http://localhost:3001/addresses/DAG04Qob76gZG7D5qZu9oeZvr8WFxs53Md383q4x/allow-spends" -curl -X GET "http://localhost:3001/addresses/DAG04Qob76gZG7D5qZu9oeZvr8WFxs53Md383q4x/spend-transactions" -curl -X GET "http://localhost:3001/addresses/DAG04Qob76gZG7D5qZu9oeZvr8WFxs53Md383q4x/allow-spend-expirations" +curl -X GET "http://localhost:3001/addresses/DAG77zerQ2BUVhtVgkmseihkEfLXieBBm57vqA4J/allow-spends" +curl -X GET "http://localhost:3001/addresses/DAG77zerQ2BUVhtVgkmseihkEfLXieBBm57vqA4J/spend-transactions" +curl -X GET "http://localhost:3001/addresses/DAG77zerQ2BUVhtVgkmseihkEfLXieBBm57vqA4J/allow-spend-expirations" echo "Currency (Metagraph) Endpoints" curl -X GET "http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/allow-spends" diff --git a/test_endpoints.sh b/test_endpoints.sh index 57ceb38..f7a0175 100644 --- a/test_endpoints.sh +++ b/test_endpoints.sh @@ -2,12 +2,12 @@ # Global Snapshots curl http://localhost:3001/global-snapshots #ok -curl http://localhost:3001/global-snapshots/695097c844c520c40577f168b85b994558024b44fd64c245154625c89fda024f #ok -curl http://localhost:3001/global-snapshots/155849 #ok +curl http://localhost:3001/global-snapshots/c54b8f21c967af9c956f7f2ebd81a20550a71357d805574c557e5128ec4f26c0 #ok +curl http://localhost:3001/global-snapshots/2528938 #ok curl http://localhost:3001/global-snapshots/latest -curl http://localhost:3001/global-snapshots/155849/rewards #ok -curl http://localhost:3001/global-snapshots/695097c844c520c40577f168b85b994558024b44fd64c245154625c89fda024f/rewards #ok +curl http://localhost:3001/global-snapshots/2528938/rewards #ok +curl http://localhost:3001/global-snapshots/c54b8f21c967af9c956f7f2ebd81a20550a71357d805574c557e5128ec4f26c0/rewards #ok curl http://localhost:3001/global-snapshots/latest/rewards # ok curl http://localhost:3001/global-snapshots/390792/transactions #ok diff --git a/test_tokenlocks.sh b/test_tokenlocks.sh index 3fd04fe..4c301d0 100644 --- a/test_tokenlocks.sh +++ b/test_tokenlocks.sh @@ -7,11 +7,11 @@ curl http://localhost:3001/token-locks -H "Content-Type: application/json" echo -e "\n" echo "Testing globalSnapshotTokenLocks endpoint..." -curl http://localhost:3001/global-snapshots/3cdde2bc02d9ad3da07fe52686a51db448129d9adf3167747be77e31f951c428/token-locks -H "Content-Type: application/json" +curl http://localhost:3001/global-snapshots/c3071648a319313afdecad217f196558a7893dde2bc78ee56fbb0d0a5b73700b/token-locks -H "Content-Type: application/json" echo -e "\n" echo "Testing addressTokenLocks endpoint..." -curl http://localhost:3001/addresses/DAG5T61H6RNLR4wLueNNZo8JR7fDKuGQSZoGTVki/token-locks -H "Content-Type: application/json" +curl http://localhost:3001/addresses/DAG5sz69nNwGF8ypn1yukFpg2pVJpdx5mnf1PJVc/token-locks -H "Content-Type: application/json" echo -e "\n" echo "Testing tokenUnlocks endpoint..." @@ -19,11 +19,11 @@ curl http://localhost:3001/token-unlocks -H"Content-Type: application/json" echo -e "\n" echo "Testing globalSnapshotTokenUnlocks endpoint..." -curl http://localhost:3001/global-snapshots/3cdde2bc02d9ad3da07fe52686a51db448129d9adf3167747be77e31f951c428/token-unlocks -H"Content-Type: application/json" +curl http://localhost:3001/global-snapshots/f5524dc1bff16b587732dc0dd36e6eb0e96fb067f9e0ec93b1cb9b55e8c2f526/token-unlocks -H"Content-Type: application/json" echo -e "\n" echo "Testing addressTokenUnlocks endpoint..." -curl http://localhost:3001/addresses/DAG4AqNNL2E7TWBUvQRxSPhraS2Lnr5xPM6xtVbs/token-unlocks -H"Content-Type: application/json" +curl http://localhost:3001/addresses/DAG77zerQ2BUVhtVgkmseihkEfLXieBBm57vqA4J/token-unlocks -H"Content-Type: application/json" echo -e "\n" echo "Testing currencyTokenLocks endpoint..." From 0760ad32528749e2fc041d0ee6db5f1777b8b534 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Thu, 3 Apr 2025 18:40:55 -0300 Subject: [PATCH 10/63] fix typo --- src/handlers/tokenLocksHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/tokenLocksHandler.ts b/src/handlers/tokenLocksHandler.ts index 2c192fc..eeb6f19 100644 --- a/src/handlers/tokenLocksHandler.ts +++ b/src/handlers/tokenLocksHandler.ts @@ -29,7 +29,7 @@ const commonTokenUnlockResponse = (transaction) => ({ amount: transaction.amount, source: transaction.source_addr, tokenLockRef: transaction.lock_reference_hash, - imestamp: transaction.created_at, + timestamp: transaction.created_at, }); const dagTokenUnlockResponse = (transaction) => ({ From 1e94422a0d632296fa9d35b8982f257e64e14f00 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Thu, 3 Apr 2025 14:59:17 -0300 Subject: [PATCH 11/63] Adjust fields optionality --- prisma/schema.prisma | 24 +++++++++++++----------- src/handlers/actionsHandler.ts | 6 +++--- src/handlers/tokenLocksHandler.ts | 5 ++--- src/response.ts | 4 ++-- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1e0aedc..9661d0e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -184,8 +184,8 @@ model dag_spend_transactions { created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) destination_addr String? @db.VarChar - allow_spend_ref String? @db.VarChar - snapshot_hash String? @db.VarChar + allow_spend_ref String @db.VarChar + snapshot_hash String @db.VarChar dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_dag_allow_spends_fk") addresses addresses? @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_destination_addr_fk") abstract_transactions_view abstract_transactions_view? @@ -210,6 +210,7 @@ model dag_token_locks { unlock_epoch BigInt? round_id String @db.Uuid snapshot_hash String @db.VarChar + parent_hash String? @db.VarChar addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_locks_source_addr_fk") dag_token_unlocks dag_token_unlocks? abstract_transactions_view abstract_transactions_view? @@ -270,8 +271,8 @@ model global_snapshots { height BigInt subheight Int last_snapshot_hash String @db.VarChar - metagraph_snapshot_count BigInt? - epoch_progress BigInt? + metagraph_snapshot_count BigInt + epoch_progress BigInt version String @db.VarChar created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) @@ -470,6 +471,7 @@ model metagraph_token_locks { unlock_epoch BigInt? round_id String @db.Uuid snapshot_hash String @db.VarChar + parent_hash String? @db.VarChar metagraph metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_locks_source_addr_fk") abstract_transactions_view abstract_transactions_view? @@ -512,7 +514,7 @@ model metagraph_transactions { fee BigInt salt BigInt parent_ordinal BigInt - parent_hash String @db.VarChar + parent_hash String? @db.VarChar ordinal BigInt block_hash String @db.VarChar metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") @@ -546,10 +548,10 @@ model dag_expired_spend_transactions { amount BigInt created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - allow_spend_ref String? @db.VarChar - snapshot_hash String? @db.VarChar + allow_spend_ref String @db.VarChar + snapshot_hash String @db.VarChar abstract_transactions_view abstract_transactions_view? - dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_expired_spend_transactions_dag_allow_spends_fk") + dag_allow_spend dag_allow_spends @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_expired_spend_transactions_dag_allow_spends_fk") } model metagraph_expired_spend_transactions { @@ -559,8 +561,8 @@ model metagraph_expired_spend_transactions { created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) metagraph_id String @db.VarChar - allow_spend_ref String? @db.VarChar - snapshot_hash String? @db.VarChar + allow_spend_ref String @db.VarChar + snapshot_hash String @db.VarChar abstract_transactions_view abstract_transactions_view? - metagraph_allow_spend metagraph_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_expired_spend_transactions_metagraph_allow_spends_fk") + metagraph_allow_spend metagraph_allow_spends @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_expired_spend_transactions_metagraph_allow_spends_fk") } diff --git a/src/handlers/actionsHandler.ts b/src/handlers/actionsHandler.ts index 36ecc07..b828618 100644 --- a/src/handlers/actionsHandler.ts +++ b/src/handlers/actionsHandler.ts @@ -53,13 +53,13 @@ const actionResponse = (transaction) => ({ hash: transaction.hash, amount: transaction.amount, source: transaction.source_addr, - destination: transaction.destination_addr, + destination: transaction.destination_addr ?? null, unlockEpoch: transaction.dag_allow_spend?.last_valid_epoch_progress ?? - transaction.dag_token_lock?.unlock_epoch, + transaction.dag_token_lock?.unlock_epoch ?? null, parentHash: transaction.dag_spend_transaction?.allow_spend_ref ?? - transaction.dag_token_unlock?.lock_reference_hash, + transaction.dag_token_unlock?.lock_reference_hash ?? null, timestamp: transaction.created_at, }); diff --git a/src/handlers/tokenLocksHandler.ts b/src/handlers/tokenLocksHandler.ts index eeb6f19..7442c3c 100644 --- a/src/handlers/tokenLocksHandler.ts +++ b/src/handlers/tokenLocksHandler.ts @@ -15,9 +15,8 @@ const tokenLockResponse = (transaction) => ({ hash: transaction.hash, amount: transaction.amount, source: transaction.source_addr, - destination: transaction.destination_addr, - unlockEpoch: transaction.unlock_epoch, - parentHash: transaction.parent_hash, + unlockEpoch: transaction.unlock_epoch ?? null, + parentHash: transaction.parent_hash ?? null, timestamp: transaction.created_at, }); diff --git a/src/response.ts b/src/response.ts index f9c07f8..b006c21 100644 --- a/src/response.ts +++ b/src/response.ts @@ -54,8 +54,8 @@ const transactionResponse = (transaction, snapshot) => ({ destination: transaction.destination_addr, fee: transaction.fee, parent: { - hash: transaction.parent_hash, - ordinal: transaction.parent_ordinal, + hash: transaction.parent_hash ?? null, + ordinal: transaction.parent_ordinal ?? null, }, salt: transaction.salt, blockHash: transaction.block_hash, From daea3d9f9e068dbbd406141be68003d7adfeff51 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Thu, 3 Apr 2025 12:34:03 -0300 Subject: [PATCH 12/63] use ordinal from query parameters --- src/handlers/dagHandler.ts | 15 +++++++++------ src/handlers/metagraphHandler.ts | 17 +++++++++-------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/handlers/dagHandler.ts b/src/handlers/dagHandler.ts index 4cd0137..95008c6 100644 --- a/src/handlers/dagHandler.ts +++ b/src/handlers/dagHandler.ts @@ -295,11 +295,11 @@ export const dagTransactionsByDestination = async ( } }; -const lastOrdinal = () => {}; - -const balanceOrZeroFn = async (balance, address) => { +const balanceOrZeroFn = async (balance, address, ordinal) => { if (balance === null) { - const snapshot_ordinal = (await latestGlobalSnapshot())?.ordinal; + const snapshot_ordinal = isFinite(ordinal) + ? ordinal + : (await latestGlobalSnapshot())?.ordinal; return { balance: 0, address, snapshot_ordinal }; } return balance; @@ -309,9 +309,12 @@ export const dagBalanceByAddress = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { address, ordinal } = event.pathParameters || {}; + const { address } = event.pathParameters || {}; + + const { ordinal } = event.queryStringParameters || {}; const ordinalNbr = toNumber(ordinal); + const ordinalCondition = isFinite(ordinalNbr) ? { snapshot_ordinal: { lte: ordinalNbr } } : {}; @@ -321,7 +324,7 @@ export const dagBalanceByAddress = async ( orderBy: { snapshot_ordinal: "desc" }, }); - const balanceOrZero = await balanceOrZeroFn(balance, address); + const balanceOrZero = await balanceOrZeroFn(balance, address, ordinalNbr); return respond(balanceOrZero, balanceResponse); } catch (error) { diff --git a/src/handlers/metagraphHandler.ts b/src/handlers/metagraphHandler.ts index 0b0feab..ceda479 100644 --- a/src/handlers/metagraphHandler.ts +++ b/src/handlers/metagraphHandler.ts @@ -363,9 +363,11 @@ export const currencyTransactionsByDestination = async ( } }; -const balanceOrZeroFn = async (balance, address) => { +const balanceOrZeroFn = async (balance, address, ordinal) => { if (balance === null) { - const snapshot_ordinal = (await latestMetagraphSnapshot())?.ordinal; + const snapshot_ordinal = isFinite(ordinal) + ? ordinal + : (await latestMetagraphSnapshot())?.ordinal; return { balance: 0, address, snapshot_ordinal }; } return balance; @@ -375,13 +377,12 @@ export const currencyBalanceByAddress = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { - identifier: metagraph_id, - address, - ordinal, - } = event.pathParameters || {}; + const { identifier: metagraph_id, address } = event.pathParameters || {}; + + const { ordinal } = event.queryStringParameters || {}; const ordinalNbr = toNumber(ordinal); + const ordinalCondition = isFinite(ordinalNbr) ? { snapshot_ordinal: { lte: ordinalNbr } } : {}; @@ -395,7 +396,7 @@ export const currencyBalanceByAddress = async ( orderBy: { snapshot_ordinal: "desc" }, }); - const balanceOrZero = await balanceOrZeroFn(balance, address); + const balanceOrZero = await balanceOrZeroFn(balance, address, ordinal); return respond(balanceOrZero, balanceResponse); } catch (error) { From a9a0a554fa89f9f0bcaaea2f34a6fcc09def7ba5 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Thu, 3 Apr 2025 16:57:30 -0300 Subject: [PATCH 13/63] update db structure --- prisma/schema.prisma | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d0e472f..1e0aedc 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -209,12 +209,12 @@ model dag_token_locks { ordinal BigInt unlock_epoch BigInt? round_id String @db.Uuid - global_snapshot_hash String @db.VarChar + snapshot_hash String @db.VarChar addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_locks_source_addr_fk") dag_token_unlocks dag_token_unlocks? abstract_transactions_view abstract_transactions_view? dag_token_lock_blocks dag_token_lock_blocks? @relation(fields: [round_id], references: [round_id]) - global_snapshot global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_lock_global_snapshot_fk") + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_lock_global_snapshot_fk") @@unique([ordinal], map: "dag_token_locks_unique") } From e4c3d4b8ec1a1deef880c025fffea4849424d33b Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Thu, 3 Apr 2025 14:59:17 -0300 Subject: [PATCH 14/63] Adjust fields optionality --- prisma/schema.prisma | 24 +++++++++++++----------- src/handlers/actionsHandler.ts | 6 +++--- src/handlers/tokenLocksHandler.ts | 5 ++--- src/response.ts | 4 ++-- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1e0aedc..9661d0e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -184,8 +184,8 @@ model dag_spend_transactions { created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) destination_addr String? @db.VarChar - allow_spend_ref String? @db.VarChar - snapshot_hash String? @db.VarChar + allow_spend_ref String @db.VarChar + snapshot_hash String @db.VarChar dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_dag_allow_spends_fk") addresses addresses? @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_destination_addr_fk") abstract_transactions_view abstract_transactions_view? @@ -210,6 +210,7 @@ model dag_token_locks { unlock_epoch BigInt? round_id String @db.Uuid snapshot_hash String @db.VarChar + parent_hash String? @db.VarChar addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_locks_source_addr_fk") dag_token_unlocks dag_token_unlocks? abstract_transactions_view abstract_transactions_view? @@ -270,8 +271,8 @@ model global_snapshots { height BigInt subheight Int last_snapshot_hash String @db.VarChar - metagraph_snapshot_count BigInt? - epoch_progress BigInt? + metagraph_snapshot_count BigInt + epoch_progress BigInt version String @db.VarChar created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) @@ -470,6 +471,7 @@ model metagraph_token_locks { unlock_epoch BigInt? round_id String @db.Uuid snapshot_hash String @db.VarChar + parent_hash String? @db.VarChar metagraph metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_locks_source_addr_fk") abstract_transactions_view abstract_transactions_view? @@ -512,7 +514,7 @@ model metagraph_transactions { fee BigInt salt BigInt parent_ordinal BigInt - parent_hash String @db.VarChar + parent_hash String? @db.VarChar ordinal BigInt block_hash String @db.VarChar metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") @@ -546,10 +548,10 @@ model dag_expired_spend_transactions { amount BigInt created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - allow_spend_ref String? @db.VarChar - snapshot_hash String? @db.VarChar + allow_spend_ref String @db.VarChar + snapshot_hash String @db.VarChar abstract_transactions_view abstract_transactions_view? - dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_expired_spend_transactions_dag_allow_spends_fk") + dag_allow_spend dag_allow_spends @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_expired_spend_transactions_dag_allow_spends_fk") } model metagraph_expired_spend_transactions { @@ -559,8 +561,8 @@ model metagraph_expired_spend_transactions { created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) metagraph_id String @db.VarChar - allow_spend_ref String? @db.VarChar - snapshot_hash String? @db.VarChar + allow_spend_ref String @db.VarChar + snapshot_hash String @db.VarChar abstract_transactions_view abstract_transactions_view? - metagraph_allow_spend metagraph_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_expired_spend_transactions_metagraph_allow_spends_fk") + metagraph_allow_spend metagraph_allow_spends @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_expired_spend_transactions_metagraph_allow_spends_fk") } diff --git a/src/handlers/actionsHandler.ts b/src/handlers/actionsHandler.ts index 36ecc07..b828618 100644 --- a/src/handlers/actionsHandler.ts +++ b/src/handlers/actionsHandler.ts @@ -53,13 +53,13 @@ const actionResponse = (transaction) => ({ hash: transaction.hash, amount: transaction.amount, source: transaction.source_addr, - destination: transaction.destination_addr, + destination: transaction.destination_addr ?? null, unlockEpoch: transaction.dag_allow_spend?.last_valid_epoch_progress ?? - transaction.dag_token_lock?.unlock_epoch, + transaction.dag_token_lock?.unlock_epoch ?? null, parentHash: transaction.dag_spend_transaction?.allow_spend_ref ?? - transaction.dag_token_unlock?.lock_reference_hash, + transaction.dag_token_unlock?.lock_reference_hash ?? null, timestamp: transaction.created_at, }); diff --git a/src/handlers/tokenLocksHandler.ts b/src/handlers/tokenLocksHandler.ts index eeb6f19..7442c3c 100644 --- a/src/handlers/tokenLocksHandler.ts +++ b/src/handlers/tokenLocksHandler.ts @@ -15,9 +15,8 @@ const tokenLockResponse = (transaction) => ({ hash: transaction.hash, amount: transaction.amount, source: transaction.source_addr, - destination: transaction.destination_addr, - unlockEpoch: transaction.unlock_epoch, - parentHash: transaction.parent_hash, + unlockEpoch: transaction.unlock_epoch ?? null, + parentHash: transaction.parent_hash ?? null, timestamp: transaction.created_at, }); diff --git a/src/response.ts b/src/response.ts index f9c07f8..b006c21 100644 --- a/src/response.ts +++ b/src/response.ts @@ -54,8 +54,8 @@ const transactionResponse = (transaction, snapshot) => ({ destination: transaction.destination_addr, fee: transaction.fee, parent: { - hash: transaction.parent_hash, - ordinal: transaction.parent_ordinal, + hash: transaction.parent_hash ?? null, + ordinal: transaction.parent_ordinal ?? null, }, salt: transaction.salt, blockHash: transaction.block_hash, From 89725696e128d48c69cafccd02d95c3454f6f757 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Thu, 3 Apr 2025 12:34:03 -0300 Subject: [PATCH 15/63] use ordinal from query parameters --- src/handlers/dagHandler.ts | 15 +++++++++------ src/handlers/metagraphHandler.ts | 17 +++++++++-------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/handlers/dagHandler.ts b/src/handlers/dagHandler.ts index 4cd0137..95008c6 100644 --- a/src/handlers/dagHandler.ts +++ b/src/handlers/dagHandler.ts @@ -295,11 +295,11 @@ export const dagTransactionsByDestination = async ( } }; -const lastOrdinal = () => {}; - -const balanceOrZeroFn = async (balance, address) => { +const balanceOrZeroFn = async (balance, address, ordinal) => { if (balance === null) { - const snapshot_ordinal = (await latestGlobalSnapshot())?.ordinal; + const snapshot_ordinal = isFinite(ordinal) + ? ordinal + : (await latestGlobalSnapshot())?.ordinal; return { balance: 0, address, snapshot_ordinal }; } return balance; @@ -309,9 +309,12 @@ export const dagBalanceByAddress = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { address, ordinal } = event.pathParameters || {}; + const { address } = event.pathParameters || {}; + + const { ordinal } = event.queryStringParameters || {}; const ordinalNbr = toNumber(ordinal); + const ordinalCondition = isFinite(ordinalNbr) ? { snapshot_ordinal: { lte: ordinalNbr } } : {}; @@ -321,7 +324,7 @@ export const dagBalanceByAddress = async ( orderBy: { snapshot_ordinal: "desc" }, }); - const balanceOrZero = await balanceOrZeroFn(balance, address); + const balanceOrZero = await balanceOrZeroFn(balance, address, ordinalNbr); return respond(balanceOrZero, balanceResponse); } catch (error) { diff --git a/src/handlers/metagraphHandler.ts b/src/handlers/metagraphHandler.ts index 0b0feab..ceda479 100644 --- a/src/handlers/metagraphHandler.ts +++ b/src/handlers/metagraphHandler.ts @@ -363,9 +363,11 @@ export const currencyTransactionsByDestination = async ( } }; -const balanceOrZeroFn = async (balance, address) => { +const balanceOrZeroFn = async (balance, address, ordinal) => { if (balance === null) { - const snapshot_ordinal = (await latestMetagraphSnapshot())?.ordinal; + const snapshot_ordinal = isFinite(ordinal) + ? ordinal + : (await latestMetagraphSnapshot())?.ordinal; return { balance: 0, address, snapshot_ordinal }; } return balance; @@ -375,13 +377,12 @@ export const currencyBalanceByAddress = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { - identifier: metagraph_id, - address, - ordinal, - } = event.pathParameters || {}; + const { identifier: metagraph_id, address } = event.pathParameters || {}; + + const { ordinal } = event.queryStringParameters || {}; const ordinalNbr = toNumber(ordinal); + const ordinalCondition = isFinite(ordinalNbr) ? { snapshot_ordinal: { lte: ordinalNbr } } : {}; @@ -395,7 +396,7 @@ export const currencyBalanceByAddress = async ( orderBy: { snapshot_ordinal: "desc" }, }); - const balanceOrZero = await balanceOrZeroFn(balance, address); + const balanceOrZero = await balanceOrZeroFn(balance, address, ordinal); return respond(balanceOrZero, balanceResponse); } catch (error) { From 916edc25c8a1e5e36532b31b3901e808164187f4 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Thu, 3 Apr 2025 13:21:44 -0300 Subject: [PATCH 16/63] add ordinal and unlock ordinal --- prisma/schema.prisma | 4 +- src/handlers/tokenLocksHandler.ts | 115 +++++++++++++++++++++++------- 2 files changed, 91 insertions(+), 28 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9661d0e..fbc322d 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -212,7 +212,7 @@ model dag_token_locks { snapshot_hash String @db.VarChar parent_hash String? @db.VarChar addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_locks_source_addr_fk") - dag_token_unlocks dag_token_unlocks? + dag_token_unlock dag_token_unlocks? abstract_transactions_view abstract_transactions_view? dag_token_lock_blocks dag_token_lock_blocks? @relation(fields: [round_id], references: [round_id]) global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_lock_global_snapshot_fk") @@ -475,7 +475,7 @@ model metagraph_token_locks { metagraph metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_locks_source_addr_fk") abstract_transactions_view abstract_transactions_view? - metagraph_token_unlocks metagraph_token_unlocks? + metagraph_token_unlock metagraph_token_unlocks? metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_lock_blocks_metagraph_snapshot_fk") @@id([hash], map: "metagraph_token_locks_pk") diff --git a/src/handlers/tokenLocksHandler.ts b/src/handlers/tokenLocksHandler.ts index 7442c3c..3626b3a 100644 --- a/src/handlers/tokenLocksHandler.ts +++ b/src/handlers/tokenLocksHandler.ts @@ -10,17 +10,33 @@ import { handleError } from "../response"; const prisma = new PrismaClient(); -const tokenLockResponse = (transaction) => ({ +const commonTokenLockResponse = (transaction) => ({ currencyId: transaction.currencyId, hash: transaction.hash, amount: transaction.amount, source: transaction.source_addr, unlockEpoch: transaction.unlock_epoch ?? null, parentHash: transaction.parent_hash ?? null, + ordinal: transaction.ordinal, timestamp: transaction.created_at, }); -const tokenLockResponses = (txs) => txs.map(tokenLockResponse); +const dagTokenLockResponse = (transaction) => ({ + ...commonTokenLockResponse(transaction), + unlockedAtOrdinal: + transaction.dag_token_unlock?.global_snapshot.ordinal ?? null, +}); + +const dagTokenLockResponses = (txs) => txs.map(dagTokenLockResponse); + +const metagraphTokenLockResponse = (transaction) => ({ + ...commonTokenLockResponse(transaction), + unlockedAtOrdinal: + transaction.metagraph_token_unlock?.metagraph_snapshot.ordinal ?? null, +}); + +const metagraphTokenLockResponses = (txs) => + txs.map(metagraphTokenLockResponse); const commonTokenUnlockResponse = (transaction) => ({ currencyId: transaction.currencyId, @@ -46,6 +62,30 @@ const metagraphTokenUnlockResponse = (transaction) => ({ const metagraphTokenUnlockResponses = (txs) => txs.map(metagraphTokenUnlockResponse); +const includeDagGlobalSnapshotOrdinal = { + global_snapshot: { + select: { ordinal: true }, + }, +}; + +const includeDagUnlockOrdinal = { + dag_token_unlock: { + include: includeDagGlobalSnapshotOrdinal, + }, +}; + +const includeMetagraphSnapshotOrdinal = { + metagraph_snapshot: { + select: { ordinal: true }, + }, +}; + +const includeMetagraphUnlockOrdinal = { + metagraph_token_unlock: { + include: includeMetagraphSnapshotOrdinal, + }, +}; + export const tokenLocks = async ( event: APIGatewayProxyEvent ): Promise => { @@ -53,9 +93,12 @@ export const tokenLocks = async ( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { orderBy: { created_at: "desc" } }, + { + include: includeDagUnlockOrdinal, + orderBy: [{ ordinal: "desc" }, { created_at: "desc" }], + }, prisma.dag_token_locks.findMany, - tokenLockResponses + dagTokenLockResponses ); }; @@ -74,10 +117,11 @@ export const globalSnapshotTokenLocks = async ( where: { global_snapshot: filter, }, - orderBy: { created_at: "desc" }, + include: includeDagUnlockOrdinal, + orderBy: [{ ordinal: "desc" }, { created_at: "desc" }], }, prisma.dag_token_locks.findMany, - tokenLockResponses + dagTokenLockResponses ); } catch (error) { return handleError(error); @@ -94,9 +138,13 @@ export const addressTokenLocks = async ( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { where: { source_addr: address }, orderBy: { created_at: "desc" } }, + { + where: { source_addr: address }, + include: includeDagUnlockOrdinal, + orderBy: [{ ordinal: "desc" }, { created_at: "desc" }], + }, prisma.dag_token_locks.findMany, - tokenLockResponses + dagTokenLockResponses ); } catch (error) { return handleError(error); @@ -111,8 +159,8 @@ export const tokenUnlocks = async ( toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, { - include: { global_snapshot: { select: { ordinal: true } } }, - orderBy: { created_at: "desc" }, + include: includeDagGlobalSnapshotOrdinal, + orderBy: [{ global_snapshot: {ordinal: "desc"} }, { created_at: "desc" }], }, prisma.dag_token_unlocks.findMany, dagTokenUnlockResponses @@ -134,8 +182,8 @@ export const globalSnapshotTokenUnlocks = async ( where: { global_snapshot: filter, }, - include: { global_snapshot: { select: { ordinal: true } } }, - orderBy: { created_at: "desc" }, + include: includeDagGlobalSnapshotOrdinal, + orderBy: [{ global_snapshot: {ordinal: "desc"} }, { created_at: "desc" }], }, prisma.dag_token_unlocks.findMany, dagTokenUnlockResponses @@ -157,8 +205,8 @@ export const addressTokenUnlocks = async ( fromCreatedAtOrdinalCursor, { where: { source_addr: address }, - include: { global_snapshot: { select: { ordinal: true } } }, - orderBy: { created_at: "desc" }, + include: includeDagGlobalSnapshotOrdinal, + orderBy: [{ global_snapshot: {ordinal: "desc"} }, { created_at: "desc" }], }, prisma.dag_token_unlocks.findMany, dagTokenUnlockResponses @@ -177,9 +225,13 @@ export const metagraphTokenLocks = async ( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { where: { metagraph_id }, orderBy: { created_at: "desc" } }, + { + where: { metagraph_id }, + include: includeMetagraphUnlockOrdinal, + orderBy: [{ ordinal: "desc" }, { created_at: "desc" }], + }, prisma.metagraph_token_locks.findMany, - tokenLockResponses + metagraphTokenLockResponses ); }; @@ -200,10 +252,11 @@ export const metagraphSnapshotTokenLocks = async ( metagraph_id, metagraph_snapshot: filter, }, - orderBy: { created_at: "desc" }, + include: includeMetagraphUnlockOrdinal, + orderBy: [{ ordinal: "desc" }, { created_at: "desc" }], }, prisma.metagraph_token_locks.findMany, - tokenLockResponses + metagraphTokenLockResponses ); } catch (error) { return handleError(error); @@ -222,10 +275,11 @@ export const metagraphAddressTokenLocks = async ( fromCreatedAtOrdinalCursor, { where: { metagraph_id, source_addr: address }, - orderBy: { created_at: "desc" }, + include: includeMetagraphUnlockOrdinal, + orderBy: [{ ordinal: "desc" }, { created_at: "desc" }], }, prisma.metagraph_token_locks.findMany, - tokenLockResponses + metagraphTokenLockResponses ); } catch (error) { return handleError(error); @@ -242,8 +296,11 @@ export const metagraphTokenUnlocks = async ( fromCreatedAtOrdinalCursor, { where: { metagraph_id }, - include: { metagraph_snapshot: { select: { ordinal: true } } }, - orderBy: { created_at: "desc" }, + include: includeMetagraphSnapshotOrdinal, + orderBy: [ + { metagraph_snapshot: { ordinal: "desc" } }, + { created_at: "desc" }, + ], }, prisma.metagraph_token_unlocks.findMany, metagraphTokenUnlockResponses @@ -267,8 +324,11 @@ export const metagraphSnapshotTokenUnlocks = async ( metagraph_id, metagraph_snapshot: filter, }, - include: { metagraph_snapshot: { select: { ordinal: true } } }, - orderBy: { created_at: "desc" }, + include: includeMetagraphSnapshotOrdinal, + orderBy: [ + { metagraph_snapshot: { ordinal: "desc" } }, + { created_at: "desc" }, + ], }, prisma.metagraph_token_unlocks.findMany, metagraphTokenUnlockResponses @@ -290,8 +350,11 @@ export const metagraphAddressTokenUnlocks = async ( fromCreatedAtOrdinalCursor, { where: { metagraph_id, source_addr: address }, - include: { metagraph_snapshot: { select: { ordinal: true } } }, - orderBy: { created_at: "desc" }, + include: includeMetagraphSnapshotOrdinal, + orderBy: [ + { metagraph_snapshot: { ordinal: "desc" } }, + { created_at: "desc" }, + ], }, prisma.metagraph_token_unlocks.findMany, metagraphTokenUnlockResponses From 005ab87cae8c90677d7901f792dd86c9b3a868aa Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Mon, 7 Apr 2025 12:14:21 -0300 Subject: [PATCH 17/63] fix: PROT-1112 add fee, staking address, owner addres, and size to metagraph snapshot response --- prisma/schema.prisma | 1 + src/response.ts | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index fbc322d..1fbb3e9 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -410,6 +410,7 @@ model metagraph_snapshots { owner_address String? @db.VarChar staking_address String? @db.VarChar epoch_progress BigInt? + size BigInt? version String @db.VarChar created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) diff --git a/src/response.ts b/src/response.ts index b006c21..c890869 100644 --- a/src/response.ts +++ b/src/response.ts @@ -93,6 +93,10 @@ export const metagraphSnapshotsResponse = (ss) => ss.map(metagraphSnapshotResponse); export const metagraphSnapshotResponse = (snapshot) => ({ ...commonSnapshotResponse(snapshot, "metagraph_blocks"), + fee: snapshot.fee, + stakingAddress: snapshot.staking_address ?? null, + ownerAddress: snapshot.owner_address ?? null, + sizeInKb: snapshot.size, }); export const metagraphBlockResponse = (block) => ({ From 3133281223c11b9efb244e66dc434e1f88831089 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Mon, 7 Apr 2025 21:44:24 -0300 Subject: [PATCH 18/63] fix: await globalSnapshotWhere and metagraphSnapshotWhere --- src/handlers/dagHandler.ts | 8 ++++++-- src/handlers/metagraphHandler.ts | 12 +++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/handlers/dagHandler.ts b/src/handlers/dagHandler.ts index 95008c6..a9fbcf4 100644 --- a/src/handlers/dagHandler.ts +++ b/src/handlers/dagHandler.ts @@ -98,6 +98,8 @@ export const globalSnapshotRewards = async ( return notFoundResponse(); } + const gsWhere = await globalSnapshotWhere(term) + const toCursor = (row) => ({ global_snapshot_hash_destination_addr: { global_snapshot_hash: row.global_snapshot_hash, @@ -114,7 +116,7 @@ export const globalSnapshotRewards = async ( toCursor, fromCursor, { - where: { global_snapshot: { ...globalSnapshotWhere(term) } }, + where: { global_snapshot: { ...gsWhere } }, orderBy: [{ destination_addr: "asc" }], }, prisma.dag_reward_transactions.findMany, @@ -135,9 +137,11 @@ export const globalSnapshotTransactions = async ( return notFoundResponse(); } + const gsWhere = await globalSnapshotWhere(term) + const query = { where: { - dag_blocks: { global_snapshot: { ...globalSnapshotWhere(term) } }, + dag_blocks: { global_snapshot: { ...gsWhere } }, }, include: { dag_blocks: { diff --git a/src/handlers/metagraphHandler.ts b/src/handlers/metagraphHandler.ts index ceda479..02bf108 100644 --- a/src/handlers/metagraphHandler.ts +++ b/src/handlers/metagraphHandler.ts @@ -159,6 +159,8 @@ export const currencySnapshotRewards = async ( return notFoundResponse(); } + const mgSnapshotWhere= await metagraphSnapshotWhere(term) + const cursor = (row) => ({ metagraph_id: row.metagraph_id, hash: row.hash, @@ -172,7 +174,7 @@ export const currencySnapshotRewards = async ( where: { metagraph_snapshot: { metagraph_id, - ...metagraphSnapshotWhere(term), + ...mgSnapshotWhere, }, }, orderBy: [ @@ -236,12 +238,14 @@ export const currencySnapshotTransactions = async ( ) return notFoundResponse(); + const mgSnapshotWhere= await metagraphSnapshotWhere(term) + const where = { where: { metagraph_blocks: { metagraph_snapshot: { metagraph_id: metagraph_id, - ...metagraphSnapshotWhere(term), + ...mgSnapshotWhere, }, }, }, @@ -465,10 +469,12 @@ export const currencySnapshotFeeTransactions = async ( if (!metagraph_id || !term) return missingParameterResponse("identifier or term"); + const mgSnapshotWhere= await metagraphSnapshotWhere(term) + const where = { where: { metagraph_id: metagraph_id, - ...metagraphSnapshotWhere(term), + ...mgSnapshotWhere, }, }; From 08581634e33c5e8f743eaaac7be8aba75e3c89e5 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Tue, 8 Apr 2025 13:58:30 -0300 Subject: [PATCH 19/63] PROT-1119 add detail endpoints --- routes/allow-spends.yml | 42 +++++++++++++ routes/token-locks.yml | 30 ++++++++- src/handlers/allowSpendsHandler.ts | 97 ++++++++++++++++++++++++++++++ src/handlers/tokenLocksHandler.ts | 85 ++++++++++++++++++++++++-- test_allowspends.sh | 10 +++ test_tokenlocks.sh | 17 ++++++ 6 files changed, 276 insertions(+), 5 deletions(-) diff --git a/routes/allow-spends.yml b/routes/allow-spends.yml index 76e4037..e48aede 100644 --- a/routes/allow-spends.yml +++ b/routes/allow-spends.yml @@ -1,3 +1,10 @@ +allowSpend: + handler: src/handlers/allowSpendsHandler.allowSpend + events: + - httpApi: + path: /allow-spends/{hash} + method: GET + allowSpends: handler: src/handlers/allowSpendsHandler.allowSpends events: @@ -26,6 +33,13 @@ spendTransactions: path: /spend-transactions method: GET +spendTransaction: + handler: src/handlers/allowSpendsHandler.spendTransaction + events: + - httpApi: + path: /spend-transactions/{hash} + method: GET + globalSnapshotSpendTransactions: handler: src/handlers/allowSpendsHandler.globalSnapshotSpendTransactions events: @@ -47,6 +61,13 @@ allowSpendExpirations: path: /allow-spend-expirations method: GET +allowSpendExpiration: + handler: src/handlers/allowSpendsHandler.allowSpendExpiration + events: + - httpApi: + path: /allow-spend-expirations/{hash} + method: GET + globalSnapshotAllowSpendExpirations: handler: src/handlers/allowSpendsHandler.globalSnapshotAllowSpendExpirations events: @@ -68,6 +89,13 @@ currencyAllowSpends: path: /currency/{metagraph_id}/allow-spends method: GET +currencyAllowSpend: + handler: src/handlers/allowSpendsHandler.currencyAllowSpend + events: + - httpApi: + path: /currency/{metagraph_id}/allow-spends/{hash} + method: GET + currencySnapshotAllowSpends: handler: src/handlers/allowSpendsHandler.currencySnapshotAllowSpends events: @@ -96,6 +124,13 @@ currencySpendTransactions: path: /currency/{metagraph_id}/spend-transactions method: GET +currencySpendTransaction: + handler: src/handlers/allowSpendsHandler.currencySpendTransaction + events: + - httpApi: + path: /currency/{metagraph_id}/spend-transactions/{hash} + method: GET + currencyAddressSpendTransactions: handler: src/handlers/allowSpendsHandler.currencyAddressSpendTransactions events: @@ -110,6 +145,13 @@ currencyAllowSpendExpirations: path: /currency/{metagraph_id}/allow-spend-expirations method: GET +currencyAllowSpendExpiration: + handler: src/handlers/allowSpendsHandler.currencyAllowSpendExpiration + events: + - httpApi: + path: /currency/{metagraph_id}/allow-spend-expirations/{hash} + method: GET + currencySnapshotAllowSpendExpirations: handler: src/handlers/allowSpendsHandler.currencySnapshotAllowSpendExpirations events: diff --git a/routes/token-locks.yml b/routes/token-locks.yml index e658dfe..a549aab 100644 --- a/routes/token-locks.yml +++ b/routes/token-locks.yml @@ -5,6 +5,13 @@ tokenLocks: path: /token-locks method: GET +tokenLock: + handler: src/handlers/tokenLocksHandler.tokenLock + events: + - httpApi: + path: /token-locks/{hash} + method: GET + globalSnapshotTokenLocks: handler: src/handlers/tokenLocksHandler.globalSnapshotTokenLocks events: @@ -26,6 +33,13 @@ tokenUnlocks: path: /token-unlocks method: GET +tokenUnlock: + handler: src/handlers/tokenLocksHandler.tokenUnlock + events: + - httpApi: + path: /token-unlocks/{hash} + method: GET + globalSnapshotTokenUnlocks: handler: src/handlers/tokenLocksHandler.globalSnapshotTokenUnlocks events: @@ -47,6 +61,13 @@ currencyTokenLocks: path: /currency/{metagraph_id}/token-locks method: GET +currencyTokenLock: + handler: src/handlers/tokenLocksHandler.metagraphTokenLock + events: + - httpApi: + path: /currency/{metagraph_id}/token-locks/{hash} + method: GET + currencySnapshotTokenLocks: handler: src/handlers/tokenLocksHandler.metagraphSnapshotTokenLocks events: @@ -75,9 +96,16 @@ currencyTokenUnlocks: path: /currency/{metagraph_id}/token-unlocks method: GET +currencyTokenUnlock: + handler: src/handlers/tokenLocksHandler.metagraphTokenUnlock + events: + - httpApi: + path: /currency/{metagraph_id}/token-unlocks/{hash} + method: GET + currencyAddressTokenUnlocks: handler: src/handlers/tokenLocksHandler.metagraphAddressTokenUnlocks events: - httpApi: path: /currency/{metagraph_id}/addresses/{address}/token-unlocks - method: GET + method: GET \ No newline at end of file diff --git a/src/handlers/allowSpendsHandler.ts b/src/handlers/allowSpendsHandler.ts index 1e2bc88..95c980f 100644 --- a/src/handlers/allowSpendsHandler.ts +++ b/src/handlers/allowSpendsHandler.ts @@ -50,6 +50,22 @@ const spendExpiredResponse = (transaction) => ({ const spendExpiredResponses = (txs) => txs.map(spendExpiredResponse); +export const allowSpend = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { hash } = event.pathParameters || {}; + + const allowSpend = await prisma.dag_allow_spends.findUnique({ + where: { hash }, + }); + + return respond(allowSpend, allowSpendResponse); + } catch (error) { + return handleError(error); + } +}; + export const allowSpends = async ( event: APIGatewayProxyEvent ): Promise => { @@ -110,6 +126,22 @@ export const addressAllowSpends = async ( } }; +export const spendTransaction = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { hash } = event.pathParameters || {}; + + const spend = await prisma.dag_spend_transactions.findUnique({ + where: { hash }, + }); + + return respond(spend, spendTransactionResponse); + } catch (error) { + return handleError(error); + } +}; + export const spendTransactions = async ( event: APIGatewayProxyEvent ): Promise => { @@ -193,6 +225,22 @@ export const allowSpendExpirations = async ( } }; +export const allowSpendExpiration = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { hash } = event.pathParameters || {}; + + const expired = await prisma.dag_expired_spend_transactions.findUnique({ + where: { hash }, + }); + + return respond(expired, spendExpiredResponse); + } catch (error) { + return handleError(error); + } +}; + export const globalSnapshotAllowSpendExpirations = async ( event: APIGatewayProxyEvent ): Promise => { @@ -264,6 +312,22 @@ export const currencyAllowSpends = async ( } }; +export const currencyAllowSpend = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { metagraph_id, hash } = event.pathParameters || {}; + + const allowSpend = await prisma.metagraph_allow_spends.findUnique({ + where: { metagraph_id, hash }, + }); + + return respond(allowSpend, allowSpendResponse); + } catch (error) { + return handleError(error); + } +}; + export const currencySnapshotAllowSpends = async ( event: APIGatewayProxyEvent ): Promise => { @@ -334,6 +398,22 @@ export const currencySpendTransactions = async ( } }; +export const currencySpendTransaction = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { metagraph_id, hash } = event.pathParameters || {}; + + const spend = await prisma.metagraph_spend_transactions.findUnique({ + where: { metagraph_id, hash }, + }); + + return respond(spend, spendTransactionResponse); + } catch (error) { + return handleError(error); + } +}; + export const currencySnapshotSpendTransactions = async ( event: APIGatewayProxyEvent ): Promise => { @@ -405,6 +485,23 @@ export const currencyAllowSpendExpirations = async ( } }; +export const currencyAllowSpendExpiration = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { metagraph_id, hash } = event.pathParameters || {}; + + const expired = + await prisma.metagraph_expired_spend_transactions.findUnique({ + where: { metagraph_id, hash }, + }); + + return respond(expired, spendExpiredResponse); + } catch (error) { + return handleError(error); + } +}; + export const currencySnapshotAllowSpendExpirations = async ( event: APIGatewayProxyEvent ): Promise => { diff --git a/src/handlers/tokenLocksHandler.ts b/src/handlers/tokenLocksHandler.ts index 3626b3a..b57fa1b 100644 --- a/src/handlers/tokenLocksHandler.ts +++ b/src/handlers/tokenLocksHandler.ts @@ -6,7 +6,7 @@ import { fromCreatedAtOrdinalCursor, toCreatedAtOrdinalCursor, } from "../pagination"; -import { handleError } from "../response"; +import { handleError, respond } from "../response"; const prisma = new PrismaClient(); @@ -102,6 +102,23 @@ export const tokenLocks = async ( ); }; +export const tokenLock = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { hash } = event.pathParameters || {}; + + const lock = await prisma.dag_token_locks.findUnique({ + where: { hash }, + include: includeDagUnlockOrdinal, + }); + + return respond(lock, dagTokenLockResponse); + } catch (error) { + return handleError(error); + } +}; + export const globalSnapshotTokenLocks = async ( event: APIGatewayProxyEvent ): Promise => { @@ -160,13 +177,33 @@ export const tokenUnlocks = async ( fromCreatedAtOrdinalCursor, { include: includeDagGlobalSnapshotOrdinal, - orderBy: [{ global_snapshot: {ordinal: "desc"} }, { created_at: "desc" }], + orderBy: [ + { global_snapshot: { ordinal: "desc" } }, + { created_at: "desc" }, + ], }, prisma.dag_token_unlocks.findMany, dagTokenUnlockResponses ); }; +export const tokenUnlock = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { hash } = event.pathParameters || {}; + + const unlock = await prisma.dag_token_unlocks.findUnique({ + where: { hash }, + include: includeDagGlobalSnapshotOrdinal, + }); + + return respond(unlock, dagTokenUnlockResponse); + } catch (error) { + return handleError(error); + } +}; + export const globalSnapshotTokenUnlocks = async ( event: APIGatewayProxyEvent ): Promise => { @@ -183,7 +220,10 @@ export const globalSnapshotTokenUnlocks = async ( global_snapshot: filter, }, include: includeDagGlobalSnapshotOrdinal, - orderBy: [{ global_snapshot: {ordinal: "desc"} }, { created_at: "desc" }], + orderBy: [ + { global_snapshot: { ordinal: "desc" } }, + { created_at: "desc" }, + ], }, prisma.dag_token_unlocks.findMany, dagTokenUnlockResponses @@ -206,7 +246,10 @@ export const addressTokenUnlocks = async ( { where: { source_addr: address }, include: includeDagGlobalSnapshotOrdinal, - orderBy: [{ global_snapshot: {ordinal: "desc"} }, { created_at: "desc" }], + orderBy: [ + { global_snapshot: { ordinal: "desc" } }, + { created_at: "desc" }, + ], }, prisma.dag_token_unlocks.findMany, dagTokenUnlockResponses @@ -235,6 +278,23 @@ export const metagraphTokenLocks = async ( ); }; +export const metagraphTokenLock = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { identifier: metagraph_id, hash } = event.pathParameters || {}; + + const lock = await prisma.metagraph_token_locks.findUnique({ + where: { metagraph_id, hash }, + include: includeMetagraphUnlockOrdinal, + }); + + return respond(lock, metagraphTokenLockResponse); + } catch (error) { + return handleError(error); + } +}; + export const metagraphSnapshotTokenLocks = async ( event: APIGatewayProxyEvent ): Promise => { @@ -307,6 +367,23 @@ export const metagraphTokenUnlocks = async ( ); }; +export const metagraphTokenUnlock = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { identifier: metagraph_id, hash } = event.pathParameters || {}; + + const unlock = await prisma.metagraph_token_unlocks.findUnique({ + where: { metagraph_id, hash }, + include: includeMetagraphSnapshotOrdinal, + }); + + return respond(unlock, metagraphTokenUnlockResponse); + } catch (error) { + return handleError(error); + } +}; + export const metagraphSnapshotTokenUnlocks = async ( event: APIGatewayProxyEvent ): Promise => { diff --git a/test_allowspends.sh b/test_allowspends.sh index 136342e..a5670dd 100644 --- a/test_allowspends.sh +++ b/test_allowspends.sh @@ -31,3 +31,13 @@ echo "Currency Address-Specific Endpoints" curl -X GET "http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/addresses/DAG04Qob76gZG7D5qZu9oeZvr8WFxs53Md383q4x/allow-spends" curl -X GET "http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/addresses/DAG04Qob76gZG7D5qZu9oeZvr8WFxs53Md383q4x/spend-transactions" curl -X GET "http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/addresses/DAG04Qob76gZG7D5qZu9oeZvr8WFxs53Md383q4x/allow-spend-expirations" + +echo "Spends by hash" +curl http://localhost:3001/allow-spends/tx003 +curl http://localhost:3001/spend-transactions/tx005 +curl http://localhost:3001/allow-spend-expirations/AlLoWsPeNdExPiRaTiOnHaSh123 + +echo "Metagraph spends by hash" +curl http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/allow-spends/tx003mg +curl http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/spend-transactions/tx005mg +curl http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/allow-spend-expirations/AlLoWsPeNdExPiRaTiOnHaSh123 diff --git a/test_tokenlocks.sh b/test_tokenlocks.sh index 4c301d0..4300015 100644 --- a/test_tokenlocks.sh +++ b/test_tokenlocks.sh @@ -6,6 +6,10 @@ echo "Testing tokenLocks endpoint..." curl http://localhost:3001/token-locks -H "Content-Type: application/json" echo -e "\n" +echo "Testing tokenLock endpoint..." +curl http://localhost:3001/token-locks/tx009 -H "Content-Type: application/json" +echo -e "\n" + echo "Testing globalSnapshotTokenLocks endpoint..." curl http://localhost:3001/global-snapshots/c3071648a319313afdecad217f196558a7893dde2bc78ee56fbb0d0a5b73700b/token-locks -H "Content-Type: application/json" echo -e "\n" @@ -18,6 +22,10 @@ echo "Testing tokenUnlocks endpoint..." curl http://localhost:3001/token-unlocks -H"Content-Type: application/json" echo -e "\n" +echo "Testing tokenUnlock endpoint..." +curl http://localhost:3001/token-unlocks/tx_unlock_009 -H "Content-Type: application/json" +echo -e "\n" + echo "Testing globalSnapshotTokenUnlocks endpoint..." curl http://localhost:3001/global-snapshots/f5524dc1bff16b587732dc0dd36e6eb0e96fb067f9e0ec93b1cb9b55e8c2f526/token-unlocks -H"Content-Type: application/json" echo -e "\n" @@ -30,6 +38,10 @@ echo "Testing currencyTokenLocks endpoint..." curl http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/token-locks -H"Content-Type: application/json" echo -e "\n" +echo "Testing currencyTokenLock endpoint..." +curl http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/token-locks/tx009mg -H "Content-Type: application/json" +echo -e "\n" + echo "Testing currencySnapshotTokenLocks endpoint..." curl http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/snapshots/74dff07f4ff96e2dc7cea2420389515e6ccbb49f2dd0115ecfa38e4e55cd666d/token-locks -H"Content-Type: application/json" echo -e "\n" @@ -46,6 +58,11 @@ echo "Testing currencyTokenUnlocks endpoint..." curl http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/token-unlocks -H"Content-Type: application/json" echo -e "\n" +echo "Testing currencyTokenUnlock endpoint..." +curl http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/token-unlocks/tx_unlock_009 -H "Content-Type: application/json" +echo -e "\n" + + echo "Testing currencyAddressTokenUnlocks endpoint..." curl http://localhost:3001/currency/DAG5ySNMCXFLRmmXdTLwKPhV8YhBB8AFU88TxSbP/addresses/DAG4AqNNL2E7TWBUvQRxSPhraS2Lnr5xPM6xtVbs/token-unlocks -H"Content-Type: application/json" echo -e "\n" From 645f3d02f2930018eee608a53b2a338a82fbafe3 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Wed, 9 Apr 2025 09:47:38 -0300 Subject: [PATCH 20/63] shorter names --- routes/actions.yml | 8 ++++---- routes/allow-spends.yml | 30 +++++++++++++++--------------- routes/metagraph.yml | 34 +++++++++++++++++----------------- routes/token-locks.yml | 20 ++++++++++---------- 4 files changed, 46 insertions(+), 46 deletions(-) diff --git a/routes/actions.yml b/routes/actions.yml index 7fbf00d..5e31c8a 100644 --- a/routes/actions.yml +++ b/routes/actions.yml @@ -5,7 +5,7 @@ actions: path: /actions method: GET -globalSnapshotActions: +gsActions: handler: src/handlers/actionsHandler.globalSnapshotActions events: - httpApi: @@ -19,21 +19,21 @@ addressActions: path: /addresses/{address}/actions method: GET -currencyActions: +ccyActions: handler: src/handlers/actionsHandler.currencyActions events: - httpApi: path: /currency/{metagraph_id}/actions method: GET -currencySnapshotActions: +ccySnapshotActions: handler: src/handlers/actionsHandler.currencySnapshotActions events: - httpApi: path: /currency/{metagraph_id}/snapshots/{term}/actions method: GET -currencyAddressActions: +ccyAddressActions: handler: src/handlers/actionsHandler.currencyAddressActions events: - httpApi: diff --git a/routes/allow-spends.yml b/routes/allow-spends.yml index e48aede..d314b30 100644 --- a/routes/allow-spends.yml +++ b/routes/allow-spends.yml @@ -12,7 +12,7 @@ allowSpends: path: /allow-spends method: GET -globalSnapshotAllowSpends: +gsAllowSpends: handler: src/handlers/allowSpendsHandler.globalSnapshotAllowSpends events: - httpApi: @@ -40,7 +40,7 @@ spendTransaction: path: /spend-transactions/{hash} method: GET -globalSnapshotSpendTransactions: +gsSpendTransactions: handler: src/handlers/allowSpendsHandler.globalSnapshotSpendTransactions events: - httpApi: @@ -68,7 +68,7 @@ allowSpendExpiration: path: /allow-spend-expirations/{hash} method: GET -globalSnapshotAllowSpendExpirations: +gsAllowSpendExpirations: handler: src/handlers/allowSpendsHandler.globalSnapshotAllowSpendExpirations events: - httpApi: @@ -82,84 +82,84 @@ addressAllowSpendExpirations: path: /addresses/{address}/allow-spend-expirations method: GET -currencyAllowSpends: +ccyAllowSpends: handler: src/handlers/allowSpendsHandler.currencyAllowSpends events: - httpApi: path: /currency/{metagraph_id}/allow-spends method: GET -currencyAllowSpend: +ccyAllowSpend: handler: src/handlers/allowSpendsHandler.currencyAllowSpend events: - httpApi: path: /currency/{metagraph_id}/allow-spends/{hash} method: GET -currencySnapshotAllowSpends: +ccySnapshotAllowSpends: handler: src/handlers/allowSpendsHandler.currencySnapshotAllowSpends events: - httpApi: path: /currency/{metagraph_id}/snapshots/{hash_or_ordinal}/allow-spends method: GET -currencyAddressAllowSpends: +ccyAddressAllowSpends: handler: src/handlers/allowSpendsHandler.currencyAddressAllowSpends events: - httpApi: path: /currency/{metagraph_id}/addresses/{address}/allow-spends method: GET -currencySnapshotSpendTransactions: +ccySnapshotSpendTransactions: handler: src/handlers/allowSpendsHandler.currencySnapshotSpendTransactions events: - httpApi: path: /currency/{metagraph_id}/snapshots/{hash_or_ordinal}/spend-transactions method: GET -currencySpendTransactions: +ccySpendTransactions: handler: src/handlers/allowSpendsHandler.currencySpendTransactions events: - httpApi: path: /currency/{metagraph_id}/spend-transactions method: GET -currencySpendTransaction: +ccySpendTransaction: handler: src/handlers/allowSpendsHandler.currencySpendTransaction events: - httpApi: path: /currency/{metagraph_id}/spend-transactions/{hash} method: GET -currencyAddressSpendTransactions: +ccyAddressSpendTransactions: handler: src/handlers/allowSpendsHandler.currencyAddressSpendTransactions events: - httpApi: path: /currency/{metagraph_id}/addresses/{address}/spend-transactions method: GET -currencyAllowSpendExpirations: +ccyAllowSpendExpirations: handler: src/handlers/allowSpendsHandler.currencyAllowSpendExpirations events: - httpApi: path: /currency/{metagraph_id}/allow-spend-expirations method: GET -currencyAllowSpendExpiration: +ccyAllowSpendExpiration: handler: src/handlers/allowSpendsHandler.currencyAllowSpendExpiration events: - httpApi: path: /currency/{metagraph_id}/allow-spend-expirations/{hash} method: GET -currencySnapshotAllowSpendExpirations: +ccySnapshotAllowSpendExpirations: handler: src/handlers/allowSpendsHandler.currencySnapshotAllowSpendExpirations events: - httpApi: path: /currency/{metagraph_id}/snapshots/{hash_or_ordinal}/allow-spend-expirations method: GET -currencyAddressAllowSpendExpirations: +ccyAddressAllowSpendExpirations: handler: src/handlers/allowSpendsHandler.currencyAddressAllowSpendExpirations events: - httpApi: diff --git a/routes/metagraph.yml b/routes/metagraph.yml index 97ef279..3368524 100644 --- a/routes/metagraph.yml +++ b/routes/metagraph.yml @@ -1,100 +1,100 @@ -currencySnapshots: +ccySnapshots: handler: src/handlers/metagraphHandler.currencySnapshots events: - httpApi: path: /currency/{identifier}/snapshots method: GET -currencySnapshotsByOwnerAddress: +ccySnapshotsByOwnerAddress: handler: src/handlers/metagraphHandler.currencySnapshotsByOwnerAddress events: - httpApi: path: /addresses/{address}/snapshots method: GET -currencySnapshot: +ccySnapshot: handler: src/handlers/metagraphHandler.currencySnapshot events: - httpApi: path: /currency/{identifier}/snapshots/{term} method: GET -currencySnapshotRewards: +ccySnapshotRewards: handler: src/handlers/metagraphHandler.currencySnapshotRewards events: - httpApi: path: /currency/{identifier}/snapshots/{term}/rewards method: GET -currencySnapshotTransactions: +ccySnapshotTransactions: handler: src/handlers/metagraphHandler.currencySnapshotTransactions events: - httpApi: path: /currency/{identifier}/snapshots/{term}/transactions method: GET -currencyBlock: +ccyBlock: handler: src/handlers/metagraphHandler.currencyBlock events: - httpApi: path: /currency/{identifier}/blocks/{hash} method: GET -currencyTransactions: +ccyTransactions: handler: src/handlers/metagraphHandler.currencyTransactions events: - httpApi: path: /currency/{identifier}/transactions method: GET -currencyTransaction: +ccyTransaction: handler: src/handlers/metagraphHandler.currencyTransaction events: - httpApi: path: /currency/{identifier}/transactions/{hash} method: GET -currencyTransactionsByAddress: +ccyTransactionsByAddress: handler: src/handlers/metagraphHandler.currencyTransactionsByAddress events: - httpApi: path: /currency/{identifier}/addresses/{address}/transactions method: GET -currencyTransactionsBySource: +ccyTransactionsBySource: handler: src/handlers/metagraphHandler.currencyTransactionsBySource events: - httpApi: path: /currency/{identifier}/addresses/{address}/transactions/sent method: GET -currencyTransactionsByDestination: +ccyTransactionsByDestination: handler: src/handlers/metagraphHandler.currencyTransactionsByDestination events: - httpApi: path: /currency/{identifier}/addresses/{address}/transactions/received method: GET -currencyBalanceByAddress: +ccyBalanceByAddress: handler: src/handlers/metagraphHandler.currencyBalanceByAddress events: - httpApi: path: /currency/{identifier}/addresses/{address}/balance method: GET -currencyFeeTransaction: +ccyFeeTransaction: handler: src/handlers/metagraphHandler.currencyFeeTransaction events: - httpApi: path: /currency/{identifier}/fee-transactions/{hash} method: GET -currencySnapshotFeeTransactions: +ccySnapshotFeeTransactions: handler: src/handlers/metagraphHandler.currencySnapshotFeeTransactions events: - httpApi: path: /currency/{identifier}/snapshots/{term}/fee-transactions method: GET -currencyFeeTransactionsByAddress: +ccyFeeTransactionsByAddress: handler: src/handlers/metagraphHandler.currencyFeeTransactionsByAddress events: - httpApi: path: /currency/{identifier}/addresses/{address}/fee-transactions method: GET -currencyFeeTransactionsBySource: +ccyFeeTransactionsBySource: handler: src/handlers/metagraphHandler.currencyFeeTransactionsBySource events: - httpApi: path: /currency/{identifier}/addresses/{address}/fee-transactions/sent method: GET -currencyFeeTransactionsByDestination: +ccyFeeTransactionsByDestination: handler: src/handlers/metagraphHandler.currencyFeeTransactionsByDestination events: - httpApi: diff --git a/routes/token-locks.yml b/routes/token-locks.yml index a549aab..7fc3796 100644 --- a/routes/token-locks.yml +++ b/routes/token-locks.yml @@ -12,7 +12,7 @@ tokenLock: path: /token-locks/{hash} method: GET -globalSnapshotTokenLocks: +gsTokenLocks: handler: src/handlers/tokenLocksHandler.globalSnapshotTokenLocks events: - httpApi: @@ -40,7 +40,7 @@ tokenUnlock: path: /token-unlocks/{hash} method: GET -globalSnapshotTokenUnlocks: +gsTokenUnlocks: handler: src/handlers/tokenLocksHandler.globalSnapshotTokenUnlocks events: - httpApi: @@ -54,56 +54,56 @@ addressTokenUnlocks: path: /addresses/{address}/token-unlocks method: GET -currencyTokenLocks: +ccyTokenLocks: handler: src/handlers/tokenLocksHandler.metagraphTokenLocks events: - httpApi: path: /currency/{metagraph_id}/token-locks method: GET -currencyTokenLock: +ccyTokenLock: handler: src/handlers/tokenLocksHandler.metagraphTokenLock events: - httpApi: path: /currency/{metagraph_id}/token-locks/{hash} method: GET -currencySnapshotTokenLocks: +ccySnapshotTokenLocks: handler: src/handlers/tokenLocksHandler.metagraphSnapshotTokenLocks events: - httpApi: path: /currency/{metagraph_id}/snapshots/{hash_or_ordinal}/token-locks method: GET -currencyAddressTokenLocks: +ccyAddressTokenLocks: handler: src/handlers/tokenLocksHandler.metagraphAddressTokenLocks events: - httpApi: path: /currency/{metagraph_id}/addresses/{address}/token-locks method: GET -currencySnapshotTokenUnlocks: +ccySnapshotTokenUnlocks: handler: src/handlers/tokenLocksHandler.metagraphSnapshotTokenUnlocks events: - httpApi: path: /currency/{metagraph_id}/snapshots/{hash_or_ordinal}/token-unlocks method: GET -currencyTokenUnlocks: +ccyTokenUnlocks: handler: src/handlers/tokenLocksHandler.metagraphTokenUnlocks events: - httpApi: path: /currency/{metagraph_id}/token-unlocks method: GET -currencyTokenUnlock: +ccyTokenUnlock: handler: src/handlers/tokenLocksHandler.metagraphTokenUnlock events: - httpApi: path: /currency/{metagraph_id}/token-unlocks/{hash} method: GET -currencyAddressTokenUnlocks: +ccyAddressTokenUnlocks: handler: src/handlers/tokenLocksHandler.metagraphAddressTokenUnlocks events: - httpApi: From a2d13d7ffe06fe3f1152a23250b65fd79bb30dbf Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Wed, 9 Apr 2025 16:43:20 -0300 Subject: [PATCH 21/63] Fix metagraph filters for findUnique --- src/handlers/metagraphHandler.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/handlers/metagraphHandler.ts b/src/handlers/metagraphHandler.ts index 02bf108..4a18f3d 100644 --- a/src/handlers/metagraphHandler.ts +++ b/src/handlers/metagraphHandler.ts @@ -42,7 +42,7 @@ const metagraphSnapshotWhere = async (term) => { } }; -const metagraphSnapshotExists = async (metagraph_id, term) => { +const metagraphSnapshotQuery = (metagraph_id, term) => { const filter = extractHashOrdinal(term); let where; @@ -51,9 +51,12 @@ const metagraphSnapshotExists = async (metagraph_id, term) => { } else { where = { metagraph_id_hash: { metagraph_id, hash: filter.hash } }; } + return where; +} +const metagraphSnapshotExists = async (metagraph_id, term) => { return prisma.metagraph_snapshots.findUnique({ - where, + where: metagraphSnapshotQuery(metagraph_id, term), select: { hash: true }, }); }; @@ -134,10 +137,9 @@ export const currencySnapshot = async ( try { const { identifier: metagraph_id, term } = event.pathParameters || {}; - const snapshot = await prisma.metagraph_snapshots.findFirst({ - where: { metagraph_id: metagraph_id, ...metagraphSnapshotWhere(term) }, + const snapshot = await prisma.metagraph_snapshots.findUnique({ + where: metagraphSnapshotQuery(metagraph_id, term), include: { metagraph_blocks: true }, - orderBy: { ordinal: "desc" }, }); return respond(snapshot, metagraphSnapshotResponse); From 6b57bdfbd38ba6cdf2e17a24152e767040ec0b94 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Thu, 10 Apr 2025 12:16:44 -0300 Subject: [PATCH 22/63] use proper latest for querying metagraph snapshot --- src/handlers/metagraphHandler.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/handlers/metagraphHandler.ts b/src/handlers/metagraphHandler.ts index 4a18f3d..29abea2 100644 --- a/src/handlers/metagraphHandler.ts +++ b/src/handlers/metagraphHandler.ts @@ -42,9 +42,7 @@ const metagraphSnapshotWhere = async (term) => { } }; -const metagraphSnapshotQuery = (metagraph_id, term) => { - const filter = extractHashOrdinal(term); - +const metagraphSnapshotQuery = (metagraph_id, filter) => { let where; if ("ordinal" in filter) { where = { metagraph_id_ordinal: { metagraph_id, ordinal: filter.ordinal } }; @@ -52,11 +50,12 @@ const metagraphSnapshotQuery = (metagraph_id, term) => { where = { metagraph_id_hash: { metagraph_id, hash: filter.hash } }; } return where; -} +}; const metagraphSnapshotExists = async (metagraph_id, term) => { + const filter = extractHashOrdinal(term); return prisma.metagraph_snapshots.findUnique({ - where: metagraphSnapshotQuery(metagraph_id, term), + where: metagraphSnapshotQuery(metagraph_id, filter), select: { hash: true }, }); }; @@ -137,8 +136,10 @@ export const currencySnapshot = async ( try { const { identifier: metagraph_id, term } = event.pathParameters || {}; + const hashOrOrdinalWithLatest = await metagraphSnapshotWhere(term); + const snapshot = await prisma.metagraph_snapshots.findUnique({ - where: metagraphSnapshotQuery(metagraph_id, term), + where: metagraphSnapshotQuery(metagraph_id, hashOrOrdinalWithLatest), include: { metagraph_blocks: true }, }); @@ -161,7 +162,7 @@ export const currencySnapshotRewards = async ( return notFoundResponse(); } - const mgSnapshotWhere= await metagraphSnapshotWhere(term) + const mgSnapshotWhere = await metagraphSnapshotWhere(term); const cursor = (row) => ({ metagraph_id: row.metagraph_id, @@ -240,7 +241,7 @@ export const currencySnapshotTransactions = async ( ) return notFoundResponse(); - const mgSnapshotWhere= await metagraphSnapshotWhere(term) + const mgSnapshotWhere = await metagraphSnapshotWhere(term); const where = { where: { @@ -471,7 +472,7 @@ export const currencySnapshotFeeTransactions = async ( if (!metagraph_id || !term) return missingParameterResponse("identifier or term"); - const mgSnapshotWhere= await metagraphSnapshotWhere(term) + const mgSnapshotWhere = await metagraphSnapshotWhere(term); const where = { where: { From fc77e212f9bb221c24a342271fb5c278e6d78dfe Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Thu, 10 Apr 2025 14:18:55 -0300 Subject: [PATCH 23/63] fix: use metagraph_id for query --- src/handlers/metagraphHandler.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/handlers/metagraphHandler.ts b/src/handlers/metagraphHandler.ts index 29abea2..011296b 100644 --- a/src/handlers/metagraphHandler.ts +++ b/src/handlers/metagraphHandler.ts @@ -26,16 +26,17 @@ import { toNumber, isFinite } from "lodash"; const prisma = new PrismaClient(); -const latestMetagraphSnapshot = async () => { +const latestMetagraphSnapshot = async (metagraph_id) => { return prisma.metagraph_snapshots.findFirst({ select: { hash: true, ordinal: true }, + where: { metagraph_id }, orderBy: { ordinal: "desc" }, }); }; -const metagraphSnapshotWhere = async (term) => { +const metagraphSnapshotWhere = async (metagraph_id, term) => { if (term == "latest") { - const latestSnapshotHash = (await latestMetagraphSnapshot())?.hash; + const latestSnapshotHash = (await latestMetagraphSnapshot(metagraph_id))?.hash; return { hash: latestSnapshotHash }; } else { return extractHashOrdinal(term); @@ -136,7 +137,7 @@ export const currencySnapshot = async ( try { const { identifier: metagraph_id, term } = event.pathParameters || {}; - const hashOrOrdinalWithLatest = await metagraphSnapshotWhere(term); + const hashOrOrdinalWithLatest = await metagraphSnapshotWhere(metagraph_id, term); const snapshot = await prisma.metagraph_snapshots.findUnique({ where: metagraphSnapshotQuery(metagraph_id, hashOrOrdinalWithLatest), @@ -162,7 +163,7 @@ export const currencySnapshotRewards = async ( return notFoundResponse(); } - const mgSnapshotWhere = await metagraphSnapshotWhere(term); + const mgSnapshotWhere = await metagraphSnapshotWhere(metagraph_id, term); const cursor = (row) => ({ metagraph_id: row.metagraph_id, @@ -241,7 +242,7 @@ export const currencySnapshotTransactions = async ( ) return notFoundResponse(); - const mgSnapshotWhere = await metagraphSnapshotWhere(term); + const mgSnapshotWhere = await metagraphSnapshotWhere(metagraph_id, term); const where = { where: { @@ -370,11 +371,11 @@ export const currencyTransactionsByDestination = async ( } }; -const balanceOrZeroFn = async (balance, address, ordinal) => { +const balanceOrZeroFn = async (metagraph_id, balance, address, ordinal) => { if (balance === null) { const snapshot_ordinal = isFinite(ordinal) ? ordinal - : (await latestMetagraphSnapshot())?.ordinal; + : (await latestMetagraphSnapshot(metagraph_id))?.ordinal; return { balance: 0, address, snapshot_ordinal }; } return balance; @@ -403,7 +404,7 @@ export const currencyBalanceByAddress = async ( orderBy: { snapshot_ordinal: "desc" }, }); - const balanceOrZero = await balanceOrZeroFn(balance, address, ordinal); + const balanceOrZero = await balanceOrZeroFn(metagraph_id, balance, address, ordinal); return respond(balanceOrZero, balanceResponse); } catch (error) { @@ -472,7 +473,7 @@ export const currencySnapshotFeeTransactions = async ( if (!metagraph_id || !term) return missingParameterResponse("identifier or term"); - const mgSnapshotWhere = await metagraphSnapshotWhere(term); + const mgSnapshotWhere = await metagraphSnapshotWhere(metagraph_id, term); const where = { where: { From 2bce9b127691fe8b826e5c63e5bfbe8d17d24caf Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Mon, 14 Apr 2025 18:54:10 -0300 Subject: [PATCH 24/63] add active token locks query param --- src/handlers/tokenLocksHandler.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/handlers/tokenLocksHandler.ts b/src/handlers/tokenLocksHandler.ts index b57fa1b..1312bc7 100644 --- a/src/handlers/tokenLocksHandler.ts +++ b/src/handlers/tokenLocksHandler.ts @@ -86,6 +86,24 @@ const includeMetagraphUnlockOrdinal = { }, }; +const ifActiveDagTokenLock = (event) => { + const { active } = event.queryStringParameters || {}; + return active === "true" + ? { + dag_token_unlock: null, + } + : {}; +}; + +const ifActiveMetagraphTokenLock = (event) => { + const { active } = event.queryStringParameters || {}; + return active === "true" + ? { + metagraph_token_unlock: null, + } + : {}; +}; + export const tokenLocks = async ( event: APIGatewayProxyEvent ): Promise => { @@ -94,6 +112,7 @@ export const tokenLocks = async ( toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, { + where: ifActiveDagTokenLock(event), include: includeDagUnlockOrdinal, orderBy: [{ ordinal: "desc" }, { created_at: "desc" }], }, @@ -269,7 +288,7 @@ export const metagraphTokenLocks = async ( toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, { - where: { metagraph_id }, + where: { metagraph_id, ...ifActiveMetagraphTokenLock(event) }, include: includeMetagraphUnlockOrdinal, orderBy: [{ ordinal: "desc" }, { created_at: "desc" }], }, From 75fe2c91eca6231d39e8cd04674189f393556881 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Mon, 21 Apr 2025 14:18:51 -0300 Subject: [PATCH 25/63] remove error details --- src/response.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/response.ts b/src/response.ts index c890869..0ed7f21 100644 --- a/src/response.ts +++ b/src/response.ts @@ -163,6 +163,6 @@ export const handleError = (error: any): APIGatewayProxyResult => { console.error(error); return { statusCode: 500, - body: JSON.stringify({ message: "Internal Server Error", errors: [error] }), + body: JSON.stringify({ message: "Internal Server Error", errors: [] }), }; }; From d97aa2f035a85aa46906757eebdef048b41f5791 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Mon, 21 Apr 2025 23:40:57 -0300 Subject: [PATCH 26/63] fix transaction ordering and add tests --- jest.config.js | 2 +- prisma/seed.ts | 341 ++++++++++++++++++------ src/handlers/dagHandler.ts | 21 +- src/handlers/metagraphHandler.ts | 32 ++- src/response.ts | 1 + tests/globalSetup.ts | 5 + tests/handlers/dagHandler.test.ts | 167 ++++++++---- tests/handlers/metagraphHandler.test.ts | 186 +++++++++++++ 8 files changed, 604 insertions(+), 151 deletions(-) create mode 100644 tests/globalSetup.ts create mode 100644 tests/handlers/metagraphHandler.test.ts diff --git a/jest.config.js b/jest.config.js index 594f69a..31a49f6 100644 --- a/jest.config.js +++ b/jest.config.js @@ -52,7 +52,7 @@ module.exports = { // forceCoverageMatch: [], // A path to a module which exports an async function that is triggered once before all test suites - // globalSetup: undefined, + globalSetup: '/tests/globalSetup.ts', // A path to a module which exports an async function that is triggered once after all test suites // globalTeardown: undefined, diff --git a/prisma/seed.ts b/prisma/seed.ts index 385cc79..ff95f7c 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -5,113 +5,286 @@ import { dag_blocks, dag_transactions, dag_balance_changes, + metagraph_snapshots, + metagraph_blocks, + metagraph_transactions, + metagraph_balance_changes, + metagraphs, } from "@prisma/client"; -const prisma = new PrismaClient(); - type TestData = { addresses: addresses[]; global_snapshots: global_snapshots[]; dag_blocks: dag_blocks[]; dag_transactions: dag_transactions[]; - dag_balance_changes: dag_balance_changes[] + dag_balance_changes: dag_balance_changes[]; + metagraphs: metagraphs[]; + metagraph_snapshots: metagraph_snapshots[]; + metagraph_blocks: metagraph_blocks[]; + metagraph_transactions: metagraph_transactions[]; + metagraph_balance_changes: metagraph_balance_changes[]; }; +export const prisma = new PrismaClient(); + +export const data_addresses = [ + { + address: "DAG4bQGdnDJ5okVdsdtvJzBwQoPGjLNzN7HC1CBV", + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + address: "DAG5zSqFVKy399PWxxv1X4TitnVn8PfLXoK7DQQM", + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; + +export const data_global_snapshots = [ + { + hash: "5649b42aad4f232ee30fe3e97b473e584ddc11c6a9823e272c32c1bb48f2042b", + ordinal: 2556535n, + height: 41997n, + subheight: 842, + last_snapshot_hash: + "5649b42aad4f232ee30fe3e97b473e584ddc11c6a9823e272c32c1bb48f2042b", + epoch_progress: 1012463n, + metagraph_snapshot_count: 1n, + version: "0.0.1", + created_at: new Date("2025-04-02T00:00:01Z"), // set explicitly to avoid race condition + updated_at: new Date("2025-04-02T00:00:01Z"), + }, + { + hash: "b9fdf9e18ac3225a35b75d4fe73e9ee5d307a21ac3e0bfd4c77a6eda2411a366", + ordinal: 2556536n, + height: 41998n, + subheight: 845, + last_snapshot_hash: + "5649b42aad4f232ee30fe3e97b473e584ddc11c6a9823e272c32c1bb48f2042b", + epoch_progress: 1012463n, + metagraph_snapshot_count: 1n, + version: "0.0.1", + created_at: new Date("2025-04-02T00:00:01Z"), // set explicitly to avoid race condition + updated_at: new Date("2025-04-02T00:00:01Z"), + }, +]; + +export const data_dag_blocks = [ + { + hash: "16593f9f612a453c28669b86067e097990ee18742e905afa330674636ca1431c", + height: 12n, + snapshot_hash: data_global_snapshots[0].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + hash: "48fd7dd45ced78be111174c5262cca65aa44798b6a01b48525590bfcce643bd2", + height: 14n, + snapshot_hash: data_global_snapshots[1].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; + +export const data_dag_transactions = [ + { + hash: "1c53bc94c735d8d6eeaddc9f5cb446e7f79144c9aa5bba9479db8dee0ec1aa4c", + source_addr: data_addresses[0].address, + destination_addr: data_addresses[1].address, + amount: 90790983n, + fee: 200000n, + salt: 1231231232n, + parent_ordinal: 21337n, + parent_hash: + "4a6d3aa5715e304b4b5f32d52f0c91e0909acf7c24b3ca9776324da68db2f30c", + ordinal: 234n, + block_hash: data_dag_blocks[0].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + hash: "6acc815979e9d1935cce65ba776fde1144c5fc0e97d3a9fe67d82d0e6e21977d", + source_addr: data_addresses[1].address, + destination_addr: data_addresses[0].address, + amount: 90790983n, + fee: 100000n, + salt: 1234n, + parent_ordinal: 21337n, + parent_hash: + "1c53bc94c735d8d6eeaddc9f5cb446e7f79144c9aa5bba9479db8dee0ec1aa4c", + ordinal: 3222n, + block_hash: data_dag_blocks[1].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; +export const data_dag_balance_changes = [ + { + snapshot_hash: data_global_snapshots[0].hash, + snapshot_ordinal: data_global_snapshots[0].ordinal, + address: data_dag_transactions[0].destination_addr, + balance: data_dag_transactions[0].amount, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; + +export const data_metagraphs = [ + { + id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; + +export const data_metagraph_snapshots = [ + { + metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", + global_snapshot_hash: data_global_snapshots[0].hash, + ordinal: 1n, + hash: "1c1e16746af43bc1f93aadaf006cd981852a706e74f1d24b66a7e5e9fa4188e5", + height: 1n, + subheight: 1, + owner_address: null, + staking_address: null, + epoch_progress: 1n, + size: 1n, + last_snapshot_hash: + "0000000000000000000000000000000000000000000000000000000000000000", + fee: 1n, + version: "0.0.1", + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + global_snapshot_hash: data_global_snapshots[1].hash, + metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", + ordinal: 2n, + hash: "9bf40b2d2e355401bbca7a7924880ab799ffa91e95d1b93f3298b549758aac64", + + height: 2n, + subheight: 2, + owner_address: null, + staking_address: null, + epoch_progress: 1n, + size: 1n, + last_snapshot_hash: + "1c1e16746af43bc1f93aadaf006cd981852a706e74f1d24b66a7e5e9fa4188e5", + fee: 1n, + version: "0.0.1", + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; + +export const data_metagraph_blocks = [ + { + metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", + hash: "33374138dd6f5f9846261d541dab33dadcbae8c9f5a39026336a34a3e2aafb93", + height: 12n, + metagraph_snapshot_hash: data_metagraph_snapshots[0].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", + hash: "3d5a9616d65a6d98fe629f1a056489df9245a40d1e8589ed9d655c6fcb3ee361", + height: 14n, + metagraph_snapshot_hash: data_metagraph_snapshots[1].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; +export const data_metagraph_transactions = [ + { + metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", + hash: "39c9909d3b00552beaa5487c38110267675ab212b3b97654fc5ca9917cb7e72b", + source_addr: data_addresses[0].address, + destination_addr: data_addresses[1].address, + amount: 90790983n, + fee: 200000n, + salt: 1231231232n, + parent_ordinal: 21337n, + parent_hash: + "5056fdfbba0637dcecfc0b7fa3f441c745c852cf850c3bfc0dbc8a7410b8d722", + ordinal: 12n, + block_hash: data_metagraph_blocks[0].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", + hash: "aa50e85a32e3e84c9b49880e103ee240f572a6febd255d97db1406f6c936af6f", + source_addr: data_addresses[1].address, + destination_addr: data_addresses[0].address, + amount: 90790983n, + fee: 100000n, + salt: 1234n, + parent_ordinal: 21337n, + parent_hash: + "39c9909d3b00552beaa5487c38110267675ab212b3b97654fc5ca9917cb7e72b", + ordinal: 123n, + block_hash: data_metagraph_blocks[1].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; +export const data_metagraph_balance_changes = [ + { + metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", + metagraph_snapshot_hash: data_metagraph_snapshots[0].hash, + snapshot_ordinal: data_metagraph_snapshots[0].ordinal, + address: data_metagraph_transactions[0].destination_addr, + balance: data_metagraph_transactions[0].amount, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; + export async function seed() { - const testData = {} as TestData; + await prisma.addresses.createManyAndReturn({ data: data_addresses }); - testData.addresses = await prisma.addresses.createManyAndReturn({ - data: [ - { address: "DAG4bQGdnDJ5okVdsdtvJzBwQoPGjLNzN7HC1CBV" }, - { address: "DAG5zSqFVKy399PWxxv1X4TitnVn8PfLXoK7DQQM" }, - ], + await prisma.global_snapshots.createManyAndReturn({ + data: data_global_snapshots, }); - testData.global_snapshots = await prisma.global_snapshots.createManyAndReturn( - { - data: [ - { - hash: - "5649b42aad4f232ee30fe3e97b473e584ddc11c6a9823e272c32c1bb48f2042b", - ordinal: 2556535, - height: 41997, - subheight: 842, - last_snapshot_hash: - "5649b42aad4f232ee30fe3e97b473e584ddc11c6a9823e272c32c1bb48f2042b", - epoch_progress: 1012463, - metagraph_snapshot_count: 0, - version: "0.0.1", - created_at: "2025-04-02T00:00:01Z", // set explicitly to avoid race condition - }, - { - hash: - "b9fdf9e18ac3225a35b75d4fe73e9ee5d307a21ac3e0bfd4c77a6eda2411a366", - ordinal: 2556536, - height: 41998, - subheight: 845, - last_snapshot_hash: - "5649b42aad4f232ee30fe3e97b473e584ddc11c6a9823e272c32c1bb48f2042b", - epoch_progress: 1012463, - metagraph_snapshot_count: 0, - version: "0.0.1", - created_at: "2025-04-02T00:00:02Z", - }, - ], - } - ); + await prisma.dag_blocks.createManyAndReturn({ + data: data_dag_blocks, + }); - testData.dag_blocks = await prisma.dag_blocks.createManyAndReturn({ - data: [ - { - hash: - "16593f9f612a453c28669b86067e097990ee18742e905afa330674636ca1431c", - height: 12, - snapshot_hash: testData.global_snapshots[0].hash, - }, - ], + await prisma.dag_transactions.createManyAndReturn({ + data: data_dag_transactions, }); - testData.dag_transactions = await prisma.dag_transactions.createManyAndReturn( - { - data: [ - { - hash: - "1c53bc94c735d8d6eeaddc9f5cb446e7f79144c9aa5bba9479db8dee0ec1aa4c", - source_addr: testData.addresses[0].address, - destination_addr: testData.addresses[1].address, - amount: 90790983, - fee: 200000, - salt: 1231231232, - parent_ordinal: 21337, - parent_hash: - "4a6d3aa5715e304b4b5f32d52f0c91e0909acf7c24b3ca9776324da68db2f30c", - ordinal: testData.global_snapshots[0].ordinal, - block_hash: testData.dag_blocks[0].hash, - }, - ], - } - ); + await prisma.dag_balance_changes.createManyAndReturn({ + data: data_dag_balance_changes, + }); - testData.dag_balance_changes = await prisma.dag_balance_changes.createManyAndReturn( - { - data: [ - { - snapshot_hash: testData.global_snapshots[0].hash, - snapshot_ordinal: testData.global_snapshots[0].ordinal, - address: testData.dag_transactions[0].destination_addr, - balance: testData.dag_transactions[0].amount, - }, - ], - } - ); + await prisma.metagraphs.createManyAndReturn({ data: data_metagraphs }); + + await prisma.metagraph_snapshots.createManyAndReturn({ + data: data_metagraph_snapshots, + }); - return testData; + await prisma.metagraph_blocks.createManyAndReturn({ + data: data_metagraph_blocks, + }); + + await prisma.metagraph_transactions.createManyAndReturn({ + data: data_metagraph_transactions, + }); + + await prisma.metagraph_balance_changes.createManyAndReturn({ + data: data_metagraph_balance_changes, + }); } export async function resetDatabase() { // Disable referential integrity temporarily if needed await prisma.$executeRawUnsafe( - `TRUNCATE TABLE "addresses", "global_snapshots", "dag_blocks", "dag_transactions" RESTART IDENTITY CASCADE;` + `TRUNCATE TABLE "addresses", "global_snapshots", "dag_blocks", "dag_transactions", "dag_balance_changes", + "metagraphs", "metagraph_snapshots" , "metagraph_blocks", "metagraph_transactions", "metagraph_balance_changes" + RESTART IDENTITY CASCADE;` ); // Add your own models above diff --git a/src/handlers/dagHandler.ts b/src/handlers/dagHandler.ts index a9fbcf4..a32e4a4 100644 --- a/src/handlers/dagHandler.ts +++ b/src/handlers/dagHandler.ts @@ -98,7 +98,7 @@ export const globalSnapshotRewards = async ( return notFoundResponse(); } - const gsWhere = await globalSnapshotWhere(term) + const gsWhere = await globalSnapshotWhere(term); const toCursor = (row) => ({ global_snapshot_hash_destination_addr: { @@ -137,7 +137,7 @@ export const globalSnapshotTransactions = async ( return notFoundResponse(); } - const gsWhere = await globalSnapshotWhere(term) + const gsWhere = await globalSnapshotWhere(term); const query = { where: { @@ -150,7 +150,7 @@ export const globalSnapshotTransactions = async ( }, }, }, - orderBy: { ordinal: "desc" }, + orderBy: { created_at: "desc" }, }; return await paginatedQuery( @@ -186,7 +186,7 @@ export const dagBlock = async ( } }; -const dagTtransactionsQuery = async ( +const dagTransactionsQuery = async ( where, event: APIGatewayProxyEvent ): Promise => { @@ -200,7 +200,10 @@ const dagTtransactionsQuery = async ( }, }, }, - orderBy: { ordinal: "desc" }, + orderBy: [ + { dag_blocks: { global_snapshot: { ordinal: "desc" } } }, + { created_at: "desc" }, + ], }; const toCursor = (row) => ({ @@ -229,7 +232,7 @@ const dagTtransactionsQuery = async ( export const dagTransactions = async ( event: APIGatewayProxyEvent ): Promise => { - return dagTtransactionsQuery({}, event); + return dagTransactionsQuery({}, event); }; export const dagTransaction = async ( @@ -265,7 +268,7 @@ export const dagTransactionsByAddress = async ( where: { OR: [{ source_addr: address }, { destination_addr: address }] }, }; - return dagTtransactionsQuery(where, event); + return dagTransactionsQuery(where, event); } catch (error) { return handleError(error); } @@ -279,7 +282,7 @@ export const dagTransactionsBySource = async ( const where = { where: { source_addr: address } }; - return dagTtransactionsQuery(where, event); + return dagTransactionsQuery(where, event); } catch (error) { return handleError(error); } @@ -293,7 +296,7 @@ export const dagTransactionsByDestination = async ( const where = { where: { destination_addr: address } }; - return dagTtransactionsQuery(where, event); + return dagTransactionsQuery(where, event); } catch (error) { return handleError(error); } diff --git a/src/handlers/metagraphHandler.ts b/src/handlers/metagraphHandler.ts index 011296b..5a92184 100644 --- a/src/handlers/metagraphHandler.ts +++ b/src/handlers/metagraphHandler.ts @@ -19,8 +19,10 @@ import { } from "../response"; import { fromCreatedAtOrdinalCursor, + fromOrdinalCursor, paginatedQuery, toCreatedAtOrdinalCursor, + toOrdinalCursor, } from "../pagination"; import { toNumber, isFinite } from "lodash"; @@ -36,7 +38,8 @@ const latestMetagraphSnapshot = async (metagraph_id) => { const metagraphSnapshotWhere = async (metagraph_id, term) => { if (term == "latest") { - const latestSnapshotHash = (await latestMetagraphSnapshot(metagraph_id))?.hash; + const latestSnapshotHash = (await latestMetagraphSnapshot(metagraph_id)) + ?.hash; return { hash: latestSnapshotHash }; } else { return extractHashOrdinal(term); @@ -68,15 +71,15 @@ export const currencySnapshots = async ( const { identifier } = event.pathParameters || {}; const toCursor = (row) => ({ - ...toCreatedAtOrdinalCursor(row), - metagraph_id: row.metagraph_id, - hash: row.hash, + metagraph_id_ordinal: { + ...toOrdinalCursor(row), + metagraph_id: row.metagraph_id, + }, }); const fromCursor = (row) => ({ - ...fromCreatedAtOrdinalCursor(row), + ...fromOrdinalCursor(row), metagraph_id: row.metagraph_id, - hash: row.hash, }); return await paginatedQuery( @@ -137,7 +140,10 @@ export const currencySnapshot = async ( try { const { identifier: metagraph_id, term } = event.pathParameters || {}; - const hashOrOrdinalWithLatest = await metagraphSnapshotWhere(metagraph_id, term); + const hashOrOrdinalWithLatest = await metagraphSnapshotWhere( + metagraph_id, + term + ); const snapshot = await prisma.metagraph_snapshots.findUnique({ where: metagraphSnapshotQuery(metagraph_id, hashOrOrdinalWithLatest), @@ -209,7 +215,10 @@ const metagraphTransactionsQuery = async ( }, }, }, - orderBy: { ordinal: "desc" }, + orderBy: [ + { metagraph_blocks: { metagraph_snapshot: { ordinal: "desc" } } }, + { created_at: "desc" }, + ], }; const cursor = (row) => ({ @@ -404,7 +413,12 @@ export const currencyBalanceByAddress = async ( orderBy: { snapshot_ordinal: "desc" }, }); - const balanceOrZero = await balanceOrZeroFn(metagraph_id, balance, address, ordinal); + const balanceOrZero = await balanceOrZeroFn( + metagraph_id, + balance, + address, + ordinal + ); return respond(balanceOrZero, balanceResponse); } catch (error) { diff --git a/src/response.ts b/src/response.ts index 0ed7f21..87d14a6 100644 --- a/src/response.ts +++ b/src/response.ts @@ -49,6 +49,7 @@ export const dagTransactionResponse = (t) => const transactionResponse = (transaction, snapshot) => ({ hash: transaction.hash, + ordinal: transaction.ordinal, amount: transaction.amount, source: transaction.source_addr, destination: transaction.destination_addr, diff --git a/tests/globalSetup.ts b/tests/globalSetup.ts new file mode 100644 index 0000000..3a868c7 --- /dev/null +++ b/tests/globalSetup.ts @@ -0,0 +1,5 @@ + +import { seed } from "../prisma/seed"; +module.exports = async function (globalConfig, projectConfig) { + await seed(); +}; \ No newline at end of file diff --git a/tests/handlers/dagHandler.test.ts b/tests/handlers/dagHandler.test.ts index dd1162c..009a7d8 100644 --- a/tests/handlers/dagHandler.test.ts +++ b/tests/handlers/dagHandler.test.ts @@ -5,25 +5,41 @@ import { validateResponseStructure, validatePaginatedResponse, } from "../testUtils"; -import { PrismaClient } from "@prisma/client"; - -const prisma = new PrismaClient(); -import { seed } from "../../prisma/seed"; - -let testData, prismaClient; -const runSeedOnce = async () => { - if (testData) return testData; - - prismaClient = new PrismaClient(); - testData = await seed(); +import { + data_addresses, + data_dag_blocks, + data_dag_transactions, + data_global_snapshots, + prisma, +} from "../../prisma/seed"; + +const validateTransaction = (tx) => { + const dbTxn = data_dag_transactions.filter( + (_dbTxn) => _dbTxn.hash === tx.hash + )[0]; + + const dbBlock = data_dag_blocks.filter( + (_dbBlock) => _dbBlock.hash === dbTxn.block_hash + )[0]; + + const dbSnapshot = data_global_snapshots.filter( + (_dbSnapshot) => _dbSnapshot.hash === dbBlock.snapshot_hash + )[0]; + + expect(tx.hash).toBe(dbTxn.hash); + expect(tx.source).toBe(dbTxn.source_addr); + expect(tx.destination).toBe(dbTxn.destination_addr); + expect(tx.amount).toBe(Number(dbTxn.amount)); + expect(tx.fee).toBe(Number(dbTxn.fee)); + expect(tx.snapshotHash).toBe(dbSnapshot.hash); + expect(tx.snapshotOrdinal).toBe(Number(dbSnapshot.ordinal)); + expect(+new Date(tx.timestamp)).toBe(+new Date(dbTxn.created_at)); }; // These tests use a real database connection, so they're integration tests describe("DAG Handler Integration Tests", () => { describe("globalSnapshots", () => { it("should return a list of global snapshots", async () => { - await runSeedOnce(); - const event = createAPIGatewayEvent({}, { limit: "10" }); const response: APIGatewayProxyResult = await dagHandler.globalSnapshots( event @@ -32,9 +48,9 @@ describe("DAG Handler Integration Tests", () => { expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); - expect(body.data.length).toBe(testData.global_snapshots.length); + expect(body.data.length).toBe(data_global_snapshots.length); - const testSnapshot = testData.global_snapshots.sort((a, b): number => { + const testSnapshot = data_global_snapshots.sort((a, b): number => { return a.ordinal < b.ordinal ? 1 : -1; })[0]; @@ -50,8 +66,6 @@ describe("DAG Handler Integration Tests", () => { }); it("should handle pagination correctly", async () => { - await runSeedOnce(); - const event = createAPIGatewayEvent({}, { limit: "1" }); const response: APIGatewayProxyResult = await dagHandler.globalSnapshots( @@ -85,8 +99,6 @@ describe("DAG Handler Integration Tests", () => { describe("globalSnapshot", () => { it('should return the latest global snapshot when term is "latest"', async () => { - await runSeedOnce(); - const event = createAPIGatewayEvent({ term: "latest" }); const response: APIGatewayProxyResult = await dagHandler.globalSnapshot( @@ -96,7 +108,7 @@ describe("DAG Handler Integration Tests", () => { expect(response.statusCode).toBe(200); const body = validateResponseStructure(response); - const testSnapshot = testData.global_snapshots.sort((a, b): number => { + const testSnapshot = data_global_snapshots.sort((a, b): number => { return a.ordinal < b.ordinal ? 1 : -1; })[0]; @@ -111,9 +123,7 @@ describe("DAG Handler Integration Tests", () => { }); it("should return a specific global snapshot when a valid hash is provided", async () => { - const testData = await runSeedOnce(); - - const requestedSnapshot = testData.global_snapshots[1]; + const requestedSnapshot = data_global_snapshots[1]; // First get the latest global snapshot to get a valid hash const event = createAPIGatewayEvent({ term: requestedSnapshot.hash }); @@ -131,8 +141,6 @@ describe("DAG Handler Integration Tests", () => { }); it("should return 404 for non-existent global snapshot", async () => { - await runSeedOnce(); - const event = createAPIGatewayEvent({ term: "nonexistent-hash" }); const response: APIGatewayProxyResult = await dagHandler.globalSnapshot( @@ -145,20 +153,17 @@ describe("DAG Handler Integration Tests", () => { describe("globalSnapshotTransactions", () => { it("should return transactions for the latest global snapshot", async () => { - const testData = await runSeedOnce(); - - const requestedSnapshot = testData.global_snapshots[1]; + const requestedSnapshot = data_global_snapshots[1]; const event = createAPIGatewayEvent( - { term: requestedSnapshot.ordinal }, + { term: requestedSnapshot.ordinal.toString() }, { limit: "10" } ); - const response: APIGatewayProxyResult = await dagHandler.globalSnapshotTransactions( - event - ); + const response: APIGatewayProxyResult = + await dagHandler.globalSnapshotTransactions(event); - const transactions = await prismaClient.dag_transactions.findMany({ + const transactions = await prisma.dag_transactions.findMany({ where: { dag_blocks: { snapshot_hash: requestedSnapshot.hash }, }, @@ -171,7 +176,7 @@ describe("DAG Handler Integration Tests", () => { const tx = body.data[0]; - const dbTxn = testData.dag_transactions.filter( + const dbTxn = data_dag_transactions.filter( (_dbTxn) => _dbTxn.hash === tx.hash )[0]; @@ -188,10 +193,8 @@ describe("DAG Handler Integration Tests", () => { describe("dagTransaction", () => { it("should return a specific transaction by hash", async () => { - const testData = await runSeedOnce(); - const event = createAPIGatewayEvent({ - hash: testData.dag_transactions[0].hash, + hash: data_dag_transactions[0].hash, }); const response: APIGatewayProxyResult = await dagHandler.dagTransaction( @@ -203,7 +206,7 @@ describe("DAG Handler Integration Tests", () => { const tx = body.data; - const dbTxn = testData.dag_transactions.filter( + const dbTxn = data_dag_transactions.filter( (_dbTxn) => _dbTxn.hash === tx.hash )[0]; @@ -218,8 +221,6 @@ describe("DAG Handler Integration Tests", () => { }); it("should return 404 for non-existent transaction", async () => { - await runSeedOnce(); - const event = createAPIGatewayEvent({ hash: "nonexistent-hash" }); const response: APIGatewayProxyResult = await dagHandler.dagTransaction( @@ -230,20 +231,90 @@ describe("DAG Handler Integration Tests", () => { }); }); - describe("dagBalanceByAddress", () => { - it("should return balance for a given address", async () => { - const testData = await runSeedOnce(); - - const testTxn = testData.dag_transactions[0]; + describe("dagTransactions", () => { + it("should return transactions sorted by snapshot ordinal descending", async () => { + const address = data_addresses[0]; - const event = createAPIGatewayEvent({ address: testTxn.destination_addr }); + const event = createAPIGatewayEvent(); - const response: APIGatewayProxyResult = await dagHandler.dagBalanceByAddress( + const response: APIGatewayProxyResult = await dagHandler.dagTransactions( event ); + const transactions = await prisma.dag_transactions.findMany({ + include: { + dag_blocks: { + include: { + global_snapshot: { select: { ordinal: true } }, + }, + }, + }, + }); + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + expect(body.data.length).toBe(transactions.length); + + expect([ + body.data[0].snapshotOrdinal, + body.data[1].snapshotOrdinal, + ]).toEqual([2556536, 2556535]); + + validateTransaction(body.data[0]); + validateTransaction(body.data[1]); + }); + }); + + describe("dagTransactionsByAddress", () => { + it("should return transactions for specified address sorted by snapshot ordinal descending", async () => { + const address = data_addresses[0].address; + + const event = createAPIGatewayEvent({ address }, { limit: "10" }); + + const response: APIGatewayProxyResult = + await dagHandler.dagTransactionsByAddress(event); + + const transactions = await prisma.dag_transactions.findMany({ + where: { + OR: [{ source_addr: address }, { destination_addr: address }], + }, + include: { + dag_blocks: { + include: { + global_snapshot: { select: { ordinal: true } }, + }, + }, + }, + }); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + expect(body.data.length).toBe(transactions.length); + + expect([ + body.data[0].snapshotOrdinal, + body.data[1].snapshotOrdinal, + ]).toEqual([2556536, 2556535]); + + validateTransaction(body.data[0]); + validateTransaction(body.data[1]); + }); + }); + + describe("dagBalanceByAddress", () => { + it("should return balance for a given address", async () => { + const testTxn = data_dag_transactions[0]; + + const event = createAPIGatewayEvent({ + address: testTxn.destination_addr, + }); + + const response: APIGatewayProxyResult = + await dagHandler.dagBalanceByAddress(event); + expect(response.statusCode).toBe(200); - const body = validateResponseStructure(response)['data']; + const body = validateResponseStructure(response)["data"]; expect(body.balance).toBe(Number(testTxn.amount)); expect(body.address).toBe(testTxn.destination_addr); diff --git a/tests/handlers/metagraphHandler.test.ts b/tests/handlers/metagraphHandler.test.ts new file mode 100644 index 0000000..0bb7fa4 --- /dev/null +++ b/tests/handlers/metagraphHandler.test.ts @@ -0,0 +1,186 @@ +import { APIGatewayProxyResult } from "aws-lambda"; +import * as metagraphHandler from "../../src/handlers/metagraphHandler"; +import { + createAPIGatewayEvent, + validateResponseStructure, + validatePaginatedResponse, +} from "../testUtils"; +import { + data_addresses, + data_metagraph_blocks, + data_metagraph_snapshots, + data_metagraph_transactions, + prisma, +} from "../../prisma/seed"; +const validateTransaction = (tx) => { + const dbTxn = data_metagraph_transactions.filter( + (_dbTxn) => _dbTxn.hash === tx.hash + )[0]; + + const dbBlock = data_metagraph_blocks.filter( + (_dbBlock) => _dbBlock.hash === dbTxn.block_hash + )[0]; + + const dbSnapshot = data_metagraph_snapshots.filter( + (_dbSnapshot) => _dbSnapshot.hash === dbBlock.metagraph_snapshot_hash + )[0]; + + expect(tx.hash).toBe(dbTxn.hash); + expect(tx.source).toBe(dbTxn.source_addr); + expect(tx.destination).toBe(dbTxn.destination_addr); + expect(tx.amount).toBe(Number(dbTxn.amount)); + expect(tx.fee).toBe(Number(dbTxn.fee)); + expect(tx.snapshotHash).toBe(dbSnapshot.hash); + expect(tx.snapshotOrdinal).toBe(Number(dbSnapshot.ordinal)); + expect(+new Date(tx.timestamp)).toBe(+new Date(dbTxn.created_at)); +}; + +// These tests use a real database connection, so they're integration tests +describe("Metagraph Handler Integration Tests", () => { + describe("currencySnapshots", () => { + it("should return a list of metagraph snapshots", async () => { + const event = createAPIGatewayEvent({}, { limit: "10" }); + const response: APIGatewayProxyResult = + await metagraphHandler.currencySnapshots(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + expect(body.data.length).toBe(data_metagraph_snapshots.length); + + const testSnapshot = data_metagraph_snapshots.sort((a, b): number => { + return a.ordinal < b.ordinal ? 1 : -1; + })[0]; + + // Verify the structure of the returned data + const snapshot = body.data[0]; + expect(snapshot.ordinal).toBe(Number(testSnapshot.ordinal)); + expect(snapshot.hash).toBe(testSnapshot.hash); + expect(snapshot.height).toBe(Number(testSnapshot.height)); + expect(snapshot.subHeight).toBe(Number(testSnapshot.subheight)); + expect(Array.isArray(snapshot.blocks)).toBe(true); + expect(snapshot.timestamp).toBeDefined(); + }); + + it("should handle pagination correctly", async () => { + const event = createAPIGatewayEvent({}, { limit: "1" }); + + const response: APIGatewayProxyResult = + await metagraphHandler.currencySnapshots(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + // Ensure pagination is working + expect(body.meta.next).toBeDefined(); + + // Try getting the next page + const nextEvent = createAPIGatewayEvent( + {}, + { + limit: "1", + next: body.meta.next, + } + ); + + const nextResponse = await metagraphHandler.currencySnapshots(nextEvent); + expect(nextResponse.statusCode).toBe(200); + + const nextBody = validatePaginatedResponse(nextResponse); + + // Ensure we got a different set of results + expect(nextBody.data[0].hash).not.toBe(body.data[0].hash); + }); + }); + + describe("currencyTransactions", () => { + it("should return transactions sorted by snapshot ordinal descending", async () => { + const address = data_addresses[0]; + + const event = createAPIGatewayEvent(); + + const response: APIGatewayProxyResult = + await metagraphHandler.currencyTransactions(event); + + const transactions = await prisma.metagraph_transactions.findMany({ + include: { + metagraph_blocks: { + include: { + metagraph_snapshot: { select: { ordinal: true } }, + }, + }, + }, + }); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + expect(body.data.length).toBe(transactions.length); + + expect([ + body.data[0].snapshotOrdinal, + body.data[1].snapshotOrdinal, + ]).toEqual([2, 1]); + + validateTransaction(body.data[0]); + validateTransaction(body.data[1]); + }); + }); + + describe("currencyTransactionsByAddress", () => { + it("should return transactions for specified address sorted by snapshot ordinal descending", async () => { + const address = data_addresses[0].address; + + const event = createAPIGatewayEvent({ address }, { limit: "10" }); + + const response: APIGatewayProxyResult = + await metagraphHandler.currencyTransactionsByAddress(event); + + const transactions = await prisma.metagraph_transactions.findMany({ + where: { + OR: [{ source_addr: address }, { destination_addr: address }], + }, + include: { + metagraph_blocks: { + include: { + metagraph_snapshot: { select: { ordinal: true } }, + }, + }, + }, + }); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + expect(body.data.length).toBe(transactions.length); + + expect([ + body.data[0].snapshotOrdinal, + body.data[1].snapshotOrdinal, + ]).toEqual([2, 1]); + + validateTransaction(body.data[0]); + validateTransaction(body.data[1]); + }); + }); + + describe("currencyBalanceByAddress", () => { + it("should return balance for a given address", async () => { + const testTxn = data_metagraph_transactions[0]; + + const event = createAPIGatewayEvent({ + address: testTxn.destination_addr, + }); + + const response: APIGatewayProxyResult = + await metagraphHandler.currencyBalanceByAddress(event); + + expect(response.statusCode).toBe(200); + const body = validateResponseStructure(response)["data"]; + + expect(body.balance).toBe(Number(testTxn.amount)); + expect(body.address).toBe(testTxn.destination_addr); + expect(body.ordinal).toBeDefined(); + }); + }); +}); From f5dc4331c083937b6ae8aa7506bc17c81fa7a354 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Thu, 24 Apr 2025 16:22:07 -0300 Subject: [PATCH 27/63] update tests --- prisma/seed.ts | 228 +---------------- routes/metagraph.yml | 32 +-- src/handlers/dagHandler.ts | 2 +- src/handlers/metagraphHandler.ts | 74 ++++-- src/handlers/tokenLocksHandler.ts | 16 +- src/response.ts | 4 +- tests/handlers/dagHandler.test.ts | 101 +++++++- tests/handlers/metagraphHandler.test.ts | 318 ++++++++++++++++++++---- 8 files changed, 459 insertions(+), 316 deletions(-) diff --git a/prisma/seed.ts b/prisma/seed.ts index ff95f7c..921d675 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -12,18 +12,6 @@ import { metagraphs, } from "@prisma/client"; -type TestData = { - addresses: addresses[]; - global_snapshots: global_snapshots[]; - dag_blocks: dag_blocks[]; - dag_transactions: dag_transactions[]; - dag_balance_changes: dag_balance_changes[]; - metagraphs: metagraphs[]; - metagraph_snapshots: metagraph_snapshots[]; - metagraph_blocks: metagraph_blocks[]; - metagraph_transactions: metagraph_transactions[]; - metagraph_balance_changes: metagraph_balance_changes[]; -}; export const prisma = new PrismaClient(); @@ -38,6 +26,16 @@ export const data_addresses = [ created_at: new Date("2025-04-02T00:00:02Z"), updated_at: new Date(), }, + { + address: "DAG8rQeDs5qRsPr2Rt4pUf4R83SKMSAbmECNoSBp", + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + address: "DAG8bxrEjLbqPeMsN233MGFGqrLcpgbdXwQzYrYv", + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + } ]; export const data_global_snapshots = [ @@ -64,180 +62,8 @@ export const data_global_snapshots = [ epoch_progress: 1012463n, metagraph_snapshot_count: 1n, version: "0.0.1", - created_at: new Date("2025-04-02T00:00:01Z"), // set explicitly to avoid race condition - updated_at: new Date("2025-04-02T00:00:01Z"), - }, -]; - -export const data_dag_blocks = [ - { - hash: "16593f9f612a453c28669b86067e097990ee18742e905afa330674636ca1431c", - height: 12n, - snapshot_hash: data_global_snapshots[0].hash, - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, - { - hash: "48fd7dd45ced78be111174c5262cca65aa44798b6a01b48525590bfcce643bd2", - height: 14n, - snapshot_hash: data_global_snapshots[1].hash, - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, -]; - -export const data_dag_transactions = [ - { - hash: "1c53bc94c735d8d6eeaddc9f5cb446e7f79144c9aa5bba9479db8dee0ec1aa4c", - source_addr: data_addresses[0].address, - destination_addr: data_addresses[1].address, - amount: 90790983n, - fee: 200000n, - salt: 1231231232n, - parent_ordinal: 21337n, - parent_hash: - "4a6d3aa5715e304b4b5f32d52f0c91e0909acf7c24b3ca9776324da68db2f30c", - ordinal: 234n, - block_hash: data_dag_blocks[0].hash, - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, - { - hash: "6acc815979e9d1935cce65ba776fde1144c5fc0e97d3a9fe67d82d0e6e21977d", - source_addr: data_addresses[1].address, - destination_addr: data_addresses[0].address, - amount: 90790983n, - fee: 100000n, - salt: 1234n, - parent_ordinal: 21337n, - parent_hash: - "1c53bc94c735d8d6eeaddc9f5cb446e7f79144c9aa5bba9479db8dee0ec1aa4c", - ordinal: 3222n, - block_hash: data_dag_blocks[1].hash, - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, -]; -export const data_dag_balance_changes = [ - { - snapshot_hash: data_global_snapshots[0].hash, - snapshot_ordinal: data_global_snapshots[0].ordinal, - address: data_dag_transactions[0].destination_addr, - balance: data_dag_transactions[0].amount, - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, -]; - -export const data_metagraphs = [ - { - id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, -]; - -export const data_metagraph_snapshots = [ - { - metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", - global_snapshot_hash: data_global_snapshots[0].hash, - ordinal: 1n, - hash: "1c1e16746af43bc1f93aadaf006cd981852a706e74f1d24b66a7e5e9fa4188e5", - height: 1n, - subheight: 1, - owner_address: null, - staking_address: null, - epoch_progress: 1n, - size: 1n, - last_snapshot_hash: - "0000000000000000000000000000000000000000000000000000000000000000", - fee: 1n, - version: "0.0.1", - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, - { - global_snapshot_hash: data_global_snapshots[1].hash, - metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", - ordinal: 2n, - hash: "9bf40b2d2e355401bbca7a7924880ab799ffa91e95d1b93f3298b549758aac64", - - height: 2n, - subheight: 2, - owner_address: null, - staking_address: null, - epoch_progress: 1n, - size: 1n, - last_snapshot_hash: - "1c1e16746af43bc1f93aadaf006cd981852a706e74f1d24b66a7e5e9fa4188e5", - fee: 1n, - version: "0.0.1", - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, -]; - -export const data_metagraph_blocks = [ - { - metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", - hash: "33374138dd6f5f9846261d541dab33dadcbae8c9f5a39026336a34a3e2aafb93", - height: 12n, - metagraph_snapshot_hash: data_metagraph_snapshots[0].hash, - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, - { - metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", - hash: "3d5a9616d65a6d98fe629f1a056489df9245a40d1e8589ed9d655c6fcb3ee361", - height: 14n, - metagraph_snapshot_hash: data_metagraph_snapshots[1].hash, - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, -]; -export const data_metagraph_transactions = [ - { - metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", - hash: "39c9909d3b00552beaa5487c38110267675ab212b3b97654fc5ca9917cb7e72b", - source_addr: data_addresses[0].address, - destination_addr: data_addresses[1].address, - amount: 90790983n, - fee: 200000n, - salt: 1231231232n, - parent_ordinal: 21337n, - parent_hash: - "5056fdfbba0637dcecfc0b7fa3f441c745c852cf850c3bfc0dbc8a7410b8d722", - ordinal: 12n, - block_hash: data_metagraph_blocks[0].hash, - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, - { - metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", - hash: "aa50e85a32e3e84c9b49880e103ee240f572a6febd255d97db1406f6c936af6f", - source_addr: data_addresses[1].address, - destination_addr: data_addresses[0].address, - amount: 90790983n, - fee: 100000n, - salt: 1234n, - parent_ordinal: 21337n, - parent_hash: - "39c9909d3b00552beaa5487c38110267675ab212b3b97654fc5ca9917cb7e72b", - ordinal: 123n, - block_hash: data_metagraph_blocks[1].hash, - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, -]; -export const data_metagraph_balance_changes = [ - { - metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", - metagraph_snapshot_hash: data_metagraph_snapshots[0].hash, - snapshot_ordinal: data_metagraph_snapshots[0].ordinal, - address: data_metagraph_transactions[0].destination_addr, - balance: data_metagraph_transactions[0].amount, - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), + created_at: new Date("2025-04-02T00:10:01Z"), // set explicitly to avoid race condition + updated_at: new Date("2025-04-02T00:10:01Z"), }, ]; @@ -247,36 +73,6 @@ export async function seed() { await prisma.global_snapshots.createManyAndReturn({ data: data_global_snapshots, }); - - await prisma.dag_blocks.createManyAndReturn({ - data: data_dag_blocks, - }); - - await prisma.dag_transactions.createManyAndReturn({ - data: data_dag_transactions, - }); - - await prisma.dag_balance_changes.createManyAndReturn({ - data: data_dag_balance_changes, - }); - - await prisma.metagraphs.createManyAndReturn({ data: data_metagraphs }); - - await prisma.metagraph_snapshots.createManyAndReturn({ - data: data_metagraph_snapshots, - }); - - await prisma.metagraph_blocks.createManyAndReturn({ - data: data_metagraph_blocks, - }); - - await prisma.metagraph_transactions.createManyAndReturn({ - data: data_metagraph_transactions, - }); - - await prisma.metagraph_balance_changes.createManyAndReturn({ - data: data_metagraph_balance_changes, - }); } export async function resetDatabase() { diff --git a/routes/metagraph.yml b/routes/metagraph.yml index 3368524..5d25925 100644 --- a/routes/metagraph.yml +++ b/routes/metagraph.yml @@ -2,7 +2,7 @@ ccySnapshots: handler: src/handlers/metagraphHandler.currencySnapshots events: - httpApi: - path: /currency/{identifier}/snapshots + path: /currency/{metagraph_id}/snapshots method: GET ccySnapshotsByOwnerAddress: handler: src/handlers/metagraphHandler.currencySnapshotsByOwnerAddress @@ -14,91 +14,91 @@ ccySnapshot: handler: src/handlers/metagraphHandler.currencySnapshot events: - httpApi: - path: /currency/{identifier}/snapshots/{term} + path: /currency/{metagraph_id}/snapshots/{term} method: GET ccySnapshotRewards: handler: src/handlers/metagraphHandler.currencySnapshotRewards events: - httpApi: - path: /currency/{identifier}/snapshots/{term}/rewards + path: /currency/{metagraph_id}/snapshots/{term}/rewards method: GET ccySnapshotTransactions: handler: src/handlers/metagraphHandler.currencySnapshotTransactions events: - httpApi: - path: /currency/{identifier}/snapshots/{term}/transactions + path: /currency/{metagraph_id}/snapshots/{term}/transactions method: GET ccyBlock: handler: src/handlers/metagraphHandler.currencyBlock events: - httpApi: - path: /currency/{identifier}/blocks/{hash} + path: /currency/{metagraph_id}/blocks/{hash} method: GET ccyTransactions: handler: src/handlers/metagraphHandler.currencyTransactions events: - httpApi: - path: /currency/{identifier}/transactions + path: /currency/{metagraph_id}/transactions method: GET ccyTransaction: handler: src/handlers/metagraphHandler.currencyTransaction events: - httpApi: - path: /currency/{identifier}/transactions/{hash} + path: /currency/{metagraph_id}/transactions/{hash} method: GET ccyTransactionsByAddress: handler: src/handlers/metagraphHandler.currencyTransactionsByAddress events: - httpApi: - path: /currency/{identifier}/addresses/{address}/transactions + path: /currency/{metagraph_id}/addresses/{address}/transactions method: GET ccyTransactionsBySource: handler: src/handlers/metagraphHandler.currencyTransactionsBySource events: - httpApi: - path: /currency/{identifier}/addresses/{address}/transactions/sent + path: /currency/{metagraph_id}/addresses/{address}/transactions/sent method: GET ccyTransactionsByDestination: handler: src/handlers/metagraphHandler.currencyTransactionsByDestination events: - httpApi: - path: /currency/{identifier}/addresses/{address}/transactions/received + path: /currency/{metagraph_id}/addresses/{address}/transactions/received method: GET ccyBalanceByAddress: handler: src/handlers/metagraphHandler.currencyBalanceByAddress events: - httpApi: - path: /currency/{identifier}/addresses/{address}/balance + path: /currency/{metagraph_id}/addresses/{address}/balance method: GET ccyFeeTransaction: handler: src/handlers/metagraphHandler.currencyFeeTransaction events: - httpApi: - path: /currency/{identifier}/fee-transactions/{hash} + path: /currency/{metagraph_id}/fee-transactions/{hash} method: GET ccySnapshotFeeTransactions: handler: src/handlers/metagraphHandler.currencySnapshotFeeTransactions events: - httpApi: - path: /currency/{identifier}/snapshots/{term}/fee-transactions + path: /currency/{metagraph_id}/snapshots/{term}/fee-transactions method: GET ccyFeeTransactionsByAddress: handler: src/handlers/metagraphHandler.currencyFeeTransactionsByAddress events: - httpApi: - path: /currency/{identifier}/addresses/{address}/fee-transactions + path: /currency/{metagraph_id}/addresses/{address}/fee-transactions method: GET ccyFeeTransactionsBySource: handler: src/handlers/metagraphHandler.currencyFeeTransactionsBySource events: - httpApi: - path: /currency/{identifier}/addresses/{address}/fee-transactions/sent + path: /currency/{metagraph_id}/addresses/{address}/fee-transactions/sent method: GET ccyFeeTransactionsByDestination: handler: src/handlers/metagraphHandler.currencyFeeTransactionsByDestination events: - httpApi: - path: /currency/{identifier}/addresses/{address}/fee-transactions/received + path: /currency/{metagraph_id}/addresses/{address}/fee-transactions/received method: GET metagraphs: handler: src/handlers/metagraphHandler.metagraphs diff --git a/src/handlers/dagHandler.ts b/src/handlers/dagHandler.ts index a32e4a4..9e9fcef 100644 --- a/src/handlers/dagHandler.ts +++ b/src/handlers/dagHandler.ts @@ -95,7 +95,7 @@ export const globalSnapshotRewards = async ( const { term } = event.pathParameters || {}; if (term != "latest" && !(await globalSnapshotExists(term))) { - return notFoundResponse(); + return notFoundResponse("global snapshot"); } const gsWhere = await globalSnapshotWhere(term); diff --git a/src/handlers/metagraphHandler.ts b/src/handlers/metagraphHandler.ts index 5a92184..f8d575b 100644 --- a/src/handlers/metagraphHandler.ts +++ b/src/handlers/metagraphHandler.ts @@ -28,6 +28,13 @@ import { toNumber, isFinite } from "lodash"; const prisma = new PrismaClient(); + +const metagraphIdExists = async (metagraph_id) => { + return prisma.metagraphs.findFirst({ + where: { id: metagraph_id }, + }); +}; + const latestMetagraphSnapshot = async (metagraph_id) => { return prisma.metagraph_snapshots.findFirst({ select: { hash: true, ordinal: true }, @@ -68,7 +75,11 @@ export const currencySnapshots = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { identifier } = event.pathParameters || {}; + const { metagraph_id } = event.pathParameters || {}; + + if (!(await metagraphIdExists(metagraph_id))){ + return notFoundResponse("metagraph"); + } const toCursor = (row) => ({ metagraph_id_ordinal: { @@ -87,7 +98,7 @@ export const currencySnapshots = async ( toCursor, fromCursor, { - where: { metagraph_id: identifier }, + where: { metagraph_id }, include: { metagraph_blocks: true }, orderBy: { ordinal: "desc" }, }, @@ -138,7 +149,11 @@ export const currencySnapshot = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { identifier: metagraph_id, term } = event.pathParameters || {}; + const { metagraph_id, term } = event.pathParameters || {}; + + if (!(await metagraphIdExists(metagraph_id))){ + return notFoundResponse("metagraph"); + } const hashOrOrdinalWithLatest = await metagraphSnapshotWhere( metagraph_id, @@ -160,7 +175,11 @@ export const currencySnapshotRewards = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { identifier: metagraph_id, term } = event.pathParameters || {}; + const { metagraph_id, term } = event.pathParameters || {}; + + if (!(await metagraphIdExists(metagraph_id))){ + return notFoundResponse("metagraph"); + } if ( term != "latest" && @@ -243,7 +262,11 @@ export const currencySnapshotTransactions = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { identifier: metagraph_id, term } = event.pathParameters || {}; + const { metagraph_id, term } = event.pathParameters || {}; + + if (!(await metagraphIdExists(metagraph_id))){ + return notFoundResponse("metagraph"); + } if ( term != "latest" && @@ -274,7 +297,11 @@ export const currencyBlock = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { identifier: metagraph_id, hash } = event.pathParameters || {}; + const { metagraph_id, hash } = event.pathParameters || {}; + + if (!(await metagraphIdExists(metagraph_id))){ + return notFoundResponse("metagraph"); + } const block = await prisma.metagraph_blocks.findUnique({ where: { metagraph_id, hash }, @@ -295,7 +322,11 @@ export const currencyTransactions = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { identifier: metagraph_id } = event.pathParameters || {}; + const { metagraph_id } = event.pathParameters || {}; + + if (!(await metagraphIdExists(metagraph_id))){ + return notFoundResponse("metagraph"); + } const where = { where: { metagraph_blocks: { metagraph_id } } }; @@ -309,7 +340,7 @@ export const currencyTransaction = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { identifier: metagraph_id, hash } = event.pathParameters || {}; + const { metagraph_id, hash } = event.pathParameters || {}; const transaction = await prisma.metagraph_transactions.findUnique({ where: { @@ -337,7 +368,11 @@ export const currencyTransactionsByAddress = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { identifier: metagraph_id, address } = event.pathParameters || {}; + const { metagraph_id, address } = event.pathParameters || {}; + + if (!(await metagraphIdExists(metagraph_id))){ + return notFoundResponse("metagraph"); + } const where = { where: { @@ -356,7 +391,12 @@ export const currencyTransactionsBySource = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { identifier: metagraph_id, address } = event.pathParameters || {}; + const { metagraph_id, address } = event.pathParameters || {}; + + if (!(await metagraphIdExists(metagraph_id))){ + return notFoundResponse("metagraph"); + } + const where = { where: { metagraph_id, source_addr: address } }; @@ -370,7 +410,7 @@ export const currencyTransactionsByDestination = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { identifier: metagraph_id, address } = event.pathParameters || {}; + const { metagraph_id, address } = event.pathParameters || {}; const where = { where: { metagraph_id, destination_addr: address } }; @@ -394,7 +434,7 @@ export const currencyBalanceByAddress = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { identifier: metagraph_id, address } = event.pathParameters || {}; + const { metagraph_id, address } = event.pathParameters || {}; const { ordinal } = event.queryStringParameters || {}; @@ -430,7 +470,7 @@ export const currencyFeeTransaction = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { identifier: metagraph_id, hash } = event.pathParameters || {}; + const { metagraph_id, hash } = event.pathParameters || {}; const transaction = await prisma.metagraph_fee_transactions.findUnique({ where: { @@ -483,7 +523,7 @@ export const currencySnapshotFeeTransactions = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { identifier: metagraph_id, term } = event.pathParameters || {}; + const { metagraph_id, term } = event.pathParameters || {}; if (!metagraph_id || !term) return missingParameterResponse("identifier or term"); @@ -506,7 +546,7 @@ export const currencyFeeTransactionsByAddress = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { identifier: metagraph_id, address } = event.pathParameters || {}; + const { metagraph_id, address } = event.pathParameters || {}; if (!metagraph_id || !address) return missingParameterResponse("identifier or address"); @@ -527,7 +567,7 @@ export const currencyFeeTransactionsBySource = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { identifier: metagraph_id, address } = event.pathParameters || {}; + const { metagraph_id, address } = event.pathParameters || {}; if (!metagraph_id || !address) return missingParameterResponse("identifier or address"); @@ -547,7 +587,7 @@ export const currencyFeeTransactionsByDestination = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { identifier: metagraph_id, address } = event.pathParameters || {}; + const { metagraph_id, address } = event.pathParameters || {}; if (!metagraph_id || !address) return missingParameterResponse("identifier or address"); diff --git a/src/handlers/tokenLocksHandler.ts b/src/handlers/tokenLocksHandler.ts index 1312bc7..c9bae09 100644 --- a/src/handlers/tokenLocksHandler.ts +++ b/src/handlers/tokenLocksHandler.ts @@ -281,7 +281,7 @@ export const addressTokenUnlocks = async ( export const metagraphTokenLocks = async ( event: APIGatewayProxyEvent ): Promise => { - const { identifier: metagraph_id } = event.pathParameters || {}; + const { metagraph_id } = event.pathParameters || {}; return paginatedQuery( extractPagination(event), @@ -301,7 +301,7 @@ export const metagraphTokenLock = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { identifier: metagraph_id, hash } = event.pathParameters || {}; + const { metagraph_id, hash } = event.pathParameters || {}; const lock = await prisma.metagraph_token_locks.findUnique({ where: { metagraph_id, hash }, @@ -318,7 +318,7 @@ export const metagraphSnapshotTokenLocks = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { identifier: metagraph_id, hash_or_ordinal } = + const { metagraph_id, hash_or_ordinal } = event.pathParameters || {}; const filter = extractHashOrdinal(hash_or_ordinal); @@ -346,7 +346,7 @@ export const metagraphAddressTokenLocks = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { identifier: metagraph_id, address } = event.pathParameters || {}; + const { metagraph_id, address } = event.pathParameters || {}; return paginatedQuery( extractPagination(event), @@ -368,7 +368,7 @@ export const metagraphAddressTokenLocks = async ( export const metagraphTokenUnlocks = async ( event: APIGatewayProxyEvent ): Promise => { - const { identifier: metagraph_id } = event.pathParameters || {}; + const { metagraph_id } = event.pathParameters || {}; return paginatedQuery( extractPagination(event), toCreatedAtOrdinalCursor, @@ -390,7 +390,7 @@ export const metagraphTokenUnlock = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { identifier: metagraph_id, hash } = event.pathParameters || {}; + const { metagraph_id, hash } = event.pathParameters || {}; const unlock = await prisma.metagraph_token_unlocks.findUnique({ where: { metagraph_id, hash }, @@ -407,7 +407,7 @@ export const metagraphSnapshotTokenUnlocks = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { identifier: metagraph_id, hash_or_ordinal } = + const { metagraph_id, hash_or_ordinal } = event.pathParameters || {}; const filter = extractHashOrdinal(hash_or_ordinal); @@ -438,7 +438,7 @@ export const metagraphAddressTokenUnlocks = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { identifier: metagraph_id, address } = event.pathParameters || {}; + const { metagraph_id, address } = event.pathParameters || {}; return paginatedQuery( extractPagination(event), diff --git a/src/response.ts b/src/response.ts index 87d14a6..21f102c 100644 --- a/src/response.ts +++ b/src/response.ts @@ -145,9 +145,9 @@ export const successResponse = (data: any): APIGatewayProxyResult => ({ ), }); -export const notFoundResponse = (): APIGatewayProxyResult => ({ +export const notFoundResponse = (msg=""): APIGatewayProxyResult => ({ statusCode: 404, - body: JSON.stringify({ message: "Not found", errors: [""] }), + body: JSON.stringify({ message: "Not found", errors: [msg] }), }); export const missingParameterResponse = ( diff --git a/tests/handlers/dagHandler.test.ts b/tests/handlers/dagHandler.test.ts index 009a7d8..e86b196 100644 --- a/tests/handlers/dagHandler.test.ts +++ b/tests/handlers/dagHandler.test.ts @@ -7,12 +7,106 @@ import { } from "../testUtils"; import { data_addresses, - data_dag_blocks, - data_dag_transactions, data_global_snapshots, prisma, } from "../../prisma/seed"; + +export const data_dag_blocks = [ + { + hash: "16593f9f612a453c28669b86067e097990ee18742e905afa330674636ca1431c", + height: 12n, + snapshot_hash: data_global_snapshots[0].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + hash: "48fd7dd45ced78be111174c5262cca65aa44798b6a01b48525590bfcce643bd2", + height: 14n, + snapshot_hash: data_global_snapshots[1].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; + +export const data_dag_transactions = [ + { + hash: "1c53bc94c735d8d6eeaddc9f5cb446e7f79144c9aa5bba9479db8dee0ec1aa4c", + source_addr: data_addresses[0].address, + destination_addr: data_addresses[1].address, + amount: 90790983n, + fee: 200000n, + salt: 1231231232n, + parent_ordinal: 21337n, + parent_hash: + "4a6d3aa5715e304b4b5f32d52f0c91e0909acf7c24b3ca9776324da68db2f30c", + ordinal: 234n, + block_hash: data_dag_blocks[0].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + hash: "6acc815979e9d1935cce65ba776fde1144c5fc0e97d3a9fe67d82d0e6e21977d", + source_addr: data_addresses[1].address, + destination_addr: data_addresses[0].address, + amount: 90790983n, + fee: 100000n, + salt: 1234n, + parent_ordinal: 21338n, + parent_hash: + "1c53bc94c735d8d6eeaddc9f5cb446e7f79144c9aa5bba9479db8dee0ec1aa4c", + ordinal: 3222n, + block_hash: data_dag_blocks[1].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + hash: "1641479831cbb2dcfca627df21cd08ae052edd44b92bbb523f1d0e2b8bd3b058", + source_addr: data_addresses[2].address, + destination_addr: data_addresses[2].address, + amount: 13245n, + fee: 10000n, + salt: 12234n, + parent_ordinal: 21339n, + parent_hash: + "6acc815979e9d1935cce65ba776fde1144c5fc0e97d3a9fe67d82d0e6e21977d", + ordinal: 3222n, + block_hash: data_dag_blocks[1].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; +export const data_dag_balance_changes = [ + { + snapshot_hash: data_global_snapshots[0].hash, + snapshot_ordinal: data_global_snapshots[0].ordinal, + address: data_dag_transactions[0].destination_addr, + balance: data_dag_transactions[0].amount, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; + + +const seedData = async () => { + + await prisma.dag_blocks.createManyAndReturn({ + data: data_dag_blocks, + }); + + await prisma.dag_transactions.createManyAndReturn({ + data: data_dag_transactions, + }); + + await prisma.dag_balance_changes.createManyAndReturn({ + data: data_dag_balance_changes, + }); +} + +beforeAll(async () => { + await seedData(); +}); + const validateTransaction = (tx) => { const dbTxn = data_dag_transactions.filter( (_dbTxn) => _dbTxn.hash === tx.hash @@ -258,7 +352,8 @@ describe("DAG Handler Integration Tests", () => { expect([ body.data[0].snapshotOrdinal, body.data[1].snapshotOrdinal, - ]).toEqual([2556536, 2556535]); + body.data[2].snapshotOrdinal, + ]).toEqual([2556536, 2556536, 2556535]); validateTransaction(body.data[0]); validateTransaction(body.data[1]); diff --git a/tests/handlers/metagraphHandler.test.ts b/tests/handlers/metagraphHandler.test.ts index 0bb7fa4..ec65ae4 100644 --- a/tests/handlers/metagraphHandler.test.ts +++ b/tests/handlers/metagraphHandler.test.ts @@ -7,46 +7,247 @@ import { } from "../testUtils"; import { data_addresses, - data_metagraph_blocks, - data_metagraph_snapshots, - data_metagraph_transactions, + data_global_snapshots, prisma, } from "../../prisma/seed"; -const validateTransaction = (tx) => { - const dbTxn = data_metagraph_transactions.filter( - (_dbTxn) => _dbTxn.hash === tx.hash - )[0]; + +const data_metagraphs = [ + { + id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + id: "DAG6666666666666666666666666666666666666", + created_at: new Date("2025-04-02T00:01:02Z"), + updated_at: new Date(), + }, +]; + +const data_metagraph_snapshots = [ + { + metagraph_id: data_metagraphs[0].id, + global_snapshot_hash: data_global_snapshots[0].hash, + ordinal: 1n, + hash: "1c1e16746af43bc1f93aadaf006cd981852a706e74f1d24b66a7e5e9fa4188e5", + height: 1n, + subheight: 1, + owner_address: null, + staking_address: null, + epoch_progress: 1n, + size: 1n, + last_snapshot_hash: + "0000000000000000000000000000000000000000000000000000000000000000", + fee: 1n, + version: "0.0.1", + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + metagraph_id: data_metagraphs[0].id, + global_snapshot_hash: data_global_snapshots[1].hash, + ordinal: 2n, + hash: "9bf40b2d2e355401bbca7a7924880ab799ffa91e95d1b93f3298b549758aac64", + + height: 2n, + subheight: 2, + owner_address: null, + staking_address: null, + epoch_progress: 1n, + size: 1n, + last_snapshot_hash: + "1c1e16746af43bc1f93aadaf006cd981852a706e74f1d24b66a7e5e9fa4188e5", + fee: 1n, + version: "0.0.1", + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + metagraph_id: data_metagraphs[1].id, + global_snapshot_hash: data_global_snapshots[1].hash, + ordinal: 1n, + hash: "9bf40b2d2e3123123123123123123123123123123123123123123549758aac64", + + height: 3n, + subheight: 2, + owner_address: null, + staking_address: null, + epoch_progress: 1n, + size: 1n, + last_snapshot_hash: + "9bf40b2d2e355401bbca7a7924880ab799ffa91e95d1b93f3298b549758aac64", + fee: 1n, + version: "0.0.1", + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; + +const data_metagraph_blocks = [ + { + metagraph_id: data_metagraphs[0].id, + hash: "33374138dd6f5f9846261d541dab33dadcbae8c9f5a39026336a34a3e2aafb93", + height: 12n, + metagraph_snapshot_hash: data_metagraph_snapshots[0].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + metagraph_id: data_metagraphs[0].id, + hash: "3d5a9616d65a6d98fe629f1a056489df9245a40d1e8589ed9d655c6fcb3ee361", + height: 14n, + metagraph_snapshot_hash: data_metagraph_snapshots[1].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + metagraph_id: data_metagraphs[1].id, + hash: "3d5a9616d65666666666666666666666666666666666666d9d655c6fcb3ee361", + height: 14n, + metagraph_snapshot_hash: data_metagraph_snapshots[2].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; +const data_metagraph_transactions = [ + { + metagraph_id: data_metagraphs[0].id, + hash: "39c9909d3b00552beaa5487c38110267675ab212b3b97654fc5ca9917cb7e72b", + source_addr: data_addresses[0].address, + destination_addr: data_addresses[1].address, + amount: 90790983n, + fee: 200000n, + salt: 1231231232n, + parent_ordinal: 21337n, + parent_hash: + "5056fdfbba0637dcecfc0b7fa3f441c745c852cf850c3bfc0dbc8a7410b8d722", + ordinal: 12n, + block_hash: data_metagraph_blocks[0].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + metagraph_id: data_metagraphs[0].id, + hash: "aa50e85a32e3e84c9b49880e103ee240f572a6febd255d97db1406f6c936af6f", + source_addr: data_addresses[1].address, + destination_addr: data_addresses[0].address, + amount: 90790983n, + fee: 100000n, + salt: 1234n, + parent_ordinal: 21337n, + parent_hash: + "39c9909d3b00552beaa5487c38110267675ab212b3b97654fc5ca9917cb7e72b", + ordinal: 123n, + block_hash: data_metagraph_blocks[1].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + metagraph_id: data_metagraphs[1].id, + hash: "aa50e85a32e3e84c9b496666666666666666666666666666666606f6c936af6f", + source_addr: data_addresses[2].address, + destination_addr: data_addresses[3].address, + amount: 123123n, + fee: 10000n, + salt: 124n, + parent_ordinal: 21338n, + parent_hash: + "39c9909d3b006666666666666666666666666666666666666c5ca9917cb7e72b", + ordinal: 123n, + block_hash: data_metagraph_blocks[2].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; +const data_metagraph_balance_changes = [ + { + metagraph_id: data_metagraphs[0].id, + metagraph_snapshot_hash: data_metagraph_snapshots[0].hash, + snapshot_ordinal: data_metagraph_snapshots[0].ordinal, + address: data_metagraph_transactions[0].destination_addr, + balance: data_metagraph_transactions[0].amount, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + metagraph_id: data_metagraphs[0].id, + metagraph_snapshot_hash: data_metagraph_snapshots[1].hash, + snapshot_ordinal: data_metagraph_snapshots[1].ordinal, + address: data_metagraph_transactions[1].destination_addr, + balance: data_metagraph_transactions[1].amount, + created_at: new Date("2025-04-02T10:00:02Z"), + updated_at: new Date(), + }, + { + metagraph_id: data_metagraphs[1].id, + metagraph_snapshot_hash: data_metagraph_snapshots[2].hash, + snapshot_ordinal: data_metagraph_snapshots[2].ordinal, + address: data_metagraph_transactions[2].destination_addr, + balance: data_metagraph_transactions[2].amount, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; + +const seedData = async () => { + await prisma.metagraphs.createManyAndReturn({ data: data_metagraphs }); + + await prisma.metagraph_snapshots.createManyAndReturn({ + data: data_metagraph_snapshots, + }); + + await prisma.metagraph_blocks.createManyAndReturn({ + data: data_metagraph_blocks, + }); + + await prisma.metagraph_transactions.createManyAndReturn({ + data: data_metagraph_transactions, + }); + + await prisma.metagraph_balance_changes.createManyAndReturn({ + data: data_metagraph_balance_changes, + }); +} + + beforeAll(async () => { + await seedData(); + }); + + +const validateTransaction = (tx, expected) => { const dbBlock = data_metagraph_blocks.filter( - (_dbBlock) => _dbBlock.hash === dbTxn.block_hash + (_dbBlock) => _dbBlock.hash === expected.block_hash )[0]; const dbSnapshot = data_metagraph_snapshots.filter( (_dbSnapshot) => _dbSnapshot.hash === dbBlock.metagraph_snapshot_hash )[0]; - expect(tx.hash).toBe(dbTxn.hash); - expect(tx.source).toBe(dbTxn.source_addr); - expect(tx.destination).toBe(dbTxn.destination_addr); - expect(tx.amount).toBe(Number(dbTxn.amount)); - expect(tx.fee).toBe(Number(dbTxn.fee)); + expect(tx.hash).toBe(expected.hash); + expect(tx.source).toBe(expected.source_addr); + expect(tx.destination).toBe(expected.destination_addr); + expect(tx.amount).toBe(Number(expected.amount)); + expect(tx.fee).toBe(Number(expected.fee)); expect(tx.snapshotHash).toBe(dbSnapshot.hash); expect(tx.snapshotOrdinal).toBe(Number(dbSnapshot.ordinal)); - expect(+new Date(tx.timestamp)).toBe(+new Date(dbTxn.created_at)); + expect(+new Date(tx.timestamp)).toBe(+new Date(expected.created_at)); }; -// These tests use a real database connection, so they're integration tests describe("Metagraph Handler Integration Tests", () => { describe("currencySnapshots", () => { it("should return a list of metagraph snapshots", async () => { - const event = createAPIGatewayEvent({}, { limit: "10" }); + const metagraph_id = data_metagraphs[0].id + const event = createAPIGatewayEvent({metagraph_id}, { limit: "10" }); const response: APIGatewayProxyResult = await metagraphHandler.currencySnapshots(event); expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); - expect(body.data.length).toBe(data_metagraph_snapshots.length); + const expected = data_metagraph_snapshots.filter(ms => ms.metagraph_id === metagraph_id) + + expect(body.data.length).toBe(expected.length); const testSnapshot = data_metagraph_snapshots.sort((a, b): number => { return a.ordinal < b.ordinal ? 1 : -1; @@ -63,7 +264,8 @@ describe("Metagraph Handler Integration Tests", () => { }); it("should handle pagination correctly", async () => { - const event = createAPIGatewayEvent({}, { limit: "1" }); + const metagraph_id = data_metagraphs[0].id + const event = createAPIGatewayEvent({metagraph_id}, { limit: "1" }); const response: APIGatewayProxyResult = await metagraphHandler.currencySnapshots(event); @@ -76,7 +278,7 @@ describe("Metagraph Handler Integration Tests", () => { // Try getting the next page const nextEvent = createAPIGatewayEvent( - {}, + {metagraph_id}, { limit: "1", next: body.meta.next, @@ -91,26 +293,31 @@ describe("Metagraph Handler Integration Tests", () => { // Ensure we got a different set of results expect(nextBody.data[0].hash).not.toBe(body.data[0].hash); }); + + it("should return not found on invalid metagraph", async () => { + const metagraph_id = "1234567" + const event = createAPIGatewayEvent({ metagraph_id }, { limit: "10" }); + const response: APIGatewayProxyResult = + await metagraphHandler.currencySnapshots(event); + + expect(response.statusCode).toBe(404); + + expect(response.body).toBe('{"message":"Not found","errors":["metagraph"]}'); + + }); + }); describe("currencyTransactions", () => { it("should return transactions sorted by snapshot ordinal descending", async () => { - const address = data_addresses[0]; + const metagraph_id = data_metagraphs[0].id - const event = createAPIGatewayEvent(); + const event = createAPIGatewayEvent({metagraph_id}); const response: APIGatewayProxyResult = await metagraphHandler.currencyTransactions(event); - const transactions = await prisma.metagraph_transactions.findMany({ - include: { - metagraph_blocks: { - include: { - metagraph_snapshot: { select: { ordinal: true } }, - }, - }, - }, - }); + const transactions = data_metagraph_transactions.filter(tx => tx.metagraph_id === metagraph_id) expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); @@ -122,32 +329,35 @@ describe("Metagraph Handler Integration Tests", () => { body.data[1].snapshotOrdinal, ]).toEqual([2, 1]); - validateTransaction(body.data[0]); - validateTransaction(body.data[1]); + validateTransaction(body.data[0], data_metagraph_transactions[1]); + validateTransaction(body.data[1], data_metagraph_transactions[0]); + }); + + it("should return not found on invalid metagraph", async () => { + const metagraph_id = "" + const event = createAPIGatewayEvent({ metagraph_id }, { limit: "10" }); + + const response: APIGatewayProxyResult = + await metagraphHandler.currencyTransactions(event); + + expect(response.statusCode).toBe(404); + + expect(response.body).toBe('{"message":"Not found","errors":["metagraph"]}'); + }); }); describe("currencyTransactionsByAddress", () => { it("should return transactions for specified address sorted by snapshot ordinal descending", async () => { const address = data_addresses[0].address; + const metagraph_id = data_metagraphs[0].id - const event = createAPIGatewayEvent({ address }, { limit: "10" }); + const event = createAPIGatewayEvent({ address, metagraph_id}, { limit: "10" }); const response: APIGatewayProxyResult = await metagraphHandler.currencyTransactionsByAddress(event); - const transactions = await prisma.metagraph_transactions.findMany({ - where: { - OR: [{ source_addr: address }, { destination_addr: address }], - }, - include: { - metagraph_blocks: { - include: { - metagraph_snapshot: { select: { ordinal: true } }, - }, - }, - }, - }); + const transactions = data_metagraph_transactions.filter(tx => tx.metagraph_id === metagraph_id && (tx.source_addr === address || tx.destination_addr === address)) expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); @@ -157,19 +367,21 @@ describe("Metagraph Handler Integration Tests", () => { expect([ body.data[0].snapshotOrdinal, body.data[1].snapshotOrdinal, - ]).toEqual([2, 1]); + ]).toEqual([2, 1]); - validateTransaction(body.data[0]); - validateTransaction(body.data[1]); + validateTransaction(body.data[0], data_metagraph_transactions[1]); + validateTransaction(body.data[1], data_metagraph_transactions[0]); }); }); describe("currencyBalanceByAddress", () => { - it("should return balance for a given address", async () => { - const testTxn = data_metagraph_transactions[0]; + it("should return the latest balance for a given address", async () => { + const testBalance = data_metagraph_balance_changes[1]; + const metagraph_id = testBalance.metagraph_id; const event = createAPIGatewayEvent({ - address: testTxn.destination_addr, + metagraph_id, + address: testBalance.address, }); const response: APIGatewayProxyResult = @@ -178,8 +390,8 @@ describe("Metagraph Handler Integration Tests", () => { expect(response.statusCode).toBe(200); const body = validateResponseStructure(response)["data"]; - expect(body.balance).toBe(Number(testTxn.amount)); - expect(body.address).toBe(testTxn.destination_addr); + expect(body.balance).toBe(Number(testBalance.balance)); + expect(body.address).toBe(testBalance.address); expect(body.ordinal).toBeDefined(); }); }); From 85257a8a6851e5acba107fb86bf337a790e68c3a Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Sun, 20 Apr 2025 21:10:43 -0300 Subject: [PATCH 28/63] Add delegated staking endpoints --- .../20250402214034_initial/migration.sql | 1383 ++++++++++------- prisma/schema.prisma | 461 +++--- prisma/seed.ts | 187 +++ routes/delegated-staking.yml | 48 + routes/metagraph.yml | 32 +- serverless.yml | 1 + src/handlers/dagHandler.ts | 2 +- src/handlers/delegatedStakingHandler.ts | 190 +++ src/handlers/metagraphHandler.ts | 34 +- .../handlers/delegatedStakingHandler.test.ts | 260 ++++ tests/handlers/metagraphHandler.test.ts | 21 +- 11 files changed, 1790 insertions(+), 829 deletions(-) create mode 100644 routes/delegated-staking.yml create mode 100644 src/handlers/delegatedStakingHandler.ts create mode 100644 tests/handlers/delegatedStakingHandler.test.ts diff --git a/prisma/migrations/20250402214034_initial/migration.sql b/prisma/migrations/20250402214034_initial/migration.sql index c7c23a4..27ca49d 100644 --- a/prisma/migrations/20250402214034_initial/migration.sql +++ b/prisma/migrations/20250402214034_initial/migration.sql @@ -1,736 +1,959 @@ --- CreateTable -CREATE TABLE "abstract_blocks" ( - "hash" VARCHAR NOT NULL, - "height" BIGINT NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "block_pkey" PRIMARY KEY ("hash") -); +-- DROP SCHEMA public; --- CreateTable -CREATE TABLE "abstract_transactions" ( - "hash" VARCHAR NOT NULL, - "source_addr" VARCHAR NOT NULL, - "amount" BIGINT NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "hash_pkey" PRIMARY KEY ("hash") -); --- CreateTable -CREATE TABLE "abstract_transactions_view" ( - "hash" VARCHAR NOT NULL, - "source_addr" VARCHAR NOT NULL, - "amount" BIGINT NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "table_name" TEXT NOT NULL, - CONSTRAINT "abstract_transactions_view_pkey" PRIMARY KEY ("hash") -); +-- DROP FUNCTION public.insert_into_parent_abstract_blocks(); --- CreateTable -CREATE TABLE "addresses" ( - "address" VARCHAR NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, +CREATE OR REPLACE FUNCTION public.insert_into_parent_abstract_blocks() + RETURNS trigger + LANGUAGE plpgsql +AS $function$ +BEGIN + INSERT INTO abstract_blocks (hash, height, created_at) + VALUES (NEW.hash, NEW.height, NEW.created_at) + ON CONFLICT (hash) DO NOTHING; + RETURN NEW; +END; +$function$ +; - CONSTRAINT "address_pkey" PRIMARY KEY ("address") -); +-- DROP FUNCTION public.insert_into_parent_abstract_transactions(); --- CreateTable -CREATE TABLE "block_parents" ( - "hash" VARCHAR NOT NULL, - "parent_proof_hash" VARCHAR NOT NULL, - "parent_height" BIGINT NOT NULL, +CREATE OR REPLACE FUNCTION public.insert_into_parent_abstract_transactions() + RETURNS trigger + LANGUAGE plpgsql +AS $function$ +BEGIN + INSERT INTO abstract_transactions (hash, source_addr, amount, created_at) + VALUES (NEW.hash, NEW.source_addr, NEW.amount, NEW.created_at) + ON CONFLICT (hash) DO NOTHING; - CONSTRAINT "block_parents_pkey" PRIMARY KEY ("hash","parent_proof_hash") -); + RETURN NEW; +END; +$function$ +; --- CreateTable -CREATE TABLE "dag_allow_spend_approvers" ( - "allow_spend_hash" VARCHAR NOT NULL, - "approver_address" VARCHAR NOT NULL, +-- DROP FUNCTION public.update_updated_at_column(); - CONSTRAINT "dag_allow_spend_approvers_pk" PRIMARY KEY ("allow_spend_hash","approver_address") -); +CREATE OR REPLACE FUNCTION public.update_updated_at_column() + RETURNS trigger + LANGUAGE plpgsql +AS $function$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$function$ +; --- CreateTable -CREATE TABLE "dag_allow_spend_blocks" ( - "round_id" UUID NOT NULL, - "global_snapshot_hash" VARCHAR NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "dag_allow_spend_blocks_pkey" PRIMARY KEY ("global_snapshot_hash") -); +-- public.abstract_blocks definition --- CreateTable -CREATE TABLE "dag_allow_spends" ( - "hash" VARCHAR NOT NULL, - "source_addr" VARCHAR NOT NULL, - "amount" BIGINT NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "destination_addr" VARCHAR NOT NULL, - "fee" BIGINT NOT NULL, - "parent_ordinal" BIGINT, - "parent_hash" VARCHAR, - "last_valid_epoch_progress" BIGINT NOT NULL, - "round_id" UUID NOT NULL, - "ordinal" BIGINT NOT NULL, - "snapshot_hash" VARCHAR NOT NULL, - - CONSTRAINT "dag_allow_spends_pk" PRIMARY KEY ("hash") -); +-- Drop table --- CreateTable -CREATE TABLE "dag_balance_changes" ( - "snapshot_hash" VARCHAR NOT NULL, - "address" VARCHAR NOT NULL, - "balance" BIGINT NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "snapshot_ordinal" BIGINT NOT NULL, +-- DROP TABLE abstract_blocks; - CONSTRAINT "dag_balance_change_pk" PRIMARY KEY ("snapshot_ordinal","address") +CREATE TABLE abstract_blocks ( + hash varchar NOT NULL, + height int8 NOT NULL, + created_at timestamp DEFAULT now() NOT NULL, + updated_at timestamp DEFAULT now() NOT NULL, + CONSTRAINT block_pkey PRIMARY KEY (hash) ); --- CreateTable -CREATE TABLE "dag_blocks" ( - "hash" VARCHAR NOT NULL, - "height" BIGINT NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "snapshot_hash" VARCHAR NOT NULL, - CONSTRAINT "dag_block_pk" PRIMARY KEY ("hash") -); +-- public.abstract_transactions definition --- CreateTable -CREATE TABLE "dag_reward_transactions" ( - "global_snapshot_hash" VARCHAR NOT NULL, - "destination_addr" VARCHAR NOT NULL, - "amount" BIGINT NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, +-- Drop table - CONSTRAINT "dag_reward_transaction_pk" PRIMARY KEY ("global_snapshot_hash","destination_addr") -); +-- DROP TABLE abstract_transactions; --- CreateTable -CREATE TABLE "dag_spend_transactions" ( - "hash" VARCHAR NOT NULL, - "source_addr" VARCHAR NOT NULL, - "amount" BIGINT NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "destination_addr" VARCHAR, - "allow_spend_ref" VARCHAR, - "snapshot_hash" VARCHAR, - - CONSTRAINT "dag_spend_transactions_pk" PRIMARY KEY ("hash") +CREATE TABLE abstract_transactions ( + hash varchar NOT NULL, + source_addr varchar NOT NULL, + amount int8 NOT NULL, + created_at timestamp DEFAULT now() NOT NULL, + updated_at timestamp DEFAULT now() NOT NULL, + CONSTRAINT hash_pkey PRIMARY KEY (hash) ); --- CreateTable -CREATE TABLE "dag_token_lock_blocks" ( - "round_id" UUID NOT NULL, - "global_snapshot_hash" VARCHAR NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "dag_token_lock_blocks_pkey" PRIMARY KEY ("round_id") -); +-- public.addresses definition --- CreateTable -CREATE TABLE "dag_token_locks" ( - "hash" VARCHAR NOT NULL, - "source_addr" VARCHAR NOT NULL, - "amount" BIGINT NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "ordinal" BIGINT NOT NULL, - "unlock_epoch" BIGINT, - "round_id" UUID NOT NULL, - "global_snapshot_hash" VARCHAR NOT NULL, - - CONSTRAINT "dag_token_locks_pk" PRIMARY KEY ("hash") -); +-- Drop table --- CreateTable -CREATE TABLE "dag_token_unlocks" ( - "hash" VARCHAR NOT NULL, - "source_addr" VARCHAR NOT NULL, - "amount" BIGINT NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "lock_reference_hash" VARCHAR NOT NULL, - "snapshot_hash" VARCHAR NOT NULL, - - CONSTRAINT "dag_token_unlocks_pk" PRIMARY KEY ("hash") -); +-- DROP TABLE addresses; --- CreateTable -CREATE TABLE "dag_transactions" ( - "hash" VARCHAR NOT NULL, - "source_addr" VARCHAR NOT NULL, - "amount" BIGINT NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "destination_addr" VARCHAR NOT NULL, - "fee" BIGINT NOT NULL, - "salt" BIGINT NOT NULL, - "parent_ordinal" BIGINT, - "parent_hash" VARCHAR, - "ordinal" BIGINT NOT NULL, - "block_hash" VARCHAR NOT NULL, - - CONSTRAINT "dag_transaction_pk" PRIMARY KEY ("hash") +CREATE TABLE addresses ( + address varchar NOT NULL, + created_at timestamp DEFAULT now() NOT NULL, + updated_at timestamp DEFAULT now() NOT NULL, + CONSTRAINT address_pkey PRIMARY KEY (address) ); --- CreateTable -CREATE TABLE "global_snapshot_proofs" ( - "id" VARCHAR NOT NULL, - "signature" VARCHAR NOT NULL, - "snapshot_hash" VARCHAR NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, +-- Table Triggers - CONSTRAINT "proof_pk" PRIMARY KEY ("snapshot_hash","id") -); +create trigger set_updated_at_addresses before +update + on + public.addresses for each row execute function update_updated_at_column(); --- CreateTable -CREATE TABLE "global_snapshots" ( - "hash" VARCHAR NOT NULL, - "ordinal" BIGINT NOT NULL, - "height" BIGINT NOT NULL, - "subheight" INTEGER NOT NULL, - "last_snapshot_hash" VARCHAR NOT NULL, - "metagraph_snapshot_count" BIGINT, - "epoch_progress" BIGINT, - "version" VARCHAR NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "global_snapshot_pk" PRIMARY KEY ("hash") -); --- CreateTable -CREATE TABLE "metagraph_allow_spend_approvers" ( - "allow_spend_hash" VARCHAR NOT NULL, - "approver_address" VARCHAR NOT NULL, +-- public.global_snapshots definition - CONSTRAINT "metagraph_allow_spend_approvers_pk" PRIMARY KEY ("allow_spend_hash","approver_address") -); +-- Drop table --- CreateTable -CREATE TABLE "metagraph_allow_spend_blocks" ( - "metagraph_id" VARCHAR NOT NULL, - "round_id" UUID NOT NULL, - "metagraph_snapshot_hash" VARCHAR NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, +-- DROP TABLE global_snapshots; - CONSTRAINT "metagraph_allow_spend_blocks_pkey" PRIMARY KEY ("round_id") +CREATE TABLE global_snapshots ( + hash varchar NOT NULL, + ordinal int8 NOT NULL, + height int8 NOT NULL, + subheight int4 NOT NULL, + last_snapshot_hash varchar NOT NULL, + metagraph_snapshot_count int8 NULL, + epoch_progress int8 NULL, + "version" varchar NOT NULL, + created_at timestamp DEFAULT now() NOT NULL, + updated_at timestamp DEFAULT now() NOT NULL, + CONSTRAINT global_snapshot_pk PRIMARY KEY (hash), + CONSTRAINT global_snapshot_unique UNIQUE (ordinal) ); --- CreateTable -CREATE TABLE "metagraph_allow_spends" ( - "hash" VARCHAR NOT NULL, - "source_addr" VARCHAR NOT NULL, - "amount" BIGINT NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "metagraph_id" VARCHAR NOT NULL, - "destination_addr" VARCHAR NOT NULL, - "fee" BIGINT NOT NULL, - "parent_ordinal" BIGINT, - "parent_hash" VARCHAR, - "last_valid_epoch_progress" BIGINT NOT NULL, - "round_id" UUID NOT NULL, - "ordinal" BIGINT NOT NULL, - "snapshot_hash" VARCHAR NOT NULL, - - CONSTRAINT "metagraph_allow_spends_pk" PRIMARY KEY ("hash") -); +-- Table Triggers --- CreateTable -CREATE TABLE "metagraph_balance_changes" ( - "metagraph_id" VARCHAR NOT NULL, - "metagraph_snapshot_hash" VARCHAR NOT NULL, - "address" VARCHAR NOT NULL, - "balance" BIGINT NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "metagraph_snapshot_ordinal" BIGINT NOT NULL, - - CONSTRAINT "metagraph_balance_change_pk" PRIMARY KEY ("metagraph_id","address","metagraph_snapshot_ordinal") -); +create trigger set_updated_at_global_snapshot before +update + on + public.global_snapshots for each row execute function update_updated_at_column(); --- CreateTable -CREATE TABLE "metagraph_blocks" ( - "hash" VARCHAR NOT NULL, - "height" BIGINT NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "metagraph_id" VARCHAR NOT NULL, - "metagraph_snapshot_hash" VARCHAR NOT NULL, - CONSTRAINT "metagraph_block_pk" PRIMARY KEY ("metagraph_id","hash") -); +-- public.metagraphs definition --- CreateTable -CREATE TABLE "metagraph_fee_transactions" ( - "hash" VARCHAR NOT NULL, - "source_addr" VARCHAR NOT NULL, - "amount" BIGINT NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "metagraph_id" VARCHAR NOT NULL, - "metagraph_snapshot_hash" VARCHAR NOT NULL, - "destination_addr" VARCHAR NOT NULL, - "data_update_ref" VARCHAR, - "metagraph_snapshot_ordinal" BIGINT, - - CONSTRAINT "fee_transaction_pk" PRIMARY KEY ("hash") -); +-- Drop table --- CreateTable -CREATE TABLE "metagraph_reward_transactions" ( - "metagraph_id" VARCHAR NOT NULL, - "metagraph_snapshot_hash" VARCHAR NOT NULL, - "destination_addr" VARCHAR NOT NULL, - "amount" BIGINT NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, +-- DROP TABLE metagraphs; - CONSTRAINT "metagraph_reward_transaction_pk" PRIMARY KEY ("metagraph_id","metagraph_snapshot_hash","destination_addr") +CREATE TABLE metagraphs ( + id varchar NOT NULL, + created_at timestamp DEFAULT now() NOT NULL, + updated_at timestamp DEFAULT now() NOT NULL, + CONSTRAINT metagraph_pkey PRIMARY KEY (id) ); --- CreateTable -CREATE TABLE "metagraph_snapshots" ( - "metagraph_id" VARCHAR NOT NULL, - "ordinal" BIGINT NOT NULL, - "global_snapshot_hash" VARCHAR, - "hash" VARCHAR NOT NULL, - "height" BIGINT NOT NULL, - "subheight" INTEGER NOT NULL, - "last_snapshot_hash" VARCHAR, - "fee" BIGINT, - "owner_address" VARCHAR, - "staking_address" VARCHAR, - "epoch_progress" BIGINT, - "version" VARCHAR NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "metagraph_snapshot_pk" PRIMARY KEY ("metagraph_id","hash") -); +-- Table Triggers --- CreateTable -CREATE TABLE "metagraph_spend_transactions" ( - "hash" VARCHAR NOT NULL, - "source_addr" VARCHAR NOT NULL, - "amount" BIGINT NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "metagraph_id" VARCHAR NOT NULL, - "destination_addr" VARCHAR NOT NULL, - "allow_spend_ref" VARCHAR, - "snapshot_hash" VARCHAR, - - CONSTRAINT "metagraph_spend_transactions_pk" PRIMARY KEY ("hash") -); +create trigger set_updated_at_metagraphs before +update + on + public.metagraphs for each row execute function update_updated_at_column(); --- CreateTable -CREATE TABLE "metagraph_token_lock_blocks" ( - "metagraph_id" VARCHAR NOT NULL, - "metagraph_snapshot_hash" VARCHAR NOT NULL, - "round_id" UUID NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "metagraph_token_lock_blocks_pkey" PRIMARY KEY ("metagraph_id","metagraph_snapshot_hash") -); +-- public.block_parents definition --- CreateTable -CREATE TABLE "metagraph_token_locks" ( - "hash" VARCHAR NOT NULL, - "source_addr" VARCHAR NOT NULL, - "amount" BIGINT NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "metagraph_id" VARCHAR NOT NULL, - "ordinal" BIGINT NOT NULL, - "unlock_epoch" BIGINT, - "round_id" UUID NOT NULL, - "snapshot_hash" VARCHAR NOT NULL, - - CONSTRAINT "metagraph_token_locks_pk" PRIMARY KEY ("hash") -); +-- Drop table --- CreateTable -CREATE TABLE "metagraph_token_unlocks" ( - "hash" VARCHAR NOT NULL, - "source_addr" VARCHAR NOT NULL, - "amount" BIGINT NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "metagraph_id" VARCHAR NOT NULL, - "lock_reference_ordinal" BIGINT NOT NULL, - "lock_reference_hash" VARCHAR NOT NULL, - "snapshot_hash" VARCHAR NOT NULL, - - CONSTRAINT "metagraph_token_unlocks_pk" PRIMARY KEY ("hash") -); +-- DROP TABLE block_parents; --- CreateTable -CREATE TABLE "metagraph_transactions" ( - "hash" VARCHAR NOT NULL, - "source_addr" VARCHAR NOT NULL, - "amount" BIGINT NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "metagraph_id" VARCHAR NOT NULL, - "destination_addr" VARCHAR NOT NULL, - "fee" BIGINT NOT NULL, - "salt" BIGINT NOT NULL, - "parent_ordinal" BIGINT NOT NULL, - "parent_hash" VARCHAR NOT NULL, - "ordinal" BIGINT NOT NULL, - "block_hash" VARCHAR NOT NULL, - - CONSTRAINT "metagraph_transaction_pk" PRIMARY KEY ("metagraph_id","hash") +CREATE TABLE block_parents ( + hash varchar NOT NULL, + parent_proof_hash varchar NOT NULL, + parent_height int8 NOT NULL, + CONSTRAINT block_parents_pkey PRIMARY KEY (hash, parent_proof_hash), + CONSTRAINT block_parents_block_fk FOREIGN KEY (hash) REFERENCES abstract_blocks(hash) ON DELETE CASCADE ); +CREATE INDEX block_parents_hash_idx ON public.block_parents USING btree (hash); --- CreateTable -CREATE TABLE "metagraphs" ( - "id" VARCHAR NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, +-- public.dag_allow_spends definition - CONSTRAINT "metagraph_pkey" PRIMARY KEY ("id") -); +-- Drop table + +-- DROP TABLE dag_allow_spends; + +CREATE TABLE dag_allow_spends ( + destination_addr varchar NOT NULL, + fee int8 NOT NULL, + parent_ordinal int8 NULL, + parent_hash varchar NULL, + last_valid_epoch_progress int8 NOT NULL, + round_id uuid NOT NULL, + ordinal int8 NOT NULL, + snapshot_hash varchar NOT NULL, + CONSTRAINT dag_allow_spends_ordinal UNIQUE (ordinal), + CONSTRAINT dag_allow_spends_pk PRIMARY KEY (hash), + CONSTRAINT dag_allow_spends_destination_addr_fk FOREIGN KEY (destination_addr) REFERENCES addresses(address) ON DELETE CASCADE, + CONSTRAINT dag_allow_spends_source_addr_fk FOREIGN KEY (source_addr) REFERENCES addresses(address) ON DELETE CASCADE +) +INHERITS (public.abstract_transactions); +CREATE INDEX dag_allow_spends_round_id_idx ON public.dag_allow_spends USING btree (round_id); + +-- Table Triggers --- CreateTable -CREATE TABLE "dag_expired_spend_transactions" ( - "hash" VARCHAR NOT NULL, - "source_addr" VARCHAR NOT NULL, - "amount" BIGINT NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "allow_spend_ref" VARCHAR, - "snapshot_hash" VARCHAR, - - CONSTRAINT "dag_expired_spend_transactions_pk" PRIMARY KEY ("hash") +create trigger set_updated_at_dag_allow_spends before +update + on + public.dag_allow_spends for each row execute function update_updated_at_column(); +create trigger trigger_insert_abstract_transactions_dag_allow_spends after +insert + on + public.dag_allow_spends for each row execute function insert_into_parent_abstract_transactions(); + + +-- public.dag_balance_changes definition + +-- Drop table + +-- DROP TABLE dag_balance_changes; + +CREATE TABLE dag_balance_changes ( + snapshot_hash varchar NOT NULL, + address varchar NOT NULL, + balance int8 NOT NULL, + created_at timestamp DEFAULT now() NOT NULL, + updated_at timestamp DEFAULT now() NOT NULL, + snapshot_ordinal int8 NOT NULL, + CONSTRAINT dag_balance_change_pk PRIMARY KEY (snapshot_ordinal, address), + CONSTRAINT dag_balance_change_address_fk FOREIGN KEY (address) REFERENCES addresses(address) ON DELETE CASCADE, + CONSTRAINT dag_balance_change_global_snapshot_fk FOREIGN KEY (snapshot_hash) REFERENCES global_snapshots(hash) ON DELETE CASCADE ); +CREATE INDEX dag_balance_changes_address_idx ON public.dag_balance_changes USING btree (address, created_at); + +-- Table Triggers + +create trigger set_updated_at_dag_balance_change before +update + on + public.dag_balance_changes for each row execute function update_updated_at_column(); + + +-- public.dag_blocks definition + +-- Drop table + +-- DROP TABLE dag_blocks; + +CREATE TABLE dag_blocks ( + snapshot_hash varchar NOT NULL, + CONSTRAINT dag_block_pk PRIMARY KEY (hash), + CONSTRAINT dag_block_global_snapshot_fk FOREIGN KEY (snapshot_hash) REFERENCES global_snapshots(hash) ON DELETE CASCADE +) +INHERITS (public.abstract_blocks); +CREATE INDEX dag_blocks_snapshot_hash_idx ON public.dag_blocks USING btree (snapshot_hash); + +-- Table Triggers + +create trigger set_updated_at_dag_blocks before +update + on + public.dag_blocks for each row execute function update_updated_at_column(); +create trigger trigger_insert_abstract_blocks_dag after +insert + on + public.dag_blocks for each row execute function insert_into_parent_abstract_blocks(); + --- CreateTable -CREATE TABLE "metagraph_expired_spend_transactions" ( - "hash" VARCHAR NOT NULL, - "source_addr" VARCHAR NOT NULL, - "amount" BIGINT NOT NULL, - "created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "metagraph_id" VARCHAR NOT NULL, - "allow_spend_ref" VARCHAR, - "snapshot_hash" VARCHAR, - - CONSTRAINT "metagraph_expired_spend_transactions_pk" PRIMARY KEY ("hash") +-- public.dag_reward_transactions definition + +-- Drop table + +-- DROP TABLE dag_reward_transactions; + +CREATE TABLE dag_reward_transactions ( + global_snapshot_hash varchar NOT NULL, + destination_addr varchar NOT NULL, + amount int8 NOT NULL, + created_at timestamp DEFAULT now() NOT NULL, + updated_at timestamp DEFAULT now() NOT NULL, + CONSTRAINT dag_reward_transaction_pk PRIMARY KEY (global_snapshot_hash, destination_addr), + CONSTRAINT dag_reward_transaction_global_snapshot_fk FOREIGN KEY (global_snapshot_hash) REFERENCES global_snapshots(hash) ON DELETE CASCADE, + CONSTRAINT dag_reward_transactions_destination_addr_fk FOREIGN KEY (destination_addr) REFERENCES addresses(address) ON DELETE CASCADE ); +CREATE INDEX dag_reward_transactions_global_snapshot_hash_idx ON public.dag_reward_transactions USING btree (global_snapshot_hash); + +-- Table Triggers + +create trigger set_updated_at_dag_reward_transaction before +update + on + public.dag_reward_transactions for each row execute function update_updated_at_column(); + + +-- public.dag_spend_transactions definition --- CreateIndex -CREATE INDEX "block_parents_hash_idx" ON "block_parents"("hash"); +-- Drop table --- CreateIndex -CREATE UNIQUE INDEX "dag_allow_spend_blocks_unique" ON "dag_allow_spend_blocks"("round_id"); +-- DROP TABLE dag_spend_transactions; --- CreateIndex -CREATE UNIQUE INDEX "dag_allow_spends_ordinal" ON "dag_allow_spends"("ordinal"); +CREATE TABLE dag_spend_transactions ( + destination_addr varchar NULL, + allow_spend_ref varchar NULL, + snapshot_hash varchar NOT NULL, + CONSTRAINT dag_spend_transactions_pk PRIMARY KEY (hash), + CONSTRAINT dag_spend_transactions_dag_allow_spends_fk FOREIGN KEY (allow_spend_ref) REFERENCES dag_allow_spends(hash) ON DELETE CASCADE, + CONSTRAINT dag_spend_transactions_destination_addr_fk FOREIGN KEY (destination_addr) REFERENCES addresses(address) ON DELETE CASCADE +) +INHERITS (public.abstract_transactions); --- CreateIndex -CREATE INDEX "dag_allow_spends_round_id_idx" ON "dag_allow_spends"("round_id"); +create trigger set_updated_at_dag_spend_transactions before +update + on + public.dag_spend_transactions for each row execute function update_updated_at_column(); +create trigger trigger_insert_abstract_transactions_dag_spend_transactions after +insert + on + public.dag_spend_transactions for each row execute function insert_into_parent_abstract_transactions(); --- CreateIndex -CREATE INDEX "dag_balance_changes_address_idx" ON "dag_balance_changes"("address", "created_at"); --- CreateIndex -CREATE INDEX "dag_blocks_snapshot_hash_idx" ON "dag_blocks"("snapshot_hash"); +CREATE TABLE dag_expired_spend_transactions ( + allow_spend_ref varchar NULL, + snapshot_hash varchar NOT NULL, + CONSTRAINT dag_expired_spend_transactions_pk PRIMARY KEY (hash), + CONSTRAINT dag_expired_spend_transactions_dag_allow_spends_fk FOREIGN KEY (allow_spend_ref) REFERENCES dag_allow_spends(hash) ON DELETE CASCADE +) +INHERITS (public.abstract_transactions); --- CreateIndex -CREATE INDEX "dag_reward_transactions_global_snapshot_hash_idx" ON "dag_reward_transactions"("global_snapshot_hash"); +-- Table Triggers --- CreateIndex -CREATE UNIQUE INDEX "dag_token_lock_blocks_unique" ON "dag_token_lock_blocks"("global_snapshot_hash"); +create trigger set_updated_at_dag_expired_spend_transactions before +update + on + public.dag_expired_spend_transactions for each row execute function update_updated_at_column(); +create trigger trigger_insert_abstract_transactions_dag_expired_spend_transact after +insert + on + public.dag_expired_spend_transactions for each row execute function insert_into_parent_abstract_transactions(); --- CreateIndex -CREATE UNIQUE INDEX "dag_token_locks_unique" ON "dag_token_locks"("ordinal"); --- CreateIndex -CREATE UNIQUE INDEX "dag_token_unlocks_lock_reference_hash_key" ON "dag_token_unlocks"("lock_reference_hash"); --- CreateIndex -CREATE UNIQUE INDEX "global_snapshot_unique" ON "global_snapshots"("ordinal"); +-- public.dag_token_lock_blocks definition --- CreateIndex -CREATE UNIQUE INDEX "metagraph_allow_spends_ordinal" ON "metagraph_allow_spends"("ordinal"); +-- Drop table --- CreateIndex -CREATE UNIQUE INDEX "hash" ON "metagraph_blocks"("hash"); +-- DROP TABLE dag_token_lock_blocks; + +CREATE TABLE dag_token_lock_blocks ( + round_id uuid NOT NULL, + global_snapshot_hash varchar NOT NULL, + created_at timestamp DEFAULT now() NOT NULL, + updated_at timestamp DEFAULT now() NOT NULL, + CONSTRAINT dag_token_lock_blocks_pkey PRIMARY KEY (round_id), + CONSTRAINT dag_token_lock_blocks_unique UNIQUE (global_snapshot_hash), + CONSTRAINT dag_token_lock_blocks_global_snapshot_fk FOREIGN KEY (global_snapshot_hash) REFERENCES global_snapshots(hash) ON DELETE CASCADE +); --- CreateIndex -CREATE UNIQUE INDEX "metagraph_snapshot_unique" ON "metagraph_snapshots"("metagraph_id", "ordinal"); --- CreateIndex -CREATE UNIQUE INDEX "metagraph_token_lock_blocks_unique" ON "metagraph_token_lock_blocks"("metagraph_id", "round_id"); +-- public.dag_token_locks definition --- CreateIndex -CREATE UNIQUE INDEX "metagraph_token_locks_unique" ON "metagraph_token_locks"("metagraph_id", "ordinal"); +-- Drop table --- CreateIndex -CREATE UNIQUE INDEX "metagraph_token_unlocks_lock_reference_hash_key" ON "metagraph_token_unlocks"("lock_reference_hash"); +-- DROP TABLE dag_token_locks; --- CreateIndex -CREATE UNIQUE INDEX "metagraph_token_unlocks_lock_reference_ordinal_key" ON "metagraph_token_unlocks"("lock_reference_ordinal"); +CREATE TABLE dag_token_locks ( + ordinal int8 NOT NULL, + unlock_epoch int8 NULL, + round_id uuid NOT NULL, + parent_hash varchar NULL, + snapshot_hash varchar NOT NULL, + CONSTRAINT dag_token_locks_pk PRIMARY KEY (hash), + CONSTRAINT dag_token_locks_unique UNIQUE (hash, ordinal), + CONSTRAINT dag_token_locks_source_addr_fk FOREIGN KEY (source_addr) REFERENCES addresses(address) ON DELETE CASCADE +) +INHERITS (public.abstract_transactions); --- AddForeignKey -ALTER TABLE "abstract_blocks" ADD CONSTRAINT "dag_blocks_hash_fk" FOREIGN KEY ("hash") REFERENCES "dag_blocks"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; +-- Table Triggers --- AddForeignKey -ALTER TABLE "abstract_blocks" ADD CONSTRAINT "metagraph_blocks_hash_fk" FOREIGN KEY ("hash") REFERENCES "metagraph_blocks"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; +create trigger set_updated_at_dag_token_locks before +update + on + public.dag_token_locks for each row execute function update_updated_at_column(); +create trigger trigger_insert_abstract_transactions_dag_token_locks after +insert + on + public.dag_token_locks for each row execute function insert_into_parent_abstract_transactions(); --- AddForeignKey -ALTER TABLE "abstract_transactions_view" ADD CONSTRAINT "dag_token_locks_ref" FOREIGN KEY ("hash") REFERENCES "dag_token_locks"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; --- AddForeignKey -ALTER TABLE "abstract_transactions_view" ADD CONSTRAINT "metagraph_token_locks_ref" FOREIGN KEY ("hash") REFERENCES "metagraph_token_locks"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; +-- public.dag_token_unlocks definition --- AddForeignKey -ALTER TABLE "abstract_transactions_view" ADD CONSTRAINT "dag_token_unlocks_ref" FOREIGN KEY ("hash") REFERENCES "dag_token_unlocks"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; +-- Drop table --- AddForeignKey -ALTER TABLE "abstract_transactions_view" ADD CONSTRAINT "metagraph_token_unlocks_ref" FOREIGN KEY ("hash") REFERENCES "metagraph_token_unlocks"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; +-- DROP TABLE dag_token_unlocks; --- AddForeignKey -ALTER TABLE "abstract_transactions_view" ADD CONSTRAINT "dag_allow_spend_ref" FOREIGN KEY ("hash") REFERENCES "dag_allow_spends"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; +CREATE TABLE dag_token_unlocks ( + lock_reference_ordinal int8 NOT NULL, + lock_reference_hash varchar NOT NULL, + snapshot_hash varchar NOT NULL, + parent_hash varchar NOT NULL, + CONSTRAINT dag_token_unlocks_pk PRIMARY KEY (lock_reference_ordinal, lock_reference_hash), + CONSTRAINT dag_token_unlocks_token_locks_fk FOREIGN KEY (lock_reference_hash,lock_reference_ordinal) REFERENCES dag_token_locks(hash,ordinal) ON DELETE CASCADE, + CONSTRAINT ddag_token_unlocks_address_fk FOREIGN KEY (source_addr) REFERENCES addresses(address) ON DELETE CASCADE +) +INHERITS (public.abstract_transactions); + +-- Table Triggers + +create trigger set_updated_at_dag_token_unlocks before +update + on + public.dag_token_unlocks for each row execute function update_updated_at_column(); +create trigger trigger_insert_abstract_transactions_dag_token_unlocks after +insert + on + public.dag_token_unlocks for each row execute function insert_into_parent_abstract_transactions(); --- AddForeignKey -ALTER TABLE "abstract_transactions_view" ADD CONSTRAINT "metagraph_allow_spend_ref" FOREIGN KEY ("hash") REFERENCES "metagraph_allow_spends"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; --- AddForeignKey -ALTER TABLE "abstract_transactions_view" ADD CONSTRAINT "dag_spend_transaction_ref" FOREIGN KEY ("hash") REFERENCES "dag_spend_transactions"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; +-- public.dag_transactions definition --- AddForeignKey -ALTER TABLE "abstract_transactions_view" ADD CONSTRAINT "metagraph_spend_transaction_ref" FOREIGN KEY ("hash") REFERENCES "metagraph_spend_transactions"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; +-- Drop table --- AddForeignKey -ALTER TABLE "abstract_transactions_view" ADD CONSTRAINT "dag_expired_spend_transaction_ref" FOREIGN KEY ("hash") REFERENCES "dag_expired_spend_transactions"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; +-- DROP TABLE dag_transactions; --- AddForeignKey -ALTER TABLE "abstract_transactions_view" ADD CONSTRAINT "metagraph_expired_spend_transaction_ref" FOREIGN KEY ("hash") REFERENCES "metagraph_expired_spend_transactions"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; +CREATE TABLE dag_transactions ( + destination_addr varchar NOT NULL, + fee int8 NOT NULL, + salt int8 NOT NULL, + parent_ordinal int8 NULL, + parent_hash varchar NULL, + ordinal int8 NOT NULL, + block_hash varchar NOT NULL, + CONSTRAINT dag_transaction_pk PRIMARY KEY (hash), + CONSTRAINT dag_transaction_dag_block_fk FOREIGN KEY (block_hash) REFERENCES dag_blocks(hash) ON DELETE CASCADE, + CONSTRAINT dag_transactions_destination_addr_fk FOREIGN KEY (destination_addr) REFERENCES addresses(address) ON DELETE CASCADE, + CONSTRAINT dag_transactions_source_addr_fk FOREIGN KEY (source_addr) REFERENCES addresses(address) ON DELETE CASCADE +) +INHERITS (public.abstract_transactions); --- AddForeignKey -ALTER TABLE "abstract_transactions_view" ADD CONSTRAINT "metagraph_fee_transaction_ref" FOREIGN KEY ("hash") REFERENCES "metagraph_fee_transactions"("hash") ON DELETE RESTRICT ON UPDATE CASCADE; +-- Table Triggers --- AddForeignKey -ALTER TABLE "block_parents" ADD CONSTRAINT "block_parents_block_fk" FOREIGN KEY ("hash") REFERENCES "abstract_blocks"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; +create trigger set_updated_at_dag_transaction before +update + on + public.dag_transactions for each row execute function update_updated_at_column(); +create trigger trigger_insert_abstract_transactions_dag_transactions after +insert + on + public.dag_transactions for each row execute function insert_into_parent_abstract_transactions(); --- AddForeignKey -ALTER TABLE "dag_allow_spend_approvers" ADD CONSTRAINT "dag_allow_spend_approvers_address_fk" FOREIGN KEY ("approver_address") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; --- AddForeignKey -ALTER TABLE "dag_allow_spend_approvers" ADD CONSTRAINT "dag_allow_spends_fk" FOREIGN KEY ("allow_spend_hash") REFERENCES "dag_allow_spends"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; +-- public.global_snapshot_proofs definition --- AddForeignKey -ALTER TABLE "dag_allow_spend_blocks" ADD CONSTRAINT "dag_allow_spend_blocks_global_snapshot_fk" FOREIGN KEY ("global_snapshot_hash") REFERENCES "global_snapshots"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; +-- Drop table --- AddForeignKey -ALTER TABLE "dag_allow_spends" ADD CONSTRAINT "dag_allow_spends_destination_addr_fk" FOREIGN KEY ("destination_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; +-- DROP TABLE global_snapshot_proofs; --- AddForeignKey -ALTER TABLE "dag_allow_spends" ADD CONSTRAINT "dag_allow_spends_source_addr_fk" FOREIGN KEY ("source_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; +CREATE TABLE global_snapshot_proofs ( + id varchar NOT NULL, + signature varchar NOT NULL, + snapshot_hash varchar NOT NULL, + created_at timestamp DEFAULT now() NOT NULL, + updated_at timestamp DEFAULT now() NOT NULL, + CONSTRAINT proof_pk PRIMARY KEY (snapshot_hash, id), + CONSTRAINT proof_global_snapshot_fk FOREIGN KEY (snapshot_hash) REFERENCES global_snapshots(hash) ON DELETE CASCADE +); + +-- Table Triggers + +create trigger set_updated_at_proof before +update + on + public.global_snapshot_proofs for each row execute function update_updated_at_column(); --- AddForeignKey -ALTER TABLE "dag_allow_spends" ADD CONSTRAINT "dag_allow_spend_blocks_global_snapshot_fk" FOREIGN KEY ("snapshot_hash") REFERENCES "global_snapshots"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; --- AddForeignKey -ALTER TABLE "dag_balance_changes" ADD CONSTRAINT "dag_balance_change_address_fk" FOREIGN KEY ("address") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; +-- public.metagraph_snapshots definition --- AddForeignKey -ALTER TABLE "dag_balance_changes" ADD CONSTRAINT "dag_balance_change_global_snapshot_fk" FOREIGN KEY ("snapshot_hash") REFERENCES "global_snapshots"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; +-- Drop table --- AddForeignKey -ALTER TABLE "dag_blocks" ADD CONSTRAINT "dag_block_global_snapshot_fk" FOREIGN KEY ("snapshot_hash") REFERENCES "global_snapshots"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; +-- DROP TABLE metagraph_snapshots; --- AddForeignKey -ALTER TABLE "dag_reward_transactions" ADD CONSTRAINT "dag_reward_transaction_global_snapshot_fk" FOREIGN KEY ("global_snapshot_hash") REFERENCES "global_snapshots"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; +CREATE TABLE metagraph_snapshots ( + metagraph_id varchar NOT NULL, + ordinal int8 NOT NULL, + global_snapshot_hash varchar NULL, + hash varchar NOT NULL, + height int8 NOT NULL, + subheight int4 NOT NULL, + last_snapshot_hash varchar NULL, + fee int8 NULL, + owner_address varchar NULL, + staking_address varchar NULL, + epoch_progress int8 NULL, + "size" int8 NULL, + "version" varchar NOT NULL, + created_at timestamp DEFAULT now() NOT NULL, + updated_at timestamp DEFAULT now() NOT NULL, + CONSTRAINT metagraph_snapshot_pk PRIMARY KEY (metagraph_id, hash), + CONSTRAINT metagraph_snapshot_unique UNIQUE (metagraph_id, ordinal), + CONSTRAINT metagraph_id_fk FOREIGN KEY (metagraph_id) REFERENCES metagraphs(id) ON DELETE CASCADE, + CONSTRAINT metagraph_snapshots_global_snapshots_fk FOREIGN KEY (global_snapshot_hash) REFERENCES global_snapshots(hash) ON DELETE CASCADE, + CONSTRAINT owner_address_fk FOREIGN KEY (owner_address) REFERENCES addresses(address) ON DELETE CASCADE, + CONSTRAINT staking_address_fk FOREIGN KEY (staking_address) REFERENCES addresses(address) ON DELETE CASCADE +); --- AddForeignKey -ALTER TABLE "dag_reward_transactions" ADD CONSTRAINT "dag_reward_transactions_destination_addr_fk" FOREIGN KEY ("destination_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; +-- Table Triggers --- AddForeignKey -ALTER TABLE "dag_spend_transactions" ADD CONSTRAINT "dag_spend_transactions_dag_allow_spends_fk" FOREIGN KEY ("allow_spend_ref") REFERENCES "dag_allow_spends"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; +create trigger set_updated_at_metagraph_snapshot before +update + on + public.metagraph_snapshots for each row execute function update_updated_at_column(); --- AddForeignKey -ALTER TABLE "dag_spend_transactions" ADD CONSTRAINT "dag_spend_transactions_destination_addr_fk" FOREIGN KEY ("destination_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; --- AddForeignKey -ALTER TABLE "dag_token_lock_blocks" ADD CONSTRAINT "dag_token_lock_blocks_global_snapshot_fk" FOREIGN KEY ("global_snapshot_hash") REFERENCES "global_snapshots"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; +-- public.metagraph_token_lock_blocks definition --- AddForeignKey -ALTER TABLE "dag_token_locks" ADD CONSTRAINT "dag_token_locks_source_addr_fk" FOREIGN KEY ("source_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; +-- Drop table --- AddForeignKey -ALTER TABLE "dag_token_locks" ADD CONSTRAINT "dag_token_locks_round_id_fkey" FOREIGN KEY ("round_id") REFERENCES "dag_token_lock_blocks"("round_id") ON DELETE RESTRICT ON UPDATE CASCADE; +-- DROP TABLE metagraph_token_lock_blocks; --- AddForeignKey -ALTER TABLE "dag_token_locks" ADD CONSTRAINT "dag_token_lock_global_snapshot_fk" FOREIGN KEY ("global_snapshot_hash") REFERENCES "global_snapshots"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; +CREATE TABLE metagraph_token_lock_blocks ( + metagraph_id varchar NOT NULL, + metagraph_snapshot_hash varchar NOT NULL, + round_id uuid NOT NULL, + created_at timestamp DEFAULT now() NOT NULL, + updated_at timestamp DEFAULT now() NOT NULL, + CONSTRAINT metagraph_token_lock_blocks_pkey PRIMARY KEY (metagraph_id, metagraph_snapshot_hash), + CONSTRAINT metagraph_token_lock_blocks_unique UNIQUE (metagraph_id, round_id), + CONSTRAINT metagraph_token_lock_blocks_metagraph_id_fk FOREIGN KEY (metagraph_id) REFERENCES metagraphs(id) ON DELETE CASCADE, + CONSTRAINT metagraph_token_lock_blocks_metagraph_snapshot_fk FOREIGN KEY (metagraph_id,metagraph_snapshot_hash) REFERENCES metagraph_snapshots(metagraph_id,hash) ON DELETE CASCADE +); --- AddForeignKey -ALTER TABLE "dag_token_unlocks" ADD CONSTRAINT "dag_token_unlocks_token_locks_fk" FOREIGN KEY ("lock_reference_hash") REFERENCES "dag_token_locks"("hash") ON DELETE NO ACTION ON UPDATE NO ACTION; +-- Table Triggers --- AddForeignKey -ALTER TABLE "dag_token_unlocks" ADD CONSTRAINT "ddag_token_unlocks_address_fk" FOREIGN KEY ("source_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; +create trigger set_updated_at_metagraph_token_lock_blocks before +update + on + public.metagraph_token_lock_blocks for each row execute function update_updated_at_column(); --- AddForeignKey -ALTER TABLE "dag_token_unlocks" ADD CONSTRAINT "dag_token_unlock_global_snapshot_fk" FOREIGN KEY ("snapshot_hash") REFERENCES "global_snapshots"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; --- AddForeignKey -ALTER TABLE "dag_transactions" ADD CONSTRAINT "dag_transaction_dag_block_fk" FOREIGN KEY ("block_hash") REFERENCES "dag_blocks"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; +-- public.metagraph_token_locks definition --- AddForeignKey -ALTER TABLE "dag_transactions" ADD CONSTRAINT "dag_transactions_destination_addr_fk" FOREIGN KEY ("destination_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; +-- Drop table --- AddForeignKey -ALTER TABLE "dag_transactions" ADD CONSTRAINT "dag_transactions_source_addr_fk" FOREIGN KEY ("source_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; +-- DROP TABLE metagraph_token_locks; --- AddForeignKey -ALTER TABLE "global_snapshot_proofs" ADD CONSTRAINT "proof_global_snapshot_fk" FOREIGN KEY ("snapshot_hash") REFERENCES "global_snapshots"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; +CREATE TABLE metagraph_token_locks ( + metagraph_id varchar NOT NULL, + ordinal int8 NOT NULL, + unlock_epoch int8 NOT NULL, + round_id varchar NOT NULL, + parent_hash varchar NULL, + snapshot_hash varchar NOT NULL, + CONSTRAINT metagraph_token_locks_pk PRIMARY KEY (metagraph_id, hash), + CONSTRAINT metagraph_token_locks_unique UNIQUE (metagraph_id, ordinal), + CONSTRAINT metagraph_id_fk FOREIGN KEY (metagraph_id) REFERENCES metagraphs(id) ON DELETE CASCADE, + CONSTRAINT metagraph_token_locks_source_addr_fk FOREIGN KEY (source_addr) REFERENCES addresses(address) ON DELETE CASCADE +) +INHERITS (public.abstract_transactions); --- AddForeignKey -ALTER TABLE "metagraph_allow_spend_approvers" ADD CONSTRAINT "ametagraph_llow_spends_fk" FOREIGN KEY ("allow_spend_hash") REFERENCES "metagraph_allow_spends"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; +-- Table Triggers --- AddForeignKey -ALTER TABLE "metagraph_allow_spend_approvers" ADD CONSTRAINT "metagraph_allow_spend_approvers_address_fk" FOREIGN KEY ("approver_address") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; +create trigger set_updated_at_metagraph_token_locks before +update + on + public.metagraph_token_locks for each row execute function update_updated_at_column(); +create trigger trigger_insert_abstract_transactions_metagraph_token_locks after +insert + on + public.metagraph_token_locks for each row execute function insert_into_parent_abstract_transactions(); --- AddForeignKey -ALTER TABLE "metagraph_allow_spend_blocks" ADD CONSTRAINT "metagraph_allow_spend_blocks_metagraph_snapshot_fk" FOREIGN KEY ("metagraph_id", "metagraph_snapshot_hash") REFERENCES "metagraph_snapshots"("metagraph_id", "hash") ON DELETE CASCADE ON UPDATE NO ACTION; --- AddForeignKey -ALTER TABLE "metagraph_allow_spends" ADD CONSTRAINT "allow_spends_block_fk" FOREIGN KEY ("round_id") REFERENCES "metagraph_allow_spend_blocks"("round_id") ON DELETE CASCADE ON UPDATE NO ACTION; +-- public.metagraph_token_unlocks definition --- AddForeignKey -ALTER TABLE "metagraph_allow_spends" ADD CONSTRAINT "metagraph_allow_spends_destination_addr_fk" FOREIGN KEY ("destination_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; +-- Drop table --- AddForeignKey -ALTER TABLE "metagraph_allow_spends" ADD CONSTRAINT "metagraph_allow_spends_source_addr_fk" FOREIGN KEY ("source_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; +-- DROP TABLE metagraph_token_unlocks; + +CREATE TABLE metagraph_token_unlocks ( + metagraph_id varchar NOT NULL, + lock_reference_ordinal int8 NOT NULL, + lock_reference_hash varchar NOT NULL, + snapshot_hash varchar NOT NULL, + parent_hash varchar NOT NULL, + CONSTRAINT metagraph_token_unlocks_pk PRIMARY KEY (lock_reference_ordinal, lock_reference_hash), + CONSTRAINT address_fk FOREIGN KEY (source_addr) REFERENCES addresses(address) ON DELETE CASCADE, + CONSTRAINT metagraph_id_fk FOREIGN KEY (metagraph_id) REFERENCES metagraphs(id) ON DELETE CASCADE, + CONSTRAINT metagraph_token_unlocks_token_locks_fk FOREIGN KEY (metagraph_id,lock_reference_hash) REFERENCES metagraph_token_locks(metagraph_id,hash) ON DELETE CASCADE, + CONSTRAINT metagraph_token_unlocks_token_locks_ordinal_fk FOREIGN KEY (metagraph_id,lock_reference_ordinal) REFERENCES metagraph_token_locks(metagraph_id,ordinal) ON DELETE CASCADE +) +INHERITS (public.abstract_transactions); --- AddForeignKey -ALTER TABLE "metagraph_allow_spends" ADD CONSTRAINT "metagraph_id_fk" FOREIGN KEY ("metagraph_id") REFERENCES "metagraphs"("id") ON DELETE CASCADE ON UPDATE NO ACTION; +-- Table Triggers --- AddForeignKey -ALTER TABLE "metagraph_allow_spends" ADD CONSTRAINT "metagraph_allow_spend_blocks_metagraph_snapshot_fk" FOREIGN KEY ("metagraph_id", "snapshot_hash") REFERENCES "metagraph_snapshots"("metagraph_id", "hash") ON DELETE CASCADE ON UPDATE NO ACTION; +create trigger set_updated_at_metagraph_token_unlocks before +update + on + public.metagraph_token_unlocks for each row execute function update_updated_at_column(); +create trigger trigger_insert_abstract_transactions_metagraph_token_unlocks after +insert + on + public.metagraph_token_unlocks for each row execute function insert_into_parent_abstract_transactions(); --- AddForeignKey -ALTER TABLE "metagraph_balance_changes" ADD CONSTRAINT "address_fk" FOREIGN KEY ("address") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; --- AddForeignKey -ALTER TABLE "metagraph_balance_changes" ADD CONSTRAINT "metagraph_balance_change_metagraph_snapshot_fk" FOREIGN KEY ("metagraph_id", "metagraph_snapshot_hash") REFERENCES "metagraph_snapshots"("metagraph_id", "hash") ON DELETE CASCADE ON UPDATE NO ACTION; +-- public.dag_allow_spend_approvers definition --- AddForeignKey -ALTER TABLE "metagraph_balance_changes" ADD CONSTRAINT "metagraph_id_fk" FOREIGN KEY ("metagraph_id") REFERENCES "metagraphs"("id") ON DELETE CASCADE ON UPDATE NO ACTION; +-- Drop table --- AddForeignKey -ALTER TABLE "metagraph_blocks" ADD CONSTRAINT "metagraph_block_metagraph_snapshot_fk" FOREIGN KEY ("metagraph_id", "metagraph_snapshot_hash") REFERENCES "metagraph_snapshots"("metagraph_id", "hash") ON DELETE CASCADE ON UPDATE NO ACTION; +-- DROP TABLE dag_allow_spend_approvers; --- AddForeignKey -ALTER TABLE "metagraph_blocks" ADD CONSTRAINT "metagraph_id_fk" FOREIGN KEY ("metagraph_id") REFERENCES "metagraphs"("id") ON DELETE CASCADE ON UPDATE NO ACTION; +CREATE TABLE dag_allow_spend_approvers ( + allow_spend_hash varchar NOT NULL, + approver_address varchar NOT NULL, + CONSTRAINT dag_allow_spend_approvers_pk PRIMARY KEY (allow_spend_hash, approver_address), + CONSTRAINT dag_allow_spend_approvers_address_fk FOREIGN KEY (approver_address) REFERENCES addresses(address) ON DELETE CASCADE, + CONSTRAINT dag_allow_spends_fk FOREIGN KEY (allow_spend_hash) REFERENCES dag_allow_spends(hash) ON DELETE CASCADE +); --- AddForeignKey -ALTER TABLE "metagraph_fee_transactions" ADD CONSTRAINT "fee_transaction_metagraph_snapshot_fk" FOREIGN KEY ("metagraph_id", "metagraph_snapshot_hash") REFERENCES "metagraph_snapshots"("metagraph_id", "hash") ON DELETE CASCADE ON UPDATE NO ACTION; --- AddForeignKey -ALTER TABLE "metagraph_fee_transactions" ADD CONSTRAINT "metagraph_fee_transactions_destination_addr_fk" FOREIGN KEY ("destination_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; +-- public.metagraph_allow_spend_blocks definition --- AddForeignKey -ALTER TABLE "metagraph_fee_transactions" ADD CONSTRAINT "metagraph_fee_transactions_source_addr_fk" FOREIGN KEY ("source_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; +-- Drop table --- AddForeignKey -ALTER TABLE "metagraph_fee_transactions" ADD CONSTRAINT "metagraph_id_fk" FOREIGN KEY ("metagraph_id") REFERENCES "metagraphs"("id") ON DELETE CASCADE ON UPDATE NO ACTION; +-- DROP TABLE metagraph_allow_spend_blocks; --- AddForeignKey -ALTER TABLE "metagraph_reward_transactions" ADD CONSTRAINT "metagraph_reward_transaction_metagraph_reward_transaction_fk" FOREIGN KEY ("metagraph_id", "metagraph_snapshot_hash") REFERENCES "metagraph_snapshots"("metagraph_id", "hash") ON DELETE CASCADE ON UPDATE NO ACTION; +CREATE TABLE metagraph_allow_spend_blocks ( + metagraph_id varchar NOT NULL, + round_id uuid NOT NULL, + metagraph_snapshot_hash varchar NOT NULL, + created_at timestamp DEFAULT now() NOT NULL, + updated_at timestamp DEFAULT now() NOT NULL, + CONSTRAINT metagraph_allow_spend_blocks_pkey PRIMARY KEY (round_id), + CONSTRAINT metagraph_allow_spend_blocks_metagraph_snapshot_fk FOREIGN KEY (metagraph_id,metagraph_snapshot_hash) REFERENCES metagraph_snapshots(metagraph_id,hash) ON DELETE CASCADE +); --- AddForeignKey -ALTER TABLE "metagraph_reward_transactions" ADD CONSTRAINT "metagraph_reward_transactions_destination_addr_fk" FOREIGN KEY ("destination_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; +-- Table Triggers --- AddForeignKey -ALTER TABLE "metagraph_reward_transactions" ADD CONSTRAINT "metagraph_reward_transactions_metagraph_id_fk" FOREIGN KEY ("metagraph_id") REFERENCES "metagraphs"("id") ON DELETE CASCADE ON UPDATE NO ACTION; +create trigger set_updated_at_metagraph_allow_spend_blocks before +update + on + public.metagraph_allow_spend_blocks for each row execute function update_updated_at_column(); --- AddForeignKey -ALTER TABLE "metagraph_snapshots" ADD CONSTRAINT "metagraph_id_fk" FOREIGN KEY ("metagraph_id") REFERENCES "metagraphs"("id") ON DELETE CASCADE ON UPDATE NO ACTION; --- AddForeignKey -ALTER TABLE "metagraph_snapshots" ADD CONSTRAINT "metagraph_snapshots_global_snapshots_fk" FOREIGN KEY ("global_snapshot_hash") REFERENCES "global_snapshots"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; +-- public.metagraph_allow_spends definition --- AddForeignKey -ALTER TABLE "metagraph_snapshots" ADD CONSTRAINT "owner_address_fk" FOREIGN KEY ("owner_address") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; +-- Drop table --- AddForeignKey -ALTER TABLE "metagraph_snapshots" ADD CONSTRAINT "staking_address_fk" FOREIGN KEY ("staking_address") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; +-- DROP TABLE metagraph_allow_spends; --- AddForeignKey -ALTER TABLE "metagraph_spend_transactions" ADD CONSTRAINT "mg_spend_transactions_mg_allow_spends_fk" FOREIGN KEY ("allow_spend_ref") REFERENCES "metagraph_allow_spends"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; +CREATE TABLE metagraph_allow_spends ( + metagraph_id varchar NOT NULL, + destination_addr varchar NOT NULL, + fee int8 NOT NULL, + parent_ordinal int8 NULL, + parent_hash varchar NULL, + last_valid_epoch_progress int8 NOT NULL, + round_id uuid NOT NULL, + ordinal int8 NOT NULL, + snapshot_hash varchar NOT NULL, + CONSTRAINT metagraph_allow_spends_ordinal UNIQUE (ordinal), + CONSTRAINT metagraph_allow_spends_pk PRIMARY KEY (hash), + CONSTRAINT allow_spends_block_fk FOREIGN KEY (round_id) REFERENCES metagraph_allow_spend_blocks(round_id) ON DELETE CASCADE, + CONSTRAINT metagraph_allow_spends_destination_addr_fk FOREIGN KEY (destination_addr) REFERENCES addresses(address) ON DELETE CASCADE, + CONSTRAINT metagraph_allow_spends_source_addr_fk FOREIGN KEY (source_addr) REFERENCES addresses(address) ON DELETE CASCADE, + CONSTRAINT metagraph_id_fk FOREIGN KEY (metagraph_id) REFERENCES metagraphs(id) ON DELETE CASCADE +) +INHERITS (public.abstract_transactions); --- AddForeignKey -ALTER TABLE "metagraph_spend_transactions" ADD CONSTRAINT "metagraph_spend_transactions_destination_addr_fk" FOREIGN KEY ("destination_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; +-- Table Triggers --- AddForeignKey -ALTER TABLE "metagraph_spend_transactions" ADD CONSTRAINT "metagraph_spend_transactions_metagraph_id_fk" FOREIGN KEY ("metagraph_id") REFERENCES "metagraphs"("id") ON DELETE CASCADE ON UPDATE NO ACTION; +create trigger set_updated_at_metagraph_allow_spends before +update + on + public.metagraph_allow_spends for each row execute function update_updated_at_column(); +create trigger trigger_insert_abstract_transactions_metagraph_allow_spends after +insert + on + public.metagraph_allow_spends for each row execute function insert_into_parent_abstract_transactions(); --- AddForeignKey -ALTER TABLE "metagraph_token_lock_blocks" ADD CONSTRAINT "metagraph_token_lock_blocks_metagraph_id_fk" FOREIGN KEY ("metagraph_id") REFERENCES "metagraphs"("id") ON DELETE CASCADE ON UPDATE NO ACTION; --- AddForeignKey -ALTER TABLE "metagraph_token_lock_blocks" ADD CONSTRAINT "metagraph_token_lock_blocks_metagraph_snapshot_fk" FOREIGN KEY ("metagraph_id", "metagraph_snapshot_hash") REFERENCES "metagraph_snapshots"("metagraph_id", "hash") ON DELETE CASCADE ON UPDATE NO ACTION; +-- public.metagraph_balance_changes definition --- AddForeignKey -ALTER TABLE "metagraph_token_locks" ADD CONSTRAINT "metagraph_id_fk" FOREIGN KEY ("metagraph_id") REFERENCES "metagraphs"("id") ON DELETE CASCADE ON UPDATE NO ACTION; +-- Drop table + +-- DROP TABLE metagraph_balance_changes; + +CREATE TABLE metagraph_balance_changes ( + metagraph_id varchar NOT NULL, + metagraph_snapshot_hash varchar NOT NULL, + address varchar NOT NULL, + balance int8 NOT NULL, + created_at timestamp DEFAULT now() NOT NULL, + updated_at timestamp DEFAULT now() NOT NULL, + metagraph_snapshot_ordinal int8 NOT NULL, + CONSTRAINT metagraph_balance_change_pk PRIMARY KEY (metagraph_id, address, metagraph_snapshot_ordinal), + CONSTRAINT address_fk FOREIGN KEY (address) REFERENCES addresses(address) ON DELETE CASCADE, + CONSTRAINT metagraph_balance_change_metagraph_snapshot_fk FOREIGN KEY (metagraph_id,metagraph_snapshot_hash) REFERENCES metagraph_snapshots(metagraph_id,hash) ON DELETE CASCADE, + CONSTRAINT metagraph_id_fk FOREIGN KEY (metagraph_id) REFERENCES metagraphs(id) ON DELETE CASCADE +); --- AddForeignKey -ALTER TABLE "metagraph_token_locks" ADD CONSTRAINT "metagraph_token_locks_source_addr_fk" FOREIGN KEY ("source_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; +-- Table Triggers --- AddForeignKey -ALTER TABLE "metagraph_token_locks" ADD CONSTRAINT "metagraph_lock_blocks_metagraph_snapshot_fk" FOREIGN KEY ("metagraph_id", "snapshot_hash") REFERENCES "metagraph_snapshots"("metagraph_id", "hash") ON DELETE CASCADE ON UPDATE NO ACTION; +create trigger set_updated_at_metagraph_balance_change before +update + on + public.metagraph_balance_changes for each row execute function update_updated_at_column(); --- AddForeignKey -ALTER TABLE "metagraph_token_unlocks" ADD CONSTRAINT "address_fk" FOREIGN KEY ("source_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; --- AddForeignKey -ALTER TABLE "metagraph_token_unlocks" ADD CONSTRAINT "metagraph_id_fk" FOREIGN KEY ("metagraph_id") REFERENCES "metagraphs"("id") ON DELETE CASCADE ON UPDATE NO ACTION; +-- public.metagraph_blocks definition --- AddForeignKey -ALTER TABLE "metagraph_token_unlocks" ADD CONSTRAINT "metagraph_token_unlocks_token_locks_fk" FOREIGN KEY ("lock_reference_hash") REFERENCES "metagraph_token_locks"("hash") ON DELETE NO ACTION ON UPDATE NO ACTION; +-- Drop table --- AddForeignKey -ALTER TABLE "metagraph_token_unlocks" ADD CONSTRAINT "metagraph_unlock_blocks_metagraph_snapshot_fk" FOREIGN KEY ("metagraph_id", "snapshot_hash") REFERENCES "metagraph_snapshots"("metagraph_id", "hash") ON DELETE CASCADE ON UPDATE NO ACTION; +-- DROP TABLE metagraph_blocks; --- AddForeignKey -ALTER TABLE "metagraph_transactions" ADD CONSTRAINT "metagraph_id_fk" FOREIGN KEY ("metagraph_id") REFERENCES "metagraphs"("id") ON DELETE CASCADE ON UPDATE NO ACTION; +CREATE TABLE metagraph_blocks ( + metagraph_id varchar NOT NULL, + metagraph_snapshot_hash varchar NOT NULL, + CONSTRAINT metagraph_block_pk PRIMARY KEY (metagraph_id, hash), + CONSTRAINT metagraph_block_metagraph_snapshot_fk FOREIGN KEY (metagraph_id,metagraph_snapshot_hash) REFERENCES metagraph_snapshots(metagraph_id,hash) ON DELETE CASCADE, + CONSTRAINT metagraph_id_fk FOREIGN KEY (metagraph_id) REFERENCES metagraphs(id) ON DELETE CASCADE +) +INHERITS (public.abstract_blocks); + +-- Table Triggers --- AddForeignKey -ALTER TABLE "metagraph_transactions" ADD CONSTRAINT "metagraph_transaction_metagraph_block_fk" FOREIGN KEY ("metagraph_id", "block_hash") REFERENCES "metagraph_blocks"("metagraph_id", "hash") ON DELETE CASCADE ON UPDATE NO ACTION; +create trigger set_updated_at_metagraph_blocks before +update + on + public.metagraph_blocks for each row execute function update_updated_at_column(); +create trigger trigger_insert_abstract_blocks_metagraph after +insert + on + public.metagraph_blocks for each row execute function insert_into_parent_abstract_blocks(); + + +-- public.metagraph_fee_transactions definition + +-- Drop table + +-- DROP TABLE metagraph_fee_transactions; + +CREATE TABLE metagraph_fee_transactions ( + created_at timestamp DEFAULT now() NOT NULL, + metagraph_id varchar NOT NULL, + metagraph_snapshot_hash varchar NOT NULL, + destination_addr varchar NOT NULL, + data_update_ref varchar NULL, + metagraph_snapshot_ordinal int8 NULL, + CONSTRAINT fee_transaction_pk PRIMARY KEY (metagraph_id, hash), + CONSTRAINT fee_transaction_metagraph_snapshot_fk FOREIGN KEY (metagraph_id,metagraph_snapshot_hash) REFERENCES metagraph_snapshots(metagraph_id,hash) ON DELETE CASCADE, + CONSTRAINT metagraph_fee_transactions_destination_addr_fk FOREIGN KEY (destination_addr) REFERENCES addresses(address) ON DELETE CASCADE, + CONSTRAINT metagraph_fee_transactions_source_addr_fk FOREIGN KEY (source_addr) REFERENCES addresses(address) ON DELETE CASCADE, + CONSTRAINT metagraph_id_fk FOREIGN KEY (metagraph_id) REFERENCES metagraphs(id) ON DELETE CASCADE +) +INHERITS (public.abstract_transactions); + +-- Table Triggers + +create trigger set_updated_at_metagraph_fee_transactions before +update + on + public.metagraph_fee_transactions for each row execute function update_updated_at_column(); +create trigger trigger_insert_abstract_transactions_metagraph_fee_transactions after +insert + on + public.metagraph_fee_transactions for each row execute function insert_into_parent_abstract_transactions(); + + +-- public.metagraph_reward_transactions definition + +-- Drop table + +-- DROP TABLE metagraph_reward_transactions; + +CREATE TABLE metagraph_reward_transactions ( + metagraph_id varchar NOT NULL, + metagraph_snapshot_hash varchar NOT NULL, + destination_addr varchar NOT NULL, + amount int8 NOT NULL, + created_at timestamp DEFAULT now() NOT NULL, + updated_at timestamp DEFAULT now() NOT NULL, + CONSTRAINT metagraph_reward_transaction_pk PRIMARY KEY (metagraph_id, metagraph_snapshot_hash, destination_addr), + CONSTRAINT metagraph_reward_transaction_metagraph_reward_transaction_fk FOREIGN KEY (metagraph_id,metagraph_snapshot_hash) REFERENCES metagraph_snapshots(metagraph_id,hash) ON DELETE CASCADE, + CONSTRAINT metagraph_reward_transactions_destination_addr_fk FOREIGN KEY (destination_addr) REFERENCES addresses(address) ON DELETE CASCADE, + CONSTRAINT metagraph_reward_transactions_metagraph_id_fk FOREIGN KEY (metagraph_id) REFERENCES metagraphs(id) ON DELETE CASCADE +); + +-- Table Triggers + +create trigger set_updated_at_metagraph_reward_transaction before +update + on + public.metagraph_reward_transactions for each row execute function update_updated_at_column(); --- AddForeignKey -ALTER TABLE "metagraph_transactions" ADD CONSTRAINT "metagraph_transactions_destination_addrfk" FOREIGN KEY ("destination_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; --- AddForeignKey -ALTER TABLE "metagraph_transactions" ADD CONSTRAINT "metagraph_transactions_source_addr_fk" FOREIGN KEY ("source_addr") REFERENCES "addresses"("address") ON DELETE CASCADE ON UPDATE NO ACTION; +-- public.metagraph_spend_transactions definition --- AddForeignKey -ALTER TABLE "dag_expired_spend_transactions" ADD CONSTRAINT "dag_expired_spend_transactions_dag_allow_spends_fk" FOREIGN KEY ("allow_spend_ref") REFERENCES "dag_allow_spends"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; +-- Drop table --- AddForeignKey -ALTER TABLE "metagraph_expired_spend_transactions" ADD CONSTRAINT "metagraph_expired_spend_transactions_metagraph_allow_spends_fk" FOREIGN KEY ("allow_spend_ref") REFERENCES "metagraph_allow_spends"("hash") ON DELETE CASCADE ON UPDATE NO ACTION; +-- DROP TABLE metagraph_spend_transactions; + +CREATE TABLE metagraph_spend_transactions ( + metagraph_id varchar NOT NULL, + destination_addr varchar NOT NULL, + allow_spend_ref varchar NULL, + snapshot_hash varchar NOT NULL, + CONSTRAINT metagraph_spend_transactions_pk PRIMARY KEY (hash), + CONSTRAINT metagraph_spend_transactions_metagraph_allow_spends_fk FOREIGN KEY (allow_spend_ref) REFERENCES metagraph_allow_spends(hash) ON DELETE CASCADE, + CONSTRAINT metagraph_spend_transactions_destination_addr_fk FOREIGN KEY (destination_addr) REFERENCES addresses(address) ON DELETE CASCADE, + CONSTRAINT metagraph_spend_transactions_metagraph_id_fk FOREIGN KEY (metagraph_id) REFERENCES metagraphs(id) ON DELETE CASCADE +) +INHERITS (public.abstract_transactions); + +-- Table Triggers + +create trigger set_updated_at_metagraph_spend_transactions before +update + on + public.metagraph_spend_transactions for each row execute function update_updated_at_column(); +create trigger trigger_insert_abstract_transactions_metagraph_spend_transactio after +insert + on + public.metagraph_spend_transactions for each row execute function insert_into_parent_abstract_transactions(); + + +CREATE TABLE metagraph_expired_spend_transactions ( + metagraph_id varchar NOT NULL, + allow_spend_ref varchar NULL, + snapshot_hash varchar NULL, + CONSTRAINT metagraph_expired_spend_transactions_pk PRIMARY KEY (hash), + CONSTRAINT metagraph_expired_spend_transactions_metagraph_allow_spends_fk FOREIGN KEY (allow_spend_ref) REFERENCES metagraph_allow_spends(hash) ON DELETE CASCADE +) +INHERITS (public.abstract_transactions); + +-- Table Triggers + +create trigger set_updated_at_metagraph_expired_spend_transactions before +update + on + public.metagraph_expired_spend_transactions for each row execute function update_updated_at_column(); +create trigger trigger_insert_abstract_transactions_metagraph_expired_spend_tr after +insert + on + public.metagraph_expired_spend_transactions for each row execute function insert_into_parent_abstract_transactions(); + +-- public.metagraph_transactions definition + +-- Drop table + +-- DROP TABLE metagraph_transactions; + +CREATE TABLE metagraph_transactions ( + metagraph_id varchar NOT NULL, + destination_addr varchar NOT NULL, + fee int8 NOT NULL, + salt int8 NOT NULL, + parent_ordinal int8 NOT NULL, + parent_hash varchar NOT NULL, + ordinal int8 NOT NULL, + block_hash varchar NOT NULL, + CONSTRAINT metagraph_transaction_pk PRIMARY KEY (metagraph_id, hash), + CONSTRAINT metagraph_id_fk FOREIGN KEY (metagraph_id) REFERENCES metagraphs(id) ON DELETE CASCADE, + CONSTRAINT metagraph_transaction_metagraph_block_fk FOREIGN KEY (metagraph_id,block_hash) REFERENCES metagraph_blocks(metagraph_id,hash) ON DELETE CASCADE, + CONSTRAINT metagraph_transactions_destination_addrfk FOREIGN KEY (destination_addr) REFERENCES addresses(address) ON DELETE CASCADE, + CONSTRAINT metagraph_transactions_source_addr_fk FOREIGN KEY (source_addr) REFERENCES addresses(address) ON DELETE CASCADE +) +INHERITS (public.abstract_transactions); + +-- Table Triggers + +create trigger set_updated_at_metagraph_transaction before +update + on + public.metagraph_transactions for each row execute function update_updated_at_column(); +create trigger trigger_insert_abstract_transactions_metagraph_transactions after +insert + on + public.metagraph_transactions for each row execute function insert_into_parent_abstract_transactions(); + + +-- public.metagraph_allow_spend_approvers definition + +-- Drop table + +-- DROP TABLE metagraph_allow_spend_approvers; + +CREATE TABLE metagraph_allow_spend_approvers ( + allow_spend_hash varchar NOT NULL, + approver_address varchar NOT NULL, + CONSTRAINT metagraph_allow_spend_approvers_pk PRIMARY KEY (allow_spend_hash, approver_address), + CONSTRAINT ametagraph_allow_spends_fk FOREIGN KEY (allow_spend_hash) REFERENCES metagraph_allow_spends(hash) ON DELETE CASCADE, + CONSTRAINT metagraph_allow_spend_approvers_address_fk FOREIGN KEY (approver_address) REFERENCES addresses(address) ON DELETE CASCADE +); + + +-- public.abstract_transactions_view source + +CREATE OR REPLACE VIEW abstract_transactions_view +AS SELECT tx.hash, + tx.source_addr, + tx.amount, + tx.created_at, + tx.updated_at, + p.relname AS table_name + FROM abstract_transactions tx + JOIN pg_class p ON tx.tableoid = p.oid + WHERE p.relname <> 'abstract_transactions'::name; + + + +CREATE TABLE delegate_stake_create_events ( + hash varchar PRIMARY KEY, + ordinal int8 NOT NULL, + source_addr varchar NOT NULL REFERENCES addresses(address) ON DELETE CASCADE, + node_id varchar NOT NULL, + amount int8 NOT NULL, + fee int8 NOT NULL DEFAULT 0, + lock_reference_hash varchar NOT NULL REFERENCES dag_token_locks(hash) ON DELETE CASCADE, + parent_hash varchar NOT NULL, + global_snapshot_hash varchar NOT NULL REFERENCES global_snapshots(hash) ON DELETE CASCADE, + is_update boolean NOT NULL, + created_at timestamp DEFAULT now() NOT NULL, + updated_at timestamp DEFAULT now() NOT NULL +); +CREATE INDEX delegate_stake_create_events_source_addr_idx ON public.delegate_stake_create_events USING btree (source_addr); +CREATE INDEX delegate_stake_create_events_lock_reference_hash_idx ON public.delegate_stake_create_events USING btree (lock_reference_hash); +CREATE INDEX delegate_stake_create_events_global_snapshot_hash_idx ON public.delegate_stake_create_events USING btree (global_snapshot_hash); + + + +CREATE TABLE delegate_stake_withdraw_events ( + hash varchar PRIMARY KEY, + source_addr varchar NOT NULL REFERENCES addresses(address) ON DELETE CASCADE, + stake_create_hash varchar NOT NULL REFERENCES delegate_stake_create_events(hash) ON DELETE CASCADE, + global_snapshot_hash varchar NOT NULL REFERENCES global_snapshots(hash) ON DELETE CASCADE, + created_at timestamp DEFAULT now() NOT NULL, + updated_at timestamp DEFAULT now() NOT NULL +); +CREATE INDEX delegate_stake_withdraw_events_source_addr_idx ON public.delegate_stake_withdraw_events USING btree (source_addr); +CREATE INDEX delegate_stake_withdraw_events_stake_create_hash_idx ON public.delegate_stake_withdraw_events USING btree (stake_create_hash); +CREATE INDEX delegate_stake_withdraw_events_global_snapshot_hash_idx ON public.delegate_stake_withdraw_events USING btree (global_snapshot_hash); + + +CREATE TABLE delegate_stake_balance_changes ( + global_snapshot_hash varchar NOT NULL REFERENCES global_snapshots(hash) ON DELETE CASCADE, + global_snapshot_ordinal int8 NOT NULL REFERENCES global_snapshots(ordinal) ON DELETE CASCADE, + address varchar NOT NULL REFERENCES addresses(address) ON DELETE CASCADE, + node_id varchar NOT NULL, + balance int8 NOT NULL, + rewards int8 NOT NULL, + stake_create_hash varchar NULL REFERENCES delegate_stake_create_events(hash) ON DELETE CASCADE, + stake_withdraw_hash varchar NULL REFERENCES delegate_stake_withdraw_events(hash) ON DELETE CASCADE, + created_at timestamp DEFAULT now() NOT NULL, + updated_at timestamp DEFAULT now() NOT NULL +); +CREATE INDEX delegate_stake_balance_changes_global_snapshot_hash_idx ON public.delegate_stake_balance_changes USING btree (global_snapshot_hash); +CREATE INDEX delegate_stake_balance_changes_global_snapshot_ordinal_idx ON public.delegate_stake_balance_changes USING btree (global_snapshot_ordinal); +CREATE INDEX delegate_stake_balance_changes_address ON public.delegate_stake_balance_changes USING btree (address); +CREATE INDEX delegate_stake_balance_changes_node_id ON public.delegate_stake_balance_changes USING btree (node_id); +ALTER TABLE public.delegate_stake_balance_changes ADD CONSTRAINT delegate_stake_balance_changes_unique UNIQUE (global_snapshot_hash, address, node_id, balance, rewards); + + +CREATE TABLE delegate_stake_rewards ( + global_snapshot_hash varchar NOT NULL REFERENCES global_snapshots(hash) ON DELETE CASCADE, + address varchar NOT NULL REFERENCES addresses(address) ON DELETE CASCADE, + node_id varchar NOT NULL, + rewards int8 NOT NULL, + created_at timestamp DEFAULT now() NOT NULL, + updated_at timestamp DEFAULT now() NOT NULL +); +CREATE INDEX delegate_stake_rewards_changes_global_snapshot_hash_idx ON public.delegate_stake_balance_changes USING btree (global_snapshot_hash); +CREATE INDEX delegate_stake_rewards_changes_address ON public.delegate_stake_balance_changes USING btree (address); +CREATE INDEX delegate_stake_rewards_changes_node_id ON public.delegate_stake_balance_changes USING btree (node_id); +ALTER TABLE public.delegate_stake_rewards ADD CONSTRAINT delegate_stake_rewards_changes_unique UNIQUE (global_snapshot_hash, address, node_id, rewards); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1fbb3e9..dcb19b6 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -10,11 +10,10 @@ datasource db { /// This table has subclasses and requires additional setup for migrations. Visit https://pris.ly/d/table-inheritance for more info. model abstract_blocks { - hash String @id(map: "block_pkey") @db.VarChar - height BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - + hash String @id(map: "block_pkey") @db.VarChar + height BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) block_parents block_parents[] dag_block dag_blocks? @relation(fields: [hash], references: [hash], map: "dag_blocks_hash_fk") metagraph_block metagraph_blocks? @relation(fields: [hash], references: [hash], map: "metagraph_blocks_hash_fk") @@ -22,11 +21,11 @@ model abstract_blocks { /// This table has subclasses and requires additional setup for migrations. Visit https://pris.ly/d/table-inheritance for more info. model abstract_transactions { - hash String @id(map: "hash_pkey") @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) + hash String @id(map: "hash_pkey") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) } model abstract_transactions_view { @@ -69,6 +68,10 @@ model addresses { dag_token_unlocks dag_token_unlocks[] dag_transactions_dag_transactions_destination_addrToaddresses dag_transactions[] @relation("dag_transactions_destination_addrToaddresses") dag_transactions_dag_transactions_source_addrToaddresses dag_transactions[] @relation("dag_transactions_source_addrToaddresses") + delegate_stake_balance_changes delegate_stake_balance_changes[] + delegate_stake_create_events delegate_stake_create_events[] + delegate_stake_rewards delegate_stake_rewards[] + delegate_stake_withdraw_events delegate_stake_withdraw_events[] metagraph_allow_spend_approvers metagraph_allow_spend_approvers[] metagraph_allow_spends_metagraph_allow_spends_destination_addrToaddresses metagraph_allow_spends[] @relation("metagraph_allow_spends_destination_addrToaddresses") metagraph_allow_spends_metagraph_allow_spends_source_addrToaddresses metagraph_allow_spends[] @relation("metagraph_allow_spends_source_addrToaddresses") @@ -104,14 +107,6 @@ model dag_allow_spend_approvers { @@id([allow_spend_hash, approver_address], map: "dag_allow_spend_approvers_pk") } -model dag_allow_spend_blocks { - round_id String @unique(map: "dag_allow_spend_blocks_unique") @db.Uuid - global_snapshot_hash String @id @db.VarChar - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - global_snapshot global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spend_blocks_global_snapshot_fk") -} - model dag_allow_spends { hash String @id(map: "dag_allow_spends_pk") @db.VarChar source_addr String @db.VarChar @@ -132,7 +127,6 @@ model dag_allow_spends { dag_expired_spend_transactions dag_expired_spend_transactions[] dag_spend_transactions dag_spend_transactions[] abstract_transactions_view abstract_transactions_view[] - global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spend_blocks_global_snapshot_fk") @@index([round_id]) } @@ -157,9 +151,9 @@ model dag_blocks { created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) snapshot_hash String @db.VarChar + super abstract_blocks? global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_block_global_snapshot_fk") dag_transactions dag_transactions[] - super abstract_blocks? @@index([snapshot_hash]) } @@ -178,62 +172,55 @@ model dag_reward_transactions { } model dag_spend_transactions { - hash String @id(map: "dag_spend_transactions_pk") @db.VarChar - source_addr String @db.VarChar + hash String @id(map: "dag_spend_transactions_pk") @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - destination_addr String? @db.VarChar - allow_spend_ref String @db.VarChar - snapshot_hash String @db.VarChar - dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_dag_allow_spends_fk") - addresses addresses? @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_destination_addr_fk") - abstract_transactions_view abstract_transactions_view? -} - -model dag_token_lock_blocks { - round_id String @id @db.Uuid - global_snapshot_hash String @unique(map: "dag_token_lock_blocks_unique") @db.VarChar - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - global_snapshot global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_lock_blocks_global_snapshot_fk") - dag_token_locks dag_token_locks[] + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + destination_addr String? @db.VarChar + allow_spend_ref String? @db.VarChar + snapshot_hash String @db.VarChar + dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_dag_allow_spends_fk") + addresses addresses? @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_destination_addr_fk") + abstract_transactions_view abstract_transactions_view[] } model dag_token_locks { - hash String @id(map: "dag_token_locks_pk") @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - ordinal BigInt - unlock_epoch BigInt? - round_id String @db.Uuid - snapshot_hash String @db.VarChar - parent_hash String? @db.VarChar - addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_locks_source_addr_fk") - dag_token_unlock dag_token_unlocks? - abstract_transactions_view abstract_transactions_view? - dag_token_lock_blocks dag_token_lock_blocks? @relation(fields: [round_id], references: [round_id]) - global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_lock_global_snapshot_fk") + hash String @id(map: "dag_token_locks_pk") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + ordinal BigInt + unlock_epoch BigInt? + round_id String @db.Uuid + parent_hash String? @db.VarChar + snapshot_hash String @db.VarChar + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_locks_source_addr_fk") + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_lock_global_snapshot_fk") + dag_token_unlock dag_token_unlocks? + delegate_stake_create_events delegate_stake_create_events[] + abstract_transactions_view abstract_transactions_view[] @@unique([ordinal], map: "dag_token_locks_unique") } model dag_token_unlocks { - hash String @id(map: "dag_token_unlocks_pk") @db.VarChar - source_addr String @db.VarChar + hash String @id(map: "dag_token_unlocks_pk") @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - lock_reference_hash String @db.VarChar - dag_token_lock dag_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "dag_token_unlocks_token_locks_fk") - addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "ddag_token_unlocks_address_fk") - abstract_transactions_view abstract_transactions_view? - snapshot_hash String @db.VarChar - global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_unlock_global_snapshot_fk") + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + lock_reference_ordinal BigInt + lock_reference_hash String @db.VarChar + snapshot_hash String @db.VarChar + parent_hash String @db.VarChar + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_unlock_global_snapshot_fk") + dag_token_lock dag_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_unlocks_token_locks_fk") + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "ddag_token_unlocks_address_fk") + abstract_transactions_view abstract_transactions_view[] - @@unique([lock_reference_hash]) + @@unique([lock_reference_hash], map: "lock_reference_hash_unique") } model dag_transactions { @@ -266,26 +253,27 @@ model global_snapshot_proofs { } model global_snapshots { - hash String @id(map: "global_snapshot_pk") @db.VarChar - ordinal BigInt @unique(map: "global_snapshot_unique") - height BigInt - subheight Int - last_snapshot_hash String @db.VarChar - metagraph_snapshot_count BigInt - epoch_progress BigInt - version String @db.VarChar - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - dag_allow_spend_blocks dag_allow_spend_blocks? - dag_balance_changes dag_balance_changes[] - dag_blocks dag_blocks[] - dag_reward_transactions dag_reward_transactions[] - dag_token_lock_blocks dag_token_lock_blocks? - global_snapshot_proofs global_snapshot_proofs[] - metagraph_snapshots metagraph_snapshots[] - dag_allow_spends dag_allow_spends[] - dag_token_locks dag_token_locks[] - dag_token_unlocks dag_token_unlocks[] + hash String @id(map: "global_snapshot_pk") @db.VarChar + ordinal BigInt @unique(map: "global_snapshot_unique") + height BigInt + subheight Int + last_snapshot_hash String @db.VarChar + metagraph_snapshot_count BigInt? + epoch_progress BigInt? + version String @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + dag_balance_changes dag_balance_changes[] + dag_blocks dag_blocks[] + dag_reward_transactions dag_reward_transactions[] + delegate_stake_balance_changes delegate_stake_balance_changes[] + delegate_stake_create_events delegate_stake_create_events[] + delegate_stake_rewards delegate_stake_rewards[] + delegate_stake_withdraw_events delegate_stake_withdraw_events[] + global_snapshot_proofs global_snapshot_proofs[] + metagraph_snapshots metagraph_snapshots[] + dag_token_locks dag_token_locks[] + dag_token_unlocks dag_token_unlocks[] } model metagraph_allow_spend_approvers { @@ -297,16 +285,6 @@ model metagraph_allow_spend_approvers { @@id([allow_spend_hash, approver_address], map: "metagraph_allow_spend_approvers_pk") } -model metagraph_allow_spend_blocks { - metagraph_id String @db.VarChar - round_id String @id @db.Uuid - metagraph_snapshot_hash String @db.VarChar - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_allow_spend_blocks_metagraph_snapshot_fk") - metagraph_allow_spends metagraph_allow_spends[] -} - model metagraph_allow_spends { hash String @id(map: "metagraph_allow_spends_pk") @db.VarChar source_addr String @db.VarChar @@ -323,27 +301,28 @@ model metagraph_allow_spends { ordinal BigInt @unique(map: "metagraph_allow_spends_ordinal") snapshot_hash String @db.VarChar metagraph_allow_spend_approvers metagraph_allow_spend_approvers[] - metagraph_allow_spend_block metagraph_allow_spend_blocks @relation(fields: [round_id], references: [round_id], onDelete: Cascade, onUpdate: NoAction, map: "allow_spends_block_fk") addresses_metagraph_allow_spends_destination_addrToaddresses addresses @relation("metagraph_allow_spends_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_allow_spends_destination_addr_fk") addresses_metagraph_allow_spends_source_addrToaddresses addresses @relation("metagraph_allow_spends_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_allow_spends_source_addr_fk") metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") metagraph_expired_spend_transactions metagraph_expired_spend_transactions[] metagraph_spend_transactions metagraph_spend_transactions[] - abstract_transactions_view abstract_transactions_view? - metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_allow_spend_blocks_metagraph_snapshot_fk") + abstract_transactions_view abstract_transactions_view[] + metagraph_snapshot metagraph_snapshots? @relation(fields: [metagraph_snapshotsMetagraph_id, metagraph_snapshotsHash], references: [metagraph_id, hash]) + metagraph_snapshotsMetagraph_id String? @db.VarChar + metagraph_snapshotsHash String? @db.VarChar } model metagraph_balance_changes { - metagraph_id String @db.VarChar - metagraph_snapshot_hash String @db.VarChar - address String @db.VarChar - balance BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - snapshot_ordinal BigInt @map("metagraph_snapshot_ordinal") - addresses addresses @relation(fields: [address], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "address_fk") - metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_balance_change_metagraph_snapshot_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") + metagraph_id String @db.VarChar + metagraph_snapshot_hash String @db.VarChar + address String @db.VarChar + balance BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + snapshot_ordinal BigInt @map("metagraph_snapshot_ordinal") + addresses addresses @relation(fields: [address], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "address_fk") + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_balance_change_metagraph_snapshot_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") @@id([metagraph_id, address, snapshot_ordinal], map: "metagraph_balance_change_pk") } @@ -355,33 +334,34 @@ model metagraph_blocks { updated_at DateTime @default(now()) @db.Timestamp(6) metagraph_id String @db.VarChar metagraph_snapshot_hash String @db.VarChar + super abstract_blocks? metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_block_metagraph_snapshot_fk") metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") metagraph_transactions metagraph_transactions[] - super abstract_blocks? @@id([metagraph_id, hash], map: "metagraph_block_pk") - @@unique([hash], map: "hash") + @@unique([hash]) } model metagraph_fee_transactions { - hash String @db.VarChar - source_addr String @db.VarChar + hash String @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar - metagraph_snapshot_hash String @db.VarChar - destination_addr String @db.VarChar - data_update_ref String? @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + metagraph_snapshot_hash String @db.VarChar + destination_addr String @db.VarChar + data_update_ref String? @db.VarChar metagraph_snapshot_ordinal BigInt? - metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "fee_transaction_metagraph_snapshot_fk") - addresses_metagraph_fee_transactions_destination_addrToaddresses addresses @relation("metagraph_fee_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_fee_transactions_destination_addr_fk") - addresses_metagraph_fee_transactions_source_addrToaddresses addresses @relation("metagraph_fee_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_fee_transactions_source_addr_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") - abstract_transactions_view abstract_transactions_view? + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "fee_transaction_metagraph_snapshot_fk") + addresses_metagraph_fee_transactions_destination_addrToaddresses addresses @relation("metagraph_fee_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_fee_transactions_destination_addr_fk") + addresses_metagraph_fee_transactions_source_addrToaddresses addresses @relation("metagraph_fee_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_fee_transactions_source_addr_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") + abstract_transactions_view abstract_transactions_view[] - @@id([hash], map: "fee_transaction_pk") + @@id([metagraph_id, hash], map: "fee_transaction_pk") + @@unique([hash]) } model metagraph_reward_transactions { @@ -414,7 +394,7 @@ model metagraph_snapshots { version String @db.VarChar created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_allow_spend_blocks metagraph_allow_spend_blocks[] + metagraph_allow_spends metagraph_allow_spends[] metagraph_balance_changes metagraph_balance_changes[] metagraph_blocks metagraph_blocks[] metagraph_fee_transactions metagraph_fee_transactions[] @@ -423,85 +403,75 @@ model metagraph_snapshots { global_snapshot global_snapshots? @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_snapshots_global_snapshots_fk") addresses_metagraph_snapshots_owner_addressToaddresses addresses? @relation("metagraph_snapshots_owner_addressToaddresses", fields: [owner_address], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "owner_address_fk") addresses_metagraph_snapshots_staking_addressToaddresses addresses? @relation("metagraph_snapshots_staking_addressToaddresses", fields: [staking_address], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "staking_address_fk") - metagraph_token_lock_blocks metagraph_token_lock_blocks? - metagraph_token_locks metagraph_token_locks[] - metagraph_token_unlocks metagraph_token_unlocks[] - metagraph_allow_spends metagraph_allow_spends[] + + metagraph_token_locks metagraph_token_locks[] + metagraph_token_unlocks metagraph_token_unlocks[] + metagraph_token_lock_blocks metagraph_token_lock_blocks[] @@id([metagraph_id, hash], map: "metagraph_snapshot_pk") @@unique([metagraph_id, ordinal], map: "metagraph_snapshot_unique") } model metagraph_spend_transactions { - hash String @id(map: "metagraph_spend_transactions_pk") @db.VarChar - source_addr String @db.VarChar + hash String @id(map: "metagraph_spend_transactions_pk") @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar - destination_addr String @db.VarChar - allow_spend_ref String? @db.VarChar - snapshot_hash String? @db.VarChar - metagraph_allow_spend metagraph_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "mg_spend_transactions_mg_allow_spends_fk") - addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_destination_addr_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_metagraph_id_fk") - abstract_transactions_view abstract_transactions_view? -} - -model metagraph_token_lock_blocks { - metagraph_id String @db.VarChar - metagraph_snapshot_hash String @db.VarChar - round_id String @db.Uuid - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_lock_blocks_metagraph_id_fk") - metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_lock_blocks_metagraph_snapshot_fk") - - @@id([metagraph_id, metagraph_snapshot_hash]) - @@unique([metagraph_id, round_id], map: "metagraph_token_lock_blocks_unique") + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + destination_addr String @db.VarChar + allow_spend_ref String? @db.VarChar + snapshot_hash String @db.VarChar + addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_destination_addr_fk") + metagraph_allow_spend metagraph_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "mg_spend_transactions_mg_allow_spends_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_metagraph_id_fk") + abstract_transactions_view abstract_transactions_view[] } model metagraph_token_locks { - hash String @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar - ordinal BigInt - unlock_epoch BigInt? - round_id String @db.Uuid - snapshot_hash String @db.VarChar - parent_hash String? @db.VarChar - metagraph metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") - addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_locks_source_addr_fk") - abstract_transactions_view abstract_transactions_view? - metagraph_token_unlock metagraph_token_unlocks? - metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_lock_blocks_metagraph_snapshot_fk") - - @@id([hash], map: "metagraph_token_locks_pk") + hash String @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + ordinal BigInt + unlock_epoch BigInt + round_id String @db.VarChar + parent_hash String? @db.VarChar + snapshot_hash String @db.VarChar + metagraph metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_locks_source_addr_fk") + abstract_transactions_view abstract_transactions_view[] + metagraph_snapshot String? @db.VarChar + metagraph_token_unlock metagraph_token_unlocks? + metagraph_snapshots metagraph_snapshots? @relation(fields: [metagraph_snapshotsMetagraph_id, metagraph_snapshotsHash], references: [metagraph_id, hash]) + metagraph_snapshotsMetagraph_id String? @db.VarChar + metagraph_snapshotsHash String? @db.VarChar + + @@id([metagraph_id, hash], map: "metagraph_token_locks_pk") + @@unique([hash]) @@unique([metagraph_id, ordinal], map: "metagraph_token_locks_unique") } model metagraph_token_unlocks { - hash String @db.VarChar - source_addr String @db.VarChar + hash String @id(map: "metagraph_token_unlocks_pk") @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar lock_reference_ordinal BigInt - lock_reference_hash String @db.VarChar - snapshot_hash String @db.VarChar - addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "address_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") - token_lock metagraph_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction, map: "metagraph_token_unlocks_token_locks_fk") - abstract_transactions_view abstract_transactions_view? - metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_unlock_blocks_metagraph_snapshot_fk") + lock_reference_hash String @db.VarChar + snapshot_hash String @db.VarChar + parent_hash String @db.VarChar + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "address_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") + abstract_transactions_view abstract_transactions_view[] + metagraph_snapshot metagraph_snapshots? @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash]) + metagraph_token_lock metagraph_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_unlocks_token_locks_fk") - @@id([hash], map: "metagraph_token_unlocks_pk") - @@unique([lock_reference_hash]) - @@unique([lock_reference_ordinal]) + @@unique([lock_reference_hash], map: "metagraph_token_unlocks_locks_fk") } model metagraph_transactions { @@ -515,7 +485,7 @@ model metagraph_transactions { fee BigInt salt BigInt parent_ordinal BigInt - parent_hash String? @db.VarChar + parent_hash String @db.VarChar ordinal BigInt block_hash String @db.VarChar metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") @@ -544,26 +514,107 @@ model metagraphs { } model dag_expired_spend_transactions { - hash String @id(map: "dag_expired_spend_transactions_pk") @db.VarChar - source_addr String @db.VarChar + hash String @id(map: "dag_expired_spend_transactions_pk") @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - allow_spend_ref String @db.VarChar - snapshot_hash String @db.VarChar - abstract_transactions_view abstract_transactions_view? - dag_allow_spend dag_allow_spends @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_expired_spend_transactions_dag_allow_spends_fk") + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + allow_spend_ref String? @db.VarChar + snapshot_hash String @db.VarChar + dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_expired_spend_transactions_dag_allow_spends_fk") + abstract_transactions_view abstract_transactions_view[] } model metagraph_expired_spend_transactions { - hash String @id(map: "metagraph_expired_spend_transactions_pk") @db.VarChar - source_addr String @db.VarChar + hash String @id(map: "metagraph_expired_spend_transactions_pk") @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar - allow_spend_ref String @db.VarChar - snapshot_hash String @db.VarChar - abstract_transactions_view abstract_transactions_view? - metagraph_allow_spend metagraph_allow_spends @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_expired_spend_transactions_metagraph_allow_spends_fk") + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + allow_spend_ref String? @db.VarChar + snapshot_hash String? @db.VarChar + metagraph_allow_spend metagraph_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_expired_spend_transactions_metagraph_allow_spends_fk") + abstract_transactions_view abstract_transactions_view[] +} + +model metagraph_token_lock_blocks { + metagraph_id String @db.VarChar + metagraph_snapshot_hash String @db.VarChar + round_id String @db.Uuid + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_lock_blocks_metagraph_id_fk") + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_lock_blocks_metagraph_snapshot_fk") + + @@id([metagraph_id, metagraph_snapshot_hash]) + @@unique([metagraph_id, round_id], map: "metagraph_token_lock_blocks_unique") +} + +model delegate_stake_create_events { + hash String @id @db.VarChar + ordinal BigInt + source_addr String @db.VarChar + node_id String @db.VarChar + amount BigInt + fee BigInt @default(0) + lock_reference_hash String @db.VarChar + parent_hash String @db.VarChar + global_snapshot_hash String @db.VarChar + is_update Boolean + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + + global_snapshots global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction) + dag_token_lock dag_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) + delegate_stake_withdraw_event delegate_stake_withdraw_events? + + @@index([global_snapshot_hash]) + @@index([source_addr]) + @@index([lock_reference_hash]) +} + +model delegate_stake_withdraw_events { + hash String @id @db.VarChar + source_addr String @db.VarChar + stake_create_hash String @unique @db.VarChar + global_snapshot_hash String @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + + global_snapshots global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction) + delegate_stake_create_event delegate_stake_create_events @relation(fields: [stake_create_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) +} + +model delegate_stake_balance_changes { + global_snapshot_hash String @db.VarChar + address String @db.VarChar + node_id String @db.VarChar + balance BigInt + rewards BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + global_snapshot_ordinal BigInt + addresses addresses @relation(fields: [address], references: [address], onDelete: Cascade, onUpdate: NoAction) + global_snapshots global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) + + @@unique([global_snapshot_hash, address, node_id, balance, rewards], map: "delegate_stake_balance_changes_unique") + @@index([address], map: "delegate_stake_rewards_changes_address") + @@index([global_snapshot_hash], map: "delegate_stake_rewards_changes_global_snapshot_hash_idx") + @@index([node_id], map: "delegate_stake_rewards_changes_node_id") +} + +model delegate_stake_rewards { + global_snapshot_hash String @db.VarChar + address String @db.VarChar + node_id String @db.VarChar + rewards BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + addresses addresses @relation(fields: [address], references: [address], onDelete: Cascade, onUpdate: NoAction) + global_snapshots global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) + + @@unique([global_snapshot_hash, address, node_id, rewards], map: "delegate_stake_rewards_changes_unique") } diff --git a/prisma/seed.ts b/prisma/seed.ts index 921d675..4fed321 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -62,6 +62,193 @@ export const data_global_snapshots = [ epoch_progress: 1012463n, metagraph_snapshot_count: 1n, version: "0.0.1", + created_at: new Date("2025-04-02T00:00:01Z"), // set explicitly to avoid race condition + updated_at: new Date("2025-04-02T00:00:01Z"), + }, + { + hash: "16593f9f612a453c28669b86067e097990ee18742e905afa330674636ca1431c", + ordinal: 2556537n, + height: 41999n, + subheight: 846, + last_snapshot_hash: + "b9fdf9e18ac3225a35b75d4fe73e9ee5d307a21ac3e0bfd4c77a6eda2411a366", + epoch_progress: 1012465n, + metagraph_snapshot_count: 1n, + version: "0.0.1", + created_at: new Date("2025-04-02T10:00:01Z"), // set explicitly to avoid race condition + updated_at: new Date("2025-04-02T10:00:01Z"), + }, +]; + +export const data_dag_blocks = [ + { + hash: "16593f9f612a453c28669b86067e097990ee18742e905afa330674636ca1431c", + height: 12n, + snapshot_hash: data_global_snapshots[0].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + hash: "48fd7dd45ced78be111174c5262cca65aa44798b6a01b48525590bfcce643bd2", + height: 14n, + snapshot_hash: data_global_snapshots[1].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; + +export const data_dag_transactions = [ + { + hash: "1c53bc94c735d8d6eeaddc9f5cb446e7f79144c9aa5bba9479db8dee0ec1aa4c", + source_addr: data_addresses[0].address, + destination_addr: data_addresses[1].address, + amount: 90790983n, + fee: 200000n, + salt: 1231231232n, + parent_ordinal: 21337n, + parent_hash: + "4a6d3aa5715e304b4b5f32d52f0c91e0909acf7c24b3ca9776324da68db2f30c", + ordinal: 234n, + block_hash: data_dag_blocks[0].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + hash: "6acc815979e9d1935cce65ba776fde1144c5fc0e97d3a9fe67d82d0e6e21977d", + source_addr: data_addresses[1].address, + destination_addr: data_addresses[0].address, + amount: 90790983n, + fee: 100000n, + salt: 1234n, + parent_ordinal: 21337n, + parent_hash: + "1c53bc94c735d8d6eeaddc9f5cb446e7f79144c9aa5bba9479db8dee0ec1aa4c", + ordinal: 3222n, + block_hash: data_dag_blocks[1].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; +export const data_dag_balance_changes = [ + { + snapshot_hash: data_global_snapshots[0].hash, + snapshot_ordinal: data_global_snapshots[0].ordinal, + address: data_dag_transactions[0].destination_addr, + balance: data_dag_transactions[0].amount, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; + +export const data_metagraphs = [ + { + id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; + +export const data_metagraph_snapshots = [ + { + metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", + global_snapshot_hash: data_global_snapshots[0].hash, + ordinal: 1n, + hash: "1c1e16746af43bc1f93aadaf006cd981852a706e74f1d24b66a7e5e9fa4188e5", + height: 1n, + subheight: 1, + owner_address: null, + staking_address: null, + epoch_progress: 1n, + size: 1n, + last_snapshot_hash: + "0000000000000000000000000000000000000000000000000000000000000000", + fee: 1n, + version: "0.0.1", + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + global_snapshot_hash: data_global_snapshots[1].hash, + metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", + ordinal: 2n, + hash: "9bf40b2d2e355401bbca7a7924880ab799ffa91e95d1b93f3298b549758aac64", + + height: 2n, + subheight: 2, + owner_address: null, + staking_address: null, + epoch_progress: 1n, + size: 1n, + last_snapshot_hash: + "1c1e16746af43bc1f93aadaf006cd981852a706e74f1d24b66a7e5e9fa4188e5", + fee: 1n, + version: "0.0.1", + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; + +export const data_metagraph_blocks = [ + { + metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", + hash: "33374138dd6f5f9846261d541dab33dadcbae8c9f5a39026336a34a3e2aafb93", + height: 12n, + metagraph_snapshot_hash: data_metagraph_snapshots[0].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", + hash: "3d5a9616d65a6d98fe629f1a056489df9245a40d1e8589ed9d655c6fcb3ee361", + height: 14n, + metagraph_snapshot_hash: data_metagraph_snapshots[1].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; +export const data_metagraph_transactions = [ + { + metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", + hash: "39c9909d3b00552beaa5487c38110267675ab212b3b97654fc5ca9917cb7e72b", + source_addr: data_addresses[0].address, + destination_addr: data_addresses[1].address, + amount: 90790983n, + fee: 200000n, + salt: 1231231232n, + parent_ordinal: 21337n, + parent_hash: + "5056fdfbba0637dcecfc0b7fa3f441c745c852cf850c3bfc0dbc8a7410b8d722", + ordinal: 12n, + block_hash: data_metagraph_blocks[0].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", + hash: "aa50e85a32e3e84c9b49880e103ee240f572a6febd255d97db1406f6c936af6f", + source_addr: data_addresses[1].address, + destination_addr: data_addresses[0].address, + amount: 90790983n, + fee: 100000n, + salt: 1234n, + parent_ordinal: 21337n, + parent_hash: + "39c9909d3b00552beaa5487c38110267675ab212b3b97654fc5ca9917cb7e72b", + ordinal: 123n, + block_hash: data_metagraph_blocks[1].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; +export const data_metagraph_balance_changes = [ + { + metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", + metagraph_snapshot_hash: data_metagraph_snapshots[0].hash, + snapshot_ordinal: data_metagraph_snapshots[0].ordinal, + address: data_metagraph_transactions[0].destination_addr, + balance: data_metagraph_transactions[0].amount, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), created_at: new Date("2025-04-02T00:10:01Z"), // set explicitly to avoid race condition updated_at: new Date("2025-04-02T00:10:01Z"), }, diff --git a/routes/delegated-staking.yml b/routes/delegated-staking.yml new file mode 100644 index 0000000..cdb67d7 --- /dev/null +++ b/routes/delegated-staking.yml @@ -0,0 +1,48 @@ +delegatedStakes: + handler: src/handlers/delegatedStakingHandler.delegatedStakes + events: + - httpApi: + path: /delegated-stakes + method: GET + +delegatedStake: + handler: src/handlers/delegatedStakingHandler.delegatedStake + events: + - httpApi: + path: /delegated-stakes/{hash} + method: GET + +addressDelegatedStakes: + handler: src/handlers/delegatedStakingHandler.addressDelegatedStakes + events: + - httpApi: + path: /addresses/{address}/delegated-stakes + method: GET + +delegatedStakeWithdrawals: + handler: src/handlers/delegatedStakingHandler.delegatedStakeWithdrawals + events: + - httpApi: + path: /delegated-stake-withdrawals + method: GET + +delegatedStakeWithdrawal: + handler: src/handlers/delegatedStakingHandler.delegatedStakeWithdrawal + events: + - httpApi: + path: /delegated-stake-withdrawals/{hash} + method: GET + +addressDelegatedStakeWithdrawals: + handler: src/handlers/delegatedStakingHandler.addressDelegatedStakeWithdrawals + events: + - httpApi: + path: /addresses/{address}/delegated-stake-withdrawals + method: GET + +stakingBalanceByAddress: + handler: src/handlers/delegatedStakingHandler.stakingBalanceByAddress + events: + - httpApi: + path: /addresses/{address}/delegated-stake-balance + method: GET \ No newline at end of file diff --git a/routes/metagraph.yml b/routes/metagraph.yml index 5d25925..3368524 100644 --- a/routes/metagraph.yml +++ b/routes/metagraph.yml @@ -2,7 +2,7 @@ ccySnapshots: handler: src/handlers/metagraphHandler.currencySnapshots events: - httpApi: - path: /currency/{metagraph_id}/snapshots + path: /currency/{identifier}/snapshots method: GET ccySnapshotsByOwnerAddress: handler: src/handlers/metagraphHandler.currencySnapshotsByOwnerAddress @@ -14,91 +14,91 @@ ccySnapshot: handler: src/handlers/metagraphHandler.currencySnapshot events: - httpApi: - path: /currency/{metagraph_id}/snapshots/{term} + path: /currency/{identifier}/snapshots/{term} method: GET ccySnapshotRewards: handler: src/handlers/metagraphHandler.currencySnapshotRewards events: - httpApi: - path: /currency/{metagraph_id}/snapshots/{term}/rewards + path: /currency/{identifier}/snapshots/{term}/rewards method: GET ccySnapshotTransactions: handler: src/handlers/metagraphHandler.currencySnapshotTransactions events: - httpApi: - path: /currency/{metagraph_id}/snapshots/{term}/transactions + path: /currency/{identifier}/snapshots/{term}/transactions method: GET ccyBlock: handler: src/handlers/metagraphHandler.currencyBlock events: - httpApi: - path: /currency/{metagraph_id}/blocks/{hash} + path: /currency/{identifier}/blocks/{hash} method: GET ccyTransactions: handler: src/handlers/metagraphHandler.currencyTransactions events: - httpApi: - path: /currency/{metagraph_id}/transactions + path: /currency/{identifier}/transactions method: GET ccyTransaction: handler: src/handlers/metagraphHandler.currencyTransaction events: - httpApi: - path: /currency/{metagraph_id}/transactions/{hash} + path: /currency/{identifier}/transactions/{hash} method: GET ccyTransactionsByAddress: handler: src/handlers/metagraphHandler.currencyTransactionsByAddress events: - httpApi: - path: /currency/{metagraph_id}/addresses/{address}/transactions + path: /currency/{identifier}/addresses/{address}/transactions method: GET ccyTransactionsBySource: handler: src/handlers/metagraphHandler.currencyTransactionsBySource events: - httpApi: - path: /currency/{metagraph_id}/addresses/{address}/transactions/sent + path: /currency/{identifier}/addresses/{address}/transactions/sent method: GET ccyTransactionsByDestination: handler: src/handlers/metagraphHandler.currencyTransactionsByDestination events: - httpApi: - path: /currency/{metagraph_id}/addresses/{address}/transactions/received + path: /currency/{identifier}/addresses/{address}/transactions/received method: GET ccyBalanceByAddress: handler: src/handlers/metagraphHandler.currencyBalanceByAddress events: - httpApi: - path: /currency/{metagraph_id}/addresses/{address}/balance + path: /currency/{identifier}/addresses/{address}/balance method: GET ccyFeeTransaction: handler: src/handlers/metagraphHandler.currencyFeeTransaction events: - httpApi: - path: /currency/{metagraph_id}/fee-transactions/{hash} + path: /currency/{identifier}/fee-transactions/{hash} method: GET ccySnapshotFeeTransactions: handler: src/handlers/metagraphHandler.currencySnapshotFeeTransactions events: - httpApi: - path: /currency/{metagraph_id}/snapshots/{term}/fee-transactions + path: /currency/{identifier}/snapshots/{term}/fee-transactions method: GET ccyFeeTransactionsByAddress: handler: src/handlers/metagraphHandler.currencyFeeTransactionsByAddress events: - httpApi: - path: /currency/{metagraph_id}/addresses/{address}/fee-transactions + path: /currency/{identifier}/addresses/{address}/fee-transactions method: GET ccyFeeTransactionsBySource: handler: src/handlers/metagraphHandler.currencyFeeTransactionsBySource events: - httpApi: - path: /currency/{metagraph_id}/addresses/{address}/fee-transactions/sent + path: /currency/{identifier}/addresses/{address}/fee-transactions/sent method: GET ccyFeeTransactionsByDestination: handler: src/handlers/metagraphHandler.currencyFeeTransactionsByDestination events: - httpApi: - path: /currency/{metagraph_id}/addresses/{address}/fee-transactions/received + path: /currency/{identifier}/addresses/{address}/fee-transactions/received method: GET metagraphs: handler: src/handlers/metagraphHandler.metagraphs diff --git a/serverless.yml b/serverless.yml index 9bbb833..15a4601 100644 --- a/serverless.yml +++ b/serverless.yml @@ -27,6 +27,7 @@ functions: - ${file(./routes/token-locks.yml)} - ${file(./routes/allow-spends.yml)} - ${file(./routes/actions.yml)} + - ${file(./routes/delegated-staking.yml)} plugins: - serverless-plugin-typescript diff --git a/src/handlers/dagHandler.ts b/src/handlers/dagHandler.ts index 9e9fcef..2af561f 100644 --- a/src/handlers/dagHandler.ts +++ b/src/handlers/dagHandler.ts @@ -29,7 +29,7 @@ const globalSnapshotExists = async (term) => { }); }; -const latestGlobalSnapshot = async () => { +export const latestGlobalSnapshot = async () => { return prisma.global_snapshots.findFirst({ select: { hash: true, ordinal: true }, orderBy: { ordinal: "desc" }, diff --git a/src/handlers/delegatedStakingHandler.ts b/src/handlers/delegatedStakingHandler.ts new file mode 100644 index 0000000..f44d23e --- /dev/null +++ b/src/handlers/delegatedStakingHandler.ts @@ -0,0 +1,190 @@ +import { PrismaClient } from "@prisma/client"; +import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; +import { extractHashOrdinal, extractPagination } from "../request-params"; +import { + paginatedQuery, + toOrdinalCursor, + fromOrdinalCursor, +} from "../pagination"; +import { handleError, respond } from "../response"; +import { toNumber } from "lodash"; +import { latestGlobalSnapshot } from "./dagHandler"; + +const prisma = new PrismaClient(); + +const delegateStakeCreateResponse = (event) => ({ + hash: event.hash, + ordinal: event.ordinal, + source: event.source_addr, + nodeId: event.node_id, + amount: event.amount, + fee: event.fee, + tokenLockRef: event.lock_reference_hash, + parentHash: event.parent_hash, + globalSnapshotHash: event.global_snapshot_hash, + timestamp: event.created_at, +}); + +const delegateStakeCreateResponses = (txs) => + txs.map(delegateStakeCreateResponse); + +const delegateStakeWithdrawResponse = (event) => ({ + hash: event.hash, + source: event.source_addr, + stakeHash: event.stake_create_hash, + globalSnapshotHash: event.global_snapshot_hash, + timestamp: event.created_at, +}); + +const delegateStakeWithdrawResponses = (txs) => + txs.map(delegateStakeWithdrawResponse); + +const delegateStakeBalanceChangeResponse = (change) => ({ + globalSnapshotOrdinal: change.global_snapshot_ordinal, + address: change.address, + nodeId: change.node_id, + balance: change.balance, + rewards: change.rewards, + timestamp: change.created_at, +}); + +const delegateStakeBalanceChangeResponses = (txs) => + txs.map(delegateStakeBalanceChangeResponse); + +export const delegatedStakes = async ( + event: APIGatewayProxyEvent +): Promise => { + return paginatedQuery( + extractPagination(event), + toOrdinalCursor, + fromOrdinalCursor, + { + where: { + delegate_stake_withdraw_event: null, + }, + orderBy: [{ ordinal: "desc" }], + }, + prisma.delegate_stake_create_events.findMany, + delegateStakeCreateResponses + ); +}; + +export const delegatedStake = async (event) => { + try { + const hash = event.pathParameters?.hash; + + const stake = await prisma.delegate_stake_create_events.findUnique({ + where: { hash }, + }); + + return respond(stake, delegateStakeCreateResponse); + } catch (error) { + return handleError(error); + } +}; + +export const addressDelegatedStakes = async (event) => { + const address = event.pathParameters?.address; + return paginatedQuery( + extractPagination(event), + toOrdinalCursor, + fromOrdinalCursor, + { + where: { + source_addr: address, + }, + orderBy: [{ ordinal: "desc" }], + }, + prisma.delegate_stake_create_events.findMany, + delegateStakeCreateResponses + ); +}; + +export const delegatedStakeWithdrawals = async (event) => { + return paginatedQuery( + extractPagination(event), + toOrdinalCursor, + fromOrdinalCursor, + { + orderBy: [{ created_at: "desc" }], + }, + prisma.delegate_stake_withdraw_events.findMany, + delegateStakeWithdrawResponses + ); +}; + +export const delegatedStakeWithdrawal = async (event) => { + try { + const hash = event.pathParameters?.hash; + + const withdrawal = await prisma.delegate_stake_withdraw_events.findUnique({ + where: { hash }, + }); + + return respond(withdrawal, delegateStakeWithdrawResponse); + } catch (error) { + return handleError(error); + } +}; + +export const addressDelegatedStakeWithdrawals = async (event) => { + const address = event.pathParameters?.address; + return paginatedQuery( + extractPagination(event), + toOrdinalCursor, + fromOrdinalCursor, + { + where: { + source_addr: address, + }, + orderBy: [{ created_at: "desc" }], + }, + prisma.delegate_stake_withdraw_events.findMany, + delegateStakeWithdrawResponses + ); +}; + +export const stakingBalanceByAddress = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { address } = event.pathParameters || {}; + + const { ordinal } = event.queryStringParameters || {}; + + const ordinalNbr = toNumber(ordinal); + + const ordinalCondition = isFinite(ordinalNbr) + ? { global_snapshot_ordinal: { lte: ordinalNbr } } + : {}; + + const latestPerNode = await prisma.delegate_stake_balance_changes.groupBy({ + by: ["node_id"], + where: { + address, + ...ordinalCondition, + }, + _max: { + global_snapshot_ordinal: true, + }, + }); + + const latestConditions = latestPerNode.map(({ node_id, _max }) => ({ + address, + node_id, + global_snapshot_ordinal: _max.global_snapshot_ordinal ?? {}, + })); + + const latestBalances = await prisma.delegate_stake_balance_changes.findMany( + { + where: { + OR: latestConditions, + }, + } + ); + + return respond(latestBalances, delegateStakeBalanceChangeResponses); + } catch (error) { + return handleError(error); + } +}; diff --git a/src/handlers/metagraphHandler.ts b/src/handlers/metagraphHandler.ts index f8d575b..31ef14c 100644 --- a/src/handlers/metagraphHandler.ts +++ b/src/handlers/metagraphHandler.ts @@ -75,7 +75,7 @@ export const currencySnapshots = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { metagraph_id } = event.pathParameters || {}; + const { identifier: metagraph_id } = event.pathParameters || {}; if (!(await metagraphIdExists(metagraph_id))){ return notFoundResponse("metagraph"); @@ -98,7 +98,7 @@ export const currencySnapshots = async ( toCursor, fromCursor, { - where: { metagraph_id }, + where: { metagraph_id }, include: { metagraph_blocks: true }, orderBy: { ordinal: "desc" }, }, @@ -149,7 +149,7 @@ export const currencySnapshot = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { metagraph_id, term } = event.pathParameters || {}; + const { identifier: metagraph_id, term } = event.pathParameters || {}; if (!(await metagraphIdExists(metagraph_id))){ return notFoundResponse("metagraph"); @@ -175,7 +175,7 @@ export const currencySnapshotRewards = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { metagraph_id, term } = event.pathParameters || {}; + const { identifier: metagraph_id, term } = event.pathParameters || {}; if (!(await metagraphIdExists(metagraph_id))){ return notFoundResponse("metagraph"); @@ -262,7 +262,7 @@ export const currencySnapshotTransactions = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { metagraph_id, term } = event.pathParameters || {}; + const { identifier: metagraph_id, term } = event.pathParameters || {}; if (!(await metagraphIdExists(metagraph_id))){ return notFoundResponse("metagraph"); @@ -297,7 +297,7 @@ export const currencyBlock = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { metagraph_id, hash } = event.pathParameters || {}; + const { identifier: metagraph_id, hash } = event.pathParameters || {}; if (!(await metagraphIdExists(metagraph_id))){ return notFoundResponse("metagraph"); @@ -322,7 +322,7 @@ export const currencyTransactions = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { metagraph_id } = event.pathParameters || {}; + const { identifier: metagraph_id } = event.pathParameters || {}; if (!(await metagraphIdExists(metagraph_id))){ return notFoundResponse("metagraph"); @@ -340,7 +340,7 @@ export const currencyTransaction = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { metagraph_id, hash } = event.pathParameters || {}; + const { identifier: metagraph_id, hash } = event.pathParameters || {}; const transaction = await prisma.metagraph_transactions.findUnique({ where: { @@ -368,7 +368,7 @@ export const currencyTransactionsByAddress = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { metagraph_id, address } = event.pathParameters || {}; + const { identifier: metagraph_id, address } = event.pathParameters || {}; if (!(await metagraphIdExists(metagraph_id))){ return notFoundResponse("metagraph"); @@ -391,7 +391,7 @@ export const currencyTransactionsBySource = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { metagraph_id, address } = event.pathParameters || {}; + const { identifier: metagraph_id, address } = event.pathParameters || {}; if (!(await metagraphIdExists(metagraph_id))){ return notFoundResponse("metagraph"); @@ -410,7 +410,7 @@ export const currencyTransactionsByDestination = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { metagraph_id, address } = event.pathParameters || {}; + const { identifier: metagraph_id, address } = event.pathParameters || {}; const where = { where: { metagraph_id, destination_addr: address } }; @@ -434,7 +434,7 @@ export const currencyBalanceByAddress = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { metagraph_id, address } = event.pathParameters || {}; + const { identifier: metagraph_id, address } = event.pathParameters || {}; const { ordinal } = event.queryStringParameters || {}; @@ -470,7 +470,7 @@ export const currencyFeeTransaction = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { metagraph_id, hash } = event.pathParameters || {}; + const { identifier: metagraph_id, hash } = event.pathParameters || {}; const transaction = await prisma.metagraph_fee_transactions.findUnique({ where: { @@ -523,7 +523,7 @@ export const currencySnapshotFeeTransactions = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { metagraph_id, term } = event.pathParameters || {}; + const { identifier: metagraph_id, term } = event.pathParameters || {}; if (!metagraph_id || !term) return missingParameterResponse("identifier or term"); @@ -546,7 +546,7 @@ export const currencyFeeTransactionsByAddress = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { metagraph_id, address } = event.pathParameters || {}; + const { identifier: metagraph_id, address } = event.pathParameters || {}; if (!metagraph_id || !address) return missingParameterResponse("identifier or address"); @@ -567,7 +567,7 @@ export const currencyFeeTransactionsBySource = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { metagraph_id, address } = event.pathParameters || {}; + const { identifier: metagraph_id, address } = event.pathParameters || {}; if (!metagraph_id || !address) return missingParameterResponse("identifier or address"); @@ -587,7 +587,7 @@ export const currencyFeeTransactionsByDestination = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { metagraph_id, address } = event.pathParameters || {}; + const { identifier: metagraph_id, address } = event.pathParameters || {}; if (!metagraph_id || !address) return missingParameterResponse("identifier or address"); diff --git a/tests/handlers/delegatedStakingHandler.test.ts b/tests/handlers/delegatedStakingHandler.test.ts new file mode 100644 index 0000000..0e618d3 --- /dev/null +++ b/tests/handlers/delegatedStakingHandler.test.ts @@ -0,0 +1,260 @@ +import { + delegatedStakes, + delegatedStake, + addressDelegatedStakes, + delegatedStakeWithdrawals, + delegatedStakeWithdrawal, + addressDelegatedStakeWithdrawals, + stakingBalanceByAddress, +} from "../../src/handlers/delegatedStakingHandler"; + +import { + createAPIGatewayEvent, + validateResponseStructure, + validatePaginatedResponse, +} from "../testUtils"; + +import { + data_addresses, + data_global_snapshots, + prisma, +} from "../../prisma/seed"; +import { randomUUID } from "crypto"; + +const data_dag_token_locks = [ + { + hash: `token-lock-hash-002`, + source_addr: data_addresses[0].address, + amount: 1000n, + ordinal: 2n, + unlock_epoch: 10n, + round_id: randomUUID(), + parent_hash: "token-lock-hash-001", + snapshot_hash: data_global_snapshots[0].hash, + }, + { + hash: `token-lock-hash-003`, + source_addr: data_addresses[0].address, + amount: 1200n, + ordinal: 3n, + unlock_epoch: 10n, + round_id: randomUUID(), + parent_hash: "token-lock-hash-002", + snapshot_hash: data_global_snapshots[1].hash, + }, +]; + +const data_delegate_stake_create_events = [ + { + hash: "stake-event-hash-001", + ordinal: 10001n, + source_addr: data_addresses[0].address, + node_id: "NODE_ABC123", + amount: 500000000000n, + fee: 500000n, + lock_reference_hash: data_dag_token_locks[0].hash, + parent_hash: "parent-hash-xyz", + global_snapshot_hash: data_global_snapshots[0].hash, + is_update: false, + }, + { + hash: "stake-event-hash-002", + ordinal: 10002n, + source_addr: data_addresses[0].address, + node_id: "NODE_ABC123", + amount: 3333300000000n, + fee: 500000n, + lock_reference_hash: data_dag_token_locks[1].hash, + parent_hash: "stake-event-hash-001", + global_snapshot_hash: data_global_snapshots[1].hash, + is_update: false, + }, + { + hash: "stake-event-hash-003", + ordinal: 10003n, + source_addr: data_addresses[0].address, + node_id: "NODE_ABC123", + amount: 2222222222, + fee: 500000n, + lock_reference_hash: data_dag_token_locks[1].hash, + parent_hash: "stake-event-hash-001", + global_snapshot_hash: data_global_snapshots[1].hash, + is_update: true, + }, +]; + +const data_delegate_stake_withdraw_events = [ + { + hash: "withdraw-event-hash-001", + source_addr: data_addresses[0].address, + stake_create_hash: data_delegate_stake_create_events[0].hash, + global_snapshot_hash: data_global_snapshots[1].hash, + }, +]; +const data_delegate_stake_balance_changes = [ + { + global_snapshot_hash: data_global_snapshots[1].hash, + global_snapshot_ordinal: data_global_snapshots[1].ordinal, + address: data_addresses[0].address, + node_id: "NODE_DEF456", + balance: 700000000000n, + rewards: 1000000000n, + }, +]; +const data_delegate_stake_rewards = [ + { + global_snapshot_hash: data_global_snapshots[2].hash, + address: data_addresses[0].address, + node_id: "NODE_ABC123", + rewards: 2500000000n, + }, +]; +const seedData = async () => { + await prisma.dag_token_locks.createManyAndReturn({ + data: data_dag_token_locks, + }); + + await prisma.delegate_stake_create_events.createManyAndReturn({ + data: data_delegate_stake_create_events, + }); + + await prisma.delegate_stake_withdraw_events.createManyAndReturn({ + data: data_delegate_stake_withdraw_events, + }); + + await prisma.delegate_stake_balance_changes.createManyAndReturn({ + data: data_delegate_stake_balance_changes, + }); + + await prisma.delegate_stake_rewards.createManyAndReturn({ + data: data_delegate_stake_rewards, + }); +}; + +const validateCreateStake = (tx) => { + const match = data_delegate_stake_create_events.find( + (d) => d.hash === tx.hash + ); + expect(tx.source).toBe(match.source_addr); + expect(tx.nodeId).toBe(match.node_id); + expect(Number(tx.amount)).toBe(Number(match.amount)); + expect(tx.timestamp).toBeDefined(); +}; + +const validateWithdrawStake = (tx) => { + const match = data_delegate_stake_withdraw_events.find( + (d) => d.hash === tx.hash + ); + expect(tx.source).toBe(match.source_addr); + expect(tx.stakeHash).toBe(match.stake_create_hash); + expect(tx.timestamp).toBeDefined(); +}; + +const validateBalanceChange = (tx) => { + const match = data_delegate_stake_balance_changes.find( + (d) => d.address === tx.address && d.node_id === tx.nodeId + ); + expect(tx.balance).toBe(Number(match.balance)); + expect(tx.rewards).toBe(Number(match.rewards)); + expect(tx.timestamp).toBeDefined(); +}; + +describe("Delegated Stake Handler Integration Tests", () => { + beforeAll(async () => { + await seedData(); + }); + + describe("delegatedStakes", () => { + it("should return a list of delegated stakes", async () => { + const activeStates = [ + data_delegate_stake_create_events[1], + data_delegate_stake_create_events[2], + ]; + const event = createAPIGatewayEvent({}, { limit: "10" }); + const response = await delegatedStakes(event); + expect(response.statusCode).toBe(200); + + const body = validatePaginatedResponse(response); + expect(body.data.length).toBe(activeStates.length); + validateCreateStake(body.data[0]); + }); + }); + + describe("delegatedStake", () => { + it("should return a specific delegated stake by hash", async () => { + const test = data_delegate_stake_create_events[0]; + const event = createAPIGatewayEvent({ hash: test.hash }); + + const response = await delegatedStake(event); + expect(response.statusCode).toBe(200); + + const body = validateResponseStructure(response)["data"]; + validateCreateStake(body); + }); + }); + + describe("addressDelegatedStakes", () => { + it("should return stakes for a specific address", async () => { + const test = data_delegate_stake_create_events[0]; + const event = createAPIGatewayEvent({ address: test.source_addr }); + + const response = await addressDelegatedStakes(event); + expect(response.statusCode).toBe(200); + + const body = validatePaginatedResponse(response); + body.data.forEach(validateCreateStake); + }); + }); + + describe("delegatedStakeWithdrawals", () => { + it("should return a list of stake withdrawals", async () => { + const event = createAPIGatewayEvent({}, { limit: "10" }); + const response = await delegatedStakeWithdrawals(event); + expect(response.statusCode).toBe(200); + + const body = validatePaginatedResponse(response); + expect(body.data.length).toBe(data_delegate_stake_withdraw_events.length); + validateWithdrawStake(body.data[0]); + }); + }); + + describe("delegatedStakeWithdrawal", () => { + it("should return a specific stake withdrawal by hash", async () => { + const test = data_delegate_stake_withdraw_events[0]; + const event = createAPIGatewayEvent({ hash: test.hash }); + + const response = await delegatedStakeWithdrawal(event); + expect(response.statusCode).toBe(200); + + const body = validateResponseStructure(response)["data"]; + validateWithdrawStake(body); + }); + }); + + describe("addressDelegatedStakeWithdrawals", () => { + it("should return withdrawals for a specific address", async () => { + const test = data_delegate_stake_withdraw_events[0]; + const event = createAPIGatewayEvent({ address: test.source_addr }); + + const response = await addressDelegatedStakeWithdrawals(event); + expect(response.statusCode).toBe(200); + + const body = validatePaginatedResponse(response); + body.data.forEach(validateWithdrawStake); + }); + }); + + describe("stakingBalanceByAddress", () => { + it("should return staking balance per node for an address", async () => { + const test = data_delegate_stake_balance_changes[0]; + const event = createAPIGatewayEvent({ address: test.address }); + + const response = await stakingBalanceByAddress(event); + expect(response.statusCode).toBe(200); + + const body = validateResponseStructure(response)["data"]; + expect(Array.isArray(body)).toBe(true); + body.forEach(validateBalanceChange); + }); + }); +}); diff --git a/tests/handlers/metagraphHandler.test.ts b/tests/handlers/metagraphHandler.test.ts index ec65ae4..f7d5918 100644 --- a/tests/handlers/metagraphHandler.test.ts +++ b/tests/handlers/metagraphHandler.test.ts @@ -238,7 +238,8 @@ describe("Metagraph Handler Integration Tests", () => { describe("currencySnapshots", () => { it("should return a list of metagraph snapshots", async () => { const metagraph_id = data_metagraphs[0].id - const event = createAPIGatewayEvent({metagraph_id}, { limit: "10" }); + + const event = createAPIGatewayEvent({identifier: metagraph_id}, { limit: "10" }); const response: APIGatewayProxyResult = await metagraphHandler.currencySnapshots(event); @@ -265,7 +266,7 @@ describe("Metagraph Handler Integration Tests", () => { it("should handle pagination correctly", async () => { const metagraph_id = data_metagraphs[0].id - const event = createAPIGatewayEvent({metagraph_id}, { limit: "1" }); + const event = createAPIGatewayEvent({identifier: metagraph_id}, { limit: "1" }); const response: APIGatewayProxyResult = await metagraphHandler.currencySnapshots(event); @@ -278,7 +279,7 @@ describe("Metagraph Handler Integration Tests", () => { // Try getting the next page const nextEvent = createAPIGatewayEvent( - {metagraph_id}, + {identifier: metagraph_id}, { limit: "1", next: body.meta.next, @@ -295,8 +296,8 @@ describe("Metagraph Handler Integration Tests", () => { }); it("should return not found on invalid metagraph", async () => { - const metagraph_id = "1234567" - const event = createAPIGatewayEvent({ metagraph_id }, { limit: "10" }); + const identifier = "1234567" + const event = createAPIGatewayEvent({ identifier }, { limit: "10" }); const response: APIGatewayProxyResult = await metagraphHandler.currencySnapshots(event); @@ -312,7 +313,7 @@ describe("Metagraph Handler Integration Tests", () => { it("should return transactions sorted by snapshot ordinal descending", async () => { const metagraph_id = data_metagraphs[0].id - const event = createAPIGatewayEvent({metagraph_id}); + const event = createAPIGatewayEvent({identifier: metagraph_id}); const response: APIGatewayProxyResult = await metagraphHandler.currencyTransactions(event); @@ -334,8 +335,8 @@ describe("Metagraph Handler Integration Tests", () => { }); it("should return not found on invalid metagraph", async () => { - const metagraph_id = "" - const event = createAPIGatewayEvent({ metagraph_id }, { limit: "10" }); + const identifier = "" + const event = createAPIGatewayEvent({ identifier }, { limit: "10" }); const response: APIGatewayProxyResult = await metagraphHandler.currencyTransactions(event); @@ -352,7 +353,7 @@ describe("Metagraph Handler Integration Tests", () => { const address = data_addresses[0].address; const metagraph_id = data_metagraphs[0].id - const event = createAPIGatewayEvent({ address, metagraph_id}, { limit: "10" }); + const event = createAPIGatewayEvent({ address, identifier: metagraph_id}, { limit: "10" }); const response: APIGatewayProxyResult = await metagraphHandler.currencyTransactionsByAddress(event); @@ -380,7 +381,7 @@ describe("Metagraph Handler Integration Tests", () => { const metagraph_id = testBalance.metagraph_id; const event = createAPIGatewayEvent({ - metagraph_id, + identifier: metagraph_id, address: testBalance.address, }); From b9ec7ee6917884868693d7a9edd0c34708516a02 Mon Sep 17 00:00:00 2001 From: Alex Brandes Date: Thu, 24 Apr 2025 12:44:52 -0700 Subject: [PATCH 29/63] docs: added openapi spec covering current endpoints --- .github/workflows/deploy-docs.yml | 39 + docs/openapi.yaml | 2172 +++++++++++++++++++++++++++++ docs/schemas/actions.yml | 60 + docs/schemas/allowSpends.yml | 160 +++ docs/schemas/dag.yml | 214 +++ docs/schemas/metagraph.yml | 201 +++ docs/schemas/params.yml | 56 + docs/schemas/responses.yml | 43 + docs/schemas/tokenLocks.yml | 107 ++ 9 files changed, 3052 insertions(+) create mode 100644 .github/workflows/deploy-docs.yml create mode 100644 docs/openapi.yaml create mode 100644 docs/schemas/actions.yml create mode 100644 docs/schemas/allowSpends.yml create mode 100644 docs/schemas/dag.yml create mode 100644 docs/schemas/metagraph.yml create mode 100644 docs/schemas/params.yml create mode 100644 docs/schemas/responses.yml create mode 100644 docs/schemas/tokenLocks.yml diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 0000000..90878ce --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,39 @@ +name: Deploy OpenAPI Docs + +on: + push: + branches: + - release/mainnet + - release/integrationnet + - release/testnet + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + environment: ${{ (github.ref_name == 'release/testnet' && 'testnet') || (github.ref_name == 'release/integrationnet' && 'integrationnet') || (github.ref_name == 'release/mainnet' && 'mainnet') }} + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + + - name: Install Redocly CLI + run: npm install -g @redocly/cli@1.34.2 + + - name: Build OpenAPI Docs + run: redocly build-docs docs/openapi.yaml -o docs/index.html + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-west-1 + + - name: Sync to S3 + run: | + aws s3 sync docs/ s3://${{ secrets.S3_BUCKET_NAME }}/ --delete \ No newline at end of file diff --git a/docs/openapi.yaml b/docs/openapi.yaml new file mode 100644 index 0000000..f980773 --- /dev/null +++ b/docs/openapi.yaml @@ -0,0 +1,2172 @@ +openapi: 3.0.3 +info: + title: Constellation Block Explorer API + description: API for exploring the Constellation chain data on both the Hypergraph and Metagraphs + version: 4.0.0 +servers: + - url: https://be-mainnet.constellationnetwork.io + description: MainNet + - url: https://be-integrationnet.constellationnetwork.io + description: IntegrationNet + +# Resource-based tag organization +tags: + - name: Metagraphs + description: Metagraph registry and information + - name: Account Information + description: Balances and address-related data + - name: Snapshots + description: Global and metagraph snapshots + - name: Blocks + description: Block information + - name: Transactions + description: Standard and fee transactions + - name: Token Operations + description: Token locks, delegated spending, and related operations + - name: Actions + description: Combined operations view + +x-tagGroups: + - name: "Network Identity" + tags: + - Metagraphs + - Account Information + - name: "Blockchain State" + tags: + - Snapshots + - Blocks + - Transactions + - name: "Token Actions" + tags: + - Token Operations + - Actions + + +paths: + # ==================== SNAPSHOTS ==================== + /global-snapshots: + get: + tags: + - Snapshots + summary: Get all global snapshots + description: Retrieve a paginated list of global snapshots + operationId: getGlobalSnapshots + parameters: + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/dag.yml#/components/schemas/PaginatedGlobalSnapshots' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /global-snapshots/{hash_or_ordinal}: + get: + tags: + - Snapshots + summary: Get global snapshot by hash, ordinal, or 'latest' + description: Retrieve a specific global snapshot by its hash, ordinal, or 'latest' keyword for the most recent snapshot + operationId: getGlobalSnapshot + parameters: + - name: hash_or_ordinal + in: path + description: Hash, ordinal, or 'latest' to identify the global snapshot + required: true + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/dag.yml#/components/schemas/GlobalSnapshotResponse' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /addresses/{address}/snapshots: + get: + tags: + - Snapshots + summary: Get metagraph snapshots by owner address + description: Retrieve metagraph snapshots associated with a specific owner address + operationId: currencySnapshotsByOwnerAddress + parameters: + - name: address + in: path + description: Owner address to filter snapshots + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/metagraph.yml#/components/schemas/PaginatedMetagraphSnapshots' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/snapshots: + get: + tags: + - Snapshots + summary: Get all snapshots for a metagraph + description: Retrieve a paginated list of snapshots for a specific metagraph + operationId: getCurrencySnapshots + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/metagraph.yml#/components/schemas/PaginatedMetagraphSnapshots' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/snapshots/{hash_or_ordinal}: + get: + tags: + - Snapshots + summary: Get metagraph snapshot by hash, ordinal, or 'latest' + description: Retrieve a specific metagraph snapshot by its hash, ordinal, or 'latest' keyword for the most recent snapshot + operationId: getCurrencySnapshot + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - name: hash_or_ordinal + in: path + description: Hash, ordinal, or 'latest' to identify the snapshot + required: true + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/metagraph.yml#/components/schemas/MetagraphSnapshotResponse' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/snapshots/{hash_or_ordinal}/rewards: + get: + tags: + - Snapshots + summary: Get rewards for a metagraph snapshot + description: Retrieve rewards associated with a specific metagraph snapshot + operationId: getCurrencySnapshotRewards + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - name: hash_or_ordinal + in: path + description: Hash, ordinal, or 'latest' to identify the snapshot + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/dag.yml#/components/schemas/PaginatedRewards' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + # ==================== BLOCKS ==================== + /blocks/{hash}: + get: + tags: + - Blocks + summary: Get block by hash + description: Retrieve a specific DAG block by its hash + operationId: getBlock + parameters: + - name: hash + in: path + description: Hash of the block + required: true + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/dag.yml#/components/schemas/BlockResponse' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/blocks/{hash}: + get: + tags: + - Blocks + summary: Get metagraph block by hash + description: Retrieve a specific metagraph block by its hash + operationId: getCurrencyBlock + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - name: hash + in: path + description: Hash of the block + required: true + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/metagraph.yml#/components/schemas/MetagraphBlockResponse' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + # ==================== TRANSACTIONS ==================== + /transactions: + get: + tags: + - Transactions + summary: Get all transactions + description: Retrieve a paginated list of transactions + operationId: getTransactions + parameters: + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/dag.yml#/components/schemas/PaginatedTransactions' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /transactions/{hash}: + get: + tags: + - Transactions + summary: Get transaction by hash + description: Retrieve a specific transaction by its hash + operationId: getTransaction + parameters: + - name: hash + in: path + description: Hash of the transaction + required: true + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/dag.yml#/components/schemas/TransactionResponse' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /global-snapshots/{hash_or_ordinal}/transactions: + get: + tags: + - Transactions + summary: Get transactions for a global snapshot + description: Retrieve transactions associated with a specific global snapshot + operationId: getGlobalSnapshotTransactions + parameters: + - name: hash_or_ordinal + in: path + description: Hash, ordinal, or 'latest' to identify the global snapshot + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/dag.yml#/components/schemas/PaginatedTransactions' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /global-snapshots/{hash_or_ordinal}/rewards: + get: + tags: + - Snapshots + summary: Get rewards for a global snapshot + description: Retrieve rewards associated with a specific global snapshot + operationId: getGlobalSnapshotRewards + parameters: + - name: hash_or_ordinal + in: path + description: Hash, ordinal, or 'latest' to identify the global snapshot + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/dag.yml#/components/schemas/PaginatedRewards' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /addresses/{address}/transactions: + get: + tags: + - Transactions + summary: Get transactions by address + description: Retrieve transactions associated with a specific address (both sent and received) + operationId: getAddressTransactions + parameters: + - name: address + in: path + description: Address to filter transactions + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/dag.yml#/components/schemas/PaginatedTransactions' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /addresses/{address}/transactions/sent: + get: + tags: + - Transactions + summary: Get transactions sent from address + description: Retrieve transactions sent from a specific address + operationId: getAddressSentTransactions + parameters: + - name: address + in: path + description: Source address to filter transactions + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/dag.yml#/components/schemas/PaginatedTransactions' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /addresses/{address}/transactions/received: + get: + tags: + - Transactions + summary: Get transactions received by address + description: Retrieve transactions received by a specific address + operationId: getAddressReceivedTransactions + parameters: + - name: address + in: path + description: Destination address to filter transactions + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/dag.yml#/components/schemas/PaginatedTransactions' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/transactions: + get: + tags: + - Transactions + summary: Get all transactions for a metagraph + description: Retrieve a paginated list of transactions for a specific metagraph + operationId: getCurrencyTransactions + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/metagraph.yml#/components/schemas/PaginatedMetagraphTransactions' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/transactions/{hash}: + get: + tags: + - Transactions + summary: Get metagraph transaction by hash + description: Retrieve a specific metagraph transaction by its hash + operationId: getCurrencyTransaction + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - name: hash + in: path + description: Hash of the transaction + required: true + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/metagraph.yml#/components/schemas/MetagraphTransactionResponse' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/snapshots/{hash_or_ordinal}/transactions: + get: + tags: + - Transactions + summary: Get transactions for a metagraph snapshot + description: Retrieve transactions associated with a specific metagraph snapshot + operationId: getCurrencySnapshotTransactions + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - name: hash_or_ordinal + in: path + description: Hash, ordinal, or 'latest' to identify the snapshot + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/metagraph.yml#/components/schemas/PaginatedMetagraphTransactions' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/snapshots/{hash_or_ordinal}/fee-transactions: + get: + tags: + - Transactions + summary: Get fee transactions for a metagraph snapshot + description: Retrieve fee transactions associated with a specific metagraph snapshot + operationId: getCurrencySnapshotFeeTransactions + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - name: hash_or_ordinal + in: path + description: Hash, ordinal, or 'latest' to identify the snapshot + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/metagraph.yml#/components/schemas/PaginatedMetagraphTransactions' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/fee-transactions/{hash}: + get: + tags: + - Transactions + summary: Get fee transaction by hash for a metagraph + description: Retrieve a specific fee transaction by its hash for a metagraph + operationId: getCurrencyFeeTransaction + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - name: hash + in: path + description: Hash of the fee transaction + required: true + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/metagraph.yml#/components/schemas/MetagraphTransactionResponse' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/addresses/{address}/fee-transactions: + get: + tags: + - Transactions + summary: Get fee transactions by address in a metagraph + description: Retrieve fee transactions associated with a specific address in a metagraph + operationId: getCurrencyFeeTransactionsByAddress + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - name: address + in: path + description: Address to filter fee transactions + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/metagraph.yml#/components/schemas/PaginatedMetagraphTransactions' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/addresses/{address}/fee-transactions/received: + get: + tags: + - Transactions + summary: Get fee transactions received by address in a metagraph + description: Retrieve fee transactions received by a specific address in a metagraph + operationId: getCurrencyFeeTransactionsByDestination + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - name: address + in: path + description: Destination address to filter fee transactions + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/metagraph.yml#/components/schemas/PaginatedMetagraphTransactions' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/addresses/{address}/fee-transactions/sent: + get: + tags: + - Transactions + summary: Get fee transactions sent from address in a metagraph + description: Retrieve fee transactions sent from a specific address in a metagraph + operationId: getCurrencyFeeTransactionsBySource + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - name: address + in: path + description: Source address to filter fee transactions + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/metagraph.yml#/components/schemas/PaginatedMetagraphTransactions' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/addresses/{address}/transactions: + get: + tags: + - Transactions + summary: Get transactions by address in a metagraph + description: Retrieve transactions associated with a specific address in a metagraph + operationId: getCurrencyTransactionsByAddress + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - name: address + in: path + description: Address to filter transactions + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/metagraph.yml#/components/schemas/PaginatedMetagraphTransactions' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/addresses/{address}/transactions/received: + get: + tags: + - Transactions + summary: Get transactions received by address in a metagraph + description: Retrieve transactions received by a specific address in a metagraph + operationId: getCurrencyTransactionsByDestination + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - name: address + in: path + description: Destination address to filter transactions + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/metagraph.yml#/components/schemas/PaginatedMetagraphTransactions' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/addresses/{address}/transactions/sent: + get: + tags: + - Transactions + summary: Get transactions sent from address in a metagraph + description: Retrieve transactions sent from a specific address in a metagraph + operationId: getCurrencyTransactionsBySource + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - name: address + in: path + description: Source address to filter transactions + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/metagraph.yml#/components/schemas/PaginatedMetagraphTransactions' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + # ==================== TOKEN OPERATIONS ==================== + /token-locks: + get: + tags: + - Token Operations + summary: Get all token locks + description: Retrieve a paginated list of token locks on the DAG + operationId: getTokenLocks + parameters: + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/tokenLocks.yml#/components/schemas/PaginatedTokenLocks' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /token-locks/{hash}: + get: + tags: + - Token Operations + summary: Get token lock by hash + description: Retrieve a specific token lock by its hash + operationId: getTokenLock + parameters: + - name: hash + in: path + description: Hash of the token lock + required: true + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/tokenLocks.yml#/components/schemas/TokenLockResponse' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /global-snapshots/{hash_or_ordinal}/token-locks: + get: + tags: + - Token Operations + summary: Get token locks by global snapshot + description: Retrieve token locks associated with a specific global snapshot, identified by hash or ordinal + operationId: getGlobalSnapshotTokenLocks + parameters: + - $ref: './schemas/params.yml#/components/parameters/hashOrOrdinalGlobalParam' + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/tokenLocks.yml#/components/schemas/PaginatedTokenLocks' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /addresses/{address}/token-locks: + get: + tags: + - Token Operations + summary: Get token locks by address + description: Retrieve token locks associated with a specific source address + operationId: getAddressTokenLocks + parameters: + - name: address + in: path + description: Source address to filter token locks + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/tokenLocks.yml#/components/schemas/PaginatedTokenLocks' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /token-unlocks: + get: + tags: + - Token Operations + summary: Get all token unlocks + description: Retrieve a paginated list of token unlocks on the DAG + operationId: getTokenUnlocks + parameters: + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/tokenLocks.yml#/components/schemas/PaginatedTokenUnlocks' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /token-unlocks/{hash}: + get: + tags: + - Token Operations + summary: Get token unlock by hash + description: Retrieve a specific token unlock by its hash + operationId: getTokenUnlock + parameters: + - name: hash + in: path + description: Hash of the token unlock + required: true + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/tokenLocks.yml#/components/schemas/TokenUnlockResponse' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /global-snapshots/{hash_or_ordinal}/token-unlocks: + get: + tags: + - Token Operations + summary: Get token unlocks by global snapshot + description: Retrieve token unlocks associated with a specific global snapshot, identified by hash or ordinal + operationId: getGlobalSnapshotTokenUnlocks + parameters: + - $ref: './schemas/params.yml#/components/parameters/hashOrOrdinalGlobalParam' + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/tokenLocks.yml#/components/schemas/PaginatedTokenUnlocks' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /addresses/{address}/token-unlocks: + get: + tags: + - Token Operations + summary: Get token unlocks by address + description: Retrieve token unlocks associated with a specific source address + operationId: getAddressTokenUnlocks + parameters: + - name: address + in: path + description: Source address to filter token unlocks + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/tokenLocks.yml#/components/schemas/PaginatedTokenUnlocks' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/token-locks: + get: + tags: + - Token Operations + summary: Get token locks for a specific metagraph + description: Retrieve token locks for a specific currency metagraph + operationId: getMetagraphTokenLocks + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/tokenLocks.yml#/components/schemas/PaginatedTokenLocks' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/token-locks/{hash}: + get: + tags: + - Token Operations + summary: Get specific token lock for a metagraph + description: Retrieve a specific token lock for a currency metagraph by its hash + operationId: getMetagraphTokenLock + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - name: hash + in: path + description: Hash of the token lock + required: true + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/tokenLocks.yml#/components/schemas/TokenLockResponse' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/snapshots/{hash_or_ordinal}/token-locks: + get: + tags: + - Token Operations + summary: Get token locks by metagraph snapshot + description: Retrieve token locks associated with a specific metagraph snapshot + operationId: getMetagraphSnapshotTokenLocks + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/hashOrOrdinalMetagraphParam' + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/tokenLocks.yml#/components/schemas/PaginatedTokenLocks' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/addresses/{address}/token-locks: + get: + tags: + - Token Operations + summary: Get token locks by metagraph and address + description: Retrieve token locks for a specific metagraph and source address + operationId: getMetagraphAddressTokenLocks + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - name: address + in: path + description: Source address to filter token locks + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/tokenLocks.yml#/components/schemas/PaginatedTokenLocks' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/token-unlocks: + get: + tags: + - Token Operations + summary: Get token unlocks for a specific metagraph + description: Retrieve token unlocks for a specific currency metagraph + operationId: getMetagraphTokenUnlocks + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/tokenLocks.yml#/components/schemas/PaginatedTokenUnlocks' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/token-unlocks/{hash}: + get: + tags: + - Token Operations + summary: Get specific token unlock for a metagraph + description: Retrieve a specific token unlock for a currency metagraph by its hash + operationId: getMetagraphTokenUnlock + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - name: hash + in: path + description: Hash of the token unlock + required: true + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/tokenLocks.yml#/components/schemas/TokenUnlockResponse' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/snapshots/{hash_or_ordinal}/token-unlocks: + get: + tags: + - Token Operations + summary: Get token unlocks by metagraph snapshot + description: Retrieve token unlocks associated with a specific metagraph snapshot + operationId: getMetagraphSnapshotTokenUnlocks + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/hashOrOrdinalMetagraphParam' + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/tokenLocks.yml#/components/schemas/PaginatedTokenUnlocks' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/addresses/{address}/token-unlocks: + get: + tags: + - Token Operations + summary: Get token unlocks by metagraph and address + description: Retrieve token unlocks for a specific metagraph and source address + operationId: getMetagraphAddressTokenUnlocks + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - name: address + in: path + description: Source address to filter token unlocks + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/tokenLocks.yml#/components/schemas/PaginatedTokenUnlocks' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + # Allow Spend operations + /allow-spends: + get: + tags: + - Token Operations + summary: Get all allow spends + description: Retrieve a paginated list of allow spend transactions on the DAG + operationId: getAllowSpends + parameters: + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/allowSpends.yml#/components/schemas/PaginatedAllowSpends' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /allow-spends/{hash}: + get: + tags: + - Token Operations + summary: Get allow spend by hash + description: Retrieve a specific allow spend transaction by its hash + operationId: getAllowSpend + parameters: + - name: hash + in: path + description: Hash of the allow spend transaction + required: true + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/allowSpends.yml#/components/schemas/AllowSpendResponse' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /global-snapshots/{hash_or_ordinal}/allow-spends: + get: + tags: + - Token Operations + summary: Get allow spends by global snapshot + description: Retrieve allow spends associated with a specific global snapshot, identified by hash or ordinal + operationId: getGlobalSnapshotAllowSpends + parameters: + - $ref: './schemas/params.yml#/components/parameters/hashOrOrdinalGlobalParam' + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/allowSpends.yml#/components/schemas/PaginatedAllowSpends' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /addresses/{address}/allow-spends: + get: + tags: + - Token Operations + summary: Get allow spends by address + description: Retrieve allow spends associated with a specific source or destination address + operationId: getAddressAllowSpends + parameters: + - name: address + in: path + description: Address to filter allow spends (source or destination) + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/allowSpends.yml#/components/schemas/PaginatedAllowSpends' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /spend-transactions: + get: + tags: + - Token Operations + summary: Get all spend transactions + description: Retrieve a paginated list of spend transactions that have used allow spends + operationId: getSpendTransactions + parameters: + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/allowSpends.yml#/components/schemas/PaginatedSpendTransactions' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /spend-transactions/{hash}: + get: + tags: + - Token Operations + summary: Get spend transaction by hash + description: Retrieve a specific spend transaction by its hash + operationId: getSpendTransaction + parameters: + - name: hash + in: path + description: Hash of the spend transaction + required: true + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/allowSpends.yml#/components/schemas/SpendTransactionResponse' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /global-snapshots/{hash_or_ordinal}/spend-transactions: + get: + tags: + - Token Operations + summary: Get spend transactions by global snapshot + description: Retrieve spend transactions associated with a specific global snapshot + operationId: getGlobalSnapshotSpendTransactions + parameters: + - $ref: './schemas/params.yml#/components/parameters/hashOrOrdinalGlobalParam' + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/allowSpends.yml#/components/schemas/PaginatedSpendTransactions' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /addresses/{address}/spend-transactions: + get: + tags: + - Token Operations + summary: Get spend transactions by address + description: Retrieve spend transactions associated with a specific source or destination address + operationId: getAddressSpendTransactions + parameters: + - name: address + in: path + description: Address to filter spend transactions (source or destination) + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/allowSpends.yml#/components/schemas/PaginatedSpendTransactions' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /allow-spend-expirations: + get: + tags: + - Token Operations + summary: Get all allow spend expirations + description: Retrieve a paginated list of expired allow spend transactions + operationId: getAllowSpendExpirations + parameters: + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/allowSpends.yml#/components/schemas/PaginatedAllowSpendExpirations' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /allow-spend-expirations/{hash}: + get: + tags: + - Token Operations + summary: Get allow spend expiration by hash + description: Retrieve a specific allow spend expiration by its hash + operationId: getAllowSpendExpiration + parameters: + - name: hash + in: path + description: Hash of the allow spend expiration + required: true + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/allowSpends.yml#/components/schemas/AllowSpendExpirationResponse' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /global-snapshots/{hash_or_ordinal}/allow-spend-expirations: + get: + tags: + - Token Operations + summary: Get allow spend expirations by global snapshot + description: Retrieve allow spend expirations associated with a specific global snapshot + operationId: getGlobalSnapshotAllowSpendExpirations + parameters: + - $ref: './schemas/params.yml#/components/parameters/hashOrOrdinalGlobalParam' + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/allowSpends.yml#/components/schemas/PaginatedAllowSpendExpirations' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /addresses/{address}/allow-spend-expirations: + get: + tags: + - Token Operations + summary: Get allow spend expirations by address + description: Retrieve allow spend expirations associated with a specific address + operationId: getAddressAllowSpendExpirations + parameters: + - name: address + in: path + description: Address to filter allow spend expirations + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/allowSpends.yml#/components/schemas/PaginatedAllowSpendExpirations' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + # Metagraph-specific allow spend operations + /currency/{metagraph_id}/allow-spends: + get: + tags: + - Token Operations + summary: Get allow spends for a specific metagraph + description: Retrieve allow spends for a specific currency metagraph + operationId: getMetagraphAllowSpends + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/allowSpends.yml#/components/schemas/PaginatedAllowSpends' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/allow-spends/{hash}: + get: + tags: + - Token Operations + summary: Get specific allow spend for a metagraph + description: Retrieve a specific allow spend for a currency metagraph by its hash + operationId: getMetagraphAllowSpend + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - name: hash + in: path + description: Hash of the allow spend + required: true + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/allowSpends.yml#/components/schemas/AllowSpendResponse' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/snapshots/{hash_or_ordinal}/allow-spends: + get: + tags: + - Token Operations + summary: Get allow spends by metagraph snapshot + description: Retrieve allow spends associated with a specific metagraph snapshot + operationId: getMetagraphSnapshotAllowSpends + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/hashOrOrdinalMetagraphParam' + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/allowSpends.yml#/components/schemas/PaginatedAllowSpends' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/addresses/{address}/allow-spends: + get: + tags: + - Token Operations + summary: Get allow spends by metagraph and address + description: Retrieve allow spends for a specific metagraph and address + operationId: getMetagraphAddressAllowSpends + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - name: address + in: path + description: Address to filter allow spends + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/allowSpends.yml#/components/schemas/PaginatedAllowSpends' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + # ==================== ACCOUNT INFORMATION ==================== + /addresses/{address}/balance: + get: + tags: + - Account Information + summary: Get DAG balance for address + description: Retrieve the current DAG balance for a specific address + operationId: getAddressBalance + parameters: + - name: address + in: path + description: Address to get balance for + required: true + schema: + type: string + - name: ordinal + in: query + description: Optional ordinal to get balance at a specific point in time + required: false + schema: + type: integer + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/dag.yml#/components/schemas/BalanceResponse' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/addresses/{address}/balance: + get: + tags: + - Account Information + summary: Get balance for address in a metagraph + description: Retrieve the current balance for a specific address in a metagraph + operationId: getCurrencyBalanceByAddress + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - name: address + in: path + description: Address to get balance for + required: true + schema: + type: string + - name: ordinal + in: query + description: Optional ordinal to get balance at a specific point in time + required: false + schema: + type: integer + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/dag.yml#/components/schemas/BalanceResponse' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + # ==================== ACTIONS ==================== + /actions: + get: + tags: + - Actions + summary: Get all actions + description: Retrieve a paginated list of recent actions + operationId: getActions + parameters: + - name: transactionTypes + in: query + description: Comma-separated list of transaction types to filter by (AllowSpend, TokenLock, TokenUnlock, SpendTransaction, FeeTransaction, ExpiredSpendTransaction) + required: false + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/actions.yml#/components/schemas/PaginatedActions' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /global-snapshots/{hash_or_ordinal}/actions: + get: + tags: + - Actions + summary: Get actions by global snapshot + description: Retrieve actions associated with a specific global snapshot, identified by hash or ordinal + operationId: getGlobalSnapshotActions + parameters: + - $ref: './schemas/params.yml#/components/parameters/hashOrOrdinalGlobalParam' + - name: transactionTypes + in: query + description: Comma-separated list of transaction types to filter by (AllowSpend, TokenLock, TokenUnlock, SpendTransaction, FeeTransaction, ExpiredSpendTransaction) + required: false + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/actions.yml#/components/schemas/PaginatedActions' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /addresses/{address}/actions: + get: + tags: + - Actions + summary: Get actions by address + description: Retrieve actions associated with a specific address (source or destination) + operationId: getAddressActions + parameters: + - name: address + in: path + description: Address to filter actions by + required: true + schema: + type: string + - name: transactionTypes + in: query + description: Comma-separated list of transaction types to filter by (AllowSpend, TokenLock, TokenUnlock, SpendTransaction, FeeTransaction, ExpiredSpendTransaction) + required: false + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/actions.yml#/components/schemas/PaginatedActions' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + # ==================== METAGRAPHS ==================== + /currency: + get: + tags: + - Metagraphs + summary: Get all metagraphs + description: Retrieve a paginated list of metagraphs + operationId: getMetagraphs + parameters: + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/metagraph.yml#/components/schemas/PaginatedMetagraphs' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + # Additional Action endpoints for metagraphs + /currency/{metagraph_id}/actions: + get: + tags: + - Actions + summary: Get actions for a specific metagraph + description: Retrieve actions for a specific currency metagraph + operationId: getCurrencyActions + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - name: transactionTypes + in: query + description: Comma-separated list of transaction types to filter by (AllowSpend, TokenLock, TokenUnlock, SpendTransaction, FeeTransaction, ExpiredSpendTransaction) + required: false + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/actions.yml#/components/schemas/PaginatedActions' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/snapshots/{hash_or_ordinal}/actions: + get: + tags: + - Actions + summary: Get actions by metagraph snapshot + description: Retrieve actions associated with a specific metagraph snapshot + operationId: getCurrencySnapshotActions + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/hashOrOrdinalMetagraphParam' + - name: transactionTypes + in: query + description: Comma-separated list of transaction types to filter by (AllowSpend, TokenLock, TokenUnlock, SpendTransaction, FeeTransaction, ExpiredSpendTransaction) + required: false + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/actions.yml#/components/schemas/PaginatedActions' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/addresses/{address}/actions: + get: + tags: + - Actions + summary: Get actions by metagraph and address + description: Retrieve actions for a specific metagraph and address + operationId: getCurrencyAddressActions + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - name: address + in: path + description: Address to filter actions by + required: true + schema: + type: string + - name: transactionTypes + in: query + description: Comma-separated list of transaction types to filter by (AllowSpend, TokenLock, TokenUnlock, SpendTransaction, FeeTransaction, ExpiredSpendTransaction) + required: false + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/actions.yml#/components/schemas/PaginatedActions' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' diff --git a/docs/schemas/actions.yml b/docs/schemas/actions.yml new file mode 100644 index 0000000..918f677 --- /dev/null +++ b/docs/schemas/actions.yml @@ -0,0 +1,60 @@ +components: + schemas: + Action: + type: object + properties: + type: + type: string + description: The type of transaction (AllowSpend, TokenLock, TokenUnlock, SpendTransaction, FeeTransaction, ExpiredSpendTransaction) + enum: [AllowSpend, TokenLock, TokenUnlock, SpendTransaction, FeeTransaction, ExpiredSpendTransaction] + currencyId: + type: string + description: The identifier of the currency + nullable: true + hash: + type: string + description: The hash of the transaction + amount: + type: string + description: The amount of tokens in the transaction + format: bigint + source: + type: string + description: The source address of the transaction + destination: + type: string + description: The destination address of the transaction + nullable: true + unlockEpoch: + type: number + description: The epoch when tokens can be unlocked or last valid epoch progress for allow spends + nullable: true + parentHash: + type: string + description: Reference to a parent transaction (e.g., token lock reference for token unlocks or allow spend reference for spend transactions) + nullable: true + timestamp: + type: string + description: The timestamp when the transaction was created + format: date-time + + ActionResponse: + type: object + properties: + data: + $ref: '#/components/schemas/Action' + + PaginatedActions: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Action' + meta: + type: object + properties: + next: + type: string + description: Cursor for the next page of results + nullable: true diff --git a/docs/schemas/allowSpends.yml b/docs/schemas/allowSpends.yml new file mode 100644 index 0000000..041177a --- /dev/null +++ b/docs/schemas/allowSpends.yml @@ -0,0 +1,160 @@ +components: + schemas: + AllowSpend: + type: object + properties: + currencyId: + type: string + description: The identifier of the currency + hash: + type: string + description: The hash of the allow spend transaction + ordinal: + type: string + description: The ordinal of the allow spend in the blockchain + format: bigint + amount: + type: string + description: The amount of tokens allowed to be spent + format: bigint + source: + type: string + description: The source address of the allow spend + destination: + type: string + description: The destination address of the allow spend + lastValidEpochProgress: + type: number + description: The last valid epoch progress for this allow spend + fee: + type: string + description: The fee associated with the allow spend transaction + format: bigint + snapshotHash: + type: string + description: The hash of the snapshot containing this allow spend + timestamp: + type: string + description: The timestamp when the allow spend was created + format: date-time + + AllowSpendResponse: + type: object + properties: + data: + $ref: '#/components/schemas/AllowSpend' + + PaginatedAllowSpends: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/AllowSpend' + meta: + type: object + properties: + next: + type: string + description: Cursor for the next page of results + nullable: true + + SpendTransaction: + type: object + properties: + currencyId: + type: string + description: The identifier of the currency + hash: + type: string + description: The hash of the spend transaction + amount: + type: string + description: The amount of tokens spent + format: bigint + source: + type: string + description: The source address of the spend transaction + destination: + type: string + description: The destination address of the spend transaction + allowSpendRef: + type: string + description: Reference to the original allow spend transaction hash + snapshotHash: + type: string + description: The hash of the snapshot containing this spend transaction + timestamp: + type: string + description: The timestamp when the spend transaction was created + format: date-time + + SpendTransactionResponse: + type: object + properties: + data: + $ref: '#/components/schemas/SpendTransaction' + + PaginatedSpendTransactions: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/SpendTransaction' + meta: + type: object + properties: + next: + type: string + description: Cursor for the next page of results + nullable: true + + AllowSpendExpiration: + type: object + properties: + currencyId: + type: string + description: The identifier of the currency + hash: + type: string + description: The hash of the expired allow spend transaction + amount: + type: string + description: The amount of tokens that were allowed to be spent + format: bigint + source: + type: string + description: The source address of the allow spend + allowSpendRef: + type: string + description: Reference to the original allow spend transaction hash + snapshotHash: + type: string + description: The hash of the snapshot containing this expiration + timestamp: + type: string + description: The timestamp when the allow spend expired + format: date-time + + AllowSpendExpirationResponse: + type: object + properties: + data: + $ref: '#/components/schemas/AllowSpendExpiration' + + PaginatedAllowSpendExpirations: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/AllowSpendExpiration' + meta: + type: object + properties: + next: + type: string + description: Cursor for the next page of results + nullable: true + diff --git a/docs/schemas/dag.yml b/docs/schemas/dag.yml new file mode 100644 index 0000000..1e5f97c --- /dev/null +++ b/docs/schemas/dag.yml @@ -0,0 +1,214 @@ +components: + schemas: + GlobalSnapshot: + type: object + properties: + hash: + type: string + description: The hash of the global snapshot + ordinal: + type: integer + description: The ordinal of the snapshot in the blockchain + height: + type: integer + description: The height of the snapshot + subHeight: + type: integer + description: The sub-height of the snapshot + lastSnapshotHash: + type: string + description: The hash of the previous snapshot + blocks: + type: array + items: + type: string + description: Array of block hashes included in this snapshot + epochProgress: + type: number + description: The progress through the current epoch + timestamp: + type: string + description: The timestamp when the snapshot was created + format: date-time + + GlobalSnapshotResponse: + type: object + properties: + data: + $ref: '#/components/schemas/GlobalSnapshot' + + PaginatedGlobalSnapshots: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/GlobalSnapshot' + meta: + type: object + properties: + next: + type: string + description: Cursor for the next page of results + nullable: true + + Reward: + type: object + properties: + destination: + type: string + description: The destination address for the reward + amount: + type: string + description: The amount of the reward + format: bigint + + PaginatedRewards: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Reward' + meta: + type: object + properties: + next: + type: string + description: Cursor for the next page of results + nullable: true + + Transaction: + type: object + properties: + hash: + type: string + description: The hash of the transaction + amount: + type: string + description: The amount of tokens transferred + format: bigint + source: + type: string + description: The source address of the transaction + destination: + type: string + description: The destination address of the transaction + fee: + type: string + description: The fee for the transaction + format: bigint + parent: + type: object + properties: + hash: + type: string + description: The hash of the parent transaction + nullable: true + ordinal: + type: integer + description: The ordinal of the parent transaction + nullable: true + salt: + type: string + description: The salt used in the transaction + blockHash: + type: string + description: The hash of the block containing this transaction + snapshotHash: + type: string + description: The hash of the snapshot containing this transaction + snapshotOrdinal: + type: integer + description: The ordinal of the snapshot containing this transaction + timestamp: + type: string + description: The timestamp when the transaction was created + format: date-time + + TransactionResponse: + type: object + properties: + data: + $ref: '#/components/schemas/Transaction' + + PaginatedTransactions: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Transaction' + meta: + type: object + properties: + next: + type: string + description: Cursor for the next page of results + nullable: true + + BlockParent: + type: object + properties: + hash: + type: string + description: The hash of the parent block + height: + type: integer + description: The height of the parent block + + Block: + type: object + properties: + hash: + type: string + description: The hash of the block + height: + type: integer + description: The height of the block in the blockchain + parents: + type: array + items: + $ref: '#/components/schemas/BlockParent' + description: Parent blocks + timestamp: + type: string + description: The timestamp when the block was created + format: date-time + transactions: + type: array + items: + type: string + description: Array of transaction hashes included in this block + snapshotHash: + type: string + description: The hash of the snapshot containing this block + snapshotOrdinal: + type: integer + description: The ordinal of the snapshot containing this block + + BlockResponse: + type: object + properties: + data: + $ref: '#/components/schemas/Block' + + Balance: + type: object + properties: + ordinal: + type: integer + description: The ordinal of the snapshot for this balance + balance: + type: string + description: The balance amount + format: bigint + address: + type: string + description: The address for this balance + + BalanceResponse: + type: object + properties: + data: + $ref: '#/components/schemas/Balance' diff --git a/docs/schemas/metagraph.yml b/docs/schemas/metagraph.yml new file mode 100644 index 0000000..7dbde1c --- /dev/null +++ b/docs/schemas/metagraph.yml @@ -0,0 +1,201 @@ +components: + schemas: + Metagraph: + type: object + properties: + id: + type: string + description: The unique identifier of the metagraph + timestamp: + type: string + description: The timestamp when the metagraph was created + format: date-time + + PaginatedMetagraphs: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Metagraph' + meta: + type: object + properties: + next: + type: string + description: Cursor for the next page of results + nullable: true + + MetagraphSnapshot: + type: object + properties: + hash: + type: string + description: The hash of the metagraph snapshot + ordinal: + type: integer + description: The ordinal of the snapshot in the metagraph + height: + type: integer + description: The height of the snapshot + subHeight: + type: integer + description: The sub-height of the snapshot + lastSnapshotHash: + type: string + description: The hash of the previous metagraph snapshot + blocks: + type: array + items: + type: string + description: Array of block hashes included in this metagraph snapshot + epochProgress: + type: number + description: The progress through the current epoch + fee: + type: string + description: The fee for this metagraph snapshot + format: bigint + stakingAddress: + type: string + description: The staking address for this metagraph snapshot + nullable: true + ownerAddress: + type: string + description: The owner address for this metagraph snapshot + nullable: true + sizeInKb: + type: number + description: The size of the snapshot in kilobytes + timestamp: + type: string + description: The timestamp when the metagraph snapshot was created + format: date-time + + MetagraphBlock: + type: object + properties: + hash: + type: string + description: The hash of the metagraph block + height: + type: integer + description: The height of the block in the metagraph + parents: + type: array + items: + $ref: './dag.yml#/components/schemas/BlockParent' + description: Parent blocks + timestamp: + type: string + description: The timestamp when the block was created + format: date-time + transactions: + type: array + items: + type: string + description: Array of transaction hashes included in this block + snapshotHash: + type: string + description: The hash of the metagraph snapshot containing this block + snapshotOrdinal: + type: integer + description: The ordinal of the metagraph snapshot containing this block + + MetagraphBlockResponse: + type: object + properties: + data: + $ref: '#/components/schemas/MetagraphBlock' + + MetagraphSnapshotResponse: + type: object + properties: + data: + $ref: '#/components/schemas/MetagraphSnapshot' + + PaginatedMetagraphSnapshots: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/MetagraphSnapshot' + meta: + type: object + properties: + next: + type: string + description: Cursor for the next page of results + nullable: true + + + + MetagraphTransaction: + type: object + properties: + hash: + type: string + description: The hash of the metagraph transaction + amount: + type: string + description: The amount of tokens transferred + format: bigint + source: + type: string + description: The source address of the transaction + destination: + type: string + description: The destination address of the transaction + fee: + type: string + description: The fee for the transaction + format: bigint + parent: + type: object + properties: + hash: + type: string + description: The hash of the parent transaction + nullable: true + ordinal: + type: integer + description: The ordinal of the parent transaction + nullable: true + salt: + type: string + description: The salt used in the transaction + blockHash: + type: string + description: The hash of the metagraph block containing this transaction + snapshotHash: + type: string + description: The hash of the metagraph snapshot containing this transaction + snapshotOrdinal: + type: integer + description: The ordinal of the metagraph snapshot containing this transaction + timestamp: + type: string + description: The timestamp when the transaction was created + format: date-time + + MetagraphTransactionResponse: + type: object + properties: + data: + $ref: '#/components/schemas/MetagraphTransaction' + + PaginatedMetagraphTransactions: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/MetagraphTransaction' + meta: + type: object + properties: + next: + type: string + description: Cursor for the next page of results + nullable: true diff --git a/docs/schemas/params.yml b/docs/schemas/params.yml new file mode 100644 index 0000000..80d0224 --- /dev/null +++ b/docs/schemas/params.yml @@ -0,0 +1,56 @@ +components: + parameters: + limitParam: + name: limit + in: query + description: Maximum number of records to return (1-100) + schema: + type: integer + minimum: 1 + maximum: 100 + default: 10 + + nextParam: + name: next + in: query + description: Cursor for the next page of results (Base64 encoded) + schema: + type: string + + searchAfterParam: + name: search_after + in: query + description: Search after cursor (exclusive with search_before and next) + schema: + type: string + + searchBeforeParam: + name: search_before + in: query + description: Search before cursor (exclusive with search_after and next) + schema: + type: string + + hashOrOrdinalGlobalParam: + name: hash_or_ordinal + in: path + description: Hash, ordinal, or the keyword 'latest' of the global snapshot + required: true + schema: + oneOf: + - type: string + description: Hash of the global snapshot or 'latest' + - type: integer + description: Ordinal of the global snapshot + + hashOrOrdinalMetagraphParam: + name: hash_or_ordinal + in: path + description: Hash, ordinal, or the keyword 'latest' of the metagraph snapshot + required: true + schema: + oneOf: + - type: string + description: Hash of the metagraph snapshot or 'latest' + - type: integer + description: Ordinal of the metagraph snapshot diff --git a/docs/schemas/responses.yml b/docs/schemas/responses.yml new file mode 100644 index 0000000..c1f4efb --- /dev/null +++ b/docs/schemas/responses.yml @@ -0,0 +1,43 @@ +components: + responses: + BadRequest: + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + example: + message: 'Missing parameter: hash' + errors: [''] + + NotFound: + description: Resource not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + example: + message: 'Not found' + errors: [''] + + InternalServerError: + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + example: + message: 'Internal Server Error' + errors: ['An error occurred while processing your request'] + schemas: + ErrorResponse: + type: object + properties: + message: + type: string + description: Error message + errors: + type: array + items: + type: string + description: Array of error details \ No newline at end of file diff --git a/docs/schemas/tokenLocks.yml b/docs/schemas/tokenLocks.yml new file mode 100644 index 0000000..281227c --- /dev/null +++ b/docs/schemas/tokenLocks.yml @@ -0,0 +1,107 @@ +components: + schemas: + TokenLock: + type: object + properties: + currencyId: + type: string + description: The identifier of the currency + hash: + type: string + description: The hash of the token lock transaction + amount: + type: string + description: The amount of tokens locked + format: bigint + source: + type: string + description: The source address of the token lock + unlockEpoch: + type: integer + description: The epoch when tokens can be unlocked + nullable: true + parentHash: + type: string + description: The hash of the parent transaction + nullable: true + ordinal: + type: string + description: The ordinal of the token lock in the blockchain + format: bigint + timestamp: + type: string + description: The timestamp when the token lock was created + format: date-time + unlockedAtOrdinal: + type: integer + description: The ordinal of the global snapshot where this token was unlocked + nullable: true + + TokenLockResponse: + type: object + properties: + data: + $ref: '#/components/schemas/TokenLock' + + PaginatedTokenLocks: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/TokenLock' + meta: + type: object + properties: + next: + type: string + description: Cursor for the next page of results + nullable: true + + TokenUnlock: + type: object + properties: + currencyId: + type: string + description: The identifier of the currency + hash: + type: string + description: The hash of the token unlock transaction + amount: + type: string + description: The amount of tokens unlocked + format: bigint + source: + type: string + description: The source address of the token unlock + tokenLockRef: + type: string + description: Reference to the original token lock transaction hash + timestamp: + type: string + description: The timestamp when the token unlock was created + format: date-time + globalSnapshotOrdinal: + type: integer + description: The ordinal of the global snapshot containing this unlock + + TokenUnlockResponse: + type: object + properties: + data: + $ref: '#/components/schemas/TokenUnlock' + + PaginatedTokenUnlocks: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/TokenUnlock' + meta: + type: object + properties: + next: + type: string + description: Cursor for the next page of results + nullable: true From 091b5ea7dd9c66466db1157df0f3eb98faed7556 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Tue, 29 Apr 2025 12:56:44 -0300 Subject: [PATCH 30/63] token lock tests --- .../20250402214034_initial/migration.sql | 9 +- prisma/migrations/20250429/migration.sql | 2 + prisma/schema.prisma | 9 +- prisma/seed.ts | 214 +++++++-- src/handlers/tokenLocksHandler.ts | 1 - .../handlers/delegatedStakingHandler.test.ts | 27 +- tests/handlers/metagraphHandler.test.ts | 79 +--- tests/handlers/tokenLocksHandler.test.ts | 420 ++++++++++++++++++ 8 files changed, 601 insertions(+), 160 deletions(-) create mode 100644 prisma/migrations/20250429/migration.sql create mode 100644 tests/handlers/tokenLocksHandler.test.ts diff --git a/prisma/migrations/20250402214034_initial/migration.sql b/prisma/migrations/20250402214034_initial/migration.sql index 27ca49d..4a12d8b 100644 --- a/prisma/migrations/20250402214034_initial/migration.sql +++ b/prisma/migrations/20250402214034_initial/migration.sql @@ -380,12 +380,11 @@ insert -- DROP TABLE dag_token_unlocks; CREATE TABLE dag_token_unlocks ( - lock_reference_ordinal int8 NOT NULL, lock_reference_hash varchar NOT NULL, snapshot_hash varchar NOT NULL, parent_hash varchar NOT NULL, - CONSTRAINT dag_token_unlocks_pk PRIMARY KEY (lock_reference_ordinal, lock_reference_hash), - CONSTRAINT dag_token_unlocks_token_locks_fk FOREIGN KEY (lock_reference_hash,lock_reference_ordinal) REFERENCES dag_token_locks(hash,ordinal) ON DELETE CASCADE, + CONSTRAINT dag_token_unlocks_pk PRIMARY KEY (lock_reference_hash), + CONSTRAINT dag_token_unlocks_token_locks_fk FOREIGN KEY (lock_reference_hash) REFERENCES dag_token_locks(hash,ordinal) ON DELETE CASCADE, CONSTRAINT ddag_token_unlocks_address_fk FOREIGN KEY (source_addr) REFERENCES addresses(address) ON DELETE CASCADE ) INHERITS (public.abstract_transactions); @@ -563,15 +562,13 @@ insert CREATE TABLE metagraph_token_unlocks ( metagraph_id varchar NOT NULL, - lock_reference_ordinal int8 NOT NULL, lock_reference_hash varchar NOT NULL, snapshot_hash varchar NOT NULL, parent_hash varchar NOT NULL, - CONSTRAINT metagraph_token_unlocks_pk PRIMARY KEY (lock_reference_ordinal, lock_reference_hash), + CONSTRAINT metagraph_token_unlocks_pk PRIMARY KEY (lock_reference_hash), CONSTRAINT address_fk FOREIGN KEY (source_addr) REFERENCES addresses(address) ON DELETE CASCADE, CONSTRAINT metagraph_id_fk FOREIGN KEY (metagraph_id) REFERENCES metagraphs(id) ON DELETE CASCADE, CONSTRAINT metagraph_token_unlocks_token_locks_fk FOREIGN KEY (metagraph_id,lock_reference_hash) REFERENCES metagraph_token_locks(metagraph_id,hash) ON DELETE CASCADE, - CONSTRAINT metagraph_token_unlocks_token_locks_ordinal_fk FOREIGN KEY (metagraph_id,lock_reference_ordinal) REFERENCES metagraph_token_locks(metagraph_id,ordinal) ON DELETE CASCADE ) INHERITS (public.abstract_transactions); diff --git a/prisma/migrations/20250429/migration.sql b/prisma/migrations/20250429/migration.sql new file mode 100644 index 0000000..a1ded4c --- /dev/null +++ b/prisma/migrations/20250429/migration.sql @@ -0,0 +1,2 @@ +ALTER TABLE public.dag_token_unlocks ALTER COLUMN parent_hash DROP NOT NULL; +ALTER TABLE public.dag_token_unlocks DROP COLUMN lock_reference_ordinal; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index dcb19b6..492bb8b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -194,7 +194,6 @@ model dag_token_locks { ordinal BigInt unlock_epoch BigInt? round_id String @db.Uuid - parent_hash String? @db.VarChar snapshot_hash String @db.VarChar addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_locks_source_addr_fk") global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_lock_global_snapshot_fk") @@ -211,10 +210,8 @@ model dag_token_unlocks { amount BigInt created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - lock_reference_ordinal BigInt lock_reference_hash String @db.VarChar snapshot_hash String @db.VarChar - parent_hash String @db.VarChar global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_unlock_global_snapshot_fk") dag_token_lock dag_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_unlocks_token_locks_fk") addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "ddag_token_unlocks_address_fk") @@ -438,14 +435,12 @@ model metagraph_token_locks { ordinal BigInt unlock_epoch BigInt round_id String @db.VarChar - parent_hash String? @db.VarChar snapshot_hash String @db.VarChar metagraph metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_locks_source_addr_fk") abstract_transactions_view abstract_transactions_view[] - metagraph_snapshot String? @db.VarChar metagraph_token_unlock metagraph_token_unlocks? - metagraph_snapshots metagraph_snapshots? @relation(fields: [metagraph_snapshotsMetagraph_id, metagraph_snapshotsHash], references: [metagraph_id, hash]) + metagraph_snapshot metagraph_snapshots? @relation(fields: [metagraph_snapshotsMetagraph_id, metagraph_snapshotsHash], references: [metagraph_id, hash]) metagraph_snapshotsMetagraph_id String? @db.VarChar metagraph_snapshotsHash String? @db.VarChar @@ -461,10 +456,8 @@ model metagraph_token_unlocks { created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) metagraph_id String @db.VarChar - lock_reference_ordinal BigInt lock_reference_hash String @db.VarChar snapshot_hash String @db.VarChar - parent_hash String @db.VarChar addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "address_fk") metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") abstract_transactions_view abstract_transactions_view[] diff --git a/prisma/seed.ts b/prisma/seed.ts index 4fed321..5aa5eb9 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -11,6 +11,7 @@ import { metagraph_balance_changes, metagraphs, } from "@prisma/client"; +import { randomUUID } from "crypto"; export const prisma = new PrismaClient(); @@ -146,51 +147,76 @@ export const data_metagraphs = [ created_at: new Date("2025-04-02T00:00:02Z"), updated_at: new Date(), }, -]; - -export const data_metagraph_snapshots = [ { - metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", - global_snapshot_hash: data_global_snapshots[0].hash, - ordinal: 1n, - hash: "1c1e16746af43bc1f93aadaf006cd981852a706e74f1d24b66a7e5e9fa4188e5", - height: 1n, - subheight: 1, - owner_address: null, - staking_address: null, - epoch_progress: 1n, - size: 1n, - last_snapshot_hash: - "0000000000000000000000000000000000000000000000000000000000000000", - fee: 1n, - version: "0.0.1", - created_at: new Date("2025-04-02T00:00:02Z"), + id: "DAG6666666666666666666666666666666666666", + created_at: new Date("2025-04-02T00:01:02Z"), updated_at: new Date(), }, - { - global_snapshot_hash: data_global_snapshots[1].hash, - metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", - ordinal: 2n, - hash: "9bf40b2d2e355401bbca7a7924880ab799ffa91e95d1b93f3298b549758aac64", +]; - height: 2n, - subheight: 2, - owner_address: null, - staking_address: null, - epoch_progress: 1n, - size: 1n, - last_snapshot_hash: - "1c1e16746af43bc1f93aadaf006cd981852a706e74f1d24b66a7e5e9fa4188e5", - fee: 1n, - version: "0.0.1", - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, +export const data_metagraph_snapshots = [ + { + metagraph_id: data_metagraphs[0].id, + global_snapshot_hash: data_global_snapshots[0].hash, + ordinal: 1n, + hash: "1c1e16746af43bc1f93aadaf006cd981852a706e74f1d24b66a7e5e9fa4188e5", + height: 1n, + subheight: 1, + owner_address: null, + staking_address: null, + epoch_progress: 1n, + size: 1n, + last_snapshot_hash: + "0000000000000000000000000000000000000000000000000000000000000000", + fee: 1n, + version: "0.0.1", + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + metagraph_id: data_metagraphs[0].id, + global_snapshot_hash: data_global_snapshots[1].hash, + ordinal: 2n, + hash: "9bf40b2d2e355401bbca7a7924880ab799ffa91e95d1b93f3298b549758aac64", + + height: 2n, + subheight: 2, + owner_address: null, + staking_address: null, + epoch_progress: 1n, + size: 1n, + last_snapshot_hash: + "1c1e16746af43bc1f93aadaf006cd981852a706e74f1d24b66a7e5e9fa4188e5", + fee: 1n, + version: "0.0.1", + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + metagraph_id: data_metagraphs[1].id, + global_snapshot_hash: data_global_snapshots[1].hash, + ordinal: 1n, + hash: "9bf40b2d2e3123123123123123123123123123123123123123123549758aac64", + + height: 3n, + subheight: 2, + owner_address: null, + staking_address: null, + epoch_progress: 1n, + size: 1n, + last_snapshot_hash: + "9bf40b2d2e355401bbca7a7924880ab799ffa91e95d1b93f3298b549758aac64", + fee: 1n, + version: "0.0.1", + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + ]; export const data_metagraph_blocks = [ { - metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", + metagraph_id: data_metagraphs[0].id, hash: "33374138dd6f5f9846261d541dab33dadcbae8c9f5a39026336a34a3e2aafb93", height: 12n, metagraph_snapshot_hash: data_metagraph_snapshots[0].hash, @@ -198,7 +224,7 @@ export const data_metagraph_blocks = [ updated_at: new Date(), }, { - metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", + metagraph_id: data_metagraphs[0].id, hash: "3d5a9616d65a6d98fe629f1a056489df9245a40d1e8589ed9d655c6fcb3ee361", height: 14n, metagraph_snapshot_hash: data_metagraph_snapshots[1].hash, @@ -208,7 +234,7 @@ export const data_metagraph_blocks = [ ]; export const data_metagraph_transactions = [ { - metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", + metagraph_id: data_metagraphs[0].id, hash: "39c9909d3b00552beaa5487c38110267675ab212b3b97654fc5ca9917cb7e72b", source_addr: data_addresses[0].address, destination_addr: data_addresses[1].address, @@ -224,7 +250,7 @@ export const data_metagraph_transactions = [ updated_at: new Date(), }, { - metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", + metagraph_id: data_metagraphs[0].id, hash: "aa50e85a32e3e84c9b49880e103ee240f572a6febd255d97db1406f6c936af6f", source_addr: data_addresses[1].address, destination_addr: data_addresses[0].address, @@ -242,24 +268,126 @@ export const data_metagraph_transactions = [ ]; export const data_metagraph_balance_changes = [ { - metagraph_id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", + metagraph_id: data_metagraphs[0].id, metagraph_snapshot_hash: data_metagraph_snapshots[0].hash, snapshot_ordinal: data_metagraph_snapshots[0].ordinal, address: data_metagraph_transactions[0].destination_addr, balance: data_metagraph_transactions[0].amount, - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), created_at: new Date("2025-04-02T00:10:01Z"), // set explicitly to avoid race condition updated_at: new Date("2025-04-02T00:10:01Z"), }, ]; +export const data_dag_token_locks = [ + { + hash: `token-lock-hash-001`, + source_addr: data_addresses[0].address, + amount: 1200n, + ordinal: 1n, + unlock_epoch: 100n, + round_id: randomUUID(), + snapshot_hash: data_global_snapshots[0].hash, + }, + { + hash: `token-lock-hash-002`, + source_addr: data_addresses[0].address, + amount: 1000n, + ordinal: 2n, + unlock_epoch: 10n, + round_id: randomUUID(), + snapshot_hash: data_global_snapshots[0].hash, + }, + { + hash: `token-lock-hash-003`, + source_addr: data_addresses[0].address, + amount: 1200n, + ordinal: 3n, + unlock_epoch: 10n, + round_id: randomUUID(), + snapshot_hash: data_global_snapshots[1].hash, + }, +]; + +// Test data for DAG token unlocks +export const data_dag_token_unlocks = [ + { + hash: 'token-unlock-hash-0010', + source_addr: data_addresses[0].address, + amount: 1000n, + lock_reference_hash: data_dag_token_locks[0].hash, + snapshot_hash: data_global_snapshots[1].hash, + } +]; + +// Test data for metagraph token locks +export const data_metagraph_token_locks = [ + { + hash: 'metagraph-token-lock-hash-0010', + metagraph_id: data_metagraphs[0].id, + source_addr: data_addresses[0].address, + amount: 3000n, + ordinal: 10n, + unlock_epoch: 15n, + round_id: randomUUID(), + snapshot_hash: data_metagraph_snapshots[0].hash, + }, + { + hash: 'metagraph-token-lock-hash-0020', + metagraph_id: data_metagraphs[0].id, + source_addr: data_addresses[0].address, + amount: 4000n, + ordinal: 20n, + unlock_epoch: 25n, + round_id: randomUUID(), + snapshot_hash: data_metagraph_snapshots[1].hash, + } +]; + +// Test data for metagraph token unlocks +export const data_metagraph_token_unlocks = [ + { + hash: 'metagraph-token-unlock-hash-001', + metagraph_id: data_metagraphs[0].id, + source_addr: data_addresses[0].address, + amount: 3000n, + lock_reference_hash: data_metagraph_token_locks[0].hash, + snapshot_hash: data_metagraph_snapshots[1].hash, + } +]; + + export async function seed() { await prisma.addresses.createManyAndReturn({ data: data_addresses }); await prisma.global_snapshots.createManyAndReturn({ data: data_global_snapshots, }); + + await prisma.metagraphs.createManyAndReturn({ data: data_metagraphs }); + + await prisma.metagraph_snapshots.createManyAndReturn({ + data: data_metagraph_snapshots, + }); + + // Create DAG token locks + await prisma.dag_token_locks.createManyAndReturn({ + data: data_dag_token_locks, + }); + + // Create DAG token unlocks + await prisma.dag_token_unlocks.createManyAndReturn({ + data: data_dag_token_unlocks, + }); + + // Create metagraph token locks + await prisma.metagraph_token_locks.createManyAndReturn({ + data: data_metagraph_token_locks, + }); + + // Create metagraph token unlocks + await prisma.metagraph_token_unlocks.createManyAndReturn({ + data: data_metagraph_token_unlocks, + }); } export async function resetDatabase() { diff --git a/src/handlers/tokenLocksHandler.ts b/src/handlers/tokenLocksHandler.ts index c9bae09..5a40a43 100644 --- a/src/handlers/tokenLocksHandler.ts +++ b/src/handlers/tokenLocksHandler.ts @@ -321,7 +321,6 @@ export const metagraphSnapshotTokenLocks = async ( const { metagraph_id, hash_or_ordinal } = event.pathParameters || {}; const filter = extractHashOrdinal(hash_or_ordinal); - return paginatedQuery( extractPagination(event), toCreatedAtOrdinalCursor, diff --git a/tests/handlers/delegatedStakingHandler.test.ts b/tests/handlers/delegatedStakingHandler.test.ts index 0e618d3..c2885ab 100644 --- a/tests/handlers/delegatedStakingHandler.test.ts +++ b/tests/handlers/delegatedStakingHandler.test.ts @@ -16,34 +16,12 @@ import { import { data_addresses, + data_dag_token_locks, data_global_snapshots, prisma, } from "../../prisma/seed"; import { randomUUID } from "crypto"; -const data_dag_token_locks = [ - { - hash: `token-lock-hash-002`, - source_addr: data_addresses[0].address, - amount: 1000n, - ordinal: 2n, - unlock_epoch: 10n, - round_id: randomUUID(), - parent_hash: "token-lock-hash-001", - snapshot_hash: data_global_snapshots[0].hash, - }, - { - hash: `token-lock-hash-003`, - source_addr: data_addresses[0].address, - amount: 1200n, - ordinal: 3n, - unlock_epoch: 10n, - round_id: randomUUID(), - parent_hash: "token-lock-hash-002", - snapshot_hash: data_global_snapshots[1].hash, - }, -]; - const data_delegate_stake_create_events = [ { hash: "stake-event-hash-001", @@ -110,9 +88,6 @@ const data_delegate_stake_rewards = [ }, ]; const seedData = async () => { - await prisma.dag_token_locks.createManyAndReturn({ - data: data_dag_token_locks, - }); await prisma.delegate_stake_create_events.createManyAndReturn({ data: data_delegate_stake_create_events, diff --git a/tests/handlers/metagraphHandler.test.ts b/tests/handlers/metagraphHandler.test.ts index f7d5918..0fb7cdb 100644 --- a/tests/handlers/metagraphHandler.test.ts +++ b/tests/handlers/metagraphHandler.test.ts @@ -8,80 +8,11 @@ import { import { data_addresses, data_global_snapshots, + data_metagraph_snapshots, + data_metagraphs, prisma, } from "../../prisma/seed"; -const data_metagraphs = [ - { - id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, - { - id: "DAG6666666666666666666666666666666666666", - created_at: new Date("2025-04-02T00:01:02Z"), - updated_at: new Date(), - }, -]; - -const data_metagraph_snapshots = [ - { - metagraph_id: data_metagraphs[0].id, - global_snapshot_hash: data_global_snapshots[0].hash, - ordinal: 1n, - hash: "1c1e16746af43bc1f93aadaf006cd981852a706e74f1d24b66a7e5e9fa4188e5", - height: 1n, - subheight: 1, - owner_address: null, - staking_address: null, - epoch_progress: 1n, - size: 1n, - last_snapshot_hash: - "0000000000000000000000000000000000000000000000000000000000000000", - fee: 1n, - version: "0.0.1", - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, - { - metagraph_id: data_metagraphs[0].id, - global_snapshot_hash: data_global_snapshots[1].hash, - ordinal: 2n, - hash: "9bf40b2d2e355401bbca7a7924880ab799ffa91e95d1b93f3298b549758aac64", - - height: 2n, - subheight: 2, - owner_address: null, - staking_address: null, - epoch_progress: 1n, - size: 1n, - last_snapshot_hash: - "1c1e16746af43bc1f93aadaf006cd981852a706e74f1d24b66a7e5e9fa4188e5", - fee: 1n, - version: "0.0.1", - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, - { - metagraph_id: data_metagraphs[1].id, - global_snapshot_hash: data_global_snapshots[1].hash, - ordinal: 1n, - hash: "9bf40b2d2e3123123123123123123123123123123123123123123549758aac64", - - height: 3n, - subheight: 2, - owner_address: null, - staking_address: null, - epoch_progress: 1n, - size: 1n, - last_snapshot_hash: - "9bf40b2d2e355401bbca7a7924880ab799ffa91e95d1b93f3298b549758aac64", - fee: 1n, - version: "0.0.1", - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, -]; const data_metagraph_blocks = [ { @@ -190,12 +121,8 @@ const data_metagraph_balance_changes = [ ]; const seedData = async () => { - await prisma.metagraphs.createManyAndReturn({ data: data_metagraphs }); - await prisma.metagraph_snapshots.createManyAndReturn({ - data: data_metagraph_snapshots, - }); - + await prisma.metagraph_blocks.createManyAndReturn({ data: data_metagraph_blocks, }); diff --git a/tests/handlers/tokenLocksHandler.test.ts b/tests/handlers/tokenLocksHandler.test.ts new file mode 100644 index 0000000..0ddf787 --- /dev/null +++ b/tests/handlers/tokenLocksHandler.test.ts @@ -0,0 +1,420 @@ +import { APIGatewayProxyResult } from "aws-lambda"; +import * as tokenLocksHandler from "../../src/handlers/tokenLocksHandler"; +import { + createAPIGatewayEvent, + validateResponseStructure, + validatePaginatedResponse, +} from "../testUtils"; +import { + data_addresses, + data_global_snapshots, + data_metagraph_snapshots, + data_metagraphs, + data_dag_token_locks, + data_dag_token_unlocks, + data_metagraph_token_locks, + data_metagraph_token_unlocks, + prisma, +} from "../../prisma/seed"; +import { randomUUID } from "crypto"; + + + + + +// Validation functions +const validateDagTokenLock = (lock) => { + const match = data_dag_token_locks.find(d => d.hash === lock.hash); + expect(match).toBeDefined(); + if (!match) return; + + expect(lock.hash).toBe(match.hash); + expect(lock.source).toBe(match.source_addr); + expect(Number(lock.amount)).toBe(Number(match.amount)); + expect(Number(lock.ordinal)).toBe(Number(match.ordinal)); + expect(Number(lock.unlockEpoch)).toBe(Number(match.unlock_epoch)); + expect(lock.timestamp).toBeDefined(); + expect(lock.unlockedAtOrdinal).toBeDefined(); // This could be null if not unlocked +}; + +const validateDagTokenUnlock = (unlock) => { + const match = data_dag_token_unlocks.find(d => d.hash === unlock.hash); + expect(match).toBeDefined(); + if (!match) return; + + expect(unlock.hash).toBe(match.hash); + expect(unlock.source).toBe(match.source_addr); + expect(Number(unlock.amount)).toBe(Number(match.amount)); + expect(unlock.tokenLockRef).toBe(match.lock_reference_hash); + expect(unlock.globalSnapshotOrdinal).toBeDefined(); + expect(unlock.timestamp).toBeDefined(); +}; + +const validateMetagraphTokenLock = (lock) => { + const match = data_metagraph_token_locks.find(d => d.hash === lock.hash); + expect(match).toBeDefined(); + if (!match) return; + + expect(lock.hash).toBe(match.hash); + expect(lock.source).toBe(match.source_addr); + expect(Number(lock.amount)).toBe(Number(match.amount)); + expect(Number(lock.ordinal)).toBe(Number(match.ordinal)); + expect(Number(lock.unlockEpoch)).toBe(Number(match.unlock_epoch)); + expect(lock.timestamp).toBeDefined(); + expect(lock.unlockedAtOrdinal).toBeDefined(); // This could be null if not unlocked +}; + +const validateMetagraphTokenUnlock = (unlock) => { + const match = data_metagraph_token_unlocks.find(d => d.hash === unlock.hash); + expect(match).toBeDefined(); + if (!match) return; + + expect(unlock.hash).toBe(match.hash); + expect(unlock.source).toBe(match.source_addr); + expect(Number(unlock.amount)).toBe(Number(match.amount)); + expect(unlock.tokenLockRef).toBe(match.lock_reference_hash); + expect(unlock.metagraphSnapshotOrdinal).toBeDefined(); + expect(unlock.timestamp).toBeDefined(); +}; + + +describe("Token Locks Handler Integration Tests", () => { + // DAG Token Locks Tests + describe("tokenLocks", () => { + it("should return a list of token locks", async () => { + const event = createAPIGatewayEvent({}, { limit: "10" }); + const response: APIGatewayProxyResult = await tokenLocksHandler.tokenLocks(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + expect(body.data.length).toBe(data_dag_token_locks.length); + validateDagTokenLock(body.data[0]); + }); + + it("should handle pagination correctly", async () => { + const event = createAPIGatewayEvent({}, { limit: "1" }); + const response: APIGatewayProxyResult = await tokenLocksHandler.tokenLocks(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + expect(body.data.length).toBe(1); + expect(body.meta.next).toBeDefined(); + + // Try getting the next page + const nextEvent = createAPIGatewayEvent({}, { limit: "1", next: body.meta.next }); + const nextResponse = await tokenLocksHandler.tokenLocks(nextEvent); + + expect(nextResponse.statusCode).toBe(200); + const nextBody = validatePaginatedResponse(nextResponse); + + expect(nextBody.data.length).toBe(1); + expect(nextBody.data[0].hash).not.toBe(body.data[0].hash); + }); + + it("should filter active token locks", async () => { + const event = createAPIGatewayEvent({}, { active: "true" }); + const response: APIGatewayProxyResult = await tokenLocksHandler.tokenLocks(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + // Only the second and third token lock should be active (not unlocked) + expect(body.data.length).toBe(2); + expect(body.data[1].hash).toBe(data_dag_token_locks[1].hash); + expect(body.data[0].hash).toBe(data_dag_token_locks[2].hash); + }); + }); + + describe("tokenLock", () => { + it("should return a specific token lock by hash", async () => { + const hash = data_dag_token_locks[0].hash; + const event = createAPIGatewayEvent({ hash }); + + const response: APIGatewayProxyResult = await tokenLocksHandler.tokenLock(event); + + expect(response.statusCode).toBe(200); + const body = validateResponseStructure(response)["data"]; + + validateDagTokenLock(body); + expect(body.hash).toBe(hash); + }); + + it("should return 404 for non-existent token lock", async () => { + const hash = "non-existent-hash"; + const event = createAPIGatewayEvent({ hash }); + + const response: APIGatewayProxyResult = await tokenLocksHandler.tokenLock(event); + + expect(response.statusCode).toBe(404); + }); + }); + + describe("globalSnapshotTokenLocks", () => { + it("should return token locks for a specific global snapshot by hash", async () => { + const hash_or_ordinal = data_global_snapshots[0].hash; + const event = createAPIGatewayEvent({ hash_or_ordinal }); + + const response: APIGatewayProxyResult = await tokenLocksHandler.globalSnapshotTokenLocks(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + expect(body.data.length).toBe(2); + expect(body.data[1].hash).toBe(data_dag_token_locks[0].hash); + expect(body.data[0].hash).toBe(data_dag_token_locks[1].hash); + }); + + it("should return token locks for a specific global snapshot by ordinal", async () => { + const hash_or_ordinal = data_global_snapshots[0].ordinal.toString(); + const event = createAPIGatewayEvent({ hash_or_ordinal }); + + const response: APIGatewayProxyResult = await tokenLocksHandler.globalSnapshotTokenLocks(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + expect(body.data.length).toBe(2); + expect(body.data[1].hash).toBe(data_dag_token_locks[0].hash); + expect(body.data[0].hash).toBe(data_dag_token_locks[1].hash); + }); + }); + + describe("addressTokenLocks", () => { + it("should return token locks for a specific address", async () => { + const address = data_addresses[0].address; + const event = createAPIGatewayEvent({ address }); + + const response: APIGatewayProxyResult = await tokenLocksHandler.addressTokenLocks(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + expect(body.data.length).toBe(data_dag_token_locks.length); + body.data.forEach(validateDagTokenLock); + }); + + it("should return empty array for address with no token locks", async () => { + const address = "non-existent-address"; + const event = createAPIGatewayEvent({ address }); + + const response: APIGatewayProxyResult = await tokenLocksHandler.addressTokenLocks(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + expect(body.data.length).toBe(0); + }); + }); + + // DAG Token Unlocks Tests + describe("tokenUnlocks", () => { + it("should return a list of token unlocks", async () => { + const event = createAPIGatewayEvent({}, { limit: "10" }); + const response: APIGatewayProxyResult = await tokenLocksHandler.tokenUnlocks(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + expect(body.data.length).toBe(data_dag_token_unlocks.length); + validateDagTokenUnlock(body.data[0]); + }); + }); + + describe("tokenUnlock", () => { + it("should return a specific token unlock by hash", async () => { + const hash = data_dag_token_unlocks[0].hash; + const event = createAPIGatewayEvent({ hash }); + + const response: APIGatewayProxyResult = await tokenLocksHandler.tokenUnlock(event); + + expect(response.statusCode).toBe(200); + const body = validateResponseStructure(response)["data"]; + + validateDagTokenUnlock(body); + expect(body.hash).toBe(hash); + }); + + it("should return 404 for non-existent token unlock", async () => { + const hash = "non-existent-hash"; + const event = createAPIGatewayEvent({ hash }); + + const response: APIGatewayProxyResult = await tokenLocksHandler.tokenUnlock(event); + + expect(response.statusCode).toBe(404); + }); + }); + + describe("globalSnapshotTokenUnlocks", () => { + it("should return token unlocks for a specific global snapshot", async () => { + const hash_or_ordinal = data_global_snapshots[1].hash; + const event = createAPIGatewayEvent({ hash_or_ordinal }); + + const response: APIGatewayProxyResult = await tokenLocksHandler.globalSnapshotTokenUnlocks(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + expect(body.data.length).toBe(1); + expect(body.data[0].hash).toBe(data_dag_token_unlocks[0].hash); + }); + }); + + describe("addressTokenUnlocks", () => { + it("should return token unlocks for a specific address", async () => { + const address = data_addresses[0].address; + const event = createAPIGatewayEvent({ address }); + + const response: APIGatewayProxyResult = await tokenLocksHandler.addressTokenUnlocks(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + expect(body.data.length).toBe(data_dag_token_unlocks.length); + validateDagTokenUnlock(body.data[0]); + }); + }); + + // Metagraph Token Locks Tests + describe("metagraphTokenLocks", () => { + it("should return token locks for a specific metagraph", async () => { + const metagraph_id = data_metagraphs[0].id; + const event = createAPIGatewayEvent({ metagraph_id }); + + const response: APIGatewayProxyResult = await tokenLocksHandler.metagraphTokenLocks(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + expect(body.data.length).toBe(data_metagraph_token_locks.length); + validateMetagraphTokenLock(body.data[0]); + }); + + it("should filter active metagraph token locks", async () => { + const metagraph_id = data_metagraphs[0].id; + const event = createAPIGatewayEvent({ metagraph_id }, { active: "true" }); + + const response: APIGatewayProxyResult = await tokenLocksHandler.metagraphTokenLocks(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + // Only the second token lock should be active (not unlocked) + expect(body.data.length).toBe(1); + expect(body.data[0].hash).toBe(data_metagraph_token_locks[1].hash); + }); + }); + + describe("metagraphTokenLock", () => { + it("should return a specific metagraph token lock", async () => { + const metagraph_id = data_metagraphs[0].id; + const hash = data_metagraph_token_locks[0].hash; + const event = createAPIGatewayEvent({ metagraph_id, hash }); + + const response: APIGatewayProxyResult = await tokenLocksHandler.metagraphTokenLock(event); + + expect(response.statusCode).toBe(200); + const body = validateResponseStructure(response)["data"]; + + validateMetagraphTokenLock(body); + expect(body.hash).toBe(hash); + }); + }); + + describe("metagraphSnapshotTokenLocks", () => { + it("should return token locks for a specific metagraph snapshot", async () => { + const metagraph_id = data_metagraphs[0].id; + const hash_or_ordinal = data_metagraph_snapshots[0].hash; + const event = createAPIGatewayEvent({ metagraph_id, hash_or_ordinal }); + + const response: APIGatewayProxyResult = await tokenLocksHandler.metagraphSnapshotTokenLocks(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + expect(body.data.length).toBe(1); + expect(body.data[0].hash).toBe(data_metagraph_token_locks[0].hash); + }); + }); + + describe("metagraphAddressTokenLocks", () => { + it("should return token locks for a specific address in a metagraph", async () => { + const metagraph_id = data_metagraphs[0].id; + const address = data_addresses[0].address; + const event = createAPIGatewayEvent({ metagraph_id, address }); + + const response: APIGatewayProxyResult = await tokenLocksHandler.metagraphAddressTokenLocks(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + expect(body.data.length).toBe(data_metagraph_token_locks.length); + validateMetagraphTokenLock(body.data[0]); + }); + }); + + // Metagraph Token Unlocks Tests + describe("metagraphTokenUnlocks", () => { + it("should return token unlocks for a specific metagraph", async () => { + const metagraph_id = data_metagraphs[0].id; + const event = createAPIGatewayEvent({ metagraph_id }); + + const response: APIGatewayProxyResult = await tokenLocksHandler.metagraphTokenUnlocks(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + expect(body.data.length).toBe(data_metagraph_token_unlocks.length); + validateMetagraphTokenUnlock(body.data[0]); + }); + }); + + describe("metagraphTokenUnlock", () => { + it("should return a specific metagraph token unlock", async () => { + const metagraph_id = data_metagraphs[0].id; + const hash = data_metagraph_token_unlocks[0].hash; + const event = createAPIGatewayEvent({ metagraph_id, hash }); + + const response: APIGatewayProxyResult = await tokenLocksHandler.metagraphTokenUnlock(event); + + expect(response.statusCode).toBe(200); + const body = validateResponseStructure(response)["data"]; + + validateMetagraphTokenUnlock(body); + expect(body.hash).toBe(hash); + }); + }); + + describe("metagraphSnapshotTokenUnlocks", () => { + it("should return token unlocks for a specific metagraph snapshot", async () => { + const metagraph_id = data_metagraphs[0].id; + const hash_or_ordinal = data_metagraph_snapshots[1].hash; + const event = createAPIGatewayEvent({ metagraph_id, hash_or_ordinal }); + + const response: APIGatewayProxyResult = await tokenLocksHandler.metagraphSnapshotTokenUnlocks(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + expect(body.data.length).toBe(1); + expect(body.data[0].hash).toBe(data_metagraph_token_unlocks[0].hash); + }); + }); + + describe("metagraphAddressTokenUnlocks", () => { + it("should return token unlocks for a specific address in a metagraph", async () => { + const metagraph_id = data_metagraphs[0].id; + const address = data_addresses[0].address; + const event = createAPIGatewayEvent({ metagraph_id, address }); + + const response: APIGatewayProxyResult = await tokenLocksHandler.metagraphAddressTokenUnlocks(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + expect(body.data.length).toBe(data_metagraph_token_unlocks.length); + validateMetagraphTokenUnlock(body.data[0]); + }); + }); +}); From 91e7243d36f7dce4a1ade16fa4c99bd0bf538cc4 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Tue, 29 Apr 2025 13:23:32 -0300 Subject: [PATCH 31/63] add active filter to address endpoint --- src/handlers/tokenLocksHandler.ts | 3 ++- tests/handlers/tokenLocksHandler.test.ts | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/handlers/tokenLocksHandler.ts b/src/handlers/tokenLocksHandler.ts index 5a40a43..7933a61 100644 --- a/src/handlers/tokenLocksHandler.ts +++ b/src/handlers/tokenLocksHandler.ts @@ -152,6 +152,7 @@ export const globalSnapshotTokenLocks = async ( { where: { global_snapshot: filter, + ...ifActiveDagTokenLock(event) }, include: includeDagUnlockOrdinal, orderBy: [{ ordinal: "desc" }, { created_at: "desc" }], @@ -175,7 +176,7 @@ export const addressTokenLocks = async ( toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, { - where: { source_addr: address }, + where: { source_addr: address, ...ifActiveDagTokenLock(event)}, include: includeDagUnlockOrdinal, orderBy: [{ ordinal: "desc" }, { created_at: "desc" }], }, diff --git a/tests/handlers/tokenLocksHandler.test.ts b/tests/handlers/tokenLocksHandler.test.ts index 0ddf787..8839af1 100644 --- a/tests/handlers/tokenLocksHandler.test.ts +++ b/tests/handlers/tokenLocksHandler.test.ts @@ -194,6 +194,19 @@ describe("Token Locks Handler Integration Tests", () => { expect(body.data.length).toBe(data_dag_token_locks.length); body.data.forEach(validateDagTokenLock); }); + + it("should return active token locks for a specific address", async () => { + const address = data_addresses[0].address; + const event = createAPIGatewayEvent({ address },{ active: "true" }); + + const response: APIGatewayProxyResult = await tokenLocksHandler.addressTokenLocks(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + expect(body.data.length).toBe(2); + body.data.forEach(validateDagTokenLock); + }); it("should return empty array for address with no token locks", async () => { const address = "non-existent-address"; From ded8f1f7af665b2daf54ff6828ebfb2dd6af740e Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Tue, 29 Apr 2025 20:48:17 -0300 Subject: [PATCH 32/63] token locks tests --- prisma/schema.prisma | 4 +- prisma/seed.ts | 22 +- src/handlers/tokenLocksHandler.ts | 11 +- tests/handlers/tokenLocksHandler.test.ts | 288 ++++++++++++----------- 4 files changed, 183 insertions(+), 142 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 492bb8b..5866a31 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -440,9 +440,7 @@ model metagraph_token_locks { addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_locks_source_addr_fk") abstract_transactions_view abstract_transactions_view[] metagraph_token_unlock metagraph_token_unlocks? - metagraph_snapshot metagraph_snapshots? @relation(fields: [metagraph_snapshotsMetagraph_id, metagraph_snapshotsHash], references: [metagraph_id, hash]) - metagraph_snapshotsMetagraph_id String? @db.VarChar - metagraph_snapshotsHash String? @db.VarChar + metagraph_snapshot metagraph_snapshots? @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash]) @@id([metagraph_id, hash], map: "metagraph_token_locks_pk") @@unique([hash]) diff --git a/prisma/seed.ts b/prisma/seed.ts index 5aa5eb9..886e057 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -340,7 +340,17 @@ export const data_metagraph_token_locks = [ unlock_epoch: 25n, round_id: randomUUID(), snapshot_hash: data_metagraph_snapshots[1].hash, - } + }, + { + hash: 'metagraph-token-lock-hash-0030', + metagraph_id: data_metagraphs[0].id, + source_addr: data_addresses[0].address, + amount: 4000n, + ordinal: 21n, + unlock_epoch: 25n, + round_id: randomUUID(), + snapshot_hash: data_metagraph_snapshots[1].hash, + } ]; // Test data for metagraph token unlocks @@ -352,7 +362,15 @@ export const data_metagraph_token_unlocks = [ amount: 3000n, lock_reference_hash: data_metagraph_token_locks[0].hash, snapshot_hash: data_metagraph_snapshots[1].hash, - } + }, + { + hash: 'metagraph-token-unlock-hash-002', + metagraph_id: data_metagraphs[0].id, + source_addr: data_addresses[0].address, + amount: 3000n, + lock_reference_hash: data_metagraph_token_locks[2].hash, + snapshot_hash: data_metagraph_snapshots[1].hash, + } ]; diff --git a/src/handlers/tokenLocksHandler.ts b/src/handlers/tokenLocksHandler.ts index 7933a61..1ce5eba 100644 --- a/src/handlers/tokenLocksHandler.ts +++ b/src/handlers/tokenLocksHandler.ts @@ -152,7 +152,7 @@ export const globalSnapshotTokenLocks = async ( { where: { global_snapshot: filter, - ...ifActiveDagTokenLock(event) + ...ifActiveDagTokenLock(event), }, include: includeDagUnlockOrdinal, orderBy: [{ ordinal: "desc" }, { created_at: "desc" }], @@ -176,7 +176,7 @@ export const addressTokenLocks = async ( toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, { - where: { source_addr: address, ...ifActiveDagTokenLock(event)}, + where: { source_addr: address, ...ifActiveDagTokenLock(event) }, include: includeDagUnlockOrdinal, orderBy: [{ ordinal: "desc" }, { created_at: "desc" }], }, @@ -319,9 +319,9 @@ export const metagraphSnapshotTokenLocks = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { metagraph_id, hash_or_ordinal } = - event.pathParameters || {}; + const { metagraph_id, hash_or_ordinal } = event.pathParameters || {}; const filter = extractHashOrdinal(hash_or_ordinal); + return paginatedQuery( extractPagination(event), toCreatedAtOrdinalCursor, @@ -407,8 +407,7 @@ export const metagraphSnapshotTokenUnlocks = async ( event: APIGatewayProxyEvent ): Promise => { try { - const { metagraph_id, hash_or_ordinal } = - event.pathParameters || {}; + const { metagraph_id, hash_or_ordinal } = event.pathParameters || {}; const filter = extractHashOrdinal(hash_or_ordinal); return paginatedQuery( diff --git a/tests/handlers/tokenLocksHandler.test.ts b/tests/handlers/tokenLocksHandler.test.ts index 8839af1..ad2bcbc 100644 --- a/tests/handlers/tokenLocksHandler.test.ts +++ b/tests/handlers/tokenLocksHandler.test.ts @@ -18,16 +18,12 @@ import { } from "../../prisma/seed"; import { randomUUID } from "crypto"; - - - - // Validation functions const validateDagTokenLock = (lock) => { - const match = data_dag_token_locks.find(d => d.hash === lock.hash); + const match = data_dag_token_locks.find((d) => d.hash === lock.hash); expect(match).toBeDefined(); if (!match) return; - + expect(lock.hash).toBe(match.hash); expect(lock.source).toBe(match.source_addr); expect(Number(lock.amount)).toBe(Number(match.amount)); @@ -38,10 +34,10 @@ const validateDagTokenLock = (lock) => { }; const validateDagTokenUnlock = (unlock) => { - const match = data_dag_token_unlocks.find(d => d.hash === unlock.hash); + const match = data_dag_token_unlocks.find((d) => d.hash === unlock.hash); expect(match).toBeDefined(); if (!match) return; - + expect(unlock.hash).toBe(match.hash); expect(unlock.source).toBe(match.source_addr); expect(Number(unlock.amount)).toBe(Number(match.amount)); @@ -51,10 +47,10 @@ const validateDagTokenUnlock = (unlock) => { }; const validateMetagraphTokenLock = (lock) => { - const match = data_metagraph_token_locks.find(d => d.hash === lock.hash); + const match = data_metagraph_token_locks.find((d) => d.hash === lock.hash); expect(match).toBeDefined(); if (!match) return; - + expect(lock.hash).toBe(match.hash); expect(lock.source).toBe(match.source_addr); expect(Number(lock.amount)).toBe(Number(match.amount)); @@ -65,10 +61,12 @@ const validateMetagraphTokenLock = (lock) => { }; const validateMetagraphTokenUnlock = (unlock) => { - const match = data_metagraph_token_unlocks.find(d => d.hash === unlock.hash); + const match = data_metagraph_token_unlocks.find( + (d) => d.hash === unlock.hash + ); expect(match).toBeDefined(); if (!match) return; - + expect(unlock.hash).toBe(match.hash); expect(unlock.source).toBe(match.source_addr); expect(Number(unlock.amount)).toBe(Number(match.amount)); @@ -77,355 +75,383 @@ const validateMetagraphTokenUnlock = (unlock) => { expect(unlock.timestamp).toBeDefined(); }; - describe("Token Locks Handler Integration Tests", () => { // DAG Token Locks Tests describe("tokenLocks", () => { it("should return a list of token locks", async () => { const event = createAPIGatewayEvent({}, { limit: "10" }); - const response: APIGatewayProxyResult = await tokenLocksHandler.tokenLocks(event); - + const response: APIGatewayProxyResult = + await tokenLocksHandler.tokenLocks(event); + expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); - + expect(body.data.length).toBe(data_dag_token_locks.length); validateDagTokenLock(body.data[0]); }); - + it("should handle pagination correctly", async () => { const event = createAPIGatewayEvent({}, { limit: "1" }); - const response: APIGatewayProxyResult = await tokenLocksHandler.tokenLocks(event); - + const response: APIGatewayProxyResult = + await tokenLocksHandler.tokenLocks(event); + expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); - + expect(body.data.length).toBe(1); expect(body.meta.next).toBeDefined(); - + // Try getting the next page - const nextEvent = createAPIGatewayEvent({}, { limit: "1", next: body.meta.next }); + const nextEvent = createAPIGatewayEvent( + {}, + { limit: "1", next: body.meta.next } + ); const nextResponse = await tokenLocksHandler.tokenLocks(nextEvent); - + expect(nextResponse.statusCode).toBe(200); const nextBody = validatePaginatedResponse(nextResponse); - + expect(nextBody.data.length).toBe(1); expect(nextBody.data[0].hash).not.toBe(body.data[0].hash); }); - + it("should filter active token locks", async () => { const event = createAPIGatewayEvent({}, { active: "true" }); - const response: APIGatewayProxyResult = await tokenLocksHandler.tokenLocks(event); - + const response: APIGatewayProxyResult = + await tokenLocksHandler.tokenLocks(event); + expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); - + // Only the second and third token lock should be active (not unlocked) expect(body.data.length).toBe(2); expect(body.data[1].hash).toBe(data_dag_token_locks[1].hash); expect(body.data[0].hash).toBe(data_dag_token_locks[2].hash); }); }); - + describe("tokenLock", () => { it("should return a specific token lock by hash", async () => { const hash = data_dag_token_locks[0].hash; const event = createAPIGatewayEvent({ hash }); - - const response: APIGatewayProxyResult = await tokenLocksHandler.tokenLock(event); - + + const response: APIGatewayProxyResult = await tokenLocksHandler.tokenLock( + event + ); + expect(response.statusCode).toBe(200); const body = validateResponseStructure(response)["data"]; - + validateDagTokenLock(body); expect(body.hash).toBe(hash); }); - + it("should return 404 for non-existent token lock", async () => { const hash = "non-existent-hash"; const event = createAPIGatewayEvent({ hash }); - - const response: APIGatewayProxyResult = await tokenLocksHandler.tokenLock(event); - + + const response: APIGatewayProxyResult = await tokenLocksHandler.tokenLock( + event + ); + expect(response.statusCode).toBe(404); }); }); - + describe("globalSnapshotTokenLocks", () => { it("should return token locks for a specific global snapshot by hash", async () => { const hash_or_ordinal = data_global_snapshots[0].hash; const event = createAPIGatewayEvent({ hash_or_ordinal }); - - const response: APIGatewayProxyResult = await tokenLocksHandler.globalSnapshotTokenLocks(event); - + + const response: APIGatewayProxyResult = + await tokenLocksHandler.globalSnapshotTokenLocks(event); + expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); - + expect(body.data.length).toBe(2); expect(body.data[1].hash).toBe(data_dag_token_locks[0].hash); expect(body.data[0].hash).toBe(data_dag_token_locks[1].hash); }); - + it("should return token locks for a specific global snapshot by ordinal", async () => { const hash_or_ordinal = data_global_snapshots[0].ordinal.toString(); const event = createAPIGatewayEvent({ hash_or_ordinal }); - - const response: APIGatewayProxyResult = await tokenLocksHandler.globalSnapshotTokenLocks(event); - + + const response: APIGatewayProxyResult = + await tokenLocksHandler.globalSnapshotTokenLocks(event); + expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); - + expect(body.data.length).toBe(2); expect(body.data[1].hash).toBe(data_dag_token_locks[0].hash); expect(body.data[0].hash).toBe(data_dag_token_locks[1].hash); }); }); - + describe("addressTokenLocks", () => { it("should return token locks for a specific address", async () => { const address = data_addresses[0].address; const event = createAPIGatewayEvent({ address }); - - const response: APIGatewayProxyResult = await tokenLocksHandler.addressTokenLocks(event); - + + const response: APIGatewayProxyResult = + await tokenLocksHandler.addressTokenLocks(event); + expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); - + expect(body.data.length).toBe(data_dag_token_locks.length); body.data.forEach(validateDagTokenLock); }); it("should return active token locks for a specific address", async () => { const address = data_addresses[0].address; - const event = createAPIGatewayEvent({ address },{ active: "true" }); - - const response: APIGatewayProxyResult = await tokenLocksHandler.addressTokenLocks(event); - + const event = createAPIGatewayEvent({ address }, { active: "true" }); + + const response: APIGatewayProxyResult = + await tokenLocksHandler.addressTokenLocks(event); + expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); - + expect(body.data.length).toBe(2); body.data.forEach(validateDagTokenLock); }); - + it("should return empty array for address with no token locks", async () => { const address = "non-existent-address"; const event = createAPIGatewayEvent({ address }); - - const response: APIGatewayProxyResult = await tokenLocksHandler.addressTokenLocks(event); - + + const response: APIGatewayProxyResult = + await tokenLocksHandler.addressTokenLocks(event); + expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); - + expect(body.data.length).toBe(0); }); }); - + // DAG Token Unlocks Tests describe("tokenUnlocks", () => { it("should return a list of token unlocks", async () => { const event = createAPIGatewayEvent({}, { limit: "10" }); - const response: APIGatewayProxyResult = await tokenLocksHandler.tokenUnlocks(event); - + const response: APIGatewayProxyResult = + await tokenLocksHandler.tokenUnlocks(event); + expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); - + expect(body.data.length).toBe(data_dag_token_unlocks.length); validateDagTokenUnlock(body.data[0]); }); }); - + describe("tokenUnlock", () => { it("should return a specific token unlock by hash", async () => { const hash = data_dag_token_unlocks[0].hash; const event = createAPIGatewayEvent({ hash }); - - const response: APIGatewayProxyResult = await tokenLocksHandler.tokenUnlock(event); - + + const response: APIGatewayProxyResult = + await tokenLocksHandler.tokenUnlock(event); + expect(response.statusCode).toBe(200); const body = validateResponseStructure(response)["data"]; - + validateDagTokenUnlock(body); expect(body.hash).toBe(hash); }); - + it("should return 404 for non-existent token unlock", async () => { const hash = "non-existent-hash"; const event = createAPIGatewayEvent({ hash }); - - const response: APIGatewayProxyResult = await tokenLocksHandler.tokenUnlock(event); - + + const response: APIGatewayProxyResult = + await tokenLocksHandler.tokenUnlock(event); + expect(response.statusCode).toBe(404); }); }); - + describe("globalSnapshotTokenUnlocks", () => { it("should return token unlocks for a specific global snapshot", async () => { const hash_or_ordinal = data_global_snapshots[1].hash; const event = createAPIGatewayEvent({ hash_or_ordinal }); - - const response: APIGatewayProxyResult = await tokenLocksHandler.globalSnapshotTokenUnlocks(event); - + + const response: APIGatewayProxyResult = + await tokenLocksHandler.globalSnapshotTokenUnlocks(event); + expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); - + expect(body.data.length).toBe(1); expect(body.data[0].hash).toBe(data_dag_token_unlocks[0].hash); }); }); - + describe("addressTokenUnlocks", () => { it("should return token unlocks for a specific address", async () => { const address = data_addresses[0].address; const event = createAPIGatewayEvent({ address }); - - const response: APIGatewayProxyResult = await tokenLocksHandler.addressTokenUnlocks(event); - + + const response: APIGatewayProxyResult = + await tokenLocksHandler.addressTokenUnlocks(event); + expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); - + expect(body.data.length).toBe(data_dag_token_unlocks.length); validateDagTokenUnlock(body.data[0]); }); }); - + // Metagraph Token Locks Tests describe("metagraphTokenLocks", () => { it("should return token locks for a specific metagraph", async () => { const metagraph_id = data_metagraphs[0].id; const event = createAPIGatewayEvent({ metagraph_id }); - - const response: APIGatewayProxyResult = await tokenLocksHandler.metagraphTokenLocks(event); - + + const response: APIGatewayProxyResult = + await tokenLocksHandler.metagraphTokenLocks(event); + expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); - + expect(body.data.length).toBe(data_metagraph_token_locks.length); validateMetagraphTokenLock(body.data[0]); }); - + it("should filter active metagraph token locks", async () => { const metagraph_id = data_metagraphs[0].id; const event = createAPIGatewayEvent({ metagraph_id }, { active: "true" }); - - const response: APIGatewayProxyResult = await tokenLocksHandler.metagraphTokenLocks(event); - + + const response: APIGatewayProxyResult = + await tokenLocksHandler.metagraphTokenLocks(event); + expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); - + // Only the second token lock should be active (not unlocked) expect(body.data.length).toBe(1); expect(body.data[0].hash).toBe(data_metagraph_token_locks[1].hash); }); }); - + describe("metagraphTokenLock", () => { it("should return a specific metagraph token lock", async () => { const metagraph_id = data_metagraphs[0].id; const hash = data_metagraph_token_locks[0].hash; const event = createAPIGatewayEvent({ metagraph_id, hash }); - - const response: APIGatewayProxyResult = await tokenLocksHandler.metagraphTokenLock(event); - + + const response: APIGatewayProxyResult = + await tokenLocksHandler.metagraphTokenLock(event); + expect(response.statusCode).toBe(200); const body = validateResponseStructure(response)["data"]; - + validateMetagraphTokenLock(body); expect(body.hash).toBe(hash); }); }); - + describe("metagraphSnapshotTokenLocks", () => { it("should return token locks for a specific metagraph snapshot", async () => { const metagraph_id = data_metagraphs[0].id; const hash_or_ordinal = data_metagraph_snapshots[0].hash; const event = createAPIGatewayEvent({ metagraph_id, hash_or_ordinal }); - - const response: APIGatewayProxyResult = await tokenLocksHandler.metagraphSnapshotTokenLocks(event); - + + const response: APIGatewayProxyResult = + await tokenLocksHandler.metagraphSnapshotTokenLocks(event); + expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); - expect(body.data.length).toBe(1); expect(body.data[0].hash).toBe(data_metagraph_token_locks[0].hash); }); }); - + describe("metagraphAddressTokenLocks", () => { it("should return token locks for a specific address in a metagraph", async () => { const metagraph_id = data_metagraphs[0].id; const address = data_addresses[0].address; const event = createAPIGatewayEvent({ metagraph_id, address }); - - const response: APIGatewayProxyResult = await tokenLocksHandler.metagraphAddressTokenLocks(event); - + + const response: APIGatewayProxyResult = + await tokenLocksHandler.metagraphAddressTokenLocks(event); + expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); - + expect(body.data.length).toBe(data_metagraph_token_locks.length); validateMetagraphTokenLock(body.data[0]); }); }); - + // Metagraph Token Unlocks Tests describe("metagraphTokenUnlocks", () => { it("should return token unlocks for a specific metagraph", async () => { const metagraph_id = data_metagraphs[0].id; const event = createAPIGatewayEvent({ metagraph_id }); - - const response: APIGatewayProxyResult = await tokenLocksHandler.metagraphTokenUnlocks(event); - + + const response: APIGatewayProxyResult = + await tokenLocksHandler.metagraphTokenUnlocks(event); + expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); - + expect(body.data.length).toBe(data_metagraph_token_unlocks.length); validateMetagraphTokenUnlock(body.data[0]); }); }); - + describe("metagraphTokenUnlock", () => { it("should return a specific metagraph token unlock", async () => { const metagraph_id = data_metagraphs[0].id; const hash = data_metagraph_token_unlocks[0].hash; const event = createAPIGatewayEvent({ metagraph_id, hash }); - - const response: APIGatewayProxyResult = await tokenLocksHandler.metagraphTokenUnlock(event); - + + const response: APIGatewayProxyResult = + await tokenLocksHandler.metagraphTokenUnlock(event); + expect(response.statusCode).toBe(200); const body = validateResponseStructure(response)["data"]; - + validateMetagraphTokenUnlock(body); expect(body.hash).toBe(hash); }); }); - + describe("metagraphSnapshotTokenUnlocks", () => { it("should return token unlocks for a specific metagraph snapshot", async () => { const metagraph_id = data_metagraphs[0].id; const hash_or_ordinal = data_metagraph_snapshots[1].hash; const event = createAPIGatewayEvent({ metagraph_id, hash_or_ordinal }); - - const response: APIGatewayProxyResult = await tokenLocksHandler.metagraphSnapshotTokenUnlocks(event); - + + const response: APIGatewayProxyResult = + await tokenLocksHandler.metagraphSnapshotTokenUnlocks(event); + expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); - - expect(body.data.length).toBe(1); + + expect(body.data.length).toBe(2); expect(body.data[0].hash).toBe(data_metagraph_token_unlocks[0].hash); + expect(body.data[1].hash).toBe(data_metagraph_token_unlocks[1].hash); }); }); - + describe("metagraphAddressTokenUnlocks", () => { it("should return token unlocks for a specific address in a metagraph", async () => { const metagraph_id = data_metagraphs[0].id; const address = data_addresses[0].address; const event = createAPIGatewayEvent({ metagraph_id, address }); - - const response: APIGatewayProxyResult = await tokenLocksHandler.metagraphAddressTokenUnlocks(event); - + + const response: APIGatewayProxyResult = + await tokenLocksHandler.metagraphAddressTokenUnlocks(event); + expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); - + expect(body.data.length).toBe(data_metagraph_token_unlocks.length); validateMetagraphTokenUnlock(body.data[0]); }); From d475b02b9b865471044eb7e0ef6962004746cea3 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Wed, 30 Apr 2025 18:06:40 -0300 Subject: [PATCH 33/63] add active filter to allow spends --- prisma/schema.prisma | 39 +- src/handlers/allowSpendsHandler.ts | 46 +- tests/handlers/allowSpendsHandler.test.ts | 616 ++++++++++++++++++++++ 3 files changed, 673 insertions(+), 28 deletions(-) create mode 100644 tests/handlers/allowSpendsHandler.test.ts diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5866a31..b1fa5c4 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -124,10 +124,10 @@ model dag_allow_spends { dag_allow_spend_approvers dag_allow_spend_approvers[] addresses_dag_allow_spends_destination_addrToaddresses addresses @relation("dag_allow_spends_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spends_destination_addr_fk") addresses_dag_allow_spends_source_addrToaddresses addresses @relation("dag_allow_spends_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spends_source_addr_fk") - dag_expired_spend_transactions dag_expired_spend_transactions[] - dag_spend_transactions dag_spend_transactions[] - abstract_transactions_view abstract_transactions_view[] - + dag_expired_spend_transaction dag_expired_spend_transactions? + dag_spend_transaction dag_spend_transactions? + abstract_transactions_view abstract_transactions_view? + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spends_global_snapshot_fk") @@index([round_id]) } @@ -178,11 +178,11 @@ model dag_spend_transactions { created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) destination_addr String? @db.VarChar - allow_spend_ref String? @db.VarChar + allow_spend_ref String? @db.VarChar @unique snapshot_hash String @db.VarChar dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_dag_allow_spends_fk") addresses addresses? @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_destination_addr_fk") - abstract_transactions_view abstract_transactions_view[] + abstract_transactions_view abstract_transactions_view? } model dag_token_locks { @@ -199,7 +199,7 @@ model dag_token_locks { global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_lock_global_snapshot_fk") dag_token_unlock dag_token_unlocks? delegate_stake_create_events delegate_stake_create_events[] - abstract_transactions_view abstract_transactions_view[] + abstract_transactions_view abstract_transactions_view? @@unique([ordinal], map: "dag_token_locks_unique") } @@ -215,7 +215,7 @@ model dag_token_unlocks { global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_unlock_global_snapshot_fk") dag_token_lock dag_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_unlocks_token_locks_fk") addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "ddag_token_unlocks_address_fk") - abstract_transactions_view abstract_transactions_view[] + abstract_transactions_view abstract_transactions_view? @@unique([lock_reference_hash], map: "lock_reference_hash_unique") } @@ -271,6 +271,7 @@ model global_snapshots { metagraph_snapshots metagraph_snapshots[] dag_token_locks dag_token_locks[] dag_token_unlocks dag_token_unlocks[] + dag_allow_spends dag_allow_spends[] } model metagraph_allow_spend_approvers { @@ -301,12 +302,10 @@ model metagraph_allow_spends { addresses_metagraph_allow_spends_destination_addrToaddresses addresses @relation("metagraph_allow_spends_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_allow_spends_destination_addr_fk") addresses_metagraph_allow_spends_source_addrToaddresses addresses @relation("metagraph_allow_spends_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_allow_spends_source_addr_fk") metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") - metagraph_expired_spend_transactions metagraph_expired_spend_transactions[] - metagraph_spend_transactions metagraph_spend_transactions[] - abstract_transactions_view abstract_transactions_view[] - metagraph_snapshot metagraph_snapshots? @relation(fields: [metagraph_snapshotsMetagraph_id, metagraph_snapshotsHash], references: [metagraph_id, hash]) - metagraph_snapshotsMetagraph_id String? @db.VarChar - metagraph_snapshotsHash String? @db.VarChar + metagraph_expired_spend_transaction metagraph_expired_spend_transactions? + metagraph_spend_transaction metagraph_spend_transactions? + abstract_transactions_view abstract_transactions_view? + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash]) } model metagraph_balance_changes { @@ -417,7 +416,7 @@ model metagraph_spend_transactions { updated_at DateTime @default(now()) @db.Timestamp(6) metagraph_id String @db.VarChar destination_addr String @db.VarChar - allow_spend_ref String? @db.VarChar + allow_spend_ref String? @db.VarChar @unique snapshot_hash String @db.VarChar addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_destination_addr_fk") metagraph_allow_spend metagraph_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "mg_spend_transactions_mg_allow_spends_fk") @@ -510,9 +509,9 @@ model dag_expired_spend_transactions { amount BigInt created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - allow_spend_ref String? @db.VarChar + allow_spend_ref String @db.VarChar @unique snapshot_hash String @db.VarChar - dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_expired_spend_transactions_dag_allow_spends_fk") + dag_allow_spend dag_allow_spends @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_expired_spend_transactions_dag_allow_spends_fk") abstract_transactions_view abstract_transactions_view[] } @@ -523,9 +522,9 @@ model metagraph_expired_spend_transactions { created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) metagraph_id String @db.VarChar - allow_spend_ref String? @db.VarChar - snapshot_hash String? @db.VarChar - metagraph_allow_spend metagraph_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_expired_spend_transactions_metagraph_allow_spends_fk") + allow_spend_ref String @db.VarChar @unique + snapshot_hash String @db.VarChar + metagraph_allow_spend metagraph_allow_spends @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_expired_spend_transactions_metagraph_allow_spends_fk") abstract_transactions_view abstract_transactions_view[] } diff --git a/src/handlers/allowSpendsHandler.ts b/src/handlers/allowSpendsHandler.ts index 95c980f..6dea8c2 100644 --- a/src/handlers/allowSpendsHandler.ts +++ b/src/handlers/allowSpendsHandler.ts @@ -31,7 +31,7 @@ const spendTransactionResponse = (transaction) => ({ amount: transaction.amount, source: transaction.source_addr, destination: transaction.destination_addr, - allowSpendRef: transaction.allow_spend_ref, + allowSpendHash: transaction.allow_spend_ref, snapshotHash: transaction.snapshot_hash, timestamp: transaction.created_at, }); @@ -43,13 +43,37 @@ const spendExpiredResponse = (transaction) => ({ hash: transaction.hash, amount: transaction.amount, source: transaction.source_addr, - allowSpendRef: transaction.allow_spend_ref, + allowSpendHash: transaction.allow_spend_ref, snapshotHash: transaction.snapshot_hash, timestamp: transaction.created_at, }); const spendExpiredResponses = (txs) => txs.map(spendExpiredResponse); +const ifActiveAllowSpend = (event) => { + const { active } = event.queryStringParameters || {}; + return active === "true" + ? { + AND: [ + { dag_spend_transaction: null }, + { dag_expired_spend_transaction: null }, + ], + } + : {}; +}; + +const ifActiveMetagraphAllowSpend = (event) => { + const { active } = event.queryStringParameters || {}; + return active === "true" + ? { + AND: [ + { metagraph_spend_transaction: null }, + { metagraph_expired_spend_transaction: null }, + ], + } + : {}; +}; + export const allowSpend = async ( event: APIGatewayProxyEvent ): Promise => { @@ -73,7 +97,7 @@ export const allowSpends = async ( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { orderBy: { created_at: "desc" } }, + { where: ifActiveAllowSpend(event), orderBy: { created_at: "desc" } }, prisma.dag_allow_spends.findMany, allowSpendResponses ); @@ -91,7 +115,7 @@ export const globalSnapshotAllowSpends = async ( toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, { - where: { global_snapshot: filter }, + where: { global_snapshot: filter, ...ifActiveAllowSpend(event) }, orderBy: { created_at: "desc" }, }, prisma.dag_allow_spends.findMany, @@ -115,6 +139,7 @@ export const addressAllowSpends = async ( { where: { OR: [{ source_addr: address }, { destination_addr: address }], + ...ifActiveAllowSpend(event), }, orderBy: { created_at: "desc" }, }, @@ -303,7 +328,10 @@ export const currencyAllowSpends = async ( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { where: { metagraph_id }, orderBy: { created_at: "desc" } }, + { + where: { metagraph_id, ...ifActiveMetagraphAllowSpend(event) }, + orderBy: { created_at: "desc" }, + }, prisma.metagraph_allow_spends.findMany, allowSpendResponses ); @@ -342,7 +370,8 @@ export const currencySnapshotAllowSpends = async ( { where: { metagraph_id, - metagraph_allow_spend_block: { metagraph_snapshot: filter }, + metagraph_snapshot: filter, + ...ifActiveMetagraphAllowSpend(event), }, orderBy: { created_at: "desc" }, }, @@ -368,6 +397,7 @@ export const currencyAddressAllowSpends = async ( where: { metagraph_id, OR: [{ source_addr: address }, { destination_addr: address }], + ...ifActiveMetagraphAllowSpend(event), }, orderBy: { created_at: "desc" }, }, @@ -429,7 +459,7 @@ export const currencySnapshotSpendTransactions = async ( where: { metagraph_id, metagraph_allow_spend: { - metagraph_allow_spend_block: { metagraph_snapshot: filter }, + metagraph_snapshot: filter, }, }, orderBy: { created_at: "desc" }, @@ -517,7 +547,7 @@ export const currencySnapshotAllowSpendExpirations = async ( where: { metagraph_id, metagraph_allow_spend: { - metagraph_allow_spend_block: { metagraph_snapshot: filter }, + metagraph_snapshot: filter, }, }, orderBy: { created_at: "desc" }, diff --git a/tests/handlers/allowSpendsHandler.test.ts b/tests/handlers/allowSpendsHandler.test.ts new file mode 100644 index 0000000..f5d6d60 --- /dev/null +++ b/tests/handlers/allowSpendsHandler.test.ts @@ -0,0 +1,616 @@ +import { APIGatewayProxyResult } from "aws-lambda"; +import * as allowSpendsHandler from "../../src/handlers/allowSpendsHandler"; +import { + createAPIGatewayEvent, + validatePaginatedResponse, + validateResponseStructure, +} from "../testUtils"; +import { + data_addresses, + data_global_snapshots, + data_metagraph_snapshots, + data_metagraphs, + prisma, +} from "../../prisma/seed"; + +export const data_dag_allow_spends = [ + { + hash: "allowSpendHash1", + source_addr: data_addresses[0].address, + destination_addr: data_addresses[1].address, + amount: 1000n, + fee: 5n, + last_valid_epoch_progress: 100n, + ordinal: 1n, + snapshot_hash: data_global_snapshots[0].hash, + round_id: "11111111-1111-1111-1111-111111111111", + created_at: new Date("2024-01-01T10:00:00Z"), + updated_at: new Date("2024-01-01T10:00:00Z"), + }, + { + hash: "allowSpendHash2", + source_addr: data_addresses[0].address, + destination_addr: data_addresses[2].address, + amount: 2000n, + fee: 10n, + last_valid_epoch_progress: 600n, + ordinal: 2n, + snapshot_hash: data_global_snapshots[0].hash, + round_id: "22222222-2222-2222-2222-222222222222", + created_at: new Date("2024-01-02T10:00:00Z"), + updated_at: new Date("2024-01-02T10:00:00Z"), + }, + { + hash: "allowSpendHash3", + source_addr: data_addresses[2].address, + destination_addr: data_addresses[1].address, + amount: 300n, + fee: 15n, + last_valid_epoch_progress: 700n, + ordinal: 3n, + snapshot_hash: data_global_snapshots[1].hash, + round_id: "33333333-3333-3333-3333-333333333333", + created_at: new Date("2024-01-03T10:00:00Z"), + updated_at: new Date("2024-01-03T10:00:00Z"), + }, + { + hash: "allowSpendHash4", + source_addr: data_addresses[2].address, + destination_addr: data_addresses[3].address, + amount: 300n, + fee: 15n, + last_valid_epoch_progress: 700n, + ordinal: 4n, + snapshot_hash: data_global_snapshots[0].hash, + round_id: "33333333-3333-3333-3333-333333333333", + created_at: new Date("2024-01-03T10:00:00Z"), + updated_at: new Date("2024-01-03T10:00:00Z"), + }, +]; + +export const data_dag_spend_transactions = [ + { + hash: "spendTxHash1", + source_addr: data_addresses[0].address, + destination_addr: data_addresses[1].address, + amount: 1000n, + allow_spend_ref: data_dag_allow_spends[0].hash, + snapshot_hash: data_global_snapshots[2].hash, + created_at: new Date("2024-01-05T10:00:00Z"), + updated_at: new Date("2024-01-05T10:00:00Z"), + }, +]; + +export const data_dag_expired_spend_transactions = [ + { + hash: "expiredSpendTxHash1", + source_addr: data_addresses[2].address, + amount: 3000n, + allow_spend_ref: data_dag_allow_spends[1].hash, + snapshot_hash: data_global_snapshots[2].hash, + created_at: new Date("2024-01-10T10:00:00Z"), + updated_at: new Date("2024-01-10T10:00:00Z"), + }, +]; + +export const data_metagraph_allow_spends = [ + { + metagraph_id: data_metagraphs[0].id, + hash: "metaAllowSpendHash1", + source_addr: data_addresses[0].address, + destination_addr: data_addresses[1].address, + amount: 500n, + fee: 3n, + last_valid_epoch_progress: 50n, + ordinal: 1n, + snapshot_hash: data_metagraph_snapshots[0].hash, + round_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + created_at: new Date("2024-01-01T11:00:00Z"), + updated_at: new Date("2024-01-01T11:00:00Z"), + }, + { + metagraph_id: data_metagraphs[0].id, + hash: "metaAllowSpendHash2", + source_addr: data_addresses[0].address, + destination_addr: data_addresses[1].address, + amount: 500n, + fee: 3n, + last_valid_epoch_progress: 50n, + ordinal: 2n, + snapshot_hash: data_metagraph_snapshots[1].hash, + round_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + created_at: new Date("2024-01-01T11:00:00Z"), + updated_at: new Date("2024-01-01T11:00:00Z"), + }, + { + metagraph_id: data_metagraphs[0].id, + hash: "metaAllowSpendHash3", + source_addr: data_addresses[2].address, + destination_addr: data_addresses[3].address, + amount: 750n, + fee: 4n, + last_valid_epoch_progress: 60n, + ordinal: 3n, + snapshot_hash: data_metagraph_snapshots[1].hash, + round_id: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", + created_at: new Date("2024-01-02T11:00:00Z"), + updated_at: new Date("2024-01-02T11:00:00Z"), + }, +]; + +export const data_metagraph_spend_transactions = [ + { + metagraph_id: data_metagraphs[0].id, + hash: "metaSpendTxHash1", + source_addr: data_addresses[0].address, + destination_addr: data_addresses[1].address, + amount: 500n, + allow_spend_ref: data_metagraph_allow_spends[0].hash, + snapshot_hash: data_metagraph_snapshots[0].hash, + created_at: new Date("2024-01-03T11:00:00Z"), + updated_at: new Date("2024-01-03T11:00:00Z"), + }, +]; + +export const data_metagraph_expired_spend_transactions = [ + { + metagraph_id: data_metagraphs[0].id, + hash: "metaExpiredTxHash1", + source_addr: data_addresses[2].address, + amount: 750n, + allow_spend_ref: data_metagraph_allow_spends[2].hash, + snapshot_hash: data_metagraph_snapshots[1].hash, + created_at: new Date("2024-01-04T11:00:00Z"), + updated_at: new Date("2024-01-04T11:00:00Z"), + }, +]; + +const seedData = async () => { + await prisma.dag_allow_spends.createManyAndReturn({ + data: data_dag_allow_spends, + }); + + await prisma.dag_spend_transactions.createManyAndReturn({ + data: data_dag_spend_transactions, + }); + + await prisma.dag_expired_spend_transactions.createManyAndReturn({ + data: data_dag_expired_spend_transactions, + }); + + await prisma.metagraph_allow_spends.createManyAndReturn({ + data: data_metagraph_allow_spends, + }); + await prisma.metagraph_spend_transactions.createManyAndReturn({ + data: data_metagraph_spend_transactions, + }); + await prisma.metagraph_expired_spend_transactions.createManyAndReturn({ + data: data_metagraph_expired_spend_transactions, + }); +}; + +beforeAll(async () => { + await seedData(); +}); + +// Helper validations +const validateDagAllowSpend = (entry) => { + const match = data_dag_allow_spends.find((d) => d.hash === entry.hash); + expect(match).toBeDefined(); + if (!match) return; + + expect(entry.hash).toBe(match.hash); + expect(Number(entry.amount)).toBe(Number(match.amount)); + expect(entry.source).toBe(match.source_addr); + expect(entry.destination).toBe(match.destination_addr); + expect(Number(entry.fee)).toBe(Number(match.fee)); + expect(Number(entry.ordinal)).toBe(Number(match.ordinal)); + expect(entry.snapshotHash).toBe(match.snapshot_hash); + expect(entry.timestamp).toBeDefined(); +}; + +const validateDagSpendTransaction = (entry) => { + const match = data_dag_spend_transactions.find((d) => d.hash === entry.hash); + expect(match).toBeDefined(); + if (!match) return; + + expect(Number(entry.amount)).toBe(Number(match.amount)); + expect(entry.source).toBe(match.source_addr); + expect(entry.destination).toBe(match.destination_addr); + expect(entry.allowSpendHash).toBe(match.allow_spend_ref); + expect(entry.snapshotHash).toBe(match.snapshot_hash); + expect(entry.timestamp).toBeDefined(); +}; + +const validateDagExpiredSpend = (entry) => { + const match = data_dag_expired_spend_transactions.find( + (d) => d.hash === entry.hash + ); + expect(match).toBeDefined(); + if (!match) return; + + expect(Number(entry.amount)).toBe(Number(match.amount)); + expect(entry.source).toBe(match.source_addr); + expect(entry.allowSpendHash).toBe(match.allow_spend_ref); + expect(entry.snapshotHash).toBe(match.snapshot_hash); + expect(entry.timestamp).toBeDefined(); +}; + +const validateMgAllowSpend = (entry) => { + const match = data_metagraph_allow_spends.find((d) => d.hash === entry.hash); + expect(match).toBeDefined(); + if (!match) return; + + expect(entry.hash).toBe(match.hash); + expect(Number(entry.amount)).toBe(Number(match.amount)); + expect(entry.source).toBe(match.source_addr); + expect(entry.destination).toBe(match.destination_addr); + expect(Number(entry.fee)).toBe(Number(match.fee)); + expect(entry.timestamp).toBeDefined(); +}; + +const validateMgSpendTransaction = (entry) => { + const match = data_metagraph_spend_transactions.find( + (d) => d.hash === entry.hash + ); + expect(match).toBeDefined(); + if (!match) return; + + expect(entry.allowSpendHash).toBe(match.allow_spend_ref); + expect(entry.snapshotHash).toBe(match.snapshot_hash); + expect(entry.timestamp).toBeDefined(); +}; + +const validateMgExpiredSpend = (entry) => { + const match = data_metagraph_expired_spend_transactions.find( + (d) => d.hash === entry.hash + ); + expect(match).toBeDefined(); + if (!match) return; + + expect(entry.allowSpendHash).toBe(match.allow_spend_ref); + expect(entry.snapshotHash).toBe(match.snapshot_hash); + expect(entry.timestamp).toBeDefined(); +}; + +describe("AllowSpends Handler Integration Tests", () => { + describe("allowSpends", () => { + it("should return a list of allow spends", async () => { + const event = createAPIGatewayEvent({}, { limit: "10" }); + const response: APIGatewayProxyResult = + await allowSpendsHandler.allowSpends(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + expect(body.data.length).toBe(data_dag_allow_spends.length); + body.data.forEach(validateDagAllowSpend); + }); + + it("should return active allow spends", async () => { + const event = createAPIGatewayEvent({}, { active: "true" }); + const response: APIGatewayProxyResult = + await allowSpendsHandler.allowSpends(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + expect(body.data.length).toBe(2); + body.data.forEach(validateDagAllowSpend); + }); + }); + + describe("allowSpend", () => { + it("should return a specific allow spend by hash", async () => { + const hash = data_dag_allow_spends[0].hash; + const event = createAPIGatewayEvent({ hash }); + + const response = await allowSpendsHandler.allowSpend(event); + expect(response.statusCode).toBe(200); + + const body = validateResponseStructure(response)["data"]; + validateDagAllowSpend(body); + }); + }); + + describe("globalSnapshotAllowSpends", () => { + it("should return allow spends for a specific global snapshot", async () => { + const hash_or_ordinal = data_global_snapshots[0].hash; + const event = createAPIGatewayEvent({ hash_or_ordinal }); + + const response = await allowSpendsHandler.globalSnapshotAllowSpends( + event + ); + expect(response.statusCode).toBe(200); + + const body = validatePaginatedResponse(response); + expect(body.data.length).toBe(3); + body.data.forEach(validateDagAllowSpend); + }); + + it("should return active only allow spends for a specific global snapshot", async () => { + const hash_or_ordinal = data_global_snapshots[0].hash; + const event = createAPIGatewayEvent( + { hash_or_ordinal }, + { active: "true" } + ); + + const response = await allowSpendsHandler.globalSnapshotAllowSpends( + event + ); + expect(response.statusCode).toBe(200); + + const body = validatePaginatedResponse(response); + expect(body.data.length).toBe(1); + body.data.forEach(validateDagAllowSpend); + }); + }); + + describe("addressAllowSpends", () => { + it("should return allow spends for a specific address", async () => { + const address = data_addresses[3].address; + const event = createAPIGatewayEvent({ address }); + + const response = await allowSpendsHandler.addressAllowSpends(event); + expect(response.statusCode).toBe(200); + + const body = validatePaginatedResponse(response); + expect(body.data.length).toBe(1); + body.data.forEach(validateDagAllowSpend); + }); + it("should return active only allow spends for a specific address", async () => { + const address = data_addresses[3].address; + const event = createAPIGatewayEvent({ address }, { active: "true" }); + + const response = await allowSpendsHandler.addressAllowSpends(event); + expect(response.statusCode).toBe(200); + + const body = validatePaginatedResponse(response); + expect(body.data.length).toBe(1); + body.data.forEach(validateDagAllowSpend); + }); + }); + + describe("spendTransactions", () => { + it("should return spend transactions", async () => { + const event = createAPIGatewayEvent({}, { limit: "10" }); + const response = await allowSpendsHandler.spendTransactions(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + expect(body.data.length).toBe(data_dag_spend_transactions.length); + validateDagSpendTransaction(body.data[0]); + }); + }); + + describe("allowSpendExpirations", () => { + it("should return expired spend transactions", async () => { + const event = createAPIGatewayEvent({}, { limit: "10" }); + const response = await allowSpendsHandler.allowSpendExpirations(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + expect(body.data.length).toBe(data_dag_expired_spend_transactions.length); + validateDagExpiredSpend(body.data[0]); + }); + }); + + describe("addressSpendTransactions", () => { + it("should return spend transactions for a specific address", async () => { + const address = data_addresses[0].address; + const event = createAPIGatewayEvent({ address }); + + const response = await allowSpendsHandler.addressSpendTransactions(event); + expect(response.statusCode).toBe(200); + + const body = validatePaginatedResponse(response); + expect(body.data.length).toBeGreaterThan(0); + body.data.forEach(validateDagSpendTransaction); + }); + }); + + describe("addressAllowSpendExpirations", () => { + it("should return expired spend transactions for an address", async () => { + const address = data_addresses[2].address; + const event = createAPIGatewayEvent({ address }); + + const response = await allowSpendsHandler.addressAllowSpendExpirations( + event + ); + expect(response.statusCode).toBe(200); + + const body = validatePaginatedResponse(response); + expect(body.data.length).toBeGreaterThan(0); + body.data.forEach(validateDagExpiredSpend); + }); + }); +}); + +describe("Metagraph AllowSpends Handler Integration Tests", () => { + describe("currencyAllowSpends", () => { + it("should return all allow spends", async () => { + const event = createAPIGatewayEvent({ + metagraph_id: data_metagraphs[0].id, + }); + const response = await allowSpendsHandler.currencyAllowSpends(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + expect(body.data.length).toBe(3); + body.data.forEach(validateMgAllowSpend); + }); + + it("should return active allow spends only", async () => { + const event = createAPIGatewayEvent( + { metagraph_id: data_metagraphs[0].id }, + { active: "true" } + ); + const response = await allowSpendsHandler.currencyAllowSpends(event); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + expect(body.data.length).toBe(1); + body.data.forEach(validateMgAllowSpend); + }); + }); + + describe("currencySpendTransactions", () => { + it("should return spend transactions", async () => { + const event = createAPIGatewayEvent({ + metagraph_id: data_metagraphs[0].id, + }); + const response = await allowSpendsHandler.currencySpendTransactions( + event + ); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + expect(body.data.length).toBe(data_metagraph_spend_transactions.length); + body.data.forEach(validateMgSpendTransaction); + }); + }); + + describe("currencyAllowSpendExpirations", () => { + it("should return expired spend transactions", async () => { + const event = createAPIGatewayEvent({ + metagraph_id: data_metagraphs[0].id, + }); + const response = await allowSpendsHandler.currencyAllowSpendExpirations( + event + ); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + expect(body.data.length).toBe( + data_metagraph_expired_spend_transactions.length + ); + body.data.forEach(validateMgExpiredSpend); + }); + }); + + describe("currencySnapshotAllowSpends", () => { + it("should return allow spends for a snapshot", async () => { + const event = createAPIGatewayEvent({ + metagraph_id: data_metagraphs[0].id, + hash_or_ordinal: data_metagraph_snapshots[0].hash, + }); + + const response = await allowSpendsHandler.currencySnapshotAllowSpends( + event + ); + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + expect(body.data.length).toBe(1); + body.data.forEach(validateMgAllowSpend); + }); + it("should return active only allow spends for a snapshot", async () => { + const event = createAPIGatewayEvent( + { + metagraph_id: data_metagraphs[0].id, + hash_or_ordinal: data_metagraph_snapshots[1].hash, + }, + { active: "true" } + ); + + const response = await allowSpendsHandler.currencySnapshotAllowSpends( + event + ); + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + expect(body.data.length).toBe(1); + body.data.forEach(validateMgAllowSpend); + }); + }); + + describe("currencySnapshotSpendTransactions", () => { + it("should return spend transactions for a snapshot", async () => { + const event = createAPIGatewayEvent({ + metagraph_id: data_metagraphs[0].id, + hash_or_ordinal: data_metagraph_snapshots[0].hash, + }); + + const response = + await allowSpendsHandler.currencySnapshotSpendTransactions(event); + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + expect(body.data.length).toBe(1); + body.data.forEach(validateMgSpendTransaction); + }); + }); + + describe("currencySnapshotAllowSpendExpirations", () => { + it("should return expired spend transactions for a snapshot", async () => { + const event = createAPIGatewayEvent({ + metagraph_id: data_metagraphs[0].id, + hash_or_ordinal: data_metagraph_snapshots[1].hash, + }); + + const response = + await allowSpendsHandler.currencySnapshotAllowSpendExpirations(event); + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + expect(body.data.length).toBe(1); + body.data.forEach(validateMgExpiredSpend); + }); + }); + + describe("currencyAddressAllowSpends", () => { + it("should return allow spends for an address", async () => { + const event = createAPIGatewayEvent({ + metagraph_id: data_metagraphs[0].id, + address: data_addresses[1].address, + }); + + const response = await allowSpendsHandler.currencyAddressAllowSpends( + event + ); + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + expect(body.data.length).toBe(2); + }); + it("should return active only allow spends for an address", async () => { + const event = createAPIGatewayEvent( + { + metagraph_id: data_metagraphs[0].id, + address: data_addresses[1].address, + }, + { active: "true" } + ); + + const response = await allowSpendsHandler.currencyAddressAllowSpends( + event + ); + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + expect(body.data.length).toBe(1); + }); + }); + + describe("currencyAddressSpendTransactions", () => { + it("should return spend transactions for an address", async () => { + const event = createAPIGatewayEvent({ + metagraph_id: data_metagraphs[0].id, + address: data_addresses[1].address, + }); + + const response = + await allowSpendsHandler.currencyAddressSpendTransactions(event); + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + expect(body.data.length).toBe(1); + }); + }); + + describe("currencyAddressAllowSpendExpirations", () => { + it("should return expired spend transactions for an address", async () => { + const event = createAPIGatewayEvent({ + metagraph_id: data_metagraphs[0].id, + address: data_addresses[2].address, + }); + + const response = + await allowSpendsHandler.currencyAddressAllowSpendExpirations(event); + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + expect(body.data.length).toBe(1); + }); + }); +}); From 1b6721f9de16938bc048406fbe0a81015817247e Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Sun, 4 May 2025 12:57:50 -0300 Subject: [PATCH 34/63] add metagraph count to response --- docs/schemas/dag.yml | 3 +++ src/response.ts | 1 + tests/handlers/dagHandler.test.ts | 1 + 3 files changed, 5 insertions(+) diff --git a/docs/schemas/dag.yml b/docs/schemas/dag.yml index 1e5f97c..9fc027b 100644 --- a/docs/schemas/dag.yml +++ b/docs/schemas/dag.yml @@ -26,6 +26,9 @@ components: epochProgress: type: number description: The progress through the current epoch + metagraphSnashotCount: + type: number + description: Metagraph snapshots included in this snapshot timestamp: type: string description: The timestamp when the snapshot was created diff --git a/src/response.ts b/src/response.ts index 21f102c..fef1f1f 100644 --- a/src/response.ts +++ b/src/response.ts @@ -33,6 +33,7 @@ const commonSnapshotResponse = (snapshot, blocksProperty) => ({ export const globalSnapshotResponse = (snapshot) => ({ ...commonSnapshotResponse(snapshot, "dag_blocks"), + metagraphSnashotCount: snapshot.metagraph_snapshot_count }); export const rewardsResponse = (rs) => rs.map(rewardResponse); diff --git a/tests/handlers/dagHandler.test.ts b/tests/handlers/dagHandler.test.ts index e86b196..3eb333a 100644 --- a/tests/handlers/dagHandler.test.ts +++ b/tests/handlers/dagHandler.test.ts @@ -157,6 +157,7 @@ describe("DAG Handler Integration Tests", () => { expect(snapshot.subHeight).toBe(Number(testSnapshot.subheight)); expect(Array.isArray(snapshot.blocks)).toBe(true); expect(snapshot.timestamp).toBeDefined(); + expect(snapshot.metagraphSnashotCount).toBe(Number(testSnapshot.metagraph_snapshot_count)); }); it("should handle pagination correctly", async () => { From 6b96d57772d6fed933fd1877e9dcb69752065c66 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Mon, 5 May 2025 13:18:38 -0300 Subject: [PATCH 35/63] Update docs/schemas/dag.yml Co-authored-by: Alex Brandes --- docs/schemas/dag.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/schemas/dag.yml b/docs/schemas/dag.yml index 9fc027b..522b662 100644 --- a/docs/schemas/dag.yml +++ b/docs/schemas/dag.yml @@ -26,7 +26,7 @@ components: epochProgress: type: number description: The progress through the current epoch - metagraphSnashotCount: + metagraphSnapshotCount: type: number description: Metagraph snapshots included in this snapshot timestamp: From f3bde87800c111813275c6058e061ac768de7e00 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Tue, 6 May 2025 21:51:34 -0300 Subject: [PATCH 36/63] PROT-1163 increase page size limit --- src/pagination.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pagination.ts b/src/pagination.ts index e4d6771..48a1567 100644 --- a/src/pagination.ts +++ b/src/pagination.ts @@ -3,7 +3,8 @@ import { handleError, respond } from "./response"; import { Pagination } from "./request-params"; import { toNumber, isFinite } from "lodash"; -export const maxSizeLimit = 100; +export const maxSizeLimit = 10000; +export const defaultPageSize = 100; export enum SortOrder { Desc = "desc", @@ -46,7 +47,7 @@ export const fromCreatedAtOrdinalCursor = (row) => ({ export const hashCursor = (row) => ({ hash: row.hash }); const buildPageQuery = (pagination: Pagination, nextToCursor) => { - const pageSize = safeNumber(pagination.size, maxSizeLimit); + const pageSize = safeNumber(pagination.size, defaultPageSize); const incrementedSize = pageSize + 1; if ( From 47c43d5ef76823821074763dbc1ec1b4472b39b1 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Sun, 4 May 2025 10:31:59 -0300 Subject: [PATCH 37/63] add snapshot ordinal to actions --- docs/schemas/actions.yml | 14 +- prisma/migrations/20250502/01_migration.sql | 48 ++++ .../migrations/20250502/02_snapshot_hash.sql | 166 +++++++++++ prisma/schema.prisma | 266 ++++++++++-------- src/handlers/actionsHandler.ts | 109 +------ 5 files changed, 381 insertions(+), 222 deletions(-) create mode 100644 prisma/migrations/20250502/01_migration.sql create mode 100644 prisma/migrations/20250502/02_snapshot_hash.sql diff --git a/docs/schemas/actions.yml b/docs/schemas/actions.yml index 918f677..4a3f6c7 100644 --- a/docs/schemas/actions.yml +++ b/docs/schemas/actions.yml @@ -9,7 +9,7 @@ components: enum: [AllowSpend, TokenLock, TokenUnlock, SpendTransaction, FeeTransaction, ExpiredSpendTransaction] currencyId: type: string - description: The identifier of the currency + description: The identifier of the currency if it's a currency action nullable: true hash: type: string @@ -37,6 +37,18 @@ components: type: string description: The timestamp when the transaction was created format: date-time + oneOf: + - required: [globalSnapshotOrdinal] + properties: + globalSnapshotOrdinal: + type: integer + description: Global snapshot ordinal if it's a DAG action + - required: [metagraphSnapshotOrdinal] + properties: + metagraphSnapshotOrdinal: + type: integer + description: Metagraph snapshot ordinal if it's a currency action + ActionResponse: type: object diff --git a/prisma/migrations/20250502/01_migration.sql b/prisma/migrations/20250502/01_migration.sql new file mode 100644 index 0000000..9946b15 --- /dev/null +++ b/prisma/migrations/20250502/01_migration.sql @@ -0,0 +1,48 @@ +-- remove intermediate block tables +ALTER TABLE metagraph_allow_spends DROP CONSTRAINT allow_spends_block_fk; + +DROP TABLE dag_token_lock_blocks; +DROP TABLE metagraph_token_lock_blocks; +DROP TABLE dag_allow_spend_blocks; +DROP TABLE metagraph_allow_spend_blocks; + +ALTER TABLE dag_token_unlocks + DROP COLUMN lock_reference_ordinal, -- replaced by lock reference hash + DROP COLUMN parent_hash; -- not used by tessellation + +ALTER TABLE dag_token_locks + DROP COLUMN global_snapshot_hash, -- replaced by snapshot hash hash + ADD CONSTRAINT dag_token_locks_global_snapshots_fk FOREIGN KEY (snapshot_hash) REFERENCES global_snapshots(hash) ON DELETE CASCADE; + +ALTER TABLE dag_spend_transactions + ADD CONSTRAINT dag_spend_transactions_destination_addr_fk FOREIGN KEY (destination_addr) REFERENCES addresses(address) ON DELETE CASCADE, + ADD CONSTRAINT dag_spend_transactions_global_snapshots_fk FOREIGN KEY (hash) REFERENCES global_snapshots(hash) ON DELETE CASCADE, + ADD CONSTRAINT dag_spend_transactions_source_addresses_fk FOREIGN KEY (source_addr) REFERENCES addresses(address) ON DELETE CASCADE; + +ALTER TABLE dag_token_unlocks + DROP CONSTRAINT dag_token_unlocks_pk, + ADD CONSTRAINT dag_token_unlocks_pk PRIMARY KEY (hash), + ADD CONSTRAINT dag_token_unlocks_dag_token_locks_fk FOREIGN KEY (lock_reference_hash) REFERENCES dag_token_locks(hash); + + +ALTER TABLE metagraph_token_unlocks + DROP CONSTRAINT metagraph_token_unlocks_pk, + ADD CONSTRAINT metagraph_token_unlocks_pk PRIMARY KEY (hash,metagraph_id); + + +CREATE INDEX dag_spend_transactions_allow_spend_ref_idx ON public.dag_spend_transactions USING btree (allow_spend_ref); +CREATE INDEX dag_spend_transactions_destination_addr_idx ON public.dag_spend_transactions USING btree (destination_addr); +CREATE INDEX dag_spend_transactions_snapshot_hash_idx ON public.dag_spend_transactions USING btree (snapshot_hash); +CREATE INDEX dag_spend_transactions_source_addr_idx ON public.dag_spend_transactions USING btree (source_addr); +CREATE INDEX dag_expired_spend_transactions_allow_spend_ref_idx ON dag_expired_spend_transactions USING btree (allow_spend_ref); +CREATE INDEX dag_expired_spend_transactions_snapshot_hash_idx ON dag_expired_spend_transactions USING btree (snapshot_hash); +CREATE INDEX dag_expired_spend_transactions_source_addr_idx ON dag_expired_spend_transactions USING btree (source_addr); + +CREATE INDEX dag_token_locks_snapshot_hash_idx ON dag_token_locks USING btree (snapshot_hash); + +CREATE INDEX dag_allow_spends_destination_addr_idx ON dag_allow_spends USING btree (destination_addr); +CREATE INDEX dag_allow_spends_source_addr_idx ON dag_allow_spends USING btree (source_addr); + +CREATE INDEX dag_balance_changes_snapshot_hash_idx ON dag_balance_changes USING btree (snapshot_hash); + + diff --git a/prisma/migrations/20250502/02_snapshot_hash.sql b/prisma/migrations/20250502/02_snapshot_hash.sql new file mode 100644 index 0000000..bdc0197 --- /dev/null +++ b/prisma/migrations/20250502/02_snapshot_hash.sql @@ -0,0 +1,166 @@ +-- Add snapshot_hash column to abstract_transactions +ALTER TABLE public.abstract_transactions +ADD COLUMN snapshot_hash varchar NULL; + +-- Replace the trigger function to propagate snapshot_hash +CREATE OR REPLACE FUNCTION public.insert_into_parent_abstract_transactions() + RETURNS trigger + LANGUAGE plpgsql +AS $function$ +BEGIN + INSERT INTO abstract_transactions (hash, source_addr, amount, created_at, snapshot_hash) + VALUES (NEW.hash, NEW.source_addr, NEW.amount, NEW.created_at, NEW.snapshot_hash) + ON CONFLICT (hash) DO NOTHING; + RETURN NEW; +END; +$function$ +; + +-- For dag snapshot transactions +CREATE OR REPLACE FUNCTION insert_into_parent_abstract_transactions_from_block() +RETURNS trigger +LANGUAGE plpgsql +AS $$ +DECLARE + snap_hash varchar; +BEGIN + SELECT snapshot_hash INTO snap_hash + FROM dag_blocks + WHERE hash = NEW.block_hash; + + INSERT INTO abstract_transactions (hash, source_addr, amount, created_at, snapshot_hash) + VALUES (NEW.hash, NEW.source_addr, NEW.amount, NEW.created_at, snap_hash) + ON CONFLICT (hash) DO NOTHING; + + RETURN NEW; +END; +$$; + +DROP TRIGGER IF EXISTS trigger_insert_abstract_transactions_dag_transactions ON dag_transactions; + +CREATE TRIGGER trigger_insert_abstract_transactions_dag_transactions +AFTER INSERT ON public.dag_transactions +FOR EACH ROW +EXECUTE FUNCTION insert_into_parent_abstract_transactions_from_block(); + +-- For metagraph snapshot transactions +CREATE OR REPLACE FUNCTION insert_into_parent_abstract_transactions_from_metagraph_block() +RETURNS trigger +LANGUAGE plpgsql +AS $$ +DECLARE + snap_hash varchar; +BEGIN + SELECT metagraph_snapshot_hash INTO snap_hash + FROM metagraph_blocks + WHERE metagraph_id = NEW.metagraph_id AND hash = NEW.block_hash; + + INSERT INTO abstract_transactions (hash, source_addr, amount, created_at, snapshot_hash) + VALUES (NEW.hash, NEW.source_addr, NEW.amount, NEW.created_at, snap_hash) + ON CONFLICT (hash) DO NOTHING; + + RETURN NEW; +END; +$$; + + +DROP TRIGGER IF EXISTS trigger_insert_abstract_transactions_metagraph_transactions ON metagraph_transactions; + +CREATE TRIGGER trigger_insert_abstract_transactions_metagraph_transactions +AFTER INSERT ON metagraph_transactions +FOR EACH ROW +EXECUTE FUNCTION insert_into_parent_abstract_transactions_from_metagraph_block(); + + +-- Backfill snapshot_hash from DAG child tables + + +UPDATE abstract_transactions +SET snapshot_hash = db.snapshot_hash +FROM dag_transactions dt +JOIN dag_blocks db ON dt.block_hash = db.hash +WHERE abstract_transactions.hash = dt.hash; + +UPDATE abstract_transactions +SET snapshot_hash = child.snapshot_hash +FROM dag_allow_spends child +WHERE abstract_transactions.hash = child.hash; + +UPDATE abstract_transactions +SET snapshot_hash = child.snapshot_hash +FROM dag_expired_spend_transactions child +WHERE abstract_transactions.hash = child.hash; + +UPDATE abstract_transactions +SET snapshot_hash = child.snapshot_hash +FROM dag_spend_transactions child +WHERE abstract_transactions.hash = child.hash; + +UPDATE abstract_transactions +SET snapshot_hash = child.snapshot_hash +FROM dag_token_locks child +WHERE abstract_transactions.hash = child.hash; + +UPDATE abstract_transactions +SET snapshot_hash = child.snapshot_hash +FROM dag_token_unlocks child +WHERE abstract_transactions.hash = child.hash; + +-- Backfill snapshot_hash from Metagraph child tables + +UPDATE abstract_transactions +SET snapshot_hash = child.snapshot_hash +FROM metagraph_allow_spends child +WHERE abstract_transactions.hash = child.hash; + +UPDATE abstract_transactions +SET snapshot_hash = child.snapshot_hash +FROM metagraph_expired_spend_transactions child +WHERE abstract_transactions.hash = child.hash; + +UPDATE abstract_transactions +SET snapshot_hash = child.snapshot_hash +FROM metagraph_spend_transactions child +WHERE abstract_transactions.hash = child.hash; + +UPDATE abstract_transactions +SET snapshot_hash = child.snapshot_hash +FROM metagraph_token_locks child +WHERE abstract_transactions.hash = child.hash; + +UPDATE abstract_transactions +SET snapshot_hash = child.snapshot_hash +FROM metagraph_token_unlocks child +WHERE abstract_transactions.hash = child.hash; + +UPDATE abstract_transactions +SET snapshot_hash = child.snapshot_hash +FROM metagraph_fee_transactions child +WHERE abstract_transactions.hash = child.hash; + +UPDATE abstract_transactions +SET snapshot_hash = mb.metagraph_snapshot_hash +FROM metagraph_transactions mt +JOIN metagraph_blocks mb + ON mt.block_hash = mb.hash AND mt.metagraph_id = mb.metagraph_id +WHERE abstract_transactions.hash = mt.hash; + + +-- Update view + +CREATE OR REPLACE VIEW abstract_transactions_view AS +SELECT + tx.hash, + tx.source_addr, + tx.amount, + tx.created_at, + tx.updated_at, + p.relname AS table_name, + tx.snapshot_hash +FROM abstract_transactions tx +JOIN pg_class p ON tx.tableoid = p.oid +WHERE p.relname <> 'abstract_transactions'::name; + +-- Enforce NOT NULL constraint after data backfill +ALTER TABLE public.abstract_transactions +ALTER COLUMN snapshot_hash SET NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b1fa5c4..095d0eb 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -29,12 +29,13 @@ model abstract_transactions { } model abstract_transactions_view { - hash String @id @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - table_name String + hash String @id @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + table_name String + snapshot_hash String @db.VarChar dag_token_lock dag_token_locks? @relation(fields: [hash], references: [hash], map: "dag_token_locks_ref") metagraph_token_lock metagraph_token_locks? @relation(fields: [hash], references: [hash], map: "metagraph_token_locks_ref") @@ -52,6 +53,10 @@ model abstract_transactions_view { metagraph_expired_spend_transaction metagraph_expired_spend_transactions? @relation(fields: [hash], references: [hash], map: "metagraph_expired_spend_transaction_ref") metagraph_fee_transaction metagraph_fee_transactions? @relation(fields: [hash], references: [hash], map: "metagraph_fee_transaction_ref") + + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "abstract_transactions_view_global_snapshot_ref") + ///metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_balance_change_metagraph_snapshot_fk") + metagraph_snapshot metagraph_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "abstract_transactions_view_metagraph_snapshot_ref") } model addresses { @@ -108,26 +113,27 @@ model dag_allow_spend_approvers { } model dag_allow_spends { - hash String @id(map: "dag_allow_spends_pk") @db.VarChar - source_addr String @db.VarChar + hash String @id(map: "dag_allow_spends_pk") @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - destination_addr String @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + destination_addr String @db.VarChar fee BigInt parent_ordinal BigInt? - parent_hash String? @db.VarChar + parent_hash String? @db.VarChar last_valid_epoch_progress BigInt - round_id String @db.Uuid - ordinal BigInt @unique(map: "dag_allow_spends_ordinal") - snapshot_hash String @db.VarChar + round_id String @db.Uuid + ordinal BigInt @unique(map: "dag_allow_spends_ordinal") + snapshot_hash String @db.VarChar dag_allow_spend_approvers dag_allow_spend_approvers[] - addresses_dag_allow_spends_destination_addrToaddresses addresses @relation("dag_allow_spends_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spends_destination_addr_fk") - addresses_dag_allow_spends_source_addrToaddresses addresses @relation("dag_allow_spends_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spends_source_addr_fk") + addresses_dag_allow_spends_destination_addrToaddresses addresses @relation("dag_allow_spends_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spends_destination_addr_fk") + addresses_dag_allow_spends_source_addrToaddresses addresses @relation("dag_allow_spends_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spends_source_addr_fk") dag_expired_spend_transaction dag_expired_spend_transactions? dag_spend_transaction dag_spend_transactions? abstract_transactions_view abstract_transactions_view? - global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spends_global_snapshot_fk") + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spends_global_snapshot_fk") + @@index([round_id]) } @@ -172,17 +178,18 @@ model dag_reward_transactions { } model dag_spend_transactions { - hash String @id(map: "dag_spend_transactions_pk") @db.VarChar - source_addr String @db.VarChar + hash String @id(map: "dag_spend_transactions_pk") @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - destination_addr String? @db.VarChar - allow_spend_ref String? @db.VarChar @unique - snapshot_hash String @db.VarChar - dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_dag_allow_spends_fk") - addresses addresses? @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_destination_addr_fk") + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + destination_addr String? @db.VarChar + allow_spend_ref String? @unique @db.VarChar + snapshot_hash String @db.VarChar + dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_dag_allow_spends_fk") + addresses addresses? @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_destination_addr_fk") abstract_transactions_view abstract_transactions_view? + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) } model dag_token_locks { @@ -205,16 +212,16 @@ model dag_token_locks { } model dag_token_unlocks { - hash String @id(map: "dag_token_unlocks_pk") @db.VarChar - source_addr String @db.VarChar + hash String @id(map: "dag_token_unlocks_pk") @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - lock_reference_hash String @db.VarChar - snapshot_hash String @db.VarChar - global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_unlock_global_snapshot_fk") - dag_token_lock dag_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_unlocks_token_locks_fk") - addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "ddag_token_unlocks_address_fk") + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + lock_reference_hash String @db.VarChar + snapshot_hash String @db.VarChar + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_unlock_global_snapshot_fk") + dag_token_lock dag_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_unlocks_token_locks_fk") + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "ddag_token_unlocks_address_fk") abstract_transactions_view abstract_transactions_view? @@unique([lock_reference_hash], map: "lock_reference_hash_unique") @@ -272,6 +279,9 @@ model global_snapshots { dag_token_locks dag_token_locks[] dag_token_unlocks dag_token_unlocks[] dag_allow_spends dag_allow_spends[] + dag_spend_transactions dag_spend_transactions[] + dag_expired_spend_transactions dag_expired_spend_transactions[] + abstract_transactions_view abstract_transactions_view[] } model metagraph_allow_spend_approvers { @@ -284,28 +294,28 @@ model metagraph_allow_spend_approvers { } model metagraph_allow_spends { - hash String @id(map: "metagraph_allow_spends_pk") @db.VarChar - source_addr String @db.VarChar + hash String @id(map: "metagraph_allow_spends_pk") @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar - destination_addr String @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + destination_addr String @db.VarChar fee BigInt parent_ordinal BigInt? - parent_hash String? @db.VarChar + parent_hash String? @db.VarChar last_valid_epoch_progress BigInt - round_id String @db.Uuid - ordinal BigInt @unique(map: "metagraph_allow_spends_ordinal") - snapshot_hash String @db.VarChar + round_id String @db.Uuid + ordinal BigInt @unique(map: "metagraph_allow_spends_ordinal") + snapshot_hash String @db.VarChar metagraph_allow_spend_approvers metagraph_allow_spend_approvers[] - addresses_metagraph_allow_spends_destination_addrToaddresses addresses @relation("metagraph_allow_spends_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_allow_spends_destination_addr_fk") - addresses_metagraph_allow_spends_source_addrToaddresses addresses @relation("metagraph_allow_spends_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_allow_spends_source_addr_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") + addresses_metagraph_allow_spends_destination_addrToaddresses addresses @relation("metagraph_allow_spends_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_allow_spends_destination_addr_fk") + addresses_metagraph_allow_spends_source_addrToaddresses addresses @relation("metagraph_allow_spends_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_allow_spends_source_addr_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") metagraph_expired_spend_transaction metagraph_expired_spend_transactions? metagraph_spend_transaction metagraph_spend_transactions? abstract_transactions_view abstract_transactions_view? - metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash]) + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash]) } model metagraph_balance_changes { @@ -340,21 +350,21 @@ model metagraph_blocks { } model metagraph_fee_transactions { - hash String @db.VarChar - source_addr String @db.VarChar + hash String @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar - metagraph_snapshot_hash String @db.VarChar - destination_addr String @db.VarChar - data_update_ref String? @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + metagraph_snapshot_hash String @db.VarChar + destination_addr String @db.VarChar + data_update_ref String? @db.VarChar metagraph_snapshot_ordinal BigInt? - metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "fee_transaction_metagraph_snapshot_fk") - addresses_metagraph_fee_transactions_destination_addrToaddresses addresses @relation("metagraph_fee_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_fee_transactions_destination_addr_fk") - addresses_metagraph_fee_transactions_source_addrToaddresses addresses @relation("metagraph_fee_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_fee_transactions_source_addr_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") - abstract_transactions_view abstract_transactions_view[] + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "fee_transaction_metagraph_snapshot_fk") + addresses_metagraph_fee_transactions_destination_addrToaddresses addresses @relation("metagraph_fee_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_fee_transactions_destination_addr_fk") + addresses_metagraph_fee_transactions_source_addrToaddresses addresses @relation("metagraph_fee_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_fee_transactions_source_addr_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") + abstract_transactions_view abstract_transactions_view? @@id([metagraph_id, hash], map: "fee_transaction_pk") @@unique([hash]) @@ -378,7 +388,7 @@ model metagraph_snapshots { metagraph_id String @db.VarChar ordinal BigInt global_snapshot_hash String? @db.VarChar - hash String @db.VarChar + hash String @unique @db.VarChar height BigInt subheight Int last_snapshot_hash String? @db.VarChar @@ -400,46 +410,50 @@ model metagraph_snapshots { addresses_metagraph_snapshots_owner_addressToaddresses addresses? @relation("metagraph_snapshots_owner_addressToaddresses", fields: [owner_address], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "owner_address_fk") addresses_metagraph_snapshots_staking_addressToaddresses addresses? @relation("metagraph_snapshots_staking_addressToaddresses", fields: [staking_address], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "staking_address_fk") - metagraph_token_locks metagraph_token_locks[] - metagraph_token_unlocks metagraph_token_unlocks[] - metagraph_token_lock_blocks metagraph_token_lock_blocks[] + metagraph_token_locks metagraph_token_locks[] + metagraph_token_unlocks metagraph_token_unlocks[] + metagraph_token_lock_blocks metagraph_token_lock_blocks[] + metagraph_spend_transactions metagraph_spend_transactions[] + metagraph_expired_spend_transactions metagraph_expired_spend_transactions[] + abstract_transactions_view abstract_transactions_view[] @@id([metagraph_id, hash], map: "metagraph_snapshot_pk") @@unique([metagraph_id, ordinal], map: "metagraph_snapshot_unique") } model metagraph_spend_transactions { - hash String @id(map: "metagraph_spend_transactions_pk") @db.VarChar - source_addr String @db.VarChar + hash String @id(map: "metagraph_spend_transactions_pk") @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar - destination_addr String @db.VarChar - allow_spend_ref String? @db.VarChar @unique - snapshot_hash String @db.VarChar - addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_destination_addr_fk") - metagraph_allow_spend metagraph_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "mg_spend_transactions_mg_allow_spends_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_metagraph_id_fk") - abstract_transactions_view abstract_transactions_view[] + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + destination_addr String @db.VarChar + allow_spend_ref String? @unique @db.VarChar + snapshot_hash String @db.VarChar + addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_destination_addr_fk") + metagraph_allow_spend metagraph_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "mg_spend_transactions_mg_allow_spends_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_metagraph_id_fk") + metagraph_snapshot metagraph_snapshots? @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash]) + abstract_transactions_view abstract_transactions_view? } model metagraph_token_locks { - hash String @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar - ordinal BigInt - unlock_epoch BigInt - round_id String @db.VarChar - snapshot_hash String @db.VarChar - metagraph metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") - addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_locks_source_addr_fk") - abstract_transactions_view abstract_transactions_view[] - metagraph_token_unlock metagraph_token_unlocks? - metagraph_snapshot metagraph_snapshots? @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash]) + hash String @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + ordinal BigInt + unlock_epoch BigInt + round_id String @db.VarChar + snapshot_hash String @db.VarChar + metagraph metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_locks_source_addr_fk") + abstract_transactions_view abstract_transactions_view? + metagraph_token_unlock metagraph_token_unlocks? + metagraph_snapshot metagraph_snapshots? @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash]) @@id([metagraph_id, hash], map: "metagraph_token_locks_pk") @@unique([hash]) @@ -447,19 +461,19 @@ model metagraph_token_locks { } model metagraph_token_unlocks { - hash String @id(map: "metagraph_token_unlocks_pk") @db.VarChar - source_addr String @db.VarChar + hash String @id(map: "metagraph_token_unlocks_pk") @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar - lock_reference_hash String @db.VarChar - snapshot_hash String @db.VarChar - addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "address_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") - abstract_transactions_view abstract_transactions_view[] - metagraph_snapshot metagraph_snapshots? @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash]) - metagraph_token_lock metagraph_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_unlocks_token_locks_fk") + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + lock_reference_hash String @db.VarChar + snapshot_hash String @db.VarChar + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "address_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") + abstract_transactions_view abstract_transactions_view? + metagraph_snapshot metagraph_snapshots? @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash]) + metagraph_token_lock metagraph_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_unlocks_token_locks_fk") @@unique([lock_reference_hash], map: "metagraph_token_unlocks_locks_fk") } @@ -504,28 +518,30 @@ model metagraphs { } model dag_expired_spend_transactions { - hash String @id(map: "dag_expired_spend_transactions_pk") @db.VarChar - source_addr String @db.VarChar + hash String @id(map: "dag_expired_spend_transactions_pk") @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - allow_spend_ref String @db.VarChar @unique - snapshot_hash String @db.VarChar - dag_allow_spend dag_allow_spends @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_expired_spend_transactions_dag_allow_spends_fk") - abstract_transactions_view abstract_transactions_view[] + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + allow_spend_ref String @unique @db.VarChar + snapshot_hash String @db.VarChar + dag_allow_spend dag_allow_spends @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_expired_spend_transactions_dag_allow_spends_fk") + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) + abstract_transactions_view abstract_transactions_view? } model metagraph_expired_spend_transactions { - hash String @id(map: "metagraph_expired_spend_transactions_pk") @db.VarChar - source_addr String @db.VarChar + hash String @id(map: "metagraph_expired_spend_transactions_pk") @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar - allow_spend_ref String @db.VarChar @unique - snapshot_hash String @db.VarChar - metagraph_allow_spend metagraph_allow_spends @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_expired_spend_transactions_metagraph_allow_spends_fk") - abstract_transactions_view abstract_transactions_view[] + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + allow_spend_ref String @unique @db.VarChar + snapshot_hash String @db.VarChar + metagraph_allow_spend metagraph_allow_spends @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_expired_spend_transactions_metagraph_allow_spends_fk") + metagraph_snapshot metagraph_snapshots? @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash]) + abstract_transactions_view abstract_transactions_view? } model metagraph_token_lock_blocks { @@ -548,7 +564,7 @@ model delegate_stake_create_events { node_id String @db.VarChar amount BigInt fee BigInt @default(0) - lock_reference_hash String @db.VarChar + lock_reference_hash String @db.VarChar parent_hash String @db.VarChar global_snapshot_hash String @db.VarChar is_update Boolean @@ -568,7 +584,7 @@ model delegate_stake_create_events { model delegate_stake_withdraw_events { hash String @id @db.VarChar source_addr String @db.VarChar - stake_create_hash String @unique @db.VarChar + stake_create_hash String @unique @db.VarChar global_snapshot_hash String @db.VarChar created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) diff --git a/src/handlers/actionsHandler.ts b/src/handlers/actionsHandler.ts index b828618..008b36f 100644 --- a/src/handlers/actionsHandler.ts +++ b/src/handlers/actionsHandler.ts @@ -40,12 +40,7 @@ const tableFilter = (event) => { }; const currencyId = (transaction) => - transaction.metagraph_token_lock?.metagraph_id ?? - transaction.metagraph_token_unlock?.metagraph_id ?? - transaction.metagraph_allow_spend?.metagraph_id ?? - transaction.metagraph_spend_transaction?.metagraph_id ?? - transaction.metagraph_fee_transaction?.metagraph_id ?? - null; + transaction.metagraph_snapshot?.metagraph_id ?? null; const actionResponse = (transaction) => ({ type: getTransactionType(transaction.table_name), @@ -56,16 +51,21 @@ const actionResponse = (transaction) => ({ destination: transaction.destination_addr ?? null, unlockEpoch: transaction.dag_allow_spend?.last_valid_epoch_progress ?? - transaction.dag_token_lock?.unlock_epoch ?? null, + transaction.dag_token_lock?.unlock_epoch ?? + null, parentHash: transaction.dag_spend_transaction?.allow_spend_ref ?? - transaction.dag_token_unlock?.lock_reference_hash ?? null, + transaction.dag_token_unlock?.lock_reference_hash ?? + null, timestamp: transaction.created_at, + globalSnapshotOrdinal: transaction.global_snapshot?.ordinal, + metagraphSnapshotOrdinal: transaction.metagraph_snapshot?.ordinal, }); export const actionsResponse = (ts) => ts.map(actionResponse); const dagInclude = { + global_snapshot: { select: { hash: true, ordinal: true } }, dag_token_lock: { select: { unlock_epoch: true } }, dag_allow_spend: { select: { last_valid_epoch_progress: true } }, dag_spend_transaction: { select: { allow_spend_ref: true } }, @@ -74,6 +74,7 @@ const dagInclude = { }; const metagraphInclude = { + metagraph_snapshot: { select: { hash: true, ordinal: true } }, metagraph_token_lock: { select: { unlock_epoch: true } }, metagraph_allow_spend: { select: { last_valid_epoch_progress: true } }, metagraph_spend_transaction: { select: { allow_spend_ref: true } }, @@ -101,40 +102,6 @@ export const dagActions = async ( ); }; -const tokenLockGlobalSnapshotCond = (filter) => ({ - dag_token_lock: { - global_snapshot: filter, - }, -}); -const tokenUnlockGlobalSnapshotCond = (filter) => ({ - dag_token_unlock: { - dag_token_lock: { - global_snapshot: filter, - }, - }, -}); -const allowSpendGlobalSnapshotCond = (filter) => ({ - dag_allow_spend: { - global_snapshot: filter, - }, -}); -const spendTxGlobalSnapshotCond = (filter) => ({ - dag_spend_transaction: allowSpendGlobalSnapshotCond(filter), -}); -const expiredSpendTxGlobalSnapshotCond = (filter) => ({ - dag_expired_spend_transaction: allowSpendGlobalSnapshotCond(filter), -}); - -const filterByGlobalSnapshot = (filter) => ({ - OR: [ - tokenLockGlobalSnapshotCond(filter), - tokenUnlockGlobalSnapshotCond(filter), - allowSpendGlobalSnapshotCond(filter), - spendTxGlobalSnapshotCond(filter), - expiredSpendTxGlobalSnapshotCond(filter), - ], -}); - export const globalSnapshotActions = async ( event: APIGatewayProxyEvent ): Promise => { @@ -150,7 +117,7 @@ export const globalSnapshotActions = async ( hashCursor, { where: { - ...filterByGlobalSnapshot(filter), + global_snapshot: filter, table_name: { in: selectedTables }, }, include: dagInclude, @@ -201,55 +168,6 @@ export const dagAddressActions = async ( } }; -const metagraphIdCond = (metagraph_id) => ({ - OR: [ - { metagraph_token_lock: { metagraph_id } }, - { metagraph_token_unlock: { metagraph_id } }, - { metagraph_allow_spend: { metagraph_id } }, - { metagraph_spend_transaction: { metagraph_id } }, - { metagraph_expired_spend_transaction: { metagraph_id } }, - { metagraph_fee_transaction: { metagraph_id } }, - ], -}); - -const tokenLockMetagraphSnapshotCond = (filter) => ({ - metagraph_token_lock: { - metagraph_snapshot: filter, - }, -}); -const tokenUnlockMetagraphSnapshotCond = (filter) => ({ - metagraph_token_unlock: { - token_lock: { - metagraph_snapshot: filter, - }, - }, -}); -const allowSpendMetagraphSnapshotCond = (filter) => ({ - metagraph_allow_spend: { - metagraph_snapshot: filter, - }, -}); -const spendTxMetagraphSnapshotCond = (filter) => ({ - metagraph_spend_transaction: allowSpendMetagraphSnapshotCond(filter), -}); -const expiredSpendTxMetagraphSnapshotCond = (filter) => ({ - metagraph_expired_spend_transaction: allowSpendMetagraphSnapshotCond(filter), -}); -const feeTxMetagraphSnapshotCond = (filter) => ({ - metagraph_fee_transaction: { metagraph_snapshot: filter }, -}); - -const filterByMetagraphSnapshot = (filter) => ({ - OR: [ - tokenLockMetagraphSnapshotCond(filter), - tokenUnlockMetagraphSnapshotCond(filter), - allowSpendMetagraphSnapshotCond(filter), - spendTxMetagraphSnapshotCond(filter), - expiredSpendTxMetagraphSnapshotCond(filter), - feeTxMetagraphSnapshotCond(filter), - ], -}); - export const currencyActions = async ( event: APIGatewayProxyEvent ): Promise => { @@ -264,7 +182,7 @@ export const currencyActions = async ( hashCursor, { where: { - ...metagraphIdCond(metagraph_id), + metagraph_snapshot: { metagraph_id }, table_name: { in: selectedTables }, }, include: metagraphInclude, @@ -293,8 +211,7 @@ export const currencySnapshotActions = async ( hashCursor, { where: { - ...metagraphIdCond(metagraph_id), - ...filterByMetagraphSnapshot(filter), + metagraph_snapshot: { metagraph_id, ...filter }, table_name: { in: selectedTables }, }, include: metagraphInclude, @@ -322,7 +239,7 @@ export const currencyAddressActions = async ( hashCursor, { where: { - ...metagraphIdCond(metagraph_id), + metagraph_snapshot: { metagraph_id }, OR: [ { source_addr: address }, { metagraph_allow_spend: { destination_addr: address } }, From 7b846b04d7e564cbfd4bdcd6068eeb75cb6e71e0 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Tue, 13 May 2025 10:46:31 -0300 Subject: [PATCH 38/63] add fee transactions endpoint and tests --- prisma/schema.prisma | 6 +- prisma/seed.ts | 207 +++++++++-------- routes/metagraph.yml | 6 + src/handlers/metagraphHandler.ts | 57 +++-- src/pagination.ts | 1 + src/response.ts | 14 +- tests/handlers/allowSpendsHandler.test.ts | 12 +- tests/handlers/dagHandler.test.ts | 15 +- .../handlers/delegatedStakingHandler.test.ts | 9 +- tests/handlers/metagraphHandler.test.ts | 215 +++++++++++++++--- 10 files changed, 361 insertions(+), 181 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 095d0eb..8a83741 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -350,7 +350,7 @@ model metagraph_blocks { } model metagraph_fee_transactions { - hash String @db.VarChar + hash String @db.VarChar @id source_addr String @db.VarChar amount BigInt created_at DateTime @default(now()) @db.Timestamp(6) @@ -365,9 +365,7 @@ model metagraph_fee_transactions { addresses_metagraph_fee_transactions_source_addrToaddresses addresses @relation("metagraph_fee_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_fee_transactions_source_addr_fk") metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") abstract_transactions_view abstract_transactions_view? - - @@id([metagraph_id, hash], map: "fee_transaction_pk") - @@unique([hash]) + @@unique([metagraph_id, hash], map: "metagraph_id_hash") } model metagraph_reward_transactions { diff --git a/prisma/seed.ts b/prisma/seed.ts index 886e057..714363b 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -13,7 +13,6 @@ import { } from "@prisma/client"; import { randomUUID } from "crypto"; - export const prisma = new PrismaClient(); export const data_addresses = [ @@ -36,7 +35,7 @@ export const data_addresses = [ address: "DAG8bxrEjLbqPeMsN233MGFGqrLcpgbdXwQzYrYv", created_at: new Date("2025-04-02T00:00:02Z"), updated_at: new Date(), - } + }, ]; export const data_global_snapshots = [ @@ -155,63 +154,62 @@ export const data_metagraphs = [ ]; export const data_metagraph_snapshots = [ - { - metagraph_id: data_metagraphs[0].id, - global_snapshot_hash: data_global_snapshots[0].hash, - ordinal: 1n, - hash: "1c1e16746af43bc1f93aadaf006cd981852a706e74f1d24b66a7e5e9fa4188e5", - height: 1n, - subheight: 1, - owner_address: null, - staking_address: null, - epoch_progress: 1n, - size: 1n, - last_snapshot_hash: - "0000000000000000000000000000000000000000000000000000000000000000", - fee: 1n, - version: "0.0.1", - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, - { - metagraph_id: data_metagraphs[0].id, - global_snapshot_hash: data_global_snapshots[1].hash, - ordinal: 2n, - hash: "9bf40b2d2e355401bbca7a7924880ab799ffa91e95d1b93f3298b549758aac64", - - height: 2n, - subheight: 2, - owner_address: null, - staking_address: null, - epoch_progress: 1n, - size: 1n, - last_snapshot_hash: - "1c1e16746af43bc1f93aadaf006cd981852a706e74f1d24b66a7e5e9fa4188e5", - fee: 1n, - version: "0.0.1", - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, - { - metagraph_id: data_metagraphs[1].id, - global_snapshot_hash: data_global_snapshots[1].hash, - ordinal: 1n, - hash: "9bf40b2d2e3123123123123123123123123123123123123123123549758aac64", - - height: 3n, - subheight: 2, - owner_address: null, - staking_address: null, - epoch_progress: 1n, - size: 1n, - last_snapshot_hash: - "9bf40b2d2e355401bbca7a7924880ab799ffa91e95d1b93f3298b549758aac64", - fee: 1n, - version: "0.0.1", - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, - + { + metagraph_id: data_metagraphs[0].id, + global_snapshot_hash: data_global_snapshots[0].hash, + ordinal: 1n, + hash: "1c1e16746af43bc1f93aadaf006cd981852a706e74f1d24b66a7e5e9fa4188e5", + height: 1n, + subheight: 1, + owner_address: null, + staking_address: null, + epoch_progress: 1n, + size: 1n, + last_snapshot_hash: + "0000000000000000000000000000000000000000000000000000000000000000", + fee: 1n, + version: "0.0.1", + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + metagraph_id: data_metagraphs[0].id, + global_snapshot_hash: data_global_snapshots[1].hash, + ordinal: 2n, + hash: "9bf40b2d2e355401bbca7a7924880ab799ffa91e95d1b93f3298b549758aac64", + + height: 2n, + subheight: 2, + owner_address: null, + staking_address: null, + epoch_progress: 1n, + size: 1n, + last_snapshot_hash: + "1c1e16746af43bc1f93aadaf006cd981852a706e74f1d24b66a7e5e9fa4188e5", + fee: 1n, + version: "0.0.1", + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + metagraph_id: data_metagraphs[1].id, + global_snapshot_hash: data_global_snapshots[1].hash, + ordinal: 1n, + hash: "9bf40b2d2e3123123123123123123123123123123123123123123549758aac64", + + height: 3n, + subheight: 2, + owner_address: null, + staking_address: null, + epoch_progress: 1n, + size: 1n, + last_snapshot_hash: + "9bf40b2d2e355401bbca7a7924880ab799ffa91e95d1b93f3298b549758aac64", + fee: 1n, + version: "0.0.1", + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, ]; export const data_metagraph_blocks = [ @@ -311,18 +309,18 @@ export const data_dag_token_locks = [ // Test data for DAG token unlocks export const data_dag_token_unlocks = [ { - hash: 'token-unlock-hash-0010', + hash: "token-unlock-hash-0010", source_addr: data_addresses[0].address, amount: 1000n, lock_reference_hash: data_dag_token_locks[0].hash, snapshot_hash: data_global_snapshots[1].hash, - } + }, ]; // Test data for metagraph token locks export const data_metagraph_token_locks = [ { - hash: 'metagraph-token-lock-hash-0010', + hash: "metagraph-token-lock-hash-0010", metagraph_id: data_metagraphs[0].id, source_addr: data_addresses[0].address, amount: 3000n, @@ -332,7 +330,7 @@ export const data_metagraph_token_locks = [ snapshot_hash: data_metagraph_snapshots[0].hash, }, { - hash: 'metagraph-token-lock-hash-0020', + hash: "metagraph-token-lock-hash-0020", metagraph_id: data_metagraphs[0].id, source_addr: data_addresses[0].address, amount: 4000n, @@ -341,22 +339,22 @@ export const data_metagraph_token_locks = [ round_id: randomUUID(), snapshot_hash: data_metagraph_snapshots[1].hash, }, - { - hash: 'metagraph-token-lock-hash-0030', - metagraph_id: data_metagraphs[0].id, - source_addr: data_addresses[0].address, - amount: 4000n, - ordinal: 21n, - unlock_epoch: 25n, - round_id: randomUUID(), - snapshot_hash: data_metagraph_snapshots[1].hash, - } + { + hash: "metagraph-token-lock-hash-0030", + metagraph_id: data_metagraphs[0].id, + source_addr: data_addresses[0].address, + amount: 4000n, + ordinal: 21n, + unlock_epoch: 25n, + round_id: randomUUID(), + snapshot_hash: data_metagraph_snapshots[1].hash, + }, ]; // Test data for metagraph token unlocks export const data_metagraph_token_unlocks = [ { - hash: 'metagraph-token-unlock-hash-001', + hash: "metagraph-token-unlock-hash-001", metagraph_id: data_metagraphs[0].id, source_addr: data_addresses[0].address, amount: 3000n, @@ -364,48 +362,47 @@ export const data_metagraph_token_unlocks = [ snapshot_hash: data_metagraph_snapshots[1].hash, }, { - hash: 'metagraph-token-unlock-hash-002', - metagraph_id: data_metagraphs[0].id, - source_addr: data_addresses[0].address, - amount: 3000n, - lock_reference_hash: data_metagraph_token_locks[2].hash, - snapshot_hash: data_metagraph_snapshots[1].hash, - } + hash: "metagraph-token-unlock-hash-002", + metagraph_id: data_metagraphs[0].id, + source_addr: data_addresses[0].address, + amount: 3000n, + lock_reference_hash: data_metagraph_token_locks[2].hash, + snapshot_hash: data_metagraph_snapshots[1].hash, + }, ]; - export async function seed() { - await prisma.addresses.createManyAndReturn({ data: data_addresses }); + await prisma.addresses.createMany({ data: data_addresses }); - await prisma.global_snapshots.createManyAndReturn({ + await prisma.global_snapshots.createMany({ data: data_global_snapshots, }); - await prisma.metagraphs.createManyAndReturn({ data: data_metagraphs }); + await prisma.metagraphs.createMany({ data: data_metagraphs }); - await prisma.metagraph_snapshots.createManyAndReturn({ + await prisma.metagraph_snapshots.createMany({ data: data_metagraph_snapshots, }); - // Create DAG token locks - await prisma.dag_token_locks.createManyAndReturn({ - data: data_dag_token_locks, - }); - - // Create DAG token unlocks - await prisma.dag_token_unlocks.createManyAndReturn({ - data: data_dag_token_unlocks, - }); - - // Create metagraph token locks - await prisma.metagraph_token_locks.createManyAndReturn({ - data: data_metagraph_token_locks, - }); - - // Create metagraph token unlocks - await prisma.metagraph_token_unlocks.createManyAndReturn({ - data: data_metagraph_token_unlocks, - }); + // Create DAG token locks + await prisma.dag_token_locks.createMany({ + data: data_dag_token_locks, + }); + + // Create DAG token unlocks + await prisma.dag_token_unlocks.createMany({ + data: data_dag_token_unlocks, + }); + + // Create metagraph token locks + await prisma.metagraph_token_locks.createMany({ + data: data_metagraph_token_locks, + }); + + // Create metagraph token unlocks + await prisma.metagraph_token_unlocks.createMany({ + data: data_metagraph_token_unlocks, + }); } export async function resetDatabase() { diff --git a/routes/metagraph.yml b/routes/metagraph.yml index 3368524..335bb1d 100644 --- a/routes/metagraph.yml +++ b/routes/metagraph.yml @@ -70,6 +70,12 @@ ccyBalanceByAddress: - httpApi: path: /currency/{identifier}/addresses/{address}/balance method: GET +ccyFeeTransactions: + handler: src/handlers/metagraphHandler.currencyFeeTransactions + events: + - httpApi: + path: /currency/{identifier}/fee-transactions + method: GET ccyFeeTransaction: handler: src/handlers/metagraphHandler.currencyFeeTransaction events: diff --git a/src/handlers/metagraphHandler.ts b/src/handlers/metagraphHandler.ts index 31ef14c..32310c9 100644 --- a/src/handlers/metagraphHandler.ts +++ b/src/handlers/metagraphHandler.ts @@ -28,7 +28,6 @@ import { toNumber, isFinite } from "lodash"; const prisma = new PrismaClient(); - const metagraphIdExists = async (metagraph_id) => { return prisma.metagraphs.findFirst({ where: { id: metagraph_id }, @@ -77,7 +76,7 @@ export const currencySnapshots = async ( try { const { identifier: metagraph_id } = event.pathParameters || {}; - if (!(await metagraphIdExists(metagraph_id))){ + if (!(await metagraphIdExists(metagraph_id))) { return notFoundResponse("metagraph"); } @@ -98,7 +97,7 @@ export const currencySnapshots = async ( toCursor, fromCursor, { - where: { metagraph_id }, + where: { metagraph_id }, include: { metagraph_blocks: true }, orderBy: { ordinal: "desc" }, }, @@ -151,7 +150,7 @@ export const currencySnapshot = async ( try { const { identifier: metagraph_id, term } = event.pathParameters || {}; - if (!(await metagraphIdExists(metagraph_id))){ + if (!(await metagraphIdExists(metagraph_id))) { return notFoundResponse("metagraph"); } @@ -177,7 +176,7 @@ export const currencySnapshotRewards = async ( try { const { identifier: metagraph_id, term } = event.pathParameters || {}; - if (!(await metagraphIdExists(metagraph_id))){ + if (!(await metagraphIdExists(metagraph_id))) { return notFoundResponse("metagraph"); } @@ -264,7 +263,7 @@ export const currencySnapshotTransactions = async ( try { const { identifier: metagraph_id, term } = event.pathParameters || {}; - if (!(await metagraphIdExists(metagraph_id))){ + if (!(await metagraphIdExists(metagraph_id))) { return notFoundResponse("metagraph"); } @@ -299,7 +298,7 @@ export const currencyBlock = async ( try { const { identifier: metagraph_id, hash } = event.pathParameters || {}; - if (!(await metagraphIdExists(metagraph_id))){ + if (!(await metagraphIdExists(metagraph_id))) { return notFoundResponse("metagraph"); } @@ -324,7 +323,7 @@ export const currencyTransactions = async ( try { const { identifier: metagraph_id } = event.pathParameters || {}; - if (!(await metagraphIdExists(metagraph_id))){ + if (!(await metagraphIdExists(metagraph_id))) { return notFoundResponse("metagraph"); } @@ -370,7 +369,7 @@ export const currencyTransactionsByAddress = async ( try { const { identifier: metagraph_id, address } = event.pathParameters || {}; - if (!(await metagraphIdExists(metagraph_id))){ + if (!(await metagraphIdExists(metagraph_id))) { return notFoundResponse("metagraph"); } @@ -393,11 +392,10 @@ export const currencyTransactionsBySource = async ( try { const { identifier: metagraph_id, address } = event.pathParameters || {}; - if (!(await metagraphIdExists(metagraph_id))){ - return notFoundResponse("metagraph"); + if (!(await metagraphIdExists(metagraph_id))) { + return notFoundResponse("metagraph"); } - const where = { where: { metagraph_id, source_addr: address } }; return metagraphTransactionsQuery(where, event); @@ -472,10 +470,16 @@ export const currencyFeeTransaction = async ( try { const { identifier: metagraph_id, hash } = event.pathParameters || {}; + if (!metagraph_id || !hash) { + throw new Error("Missing required path parameters: identifier or hash."); + } + const transaction = await prisma.metagraph_fee_transactions.findUnique({ where: { - metagraph_id: metagraph_id!, - hash: hash!, + metagraph_id_hash: { + metagraph_id, + hash, + }, }, include: { metagraph_snapshot: { select: { hash: true, ordinal: true } }, @@ -519,6 +523,24 @@ const metagraphFeeTransactionsQuery = async ( } }; +export const currencyFeeTransactions = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const { identifier: metagraph_id } = event.pathParameters || {}; + + const where = { + where: { + metagraph_id: metagraph_id, + }, + }; + + return metagraphFeeTransactionsQuery(where, event); + } catch (error) { + return handleError(error); + } +}; + export const currencySnapshotFeeTransactions = async ( event: APIGatewayProxyEvent ): Promise => { @@ -531,8 +553,10 @@ export const currencySnapshotFeeTransactions = async ( const where = { where: { - metagraph_id: metagraph_id, - ...mgSnapshotWhere, + metagraph_snapshot: { + metagraph_id: metagraph_id, + ...mgSnapshotWhere, + }, }, }; @@ -556,7 +580,6 @@ export const currencyFeeTransactionsByAddress = async ( OR: [{ source_addr: address }, { destination_addr: address }], }, }; - return metagraphFeeTransactionsQuery(where, event); } catch (error) { return handleError(error); diff --git a/src/pagination.ts b/src/pagination.ts index 48a1567..988a9b3 100644 --- a/src/pagination.ts +++ b/src/pagination.ts @@ -93,6 +93,7 @@ export const paginatedQuery = async ( ...baseQuery, ...(pagination ? pageQueryParams : {}), }; + const rawResults = await findMany(pagedQuery); const pageSize = pageQueryParams.take diff --git a/src/response.ts b/src/response.ts index fef1f1f..d052d3a 100644 --- a/src/response.ts +++ b/src/response.ts @@ -33,7 +33,7 @@ const commonSnapshotResponse = (snapshot, blocksProperty) => ({ export const globalSnapshotResponse = (snapshot) => ({ ...commonSnapshotResponse(snapshot, "dag_blocks"), - metagraphSnashotCount: snapshot.metagraph_snapshot_count + metagraphSnashotCount: snapshot.metagraph_snapshot_count, }); export const rewardsResponse = (rs) => rs.map(rewardResponse); @@ -93,6 +93,7 @@ export const balanceResponse = (balance) => ({ export const metagraphSnapshotsResponse = (ss) => ss.map(metagraphSnapshotResponse); + export const metagraphSnapshotResponse = (snapshot) => ({ ...commonSnapshotResponse(snapshot, "metagraph_blocks"), fee: snapshot.fee, @@ -110,13 +111,16 @@ export const metagraphBlockResponse = (block) => ({ export const metagraphTransactionsResponse = (ts) => ts.map(metagraphTransactionResponse); + export const metagraphTransactionResponse = (t) => transactionResponse(t, t.metagraph_blocks.metagraph_snapshot); export const metagraphFeeTransactionsResponse = (ts) => - ts.map(metagraphTransactionResponse); -export const metagraphFeeTransactionResponse = (t) => - transactionResponse(t, t.metagraph_snapshot); + ts.map(metagraphFeeTransactionResponse); + +export const metagraphFeeTransactionResponse = (t) => { + return transactionResponse(t, t.metagraph_snapshot); +}; export const metagraphsResponse = (mgs) => mgs.map(metagraphResponse); export const metagraphResponse = (mg) => ({ @@ -146,7 +150,7 @@ export const successResponse = (data: any): APIGatewayProxyResult => ({ ), }); -export const notFoundResponse = (msg=""): APIGatewayProxyResult => ({ +export const notFoundResponse = (msg = ""): APIGatewayProxyResult => ({ statusCode: 404, body: JSON.stringify({ message: "Not found", errors: [msg] }), }); diff --git a/tests/handlers/allowSpendsHandler.test.ts b/tests/handlers/allowSpendsHandler.test.ts index f5d6d60..4df9b48 100644 --- a/tests/handlers/allowSpendsHandler.test.ts +++ b/tests/handlers/allowSpendsHandler.test.ts @@ -166,25 +166,25 @@ export const data_metagraph_expired_spend_transactions = [ ]; const seedData = async () => { - await prisma.dag_allow_spends.createManyAndReturn({ + await prisma.dag_allow_spends.createMany({ data: data_dag_allow_spends, }); - await prisma.dag_spend_transactions.createManyAndReturn({ + await prisma.dag_spend_transactions.createMany({ data: data_dag_spend_transactions, }); - await prisma.dag_expired_spend_transactions.createManyAndReturn({ + await prisma.dag_expired_spend_transactions.createMany({ data: data_dag_expired_spend_transactions, }); - await prisma.metagraph_allow_spends.createManyAndReturn({ + await prisma.metagraph_allow_spends.createMany({ data: data_metagraph_allow_spends, }); - await prisma.metagraph_spend_transactions.createManyAndReturn({ + await prisma.metagraph_spend_transactions.createMany({ data: data_metagraph_spend_transactions, }); - await prisma.metagraph_expired_spend_transactions.createManyAndReturn({ + await prisma.metagraph_expired_spend_transactions.createMany({ data: data_metagraph_expired_spend_transactions, }); }; diff --git a/tests/handlers/dagHandler.test.ts b/tests/handlers/dagHandler.test.ts index 3eb333a..d6219e0 100644 --- a/tests/handlers/dagHandler.test.ts +++ b/tests/handlers/dagHandler.test.ts @@ -11,7 +11,6 @@ import { prisma, } from "../../prisma/seed"; - export const data_dag_blocks = [ { hash: "16593f9f612a453c28669b86067e097990ee18742e905afa330674636ca1431c", @@ -87,21 +86,19 @@ export const data_dag_balance_changes = [ }, ]; - const seedData = async () => { - - await prisma.dag_blocks.createManyAndReturn({ + await prisma.dag_blocks.createMany({ data: data_dag_blocks, }); - await prisma.dag_transactions.createManyAndReturn({ + await prisma.dag_transactions.createMany({ data: data_dag_transactions, }); - await prisma.dag_balance_changes.createManyAndReturn({ + await prisma.dag_balance_changes.createMany({ data: data_dag_balance_changes, }); -} +}; beforeAll(async () => { await seedData(); @@ -157,7 +154,9 @@ describe("DAG Handler Integration Tests", () => { expect(snapshot.subHeight).toBe(Number(testSnapshot.subheight)); expect(Array.isArray(snapshot.blocks)).toBe(true); expect(snapshot.timestamp).toBeDefined(); - expect(snapshot.metagraphSnashotCount).toBe(Number(testSnapshot.metagraph_snapshot_count)); + expect(snapshot.metagraphSnashotCount).toBe( + Number(testSnapshot.metagraph_snapshot_count) + ); }); it("should handle pagination correctly", async () => { diff --git a/tests/handlers/delegatedStakingHandler.test.ts b/tests/handlers/delegatedStakingHandler.test.ts index c2885ab..69b5926 100644 --- a/tests/handlers/delegatedStakingHandler.test.ts +++ b/tests/handlers/delegatedStakingHandler.test.ts @@ -88,20 +88,19 @@ const data_delegate_stake_rewards = [ }, ]; const seedData = async () => { - - await prisma.delegate_stake_create_events.createManyAndReturn({ + await prisma.delegate_stake_create_events.createMany({ data: data_delegate_stake_create_events, }); - await prisma.delegate_stake_withdraw_events.createManyAndReturn({ + await prisma.delegate_stake_withdraw_events.createMany({ data: data_delegate_stake_withdraw_events, }); - await prisma.delegate_stake_balance_changes.createManyAndReturn({ + await prisma.delegate_stake_balance_changes.createMany({ data: data_delegate_stake_balance_changes, }); - await prisma.delegate_stake_rewards.createManyAndReturn({ + await prisma.delegate_stake_rewards.createMany({ data: data_delegate_stake_rewards, }); }; diff --git a/tests/handlers/metagraphHandler.test.ts b/tests/handlers/metagraphHandler.test.ts index 0fb7cdb..c46c0c2 100644 --- a/tests/handlers/metagraphHandler.test.ts +++ b/tests/handlers/metagraphHandler.test.ts @@ -13,7 +13,6 @@ import { prisma, } from "../../prisma/seed"; - const data_metagraph_blocks = [ { metagraph_id: data_metagraphs[0].id, @@ -110,7 +109,7 @@ const data_metagraph_balance_changes = [ updated_at: new Date(), }, { - metagraph_id: data_metagraphs[1].id, + metagraph_id: data_metagraphs[1].id, metagraph_snapshot_hash: data_metagraph_snapshots[2].hash, snapshot_ordinal: data_metagraph_snapshots[2].ordinal, address: data_metagraph_transactions[2].destination_addr, @@ -120,29 +119,58 @@ const data_metagraph_balance_changes = [ }, ]; -const seedData = async () => { +const data_metagraph_fee_transactions = [ + { + metagraph_id: data_metagraphs[0].id, + metagraph_snapshot_hash: data_metagraph_snapshots[0].hash, + metagraph_snapshot_ordinal: data_metagraph_snapshots[0].ordinal, + hash: "39c9909d3b00552beaa5487c38110267675ab212b3b97654fc5ca9917cb7e72b", + source_addr: data_addresses[0].address, + destination_addr: data_addresses[1].address, + amount: 90790983n, + data_update_ref: + "5056fdfbba0637dcecfc0b7fa3f441c745c852cf850c3bfc0dbc8a7410b8d722", + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + metagraph_id: data_metagraphs[1].id, + metagraph_snapshot_hash: data_metagraph_snapshots[2].hash, + metagraph_snapshot_ordinal: data_metagraph_snapshots[2].ordinal, + hash: "39c990000000000000000000000000000000000000007654fc5ca9917cb7e72b", + source_addr: data_addresses[2].address, + destination_addr: data_addresses[3].address, + amount: 10090983n, + data_update_ref: + "39c9909d3b00552beaa5487c38110267675ab212b3b97654fc5ca9917cb7e72b", + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; - - await prisma.metagraph_blocks.createManyAndReturn({ +const seedData = async () => { + await prisma.metagraph_blocks.createMany({ data: data_metagraph_blocks, }); - await prisma.metagraph_transactions.createManyAndReturn({ + await prisma.metagraph_transactions.createMany({ data: data_metagraph_transactions, }); - await prisma.metagraph_balance_changes.createManyAndReturn({ + await prisma.metagraph_balance_changes.createMany({ data: data_metagraph_balance_changes, }); -} - beforeAll(async () => { - await seedData(); + await prisma.metagraph_fee_transactions.createMany({ + data: data_metagraph_fee_transactions, }); +}; +beforeAll(async () => { + await seedData(); +}); const validateTransaction = (tx, expected) => { - const dbBlock = data_metagraph_blocks.filter( (_dbBlock) => _dbBlock.hash === expected.block_hash )[0]; @@ -161,19 +189,38 @@ const validateTransaction = (tx, expected) => { expect(+new Date(tx.timestamp)).toBe(+new Date(expected.created_at)); }; +const validateFeeTransaction = (tx, expected) => { + const dbSnapshot = data_metagraph_snapshots.filter( + (_dbSnapshot) => _dbSnapshot.hash === expected.metagraph_snapshot_hash + )[0]; + + expect(tx.hash).toBe(expected.hash); + expect(tx.source).toBe(expected.source_addr); + expect(tx.destination).toBe(expected.destination_addr); + expect(tx.amount).toBe(Number(expected.amount)); + expect(tx.snapshotHash).toBe(dbSnapshot.hash); + expect(tx.snapshotOrdinal).toBe(Number(dbSnapshot.ordinal)); + expect(+new Date(tx.timestamp)).toBe(+new Date(expected.created_at)); +}; + describe("Metagraph Handler Integration Tests", () => { describe("currencySnapshots", () => { it("should return a list of metagraph snapshots", async () => { - const metagraph_id = data_metagraphs[0].id + const metagraph_id = data_metagraphs[0].id; - const event = createAPIGatewayEvent({identifier: metagraph_id}, { limit: "10" }); + const event = createAPIGatewayEvent( + { identifier: metagraph_id }, + { limit: "10" } + ); const response: APIGatewayProxyResult = await metagraphHandler.currencySnapshots(event); expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); - const expected = data_metagraph_snapshots.filter(ms => ms.metagraph_id === metagraph_id) + const expected = data_metagraph_snapshots.filter( + (ms) => ms.metagraph_id === metagraph_id + ); expect(body.data.length).toBe(expected.length); @@ -192,8 +239,11 @@ describe("Metagraph Handler Integration Tests", () => { }); it("should handle pagination correctly", async () => { - const metagraph_id = data_metagraphs[0].id - const event = createAPIGatewayEvent({identifier: metagraph_id}, { limit: "1" }); + const metagraph_id = data_metagraphs[0].id; + const event = createAPIGatewayEvent( + { identifier: metagraph_id }, + { limit: "1" } + ); const response: APIGatewayProxyResult = await metagraphHandler.currencySnapshots(event); @@ -206,7 +256,7 @@ describe("Metagraph Handler Integration Tests", () => { // Try getting the next page const nextEvent = createAPIGatewayEvent( - {identifier: metagraph_id}, + { identifier: metagraph_id }, { limit: "1", next: body.meta.next, @@ -223,29 +273,31 @@ describe("Metagraph Handler Integration Tests", () => { }); it("should return not found on invalid metagraph", async () => { - const identifier = "1234567" + const identifier = "1234567"; const event = createAPIGatewayEvent({ identifier }, { limit: "10" }); const response: APIGatewayProxyResult = await metagraphHandler.currencySnapshots(event); expect(response.statusCode).toBe(404); - expect(response.body).toBe('{"message":"Not found","errors":["metagraph"]}'); - + expect(response.body).toBe( + '{"message":"Not found","errors":["metagraph"]}' + ); }); - }); describe("currencyTransactions", () => { it("should return transactions sorted by snapshot ordinal descending", async () => { - const metagraph_id = data_metagraphs[0].id + const metagraph_id = data_metagraphs[0].id; - const event = createAPIGatewayEvent({identifier: metagraph_id}); + const event = createAPIGatewayEvent({ identifier: metagraph_id }); const response: APIGatewayProxyResult = await metagraphHandler.currencyTransactions(event); - const transactions = data_metagraph_transactions.filter(tx => tx.metagraph_id === metagraph_id) + const transactions = data_metagraph_transactions.filter( + (tx) => tx.metagraph_id === metagraph_id + ); expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); @@ -262,7 +314,7 @@ describe("Metagraph Handler Integration Tests", () => { }); it("should return not found on invalid metagraph", async () => { - const identifier = "" + const identifier = ""; const event = createAPIGatewayEvent({ identifier }, { limit: "10" }); const response: APIGatewayProxyResult = @@ -270,22 +322,30 @@ describe("Metagraph Handler Integration Tests", () => { expect(response.statusCode).toBe(404); - expect(response.body).toBe('{"message":"Not found","errors":["metagraph"]}'); - + expect(response.body).toBe( + '{"message":"Not found","errors":["metagraph"]}' + ); }); }); describe("currencyTransactionsByAddress", () => { it("should return transactions for specified address sorted by snapshot ordinal descending", async () => { const address = data_addresses[0].address; - const metagraph_id = data_metagraphs[0].id + const metagraph_id = data_metagraphs[0].id; - const event = createAPIGatewayEvent({ address, identifier: metagraph_id}, { limit: "10" }); + const event = createAPIGatewayEvent( + { address, identifier: metagraph_id }, + { limit: "10" } + ); const response: APIGatewayProxyResult = await metagraphHandler.currencyTransactionsByAddress(event); - const transactions = data_metagraph_transactions.filter(tx => tx.metagraph_id === metagraph_id && (tx.source_addr === address || tx.destination_addr === address)) + const transactions = data_metagraph_transactions.filter( + (tx) => + tx.metagraph_id === metagraph_id && + (tx.source_addr === address || tx.destination_addr === address) + ); expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); @@ -295,7 +355,7 @@ describe("Metagraph Handler Integration Tests", () => { expect([ body.data[0].snapshotOrdinal, body.data[1].snapshotOrdinal, - ]).toEqual([2, 1]); + ]).toEqual([2, 1]); validateTransaction(body.data[0], data_metagraph_transactions[1]); validateTransaction(body.data[1], data_metagraph_transactions[0]); @@ -323,4 +383,97 @@ describe("Metagraph Handler Integration Tests", () => { expect(body.ordinal).toBeDefined(); }); }); + + test("currencyFeeTransaction returns expected result", async () => { + const event = createAPIGatewayEvent({ + identifier: data_metagraphs[0].id, + hash: data_metagraph_transactions[0].hash, + }); + + const result = (await metagraphHandler.currencyFeeTransaction( + event + )) as APIGatewayProxyResult; + expect(result.statusCode).toBe(200); + const body = validateResponseStructure(result); + validateFeeTransaction(body.data, data_metagraph_fee_transactions[0]); + }); + + test("currencyFeeTransactions returns list of transactions", async () => { + const d = await prisma.metagraph_fee_transactions.findMany(); + + const event = createAPIGatewayEvent({ + identifier: data_metagraphs[0].id, + }); + + const result = (await metagraphHandler.currencyFeeTransactions( + event + )) as APIGatewayProxyResult; + expect(result.statusCode).toBe(200); + const body = validatePaginatedResponse(result); + + expect(body.data.length).toBe(1); + validateFeeTransaction(body.data[0], data_metagraph_fee_transactions[0]); + }); + + test("currencySnapshotFeeTransactions returns snapshot fee transactions", async () => { + const event = createAPIGatewayEvent({ + identifier: data_metagraph_fee_transactions[0].metagraph_id, + term: data_metagraph_fee_transactions[0].metagraph_snapshot_hash, + }); + + const result = (await metagraphHandler.currencySnapshotFeeTransactions( + event + )) as APIGatewayProxyResult; + expect(result.statusCode).toBe(200); + const body = validatePaginatedResponse(result); + + expect(body.data.length).toBe(1); + validateFeeTransaction(body.data[0], data_metagraph_fee_transactions[0]); + }); + + test("currencyFeeTransactionsByAddress returns transactions for address", async () => { + const event = createAPIGatewayEvent({ + identifier: data_metagraphs[0].id, + address: data_addresses[0].address, + }); + + const result = (await metagraphHandler.currencyFeeTransactionsByAddress( + event + )) as APIGatewayProxyResult; + expect(result.statusCode).toBe(200); + const body = validatePaginatedResponse(result); + expect(body.data.length).toBe(1); + validateFeeTransaction(body.data[0], data_metagraph_fee_transactions[0]); + }); + + test("currencyFeeTransactionsBySource returns transactions for source address", async () => { + const event = createAPIGatewayEvent({ + identifier: data_metagraphs[0].id, + address: data_addresses[0].address, + }); + + const result = (await metagraphHandler.currencyFeeTransactionsBySource( + event + )) as APIGatewayProxyResult; + expect(result.statusCode).toBe(200); + const body = validatePaginatedResponse(result); + expect(body.data.length).toBe(1); + validateFeeTransaction(body.data[0], data_metagraph_fee_transactions[0]); + }); + + test("currencyFeeTransactionsByDestination returns transactions for destination address", async () => { + const event = createAPIGatewayEvent({ + identifier: data_metagraphs[0].id, + address: data_addresses[1].address, + }); + + const result = (await metagraphHandler.currencyFeeTransactionsByDestination( + event + )) as APIGatewayProxyResult; + expect(result.statusCode).toBe(200); + const body = validatePaginatedResponse(result); + + expect(body.data.length).toBe(1); + validateFeeTransaction(body.data[0], data_metagraph_fee_transactions[0]); + }); }); From 434d48f161a8165d00b725bb1036ad119a18a8e2 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Tue, 13 May 2025 11:22:19 -0300 Subject: [PATCH 39/63] add new fee tx endpoint docs --- docs/openapi.yaml | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index f980773..c752ffd 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -599,7 +599,39 @@ paths: $ref: './schemas/responses.yml#/components/responses/NotFound' '500': $ref: './schemas/responses.yml#/components/responses/InternalServerError' - + /currency/{metagraph_id}/snapshots/fee-transactions: + get: + tags: + - Transactions + summary: Get fee transactions for a metagraph + description: Retrieve fee transactions for a metagraph + operationId: getCurrencyFeeTransactions + parameters: + - name: metagraph_id + in: path + description: Identifier for the metagraph + required: true + schema: + type: string + - $ref: './schemas/params.yml#/components/parameters/limitParam' + - $ref: './schemas/params.yml#/components/parameters/nextParam' + - $ref: './schemas/params.yml#/components/parameters/searchAfterParam' + - $ref: './schemas/params.yml#/components/parameters/searchBeforeParam' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: './schemas/metagraph.yml#/components/schemas/PaginatedMetagraphTransactions' + '400': + $ref: './schemas/responses.yml#/components/responses/BadRequest' + '404': + $ref: './schemas/responses.yml#/components/responses/NotFound' + '500': + $ref: './schemas/responses.yml#/components/responses/InternalServerError' + + /currency/{metagraph_id}/snapshots/{hash_or_ordinal}/fee-transactions: get: tags: From 9460cf7a7620128f8ca03159b386c9aa10583d21 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Fri, 9 May 2025 15:37:23 -0300 Subject: [PATCH 40/63] add position endponts --- docs/openapi.yaml | 174 +++++++++++++ docs/schemas/delegatedStaking.yml | 44 ++++ prisma/migrations/20250429/migration.sql | 6 +- .../migrations/20250506/01_update_staking.sql | 68 +++++ prisma/schema.prisma | 72 +++--- routes/delegated-staking.yml | 20 +- src/handlers/delegatedStakingHandler.ts | 236 ++++++++++++++---- src/pagination.ts | 1 - .../handlers/delegatedStakingHandler.test.ts | 127 +++++++--- 9 files changed, 614 insertions(+), 134 deletions(-) create mode 100644 docs/schemas/delegatedStaking.yml create mode 100644 prisma/migrations/20250506/01_update_staking.sql diff --git a/docs/openapi.yaml b/docs/openapi.yaml index c752ffd..25165a4 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -2202,3 +2202,177 @@ paths: $ref: './schemas/responses.yml#/components/responses/NotFound' '500': $ref: './schemas/responses.yml#/components/responses/InternalServerError' + +# ==================== DELEGATED STAKING ==================== + /delegated-stake-events: + get: + summary: List all delegated stake events + operationId: getDelegatedStakeEvents + tags: [Delegated Stake Events] + parameters: + - name: status + in: query + description: Filter by stake status + required: false + schema: + type: string + enum: [active, transferred, pendingWithdrawal, withdrawalComplete] + responses: + '200': + description: A list of delegated stake events + content: + application/json: + schema: + type: array + items: + $ref: './schemas/delegatedStaking.yml#/components/schemas/DelegatedStakeEvent' + + /delegated-stake-events/{hash}: + get: + summary: Get a delegated stake event by hash + operationId: getDelegatedStakeEvent + tags: [Delegated Stake Events] + parameters: + - name: hash + in: path + required: true + schema: + type: string + responses: + '200': + description: Delegated stake event + content: + application/json: + schema: + $ref: '#/components/schemas/DelegatedStakeEvent' + + /addresses/{address}/delegated-stake-events: + get: + summary: List delegated stake events by address + operationId: getDelegatedStakeEventsByAddress + tags: [Delegated Stake Events] + parameters: + - name: address + in: path + required: true + schema: + type: string + - name: status + in: query + required: false + schema: + type: string + enum: [active, transferred, pendingWithdrawal, withdrawalComplete] + responses: + '200': + description: Delegated stake events for address + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/DelegatedStakeEvent' + + /delegated-stake-withdrawals: + get: + summary: List all delegated stake withdrawals + operationId: getDelegatedStakeWithdrawals + tags: [Delegated Stake Withdrawals] + responses: + '200': + description: A list of delegated stake withdrawals + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/DelegatedStakeWithdrawal' + + /delegated-stake-withdrawals/{hash}: + get: + summary: Get a delegated stake withdrawal by hash + operationId: getDelegatedStakeWithdrawal + tags: [Delegated Stake Withdrawals] + parameters: + - name: hash + in: path + required: true + schema: + type: string + responses: + '200': + description: Delegated stake withdrawal + content: + application/json: + schema: + $ref: '#/components/schemas/DelegatedStakeWithdrawal' + + /addresses/{address}/delegated-stake-withdrawals: + get: + summary: List delegated stake withdrawals by address + operationId: getDelegatedStakeWithdrawalsByAddress + tags: [Delegated Stake Withdrawals] + parameters: + - name: address + in: path + required: true + schema: + type: string + responses: + '200': + description: Delegated stake withdrawals for address + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/DelegatedStakeWithdrawal' + + /delegated-stake-positions: + get: + summary: List all delegated staking positions + operationId: getDelegatedStakePositions + tags: [Delegated Stake Positions] + parameters: + - name: status + in: query + required: false + schema: + type: string + enum: [active, transferred, pendingWithdrawal, withdrawalComplete] + responses: + '200': + description: Delegated staking positions + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/DelegatedStakePosition' + + /addresses/{address}/delegated-stake-positions: + get: + summary: List delegated staking positions by address + operationId: getDelegatedStakePositionsByAddress + tags: [Delegated Stake Positions] + parameters: + - name: address + in: path + required: true + schema: + type: string + - name: status + in: query + required: false + schema: + type: string + enum: [active, transferred, pendingWithdrawal, withdrawalComplete] + responses: + '200': + description: Delegated staking positions for address + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/DelegatedStakePosition' \ No newline at end of file diff --git a/docs/schemas/delegatedStaking.yml b/docs/schemas/delegatedStaking.yml new file mode 100644 index 0000000..10e6248 --- /dev/null +++ b/docs/schemas/delegatedStaking.yml @@ -0,0 +1,44 @@ +components: + schemas: + DelegatedStakeEvent: + type: object + properties: + hash: { type: string } + ordinal: { type: integer } + source: { type: string } + nodeId: { type: string } + amount: { type: string, description: "BigInt represented as a string" } + fee: { type: string, description: "BigInt represented as a string" } + tokenLockHash: { type: string } + parentHash: { type: string, nullable: true } + type: { type: string, enum: [create, transfer] } + status: { type: string, enum: [active, transferred, pendingWithdrawal, withdrawalComplete] } + timestamp: { type: string, format: date-time } + + DelegatedStakeWithdrawal: + type: object + properties: + hash: { type: string } + source: { type: string } + stake: { $ref: '#/components/schemas/DelegatedStakeEvent' } + globalSnapshotHash: { type: string } + unlockEpoch: { type: integer } + status: { type: string, enum: [pendingWithdrawal, withdrawalComplete] } + timestamp: { type: string, format: date-time } + + DelegatedStakePosition: + type: object + properties: + address: { type: string } + nodeId: { type: string } + status: { type: string, enum: [active, transferred, pendingWithdrawal, withdrawalComplete] } + stakeHash: { type: string } + lockAmount: { type: string, description: "BigInt represented as a string" } + rewardsAccrued: { type: string, description: "BigInt represented as a string" } + withdrawnAmount: { type: string, description: "BigInt represented as a string" } + transferedFromHash: { type: string, nullable: true } + transferedToHash: { type: string, nullable: true } + createdAt: { type: string, format: date-time } + transferredAt: { type: string, format: date-time, nullable: true } + withdrawalStartedAt: { type: string, format: date-time, nullable: true } + withdrawalCompletedAt: { type: string, format: date-time, nullable: true } \ No newline at end of file diff --git a/prisma/migrations/20250429/migration.sql b/prisma/migrations/20250429/migration.sql index a1ded4c..2d95642 100644 --- a/prisma/migrations/20250429/migration.sql +++ b/prisma/migrations/20250429/migration.sql @@ -1,2 +1,4 @@ -ALTER TABLE public.dag_token_unlocks ALTER COLUMN parent_hash DROP NOT NULL; -ALTER TABLE public.dag_token_unlocks DROP COLUMN lock_reference_ordinal; +-- ALTER TABLE dag_token_unlocks ALTER COLUMN parent_hash DROP NOT NULL; +-- ALTER TABLE dag_token_unlocks ALTER COLUMN lock_reference_ordinal DROP NOT NULL; +ALTER TABLE dag_token_unlocks DROP COLUMN parent_hash; +ALTER TABLE dag_token_unlocks DROP COLUMN lock_reference_ordinal; diff --git a/prisma/migrations/20250506/01_update_staking.sql b/prisma/migrations/20250506/01_update_staking.sql new file mode 100644 index 0000000..e9e7b81 --- /dev/null +++ b/prisma/migrations/20250506/01_update_staking.sql @@ -0,0 +1,68 @@ +ALTER TABLE delegate_stake_create_events DROP COLUMN is_update; +ALTER TABLE delegate_stake_create_events ADD transfer_from_hash varchar NULL; +ALTER TABLE delegate_stake_position_changes RENAME TO delegate_stake_position_changes; +ALTER TABLE delegate_stake_withdraw_events ADD unlock_epoch int8 NULL; +ALTER TABLE delegate_stake_withdraw_events ADD created_at_epoch int8 NULL; +ALTER TABLE public.delegate_stake_withdraw_events ADD is_completed boolean NULL; + +ALTER TABLE public.delegate_stake_rewards ADD stake_create_hash varchar NOT NULL; +ALTER TABLE public.delegate_stake_rewards ADD CONSTRAINT delegate_stake_rewards_delegate_stake_create_events_fk FOREIGN KEY (stake_create_hash) REFERENCES public.delegate_stake_create_events(hash); + + +--TODO Update is_completed + + + + + + +CREATE TABLE delegate_stake_create_events ( + hash varchar PRIMARY KEY, + ordinal int8 NOT NULL, + source_addr varchar NOT NULL REFERENCES addresses(address) ON DELETE CASCADE, + node_id varchar NOT NULL, + amount int8 NOT NULL, + fee int8 NOT NULL DEFAULT 0, + lock_reference_hash varchar NOT NULL REFERENCES dag_token_locks(hash) ON DELETE CASCADE, + parent_hash varchar NOT NULL, + global_snapshot_hash varchar NOT NULL REFERENCES global_snapshots(hash) ON DELETE CASCADE, + transfer_from_hash varchar NULL, + created_at timestamp DEFAULT now() NOT NULL, + updated_at timestamp DEFAULT now() NOT NULL +); +CREATE INDEX delegate_stake_create_events_source_addr_idx ON delegate_stake_create_events USING btree (source_addr); +CREATE INDEX delegate_stake_create_changes_node_id ON delegate_stake_position_changes USING btree (node_id); +CREATE INDEX delegate_stake_create_events_lock_reference_hash_idx ON delegate_stake_create_events USING btree (lock_reference_hash); +CREATE INDEX delegate_stake_create_events_global_snapshot_hash_idx ON delegate_stake_create_events USING btree (global_snapshot_hash); + + + +CREATE TABLE delegate_stake_withdraw_events ( + hash varchar PRIMARY KEY, + source_addr varchar NOT NULL REFERENCES addresses(address) ON DELETE CASCADE, + stake_create_hash varchar NOT NULL REFERENCES delegate_stake_create_events(hash) ON DELETE CASCADE, + global_snapshot_hash varchar NOT NULL REFERENCES global_snapshots(hash) ON DELETE CASCADE, + unlock_epoch int8 NULL, + created_at_epoch int8 NULL, + is_completed boolean NULL, + created_at timestamp DEFAULT now() NOT NULL, + updated_at timestamp DEFAULT now() NOT NULL +); +CREATE INDEX delegate_stake_withdraw_events_source_addr_idx ON delegate_stake_withdraw_events USING btree (source_addr); +CREATE INDEX delegate_stake_withdraw_events_stake_create_hash_idx ON delegate_stake_withdraw_events USING btree (stake_create_hash); +CREATE INDEX delegate_stake_withdraw_events_global_snapshot_hash_idx ON delegate_stake_withdraw_events USING btree (global_snapshot_hash); + +CREATE TABLE delegate_stake_rewards ( + global_snapshot_hash varchar NOT NULL REFERENCES global_snapshots(hash) ON DELETE CASCADE, + address varchar NOT NULL REFERENCES addresses(address) ON DELETE CASCADE, + node_id varchar NOT NULL, + rewards int8 NOT NULL, + stake_create_hash varchar NOT NULL, + created_at timestamp DEFAULT now() NOT NULL, + updated_at timestamp DEFAULT now() NOT NULL, + PRIMARY KEY (global_snapshot_hash, stake_create_hash) +); +CREATE INDEX delegate_stake_rewards_changes_global_snapshot_hash_idx ON delegate_stake_position_changes USING btree (global_snapshot_hash); +CREATE INDEX delegate_stake_rewards_changes_address ON delegate_stake_position_changes USING btree (address); +CREATE INDEX delegate_stake_rewards_changes_node_id ON delegate_stake_position_changes USING btree (node_id); +ALTER TABLE delegate_stake_rewards ADD CONSTRAINT delegate_stake_rewards_changes_unique UNIQUE (global_snapshot_hash, address, node_id, rewards); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8a83741..eb2cec0 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -73,7 +73,6 @@ model addresses { dag_token_unlocks dag_token_unlocks[] dag_transactions_dag_transactions_destination_addrToaddresses dag_transactions[] @relation("dag_transactions_destination_addrToaddresses") dag_transactions_dag_transactions_source_addrToaddresses dag_transactions[] @relation("dag_transactions_source_addrToaddresses") - delegate_stake_balance_changes delegate_stake_balance_changes[] delegate_stake_create_events delegate_stake_create_events[] delegate_stake_rewards delegate_stake_rewards[] delegate_stake_withdraw_events delegate_stake_withdraw_events[] @@ -270,7 +269,6 @@ model global_snapshots { dag_balance_changes dag_balance_changes[] dag_blocks dag_blocks[] dag_reward_transactions dag_reward_transactions[] - delegate_stake_balance_changes delegate_stake_balance_changes[] delegate_stake_create_events delegate_stake_create_events[] delegate_stake_rewards delegate_stake_rewards[] delegate_stake_withdraw_events delegate_stake_withdraw_events[] @@ -427,7 +425,7 @@ model metagraph_spend_transactions { updated_at DateTime @default(now()) @db.Timestamp(6) metagraph_id String @db.VarChar destination_addr String @db.VarChar - allow_spend_ref String? @unique @db.VarChar + allow_spend_ref String? @unique @db.VarChar snapshot_hash String @db.VarChar addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_destination_addr_fk") metagraph_allow_spend metagraph_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "mg_spend_transactions_mg_allow_spends_fk") @@ -437,21 +435,21 @@ model metagraph_spend_transactions { } model metagraph_token_locks { - hash String @db.VarChar - source_addr String @db.VarChar + hash String @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar ordinal BigInt unlock_epoch BigInt - round_id String @db.VarChar - snapshot_hash String @db.VarChar - metagraph metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") - addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_locks_source_addr_fk") + round_id String @db.VarChar + snapshot_hash String @db.VarChar + metagraph metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_locks_source_addr_fk") abstract_transactions_view abstract_transactions_view? metagraph_token_unlock metagraph_token_unlocks? - metagraph_snapshot metagraph_snapshots? @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash]) + metagraph_snapshot metagraph_snapshots? @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash]) @@id([metagraph_id, hash], map: "metagraph_token_locks_pk") @@unique([hash]) @@ -521,7 +519,7 @@ model dag_expired_spend_transactions { amount BigInt created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - allow_spend_ref String @unique @db.VarChar + allow_spend_ref String @unique @db.VarChar snapshot_hash String @db.VarChar dag_allow_spend dag_allow_spends @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_expired_spend_transactions_dag_allow_spends_fk") global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) @@ -535,7 +533,7 @@ model metagraph_expired_spend_transactions { created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) metagraph_id String @db.VarChar - allow_spend_ref String @unique @db.VarChar + allow_spend_ref String @unique @db.VarChar snapshot_hash String @db.VarChar metagraph_allow_spend metagraph_allow_spends @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_expired_spend_transactions_metagraph_allow_spends_fk") metagraph_snapshot metagraph_snapshots? @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash]) @@ -565,14 +563,17 @@ model delegate_stake_create_events { lock_reference_hash String @db.VarChar parent_hash String @db.VarChar global_snapshot_hash String @db.VarChar - is_update Boolean + transfer_from_hash String? @unique @db.VarChar created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) global_snapshots global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction) dag_token_lock dag_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) - delegate_stake_withdraw_event delegate_stake_withdraw_events? + delegated_from delegate_stake_create_events? @relation(name: "delegation_self") + delegated_to delegate_stake_create_events? @relation(name: "delegation_self", fields: [transfer_from_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction) + delegate_stake_withdraw_events delegate_stake_withdraw_events[] + delegate_stake_rewards delegate_stake_rewards[] @@index([global_snapshot_hash]) @@index([source_addr]) @@ -584,6 +585,8 @@ model delegate_stake_withdraw_events { source_addr String @db.VarChar stake_create_hash String @unique @db.VarChar global_snapshot_hash String @db.VarChar + unlock_epoch BigInt + is_completed Boolean created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) @@ -592,33 +595,18 @@ model delegate_stake_withdraw_events { delegate_stake_create_event delegate_stake_create_events @relation(fields: [stake_create_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) } -model delegate_stake_balance_changes { - global_snapshot_hash String @db.VarChar - address String @db.VarChar - node_id String @db.VarChar - balance BigInt - rewards BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - global_snapshot_ordinal BigInt - addresses addresses @relation(fields: [address], references: [address], onDelete: Cascade, onUpdate: NoAction) - global_snapshots global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) - - @@unique([global_snapshot_hash, address, node_id, balance, rewards], map: "delegate_stake_balance_changes_unique") - @@index([address], map: "delegate_stake_rewards_changes_address") - @@index([global_snapshot_hash], map: "delegate_stake_rewards_changes_global_snapshot_hash_idx") - @@index([node_id], map: "delegate_stake_rewards_changes_node_id") -} - model delegate_stake_rewards { - global_snapshot_hash String @db.VarChar - address String @db.VarChar - node_id String @db.VarChar + global_snapshot_hash String @db.VarChar + address String @db.VarChar + node_id String @db.VarChar rewards BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - addresses addresses @relation(fields: [address], references: [address], onDelete: Cascade, onUpdate: NoAction) - global_snapshots global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + stake_create_hash String @db.VarChar + + addresses addresses @relation(fields: [address], references: [address], onDelete: Cascade, onUpdate: NoAction) + global_snapshots global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) + stake_create delegate_stake_create_events @relation(fields: [stake_create_hash], references: [hash]) @@unique([global_snapshot_hash, address, node_id, rewards], map: "delegate_stake_rewards_changes_unique") } diff --git a/routes/delegated-staking.yml b/routes/delegated-staking.yml index cdb67d7..6a99223 100644 --- a/routes/delegated-staking.yml +++ b/routes/delegated-staking.yml @@ -2,21 +2,21 @@ delegatedStakes: handler: src/handlers/delegatedStakingHandler.delegatedStakes events: - httpApi: - path: /delegated-stakes + path: /delegated-stake-events method: GET delegatedStake: handler: src/handlers/delegatedStakingHandler.delegatedStake events: - httpApi: - path: /delegated-stakes/{hash} + path: /delegated-stake-events/{hash} method: GET addressDelegatedStakes: handler: src/handlers/delegatedStakingHandler.addressDelegatedStakes events: - httpApi: - path: /addresses/{address}/delegated-stakes + path: /addresses/{address}/delegated-stake-events method: GET delegatedStakeWithdrawals: @@ -40,9 +40,17 @@ addressDelegatedStakeWithdrawals: path: /addresses/{address}/delegated-stake-withdrawals method: GET -stakingBalanceByAddress: - handler: src/handlers/delegatedStakingHandler.stakingBalanceByAddress + +stakingPositions: + handler: src/handlers/delegatedStakingHandler.stakingPositions + events: + - httpApi: + path: /delegated-stake-positions + method: GET + +stakingPositionsByAddress: + handler: src/handlers/delegatedStakingHandler.stakingPositions events: - httpApi: - path: /addresses/{address}/delegated-stake-balance + path: /addresses/{address}/delegated-stake-positions method: GET \ No newline at end of file diff --git a/src/handlers/delegatedStakingHandler.ts b/src/handlers/delegatedStakingHandler.ts index f44d23e..46da809 100644 --- a/src/handlers/delegatedStakingHandler.ts +++ b/src/handlers/delegatedStakingHandler.ts @@ -1,17 +1,50 @@ -import { PrismaClient } from "@prisma/client"; +import { + delegate_stake_create_events, + delegate_stake_withdraw_events, + PrismaClient, +} from "@prisma/client"; import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; -import { extractHashOrdinal, extractPagination } from "../request-params"; +import { extractPagination } from "../request-params"; import { paginatedQuery, toOrdinalCursor, fromOrdinalCursor, } from "../pagination"; import { handleError, respond } from "../response"; -import { toNumber } from "lodash"; -import { latestGlobalSnapshot } from "./dagHandler"; const prisma = new PrismaClient(); +const latestWithdrawEvents = ( + events: delegate_stake_withdraw_events[] = [] +) => { + let withdrawalCreate: delegate_stake_withdraw_events | null = null; + let withdrawalComplete: delegate_stake_withdraw_events | null = null; + + for (const event of events) { + if (event.is_completed) { + withdrawalComplete = event; + } else { + withdrawalCreate = event; + } + } + + return { withdrawalCreate, withdrawalComplete }; +}; + +const withdrawalStatus = (isCompleted) => + isCompleted ? "withdrawalComplete" : "pendingWithdrawal"; + +const createStatus = (ce) => (ce.transfer_from_hash ? "transfered" : "active"); + +const stakeStatus = (event) => { + const withdrawalEvents = event.delegate_stake_withdraw_events; + if (withdrawalEvents?.length > 0) { + return withdrawalStatus(withdrawalEvents > 1); + } else { + return createStatus(event); + } +}; + const delegateStakeCreateResponse = (event) => ({ hash: event.hash, ordinal: event.ordinal, @@ -19,9 +52,10 @@ const delegateStakeCreateResponse = (event) => ({ nodeId: event.node_id, amount: event.amount, fee: event.fee, - tokenLockRef: event.lock_reference_hash, + tokenLockHash: event.lock_reference_hash, parentHash: event.parent_hash, - globalSnapshotHash: event.global_snapshot_hash, + type: event.transfer_from_hash ? "transfer" : "create", + status: stakeStatus(event), timestamp: event.created_at, }); @@ -31,36 +65,125 @@ const delegateStakeCreateResponses = (txs) => const delegateStakeWithdrawResponse = (event) => ({ hash: event.hash, source: event.source_addr, - stakeHash: event.stake_create_hash, + stake: event.delegate_stake_create_event, globalSnapshotHash: event.global_snapshot_hash, + unlockEpoch: event.unlock_epoch, + status: withdrawalStatus(event.is_completed), timestamp: event.created_at, }); const delegateStakeWithdrawResponses = (txs) => txs.map(delegateStakeWithdrawResponse); -const delegateStakeBalanceChangeResponse = (change) => ({ - globalSnapshotOrdinal: change.global_snapshot_ordinal, - address: change.address, - nodeId: change.node_id, - balance: change.balance, - rewards: change.rewards, - timestamp: change.created_at, -}); +const totalRewards = (rs) => + (rs ?? []).reduce((sum, r) => sum + r.rewards, BigInt(0)); + +const completedAmount = (change) => { + const withdrawal = change.withdrawal_event; + if (change.withdrawal_event?.is_complete) { + const createEvent = withdrawal.delegate_stake_create_even; + return ( + createEvent.amount + totalRewards(createEvent.delegate_stake_rewards) + ); + } else { + return 0; + } +}; + +const delegateStakePositionResponse = (change) => { + const { withdrawalCreate, withdrawalComplete } = latestWithdrawEvents( + change.delegate_stake_withdraw_events + ); + return { + address: change.source_addr, + nodeId: change.node_id, + status: stakeStatus(change), + stakeHash: change.hash, + lockAmount: change.amount, + rewardsAccrued: totalRewards(change.delegate_stake_rewards), + withdrawnAmount: completedAmount(change), + transferedFromHash: change.delegated_from?.hash ?? null, + transferedToHash: change.delegated_to?.hash ?? null, + createdAt: change.created_at, + transferredAt: change.delegated_to?.created_at ?? null, + withdrawalStartedAt: withdrawalCreate?.created_at ?? null, + withdrawalCompletedAt: withdrawalComplete?.created_at ?? null, + }; +}; + +const delegateStakePositionResponses = (txs) => + txs.map(delegateStakePositionResponse); + +const statusFilter = (event) => { + const statusParam = + event.queryStringParameters?.status ?? "active,pendingWithdrawal"; + return statusParam.split(","); +}; + +const buildStatusWhereQuery = (statuses) => { + let statusFilters: any[] = []; + + if (statuses.includes("active")) { + statusFilters.push({ + transfer_from_hash: null, + delegate_stake_withdraw_events: { + none: {}, + }, + }); + } + + if (statuses.includes("transfered")) { + statusFilters.push({ + transfer_from_hash: { + not: null, + }, + delegate_stake_withdraw_events: { + none: {}, + }, + }); + } -const delegateStakeBalanceChangeResponses = (txs) => - txs.map(delegateStakeBalanceChangeResponse); + if (statuses.includes("pendingWithdrawal")) { + statusFilters.push({ + delegate_stake_withdraw_events: { + some: {}, + every: { + is_completed: false, + }, + }, + }); + } + + if (statuses.includes("withdrawalComplete")) { + statusFilters.push({ + delegate_stake_withdraw_events: { + some: { + is_completed: true, + }, + }, + }); + } + + return statusFilters.length > 0 ? { OR: statusFilters } : {}; +}; export const delegatedStakes = async ( event: APIGatewayProxyEvent ): Promise => { + const statuses = statusFilter(event); + const statusWhere = buildStatusWhereQuery(statuses); return paginatedQuery( extractPagination(event), toOrdinalCursor, fromOrdinalCursor, { where: { - delegate_stake_withdraw_event: null, + ...statusWhere, + }, + include: { + delegate_stake_withdraw_events: true, + delegated_to: true, + delegated_from: true, }, orderBy: [{ ordinal: "desc" }], }, @@ -75,6 +198,11 @@ export const delegatedStake = async (event) => { const stake = await prisma.delegate_stake_create_events.findUnique({ where: { hash }, + include: { + delegate_stake_withdraw_events: true, + delegated_to: true, + delegated_from: true, + }, }); return respond(stake, delegateStakeCreateResponse); @@ -85,6 +213,10 @@ export const delegatedStake = async (event) => { export const addressDelegatedStakes = async (event) => { const address = event.pathParameters?.address; + + const statuses = statusFilter(event); + const statusWhere = buildStatusWhereQuery(statuses); + return paginatedQuery( extractPagination(event), toOrdinalCursor, @@ -92,6 +224,12 @@ export const addressDelegatedStakes = async (event) => { { where: { source_addr: address, + ...statusWhere, + }, + include: { + delegate_stake_withdraw_events: true, + delegated_to: true, + delegated_from: true, }, orderBy: [{ ordinal: "desc" }], }, @@ -106,6 +244,7 @@ export const delegatedStakeWithdrawals = async (event) => { toOrdinalCursor, fromOrdinalCursor, { + include: { delegate_stake_create_event: true }, orderBy: [{ created_at: "desc" }], }, prisma.delegate_stake_withdraw_events.findMany, @@ -119,6 +258,7 @@ export const delegatedStakeWithdrawal = async (event) => { const withdrawal = await prisma.delegate_stake_withdraw_events.findUnique({ where: { hash }, + include: { delegate_stake_create_event: true }, }); return respond(withdrawal, delegateStakeWithdrawResponse); @@ -137,6 +277,7 @@ export const addressDelegatedStakeWithdrawals = async (event) => { where: { source_addr: address, }, + include: { delegate_stake_create_event: true }, orderBy: [{ created_at: "desc" }], }, prisma.delegate_stake_withdraw_events.findMany, @@ -144,46 +285,55 @@ export const addressDelegatedStakeWithdrawals = async (event) => { ); }; -export const stakingBalanceByAddress = async ( +export const stakingPositions = async ( event: APIGatewayProxyEvent ): Promise => { try { const { address } = event.pathParameters || {}; - const { ordinal } = event.queryStringParameters || {}; + const statuses = statusFilter(event); + const statusWhere = buildStatusWhereQuery(statuses); - const ordinalNbr = toNumber(ordinal); - - const ordinalCondition = isFinite(ordinalNbr) - ? { global_snapshot_ordinal: { lte: ordinalNbr } } - : {}; - - const latestPerNode = await prisma.delegate_stake_balance_changes.groupBy({ - by: ["node_id"], - where: { - address, - ...ordinalCondition, - }, + const maxOrdinals = await prisma.delegate_stake_create_events.groupBy({ + by: ["source_addr", "node_id"], _max: { - global_snapshot_ordinal: true, + ordinal: true, }, }); - const latestConditions = latestPerNode.map(({ node_id, _max }) => ({ - address, - node_id, - global_snapshot_ordinal: _max.global_snapshot_ordinal ?? {}, - })); + const whereConditions = maxOrdinals.map( + ({ source_addr, node_id, _max }) => ({ + source_addr, + node_id, + ordinal: _max.ordinal!, + }) + ); + + const addressFilter = address?.trim() + ? { source_addr: address.trim() } + : {}; - const latestBalances = await prisma.delegate_stake_balance_changes.findMany( + return paginatedQuery( + extractPagination(event), + toOrdinalCursor, + fromOrdinalCursor, { where: { - OR: latestConditions, + OR: whereConditions, + ...statusWhere, + ...addressFilter, }, - } + include: { + delegate_stake_withdraw_events: true, + delegate_stake_rewards: true, + delegated_to: true, + delegated_from: true, + }, + orderBy: [{ source_addr: "asc" }, { node_id: "asc" }], + }, + prisma.delegate_stake_create_events.findMany, + delegateStakePositionResponses ); - - return respond(latestBalances, delegateStakeBalanceChangeResponses); } catch (error) { return handleError(error); } diff --git a/src/pagination.ts b/src/pagination.ts index 988a9b3..895b2b8 100644 --- a/src/pagination.ts +++ b/src/pagination.ts @@ -95,7 +95,6 @@ export const paginatedQuery = async ( }; const rawResults = await findMany(pagedQuery); - const pageSize = pageQueryParams.take ? pageQueryParams.take - 1 : maxSizeLimit; diff --git a/tests/handlers/delegatedStakingHandler.test.ts b/tests/handlers/delegatedStakingHandler.test.ts index 69b5926..162e01e 100644 --- a/tests/handlers/delegatedStakingHandler.test.ts +++ b/tests/handlers/delegatedStakingHandler.test.ts @@ -5,7 +5,7 @@ import { delegatedStakeWithdrawals, delegatedStakeWithdrawal, addressDelegatedStakeWithdrawals, - stakingBalanceByAddress, + stakingPositions, } from "../../src/handlers/delegatedStakingHandler"; import { @@ -20,7 +20,6 @@ import { data_global_snapshots, prisma, } from "../../prisma/seed"; -import { randomUUID } from "crypto"; const data_delegate_stake_create_events = [ { @@ -33,7 +32,6 @@ const data_delegate_stake_create_events = [ lock_reference_hash: data_dag_token_locks[0].hash, parent_hash: "parent-hash-xyz", global_snapshot_hash: data_global_snapshots[0].hash, - is_update: false, }, { hash: "stake-event-hash-002", @@ -45,19 +43,18 @@ const data_delegate_stake_create_events = [ lock_reference_hash: data_dag_token_locks[1].hash, parent_hash: "stake-event-hash-001", global_snapshot_hash: data_global_snapshots[1].hash, - is_update: false, }, { hash: "stake-event-hash-003", ordinal: 10003n, source_addr: data_addresses[0].address, node_id: "NODE_ABC123", - amount: 2222222222, + amount: 2222222222n, fee: 500000n, lock_reference_hash: data_dag_token_locks[1].hash, parent_hash: "stake-event-hash-001", global_snapshot_hash: data_global_snapshots[1].hash, - is_update: true, + transfer_from_hash: "stake-event-hash-002", }, ]; @@ -67,26 +64,29 @@ const data_delegate_stake_withdraw_events = [ source_addr: data_addresses[0].address, stake_create_hash: data_delegate_stake_create_events[0].hash, global_snapshot_hash: data_global_snapshots[1].hash, + unlock_epoch: 11000n, + is_completed: false, }, -]; -const data_delegate_stake_balance_changes = [ { + hash: "withdraw-event-hash-002", + source_addr: data_addresses[0].address, + stake_create_hash: data_delegate_stake_create_events[1].hash, global_snapshot_hash: data_global_snapshots[1].hash, - global_snapshot_ordinal: data_global_snapshots[1].ordinal, - address: data_addresses[0].address, - node_id: "NODE_DEF456", - balance: 700000000000n, - rewards: 1000000000n, + unlock_epoch: 8000n, + is_completed: true, }, ]; + const data_delegate_stake_rewards = [ { global_snapshot_hash: data_global_snapshots[2].hash, address: data_addresses[0].address, node_id: "NODE_ABC123", rewards: 2500000000n, + stake_create_hash: data_delegate_stake_create_events[0].hash, }, ]; + const seedData = async () => { await prisma.delegate_stake_create_events.createMany({ data: data_delegate_stake_create_events, @@ -96,23 +96,32 @@ const seedData = async () => { data: data_delegate_stake_withdraw_events, }); - await prisma.delegate_stake_balance_changes.createMany({ - data: data_delegate_stake_balance_changes, - }); - await prisma.delegate_stake_rewards.createMany({ data: data_delegate_stake_rewards, }); }; +expect.extend({ + toBeBigInt(received, expected) { + const pass = BigInt(received) === BigInt(expected); + return { + pass, + message: () => + `expected ${received} to be the same BigInt as ${expected}`, + }; + }, +}); + const validateCreateStake = (tx) => { const match = data_delegate_stake_create_events.find( (d) => d.hash === tx.hash ); expect(tx.source).toBe(match.source_addr); expect(tx.nodeId).toBe(match.node_id); - expect(Number(tx.amount)).toBe(Number(match.amount)); + expect(tx.amount).toBeBigInt(match.amount); + expect(tx.fee).toBeBigInt(match.fee); expect(tx.timestamp).toBeDefined(); + expect(tx.type).toBe(match.transfer_from_hash ? "transfer" : "create"); }; const validateWithdrawStake = (tx) => { @@ -120,17 +129,26 @@ const validateWithdrawStake = (tx) => { (d) => d.hash === tx.hash ); expect(tx.source).toBe(match.source_addr); - expect(tx.stakeHash).toBe(match.stake_create_hash); + expect(tx.stake.hash).toBe(match.stake_create_hash); + expect(tx.unlockEpoch).toBeBigInt(match.unlock_epoch); + expect(tx.status).toBe( + match.is_completed ? "withdrawalComplete" : "pendingWithdrawal" + ); expect(tx.timestamp).toBeDefined(); }; -const validateBalanceChange = (tx) => { - const match = data_delegate_stake_balance_changes.find( - (d) => d.address === tx.address && d.node_id === tx.nodeId +const validateStakingPosition = (tx) => { + const match = data_delegate_stake_create_events.find( + (d) => d.hash === tx.stakeHash + ); + expect(tx.address).toBe(match.source_addr); + expect(tx.nodeId).toBe(match.node_id); + expect(tx.lockAmount).toBeBigInt(match.amount); + expect(tx.rewardsAccrued).toBeBigInt( + data_delegate_stake_rewards + .filter((r) => r.stake_create_hash === match.hash) + .reduce((sum, r) => sum + r.rewards, BigInt(0)) ); - expect(tx.balance).toBe(Number(match.balance)); - expect(tx.rewards).toBe(Number(match.rewards)); - expect(tx.timestamp).toBeDefined(); }; describe("Delegated Stake Handler Integration Tests", () => { @@ -140,17 +158,30 @@ describe("Delegated Stake Handler Integration Tests", () => { describe("delegatedStakes", () => { it("should return a list of delegated stakes", async () => { - const activeStates = [ - data_delegate_stake_create_events[1], - data_delegate_stake_create_events[2], - ]; const event = createAPIGatewayEvent({}, { limit: "10" }); const response = await delegatedStakes(event); expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); - expect(body.data.length).toBe(activeStates.length); - validateCreateStake(body.data[0]); + expect(body.data.length).toBeGreaterThan(0); + body.data.forEach(validateCreateStake); + }); + + it("should handle status filtering", async () => { + const event = createAPIGatewayEvent( + {}, + { + limit: "10", + status: "transfered,active", + } + ); + const response = await delegatedStakes(event); + expect(response.statusCode).toBe(200); + + const body = validatePaginatedResponse(response); + body.data.forEach((tx) => { + expect(["transfered", "active"]).toContain(tx.status); + }); }); }); @@ -188,7 +219,7 @@ describe("Delegated Stake Handler Integration Tests", () => { const body = validatePaginatedResponse(response); expect(body.data.length).toBe(data_delegate_stake_withdraw_events.length); - validateWithdrawStake(body.data[0]); + body.data.forEach(validateWithdrawStake); }); }); @@ -218,17 +249,33 @@ describe("Delegated Stake Handler Integration Tests", () => { }); }); - describe("stakingBalanceByAddress", () => { - it("should return staking balance per node for an address", async () => { - const test = data_delegate_stake_balance_changes[0]; - const event = createAPIGatewayEvent({ address: test.address }); + describe("stakingPositions", () => { + it("should return staking positions for an address", async () => { + const test = data_delegate_stake_create_events[0]; + const event = createAPIGatewayEvent({ address: test.source_addr }); + + const response = await stakingPositions(event); + expect(response.statusCode).toBe(200); + + const body = validatePaginatedResponse(response); + body.data.forEach(validateStakingPosition); + }); - const response = await stakingBalanceByAddress(event); + it("should handle status filtering", async () => { + const event = createAPIGatewayEvent( + {}, + { + limit: "10", + status: "active,pendingWithdrawal", + } + ); + const response = await stakingPositions(event); expect(response.statusCode).toBe(200); - const body = validateResponseStructure(response)["data"]; - expect(Array.isArray(body)).toBe(true); - body.forEach(validateBalanceChange); + const body = validatePaginatedResponse(response); + body.data.forEach((tx) => { + expect(["active", "pendingWithdrawal"]).toContain(tx.status); + }); }); }); }); From a56370363c6eed56bbbbd98e20f65aa3cfd5df52 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Sat, 17 May 2025 21:09:58 -0300 Subject: [PATCH 41/63] query tx by snapshot directly --- prisma/migrations/20250502/01_migration.sql | 9 ++ .../migrations/20250502/02_snapshot_hash.sql | 10 ++ prisma/schema.prisma | 6 + prisma/seed.ts | 124 ------------------ src/handlers/dagHandler.ts | 29 +--- src/handlers/metagraphHandler.ts | 34 ++--- src/response.ts | 56 ++++---- tests/handlers/dagHandler.test.ts | 31 ++--- tests/handlers/metagraphHandler.test.ts | 10 +- 9 files changed, 88 insertions(+), 221 deletions(-) diff --git a/prisma/migrations/20250502/01_migration.sql b/prisma/migrations/20250502/01_migration.sql index 9946b15..8a69f8d 100644 --- a/prisma/migrations/20250502/01_migration.sql +++ b/prisma/migrations/20250502/01_migration.sql @@ -46,3 +46,12 @@ CREATE INDEX dag_allow_spends_source_addr_idx ON dag_allow_spends USING btree (s CREATE INDEX dag_balance_changes_snapshot_hash_idx ON dag_balance_changes USING btree (snapshot_hash); + + +ALTER TABLE dag_allow_spends DROP CONSTRAINT dag_allow_spends_ordinal; +ALTER TABLE metagraph_allow_spends DROP CONSTRAINT metagraph_allow_spends_ordinal; + + + + +ALTER TABLE public.metagraph_spend_transactions ADD CONSTRAINT dag_spend_transactions_metagraph_allow_spends_fk FOREIGN KEY (allow_spend_ref) REFERENCES metagraph_allow_spends(hash) \ No newline at end of file diff --git a/prisma/migrations/20250502/02_snapshot_hash.sql b/prisma/migrations/20250502/02_snapshot_hash.sql index bdc0197..631e3c0 100644 --- a/prisma/migrations/20250502/02_snapshot_hash.sql +++ b/prisma/migrations/20250502/02_snapshot_hash.sql @@ -32,6 +32,11 @@ BEGIN VALUES (NEW.hash, NEW.source_addr, NEW.amount, NEW.created_at, snap_hash) ON CONFLICT (hash) DO NOTHING; + --temporarily until new streaming is deployed + UPDATE dag_transactions + SET snapshot_hash = snap_hash + WHERE hash = NEW.hash; + RETURN NEW; END; $$; @@ -59,6 +64,11 @@ BEGIN VALUES (NEW.hash, NEW.source_addr, NEW.amount, NEW.created_at, snap_hash) ON CONFLICT (hash) DO NOTHING; + --temporarily until new streaming is deployed + UPDATE metagraph_transactions + SET snapshot_hash = snap_hash + WHERE hash = NEW.hash; + RETURN NEW; END; $$; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index eb2cec0..55c6234 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -240,6 +240,8 @@ model dag_transactions { ordinal BigInt block_hash String @db.VarChar dag_blocks dag_blocks @relation(fields: [block_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_transaction_dag_block_fk") + snapshot_hash String @db.VarChar + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_dag_transactions_global_snapshot_fk") addresses_dag_transactions_destination_addrToaddresses addresses @relation("dag_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_transactions_destination_addr_fk") addresses_dag_transactions_source_addrToaddresses addresses @relation("dag_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_transactions_source_addr_fk") } @@ -268,6 +270,7 @@ model global_snapshots { updated_at DateTime @default(now()) @db.Timestamp(6) dag_balance_changes dag_balance_changes[] dag_blocks dag_blocks[] + dag_transactions dag_transactions[] dag_reward_transactions dag_reward_transactions[] delegate_stake_create_events delegate_stake_create_events[] delegate_stake_rewards delegate_stake_rewards[] @@ -396,6 +399,7 @@ model metagraph_snapshots { version String @db.VarChar created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_transactions metagraph_transactions[] metagraph_allow_spends metagraph_allow_spends[] metagraph_balance_changes metagraph_balance_changes[] metagraph_blocks metagraph_blocks[] @@ -488,6 +492,8 @@ model metagraph_transactions { parent_hash String @db.VarChar ordinal BigInt block_hash String @db.VarChar + snapshot_hash String @db.VarChar + metagraph_snapshot metagraph_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transactions_metagraph_snapshot_fk") metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") metagraph_blocks metagraph_blocks @relation(fields: [metagraph_id, block_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transaction_metagraph_block_fk") addresses_metagraph_transactions_destination_addrToaddresses addresses @relation("metagraph_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transactions_destination_addrfk") diff --git a/prisma/seed.ts b/prisma/seed.ts index 714363b..4201491 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -80,66 +80,6 @@ export const data_global_snapshots = [ }, ]; -export const data_dag_blocks = [ - { - hash: "16593f9f612a453c28669b86067e097990ee18742e905afa330674636ca1431c", - height: 12n, - snapshot_hash: data_global_snapshots[0].hash, - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, - { - hash: "48fd7dd45ced78be111174c5262cca65aa44798b6a01b48525590bfcce643bd2", - height: 14n, - snapshot_hash: data_global_snapshots[1].hash, - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, -]; - -export const data_dag_transactions = [ - { - hash: "1c53bc94c735d8d6eeaddc9f5cb446e7f79144c9aa5bba9479db8dee0ec1aa4c", - source_addr: data_addresses[0].address, - destination_addr: data_addresses[1].address, - amount: 90790983n, - fee: 200000n, - salt: 1231231232n, - parent_ordinal: 21337n, - parent_hash: - "4a6d3aa5715e304b4b5f32d52f0c91e0909acf7c24b3ca9776324da68db2f30c", - ordinal: 234n, - block_hash: data_dag_blocks[0].hash, - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, - { - hash: "6acc815979e9d1935cce65ba776fde1144c5fc0e97d3a9fe67d82d0e6e21977d", - source_addr: data_addresses[1].address, - destination_addr: data_addresses[0].address, - amount: 90790983n, - fee: 100000n, - salt: 1234n, - parent_ordinal: 21337n, - parent_hash: - "1c53bc94c735d8d6eeaddc9f5cb446e7f79144c9aa5bba9479db8dee0ec1aa4c", - ordinal: 3222n, - block_hash: data_dag_blocks[1].hash, - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, -]; -export const data_dag_balance_changes = [ - { - snapshot_hash: data_global_snapshots[0].hash, - snapshot_ordinal: data_global_snapshots[0].ordinal, - address: data_dag_transactions[0].destination_addr, - balance: data_dag_transactions[0].amount, - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, -]; - export const data_metagraphs = [ { id: "DAG5kfY9GoHF1CYaY8tuRJxmB3JSzAEARJEAkA2C", @@ -212,70 +152,6 @@ export const data_metagraph_snapshots = [ }, ]; -export const data_metagraph_blocks = [ - { - metagraph_id: data_metagraphs[0].id, - hash: "33374138dd6f5f9846261d541dab33dadcbae8c9f5a39026336a34a3e2aafb93", - height: 12n, - metagraph_snapshot_hash: data_metagraph_snapshots[0].hash, - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, - { - metagraph_id: data_metagraphs[0].id, - hash: "3d5a9616d65a6d98fe629f1a056489df9245a40d1e8589ed9d655c6fcb3ee361", - height: 14n, - metagraph_snapshot_hash: data_metagraph_snapshots[1].hash, - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, -]; -export const data_metagraph_transactions = [ - { - metagraph_id: data_metagraphs[0].id, - hash: "39c9909d3b00552beaa5487c38110267675ab212b3b97654fc5ca9917cb7e72b", - source_addr: data_addresses[0].address, - destination_addr: data_addresses[1].address, - amount: 90790983n, - fee: 200000n, - salt: 1231231232n, - parent_ordinal: 21337n, - parent_hash: - "5056fdfbba0637dcecfc0b7fa3f441c745c852cf850c3bfc0dbc8a7410b8d722", - ordinal: 12n, - block_hash: data_metagraph_blocks[0].hash, - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, - { - metagraph_id: data_metagraphs[0].id, - hash: "aa50e85a32e3e84c9b49880e103ee240f572a6febd255d97db1406f6c936af6f", - source_addr: data_addresses[1].address, - destination_addr: data_addresses[0].address, - amount: 90790983n, - fee: 100000n, - salt: 1234n, - parent_ordinal: 21337n, - parent_hash: - "39c9909d3b00552beaa5487c38110267675ab212b3b97654fc5ca9917cb7e72b", - ordinal: 123n, - block_hash: data_metagraph_blocks[1].hash, - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, -]; -export const data_metagraph_balance_changes = [ - { - metagraph_id: data_metagraphs[0].id, - metagraph_snapshot_hash: data_metagraph_snapshots[0].hash, - snapshot_ordinal: data_metagraph_snapshots[0].ordinal, - address: data_metagraph_transactions[0].destination_addr, - balance: data_metagraph_transactions[0].amount, - created_at: new Date("2025-04-02T00:10:01Z"), // set explicitly to avoid race condition - updated_at: new Date("2025-04-02T00:10:01Z"), - }, -]; - export const data_dag_token_locks = [ { hash: `token-lock-hash-001`, diff --git a/src/handlers/dagHandler.ts b/src/handlers/dagHandler.ts index 2af561f..9484ad4 100644 --- a/src/handlers/dagHandler.ts +++ b/src/handlers/dagHandler.ts @@ -4,7 +4,6 @@ import { extractHashOrdinal, extractPagination } from "../request-params"; import { balanceResponse, dagBlockResponse, - dagTransactionResponse, dagTransactionsResponse, globalSnapshotResponse, globalSnapshotsResponse, @@ -12,6 +11,7 @@ import { notFoundResponse, respond, rewardsResponse, + transactionResponse, } from "../response"; import { fromCreatedAtOrdinalCursor, @@ -141,14 +141,10 @@ export const globalSnapshotTransactions = async ( const query = { where: { - dag_blocks: { global_snapshot: { ...gsWhere } }, + global_snapshot: { ...gsWhere }, }, include: { - dag_blocks: { - select: { - global_snapshot: { select: { hash: true, ordinal: true } }, - }, - }, + global_snapshot: { select: { hash: true, ordinal: true } }, }, orderBy: { created_at: "desc" }, }; @@ -194,16 +190,9 @@ const dagTransactionsQuery = async ( const query = { ...where, include: { - dag_blocks: { - include: { - global_snapshot: { select: { hash: true, ordinal: true } }, - }, - }, + global_snapshot: { select: { hash: true, ordinal: true } }, }, - orderBy: [ - { dag_blocks: { global_snapshot: { ordinal: "desc" } } }, - { created_at: "desc" }, - ], + orderBy: [{ created_at: "desc" }], }; const toCursor = (row) => ({ @@ -244,15 +233,11 @@ export const dagTransaction = async ( const transaction = await prisma.dag_transactions.findUnique({ where: { hash }, include: { - dag_blocks: { - include: { - global_snapshot: { select: { hash: true, ordinal: true } }, - }, - }, + global_snapshot: { select: { hash: true, ordinal: true } }, }, }); - return respond(transaction, dagTransactionResponse); + return respond(transaction, transactionResponse); } catch (error) { return handleError(error); } diff --git a/src/handlers/metagraphHandler.ts b/src/handlers/metagraphHandler.ts index 32310c9..5061fea 100644 --- a/src/handlers/metagraphHandler.ts +++ b/src/handlers/metagraphHandler.ts @@ -5,17 +5,16 @@ import { balanceResponse, handleError, metagraphBlockResponse, - metagraphFeeTransactionResponse, metagraphFeeTransactionsResponse, metagraphSnapshotResponse, metagraphSnapshotsResponse, metagraphsResponse, metagraphTransactionsResponse, - metagraphTransactionResponse, missingParameterResponse, notFoundResponse, respond, rewardsResponse, + transactionResponse, } from "../response"; import { fromCreatedAtOrdinalCursor, @@ -227,16 +226,9 @@ const metagraphTransactionsQuery = async ( const query = { ...baseQuery, include: { - metagraph_blocks: { - include: { - metagraph_snapshot: { select: { hash: true, ordinal: true } }, - }, - }, + metagraph_snapshot: { select: { hash: true, ordinal: true } }, }, - orderBy: [ - { metagraph_blocks: { metagraph_snapshot: { ordinal: "desc" } } }, - { created_at: "desc" }, - ], + orderBy: [{ created_at: "desc" }], }; const cursor = (row) => ({ @@ -277,11 +269,9 @@ export const currencySnapshotTransactions = async ( const where = { where: { - metagraph_blocks: { - metagraph_snapshot: { - metagraph_id: metagraph_id, - ...mgSnapshotWhere, - }, + metagraph_snapshot: { + metagraph_id: metagraph_id, + ...mgSnapshotWhere, }, }, }; @@ -327,7 +317,7 @@ export const currencyTransactions = async ( return notFoundResponse("metagraph"); } - const where = { where: { metagraph_blocks: { metagraph_id } } }; + const where = { where: { metagraph_id } }; return metagraphTransactionsQuery(where, event); } catch (error) { @@ -349,15 +339,11 @@ export const currencyTransaction = async ( }, }, include: { - metagraph_blocks: { - include: { - metagraph_snapshot: { select: { hash: true, ordinal: true } }, - }, - }, + metagraph_snapshot: { select: { hash: true, ordinal: true } }, }, }); - return respond(transaction, metagraphTransactionResponse); + return respond(transaction, transactionResponse); } catch (error) { return handleError(error); } @@ -486,7 +472,7 @@ export const currencyFeeTransaction = async ( }, }); - return respond(transaction, metagraphFeeTransactionResponse); + return respond(transaction, transactionResponse); } catch (error) { return handleError(error); } diff --git a/src/response.ts b/src/response.ts index d052d3a..adf5e5b 100644 --- a/src/response.ts +++ b/src/response.ts @@ -43,28 +43,29 @@ export const rewardResponse = (reward) => ({ amount: reward.amount, }); -export const dagTransactionsResponse = (ts) => ts.map(dagTransactionResponse); - -export const dagTransactionResponse = (t) => - transactionResponse(t, t.dag_blocks.global_snapshot); - -const transactionResponse = (transaction, snapshot) => ({ - hash: transaction.hash, - ordinal: transaction.ordinal, - amount: transaction.amount, - source: transaction.source_addr, - destination: transaction.destination_addr, - fee: transaction.fee, - parent: { - hash: transaction.parent_hash ?? null, - ordinal: transaction.parent_ordinal ?? null, - }, - salt: transaction.salt, - blockHash: transaction.block_hash, - snapshotHash: snapshot.hash, - snapshotOrdinal: snapshot.ordinal, - timestamp: transaction.created_at, -}); +export const dagTransactionsResponse = (ts) => ts.map(transactionResponse); + +export const transactionResponse = (transaction) => { + const snapshot = + transaction.metagraph_snapshot ?? transaction.global_snapshot ?? null; + return { + hash: transaction.hash, + ordinal: transaction.ordinal, + amount: transaction.amount, + source: transaction.source_addr, + destination: transaction.destination_addr, + fee: transaction.fee, + parent: { + hash: transaction.parent_hash ?? null, + ordinal: transaction.parent_ordinal ?? null, + }, + salt: transaction.salt, + blockHash: transaction.block_hash, + snapshotHash: snapshot.hash, + snapshotOrdinal: snapshot.ordinal, + timestamp: transaction.created_at, + }; +}; const blockResponse = (block) => ({ hash: block.hash, @@ -110,17 +111,10 @@ export const metagraphBlockResponse = (block) => ({ }); export const metagraphTransactionsResponse = (ts) => - ts.map(metagraphTransactionResponse); - -export const metagraphTransactionResponse = (t) => - transactionResponse(t, t.metagraph_blocks.metagraph_snapshot); + ts.map(transactionResponse); export const metagraphFeeTransactionsResponse = (ts) => - ts.map(metagraphFeeTransactionResponse); - -export const metagraphFeeTransactionResponse = (t) => { - return transactionResponse(t, t.metagraph_snapshot); -}; + ts.map(transactionResponse); export const metagraphsResponse = (mgs) => mgs.map(metagraphResponse); export const metagraphResponse = (mg) => ({ diff --git a/tests/handlers/dagHandler.test.ts b/tests/handlers/dagHandler.test.ts index d6219e0..d10f603 100644 --- a/tests/handlers/dagHandler.test.ts +++ b/tests/handlers/dagHandler.test.ts @@ -11,7 +11,7 @@ import { prisma, } from "../../prisma/seed"; -export const data_dag_blocks = [ +const data_dag_blocks = [ { hash: "16593f9f612a453c28669b86067e097990ee18742e905afa330674636ca1431c", height: 12n, @@ -28,7 +28,7 @@ export const data_dag_blocks = [ }, ]; -export const data_dag_transactions = [ +const data_dag_transactions = [ { hash: "1c53bc94c735d8d6eeaddc9f5cb446e7f79144c9aa5bba9479db8dee0ec1aa4c", source_addr: data_addresses[0].address, @@ -41,6 +41,7 @@ export const data_dag_transactions = [ "4a6d3aa5715e304b4b5f32d52f0c91e0909acf7c24b3ca9776324da68db2f30c", ordinal: 234n, block_hash: data_dag_blocks[0].hash, + snapshot_hash: data_dag_blocks[0].snapshot_hash, created_at: new Date("2025-04-02T00:00:02Z"), updated_at: new Date(), }, @@ -51,12 +52,13 @@ export const data_dag_transactions = [ amount: 90790983n, fee: 100000n, salt: 1234n, - parent_ordinal: 21338n, + parent_ordinal: 21337n, parent_hash: "1c53bc94c735d8d6eeaddc9f5cb446e7f79144c9aa5bba9479db8dee0ec1aa4c", ordinal: 3222n, block_hash: data_dag_blocks[1].hash, - created_at: new Date("2025-04-02T00:00:02Z"), + snapshot_hash: data_dag_blocks[1].snapshot_hash, + created_at: new Date("2025-04-02T00:01:02Z"), updated_at: new Date(), }, { @@ -71,11 +73,13 @@ export const data_dag_transactions = [ "6acc815979e9d1935cce65ba776fde1144c5fc0e97d3a9fe67d82d0e6e21977d", ordinal: 3222n, block_hash: data_dag_blocks[1].hash, - created_at: new Date("2025-04-02T00:00:02Z"), + snapshot_hash: data_dag_blocks[1].snapshot_hash, + created_at: new Date("2025-04-02T00:02:02Z"), updated_at: new Date(), }, ]; -export const data_dag_balance_changes = [ + +const data_dag_balance_changes = [ { snapshot_hash: data_global_snapshots[0].hash, snapshot_ordinal: data_global_snapshots[0].ordinal, @@ -259,7 +263,7 @@ describe("DAG Handler Integration Tests", () => { const transactions = await prisma.dag_transactions.findMany({ where: { - dag_blocks: { snapshot_hash: requestedSnapshot.hash }, + snapshot_hash: requestedSnapshot.hash, }, }); @@ -337,13 +341,10 @@ describe("DAG Handler Integration Tests", () => { const transactions = await prisma.dag_transactions.findMany({ include: { - dag_blocks: { - include: { - global_snapshot: { select: { ordinal: true } }, - }, - }, + global_snapshot: { select: { ordinal: true, hash: true } }, }, }); + expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); @@ -374,11 +375,7 @@ describe("DAG Handler Integration Tests", () => { OR: [{ source_addr: address }, { destination_addr: address }], }, include: { - dag_blocks: { - include: { - global_snapshot: { select: { ordinal: true } }, - }, - }, + global_snapshot: { select: { ordinal: true } }, }, }); diff --git a/tests/handlers/metagraphHandler.test.ts b/tests/handlers/metagraphHandler.test.ts index c46c0c2..9940645 100644 --- a/tests/handlers/metagraphHandler.test.ts +++ b/tests/handlers/metagraphHandler.test.ts @@ -7,7 +7,6 @@ import { } from "../testUtils"; import { data_addresses, - data_global_snapshots, data_metagraph_snapshots, data_metagraphs, prisma, @@ -39,6 +38,7 @@ const data_metagraph_blocks = [ updated_at: new Date(), }, ]; + const data_metagraph_transactions = [ { metagraph_id: data_metagraphs[0].id, @@ -53,6 +53,7 @@ const data_metagraph_transactions = [ "5056fdfbba0637dcecfc0b7fa3f441c745c852cf850c3bfc0dbc8a7410b8d722", ordinal: 12n, block_hash: data_metagraph_blocks[0].hash, + snapshot_hash: data_metagraph_blocks[0].metagraph_snapshot_hash, created_at: new Date("2025-04-02T00:00:02Z"), updated_at: new Date(), }, @@ -69,7 +70,8 @@ const data_metagraph_transactions = [ "39c9909d3b00552beaa5487c38110267675ab212b3b97654fc5ca9917cb7e72b", ordinal: 123n, block_hash: data_metagraph_blocks[1].hash, - created_at: new Date("2025-04-02T00:00:02Z"), + snapshot_hash: data_metagraph_blocks[1].metagraph_snapshot_hash, + created_at: new Date("2025-04-02T00:01:02Z"), updated_at: new Date(), }, { @@ -85,10 +87,12 @@ const data_metagraph_transactions = [ "39c9909d3b006666666666666666666666666666666666666c5ca9917cb7e72b", ordinal: 123n, block_hash: data_metagraph_blocks[2].hash, - created_at: new Date("2025-04-02T00:00:02Z"), + snapshot_hash: data_metagraph_blocks[2].metagraph_snapshot_hash, + created_at: new Date("2025-04-02T00:02:02Z"), updated_at: new Date(), }, ]; + const data_metagraph_balance_changes = [ { metagraph_id: data_metagraphs[0].id, From 221e3ae9c75d3d52707891caff763012dc1758e4 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Wed, 21 May 2025 10:16:21 -0300 Subject: [PATCH 42/63] rename sizeInKB --- src/response.ts | 2 +- tests/handlers/metagraphHandler.test.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/response.ts b/src/response.ts index adf5e5b..8e9e3df 100644 --- a/src/response.ts +++ b/src/response.ts @@ -100,7 +100,7 @@ export const metagraphSnapshotResponse = (snapshot) => ({ fee: snapshot.fee, stakingAddress: snapshot.staking_address ?? null, ownerAddress: snapshot.owner_address ?? null, - sizeInKb: snapshot.size, + sizeInKB: snapshot.size, }); export const metagraphBlockResponse = (block) => ({ diff --git a/tests/handlers/metagraphHandler.test.ts b/tests/handlers/metagraphHandler.test.ts index 9940645..70875cf 100644 --- a/tests/handlers/metagraphHandler.test.ts +++ b/tests/handlers/metagraphHandler.test.ts @@ -240,6 +240,7 @@ describe("Metagraph Handler Integration Tests", () => { expect(snapshot.subHeight).toBe(Number(testSnapshot.subheight)); expect(Array.isArray(snapshot.blocks)).toBe(true); expect(snapshot.timestamp).toBeDefined(); + expect(snapshot.sizeInKB).toBe(Number(testSnapshot.size)); }); it("should handle pagination correctly", async () => { From 36c35fcb9f46582b580b41c4f19fe5a0a85ca170 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Thu, 29 May 2025 10:00:37 -0300 Subject: [PATCH 43/63] fix: delegated staking pagination --- src/handlers/delegatedStakingHandler.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/handlers/delegatedStakingHandler.ts b/src/handlers/delegatedStakingHandler.ts index 46da809..24a7054 100644 --- a/src/handlers/delegatedStakingHandler.ts +++ b/src/handlers/delegatedStakingHandler.ts @@ -9,6 +9,7 @@ import { paginatedQuery, toOrdinalCursor, fromOrdinalCursor, + hashCursor, } from "../pagination"; import { handleError, respond } from "../response"; @@ -174,8 +175,8 @@ export const delegatedStakes = async ( const statusWhere = buildStatusWhereQuery(statuses); return paginatedQuery( extractPagination(event), - toOrdinalCursor, - fromOrdinalCursor, + hashCursor, + hashCursor, { where: { ...statusWhere, @@ -219,8 +220,8 @@ export const addressDelegatedStakes = async (event) => { return paginatedQuery( extractPagination(event), - toOrdinalCursor, - fromOrdinalCursor, + hashCursor, + hashCursor, { where: { source_addr: address, @@ -241,8 +242,8 @@ export const addressDelegatedStakes = async (event) => { export const delegatedStakeWithdrawals = async (event) => { return paginatedQuery( extractPagination(event), - toOrdinalCursor, - fromOrdinalCursor, + hashCursor, + hashCursor, { include: { delegate_stake_create_event: true }, orderBy: [{ created_at: "desc" }], @@ -271,8 +272,8 @@ export const addressDelegatedStakeWithdrawals = async (event) => { const address = event.pathParameters?.address; return paginatedQuery( extractPagination(event), - toOrdinalCursor, - fromOrdinalCursor, + hashCursor, + hashCursor, { where: { source_addr: address, @@ -315,8 +316,8 @@ export const stakingPositions = async ( return paginatedQuery( extractPagination(event), - toOrdinalCursor, - fromOrdinalCursor, + hashCursor, + hashCursor, { where: { OR: whereConditions, From 730c121877b8e1ff0c79245679c83a08d8d01764 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Mon, 2 Jun 2025 18:28:42 -0300 Subject: [PATCH 44/63] add nodeId filter --- src/handlers/delegatedStakingHandler.ts | 4 +++ .../handlers/delegatedStakingHandler.test.ts | 29 ++++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/handlers/delegatedStakingHandler.ts b/src/handlers/delegatedStakingHandler.ts index 24a7054..205d926 100644 --- a/src/handlers/delegatedStakingHandler.ts +++ b/src/handlers/delegatedStakingHandler.ts @@ -295,6 +295,9 @@ export const stakingPositions = async ( const statuses = statusFilter(event); const statusWhere = buildStatusWhereQuery(statuses); + const nodeId = event.queryStringParameters?.nodeId; + const nodeIdWhere = nodeId ? { node_id: nodeId } : {}; + const maxOrdinals = await prisma.delegate_stake_create_events.groupBy({ by: ["source_addr", "node_id"], _max: { @@ -322,6 +325,7 @@ export const stakingPositions = async ( where: { OR: whereConditions, ...statusWhere, + ...nodeIdWhere, ...addressFilter, }, include: { diff --git a/tests/handlers/delegatedStakingHandler.test.ts b/tests/handlers/delegatedStakingHandler.test.ts index 162e01e..16f127d 100644 --- a/tests/handlers/delegatedStakingHandler.test.ts +++ b/tests/handlers/delegatedStakingHandler.test.ts @@ -48,7 +48,7 @@ const data_delegate_stake_create_events = [ hash: "stake-event-hash-003", ordinal: 10003n, source_addr: data_addresses[0].address, - node_id: "NODE_ABC123", + node_id: "NODE_ABC234", amount: 2222222222n, fee: 500000n, lock_reference_hash: data_dag_token_locks[1].hash, @@ -250,9 +250,8 @@ describe("Delegated Stake Handler Integration Tests", () => { }); describe("stakingPositions", () => { - it("should return staking positions for an address", async () => { - const test = data_delegate_stake_create_events[0]; - const event = createAPIGatewayEvent({ address: test.source_addr }); + it("should return staking positions", async () => { + const event = createAPIGatewayEvent(); const response = await stakingPositions(event); expect(response.statusCode).toBe(200); @@ -277,5 +276,27 @@ describe("Delegated Stake Handler Integration Tests", () => { expect(["active", "pendingWithdrawal"]).toContain(tx.status); }); }); + + it("should return staking positions for an address", async () => { + const test = data_delegate_stake_create_events[0]; + const event = createAPIGatewayEvent({ address: test.source_addr }); + + const response = await stakingPositions(event); + expect(response.statusCode).toBe(200); + + const body = validatePaginatedResponse(response); + body.data.forEach(validateStakingPosition); + }); + + it("should return staking positions for a node", async () => { + const test = data_delegate_stake_create_events[0]; + const event = createAPIGatewayEvent({}, { nodeId: "NODE_ABC123" }); + + const response = await stakingPositions(event); + expect(response.statusCode).toBe(200); + + const body = validatePaginatedResponse(response); + body.data.forEach(validateStakingPosition); + }); }); }); From 5c2b2a106d140a05ed184ad0bd1817d54d2b403d Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Mon, 2 Jun 2025 11:36:14 -0300 Subject: [PATCH 45/63] return transaction original --- prisma/schema.prisma | 2 ++ src/response.ts | 1 + tests/handlers/dagHandler.test.ts | 34 +++++++++++++++++++++++++ tests/handlers/metagraphHandler.test.ts | 34 +++++++++++++++++++++++++ 4 files changed, 71 insertions(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 55c6234..fb3b64e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -241,6 +241,7 @@ model dag_transactions { block_hash String @db.VarChar dag_blocks dag_blocks @relation(fields: [block_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_transaction_dag_block_fk") snapshot_hash String @db.VarChar + transaction_original Json? global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_dag_transactions_global_snapshot_fk") addresses_dag_transactions_destination_addrToaddresses addresses @relation("dag_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_transactions_destination_addr_fk") addresses_dag_transactions_source_addrToaddresses addresses @relation("dag_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_transactions_source_addr_fk") @@ -493,6 +494,7 @@ model metagraph_transactions { ordinal BigInt block_hash String @db.VarChar snapshot_hash String @db.VarChar + transaction_original Json? metagraph_snapshot metagraph_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transactions_metagraph_snapshot_fk") metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") metagraph_blocks metagraph_blocks @relation(fields: [metagraph_id, block_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transaction_metagraph_block_fk") diff --git a/src/response.ts b/src/response.ts index 8e9e3df..82bbcce 100644 --- a/src/response.ts +++ b/src/response.ts @@ -63,6 +63,7 @@ export const transactionResponse = (transaction) => { blockHash: transaction.block_hash, snapshotHash: snapshot.hash, snapshotOrdinal: snapshot.ordinal, + transactionOriginal: transaction.transaction_original, timestamp: transaction.created_at, }; }; diff --git a/tests/handlers/dagHandler.test.ts b/tests/handlers/dagHandler.test.ts index d10f603..f301144 100644 --- a/tests/handlers/dagHandler.test.ts +++ b/tests/handlers/dagHandler.test.ts @@ -42,6 +42,17 @@ const data_dag_transactions = [ ordinal: 234n, block_hash: data_dag_blocks[0].hash, snapshot_hash: data_dag_blocks[0].snapshot_hash, + transaction_original: { + fee: 1, + salt: 8971636413389910, + amount: 2768293270, + parent: { + hash: "ec37fa4b293d4c3d08c8474dad933d37219c94a80d4b934af3b27114cac7bf6e", + ordinal: 20631, + }, + source: "DAG4nBMH7KwFAnpfB6VaFRaKEwACNSidzeMuPtgV", + destination: "DAG5t8AVrmUK2dLF9DNLavMJvFRm4dpMYKBqnd9r", + }, created_at: new Date("2025-04-02T00:00:02Z"), updated_at: new Date(), }, @@ -58,6 +69,17 @@ const data_dag_transactions = [ ordinal: 3222n, block_hash: data_dag_blocks[1].hash, snapshot_hash: data_dag_blocks[1].snapshot_hash, + transaction_original: { + fee: 1, + salt: 8885443039669825, + amount: 163220763999, + parent: { + hash: "0000000000000000000000000000000000000000000000000000000000000000", + ordinal: 0, + }, + source: "DAG8YtxgtUe8rfvpy1n3z4Q3ayATmZ7M2Xck7xeW", + destination: "DAG1pLpkyX7aTtFZtbF98kgA9QTZRzrsGaFmf4BT", + }, created_at: new Date("2025-04-02T00:01:02Z"), updated_at: new Date(), }, @@ -74,6 +96,17 @@ const data_dag_transactions = [ ordinal: 3222n, block_hash: data_dag_blocks[1].hash, snapshot_hash: data_dag_blocks[1].snapshot_hash, + transaction_original: { + fee: 200000, + salt: 8759543125914451, + amount: 5000000000, + parent: { + hash: "ef8e659dabf02247eabdf91ce143c0986efa8b1dbbc0305d8e6c0866820f3c77", + ordinal: 45, + }, + source: "DAG3Vj5dp43ZqcJWNzrKVaeaDTkLJZqpbqWuEhBj", + destination: "DAG85EgRCHMeBYak31AxBzfinmML4bJWdRVH4XxF", + }, created_at: new Date("2025-04-02T00:02:02Z"), updated_at: new Date(), }, @@ -128,6 +161,7 @@ const validateTransaction = (tx) => { expect(tx.fee).toBe(Number(dbTxn.fee)); expect(tx.snapshotHash).toBe(dbSnapshot.hash); expect(tx.snapshotOrdinal).toBe(Number(dbSnapshot.ordinal)); + expect(tx.transactionOriginal).toEqual(dbTxn.transaction_original); expect(+new Date(tx.timestamp)).toBe(+new Date(dbTxn.created_at)); }; diff --git a/tests/handlers/metagraphHandler.test.ts b/tests/handlers/metagraphHandler.test.ts index 70875cf..0e42839 100644 --- a/tests/handlers/metagraphHandler.test.ts +++ b/tests/handlers/metagraphHandler.test.ts @@ -54,6 +54,17 @@ const data_metagraph_transactions = [ ordinal: 12n, block_hash: data_metagraph_blocks[0].hash, snapshot_hash: data_metagraph_blocks[0].metagraph_snapshot_hash, + transaction_original: { + fee: 200000, + salt: 8729335446529965, + amount: 90790983, + parent: { + hash: "b2eddf3965dfb783912988b102f96ac5cb88e25e86435a1cdc4ad8e5c20ddd92", + ordinal: 88, + }, + source: "DAG3QYhN1CmobGVpFNr3fgt8bWmeNSGavFLLKyKZ", + destination: "DAG3zhEZcnpZEfrQvmKxn8oBAUDUGNQQ4ENQxyQV", + }, created_at: new Date("2025-04-02T00:00:02Z"), updated_at: new Date(), }, @@ -71,6 +82,17 @@ const data_metagraph_transactions = [ ordinal: 123n, block_hash: data_metagraph_blocks[1].hash, snapshot_hash: data_metagraph_blocks[1].metagraph_snapshot_hash, + transaction_original: { + fee: 20, + salt: 8729335446529965, + amount: 90790983, + parent: { + hash: "b2eddf3965dfb783912988b102f96ac5cb88e25e86435a1cdc4ad8e5c20ddd92", + ordinal: 88, + }, + source: "DAG3QYhN1CmobGVpFNr3fgt8bWmeNSGavFLLKyKZ", + destination: "DAG3zhEZcnpZEfrQvmKxn8oBAUDUGNQQ4ENQxyQV", + }, created_at: new Date("2025-04-02T00:01:02Z"), updated_at: new Date(), }, @@ -88,6 +110,17 @@ const data_metagraph_transactions = [ ordinal: 123n, block_hash: data_metagraph_blocks[2].hash, snapshot_hash: data_metagraph_blocks[2].metagraph_snapshot_hash, + transaction_original: { + fee: 10, + salt: 8729335446529965, + amount: 123123, + parent: { + hash: "b2eddf3965dfb783912988b102f96ac5cb88e25e86435a1cdc4ad8e5c20ddd92", + ordinal: 88, + }, + source: "DAG3QYhN1CmobGVpFNr3fgt8bWmeNSGavFLLKyKZ", + destination: "DAG3zhEZcnpZEfrQvmKxn8oBAUDUGNQQ4ENQxyQV", + }, created_at: new Date("2025-04-02T00:02:02Z"), updated_at: new Date(), }, @@ -190,6 +223,7 @@ const validateTransaction = (tx, expected) => { expect(tx.fee).toBe(Number(expected.fee)); expect(tx.snapshotHash).toBe(dbSnapshot.hash); expect(tx.snapshotOrdinal).toBe(Number(dbSnapshot.ordinal)); + expect(tx.transactionOriginal).toEqual(expected.transaction_original); expect(+new Date(tx.timestamp)).toBe(+new Date(expected.created_at)); }; From e7527b39c433a80c9f4474b93e0e4688c52e4f59 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Tue, 3 Jun 2025 10:18:24 -0300 Subject: [PATCH 46/63] add delegated staking to actions --- docs/schemas/actions.yml | 7 + .../20250529/01_add_staking_to_actions.sql | 73 ++++ prisma/schema.prisma | 363 +++++++++--------- prisma/seed.ts | 314 +++++++++++++++ src/handlers/actionsHandler.ts | 114 +++--- tests/handlers/actionsHandler.test.ts | 162 ++++++++ tests/handlers/allowSpendsHandler.test.ts | 186 +-------- .../handlers/delegatedStakingHandler.test.ts | 87 +---- tests/handlers/metagraphHandler.test.ts | 34 +- 9 files changed, 800 insertions(+), 540 deletions(-) create mode 100644 prisma/migrations/20250529/01_add_staking_to_actions.sql create mode 100644 tests/handlers/actionsHandler.test.ts diff --git a/docs/schemas/actions.yml b/docs/schemas/actions.yml index 4a3f6c7..7a8c9f8 100644 --- a/docs/schemas/actions.yml +++ b/docs/schemas/actions.yml @@ -33,6 +33,13 @@ components: type: string description: Reference to a parent transaction (e.g., token lock reference for token unlocks or allow spend reference for spend transactions) nullable: true + oneOf: + globalSnapshotOrdinal: + type: integer + description: Global snapshot ordinal if it's a DAG action + metagraphSnapshotOrdinal: + type: integer + description: Metagraph snapshot ordinal if it's a currency action timestamp: type: string description: The timestamp when the transaction was created diff --git a/prisma/migrations/20250529/01_add_staking_to_actions.sql b/prisma/migrations/20250529/01_add_staking_to_actions.sql new file mode 100644 index 0000000..27ae4a0 --- /dev/null +++ b/prisma/migrations/20250529/01_add_staking_to_actions.sql @@ -0,0 +1,73 @@ +-- DAG Tables +CREATE OR REPLACE VIEW dag_actions_view AS + SELECT + hash, source_addr, amount, created_at, updated_at, 'AllowSpend' as transaction_type, snapshot_hash, + destination_addr, last_valid_epoch_progress AS unlock_epoch, parent_hash, fee + FROM dag_allow_spends +UNION ALL + SELECT + hash, source_addr, amount, created_at, updated_at, 'SpendTransaction', snapshot_hash, + destination_addr, null, allow_spend_ref, null + FROM dag_spend_transactions +UNION ALL + SELECT + hash, source_addr, amount, created_at, updated_at, 'ExpiredSpendTransaction', snapshot_hash, + null, null, allow_spend_ref, null + FROM dag_expired_spend_transactions +UNION ALL + SELECT + hash, source_addr, amount, created_at, updated_at, 'TokenLock', snapshot_hash, + null, unlock_epoch, parent_hash, null + FROM dag_token_locks +UNION ALL + SELECT hash, source_addr, amount, created_at, updated_at, 'TokenUnlock', snapshot_hash, + null, null, lock_reference_hash, null + FROM dag_token_unlocks +UNION ALL + SELECT + hash, source_addr, amount, created_at, updated_at, 'DelegateStakeCreate', global_snapshot_hash, + null, null, parent_hash, fee + FROM delegate_stake_create_events +UNION ALL + SELECT + dswe.hash, dswe.source_addr, dsce.amount, dswe.created_at, dswe.updated_at, 'DelegateStakeWithdraw', dswe.global_snapshot_hash, + null, dswe.unlock_epoch, dswe.stake_create_hash, null + FROM delegate_stake_withdraw_events dswe + LEFT JOIN delegate_stake_create_events dsce ON dswe.stake_create_hash = dsce.hash; +-- UNION ALL + -- SELECT + -- hash, source_addr, amount, created_at, updated_at, 'Transaction', snapshot_hash, + -- destination_addr, null, parent_hash, fee + -- FROM dag_transactions + + +-- Metagraph Tables +CREATE OR REPLACE VIEW metagraph_actions_view AS + SELECT hash, source_addr, amount, created_at, updated_at, 'AllowSpend' as transaction_type, snapshot_hash, + destination_addr, last_valid_epoch_progress AS unlock_epoch, parent_hash, fee + FROM metagraph_allow_spends +UNION ALL + SELECT hash, source_addr, amount, created_at, updated_at, 'SpendTransaction', snapshot_hash, + destination_addr, null, allow_spend_ref, null + FROM metagraph_spend_transactions +UNION ALL + SELECT hash, source_addr, amount, created_at, updated_at, 'ExpiredSpendTransaction', snapshot_hash, + null, null, allow_spend_ref, null + FROM metagraph_expired_spend_transactions +UNION ALL + SELECT hash, source_addr, amount, created_at, updated_at, 'TokenLock' as transaction_type, snapshot_hash, + null, unlock_epoch, parent_hash, null + FROM metagraph_token_locks +UNION ALL + SELECT hash, source_addr, amount, created_at, updated_at, 'TokenUnlock', snapshot_hash, + null, null, lock_reference_hash, null + FROM metagraph_token_unlocks +UNION ALL + SELECT hash, source_addr, amount, created_at, updated_at, 'FeeTransaction', metagraph_snapshot_hash, + destination_addr, null, data_update_ref, null + FROM metagraph_fee_transactions +-- UNION ALL +-- SELECT hash, source_addr, amount, created_at, updated_at, 'Transaction', snapshot_hash, +-- destination_addr, null, parent_hash, fee +-- FROM metagraph_transactions; + diff --git a/prisma/schema.prisma b/prisma/schema.prisma index fb3b64e..4164b5a 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -19,44 +19,48 @@ model abstract_blocks { metagraph_block metagraph_blocks? @relation(fields: [hash], references: [hash], map: "metagraph_blocks_hash_fk") } -/// This table has subclasses and requires additional setup for migrations. Visit https://pris.ly/d/table-inheritance for more info. -model abstract_transactions { - hash String @id(map: "hash_pkey") @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) -} - -model abstract_transactions_view { - hash String @id @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - table_name String - snapshot_hash String @db.VarChar - - dag_token_lock dag_token_locks? @relation(fields: [hash], references: [hash], map: "dag_token_locks_ref") - metagraph_token_lock metagraph_token_locks? @relation(fields: [hash], references: [hash], map: "metagraph_token_locks_ref") - - dag_token_unlock dag_token_unlocks? @relation(fields: [hash], references: [hash], map: "dag_token_unlocks_ref") - metagraph_token_unlock metagraph_token_unlocks? @relation(fields: [hash], references: [hash], map: "metagraph_token_unlocks_ref") - - dag_allow_spend dag_allow_spends? @relation(fields: [hash], references: [hash], map: "dag_allow_spend_ref") - metagraph_allow_spend metagraph_allow_spends? @relation(fields: [hash], references: [hash], map: "metagraph_allow_spend_ref") - - dag_spend_transaction dag_spend_transactions? @relation(fields: [hash], references: [hash], map: "dag_spend_transaction_ref") - metagraph_spend_transaction metagraph_spend_transactions? @relation(fields: [hash], references: [hash], map: "metagraph_spend_transaction_ref") - - dag_expired_spend_transaction dag_expired_spend_transactions? @relation(fields: [hash], references: [hash], map: "dag_expired_spend_transaction_ref") +model dag_actions_view { + hash String @id @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + transaction_type String @db.VarChar + snapshot_hash String @db.VarChar + destination_addr String? @db.VarChar + unlock_epoch BigInt? + parent_hash String? @db.VarChar + fee BigInt? + + dag_token_lock dag_token_locks? @relation(fields: [hash], references: [hash], map: "dag_token_locks_ref") + dag_token_unlock dag_token_unlocks? @relation(fields: [hash], references: [hash], map: "dag_token_unlocks_ref") + dag_allow_spend dag_allow_spends? @relation(fields: [hash], references: [hash], map: "dag_allow_spend_ref") + dag_spend_transaction dag_spend_transactions? @relation(fields: [hash], references: [hash], map: "dag_spend_transaction_ref") + dag_expired_spend_transaction dag_expired_spend_transactions? @relation(fields: [hash], references: [hash], map: "dag_expired_spend_transaction_ref") + + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_actions_view_global_snapshot_ref") +} + +model metagraph_actions_view { + hash String @id @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + transaction_type String + snapshot_hash String @db.VarChar + destination_addr String? @db.VarChar + unlock_epoch BigInt? + parent_hash String? @db.VarChar + fee BigInt? + + metagraph_token_lock metagraph_token_locks? @relation(fields: [hash], references: [hash], map: "metagraph_token_locks_ref") + metagraph_token_unlock metagraph_token_unlocks? @relation(fields: [hash], references: [hash], map: "metagraph_token_unlocks_ref") + metagraph_allow_spend metagraph_allow_spends? @relation(fields: [hash], references: [hash], map: "metagraph_allow_spend_ref") + metagraph_spend_transaction metagraph_spend_transactions? @relation(fields: [hash], references: [hash], map: "metagraph_spend_transaction_ref") metagraph_expired_spend_transaction metagraph_expired_spend_transactions? @relation(fields: [hash], references: [hash], map: "metagraph_expired_spend_transaction_ref") - - metagraph_fee_transaction metagraph_fee_transactions? @relation(fields: [hash], references: [hash], map: "metagraph_fee_transaction_ref") - - global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "abstract_transactions_view_global_snapshot_ref") - ///metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_balance_change_metagraph_snapshot_fk") - metagraph_snapshot metagraph_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "abstract_transactions_view_metagraph_snapshot_ref") + metagraph_fee_transaction metagraph_fee_transactions? @relation(fields: [hash], references: [hash], map: "metagraph_fee_transaction_ref") + metagraph_snapshot metagraph_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_actions_view_metagraph_snapshot_ref") } model addresses { @@ -130,8 +134,8 @@ model dag_allow_spends { addresses_dag_allow_spends_source_addrToaddresses addresses @relation("dag_allow_spends_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spends_source_addr_fk") dag_expired_spend_transaction dag_expired_spend_transactions? dag_spend_transaction dag_spend_transactions? - abstract_transactions_view abstract_transactions_view? global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spends_global_snapshot_fk") + dag_actions_view dag_actions_view[] @@index([round_id]) } @@ -177,18 +181,19 @@ model dag_reward_transactions { } model dag_spend_transactions { - hash String @id(map: "dag_spend_transactions_pk") @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - destination_addr String? @db.VarChar - allow_spend_ref String? @unique @db.VarChar - snapshot_hash String @db.VarChar - dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_dag_allow_spends_fk") - addresses addresses? @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_destination_addr_fk") - abstract_transactions_view abstract_transactions_view? - global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) + hash String @id(map: "dag_spend_transactions_pk") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + destination_addr String? @db.VarChar + allow_spend_ref String? @unique @db.VarChar + snapshot_hash String @db.VarChar + dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_dag_allow_spends_fk") + addresses addresses? @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_destination_addr_fk") + + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) + dag_actions_view dag_actions_view[] } model dag_token_locks { @@ -201,50 +206,51 @@ model dag_token_locks { unlock_epoch BigInt? round_id String @db.Uuid snapshot_hash String @db.VarChar + parent_hash String? @db.VarChar addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_locks_source_addr_fk") global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_lock_global_snapshot_fk") dag_token_unlock dag_token_unlocks? delegate_stake_create_events delegate_stake_create_events[] - abstract_transactions_view abstract_transactions_view? + dag_actions_view dag_actions_view[] @@unique([ordinal], map: "dag_token_locks_unique") } model dag_token_unlocks { - hash String @id(map: "dag_token_unlocks_pk") @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - lock_reference_hash String @db.VarChar - snapshot_hash String @db.VarChar - global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_unlock_global_snapshot_fk") - dag_token_lock dag_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_unlocks_token_locks_fk") - addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "ddag_token_unlocks_address_fk") - abstract_transactions_view abstract_transactions_view? + hash String @id(map: "dag_token_unlocks_pk") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + lock_reference_hash String @db.VarChar + snapshot_hash String @db.VarChar + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_unlock_global_snapshot_fk") + dag_token_lock dag_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_unlocks_token_locks_fk") + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "ddag_token_unlocks_address_fk") + dag_actions_view dag_actions_view[] @@unique([lock_reference_hash], map: "lock_reference_hash_unique") } model dag_transactions { - hash String @id(map: "dag_transaction_pk") @db.VarChar - source_addr String @db.VarChar + hash String @id(map: "dag_transaction_pk") @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - destination_addr String @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + destination_addr String @db.VarChar fee BigInt salt BigInt parent_ordinal BigInt? - parent_hash String? @db.VarChar + parent_hash String? @db.VarChar ordinal BigInt - block_hash String @db.VarChar - dag_blocks dag_blocks @relation(fields: [block_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_transaction_dag_block_fk") - snapshot_hash String @db.VarChar + block_hash String @db.VarChar + dag_blocks dag_blocks @relation(fields: [block_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_transaction_dag_block_fk") + snapshot_hash String @db.VarChar transaction_original Json? - global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_dag_transactions_global_snapshot_fk") - addresses_dag_transactions_destination_addrToaddresses addresses @relation("dag_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_transactions_destination_addr_fk") - addresses_dag_transactions_source_addrToaddresses addresses @relation("dag_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_transactions_source_addr_fk") + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_dag_transactions_global_snapshot_fk") + addresses_dag_transactions_destination_addrToaddresses addresses @relation("dag_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_transactions_destination_addr_fk") + addresses_dag_transactions_source_addrToaddresses addresses @relation("dag_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_transactions_source_addr_fk") } model global_snapshot_proofs { @@ -283,7 +289,7 @@ model global_snapshots { dag_allow_spends dag_allow_spends[] dag_spend_transactions dag_spend_transactions[] dag_expired_spend_transactions dag_expired_spend_transactions[] - abstract_transactions_view abstract_transactions_view[] + dag_actions_view dag_actions_view[] } model metagraph_allow_spend_approvers { @@ -316,8 +322,8 @@ model metagraph_allow_spends { metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") metagraph_expired_spend_transaction metagraph_expired_spend_transactions? metagraph_spend_transaction metagraph_spend_transactions? - abstract_transactions_view abstract_transactions_view? metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash]) + metagraph_actions_view metagraph_actions_view[] } model metagraph_balance_changes { @@ -352,21 +358,22 @@ model metagraph_blocks { } model metagraph_fee_transactions { - hash String @db.VarChar @id - source_addr String @db.VarChar + hash String @id @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar - metagraph_snapshot_hash String @db.VarChar - destination_addr String @db.VarChar - data_update_ref String? @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + metagraph_snapshot_hash String @db.VarChar + destination_addr String @db.VarChar + data_update_ref String? @db.VarChar metagraph_snapshot_ordinal BigInt? - metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "fee_transaction_metagraph_snapshot_fk") - addresses_metagraph_fee_transactions_destination_addrToaddresses addresses @relation("metagraph_fee_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_fee_transactions_destination_addr_fk") - addresses_metagraph_fee_transactions_source_addrToaddresses addresses @relation("metagraph_fee_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_fee_transactions_source_addr_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") - abstract_transactions_view abstract_transactions_view? + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_id, metagraph_snapshot_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "fee_transaction_metagraph_snapshot_fk") + addresses_metagraph_fee_transactions_destination_addrToaddresses addresses @relation("metagraph_fee_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_fee_transactions_destination_addr_fk") + addresses_metagraph_fee_transactions_source_addrToaddresses addresses @relation("metagraph_fee_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_fee_transactions_source_addr_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") + metagraph_actions_view metagraph_actions_view[] + @@unique([metagraph_id, hash], map: "metagraph_id_hash") } @@ -400,7 +407,7 @@ model metagraph_snapshots { version String @db.VarChar created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_transactions metagraph_transactions[] + metagraph_transactions metagraph_transactions[] metagraph_allow_spends metagraph_allow_spends[] metagraph_balance_changes metagraph_balance_changes[] metagraph_blocks metagraph_blocks[] @@ -416,45 +423,48 @@ model metagraph_snapshots { metagraph_token_lock_blocks metagraph_token_lock_blocks[] metagraph_spend_transactions metagraph_spend_transactions[] metagraph_expired_spend_transactions metagraph_expired_spend_transactions[] - abstract_transactions_view abstract_transactions_view[] + metagraph_actions_view metagraph_actions_view[] @@id([metagraph_id, hash], map: "metagraph_snapshot_pk") @@unique([metagraph_id, ordinal], map: "metagraph_snapshot_unique") } model metagraph_spend_transactions { - hash String @id(map: "metagraph_spend_transactions_pk") @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar - destination_addr String @db.VarChar - allow_spend_ref String? @unique @db.VarChar - snapshot_hash String @db.VarChar - addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_destination_addr_fk") - metagraph_allow_spend metagraph_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "mg_spend_transactions_mg_allow_spends_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_metagraph_id_fk") - metagraph_snapshot metagraph_snapshots? @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash]) - abstract_transactions_view abstract_transactions_view? + hash String @id(map: "metagraph_spend_transactions_pk") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + destination_addr String @db.VarChar + allow_spend_ref String? @unique @db.VarChar + snapshot_hash String @db.VarChar + addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_destination_addr_fk") + metagraph_allow_spend metagraph_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "mg_spend_transactions_mg_allow_spends_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_metagraph_id_fk") + metagraph_snapshot metagraph_snapshots? @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash]) + + metagraph_actions_view metagraph_actions_view[] } model metagraph_token_locks { - hash String @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar - ordinal BigInt - unlock_epoch BigInt - round_id String @db.VarChar - snapshot_hash String @db.VarChar - metagraph metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") - addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_locks_source_addr_fk") - abstract_transactions_view abstract_transactions_view? - metagraph_token_unlock metagraph_token_unlocks? - metagraph_snapshot metagraph_snapshots? @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash]) + hash String @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + ordinal BigInt + unlock_epoch BigInt + parent_hash String? @db.VarChar + round_id String @db.VarChar + snapshot_hash String @db.VarChar + metagraph metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_locks_source_addr_fk") + + metagraph_token_unlock metagraph_token_unlocks? + metagraph_snapshot metagraph_snapshots? @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash]) + metagraph_actions_view metagraph_actions_view[] @@id([metagraph_id, hash], map: "metagraph_token_locks_pk") @@unique([hash]) @@ -462,44 +472,45 @@ model metagraph_token_locks { } model metagraph_token_unlocks { - hash String @id(map: "metagraph_token_unlocks_pk") @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar - lock_reference_hash String @db.VarChar - snapshot_hash String @db.VarChar - addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "address_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") - abstract_transactions_view abstract_transactions_view? - metagraph_snapshot metagraph_snapshots? @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash]) - metagraph_token_lock metagraph_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_unlocks_token_locks_fk") + hash String @id(map: "metagraph_token_unlocks_pk") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + lock_reference_hash String @db.VarChar + snapshot_hash String @db.VarChar + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "address_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") + + metagraph_snapshot metagraph_snapshots? @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash]) + metagraph_token_lock metagraph_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_unlocks_token_locks_fk") + metagraph_actions_view metagraph_actions_view[] @@unique([lock_reference_hash], map: "metagraph_token_unlocks_locks_fk") } model metagraph_transactions { - hash String @db.VarChar - source_addr String @db.VarChar + hash String @db.VarChar + source_addr String @db.VarChar amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar - destination_addr String @db.VarChar + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + destination_addr String @db.VarChar fee BigInt salt BigInt parent_ordinal BigInt - parent_hash String @db.VarChar + parent_hash String @db.VarChar ordinal BigInt - block_hash String @db.VarChar - snapshot_hash String @db.VarChar + block_hash String @db.VarChar + snapshot_hash String @db.VarChar transaction_original Json? - metagraph_snapshot metagraph_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transactions_metagraph_snapshot_fk") - metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") - metagraph_blocks metagraph_blocks @relation(fields: [metagraph_id, block_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transaction_metagraph_block_fk") - addresses_metagraph_transactions_destination_addrToaddresses addresses @relation("metagraph_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transactions_destination_addrfk") - addresses_metagraph_transactions_source_addrToaddresses addresses @relation("metagraph_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transactions_source_addr_fk") + metagraph_snapshot metagraph_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transactions_metagraph_snapshot_fk") + metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") + metagraph_blocks metagraph_blocks @relation(fields: [metagraph_id, block_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transaction_metagraph_block_fk") + addresses_metagraph_transactions_destination_addrToaddresses addresses @relation("metagraph_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transactions_destination_addrfk") + addresses_metagraph_transactions_source_addrToaddresses addresses @relation("metagraph_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transactions_source_addr_fk") @@id([metagraph_id, hash], map: "metagraph_transaction_pk") } @@ -522,30 +533,32 @@ model metagraphs { } model dag_expired_spend_transactions { - hash String @id(map: "dag_expired_spend_transactions_pk") @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - allow_spend_ref String @unique @db.VarChar - snapshot_hash String @db.VarChar - dag_allow_spend dag_allow_spends @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_expired_spend_transactions_dag_allow_spends_fk") - global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) - abstract_transactions_view abstract_transactions_view? + hash String @id(map: "dag_expired_spend_transactions_pk") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + allow_spend_ref String @unique @db.VarChar + snapshot_hash String @db.VarChar + dag_allow_spend dag_allow_spends @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_expired_spend_transactions_dag_allow_spends_fk") + global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) + + dag_actions_view dag_actions_view[] } model metagraph_expired_spend_transactions { - hash String @id(map: "metagraph_expired_spend_transactions_pk") @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - metagraph_id String @db.VarChar - allow_spend_ref String @unique @db.VarChar - snapshot_hash String @db.VarChar - metagraph_allow_spend metagraph_allow_spends @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_expired_spend_transactions_metagraph_allow_spends_fk") - metagraph_snapshot metagraph_snapshots? @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash]) - abstract_transactions_view abstract_transactions_view? + hash String @id(map: "metagraph_expired_spend_transactions_pk") @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + metagraph_id String @db.VarChar + allow_spend_ref String @unique @db.VarChar + snapshot_hash String @db.VarChar + metagraph_allow_spend metagraph_allow_spends @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_expired_spend_transactions_metagraph_allow_spends_fk") + metagraph_snapshot metagraph_snapshots? @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash]) + + metagraph_actions_view metagraph_actions_view[] } model metagraph_token_lock_blocks { @@ -575,13 +588,13 @@ model delegate_stake_create_events { created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - global_snapshots global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) - addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction) - dag_token_lock dag_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) - delegated_from delegate_stake_create_events? @relation(name: "delegation_self") - delegated_to delegate_stake_create_events? @relation(name: "delegation_self", fields: [transfer_from_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction) + global_snapshots global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction) + dag_token_lock dag_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) + delegated_from delegate_stake_create_events? @relation(name: "delegation_self") + delegated_to delegate_stake_create_events? @relation(name: "delegation_self", fields: [transfer_from_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction) delegate_stake_withdraw_events delegate_stake_withdraw_events[] - delegate_stake_rewards delegate_stake_rewards[] + delegate_stake_rewards delegate_stake_rewards[] @@index([global_snapshot_hash]) @@index([source_addr]) @@ -594,7 +607,7 @@ model delegate_stake_withdraw_events { stake_create_hash String @unique @db.VarChar global_snapshot_hash String @db.VarChar unlock_epoch BigInt - is_completed Boolean + is_completed Boolean created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) diff --git a/prisma/seed.ts b/prisma/seed.ts index 4201491..af54280 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -12,6 +12,8 @@ import { metagraphs, } from "@prisma/client"; import { randomUUID } from "crypto"; +import { readFileSync } from "fs"; +import path from "path"; export const prisma = new PrismaClient(); @@ -247,6 +249,253 @@ export const data_metagraph_token_unlocks = [ }, ]; +export const data_dag_allow_spends = [ + { + hash: "allowSpendHash1", + source_addr: data_addresses[0].address, + destination_addr: data_addresses[1].address, + amount: 1000n, + fee: 5n, + last_valid_epoch_progress: 100n, + ordinal: 1n, + snapshot_hash: data_global_snapshots[0].hash, + round_id: "11111111-1111-1111-1111-111111111111", + created_at: new Date("2024-01-01T10:00:00Z"), + updated_at: new Date("2024-01-01T10:00:00Z"), + }, + { + hash: "allowSpendHash2", + source_addr: data_addresses[0].address, + destination_addr: data_addresses[2].address, + amount: 2000n, + fee: 10n, + last_valid_epoch_progress: 600n, + ordinal: 2n, + snapshot_hash: data_global_snapshots[0].hash, + round_id: "22222222-2222-2222-2222-222222222222", + created_at: new Date("2024-01-02T10:00:00Z"), + updated_at: new Date("2024-01-02T10:00:00Z"), + }, + { + hash: "allowSpendHash3", + source_addr: data_addresses[2].address, + destination_addr: data_addresses[1].address, + amount: 300n, + fee: 15n, + last_valid_epoch_progress: 700n, + ordinal: 3n, + snapshot_hash: data_global_snapshots[1].hash, + round_id: "33333333-3333-3333-3333-333333333333", + created_at: new Date("2024-01-03T10:00:00Z"), + updated_at: new Date("2024-01-03T10:00:00Z"), + }, + { + hash: "allowSpendHash4", + source_addr: data_addresses[2].address, + destination_addr: data_addresses[3].address, + amount: 300n, + fee: 15n, + last_valid_epoch_progress: 700n, + ordinal: 4n, + snapshot_hash: data_global_snapshots[0].hash, + round_id: "33333333-3333-3333-3333-333333333333", + created_at: new Date("2024-01-03T10:00:00Z"), + updated_at: new Date("2024-01-03T10:00:00Z"), + }, +]; + +export const data_dag_spend_transactions = [ + { + hash: "spendTxHash1", + source_addr: data_addresses[0].address, + destination_addr: data_addresses[1].address, + amount: 1000n, + allow_spend_ref: data_dag_allow_spends[0].hash, + snapshot_hash: data_global_snapshots[2].hash, + created_at: new Date("2024-01-05T10:00:00Z"), + updated_at: new Date("2024-01-05T10:00:00Z"), + }, +]; + +export const data_dag_expired_spend_transactions = [ + { + hash: "expiredSpendTxHash1", + source_addr: data_addresses[2].address, + amount: 3000n, + allow_spend_ref: data_dag_allow_spends[1].hash, + snapshot_hash: data_global_snapshots[2].hash, + created_at: new Date("2024-01-10T10:00:00Z"), + updated_at: new Date("2024-01-10T10:00:00Z"), + }, +]; + +export const data_metagraph_allow_spends = [ + { + metagraph_id: data_metagraphs[0].id, + hash: "metaAllowSpendHash1", + source_addr: data_addresses[0].address, + destination_addr: data_addresses[1].address, + amount: 500n, + fee: 3n, + last_valid_epoch_progress: 50n, + ordinal: 1n, + snapshot_hash: data_metagraph_snapshots[0].hash, + round_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + created_at: new Date("2024-01-01T11:00:00Z"), + updated_at: new Date("2024-01-01T11:00:00Z"), + }, + { + metagraph_id: data_metagraphs[0].id, + hash: "metaAllowSpendHash2", + source_addr: data_addresses[0].address, + destination_addr: data_addresses[1].address, + amount: 500n, + fee: 3n, + last_valid_epoch_progress: 50n, + ordinal: 2n, + snapshot_hash: data_metagraph_snapshots[1].hash, + round_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + created_at: new Date("2024-01-01T11:00:00Z"), + updated_at: new Date("2024-01-01T11:00:00Z"), + }, + { + metagraph_id: data_metagraphs[0].id, + hash: "metaAllowSpendHash3", + source_addr: data_addresses[2].address, + destination_addr: data_addresses[3].address, + amount: 750n, + fee: 4n, + last_valid_epoch_progress: 60n, + ordinal: 3n, + snapshot_hash: data_metagraph_snapshots[1].hash, + round_id: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", + created_at: new Date("2024-01-02T11:00:00Z"), + updated_at: new Date("2024-01-02T11:00:00Z"), + }, +]; + +export const data_metagraph_spend_transactions = [ + { + metagraph_id: data_metagraphs[0].id, + hash: "metaSpendTxHash1", + source_addr: data_addresses[0].address, + destination_addr: data_addresses[1].address, + amount: 500n, + allow_spend_ref: data_metagraph_allow_spends[0].hash, + snapshot_hash: data_metagraph_snapshots[0].hash, + created_at: new Date("2024-01-03T11:00:00Z"), + updated_at: new Date("2024-01-03T11:00:00Z"), + }, +]; + +export const data_metagraph_expired_spend_transactions = [ + { + metagraph_id: data_metagraphs[0].id, + hash: "metaExpiredTxHash1", + source_addr: data_addresses[2].address, + amount: 750n, + allow_spend_ref: data_metagraph_allow_spends[2].hash, + snapshot_hash: data_metagraph_snapshots[1].hash, + created_at: new Date("2024-01-04T11:00:00Z"), + updated_at: new Date("2024-01-04T11:00:00Z"), + }, +]; + +export const data_delegate_stake_create_events = [ + { + hash: "stake-event-hash-001", + ordinal: 10001n, + source_addr: data_addresses[0].address, + node_id: "NODE_ABC123", + amount: 500000000000n, + fee: 500000n, + lock_reference_hash: data_dag_token_locks[0].hash, + parent_hash: "parent-hash-xyz", + global_snapshot_hash: data_global_snapshots[0].hash, + }, + { + hash: "stake-event-hash-002", + ordinal: 10002n, + source_addr: data_addresses[0].address, + node_id: "NODE_ABC123", + amount: 3333300000000n, + fee: 500000n, + lock_reference_hash: data_dag_token_locks[1].hash, + parent_hash: "stake-event-hash-001", + global_snapshot_hash: data_global_snapshots[1].hash, + }, + { + hash: "stake-event-hash-003", + ordinal: 10003n, + source_addr: data_addresses[0].address, + node_id: "NODE_ABC234", + amount: 2222222222n, + fee: 500000n, + lock_reference_hash: data_dag_token_locks[1].hash, + parent_hash: "stake-event-hash-001", + global_snapshot_hash: data_global_snapshots[1].hash, + transfer_from_hash: "stake-event-hash-002", + }, +]; + +export const data_delegate_stake_withdraw_events = [ + { + hash: "withdraw-event-hash-001", + source_addr: data_addresses[0].address, + stake_create_hash: data_delegate_stake_create_events[0].hash, + global_snapshot_hash: data_global_snapshots[1].hash, + unlock_epoch: 11000n, + is_completed: false, + }, + { + hash: "withdraw-event-hash-002", + source_addr: data_addresses[0].address, + stake_create_hash: data_delegate_stake_create_events[1].hash, + global_snapshot_hash: data_global_snapshots[1].hash, + unlock_epoch: 8000n, + is_completed: true, + }, +]; + +export const data_delegate_stake_rewards = [ + { + global_snapshot_hash: data_global_snapshots[2].hash, + address: data_addresses[0].address, + node_id: "NODE_ABC123", + rewards: 2500000000n, + stake_create_hash: data_delegate_stake_create_events[0].hash, + }, +]; + +export const data_metagraph_fee_transactions = [ + { + metagraph_id: data_metagraphs[0].id, + metagraph_snapshot_hash: data_metagraph_snapshots[0].hash, + metagraph_snapshot_ordinal: data_metagraph_snapshots[0].ordinal, + hash: "39c9909d3b00552beaa5487c38110267675ab212b3b97654fc5ca9917cb7e72b", + source_addr: data_addresses[0].address, + destination_addr: data_addresses[1].address, + amount: 90790983n, + data_update_ref: + "5056fdfbba0637dcecfc0b7fa3f441c745c852cf850c3bfc0dbc8a7410b8d722", + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + metagraph_id: data_metagraphs[1].id, + metagraph_snapshot_hash: data_metagraph_snapshots[2].hash, + metagraph_snapshot_ordinal: data_metagraph_snapshots[2].ordinal, + hash: "39c990000000000000000000000000000000000000007654fc5ca9917cb7e72b", + source_addr: data_addresses[2].address, + destination_addr: data_addresses[3].address, + amount: 10090983n, + data_update_ref: + "39c9909d3b00552beaa5487c38110267675ab212b3b97654fc5ca9917cb7e72b", + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; + export async function seed() { await prisma.addresses.createMany({ data: data_addresses }); @@ -279,6 +528,71 @@ export async function seed() { await prisma.metagraph_token_unlocks.createMany({ data: data_metagraph_token_unlocks, }); + + await prisma.metagraph_allow_spends.createMany({ + data: data_metagraph_allow_spends, + }); + await prisma.metagraph_spend_transactions.createMany({ + data: data_metagraph_spend_transactions, + }); + await prisma.metagraph_expired_spend_transactions.createMany({ + data: data_metagraph_expired_spend_transactions, + }); + + await prisma.metagraph_fee_transactions.createMany({ + data: data_metagraph_fee_transactions, + }); + + await prisma.dag_allow_spends.createMany({ + data: data_dag_allow_spends, + }); + + await prisma.dag_spend_transactions.createMany({ + data: data_dag_spend_transactions, + }); + + await prisma.dag_expired_spend_transactions.createMany({ + data: data_dag_expired_spend_transactions, + }); + + await prisma.delegate_stake_create_events.createMany({ + data: data_delegate_stake_create_events, + }); + + await prisma.delegate_stake_withdraw_events.createMany({ + data: data_delegate_stake_withdraw_events, + }); + + await prisma.delegate_stake_rewards.createMany({ + data: data_delegate_stake_rewards, + }); + + //generate views + //first drop the prisma generated tables + await prisma.$executeRawUnsafe("DROP TABLE dag_actions_view"); + await prisma.$executeRawUnsafe("DROP TABLE metagraph_actions_view"); + runSqlFromFile("./migrations/20250529/01_add_staking_to_actions.sql"); +} + +async function runSqlFromFile(filename: string) { + const filePath = path.resolve(__dirname, filename); + const sql = readFileSync(filePath, "utf-8"); + + const statements = sql + .split(/;\s*$/m) // splits on semicolon at end of line + .map((s) => s.trim()) + .filter((s) => s.length > 0); + + try { + for (const stmt of statements) { + await prisma.$executeRawUnsafe(stmt); + } + console.log(`Executed ${statements.length} statements from: ${filename}`); + } catch (err) { + console.error(`Error executing SQL from: ${filename}`, err); + } finally { + await prisma.$disconnect(); + } } export async function resetDatabase() { diff --git a/src/handlers/actionsHandler.ts b/src/handlers/actionsHandler.ts index 008b36f..aff6777 100644 --- a/src/handlers/actionsHandler.ts +++ b/src/handlers/actionsHandler.ts @@ -6,98 +6,76 @@ import { handleError } from "../response"; const prisma = new PrismaClient(); -const transactionTypeMap: Record = { - AllowSpend: "allow_spends", - TokenLock: "token_locks", - TokenUnlock: "token_unlocks", - SpendTransaction: "spend_transactions", - FeeTransaction: "fee_transactions", - ExpiredSpendTransaction: "expired_spend_transactions", -}; - -const reverseTransactionTypeMap = Object.entries(transactionTypeMap).reduce( - (acc, [key, value]) => ({ ...acc, [value]: key }), - {} as Record -); - -const getTransactionType = (tableName: string): string | undefined => { - const strippedName = tableName.replace(/^(dag_|metagraph_)/, ""); // Remove prefix - return reverseTransactionTypeMap[strippedName]; -}; - -const dagTable = (name: string) => `dag_${name}`; -const metagraphTable = (name: string) => `metagraph_${name}`; - -const actionsTables = Object.values(transactionTypeMap); - -const tableFilter = (event) => { +const allowedTypes = [ + "AllowSpend", + "TokenLock", + "TokenUnlock", + "SpendTransaction", + "FeeTransaction", + "ExpiredSpendTransaction", + "DelegateStakeCreate", + "DelegateStakeWithdraw", +] as const; + +type TransactionType = (typeof allowedTypes)[number]; + +const actionsTransactions: TransactionType[] = [...allowedTypes]; + +function isValidTransactionType(type: string): type is TransactionType { + return allowedTypes.includes(type as TransactionType); +} + +const transactionFilter = (event): TransactionType[] => { const queryParams = event.queryStringParameters || {}; - const transactionTypes = queryParams.transactionTypes?.split(",") || []; + const rawTypes = queryParams.transactionTypes?.split(",") || []; + + const filtered = rawTypes.filter(isValidTransactionType); - return transactionTypes.length > 0 - ? transactionTypes.map((type) => transactionTypeMap[type]).filter(Boolean) - : actionsTables; + return filtered.length > 0 ? filtered : actionsTransactions; }; const currencyId = (transaction) => transaction.metagraph_snapshot?.metagraph_id ?? null; const actionResponse = (transaction) => ({ - type: getTransactionType(transaction.table_name), + type: transaction.transaction_type, currencyId: currencyId(transaction), hash: transaction.hash, amount: transaction.amount, source: transaction.source_addr, destination: transaction.destination_addr ?? null, - unlockEpoch: - transaction.dag_allow_spend?.last_valid_epoch_progress ?? - transaction.dag_token_lock?.unlock_epoch ?? - null, - parentHash: - transaction.dag_spend_transaction?.allow_spend_ref ?? - transaction.dag_token_unlock?.lock_reference_hash ?? - null, + unlockEpoch: transaction.unlock_epoch ?? null, + parentHash: transaction.parent_hash ?? null, timestamp: transaction.created_at, globalSnapshotOrdinal: transaction.global_snapshot?.ordinal, - metagraphSnapshotOrdinal: transaction.metagraph_snapshot?.ordinal, + metagraphSnapshotOrdinal: transaction.metagraph_snapshot?.ordinal, }); export const actionsResponse = (ts) => ts.map(actionResponse); const dagInclude = { global_snapshot: { select: { hash: true, ordinal: true } }, - dag_token_lock: { select: { unlock_epoch: true } }, - dag_allow_spend: { select: { last_valid_epoch_progress: true } }, - dag_spend_transaction: { select: { allow_spend_ref: true } }, - dag_token_unlock: { select: { lock_reference_hash: true } }, - dag_expired_spend_transaction: { select: { allow_spend_ref: true } }, }; const metagraphInclude = { metagraph_snapshot: { select: { hash: true, ordinal: true } }, - metagraph_token_lock: { select: { unlock_epoch: true } }, - metagraph_allow_spend: { select: { last_valid_epoch_progress: true } }, - metagraph_spend_transaction: { select: { allow_spend_ref: true } }, - metagraph_token_unlock: { select: { lock_reference_hash: true } }, - metagraph_expired_spend_transaction: { select: { allow_spend_ref: true } }, - metagraph_fee_transaction: { select: { data_update_ref: true } }, }; export const dagActions = async ( event: APIGatewayProxyEvent ): Promise => { - const selectedTables = tableFilter(event).map(dagTable); + const selectedTransactions = transactionFilter(event); return await paginatedQuery( extractPagination(event), hashCursor, hashCursor, { - where: { table_name: { in: selectedTables } }, + where: { transaction_type: { in: selectedTransactions } }, include: dagInclude, orderBy: [{ created_at: "desc" }, { hash: "desc" }], }, - prisma.abstract_transactions_view.findMany, + prisma.dag_actions_view.findMany, actionsResponse ); }; @@ -109,7 +87,7 @@ export const globalSnapshotActions = async ( const { term } = event.pathParameters || {}; const filter = extractHashOrdinal(term); - const selectedTables = tableFilter(event).map(dagTable); + const selectedTransactions = transactionFilter(event); return await paginatedQuery( extractPagination(event), @@ -118,12 +96,12 @@ export const globalSnapshotActions = async ( { where: { global_snapshot: filter, - table_name: { in: selectedTables }, + transaction_type: { in: selectedTransactions }, }, include: dagInclude, orderBy: [{ created_at: "desc" }, { hash: "asc" }], }, - prisma.abstract_transactions_view.findMany, + prisma.dag_actions_view.findMany, actionsResponse ); } catch (error) { @@ -137,7 +115,7 @@ export const dagAddressActions = async ( try { const { address } = event.pathParameters || {}; - const selectedTables = tableFilter(event).map(dagTable); + const selectedTransactions = transactionFilter(event); return await paginatedQuery( extractPagination(event), @@ -155,12 +133,12 @@ export const dagAddressActions = async ( }, }, ], - table_name: { in: selectedTables }, + transaction_type: { in: selectedTransactions }, }, include: dagInclude, orderBy: [{ created_at: "desc" }, { hash: "asc" }], }, - prisma.abstract_transactions_view.findMany, + prisma.dag_actions_view.findMany, actionsResponse ); } catch (error) { @@ -174,7 +152,7 @@ export const currencyActions = async ( try { const { metagraph_id } = event.pathParameters || {}; - const selectedTables = tableFilter(event).map(metagraphTable); + const selectedTransactions = transactionFilter(event); return await paginatedQuery( extractPagination(event), @@ -183,12 +161,12 @@ export const currencyActions = async ( { where: { metagraph_snapshot: { metagraph_id }, - table_name: { in: selectedTables }, + transaction_type: { in: selectedTransactions }, }, include: metagraphInclude, orderBy: [{ created_at: "desc" }, { hash: "asc" }], }, - prisma.abstract_transactions_view.findMany, + prisma.metagraph_actions_view.findMany, actionsResponse ); } catch (error) { @@ -203,7 +181,7 @@ export const currencySnapshotActions = async ( const { metagraph_id, term } = event.pathParameters || {}; const filter = extractHashOrdinal(term); - const selectedTables = tableFilter(event).map(metagraphTable); + const selectedTransactions = transactionFilter(event); return await paginatedQuery( extractPagination(event), @@ -212,12 +190,12 @@ export const currencySnapshotActions = async ( { where: { metagraph_snapshot: { metagraph_id, ...filter }, - table_name: { in: selectedTables }, + transaction_type: { in: selectedTransactions }, }, include: metagraphInclude, orderBy: [{ created_at: "desc" }, { hash: "asc" }], }, - prisma.abstract_transactions_view.findMany, + prisma.metagraph_actions_view.findMany, actionsResponse ); } catch (error) { @@ -231,7 +209,7 @@ export const currencyAddressActions = async ( try { const { metagraph_id, address } = event.pathParameters || {}; - const selectedTables = tableFilter(event).map(metagraphTable); + const selectedTransactions = transactionFilter(event); return await paginatedQuery( extractPagination(event), @@ -251,12 +229,12 @@ export const currencyAddressActions = async ( }, }, ], - table_name: { in: selectedTables }, + transaction_type: { in: selectedTransactions }, }, include: metagraphInclude, orderBy: [{ created_at: "desc" }, { hash: "asc" }], }, - prisma.abstract_transactions_view.findMany, + prisma.metagraph_actions_view.findMany, actionsResponse ); } catch (error) { diff --git a/tests/handlers/actionsHandler.test.ts b/tests/handlers/actionsHandler.test.ts new file mode 100644 index 0000000..81942ba --- /dev/null +++ b/tests/handlers/actionsHandler.test.ts @@ -0,0 +1,162 @@ +import { APIGatewayProxyResult } from "aws-lambda"; +import * as actionsHandler from "../../src/handlers/actionsHandler"; +import { + createAPIGatewayEvent, + validateResponseStructure, + validatePaginatedResponse, +} from "../testUtils"; +import { + data_dag_token_locks, + data_metagraph_token_locks, + data_dag_token_unlocks, + data_metagraph_token_unlocks, + data_dag_spend_transactions, + data_metagraph_spend_transactions, + data_dag_allow_spends, + data_dag_expired_spend_transactions, + data_metagraph_allow_spends, + data_metagraph_expired_spend_transactions, + data_delegate_stake_withdraw_events, + data_delegate_stake_create_events, + data_metagraph_fee_transactions, + data_metagraph_snapshots, +} from "../../prisma/seed"; + +const datasets = [ + data_dag_token_locks, + data_dag_token_unlocks, + data_dag_spend_transactions, + data_dag_expired_spend_transactions, + data_dag_spend_transactions, + data_dag_allow_spends, + data_delegate_stake_create_events, + data_delegate_stake_withdraw_events, + data_metagraph_token_locks, + data_metagraph_token_unlocks, + data_metagraph_spend_transactions, + data_metagraph_expired_spend_transactions, + data_metagraph_spend_transactions, + data_metagraph_allow_spends, + data_metagraph_fee_transactions, +]; + +expect.extend({ + toBeBigInt(received, expected) { + const pass = BigInt(received) === BigInt(expected); + return { + pass, + message: () => + `expected ${received} to be the same BigInt as ${expected}`, + }; + }, +}); + +const validateAction = (action) => { + const match = datasets.flat().find((tx) => tx.hash === action.hash); + + if (!match) console.log(action); + + expect(match).toBeDefined(); + if (!match) return; + + expect(action.hash).toBe(match.hash); + if (action.type == "DelegateStakeWithdraw") { + const stake = data_delegate_stake_create_events.find( + (tx) => tx.hash === match.stake_create_hash + ); + expect(action.amount).toBeBigInt(stake.amount); + } else expect(action.amount).toBeBigInt(match.amount); + expect(action.source).toBe(match.source_addr); + expect(action.type).toBeDefined(); + if (action.currencyId !== null) { + expect(typeof action.currencyId).toBe("string"); + } +}; + +describe("actionsHandler", () => { + it("dagActions", async () => { + const event = createAPIGatewayEvent({}, { pageSize: "10" }); + const result = (await actionsHandler.dagActions( + event + )) as APIGatewayProxyResult; + validateResponseStructure(result); + + expect(result.statusCode).toBe(200); + const body = validatePaginatedResponse(result); + expect(body.data.length).toBe(15); + body.data.forEach(validateAction); + }); + + it("globalSnapshotActions", async () => { + const event = createAPIGatewayEvent({ term: "1000" }, {}); + const result = (await actionsHandler.globalSnapshotActions( + event + )) as APIGatewayProxyResult; + validateResponseStructure(result); + + expect(result.statusCode).toBe(200); + const body = validatePaginatedResponse(result); + body.data.forEach(validateAction); + }); + + it("dagAddressActions", async () => { + const address = data_dag_token_locks[0].source_addr; + const event = createAPIGatewayEvent({ address }, {}); + const result = (await actionsHandler.dagAddressActions( + event + )) as APIGatewayProxyResult; + validateResponseStructure(result); + + expect(result.statusCode).toBe(200); + const body = validatePaginatedResponse(result); + expect(body.data.length).toBe(12); + body.data.forEach(validateAction); + }); + + it("currencyActions", async () => { + const metagraph_id = data_metagraph_token_locks[0].metagraph_id; + const event = createAPIGatewayEvent({ metagraph_id }, {}); + const result = (await actionsHandler.currencyActions( + event + )) as APIGatewayProxyResult; + validateResponseStructure(result); + + expect(result.statusCode).toBe(200); + const body = validatePaginatedResponse(result); + expect(body.data.length).toBe(11); + body.data.forEach(validateAction); + }); + + it("currencySnapshotActions", async () => { + const metagraph_id = data_metagraph_token_locks[0].metagraph_id; + const snapshotHash = data_metagraph_snapshots[0].hash; + const event = createAPIGatewayEvent( + { metagraph_id, term: snapshotHash }, + {} + ); + const result = (await actionsHandler.currencySnapshotActions( + event + )) as APIGatewayProxyResult; + validateResponseStructure(result); + + expect(result.statusCode).toBe(200); + const body = validatePaginatedResponse(result); + expect(body.data.length).toBe(4); + body.data.forEach(validateAction); + }); + + it("currencyAddressActions", async () => { + const metagraph_id = data_metagraph_token_locks[0].metagraph_id; + const address = data_metagraph_token_locks[0].source_addr; + const event = createAPIGatewayEvent({ metagraph_id, address }, {}); + const result = (await actionsHandler.currencyAddressActions( + event + )) as APIGatewayProxyResult; + validateResponseStructure(result); + + expect(result.statusCode).toBe(200); + const body = validatePaginatedResponse(result); + expect(body.data.length).toBe(9); + body.data.forEach(validateAction); + }); +}); diff --git a/tests/handlers/allowSpendsHandler.test.ts b/tests/handlers/allowSpendsHandler.test.ts index 4df9b48..9c36dcf 100644 --- a/tests/handlers/allowSpendsHandler.test.ts +++ b/tests/handlers/allowSpendsHandler.test.ts @@ -7,192 +7,18 @@ import { } from "../testUtils"; import { data_addresses, + data_dag_allow_spends, + data_dag_expired_spend_transactions, + data_dag_spend_transactions, data_global_snapshots, + data_metagraph_allow_spends, + data_metagraph_expired_spend_transactions, data_metagraph_snapshots, + data_metagraph_spend_transactions, data_metagraphs, prisma, } from "../../prisma/seed"; -export const data_dag_allow_spends = [ - { - hash: "allowSpendHash1", - source_addr: data_addresses[0].address, - destination_addr: data_addresses[1].address, - amount: 1000n, - fee: 5n, - last_valid_epoch_progress: 100n, - ordinal: 1n, - snapshot_hash: data_global_snapshots[0].hash, - round_id: "11111111-1111-1111-1111-111111111111", - created_at: new Date("2024-01-01T10:00:00Z"), - updated_at: new Date("2024-01-01T10:00:00Z"), - }, - { - hash: "allowSpendHash2", - source_addr: data_addresses[0].address, - destination_addr: data_addresses[2].address, - amount: 2000n, - fee: 10n, - last_valid_epoch_progress: 600n, - ordinal: 2n, - snapshot_hash: data_global_snapshots[0].hash, - round_id: "22222222-2222-2222-2222-222222222222", - created_at: new Date("2024-01-02T10:00:00Z"), - updated_at: new Date("2024-01-02T10:00:00Z"), - }, - { - hash: "allowSpendHash3", - source_addr: data_addresses[2].address, - destination_addr: data_addresses[1].address, - amount: 300n, - fee: 15n, - last_valid_epoch_progress: 700n, - ordinal: 3n, - snapshot_hash: data_global_snapshots[1].hash, - round_id: "33333333-3333-3333-3333-333333333333", - created_at: new Date("2024-01-03T10:00:00Z"), - updated_at: new Date("2024-01-03T10:00:00Z"), - }, - { - hash: "allowSpendHash4", - source_addr: data_addresses[2].address, - destination_addr: data_addresses[3].address, - amount: 300n, - fee: 15n, - last_valid_epoch_progress: 700n, - ordinal: 4n, - snapshot_hash: data_global_snapshots[0].hash, - round_id: "33333333-3333-3333-3333-333333333333", - created_at: new Date("2024-01-03T10:00:00Z"), - updated_at: new Date("2024-01-03T10:00:00Z"), - }, -]; - -export const data_dag_spend_transactions = [ - { - hash: "spendTxHash1", - source_addr: data_addresses[0].address, - destination_addr: data_addresses[1].address, - amount: 1000n, - allow_spend_ref: data_dag_allow_spends[0].hash, - snapshot_hash: data_global_snapshots[2].hash, - created_at: new Date("2024-01-05T10:00:00Z"), - updated_at: new Date("2024-01-05T10:00:00Z"), - }, -]; - -export const data_dag_expired_spend_transactions = [ - { - hash: "expiredSpendTxHash1", - source_addr: data_addresses[2].address, - amount: 3000n, - allow_spend_ref: data_dag_allow_spends[1].hash, - snapshot_hash: data_global_snapshots[2].hash, - created_at: new Date("2024-01-10T10:00:00Z"), - updated_at: new Date("2024-01-10T10:00:00Z"), - }, -]; - -export const data_metagraph_allow_spends = [ - { - metagraph_id: data_metagraphs[0].id, - hash: "metaAllowSpendHash1", - source_addr: data_addresses[0].address, - destination_addr: data_addresses[1].address, - amount: 500n, - fee: 3n, - last_valid_epoch_progress: 50n, - ordinal: 1n, - snapshot_hash: data_metagraph_snapshots[0].hash, - round_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", - created_at: new Date("2024-01-01T11:00:00Z"), - updated_at: new Date("2024-01-01T11:00:00Z"), - }, - { - metagraph_id: data_metagraphs[0].id, - hash: "metaAllowSpendHash2", - source_addr: data_addresses[0].address, - destination_addr: data_addresses[1].address, - amount: 500n, - fee: 3n, - last_valid_epoch_progress: 50n, - ordinal: 2n, - snapshot_hash: data_metagraph_snapshots[1].hash, - round_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", - created_at: new Date("2024-01-01T11:00:00Z"), - updated_at: new Date("2024-01-01T11:00:00Z"), - }, - { - metagraph_id: data_metagraphs[0].id, - hash: "metaAllowSpendHash3", - source_addr: data_addresses[2].address, - destination_addr: data_addresses[3].address, - amount: 750n, - fee: 4n, - last_valid_epoch_progress: 60n, - ordinal: 3n, - snapshot_hash: data_metagraph_snapshots[1].hash, - round_id: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", - created_at: new Date("2024-01-02T11:00:00Z"), - updated_at: new Date("2024-01-02T11:00:00Z"), - }, -]; - -export const data_metagraph_spend_transactions = [ - { - metagraph_id: data_metagraphs[0].id, - hash: "metaSpendTxHash1", - source_addr: data_addresses[0].address, - destination_addr: data_addresses[1].address, - amount: 500n, - allow_spend_ref: data_metagraph_allow_spends[0].hash, - snapshot_hash: data_metagraph_snapshots[0].hash, - created_at: new Date("2024-01-03T11:00:00Z"), - updated_at: new Date("2024-01-03T11:00:00Z"), - }, -]; - -export const data_metagraph_expired_spend_transactions = [ - { - metagraph_id: data_metagraphs[0].id, - hash: "metaExpiredTxHash1", - source_addr: data_addresses[2].address, - amount: 750n, - allow_spend_ref: data_metagraph_allow_spends[2].hash, - snapshot_hash: data_metagraph_snapshots[1].hash, - created_at: new Date("2024-01-04T11:00:00Z"), - updated_at: new Date("2024-01-04T11:00:00Z"), - }, -]; - -const seedData = async () => { - await prisma.dag_allow_spends.createMany({ - data: data_dag_allow_spends, - }); - - await prisma.dag_spend_transactions.createMany({ - data: data_dag_spend_transactions, - }); - - await prisma.dag_expired_spend_transactions.createMany({ - data: data_dag_expired_spend_transactions, - }); - - await prisma.metagraph_allow_spends.createMany({ - data: data_metagraph_allow_spends, - }); - await prisma.metagraph_spend_transactions.createMany({ - data: data_metagraph_spend_transactions, - }); - await prisma.metagraph_expired_spend_transactions.createMany({ - data: data_metagraph_expired_spend_transactions, - }); -}; - -beforeAll(async () => { - await seedData(); -}); - // Helper validations const validateDagAllowSpend = (entry) => { const match = data_dag_allow_spends.find((d) => d.hash === entry.hash); diff --git a/tests/handlers/delegatedStakingHandler.test.ts b/tests/handlers/delegatedStakingHandler.test.ts index 16f127d..ddd0cb9 100644 --- a/tests/handlers/delegatedStakingHandler.test.ts +++ b/tests/handlers/delegatedStakingHandler.test.ts @@ -17,90 +17,13 @@ import { import { data_addresses, data_dag_token_locks, + data_delegate_stake_create_events, + data_delegate_stake_rewards, + data_delegate_stake_withdraw_events, data_global_snapshots, prisma, } from "../../prisma/seed"; -const data_delegate_stake_create_events = [ - { - hash: "stake-event-hash-001", - ordinal: 10001n, - source_addr: data_addresses[0].address, - node_id: "NODE_ABC123", - amount: 500000000000n, - fee: 500000n, - lock_reference_hash: data_dag_token_locks[0].hash, - parent_hash: "parent-hash-xyz", - global_snapshot_hash: data_global_snapshots[0].hash, - }, - { - hash: "stake-event-hash-002", - ordinal: 10002n, - source_addr: data_addresses[0].address, - node_id: "NODE_ABC123", - amount: 3333300000000n, - fee: 500000n, - lock_reference_hash: data_dag_token_locks[1].hash, - parent_hash: "stake-event-hash-001", - global_snapshot_hash: data_global_snapshots[1].hash, - }, - { - hash: "stake-event-hash-003", - ordinal: 10003n, - source_addr: data_addresses[0].address, - node_id: "NODE_ABC234", - amount: 2222222222n, - fee: 500000n, - lock_reference_hash: data_dag_token_locks[1].hash, - parent_hash: "stake-event-hash-001", - global_snapshot_hash: data_global_snapshots[1].hash, - transfer_from_hash: "stake-event-hash-002", - }, -]; - -const data_delegate_stake_withdraw_events = [ - { - hash: "withdraw-event-hash-001", - source_addr: data_addresses[0].address, - stake_create_hash: data_delegate_stake_create_events[0].hash, - global_snapshot_hash: data_global_snapshots[1].hash, - unlock_epoch: 11000n, - is_completed: false, - }, - { - hash: "withdraw-event-hash-002", - source_addr: data_addresses[0].address, - stake_create_hash: data_delegate_stake_create_events[1].hash, - global_snapshot_hash: data_global_snapshots[1].hash, - unlock_epoch: 8000n, - is_completed: true, - }, -]; - -const data_delegate_stake_rewards = [ - { - global_snapshot_hash: data_global_snapshots[2].hash, - address: data_addresses[0].address, - node_id: "NODE_ABC123", - rewards: 2500000000n, - stake_create_hash: data_delegate_stake_create_events[0].hash, - }, -]; - -const seedData = async () => { - await prisma.delegate_stake_create_events.createMany({ - data: data_delegate_stake_create_events, - }); - - await prisma.delegate_stake_withdraw_events.createMany({ - data: data_delegate_stake_withdraw_events, - }); - - await prisma.delegate_stake_rewards.createMany({ - data: data_delegate_stake_rewards, - }); -}; - expect.extend({ toBeBigInt(received, expected) { const pass = BigInt(received) === BigInt(expected); @@ -152,10 +75,6 @@ const validateStakingPosition = (tx) => { }; describe("Delegated Stake Handler Integration Tests", () => { - beforeAll(async () => { - await seedData(); - }); - describe("delegatedStakes", () => { it("should return a list of delegated stakes", async () => { const event = createAPIGatewayEvent({}, { limit: "10" }); diff --git a/tests/handlers/metagraphHandler.test.ts b/tests/handlers/metagraphHandler.test.ts index 0e42839..73967e1 100644 --- a/tests/handlers/metagraphHandler.test.ts +++ b/tests/handlers/metagraphHandler.test.ts @@ -7,6 +7,7 @@ import { } from "../testUtils"; import { data_addresses, + data_metagraph_fee_transactions, data_metagraph_snapshots, data_metagraphs, prisma, @@ -156,35 +157,6 @@ const data_metagraph_balance_changes = [ }, ]; -const data_metagraph_fee_transactions = [ - { - metagraph_id: data_metagraphs[0].id, - metagraph_snapshot_hash: data_metagraph_snapshots[0].hash, - metagraph_snapshot_ordinal: data_metagraph_snapshots[0].ordinal, - hash: "39c9909d3b00552beaa5487c38110267675ab212b3b97654fc5ca9917cb7e72b", - source_addr: data_addresses[0].address, - destination_addr: data_addresses[1].address, - amount: 90790983n, - data_update_ref: - "5056fdfbba0637dcecfc0b7fa3f441c745c852cf850c3bfc0dbc8a7410b8d722", - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, - { - metagraph_id: data_metagraphs[1].id, - metagraph_snapshot_hash: data_metagraph_snapshots[2].hash, - metagraph_snapshot_ordinal: data_metagraph_snapshots[2].ordinal, - hash: "39c990000000000000000000000000000000000000007654fc5ca9917cb7e72b", - source_addr: data_addresses[2].address, - destination_addr: data_addresses[3].address, - amount: 10090983n, - data_update_ref: - "39c9909d3b00552beaa5487c38110267675ab212b3b97654fc5ca9917cb7e72b", - created_at: new Date("2025-04-02T00:00:02Z"), - updated_at: new Date(), - }, -]; - const seedData = async () => { await prisma.metagraph_blocks.createMany({ data: data_metagraph_blocks, @@ -197,10 +169,6 @@ const seedData = async () => { await prisma.metagraph_balance_changes.createMany({ data: data_metagraph_balance_changes, }); - - await prisma.metagraph_fee_transactions.createMany({ - data: data_metagraph_fee_transactions, - }); }; beforeAll(async () => { From 22f763a151b10c8c8ae28ba47fbf149e2a94e1dd Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Fri, 6 Jun 2025 11:18:04 -0300 Subject: [PATCH 47/63] fix ordinal number --- src/handlers/metagraphHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/metagraphHandler.ts b/src/handlers/metagraphHandler.ts index 5061fea..9d72653 100644 --- a/src/handlers/metagraphHandler.ts +++ b/src/handlers/metagraphHandler.ts @@ -441,7 +441,7 @@ export const currencyBalanceByAddress = async ( metagraph_id, balance, address, - ordinal + ordinalNbr ); return respond(balanceOrZero, balanceResponse); From 04b693e8c76ea2aeb2f7595413693db0aaa68ef2 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Tue, 20 May 2025 13:16:59 -0300 Subject: [PATCH 48/63] fallback to opensearch for balance changes --- env.yml | 1 + package-lock.json | 34 +- package.json | 1 + serverless.yml | 1 + src/handlers/dagHandler.ts | 33 +- src/handlers/metagraphHandler.ts | 42 +- src/opensearch/handler.ts | 94 ++ src/opensearch/http.ts | 68 ++ src/opensearch/model/balance.ts | 12 + src/opensearch/model/block.ts | 20 + src/opensearch/model/currency-data.ts | 4 + src/opensearch/model/currency-snapshot.ts | 36 + src/opensearch/model/fee-transaction.ts | 15 + src/opensearch/model/index.ts | 7 + src/opensearch/model/metagraph.ts | 6 + src/opensearch/model/properties.ts | 23 + src/opensearch/model/snapshot.ts | 14 + src/opensearch/model/transaction.ts | 30 + src/opensearch/opensearch.ts | 1037 +++++++++++++++++++++ src/opensearch/os-request-params.ts | 217 +++++ src/opensearch/query.ts | 237 +++++ src/opensearch/service.ts | 682 ++++++++++++++ src/opensearch/ts-extensions.ts | 97 ++ tests/opensearch.test.ts | 67 ++ tests/request-params.test.ts | 81 ++ 25 files changed, 2846 insertions(+), 13 deletions(-) create mode 100644 src/opensearch/handler.ts create mode 100644 src/opensearch/http.ts create mode 100644 src/opensearch/model/balance.ts create mode 100644 src/opensearch/model/block.ts create mode 100644 src/opensearch/model/currency-data.ts create mode 100644 src/opensearch/model/currency-snapshot.ts create mode 100644 src/opensearch/model/fee-transaction.ts create mode 100644 src/opensearch/model/index.ts create mode 100644 src/opensearch/model/metagraph.ts create mode 100644 src/opensearch/model/properties.ts create mode 100644 src/opensearch/model/snapshot.ts create mode 100644 src/opensearch/model/transaction.ts create mode 100644 src/opensearch/opensearch.ts create mode 100644 src/opensearch/os-request-params.ts create mode 100644 src/opensearch/query.ts create mode 100644 src/opensearch/service.ts create mode 100644 src/opensearch/ts-extensions.ts create mode 100644 tests/opensearch.test.ts create mode 100644 tests/request-params.test.ts diff --git a/env.yml b/env.yml index 6e12f4e..b3c682f 100644 --- a/env.yml +++ b/env.yml @@ -1,3 +1,4 @@ default: + opensearch: 'http://localhost:9200' vpc: {} db_url: postgresql://postgres:postgres@localhost:5432/new_block_explorer?schema=public \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 350bef1..58bcb01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "3.1.2", "license": "MIT", "dependencies": { + "@opensearch-project/opensearch": "^1.2.0", "@prisma/client": "^6.2.1", "aws-lambda": "^1.0.7", "date-fns": "^2.30.0", @@ -3510,6 +3511,21 @@ "node": ">= 8" } }, + "node_modules/@opensearch-project/opensearch": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@opensearch-project/opensearch/-/opensearch-1.2.0.tgz", + "integrity": "sha512-bX0aUz5e7rlY1lKz1rFrqnNbl/l1CHvvysYB2Jn+C3WNs7nL6FnQjuxLhGwyRdW9W1bFokDoOVgPMIOi/Nn9/g==", + "dependencies": { + "aws4": "^1.11.0", + "debug": "^4.3.1", + "hpagent": "^0.1.1", + "ms": "^2.1.3", + "secure-json-parse": "^2.4.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@prisma/client": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.2.1.tgz", @@ -5128,6 +5144,11 @@ "node": ">= 10.0.0" } }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==" + }, "node_modules/axios": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz", @@ -7616,6 +7637,11 @@ "node": ">=8" } }, + "node_modules/hpagent": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-0.1.2.tgz", + "integrity": "sha512-ePqFXHtSQWAFXYmj+JtOTHr84iNrII4/QRlAAPPE+zqnKy4xJo7Ie1Y4kC7AdB+LxLxSTTzBMASsEcy0q8YyvQ==" + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -10506,8 +10532,7 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "peer": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/mute-stream": { "version": "0.0.8", @@ -11690,6 +11715,11 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==" }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" + }, "node_modules/seek-bzip": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", diff --git a/package.json b/package.json index 171205f..51f6cc2 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "", "main": "src/handler.ts", "dependencies": { + "@opensearch-project/opensearch": "^1.2.0", "@prisma/client": "^6.2.1", "aws-lambda": "^1.0.7", "date-fns": "^2.30.0", diff --git a/serverless.yml b/serverless.yml index 15a4601..a30eca0 100644 --- a/serverless.yml +++ b/serverless.yml @@ -15,6 +15,7 @@ provider: environment: SLS_DEBUG: "*" DATABASE_URL: ${self:custom.env.db_url} + OPENSEARCH_NODE: ${self:custom.env.opensearch} httpApi: cors: true vpc: ${self:custom.env.vpc} diff --git a/src/handlers/dagHandler.ts b/src/handlers/dagHandler.ts index 9484ad4..9165e6f 100644 --- a/src/handlers/dagHandler.ts +++ b/src/handlers/dagHandler.ts @@ -1,6 +1,7 @@ import { PrismaClient } from "@prisma/client"; import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; import { extractHashOrdinal, extractPagination } from "../request-params"; +import { isRight } from "fp-ts/Either"; import { balanceResponse, dagBlockResponse, @@ -18,6 +19,8 @@ import { paginatedQuery, toCreatedAtOrdinalCursor, } from "../pagination"; +import * as OpenSearch from "../opensearch/opensearch"; + import { toNumber, isFinite } from "lodash"; const prisma = new PrismaClient(); @@ -297,6 +300,8 @@ const balanceOrZeroFn = async (balance, address, ordinal) => { return balance; }; +const osClient = OpenSearch.getClient(); + export const dagBalanceByAddress = async ( event: APIGatewayProxyEvent ): Promise => { @@ -311,14 +316,36 @@ export const dagBalanceByAddress = async ( ? { snapshot_ordinal: { lte: ordinalNbr } } : {}; - const balance = await prisma.dag_balance_changes.findFirst({ + const dbBalance = await prisma.dag_balance_changes.findFirst({ where: { address, ...ordinalCondition }, orderBy: { snapshot_ordinal: "desc" }, }); - const balanceOrZero = await balanceOrZeroFn(balance, address, ordinalNbr); + const eitherOsBalance = await OpenSearch.findBalanceByAddress(osClient)( + address!, + null, + ordinal !== undefined ? ordinalNbr : undefined + )(); + const osBalance = isRight(eitherOsBalance) + ? eitherOsBalance.right.data + : null; + + let balance; + if (dbBalance === null) { + if (osBalance === null) { + balance = await balanceOrZeroFn(balance, address, ordinalNbr); + } else { + balance = { ...osBalance, snapshot_ordinal: ordinalNbr }; + } + } else { + if (osBalance != null && osBalance.ordinal > dbBalance.snapshot_ordinal) { + balance = { ...osBalance, snapshot_ordinal: ordinalNbr }; + } else { + balance = dbBalance; + } + } - return respond(balanceOrZero, balanceResponse); + return respond(balance, balanceResponse); } catch (error) { return handleError(error); } diff --git a/src/handlers/metagraphHandler.ts b/src/handlers/metagraphHandler.ts index 5061fea..0b47f24 100644 --- a/src/handlers/metagraphHandler.ts +++ b/src/handlers/metagraphHandler.ts @@ -1,6 +1,7 @@ import { PrismaClient } from "@prisma/client"; import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; import { extractHashOrdinal, extractPagination } from "../request-params"; +import { isRight } from "fp-ts/Either"; import { balanceResponse, handleError, @@ -23,6 +24,7 @@ import { toCreatedAtOrdinalCursor, toOrdinalCursor, } from "../pagination"; +import * as OpenSearch from "../opensearch/opensearch"; import { toNumber, isFinite } from "lodash"; const prisma = new PrismaClient(); @@ -414,6 +416,8 @@ const balanceOrZeroFn = async (metagraph_id, balance, address, ordinal) => { return balance; }; +const osClient = OpenSearch.getClient(); + export const currencyBalanceByAddress = async ( event: APIGatewayProxyEvent ): Promise => { @@ -428,7 +432,7 @@ export const currencyBalanceByAddress = async ( ? { snapshot_ordinal: { lte: ordinalNbr } } : {}; - const balance = await prisma.metagraph_balance_changes.findFirst({ + const dbBalance = await prisma.metagraph_balance_changes.findFirst({ where: { metagraph_id, address, @@ -437,14 +441,36 @@ export const currencyBalanceByAddress = async ( orderBy: { snapshot_ordinal: "desc" }, }); - const balanceOrZero = await balanceOrZeroFn( - metagraph_id, - balance, - address, - ordinal - ); + const eitherOsBalance = await OpenSearch.findBalanceByAddress(osClient)( + address!, + metagraph_id!, + ordinalNbr + )(); + const osBalance = isRight(eitherOsBalance) + ? eitherOsBalance.right.data + : null; + + let balance; + if (dbBalance === null) { + if (osBalance === null) { + balance = await balanceOrZeroFn( + metagraph_id, + balance, + address, + ordinal !== undefined ? ordinalNbr : undefined + ); + } else { + balance = { ...osBalance, snapshot_ordinal: ordinalNbr }; + } + } else { + if (osBalance != null && osBalance.ordinal > dbBalance.snapshot_ordinal) { + balance = { ...osBalance, snapshot_ordinal: ordinalNbr }; + } else { + balance = dbBalance; + } + } - return respond(balanceOrZero, balanceResponse); + return respond(balance, balanceResponse); } catch (error) { return handleError(error); } diff --git a/src/opensearch/handler.ts b/src/opensearch/handler.ts new file mode 100644 index 0000000..5d2d4f3 --- /dev/null +++ b/src/opensearch/handler.ts @@ -0,0 +1,94 @@ +import { getClient } from "./opensearch"; +import { + getBalanceByAddress, + getBlock, + getCurrencyBalanceByAddress, + getCurrencyBlock, + getCurrencyFeeTransaction, + getCurrencyFeeTransactionsByAddress, + getCurrencyFeeTransactionsByDestination, + getCurrencyFeeTransactionsBySource, + getCurrencySnapshot, + getCurrencySnapshotFeeTransactions, + getCurrencySnapshotRewards, + getCurrencySnapshots, + getCurrencySnapshotTransactions, + getCurrencyTransaction, + getCurrencyTransactions, + getCurrencyTransactionsByAddress, + getCurrencyTransactionsByDestination, + getCurrencyTransactionsBySource, + getGlobalSnapshot, + getGlobalSnapshotRewards, + getGlobalSnapshots, + getGlobalSnapshotTransactions, + getMetagraphs, + getCurrencySnapshotsByOwnerAddress, + getTransaction, + getTransactions, + getTransactionsByAddress, + getTransactionsByDestination, + getTransactionsBySource, +} from "./service"; + +const osClient = getClient(); + +export const globalSnapshot = (event) => getGlobalSnapshot(event, osClient)(); +export const globalSnapshots = (event) => getGlobalSnapshots(event, osClient)(); +export const globalSnapshotRewards = (event) => + getGlobalSnapshotRewards(event, osClient)(); +export const globalSnapshotTransactions = (event) => + getGlobalSnapshotTransactions(event, osClient)(); +export const block = (event) => getBlock(event, osClient)(); + +export const transaction = (event) => getTransaction(event, osClient, null)(); +export const transactions = (event) => getTransactions(event, osClient)(); +export const transactionsByAddress = (event) => + getTransactionsByAddress(event, osClient)(); +export const transactionsBySource = (event) => + getTransactionsBySource(event, osClient)(); +export const transactionsByDestination = (event) => + getTransactionsByDestination(event, osClient)(); +export const balanceByAddress = (event) => + getBalanceByAddress(event, osClient)(); + +// Currency + +export const currencySnapshot = (event) => + getCurrencySnapshot(event, osClient)(); +export const currencySnapshots = (event) => + getCurrencySnapshots(event, osClient)(); +export const currencySnapshotsByOwnerAddress = (event) => + getCurrencySnapshotsByOwnerAddress(event, osClient)(); +export const currencySnapshotRewards = (event) => + getCurrencySnapshotRewards(event, osClient)(); +export const currencySnapshotTransactions = (event) => + getCurrencySnapshotTransactions(event, osClient)(); + +export const currencyBlock = (event) => getCurrencyBlock(event, osClient)(); + +export const currencyTransaction = (event) => + getCurrencyTransaction(event, osClient)(); +export const currencyTransactions = (event) => + getCurrencyTransactions(event, osClient)(); +export const currencyTransactionsByAddress = (event) => + getCurrencyTransactionsByAddress(event, osClient)(); +export const currencyTransactionsBySource = (event) => + getCurrencyTransactionsBySource(event, osClient)(); +export const currencyTransactionsByDestination = (event) => + getCurrencyTransactionsByDestination(event, osClient)(); + +export const currencyFeeTransaction = (event) => + getCurrencyFeeTransaction(event, osClient)(); +export const currencySnapshotFeeTransactions = (event) => + getCurrencySnapshotFeeTransactions(event, osClient)(); +export const currencyFeeTransactionsByDestination = (event) => + getCurrencyFeeTransactionsByDestination(event, osClient)(); +export const currencyFeeTransactionsBySource = (event) => + getCurrencyFeeTransactionsBySource(event, osClient)(); +export const currencyFeeTransactionsByAddress = (event) => + getCurrencyFeeTransactionsByAddress(event, osClient)(); +export const currencyBalanceByAddress = (event) => + getCurrencyBalanceByAddress(event, osClient)(); + +export const metagraphs = (event) => getMetagraphs(event, osClient)(); diff --git a/src/opensearch/http.ts b/src/opensearch/http.ts new file mode 100644 index 0000000..34fc182 --- /dev/null +++ b/src/opensearch/http.ts @@ -0,0 +1,68 @@ +import { PaginatedResult, Result } from "./opensearch"; + +const DEFAULT_HEADERS = { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*", +}; + +export enum StatusCodes { + OK = 200, + CREATED = 201, + BAD_REQUEST = 400, + NOT_FOUND = 404, + SERVER_ERROR = 500, +} + +type ErrorCodes = + | StatusCodes.BAD_REQUEST + | StatusCodes.NOT_FOUND + | StatusCodes.SERVER_ERROR; +type SuccessCodes = Exclude; + +export class ApplicationError { + public readonly message: string; + public readonly errors: string[]; + public readonly statusCode: ErrorCodes; + + public constructor(message: string, errors: string[], status: ErrorCodes) { + this.message = message; + this.errors = errors; + this.statusCode = status; + } +} + +export class OpenSearchError extends ApplicationError { + public constructor(error: string) { + super("OpenSearch error", [error], StatusCodes.SERVER_ERROR); + } +} + +type SuccessResponse = { + statusCode: SuccessCodes; + headers: typeof DEFAULT_HEADERS; + body: string; +}; + +export const successResponse = + (statusCode: SuccessCodes) => + (result: Result | PaginatedResult): Response => { + return { + statusCode, + headers: DEFAULT_HEADERS, + body: JSON.stringify(result), + }; + }; + +type ErrorResponse = { + statusCode: ErrorCodes; + headers: typeof DEFAULT_HEADERS; + body: string; +}; + +export const errorResponse = (error: ApplicationError): Response => ({ + statusCode: error.statusCode, + headers: DEFAULT_HEADERS, + body: JSON.stringify({ message: error.message, errors: error.errors }), +}); + +export type Response = SuccessResponse | ErrorResponse; diff --git a/src/opensearch/model/balance.ts b/src/opensearch/model/balance.ts new file mode 100644 index 0000000..c395728 --- /dev/null +++ b/src/opensearch/model/balance.ts @@ -0,0 +1,12 @@ +import { SnapshotHash, SnapshotOrdinal, Timestamp } from "./properties"; + +export type OpenSearchBalance = { + address: string; + balance: number; +} & SnapshotOrdinal & + SnapshotHash & + Timestamp; + +export type Balance = Pick & { + ordinal: number; +}; diff --git a/src/opensearch/model/block.ts b/src/opensearch/model/block.ts new file mode 100644 index 0000000..d996111 --- /dev/null +++ b/src/opensearch/model/block.ts @@ -0,0 +1,20 @@ +import { + Hash, + Height, + SnapshotHash, + SnapshotOrdinal, + Timestamp, +} from "./properties"; + +export type OpenSearchBlock = { + transactions: string[]; + parent: BlockReference[]; +} & Timestamp & + SnapshotOrdinal & + SnapshotHash & + Hash & + Height; + +export type BlockReference = Hash & Height; + +export type Block = OpenSearchBlock; diff --git a/src/opensearch/model/currency-data.ts b/src/opensearch/model/currency-data.ts new file mode 100644 index 0000000..7d87d35 --- /dev/null +++ b/src/opensearch/model/currency-data.ts @@ -0,0 +1,4 @@ +export type CurrencyData = { + data: A; + identifier: string; +}; diff --git a/src/opensearch/model/currency-snapshot.ts b/src/opensearch/model/currency-snapshot.ts new file mode 100644 index 0000000..688d9d9 --- /dev/null +++ b/src/opensearch/model/currency-snapshot.ts @@ -0,0 +1,36 @@ +import { OpenSearchSnapshot, WithoutRewards } from "./snapshot"; + +export type OpenSearchCurrencySnapshotV1 = OpenSearchSnapshot & { + fee?: number | null; + stakingAddress?: string | null; + ownerAddress?: string | null; + sizeInKB?: number | null; +}; + +export type OpenSearchCurrencySnapshot = OpenSearchCurrencySnapshotV1 & { + fee: number | null; + stakingAddress: string | null; + ownerAddress: string | null; + sizeInKB: number | null; +}; + +export const openSearchCurrencySnapshotToV2 = ( + data: + | OpenSearchCurrencySnapshotV1 + | OpenSearchCurrencySnapshot + | WithoutRewards + | WithoutRewards +): WithoutRewards => ({ + ...data, + fee: data.fee ?? null, + stakingAddress: data.stakingAddress ?? null, + ownerAddress: data.ownerAddress ?? null, + sizeInKB: data.sizeInKB ?? null, +}); + +export type CurrencySnapshot = { + fee: number | null; + stakingAddress: string | null; + ownerAddress: string | null; + sizeInKB: number | null; +}; diff --git a/src/opensearch/model/fee-transaction.ts b/src/opensearch/model/fee-transaction.ts new file mode 100644 index 0000000..ed78256 --- /dev/null +++ b/src/opensearch/model/fee-transaction.ts @@ -0,0 +1,15 @@ +import { Hash, SnapshotHash, SnapshotOrdinal, Timestamp } from "./properties"; +import { TransactionReference } from "./transaction"; + +export type OpenSearchFeeTransaction = { + source: string; + destination: string; + amount: number; + parent: TransactionReference; + salt: number; +} & Timestamp & + SnapshotHash & + SnapshotOrdinal & + Hash; + +export type FeeTransaction = OpenSearchFeeTransaction; diff --git a/src/opensearch/model/index.ts b/src/opensearch/model/index.ts new file mode 100644 index 0000000..cfd37dd --- /dev/null +++ b/src/opensearch/model/index.ts @@ -0,0 +1,7 @@ +export * from "./block"; +export * from "./balance"; +export * from "./snapshot"; +export * from "./transaction"; +export * from "./properties"; +export * from "./currency-data"; +export * from "./fee-transaction"; diff --git a/src/opensearch/model/metagraph.ts b/src/opensearch/model/metagraph.ts new file mode 100644 index 0000000..d1bbc07 --- /dev/null +++ b/src/opensearch/model/metagraph.ts @@ -0,0 +1,6 @@ +export type Metagraph = { + id: string; + lastSnapshotHash: string; + ownerAddress: string | null; + stakingAddress: string | null; +}; diff --git a/src/opensearch/model/properties.ts b/src/opensearch/model/properties.ts new file mode 100644 index 0000000..d19d275 --- /dev/null +++ b/src/opensearch/model/properties.ts @@ -0,0 +1,23 @@ +export type Timestamp = { + timestamp: string; +}; + +export type Hash = { + hash: string; +}; + +export type Ordinal = { + ordinal: number; +}; + +export type SnapshotOrdinal = { + snapshotOrdinal: number; +}; + +export type SnapshotHash = { + snapshotHash: number; +}; + +export type Height = { + height: number; +}; diff --git a/src/opensearch/model/snapshot.ts b/src/opensearch/model/snapshot.ts new file mode 100644 index 0000000..7fd6704 --- /dev/null +++ b/src/opensearch/model/snapshot.ts @@ -0,0 +1,14 @@ +import { Hash, Height, Ordinal, Timestamp } from "./properties"; +import { RewardTransaction } from "./transaction"; + +export type OpenSearchSnapshot = { + subHeight: number; + lastSnapshotHash: string; + blocks: string[]; + rewards: RewardTransaction[]; +} & Timestamp & + Ordinal & + Hash & + Height; + +export type WithoutRewards = Omit; diff --git a/src/opensearch/model/transaction.ts b/src/opensearch/model/transaction.ts new file mode 100644 index 0000000..324c307 --- /dev/null +++ b/src/opensearch/model/transaction.ts @@ -0,0 +1,30 @@ +import { + Hash, + Ordinal, + SnapshotHash, + SnapshotOrdinal, + Timestamp, +} from "./properties"; + +export type OpenSearchTransaction = { + source: string; + destination: string; + amount: number; + fee: number; + parent: TransactionReference; + blockHash: string; + salt: number; + transactionOriginal: object; +} & Timestamp & + SnapshotHash & + SnapshotOrdinal & + Hash; + +export type TransactionReference = Hash & Ordinal; + +export type Transaction = Omit; + +export type RewardTransaction = { + destination: string; + amount: number; +}; diff --git a/src/opensearch/opensearch.ts b/src/opensearch/opensearch.ts new file mode 100644 index 0000000..ee01729 --- /dev/null +++ b/src/opensearch/opensearch.ts @@ -0,0 +1,1037 @@ +import { Client } from "@opensearch-project/opensearch"; +import { pipe } from "fp-ts/lib/function"; + +import { ApplicationError, OpenSearchError, StatusCodes } from "./http"; +import { + Balance, + Block, + CurrencyData, + FeeTransaction, + OpenSearchBalance, + OpenSearchBlock, + OpenSearchFeeTransaction, + OpenSearchSnapshot, + OpenSearchTransaction, + RewardTransaction, + Transaction, + WithoutRewards, +} from "./model"; +import { + findAll, + findOne, + getAll, + getByFieldQuery, + getDocumentQuery, + getLatestQuery, + getMultiQuery, + getSearchSince, + maxSizeLimit, + SearchDirection, + SortOption, + SortOptions, + SortOptionSince, + SortOrder, +} from "./query"; + +import { + chain, + left, + map, + of, + orElse, + right, + TaskEither, + tryCatch, +} from "fp-ts/lib/TaskEither"; +import { fromNextString, Pagination, toNextString } from "./os-request-params"; +import { Get, Paths } from "./ts-extensions"; +import { Metagraph } from "./model/metagraph"; +import { + CurrencySnapshot, + OpenSearchCurrencySnapshot, + openSearchCurrencySnapshotToV2, + OpenSearchCurrencySnapshotV1, +} from "./model/currency-snapshot"; + +enum OSIndex { + Snapshots = "snapshots", + Blocks = "blocks", + Transactions = "transactions", + Balances = "balances-*", + CurrencySnapshots = "currency-snapshots-*", + CurrencyBlocks = "currency-blocks", + CurrencyTransactions = "currency-transactions", + CurrencyFeeTransactions = "currency-fee-transactions", + CurrencyBalances = "currency-balances-*", +} + +export type Result = { + data: T; + meta?: {}; +}; + +export type PaginatedResult = { + data: T[]; + meta: { + next: string | null; + }; +}; + +export const getClient = (): Client => { + return new Client({ node: process.env.OPENSEARCH_NODE }); +}; + +const getResultWithNextString = + (sortOptions: SortOptions) => + ( + getData: (sortOptions: SortOptions) => TaskEither + ): TaskEither> => { + // NOTE: We exceed the limit by 1 additional element to determine if next page is empty + const plusOneSize = sortOptions.size + ? sortOptions.size + 1 + : maxSizeLimit + 1; + + return pipe( + getData({ + ...sortOptions, + size: plusOneSize, + }), + chain((plusOneData) => { + if (plusOneData.length === 0) + return left( + new ApplicationError("Not found", [], StatusCodes.NOT_FOUND) + ); + + if (plusOneData.length < plusOneSize) { + return right({ data: plusOneData, meta: { next: null } }); + } + + const data = plusOneData.slice(0, -1); + const element = data[data.length - 1]; + + const getValue = (option: SortOption | SortOptionSince) => + option.sortField.split(".").reduce((acc, n) => acc[n], element); + + return right>({ + data, + meta: { + next: toNextString({ + size: sortOptions.size, + options: sortOptions.options.map((option) => ({ + ...option, + searchSince: getValue(option), + })), + }), + }, + }); + }) + ); + }; + +const getAggregationResultWithNextString = + (sortOptions: SortOptions) => + ( + getDataWithAfterKey: ( + sortOptions: SortOptions + ) => TaskEither + ): TaskEither> => { + // NOTE: We exceed the limit by 1 additional element to determine if next page is empty + const plusOneSize = sortOptions.size + ? sortOptions.size + 1 + : maxSizeLimit + 1; + + return pipe( + getDataWithAfterKey({ + ...sortOptions, + size: plusOneSize, + }), + chain(([plusOneData, after_key]) => { + if (plusOneData.length === 0) + return left( + new ApplicationError("Not found", [], StatusCodes.NOT_FOUND) + ); + + if (plusOneData.length < plusOneSize) { + return right({ data: plusOneData, meta: { next: null } }); + } + + const data = plusOneData.slice(0, -1); + + return right>({ + data, + meta: { + next: toNextString({ + size: sortOptions.size, + options: sortOptions.options + .filter((option) => after_key[option.sortField] !== undefined) + .map((option) => ({ + ...option, + searchSince: after_key[option.sortField], + })), + }), + }, + }); + }) + ); + }; + +const getSortOptions = (pagination: Pagination) => ({ + withDefault: (def: SortOptions): SortOptions => { + if ("next" in pagination) { + const sortOptionsFromNext = fromNextString(pagination.next); + return { + ...sortOptionsFromNext, + size: pagination.size ?? sortOptionsFromNext.size, + }; + } else { + return def; + } + }, +}); + +export function getFromPath(obj: O, path: K): Get; +export function getFromPath( + obj: Record, + path: string +): unknown { + const [firstKey, ...restKeys] = path.split("."); + + if (firstKey === undefined || firstKey === "") { + return obj; + } + + const value = obj[firstKey]; + + if (value === undefined || value === null) { + return undefined; + } + + if (restKeys.length === 0) { + return value; + } + + if (typeof value !== "object") { + return undefined; + } + + return getFromPath(value as Record, restKeys.join(".")); +} + +export const findCollectionByTerm = + (os: Client) => + ( + term: OSC[keyof OSC], + fields: (keyof OSC)[], + sortOptions: SortOptions, + index: OSIndex, + currencyIdentifier: string | null + ): TaskEither> => { + const stringifyTerm = (term: OSC[keyof OSC]) => { + return term !== null && term !== undefined && typeof term !== "object" + ? (String(term) as OSC[keyof OSC]) + : ("" as OSC[keyof OSC]); + }; + + return getResultWithNextString(sortOptions)((sort) => { + const query = + fields.length === 1 + ? getByFieldQuery( + index, + fields[0], + stringifyTerm(term), + sort, + currencyIdentifier + ) + : getMultiQuery( + index, + fields, + stringifyTerm(term), + sort, + currencyIdentifier + ); + return findAll(os.search(query), currencyIdentifier); + }); + }; + +export const findSnapshotRewards = + (os: Client) => + ( + term: string, + currencyIdentifier: string | null + ): TaskEither> => + pipe( + findOne( + findSnapshotByTerm(os)(term, currencyIdentifier), + currencyIdentifier + ), + map((r) => ({ ...r, data: r.data.rewards })) + ); + +export const listMetagraphs = + (os: Client) => + ( + pagination: Pagination + ): TaskEither> => { + const { size, ...options } = pagination; + const sortOptions = getSortOptions< + CurrencyData, + Metagraph + >(pagination).withDefault({ + size, + options: [ + { + ...options, + searchDirection: SearchDirection.Before, + sortField: "identifier" as Paths< + CurrencyData + >, + }, + ], + }); + + type LastMetagraphSnapshot = CurrencyData< + Pick< + OpenSearchCurrencySnapshotV1, + "hash" | "ownerAddress" | "stakingAddress" + > + >; + + type MetagraphBucket = { + latestSnapshot: { + hits: { + hits: [{ _source: LastMetagraphSnapshot }]; + }; + }; + }; + + type MetagraphsAggregation = { + after_key: { + identifier: + | CurrencyData["identifier"] + | null; + }; + buckets: MetagraphBucket[]; + }; + + return getAggregationResultWithNextString< + Metagraph, + LastMetagraphSnapshot, + MetagraphsAggregation["after_key"] + >(sortOptions)((sort) => { + const after = sortOptions.options + .filter(isSearchSinceOption) + .map>( + (opt) => opt as SortOptionSince + ) + .reduce( + (acc, curr) => ({ + ...acc, + [curr.sortField]: curr.searchSince, + }), + {} + ); + + const query = { + size: 0, + aggs: { + metagraphs: { + composite: { + sources: [ + { + identifier: { + terms: { + field: "identifier" as Paths< + CurrencyData + >, + }, + }, + }, + ], + size: sort.size, + ...(Object.keys(after).length > 0 ? { after } : {}), + }, + aggs: { + latestSnapshot: { + top_hits: { + sort: [ + { + "data.ordinal": { + order: SortOrder.Desc, + }, + } as { + [K in Paths< + CurrencyData + >]: { + order: SortOrder; + }; + } & { [key: string]: never }, + ], + _source: { + includes: [ + "identifier", + "data.hash", + "data.ownerAddress", + "data.stakingAddress", + ] as Paths>[], + }, + size: 1, + }, + }, + }, + }, + }, + }; + + return pipe( + tryCatch( + () => + os + .search({ + index: OSIndex.CurrencySnapshots, + body: query, + }) + .then((r) => r.body.aggregations.metagraphs), + (err) => + new ApplicationError( + "OpenSearch error", + [err as string], + StatusCodes.SERVER_ERROR + ) + ), + map(({ buckets, after_key }) => { + const data = buckets + .map(({ latestSnapshot }) => latestSnapshot.hits.hits) + .map(([hit]) => hit._source) + .map( + ({ + identifier, + data: { hash, stakingAddress, ownerAddress }, + }) => ({ + id: identifier, + lastSnapshotHash: hash, + stakingAddress: stakingAddress ?? null, + ownerAddress: ownerAddress ?? null, + }) + ); + + return [data, after_key]; + }) + ); + }); + }; + +export const listSnapshots = + (os: Client) => + ( + pagination: Pagination>, + currencyIdentifier: string | null + ): TaskEither> => { + const { size, ...options } = pagination; + const sortOptions = getSortOptions, OSS>( + pagination + ).withDefault({ + size, + options: [ + { + ...options, + searchDirection: options["searchDirection"] || SearchDirection.Before, + sortField: "ordinal", + }, + ], + }); + + return getResultWithNextString(sortOptions)((sort) => + findAll( + os.search( + getAll( + currencyIdentifier ? OSIndex.CurrencySnapshots : OSIndex.Snapshots, + sort, + currencyIdentifier + ) + ), + currencyIdentifier + ) + ); + }; + +export const findCurrencySnapshotsByOwnerAddress = + (os: Client) => + ( + ownerAddress: string, + pagination: Pagination + ): TaskEither< + OpenSearchError, + PaginatedResult + > => { + const { size, ...options } = pagination; + + const sortOptions = getSortOptions(pagination).withDefault({ + size, + options: [ + { + ...options, + searchDirection: SearchDirection.Before, + sortField: "data.ordinal", + }, + { + ...options, + searchDirection: SearchDirection.Before, + sortField: "identifier", + }, + ], + }); + + return pipe( + getResultWithNextString< + CurrencyData> + >(sortOptions)((sort) => { + const query = { + index: OSIndex.CurrencySnapshots, + body: { + ...getSearchSince>(sort), + sort: sort.options.map((s) => ({ + [s.sortField]: + s.searchDirection === SearchDirection.After + ? SortOrder.Asc + : SortOrder.Desc, + })), + size: sort.size || maxSizeLimit, + _source: { + excludes: ["data.rewards"] as Paths< + CurrencyData + >[], + }, + query: { + bool: { + must: { + term: { ["data.ownerAddress"]: ownerAddress } as Record< + Paths>, + string + >, + }, + }, + }, + }, + }; + + return pipe( + tryCatch< + OpenSearchError, + CurrencyData>[] + >( + () => + os + .search(query) + .then((r) => r.body.hits.hits.map((h) => h._source)), + (err) => new OpenSearchError(err as string) + ) + ); + }), + map(({ data, ...rest }) => ({ + ...rest, + data: data.map(({ identifier, data }) => ({ + metagraphId: identifier, + ...openSearchCurrencySnapshotToV2(data), + })), + })) + ); + }; + +const exportSortOptions = ( + pagination: Pagination, + sortFields: Paths[], + findByHashFallback: () => TaskEither> +): TaskEither> => { + const { size, ...options } = pagination; + + return pagination["searchSince"] + ? (() => + pipe( + findByHashFallback(), + map((r: Result) => ({ + size, + options: sortFields.map((sortField) => ({ + sortField, + searchDirection: + options["searchDirection"] || SearchDirection.Before, + searchSince: getFromPath(r.data, sortField), + })), + })) + ))() + : of( + getSortOptions(pagination).withDefault({ + size, + options: sortFields.map((sortField) => ({ + ...options, + sortField, + searchDirection: + options["searchDirection"] || SearchDirection.Before, + })), + }) + ); +}; + +export const listTransactions = + (os: Client) => + ( + pagination: Pagination, + currencyIdentifier: string | null + ): TaskEither> => + pipe( + exportSortOptions( + pagination, + ["snapshotOrdinal", "source", "parent.ordinal"], + () => + findTransactionByHash(os)( + pagination["searchSince"], + currencyIdentifier + ) + ), + chain< + ApplicationError, + SortOptions, + PaginatedResult + >((sortOptions) => + getResultWithNextString(sortOptions)((sort) => + findAll( + os.search( + getAll( + currencyIdentifier + ? OSIndex.CurrencyTransactions + : OSIndex.Transactions, + sort, + currencyIdentifier + ) + ), + currencyIdentifier + ) + ) + ) + ); + +export const findSnapshot = + (os: Client) => + ( + term: string, + currencyIdentifier: string | null + ): TaskEither>> => { + return pipe( + findOne( + findSnapshotByTerm(os)(term, currencyIdentifier), + currencyIdentifier + ), + map((s) => { + const { rewards, ...rest } = s.data; + return { ...s, data: rest }; + }) + ); + }; + +const findSnapshotByTerm = + (os: Client) => + (term: string, currencyIdentifier: string | null) => { + const index = currencyIdentifier + ? OSIndex.CurrencySnapshots + : OSIndex.Snapshots; + + if (isLatest(term)) { + return os.search(getLatestQuery(index, currencyIdentifier)); + } + if (isOrdinal(term)) { + return os.search( + getByFieldQuery( + index, + "ordinal", + term, + { + options: [{ sortField: "ordinal" }], + size: 1, + }, + currencyIdentifier + ) + ); + } + return os.get(getDocumentQuery(index, term, currencyIdentifier)); + }; + +export const findTransactionsBySnapshot = + (os: Client) => + ( + term: string, + pagination: Pagination, + currencyIdentifier: string | null + ): TaskEither> => { + return pipe( + findSnapshot(os)(term, currencyIdentifier), + chain((s) => + pipe( + exportSortOptions( + pagination, + ["snapshotOrdinal", "source", "parent.ordinal"], + () => + findTransactionByHash(os)( + pagination["searchSince"], + currencyIdentifier + ) + ), + chain((sortOptions) => + findCollectionByTerm(os)( + s.data.ordinal, + ["snapshotOrdinal"], + sortOptions, + currencyIdentifier + ? OSIndex.CurrencyTransactions + : OSIndex.Transactions, + currencyIdentifier + ) + ), + orElse((e: ApplicationError) => + e.statusCode === StatusCodes.NOT_FOUND + ? right({ data: [], meta: { next: null } }) + : left(e) + ) + ) + ) + ); + }; + +export const findCurrencyFeeTransactionsBySnapshot = + (os: Client) => + ( + term: string, + pagination: Pagination, + currencyIdentifier: string + ): TaskEither< + ApplicationError, + PaginatedResult + > => { + return pipe( + findSnapshot(os)(term, currencyIdentifier), + chain((s) => { + return pipe( + exportSortOptions( + pagination, + ["snapshotOrdinal", "source", "parent.ordinal"], + () => + findCurrencyFeeTransactionByHash(os)( + pagination["searchSince"], + currencyIdentifier + ) + ), + chain((sortOptions) => { + return findCollectionByTerm(os)( + s.data.ordinal, + ["snapshotOrdinal"], + sortOptions, + OSIndex.CurrencyFeeTransactions, + currencyIdentifier + ); + }), + orElse((e: ApplicationError) => + e.statusCode === StatusCodes.NOT_FOUND + ? right({ data: [], meta: { next: null } }) + : left(e) + ) + ); + }) + ); + }; + +export const findTransactionsByAddress = + (os: Client) => + ( + address: string, + pagination: Pagination, + currencyIdentifier: string | null + ) => + pipe( + exportSortOptions( + pagination, + ["snapshotOrdinal", "source", "parent.ordinal"], + () => + findTransactionByHash(os)( + pagination["searchSince"], + currencyIdentifier + ) + ), + chain((sortOptions) => + findCollectionByTerm(os)( + address, + ["source", "destination"], + sortOptions, + currencyIdentifier + ? OSIndex.CurrencyTransactions + : OSIndex.Transactions, + currencyIdentifier + ) + ) + ); + +export const findCurrencyFeeTransactionsByAddress = + (os: Client) => + ( + address: string, + pagination: Pagination, + currencyIdentifier: string + ) => + pipe( + exportSortOptions( + pagination, + ["snapshotOrdinal", "source", "parent.ordinal"], + () => + findCurrencyFeeTransactionByHash(os)( + pagination["searchSince"], + currencyIdentifier + ) + ), + chain((sortOptions) => + findCollectionByTerm(os)( + address, + ["source", "destination"], + sortOptions, + OSIndex.CurrencyFeeTransactions, + currencyIdentifier + ) + ) + ); + +export const findTransactionsBySource = + (os: Client) => + ( + term: string, + pagination: Pagination, + currencyIdentifier: string | null + ): TaskEither> => + pipe( + exportSortOptions( + pagination, + ["snapshotOrdinal", "source", "parent.ordinal"], + () => + findTransactionByHash(os)( + pagination["searchSince"], + currencyIdentifier + ) + ), + chain((sortOptions) => + findCollectionByTerm(os)( + term, + ["source"], + sortOptions, + currencyIdentifier + ? OSIndex.CurrencyTransactions + : OSIndex.Transactions, + currencyIdentifier + ) + ) + ); + +export const findCurrencyFeeTransactionsBySource = + (os: Client) => + ( + address: string, + pagination: Pagination, + currencyIdentifier: string + ) => + pipe( + exportSortOptions( + pagination, + ["snapshotOrdinal", "source", "parent.ordinal"], + () => + findCurrencyFeeTransactionByHash(os)( + pagination["searchSince"], + currencyIdentifier + ) + ), + chain((sortOptions) => + findCollectionByTerm(os)( + address, + ["source"], + sortOptions, + OSIndex.CurrencyFeeTransactions, + currencyIdentifier + ) + ) + ); + +export const findTransactionsByDestination = + (os: Client) => + ( + term: string, + pagination: Pagination, + currencyIdentifier: string | null + ): TaskEither> => + pipe( + exportSortOptions( + pagination, + ["snapshotOrdinal", "source", "parent.ordinal"], + () => + findTransactionByHash(os)( + pagination["searchSince"], + currencyIdentifier + ) + ), + chain((sortOptions) => + findCollectionByTerm(os)( + term, + ["destination"], + sortOptions, + currencyIdentifier + ? OSIndex.CurrencyTransactions + : OSIndex.Transactions, + currencyIdentifier + ) + ) + ); + +export const findCurrencyFeeTransactionsByDestination = + (os: Client) => + ( + address: string, + pagination: Pagination, + currencyIdentifier: string | null + ) => + pipe( + exportSortOptions( + pagination, + ["snapshotOrdinal", "source", "parent.ordinal"], + () => + findTransactionByHash(os)( + pagination["searchSince"], + currencyIdentifier + ) + ), + chain((sortOptions) => + findCollectionByTerm(os)( + address, + ["destination"], + sortOptions, + OSIndex.CurrencyFeeTransactions, + currencyIdentifier + ) + ) + ); + +export const findTransactionByHash = + (os: Client) => + ( + hash: string, + currencyIdentifier: string | null + ): TaskEither> => + pipe( + findOne( + os.get( + getDocumentQuery( + currencyIdentifier + ? OSIndex.CurrencyTransactions + : OSIndex.Transactions, + hash, + currencyIdentifier + ) + ), + currencyIdentifier + ), + map((r) => { + const { salt, ...tx } = r.data; + return { + ...r, + data: tx, + }; + }) + ); + +export const findCurrencyFeeTransactionByHash = + (os: Client) => + ( + hash: string, + currencyIdentifier: string + ): TaskEither> => + pipe( + findOne( + os.get( + getDocumentQuery( + OSIndex.CurrencyFeeTransactions, + hash, + currencyIdentifier + ) + ), + currencyIdentifier + ) + ); + +export const findBalanceByAddress = + (os: Client) => + ( + address: string, + currencyIdentifier: string | null, + ordinal?: number + ): TaskEither> => { + const sort = { + options: [ + { + sortField: "snapshotOrdinal", + // To achieve (0, ordinal> we need to make (0, ordinal + 1) + ...(ordinal !== undefined ? { searchSince: ordinal + 1 } : {}), + searchDirection: SearchDirection.Before, + }, + ], + size: 1, + }; + + return pipe( + findOne( + os.search( + getByFieldQuery( + currencyIdentifier ? OSIndex.CurrencyBalances : OSIndex.Balances, + "address", + address, + sort, + currencyIdentifier + ) + ), + currencyIdentifier + ), + map(({ data: { snapshotOrdinal, balance, address }, meta }) => ({ + data: { ordinal: snapshotOrdinal, balance, address }, + meta, + })), + orElse((e: ApplicationError) => { + return e.statusCode === StatusCodes.NOT_FOUND + ? pipe( + findSnapshot(os)("latest", currencyIdentifier), + map((s) => ({ + data: { ordinal: s.data.ordinal, balance: 0, address }, + meta: {}, + })) + ) + : left(e); + }) + ); + }; + +export const findBlockByHash = + (os: Client) => + ( + hash: string, + currencyIdentifier: string | null + ): TaskEither> => { + return findOne( + os.get( + getDocumentQuery( + currencyIdentifier ? OSIndex.CurrencyBlocks : OSIndex.Blocks, + hash, + currencyIdentifier + ) + ), + currencyIdentifier + ); + }; + +const isOrdinal = (term: string | number): term is number => + /^\d+$/.test(term.toString()); + +const isLatest = ( + termValue: string | number | "latest" +): termValue is "latest" => termValue === "latest"; + +const isSearchSinceOption = (option: any): option is SortOptionSince => + "searchSince" in option && option.searchSince !== undefined; diff --git a/src/opensearch/os-request-params.ts b/src/opensearch/os-request-params.ts new file mode 100644 index 0000000..32aa9c5 --- /dev/null +++ b/src/opensearch/os-request-params.ts @@ -0,0 +1,217 @@ +import { APIGatewayEvent } from "aws-lambda"; +import { ApplicationError, StatusCodes } from "./http"; +import { Lens, Optional } from "monocle-ts"; +import { pipe } from "fp-ts/lib/function"; +import * as O from "fp-ts/Option"; +import * as R from "fp-ts/Record"; +import * as TE from "fp-ts/TaskEither"; +import { TaskEither } from "fp-ts/TaskEither"; +import { maxSizeLimit, SearchDirection, SortOptions } from "./query"; + +export type Pagination = + | { + size?: number; + searchDirection: SearchDirection; + searchSince: string; + } + | { size?: number } + | { size?: number; next: string }; + +type PaginationQueryParams = { + search_after?: string; + search_before?: string; + limit?: string; + next?: string; +}; + +const pathParams = Lens.fromNullableProp()( + "pathParameters", + {} +); +type PathParams = NonNullable< + APIGatewayEvent["pathParameters"] & { + hash?: string; + term?: string; + address?: string; + } +>; + +const pathParamsIsNotNull = (event: APIGatewayEvent) => + TE.fromPredicate( + () => Object.keys(pathParams.get(event)).length > 0, + () => + new ApplicationError( + "Error parsing request path params", + ["Path params should not be empty"], + StatusCodes.BAD_REQUEST + ) + )(event); + +export const extractPagination = ( + event: APIGatewayEvent +): TaskEither> => { + const params = event.queryStringParameters as PaginationQueryParams; + const searchBefore = params?.search_before; + const searchAfter = params?.search_after; + const limit = Number(params?.limit); + const next = params?.next; + + if (searchBefore && searchAfter) { + return TE.left( + new ApplicationError( + "search_after & search_before should be mutually exclusive", + [], + StatusCodes.BAD_REQUEST + ) + ); + } + + if (params?.limit !== undefined) { + if (isNaN(limit)) { + return TE.left( + new ApplicationError( + "limit must be a number", + [], + StatusCodes.BAD_REQUEST + ) + ); + } + + if (limit < 1) { + return TE.left( + new ApplicationError( + "limit must be a positive number", + [], + StatusCodes.BAD_REQUEST + ) + ); + } + + if (limit > maxSizeLimit) { + return TE.left( + new ApplicationError( + `limit must be lower or equal ${maxSizeLimit}`, + [], + StatusCodes.BAD_REQUEST + ) + ); + } + } + + if (next && searchAfter && searchBefore) { + return TE.left( + new ApplicationError( + "next and search_after/search_before should be mutually exclusive", + [], + StatusCodes.BAD_REQUEST + ) + ); + } + + if (next) { + return TE.right({ + next, + size: params.limit !== undefined ? limit : undefined, + }); + } + + return TE.right({ + searchSince: searchAfter || searchBefore, + searchDirection: + (searchAfter && SearchDirection.After) || + (searchBefore && SearchDirection.Before) || + undefined, + size: limit, + }); +}; + +export const toNextString = (options: SortOptions): string => { + const buffer = Buffer.from(JSON.stringify(options)); + return buffer.toString("base64"); +}; + +export const fromNextString = (next: string): SortOptions => { + const buffer = Buffer.from(next, "base64"); + return JSON.parse(buffer.toString("ascii")); +}; + +const pathParamExists = + (pathParam: keyof Partial) => (event: APIGatewayEvent) => + pipe( + TE.of(event), + TE.chainFirst(pathParamsIsNotNull), + TE.chainFirst(() => + pipe( + pathParams + .composeOptional(Optional.fromPath()([pathParam])) + .getOption(event), + TE.fromOption( + () => + new ApplicationError( + "Error parsing request path params", + [`${pathParam} param should not be empty`], + StatusCodes.BAD_REQUEST + ) + ) + ) + ) + ); + +export class RequestParamMissingError extends ApplicationError { + constructor(param: string) { + super( + "Error parsing request path params", + [`${param} param should not be empty`], + StatusCodes.BAD_REQUEST + ); + } +} + +export const getPathParam: ( + param: K +) => (event: APIGatewayEvent) => TE.TaskEither = + (param: K) => + (event: APIGatewayEvent) => + pipe( + O.fromNullable(event.pathParameters), + O.chain((params) => + pipe( + params, + R.lookup(param), + O.chain( + O.fromPredicate( + (value): value is string => typeof value === "string" + ) + ) + ) + ), + TE.fromOption(() => new RequestParamMissingError(param)) + ); + +/** @deprecated use extractCurrencyIdentifierParam */ +export const validateCurrencyIdentifierParam = (event: APIGatewayEvent) => + pipe( + TE.of(event), + TE.chain(pathParamExists("identifier")) + ); + +/** @deprecated use extractTermParam */ +export const validateTermParam = (event: APIGatewayEvent) => + pipe( + TE.of(event), + TE.chain(pathParamExists("term")) + ); + +/** @deprecated use extractHashParam */ +export const validateHashParam = (event: APIGatewayEvent) => + pipe( + TE.of(event), + TE.chain(pathParamExists("hash")) + ); + +/** @deprecated use extractAddressParam */ +export const validateAddressParam = (event: APIGatewayEvent) => + pipe( + TE.of(event), + TE.chain(pathParamExists("address")) + ); diff --git a/src/opensearch/query.ts b/src/opensearch/query.ts new file mode 100644 index 0000000..79730b1 --- /dev/null +++ b/src/opensearch/query.ts @@ -0,0 +1,237 @@ +import { ApiResponse } from "@opensearch-project/opensearch"; +import { ApplicationError, StatusCodes } from "./http"; +import { TransportRequestPromise } from "@opensearch-project/opensearch/lib/Transport"; +import { pipe } from "fp-ts/lib/function"; +import { filterOrElse, map, TaskEither, tryCatch } from "fp-ts/lib/TaskEither"; +import { CurrencyData, Ordinal } from "./model"; +import { SearchRequest } from "@opensearch-project/opensearch/api/types"; +import { Result } from "./opensearch"; + +export enum SortOrder { + Desc = "desc", + Asc = "asc", +} + +export enum SearchDirection { + After = "search_after", + Before = "search_before", +} + +export type SortOption = { + // TODO: Use Paths + sortField: string; // path or nested path + searchDirection?: SearchDirection; +}; + +export type SortOptionSince = { + // TODO: Use Paths + sortField: string; // path or nested path + searchSince: string | number; + searchDirection?: SearchDirection; +}; + +export type SortOptions = { + options: SortOption[] | SortOptionSince[]; + size?: number; +}; + +export const maxSizeLimit = 10000; + +export const getDocumentQuery = ( + index: string, + id: string, + currencyIdentifier: string | null +) => ({ + index, + id: currencyIdentifier ? currencyIdentifier + id : id, +}); + +export const getLatestQuery = ( + index: string, + currencyIdentifier: string | null +): any => ({ + index, + body: { + size: 1, + sort: { + [currencyIdentifier ? "data.ordinal" : "ordinal"]: { + order: SortOrder.Desc, + }, + }, + ...(currencyIdentifier + ? { + query: { + bool: { must: [{ match: { identifier: currencyIdentifier } }] }, + }, + } + : {}), + }, +}); + +const isSearchSince = (options: any): options is SortOptionSince[] => + typeof options[0]?.searchSince === "string" || + typeof options[0]?.searchSince === "number"; + +export const getSearchSince = (sort: SortOptions) => { + return isSearchSince(sort.options) + ? { search_after: sort.options.map((a) => a.searchSince) } + : {}; +}; + +export const getSort = ( + sort: SortOptions, + currencyIdentifier: string | null +) => { + return sort.options.length === 0 + ? [] + : sort.options.map((s) => ({ + [currencyIdentifier ? `data.${s.sortField}` : s.sortField]: + s.searchDirection === SearchDirection.After + ? SortOrder.Asc + : SortOrder.Desc, + })); +}; + +export const getMultiQuery = ( + index: string, + fields: K[], + value: T[keyof T], + sort: SortOptions, + currencyIdentifier: string | null +): SearchRequest => ({ + index, + body: { + ...getSearchSince(sort), + size: sort.size || maxSizeLimit, + sort: getSort(sort, currencyIdentifier), + query: { + bool: { + should: fields.map((field) => ({ + term: { + [currencyIdentifier ? `data.${String(field)}` : field]: value, + }, + })), + ...(currencyIdentifier + ? { must: [{ match: { identifier: currencyIdentifier } }] } + : {}), + minimum_should_match: 1, + boost: 1.0, + }, + }, + }, +}); + +export function getByFieldQuery( + index: string, + field: K, + value: T[K], + sort: SortOptions, + currencyIdentifier: string | null +): any { + return { + index, + body: { + ...getSearchSince(sort), + size: sort.size || maxSizeLimit, + sort: getSort(sort, currencyIdentifier), + query: { + bool: { + must: [ + { + term: { + [currencyIdentifier ? `data.${String(field)}` : field]: value, + }, + }, + ...(currencyIdentifier + ? [{ term: { identifier: currencyIdentifier } }] + : []), + ], + }, + }, + }, + }; +} + +export function getAll( + index: string, + sort: SortOptions, + currencyIdentifier: string | null +): any { + return { + index, + body: { + ...getSearchSince(sort), + size: sort.size || maxSizeLimit, + sort: getSort(sort, currencyIdentifier), + query: currencyIdentifier + ? { + bool: { + must: [{ term: { identifier: currencyIdentifier } }], + }, + } + : { + match_all: {}, + }, + }, + }; +} + +export const findOne = ( + search: TransportRequestPromise, + currencyIdentifier: string | null +): TaskEither> => + pipe( + tryCatch( + () => search.then((r) => (r.body.found ? [r.body] : r.body.hits.hits)), + (err: any) => { + if (err.meta?.body?.found === false) { + return new ApplicationError("Not Found", [""], StatusCodes.NOT_FOUND); + } else { + return new ApplicationError( + "OpenSearch error", + [err as string], + StatusCodes.SERVER_ERROR + ); + } + } + ), + filterOrElse( + (hits) => hits.length > 0, + () => new ApplicationError("Not Found", [], StatusCodes.NOT_FOUND) + ), + map((hits) => ({ + data: hits[0]._source as T, + })), + map((h) => { + return isWrapped(h.data) && currencyIdentifier + ? { ...h, data: h.data.data } + : h; + }) + ); + +export const findAll = ( + search: TransportRequestPromise, + currencyIdentifier: string | null +): TaskEither => + pipe( + tryCatch( + () => + search.then((r) => { + return r.body.hits.hits; + }), + (err) => + new ApplicationError( + "OpenSearch error", + [err as string], + StatusCodes.SERVER_ERROR + ) + ), + map((hits) => { + return hits.map((hit) => hit._source as T); + }), + map((hits) => + hits.map((h) => (isWrapped(h) && currencyIdentifier ? h.data : h)) + ) + ); + +const isWrapped = (a: any): a is CurrencyData => a.identifier && a.data; diff --git a/src/opensearch/service.ts b/src/opensearch/service.ts new file mode 100644 index 0000000..572a6fb --- /dev/null +++ b/src/opensearch/service.ts @@ -0,0 +1,682 @@ +import { Client } from "@opensearch-project/opensearch"; +import { APIGatewayEvent } from "aws-lambda"; +import * as T from "fp-ts/lib/Task"; +import * as TE from "fp-ts/lib/TaskEither"; +import { Task } from "fp-ts/lib/Task"; +import { chain, fold, map, of } from "fp-ts/lib/TaskEither"; +import { + ApplicationError, + errorResponse, + Response, + StatusCodes, + successResponse, +} from "./http"; + +import { + extractPagination, + validateTermParam, + validateAddressParam, + validateHashParam, + validateCurrencyIdentifierParam, + getPathParam, +} from "./os-request-params"; +import { + findBalanceByAddress, + findBlockByHash, + findCurrencyFeeTransactionByHash, + findCurrencyFeeTransactionsByAddress, + findCurrencyFeeTransactionsByDestination, + findCurrencyFeeTransactionsBySnapshot, + findCurrencyFeeTransactionsBySource, + findCurrencySnapshotsByOwnerAddress, + findSnapshot, + findSnapshotRewards, + findTransactionByHash, + findTransactionsByAddress, + findTransactionsByDestination, + findTransactionsBySnapshot, + findTransactionsBySource, + listMetagraphs, + listSnapshots, + listTransactions, +} from "./opensearch"; +import { pipe } from "fp-ts/lib/function"; +import { OpenSearchCurrencySnapshotV1 } from "./model/currency-snapshot"; + +export const getCurrencySnapshots = (event: APIGatewayEvent, os: Client) => + pipe( + of(event), + chain(validateCurrencyIdentifierParam), + chain(() => + pipe( + extractPagination(event), + map((pagination) => { + return { pagination, ...extractCurrencyIdentifier(event) }; + }) + ) + ), + chain(({ pagination, currencyIdentifier }) => + listSnapshots(os)( + pagination, + currencyIdentifier + ) + ), + fold( + (reason) => T.of(errorResponse(reason)), + (value) => T.of(successResponse(StatusCodes.OK)(value)) + ) + ); + +export const getCurrencySnapshotsByOwnerAddress = ( + event: APIGatewayEvent, + os: Client +) => + pipe( + TE.Do, + TE.bind("pagination", () => extractPagination(event)), + TE.bind("ownerAddress", () => extractAddressParam(event)), + TE.chain(({ pagination, ownerAddress }) => + findCurrencySnapshotsByOwnerAddress(os)(ownerAddress, pagination) + ), + fold( + (reason) => T.of(errorResponse(reason)), + (value) => T.of(successResponse(StatusCodes.OK)(value)) + ) + ); + +export const getGlobalSnapshots = (event: APIGatewayEvent, os: Client) => + pipe( + of(event), + chain(() => + pipe( + extractPagination(event), + map((pagination) => { + return { pagination }; + }) + ) + ), + chain(({ pagination }) => listSnapshots(os)(pagination, null)), + fold( + (reason) => T.of(errorResponse(reason)), + (value) => T.of(successResponse(StatusCodes.OK)(value)) + ) + ); + +export const getGlobalSnapshot = ( + event: APIGatewayEvent, + os: Client +): Task => + pipe( + of(event), + chain(validateTermParam), + map(extractTerm), + chain(({ termName, termValue }) => findSnapshot(os)(termValue, null)), + fold( + (reason) => T.of(errorResponse(reason)), + (value) => T.of(successResponse(StatusCodes.OK)(value)) + ) + ); + +export const getCurrencySnapshot = ( + event: APIGatewayEvent, + os: Client +): Task => + pipe( + of(event), + chain(validateCurrencyIdentifierParam), + chain(validateTermParam), + map((event) => ({ + ...extractTerm(event), + ...extractCurrencyIdentifier(event), + })), + chain(({ termName, termValue, currencyIdentifier }) => + findSnapshot(os)( + termValue, + currencyIdentifier + ) + ), + fold( + (reason) => T.of(errorResponse(reason)), + (value) => T.of(successResponse(StatusCodes.OK)(value)) + ) + ); + +export const getGlobalSnapshotRewards = ( + event: APIGatewayEvent, + os: Client +): Task => + pipe( + of(event), + chain(validateTermParam), + map(extractTerm), + chain(({ termName, termValue }) => + findSnapshotRewards(os)(termValue, null) + ), + fold( + (reason) => T.of(errorResponse(reason)), + (value) => T.of(successResponse(StatusCodes.OK)(value)) + ) + ); + +export const getCurrencySnapshotRewards = ( + event: APIGatewayEvent, + os: Client +): Task => + pipe( + of(event), + chain(validateCurrencyIdentifierParam), + chain(validateTermParam), + map((event) => ({ + ...extractTerm(event), + ...extractCurrencyIdentifier(event), + })), + chain(({ termName, termValue, currencyIdentifier }) => + findSnapshotRewards(os)(termValue, currencyIdentifier) + ), + fold( + (reason) => T.of(errorResponse(reason)), + (value) => T.of(successResponse(StatusCodes.OK)(value)) + ) + ); + +export const getGlobalSnapshotTransactions = ( + event: APIGatewayEvent, + os: Client +): Task => + pipe( + of(event), + chain(validateTermParam), + map(extractTerm), + chain(({ termName, termValue }) => + pipe( + extractPagination(event), + map((pagination) => ({ termName, termValue, pagination })) + ) + ), + chain(({ termName, termValue, pagination }) => + findTransactionsBySnapshot(os)(termValue, pagination, null) + ), + fold( + (reason) => T.of(errorResponse(reason)), + (value) => T.of(successResponse(StatusCodes.OK)(value)) + ) + ); + +export const getCurrencySnapshotTransactions = ( + event: APIGatewayEvent, + os: Client +): Task => + pipe( + of(event), + chain(validateTermParam), + chain(validateCurrencyIdentifierParam), + map((event) => ({ + ...extractTerm(event), + ...extractCurrencyIdentifier(event), + })), + chain(({ termName, termValue, currencyIdentifier }) => + pipe( + extractPagination(event), + map((pagination) => ({ + termName, + termValue, + pagination, + currencyIdentifier, + })) + ) + ), + chain(({ termName, termValue, pagination, currencyIdentifier }) => + findTransactionsBySnapshot(os)(termValue, pagination, currencyIdentifier) + ), + fold( + (reason) => T.of(errorResponse(reason)), + (value) => T.of(successResponse(StatusCodes.OK)(value)) + ) + ); + +export const getCurrencySnapshotFeeTransactions = ( + event: APIGatewayEvent, + os: Client +) => + pipe( + TE.Do, + TE.bind("identifier", () => extractCurrencyIdentifierParam(event)), + TE.bind("term", () => extractTermParam(event)), + TE.bind("pagination", () => extractPagination(event)), + TE.chain(({ identifier, term: { termValue }, pagination }) => { + console.log("params: ", { identifier, termValue, pagination }); + return findCurrencyFeeTransactionsBySnapshot(os)( + termValue, + pagination, + identifier + ); + }), + TE.fold( + (reason) => T.of(errorResponse(reason)), + (value) => T.of(successResponse(StatusCodes.OK)(value)) + ) + ); + +export const getCurrencyBlock = (event: APIGatewayEvent, os: Client) => + pipe( + of(event), + chain(validateCurrencyIdentifierParam), + fold( + (reason) => T.of(errorResponse(reason)), + (validatedEvent) => getBlock(validatedEvent, os) + ) + ); + +export const getBlock = (event: APIGatewayEvent, os: Client): Task => + pipe( + of(event), + chain(validateHashParam), + map(extractHash), + map((hash) => ({ + ...hash, + ...extractCurrencyIdentifier(event), + })), + chain(({ hash, currencyIdentifier }) => + findBlockByHash(os)(hash, currencyIdentifier) + ), + fold( + (reason) => T.of(errorResponse(reason)), + (value) => T.of(successResponse(StatusCodes.OK)(value)) + ) + ); + +export const getCurrencyTransactions = (event: APIGatewayEvent, os: Client) => + pipe( + of(event), + chain(validateCurrencyIdentifierParam), + fold( + (reason) => T.of(errorResponse(reason)), + (validatedEvent) => getTransactions(validatedEvent, os) + ) + ); + +export const getTransactions = (event: APIGatewayEvent, os: Client) => + pipe( + of(event), + chain(() => + pipe( + extractPagination(event), + map((pagination) => { + return { + pagination, + ...extractCurrencyIdentifier(event), + }; + }) + ) + ), + chain(({ pagination, currencyIdentifier }) => + listTransactions(os)(pagination, currencyIdentifier) + ), + fold( + (reason) => T.of(errorResponse(reason)), + (value) => T.of(successResponse(StatusCodes.OK)(value)) + ) + ); + +export const getCurrencyTransactionsByAddress = ( + event: APIGatewayEvent, + os: Client +) => + pipe( + of(event), + chain(validateCurrencyIdentifierParam), + fold( + (reason) => T.of(errorResponse(reason)), + (validatedEvent) => getTransactionsByAddress(validatedEvent, os) + ) + ); + +export const getCurrencyFeeTransactionsByAddress = ( + event: APIGatewayEvent, + os: Client +) => + pipe( + TE.Do, + TE.bind("address", () => extractAddressParam(event)), + TE.bind("pagination", () => extractPagination(event)), + TE.bind("identifier", () => extractCurrencyIdentifierParam(event)), + TE.chain(({ address, pagination, identifier }) => + findCurrencyFeeTransactionsByAddress(os)(address, pagination, identifier) + ), + fold( + (reason) => T.of(errorResponse(reason)), + (value) => T.of(successResponse(StatusCodes.OK)(value)) + ) + ); + +export const getTransactionsByAddress = ( + event: APIGatewayEvent, + os: Client +): Task => + pipe( + of(event), + chain(validateAddressParam), + map(extractAddress), + chain(({ address }) => + pipe( + extractPagination(event), + map((pagination) => { + return { address, pagination, ...extractCurrencyIdentifier(event) }; + }) + ) + ), + chain(({ address, pagination, currencyIdentifier }) => + findTransactionsByAddress(os)(address, pagination, currencyIdentifier) + ), + fold( + (reason) => T.of(errorResponse(reason)), + (value) => T.of(successResponse(StatusCodes.OK)(value)) + ) + ); + +export const getCurrencyTransactionsBySource = ( + event: APIGatewayEvent, + os: Client +) => + pipe( + of(event), + chain(validateCurrencyIdentifierParam), + fold( + (reason) => T.of(errorResponse(reason)), + (validatedEvent) => getTransactionsBySource(validatedEvent, os) + ) + ); + +export const getCurrencyFeeTransactionsBySource = ( + event: APIGatewayEvent, + os: Client +) => + pipe( + TE.Do, + TE.bind("address", () => extractAddressParam(event)), + TE.bind("pagination", () => extractPagination(event)), + TE.bind("identifier", () => extractCurrencyIdentifierParam(event)), + TE.chain(({ address, pagination, identifier }) => + findCurrencyFeeTransactionsBySource(os)(address, pagination, identifier) + ), + fold( + (reason) => T.of(errorResponse(reason)), + (value) => T.of(successResponse(StatusCodes.OK)(value)) + ) + ); + +export const getTransactionsBySource = ( + event: APIGatewayEvent, + os: Client +): Task => + pipe( + of(event), + chain(validateAddressParam), + map(extractAddress), + chain(({ address }) => + pipe( + extractPagination(event), + map((pagination) => ({ + address, + pagination, + ...extractCurrencyIdentifier(event), + })) + ) + ), + chain(({ address, pagination, currencyIdentifier }) => + findTransactionsBySource(os)(address, pagination, currencyIdentifier) + ), + fold( + (reason) => T.of(errorResponse(reason)), + (value) => T.of(successResponse(StatusCodes.OK)(value)) + ) + ); + +export const getCurrencyTransactionsByDestination = ( + event: APIGatewayEvent, + os: Client +) => + pipe( + of(event), + chain(validateCurrencyIdentifierParam), + fold( + (reason) => T.of(errorResponse(reason)), + (validatedEvent) => getTransactionsByDestination(validatedEvent, os) + ) + ); + +export const getCurrencyFeeTransactionsByDestination = ( + event: APIGatewayEvent, + os: Client +) => + pipe( + TE.Do, + TE.bind("address", () => extractAddressParam(event)), + TE.bind("pagination", () => extractPagination(event)), + TE.bind("identifier", () => extractCurrencyIdentifierParam(event)), + TE.chain(({ address, pagination, identifier }) => + findCurrencyFeeTransactionsByDestination(os)( + address, + pagination, + identifier + ) + ), + fold( + (reason) => T.of(errorResponse(reason)), + (value) => T.of(successResponse(StatusCodes.OK)(value)) + ) + ); + +export const getTransactionsByDestination = ( + event: APIGatewayEvent, + os: Client +): Task => + pipe( + of(event), + chain(validateAddressParam), + map(extractAddress), + chain(({ address }) => + pipe( + extractPagination(event), + map((pagination) => ({ + address, + pagination, + ...extractCurrencyIdentifier(event), + })) + ) + ), + chain(({ address, pagination, currencyIdentifier }) => + findTransactionsByDestination(os)(address, pagination, currencyIdentifier) + ), + fold( + (reason) => T.of(errorResponse(reason)), + (value) => T.of(successResponse(StatusCodes.OK)(value)) + ) + ); + +export const getCurrencyBalanceByAddress = ( + event: APIGatewayEvent, + os: Client +) => + pipe( + of(event), + chain(validateCurrencyIdentifierParam), + fold( + (reason) => T.of(errorResponse(reason)), + (validatedEvent) => getBalanceByAddress(validatedEvent, os) + ) + ); + +export const getBalanceByAddress = ( + event: APIGatewayEvent, + os: Client +): Task => + pipe( + of(event), + chain(validateAddressParam), + map(extractAddressAndOrdinal), + map((params) => ({ + ...params, + ...extractCurrencyIdentifier(event), + })), + chain(({ address, ordinal, currencyIdentifier }) => + findBalanceByAddress(os)(address, currencyIdentifier, ordinal) + ), + fold( + (reason) => T.of(errorResponse(reason)), + (value) => T.of(successResponse(StatusCodes.OK)(value)) + ) + ); + +export const getCurrencyTransaction = (event: APIGatewayEvent, os: Client) => + pipe( + extractCurrencyIdentifierParam(event), + TE.fold( + (reason) => T.of(errorResponse(reason)), + (identifier) => getTransaction(event, os, identifier) + ) + ); + +export const getCurrencyFeeTransaction = (event: APIGatewayEvent, os: Client) => + pipe( + TE.Do, + TE.bind("hash", () => extractHashParam(event)), + TE.bind("identifier", () => extractCurrencyIdentifierParam(event)), + TE.chain(({ hash, identifier }) => + findCurrencyFeeTransactionByHash(os)(hash, identifier) + ), + fold( + (reason) => T.of(errorResponse(reason)), + (value) => T.of(successResponse(StatusCodes.OK)(value)) + ) + ); + +export const getMetagraphs = (event: APIGatewayEvent, os: Client) => + pipe( + TE.Do, + TE.bind("pagination", () => extractPagination(event)), + TE.chain(({ pagination }) => listMetagraphs(os)(pagination)), + fold( + (reason) => T.of(errorResponse(reason)), + (value) => T.of(successResponse(StatusCodes.OK)(value)) + ) + ); + +export const getTransaction = ( + event: APIGatewayEvent, + os: Client, + currencyIdentifier: string | null +): Task => + pipe( + extractHashParam(event), + chain((hash) => findTransactionByHash(os)(hash, currencyIdentifier)), + fold( + (reason) => T.of(errorResponse(reason)), + (value) => T.of(successResponse(StatusCodes.OK)(value)) + ) + ); + +/** @deprecated use extractCurrencyIdentifierParam */ +const extractCurrencyIdentifier = ( + event: APIGatewayEvent +): { currencyIdentifier: string | null } => { + return { + currencyIdentifier: event.pathParameters?.identifier || null, + }; +}; + +/** @deprecated use extractHashParam */ +const extractHash = (event: APIGatewayEvent) => { + return { hash: event.pathParameters!.hash! }; +}; + +/** @deprecated use extractAddressParam */ +const extractAddress = (event: APIGatewayEvent) => { + return { address: event.pathParameters!.address! }; +}; + +/** @deprecated use extractTermParam */ +const extractTerm = (event: APIGatewayEvent) => { + if (event.pathParameters!.term == "latest") + return { + termName: "ordinal", + termValue: "latest", + }; + return { + termName: isNaN(Number(event.pathParameters!.term!)) ? "hash" : "ordinal", + termValue: event.pathParameters!.term!, + }; +}; + +/** @deprecated use extractAddressParam and extractOrdinalParam */ +const extractAddressAndOrdinal = ( + event: APIGatewayEvent +): { address: string; ordinal?: number } => { + const ordinal = Number(event.queryStringParameters?.ordinal); + return { + address: event.pathParameters!.address!, + ...(!isNaN(ordinal) ? { ordinal } : {}), + }; +}; + +export const extractCurrencyIdentifierParam = (event: APIGatewayEvent) => + pipe( + TE.of(event), + TE.chain(getPathParam("identifier")) + ); + +export const extractTermParam = (event: APIGatewayEvent) => + pipe( + TE.of(event), + TE.chain(getPathParam("term")), + TE.map((term) => { + if (term == "latest") + return { + termName: "ordinal", + termValue: "latest", + }; + return { + termName: isNaN(Number(term)) ? "hash" : "ordinal", + termValue: term, + }; + }) + ); + +export const extractHashParam = (event: APIGatewayEvent) => + pipe( + TE.of(event), + TE.chain(getPathParam("hash")) + ); + +export const extractAddressParam = (event: APIGatewayEvent) => + pipe( + TE.of(event), + TE.chain(getPathParam("address")) + ); + +export const extractNextParam = (event: APIGatewayEvent) => + pipe( + TE.of(event), + TE.map((event) => event.queryStringParameters?.next) + ); + +export const extractLimitParam = (event: APIGatewayEvent) => + pipe( + TE.of(event), + TE.chain((event) => { + const limitParam = event.queryStringParameters?.limit; + const limit = Number(limitParam); + + if (limitParam !== undefined && isNaN(limit)) { + return TE.left( + new ApplicationError( + "limit must be a number", + [], + StatusCodes.BAD_REQUEST + ) + ); + } + + return TE.right(limit || undefined); + }) + ); diff --git a/src/opensearch/ts-extensions.ts b/src/opensearch/ts-extensions.ts new file mode 100644 index 0000000..2ed014c --- /dev/null +++ b/src/opensearch/ts-extensions.ts @@ -0,0 +1,97 @@ +type FilterUndefined = T extends undefined ? never : T; +type FilterNull = T extends null ? never : T; +type FilterUndefinedAndNull = FilterUndefined>; + +type ExtractFromObject< + O extends Record, + K +> = K extends keyof O + ? O[K] + : K extends keyof FilterUndefinedAndNull + ? FilterUndefinedAndNull[K] | undefined + : undefined; + +type ExtractFromArray = any[] extends A + ? A extends readonly (infer T)[] + ? T | undefined + : undefined + : K extends keyof A + ? A[K] + : undefined; + +type GetWithArray = K extends [] + ? O + : K extends [infer Key, ...infer Rest] + ? O extends Record + ? GetWithArray, Rest> + : O extends readonly any[] + ? GetWithArray, Rest> + : undefined + : never; + +export type Path = T extends `${infer Key}.${infer Rest}` + ? [Key, ...Path] + : T extends `${infer Key}` + ? [Key] + : []; + +export type Get = GetWithArray>; + +type Join = K extends string | number + ? P extends string | number + ? `${K}${"" extends P ? "" : "."}${P}` + : never + : never; + +type Prev = [ + never, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + ...0[] +]; + +export type Paths = [D] extends [never] + ? never + : T extends object + ? { + [K in keyof T]-?: K extends string | number + ? `${K}` | Join> + : never; + }[keyof T] + : ""; + +export type ResolvePath< + T, + P extends string +> = P extends `${infer K}.${infer Rest}` + ? K extends keyof T + ? ResolvePath + : never + : P extends keyof T + ? T[P] + : never; + +export type ResolveValues> = K extends infer P + ? P extends string + ? ResolvePath + : never + : never; diff --git a/tests/opensearch.test.ts b/tests/opensearch.test.ts new file mode 100644 index 0000000..e7d81e2 --- /dev/null +++ b/tests/opensearch.test.ts @@ -0,0 +1,67 @@ +import { getFromPath } from "../src/opensearch/opensearch"; + +describe("getFromPath", () => { + const testObj = { + a: 1, + b: { + c: 2, + d: { + e: 3, + f: null, + g: undefined, + }, + }, + h: [1, 2, { i: 4 }], + j: null, + k: undefined, + }; + + it("should return the value for a simple path", () => { + expect(getFromPath(testObj, "a")).toBe(1); + }); + + it("should return the value for a nested path", () => { + expect(getFromPath(testObj, "b.c")).toBe(2); + expect(getFromPath(testObj, "b.d.e")).toBe(3); + }); + + it("should return undefined for non-existent paths", () => { + expect(getFromPath(testObj, "x")).toBeUndefined(); + expect(getFromPath(testObj, "b.x")).toBeUndefined(); + expect(getFromPath(testObj, "b.c.x")).toBeUndefined(); + }); + + it("should return undefined for paths leading to null or undefined", () => { + expect(getFromPath(testObj, "b.d.f")).toBeUndefined(); + expect(getFromPath(testObj, "b.d.g")).toBeUndefined(); + expect(getFromPath(testObj, "j")).toBeUndefined(); + expect(getFromPath(testObj, "k")).toBeUndefined(); + }); + + it("should handle array access", () => { + expect(getFromPath(testObj, "h.0")).toBe(1); + expect(getFromPath(testObj, "h.2.i")).toBe(4); + }); + + it("should return undefined for out-of-bounds array access", () => { + expect(getFromPath(testObj, "h.3")).toBeUndefined(); + }); + + it("should return the entire object for an empty path", () => { + expect(getFromPath(testObj, "")).toEqual(testObj); + }); + + it("should return undefined when trying to access properties on primitives", () => { + expect(getFromPath(testObj, "a.x")).toBeUndefined(); + }); + + it("should handle complex nested structures", () => { + const complexObj = { + a: { + b: [{ c: { d: 1 } }, { c: { d: 2 } }], + }, + }; + expect(getFromPath(complexObj, "a.b.0.c.d")).toBe(1); + expect(getFromPath(complexObj, "a.b.1.c.d")).toBe(2); + }); +}); diff --git a/tests/request-params.test.ts b/tests/request-params.test.ts new file mode 100644 index 0000000..9e99991 --- /dev/null +++ b/tests/request-params.test.ts @@ -0,0 +1,81 @@ +import { APIGatewayEvent } from "aws-lambda"; +import * as E from "fp-ts/Either"; +import { pipe } from "fp-ts/function"; +import { getPathParam, RequestParamMissingError } from "../src/request-params"; + +describe("getPathParam", () => { + const mockEvent = ( + pathParameters: Record | null + ): APIGatewayEvent => + ({ + pathParameters, + } as APIGatewayEvent); + + it("should return the correct path parameter when it exists", async () => { + const event = mockEvent({ id: "123" }); + const result = await getPathParam("id")(event)(); + + pipe( + result, + E.fold( + (error) => { + fail(`Expected Right, but got Left: ${error}`); + }, + (value) => { + expect(value).toBe("123"); + } + ) + ); + }); + + it("should return a RequestParamMissingError when pathParameters is null", async () => { + const event = mockEvent(null); + const result = await getPathParam("id")(event)(); + + pipe( + result, + E.fold( + (error) => { + expect(error).toBeInstanceOf(RequestParamMissingError); + }, + (value) => { + fail(`Expected Left, but got Right: ${value}`); + } + ) + ); + }); + + it("should return a RequestParamMissingError when the requested parameter does not exist", async () => { + const event = mockEvent({ otherId: "456" }); + const result = await getPathParam("id")(event)(); + + pipe( + result, + E.fold( + (error) => { + expect(error).toBeInstanceOf(RequestParamMissingError); + }, + (value) => { + fail(`Expected Left, but got Right: ${value}`); + } + ) + ); + }); + + it("should return a RequestParamMissingError when the parameter value is undefined", async () => { + const event = mockEvent({ id: undefined }); + const result = await getPathParam("id")(event)(); + + pipe( + result, + E.fold( + (error) => { + expect(error).toBeInstanceOf(RequestParamMissingError); + }, + (value) => { + fail(`Expected Left, but got Right: ${value}`); + } + ) + ); + }); +}); From 4c2cd76d3e0ee1b3e96195c16f1d5b31c0f03b3f Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Wed, 21 May 2025 18:05:16 -0300 Subject: [PATCH 49/63] fix not found --- src/handlers/dagHandler.ts | 2 +- src/handlers/metagraphHandler.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/handlers/dagHandler.ts b/src/handlers/dagHandler.ts index 9165e6f..38cf99f 100644 --- a/src/handlers/dagHandler.ts +++ b/src/handlers/dagHandler.ts @@ -330,7 +330,7 @@ export const dagBalanceByAddress = async ( ? eitherOsBalance.right.data : null; - let balance; + let balance: {} | null = null; if (dbBalance === null) { if (osBalance === null) { balance = await balanceOrZeroFn(balance, address, ordinalNbr); diff --git a/src/handlers/metagraphHandler.ts b/src/handlers/metagraphHandler.ts index 0b47f24..b0f2546 100644 --- a/src/handlers/metagraphHandler.ts +++ b/src/handlers/metagraphHandler.ts @@ -408,9 +408,12 @@ export const currencyTransactionsByDestination = async ( const balanceOrZeroFn = async (metagraph_id, balance, address, ordinal) => { if (balance === null) { + console.log("balanceOrZeroFn", balance); const snapshot_ordinal = isFinite(ordinal) ? ordinal : (await latestMetagraphSnapshot(metagraph_id))?.ordinal; + + console.log("snapshot_ordinal", snapshot_ordinal); return { balance: 0, address, snapshot_ordinal }; } return balance; @@ -449,8 +452,7 @@ export const currencyBalanceByAddress = async ( const osBalance = isRight(eitherOsBalance) ? eitherOsBalance.right.data : null; - - let balance; + let balance: {} | null = null; if (dbBalance === null) { if (osBalance === null) { balance = await balanceOrZeroFn( From 12e66d2c38a18c8fd030c8eefd4947e1c98c3fd6 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Mon, 26 May 2025 13:37:36 -0300 Subject: [PATCH 50/63] remove opensearch fallback --- env.yml | 3 +- package-lock.json | 34 +- package.json | 1 - serverless.yml | 1 - src/handlers/dagHandler.ts | 33 +- src/handlers/metagraphHandler.ts | 45 +- src/opensearch/handler.ts | 94 -- src/opensearch/http.ts | 68 -- src/opensearch/model/balance.ts | 12 - src/opensearch/model/block.ts | 20 - src/opensearch/model/currency-data.ts | 4 - src/opensearch/model/currency-snapshot.ts | 36 - src/opensearch/model/fee-transaction.ts | 15 - src/opensearch/model/index.ts | 7 - src/opensearch/model/metagraph.ts | 6 - src/opensearch/model/properties.ts | 23 - src/opensearch/model/snapshot.ts | 14 - src/opensearch/model/transaction.ts | 30 - src/opensearch/opensearch.ts | 1037 --------------------- src/opensearch/os-request-params.ts | 217 ----- src/opensearch/query.ts | 237 ----- src/opensearch/service.ts | 682 -------------- src/opensearch/ts-extensions.ts | 97 -- tests/opensearch.test.ts | 67 -- tests/request-params.test.ts | 81 -- 25 files changed, 13 insertions(+), 2851 deletions(-) delete mode 100644 src/opensearch/handler.ts delete mode 100644 src/opensearch/http.ts delete mode 100644 src/opensearch/model/balance.ts delete mode 100644 src/opensearch/model/block.ts delete mode 100644 src/opensearch/model/currency-data.ts delete mode 100644 src/opensearch/model/currency-snapshot.ts delete mode 100644 src/opensearch/model/fee-transaction.ts delete mode 100644 src/opensearch/model/index.ts delete mode 100644 src/opensearch/model/metagraph.ts delete mode 100644 src/opensearch/model/properties.ts delete mode 100644 src/opensearch/model/snapshot.ts delete mode 100644 src/opensearch/model/transaction.ts delete mode 100644 src/opensearch/opensearch.ts delete mode 100644 src/opensearch/os-request-params.ts delete mode 100644 src/opensearch/query.ts delete mode 100644 src/opensearch/service.ts delete mode 100644 src/opensearch/ts-extensions.ts delete mode 100644 tests/opensearch.test.ts delete mode 100644 tests/request-params.test.ts diff --git a/env.yml b/env.yml index b3c682f..ac66bc8 100644 --- a/env.yml +++ b/env.yml @@ -1,4 +1,3 @@ default: - opensearch: 'http://localhost:9200' vpc: {} - db_url: postgresql://postgres:postgres@localhost:5432/new_block_explorer?schema=public \ No newline at end of file + db_url: postgresql://postgres:postgres@localhost:5432/postgres?schema=public \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 58bcb01..350bef1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "3.1.2", "license": "MIT", "dependencies": { - "@opensearch-project/opensearch": "^1.2.0", "@prisma/client": "^6.2.1", "aws-lambda": "^1.0.7", "date-fns": "^2.30.0", @@ -3511,21 +3510,6 @@ "node": ">= 8" } }, - "node_modules/@opensearch-project/opensearch": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@opensearch-project/opensearch/-/opensearch-1.2.0.tgz", - "integrity": "sha512-bX0aUz5e7rlY1lKz1rFrqnNbl/l1CHvvysYB2Jn+C3WNs7nL6FnQjuxLhGwyRdW9W1bFokDoOVgPMIOi/Nn9/g==", - "dependencies": { - "aws4": "^1.11.0", - "debug": "^4.3.1", - "hpagent": "^0.1.1", - "ms": "^2.1.3", - "secure-json-parse": "^2.4.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@prisma/client": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.2.1.tgz", @@ -5144,11 +5128,6 @@ "node": ">= 10.0.0" } }, - "node_modules/aws4": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", - "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==" - }, "node_modules/axios": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz", @@ -7637,11 +7616,6 @@ "node": ">=8" } }, - "node_modules/hpagent": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-0.1.2.tgz", - "integrity": "sha512-ePqFXHtSQWAFXYmj+JtOTHr84iNrII4/QRlAAPPE+zqnKy4xJo7Ie1Y4kC7AdB+LxLxSTTzBMASsEcy0q8YyvQ==" - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -10532,7 +10506,8 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "peer": true }, "node_modules/mute-stream": { "version": "0.0.8", @@ -11715,11 +11690,6 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==" }, - "node_modules/secure-json-parse": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", - "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" - }, "node_modules/seek-bzip": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", diff --git a/package.json b/package.json index 51f6cc2..171205f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,6 @@ "description": "", "main": "src/handler.ts", "dependencies": { - "@opensearch-project/opensearch": "^1.2.0", "@prisma/client": "^6.2.1", "aws-lambda": "^1.0.7", "date-fns": "^2.30.0", diff --git a/serverless.yml b/serverless.yml index a30eca0..15a4601 100644 --- a/serverless.yml +++ b/serverless.yml @@ -15,7 +15,6 @@ provider: environment: SLS_DEBUG: "*" DATABASE_URL: ${self:custom.env.db_url} - OPENSEARCH_NODE: ${self:custom.env.opensearch} httpApi: cors: true vpc: ${self:custom.env.vpc} diff --git a/src/handlers/dagHandler.ts b/src/handlers/dagHandler.ts index 38cf99f..9484ad4 100644 --- a/src/handlers/dagHandler.ts +++ b/src/handlers/dagHandler.ts @@ -1,7 +1,6 @@ import { PrismaClient } from "@prisma/client"; import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; import { extractHashOrdinal, extractPagination } from "../request-params"; -import { isRight } from "fp-ts/Either"; import { balanceResponse, dagBlockResponse, @@ -19,8 +18,6 @@ import { paginatedQuery, toCreatedAtOrdinalCursor, } from "../pagination"; -import * as OpenSearch from "../opensearch/opensearch"; - import { toNumber, isFinite } from "lodash"; const prisma = new PrismaClient(); @@ -300,8 +297,6 @@ const balanceOrZeroFn = async (balance, address, ordinal) => { return balance; }; -const osClient = OpenSearch.getClient(); - export const dagBalanceByAddress = async ( event: APIGatewayProxyEvent ): Promise => { @@ -316,36 +311,14 @@ export const dagBalanceByAddress = async ( ? { snapshot_ordinal: { lte: ordinalNbr } } : {}; - const dbBalance = await prisma.dag_balance_changes.findFirst({ + const balance = await prisma.dag_balance_changes.findFirst({ where: { address, ...ordinalCondition }, orderBy: { snapshot_ordinal: "desc" }, }); - const eitherOsBalance = await OpenSearch.findBalanceByAddress(osClient)( - address!, - null, - ordinal !== undefined ? ordinalNbr : undefined - )(); - const osBalance = isRight(eitherOsBalance) - ? eitherOsBalance.right.data - : null; - - let balance: {} | null = null; - if (dbBalance === null) { - if (osBalance === null) { - balance = await balanceOrZeroFn(balance, address, ordinalNbr); - } else { - balance = { ...osBalance, snapshot_ordinal: ordinalNbr }; - } - } else { - if (osBalance != null && osBalance.ordinal > dbBalance.snapshot_ordinal) { - balance = { ...osBalance, snapshot_ordinal: ordinalNbr }; - } else { - balance = dbBalance; - } - } + const balanceOrZero = await balanceOrZeroFn(balance, address, ordinalNbr); - return respond(balance, balanceResponse); + return respond(balanceOrZero, balanceResponse); } catch (error) { return handleError(error); } diff --git a/src/handlers/metagraphHandler.ts b/src/handlers/metagraphHandler.ts index b0f2546..5f7bcc8 100644 --- a/src/handlers/metagraphHandler.ts +++ b/src/handlers/metagraphHandler.ts @@ -1,7 +1,6 @@ import { PrismaClient } from "@prisma/client"; import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; import { extractHashOrdinal, extractPagination } from "../request-params"; -import { isRight } from "fp-ts/Either"; import { balanceResponse, handleError, @@ -24,7 +23,6 @@ import { toCreatedAtOrdinalCursor, toOrdinalCursor, } from "../pagination"; -import * as OpenSearch from "../opensearch/opensearch"; import { toNumber, isFinite } from "lodash"; const prisma = new PrismaClient(); @@ -408,19 +406,14 @@ export const currencyTransactionsByDestination = async ( const balanceOrZeroFn = async (metagraph_id, balance, address, ordinal) => { if (balance === null) { - console.log("balanceOrZeroFn", balance); const snapshot_ordinal = isFinite(ordinal) ? ordinal : (await latestMetagraphSnapshot(metagraph_id))?.ordinal; - - console.log("snapshot_ordinal", snapshot_ordinal); return { balance: 0, address, snapshot_ordinal }; } return balance; }; -const osClient = OpenSearch.getClient(); - export const currencyBalanceByAddress = async ( event: APIGatewayProxyEvent ): Promise => { @@ -434,8 +427,7 @@ export const currencyBalanceByAddress = async ( const ordinalCondition = isFinite(ordinalNbr) ? { snapshot_ordinal: { lte: ordinalNbr } } : {}; - - const dbBalance = await prisma.metagraph_balance_changes.findFirst({ + const balance = await prisma.metagraph_balance_changes.findFirst({ where: { metagraph_id, address, @@ -443,36 +435,13 @@ export const currencyBalanceByAddress = async ( }, orderBy: { snapshot_ordinal: "desc" }, }); - - const eitherOsBalance = await OpenSearch.findBalanceByAddress(osClient)( - address!, - metagraph_id!, + const balanceOrZero = await balanceOrZeroFn( + metagraph_id, + balance, + address, ordinalNbr - )(); - const osBalance = isRight(eitherOsBalance) - ? eitherOsBalance.right.data - : null; - let balance: {} | null = null; - if (dbBalance === null) { - if (osBalance === null) { - balance = await balanceOrZeroFn( - metagraph_id, - balance, - address, - ordinal !== undefined ? ordinalNbr : undefined - ); - } else { - balance = { ...osBalance, snapshot_ordinal: ordinalNbr }; - } - } else { - if (osBalance != null && osBalance.ordinal > dbBalance.snapshot_ordinal) { - balance = { ...osBalance, snapshot_ordinal: ordinalNbr }; - } else { - balance = dbBalance; - } - } - - return respond(balance, balanceResponse); + ); + return respond(balanceOrZero, balanceResponse); } catch (error) { return handleError(error); } diff --git a/src/opensearch/handler.ts b/src/opensearch/handler.ts deleted file mode 100644 index 5d2d4f3..0000000 --- a/src/opensearch/handler.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { getClient } from "./opensearch"; -import { - getBalanceByAddress, - getBlock, - getCurrencyBalanceByAddress, - getCurrencyBlock, - getCurrencyFeeTransaction, - getCurrencyFeeTransactionsByAddress, - getCurrencyFeeTransactionsByDestination, - getCurrencyFeeTransactionsBySource, - getCurrencySnapshot, - getCurrencySnapshotFeeTransactions, - getCurrencySnapshotRewards, - getCurrencySnapshots, - getCurrencySnapshotTransactions, - getCurrencyTransaction, - getCurrencyTransactions, - getCurrencyTransactionsByAddress, - getCurrencyTransactionsByDestination, - getCurrencyTransactionsBySource, - getGlobalSnapshot, - getGlobalSnapshotRewards, - getGlobalSnapshots, - getGlobalSnapshotTransactions, - getMetagraphs, - getCurrencySnapshotsByOwnerAddress, - getTransaction, - getTransactions, - getTransactionsByAddress, - getTransactionsByDestination, - getTransactionsBySource, -} from "./service"; - -const osClient = getClient(); - -export const globalSnapshot = (event) => getGlobalSnapshot(event, osClient)(); -export const globalSnapshots = (event) => getGlobalSnapshots(event, osClient)(); -export const globalSnapshotRewards = (event) => - getGlobalSnapshotRewards(event, osClient)(); -export const globalSnapshotTransactions = (event) => - getGlobalSnapshotTransactions(event, osClient)(); -export const block = (event) => getBlock(event, osClient)(); - -export const transaction = (event) => getTransaction(event, osClient, null)(); -export const transactions = (event) => getTransactions(event, osClient)(); -export const transactionsByAddress = (event) => - getTransactionsByAddress(event, osClient)(); -export const transactionsBySource = (event) => - getTransactionsBySource(event, osClient)(); -export const transactionsByDestination = (event) => - getTransactionsByDestination(event, osClient)(); -export const balanceByAddress = (event) => - getBalanceByAddress(event, osClient)(); - -// Currency - -export const currencySnapshot = (event) => - getCurrencySnapshot(event, osClient)(); -export const currencySnapshots = (event) => - getCurrencySnapshots(event, osClient)(); -export const currencySnapshotsByOwnerAddress = (event) => - getCurrencySnapshotsByOwnerAddress(event, osClient)(); -export const currencySnapshotRewards = (event) => - getCurrencySnapshotRewards(event, osClient)(); -export const currencySnapshotTransactions = (event) => - getCurrencySnapshotTransactions(event, osClient)(); - -export const currencyBlock = (event) => getCurrencyBlock(event, osClient)(); - -export const currencyTransaction = (event) => - getCurrencyTransaction(event, osClient)(); -export const currencyTransactions = (event) => - getCurrencyTransactions(event, osClient)(); -export const currencyTransactionsByAddress = (event) => - getCurrencyTransactionsByAddress(event, osClient)(); -export const currencyTransactionsBySource = (event) => - getCurrencyTransactionsBySource(event, osClient)(); -export const currencyTransactionsByDestination = (event) => - getCurrencyTransactionsByDestination(event, osClient)(); - -export const currencyFeeTransaction = (event) => - getCurrencyFeeTransaction(event, osClient)(); -export const currencySnapshotFeeTransactions = (event) => - getCurrencySnapshotFeeTransactions(event, osClient)(); -export const currencyFeeTransactionsByDestination = (event) => - getCurrencyFeeTransactionsByDestination(event, osClient)(); -export const currencyFeeTransactionsBySource = (event) => - getCurrencyFeeTransactionsBySource(event, osClient)(); -export const currencyFeeTransactionsByAddress = (event) => - getCurrencyFeeTransactionsByAddress(event, osClient)(); -export const currencyBalanceByAddress = (event) => - getCurrencyBalanceByAddress(event, osClient)(); - -export const metagraphs = (event) => getMetagraphs(event, osClient)(); diff --git a/src/opensearch/http.ts b/src/opensearch/http.ts deleted file mode 100644 index 34fc182..0000000 --- a/src/opensearch/http.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { PaginatedResult, Result } from "./opensearch"; - -const DEFAULT_HEADERS = { - "Content-Type": "application/json", - "Access-Control-Allow-Origin": "*", -}; - -export enum StatusCodes { - OK = 200, - CREATED = 201, - BAD_REQUEST = 400, - NOT_FOUND = 404, - SERVER_ERROR = 500, -} - -type ErrorCodes = - | StatusCodes.BAD_REQUEST - | StatusCodes.NOT_FOUND - | StatusCodes.SERVER_ERROR; -type SuccessCodes = Exclude; - -export class ApplicationError { - public readonly message: string; - public readonly errors: string[]; - public readonly statusCode: ErrorCodes; - - public constructor(message: string, errors: string[], status: ErrorCodes) { - this.message = message; - this.errors = errors; - this.statusCode = status; - } -} - -export class OpenSearchError extends ApplicationError { - public constructor(error: string) { - super("OpenSearch error", [error], StatusCodes.SERVER_ERROR); - } -} - -type SuccessResponse = { - statusCode: SuccessCodes; - headers: typeof DEFAULT_HEADERS; - body: string; -}; - -export const successResponse = - (statusCode: SuccessCodes) => - (result: Result | PaginatedResult): Response => { - return { - statusCode, - headers: DEFAULT_HEADERS, - body: JSON.stringify(result), - }; - }; - -type ErrorResponse = { - statusCode: ErrorCodes; - headers: typeof DEFAULT_HEADERS; - body: string; -}; - -export const errorResponse = (error: ApplicationError): Response => ({ - statusCode: error.statusCode, - headers: DEFAULT_HEADERS, - body: JSON.stringify({ message: error.message, errors: error.errors }), -}); - -export type Response = SuccessResponse | ErrorResponse; diff --git a/src/opensearch/model/balance.ts b/src/opensearch/model/balance.ts deleted file mode 100644 index c395728..0000000 --- a/src/opensearch/model/balance.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { SnapshotHash, SnapshotOrdinal, Timestamp } from "./properties"; - -export type OpenSearchBalance = { - address: string; - balance: number; -} & SnapshotOrdinal & - SnapshotHash & - Timestamp; - -export type Balance = Pick & { - ordinal: number; -}; diff --git a/src/opensearch/model/block.ts b/src/opensearch/model/block.ts deleted file mode 100644 index d996111..0000000 --- a/src/opensearch/model/block.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - Hash, - Height, - SnapshotHash, - SnapshotOrdinal, - Timestamp, -} from "./properties"; - -export type OpenSearchBlock = { - transactions: string[]; - parent: BlockReference[]; -} & Timestamp & - SnapshotOrdinal & - SnapshotHash & - Hash & - Height; - -export type BlockReference = Hash & Height; - -export type Block = OpenSearchBlock; diff --git a/src/opensearch/model/currency-data.ts b/src/opensearch/model/currency-data.ts deleted file mode 100644 index 7d87d35..0000000 --- a/src/opensearch/model/currency-data.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type CurrencyData = { - data: A; - identifier: string; -}; diff --git a/src/opensearch/model/currency-snapshot.ts b/src/opensearch/model/currency-snapshot.ts deleted file mode 100644 index 688d9d9..0000000 --- a/src/opensearch/model/currency-snapshot.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { OpenSearchSnapshot, WithoutRewards } from "./snapshot"; - -export type OpenSearchCurrencySnapshotV1 = OpenSearchSnapshot & { - fee?: number | null; - stakingAddress?: string | null; - ownerAddress?: string | null; - sizeInKB?: number | null; -}; - -export type OpenSearchCurrencySnapshot = OpenSearchCurrencySnapshotV1 & { - fee: number | null; - stakingAddress: string | null; - ownerAddress: string | null; - sizeInKB: number | null; -}; - -export const openSearchCurrencySnapshotToV2 = ( - data: - | OpenSearchCurrencySnapshotV1 - | OpenSearchCurrencySnapshot - | WithoutRewards - | WithoutRewards -): WithoutRewards => ({ - ...data, - fee: data.fee ?? null, - stakingAddress: data.stakingAddress ?? null, - ownerAddress: data.ownerAddress ?? null, - sizeInKB: data.sizeInKB ?? null, -}); - -export type CurrencySnapshot = { - fee: number | null; - stakingAddress: string | null; - ownerAddress: string | null; - sizeInKB: number | null; -}; diff --git a/src/opensearch/model/fee-transaction.ts b/src/opensearch/model/fee-transaction.ts deleted file mode 100644 index ed78256..0000000 --- a/src/opensearch/model/fee-transaction.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Hash, SnapshotHash, SnapshotOrdinal, Timestamp } from "./properties"; -import { TransactionReference } from "./transaction"; - -export type OpenSearchFeeTransaction = { - source: string; - destination: string; - amount: number; - parent: TransactionReference; - salt: number; -} & Timestamp & - SnapshotHash & - SnapshotOrdinal & - Hash; - -export type FeeTransaction = OpenSearchFeeTransaction; diff --git a/src/opensearch/model/index.ts b/src/opensearch/model/index.ts deleted file mode 100644 index cfd37dd..0000000 --- a/src/opensearch/model/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from "./block"; -export * from "./balance"; -export * from "./snapshot"; -export * from "./transaction"; -export * from "./properties"; -export * from "./currency-data"; -export * from "./fee-transaction"; diff --git a/src/opensearch/model/metagraph.ts b/src/opensearch/model/metagraph.ts deleted file mode 100644 index d1bbc07..0000000 --- a/src/opensearch/model/metagraph.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type Metagraph = { - id: string; - lastSnapshotHash: string; - ownerAddress: string | null; - stakingAddress: string | null; -}; diff --git a/src/opensearch/model/properties.ts b/src/opensearch/model/properties.ts deleted file mode 100644 index d19d275..0000000 --- a/src/opensearch/model/properties.ts +++ /dev/null @@ -1,23 +0,0 @@ -export type Timestamp = { - timestamp: string; -}; - -export type Hash = { - hash: string; -}; - -export type Ordinal = { - ordinal: number; -}; - -export type SnapshotOrdinal = { - snapshotOrdinal: number; -}; - -export type SnapshotHash = { - snapshotHash: number; -}; - -export type Height = { - height: number; -}; diff --git a/src/opensearch/model/snapshot.ts b/src/opensearch/model/snapshot.ts deleted file mode 100644 index 7fd6704..0000000 --- a/src/opensearch/model/snapshot.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Hash, Height, Ordinal, Timestamp } from "./properties"; -import { RewardTransaction } from "./transaction"; - -export type OpenSearchSnapshot = { - subHeight: number; - lastSnapshotHash: string; - blocks: string[]; - rewards: RewardTransaction[]; -} & Timestamp & - Ordinal & - Hash & - Height; - -export type WithoutRewards = Omit; diff --git a/src/opensearch/model/transaction.ts b/src/opensearch/model/transaction.ts deleted file mode 100644 index 324c307..0000000 --- a/src/opensearch/model/transaction.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { - Hash, - Ordinal, - SnapshotHash, - SnapshotOrdinal, - Timestamp, -} from "./properties"; - -export type OpenSearchTransaction = { - source: string; - destination: string; - amount: number; - fee: number; - parent: TransactionReference; - blockHash: string; - salt: number; - transactionOriginal: object; -} & Timestamp & - SnapshotHash & - SnapshotOrdinal & - Hash; - -export type TransactionReference = Hash & Ordinal; - -export type Transaction = Omit; - -export type RewardTransaction = { - destination: string; - amount: number; -}; diff --git a/src/opensearch/opensearch.ts b/src/opensearch/opensearch.ts deleted file mode 100644 index ee01729..0000000 --- a/src/opensearch/opensearch.ts +++ /dev/null @@ -1,1037 +0,0 @@ -import { Client } from "@opensearch-project/opensearch"; -import { pipe } from "fp-ts/lib/function"; - -import { ApplicationError, OpenSearchError, StatusCodes } from "./http"; -import { - Balance, - Block, - CurrencyData, - FeeTransaction, - OpenSearchBalance, - OpenSearchBlock, - OpenSearchFeeTransaction, - OpenSearchSnapshot, - OpenSearchTransaction, - RewardTransaction, - Transaction, - WithoutRewards, -} from "./model"; -import { - findAll, - findOne, - getAll, - getByFieldQuery, - getDocumentQuery, - getLatestQuery, - getMultiQuery, - getSearchSince, - maxSizeLimit, - SearchDirection, - SortOption, - SortOptions, - SortOptionSince, - SortOrder, -} from "./query"; - -import { - chain, - left, - map, - of, - orElse, - right, - TaskEither, - tryCatch, -} from "fp-ts/lib/TaskEither"; -import { fromNextString, Pagination, toNextString } from "./os-request-params"; -import { Get, Paths } from "./ts-extensions"; -import { Metagraph } from "./model/metagraph"; -import { - CurrencySnapshot, - OpenSearchCurrencySnapshot, - openSearchCurrencySnapshotToV2, - OpenSearchCurrencySnapshotV1, -} from "./model/currency-snapshot"; - -enum OSIndex { - Snapshots = "snapshots", - Blocks = "blocks", - Transactions = "transactions", - Balances = "balances-*", - CurrencySnapshots = "currency-snapshots-*", - CurrencyBlocks = "currency-blocks", - CurrencyTransactions = "currency-transactions", - CurrencyFeeTransactions = "currency-fee-transactions", - CurrencyBalances = "currency-balances-*", -} - -export type Result = { - data: T; - meta?: {}; -}; - -export type PaginatedResult = { - data: T[]; - meta: { - next: string | null; - }; -}; - -export const getClient = (): Client => { - return new Client({ node: process.env.OPENSEARCH_NODE }); -}; - -const getResultWithNextString = - (sortOptions: SortOptions) => - ( - getData: (sortOptions: SortOptions) => TaskEither - ): TaskEither> => { - // NOTE: We exceed the limit by 1 additional element to determine if next page is empty - const plusOneSize = sortOptions.size - ? sortOptions.size + 1 - : maxSizeLimit + 1; - - return pipe( - getData({ - ...sortOptions, - size: plusOneSize, - }), - chain((plusOneData) => { - if (plusOneData.length === 0) - return left( - new ApplicationError("Not found", [], StatusCodes.NOT_FOUND) - ); - - if (plusOneData.length < plusOneSize) { - return right({ data: plusOneData, meta: { next: null } }); - } - - const data = plusOneData.slice(0, -1); - const element = data[data.length - 1]; - - const getValue = (option: SortOption | SortOptionSince) => - option.sortField.split(".").reduce((acc, n) => acc[n], element); - - return right>({ - data, - meta: { - next: toNextString({ - size: sortOptions.size, - options: sortOptions.options.map((option) => ({ - ...option, - searchSince: getValue(option), - })), - }), - }, - }); - }) - ); - }; - -const getAggregationResultWithNextString = - (sortOptions: SortOptions) => - ( - getDataWithAfterKey: ( - sortOptions: SortOptions - ) => TaskEither - ): TaskEither> => { - // NOTE: We exceed the limit by 1 additional element to determine if next page is empty - const plusOneSize = sortOptions.size - ? sortOptions.size + 1 - : maxSizeLimit + 1; - - return pipe( - getDataWithAfterKey({ - ...sortOptions, - size: plusOneSize, - }), - chain(([plusOneData, after_key]) => { - if (plusOneData.length === 0) - return left( - new ApplicationError("Not found", [], StatusCodes.NOT_FOUND) - ); - - if (plusOneData.length < plusOneSize) { - return right({ data: plusOneData, meta: { next: null } }); - } - - const data = plusOneData.slice(0, -1); - - return right>({ - data, - meta: { - next: toNextString({ - size: sortOptions.size, - options: sortOptions.options - .filter((option) => after_key[option.sortField] !== undefined) - .map((option) => ({ - ...option, - searchSince: after_key[option.sortField], - })), - }), - }, - }); - }) - ); - }; - -const getSortOptions = (pagination: Pagination) => ({ - withDefault: (def: SortOptions): SortOptions => { - if ("next" in pagination) { - const sortOptionsFromNext = fromNextString(pagination.next); - return { - ...sortOptionsFromNext, - size: pagination.size ?? sortOptionsFromNext.size, - }; - } else { - return def; - } - }, -}); - -export function getFromPath(obj: O, path: K): Get; -export function getFromPath( - obj: Record, - path: string -): unknown { - const [firstKey, ...restKeys] = path.split("."); - - if (firstKey === undefined || firstKey === "") { - return obj; - } - - const value = obj[firstKey]; - - if (value === undefined || value === null) { - return undefined; - } - - if (restKeys.length === 0) { - return value; - } - - if (typeof value !== "object") { - return undefined; - } - - return getFromPath(value as Record, restKeys.join(".")); -} - -export const findCollectionByTerm = - (os: Client) => - ( - term: OSC[keyof OSC], - fields: (keyof OSC)[], - sortOptions: SortOptions, - index: OSIndex, - currencyIdentifier: string | null - ): TaskEither> => { - const stringifyTerm = (term: OSC[keyof OSC]) => { - return term !== null && term !== undefined && typeof term !== "object" - ? (String(term) as OSC[keyof OSC]) - : ("" as OSC[keyof OSC]); - }; - - return getResultWithNextString(sortOptions)((sort) => { - const query = - fields.length === 1 - ? getByFieldQuery( - index, - fields[0], - stringifyTerm(term), - sort, - currencyIdentifier - ) - : getMultiQuery( - index, - fields, - stringifyTerm(term), - sort, - currencyIdentifier - ); - return findAll(os.search(query), currencyIdentifier); - }); - }; - -export const findSnapshotRewards = - (os: Client) => - ( - term: string, - currencyIdentifier: string | null - ): TaskEither> => - pipe( - findOne( - findSnapshotByTerm(os)(term, currencyIdentifier), - currencyIdentifier - ), - map((r) => ({ ...r, data: r.data.rewards })) - ); - -export const listMetagraphs = - (os: Client) => - ( - pagination: Pagination - ): TaskEither> => { - const { size, ...options } = pagination; - const sortOptions = getSortOptions< - CurrencyData, - Metagraph - >(pagination).withDefault({ - size, - options: [ - { - ...options, - searchDirection: SearchDirection.Before, - sortField: "identifier" as Paths< - CurrencyData - >, - }, - ], - }); - - type LastMetagraphSnapshot = CurrencyData< - Pick< - OpenSearchCurrencySnapshotV1, - "hash" | "ownerAddress" | "stakingAddress" - > - >; - - type MetagraphBucket = { - latestSnapshot: { - hits: { - hits: [{ _source: LastMetagraphSnapshot }]; - }; - }; - }; - - type MetagraphsAggregation = { - after_key: { - identifier: - | CurrencyData["identifier"] - | null; - }; - buckets: MetagraphBucket[]; - }; - - return getAggregationResultWithNextString< - Metagraph, - LastMetagraphSnapshot, - MetagraphsAggregation["after_key"] - >(sortOptions)((sort) => { - const after = sortOptions.options - .filter(isSearchSinceOption) - .map>( - (opt) => opt as SortOptionSince - ) - .reduce( - (acc, curr) => ({ - ...acc, - [curr.sortField]: curr.searchSince, - }), - {} - ); - - const query = { - size: 0, - aggs: { - metagraphs: { - composite: { - sources: [ - { - identifier: { - terms: { - field: "identifier" as Paths< - CurrencyData - >, - }, - }, - }, - ], - size: sort.size, - ...(Object.keys(after).length > 0 ? { after } : {}), - }, - aggs: { - latestSnapshot: { - top_hits: { - sort: [ - { - "data.ordinal": { - order: SortOrder.Desc, - }, - } as { - [K in Paths< - CurrencyData - >]: { - order: SortOrder; - }; - } & { [key: string]: never }, - ], - _source: { - includes: [ - "identifier", - "data.hash", - "data.ownerAddress", - "data.stakingAddress", - ] as Paths>[], - }, - size: 1, - }, - }, - }, - }, - }, - }; - - return pipe( - tryCatch( - () => - os - .search({ - index: OSIndex.CurrencySnapshots, - body: query, - }) - .then((r) => r.body.aggregations.metagraphs), - (err) => - new ApplicationError( - "OpenSearch error", - [err as string], - StatusCodes.SERVER_ERROR - ) - ), - map(({ buckets, after_key }) => { - const data = buckets - .map(({ latestSnapshot }) => latestSnapshot.hits.hits) - .map(([hit]) => hit._source) - .map( - ({ - identifier, - data: { hash, stakingAddress, ownerAddress }, - }) => ({ - id: identifier, - lastSnapshotHash: hash, - stakingAddress: stakingAddress ?? null, - ownerAddress: ownerAddress ?? null, - }) - ); - - return [data, after_key]; - }) - ); - }); - }; - -export const listSnapshots = - (os: Client) => - ( - pagination: Pagination>, - currencyIdentifier: string | null - ): TaskEither> => { - const { size, ...options } = pagination; - const sortOptions = getSortOptions, OSS>( - pagination - ).withDefault({ - size, - options: [ - { - ...options, - searchDirection: options["searchDirection"] || SearchDirection.Before, - sortField: "ordinal", - }, - ], - }); - - return getResultWithNextString(sortOptions)((sort) => - findAll( - os.search( - getAll( - currencyIdentifier ? OSIndex.CurrencySnapshots : OSIndex.Snapshots, - sort, - currencyIdentifier - ) - ), - currencyIdentifier - ) - ); - }; - -export const findCurrencySnapshotsByOwnerAddress = - (os: Client) => - ( - ownerAddress: string, - pagination: Pagination - ): TaskEither< - OpenSearchError, - PaginatedResult - > => { - const { size, ...options } = pagination; - - const sortOptions = getSortOptions(pagination).withDefault({ - size, - options: [ - { - ...options, - searchDirection: SearchDirection.Before, - sortField: "data.ordinal", - }, - { - ...options, - searchDirection: SearchDirection.Before, - sortField: "identifier", - }, - ], - }); - - return pipe( - getResultWithNextString< - CurrencyData> - >(sortOptions)((sort) => { - const query = { - index: OSIndex.CurrencySnapshots, - body: { - ...getSearchSince>(sort), - sort: sort.options.map((s) => ({ - [s.sortField]: - s.searchDirection === SearchDirection.After - ? SortOrder.Asc - : SortOrder.Desc, - })), - size: sort.size || maxSizeLimit, - _source: { - excludes: ["data.rewards"] as Paths< - CurrencyData - >[], - }, - query: { - bool: { - must: { - term: { ["data.ownerAddress"]: ownerAddress } as Record< - Paths>, - string - >, - }, - }, - }, - }, - }; - - return pipe( - tryCatch< - OpenSearchError, - CurrencyData>[] - >( - () => - os - .search(query) - .then((r) => r.body.hits.hits.map((h) => h._source)), - (err) => new OpenSearchError(err as string) - ) - ); - }), - map(({ data, ...rest }) => ({ - ...rest, - data: data.map(({ identifier, data }) => ({ - metagraphId: identifier, - ...openSearchCurrencySnapshotToV2(data), - })), - })) - ); - }; - -const exportSortOptions = ( - pagination: Pagination, - sortFields: Paths[], - findByHashFallback: () => TaskEither> -): TaskEither> => { - const { size, ...options } = pagination; - - return pagination["searchSince"] - ? (() => - pipe( - findByHashFallback(), - map((r: Result) => ({ - size, - options: sortFields.map((sortField) => ({ - sortField, - searchDirection: - options["searchDirection"] || SearchDirection.Before, - searchSince: getFromPath(r.data, sortField), - })), - })) - ))() - : of( - getSortOptions(pagination).withDefault({ - size, - options: sortFields.map((sortField) => ({ - ...options, - sortField, - searchDirection: - options["searchDirection"] || SearchDirection.Before, - })), - }) - ); -}; - -export const listTransactions = - (os: Client) => - ( - pagination: Pagination, - currencyIdentifier: string | null - ): TaskEither> => - pipe( - exportSortOptions( - pagination, - ["snapshotOrdinal", "source", "parent.ordinal"], - () => - findTransactionByHash(os)( - pagination["searchSince"], - currencyIdentifier - ) - ), - chain< - ApplicationError, - SortOptions, - PaginatedResult - >((sortOptions) => - getResultWithNextString(sortOptions)((sort) => - findAll( - os.search( - getAll( - currencyIdentifier - ? OSIndex.CurrencyTransactions - : OSIndex.Transactions, - sort, - currencyIdentifier - ) - ), - currencyIdentifier - ) - ) - ) - ); - -export const findSnapshot = - (os: Client) => - ( - term: string, - currencyIdentifier: string | null - ): TaskEither>> => { - return pipe( - findOne( - findSnapshotByTerm(os)(term, currencyIdentifier), - currencyIdentifier - ), - map((s) => { - const { rewards, ...rest } = s.data; - return { ...s, data: rest }; - }) - ); - }; - -const findSnapshotByTerm = - (os: Client) => - (term: string, currencyIdentifier: string | null) => { - const index = currencyIdentifier - ? OSIndex.CurrencySnapshots - : OSIndex.Snapshots; - - if (isLatest(term)) { - return os.search(getLatestQuery(index, currencyIdentifier)); - } - if (isOrdinal(term)) { - return os.search( - getByFieldQuery( - index, - "ordinal", - term, - { - options: [{ sortField: "ordinal" }], - size: 1, - }, - currencyIdentifier - ) - ); - } - return os.get(getDocumentQuery(index, term, currencyIdentifier)); - }; - -export const findTransactionsBySnapshot = - (os: Client) => - ( - term: string, - pagination: Pagination, - currencyIdentifier: string | null - ): TaskEither> => { - return pipe( - findSnapshot(os)(term, currencyIdentifier), - chain((s) => - pipe( - exportSortOptions( - pagination, - ["snapshotOrdinal", "source", "parent.ordinal"], - () => - findTransactionByHash(os)( - pagination["searchSince"], - currencyIdentifier - ) - ), - chain((sortOptions) => - findCollectionByTerm(os)( - s.data.ordinal, - ["snapshotOrdinal"], - sortOptions, - currencyIdentifier - ? OSIndex.CurrencyTransactions - : OSIndex.Transactions, - currencyIdentifier - ) - ), - orElse((e: ApplicationError) => - e.statusCode === StatusCodes.NOT_FOUND - ? right({ data: [], meta: { next: null } }) - : left(e) - ) - ) - ) - ); - }; - -export const findCurrencyFeeTransactionsBySnapshot = - (os: Client) => - ( - term: string, - pagination: Pagination, - currencyIdentifier: string - ): TaskEither< - ApplicationError, - PaginatedResult - > => { - return pipe( - findSnapshot(os)(term, currencyIdentifier), - chain((s) => { - return pipe( - exportSortOptions( - pagination, - ["snapshotOrdinal", "source", "parent.ordinal"], - () => - findCurrencyFeeTransactionByHash(os)( - pagination["searchSince"], - currencyIdentifier - ) - ), - chain((sortOptions) => { - return findCollectionByTerm(os)( - s.data.ordinal, - ["snapshotOrdinal"], - sortOptions, - OSIndex.CurrencyFeeTransactions, - currencyIdentifier - ); - }), - orElse((e: ApplicationError) => - e.statusCode === StatusCodes.NOT_FOUND - ? right({ data: [], meta: { next: null } }) - : left(e) - ) - ); - }) - ); - }; - -export const findTransactionsByAddress = - (os: Client) => - ( - address: string, - pagination: Pagination, - currencyIdentifier: string | null - ) => - pipe( - exportSortOptions( - pagination, - ["snapshotOrdinal", "source", "parent.ordinal"], - () => - findTransactionByHash(os)( - pagination["searchSince"], - currencyIdentifier - ) - ), - chain((sortOptions) => - findCollectionByTerm(os)( - address, - ["source", "destination"], - sortOptions, - currencyIdentifier - ? OSIndex.CurrencyTransactions - : OSIndex.Transactions, - currencyIdentifier - ) - ) - ); - -export const findCurrencyFeeTransactionsByAddress = - (os: Client) => - ( - address: string, - pagination: Pagination, - currencyIdentifier: string - ) => - pipe( - exportSortOptions( - pagination, - ["snapshotOrdinal", "source", "parent.ordinal"], - () => - findCurrencyFeeTransactionByHash(os)( - pagination["searchSince"], - currencyIdentifier - ) - ), - chain((sortOptions) => - findCollectionByTerm(os)( - address, - ["source", "destination"], - sortOptions, - OSIndex.CurrencyFeeTransactions, - currencyIdentifier - ) - ) - ); - -export const findTransactionsBySource = - (os: Client) => - ( - term: string, - pagination: Pagination, - currencyIdentifier: string | null - ): TaskEither> => - pipe( - exportSortOptions( - pagination, - ["snapshotOrdinal", "source", "parent.ordinal"], - () => - findTransactionByHash(os)( - pagination["searchSince"], - currencyIdentifier - ) - ), - chain((sortOptions) => - findCollectionByTerm(os)( - term, - ["source"], - sortOptions, - currencyIdentifier - ? OSIndex.CurrencyTransactions - : OSIndex.Transactions, - currencyIdentifier - ) - ) - ); - -export const findCurrencyFeeTransactionsBySource = - (os: Client) => - ( - address: string, - pagination: Pagination, - currencyIdentifier: string - ) => - pipe( - exportSortOptions( - pagination, - ["snapshotOrdinal", "source", "parent.ordinal"], - () => - findCurrencyFeeTransactionByHash(os)( - pagination["searchSince"], - currencyIdentifier - ) - ), - chain((sortOptions) => - findCollectionByTerm(os)( - address, - ["source"], - sortOptions, - OSIndex.CurrencyFeeTransactions, - currencyIdentifier - ) - ) - ); - -export const findTransactionsByDestination = - (os: Client) => - ( - term: string, - pagination: Pagination, - currencyIdentifier: string | null - ): TaskEither> => - pipe( - exportSortOptions( - pagination, - ["snapshotOrdinal", "source", "parent.ordinal"], - () => - findTransactionByHash(os)( - pagination["searchSince"], - currencyIdentifier - ) - ), - chain((sortOptions) => - findCollectionByTerm(os)( - term, - ["destination"], - sortOptions, - currencyIdentifier - ? OSIndex.CurrencyTransactions - : OSIndex.Transactions, - currencyIdentifier - ) - ) - ); - -export const findCurrencyFeeTransactionsByDestination = - (os: Client) => - ( - address: string, - pagination: Pagination, - currencyIdentifier: string | null - ) => - pipe( - exportSortOptions( - pagination, - ["snapshotOrdinal", "source", "parent.ordinal"], - () => - findTransactionByHash(os)( - pagination["searchSince"], - currencyIdentifier - ) - ), - chain((sortOptions) => - findCollectionByTerm(os)( - address, - ["destination"], - sortOptions, - OSIndex.CurrencyFeeTransactions, - currencyIdentifier - ) - ) - ); - -export const findTransactionByHash = - (os: Client) => - ( - hash: string, - currencyIdentifier: string | null - ): TaskEither> => - pipe( - findOne( - os.get( - getDocumentQuery( - currencyIdentifier - ? OSIndex.CurrencyTransactions - : OSIndex.Transactions, - hash, - currencyIdentifier - ) - ), - currencyIdentifier - ), - map((r) => { - const { salt, ...tx } = r.data; - return { - ...r, - data: tx, - }; - }) - ); - -export const findCurrencyFeeTransactionByHash = - (os: Client) => - ( - hash: string, - currencyIdentifier: string - ): TaskEither> => - pipe( - findOne( - os.get( - getDocumentQuery( - OSIndex.CurrencyFeeTransactions, - hash, - currencyIdentifier - ) - ), - currencyIdentifier - ) - ); - -export const findBalanceByAddress = - (os: Client) => - ( - address: string, - currencyIdentifier: string | null, - ordinal?: number - ): TaskEither> => { - const sort = { - options: [ - { - sortField: "snapshotOrdinal", - // To achieve (0, ordinal> we need to make (0, ordinal + 1) - ...(ordinal !== undefined ? { searchSince: ordinal + 1 } : {}), - searchDirection: SearchDirection.Before, - }, - ], - size: 1, - }; - - return pipe( - findOne( - os.search( - getByFieldQuery( - currencyIdentifier ? OSIndex.CurrencyBalances : OSIndex.Balances, - "address", - address, - sort, - currencyIdentifier - ) - ), - currencyIdentifier - ), - map(({ data: { snapshotOrdinal, balance, address }, meta }) => ({ - data: { ordinal: snapshotOrdinal, balance, address }, - meta, - })), - orElse((e: ApplicationError) => { - return e.statusCode === StatusCodes.NOT_FOUND - ? pipe( - findSnapshot(os)("latest", currencyIdentifier), - map((s) => ({ - data: { ordinal: s.data.ordinal, balance: 0, address }, - meta: {}, - })) - ) - : left(e); - }) - ); - }; - -export const findBlockByHash = - (os: Client) => - ( - hash: string, - currencyIdentifier: string | null - ): TaskEither> => { - return findOne( - os.get( - getDocumentQuery( - currencyIdentifier ? OSIndex.CurrencyBlocks : OSIndex.Blocks, - hash, - currencyIdentifier - ) - ), - currencyIdentifier - ); - }; - -const isOrdinal = (term: string | number): term is number => - /^\d+$/.test(term.toString()); - -const isLatest = ( - termValue: string | number | "latest" -): termValue is "latest" => termValue === "latest"; - -const isSearchSinceOption = (option: any): option is SortOptionSince => - "searchSince" in option && option.searchSince !== undefined; diff --git a/src/opensearch/os-request-params.ts b/src/opensearch/os-request-params.ts deleted file mode 100644 index 32aa9c5..0000000 --- a/src/opensearch/os-request-params.ts +++ /dev/null @@ -1,217 +0,0 @@ -import { APIGatewayEvent } from "aws-lambda"; -import { ApplicationError, StatusCodes } from "./http"; -import { Lens, Optional } from "monocle-ts"; -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/Option"; -import * as R from "fp-ts/Record"; -import * as TE from "fp-ts/TaskEither"; -import { TaskEither } from "fp-ts/TaskEither"; -import { maxSizeLimit, SearchDirection, SortOptions } from "./query"; - -export type Pagination = - | { - size?: number; - searchDirection: SearchDirection; - searchSince: string; - } - | { size?: number } - | { size?: number; next: string }; - -type PaginationQueryParams = { - search_after?: string; - search_before?: string; - limit?: string; - next?: string; -}; - -const pathParams = Lens.fromNullableProp()( - "pathParameters", - {} -); -type PathParams = NonNullable< - APIGatewayEvent["pathParameters"] & { - hash?: string; - term?: string; - address?: string; - } ->; - -const pathParamsIsNotNull = (event: APIGatewayEvent) => - TE.fromPredicate( - () => Object.keys(pathParams.get(event)).length > 0, - () => - new ApplicationError( - "Error parsing request path params", - ["Path params should not be empty"], - StatusCodes.BAD_REQUEST - ) - )(event); - -export const extractPagination = ( - event: APIGatewayEvent -): TaskEither> => { - const params = event.queryStringParameters as PaginationQueryParams; - const searchBefore = params?.search_before; - const searchAfter = params?.search_after; - const limit = Number(params?.limit); - const next = params?.next; - - if (searchBefore && searchAfter) { - return TE.left( - new ApplicationError( - "search_after & search_before should be mutually exclusive", - [], - StatusCodes.BAD_REQUEST - ) - ); - } - - if (params?.limit !== undefined) { - if (isNaN(limit)) { - return TE.left( - new ApplicationError( - "limit must be a number", - [], - StatusCodes.BAD_REQUEST - ) - ); - } - - if (limit < 1) { - return TE.left( - new ApplicationError( - "limit must be a positive number", - [], - StatusCodes.BAD_REQUEST - ) - ); - } - - if (limit > maxSizeLimit) { - return TE.left( - new ApplicationError( - `limit must be lower or equal ${maxSizeLimit}`, - [], - StatusCodes.BAD_REQUEST - ) - ); - } - } - - if (next && searchAfter && searchBefore) { - return TE.left( - new ApplicationError( - "next and search_after/search_before should be mutually exclusive", - [], - StatusCodes.BAD_REQUEST - ) - ); - } - - if (next) { - return TE.right({ - next, - size: params.limit !== undefined ? limit : undefined, - }); - } - - return TE.right({ - searchSince: searchAfter || searchBefore, - searchDirection: - (searchAfter && SearchDirection.After) || - (searchBefore && SearchDirection.Before) || - undefined, - size: limit, - }); -}; - -export const toNextString = (options: SortOptions): string => { - const buffer = Buffer.from(JSON.stringify(options)); - return buffer.toString("base64"); -}; - -export const fromNextString = (next: string): SortOptions => { - const buffer = Buffer.from(next, "base64"); - return JSON.parse(buffer.toString("ascii")); -}; - -const pathParamExists = - (pathParam: keyof Partial) => (event: APIGatewayEvent) => - pipe( - TE.of(event), - TE.chainFirst(pathParamsIsNotNull), - TE.chainFirst(() => - pipe( - pathParams - .composeOptional(Optional.fromPath()([pathParam])) - .getOption(event), - TE.fromOption( - () => - new ApplicationError( - "Error parsing request path params", - [`${pathParam} param should not be empty`], - StatusCodes.BAD_REQUEST - ) - ) - ) - ) - ); - -export class RequestParamMissingError extends ApplicationError { - constructor(param: string) { - super( - "Error parsing request path params", - [`${param} param should not be empty`], - StatusCodes.BAD_REQUEST - ); - } -} - -export const getPathParam: ( - param: K -) => (event: APIGatewayEvent) => TE.TaskEither = - (param: K) => - (event: APIGatewayEvent) => - pipe( - O.fromNullable(event.pathParameters), - O.chain((params) => - pipe( - params, - R.lookup(param), - O.chain( - O.fromPredicate( - (value): value is string => typeof value === "string" - ) - ) - ) - ), - TE.fromOption(() => new RequestParamMissingError(param)) - ); - -/** @deprecated use extractCurrencyIdentifierParam */ -export const validateCurrencyIdentifierParam = (event: APIGatewayEvent) => - pipe( - TE.of(event), - TE.chain(pathParamExists("identifier")) - ); - -/** @deprecated use extractTermParam */ -export const validateTermParam = (event: APIGatewayEvent) => - pipe( - TE.of(event), - TE.chain(pathParamExists("term")) - ); - -/** @deprecated use extractHashParam */ -export const validateHashParam = (event: APIGatewayEvent) => - pipe( - TE.of(event), - TE.chain(pathParamExists("hash")) - ); - -/** @deprecated use extractAddressParam */ -export const validateAddressParam = (event: APIGatewayEvent) => - pipe( - TE.of(event), - TE.chain(pathParamExists("address")) - ); diff --git a/src/opensearch/query.ts b/src/opensearch/query.ts deleted file mode 100644 index 79730b1..0000000 --- a/src/opensearch/query.ts +++ /dev/null @@ -1,237 +0,0 @@ -import { ApiResponse } from "@opensearch-project/opensearch"; -import { ApplicationError, StatusCodes } from "./http"; -import { TransportRequestPromise } from "@opensearch-project/opensearch/lib/Transport"; -import { pipe } from "fp-ts/lib/function"; -import { filterOrElse, map, TaskEither, tryCatch } from "fp-ts/lib/TaskEither"; -import { CurrencyData, Ordinal } from "./model"; -import { SearchRequest } from "@opensearch-project/opensearch/api/types"; -import { Result } from "./opensearch"; - -export enum SortOrder { - Desc = "desc", - Asc = "asc", -} - -export enum SearchDirection { - After = "search_after", - Before = "search_before", -} - -export type SortOption = { - // TODO: Use Paths - sortField: string; // path or nested path - searchDirection?: SearchDirection; -}; - -export type SortOptionSince = { - // TODO: Use Paths - sortField: string; // path or nested path - searchSince: string | number; - searchDirection?: SearchDirection; -}; - -export type SortOptions = { - options: SortOption[] | SortOptionSince[]; - size?: number; -}; - -export const maxSizeLimit = 10000; - -export const getDocumentQuery = ( - index: string, - id: string, - currencyIdentifier: string | null -) => ({ - index, - id: currencyIdentifier ? currencyIdentifier + id : id, -}); - -export const getLatestQuery = ( - index: string, - currencyIdentifier: string | null -): any => ({ - index, - body: { - size: 1, - sort: { - [currencyIdentifier ? "data.ordinal" : "ordinal"]: { - order: SortOrder.Desc, - }, - }, - ...(currencyIdentifier - ? { - query: { - bool: { must: [{ match: { identifier: currencyIdentifier } }] }, - }, - } - : {}), - }, -}); - -const isSearchSince = (options: any): options is SortOptionSince[] => - typeof options[0]?.searchSince === "string" || - typeof options[0]?.searchSince === "number"; - -export const getSearchSince = (sort: SortOptions) => { - return isSearchSince(sort.options) - ? { search_after: sort.options.map((a) => a.searchSince) } - : {}; -}; - -export const getSort = ( - sort: SortOptions, - currencyIdentifier: string | null -) => { - return sort.options.length === 0 - ? [] - : sort.options.map((s) => ({ - [currencyIdentifier ? `data.${s.sortField}` : s.sortField]: - s.searchDirection === SearchDirection.After - ? SortOrder.Asc - : SortOrder.Desc, - })); -}; - -export const getMultiQuery = ( - index: string, - fields: K[], - value: T[keyof T], - sort: SortOptions, - currencyIdentifier: string | null -): SearchRequest => ({ - index, - body: { - ...getSearchSince(sort), - size: sort.size || maxSizeLimit, - sort: getSort(sort, currencyIdentifier), - query: { - bool: { - should: fields.map((field) => ({ - term: { - [currencyIdentifier ? `data.${String(field)}` : field]: value, - }, - })), - ...(currencyIdentifier - ? { must: [{ match: { identifier: currencyIdentifier } }] } - : {}), - minimum_should_match: 1, - boost: 1.0, - }, - }, - }, -}); - -export function getByFieldQuery( - index: string, - field: K, - value: T[K], - sort: SortOptions, - currencyIdentifier: string | null -): any { - return { - index, - body: { - ...getSearchSince(sort), - size: sort.size || maxSizeLimit, - sort: getSort(sort, currencyIdentifier), - query: { - bool: { - must: [ - { - term: { - [currencyIdentifier ? `data.${String(field)}` : field]: value, - }, - }, - ...(currencyIdentifier - ? [{ term: { identifier: currencyIdentifier } }] - : []), - ], - }, - }, - }, - }; -} - -export function getAll( - index: string, - sort: SortOptions, - currencyIdentifier: string | null -): any { - return { - index, - body: { - ...getSearchSince(sort), - size: sort.size || maxSizeLimit, - sort: getSort(sort, currencyIdentifier), - query: currencyIdentifier - ? { - bool: { - must: [{ term: { identifier: currencyIdentifier } }], - }, - } - : { - match_all: {}, - }, - }, - }; -} - -export const findOne = ( - search: TransportRequestPromise, - currencyIdentifier: string | null -): TaskEither> => - pipe( - tryCatch( - () => search.then((r) => (r.body.found ? [r.body] : r.body.hits.hits)), - (err: any) => { - if (err.meta?.body?.found === false) { - return new ApplicationError("Not Found", [""], StatusCodes.NOT_FOUND); - } else { - return new ApplicationError( - "OpenSearch error", - [err as string], - StatusCodes.SERVER_ERROR - ); - } - } - ), - filterOrElse( - (hits) => hits.length > 0, - () => new ApplicationError("Not Found", [], StatusCodes.NOT_FOUND) - ), - map((hits) => ({ - data: hits[0]._source as T, - })), - map((h) => { - return isWrapped(h.data) && currencyIdentifier - ? { ...h, data: h.data.data } - : h; - }) - ); - -export const findAll = ( - search: TransportRequestPromise, - currencyIdentifier: string | null -): TaskEither => - pipe( - tryCatch( - () => - search.then((r) => { - return r.body.hits.hits; - }), - (err) => - new ApplicationError( - "OpenSearch error", - [err as string], - StatusCodes.SERVER_ERROR - ) - ), - map((hits) => { - return hits.map((hit) => hit._source as T); - }), - map((hits) => - hits.map((h) => (isWrapped(h) && currencyIdentifier ? h.data : h)) - ) - ); - -const isWrapped = (a: any): a is CurrencyData => a.identifier && a.data; diff --git a/src/opensearch/service.ts b/src/opensearch/service.ts deleted file mode 100644 index 572a6fb..0000000 --- a/src/opensearch/service.ts +++ /dev/null @@ -1,682 +0,0 @@ -import { Client } from "@opensearch-project/opensearch"; -import { APIGatewayEvent } from "aws-lambda"; -import * as T from "fp-ts/lib/Task"; -import * as TE from "fp-ts/lib/TaskEither"; -import { Task } from "fp-ts/lib/Task"; -import { chain, fold, map, of } from "fp-ts/lib/TaskEither"; -import { - ApplicationError, - errorResponse, - Response, - StatusCodes, - successResponse, -} from "./http"; - -import { - extractPagination, - validateTermParam, - validateAddressParam, - validateHashParam, - validateCurrencyIdentifierParam, - getPathParam, -} from "./os-request-params"; -import { - findBalanceByAddress, - findBlockByHash, - findCurrencyFeeTransactionByHash, - findCurrencyFeeTransactionsByAddress, - findCurrencyFeeTransactionsByDestination, - findCurrencyFeeTransactionsBySnapshot, - findCurrencyFeeTransactionsBySource, - findCurrencySnapshotsByOwnerAddress, - findSnapshot, - findSnapshotRewards, - findTransactionByHash, - findTransactionsByAddress, - findTransactionsByDestination, - findTransactionsBySnapshot, - findTransactionsBySource, - listMetagraphs, - listSnapshots, - listTransactions, -} from "./opensearch"; -import { pipe } from "fp-ts/lib/function"; -import { OpenSearchCurrencySnapshotV1 } from "./model/currency-snapshot"; - -export const getCurrencySnapshots = (event: APIGatewayEvent, os: Client) => - pipe( - of(event), - chain(validateCurrencyIdentifierParam), - chain(() => - pipe( - extractPagination(event), - map((pagination) => { - return { pagination, ...extractCurrencyIdentifier(event) }; - }) - ) - ), - chain(({ pagination, currencyIdentifier }) => - listSnapshots(os)( - pagination, - currencyIdentifier - ) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getCurrencySnapshotsByOwnerAddress = ( - event: APIGatewayEvent, - os: Client -) => - pipe( - TE.Do, - TE.bind("pagination", () => extractPagination(event)), - TE.bind("ownerAddress", () => extractAddressParam(event)), - TE.chain(({ pagination, ownerAddress }) => - findCurrencySnapshotsByOwnerAddress(os)(ownerAddress, pagination) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getGlobalSnapshots = (event: APIGatewayEvent, os: Client) => - pipe( - of(event), - chain(() => - pipe( - extractPagination(event), - map((pagination) => { - return { pagination }; - }) - ) - ), - chain(({ pagination }) => listSnapshots(os)(pagination, null)), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getGlobalSnapshot = ( - event: APIGatewayEvent, - os: Client -): Task => - pipe( - of(event), - chain(validateTermParam), - map(extractTerm), - chain(({ termName, termValue }) => findSnapshot(os)(termValue, null)), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getCurrencySnapshot = ( - event: APIGatewayEvent, - os: Client -): Task => - pipe( - of(event), - chain(validateCurrencyIdentifierParam), - chain(validateTermParam), - map((event) => ({ - ...extractTerm(event), - ...extractCurrencyIdentifier(event), - })), - chain(({ termName, termValue, currencyIdentifier }) => - findSnapshot(os)( - termValue, - currencyIdentifier - ) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getGlobalSnapshotRewards = ( - event: APIGatewayEvent, - os: Client -): Task => - pipe( - of(event), - chain(validateTermParam), - map(extractTerm), - chain(({ termName, termValue }) => - findSnapshotRewards(os)(termValue, null) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getCurrencySnapshotRewards = ( - event: APIGatewayEvent, - os: Client -): Task => - pipe( - of(event), - chain(validateCurrencyIdentifierParam), - chain(validateTermParam), - map((event) => ({ - ...extractTerm(event), - ...extractCurrencyIdentifier(event), - })), - chain(({ termName, termValue, currencyIdentifier }) => - findSnapshotRewards(os)(termValue, currencyIdentifier) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getGlobalSnapshotTransactions = ( - event: APIGatewayEvent, - os: Client -): Task => - pipe( - of(event), - chain(validateTermParam), - map(extractTerm), - chain(({ termName, termValue }) => - pipe( - extractPagination(event), - map((pagination) => ({ termName, termValue, pagination })) - ) - ), - chain(({ termName, termValue, pagination }) => - findTransactionsBySnapshot(os)(termValue, pagination, null) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getCurrencySnapshotTransactions = ( - event: APIGatewayEvent, - os: Client -): Task => - pipe( - of(event), - chain(validateTermParam), - chain(validateCurrencyIdentifierParam), - map((event) => ({ - ...extractTerm(event), - ...extractCurrencyIdentifier(event), - })), - chain(({ termName, termValue, currencyIdentifier }) => - pipe( - extractPagination(event), - map((pagination) => ({ - termName, - termValue, - pagination, - currencyIdentifier, - })) - ) - ), - chain(({ termName, termValue, pagination, currencyIdentifier }) => - findTransactionsBySnapshot(os)(termValue, pagination, currencyIdentifier) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getCurrencySnapshotFeeTransactions = ( - event: APIGatewayEvent, - os: Client -) => - pipe( - TE.Do, - TE.bind("identifier", () => extractCurrencyIdentifierParam(event)), - TE.bind("term", () => extractTermParam(event)), - TE.bind("pagination", () => extractPagination(event)), - TE.chain(({ identifier, term: { termValue }, pagination }) => { - console.log("params: ", { identifier, termValue, pagination }); - return findCurrencyFeeTransactionsBySnapshot(os)( - termValue, - pagination, - identifier - ); - }), - TE.fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getCurrencyBlock = (event: APIGatewayEvent, os: Client) => - pipe( - of(event), - chain(validateCurrencyIdentifierParam), - fold( - (reason) => T.of(errorResponse(reason)), - (validatedEvent) => getBlock(validatedEvent, os) - ) - ); - -export const getBlock = (event: APIGatewayEvent, os: Client): Task => - pipe( - of(event), - chain(validateHashParam), - map(extractHash), - map((hash) => ({ - ...hash, - ...extractCurrencyIdentifier(event), - })), - chain(({ hash, currencyIdentifier }) => - findBlockByHash(os)(hash, currencyIdentifier) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getCurrencyTransactions = (event: APIGatewayEvent, os: Client) => - pipe( - of(event), - chain(validateCurrencyIdentifierParam), - fold( - (reason) => T.of(errorResponse(reason)), - (validatedEvent) => getTransactions(validatedEvent, os) - ) - ); - -export const getTransactions = (event: APIGatewayEvent, os: Client) => - pipe( - of(event), - chain(() => - pipe( - extractPagination(event), - map((pagination) => { - return { - pagination, - ...extractCurrencyIdentifier(event), - }; - }) - ) - ), - chain(({ pagination, currencyIdentifier }) => - listTransactions(os)(pagination, currencyIdentifier) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getCurrencyTransactionsByAddress = ( - event: APIGatewayEvent, - os: Client -) => - pipe( - of(event), - chain(validateCurrencyIdentifierParam), - fold( - (reason) => T.of(errorResponse(reason)), - (validatedEvent) => getTransactionsByAddress(validatedEvent, os) - ) - ); - -export const getCurrencyFeeTransactionsByAddress = ( - event: APIGatewayEvent, - os: Client -) => - pipe( - TE.Do, - TE.bind("address", () => extractAddressParam(event)), - TE.bind("pagination", () => extractPagination(event)), - TE.bind("identifier", () => extractCurrencyIdentifierParam(event)), - TE.chain(({ address, pagination, identifier }) => - findCurrencyFeeTransactionsByAddress(os)(address, pagination, identifier) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getTransactionsByAddress = ( - event: APIGatewayEvent, - os: Client -): Task => - pipe( - of(event), - chain(validateAddressParam), - map(extractAddress), - chain(({ address }) => - pipe( - extractPagination(event), - map((pagination) => { - return { address, pagination, ...extractCurrencyIdentifier(event) }; - }) - ) - ), - chain(({ address, pagination, currencyIdentifier }) => - findTransactionsByAddress(os)(address, pagination, currencyIdentifier) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getCurrencyTransactionsBySource = ( - event: APIGatewayEvent, - os: Client -) => - pipe( - of(event), - chain(validateCurrencyIdentifierParam), - fold( - (reason) => T.of(errorResponse(reason)), - (validatedEvent) => getTransactionsBySource(validatedEvent, os) - ) - ); - -export const getCurrencyFeeTransactionsBySource = ( - event: APIGatewayEvent, - os: Client -) => - pipe( - TE.Do, - TE.bind("address", () => extractAddressParam(event)), - TE.bind("pagination", () => extractPagination(event)), - TE.bind("identifier", () => extractCurrencyIdentifierParam(event)), - TE.chain(({ address, pagination, identifier }) => - findCurrencyFeeTransactionsBySource(os)(address, pagination, identifier) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getTransactionsBySource = ( - event: APIGatewayEvent, - os: Client -): Task => - pipe( - of(event), - chain(validateAddressParam), - map(extractAddress), - chain(({ address }) => - pipe( - extractPagination(event), - map((pagination) => ({ - address, - pagination, - ...extractCurrencyIdentifier(event), - })) - ) - ), - chain(({ address, pagination, currencyIdentifier }) => - findTransactionsBySource(os)(address, pagination, currencyIdentifier) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getCurrencyTransactionsByDestination = ( - event: APIGatewayEvent, - os: Client -) => - pipe( - of(event), - chain(validateCurrencyIdentifierParam), - fold( - (reason) => T.of(errorResponse(reason)), - (validatedEvent) => getTransactionsByDestination(validatedEvent, os) - ) - ); - -export const getCurrencyFeeTransactionsByDestination = ( - event: APIGatewayEvent, - os: Client -) => - pipe( - TE.Do, - TE.bind("address", () => extractAddressParam(event)), - TE.bind("pagination", () => extractPagination(event)), - TE.bind("identifier", () => extractCurrencyIdentifierParam(event)), - TE.chain(({ address, pagination, identifier }) => - findCurrencyFeeTransactionsByDestination(os)( - address, - pagination, - identifier - ) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getTransactionsByDestination = ( - event: APIGatewayEvent, - os: Client -): Task => - pipe( - of(event), - chain(validateAddressParam), - map(extractAddress), - chain(({ address }) => - pipe( - extractPagination(event), - map((pagination) => ({ - address, - pagination, - ...extractCurrencyIdentifier(event), - })) - ) - ), - chain(({ address, pagination, currencyIdentifier }) => - findTransactionsByDestination(os)(address, pagination, currencyIdentifier) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getCurrencyBalanceByAddress = ( - event: APIGatewayEvent, - os: Client -) => - pipe( - of(event), - chain(validateCurrencyIdentifierParam), - fold( - (reason) => T.of(errorResponse(reason)), - (validatedEvent) => getBalanceByAddress(validatedEvent, os) - ) - ); - -export const getBalanceByAddress = ( - event: APIGatewayEvent, - os: Client -): Task => - pipe( - of(event), - chain(validateAddressParam), - map(extractAddressAndOrdinal), - map((params) => ({ - ...params, - ...extractCurrencyIdentifier(event), - })), - chain(({ address, ordinal, currencyIdentifier }) => - findBalanceByAddress(os)(address, currencyIdentifier, ordinal) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getCurrencyTransaction = (event: APIGatewayEvent, os: Client) => - pipe( - extractCurrencyIdentifierParam(event), - TE.fold( - (reason) => T.of(errorResponse(reason)), - (identifier) => getTransaction(event, os, identifier) - ) - ); - -export const getCurrencyFeeTransaction = (event: APIGatewayEvent, os: Client) => - pipe( - TE.Do, - TE.bind("hash", () => extractHashParam(event)), - TE.bind("identifier", () => extractCurrencyIdentifierParam(event)), - TE.chain(({ hash, identifier }) => - findCurrencyFeeTransactionByHash(os)(hash, identifier) - ), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getMetagraphs = (event: APIGatewayEvent, os: Client) => - pipe( - TE.Do, - TE.bind("pagination", () => extractPagination(event)), - TE.chain(({ pagination }) => listMetagraphs(os)(pagination)), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -export const getTransaction = ( - event: APIGatewayEvent, - os: Client, - currencyIdentifier: string | null -): Task => - pipe( - extractHashParam(event), - chain((hash) => findTransactionByHash(os)(hash, currencyIdentifier)), - fold( - (reason) => T.of(errorResponse(reason)), - (value) => T.of(successResponse(StatusCodes.OK)(value)) - ) - ); - -/** @deprecated use extractCurrencyIdentifierParam */ -const extractCurrencyIdentifier = ( - event: APIGatewayEvent -): { currencyIdentifier: string | null } => { - return { - currencyIdentifier: event.pathParameters?.identifier || null, - }; -}; - -/** @deprecated use extractHashParam */ -const extractHash = (event: APIGatewayEvent) => { - return { hash: event.pathParameters!.hash! }; -}; - -/** @deprecated use extractAddressParam */ -const extractAddress = (event: APIGatewayEvent) => { - return { address: event.pathParameters!.address! }; -}; - -/** @deprecated use extractTermParam */ -const extractTerm = (event: APIGatewayEvent) => { - if (event.pathParameters!.term == "latest") - return { - termName: "ordinal", - termValue: "latest", - }; - return { - termName: isNaN(Number(event.pathParameters!.term!)) ? "hash" : "ordinal", - termValue: event.pathParameters!.term!, - }; -}; - -/** @deprecated use extractAddressParam and extractOrdinalParam */ -const extractAddressAndOrdinal = ( - event: APIGatewayEvent -): { address: string; ordinal?: number } => { - const ordinal = Number(event.queryStringParameters?.ordinal); - return { - address: event.pathParameters!.address!, - ...(!isNaN(ordinal) ? { ordinal } : {}), - }; -}; - -export const extractCurrencyIdentifierParam = (event: APIGatewayEvent) => - pipe( - TE.of(event), - TE.chain(getPathParam("identifier")) - ); - -export const extractTermParam = (event: APIGatewayEvent) => - pipe( - TE.of(event), - TE.chain(getPathParam("term")), - TE.map((term) => { - if (term == "latest") - return { - termName: "ordinal", - termValue: "latest", - }; - return { - termName: isNaN(Number(term)) ? "hash" : "ordinal", - termValue: term, - }; - }) - ); - -export const extractHashParam = (event: APIGatewayEvent) => - pipe( - TE.of(event), - TE.chain(getPathParam("hash")) - ); - -export const extractAddressParam = (event: APIGatewayEvent) => - pipe( - TE.of(event), - TE.chain(getPathParam("address")) - ); - -export const extractNextParam = (event: APIGatewayEvent) => - pipe( - TE.of(event), - TE.map((event) => event.queryStringParameters?.next) - ); - -export const extractLimitParam = (event: APIGatewayEvent) => - pipe( - TE.of(event), - TE.chain((event) => { - const limitParam = event.queryStringParameters?.limit; - const limit = Number(limitParam); - - if (limitParam !== undefined && isNaN(limit)) { - return TE.left( - new ApplicationError( - "limit must be a number", - [], - StatusCodes.BAD_REQUEST - ) - ); - } - - return TE.right(limit || undefined); - }) - ); diff --git a/src/opensearch/ts-extensions.ts b/src/opensearch/ts-extensions.ts deleted file mode 100644 index 2ed014c..0000000 --- a/src/opensearch/ts-extensions.ts +++ /dev/null @@ -1,97 +0,0 @@ -type FilterUndefined = T extends undefined ? never : T; -type FilterNull = T extends null ? never : T; -type FilterUndefinedAndNull = FilterUndefined>; - -type ExtractFromObject< - O extends Record, - K -> = K extends keyof O - ? O[K] - : K extends keyof FilterUndefinedAndNull - ? FilterUndefinedAndNull[K] | undefined - : undefined; - -type ExtractFromArray = any[] extends A - ? A extends readonly (infer T)[] - ? T | undefined - : undefined - : K extends keyof A - ? A[K] - : undefined; - -type GetWithArray = K extends [] - ? O - : K extends [infer Key, ...infer Rest] - ? O extends Record - ? GetWithArray, Rest> - : O extends readonly any[] - ? GetWithArray, Rest> - : undefined - : never; - -export type Path = T extends `${infer Key}.${infer Rest}` - ? [Key, ...Path] - : T extends `${infer Key}` - ? [Key] - : []; - -export type Get = GetWithArray>; - -type Join = K extends string | number - ? P extends string | number - ? `${K}${"" extends P ? "" : "."}${P}` - : never - : never; - -type Prev = [ - never, - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - ...0[] -]; - -export type Paths = [D] extends [never] - ? never - : T extends object - ? { - [K in keyof T]-?: K extends string | number - ? `${K}` | Join> - : never; - }[keyof T] - : ""; - -export type ResolvePath< - T, - P extends string -> = P extends `${infer K}.${infer Rest}` - ? K extends keyof T - ? ResolvePath - : never - : P extends keyof T - ? T[P] - : never; - -export type ResolveValues> = K extends infer P - ? P extends string - ? ResolvePath - : never - : never; diff --git a/tests/opensearch.test.ts b/tests/opensearch.test.ts deleted file mode 100644 index e7d81e2..0000000 --- a/tests/opensearch.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { getFromPath } from "../src/opensearch/opensearch"; - -describe("getFromPath", () => { - const testObj = { - a: 1, - b: { - c: 2, - d: { - e: 3, - f: null, - g: undefined, - }, - }, - h: [1, 2, { i: 4 }], - j: null, - k: undefined, - }; - - it("should return the value for a simple path", () => { - expect(getFromPath(testObj, "a")).toBe(1); - }); - - it("should return the value for a nested path", () => { - expect(getFromPath(testObj, "b.c")).toBe(2); - expect(getFromPath(testObj, "b.d.e")).toBe(3); - }); - - it("should return undefined for non-existent paths", () => { - expect(getFromPath(testObj, "x")).toBeUndefined(); - expect(getFromPath(testObj, "b.x")).toBeUndefined(); - expect(getFromPath(testObj, "b.c.x")).toBeUndefined(); - }); - - it("should return undefined for paths leading to null or undefined", () => { - expect(getFromPath(testObj, "b.d.f")).toBeUndefined(); - expect(getFromPath(testObj, "b.d.g")).toBeUndefined(); - expect(getFromPath(testObj, "j")).toBeUndefined(); - expect(getFromPath(testObj, "k")).toBeUndefined(); - }); - - it("should handle array access", () => { - expect(getFromPath(testObj, "h.0")).toBe(1); - expect(getFromPath(testObj, "h.2.i")).toBe(4); - }); - - it("should return undefined for out-of-bounds array access", () => { - expect(getFromPath(testObj, "h.3")).toBeUndefined(); - }); - - it("should return the entire object for an empty path", () => { - expect(getFromPath(testObj, "")).toEqual(testObj); - }); - - it("should return undefined when trying to access properties on primitives", () => { - expect(getFromPath(testObj, "a.x")).toBeUndefined(); - }); - - it("should handle complex nested structures", () => { - const complexObj = { - a: { - b: [{ c: { d: 1 } }, { c: { d: 2 } }], - }, - }; - expect(getFromPath(complexObj, "a.b.0.c.d")).toBe(1); - expect(getFromPath(complexObj, "a.b.1.c.d")).toBe(2); - }); -}); diff --git a/tests/request-params.test.ts b/tests/request-params.test.ts deleted file mode 100644 index 9e99991..0000000 --- a/tests/request-params.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { APIGatewayEvent } from "aws-lambda"; -import * as E from "fp-ts/Either"; -import { pipe } from "fp-ts/function"; -import { getPathParam, RequestParamMissingError } from "../src/request-params"; - -describe("getPathParam", () => { - const mockEvent = ( - pathParameters: Record | null - ): APIGatewayEvent => - ({ - pathParameters, - } as APIGatewayEvent); - - it("should return the correct path parameter when it exists", async () => { - const event = mockEvent({ id: "123" }); - const result = await getPathParam("id")(event)(); - - pipe( - result, - E.fold( - (error) => { - fail(`Expected Right, but got Left: ${error}`); - }, - (value) => { - expect(value).toBe("123"); - } - ) - ); - }); - - it("should return a RequestParamMissingError when pathParameters is null", async () => { - const event = mockEvent(null); - const result = await getPathParam("id")(event)(); - - pipe( - result, - E.fold( - (error) => { - expect(error).toBeInstanceOf(RequestParamMissingError); - }, - (value) => { - fail(`Expected Left, but got Right: ${value}`); - } - ) - ); - }); - - it("should return a RequestParamMissingError when the requested parameter does not exist", async () => { - const event = mockEvent({ otherId: "456" }); - const result = await getPathParam("id")(event)(); - - pipe( - result, - E.fold( - (error) => { - expect(error).toBeInstanceOf(RequestParamMissingError); - }, - (value) => { - fail(`Expected Left, but got Right: ${value}`); - } - ) - ); - }); - - it("should return a RequestParamMissingError when the parameter value is undefined", async () => { - const event = mockEvent({ id: undefined }); - const result = await getPathParam("id")(event)(); - - pipe( - result, - E.fold( - (error) => { - expect(error).toBeInstanceOf(RequestParamMissingError); - }, - (value) => { - fail(`Expected Left, but got Right: ${value}`); - } - ) - ); - }); -}); From 9a20105ece69169c3047020dde1f93a999d1f069 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Fri, 6 Jun 2025 13:24:49 -0300 Subject: [PATCH 51/63] optomize total rewards --- prisma/schema.prisma | 27 ++++++++++++++++--------- src/handlers/delegatedStakingHandler.ts | 11 +++++----- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 4164b5a..cad2038 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -247,7 +247,7 @@ model dag_transactions { block_hash String @db.VarChar dag_blocks dag_blocks @relation(fields: [block_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_transaction_dag_block_fk") snapshot_hash String @db.VarChar - transaction_original Json? + transaction_original Json? global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_dag_transactions_global_snapshot_fk") addresses_dag_transactions_destination_addrToaddresses addresses @relation("dag_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_transactions_destination_addr_fk") addresses_dag_transactions_source_addrToaddresses addresses @relation("dag_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_transactions_source_addr_fk") @@ -505,7 +505,7 @@ model metagraph_transactions { ordinal BigInt block_hash String @db.VarChar snapshot_hash String @db.VarChar - transaction_original Json? + transaction_original Json? metagraph_snapshot metagraph_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transactions_metagraph_snapshot_fk") metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") metagraph_blocks metagraph_blocks @relation(fields: [metagraph_id, block_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transaction_metagraph_block_fk") @@ -588,13 +588,15 @@ model delegate_stake_create_events { created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - global_snapshots global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) - addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction) - dag_token_lock dag_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) - delegated_from delegate_stake_create_events? @relation(name: "delegation_self") - delegated_to delegate_stake_create_events? @relation(name: "delegation_self", fields: [transfer_from_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction) - delegate_stake_withdraw_events delegate_stake_withdraw_events[] - delegate_stake_rewards delegate_stake_rewards[] + global_snapshots global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) + addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction) + dag_token_lock dag_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) + delegated_from delegate_stake_create_events? @relation(name: "delegation_self") + delegated_to delegate_stake_create_events? @relation(name: "delegation_self", fields: [transfer_from_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction) + delegate_stake_withdraw_events delegate_stake_withdraw_events[] + delegate_stake_rewards delegate_stake_rewards[] + delegate_stake_total_rewards delegate_stake_total_rewards_view @relation(fields: [hash], references: [stake_create_hash]) + @@index([global_snapshot_hash]) @@index([source_addr]) @@ -631,3 +633,10 @@ model delegate_stake_rewards { @@unique([global_snapshot_hash, address, node_id, rewards], map: "delegate_stake_rewards_changes_unique") } + +model delegate_stake_total_rewards_view { + stake_create_hash String @id + delegate_stake_total_rewards BigInt + + delegate_stake_create_events delegate_stake_create_events[] +} diff --git a/src/handlers/delegatedStakingHandler.ts b/src/handlers/delegatedStakingHandler.ts index 205d926..a74a50d 100644 --- a/src/handlers/delegatedStakingHandler.ts +++ b/src/handlers/delegatedStakingHandler.ts @@ -76,15 +76,13 @@ const delegateStakeWithdrawResponse = (event) => ({ const delegateStakeWithdrawResponses = (txs) => txs.map(delegateStakeWithdrawResponse); -const totalRewards = (rs) => - (rs ?? []).reduce((sum, r) => sum + r.rewards, BigInt(0)); - const completedAmount = (change) => { const withdrawal = change.withdrawal_event; if (change.withdrawal_event?.is_complete) { const createEvent = withdrawal.delegate_stake_create_even; return ( - createEvent.amount + totalRewards(createEvent.delegate_stake_rewards) + createEvent.amount + + createEvent.delegate_stake_total_rewards?.delegate_stake_total_rewards ); } else { return 0; @@ -101,7 +99,8 @@ const delegateStakePositionResponse = (change) => { status: stakeStatus(change), stakeHash: change.hash, lockAmount: change.amount, - rewardsAccrued: totalRewards(change.delegate_stake_rewards), + rewardsAccrued: + change.delegate_stake_total_rewards?.delegate_stake_total_rewards ?? 0, withdrawnAmount: completedAmount(change), transferedFromHash: change.delegated_from?.hash ?? null, transferedToHash: change.delegated_to?.hash ?? null, @@ -330,7 +329,7 @@ export const stakingPositions = async ( }, include: { delegate_stake_withdraw_events: true, - delegate_stake_rewards: true, + delegate_stake_total_rewards: true, delegated_to: true, delegated_from: true, }, From b1dc9f02cdf30a34e6616ba81e47560ec20c0dc6 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Wed, 11 Jun 2025 21:50:13 -0300 Subject: [PATCH 52/63] fix snapshot count typo --- src/response.ts | 2 +- tests/handlers/dagHandler.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/response.ts b/src/response.ts index 82bbcce..479651e 100644 --- a/src/response.ts +++ b/src/response.ts @@ -33,7 +33,7 @@ const commonSnapshotResponse = (snapshot, blocksProperty) => ({ export const globalSnapshotResponse = (snapshot) => ({ ...commonSnapshotResponse(snapshot, "dag_blocks"), - metagraphSnashotCount: snapshot.metagraph_snapshot_count, + metagraphSnapshotCount: snapshot.metagraph_snapshot_count, }); export const rewardsResponse = (rs) => rs.map(rewardResponse); diff --git a/tests/handlers/dagHandler.test.ts b/tests/handlers/dagHandler.test.ts index f301144..1b8fd58 100644 --- a/tests/handlers/dagHandler.test.ts +++ b/tests/handlers/dagHandler.test.ts @@ -192,7 +192,7 @@ describe("DAG Handler Integration Tests", () => { expect(snapshot.subHeight).toBe(Number(testSnapshot.subheight)); expect(Array.isArray(snapshot.blocks)).toBe(true); expect(snapshot.timestamp).toBeDefined(); - expect(snapshot.metagraphSnashotCount).toBe( + expect(snapshot.metagraphSnapshotCount).toBe( Number(testSnapshot.metagraph_snapshot_count) ); }); From f90f02fa9dab0f9c28505277600b840076544e76 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Thu, 12 Jun 2025 11:29:28 -0300 Subject: [PATCH 53/63] fix dag/mg pagination --- src/handlers/dagHandler.ts | 11 ++-- src/handlers/metagraphHandler.ts | 94 +++++++++++++++++--------------- 2 files changed, 57 insertions(+), 48 deletions(-) diff --git a/src/handlers/dagHandler.ts b/src/handlers/dagHandler.ts index 9484ad4..3601e60 100644 --- a/src/handlers/dagHandler.ts +++ b/src/handlers/dagHandler.ts @@ -54,7 +54,7 @@ export const globalSnapshots = async ( fromCreatedAtOrdinalCursor, { include: { dag_blocks: true }, - orderBy: { ordinal: "desc" }, + orderBy: [{ created_at: "desc" }, { ordinal: "desc" }], }, prisma.global_snapshots.findMany, globalSnapshotsResponse @@ -117,7 +117,10 @@ export const globalSnapshotRewards = async ( fromCursor, { where: { global_snapshot: { ...gsWhere } }, - orderBy: [{ destination_addr: "asc" }], + orderBy: [ + { global_snapshot_hash: "desc" }, + { destination_addr: "asc" }, + ], }, prisma.dag_reward_transactions.findMany, rewardsResponse @@ -146,7 +149,7 @@ export const globalSnapshotTransactions = async ( include: { global_snapshot: { select: { hash: true, ordinal: true } }, }, - orderBy: { created_at: "desc" }, + orderBy: [{ created_at: "desc" }, { ordinal: "desc" }], }; return await paginatedQuery( @@ -192,7 +195,7 @@ const dagTransactionsQuery = async ( include: { global_snapshot: { select: { hash: true, ordinal: true } }, }, - orderBy: [{ created_at: "desc" }], + orderBy: [{ created_at: "desc" }, { hash: "desc" }], }; const toCursor = (row) => ({ diff --git a/src/handlers/metagraphHandler.ts b/src/handlers/metagraphHandler.ts index 5f7bcc8..899049f 100644 --- a/src/handlers/metagraphHandler.ts +++ b/src/handlers/metagraphHandler.ts @@ -41,6 +41,29 @@ const latestMetagraphSnapshot = async (metagraph_id) => { }); }; +const mgIdOrdinalToCursor = (row) => ({ + metagraph_id_ordinal: { + ...toOrdinalCursor(row), + metagraph_id: row.metagraph_id, + }, +}); + +const mgIdOrdinalFromCursor = (row) => ({ + ...fromOrdinalCursor(row), + metagraph_id: row.metagraph_id, +}); + +const mgIdHashToCursor = (row) => ({ + metagraph_id_hash: { + metagraph_id: row.metagraph_id, + hash: row.hash, + }, +}); +const mgIdHashFromCursor = (row) => ({ + metagraph_id: row.metagraph_id, + hash: row.hash, +}); + const metagraphSnapshotWhere = async (metagraph_id, term) => { if (term == "latest") { const latestSnapshotHash = (await latestMetagraphSnapshot(metagraph_id)) @@ -79,26 +102,14 @@ export const currencySnapshots = async ( return notFoundResponse("metagraph"); } - const toCursor = (row) => ({ - metagraph_id_ordinal: { - ...toOrdinalCursor(row), - metagraph_id: row.metagraph_id, - }, - }); - - const fromCursor = (row) => ({ - ...fromOrdinalCursor(row), - metagraph_id: row.metagraph_id, - }); - return await paginatedQuery( extractPagination(event), - toCursor, - fromCursor, + mgIdOrdinalToCursor, + mgIdOrdinalFromCursor, { where: { metagraph_id }, include: { metagraph_blocks: true }, - orderBy: { ordinal: "desc" }, + orderBy: [{ metagraph_id: "desc" }, { ordinal: "desc" }], }, prisma.metagraph_snapshots.findMany, metagraphSnapshotsResponse @@ -114,26 +125,14 @@ export const currencySnapshotsByOwnerAddress = async ( try { const { address } = event.pathParameters || {}; - const toCursor = (row) => ({ - ...toCreatedAtOrdinalCursor(row), - metagraph_id: row.metagraph_id, - hash: row.hash, - }); - - const fromCursor = (row) => ({ - ...fromCreatedAtOrdinalCursor(row), - metagraph_id: row.metagraph_id, - hash: row.hash, - }); - return await paginatedQuery( extractPagination(event), - toCursor, - fromCursor, + mgIdOrdinalToCursor, + mgIdOrdinalFromCursor, { where: { owner_address: address }, include: { metagraph_blocks: true }, - orderBy: { ordinal: "desc" }, + orderBy: [{ metagraph_id: "desc" }, { ordinal: "desc" }], }, prisma.metagraph_snapshots.findMany, metagraphSnapshotsResponse @@ -188,15 +187,23 @@ export const currencySnapshotRewards = async ( const mgSnapshotWhere = await metagraphSnapshotWhere(metagraph_id, term); - const cursor = (row) => ({ + const nextToCursor = (row) => ({ + metagraph_id_metagraph_snapshot_hash_destination_addr: { + metagraph_id: row.metagraph_id, + metagraph_snapshot_hash: row.metagraph_snapshot_hash, + destination_addr: row.destination_addr, + }, + }); + const cursorToNext = (row) => ({ metagraph_id: row.metagraph_id, - hash: row.hash, + metagraph_snapshot_hash: row.metagraph_snapshot_hash, + destination_addr: row.destination_addr, }); return await paginatedQuery( extractPagination(event), - cursor, - cursor, + nextToCursor, + cursorToNext, { where: { metagraph_snapshot: { @@ -228,18 +235,17 @@ const metagraphTransactionsQuery = async ( include: { metagraph_snapshot: { select: { hash: true, ordinal: true } }, }, - orderBy: [{ created_at: "desc" }], + orderBy: [ + { metagraph_id: "desc" }, + { hash: "desc" }, + { created_at: "desc" }, + ], }; - const cursor = (row) => ({ - metagraph_id: row.metagraph_id, - hash: row.hash, - }); - return await paginatedQuery( extractPagination(event), - cursor, - cursor, + mgIdHashToCursor, + mgIdHashFromCursor, query, prisma.metagraph_transactions.findMany, metagraphTransactionsResponse @@ -485,7 +491,7 @@ const metagraphFeeTransactionsQuery = async ( include: { metagraph_snapshot: { select: { hash: true, ordinal: true } }, }, - orderBy: { hash: "asc" }, + orderBy: [{ metagraph_id: "asc" }, { hash: "asc" }], }; const cursor = (row) => ({ @@ -619,7 +625,7 @@ export const metagraphs = async ( extractPagination(event), cursor, cursor, - {}, + { orderBy: { id: "asc" } }, prisma.metagraphs.findMany, metagraphsResponse ); From fc66cb9e5a8dcc87283a86072a2da9628b77202c Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Fri, 13 Jun 2025 10:56:40 -0300 Subject: [PATCH 54/63] PROT 1236 remove transferred stakes from active and include transferred rewards in total withdrawal (#187) * optimize positions and fix bug * fix transfer from/to logic * fix total withdrawn amount and tests --- docs/schemas/delegatedStaking.yml | 4 +- .../20250606/01_total_rewards_view.sql | 21 ++++++ ...legate_stake_create_events_latest_view.sql | 12 ++++ .../02_optimize_metagraph_snapshot_query.sql | 8 +++ prisma/schema.prisma | 21 ++++-- prisma/seed.ts | 68 +++++++++++++++---- serverless.yml | 2 +- src/handlers/delegatedStakingHandler.ts | 50 ++++---------- tests/handlers/actionsHandler.test.ts | 6 +- .../handlers/delegatedStakingHandler.test.ts | 35 ++++++++-- tests/handlers/metagraphHandler.test.ts | 2 +- 11 files changed, 159 insertions(+), 70 deletions(-) create mode 100644 prisma/migrations/20250606/01_total_rewards_view.sql create mode 100644 prisma/migrations/20250611/01_delegate_stake_create_events_latest_view.sql create mode 100644 prisma/migrations/20250611/02_optimize_metagraph_snapshot_query.sql diff --git a/docs/schemas/delegatedStaking.yml b/docs/schemas/delegatedStaking.yml index 10e6248..8f42902 100644 --- a/docs/schemas/delegatedStaking.yml +++ b/docs/schemas/delegatedStaking.yml @@ -36,8 +36,8 @@ components: lockAmount: { type: string, description: "BigInt represented as a string" } rewardsAccrued: { type: string, description: "BigInt represented as a string" } withdrawnAmount: { type: string, description: "BigInt represented as a string" } - transferedFromHash: { type: string, nullable: true } - transferedToHash: { type: string, nullable: true } + transferredFromHash: { type: string, nullable: true } + transferredToHash: { type: string, nullable: true } createdAt: { type: string, format: date-time } transferredAt: { type: string, format: date-time, nullable: true } withdrawalStartedAt: { type: string, format: date-time, nullable: true } diff --git a/prisma/migrations/20250606/01_total_rewards_view.sql b/prisma/migrations/20250606/01_total_rewards_view.sql new file mode 100644 index 0000000..d6bdbd4 --- /dev/null +++ b/prisma/migrations/20250606/01_total_rewards_view.sql @@ -0,0 +1,21 @@ +CREATE VIEW delegate_stake_total_rewards_view AS +SELECT + stake_create_hash, + SUM(rewards) AS delegate_stake_total_rewards +FROM + delegate_stake_rewards +GROUP BY + stake_create_hash; + + +CREATE VIEW token_lock_total_rewards_view as SELECT + lock_reference_hash, + sum(rewards) as total_rewards +FROM + delegate_stake_rewards dsr, + delegate_stake_create_events dsce, + delegate_stake_withdraw_events dswe + where dsce.hash = dsr.stake_create_hash and dsce.hash = dswe.stake_create_hash and dswe.is_completed +GROUP BY + lock_reference_hash; + diff --git a/prisma/migrations/20250611/01_delegate_stake_create_events_latest_view.sql b/prisma/migrations/20250611/01_delegate_stake_create_events_latest_view.sql new file mode 100644 index 0000000..dbda753 --- /dev/null +++ b/prisma/migrations/20250611/01_delegate_stake_create_events_latest_view.sql @@ -0,0 +1,12 @@ +CREATE INDEX CONCURRENTLY idx_delegate_stake_events_ord_desc ON delegate_stake_create_events (source_addr, node_id, ordinal DESC); + +CREATE INDEX CONCURRENTLY idx_withdraw_stake_hash_completed ON public.delegate_stake_withdraw_events(stake_create_hash, is_completed); +CREATE INDEX CONCURRENTLY idx_create_events_hash ON public.delegate_stake_create_events(hash); + +CREATE INDEX CONCURRENTLY idx_withdraw_stake_hash ON delegate_stake_withdraw_events(stake_create_hash); +CREATE INDEX CONCURRENTLY idx_create_transfer_from_hash ON delegate_stake_create_events(transfer_from_hash); + +CREATE INDEX idx_create_events_hash_lock ON delegate_stake_create_events(hash, lock_reference_hash); + +CREATE INDEX idx_dswe_completed_hash ON delegate_stake_withdraw_events(stake_create_hash) +WHERE is_completed; \ No newline at end of file diff --git a/prisma/migrations/20250611/02_optimize_metagraph_snapshot_query.sql b/prisma/migrations/20250611/02_optimize_metagraph_snapshot_query.sql new file mode 100644 index 0000000..e5708f1 --- /dev/null +++ b/prisma/migrations/20250611/02_optimize_metagraph_snapshot_query.sql @@ -0,0 +1,8 @@ +CREATE INDEX idx_withdraw_stake_hash_completed ON public.delegate_stake_withdraw_events(stake_create_hash, is_completed); +CREATE INDEX idx_create_events_hash ON public.delegate_stake_create_events(hash); + +CREATE INDEX idx_withdraw_stake_hash ON delegate_stake_withdraw_events(stake_create_hash); +CREATE INDEX idx_create_transfer_from_hash ON delegate_stake_create_events(transfer_from_hash); + +CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_metagraph_blocks_snap ON metagraph_blocks (metagraph_id, metagraph_snapshot_hash); +CREATE UNIQUE INDEX metagraph_id_ordinal ON metagraph_snapshots (metagraph_id, ordinal); \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index cad2038..66db7e5 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -210,7 +210,7 @@ model dag_token_locks { addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_locks_source_addr_fk") global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_lock_global_snapshot_fk") dag_token_unlock dag_token_unlocks? - delegate_stake_create_events delegate_stake_create_events[] + dag_actions_view dag_actions_view[] @@unique([ordinal], map: "dag_token_locks_unique") @@ -590,14 +590,14 @@ model delegate_stake_create_events { global_snapshots global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction) - dag_token_lock dag_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) - delegated_from delegate_stake_create_events? @relation(name: "delegation_self") - delegated_to delegate_stake_create_events? @relation(name: "delegation_self", fields: [transfer_from_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction) + + delegated_from delegate_stake_create_events? @relation(name: "delegation_self", fields: [transfer_from_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction) + delegated_to delegate_stake_create_events? @relation(name: "delegation_self") delegate_stake_withdraw_events delegate_stake_withdraw_events[] delegate_stake_rewards delegate_stake_rewards[] - delegate_stake_total_rewards delegate_stake_total_rewards_view @relation(fields: [hash], references: [stake_create_hash]) + delegate_stake_total_rewards delegate_stake_total_rewards_view? + total_rewards_view token_lock_total_rewards_view? @relation( fields: [lock_reference_hash], references: [lock_reference_hash], onDelete: Cascade, onUpdate: NoAction) - @@index([global_snapshot_hash]) @@index([source_addr]) @@index([lock_reference_hash]) @@ -638,5 +638,12 @@ model delegate_stake_total_rewards_view { stake_create_hash String @id delegate_stake_total_rewards BigInt - delegate_stake_create_events delegate_stake_create_events[] + delegate_stake_create_events delegate_stake_create_events @relation(fields: [stake_create_hash], references: [hash]) } + +model token_lock_total_rewards_view { + lock_reference_hash String @id + total_rewards BigInt + + delegate_stake_create_events delegate_stake_create_events[] +} \ No newline at end of file diff --git a/prisma/seed.ts b/prisma/seed.ts index af54280..c08672a 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -416,8 +416,8 @@ export const data_delegate_stake_create_events = [ { hash: "stake-event-hash-002", ordinal: 10002n, - source_addr: data_addresses[0].address, - node_id: "NODE_ABC123", + source_addr: data_addresses[1].address, + node_id: "NODE_ABC234", amount: 3333300000000n, fee: 500000n, lock_reference_hash: data_dag_token_locks[1].hash, @@ -429,13 +429,24 @@ export const data_delegate_stake_create_events = [ ordinal: 10003n, source_addr: data_addresses[0].address, node_id: "NODE_ABC234", - amount: 2222222222n, + amount: 3333300000000n, fee: 500000n, lock_reference_hash: data_dag_token_locks[1].hash, parent_hash: "stake-event-hash-001", - global_snapshot_hash: data_global_snapshots[1].hash, + global_snapshot_hash: data_global_snapshots[2].hash, transfer_from_hash: "stake-event-hash-002", }, + { + hash: "stake-event-hash-004", + ordinal: 10011n, + source_addr: data_addresses[2].address, + node_id: "NODE_ABC444", + amount: 600000000000n, + fee: 500000n, + lock_reference_hash: data_dag_token_locks[2].hash, + parent_hash: "parent-hash-xyz1", + global_snapshot_hash: data_global_snapshots[2].hash, + }, ]; export const data_delegate_stake_withdraw_events = [ @@ -450,8 +461,8 @@ export const data_delegate_stake_withdraw_events = [ { hash: "withdraw-event-hash-002", source_addr: data_addresses[0].address, - stake_create_hash: data_delegate_stake_create_events[1].hash, - global_snapshot_hash: data_global_snapshots[1].hash, + stake_create_hash: data_delegate_stake_create_events[2].hash, + global_snapshot_hash: data_global_snapshots[2].hash, unlock_epoch: 8000n, is_completed: true, }, @@ -459,12 +470,33 @@ export const data_delegate_stake_withdraw_events = [ export const data_delegate_stake_rewards = [ { - global_snapshot_hash: data_global_snapshots[2].hash, + global_snapshot_hash: data_global_snapshots[0].hash, address: data_addresses[0].address, - node_id: "NODE_ABC123", - rewards: 2500000000n, + node_id: data_delegate_stake_create_events[0].node_id, + rewards: 1000n, stake_create_hash: data_delegate_stake_create_events[0].hash, }, + { + global_snapshot_hash: data_global_snapshots[1].hash, + address: data_addresses[0].address, + node_id: data_delegate_stake_create_events[0].node_id, + rewards: 1000n, + stake_create_hash: data_delegate_stake_create_events[0].hash, + }, + { + global_snapshot_hash: data_global_snapshots[1].hash, + address: data_addresses[1].address, + node_id: data_delegate_stake_create_events[1].node_id, + rewards: 2000n, + stake_create_hash: data_delegate_stake_create_events[1].hash, + }, + { + global_snapshot_hash: data_global_snapshots[2].hash, + address: data_addresses[0].address, + node_id: data_delegate_stake_create_events[2].node_id, + rewards: 3000n, + stake_create_hash: data_delegate_stake_create_events[2].hash, + }, ]; export const data_metagraph_fee_transactions = [ @@ -472,7 +504,7 @@ export const data_metagraph_fee_transactions = [ metagraph_id: data_metagraphs[0].id, metagraph_snapshot_hash: data_metagraph_snapshots[0].hash, metagraph_snapshot_ordinal: data_metagraph_snapshots[0].ordinal, - hash: "39c9909d3b00552beaa5487c38110267675ab212b3b97654fc5ca9917cb7e72b", + hash: "39c9909d3b00552beaa5487c38110267675ab212b3b97654fc5ca9917cb7e72b1", source_addr: data_addresses[0].address, destination_addr: data_addresses[1].address, amount: 90790983n, @@ -485,18 +517,23 @@ export const data_metagraph_fee_transactions = [ metagraph_id: data_metagraphs[1].id, metagraph_snapshot_hash: data_metagraph_snapshots[2].hash, metagraph_snapshot_ordinal: data_metagraph_snapshots[2].ordinal, - hash: "39c990000000000000000000000000000000000000007654fc5ca9917cb7e72b", + hash: "39c990000000000000000000000000000000000000007654fc5ca9917cb7e72b1", source_addr: data_addresses[2].address, destination_addr: data_addresses[3].address, amount: 10090983n, data_update_ref: - "39c9909d3b00552beaa5487c38110267675ab212b3b97654fc5ca9917cb7e72b", + "39c9909d3b00552beaa5487c38110267675ab212b3b97654fc5ca9917cb7e72b1", created_at: new Date("2025-04-02T00:00:02Z"), updated_at: new Date(), }, ]; export async function seed() { + //prisma is annoying and gereates an fk to the view + await prisma.$executeRawUnsafe( + "ALTER TABLE delegate_stake_create_events DROP CONSTRAINT delegate_stake_create_events_lock_reference_hash_fkey" + ); + await prisma.addresses.createMany({ data: data_addresses }); await prisma.global_snapshots.createMany({ @@ -528,7 +565,6 @@ export async function seed() { await prisma.metagraph_token_unlocks.createMany({ data: data_metagraph_token_unlocks, }); - await prisma.metagraph_allow_spends.createMany({ data: data_metagraph_allow_spends, }); @@ -554,7 +590,6 @@ export async function seed() { await prisma.dag_expired_spend_transactions.createMany({ data: data_dag_expired_spend_transactions, }); - await prisma.delegate_stake_create_events.createMany({ data: data_delegate_stake_create_events, }); @@ -571,7 +606,12 @@ export async function seed() { //first drop the prisma generated tables await prisma.$executeRawUnsafe("DROP TABLE dag_actions_view"); await prisma.$executeRawUnsafe("DROP TABLE metagraph_actions_view"); + await prisma.$executeRawUnsafe( + "DROP TABLE delegate_stake_total_rewards_view" + ); + await prisma.$executeRawUnsafe("DROP TABLE token_lock_total_rewards_view"); runSqlFromFile("./migrations/20250529/01_add_staking_to_actions.sql"); + runSqlFromFile("./migrations/20250606/01_total_rewards_view.sql"); } async function runSqlFromFile(filename: string) { diff --git a/serverless.yml b/serverless.yml index 15a4601..3c22e2a 100644 --- a/serverless.yml +++ b/serverless.yml @@ -19,7 +19,7 @@ provider: cors: true vpc: ${self:custom.env.vpc} versionFunctions: false - timeout: 10 + timeout: 60 functions: - ${file(./routes/dag.yml)} diff --git a/src/handlers/delegatedStakingHandler.ts b/src/handlers/delegatedStakingHandler.ts index a74a50d..99d8957 100644 --- a/src/handlers/delegatedStakingHandler.ts +++ b/src/handlers/delegatedStakingHandler.ts @@ -5,12 +5,7 @@ import { } from "@prisma/client"; import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; import { extractPagination } from "../request-params"; -import { - paginatedQuery, - toOrdinalCursor, - fromOrdinalCursor, - hashCursor, -} from "../pagination"; +import { paginatedQuery, hashCursor } from "../pagination"; import { handleError, respond } from "../response"; const prisma = new PrismaClient(); @@ -35,7 +30,8 @@ const latestWithdrawEvents = ( const withdrawalStatus = (isCompleted) => isCompleted ? "withdrawalComplete" : "pendingWithdrawal"; -const createStatus = (ce) => (ce.transfer_from_hash ? "transfered" : "active"); +const createStatus = (ce) => + ce.delegated_to == null ? "active" : "transferred"; const stakeStatus = (event) => { const withdrawalEvents = event.delegate_stake_withdraw_events; @@ -77,13 +73,8 @@ const delegateStakeWithdrawResponses = (txs) => txs.map(delegateStakeWithdrawResponse); const completedAmount = (change) => { - const withdrawal = change.withdrawal_event; if (change.withdrawal_event?.is_complete) { - const createEvent = withdrawal.delegate_stake_create_even; - return ( - createEvent.amount + - createEvent.delegate_stake_total_rewards?.delegate_stake_total_rewards - ); + return change.amount + change.total_rewards_view?.total_rewards; } else { return 0; } @@ -102,8 +93,8 @@ const delegateStakePositionResponse = (change) => { rewardsAccrued: change.delegate_stake_total_rewards?.delegate_stake_total_rewards ?? 0, withdrawnAmount: completedAmount(change), - transferedFromHash: change.delegated_from?.hash ?? null, - transferedToHash: change.delegated_to?.hash ?? null, + transferredFromHash: change.delegated_from?.hash ?? null, + transferredToHash: change.delegated_to?.hash ?? null, createdAt: change.created_at, transferredAt: change.delegated_to?.created_at ?? null, withdrawalStartedAt: withdrawalCreate?.created_at ?? null, @@ -125,17 +116,17 @@ const buildStatusWhereQuery = (statuses) => { if (statuses.includes("active")) { statusFilters.push({ - transfer_from_hash: null, + delegated_to: null, delegate_stake_withdraw_events: { none: {}, }, }); } - if (statuses.includes("transfered")) { + if (statuses.includes("transferred")) { statusFilters.push({ - transfer_from_hash: { - not: null, + delegated_to: { + isNot: null, }, delegate_stake_withdraw_events: { none: {}, @@ -297,21 +288,6 @@ export const stakingPositions = async ( const nodeId = event.queryStringParameters?.nodeId; const nodeIdWhere = nodeId ? { node_id: nodeId } : {}; - const maxOrdinals = await prisma.delegate_stake_create_events.groupBy({ - by: ["source_addr", "node_id"], - _max: { - ordinal: true, - }, - }); - - const whereConditions = maxOrdinals.map( - ({ source_addr, node_id, _max }) => ({ - source_addr, - node_id, - ordinal: _max.ordinal!, - }) - ); - const addressFilter = address?.trim() ? { source_addr: address.trim() } : {}; @@ -322,7 +298,6 @@ export const stakingPositions = async ( hashCursor, { where: { - OR: whereConditions, ...statusWhere, ...nodeIdWhere, ...addressFilter, @@ -330,6 +305,11 @@ export const stakingPositions = async ( include: { delegate_stake_withdraw_events: true, delegate_stake_total_rewards: true, + total_rewards_view: { + select: { + total_rewards: true, + }, + }, delegated_to: true, delegated_from: true, }, diff --git a/tests/handlers/actionsHandler.test.ts b/tests/handlers/actionsHandler.test.ts index 81942ba..436f7d7 100644 --- a/tests/handlers/actionsHandler.test.ts +++ b/tests/handlers/actionsHandler.test.ts @@ -54,8 +54,6 @@ expect.extend({ const validateAction = (action) => { const match = datasets.flat().find((tx) => tx.hash === action.hash); - if (!match) console.log(action); - expect(match).toBeDefined(); if (!match) return; @@ -83,7 +81,7 @@ describe("actionsHandler", () => { expect(result.statusCode).toBe(200); const body = validatePaginatedResponse(result); - expect(body.data.length).toBe(15); + expect(body.data.length).toBe(16); body.data.forEach(validateAction); }); @@ -109,7 +107,7 @@ describe("actionsHandler", () => { expect(result.statusCode).toBe(200); const body = validatePaginatedResponse(result); - expect(body.data.length).toBe(12); + expect(body.data.length).toBe(11); body.data.forEach(validateAction); }); diff --git a/tests/handlers/delegatedStakingHandler.test.ts b/tests/handlers/delegatedStakingHandler.test.ts index ddd0cb9..a878e02 100644 --- a/tests/handlers/delegatedStakingHandler.test.ts +++ b/tests/handlers/delegatedStakingHandler.test.ts @@ -67,22 +67,37 @@ const validateStakingPosition = (tx) => { expect(tx.address).toBe(match.source_addr); expect(tx.nodeId).toBe(match.node_id); expect(tx.lockAmount).toBeBigInt(match.amount); + //rewards for this staking event expect(tx.rewardsAccrued).toBeBigInt( data_delegate_stake_rewards .filter((r) => r.stake_create_hash === match.hash) .reduce((sum, r) => sum + r.rewards, BigInt(0)) ); + //total amount including rewards of transferred events + if (tx.withdrawalCompletedAt != null) { + expect(tx.withdrawnAmount).toBeBigInt( + data_delegate_stake_rewards + .filter((r) => + data_delegate_stake_create_events.some( + (event) => + event.lock_reference_hash === match.lock_reference_hash && + event.hash === r.stake_create_hash + ) + ) + .reduce((sum, r) => sum + r.rewards, BigInt(0)) + ); + } }; describe("Delegated Stake Handler Integration Tests", () => { describe("delegatedStakes", () => { - it("should return a list of delegated stakes", async () => { + it("should return a list of delegated stakes with default filtering", async () => { const event = createAPIGatewayEvent({}, { limit: "10" }); const response = await delegatedStakes(event); expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); - expect(body.data.length).toBeGreaterThan(0); + expect(body.data.length).toBe(2); body.data.forEach(validateCreateStake); }); @@ -91,15 +106,21 @@ describe("Delegated Stake Handler Integration Tests", () => { {}, { limit: "10", - status: "transfered,active", + status: "transferred,active,pendingWithdrawal,withdrawalComplete", } ); const response = await delegatedStakes(event); expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); + expect(body.data.length).toBe(4); body.data.forEach((tx) => { - expect(["transfered", "active"]).toContain(tx.status); + expect([ + "transferred", + "active", + "pendingWithdrawal", + "withdrawalComplete", + ]).toContain(tx.status); }); }); }); @@ -176,6 +197,7 @@ describe("Delegated Stake Handler Integration Tests", () => { expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); + expect(body.data.length).toBe(2); body.data.forEach(validateStakingPosition); }); @@ -184,15 +206,16 @@ describe("Delegated Stake Handler Integration Tests", () => { {}, { limit: "10", - status: "active,pendingWithdrawal", + status: "pendingWithdrawal", } ); const response = await stakingPositions(event); expect(response.statusCode).toBe(200); const body = validatePaginatedResponse(response); + expect(body.data.length).toBe(1); body.data.forEach((tx) => { - expect(["active", "pendingWithdrawal"]).toContain(tx.status); + expect(["pendingWithdrawal"]).toContain(tx.status); }); }); diff --git a/tests/handlers/metagraphHandler.test.ts b/tests/handlers/metagraphHandler.test.ts index 73967e1..18733dd 100644 --- a/tests/handlers/metagraphHandler.test.ts +++ b/tests/handlers/metagraphHandler.test.ts @@ -394,7 +394,7 @@ describe("Metagraph Handler Integration Tests", () => { test("currencyFeeTransaction returns expected result", async () => { const event = createAPIGatewayEvent({ identifier: data_metagraphs[0].id, - hash: data_metagraph_transactions[0].hash, + hash: data_metagraph_fee_transactions[0].hash, }); const result = (await metagraphHandler.currencyFeeTransaction( From 5e62e2eeaa02e5f62624de1fedfe742f9281619c Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Fri, 13 Jun 2025 18:13:37 -0300 Subject: [PATCH 55/63] feat: PROT 1237 add global snapshot to txs (#184) add global snapshot hash and ordinal to txs --- .../20250529/01_add_staking_to_actions.sql | 148 ++++++++++++------ prisma/schema.prisma | 70 +++++---- src/handlers/actionsHandler.ts | 24 +-- src/handlers/allowSpendsHandler.ts | 79 +++++++++- src/handlers/delegatedStakingHandler.ts | 23 ++- src/handlers/metagraphHandler.ts | 57 ++++++- src/handlers/tokenLocksHandler.ts | 83 ++++++++-- src/response.ts | 5 + tests/handlers/actionsHandler.test.ts | 2 + tests/handlers/allowSpendsHandler.test.ts | 12 ++ .../handlers/delegatedStakingHandler.test.ts | 4 + tests/handlers/metagraphHandler.test.ts | 4 + tests/handlers/tokenLocksHandler.test.ts | 7 + 13 files changed, 395 insertions(+), 123 deletions(-) diff --git a/prisma/migrations/20250529/01_add_staking_to_actions.sql b/prisma/migrations/20250529/01_add_staking_to_actions.sql index 27ae4a0..3498b22 100644 --- a/prisma/migrations/20250529/01_add_staking_to_actions.sql +++ b/prisma/migrations/20250529/01_add_staking_to_actions.sql @@ -1,73 +1,127 @@ -- DAG Tables CREATE OR REPLACE VIEW dag_actions_view AS SELECT - hash, source_addr, amount, created_at, updated_at, 'AllowSpend' as transaction_type, snapshot_hash, + das.hash, source_addr, amount, das.created_at, das.updated_at, + 'AllowSpend' as transaction_type, snapshot_hash as global_snapshot_hash, gs.ordinal as global_snapshot_ordinal, destination_addr, last_valid_epoch_progress AS unlock_epoch, parent_hash, fee - FROM dag_allow_spends + FROM dag_allow_spends das + JOIN global_snapshots gs ON das.snapshot_hash = gs.hash + UNION ALL SELECT - hash, source_addr, amount, created_at, updated_at, 'SpendTransaction', snapshot_hash, - destination_addr, null, allow_spend_ref, null - FROM dag_spend_transactions + dst.hash, dst.source_addr, dst.amount, dst.created_at, dst.updated_at, + 'SpendTransaction', dst.snapshot_hash, gs.ordinal, + dst.destination_addr, null, dst.allow_spend_ref, null + FROM dag_spend_transactions dst + JOIN global_snapshots gs ON dst.snapshot_hash = gs.hash + UNION ALL SELECT - hash, source_addr, amount, created_at, updated_at, 'ExpiredSpendTransaction', snapshot_hash, - null, null, allow_spend_ref, null - FROM dag_expired_spend_transactions + dest.hash, dest.source_addr, dest.amount, dest.created_at, dest.updated_at, + 'ExpiredAllowSpend', dest.snapshot_hash, gs.ordinal, + null, null, dest.allow_spend_ref, null + FROM dag_expired_spend_transactions dest + JOIN global_snapshots gs ON dest.snapshot_hash = gs.hash + UNION ALL SELECT - hash, source_addr, amount, created_at, updated_at, 'TokenLock', snapshot_hash, - null, unlock_epoch, parent_hash, null - FROM dag_token_locks + dtl.hash, dtl.source_addr, dtl.amount, dtl.created_at, dtl.updated_at, + 'TokenLock', dtl.snapshot_hash, gs.ordinal, + null, dtl.unlock_epoch, dtl.parent_hash, null + FROM dag_token_locks dtl + JOIN global_snapshots gs ON dtl.snapshot_hash = gs.hash + UNION ALL - SELECT hash, source_addr, amount, created_at, updated_at, 'TokenUnlock', snapshot_hash, - null, null, lock_reference_hash, null - FROM dag_token_unlocks + SELECT + dtu.hash, dtu.source_addr, dtu.amount, dtu.created_at, dtu.updated_at, + 'TokenUnlock', dtu.snapshot_hash, gs.ordinal, + null, null, dtu.lock_reference_hash, null + FROM dag_token_unlocks dtu + JOIN global_snapshots gs ON dtu.snapshot_hash = gs.hash + UNION ALL SELECT - hash, source_addr, amount, created_at, updated_at, 'DelegateStakeCreate', global_snapshot_hash, - null, null, parent_hash, fee - FROM delegate_stake_create_events + dsce.hash, dsce.source_addr, dsce.amount, dsce.created_at, dsce.updated_at, + 'DelegateStakeCreate', dsce.global_snapshot_hash, gs.ordinal, + null, null, dsce.parent_hash, dsce.fee + FROM delegate_stake_create_events dsce + JOIN global_snapshots gs ON dsce.global_snapshot_hash = gs.hash + UNION ALL SELECT - dswe.hash, dswe.source_addr, dsce.amount, dswe.created_at, dswe.updated_at, 'DelegateStakeWithdraw', dswe.global_snapshot_hash, + dswe.hash, dswe.source_addr, dsce.amount, dswe.created_at, dswe.updated_at, + 'DelegateStakeWithdraw', dswe.global_snapshot_hash, gs.ordinal, null, dswe.unlock_epoch, dswe.stake_create_hash, null FROM delegate_stake_withdraw_events dswe - LEFT JOIN delegate_stake_create_events dsce ON dswe.stake_create_hash = dsce.hash; --- UNION ALL - -- SELECT - -- hash, source_addr, amount, created_at, updated_at, 'Transaction', snapshot_hash, - -- destination_addr, null, parent_hash, fee - -- FROM dag_transactions + LEFT JOIN delegate_stake_create_events dsce ON dswe.stake_create_hash = dsce.hash + JOIN global_snapshots gs ON dswe.global_snapshot_hash = gs.hash; -- Metagraph Tables CREATE OR REPLACE VIEW metagraph_actions_view AS - SELECT hash, source_addr, amount, created_at, updated_at, 'AllowSpend' as transaction_type, snapshot_hash, - destination_addr, last_valid_epoch_progress AS unlock_epoch, parent_hash, fee - FROM metagraph_allow_spends +SELECT + mas.metagraph_id, mas.hash, mas.source_addr, mas.amount, mas.created_at, mas.updated_at, + 'AllowSpend' AS transaction_type, mas.snapshot_hash as metagraph_snapshot_hash, ms.ordinal as metagraph_snapshot_ordinal, + mas.destination_addr, mas.last_valid_epoch_progress AS unlock_epoch, + mas.parent_hash, mas.fee, + gs.hash AS global_snapshot_hash, gs.ordinal AS global_snapshot_ordinal +FROM metagraph_allow_spends mas +JOIN metagraph_snapshots ms + ON mas.metagraph_id = ms.metagraph_id AND mas.snapshot_hash = ms.hash +JOIN global_snapshots gs + ON ms.global_snapshot_hash = gs.hash UNION ALL - SELECT hash, source_addr, amount, created_at, updated_at, 'SpendTransaction', snapshot_hash, - destination_addr, null, allow_spend_ref, null - FROM metagraph_spend_transactions +SELECT + mst.metagraph_id, mst.hash, mst.source_addr, mst.amount, mst.created_at, mst.updated_at, + 'SpendTransaction', mst.snapshot_hash as metagraph_snapshot_hash, ms.ordinal as metagraph_snapshot_ordinal, + mst.destination_addr, NULL, mst.allow_spend_ref, NULL, + gs.hash AS global_snapshot_hash, gs.ordinal AS global_snapshot_ordinal +FROM metagraph_spend_transactions mst +JOIN metagraph_snapshots ms + ON mst.metagraph_id = ms.metagraph_id AND mst.snapshot_hash = ms.hash +JOIN global_snapshots gs + ON ms.global_snapshot_hash = gs.hash UNION ALL - SELECT hash, source_addr, amount, created_at, updated_at, 'ExpiredSpendTransaction', snapshot_hash, - null, null, allow_spend_ref, null - FROM metagraph_expired_spend_transactions +SELECT + mest.metagraph_id, mest.hash, mest.source_addr, mest.amount, mest.created_at, mest.updated_at, + 'ExpiredAllowSpend', mest.snapshot_hash as metagraph_snapshot_hash, ms.ordinal as metagraph_snapshot_ordinal, + NULL, NULL, mest.allow_spend_ref, NULL, + gs.hash AS global_snapshot_hash, gs.ordinal AS global_snapshot_ordinal +FROM metagraph_expired_spend_transactions mest +JOIN metagraph_snapshots ms + ON mest.metagraph_id = ms.metagraph_id AND mest.snapshot_hash = ms.hash +JOIN global_snapshots gs + ON ms.global_snapshot_hash = gs.hash UNION ALL - SELECT hash, source_addr, amount, created_at, updated_at, 'TokenLock' as transaction_type, snapshot_hash, - null, unlock_epoch, parent_hash, null - FROM metagraph_token_locks +SELECT + mtl.metagraph_id, mtl.hash, mtl.source_addr, mtl.amount, mtl.created_at, mtl.updated_at, + 'TokenLock', mtl.snapshot_hash as metagraph_snapshot_hash, ms.ordinal as metagraph_snapshot_ordinal, + NULL, mtl.unlock_epoch, mtl.parent_hash, NULL, + gs.hash AS global_snapshot_hash, gs.ordinal AS global_snapshot_ordinal +FROM metagraph_token_locks mtl +JOIN metagraph_snapshots ms + ON mtl.metagraph_id = ms.metagraph_id AND mtl.snapshot_hash = ms.hash +JOIN global_snapshots gs + ON ms.global_snapshot_hash = gs.hash UNION ALL - SELECT hash, source_addr, amount, created_at, updated_at, 'TokenUnlock', snapshot_hash, - null, null, lock_reference_hash, null - FROM metagraph_token_unlocks +SELECT + mtu.metagraph_id, mtu.hash, mtu.source_addr, mtu.amount, mtu.created_at, mtu.updated_at, + 'TokenUnlock', mtu.snapshot_hash as metagraph_snapshot_hash, ms.ordinal as metagraph_snapshot_ordinal, + NULL, NULL, mtu.lock_reference_hash, NULL, + gs.hash AS global_snapshot_hash, gs.ordinal AS global_snapshot_ordinal +FROM metagraph_token_unlocks mtu +JOIN metagraph_snapshots ms + ON mtu.metagraph_id = ms.metagraph_id AND mtu.snapshot_hash = ms.hash +JOIN global_snapshots gs + ON ms.global_snapshot_hash = gs.hash UNION ALL - SELECT hash, source_addr, amount, created_at, updated_at, 'FeeTransaction', metagraph_snapshot_hash, - destination_addr, null, data_update_ref, null - FROM metagraph_fee_transactions --- UNION ALL --- SELECT hash, source_addr, amount, created_at, updated_at, 'Transaction', snapshot_hash, --- destination_addr, null, parent_hash, fee --- FROM metagraph_transactions; - +SELECT + mft.metagraph_id, mft.hash, mft.source_addr, mft.amount, mft.created_at, mft.updated_at, + 'FeeTransaction', mft.metagraph_snapshot_hash, ms.ordinal, + mft.destination_addr, NULL, mft.data_update_ref, NULL, + gs.hash AS global_snapshot_hash, gs.ordinal AS global_snapshot_ordinal +FROM metagraph_fee_transactions mft +JOIN metagraph_snapshots ms + ON mft.metagraph_id = ms.metagraph_id AND mft.metagraph_snapshot_hash = ms.hash +JOIN global_snapshots gs + ON ms.global_snapshot_hash = gs.hash; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 66db7e5..1217939 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -20,17 +20,18 @@ model abstract_blocks { } model dag_actions_view { - hash String @id @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - transaction_type String @db.VarChar - snapshot_hash String @db.VarChar - destination_addr String? @db.VarChar - unlock_epoch BigInt? - parent_hash String? @db.VarChar - fee BigInt? + hash String @id @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + transaction_type String @db.VarChar + global_snapshot_hash String @db.VarChar + global_snapshot_ordinal BigInt + destination_addr String? @db.VarChar + unlock_epoch BigInt? + parent_hash String? @db.VarChar + fee BigInt? dag_token_lock dag_token_locks? @relation(fields: [hash], references: [hash], map: "dag_token_locks_ref") dag_token_unlock dag_token_unlocks? @relation(fields: [hash], references: [hash], map: "dag_token_unlocks_ref") @@ -38,21 +39,24 @@ model dag_actions_view { dag_spend_transaction dag_spend_transactions? @relation(fields: [hash], references: [hash], map: "dag_spend_transaction_ref") dag_expired_spend_transaction dag_expired_spend_transactions? @relation(fields: [hash], references: [hash], map: "dag_expired_spend_transaction_ref") - global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_actions_view_global_snapshot_ref") + global_snapshot global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_actions_view_global_snapshot_ref") } model metagraph_actions_view { - hash String @id @db.VarChar - source_addr String @db.VarChar - amount BigInt - created_at DateTime @default(now()) @db.Timestamp(6) - updated_at DateTime @default(now()) @db.Timestamp(6) - transaction_type String - snapshot_hash String @db.VarChar - destination_addr String? @db.VarChar - unlock_epoch BigInt? - parent_hash String? @db.VarChar - fee BigInt? + hash String @id @db.VarChar + source_addr String @db.VarChar + amount BigInt + created_at DateTime @default(now()) @db.Timestamp(6) + updated_at DateTime @default(now()) @db.Timestamp(6) + transaction_type String + metagraph_snapshot_hash String @db.VarChar + metagraph_snapshot_ordinal BigInt + global_snapshot_hash String @db.VarChar + global_snapshot_ordinal BigInt + destination_addr String? @db.VarChar + unlock_epoch BigInt? + parent_hash String? @db.VarChar + fee BigInt? metagraph_token_lock metagraph_token_locks? @relation(fields: [hash], references: [hash], map: "metagraph_token_locks_ref") metagraph_token_unlock metagraph_token_unlocks? @relation(fields: [hash], references: [hash], map: "metagraph_token_unlocks_ref") @@ -60,7 +64,7 @@ model metagraph_actions_view { metagraph_spend_transaction metagraph_spend_transactions? @relation(fields: [hash], references: [hash], map: "metagraph_spend_transaction_ref") metagraph_expired_spend_transaction metagraph_expired_spend_transactions? @relation(fields: [hash], references: [hash], map: "metagraph_expired_spend_transaction_ref") metagraph_fee_transaction metagraph_fee_transactions? @relation(fields: [hash], references: [hash], map: "metagraph_fee_transaction_ref") - metagraph_snapshot metagraph_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_actions_view_metagraph_snapshot_ref") + metagraph_snapshot metagraph_snapshots @relation(fields: [metagraph_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_actions_view_metagraph_snapshot_ref") } model addresses { @@ -210,7 +214,7 @@ model dag_token_locks { addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_locks_source_addr_fk") global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_lock_global_snapshot_fk") dag_token_unlock dag_token_unlocks? - + dag_actions_view dag_actions_view[] @@unique([ordinal], map: "dag_token_locks_unique") @@ -588,16 +592,16 @@ model delegate_stake_create_events { created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - global_snapshots global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) + global_snapshot global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction) - + delegated_from delegate_stake_create_events? @relation(name: "delegation_self", fields: [transfer_from_hash], references: [hash], onDelete: NoAction, onUpdate: NoAction) delegated_to delegate_stake_create_events? @relation(name: "delegation_self") delegate_stake_withdraw_events delegate_stake_withdraw_events[] delegate_stake_rewards delegate_stake_rewards[] delegate_stake_total_rewards delegate_stake_total_rewards_view? total_rewards_view token_lock_total_rewards_view? @relation( fields: [lock_reference_hash], references: [lock_reference_hash], onDelete: Cascade, onUpdate: NoAction) - + @@index([global_snapshot_hash]) @@index([source_addr]) @@index([lock_reference_hash]) @@ -613,7 +617,7 @@ model delegate_stake_withdraw_events { created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - global_snapshots global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) + global_snapshot global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction) delegate_stake_create_event delegate_stake_create_events @relation(fields: [stake_create_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) } @@ -627,9 +631,9 @@ model delegate_stake_rewards { updated_at DateTime @default(now()) @db.Timestamp(6) stake_create_hash String @db.VarChar - addresses addresses @relation(fields: [address], references: [address], onDelete: Cascade, onUpdate: NoAction) - global_snapshots global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) - stake_create delegate_stake_create_events @relation(fields: [stake_create_hash], references: [hash]) + addresses addresses @relation(fields: [address], references: [address], onDelete: Cascade, onUpdate: NoAction) + global_snapshot global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) + stake_create delegate_stake_create_events @relation(fields: [stake_create_hash], references: [hash]) @@unique([global_snapshot_hash, address, node_id, rewards], map: "delegate_stake_rewards_changes_unique") } @@ -645,5 +649,5 @@ model token_lock_total_rewards_view { lock_reference_hash String @id total_rewards BigInt - delegate_stake_create_events delegate_stake_create_events[] + delegate_stake_create_events delegate_stake_create_events[] } \ No newline at end of file diff --git a/src/handlers/actionsHandler.ts b/src/handlers/actionsHandler.ts index aff6777..b3fa5b2 100644 --- a/src/handlers/actionsHandler.ts +++ b/src/handlers/actionsHandler.ts @@ -8,11 +8,11 @@ const prisma = new PrismaClient(); const allowedTypes = [ "AllowSpend", + "SpendTransaction", + "ExpiredAllowSpend", "TokenLock", "TokenUnlock", - "SpendTransaction", "FeeTransaction", - "ExpiredSpendTransaction", "DelegateStakeCreate", "DelegateStakeWithdraw", ] as const; @@ -47,20 +47,14 @@ const actionResponse = (transaction) => ({ unlockEpoch: transaction.unlock_epoch ?? null, parentHash: transaction.parent_hash ?? null, timestamp: transaction.created_at, - globalSnapshotOrdinal: transaction.global_snapshot?.ordinal, - metagraphSnapshotOrdinal: transaction.metagraph_snapshot?.ordinal, + globalSnapshotHash: transaction.global_snapshot_hash, + metagraphSnapshotHash: transaction.metagraph_snapshot_hash, + globalSnapshotOrdinal: transaction.global_snapshot_ordinal, + metagraphSnapshotOrdinal: transaction.metagraph_snapshot_ordinal, }); export const actionsResponse = (ts) => ts.map(actionResponse); -const dagInclude = { - global_snapshot: { select: { hash: true, ordinal: true } }, -}; - -const metagraphInclude = { - metagraph_snapshot: { select: { hash: true, ordinal: true } }, -}; - export const dagActions = async ( event: APIGatewayProxyEvent ): Promise => { @@ -72,7 +66,6 @@ export const dagActions = async ( hashCursor, { where: { transaction_type: { in: selectedTransactions } }, - include: dagInclude, orderBy: [{ created_at: "desc" }, { hash: "desc" }], }, prisma.dag_actions_view.findMany, @@ -98,7 +91,6 @@ export const globalSnapshotActions = async ( global_snapshot: filter, transaction_type: { in: selectedTransactions }, }, - include: dagInclude, orderBy: [{ created_at: "desc" }, { hash: "asc" }], }, prisma.dag_actions_view.findMany, @@ -135,7 +127,6 @@ export const dagAddressActions = async ( ], transaction_type: { in: selectedTransactions }, }, - include: dagInclude, orderBy: [{ created_at: "desc" }, { hash: "asc" }], }, prisma.dag_actions_view.findMany, @@ -163,7 +154,6 @@ export const currencyActions = async ( metagraph_snapshot: { metagraph_id }, transaction_type: { in: selectedTransactions }, }, - include: metagraphInclude, orderBy: [{ created_at: "desc" }, { hash: "asc" }], }, prisma.metagraph_actions_view.findMany, @@ -192,7 +182,6 @@ export const currencySnapshotActions = async ( metagraph_snapshot: { metagraph_id, ...filter }, transaction_type: { in: selectedTransactions }, }, - include: metagraphInclude, orderBy: [{ created_at: "desc" }, { hash: "asc" }], }, prisma.metagraph_actions_view.findMany, @@ -231,7 +220,6 @@ export const currencyAddressActions = async ( ], transaction_type: { in: selectedTransactions }, }, - include: metagraphInclude, orderBy: [{ created_at: "desc" }, { hash: "asc" }], }, prisma.metagraph_actions_view.findMany, diff --git a/src/handlers/allowSpendsHandler.ts b/src/handlers/allowSpendsHandler.ts index 6dea8c2..ed50e8d 100644 --- a/src/handlers/allowSpendsHandler.ts +++ b/src/handlers/allowSpendsHandler.ts @@ -7,6 +7,7 @@ import { toCreatedAtOrdinalCursor, } from "../pagination"; import { respond, handleError } from "../response"; +import { includes } from "lodash"; const prisma = new PrismaClient(); @@ -21,6 +22,12 @@ const allowSpendResponse = (transaction) => ({ fee: transaction.fee, snapshotHash: transaction.snapshot_hash, timestamp: transaction.created_at, + globalSnapshotHash: + transaction.global_snapshot?.hash ?? + transaction.metagraph_snapshot?.global_snapshot?.hash, + globalSnapshotOrdinal: + transaction.global_snapshot?.ordinal ?? + transaction.metagraph_snapshot?.global_snapshot?.ordinal, }); const allowSpendResponses = (txs) => txs.map(allowSpendResponse); @@ -34,6 +41,12 @@ const spendTransactionResponse = (transaction) => ({ allowSpendHash: transaction.allow_spend_ref, snapshotHash: transaction.snapshot_hash, timestamp: transaction.created_at, + globalSnapshotHash: + transaction.global_snapshot?.hash ?? + transaction.metagraph_snapshot?.global_snapshot?.hash, + globalSnapshotOrdinal: + transaction.global_snapshot?.ordinal ?? + transaction.metagraph_snapshot?.global_snapshot?.ordinal, }); const spendTransactionResponses = (txs) => txs.map(spendTransactionResponse); @@ -46,6 +59,12 @@ const spendExpiredResponse = (transaction) => ({ allowSpendHash: transaction.allow_spend_ref, snapshotHash: transaction.snapshot_hash, timestamp: transaction.created_at, + globalSnapshotHash: + transaction.global_snapshot?.hash ?? + transaction.metagraph_snapshot?.global_snapshot?.hash, + globalSnapshotOrdinal: + transaction.global_snapshot?.ordinal ?? + transaction.metagraph_snapshot?.global_snapshot?.ordinal, }); const spendExpiredResponses = (txs) => txs.map(spendExpiredResponse); @@ -74,6 +93,25 @@ const ifActiveMetagraphAllowSpend = (event) => { : {}; }; +const dagInclude = { + global_snapshot: { select: { hash: true, ordinal: true } }, +}; + +const metagraphInclude = { + metagraph_snapshot: { + select: { + hash: true, + ordinal: true, + global_snapshot: { + select: { + hash: true, + ordinal: true, + }, + }, + }, + }, +}; + export const allowSpend = async ( event: APIGatewayProxyEvent ): Promise => { @@ -82,6 +120,7 @@ export const allowSpend = async ( const allowSpend = await prisma.dag_allow_spends.findUnique({ where: { hash }, + include: dagInclude, }); return respond(allowSpend, allowSpendResponse); @@ -97,7 +136,11 @@ export const allowSpends = async ( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { where: ifActiveAllowSpend(event), orderBy: { created_at: "desc" } }, + { + where: ifActiveAllowSpend(event), + include: dagInclude, + orderBy: { created_at: "desc" }, + }, prisma.dag_allow_spends.findMany, allowSpendResponses ); @@ -116,6 +159,7 @@ export const globalSnapshotAllowSpends = async ( fromCreatedAtOrdinalCursor, { where: { global_snapshot: filter, ...ifActiveAllowSpend(event) }, + include: dagInclude, orderBy: { created_at: "desc" }, }, prisma.dag_allow_spends.findMany, @@ -141,6 +185,7 @@ export const addressAllowSpends = async ( OR: [{ source_addr: address }, { destination_addr: address }], ...ifActiveAllowSpend(event), }, + include: dagInclude, orderBy: { created_at: "desc" }, }, prisma.dag_allow_spends.findMany, @@ -159,6 +204,7 @@ export const spendTransaction = async ( const spend = await prisma.dag_spend_transactions.findUnique({ where: { hash }, + include: dagInclude, }); return respond(spend, spendTransactionResponse); @@ -175,7 +221,7 @@ export const spendTransactions = async ( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { orderBy: { created_at: "desc" } }, + { include: dagInclude, orderBy: { created_at: "desc" } }, prisma.dag_spend_transactions.findMany, spendTransactionResponses ); @@ -199,6 +245,7 @@ export const globalSnapshotSpendTransactions = async ( where: { dag_allow_spend: { global_snapshot: filter }, }, + include: dagInclude, orderBy: { created_at: "desc" }, }, prisma.dag_spend_transactions.findMany, @@ -223,6 +270,7 @@ export const addressSpendTransactions = async ( where: { OR: [{ source_addr: address }, { destination_addr: address }], }, + include: dagInclude, orderBy: { created_at: "desc" }, }, prisma.dag_spend_transactions.findMany, @@ -241,7 +289,7 @@ export const allowSpendExpirations = async ( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { orderBy: { created_at: "desc" } }, + { include: dagInclude, orderBy: { created_at: "desc" } }, prisma.dag_expired_spend_transactions.findMany, spendExpiredResponses ); @@ -258,6 +306,7 @@ export const allowSpendExpiration = async ( const expired = await prisma.dag_expired_spend_transactions.findUnique({ where: { hash }, + include: dagInclude, }); return respond(expired, spendExpiredResponse); @@ -281,6 +330,7 @@ export const globalSnapshotAllowSpendExpirations = async ( where: { dag_allow_spend: { global_snapshot: filter }, }, + include: dagInclude, orderBy: { created_at: "desc" }, }, prisma.dag_expired_spend_transactions.findMany, @@ -308,6 +358,7 @@ export const addressAllowSpendExpirations = async ( { dag_allow_spend: { destination_addr: address } }, ], }, + include: dagInclude, orderBy: { created_at: "desc" }, }, prisma.dag_expired_spend_transactions.findMany, @@ -330,6 +381,7 @@ export const currencyAllowSpends = async ( fromCreatedAtOrdinalCursor, { where: { metagraph_id, ...ifActiveMetagraphAllowSpend(event) }, + include: metagraphInclude, orderBy: { created_at: "desc" }, }, prisma.metagraph_allow_spends.findMany, @@ -348,6 +400,7 @@ export const currencyAllowSpend = async ( const allowSpend = await prisma.metagraph_allow_spends.findUnique({ where: { metagraph_id, hash }, + include: metagraphInclude, }); return respond(allowSpend, allowSpendResponse); @@ -373,6 +426,7 @@ export const currencySnapshotAllowSpends = async ( metagraph_snapshot: filter, ...ifActiveMetagraphAllowSpend(event), }, + include: metagraphInclude, orderBy: { created_at: "desc" }, }, prisma.metagraph_allow_spends.findMany, @@ -399,6 +453,7 @@ export const currencyAddressAllowSpends = async ( OR: [{ source_addr: address }, { destination_addr: address }], ...ifActiveMetagraphAllowSpend(event), }, + include: metagraphInclude, orderBy: { created_at: "desc" }, }, prisma.metagraph_allow_spends.findMany, @@ -419,7 +474,11 @@ export const currencySpendTransactions = async ( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { where: { metagraph_id }, orderBy: { created_at: "desc" } }, + { + where: { metagraph_id }, + include: metagraphInclude, + orderBy: { created_at: "desc" }, + }, prisma.metagraph_spend_transactions.findMany, spendTransactionResponses ); @@ -436,6 +495,7 @@ export const currencySpendTransaction = async ( const spend = await prisma.metagraph_spend_transactions.findUnique({ where: { metagraph_id, hash }, + include: metagraphInclude, }); return respond(spend, spendTransactionResponse); @@ -462,6 +522,7 @@ export const currencySnapshotSpendTransactions = async ( metagraph_snapshot: filter, }, }, + include: metagraphInclude, orderBy: { created_at: "desc" }, }, prisma.metagraph_spend_transactions.findMany, @@ -486,6 +547,7 @@ export const currencyAddressSpendTransactions = async ( where: { OR: [{ source_addr: address }, { destination_addr: address }], }, + include: metagraphInclude, orderBy: { created_at: "desc" }, }, prisma.metagraph_spend_transactions.findMany, @@ -506,7 +568,11 @@ export const currencyAllowSpendExpirations = async ( extractPagination(event), toCreatedAtOrdinalCursor, fromCreatedAtOrdinalCursor, - { where: { metagraph_id }, orderBy: { created_at: "desc" } }, + { + where: { metagraph_id }, + include: metagraphInclude, + orderBy: { created_at: "desc" }, + }, prisma.metagraph_expired_spend_transactions.findMany, spendExpiredResponses ); @@ -524,6 +590,7 @@ export const currencyAllowSpendExpiration = async ( const expired = await prisma.metagraph_expired_spend_transactions.findUnique({ where: { metagraph_id, hash }, + include: metagraphInclude, }); return respond(expired, spendExpiredResponse); @@ -550,6 +617,7 @@ export const currencySnapshotAllowSpendExpirations = async ( metagraph_snapshot: filter, }, }, + include: metagraphInclude, orderBy: { created_at: "desc" }, }, prisma.metagraph_expired_spend_transactions.findMany, @@ -578,6 +646,7 @@ export const currencyAddressAllowSpendExpirations = async ( { metagraph_allow_spend: { destination_addr: address } }, ], }, + include: metagraphInclude, orderBy: { created_at: "desc" }, }, prisma.metagraph_expired_spend_transactions.findMany, diff --git a/src/handlers/delegatedStakingHandler.ts b/src/handlers/delegatedStakingHandler.ts index 99d8957..e4a996d 100644 --- a/src/handlers/delegatedStakingHandler.ts +++ b/src/handlers/delegatedStakingHandler.ts @@ -54,6 +54,8 @@ const delegateStakeCreateResponse = (event) => ({ type: event.transfer_from_hash ? "transfer" : "create", status: stakeStatus(event), timestamp: event.created_at, + globalSnapshotHash: event.global_snapshot_hash, + globalSnapshotOrdinal: event.global_snapshot?.ordinal, }); const delegateStakeCreateResponses = (txs) => @@ -63,10 +65,11 @@ const delegateStakeWithdrawResponse = (event) => ({ hash: event.hash, source: event.source_addr, stake: event.delegate_stake_create_event, - globalSnapshotHash: event.global_snapshot_hash, unlockEpoch: event.unlock_epoch, status: withdrawalStatus(event.is_completed), timestamp: event.created_at, + globalSnapshotHash: event.global_snapshot_hash, + globalSnapshotOrdinal: event.global_snapshot?.ordinal, }); const delegateStakeWithdrawResponses = (txs) => @@ -175,6 +178,7 @@ export const delegatedStakes = async ( delegate_stake_withdraw_events: true, delegated_to: true, delegated_from: true, + global_snapshot: { select: { ordinal: true } }, }, orderBy: [{ ordinal: "desc" }], }, @@ -193,6 +197,7 @@ export const delegatedStake = async (event) => { delegate_stake_withdraw_events: true, delegated_to: true, delegated_from: true, + global_snapshot: { select: { ordinal: true } }, }, }); @@ -221,6 +226,7 @@ export const addressDelegatedStakes = async (event) => { delegate_stake_withdraw_events: true, delegated_to: true, delegated_from: true, + global_snapshot: { select: { ordinal: true } }, }, orderBy: [{ ordinal: "desc" }], }, @@ -235,7 +241,10 @@ export const delegatedStakeWithdrawals = async (event) => { hashCursor, hashCursor, { - include: { delegate_stake_create_event: true }, + include: { + delegate_stake_create_event: true, + global_snapshot: { select: { ordinal: true } }, + }, orderBy: [{ created_at: "desc" }], }, prisma.delegate_stake_withdraw_events.findMany, @@ -249,7 +258,10 @@ export const delegatedStakeWithdrawal = async (event) => { const withdrawal = await prisma.delegate_stake_withdraw_events.findUnique({ where: { hash }, - include: { delegate_stake_create_event: true }, + include: { + delegate_stake_create_event: true, + global_snapshot: { select: { ordinal: true } }, + }, }); return respond(withdrawal, delegateStakeWithdrawResponse); @@ -268,7 +280,10 @@ export const addressDelegatedStakeWithdrawals = async (event) => { where: { source_addr: address, }, - include: { delegate_stake_create_event: true }, + include: { + delegate_stake_create_event: true, + global_snapshot: { select: { ordinal: true } }, + }, orderBy: [{ created_at: "desc" }], }, prisma.delegate_stake_withdraw_events.findMany, diff --git a/src/handlers/metagraphHandler.ts b/src/handlers/metagraphHandler.ts index 899049f..4b27e42 100644 --- a/src/handlers/metagraphHandler.ts +++ b/src/handlers/metagraphHandler.ts @@ -64,6 +64,19 @@ const mgIdHashFromCursor = (row) => ({ hash: row.hash, }); +const includeGlobalSnapshotOrdinalFromMetagraph = { + metagraph_snapshot: { + select: { + global_snapshot: { + select: { + hash: true, + ordinal: true, + }, + }, + }, + }, +}; + const metagraphSnapshotWhere = async (metagraph_id, term) => { if (term == "latest") { const latestSnapshotHash = (await latestMetagraphSnapshot(metagraph_id)) @@ -233,7 +246,18 @@ const metagraphTransactionsQuery = async ( const query = { ...baseQuery, include: { - metagraph_snapshot: { select: { hash: true, ordinal: true } }, + metagraph_snapshot: { + select: { + hash: true, + ordinal: true, + global_snapshot: { + select: { + hash: true, + ordinal: true, + }, + }, + }, + }, }, orderBy: [ { metagraph_id: "desc" }, @@ -471,7 +495,18 @@ export const currencyFeeTransaction = async ( }, }, include: { - metagraph_snapshot: { select: { hash: true, ordinal: true } }, + metagraph_snapshot: { + select: { + hash: true, + ordinal: true, + global_snapshot: { + select: { + hash: true, + ordinal: true, + }, + }, + }, + }, }, }); @@ -489,7 +524,18 @@ const metagraphFeeTransactionsQuery = async ( const query = { ...baseQuery, include: { - metagraph_snapshot: { select: { hash: true, ordinal: true } }, + metagraph_snapshot: { + select: { + hash: true, + ordinal: true, + global_snapshot: { + select: { + hash: true, + ordinal: true, + }, + }, + }, + }, }, orderBy: [{ metagraph_id: "asc" }, { hash: "asc" }], }; @@ -522,6 +568,7 @@ export const currencyFeeTransactions = async ( where: { metagraph_id: metagraph_id, }, + include: includeGlobalSnapshotOrdinalFromMetagraph, }; return metagraphFeeTransactionsQuery(where, event); @@ -547,6 +594,7 @@ export const currencySnapshotFeeTransactions = async ( ...mgSnapshotWhere, }, }, + include: includeGlobalSnapshotOrdinalFromMetagraph, }; return metagraphFeeTransactionsQuery(where, event); @@ -568,6 +616,7 @@ export const currencyFeeTransactionsByAddress = async ( metagraph_id: metagraph_id, OR: [{ source_addr: address }, { destination_addr: address }], }, + include: includeGlobalSnapshotOrdinalFromMetagraph, }; return metagraphFeeTransactionsQuery(where, event); } catch (error) { @@ -588,6 +637,7 @@ export const currencyFeeTransactionsBySource = async ( metagraph_id: metagraph_id, source_addr: address, }, + include: includeGlobalSnapshotOrdinalFromMetagraph, }; return metagraphFeeTransactionsQuery(where, event); } catch (error) { @@ -608,6 +658,7 @@ export const currencyFeeTransactionsByDestination = async ( metagraph_id: metagraph_id, destination_addr: address, }, + include: includeGlobalSnapshotOrdinalFromMetagraph, }; return metagraphFeeTransactionsQuery(where, event); } catch (error) { diff --git a/src/handlers/tokenLocksHandler.ts b/src/handlers/tokenLocksHandler.ts index 1ce5eba..0cb70e2 100644 --- a/src/handlers/tokenLocksHandler.ts +++ b/src/handlers/tokenLocksHandler.ts @@ -25,6 +25,8 @@ const dagTokenLockResponse = (transaction) => ({ ...commonTokenLockResponse(transaction), unlockedAtOrdinal: transaction.dag_token_unlock?.global_snapshot.ordinal ?? null, + globalSnapshotHash: transaction.snapshot_hash, + globalSnapshotOrdinal: transaction.global_snapshot.ordinal, }); const dagTokenLockResponses = (txs) => txs.map(dagTokenLockResponse); @@ -33,6 +35,9 @@ const metagraphTokenLockResponse = (transaction) => ({ ...commonTokenLockResponse(transaction), unlockedAtOrdinal: transaction.metagraph_token_unlock?.metagraph_snapshot.ordinal ?? null, + metagraphSnapshotOrdinal: transaction.metagraph_snapshot.ordinal, + globalSnapshotHash: transaction.metagraph_snapshot.global_snapshot.hash, + globalSnapshotOrdinal: transaction.metagraph_snapshot.global_snapshot.ordinal, }); const metagraphTokenLockResponses = (txs) => @@ -49,6 +54,7 @@ const commonTokenUnlockResponse = (transaction) => ({ const dagTokenUnlockResponse = (transaction) => ({ ...commonTokenUnlockResponse(transaction), + globalSnapshotHash: transaction.snapshot_hash, globalSnapshotOrdinal: transaction.global_snapshot.ordinal, }); @@ -57,6 +63,8 @@ const dagTokenUnlockResponses = (txs) => txs.map(dagTokenUnlockResponse); const metagraphTokenUnlockResponse = (transaction) => ({ ...commonTokenUnlockResponse(transaction), metagraphSnapshotOrdinal: transaction.metagraph_snapshot.ordinal, + globalSnapshotHash: transaction.metagraph_snapshot.global_snapshot.hash, + globalSnapshotOrdinal: transaction.metagraph_snapshot.global_snapshot.ordinal, }); const metagraphTokenUnlockResponses = (txs) => @@ -86,6 +94,20 @@ const includeMetagraphUnlockOrdinal = { }, }; +const includeGlobalSnapshotOrdinalFromMetagraph = { + metagraph_snapshot: { + select: { + ordinal: true, + global_snapshot: { + select: { + hash: true, + ordinal: true, + }, + }, + }, + }, +}; + const ifActiveDagTokenLock = (event) => { const { active } = event.queryStringParameters || {}; return active === "true" @@ -113,7 +135,10 @@ export const tokenLocks = async ( fromCreatedAtOrdinalCursor, { where: ifActiveDagTokenLock(event), - include: includeDagUnlockOrdinal, + include: { + ...includeDagUnlockOrdinal, + ...includeDagGlobalSnapshotOrdinal, + }, orderBy: [{ ordinal: "desc" }, { created_at: "desc" }], }, prisma.dag_token_locks.findMany, @@ -129,7 +154,10 @@ export const tokenLock = async ( const lock = await prisma.dag_token_locks.findUnique({ where: { hash }, - include: includeDagUnlockOrdinal, + include: { + ...includeDagUnlockOrdinal, + ...includeDagGlobalSnapshotOrdinal, + }, }); return respond(lock, dagTokenLockResponse); @@ -154,7 +182,10 @@ export const globalSnapshotTokenLocks = async ( global_snapshot: filter, ...ifActiveDagTokenLock(event), }, - include: includeDagUnlockOrdinal, + include: { + ...includeDagUnlockOrdinal, + ...includeDagGlobalSnapshotOrdinal, + }, orderBy: [{ ordinal: "desc" }, { created_at: "desc" }], }, prisma.dag_token_locks.findMany, @@ -177,7 +208,10 @@ export const addressTokenLocks = async ( fromCreatedAtOrdinalCursor, { where: { source_addr: address, ...ifActiveDagTokenLock(event) }, - include: includeDagUnlockOrdinal, + include: { + ...includeDagUnlockOrdinal, + ...includeDagGlobalSnapshotOrdinal, + }, orderBy: [{ ordinal: "desc" }, { created_at: "desc" }], }, prisma.dag_token_locks.findMany, @@ -290,7 +324,10 @@ export const metagraphTokenLocks = async ( fromCreatedAtOrdinalCursor, { where: { metagraph_id, ...ifActiveMetagraphTokenLock(event) }, - include: includeMetagraphUnlockOrdinal, + include: { + ...includeMetagraphUnlockOrdinal, + ...includeGlobalSnapshotOrdinalFromMetagraph, + }, orderBy: [{ ordinal: "desc" }, { created_at: "desc" }], }, prisma.metagraph_token_locks.findMany, @@ -306,9 +343,11 @@ export const metagraphTokenLock = async ( const lock = await prisma.metagraph_token_locks.findUnique({ where: { metagraph_id, hash }, - include: includeMetagraphUnlockOrdinal, + include: { + ...includeMetagraphUnlockOrdinal, + ...includeGlobalSnapshotOrdinalFromMetagraph, + }, }); - return respond(lock, metagraphTokenLockResponse); } catch (error) { return handleError(error); @@ -331,7 +370,10 @@ export const metagraphSnapshotTokenLocks = async ( metagraph_id, metagraph_snapshot: filter, }, - include: includeMetagraphUnlockOrdinal, + include: { + ...includeMetagraphUnlockOrdinal, + ...includeGlobalSnapshotOrdinalFromMetagraph, + }, orderBy: [{ ordinal: "desc" }, { created_at: "desc" }], }, prisma.metagraph_token_locks.findMany, @@ -354,7 +396,10 @@ export const metagraphAddressTokenLocks = async ( fromCreatedAtOrdinalCursor, { where: { metagraph_id, source_addr: address }, - include: includeMetagraphUnlockOrdinal, + include: { + ...includeMetagraphUnlockOrdinal, + ...includeGlobalSnapshotOrdinalFromMetagraph, + }, orderBy: [{ ordinal: "desc" }, { created_at: "desc" }], }, prisma.metagraph_token_locks.findMany, @@ -375,7 +420,10 @@ export const metagraphTokenUnlocks = async ( fromCreatedAtOrdinalCursor, { where: { metagraph_id }, - include: includeMetagraphSnapshotOrdinal, + include: { + ...includeMetagraphSnapshotOrdinal, + ...includeGlobalSnapshotOrdinalFromMetagraph, + }, orderBy: [ { metagraph_snapshot: { ordinal: "desc" } }, { created_at: "desc" }, @@ -394,7 +442,10 @@ export const metagraphTokenUnlock = async ( const unlock = await prisma.metagraph_token_unlocks.findUnique({ where: { metagraph_id, hash }, - include: includeMetagraphSnapshotOrdinal, + include: { + ...includeMetagraphSnapshotOrdinal, + ...includeGlobalSnapshotOrdinalFromMetagraph, + }, }); return respond(unlock, metagraphTokenUnlockResponse); @@ -419,7 +470,10 @@ export const metagraphSnapshotTokenUnlocks = async ( metagraph_id, metagraph_snapshot: filter, }, - include: includeMetagraphSnapshotOrdinal, + include: { + ...includeMetagraphSnapshotOrdinal, + ...includeGlobalSnapshotOrdinalFromMetagraph, + }, orderBy: [ { metagraph_snapshot: { ordinal: "desc" } }, { created_at: "desc" }, @@ -445,7 +499,10 @@ export const metagraphAddressTokenUnlocks = async ( fromCreatedAtOrdinalCursor, { where: { metagraph_id, source_addr: address }, - include: includeMetagraphSnapshotOrdinal, + include: { + ...includeMetagraphSnapshotOrdinal, + ...includeGlobalSnapshotOrdinalFromMetagraph, + }, orderBy: [ { metagraph_snapshot: { ordinal: "desc" } }, { created_at: "desc" }, diff --git a/src/response.ts b/src/response.ts index 479651e..3c095ec 100644 --- a/src/response.ts +++ b/src/response.ts @@ -65,6 +65,11 @@ export const transactionResponse = (transaction) => { snapshotOrdinal: snapshot.ordinal, transactionOriginal: transaction.transaction_original, timestamp: transaction.created_at, + globalSnapshotHash: + transaction.metagraph_snapshot?.global_snapshot.hash ?? snapshot.hash, + globalSnapshotOrdinal: + transaction.metagraph_snapshot?.global_snapshot.ordinal ?? + snapshot.ordinal, }; }; diff --git a/tests/handlers/actionsHandler.test.ts b/tests/handlers/actionsHandler.test.ts index 436f7d7..8f3c546 100644 --- a/tests/handlers/actionsHandler.test.ts +++ b/tests/handlers/actionsHandler.test.ts @@ -69,6 +69,8 @@ const validateAction = (action) => { if (action.currencyId !== null) { expect(typeof action.currencyId).toBe("string"); } + expect(action.globalSnapshotHash).toBeDefined(); + expect(action.globalSnapshotOrdinal).toBeDefined(); }; describe("actionsHandler", () => { diff --git a/tests/handlers/allowSpendsHandler.test.ts b/tests/handlers/allowSpendsHandler.test.ts index 9c36dcf..e2ea3fd 100644 --- a/tests/handlers/allowSpendsHandler.test.ts +++ b/tests/handlers/allowSpendsHandler.test.ts @@ -33,6 +33,8 @@ const validateDagAllowSpend = (entry) => { expect(Number(entry.ordinal)).toBe(Number(match.ordinal)); expect(entry.snapshotHash).toBe(match.snapshot_hash); expect(entry.timestamp).toBeDefined(); + expect(entry.globalSnapshotHash).toBeDefined(); + expect(entry.globalSnapshotOrdinal).toBeDefined(); }; const validateDagSpendTransaction = (entry) => { @@ -46,6 +48,8 @@ const validateDagSpendTransaction = (entry) => { expect(entry.allowSpendHash).toBe(match.allow_spend_ref); expect(entry.snapshotHash).toBe(match.snapshot_hash); expect(entry.timestamp).toBeDefined(); + expect(entry.globalSnapshotHash).toBeDefined(); + expect(entry.globalSnapshotOrdinal).toBeDefined(); }; const validateDagExpiredSpend = (entry) => { @@ -60,6 +64,8 @@ const validateDagExpiredSpend = (entry) => { expect(entry.allowSpendHash).toBe(match.allow_spend_ref); expect(entry.snapshotHash).toBe(match.snapshot_hash); expect(entry.timestamp).toBeDefined(); + expect(entry.globalSnapshotHash).toBeDefined(); + expect(entry.globalSnapshotOrdinal).toBeDefined(); }; const validateMgAllowSpend = (entry) => { @@ -73,6 +79,8 @@ const validateMgAllowSpend = (entry) => { expect(entry.destination).toBe(match.destination_addr); expect(Number(entry.fee)).toBe(Number(match.fee)); expect(entry.timestamp).toBeDefined(); + expect(entry.globalSnapshotHash).toBeDefined(); + expect(entry.globalSnapshotOrdinal).toBeDefined(); }; const validateMgSpendTransaction = (entry) => { @@ -85,6 +93,8 @@ const validateMgSpendTransaction = (entry) => { expect(entry.allowSpendHash).toBe(match.allow_spend_ref); expect(entry.snapshotHash).toBe(match.snapshot_hash); expect(entry.timestamp).toBeDefined(); + expect(entry.globalSnapshotHash).toBeDefined(); + expect(entry.globalSnapshotOrdinal).toBeDefined(); }; const validateMgExpiredSpend = (entry) => { @@ -97,6 +107,8 @@ const validateMgExpiredSpend = (entry) => { expect(entry.allowSpendHash).toBe(match.allow_spend_ref); expect(entry.snapshotHash).toBe(match.snapshot_hash); expect(entry.timestamp).toBeDefined(); + expect(entry.globalSnapshotHash).toBeDefined(); + expect(entry.globalSnapshotOrdinal).toBeDefined(); }; describe("AllowSpends Handler Integration Tests", () => { diff --git a/tests/handlers/delegatedStakingHandler.test.ts b/tests/handlers/delegatedStakingHandler.test.ts index a878e02..6b64c8d 100644 --- a/tests/handlers/delegatedStakingHandler.test.ts +++ b/tests/handlers/delegatedStakingHandler.test.ts @@ -45,6 +45,8 @@ const validateCreateStake = (tx) => { expect(tx.fee).toBeBigInt(match.fee); expect(tx.timestamp).toBeDefined(); expect(tx.type).toBe(match.transfer_from_hash ? "transfer" : "create"); + expect(tx.globalSnapshotHash).toBeDefined(); + expect(tx.globalSnapshotOrdinal).toBeDefined(); }; const validateWithdrawStake = (tx) => { @@ -58,6 +60,8 @@ const validateWithdrawStake = (tx) => { match.is_completed ? "withdrawalComplete" : "pendingWithdrawal" ); expect(tx.timestamp).toBeDefined(); + expect(tx.globalSnapshotHash).toBeDefined(); + expect(tx.globalSnapshotOrdinal).toBeDefined(); }; const validateStakingPosition = (tx) => { diff --git a/tests/handlers/metagraphHandler.test.ts b/tests/handlers/metagraphHandler.test.ts index 18733dd..1773b8e 100644 --- a/tests/handlers/metagraphHandler.test.ts +++ b/tests/handlers/metagraphHandler.test.ts @@ -193,6 +193,8 @@ const validateTransaction = (tx, expected) => { expect(tx.snapshotOrdinal).toBe(Number(dbSnapshot.ordinal)); expect(tx.transactionOriginal).toEqual(expected.transaction_original); expect(+new Date(tx.timestamp)).toBe(+new Date(expected.created_at)); + expect(tx.globalSnapshotHash).toBeDefined(); + expect(tx.globalSnapshotOrdinal).toBeDefined(); }; const validateFeeTransaction = (tx, expected) => { @@ -207,6 +209,8 @@ const validateFeeTransaction = (tx, expected) => { expect(tx.snapshotHash).toBe(dbSnapshot.hash); expect(tx.snapshotOrdinal).toBe(Number(dbSnapshot.ordinal)); expect(+new Date(tx.timestamp)).toBe(+new Date(expected.created_at)); + expect(tx.globalSnapshotHash).toBeDefined(); + expect(tx.globalSnapshotOrdinal).toBeDefined(); }; describe("Metagraph Handler Integration Tests", () => { diff --git a/tests/handlers/tokenLocksHandler.test.ts b/tests/handlers/tokenLocksHandler.test.ts index ad2bcbc..46bee50 100644 --- a/tests/handlers/tokenLocksHandler.test.ts +++ b/tests/handlers/tokenLocksHandler.test.ts @@ -31,6 +31,8 @@ const validateDagTokenLock = (lock) => { expect(Number(lock.unlockEpoch)).toBe(Number(match.unlock_epoch)); expect(lock.timestamp).toBeDefined(); expect(lock.unlockedAtOrdinal).toBeDefined(); // This could be null if not unlocked + expect(lock.globalSnapshotHash).toBeDefined(); + expect(lock.globalSnapshotOrdinal).toBeDefined(); }; const validateDagTokenUnlock = (unlock) => { @@ -42,6 +44,7 @@ const validateDagTokenUnlock = (unlock) => { expect(unlock.source).toBe(match.source_addr); expect(Number(unlock.amount)).toBe(Number(match.amount)); expect(unlock.tokenLockRef).toBe(match.lock_reference_hash); + expect(unlock.globalSnapshotHash).toBeDefined(); expect(unlock.globalSnapshotOrdinal).toBeDefined(); expect(unlock.timestamp).toBeDefined(); }; @@ -58,6 +61,8 @@ const validateMetagraphTokenLock = (lock) => { expect(Number(lock.unlockEpoch)).toBe(Number(match.unlock_epoch)); expect(lock.timestamp).toBeDefined(); expect(lock.unlockedAtOrdinal).toBeDefined(); // This could be null if not unlocked + expect(lock.globalSnapshotHash).toBeDefined(); + expect(lock.globalSnapshotOrdinal).toBeDefined(); }; const validateMetagraphTokenUnlock = (unlock) => { @@ -72,6 +77,8 @@ const validateMetagraphTokenUnlock = (unlock) => { expect(Number(unlock.amount)).toBe(Number(match.amount)); expect(unlock.tokenLockRef).toBe(match.lock_reference_hash); expect(unlock.metagraphSnapshotOrdinal).toBeDefined(); + expect(unlock.globalSnapshotHash).toBeDefined(); + expect(unlock.globalSnapshotOrdinal).toBeDefined(); expect(unlock.timestamp).toBeDefined(); }; From eb1b913155d6064d6bfb67b6059b036ac825fea7 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Tue, 17 Jun 2025 23:53:39 -0300 Subject: [PATCH 56/63] Merge branch 'PROT-1266-add-before-after' into release/testnet --- docs/schemas/params.yml | 4 +- .../20250617/01_snapshot_ordinal_triggers.sql | 48 ++++++ prisma/schema.prisma | 6 +- src/handlers/actionsHandler.ts | 10 +- src/handlers/dagHandler.ts | 20 ++- src/handlers/delegatedStakingHandler.ts | 2 +- src/handlers/metagraphHandler.ts | 146 ++++++++++-------- src/pagination.ts | 30 +++- src/request-params.ts | 17 +- src/response.ts | 21 ++- tests/handlers/dagHandler.test.ts | 9 +- tests/handlers/metagraphHandler.test.ts | 9 +- tests/validation.test.ts | 28 +++- 13 files changed, 247 insertions(+), 103 deletions(-) create mode 100644 prisma/migrations/20250617/01_snapshot_ordinal_triggers.sql diff --git a/docs/schemas/params.yml b/docs/schemas/params.yml index 80d0224..9203849 100644 --- a/docs/schemas/params.yml +++ b/docs/schemas/params.yml @@ -20,14 +20,14 @@ components: searchAfterParam: name: search_after in: query - description: Search after cursor (exclusive with search_before and next) + description: Search after hash (exclusive with search_before and next) schema: type: string searchBeforeParam: name: search_before in: query - description: Search before cursor (exclusive with search_after and next) + description: Search before hash (exclusive with search_after and next) schema: type: string diff --git a/prisma/migrations/20250617/01_snapshot_ordinal_triggers.sql b/prisma/migrations/20250617/01_snapshot_ordinal_triggers.sql new file mode 100644 index 0000000..2d01e4c --- /dev/null +++ b/prisma/migrations/20250617/01_snapshot_ordinal_triggers.sql @@ -0,0 +1,48 @@ +ALTER TABLE metagraph_transactions ADD CONSTRAINT metagraph_transactions_unique_hash UNIQUE (hash); + +ALTER TABLE metagraph_transactions ALTER COLUMN snapshot_hash SET NOT NULL; +ALTER TABLE metagraph_transactions ADD snapshot_ordinal int8 NULL; +ALTER TABLE dag_transactions ADD snapshot_ordinal int8 NULL; + + +CREATE INDEX idx_transactions_sorting ON metagraph_transactions (snapshot_hash, created_at DESC, hash DESC); + +CREATE OR REPLACE FUNCTION batch_set_mg_tx_snapshot_ordinal() +RETURNS trigger AS $$ +BEGIN + UPDATE metagraph_transactions tx + SET snapshot_ordinal = s.ordinal + FROM metagraph_snapshots s + WHERE tx.snapshot_hash = s.hash + AND tx.snapshot_ordinal IS NULL; + + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER batch_trigger_set_mg_tx_snapshot_ordinal +AFTER INSERT +ON metagraph_transactions +FOR EACH STATEMENT +EXECUTE FUNCTION batch_set_mg_tx_snapshot_ordinal(); + + + +CREATE OR REPLACE FUNCTION batch_set_dag_tx_snapshot_ordinal() +RETURNS trigger AS $$ +BEGIN + UPDATE dag_transactions tx + SET snapshot_ordinal = s.ordinal + FROM global_snapshots s + WHERE tx.snapshot_hash = s.hash + AND tx.snapshot_ordinal IS NULL; + + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER batch_trigger_set_dag_tx_snapshot_ordinal +AFTER INSERT +ON dag_transactions +FOR EACH STATEMENT +EXECUTE FUNCTION batch_set_dag_tx_snapshot_ordinal(); \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1217939..cc6bd46 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -251,6 +251,7 @@ model dag_transactions { block_hash String @db.VarChar dag_blocks dag_blocks @relation(fields: [block_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_transaction_dag_block_fk") snapshot_hash String @db.VarChar + snapshot_ordinal BigInt transaction_original Json? global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_dag_transactions_global_snapshot_fk") addresses_dag_transactions_destination_addrToaddresses addresses @relation("dag_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_transactions_destination_addr_fk") @@ -495,7 +496,7 @@ model metagraph_token_unlocks { } model metagraph_transactions { - hash String @db.VarChar + hash String @id @db.VarChar source_addr String @db.VarChar amount BigInt created_at DateTime @default(now()) @db.Timestamp(6) @@ -509,14 +510,13 @@ model metagraph_transactions { ordinal BigInt block_hash String @db.VarChar snapshot_hash String @db.VarChar + snapshot_ordinal BigInt transaction_original Json? metagraph_snapshot metagraph_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transactions_metagraph_snapshot_fk") metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") metagraph_blocks metagraph_blocks @relation(fields: [metagraph_id, block_hash], references: [metagraph_id, hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transaction_metagraph_block_fk") addresses_metagraph_transactions_destination_addrToaddresses addresses @relation("metagraph_transactions_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transactions_destination_addrfk") addresses_metagraph_transactions_source_addrToaddresses addresses @relation("metagraph_transactions_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_transactions_source_addr_fk") - - @@id([metagraph_id, hash], map: "metagraph_transaction_pk") } model metagraphs { diff --git a/src/handlers/actionsHandler.ts b/src/handlers/actionsHandler.ts index b3fa5b2..09d4a2f 100644 --- a/src/handlers/actionsHandler.ts +++ b/src/handlers/actionsHandler.ts @@ -91,7 +91,7 @@ export const globalSnapshotActions = async ( global_snapshot: filter, transaction_type: { in: selectedTransactions }, }, - orderBy: [{ created_at: "desc" }, { hash: "asc" }], + orderBy: [{ created_at: "desc" }, { hash: "desc" }], }, prisma.dag_actions_view.findMany, actionsResponse @@ -127,7 +127,7 @@ export const dagAddressActions = async ( ], transaction_type: { in: selectedTransactions }, }, - orderBy: [{ created_at: "desc" }, { hash: "asc" }], + orderBy: [{ created_at: "desc" }, { hash: "desc" }], }, prisma.dag_actions_view.findMany, actionsResponse @@ -154,7 +154,7 @@ export const currencyActions = async ( metagraph_snapshot: { metagraph_id }, transaction_type: { in: selectedTransactions }, }, - orderBy: [{ created_at: "desc" }, { hash: "asc" }], + orderBy: [{ created_at: "desc" }, { hash: "desc" }], }, prisma.metagraph_actions_view.findMany, actionsResponse @@ -182,7 +182,7 @@ export const currencySnapshotActions = async ( metagraph_snapshot: { metagraph_id, ...filter }, transaction_type: { in: selectedTransactions }, }, - orderBy: [{ created_at: "desc" }, { hash: "asc" }], + orderBy: [{ created_at: "desc" }, { hash: "desc" }], }, prisma.metagraph_actions_view.findMany, actionsResponse @@ -220,7 +220,7 @@ export const currencyAddressActions = async ( ], transaction_type: { in: selectedTransactions }, }, - orderBy: [{ created_at: "desc" }, { hash: "asc" }], + orderBy: [{ created_at: "desc" }, { hash: "desc" }], }, prisma.metagraph_actions_view.findMany, actionsResponse diff --git a/src/handlers/dagHandler.ts b/src/handlers/dagHandler.ts index 3601e60..57013d3 100644 --- a/src/handlers/dagHandler.ts +++ b/src/handlers/dagHandler.ts @@ -12,6 +12,7 @@ import { respond, rewardsResponse, transactionResponse, + unsuportedRequest, } from "../response"; import { fromCreatedAtOrdinalCursor, @@ -92,6 +93,13 @@ export const globalSnapshotRewards = async ( event: APIGatewayProxyEvent ): Promise => { try { + if ( + event.queryStringParameters?.search_before !== undefined || + event.queryStringParameters?.search_after !== undefined + ) { + return unsuportedRequest("search_before or search_after not supported"); + } + const { term } = event.pathParameters || {}; if (term != "latest" && !(await globalSnapshotExists(term))) { @@ -119,7 +127,7 @@ export const globalSnapshotRewards = async ( where: { global_snapshot: { ...gsWhere } }, orderBy: [ { global_snapshot_hash: "desc" }, - { destination_addr: "asc" }, + { destination_addr: "desc" }, ], }, prisma.dag_reward_transactions.findMany, @@ -195,16 +203,20 @@ const dagTransactionsQuery = async ( include: { global_snapshot: { select: { hash: true, ordinal: true } }, }, - orderBy: [{ created_at: "desc" }, { hash: "desc" }], + orderBy: [ + { + snapshot_ordinal: "desc", + }, + { created_at: "desc" }, + { hash: "desc" }, + ], }; const toCursor = (row) => ({ - ...toCreatedAtOrdinalCursor(row), hash: row.hash, }); const fromCursor = (row) => ({ - ...fromCreatedAtOrdinalCursor(row), hash: row.hash, }); diff --git a/src/handlers/delegatedStakingHandler.ts b/src/handlers/delegatedStakingHandler.ts index e4a996d..363ca86 100644 --- a/src/handlers/delegatedStakingHandler.ts +++ b/src/handlers/delegatedStakingHandler.ts @@ -328,7 +328,7 @@ export const stakingPositions = async ( delegated_to: true, delegated_from: true, }, - orderBy: [{ source_addr: "asc" }, { node_id: "asc" }], + orderBy: [{ source_addr: "desc" }, { node_id: "desc" }], }, prisma.delegate_stake_create_events.findMany, delegateStakePositionResponses diff --git a/src/handlers/metagraphHandler.ts b/src/handlers/metagraphHandler.ts index 4b27e42..12c4976 100644 --- a/src/handlers/metagraphHandler.ts +++ b/src/handlers/metagraphHandler.ts @@ -15,14 +15,9 @@ import { respond, rewardsResponse, transactionResponse, + unsuportedRequest, } from "../response"; -import { - fromCreatedAtOrdinalCursor, - fromOrdinalCursor, - paginatedQuery, - toCreatedAtOrdinalCursor, - toOrdinalCursor, -} from "../pagination"; +import { paginatedQuery } from "../pagination"; import { toNumber, isFinite } from "lodash"; const prisma = new PrismaClient(); @@ -41,29 +36,6 @@ const latestMetagraphSnapshot = async (metagraph_id) => { }); }; -const mgIdOrdinalToCursor = (row) => ({ - metagraph_id_ordinal: { - ...toOrdinalCursor(row), - metagraph_id: row.metagraph_id, - }, -}); - -const mgIdOrdinalFromCursor = (row) => ({ - ...fromOrdinalCursor(row), - metagraph_id: row.metagraph_id, -}); - -const mgIdHashToCursor = (row) => ({ - metagraph_id_hash: { - metagraph_id: row.metagraph_id, - hash: row.hash, - }, -}); -const mgIdHashFromCursor = (row) => ({ - metagraph_id: row.metagraph_id, - hash: row.hash, -}); - const includeGlobalSnapshotOrdinalFromMetagraph = { metagraph_snapshot: { select: { @@ -77,6 +49,10 @@ const includeGlobalSnapshotOrdinalFromMetagraph = { }, }; +const cursor = (row) => ({ + hash: row.hash, +}); + const metagraphSnapshotWhere = async (metagraph_id, term) => { if (term == "latest") { const latestSnapshotHash = (await latestMetagraphSnapshot(metagraph_id)) @@ -105,6 +81,14 @@ const metagraphSnapshotExists = async (metagraph_id, term) => { }); }; +const paginationWithMetagraphId = (metagraph_id, event) => { + let pagination = extractPagination(event); + if ("searchSince" in pagination && pagination.searchSince) { + (pagination.searchSince as any).metagraph_id = metagraph_id; + } + return pagination; +}; + export const currencySnapshots = async ( event: APIGatewayProxyEvent ): Promise => { @@ -116,9 +100,9 @@ export const currencySnapshots = async ( } return await paginatedQuery( - extractPagination(event), - mgIdOrdinalToCursor, - mgIdOrdinalFromCursor, + paginationWithMetagraphId(metagraph_id, event), + cursor, + cursor, { where: { metagraph_id }, include: { metagraph_blocks: true }, @@ -138,10 +122,17 @@ export const currencySnapshotsByOwnerAddress = async ( try { const { address } = event.pathParameters || {}; + if ( + event.queryStringParameters?.search_before !== undefined || + event.queryStringParameters?.search_after !== undefined + ) { + return unsuportedRequest("search_before or search_after not supported"); + } + return await paginatedQuery( extractPagination(event), - mgIdOrdinalToCursor, - mgIdOrdinalFromCursor, + cursor, + cursor, { where: { owner_address: address }, include: { metagraph_blocks: true }, @@ -187,6 +178,13 @@ export const currencySnapshotRewards = async ( try { const { identifier: metagraph_id, term } = event.pathParameters || {}; + if ( + event.queryStringParameters?.search_before !== undefined || + event.queryStringParameters?.search_after !== undefined + ) { + return unsuportedRequest("search_before or search_after not supported"); + } + if (!(await metagraphIdExists(metagraph_id))) { return notFoundResponse("metagraph"); } @@ -213,8 +211,13 @@ export const currencySnapshotRewards = async ( destination_addr: row.destination_addr, }); + let pagination = extractPagination(event); + if ("searchSince" in pagination && pagination.searchSince) { + (pagination.searchSince as any).metagraph_id = metagraph_id; + } + return await paginatedQuery( - extractPagination(event), + pagination, nextToCursor, cursorToNext, { @@ -225,9 +228,9 @@ export const currencySnapshotRewards = async ( }, }, orderBy: [ - { metagraph_id: "asc" }, - { metagraph_snapshot_hash: "asc" }, - { destination_addr: "asc" }, + { metagraph_id: "desc" }, + { metagraph_snapshot_hash: "desc" }, + { destination_addr: "desc" }, ], }, prisma.metagraph_reward_transactions.findMany, @@ -239,6 +242,7 @@ export const currencySnapshotRewards = async ( }; const metagraphTransactionsQuery = async ( + metagraph_id, baseQuery, event: APIGatewayProxyEvent ): Promise => { @@ -248,8 +252,6 @@ const metagraphTransactionsQuery = async ( include: { metagraph_snapshot: { select: { - hash: true, - ordinal: true, global_snapshot: { select: { hash: true, @@ -260,16 +262,20 @@ const metagraphTransactionsQuery = async ( }, }, orderBy: [ - { metagraph_id: "desc" }, - { hash: "desc" }, + { + snapshot_ordinal: "desc", + }, { created_at: "desc" }, + { hash: "desc" }, ], }; + let pagination = paginationWithMetagraphId(metagraph_id, event); + return await paginatedQuery( - extractPagination(event), - mgIdHashToCursor, - mgIdHashFromCursor, + pagination, + cursor, + cursor, query, prisma.metagraph_transactions.findMany, metagraphTransactionsResponse @@ -306,7 +312,7 @@ export const currencySnapshotTransactions = async ( }, }; - return metagraphTransactionsQuery(where, event); + return metagraphTransactionsQuery(metagraph_id, where, event); } catch (error) { return handleError(error); } @@ -349,7 +355,7 @@ export const currencyTransactions = async ( const where = { where: { metagraph_id } }; - return metagraphTransactionsQuery(where, event); + return metagraphTransactionsQuery(metagraph_id, where, event); } catch (error) { return handleError(error); } @@ -363,10 +369,8 @@ export const currencyTransaction = async ( const transaction = await prisma.metagraph_transactions.findUnique({ where: { - metagraph_id_hash: { - metagraph_id: metagraph_id!, - hash: hash!, - }, + metagraph_id: metagraph_id!, + hash: hash!, }, include: { metagraph_snapshot: { select: { hash: true, ordinal: true } }, @@ -396,7 +400,7 @@ export const currencyTransactionsByAddress = async ( }, }; - return metagraphTransactionsQuery(where, event); + return metagraphTransactionsQuery(metagraph_id, where, event); } catch (error) { return handleError(error); } @@ -414,7 +418,7 @@ export const currencyTransactionsBySource = async ( const where = { where: { metagraph_id, source_addr: address } }; - return metagraphTransactionsQuery(where, event); + return metagraphTransactionsQuery(metagraph_id, where, event); } catch (error) { return handleError(error); } @@ -428,7 +432,7 @@ export const currencyTransactionsByDestination = async ( const where = { where: { metagraph_id, destination_addr: address } }; - return metagraphTransactionsQuery(where, event); + return metagraphTransactionsQuery(metagraph_id, where, event); } catch (error) { return handleError(error); } @@ -517,6 +521,7 @@ export const currencyFeeTransaction = async ( }; const metagraphFeeTransactionsQuery = async ( + metagraph_id, baseQuery, event: APIGatewayProxyEvent ): Promise => { @@ -526,8 +531,6 @@ const metagraphFeeTransactionsQuery = async ( include: { metagraph_snapshot: { select: { - hash: true, - ordinal: true, global_snapshot: { select: { hash: true, @@ -537,7 +540,13 @@ const metagraphFeeTransactionsQuery = async ( }, }, }, - orderBy: [{ metagraph_id: "asc" }, { hash: "asc" }], + orderBy: [ + { + metagraph_snapshot_ordinal: "desc", + }, + { created_at: "desc" }, + { hash: "desc" }, + ], }; const cursor = (row) => ({ @@ -546,7 +555,7 @@ const metagraphFeeTransactionsQuery = async ( }); return await paginatedQuery( - extractPagination(event), + paginationWithMetagraphId(metagraph_id, event), cursor, cursor, query, @@ -571,7 +580,7 @@ export const currencyFeeTransactions = async ( include: includeGlobalSnapshotOrdinalFromMetagraph, }; - return metagraphFeeTransactionsQuery(where, event); + return metagraphFeeTransactionsQuery(metagraph_id, where, event); } catch (error) { return handleError(error); } @@ -597,7 +606,7 @@ export const currencySnapshotFeeTransactions = async ( include: includeGlobalSnapshotOrdinalFromMetagraph, }; - return metagraphFeeTransactionsQuery(where, event); + return metagraphFeeTransactionsQuery(metagraph_id, where, event); } catch (error) { return handleError(error); } @@ -618,7 +627,7 @@ export const currencyFeeTransactionsByAddress = async ( }, include: includeGlobalSnapshotOrdinalFromMetagraph, }; - return metagraphFeeTransactionsQuery(where, event); + return metagraphFeeTransactionsQuery(metagraph_id, where, event); } catch (error) { return handleError(error); } @@ -639,7 +648,7 @@ export const currencyFeeTransactionsBySource = async ( }, include: includeGlobalSnapshotOrdinalFromMetagraph, }; - return metagraphFeeTransactionsQuery(where, event); + return metagraphFeeTransactionsQuery(metagraph_id, where, event); } catch (error) { return handleError(error); } @@ -660,7 +669,7 @@ export const currencyFeeTransactionsByDestination = async ( }, include: includeGlobalSnapshotOrdinalFromMetagraph, }; - return metagraphFeeTransactionsQuery(where, event); + return metagraphFeeTransactionsQuery(metagraph_id, where, event); } catch (error) { return handleError(error); } @@ -670,13 +679,20 @@ export const metagraphs = async ( event: APIGatewayProxyEvent ): Promise => { try { + if ( + event.queryStringParameters?.search_before !== undefined || + event.queryStringParameters?.search_after !== undefined + ) { + return unsuportedRequest("search_before or search_after not supported"); + } + const cursor = (row) => ({ id: row.id }); return await paginatedQuery( extractPagination(event), cursor, cursor, - { orderBy: { id: "asc" } }, + { orderBy: { id: "desc" } }, prisma.metagraphs.findMany, metagraphsResponse ); diff --git a/src/pagination.ts b/src/pagination.ts index 895b2b8..97b421d 100644 --- a/src/pagination.ts +++ b/src/pagination.ts @@ -2,13 +2,14 @@ import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; import { handleError, respond } from "./response"; import { Pagination } from "./request-params"; import { toNumber, isFinite } from "lodash"; +import { Prisma } from "@prisma/client"; export const maxSizeLimit = 10000; export const defaultPageSize = 100; export enum SortOrder { Desc = "desc", - Asc = "asc", + Asc = "desc", } export enum SearchDirection { @@ -59,10 +60,12 @@ const buildPageQuery = (pagination: Pagination, nextToCursor) => { ) { const page = pagination.searchDirection === SearchDirection.Before - ? -incrementedSize - : incrementedSize; + ? incrementedSize + : -incrementedSize; + return { take: page, + skip: pagination.searchDirection === SearchDirection.Before ? 1 : 0, cursor: pagination.searchSince, }; } @@ -94,7 +97,9 @@ export const paginatedQuery = async ( ...(pagination ? pageQueryParams : {}), }; - const rawResults = await findMany(pagedQuery); + const withCursorQuery = withCursorSafeOrdering(pagedQuery); + + const rawResults = await findMany(withCursorQuery); const pageSize = pageQueryParams.take ? pageQueryParams.take - 1 : maxSizeLimit; @@ -112,3 +117,20 @@ export const paginatedQuery = async ( return handleError(error); } }; + +function withCursorSafeOrdering(query, searchSince?: { hash: string }) { + if (!searchSince) return query; + + const originalOrder = query.orderBy; + + const extendedOrder = Array.isArray(originalOrder) + ? [...originalOrder, { hash: "desc" }] + : originalOrder + ? [originalOrder, { hash: "desc" }] + : [{ hash: "desc" }]; + + return { + ...query, + orderBy: extendedOrder, + }; +} diff --git a/src/request-params.ts b/src/request-params.ts index 1a3efc2..412027b 100644 --- a/src/request-params.ts +++ b/src/request-params.ts @@ -5,7 +5,12 @@ export type Pagination = | { size?: number; searchDirection: SearchDirection; - searchSince: any; + searchSince: + | { hash: string } + | { metagraph_id: string; hash: string } + | { metagraph_id_hash: { metagraph_id: string; hash: string } } + | { id: string } + | undefined; } | { size?: number } | { size?: number; next: any }; @@ -65,8 +70,16 @@ export const extractPagination = (event: APIGatewayEvent): Pagination => { }; } + let searchSince; + + if (searchAfter) { + searchSince = { hash: searchAfter }; + } else if (searchBefore) { + searchSince = { hash: searchBefore }; + } + return { - searchSince: searchAfter || searchBefore, + searchSince, searchDirection: (searchAfter && SearchDirection.After) || (searchBefore && SearchDirection.Before) || diff --git a/src/response.ts b/src/response.ts index 3c095ec..55020cb 100644 --- a/src/response.ts +++ b/src/response.ts @@ -46,8 +46,6 @@ export const rewardResponse = (reward) => ({ export const dagTransactionsResponse = (ts) => ts.map(transactionResponse); export const transactionResponse = (transaction) => { - const snapshot = - transaction.metagraph_snapshot ?? transaction.global_snapshot ?? null; return { hash: transaction.hash, ordinal: transaction.ordinal, @@ -61,15 +59,18 @@ export const transactionResponse = (transaction) => { }, salt: transaction.salt, blockHash: transaction.block_hash, - snapshotHash: snapshot.hash, - snapshotOrdinal: snapshot.ordinal, + snapshotHash: + transaction.snapshot_hash ?? transaction.metagraph_snapshot_hash, + snapshotOrdinal: + transaction.snapshot_ordinal ?? transaction.metagraph_snapshot_ordinal, transactionOriginal: transaction.transaction_original, timestamp: transaction.created_at, globalSnapshotHash: - transaction.metagraph_snapshot?.global_snapshot.hash ?? snapshot.hash, + transaction.metagraph_snapshot?.global_snapshot.hash ?? + transaction.snapshot_hash, globalSnapshotOrdinal: transaction.metagraph_snapshot?.global_snapshot.ordinal ?? - snapshot.ordinal, + transaction.snapshot_ordinal, }; }; @@ -165,6 +166,14 @@ export const missingParameterResponse = ( }), }); +export const unsuportedRequest = (param: string): APIGatewayProxyResult => ({ + statusCode: 400, + body: JSON.stringify({ + message: `Unsuported request: ${param}`, + errors: [""], + }), +}); + export const handleError = (error: any): APIGatewayProxyResult => { console.error(error); return { diff --git a/tests/handlers/dagHandler.test.ts b/tests/handlers/dagHandler.test.ts index 1b8fd58..9cc4ec1 100644 --- a/tests/handlers/dagHandler.test.ts +++ b/tests/handlers/dagHandler.test.ts @@ -41,7 +41,8 @@ const data_dag_transactions = [ "4a6d3aa5715e304b4b5f32d52f0c91e0909acf7c24b3ca9776324da68db2f30c", ordinal: 234n, block_hash: data_dag_blocks[0].hash, - snapshot_hash: data_dag_blocks[0].snapshot_hash, + snapshot_hash: data_global_snapshots[0].hash, + snapshot_ordinal: data_global_snapshots[0].ordinal, transaction_original: { fee: 1, salt: 8971636413389910, @@ -68,7 +69,8 @@ const data_dag_transactions = [ "1c53bc94c735d8d6eeaddc9f5cb446e7f79144c9aa5bba9479db8dee0ec1aa4c", ordinal: 3222n, block_hash: data_dag_blocks[1].hash, - snapshot_hash: data_dag_blocks[1].snapshot_hash, + snapshot_hash: data_global_snapshots[1].hash, + snapshot_ordinal: data_global_snapshots[1].ordinal, transaction_original: { fee: 1, salt: 8885443039669825, @@ -95,7 +97,8 @@ const data_dag_transactions = [ "6acc815979e9d1935cce65ba776fde1144c5fc0e97d3a9fe67d82d0e6e21977d", ordinal: 3222n, block_hash: data_dag_blocks[1].hash, - snapshot_hash: data_dag_blocks[1].snapshot_hash, + snapshot_hash: data_global_snapshots[1].hash, + snapshot_ordinal: data_global_snapshots[1].ordinal, transaction_original: { fee: 200000, salt: 8759543125914451, diff --git a/tests/handlers/metagraphHandler.test.ts b/tests/handlers/metagraphHandler.test.ts index 1773b8e..1fae8de 100644 --- a/tests/handlers/metagraphHandler.test.ts +++ b/tests/handlers/metagraphHandler.test.ts @@ -54,7 +54,8 @@ const data_metagraph_transactions = [ "5056fdfbba0637dcecfc0b7fa3f441c745c852cf850c3bfc0dbc8a7410b8d722", ordinal: 12n, block_hash: data_metagraph_blocks[0].hash, - snapshot_hash: data_metagraph_blocks[0].metagraph_snapshot_hash, + snapshot_hash: data_metagraph_snapshots[0].hash, + snapshot_ordinal: data_metagraph_snapshots[0].ordinal, transaction_original: { fee: 200000, salt: 8729335446529965, @@ -82,7 +83,8 @@ const data_metagraph_transactions = [ "39c9909d3b00552beaa5487c38110267675ab212b3b97654fc5ca9917cb7e72b", ordinal: 123n, block_hash: data_metagraph_blocks[1].hash, - snapshot_hash: data_metagraph_blocks[1].metagraph_snapshot_hash, + snapshot_hash: data_metagraph_snapshots[1].hash, + snapshot_ordinal: data_metagraph_snapshots[1].ordinal, transaction_original: { fee: 20, salt: 8729335446529965, @@ -110,7 +112,8 @@ const data_metagraph_transactions = [ "39c9909d3b006666666666666666666666666666666666666c5ca9917cb7e72b", ordinal: 123n, block_hash: data_metagraph_blocks[2].hash, - snapshot_hash: data_metagraph_blocks[2].metagraph_snapshot_hash, + snapshot_hash: data_metagraph_snapshots[2].hash, + snapshot_ordinal: data_metagraph_snapshots[2].ordinal, transaction_original: { fee: 10, salt: 8729335446529965, diff --git a/tests/validation.test.ts b/tests/validation.test.ts index 4d0c363..348c4e9 100644 --- a/tests/validation.test.ts +++ b/tests/validation.test.ts @@ -43,7 +43,9 @@ describe("extractPagination", () => { const result = () => extractPagination(event); expect(result).toThrow(Error); - expect(result).toThrow("search_after & search_before should be mutually exclusive"); + expect(result).toThrow( + "search_after & search_before should be mutually exclusive" + ); }); it("should pass when searchAfter is provided but limit not", async () => { @@ -54,14 +56,22 @@ describe("extractPagination", () => { ); const result = await extractPagination(event); - expect(result).toEqual({searchDirection: "search_after", searchSince: "aa", size: NaN}); + expect(result).toEqual({ + searchDirection: "search_after", + searchSince: { hash: "aa" }, + size: NaN, + }); }); it("should pass when limit is provided but searchAfter not", async () => { const event = pipe(baseEvent, setParam("address", "123"), setLimit("12")); const result = await extractPagination(event); - expect(result).toEqual({"searchDirection": undefined, "searchSince": undefined, "size": 12}); + expect(result).toEqual({ + searchDirection: undefined, + searchSince: undefined, + size: 12, + }); }); it("should pass returning event when both searchAfter and limit are provided", async () => { @@ -73,7 +83,11 @@ describe("extractPagination", () => { ); const result = await extractPagination(event); - expect(result).toEqual({"searchDirection": "search_after", "searchSince": "aa", "size": 2}); + expect(result).toEqual({ + searchDirection: "search_after", + searchSince: { hash: "aa" }, + size: 2, + }); }); it("should pass returning event when both searchBefore and limit are provided", async () => { @@ -86,6 +100,10 @@ describe("extractPagination", () => { const result = await extractPagination(event); - expect(result).toEqual({"searchDirection": "search_before", "searchSince": "aa", "size": 2}); + expect(result).toEqual({ + searchDirection: "search_before", + searchSince: { hash: "aa" }, + size: 2, + }); }); }); From 1433bb961039164be00c87006946b48325c88314 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Wed, 18 Jun 2025 00:18:03 -0300 Subject: [PATCH 57/63] PROT-1257 add currencyId to locks and spends (#189) * add currencyId to locks and spends * remove obsolete triggers --- .../20250609/01_add_currency_id_to_spends.sql | 31 ++++++ .../02_add_staking_to_actions.sql} | 94 ++++++++++--------- .../20250609/03_drop_abstract_triggers.sql | 18 ++++ prisma/schema.prisma | 12 +++ prisma/seed.ts | 22 ++++- src/handlers/actionsHandler.ts | 8 +- src/handlers/allowSpendsHandler.ts | 6 +- src/handlers/metagraphHandler.ts | 14 ++- src/handlers/tokenLocksHandler.ts | 4 +- tests/handlers/tokenLocksHandler.test.ts | 4 + 10 files changed, 155 insertions(+), 58 deletions(-) create mode 100644 prisma/migrations/20250609/01_add_currency_id_to_spends.sql rename prisma/migrations/{20250529/01_add_staking_to_actions.sql => 20250609/02_add_staking_to_actions.sql} (74%) create mode 100644 prisma/migrations/20250609/03_drop_abstract_triggers.sql diff --git a/prisma/migrations/20250609/01_add_currency_id_to_spends.sql b/prisma/migrations/20250609/01_add_currency_id_to_spends.sql new file mode 100644 index 0000000..1ae69f3 --- /dev/null +++ b/prisma/migrations/20250609/01_add_currency_id_to_spends.sql @@ -0,0 +1,31 @@ +ALTER TABLE dag_allow_spends ADD currency_id varchar NULL; +ALTER TABLE dag_allow_spends ADD CONSTRAINT dag_allow_spends_metagraphs_fk FOREIGN KEY (currency_id) REFERENCES public.metagraphs(id) ON DELETE CASCADE; + +ALTER TABLE dag_spend_transactions ADD currency_id varchar NULL; +ALTER TABLE dag_spend_transactions ADD CONSTRAINT dag_spend_transactions_metagraphs_fk FOREIGN KEY (currency_id) REFERENCES public.metagraphs(id) ON DELETE CASCADE; + +ALTER TABLE dag_expired_spend_transactions ADD currency_id varchar NULL; +ALTER TABLE dag_expired_spend_transactions ADD CONSTRAINT dag_expired_spend_transactions_metagraphs_fk FOREIGN KEY (currency_id) REFERENCES public.metagraphs(id) ON DELETE CASCADE; + +ALTER TABLE metagraph_allow_spends ADD currency_id varchar NULL; +ALTER TABLE metagraph_allow_spends ADD CONSTRAINT metagraph_allow_spends_metagraphs_fk FOREIGN KEY (currency_id) REFERENCES public.metagraphs(id) ON DELETE CASCADE; + +ALTER TABLE metagraph_spend_transactions ADD currency_id varchar NULL; +ALTER TABLE metagraph_spend_transactions ADD CONSTRAINT metagraph_spend_transactions_metagraphs_fk FOREIGN KEY (currency_id) REFERENCES public.metagraphs(id) ON DELETE CASCADE; + +ALTER TABLE metagraph_expired_spend_transactions ADD currency_id varchar NULL; +ALTER TABLE metagraph_expired_spend_transactions ADD CONSTRAINT metagraph_expired_spend_transactions_metagraphs_fk FOREIGN KEY (currency_id) REFERENCES public.metagraphs(id) ON DELETE CASCADE; + + + +ALTER TABLE dag_token_locks ADD currency_id varchar NULL; +ALTER TABLE dag_token_locks ADD CONSTRAINT dag_token_locks_metagraphs_fk FOREIGN KEY (currency_id) REFERENCES public.metagraphs(id) ON DELETE CASCADE; + +ALTER TABLE dag_token_unlocks ADD currency_id varchar NULL; +ALTER TABLE dag_token_unlocks ADD CONSTRAINT dag_token_unlocks_metagraphs_fk FOREIGN KEY (currency_id) REFERENCES public.metagraphs(id) ON DELETE CASCADE; + +ALTER TABLE metagraph_token_locks ADD currency_id varchar NULL; +ALTER TABLE metagraph_token_locks ADD CONSTRAINT metagraph_token_locks_metagraphs_fk FOREIGN KEY (currency_id) REFERENCES public.metagraphs(id) ON DELETE CASCADE; + +ALTER TABLE metagraph_token_unlocks ADD currency_id varchar NULL; +ALTER TABLE metagraph_token_unlocks ADD CONSTRAINT metagraph_token_unlocks_metagraphs_fk FOREIGN KEY (currency_id) REFERENCES public.metagraphs(id) ON DELETE CASCADE; diff --git a/prisma/migrations/20250529/01_add_staking_to_actions.sql b/prisma/migrations/20250609/02_add_staking_to_actions.sql similarity index 74% rename from prisma/migrations/20250529/01_add_staking_to_actions.sql rename to prisma/migrations/20250609/02_add_staking_to_actions.sql index 3498b22..184686f 100644 --- a/prisma/migrations/20250529/01_add_staking_to_actions.sql +++ b/prisma/migrations/20250609/02_add_staking_to_actions.sql @@ -1,127 +1,129 @@ -- DAG Tables +drop VIEW dag_actions_view; CREATE OR REPLACE VIEW dag_actions_view AS SELECT - das.hash, source_addr, amount, das.created_at, das.updated_at, - 'AllowSpend' as transaction_type, snapshot_hash as global_snapshot_hash, gs.ordinal as global_snapshot_ordinal, - destination_addr, last_valid_epoch_progress AS unlock_epoch, parent_hash, fee - FROM dag_allow_spends das + das.hash, source_addr, amount, das.created_at, das.updated_at, + 'AllowSpend' as transaction_type, snapshot_hash as global_snapshot_hash, gs.ordinal as global_snapshot_ordinal, + destination_addr, last_valid_epoch_progress AS unlock_epoch, parent_hash, fee, currency_id + FROM dag_allow_spends das JOIN global_snapshots gs ON das.snapshot_hash = gs.hash UNION ALL SELECT - dst.hash, dst.source_addr, dst.amount, dst.created_at, dst.updated_at, + dst.hash, dst.source_addr, dst.amount, dst.created_at, dst.updated_at, 'SpendTransaction', dst.snapshot_hash, gs.ordinal, - dst.destination_addr, null, dst.allow_spend_ref, null - FROM dag_spend_transactions dst + dst.destination_addr, null, dst.allow_spend_ref, null, currency_id + FROM dag_spend_transactions dst JOIN global_snapshots gs ON dst.snapshot_hash = gs.hash UNION ALL SELECT - dest.hash, dest.source_addr, dest.amount, dest.created_at, dest.updated_at, + dest.hash, dest.source_addr, dest.amount, dest.created_at, dest.updated_at, 'ExpiredAllowSpend', dest.snapshot_hash, gs.ordinal, - null, null, dest.allow_spend_ref, null - FROM dag_expired_spend_transactions dest + null, null, dest.allow_spend_ref, null, currency_id + FROM dag_expired_spend_transactions dest JOIN global_snapshots gs ON dest.snapshot_hash = gs.hash UNION ALL SELECT - dtl.hash, dtl.source_addr, dtl.amount, dtl.created_at, dtl.updated_at, + dtl.hash, dtl.source_addr, dtl.amount, dtl.created_at, dtl.updated_at, 'TokenLock', dtl.snapshot_hash, gs.ordinal, - null, dtl.unlock_epoch, dtl.parent_hash, null - FROM dag_token_locks dtl + null, dtl.unlock_epoch, dtl.parent_hash, null, currency_id + FROM dag_token_locks dtl JOIN global_snapshots gs ON dtl.snapshot_hash = gs.hash UNION ALL - SELECT - dtu.hash, dtu.source_addr, dtu.amount, dtu.created_at, dtu.updated_at, + SELECT + dtu.hash, dtu.source_addr, dtu.amount, dtu.created_at, dtu.updated_at, 'TokenUnlock', dtu.snapshot_hash, gs.ordinal, - null, null, dtu.lock_reference_hash, null - FROM dag_token_unlocks dtu + null, null, dtu.lock_reference_hash, null, currency_id + FROM dag_token_unlocks dtu JOIN global_snapshots gs ON dtu.snapshot_hash = gs.hash UNION ALL SELECT - dsce.hash, dsce.source_addr, dsce.amount, dsce.created_at, dsce.updated_at, + dsce.hash, dsce.source_addr, dsce.amount, dsce.created_at, dsce.updated_at, 'DelegateStakeCreate', dsce.global_snapshot_hash, gs.ordinal, - null, null, dsce.parent_hash, dsce.fee - FROM delegate_stake_create_events dsce + null, null, dsce.parent_hash, dsce.fee, null + FROM delegate_stake_create_events dsce JOIN global_snapshots gs ON dsce.global_snapshot_hash = gs.hash UNION ALL SELECT - dswe.hash, dswe.source_addr, dsce.amount, dswe.created_at, dswe.updated_at, + dswe.hash, dswe.source_addr, dsce.amount, dswe.created_at, dswe.updated_at, 'DelegateStakeWithdraw', dswe.global_snapshot_hash, gs.ordinal, - null, dswe.unlock_epoch, dswe.stake_create_hash, null + null, dswe.unlock_epoch, dswe.stake_create_hash, null, null FROM delegate_stake_withdraw_events dswe LEFT JOIN delegate_stake_create_events dsce ON dswe.stake_create_hash = dsce.hash JOIN global_snapshots gs ON dswe.global_snapshot_hash = gs.hash; -- Metagraph Tables +drop VIEW metagraph_actions_view; CREATE OR REPLACE VIEW metagraph_actions_view AS -SELECT +SELECT mas.metagraph_id, mas.hash, mas.source_addr, mas.amount, mas.created_at, mas.updated_at, 'AllowSpend' AS transaction_type, mas.snapshot_hash as metagraph_snapshot_hash, ms.ordinal as metagraph_snapshot_ordinal, mas.destination_addr, mas.last_valid_epoch_progress AS unlock_epoch, - mas.parent_hash, mas.fee, + mas.parent_hash, mas.fee, currency_id, gs.hash AS global_snapshot_hash, gs.ordinal AS global_snapshot_ordinal FROM metagraph_allow_spends mas -JOIN metagraph_snapshots ms +JOIN metagraph_snapshots ms ON mas.metagraph_id = ms.metagraph_id AND mas.snapshot_hash = ms.hash -JOIN global_snapshots gs +JOIN global_snapshots gs ON ms.global_snapshot_hash = gs.hash UNION ALL -SELECT +SELECT mst.metagraph_id, mst.hash, mst.source_addr, mst.amount, mst.created_at, mst.updated_at, 'SpendTransaction', mst.snapshot_hash as metagraph_snapshot_hash, ms.ordinal as metagraph_snapshot_ordinal, - mst.destination_addr, NULL, mst.allow_spend_ref, NULL, + mst.destination_addr, NULL, mst.allow_spend_ref, NULL, currency_id, gs.hash AS global_snapshot_hash, gs.ordinal AS global_snapshot_ordinal FROM metagraph_spend_transactions mst -JOIN metagraph_snapshots ms +JOIN metagraph_snapshots ms ON mst.metagraph_id = ms.metagraph_id AND mst.snapshot_hash = ms.hash -JOIN global_snapshots gs +JOIN global_snapshots gs ON ms.global_snapshot_hash = gs.hash UNION ALL -SELECT +SELECT mest.metagraph_id, mest.hash, mest.source_addr, mest.amount, mest.created_at, mest.updated_at, 'ExpiredAllowSpend', mest.snapshot_hash as metagraph_snapshot_hash, ms.ordinal as metagraph_snapshot_ordinal, - NULL, NULL, mest.allow_spend_ref, NULL, + NULL, NULL, mest.allow_spend_ref, NULL, currency_id, gs.hash AS global_snapshot_hash, gs.ordinal AS global_snapshot_ordinal FROM metagraph_expired_spend_transactions mest -JOIN metagraph_snapshots ms +JOIN metagraph_snapshots ms ON mest.metagraph_id = ms.metagraph_id AND mest.snapshot_hash = ms.hash -JOIN global_snapshots gs +JOIN global_snapshots gs ON ms.global_snapshot_hash = gs.hash UNION ALL -SELECT +SELECT mtl.metagraph_id, mtl.hash, mtl.source_addr, mtl.amount, mtl.created_at, mtl.updated_at, 'TokenLock', mtl.snapshot_hash as metagraph_snapshot_hash, ms.ordinal as metagraph_snapshot_ordinal, - NULL, mtl.unlock_epoch, mtl.parent_hash, NULL, + NULL, mtl.unlock_epoch, mtl.parent_hash, NULL, currency_id, gs.hash AS global_snapshot_hash, gs.ordinal AS global_snapshot_ordinal FROM metagraph_token_locks mtl -JOIN metagraph_snapshots ms +JOIN metagraph_snapshots ms ON mtl.metagraph_id = ms.metagraph_id AND mtl.snapshot_hash = ms.hash -JOIN global_snapshots gs +JOIN global_snapshots gs ON ms.global_snapshot_hash = gs.hash UNION ALL -SELECT +SELECT mtu.metagraph_id, mtu.hash, mtu.source_addr, mtu.amount, mtu.created_at, mtu.updated_at, 'TokenUnlock', mtu.snapshot_hash as metagraph_snapshot_hash, ms.ordinal as metagraph_snapshot_ordinal, - NULL, NULL, mtu.lock_reference_hash, NULL, + NULL, NULL, mtu.lock_reference_hash, NULL, currency_id, gs.hash AS global_snapshot_hash, gs.ordinal AS global_snapshot_ordinal FROM metagraph_token_unlocks mtu -JOIN metagraph_snapshots ms +JOIN metagraph_snapshots ms ON mtu.metagraph_id = ms.metagraph_id AND mtu.snapshot_hash = ms.hash -JOIN global_snapshots gs +JOIN global_snapshots gs ON ms.global_snapshot_hash = gs.hash UNION ALL -SELECT +SELECT mft.metagraph_id, mft.hash, mft.source_addr, mft.amount, mft.created_at, mft.updated_at, 'FeeTransaction', mft.metagraph_snapshot_hash, ms.ordinal, - mft.destination_addr, NULL, mft.data_update_ref, NULL, + mft.destination_addr, NULL, mft.data_update_ref, NULL, null, gs.hash AS global_snapshot_hash, gs.ordinal AS global_snapshot_ordinal FROM metagraph_fee_transactions mft -JOIN metagraph_snapshots ms +JOIN metagraph_snapshots ms ON mft.metagraph_id = ms.metagraph_id AND mft.metagraph_snapshot_hash = ms.hash -JOIN global_snapshots gs +JOIN global_snapshots gs ON ms.global_snapshot_hash = gs.hash; diff --git a/prisma/migrations/20250609/03_drop_abstract_triggers.sql b/prisma/migrations/20250609/03_drop_abstract_triggers.sql new file mode 100644 index 0000000..18cde02 --- /dev/null +++ b/prisma/migrations/20250609/03_drop_abstract_triggers.sql @@ -0,0 +1,18 @@ +delete from only abstract_transactions; +delete from only abstract_blocks; + +DROP TRIGGER IF EXISTS "trigger_insert_abstract_transactions_dag_allow_spends" ON "dag_allow_spends"; +DROP TRIGGER IF EXISTS "trigger_insert_abstract_blocks_dag" ON "dag_blocks"; +DROP TRIGGER IF EXISTS "trigger_insert_abstract_transactions_dag_spend_transactions" ON "dag_spend_transactions"; +DROP TRIGGER IF EXISTS "trigger_insert_abstract_transactions_dag_expired_spend_transact" ON "dag_expired_spend_transactions"; +DROP TRIGGER IF EXISTS "trigger_insert_abstract_transactions_dag_token_locks" ON "dag_token_locks"; +DROP TRIGGER IF EXISTS "trigger_insert_abstract_transactions_dag_token_unlocks" ON "dag_token_unlocks"; +DROP TRIGGER IF EXISTS "trigger_insert_abstract_transactions_metagraph_token_locks" ON "metagraph_token_locks"; +DROP TRIGGER IF EXISTS "trigger_insert_abstract_transactions_metagraph_token_unlocks" ON "metagraph_token_unlocks"; +DROP TRIGGER IF EXISTS "trigger_insert_abstract_transactions_metagraph_allow_spends" ON "metagraph_allow_spends"; +DROP TRIGGER IF EXISTS "trigger_insert_abstract_blocks_metagraph" ON "metagraph_blocks"; +DROP TRIGGER IF EXISTS "trigger_insert_abstract_transactions_metagraph_fee_transactions" ON "metagraph_fee_transactions"; +DROP TRIGGER IF EXISTS "trigger_insert_abstract_transactions_metagraph_spend_transactio" ON "metagraph_spend_transactions"; +DROP TRIGGER IF EXISTS "trigger_insert_abstract_transactions_metagraph_expired_spend_tr" ON "metagraph_expired_spend_transactions"; +DROP TRIGGER IF EXISTS "trigger_insert_abstract_transactions_dag_transactions" ON "dag_transactions"; +DROP TRIGGER IF EXISTS "trigger_insert_abstract_transactions_metagraph_transactions" ON "metagraph_transactions"; \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index cc6bd46..7b8b678 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -32,6 +32,7 @@ model dag_actions_view { unlock_epoch BigInt? parent_hash String? @db.VarChar fee BigInt? + currency_id String? @db.VarChar dag_token_lock dag_token_locks? @relation(fields: [hash], references: [hash], map: "dag_token_locks_ref") dag_token_unlock dag_token_unlocks? @relation(fields: [hash], references: [hash], map: "dag_token_unlocks_ref") @@ -57,6 +58,7 @@ model metagraph_actions_view { unlock_epoch BigInt? parent_hash String? @db.VarChar fee BigInt? + currency_id String? @db.VarChar metagraph_token_lock metagraph_token_locks? @relation(fields: [hash], references: [hash], map: "metagraph_token_locks_ref") metagraph_token_unlock metagraph_token_unlocks? @relation(fields: [hash], references: [hash], map: "metagraph_token_unlocks_ref") @@ -133,6 +135,7 @@ model dag_allow_spends { round_id String @db.Uuid ordinal BigInt @unique(map: "dag_allow_spends_ordinal") snapshot_hash String @db.VarChar + currency_id String? @db.VarChar dag_allow_spend_approvers dag_allow_spend_approvers[] addresses_dag_allow_spends_destination_addrToaddresses addresses @relation("dag_allow_spends_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spends_destination_addr_fk") addresses_dag_allow_spends_source_addrToaddresses addresses @relation("dag_allow_spends_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_allow_spends_source_addr_fk") @@ -193,6 +196,7 @@ model dag_spend_transactions { destination_addr String? @db.VarChar allow_spend_ref String? @unique @db.VarChar snapshot_hash String @db.VarChar + currency_id String? @db.VarChar dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_dag_allow_spends_fk") addresses addresses? @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_destination_addr_fk") @@ -211,6 +215,7 @@ model dag_token_locks { round_id String @db.Uuid snapshot_hash String @db.VarChar parent_hash String? @db.VarChar + currency_id String? @db.VarChar addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_locks_source_addr_fk") global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_lock_global_snapshot_fk") dag_token_unlock dag_token_unlocks? @@ -228,6 +233,7 @@ model dag_token_unlocks { updated_at DateTime @default(now()) @db.Timestamp(6) lock_reference_hash String @db.VarChar snapshot_hash String @db.VarChar + currency_id String? @db.VarChar global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_unlock_global_snapshot_fk") dag_token_lock dag_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_unlocks_token_locks_fk") addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "ddag_token_unlocks_address_fk") @@ -321,6 +327,7 @@ model metagraph_allow_spends { round_id String @db.Uuid ordinal BigInt @unique(map: "metagraph_allow_spends_ordinal") snapshot_hash String @db.VarChar + currency_id String? @db.VarChar metagraph_allow_spend_approvers metagraph_allow_spend_approvers[] addresses_metagraph_allow_spends_destination_addrToaddresses addresses @relation("metagraph_allow_spends_destination_addrToaddresses", fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_allow_spends_destination_addr_fk") addresses_metagraph_allow_spends_source_addrToaddresses addresses @relation("metagraph_allow_spends_source_addrToaddresses", fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_allow_spends_source_addr_fk") @@ -444,6 +451,7 @@ model metagraph_spend_transactions { destination_addr String @db.VarChar allow_spend_ref String? @unique @db.VarChar snapshot_hash String @db.VarChar + currency_id String? @db.VarChar addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_destination_addr_fk") metagraph_allow_spend metagraph_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "mg_spend_transactions_mg_allow_spends_fk") metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_spend_transactions_metagraph_id_fk") @@ -464,6 +472,7 @@ model metagraph_token_locks { parent_hash String? @db.VarChar round_id String @db.VarChar snapshot_hash String @db.VarChar + currency_id String? @db.VarChar metagraph metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_token_locks_source_addr_fk") @@ -485,6 +494,7 @@ model metagraph_token_unlocks { metagraph_id String @db.VarChar lock_reference_hash String @db.VarChar snapshot_hash String @db.VarChar + currency_id String? @db.VarChar addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "address_fk") metagraphs metagraphs @relation(fields: [metagraph_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_id_fk") @@ -544,6 +554,7 @@ model dag_expired_spend_transactions { updated_at DateTime @default(now()) @db.Timestamp(6) allow_spend_ref String @unique @db.VarChar snapshot_hash String @db.VarChar + currency_id String? @db.VarChar dag_allow_spend dag_allow_spends @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_expired_spend_transactions_dag_allow_spends_fk") global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction) @@ -559,6 +570,7 @@ model metagraph_expired_spend_transactions { metagraph_id String @db.VarChar allow_spend_ref String @unique @db.VarChar snapshot_hash String @db.VarChar + currency_id String? @db.VarChar metagraph_allow_spend metagraph_allow_spends @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "metagraph_expired_spend_transactions_metagraph_allow_spends_fk") metagraph_snapshot metagraph_snapshots? @relation(fields: [metagraph_id, snapshot_hash], references: [metagraph_id, hash]) diff --git a/prisma/seed.ts b/prisma/seed.ts index c08672a..c082e27 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -157,6 +157,7 @@ export const data_metagraph_snapshots = [ export const data_dag_token_locks = [ { hash: `token-lock-hash-001`, + currency_id: "currency-1", source_addr: data_addresses[0].address, amount: 1200n, ordinal: 1n, @@ -166,6 +167,7 @@ export const data_dag_token_locks = [ }, { hash: `token-lock-hash-002`, + currency_id: "currency-2", source_addr: data_addresses[0].address, amount: 1000n, ordinal: 2n, @@ -175,6 +177,7 @@ export const data_dag_token_locks = [ }, { hash: `token-lock-hash-003`, + currency_id: "currency-3", source_addr: data_addresses[0].address, amount: 1200n, ordinal: 3n, @@ -188,6 +191,7 @@ export const data_dag_token_locks = [ export const data_dag_token_unlocks = [ { hash: "token-unlock-hash-0010", + currency_id: data_dag_token_locks[0].currency_id, source_addr: data_addresses[0].address, amount: 1000n, lock_reference_hash: data_dag_token_locks[0].hash, @@ -199,6 +203,7 @@ export const data_dag_token_unlocks = [ export const data_metagraph_token_locks = [ { hash: "metagraph-token-lock-hash-0010", + currency_id: "currency-1", metagraph_id: data_metagraphs[0].id, source_addr: data_addresses[0].address, amount: 3000n, @@ -209,6 +214,7 @@ export const data_metagraph_token_locks = [ }, { hash: "metagraph-token-lock-hash-0020", + currency_id: "currency-2", metagraph_id: data_metagraphs[0].id, source_addr: data_addresses[0].address, amount: 4000n, @@ -219,6 +225,7 @@ export const data_metagraph_token_locks = [ }, { hash: "metagraph-token-lock-hash-0030", + currency_id: "currency-3", metagraph_id: data_metagraphs[0].id, source_addr: data_addresses[0].address, amount: 4000n, @@ -234,6 +241,7 @@ export const data_metagraph_token_unlocks = [ { hash: "metagraph-token-unlock-hash-001", metagraph_id: data_metagraphs[0].id, + currency_id: data_metagraph_token_locks[0].currency_id, source_addr: data_addresses[0].address, amount: 3000n, lock_reference_hash: data_metagraph_token_locks[0].hash, @@ -242,6 +250,7 @@ export const data_metagraph_token_unlocks = [ { hash: "metagraph-token-unlock-hash-002", metagraph_id: data_metagraphs[0].id, + currency_id: data_metagraph_token_locks[2].currency_id, source_addr: data_addresses[0].address, amount: 3000n, lock_reference_hash: data_metagraph_token_locks[2].hash, @@ -252,6 +261,7 @@ export const data_metagraph_token_unlocks = [ export const data_dag_allow_spends = [ { hash: "allowSpendHash1", + currency_id: "currency-1", source_addr: data_addresses[0].address, destination_addr: data_addresses[1].address, amount: 1000n, @@ -265,6 +275,7 @@ export const data_dag_allow_spends = [ }, { hash: "allowSpendHash2", + currency_id: "currency-2", source_addr: data_addresses[0].address, destination_addr: data_addresses[2].address, amount: 2000n, @@ -278,6 +289,7 @@ export const data_dag_allow_spends = [ }, { hash: "allowSpendHash3", + currency_id: "currency-1", source_addr: data_addresses[2].address, destination_addr: data_addresses[1].address, amount: 300n, @@ -291,6 +303,7 @@ export const data_dag_allow_spends = [ }, { hash: "allowSpendHash4", + currency_id: "currency-2", source_addr: data_addresses[2].address, destination_addr: data_addresses[3].address, amount: 300n, @@ -307,6 +320,7 @@ export const data_dag_allow_spends = [ export const data_dag_spend_transactions = [ { hash: "spendTxHash1", + currency_id: data_dag_allow_spends[0].currency_id, source_addr: data_addresses[0].address, destination_addr: data_addresses[1].address, amount: 1000n, @@ -320,6 +334,7 @@ export const data_dag_spend_transactions = [ export const data_dag_expired_spend_transactions = [ { hash: "expiredSpendTxHash1", + currency_id: data_dag_allow_spends[1].currency_id, source_addr: data_addresses[2].address, amount: 3000n, allow_spend_ref: data_dag_allow_spends[1].hash, @@ -333,6 +348,7 @@ export const data_metagraph_allow_spends = [ { metagraph_id: data_metagraphs[0].id, hash: "metaAllowSpendHash1", + currency_id: "currency-1", source_addr: data_addresses[0].address, destination_addr: data_addresses[1].address, amount: 500n, @@ -347,6 +363,7 @@ export const data_metagraph_allow_spends = [ { metagraph_id: data_metagraphs[0].id, hash: "metaAllowSpendHash2", + currency_id: "currency-2", source_addr: data_addresses[0].address, destination_addr: data_addresses[1].address, amount: 500n, @@ -361,6 +378,7 @@ export const data_metagraph_allow_spends = [ { metagraph_id: data_metagraphs[0].id, hash: "metaAllowSpendHash3", + currency_id: "currency-3", source_addr: data_addresses[2].address, destination_addr: data_addresses[3].address, amount: 750n, @@ -378,6 +396,7 @@ export const data_metagraph_spend_transactions = [ { metagraph_id: data_metagraphs[0].id, hash: "metaSpendTxHash1", + currency_id: data_metagraph_allow_spends[0].currency_id, source_addr: data_addresses[0].address, destination_addr: data_addresses[1].address, amount: 500n, @@ -392,6 +411,7 @@ export const data_metagraph_expired_spend_transactions = [ { metagraph_id: data_metagraphs[0].id, hash: "metaExpiredTxHash1", + currency_id: data_metagraph_allow_spends[2].currency_id, source_addr: data_addresses[2].address, amount: 750n, allow_spend_ref: data_metagraph_allow_spends[2].hash, @@ -610,7 +630,7 @@ export async function seed() { "DROP TABLE delegate_stake_total_rewards_view" ); await prisma.$executeRawUnsafe("DROP TABLE token_lock_total_rewards_view"); - runSqlFromFile("./migrations/20250529/01_add_staking_to_actions.sql"); + runSqlFromFile("./migrations/20250609/02_add_staking_to_actions.sql"); runSqlFromFile("./migrations/20250606/01_total_rewards_view.sql"); } diff --git a/src/handlers/actionsHandler.ts b/src/handlers/actionsHandler.ts index 09d4a2f..16b0600 100644 --- a/src/handlers/actionsHandler.ts +++ b/src/handlers/actionsHandler.ts @@ -34,12 +34,12 @@ const transactionFilter = (event): TransactionType[] => { return filtered.length > 0 ? filtered : actionsTransactions; }; -const currencyId = (transaction) => - transaction.metagraph_snapshot?.metagraph_id ?? null; - const actionResponse = (transaction) => ({ type: transaction.transaction_type, - currencyId: currencyId(transaction), + currencyId: + transaction.currency_id ?? + transaction.metagraph_snapshot?.metagraph_id ?? + null, hash: transaction.hash, amount: transaction.amount, source: transaction.source_addr, diff --git a/src/handlers/allowSpendsHandler.ts b/src/handlers/allowSpendsHandler.ts index ed50e8d..0541b2e 100644 --- a/src/handlers/allowSpendsHandler.ts +++ b/src/handlers/allowSpendsHandler.ts @@ -12,7 +12,7 @@ import { includes } from "lodash"; const prisma = new PrismaClient(); const allowSpendResponse = (transaction) => ({ - currencyId: transaction.currencyId, + currencyId: transaction.currency_id, hash: transaction.hash, ordinal: transaction.ordinal, amount: transaction.amount, @@ -33,7 +33,7 @@ const allowSpendResponse = (transaction) => ({ const allowSpendResponses = (txs) => txs.map(allowSpendResponse); const spendTransactionResponse = (transaction) => ({ - currencyId: transaction.currencyId, + currencyId: transaction.currency_id, hash: transaction.hash, amount: transaction.amount, source: transaction.source_addr, @@ -52,7 +52,7 @@ const spendTransactionResponse = (transaction) => ({ const spendTransactionResponses = (txs) => txs.map(spendTransactionResponse); const spendExpiredResponse = (transaction) => ({ - currencyId: transaction.currencyId, + currencyId: transaction.currency_id, hash: transaction.hash, amount: transaction.amount, source: transaction.source_addr, diff --git a/src/handlers/metagraphHandler.ts b/src/handlers/metagraphHandler.ts index 12c4976..c5e7c50 100644 --- a/src/handlers/metagraphHandler.ts +++ b/src/handlers/metagraphHandler.ts @@ -373,10 +373,20 @@ export const currencyTransaction = async ( hash: hash!, }, include: { - metagraph_snapshot: { select: { hash: true, ordinal: true } }, + metagraph_snapshot: { + select: { + hash: true, + ordinal: true, + global_snapshot: { + select: { + hash: true, + ordinal: true, + }, + }, + }, + }, }, }); - return respond(transaction, transactionResponse); } catch (error) { return handleError(error); diff --git a/src/handlers/tokenLocksHandler.ts b/src/handlers/tokenLocksHandler.ts index 0cb70e2..51ea845 100644 --- a/src/handlers/tokenLocksHandler.ts +++ b/src/handlers/tokenLocksHandler.ts @@ -11,7 +11,7 @@ import { handleError, respond } from "../response"; const prisma = new PrismaClient(); const commonTokenLockResponse = (transaction) => ({ - currencyId: transaction.currencyId, + currencyId: transaction.currency_id, hash: transaction.hash, amount: transaction.amount, source: transaction.source_addr, @@ -44,7 +44,7 @@ const metagraphTokenLockResponses = (txs) => txs.map(metagraphTokenLockResponse); const commonTokenUnlockResponse = (transaction) => ({ - currencyId: transaction.currencyId, + currencyId: transaction.currency_id, hash: transaction.hash, amount: transaction.amount, source: transaction.source_addr, diff --git a/tests/handlers/tokenLocksHandler.test.ts b/tests/handlers/tokenLocksHandler.test.ts index 46bee50..7270acf 100644 --- a/tests/handlers/tokenLocksHandler.test.ts +++ b/tests/handlers/tokenLocksHandler.test.ts @@ -25,6 +25,7 @@ const validateDagTokenLock = (lock) => { if (!match) return; expect(lock.hash).toBe(match.hash); + expect(lock.currencyId).toBe(match.currency_id); expect(lock.source).toBe(match.source_addr); expect(Number(lock.amount)).toBe(Number(match.amount)); expect(Number(lock.ordinal)).toBe(Number(match.ordinal)); @@ -41,6 +42,7 @@ const validateDagTokenUnlock = (unlock) => { if (!match) return; expect(unlock.hash).toBe(match.hash); + expect(unlock.currencyId).toBe(match.currency_id); expect(unlock.source).toBe(match.source_addr); expect(Number(unlock.amount)).toBe(Number(match.amount)); expect(unlock.tokenLockRef).toBe(match.lock_reference_hash); @@ -55,6 +57,7 @@ const validateMetagraphTokenLock = (lock) => { if (!match) return; expect(lock.hash).toBe(match.hash); + expect(lock.currencyId).toBe(match.currency_id); expect(lock.source).toBe(match.source_addr); expect(Number(lock.amount)).toBe(Number(match.amount)); expect(Number(lock.ordinal)).toBe(Number(match.ordinal)); @@ -73,6 +76,7 @@ const validateMetagraphTokenUnlock = (unlock) => { if (!match) return; expect(unlock.hash).toBe(match.hash); + expect(unlock.currencyId).toBe(match.currency_id); expect(unlock.source).toBe(match.source_addr); expect(Number(unlock.amount)).toBe(Number(match.amount)); expect(unlock.tokenLockRef).toBe(match.lock_reference_hash); From 0805ff09e40de632669d55d9a2fff52bdbb72b3a Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Thu, 19 Jun 2025 12:15:55 -0300 Subject: [PATCH 58/63] add desc indexes --- .../20250617/01_snapshot_ordinal_triggers.sql | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/prisma/migrations/20250617/01_snapshot_ordinal_triggers.sql b/prisma/migrations/20250617/01_snapshot_ordinal_triggers.sql index 2d01e4c..2a03f99 100644 --- a/prisma/migrations/20250617/01_snapshot_ordinal_triggers.sql +++ b/prisma/migrations/20250617/01_snapshot_ordinal_triggers.sql @@ -45,4 +45,17 @@ CREATE TRIGGER batch_trigger_set_dag_tx_snapshot_ordinal AFTER INSERT ON dag_transactions FOR EACH STATEMENT -EXECUTE FUNCTION batch_set_dag_tx_snapshot_ordinal(); \ No newline at end of file +EXECUTE FUNCTION batch_set_dag_tx_snapshot_ordinal(); + + +CREATE INDEX CONCURRENTLY idx_meta_txn_filter_order +ON metagraph_transactions ( + metagraph_id, + source_addr, + snapshot_ordinal DESC, + created_at DESC, + hash DESC +); + +CREATE INDEX CONCURRENTLY idx_global_snapshots_created_ordinal +ON global_snapshots (created_at DESC, ordinal DESC); \ No newline at end of file From 014db4d6467b1de8fac9ca21808f1997df928235 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Thu, 19 Jun 2025 12:26:04 -0300 Subject: [PATCH 59/63] fix view create script for test --- prisma/migrations/20250609/02_add_staking_to_actions.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prisma/migrations/20250609/02_add_staking_to_actions.sql b/prisma/migrations/20250609/02_add_staking_to_actions.sql index 184686f..4bc1cd0 100644 --- a/prisma/migrations/20250609/02_add_staking_to_actions.sql +++ b/prisma/migrations/20250609/02_add_staking_to_actions.sql @@ -1,5 +1,5 @@ -- DAG Tables -drop VIEW dag_actions_view; +DROP VIEW IF EXISTS dag_actions_view; CREATE OR REPLACE VIEW dag_actions_view AS SELECT das.hash, source_addr, amount, das.created_at, das.updated_at, @@ -59,7 +59,7 @@ UNION ALL -- Metagraph Tables -drop VIEW metagraph_actions_view; +DROP VIEW IF EXISTS metagraph_actions_view; CREATE OR REPLACE VIEW metagraph_actions_view AS SELECT mas.metagraph_id, mas.hash, mas.source_addr, mas.amount, mas.created_at, mas.updated_at, From 78bc1a147a7f42c7f428ab82920c741721d7b0be Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Thu, 19 Jun 2025 12:27:40 -0300 Subject: [PATCH 60/63] fix: global snapshot transactions pagination (#191) fix pagination for global snapshot txs --- src/handlers/dagHandler.ts | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/handlers/dagHandler.ts b/src/handlers/dagHandler.ts index 57013d3..259d362 100644 --- a/src/handlers/dagHandler.ts +++ b/src/handlers/dagHandler.ts @@ -23,6 +23,10 @@ import { toNumber, isFinite } from "lodash"; const prisma = new PrismaClient(); +const cursor = (row) => ({ + hash: row.hash, +}); + const globalSnapshotExists = async (term) => { return prisma.global_snapshots.findUnique({ where: extractHashOrdinal(term), @@ -157,13 +161,13 @@ export const globalSnapshotTransactions = async ( include: { global_snapshot: { select: { hash: true, ordinal: true } }, }, - orderBy: [{ created_at: "desc" }, { ordinal: "desc" }], + orderBy: [{ created_at: "desc" }, { ordinal: "desc" }, { hash: "desc" }], }; return await paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, + cursor, + cursor, query, prisma.dag_transactions.findMany, dagTransactionsResponse @@ -212,18 +216,10 @@ const dagTransactionsQuery = async ( ], }; - const toCursor = (row) => ({ - hash: row.hash, - }); - - const fromCursor = (row) => ({ - hash: row.hash, - }); - return await paginatedQuery( extractPagination(event), - toCursor, - fromCursor, + cursor, + cursor, query, prisma.dag_transactions.findMany, dagTransactionsResponse From 8fb6d7fed5619dce68f63b41b9af0de8547785ca Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Mon, 28 Jul 2025 11:13:06 -0300 Subject: [PATCH 61/63] support multiple rewards per address (#192) --- prisma/schema.prisma | 3 +- src/handlers/dagHandler.ts | 5 +- src/response.ts | 22 ++++++++- tests/handlers/dagHandler.test.ts | 76 +++++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+), 4 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 7b8b678..e2a84b9 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -178,12 +178,13 @@ model dag_reward_transactions { global_snapshot_hash String @db.VarChar destination_addr String @db.VarChar amount BigInt + idx Int created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) global_snapshot global_snapshots @relation(fields: [global_snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_reward_transaction_global_snapshot_fk") addresses addresses @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_reward_transactions_destination_addr_fk") - @@id([global_snapshot_hash, destination_addr], map: "dag_reward_transaction_pk") + @@id([global_snapshot_hash, destination_addr, idx], map: "dag_reward_transaction_pk") @@index([global_snapshot_hash]) } diff --git a/src/handlers/dagHandler.ts b/src/handlers/dagHandler.ts index 259d362..ffa9a77 100644 --- a/src/handlers/dagHandler.ts +++ b/src/handlers/dagHandler.ts @@ -113,16 +113,17 @@ export const globalSnapshotRewards = async ( const gsWhere = await globalSnapshotWhere(term); const toCursor = (row) => ({ - global_snapshot_hash_destination_addr: { + global_snapshot_hash_destination_addr_idx: { global_snapshot_hash: row.global_snapshot_hash, destination_addr: row.destination_addr, + idx: row.idx, }, }); const fromCursor = (row) => ({ global_snapshot_hash: row.global_snapshot_hash, destination_addr: row.destination_addr, + idx: row.idx, }); - return await paginatedQuery( extractPagination(event), toCursor, diff --git a/src/response.ts b/src/response.ts index 55020cb..742e848 100644 --- a/src/response.ts +++ b/src/response.ts @@ -36,7 +36,27 @@ export const globalSnapshotResponse = (snapshot) => ({ metagraphSnapshotCount: snapshot.metagraph_snapshot_count, }); -export const rewardsResponse = (rs) => rs.map(rewardResponse); +export const rewardsResponse = (rs) => { + // legacy: remove idx <0 if same address, amount exists with idx>=0 + // Group elements by address + const grouped = rs.reduce((acc, item) => { + acc[item.destination_addr] = acc[item.destination_addr] || []; + acc[item.destination_addr].push(item); + return acc; + }, {} as Record); + + // Filter based on the group size and index + + const filtered = Object.values(grouped).flatMap((items) => { + if (items.length === 1) { + return items; + } else { + return items.filter((item) => item.idx >= 0); + } + }); + + return filtered.map(rewardResponse); +}; export const rewardResponse = (reward) => ({ destination: reward.destination_addr, diff --git a/tests/handlers/dagHandler.test.ts b/tests/handlers/dagHandler.test.ts index 9cc4ec1..a6fbeb8 100644 --- a/tests/handlers/dagHandler.test.ts +++ b/tests/handlers/dagHandler.test.ts @@ -10,6 +10,7 @@ import { data_global_snapshots, prisma, } from "../../prisma/seed"; +import { rewardResponse } from "../../src/response"; const data_dag_blocks = [ { @@ -115,6 +116,41 @@ const data_dag_transactions = [ }, ]; +const data_dag_reward_transactions = [ + { + destination_addr: data_addresses[1].address, + amount: 1000n, + idx: -1, + global_snapshot_hash: data_global_snapshots[0].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, + { + destination_addr: data_addresses[1].address, + amount: 1000n, + idx: 1, + global_snapshot_hash: data_global_snapshots[0].hash, + created_at: new Date("2025-04-02T00:00:03Z"), + updated_at: new Date(), + }, + { + destination_addr: data_addresses[1].address, + amount: 3000n, + idx: 2, + global_snapshot_hash: data_global_snapshots[0].hash, + created_at: new Date("2025-04-02T00:00:03Z"), + updated_at: new Date(), + }, + { + destination_addr: data_addresses[0].address, + amount: 1000n, + idx: -1, + global_snapshot_hash: data_global_snapshots[0].hash, + created_at: new Date("2025-04-02T00:00:02Z"), + updated_at: new Date(), + }, +]; + const data_dag_balance_changes = [ { snapshot_hash: data_global_snapshots[0].hash, @@ -135,6 +171,10 @@ const seedData = async () => { data: data_dag_transactions, }); + await prisma.dag_reward_transactions.createMany({ + data: data_dag_reward_transactions, + }); + await prisma.dag_balance_changes.createMany({ data: data_dag_balance_changes, }); @@ -326,6 +366,42 @@ describe("DAG Handler Integration Tests", () => { }); }); + const normalize = (arr: any[]) => + arr.map(({ destination_addr, amount }) => ({ + destination: destination_addr, + amount: Number(amount), // whether it’s bigint or number + })); + + describe("globalSnapshotRewards", () => { + it("should return reward transactions for a global snapshot", async () => { + const requestedSnapshotHash = + data_dag_reward_transactions[0].global_snapshot_hash; + + const event = createAPIGatewayEvent( + { term: requestedSnapshotHash }, + { limit: "10" } + ); + + const response: APIGatewayProxyResult = + await dagHandler.globalSnapshotRewards(event); + + const expected = normalize([ + data_dag_reward_transactions[1], + data_dag_reward_transactions[2], + data_dag_reward_transactions[3], + ]); + + expect(response.statusCode).toBe(200); + const body = validatePaginatedResponse(response); + + expect(body.data.length).toBe(expected.length); + + const txs = body.data; + + expect(txs).toMatchObject(expected); + }); + }); + describe("dagTransaction", () => { it("should return a specific transaction by hash", async () => { const event = createAPIGatewayEvent({ From 5e8832203497238604fbe06ad73589a4ecdbb734 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Mon, 28 Jul 2025 11:31:05 -0300 Subject: [PATCH 62/63] Support many rewards per address (#193) update types --- src/response.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/response.ts b/src/response.ts index 742e848..8b48509 100644 --- a/src/response.ts +++ b/src/response.ts @@ -1,5 +1,6 @@ import { APIGatewayProxyResult } from "aws-lambda"; import { toNextString } from "./request-params"; +import { dag_reward_transactions } from "@prisma/client"; const DEFAULT_HEADERS = { "Content-Type": "application/json", @@ -36,14 +37,14 @@ export const globalSnapshotResponse = (snapshot) => ({ metagraphSnapshotCount: snapshot.metagraph_snapshot_count, }); -export const rewardsResponse = (rs) => { +export const rewardsResponse = (rs: dag_reward_transactions[]) => { // legacy: remove idx <0 if same address, amount exists with idx>=0 // Group elements by address const grouped = rs.reduce((acc, item) => { acc[item.destination_addr] = acc[item.destination_addr] || []; acc[item.destination_addr].push(item); return acc; - }, {} as Record); + }, {} as Record); // Filter based on the group size and index From 28dc8c92cc167357fb85bbdd7152d4d95d900814 Mon Sep 17 00:00:00 2001 From: Gabriel Claramunt Date: Wed, 30 Jul 2025 11:46:53 -0300 Subject: [PATCH 63/63] Improve spend pagination and add more comprehensive pagination test (#194) add more comprehensive pagination test --- prisma/schema.prisma | 4 +- src/handlers/allowSpendsHandler.ts | 81 ++++++++++--------- src/handlers/tokenLocksHandler.ts | 31 +++++--- tests/handlers/actionsHandler.test.ts | 57 +++++++++++++- tests/handlers/allowSpendsHandler.test.ts | 94 +++++++++++++++++++++++ tests/testUtils.ts | 57 ++++++++++---- 6 files changed, 255 insertions(+), 69 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e2a84b9..2ca4fc1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -197,7 +197,7 @@ model dag_spend_transactions { destination_addr String? @db.VarChar allow_spend_ref String? @unique @db.VarChar snapshot_hash String @db.VarChar - currency_id String? @db.VarChar + currency_id String? @db.VarChar dag_allow_spend dag_allow_spends? @relation(fields: [allow_spend_ref], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_dag_allow_spends_fk") addresses addresses? @relation(fields: [destination_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "dag_spend_transactions_destination_addr_fk") @@ -234,7 +234,7 @@ model dag_token_unlocks { updated_at DateTime @default(now()) @db.Timestamp(6) lock_reference_hash String @db.VarChar snapshot_hash String @db.VarChar - currency_id String? @db.VarChar + currency_id String? @db.VarChar global_snapshot global_snapshots @relation(fields: [snapshot_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_unlock_global_snapshot_fk") dag_token_lock dag_token_locks @relation(fields: [lock_reference_hash], references: [hash], onDelete: Cascade, onUpdate: NoAction, map: "dag_token_unlocks_token_locks_fk") addresses addresses @relation(fields: [source_addr], references: [address], onDelete: Cascade, onUpdate: NoAction, map: "ddag_token_unlocks_address_fk") diff --git a/src/handlers/allowSpendsHandler.ts b/src/handlers/allowSpendsHandler.ts index 0541b2e..e281a21 100644 --- a/src/handlers/allowSpendsHandler.ts +++ b/src/handlers/allowSpendsHandler.ts @@ -5,6 +5,9 @@ import { paginatedQuery, fromCreatedAtOrdinalCursor, toCreatedAtOrdinalCursor, + fromCreatedAtCursor, + toCreatedAtCursor, + hashCursor, } from "../pagination"; import { respond, handleError } from "../response"; import { includes } from "lodash"; @@ -219,9 +222,12 @@ export const spendTransactions = async ( try { return await paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, - { include: dagInclude, orderBy: { created_at: "desc" } }, + hashCursor, + hashCursor, + { + include: dagInclude, + orderBy: [{ created_at: "desc" }, { hash: "asc" }], + }, prisma.dag_spend_transactions.findMany, spendTransactionResponses ); @@ -239,14 +245,14 @@ export const globalSnapshotSpendTransactions = async ( return await paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, + hashCursor, + hashCursor, { where: { dag_allow_spend: { global_snapshot: filter }, }, include: dagInclude, - orderBy: { created_at: "desc" }, + orderBy: [{ created_at: "desc" }, { hash: "asc" }], }, prisma.dag_spend_transactions.findMany, spendTransactionResponses @@ -264,14 +270,14 @@ export const addressSpendTransactions = async ( return await paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, + hashCursor, + hashCursor, { where: { OR: [{ source_addr: address }, { destination_addr: address }], }, include: dagInclude, - orderBy: { created_at: "desc" }, + orderBy: [{ created_at: "desc" }, { hash: "asc" }], }, prisma.dag_spend_transactions.findMany, spendTransactionResponses @@ -287,9 +293,12 @@ export const allowSpendExpirations = async ( try { return await paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, - { include: dagInclude, orderBy: { created_at: "desc" } }, + hashCursor, + hashCursor, + { + include: dagInclude, + orderBy: [{ created_at: "desc" }, { hash: "asc" }], + }, prisma.dag_expired_spend_transactions.findMany, spendExpiredResponses ); @@ -324,14 +333,14 @@ export const globalSnapshotAllowSpendExpirations = async ( return await paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, + hashCursor, + hashCursor, { where: { dag_allow_spend: { global_snapshot: filter }, }, include: dagInclude, - orderBy: { created_at: "desc" }, + orderBy: [{ created_at: "desc" }, { hash: "asc" }], }, prisma.dag_expired_spend_transactions.findMany, spendExpiredResponses @@ -349,8 +358,8 @@ export const addressAllowSpendExpirations = async ( return await paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, + hashCursor, + hashCursor, { where: { OR: [ @@ -359,7 +368,7 @@ export const addressAllowSpendExpirations = async ( ], }, include: dagInclude, - orderBy: { created_at: "desc" }, + orderBy: [{ created_at: "desc" }, { hash: "asc" }], }, prisma.dag_expired_spend_transactions.findMany, spendExpiredResponses @@ -472,12 +481,12 @@ export const currencySpendTransactions = async ( return await paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, + hashCursor, + hashCursor, { where: { metagraph_id }, include: metagraphInclude, - orderBy: { created_at: "desc" }, + orderBy: [{ created_at: "desc" }, { hash: "asc" }], }, prisma.metagraph_spend_transactions.findMany, spendTransactionResponses @@ -513,8 +522,8 @@ export const currencySnapshotSpendTransactions = async ( return await paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, + hashCursor, + hashCursor, { where: { metagraph_id, @@ -523,7 +532,7 @@ export const currencySnapshotSpendTransactions = async ( }, }, include: metagraphInclude, - orderBy: { created_at: "desc" }, + orderBy: [{ created_at: "desc" }, { hash: "asc" }], }, prisma.metagraph_spend_transactions.findMany, spendTransactionResponses @@ -541,14 +550,14 @@ export const currencyAddressSpendTransactions = async ( return await paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, + hashCursor, + hashCursor, { where: { OR: [{ source_addr: address }, { destination_addr: address }], }, include: metagraphInclude, - orderBy: { created_at: "desc" }, + orderBy: [{ created_at: "desc" }, { hash: "asc" }], }, prisma.metagraph_spend_transactions.findMany, spendExpiredResponses @@ -566,12 +575,12 @@ export const currencyAllowSpendExpirations = async ( return await paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, + hashCursor, + hashCursor, { where: { metagraph_id }, include: metagraphInclude, - orderBy: { created_at: "desc" }, + orderBy: [{ created_at: "desc" }, { hash: "asc" }], }, prisma.metagraph_expired_spend_transactions.findMany, spendExpiredResponses @@ -608,8 +617,8 @@ export const currencySnapshotAllowSpendExpirations = async ( return await paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, + hashCursor, + hashCursor, { where: { metagraph_id, @@ -618,7 +627,7 @@ export const currencySnapshotAllowSpendExpirations = async ( }, }, include: metagraphInclude, - orderBy: { created_at: "desc" }, + orderBy: [{ created_at: "desc" }, { hash: "asc" }], }, prisma.metagraph_expired_spend_transactions.findMany, spendExpiredResponses @@ -636,8 +645,8 @@ export const currencyAddressAllowSpendExpirations = async ( return await paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, + hashCursor, + hashCursor, { where: { metagraph_id, @@ -647,7 +656,7 @@ export const currencyAddressAllowSpendExpirations = async ( ], }, include: metagraphInclude, - orderBy: { created_at: "desc" }, + orderBy: [{ created_at: "desc" }, { hash: "asc" }], }, prisma.metagraph_expired_spend_transactions.findMany, spendExpiredResponses diff --git a/src/handlers/tokenLocksHandler.ts b/src/handlers/tokenLocksHandler.ts index 51ea845..90b66bb 100644 --- a/src/handlers/tokenLocksHandler.ts +++ b/src/handlers/tokenLocksHandler.ts @@ -5,6 +5,7 @@ import { paginatedQuery, fromCreatedAtOrdinalCursor, toCreatedAtOrdinalCursor, + hashCursor, } from "../pagination"; import { handleError, respond } from "../response"; @@ -227,13 +228,14 @@ export const tokenUnlocks = async ( ): Promise => { return paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, + hashCursor, + hashCursor, { include: includeDagGlobalSnapshotOrdinal, orderBy: [ { global_snapshot: { ordinal: "desc" } }, { created_at: "desc" }, + { hash: "asc" }, ], }, prisma.dag_token_unlocks.findMany, @@ -267,8 +269,8 @@ export const globalSnapshotTokenUnlocks = async ( return paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, + hashCursor, + hashCursor, { where: { global_snapshot: filter, @@ -277,6 +279,7 @@ export const globalSnapshotTokenUnlocks = async ( orderBy: [ { global_snapshot: { ordinal: "desc" } }, { created_at: "desc" }, + { hash: "asc" }, ], }, prisma.dag_token_unlocks.findMany, @@ -295,14 +298,15 @@ export const addressTokenUnlocks = async ( return paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, + hashCursor, + hashCursor, { where: { source_addr: address }, include: includeDagGlobalSnapshotOrdinal, orderBy: [ { global_snapshot: { ordinal: "desc" } }, { created_at: "desc" }, + { hash: "asc" }, ], }, prisma.dag_token_unlocks.findMany, @@ -416,8 +420,8 @@ export const metagraphTokenUnlocks = async ( const { metagraph_id } = event.pathParameters || {}; return paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, + hashCursor, + hashCursor, { where: { metagraph_id }, include: { @@ -427,6 +431,7 @@ export const metagraphTokenUnlocks = async ( orderBy: [ { metagraph_snapshot: { ordinal: "desc" } }, { created_at: "desc" }, + { hash: "asc" }, ], }, prisma.metagraph_token_unlocks.findMany, @@ -463,8 +468,8 @@ export const metagraphSnapshotTokenUnlocks = async ( return paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, + hashCursor, + hashCursor, { where: { metagraph_id, @@ -477,6 +482,7 @@ export const metagraphSnapshotTokenUnlocks = async ( orderBy: [ { metagraph_snapshot: { ordinal: "desc" } }, { created_at: "desc" }, + { hash: "asc" }, ], }, prisma.metagraph_token_unlocks.findMany, @@ -495,8 +501,8 @@ export const metagraphAddressTokenUnlocks = async ( return paginatedQuery( extractPagination(event), - toCreatedAtOrdinalCursor, - fromCreatedAtOrdinalCursor, + hashCursor, + hashCursor, { where: { metagraph_id, source_addr: address }, include: { @@ -506,6 +512,7 @@ export const metagraphAddressTokenUnlocks = async ( orderBy: [ { metagraph_snapshot: { ordinal: "desc" } }, { created_at: "desc" }, + { hash: "asc" }, ], }, prisma.metagraph_token_unlocks.findMany, diff --git a/tests/handlers/actionsHandler.test.ts b/tests/handlers/actionsHandler.test.ts index 8f3c546..f86ba7d 100644 --- a/tests/handlers/actionsHandler.test.ts +++ b/tests/handlers/actionsHandler.test.ts @@ -4,6 +4,7 @@ import { createAPIGatewayEvent, validateResponseStructure, validatePaginatedResponse, + validatePaginationNext, } from "../testUtils"; import { data_dag_token_locks, @@ -87,8 +88,12 @@ describe("actionsHandler", () => { body.data.forEach(validateAction); }); + it("dagActionsPagination", async () => { + await validatePaginationNext("1", {}, actionsHandler.dagActions); + }); + it("globalSnapshotActions", async () => { - const event = createAPIGatewayEvent({ term: "1000" }, {}); + const event = createAPIGatewayEvent({ term: "2556535" }, {}); const result = (await actionsHandler.globalSnapshotActions( event )) as APIGatewayProxyResult; @@ -99,6 +104,14 @@ describe("actionsHandler", () => { body.data.forEach(validateAction); }); + it("globalSnapshotDagActionsPagination", async () => { + await validatePaginationNext( + "1", + { term: "2556535" }, + actionsHandler.globalSnapshotActions + ); + }); + it("dagAddressActions", async () => { const address = data_dag_token_locks[0].source_addr; const event = createAPIGatewayEvent({ address }, {}); @@ -113,6 +126,15 @@ describe("actionsHandler", () => { body.data.forEach(validateAction); }); + it("dagAddressActionsPagination", async () => { + const address = data_dag_token_locks[0].source_addr; + await validatePaginationNext( + "1", + { address }, + actionsHandler.dagAddressActions + ); + }); + it("currencyActions", async () => { const metagraph_id = data_metagraph_token_locks[0].metagraph_id; const event = createAPIGatewayEvent({ metagraph_id }, {}); @@ -127,6 +149,15 @@ describe("actionsHandler", () => { body.data.forEach(validateAction); }); + it("currencyActionsPagination", async () => { + const metagraph_id = data_metagraph_token_locks[0].metagraph_id; + await validatePaginationNext( + "1", + { metagraph_id }, + actionsHandler.currencyActions + ); + }); + it("currencySnapshotActions", async () => { const metagraph_id = data_metagraph_token_locks[0].metagraph_id; const snapshotHash = data_metagraph_snapshots[0].hash; @@ -145,9 +176,19 @@ describe("actionsHandler", () => { body.data.forEach(validateAction); }); - it("currencyAddressActions", async () => { + it("currencySnapshotActionsPagination", async () => { const metagraph_id = data_metagraph_token_locks[0].metagraph_id; - const address = data_metagraph_token_locks[0].source_addr; + const snapshotHash = data_metagraph_snapshots[0].hash; + await validatePaginationNext( + "1", + { metagraph_id, term: snapshotHash }, + actionsHandler.currencySnapshotActions + ); + }); + + it("currencyAddressActions", async () => { + const metagraph_id = data_metagraph_allow_spends[0].metagraph_id; + const address = data_metagraph_allow_spends[0].source_addr; const event = createAPIGatewayEvent({ metagraph_id, address }, {}); const result = (await actionsHandler.currencyAddressActions( event @@ -159,4 +200,14 @@ describe("actionsHandler", () => { expect(body.data.length).toBe(9); body.data.forEach(validateAction); }); + + it("currencyAddressActionsPagination", async () => { + const metagraph_id = data_metagraph_allow_spends[0].metagraph_id; + const address = data_metagraph_allow_spends[0].source_addr; + await validatePaginationNext( + "1", + { metagraph_id, address }, + actionsHandler.currencyAddressActions + ); + }); }); diff --git a/tests/handlers/allowSpendsHandler.test.ts b/tests/handlers/allowSpendsHandler.test.ts index e2ea3fd..b81d92b 100644 --- a/tests/handlers/allowSpendsHandler.test.ts +++ b/tests/handlers/allowSpendsHandler.test.ts @@ -3,6 +3,7 @@ import * as allowSpendsHandler from "../../src/handlers/allowSpendsHandler"; import { createAPIGatewayEvent, validatePaginatedResponse, + validatePaginationNext, validateResponseStructure, } from "../testUtils"; import { @@ -136,6 +137,12 @@ describe("AllowSpends Handler Integration Tests", () => { }); }); + it("allowSpendsPagination", async () => { + const metagraph_id = data_metagraph_allow_spends[0].metagraph_id; + const address = data_metagraph_allow_spends[0].source_addr; + await validatePaginationNext("1", {}, allowSpendsHandler.allowSpends); + }); + describe("allowSpend", () => { it("should return a specific allow spend by hash", async () => { const hash = data_dag_allow_spends[0].hash; @@ -180,6 +187,14 @@ describe("AllowSpends Handler Integration Tests", () => { expect(body.data.length).toBe(1); body.data.forEach(validateDagAllowSpend); }); + + it("globalSnapshotAllowSpendsPagination", async () => { + await validatePaginationNext( + "1", + {}, + allowSpendsHandler.globalSnapshotAllowSpends + ); + }); }); describe("addressAllowSpends", () => { @@ -194,6 +209,7 @@ describe("AllowSpends Handler Integration Tests", () => { expect(body.data.length).toBe(1); body.data.forEach(validateDagAllowSpend); }); + it("should return active only allow spends for a specific address", async () => { const address = data_addresses[3].address; const event = createAPIGatewayEvent({ address }, { active: "true" }); @@ -205,6 +221,15 @@ describe("AllowSpends Handler Integration Tests", () => { expect(body.data.length).toBe(1); body.data.forEach(validateDagAllowSpend); }); + + it("addressAllowSpendsPagination", async () => { + const address = data_addresses[0].address; + await validatePaginationNext( + "1", + { address }, + allowSpendsHandler.addressAllowSpends + ); + }); }); describe("spendTransactions", () => { @@ -217,6 +242,10 @@ describe("AllowSpends Handler Integration Tests", () => { expect(body.data.length).toBe(data_dag_spend_transactions.length); validateDagSpendTransaction(body.data[0]); }); + + // it("spendTransactionsPagination", async () => { + // await validatePaginationNext("1", {}, allowSpendsHandler.spendTransactions); + // }); }); describe("allowSpendExpirations", () => { @@ -229,6 +258,10 @@ describe("AllowSpends Handler Integration Tests", () => { expect(body.data.length).toBe(data_dag_expired_spend_transactions.length); validateDagExpiredSpend(body.data[0]); }); + + // it("spendTransactionsPagination", async () => { + // await validatePaginationNext("1", {}, allowSpendsHandler.allowSpendExpirations); + // }); }); describe("addressSpendTransactions", () => { @@ -243,6 +276,11 @@ describe("AllowSpends Handler Integration Tests", () => { expect(body.data.length).toBeGreaterThan(0); body.data.forEach(validateDagSpendTransaction); }); + + // it("addressSpendTransactionsPagination", async () => { + // const address = data_addresses[0].address; + // await validatePaginationNext("1", {address}, allowSpendsHandler.addressSpendTransactions); + // }); }); describe("addressAllowSpendExpirations", () => { @@ -259,6 +297,10 @@ describe("AllowSpends Handler Integration Tests", () => { expect(body.data.length).toBeGreaterThan(0); body.data.forEach(validateDagExpiredSpend); }); + // it("addressAllowSpendExpirationsPagination", async () => { + // const address = data_addresses[0].address; + // await validatePaginationNext("1", {address}, allowSpendsHandler.addressAllowSpendExpirations); + // }); }); }); @@ -290,6 +332,15 @@ describe("Metagraph AllowSpends Handler Integration Tests", () => { }); }); + it("currencyAllowSpendsPagination", async () => { + const metagraph_id = data_metagraphs[0].id; + await validatePaginationNext( + "1", + { metagraph_id }, + allowSpendsHandler.currencyAllowSpends + ); + }); + describe("currencySpendTransactions", () => { it("should return spend transactions", async () => { const event = createAPIGatewayEvent({ @@ -304,6 +355,10 @@ describe("Metagraph AllowSpends Handler Integration Tests", () => { expect(body.data.length).toBe(data_metagraph_spend_transactions.length); body.data.forEach(validateMgSpendTransaction); }); + // it("currencySpendTransactionsPagination", async () => { + // const metagraph_id= data_metagraphs[0].id + // await validatePaginationNext("1", {metagraph_id}, allowSpendsHandler.currencySpendTransactions); + // }); }); describe("currencyAllowSpendExpirations", () => { @@ -322,6 +377,10 @@ describe("Metagraph AllowSpends Handler Integration Tests", () => { ); body.data.forEach(validateMgExpiredSpend); }); + // it("currencyAllowSpendExpirationsPagination", async () => { + // const metagraph_id= data_metagraphs[0].id + // await validatePaginationNext("1", {metagraph_id}, allowSpendsHandler.currencyAllowSpendExpirations); + // }); }); describe("currencySnapshotAllowSpends", () => { @@ -356,6 +415,12 @@ describe("Metagraph AllowSpends Handler Integration Tests", () => { expect(body.data.length).toBe(1); body.data.forEach(validateMgAllowSpend); }); + + // it("currencySnapshotAllowSpendsPagination", async () => { + // const metagraph_id= data_metagraphs[0].id + // const hash_or_ordinal= data_metagraph_snapshots[0].hash + // await validatePaginationNext("1", {metagraph_id, hash_or_ordinal}, allowSpendsHandler.currencySnapshotAllowSpends); + // }); }); describe("currencySnapshotSpendTransactions", () => { @@ -372,6 +437,11 @@ describe("Metagraph AllowSpends Handler Integration Tests", () => { expect(body.data.length).toBe(1); body.data.forEach(validateMgSpendTransaction); }); + // it("currencySnapshotSpendTransactionsPagination", async () => { + // const metagraph_id= data_metagraphs[0].id + // const hash_or_ordinal= data_metagraph_snapshots[0].hash + // await validatePaginationNext("1", {metagraph_id, hash_or_ordinal}, allowSpendsHandler.currencySnapshotSpendTransactions); + // }); }); describe("currencySnapshotAllowSpendExpirations", () => { @@ -388,6 +458,11 @@ describe("Metagraph AllowSpends Handler Integration Tests", () => { expect(body.data.length).toBe(1); body.data.forEach(validateMgExpiredSpend); }); + // it("currencySnapshotAllowSpendExpirationsPagination", async () => { + // const metagraph_id= data_metagraphs[0].id + // const hash_or_ordinal= data_metagraph_snapshots[0].hash + // await validatePaginationNext("1", {metagraph_id, hash_or_ordinal}, allowSpendsHandler.currencySnapshotAllowSpendExpirations); + // }); }); describe("currencyAddressAllowSpends", () => { @@ -420,6 +495,15 @@ describe("Metagraph AllowSpends Handler Integration Tests", () => { const body = validatePaginatedResponse(response); expect(body.data.length).toBe(1); }); + it("currencyAddressAllowSpendsPagination", async () => { + const metagraph_id = data_metagraphs[0].id; + const address = data_addresses[1].address; + await validatePaginationNext( + "1", + { metagraph_id, address }, + allowSpendsHandler.currencyAddressAllowSpends + ); + }); }); describe("currencyAddressSpendTransactions", () => { @@ -435,6 +519,11 @@ describe("Metagraph AllowSpends Handler Integration Tests", () => { const body = validatePaginatedResponse(response); expect(body.data.length).toBe(1); }); + // it("currencyAddressSpendTransactionsPagination", async () => { + // const metagraph_id= data_metagraphs[0].id + // const address = data_addresses[1].address + // await validatePaginationNext("1", {metagraph_id, address}, allowSpendsHandler.currencyAddressSpendTransactions); + // }); }); describe("currencyAddressAllowSpendExpirations", () => { @@ -450,5 +539,10 @@ describe("Metagraph AllowSpends Handler Integration Tests", () => { const body = validatePaginatedResponse(response); expect(body.data.length).toBe(1); }); + // it("currencyAddressAllowSpendExpirationsPagination", async () => { + // const metagraph_id= data_metagraphs[0].id + // const address = data_addresses[0].address + // await validatePaginationNext("1", {metagraph_id, address}, allowSpendsHandler.currencyAddressAllowSpendExpirations); + // }); }); }); diff --git a/tests/testUtils.ts b/tests/testUtils.ts index db1ed93..0219b18 100644 --- a/tests/testUtils.ts +++ b/tests/testUtils.ts @@ -1,4 +1,4 @@ -import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; +import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; /** * Creates an API Gateway event object for testing handler functions @@ -8,15 +8,17 @@ export const createAPIGatewayEvent = ( queryStringParameters: Record = {}, body: string | null = null ): APIGatewayProxyEvent => ({ - httpMethod: 'GET', + httpMethod: "GET", isBase64Encoded: false, - path: '', - resource: '', + path: "", + resource: "", body, headers: {}, multiValueHeaders: {}, pathParameters: Object.keys(pathParameters).length ? pathParameters : null, - queryStringParameters: Object.keys(queryStringParameters).length ? queryStringParameters : null, + queryStringParameters: Object.keys(queryStringParameters).length + ? queryStringParameters + : null, multiValueQueryStringParameters: null, stageVariables: null, requestContext: {} as any, @@ -35,44 +37,67 @@ export const parseResponseBody = (response: APIGatewayProxyResult) => { export const validateResponseStructure = (response: APIGatewayProxyResult) => { expect(response.statusCode).toBeDefined(); expect(response.headers).toBeDefined(); - + // Check headers if they exist if (response.headers) { - expect(response.headers['Content-Type']).toBe('application/json'); - expect(response.headers['Access-Control-Allow-Origin']).toBe('*'); + expect(response.headers["Content-Type"]).toBe("application/json"); + expect(response.headers["Access-Control-Allow-Origin"]).toBe("*"); } - + expect(response.body).toBeDefined(); - + const body = parseResponseBody(response); expect(body).toBeDefined(); - + // Success responses should have a data field if (response.statusCode === 200) { expect(body.data).toBeDefined(); } - + // Error responses should have message and errors fields if (response.statusCode >= 400) { expect(body.message).toBeDefined(); expect(body.errors).toBeDefined(); } - + return body; }; /** * Validates that a paginated response has the correct structure */ -export const validatePaginatedResponse = (response: APIGatewayProxyResult, shouldHaveNext: boolean = false) => { +export const validatePaginatedResponse = ( + response: APIGatewayProxyResult, + shouldHaveNext: boolean = false +) => { const body = validateResponseStructure(response); - + expect(Array.isArray(body.data)).toBe(true); if (shouldHaveNext) { expect(body.meta).toBeDefined(); expect(body.meta.next !== undefined).toBe(true); } - + return body; }; + +export const validatePaginationNext = async ( + limit: string, + pathParameters: Record = {}, + action: (event: APIGatewayProxyEvent) => Promise +) => { + const event = createAPIGatewayEvent(pathParameters, { limit }); + + const response = await action(event); + expect(response.statusCode).toBe(200); + + const body = validatePaginatedResponse(response); + const next = body.meta.next; + + const event2 = createAPIGatewayEvent(pathParameters, { limit, next }); + const response2 = await action(event2); + expect(response2.statusCode).toBe(200); + + validatePaginatedResponse(response2); +};