diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..1d6eef41 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,71 @@ +name: release-images + +on: + push: + tags: + - "v*" # ex: v1.0.0 + +permissions: + contents: read + packages: write + +jobs: + build-and-push: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - service: api + dockerfile: backend/Dockerfile.api + image: ghcr.io/forgeyields/svk-api + - service: indexer + dockerfile: backend/Dockerfile.indexer + image: ghcr.io/forgeyields/svk-indexer + - service: aum + dockerfile: backend/Dockerfile.relayerOnChainAum + image: ghcr.io/forgeyields/svk-aum + - service: relayer-redeem + dockerfile: backend/Dockerfile.relayerAutomaticRedeem + image: ghcr.io/forgeyields/svk-relayer-redeem + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Extract version + id: ver + run: | + TAG="${GITHUB_REF_NAME}" + echo "version=${TAG#v}" >> $GITHUB_OUTPUT + echo "mm=$(echo ${TAG#v} | awk -F. '{print $1"."$2}')" >> $GITHUB_OUTPUT + echo "maj=$(echo ${TAG#v} | awk -F. '{print $1}')" >> $GITHUB_OUTPUT + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build + Push ${{ matrix.service }} + uses: docker/build-push-action@v6 + with: + context: . + file: ${{ matrix.dockerfile }} + push: true + platforms: linux/amd64 + tags: | + ${{ matrix.image }}:v${{ steps.ver.outputs.version }} + ${{ matrix.image }}:v${{ steps.ver.outputs.mm }} + ${{ matrix.image }}:latest + cache-from: type=gha + cache-to: type=gha,mode=max + provenance: false + sbom: false diff --git a/.gitignore b/.gitignore index ef21a7cc..801ede80 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ target -.snfoundry_cache \ No newline at end of file +.snfoundry_cache + +# Deployment files (user-specific) +scripts/deployments.json + +.env \ No newline at end of file diff --git a/README.md b/README.md index 5d2c7c76..238a983e 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,12 @@ scarb build snforge test ``` +### Scripts & Backend + +For deployment scripts and configuration utilities, see [scripts/README.md](scripts/README.md). + +For backend services (API and indexer), see [backend/README.md](backend/README.md). + ## Testing The project includes comprehensive test suites: diff --git a/Scarb.lock b/Scarb.lock index 95633666..b67ee2d0 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -9,14 +9,14 @@ checksum = "sha256:1e08ebba0ed9f7217b8efc283d2ad41730257cf41a47ca88a94fb0fafad22 [[package]] name = "openzeppelin" -version = "2.0.0" -source = "registry+https://scarbs.xyz/" -checksum = "sha256:5e4fdecc957cfca7854d95912dc72d9f725517c063b116512900900add29fd77" +version = "3.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" dependencies = [ "openzeppelin_access", "openzeppelin_account", "openzeppelin_finance", "openzeppelin_governance", + "openzeppelin_interfaces", "openzeppelin_introspection", "openzeppelin_merkle_tree", "openzeppelin_presets", @@ -28,67 +28,73 @@ dependencies = [ [[package]] name = "openzeppelin_access" -version = "2.0.0" -source = "registry+https://scarbs.xyz/" -checksum = "sha256:511681dd26d814ee2bc996d44ff8cb4aaa5ae9d14272130def7eb901cf004850" +version = "3.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" dependencies = [ + "openzeppelin_interfaces", "openzeppelin_introspection", ] [[package]] name = "openzeppelin_account" -version = "2.0.0" -source = "registry+https://scarbs.xyz/" -checksum = "sha256:fb3381c50d68b028d3801feb43df378e2bd62137b6884844f8f60aefe796188b" +version = "3.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" dependencies = [ + "openzeppelin_interfaces", "openzeppelin_introspection", "openzeppelin_utils", ] [[package]] name = "openzeppelin_finance" -version = "2.0.0" -source = "registry+https://scarbs.xyz/" -checksum = "sha256:e9456ef69502a87c4c99bf50145351b50950f8b11244847d92935c466c4ba787" +version = "3.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" dependencies = [ "openzeppelin_access", + "openzeppelin_interfaces", "openzeppelin_token", ] [[package]] name = "openzeppelin_governance" -version = "2.0.0" -source = "registry+https://scarbs.xyz/" -checksum = "sha256:056e6d6f3d48193b53f06283884f8a9675f986fc85425f6a40e8c1aeb3b3ecfa" +version = "3.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" dependencies = [ "openzeppelin_access", "openzeppelin_account", + "openzeppelin_interfaces", "openzeppelin_introspection", "openzeppelin_token", "openzeppelin_utils", ] +[[package]] +name = "openzeppelin_interfaces" +version = "2.1.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" + [[package]] name = "openzeppelin_introspection" -version = "2.0.0" -source = "registry+https://scarbs.xyz/" -checksum = "sha256:87773ed6cd2318f169283ecbbb161890d1996260a80302d81ec45b70ee5e54c1" +version = "3.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" +dependencies = [ + "openzeppelin_interfaces", +] [[package]] name = "openzeppelin_merkle_tree" -version = "2.0.0" -source = "registry+https://scarbs.xyz/" -checksum = "sha256:47f80c9ce59557774243214f8e75c5e866f30f3d8daa755855f6ffd01c89ca89" +version = "3.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" [[package]] name = "openzeppelin_presets" -version = "2.0.0" -source = "registry+https://scarbs.xyz/" -checksum = "sha256:36c761ee923f1dc0887c0eab8c224b49ac242dbfe9163fbb0b08562042ab3d98" +version = "3.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" dependencies = [ "openzeppelin_access", "openzeppelin_account", "openzeppelin_finance", + "openzeppelin_interfaces", "openzeppelin_introspection", "openzeppelin_token", "openzeppelin_upgrades", @@ -97,40 +103,35 @@ dependencies = [ [[package]] name = "openzeppelin_security" -version = "2.0.0" -source = "registry+https://scarbs.xyz/" -checksum = "sha256:902932ec296c2f400e0ac7c579edeaafd6067b6ce6d9854c1191de28e396ffe3" +version = "3.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" +dependencies = [ + "openzeppelin_interfaces", +] [[package]] name = "openzeppelin_token" -version = "2.0.0" -source = "registry+https://scarbs.xyz/" -checksum = "sha256:6fe61f63b5a6706018265fb7373b6e5bd3ff829bdc760b2b90296b1e708d180c" +version = "3.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" dependencies = [ "openzeppelin_access", "openzeppelin_account", + "openzeppelin_interfaces", "openzeppelin_introspection", "openzeppelin_utils", ] [[package]] name = "openzeppelin_upgrades" -version = "2.0.0" -source = "registry+https://scarbs.xyz/" -checksum = "sha256:560d57a9c3f3ec5a476e82fec8963c93c8df63a4ff9ff134f64ab8383bde3c61" +version = "3.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" [[package]] name = "openzeppelin_utils" -version = "2.0.0" -source = "registry+https://scarbs.xyz/" -checksum = "sha256:bf799c794139837f397975ffdf6a7ed5032d198bbf70e87a8f44f144a9dfc505" - -[[package]] -name = "pragma_lib" -version = "2.11.4" -source = "git+https://github.com/astraly-labs/pragma-lib#edb55442d36565cbd99c226e38c4f8040efb774b" +version = "3.0.0-alpha.1" +source = "git+https://github.com/OpenZeppelin/cairo-contracts#a2bdd1bdbdf96a9291ba0f2269f1e11c451fddb9" dependencies = [ - "openzeppelin", + "openzeppelin_interfaces", ] [[package]] @@ -173,6 +174,5 @@ version = "0.1.0" dependencies = [ "alexandria_math", "openzeppelin", - "pragma_lib", "snforge_std", ] diff --git a/Scarb.toml b/Scarb.toml index bedef851..b4943bfe 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -28,10 +28,9 @@ keywords = [ [workspace.dependencies] starknet = "2.12.0" -openzeppelin = "2.0.0" +openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts" } snforge_std = "0.48.0" alexandria_math = "0.6.0" -pragma_lib = { git = "https://github.com/astraly-labs/pragma-lib" } [dependencies] diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 00000000..09e1868d --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,83 @@ +# Dependencies +node_modules +**/node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Build outputs +dist +build +**/dist +**/build + +# Development files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# IDE and editor files +.vscode +.idea +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Git +.git +.gitignore + +# Docker +Dockerfile* +docker-compose*.yml +.dockerignore + +# Logs +logs +*.log + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Dependency directories +.pnp +.pnp.js + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Prisma generated client (will be regenerated in container) +**/prisma/generated + +# Temporary files +tmp/ +temp/ + +# Documentation +*.md +docs/ + +# Test files +test/ +tests/ +**/*.test.* +**/*.spec.* \ No newline at end of file diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 00000000..fe5124d7 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,70 @@ +# ========================================== +# SHARED ENVIRONMENT VARIABLES +# ========================================== + +# Database (required by all services) +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/starknet_vault_kit" + +# StarkNet RPC (required by API, relayers) +RPC_URL="https://starknet-mainnet.public.blastapi.io" + +# Vault Contract Address (required by all services) +VAULT_ADDRESS="0x0000000000000000000000000000000000000000000000000000000000000000" + +# ========================================== +# API SERVICE +# ========================================== + +# Server port (optional, defaults to 3000) +PORT=3000 + +# ========================================== +# INDEXER SERVICE +# ========================================== + +# Apibara token for indexing (required) +APIBARA_TOKEN="your_apibara_token_here" + +# Indexing start block (optional) +START_BLOCK=12993 + +# Force start from START_BLOCK ignoring database state (optional, defaults to false) +FORCE_START_BLOCK=false + +# ========================================== +# RELAYER AUTOMATIC REDEEM SERVICE +# ========================================== + +# Relayer wallet address (required) +RELAYER_ADDRESS="0x..." + +# Relayer private key (required) +RELAYER_PRIVATE_KEY="0x..." + +# Cron schedule for automatic redeem checks (optional, defaults to "*/5 * * * *") +CRON_SCHEDULE="*/5 * * * *" + +# ========================================== +# RELAYER ON-CHAIN AUM SERVICE +# ========================================== + +# Note: RELAYER_ADDRESS and RELAYER_PRIVATE_KEY are shared with automatic redeem service + +# On-chain AUM provider contract address (required) +ON_CHAIN_AUM_PROVIDER="0x..." + +# ========================================== +# LOGGING CONFIGURATION (optional) +# ========================================== + +# Log level (optional, defaults to info) +LOG_LEVEL="info" # Options: error, warn, info, debug + +# Enable file logging (optional, defaults to false) +ENABLE_FILE_LOGGING="false" + +# Log directory (optional, defaults to logs) +LOG_DIR="logs" + +# Node environment (optional, defaults to development) +NODE_ENV="development" # Options: development, production \ No newline at end of file diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 00000000..9fdf324d --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,142 @@ +# Logs +logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Build artifacts +dist/ +*.js +*.d.ts +*.js.map +!node_modules/** + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db \ No newline at end of file diff --git a/backend/.prettierrc b/backend/.prettierrc new file mode 100644 index 00000000..526aaef6 --- /dev/null +++ b/backend/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": true, + "trailingComma": "es5", + "singleQuote": true, + "printWidth": 120, + "tabWidth": 2 +} \ No newline at end of file diff --git a/backend/Dockerfile.api b/backend/Dockerfile.api new file mode 100644 index 00000000..7dbd51be --- /dev/null +++ b/backend/Dockerfile.api @@ -0,0 +1,42 @@ +# ---- build ---- +FROM node:20-alpine AS build +WORKDIR /app +RUN corepack enable && corepack prepare pnpm@9.9.0 --activate +COPY backend/ . +RUN pnpm install --frozen-lockfile +# generate Prisma client +RUN pnpm prisma:generate +# build tous les workspaces (libs + apps) pour résoudre les liens pnpm +RUN pnpm -r build +# copy the compiled apps directly without pruning to preserve all dependencies + +# ---- runtime ---- +FROM node:20-alpine +ENV NODE_ENV=production +WORKDIR /app +# user non-root +RUN addgroup -S app && adduser -S app -G app +# copier deps prod +COPY --from=build /app/node_modules ./node_modules +# copier manifests afin que les liens pnpm workspace restent valides +COPY --from=build /app/package.json ./ +COPY --from=build /app/pnpm-workspace.yaml ./ +# copier les package.json des apps et libs pour les deps +COPY --from=build /app/apps/api/package.json ./apps/api/ +COPY --from=build /app/libs/*/package.json ./libs/*/ +# copier le code compilé des apps et libs +COPY --from=build /app/apps/api/dist ./apps/api/dist +COPY --from=build /app/libs ./libs +# créer les symlinks pour les packages workspace et dependencies manquantes +RUN mkdir -p node_modules/@forge && \ + ln -sf ../../libs/config node_modules/@forge/config && \ + ln -sf ../../libs/db node_modules/@forge/db && \ + ln -sf ../../libs/logger node_modules/@forge/logger && \ + ln -sf ../../libs/starknet node_modules/@forge/starknet && \ + ln -sf .pnpm/decimal.js@10.6.0/node_modules/decimal.js node_modules/decimal.js && \ + ln -sf .pnpm/starknet@7.6.4/node_modules/starknet node_modules/starknet && \ + chown -R app:app . +USER app +CMD ["node", "apps/api/dist/apps/api/src/main.js"] +EXPOSE 3000 + \ No newline at end of file diff --git a/backend/Dockerfile.api.dev b/backend/Dockerfile.api.dev new file mode 100644 index 00000000..7845208f --- /dev/null +++ b/backend/Dockerfile.api.dev @@ -0,0 +1,35 @@ +FROM node:20-alpine + +# Install necessary libraries for Prisma on Alpine and pnpm +RUN apk add --no-cache openssl libssl3 libcrypto3 && \ + npm install -g pnpm + +WORKDIR /app + +# Copy package files +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json ./ +COPY apps/api/package.json ./apps/api/ +COPY libs/config/package.json ./libs/config/ +COPY libs/db/package.json ./libs/db/ +COPY libs/logger/package.json ./libs/logger/ +COPY libs/starknet/package.json ./libs/starknet/ + +# Install dependencies +RUN pnpm install --frozen-lockfile + +# Copy source code +COPY apps/api ./apps/api/ +COPY libs ./libs/ +COPY tsconfig.json ./ + +# Generate Prisma client and build libraries in parallel +RUN pnpm prisma generate --schema=libs/db/prisma/schema.prisma && \ + pnpm --filter @forge/config build && \ + pnpm --filter @forge/logger build && \ + pnpm --filter @forge/starknet build && \ + pnpm --filter @forge/db build && \ + pnpm run build:api + +EXPOSE 3000 + +CMD ["pnpm", "run", "start:api"] \ No newline at end of file diff --git a/backend/Dockerfile.indexer b/backend/Dockerfile.indexer new file mode 100644 index 00000000..10d5279e --- /dev/null +++ b/backend/Dockerfile.indexer @@ -0,0 +1,36 @@ +# ---- build ---- +FROM node:20-alpine AS build +WORKDIR /app +RUN corepack enable && corepack prepare pnpm@9.9.0 --activate +COPY backend/ . +RUN pnpm install --frozen-lockfile +# generate Prisma client +RUN pnpm prisma:generate +RUN pnpm -r build +# skip pruning to preserve all workspace dependencies + +# ---- runtime ---- +FROM node:20-alpine +ENV NODE_ENV=production +WORKDIR /app +RUN addgroup -S app && adduser -S app -G app +COPY --from=build /app/node_modules ./node_modules +COPY --from=build /app/package.json ./ +COPY --from=build /app/pnpm-workspace.yaml ./ +# copier les package.json des apps et libs pour les deps +COPY --from=build /app/apps/indexer/package.json ./apps/indexer/ +COPY --from=build /app/libs/*/package.json ./libs/*/ +COPY --from=build /app/apps/indexer/dist ./apps/indexer/dist +COPY --from=build /app/libs ./libs +# créer les symlinks pour les packages workspace et dependencies manquantes +RUN mkdir -p node_modules/@forge node_modules/@apibara && \ + ln -sf ../../libs/config node_modules/@forge/config && \ + ln -sf ../../libs/db node_modules/@forge/db && \ + ln -sf ../../libs/logger node_modules/@forge/logger && \ + ln -sf .pnpm/decimal.js@10.6.0/node_modules/decimal.js node_modules/decimal.js && \ + ln -sf .pnpm/starknet@7.6.4/node_modules/starknet node_modules/starknet && \ + ln -sf ../.pnpm/@apibara+protocol@0.4.9/node_modules/@apibara/protocol node_modules/@apibara/protocol && \ + ln -sf ../.pnpm/@apibara+starknet@0.5.0/node_modules/@apibara/starknet node_modules/@apibara/starknet && \ + chown -R app:app . +USER app +CMD ["node", "apps/indexer/dist/apps/indexer/src/main.js"] diff --git a/backend/Dockerfile.indexer.dev b/backend/Dockerfile.indexer.dev new file mode 100644 index 00000000..11f66c34 --- /dev/null +++ b/backend/Dockerfile.indexer.dev @@ -0,0 +1,32 @@ +FROM node:20-alpine + +# Install necessary libraries for Prisma on Alpine and pnpm +RUN apk add --no-cache openssl libssl3 libcrypto3 && \ + npm install -g pnpm + +WORKDIR /app + +# Copy package files +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json ./ +COPY apps/indexer/package.json ./apps/indexer/ +COPY libs/config/package.json ./libs/config/ +COPY libs/db/package.json ./libs/db/ +COPY libs/logger/package.json ./libs/logger/ +COPY libs/starknet/package.json ./libs/starknet/ + +# Install dependencies +RUN pnpm install --frozen-lockfile + +# Copy source code +COPY apps/indexer ./apps/indexer/ +COPY libs ./libs/ +COPY tsconfig.json ./ + +# Generate Prisma client and build libraries +RUN pnpm prisma generate --schema=libs/db/prisma/schema.prisma && \ + pnpm --filter @forge/config build && \ + pnpm --filter @forge/logger build && \ + pnpm --filter @forge/starknet build && \ + pnpm --filter @forge/db build + +CMD ["pnpm", "run", "dev:indexer"] \ No newline at end of file diff --git a/backend/Dockerfile.relayerAutomaticRedeem b/backend/Dockerfile.relayerAutomaticRedeem new file mode 100644 index 00000000..63ff47e5 --- /dev/null +++ b/backend/Dockerfile.relayerAutomaticRedeem @@ -0,0 +1,36 @@ +# ---- build ---- +FROM node:20-alpine AS build +WORKDIR /app +RUN corepack enable && corepack prepare pnpm@9.9.0 --activate +COPY backend/ . +RUN pnpm install --frozen-lockfile +# generate Prisma client +RUN pnpm prisma:generate +# build tous les workspaces (libs + apps) pour résoudre les liens pnpm +RUN pnpm -r build +# skip pruning to preserve all workspace dependencies + +# ---- runtime ---- +FROM node:20-alpine +ENV NODE_ENV=production +WORKDIR /app +# user non-root +RUN addgroup -S app && adduser -S app -G app +# copier deps prod +COPY --from=build /app/node_modules ./node_modules +# copier manifests afin que les liens pnpm workspace restent valides +COPY --from=build /app/package.json ./ +COPY --from=build /app/pnpm-workspace.yaml ./ +# copier le code compilé des apps et libs +COPY --from=build /app/apps/relayerAutomaticRedeem/dist ./apps/relayerAutomaticRedeem/dist +COPY --from=build /app/libs ./libs +# créer les symlinks pour les packages workspace et dependencies manquantes +RUN mkdir -p node_modules/@forge && \ + ln -sf ../../libs/config node_modules/@forge/config && \ + ln -sf ../../libs/db node_modules/@forge/db && \ + ln -sf ../../libs/logger node_modules/@forge/logger && \ + ln -sf ../../libs/starknet node_modules/@forge/starknet && \ + ln -sf .pnpm/cron@4.3.3/node_modules/cron node_modules/cron && \ + chown -R app:app . +USER app +CMD ["node", "apps/relayerAutomaticRedeem/dist/main.js"] \ No newline at end of file diff --git a/backend/Dockerfile.relayerAutomaticRedeem.dev b/backend/Dockerfile.relayerAutomaticRedeem.dev new file mode 100644 index 00000000..6ffeef94 --- /dev/null +++ b/backend/Dockerfile.relayerAutomaticRedeem.dev @@ -0,0 +1,32 @@ +FROM node:20-alpine + +# Install necessary libraries for Prisma on Alpine and pnpm +RUN apk add --no-cache openssl libssl3 libcrypto3 && \ + npm install -g pnpm + +WORKDIR /app + +# Copy package files +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json ./ +COPY apps/relayerAutomaticRedeem/package.json ./apps/relayerAutomaticRedeem/ +COPY libs/config/package.json ./libs/config/ +COPY libs/db/package.json ./libs/db/ +COPY libs/logger/package.json ./libs/logger/ +COPY libs/starknet/package.json ./libs/starknet/ + +# Install dependencies +RUN pnpm install --frozen-lockfile + +# Copy source code +COPY apps/relayerAutomaticRedeem ./apps/relayerAutomaticRedeem/ +COPY libs ./libs/ +COPY tsconfig.json ./ + +# Generate Prisma client and build libraries +RUN pnpm prisma generate --schema=libs/db/prisma/schema.prisma && \ + pnpm --filter @forge/config build && \ + pnpm --filter @forge/logger build && \ + pnpm --filter @forge/starknet build && \ + pnpm --filter @forge/db build + +CMD ["pnpm", "run", "dev:relayerAutomaticRedeem"] \ No newline at end of file diff --git a/backend/Dockerfile.relayerOnChainAum b/backend/Dockerfile.relayerOnChainAum new file mode 100644 index 00000000..58af4d20 --- /dev/null +++ b/backend/Dockerfile.relayerOnChainAum @@ -0,0 +1,36 @@ +# ---- build ---- + FROM node:20-alpine AS build + WORKDIR /app + RUN corepack enable && corepack prepare pnpm@9.9.0 --activate + COPY backend/ . + RUN pnpm install --frozen-lockfile + # generate Prisma client + RUN pnpm prisma:generate + RUN pnpm -r build + # skip pruning to preserve all workspace dependencies + + # ---- runtime ---- + FROM node:20-alpine + ENV NODE_ENV=production + WORKDIR /app + RUN addgroup -S app && adduser -S app -G app + COPY --from=build /app/node_modules ./node_modules + COPY --from=build /app/package.json ./ + COPY --from=build /app/pnpm-workspace.yaml ./ + # copier les package.json des apps et libs pour les deps + COPY --from=build /app/apps/relayerOnChainAum/package.json ./apps/relayerOnChainAum/ + COPY --from=build /app/libs/*/package.json ./libs/*/ + COPY --from=build /app/apps/relayerOnChainAum/dist ./apps/relayerOnChainAum/dist + COPY --from=build /app/libs ./libs + # créer les symlinks pour les packages workspace et dependencies manquantes + RUN mkdir -p node_modules/@forge && \ + ln -sf ../../libs/config node_modules/@forge/config && \ + ln -sf ../../libs/db node_modules/@forge/db && \ + ln -sf ../../libs/logger node_modules/@forge/logger && \ + ln -sf ../../libs/starknet node_modules/@forge/starknet && \ + ln -sf .pnpm/decimal.js@10.6.0/node_modules/decimal.js node_modules/decimal.js && \ + ln -sf .pnpm/starknet@7.6.4/node_modules/starknet node_modules/starknet && \ + chown -R app:app . + USER app + CMD ["node", "apps/relayerOnChainAum/dist/apps/relayerOnChainAum/src/main.js"] + \ No newline at end of file diff --git a/backend/Dockerfile.relayerOnChainAum.dev b/backend/Dockerfile.relayerOnChainAum.dev new file mode 100644 index 00000000..99e1aefa --- /dev/null +++ b/backend/Dockerfile.relayerOnChainAum.dev @@ -0,0 +1,32 @@ +FROM node:20-alpine + +# Install necessary libraries for Prisma on Alpine and pnpm +RUN apk add --no-cache openssl libssl3 libcrypto3 && \ + npm install -g pnpm + +WORKDIR /app + +# Copy package files +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json ./ +COPY apps/relayerOnChainAum/package.json ./apps/relayerOnChainAum/ +COPY libs/config/package.json ./libs/config/ +COPY libs/db/package.json ./libs/db/ +COPY libs/logger/package.json ./libs/logger/ +COPY libs/starknet/package.json ./libs/starknet/ + +# Install dependencies +RUN pnpm install --frozen-lockfile + +# Copy source code +COPY apps/relayerOnChainAum ./apps/relayerOnChainAum/ +COPY libs ./libs/ +COPY tsconfig.json ./ + +# Generate Prisma client and build libraries +RUN pnpm prisma generate --schema=libs/db/prisma/schema.prisma && \ + pnpm --filter @forge/config build && \ + pnpm --filter @forge/logger build && \ + pnpm --filter @forge/starknet build && \ + pnpm --filter @forge/db build + +CMD ["pnpm", "run", "dev:relayerOnChainAum"] \ No newline at end of file diff --git a/backend/LOGGING.md b/backend/LOGGING.md new file mode 100644 index 00000000..30c959be --- /dev/null +++ b/backend/LOGGING.md @@ -0,0 +1,120 @@ +# Logging Guide + +## Overview + +The StarkNet Vault Kit backend uses Winston for structured logging across all applications. The logging system provides clean, consistent output with support for both console and file logging. + +## Configuration + +Logging can be configured via environment variables: + +```bash +# Log level (error, warn, info, debug) +LOG_LEVEL=info + +# Enable file logging (automatically enabled in production) +ENABLE_FILE_LOGGING=false + +# Directory for log files +LOG_DIR=logs + +# Environment (affects log format) +NODE_ENV=development +``` + +## Log Levels + +- **error**: Error messages and stack traces +- **warn**: Warning messages +- **info**: General information (default) +- **debug**: Detailed debugging information + +## Output Format + +### Development (Console) +``` +10:45:23 info [API:Main] Server started on port 3000 +10:45:24 debug [Indexer:Service] Processing block { + "blockNumber": 12345, + "timestamp": 1234567890 +} +``` + +### Production (JSON) +```json +{ + "timestamp": "2024-01-01T10:45:23.000Z", + "level": "info", + "service": "starknet-vault-kit", + "context": "API:Main", + "message": "Server started on port 3000", + "environment": "production" +} +``` + +## Usage in Code + +```typescript +import { Logger } from "@forge/logger"; + +// Create a logger with context +const logger = Logger.create("MyService"); + +// Log messages +logger.info("Service started"); +logger.debug("Processing data", { count: 100 }); +logger.warn("High memory usage", { usage: "85%" }); +logger.error("Failed to process", error); +``` + +## File Logging + +When file logging is enabled, logs are written to: +- `logs/starknet-vault-kit-YYYY-MM-DD.log` - All logs +- `logs/starknet-vault-kit-error-YYYY-MM-DD.log` - Error logs only + +Files are automatically rotated daily and kept for 14 days. + +## Best Practices + +1. **Use appropriate log levels** + - `error`: For errors that need immediate attention + - `warn`: For potential issues or degraded performance + - `info`: For important application events + - `debug`: For detailed debugging information + +2. **Include context in log messages** + ```typescript + logger.info("Processing redeem", { + redeemId: 123, + user: "0x..." + }); + ``` + +3. **Avoid logging sensitive information** + - Never log private keys, passwords, or tokens + - Truncate addresses when appropriate + +4. **Keep messages concise** + - Use clear, descriptive messages + - Avoid emojis and special characters + - Put details in metadata objects + +## Application Contexts + +Each application uses its own context prefix: + +- **API**: `API:Main`, `API:Service`, `API:Controller` +- **Indexer**: `Indexer:Main`, `Indexer:Service` +- **RelayerAutomaticRedeem**: `RelayerAutomaticRedeem:Main`, `RelayerAutomaticRedeem:Service` +- **StarkNet**: `Starknet:Service` + +## Monitoring + +In production, logs can be aggregated and monitored using tools like: +- ELK Stack (Elasticsearch, Logstash, Kibana) +- Datadog +- New Relic +- CloudWatch (AWS) + +The JSON format makes it easy to parse and query logs in these systems. \ No newline at end of file diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 00000000..02ebcd83 --- /dev/null +++ b/backend/README.md @@ -0,0 +1,132 @@ +# StarkNet Vault Kit Backend - OSS Version + +**This is a reference backend (read-only) for the StarkNet Vault Kit.** + +It provides minimal functionality to index vault events and expose basic API endpoints. This OSS version does not include premium features like pricing, automated redeems, SLA monitoring, or advanced analytics. + +**For production deployments with full features, see our premium services.** + +## What's Included + +### Applications + +- **`api`** - HTTP API with vault data endpoints +- **`indexer`** - Event indexer for vault reports and redeems +- **`relayerAutomaticRedeem`** - ⚠️ **INCOMPLETE** - Automatic redeem processing service +- **`relayerOnChainAum`** - ⚠️ **INCOMPLETE** - On-chain AUM management service + +### Libraries + +- **`@forge/config`** - Configuration management +- **`@forge/db`** - Prisma database client with Prisma schema +- **`@forge/logger`** - Structured logging with Winston +- **`@forge/starknet`** - StarkNet interaction service + +## API Endpoints (6 total) + +1. `GET /health` - Health check and API info +2. `GET /pending-redeems/:address` - Pending redeems for an address (with limit/offset) +3. `GET /reports/last` - Latest report from database +4. `GET /redeems/:id` - Redeem details by ID +5. `GET /strategy-analytics` - Strategy analytics with APY calculations (with limit/offset) +6. `GET /redeem-required-assets` - Required assets for pending redeems by epoch + +## Events Indexed (3 total) + +The indexer only tracks these events: + +1. **Report** - Vault reports with epoch, supply, and assets +2. **RedeemRequested** - User redeem requests +3. **RedeemClaimed** - Completed redeem claims + +## Database Schema (3 models + status) + +- `Report` +- `RedeemRequested` +- `RedeemClaimed` +- `IndexerStatus` + +## Quick Start with Docker (Development) + +1. **Clone and setup:** + +```bash +cp .env.example .env +# Edit .env with your configuration +``` + +2. **Run with Docker Compose (Development Mode):** + +```bash +docker-compose -f docker-compose.dev.yml up -d +``` + +This starts: + +- PostgreSQL database +- API service on port 3000 (with hot reload) +- Indexer service (with hot reload) +- ⚠️ **Note**: Relayer services have Dockerfiles but are not yet functional + +3. **Available Services:** + - API: http://localhost:3000 + - Database: localhost:5432 + +4. **Check health:** + +```bash +curl http://localhost:3000/health +``` + +## Manual Setup + +### Prerequisites + +- Node.js 18+ +- pnpm +- PostgreSQL +- Apibara token (for indexing) + +### Installation + +```bash +# Install dependencies +pnpm install + +# Setup database +pnpm prisma:generate +pnpm prisma:migrate:deploy + +# Build +pnpm build +``` + +### Run Services + +```bash +# Start API +pnpm start:api + +# Start Indexer (separate terminal) +pnpm start:indexer +``` + +## Configuration + +Copy `.env.example` to `.env` and configure: + +```bash +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/starknet_vault_kit" +RPC_URL="https://starknet-mainnet.public.blastapi.io" +VAULT_ADDRESS="0x..." # Your vault contract address +START_BLOCK=12993 +APIBARA_TOKEN="your_token_here" +``` + +## License + +This project is licensed for reference use only. For production deployments with full features and support, please contact us. + +--- + +_Built for the StarkNet ecosystem 🚀_ diff --git a/backend/apps/api/nest-cli.json b/backend/apps/api/nest-cli.json new file mode 100644 index 00000000..e84723eb --- /dev/null +++ b/backend/apps/api/nest-cli.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true, + "watchAssets": true, + "assets": ["**/*.json", "**/*.md"] + }, + "entryFile": "apps/api/src/main" +} \ No newline at end of file diff --git a/backend/apps/api/package.json b/backend/apps/api/package.json new file mode 100644 index 00000000..39e4416d --- /dev/null +++ b/backend/apps/api/package.json @@ -0,0 +1,27 @@ +{ + "name": "api", + "version": "0.0.1", + "description": "NestJS HTTP API for StarkNet Vault Kit", + "main": "dist/main.js", + "scripts": { + "build": "nest build", + "dev": "nest start --watch", + "start": "node dist/apps/api/src/main.js", + "lint": "eslint \"src/**/*.ts\" --fix", + "test": "jest" + }, + "dependencies": { + "@forge/config": "workspace:*", + "@forge/db": "workspace:*", + "@forge/logger": "workspace:*", + "@forge/starknet": "workspace:*", + "@nestjs/axios": "^3.1.3", + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.4.20", + "@nestjs/platform-express": "^10.0.0", + "@nestjs/swagger": "^7.4.2", + "@nestjs/terminus": "^11.0.0", + "decimal.js": "^10.6.0", + "starknet": "^7.6.4" + } +} \ No newline at end of file diff --git a/backend/apps/api/src/app.controller.ts b/backend/apps/api/src/app.controller.ts new file mode 100644 index 00000000..2325b627 --- /dev/null +++ b/backend/apps/api/src/app.controller.ts @@ -0,0 +1,88 @@ +import { Controller, Get, Param, Query } from '@nestjs/common'; +import { ApiTags, ApiResponse, ApiParam, ApiQuery } from '@nestjs/swagger'; +import { AppService, PendingRedeem, StrategyAnalytics, RedeemRequiredAssets } from './app.service'; + +@ApiTags('api') +@Controller() +export class AppController { + constructor(private readonly appService: AppService) {} + + @Get() + @ApiResponse({ status: 200, description: 'API information and available endpoints' }) + getApiInfo() { + return this.appService.getApiInfo(); + } + + @Get('pending-redeems/:address') + @ApiParam({ name: 'address', description: 'User address' }) + @ApiQuery({ + name: 'limit', + required: false, + description: 'Number of pending redeems to return', + type: Number, + }) + @ApiQuery({ + name: 'offset', + required: false, + description: 'Number of pending redeems to skip', + type: Number, + }) + @ApiResponse({ status: 200, description: 'Pending redeems for the address' }) + async getPendingRedeems( + @Param('address') address: string, + @Query('limit') limit?: string, + @Query('offset') offset?: string + ): Promise { + const limitNumber = limit ? parseInt(limit, 10) : undefined; + const offsetNumber = offset ? parseInt(offset, 10) : undefined; + return this.appService.getPendingRedeems(address, limitNumber, offsetNumber); + } + + @Get('reports/last') + @ApiResponse({ status: 200, description: 'Latest report from database' }) + async getLastReport() { + return this.appService.getLastReport(); + } + + @Get('redeems/:id') + @ApiParam({ name: 'id', description: 'Redeem ID' }) + @ApiResponse({ status: 200, description: 'Redeem details by ID' }) + async getRedeemById(@Param('id') id: string) { + return this.appService.getRedeemById(id); + } + + @Get('strategy-analytics') + @ApiQuery({ + name: 'limit', + required: false, + description: 'Number of reports to return', + type: Number, + }) + @ApiQuery({ + name: 'offset', + required: false, + description: 'Number of reports to skip', + type: Number, + }) + @ApiResponse({ status: 200, description: 'Strategy analytics data' }) + async getStrategyAnalytics( + @Query('limit') limit?: string, + @Query('offset') offset?: string + ): Promise { + const limitNumber = limit ? parseInt(limit, 10) : 10; + const offsetNumber = offset ? parseInt(offset, 10) : undefined; + return this.appService.getStrategyAnalytics(limitNumber, offsetNumber); + } + + @Get('redeem-required-assets') + @ApiResponse({ status: 200, description: 'Redeem required assets for each epoch' }) + async getRedeemRequiredAssets(): Promise { + return this.appService.getRedeemRequiredAssets(); + } + + @Get('indexer-status') + @ApiResponse({ status: 200, description: 'Current indexer status including last indexed block' }) + async getIndexerStatus() { + return this.appService.getIndexerStatus(); + } +} diff --git a/backend/apps/api/src/app.module.ts b/backend/apps/api/src/app.module.ts new file mode 100644 index 00000000..b73c25d0 --- /dev/null +++ b/backend/apps/api/src/app.module.ts @@ -0,0 +1,19 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@forge/config'; +import { PrismaModule } from '@forge/db'; +import { StarknetModule } from '@forge/starknet'; +import { HealthModule } from './health'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; + +@Module({ + imports: [ + ConfigModule, + PrismaModule, + StarknetModule, + HealthModule, + ], + controllers: [AppController], + providers: [AppService], +}) +export class AppModule {} \ No newline at end of file diff --git a/backend/apps/api/src/app.service.ts b/backend/apps/api/src/app.service.ts new file mode 100644 index 00000000..dfc4267e --- /dev/null +++ b/backend/apps/api/src/app.service.ts @@ -0,0 +1,433 @@ +import { Injectable, OnModuleInit } from '@nestjs/common'; +import { PrismaService } from '@forge/db'; +import { StarknetService } from '@forge/starknet'; +import Decimal from 'decimal.js'; +import { ConfigService } from '@forge/config'; +import { Logger } from '@forge/logger'; +import { validateAndParseAddress } from 'starknet'; +// Types moved from @forge/core +export interface Strategy { + vault: string; + startBlockIndexing: number; +} + +export interface PendingRedeem { + epoch: number; + sharesBurn: string; + nominal: string; + assets: string; + redeemId: string; + timestamp: number; + transactionHash: string; +} + +export interface StrategyAnalytics { + sharePrice: string; + totalSupply: string; + totalAssets: string; + epoch: string; + timestamp: number; + managementFeeShares: string; + performanceFeeShares: string; + apy1Report?: string; + apy2Reports?: string; + apy3Reports?: string; + redeemDelaySeconds?: number; +} + +export interface RedeemRequiredAssets { + epoch: number; + is_current_epoch: boolean; + redeem_assets_required: string; + cumulated_liquidity_required: string; +} + +@Injectable() +export class AppService implements OnModuleInit { + strategyDecimals: number; + private readonly logger = Logger.create('API:Service'); + + constructor( + private readonly prismaService: PrismaService, + private readonly starknetService: StarknetService, + private readonly configService: ConfigService + ) {} + + async onModuleInit() { + try { + const vaultAddress = this.configService.get('VAULT_ADDRESS') as string; + this.logger.info('Initializing AppService', { vaultAddress }); + this.strategyDecimals = Number(await this.starknetService.vault_decimals(vaultAddress)); + this.logger.info('AppService initialized successfully', { + strategyDecimals: this.strategyDecimals, + vaultAddress, + }); + } catch (error) { + this.logger.error('Failed to initialize AppService', error); + throw error; + } + } + + getApiInfo() { + return { + name: 'StarkNet Vault Kit API', + version: '1.0.0', + description: 'Backend API for StarkNet Vault Kit', + endpoints: { + health: '/health', + pendingRedeems: '/pending-redeems/:address', + lastReport: '/reports/last', + redeemById: '/redeems/:id', + strategyAnalytics: '/strategy-analytics', + redeemRequiredAssets: '/redeem-required-assets', + }, + documentation: '/api', + }; + } + + public async getPendingRedeems(address: string, limit?: number, offset?: number): Promise { + try { + const addressToUse = validateAndParseAddress(address); + this.logger.info('Fetching pending redeems', { + address: addressToUse, + limit, + offset, + }); + + const pendingRedeems: PendingRedeem[] = []; + const vaultAddress = this.configService.get('VAULT_ADDRESS') as string; + + const lastRedeemRequestedIdForAddress = + await this.prismaService.fetchLastRedeemRequestedIdForAddress(addressToUse); + + if (lastRedeemRequestedIdForAddress === undefined) { + return []; + } + + const pendingRedeemsForStrategy = await this.prismaService.fetchPendingRedeemsForAddress( + addressToUse, + -1, // Start from the beginning + Number(lastRedeemRequestedIdForAddress), // Up to the last requested ID + limit, + offset + ); + + this.logger.debug('Found pending redeems from database', { + count: pendingRedeemsForStrategy.length, + }); + + const pendingRedeemPromises = pendingRedeemsForStrategy.map(async (redeem) => { + try { + const dueAssets = await this.starknetService.vault_due_assets_from_id(vaultAddress, Number(redeem.redeemId)); + + return { + epoch: Number(redeem.epoch), + sharesBurn: this.formatBigIntToDecimal(redeem.shares), + nominal: this.formatBigIntToDecimal(redeem.assets), + assets: this.formatBigIntToDecimal(dueAssets), + redeemId: redeem.redeemId.toString(), + timestamp: redeem.timestamp, + transactionHash: redeem.transactionHash, + }; + } catch (error) { + this.logger.error('Failed to process pending redeem', error, { + redeemId: redeem.redeemId.toString(), + }); + throw error; + } + }); + + const resolvedPendingRedeems = await Promise.all(pendingRedeemPromises); + resolvedPendingRedeems.sort((a, b) => Number(b.redeemId) - Number(a.redeemId)); + pendingRedeems.push(...resolvedPendingRedeems); + + this.logger.info('Successfully processed pending redeems', { + address: addressToUse, + count: resolvedPendingRedeems.length, + }); + + return pendingRedeems; + } catch (error) { + this.logger.error('Failed to get pending redeems', error, { address }); + throw error; + } + } + + public async getLastReport() { + try { + this.logger.debug('Fetching last report'); + + const lastReport = await this.prismaService.fetchLastReport(); + if (!lastReport) { + this.logger.info('No reports found in database'); + return null; + } + + const result = { + id: lastReport.id, + blockNumber: lastReport.blockNumber, + timestamp: lastReport.timestamp, + transactionHash: lastReport.transactionHash, + newEpoch: lastReport.newEpoch.toString(), + newHandledEpochLen: lastReport.newHandledEpochLen.toString(), + totalSupply: this.formatBigIntToDecimal(lastReport.totalSupply), + totalAssets: this.formatBigIntToDecimal(lastReport.totalAssets), + managementFeeShares: this.formatBigIntToDecimal(lastReport.managementFeeShares), + performanceFeeShares: this.formatBigIntToDecimal(lastReport.performanceFeeShares), + }; + + this.logger.info('Successfully fetched last report', { + reportId: result.id, + blockNumber: result.blockNumber, + epoch: result.newEpoch, + }); + + return result; + } catch (error) { + this.logger.error('Failed to get last report', error); + throw error; + } + } + + public async getRedeemById(redeemId: string) { + try { + this.logger.info('Fetching redeem by ID', { redeemId }); + + const redeemRequested = await this.prismaService.redeemRequested.findUnique({ + where: { redeemId: BigInt(redeemId) }, + }); + + if (!redeemRequested) { + this.logger.info('Redeem request not found', { redeemId }); + return null; + } + + const redeemClaimed = await this.prismaService.redeemClaimed.findUnique({ + where: { redeemId: BigInt(redeemId) }, + }); + + let currentDueAssets: bigint | null = null; + if (!redeemClaimed) { + try { + const vaultAddress = this.configService.get('VAULT_ADDRESS') as string; + currentDueAssets = await this.starknetService.vault_due_assets_from_id(vaultAddress, Number(redeemId)); + this.logger.debug('Fetched current due assets from contract', { + redeemId, + dueAssets: currentDueAssets?.toString(), + }); + } catch (error) { + this.logger.warn('Failed to fetch due assets for redeem ID', { + redeemId, + error: error.message, + }); + } + } + + const result = { + redeemId: redeemRequested.redeemId.toString(), + owner: redeemRequested.owner, + receiver: redeemRequested.receiver, + epoch: redeemRequested.epoch.toString(), + sharesBurn: this.formatBigIntToDecimal(redeemRequested.shares), + nominal: this.formatBigIntToDecimal(redeemRequested.assets), + assets: currentDueAssets ? this.formatBigIntToDecimal(currentDueAssets) : null, + requestTimestamp: redeemRequested.timestamp, + requestTransactionHash: redeemRequested.transactionHash, + claimedTimestamp: redeemClaimed?.timestamp, + claimedTransactionHash: redeemClaimed?.transactionHash, + claimedAssets: redeemClaimed ? this.formatBigIntToDecimal(redeemClaimed.assets) : null, + }; + + this.logger.info('Successfully fetched redeem details', { + redeemId, + isClaimed: !!redeemClaimed, + owner: result.owner, + }); + + return result; + } catch (error) { + this.logger.error('Failed to get redeem by ID', error, { redeemId }); + throw error; + } + } + + public async getStrategyAnalytics(limit: number = 10, offset?: number): Promise { + try { + this.logger.info('Fetching strategy analytics', { limit, offset }); + + const reports = await this.prismaService.fetchLastReports(limit, offset); + + if (!reports || reports.length === 0) { + this.logger.info('No reports found for analytics'); + return []; + } + + this.logger.debug('Found reports for analytics', { count: reports.length }); + + const calculateAPY = ( + currentSharePrice: Decimal, + previousSharePrice: Decimal, + currentTimestamp: number, + previousTimestamp: number + ): string => { + const priceRatio = currentSharePrice.div(previousSharePrice); + const growth = priceRatio.sub(1); + const timeDiffSeconds = currentTimestamp - previousTimestamp; + const timeDiffYears = timeDiffSeconds / (365.25 * 24 * 60 * 60); + const annualizedGrowth = growth.div(timeDiffYears); + return annualizedGrowth.mul(100).toString(); + }; + + const analytics = reports.map((report, index) => { + const sharePrice = + report.totalSupply > 0n + ? new Decimal(report.totalAssets.toString()).div(new Decimal(report.totalSupply.toString())) + : new Decimal(1); + + let apy1Report: string | undefined; + let apy2Reports: string | undefined; + let apy3Reports: string | undefined; + + if (index + 1 < reports.length) { + const prevReport = reports[index + 1]; + const prevSharePrice = + prevReport.totalSupply > 0n + ? new Decimal(prevReport.totalAssets.toString()).div(new Decimal(prevReport.totalSupply.toString())) + : new Decimal(1); + apy1Report = calculateAPY(sharePrice, prevSharePrice, report.timestamp, prevReport.timestamp); + } + + if (index + 2 < reports.length) { + const prevReport = reports[index + 2]; + const prevSharePrice = + prevReport.totalSupply > 0n + ? new Decimal(prevReport.totalAssets.toString()).div(new Decimal(prevReport.totalSupply.toString())) + : new Decimal(1); + apy2Reports = calculateAPY(sharePrice, prevSharePrice, report.timestamp, prevReport.timestamp); + } + + if (index + 3 < reports.length) { + const prevReport = reports[index + 3]; + const prevSharePrice = + prevReport.totalSupply > 0n + ? new Decimal(prevReport.totalAssets.toString()).div(new Decimal(prevReport.totalSupply.toString())) + : new Decimal(1); + apy3Reports = calculateAPY(sharePrice, prevSharePrice, report.timestamp, prevReport.timestamp); + } + + let redeemDelaySeconds: number | undefined; + + if (index === 0) { + redeemDelaySeconds = undefined; + } else { + const currentEpoch = Number(report.newEpoch); + const handlingReport = reports.find((r) => Number(r.newHandledEpochLen) >= currentEpoch); + + if (handlingReport) { + redeemDelaySeconds = handlingReport.timestamp - report.timestamp; + } else { + redeemDelaySeconds = undefined; + } + } + + return { + sharePrice: sharePrice.toString(), + totalSupply: this.formatBigIntToDecimal(report.totalSupply), + totalAssets: this.formatBigIntToDecimal(report.totalAssets), + epoch: report.newEpoch.toString(), + timestamp: report.timestamp, + managementFeeShares: this.formatBigIntToDecimal(report.managementFeeShares), + performanceFeeShares: this.formatBigIntToDecimal(report.performanceFeeShares), + apy1Report, + apy2Reports, + apy3Reports, + redeemDelaySeconds, + }; + }); + + this.logger.info('Successfully processed strategy analytics', { + count: analytics.length, + }); + + return analytics; + } catch (error) { + this.logger.error('Failed to get strategy analytics', error); + throw error; + } + } + + public async getRedeemRequiredAssets(): Promise { + try { + this.logger.info('Fetching redeem required assets'); + + const vaultAddress = this.configService.get('VAULT_ADDRESS') as string; + + let [buffer, epoch, handledEpochLen] = await Promise.all([ + this.starknetService.vault_buffer(vaultAddress), + this.starknetService.vault_epoch(vaultAddress), + this.starknetService.vault_handled_epoch_len(vaultAddress), + ]); + + this.logger.debug('Fetched vault state', { + buffer: buffer?.toString(), + epoch: epoch?.toString(), + handledEpochLen: handledEpochLen?.toString(), + }); + + let redeemRequiredAssets: RedeemRequiredAssets[] = []; + + for (let index = Number(handledEpochLen); index <= Number(epoch); index++) { + const redeemAssets = await this.starknetService.vault_redeem_assets(vaultAddress, index); + + let cumulated_liquidity_required = redeemAssets; + + if (buffer >= redeemAssets) { + buffer -= redeemAssets; + cumulated_liquidity_required = 0n; + } else { + buffer = 0n; + cumulated_liquidity_required = redeemAssets - buffer; + } + + redeemRequiredAssets.push({ + epoch: index, + is_current_epoch: index === Number(epoch), + redeem_assets_required: this.formatBigIntToDecimal(redeemAssets), + cumulated_liquidity_required: this.formatBigIntToDecimal(cumulated_liquidity_required), + }); + } + + this.logger.info('Successfully calculated redeem required assets', { + epochsProcessed: redeemRequiredAssets.length, + }); + + return redeemRequiredAssets; + } catch (error) { + this.logger.error('Failed to get redeem required assets', error); + throw error; + } + } + + formatBigIntToDecimal = (value: bigint): string => { + return new Decimal(value.toString()).div(new Decimal(10).pow(this.strategyDecimals)).toFixed(); + }; + + async getIndexerStatus() { + const status = await this.prismaService.getIndexerStatus(); + if (!status) { + return { + lastBlock: 0, + updatedAt: null, + synced: false, + message: 'Indexer has not started yet', + }; + } + + return { + lastBlock: status.lastBlock, + updatedAt: status.updatedAt, + synced: true, + message: 'Indexer is running', + }; + } +} diff --git a/backend/apps/api/src/health/health.constants.ts b/backend/apps/api/src/health/health.constants.ts new file mode 100644 index 00000000..ff9ab0a5 --- /dev/null +++ b/backend/apps/api/src/health/health.constants.ts @@ -0,0 +1 @@ +export const HEALTH_URL = 'health'; \ No newline at end of file diff --git a/backend/apps/api/src/health/health.controller.ts b/backend/apps/api/src/health/health.controller.ts new file mode 100644 index 00000000..9a0e6c78 --- /dev/null +++ b/backend/apps/api/src/health/health.controller.ts @@ -0,0 +1,23 @@ +import { + HealthCheckService, + MemoryHealthIndicator, + HealthCheck, +} from "@nestjs/terminus"; +import { Controller, Get } from "@nestjs/common"; +import { HEALTH_URL } from "./health.constants"; + +@Controller(HEALTH_URL) +export class HealthController { + constructor( + private health: HealthCheckService, + private memory: MemoryHealthIndicator + ) {} + + @Get() + @HealthCheck() + check() { + return this.health.check([ + () => this.memory.checkHeap("memoryHeap", 1024 * 1024 * 1024), + ]); + } +} diff --git a/backend/apps/api/src/health/health.module.ts b/backend/apps/api/src/health/health.module.ts new file mode 100644 index 00000000..50c51866 --- /dev/null +++ b/backend/apps/api/src/health/health.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { TerminusModule } from '@nestjs/terminus'; +import { HealthController } from './health.controller'; +import { HttpModule } from '@nestjs/axios'; + +@Module({ + providers: [], + controllers: [HealthController], + imports: [TerminusModule, HttpModule], +}) +export class HealthModule {} diff --git a/backend/apps/api/src/health/index.ts b/backend/apps/api/src/health/index.ts new file mode 100644 index 00000000..8dda1073 --- /dev/null +++ b/backend/apps/api/src/health/index.ts @@ -0,0 +1,3 @@ +export * from './health.module'; +export * from './health.controller'; +export * from './health.constants'; \ No newline at end of file diff --git a/backend/apps/api/src/main.ts b/backend/apps/api/src/main.ts new file mode 100644 index 00000000..31be9327 --- /dev/null +++ b/backend/apps/api/src/main.ts @@ -0,0 +1,52 @@ +import { NestFactory } from '@nestjs/core'; +import { ValidationPipe } from '@nestjs/common'; +import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; +import { validateApiConfig } from '@forge/config'; +import { Logger, initializeLogger } from '@forge/logger'; +import { AppModule } from './app.module'; + +async function bootstrap() { + initializeLogger(); + const logger = Logger.create('API:Main'); + + try { + validateApiConfig(process.env); + logger.info('Environment configuration validated successfully'); + + const app = await NestFactory.create(AppModule); + + app.useGlobalPipes( + new ValidationPipe({ + whitelist: true, + forbidNonWhitelisted: true, + transform: true, + }) + ); + logger.info('Global validation pipes configured'); + + app.enableCors(); + logger.info('CORS enabled'); + const config = new DocumentBuilder() + .setTitle('StarkNet Vault Kit API') + .setDescription('API for StarkNet Vault Kit indexer and services') + .setVersion('1.0') + .build(); + + const document = SwaggerModule.createDocument(app, config); + SwaggerModule.setup('api', app, document); + logger.info('Swagger documentation configured at /api'); + + const port = process.env.PORT || 3000; + await app.listen(port); + + logger.info(`API server started on port ${port}`, { + environment: process.env.NODE_ENV || 'development', + docs: `/api`, + }); + } catch (error) { + logger.error('Failed to start API server', error); + process.exit(1); + } +} + +bootstrap(); diff --git a/backend/apps/api/src/types/strategy.ts b/backend/apps/api/src/types/strategy.ts new file mode 100644 index 00000000..ac2e3310 --- /dev/null +++ b/backend/apps/api/src/types/strategy.ts @@ -0,0 +1 @@ +export * from '../app.service'; \ No newline at end of file diff --git a/backend/apps/api/tsconfig.json b/backend/apps/api/tsconfig.json new file mode 100644 index 00000000..fdf7ed1d --- /dev/null +++ b/backend/apps/api/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist" + }, + "include": ["src/**/*", "../../libs/*/src/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/backend/apps/indexer/nest-cli.json b/backend/apps/indexer/nest-cli.json new file mode 100644 index 00000000..ab44b5d0 --- /dev/null +++ b/backend/apps/indexer/nest-cli.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true, + "watchAssets": true, + "assets": ["**/*.json", "**/*.md"] + }, + "entryFile": "apps/indexer/src/main" +} \ No newline at end of file diff --git a/backend/apps/indexer/package.json b/backend/apps/indexer/package.json new file mode 100644 index 00000000..62fe67bc --- /dev/null +++ b/backend/apps/indexer/package.json @@ -0,0 +1,25 @@ +{ + "name": "indexer", + "version": "0.0.1", + "description": "Apibara indexer for StarkNet Vault Kit events", + "main": "dist/main.js", + "scripts": { + "build": "tsc", + "dev": "nest start --watch", + "start": "node dist/apps/indexer/src/main.js", + "lint": "eslint \"src/**/*.ts\" --fix" + }, + "dependencies": { + "@apibara/protocol": "^0.4.9", + "@apibara/starknet": "^0.5.0", + "@forge/config": "workspace:*", + "@forge/db": "workspace:*", + "@forge/logger": "workspace:*", + "starknet": "^7.6.4", + "@nestjs/core": "^10.0.0", + "@nestjs/common": "^10.0.0" + }, + "devDependencies": { + "ts-node": "^10.9.1" + } +} \ No newline at end of file diff --git a/backend/apps/indexer/src/decoder.ts b/backend/apps/indexer/src/decoder.ts new file mode 100644 index 00000000..42ddb51d --- /dev/null +++ b/backend/apps/indexer/src/decoder.ts @@ -0,0 +1,69 @@ +// Convert u256 from two field elements +export function parseU256(data: string[]): bigint { + const low = BigInt(data[0]); + const high = BigInt(data[1]); + return (high << 128n) | low; +} + +// Parse a u64 (1 felt) +export function parseFelt(felt: string): bigint { + return BigInt(felt); +} + +export function decodeRedeemRequestedEvent(rawData: string[]) { + let i = 0; + const owner = parseFelt(rawData[i++]); + const receiver = parseFelt(rawData[i++]); + const shares = parseU256([rawData[i++], rawData[i++]]); + const assets = parseU256([rawData[i++], rawData[i++]]); + const redeemId = parseU256([rawData[i++], rawData[i++]]); + const epoch = parseU256([rawData[i++], rawData[i++]]); + + return { + owner, + receiver, + shares, + assets, + redeemId, + epoch, + }; +} + +export function decodeRedeemClaimedEvent(rawData: string[]) { + let i = 0; + + // Parse ContractAddress value as bigint + const receiver = parseFelt(rawData[i++]); + + // Parse u256 values (each takes two array elements) + const redeemRequestNominal = parseU256([rawData[i++], rawData[i++]]); + const assets = parseU256([rawData[i++], rawData[i++]]); + const redeemId = parseU256([rawData[i++], rawData[i++]]); + const epoch = parseU256([rawData[i++], rawData[i++]]); + + return { + receiver, + redeemRequestNominal, + assets, + redeemId, + epoch, + }; +} + +export function decodeReportEvent(rawData: string[]) { + let i = 0; + const newEpoch = parseU256([rawData[i++], rawData[i++]]); + const newHandledEpochLen = parseU256([rawData[i++], rawData[i++]]); + const totalSupply = parseU256([rawData[i++], rawData[i++]]); + const totalAssets = parseU256([rawData[i++], rawData[i++]]); + const managementFeeShares = parseU256([rawData[i++], rawData[i++]]); + const performanceFeeShares = parseU256([rawData[i++], rawData[i++]]); + return { + newEpoch, + newHandledEpochLen, + totalSupply, + totalAssets, + managementFeeShares, + performanceFeeShares, + }; +} \ No newline at end of file diff --git a/backend/apps/indexer/src/indexer.constants.ts b/backend/apps/indexer/src/indexer.constants.ts new file mode 100644 index 00000000..02bde624 --- /dev/null +++ b/backend/apps/indexer/src/indexer.constants.ts @@ -0,0 +1,4 @@ +export const BATCH_SIZE = 1000; + +export const MIN_SLEEP_TIME_AFTER_RECONNECT_NO_INTERNAL_ERROR = 60_000; +export const MAX_RETRY_AFTER_RECONNECT_NO_INTERNAL_ERROR = 20; \ No newline at end of file diff --git a/backend/apps/indexer/src/indexer.module.ts b/backend/apps/indexer/src/indexer.module.ts new file mode 100644 index 00000000..eecb3961 --- /dev/null +++ b/backend/apps/indexer/src/indexer.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@forge/config'; +import { PrismaModule } from '@forge/db'; +import { IndexerService } from './indexer.service'; + +@Module({ + imports: [ConfigModule, PrismaModule], + providers: [IndexerService], + exports: [IndexerService], +}) +export class IndexerModule {} diff --git a/backend/apps/indexer/src/indexer.service.ts b/backend/apps/indexer/src/indexer.service.ts new file mode 100644 index 00000000..cf2d1609 --- /dev/null +++ b/backend/apps/indexer/src/indexer.service.ts @@ -0,0 +1,421 @@ +import { Injectable } from '@nestjs/common'; +import { Filter, FieldElement, StarkNetCursor, v1alpha2 } from '@apibara/starknet'; +import { StreamClient } from '@apibara/protocol'; +import { DataFinality } from '@apibara/protocol/dist/proto/apibara/node/v1alpha2/DataFinality'; +import { ConfigService } from '@forge/config'; +import { PrismaService } from '@forge/db'; +import { Logger } from '@forge/logger'; +import { hash, validateAndParseAddress } from 'starknet'; +import { decodeReportEvent, decodeRedeemRequestedEvent, decodeRedeemClaimedEvent } from './decoder'; +import { + BATCH_SIZE, + MAX_RETRY_AFTER_RECONNECT_NO_INTERNAL_ERROR, + MIN_SLEEP_TIME_AFTER_RECONNECT_NO_INTERNAL_ERROR, +} from './indexer.constants'; + +interface EventData { + blockNumber: number; + timestamp: number; + transactionHash: string; + hexData: string[]; + eventTypeHex: string; +} + +@Injectable() +export class IndexerService { + private eventKeys: { + redeemRequested: string; + redeemClaimed: string; + report: string; + }; + + public lastBlockIndexedVault = 0; + private url: string; + private apibaraToken: string; + private vaultFe: v1alpha2.IFieldElement; + private logger: Logger; + + private redeemRequestedBuffer: any[] = []; + private redeemClaimedBuffer: any[] = []; + private reportBuffer: any[] = []; + + constructor( + private readonly configService: ConfigService, + private readonly prismaService: PrismaService + ) { + this.logger = Logger.create('Indexer:Service'); + this.url = 'mainnet.starknet.a5a.ch'; + this.apibaraToken = this.configService.get('APIBARA_TOKEN') as string; + + // Initialize event keys using hash function + this.eventKeys = { + redeemRequested: FieldElement.toHex(FieldElement.fromBigInt(BigInt(hash.getSelector('RedeemRequested')))), + redeemClaimed: FieldElement.toHex(FieldElement.fromBigInt(BigInt(hash.getSelector('RedeemClaimed')))), + report: FieldElement.toHex(FieldElement.fromBigInt(BigInt(hash.getSelector('Report')))), + }; + + const graceful = async () => { + try { + await this.flushAllBuffers(); + } finally { + process.exit(0); + } + }; + process.on('SIGINT', graceful); + process.on('SIGTERM', graceful); + } + + async run() { + await this.runVaultIndexer(); + } + + async runVaultIndexer() { + const client = new StreamClient({ + url: this.url, + token: this.apibaraToken, + onReconnect: async (err, retryCount) => { + this.logger.error('Connection lost', err, { + retryCount, + errorCode: err.code, + }); + if (err.code !== 13 && err.code !== 14) { + return { reconnect: false }; + } + const base = Math.min(MIN_SLEEP_TIME_AFTER_RECONNECT_NO_INTERNAL_ERROR, 1000 * 2 ** Math.min(retryCount, 6)); + const jitter = Math.floor(Math.random() * 500); + await new Promise((r) => setTimeout(r, base + jitter)); + return { + reconnect: retryCount < MAX_RETRY_AFTER_RECONNECT_NO_INTERNAL_ERROR, + }; + }, + }); + + const filterBuilder = Filter.create().withHeader({ weak: false }); + + const vaultAddress = this.configService.get('VAULT_ADDRESS') as string; + try { + this.vaultFe = FieldElement.fromBigInt(validateAndParseAddress(vaultAddress)); + } catch (e) { + this.logger.error('Invalid vault address', e, { vaultAddress }); + throw e; + } + + [this.eventKeys.redeemRequested, this.eventKeys.redeemClaimed, this.eventKeys.report].forEach((keyHex) => { + const key = FieldElement.fromBigInt(BigInt(keyHex)); + filterBuilder.addEvent((event) => + event.withFromAddress(this.vaultFe).withIncludeTransaction(true).withIncludeReceipt(true).withKeys([key]) + ); + }); + + let startBlock = Number(this.configService.get('START_BLOCK')) || 0; + const forceStartBlock = this.configService.get('FORCE_START_BLOCK'); + + if (forceStartBlock == 'true') { + this.logger.info(`🔥 Force starting from block: ${startBlock} (FORCE_START_BLOCK=true, using START_BLOCK)`); + } else { + const [lastRedeemRequested, lastRedeemClaimed, lastReport, indexerStatus] = await Promise.all([ + this.prismaService.fetchLastRedeemRequested(), + this.prismaService.fetchLastRedeemClaimed(), + this.prismaService.fetchLastReport(), + this.prismaService.getIndexerStatus(), + ]); + + startBlock = Math.max( + lastRedeemRequested?.blockNumber || 0, + lastRedeemClaimed?.blockNumber || 0, + lastReport?.blockNumber || 0, + indexerStatus?.lastBlock || 0, + startBlock + ); + + this.logger.info(`📦 Resuming from block: ${startBlock} (maxFetchedBlock + 1)`, { + startBlock, + lastRedeemRequestedBlock: lastRedeemRequested?.blockNumber || 0, + lastRedeemClaimedBlock: lastRedeemClaimed?.blockNumber || 0, + lastReportBlock: lastReport?.blockNumber || 0, + }); + } + + const cursor = StarkNetCursor.createWithBlockNumber(startBlock); + + client.configure({ + filter: filterBuilder.encode(), + finality: DataFinality.DATA_STATUS_ACCEPTED, + cursor, + }); + + for await (const message of client) { + if (message.data?.data) { + for (let item of message.data.data) { + const block = v1alpha2.Block.decode(item); + const blockNumber = block.header?.blockNumber; + const timestamp = block.header?.timestamp?.seconds; + if (!blockNumber) { + throw new Error('No block number'); + } + + const blockNum = +blockNumber; + + if (!timestamp) { + throw new Error('No timestamp in block header'); + } + + this.logger.debug('Processing block', { + blockNumber: blockNum, + lastBlockIndexed: this.lastBlockIndexedVault, + timestamp: Number(timestamp), + }); + + for (let event of block.events) { + const hash = event.transaction?.meta?.hash; + + if (!hash) { + throw new Error('No hash'); + } + const hashHex = FieldElement.toHex(hash); + + const eventType = event.event?.keys?.[0]; + if (!eventType) { + throw new Error('No event type'); + } + const eventTypeHex = FieldElement.toHex(eventType); + + const data = event.event?.data; + if (!data) { + throw new Error('No data'); + } + const hexData: string[] = data.map((item) => { + try { + return FieldElement.toHex(item); + } catch (err) { + throw new Error(`Invalid FieldElement item ${item} ${err}`); + } + }); + + this.logger.debug('Processing event', { + eventType: this.getEventTypeName(eventTypeHex), + blockNumber: blockNum, + transactionHash: hashHex, + eventTypeHex, + }); + + try { + await this.processEvent(eventTypeHex, hexData, { + blockNumber: blockNum, + timestamp: Number(timestamp), + transactionHash: hashHex, + hexData, + eventTypeHex, + }); + } catch (error) { + this.logger.error('Failed to process event', error, { + blockNumber: blockNum, + transactionHash: hashHex, + eventType: this.getEventTypeName(eventTypeHex), + }); + throw error; + } + } + + await this.flushAllBuffers(); + + if (this.lastBlockIndexedVault < blockNum) { + this.lastBlockIndexedVault = blockNum; + await this.prismaService.updateIndexerStatus(blockNum); + this.logger.debug('Updated indexer status', { lastBlock: blockNum }); + } + } + } + } + + await this.flushAllBuffers(); + throw new Error('Stream ended without reconnect'); + } + + private formatStarknetAddress(value: bigint): string { + return '0x' + value.toString(16).padStart(64, '0'); + } + + private async processEvent(eventTypeHex: string, hexData: string[], eventData: EventData): Promise { + const { blockNumber, timestamp, transactionHash } = eventData; + + if (eventTypeHex === this.eventKeys.redeemRequested) { + await this.bufferRedeemRequestedEvent(hexData, blockNumber, timestamp, transactionHash); + } else if (eventTypeHex === this.eventKeys.redeemClaimed) { + await this.bufferRedeemClaimedEvent(hexData, blockNumber, timestamp, transactionHash); + } else if (eventTypeHex === this.eventKeys.report) { + await this.bufferReportEvent(hexData, blockNumber, timestamp, transactionHash); + } else { + this.logger.warn('Unknown event type', { eventTypeHex }); + } + } + + private async bufferRedeemRequestedEvent( + hexData: string[], + blockNumber: number, + timestamp: number, + transactionHash: string + ): Promise { + try { + const redeemRequested = decodeRedeemRequestedEvent(hexData); + const data = { + blockNumber, + timestamp, + transactionHash, + owner: validateAndParseAddress(this.formatStarknetAddress(redeemRequested.owner)), + receiver: validateAndParseAddress(this.formatStarknetAddress(redeemRequested.receiver)), + shares: redeemRequested.shares, + assets: redeemRequested.assets, + redeemId: redeemRequested.redeemId, + epoch: redeemRequested.epoch, + }; + + this.redeemRequestedBuffer.push(data); + + if (this.redeemRequestedBuffer.length >= BATCH_SIZE) { + await this.flushRedeemRequestedBuffer(); + } + } catch (error) { + this.logger.error('Failed to process RedeemRequested event', error, { + blockNumber, + transactionHash, + }); + } + } + + private async bufferRedeemClaimedEvent( + hexData: string[], + blockNumber: number, + timestamp: number, + transactionHash: string + ): Promise { + try { + const redeemClaimed = decodeRedeemClaimedEvent(hexData); + const data = { + blockNumber, + timestamp, + transactionHash, + receiver: validateAndParseAddress(this.formatStarknetAddress(redeemClaimed.receiver)), + redeemRequestNominal: redeemClaimed.redeemRequestNominal, + assets: redeemClaimed.assets, + redeemId: redeemClaimed.redeemId, + epoch: redeemClaimed.epoch, + }; + + this.redeemClaimedBuffer.push(data); + + if (this.redeemClaimedBuffer.length >= BATCH_SIZE) { + await this.flushRedeemClaimedBuffer(); + } + } catch (error) { + this.logger.error('Failed to process RedeemClaimed event', error, { + blockNumber, + transactionHash, + }); + } + } + + private async bufferReportEvent( + hexData: string[], + blockNumber: number, + timestamp: number, + transactionHash: string + ): Promise { + try { + const report = decodeReportEvent(hexData); + const data = { + blockNumber, + timestamp, + transactionHash, + newEpoch: report.newEpoch, + newHandledEpochLen: report.newHandledEpochLen, + totalSupply: report.totalSupply, + totalAssets: report.totalAssets, + managementFeeShares: report.managementFeeShares, + performanceFeeShares: report.performanceFeeShares, + }; + + this.reportBuffer.push(data); + + if (this.reportBuffer.length >= BATCH_SIZE) { + await this.flushReportBuffer(); + } + } catch (error) { + this.logger.error('Failed to process Report event', error, { + blockNumber, + transactionHash, + }); + } + } + + private async flushRedeemRequestedBuffer(): Promise { + if (this.redeemRequestedBuffer.length === 0) return; + + try { + await this.prismaService.redeemRequested.createMany({ + data: this.redeemRequestedBuffer, + skipDuplicates: true, + }); + this.logger.info('Flushed RedeemRequested events', { + count: this.redeemRequestedBuffer.length, + }); + this.redeemRequestedBuffer = []; + } catch (err) { + this.logger.error('Failed to flush RedeemRequested buffer', err); + throw err; + } + } + + private async flushRedeemClaimedBuffer(): Promise { + if (this.redeemClaimedBuffer.length === 0) return; + + try { + await this.prismaService.redeemClaimed.createMany({ + data: this.redeemClaimedBuffer, + skipDuplicates: true, + }); + this.logger.info('Flushed RedeemClaimed events', { + count: this.redeemClaimedBuffer.length, + }); + this.redeemClaimedBuffer = []; + } catch (err) { + this.logger.error('Failed to flush RedeemClaimed buffer', err); + throw err; + } + } + + private async flushReportBuffer(): Promise { + if (this.reportBuffer.length === 0) return; + + try { + await this.prismaService.report.createMany({ + data: this.reportBuffer, + skipDuplicates: true, + }); + this.logger.info('Flushed Report events', { + count: this.reportBuffer.length, + }); + this.reportBuffer = []; + } catch (err) { + this.logger.error('Failed to flush Report buffer', err); + throw err; + } + } + + private async flushAllBuffers(): Promise { + await Promise.all([this.flushRedeemRequestedBuffer(), this.flushRedeemClaimedBuffer(), this.flushReportBuffer()]); + } + + private getEventTypeName(eventTypeHex: string): string { + if (eventTypeHex === this.eventKeys.redeemRequested) return 'RedeemRequested'; + if (eventTypeHex === this.eventKeys.redeemClaimed) return 'RedeemClaimed'; + if (eventTypeHex === this.eventKeys.report) return 'Report'; + return 'Unknown'; + } + + getStatus() { + return { + lastBlockIndexedVault: this.lastBlockIndexedVault, + }; + } +} diff --git a/backend/apps/indexer/src/main.ts b/backend/apps/indexer/src/main.ts new file mode 100644 index 00000000..c0c2092c --- /dev/null +++ b/backend/apps/indexer/src/main.ts @@ -0,0 +1,31 @@ +import { NestFactory } from '@nestjs/core'; +import { validateIndexerConfig } from '@forge/config'; +import { Logger, initializeLogger } from '@forge/logger'; +import { IndexerModule } from './indexer.module'; +import { IndexerService } from './indexer.service'; + +async function bootstrap() { + initializeLogger(); + const logger = Logger.create('Indexer:Main'); + + try { + validateIndexerConfig(process.env); + logger.info('Environment configuration validated successfully'); + + const app = await NestFactory.create(IndexerModule); + const indexerService = app.get(IndexerService); + + logger.info('Starting StarkNet Vault Kit Indexer'); + + await indexerService.run(); + } catch (error) { + logger.error('Indexer failed', error); + process.exit(1); + } +} + +bootstrap().catch((error) => { + const logger = Logger.create('Indexer:Main'); + logger.error('Bootstrap failed', error); + process.exit(1); +}); diff --git a/backend/apps/indexer/tsconfig.json b/backend/apps/indexer/tsconfig.json new file mode 100644 index 00000000..10a1485d --- /dev/null +++ b/backend/apps/indexer/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist" + }, + "include": ["src/**/*", "../../libs/*/src/**/*"], + "exclude": ["node_modules", "dist"], + "ts-node": { + "esm": false + } +} \ No newline at end of file diff --git a/backend/apps/relayerAutomaticRedeem/nest-cli.json b/backend/apps/relayerAutomaticRedeem/nest-cli.json new file mode 100644 index 00000000..98e5edb5 --- /dev/null +++ b/backend/apps/relayerAutomaticRedeem/nest-cli.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true, + "watchAssets": true, + "assets": ["**/*.json", "**/*.md"] + }, + "entryFile": "apps/relayerAutomaticRedeem/src/main" +} \ No newline at end of file diff --git a/backend/apps/relayerAutomaticRedeem/package.json b/backend/apps/relayerAutomaticRedeem/package.json new file mode 100644 index 00000000..523506e6 --- /dev/null +++ b/backend/apps/relayerAutomaticRedeem/package.json @@ -0,0 +1,25 @@ +{ + "name": "relayerAutomaticRedeem", + "version": "0.0.1", + "description": "Automatic redeem relayer for StarkNet Vault Kit - processes pending redeems automatically", + "main": "dist/main.js", + "scripts": { + "build": "tsc", + "dev": "nest start --watch", + "start": "node dist/main.js", + "lint": "eslint \"src/**/*.ts\" --fix" + }, + "dependencies": { + "@forge/config": "workspace:*", + "@forge/db": "workspace:*", + "@forge/logger": "workspace:*", + "@forge/starknet": "workspace:*", + "cron": "^4.3.3", + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0" + }, + "devDependencies": { + "@types/cron": "^2.0.0", + "ts-node": "^10.9.1" + } +} \ No newline at end of file diff --git a/backend/apps/relayerAutomaticRedeem/src/main.ts b/backend/apps/relayerAutomaticRedeem/src/main.ts new file mode 100644 index 00000000..c4422df8 --- /dev/null +++ b/backend/apps/relayerAutomaticRedeem/src/main.ts @@ -0,0 +1,53 @@ +import { NestFactory } from "@nestjs/core"; +import { validateRelayerAutomaticRedeemConfig } from "@forge/config"; +import { Logger, initializeLogger } from "@forge/logger"; +import { RelayerAutomaticRedeemModule } from "./relayerAutomaticRedeem.module"; +import { RelayerAutomaticRedeemService } from "./relayerAutomaticRedeem.service"; + +async function bootstrap() { + initializeLogger(); + const logger = Logger.create("RelayerAutomaticRedeem:Main"); + + try { + validateRelayerAutomaticRedeemConfig(process.env); + logger.info('Environment configuration validated successfully'); + + const app = await NestFactory.create(RelayerAutomaticRedeemModule); + await app.init(); + const relayerAutomaticRedeemService = app.get(RelayerAutomaticRedeemService); + + logger.info("Starting StarkNet Vault Kit Relayer"); + + let shouldShutdown = false; + const gracefulShutdown = async (signal: string) => { + logger.info(`Received ${signal}, shutting down gracefully`); + shouldShutdown = true; + await app.close(); + process.exit(0); + }; + + process.on("SIGINT", () => gracefulShutdown("SIGINT")); + process.on("SIGTERM", () => gracefulShutdown("SIGTERM")); + + while (!shouldShutdown) { + try { + await relayerAutomaticRedeemService.execute(); + + logger.debug("Waiting 5 minutes before next execution"); + await new Promise(resolve => setTimeout(resolve, 5 * 60 * 1000)); + } catch (error) { + logger.error("Relayer execution failed:", error); + + logger.warn("Waiting 1 minute before retry after error"); + await new Promise(resolve => setTimeout(resolve, 1 * 60 * 1000)); + } + } + + logger.info("Relayer started successfully"); + } catch (error) { + logger.error("Bootstrap failed", error); + process.exit(1); + } +} + +bootstrap(); diff --git a/backend/apps/relayerAutomaticRedeem/src/relayerAutomaticRedeem.constants.ts b/backend/apps/relayerAutomaticRedeem/src/relayerAutomaticRedeem.constants.ts new file mode 100644 index 00000000..ef680b46 --- /dev/null +++ b/backend/apps/relayerAutomaticRedeem/src/relayerAutomaticRedeem.constants.ts @@ -0,0 +1,4 @@ +export const SLEEP_TIME_AFTER_AUTO_REDEEM_CHECK = 30 * 60 * 1000; // 30 minutes +export const SLEEP_TIME_AFTER_AUTO_REDEEM_CHECK_FAILED = 10 * 60 * 1000; // 10 minutes +export const SLEEP_TIME_AFTER_CLAIM_MULTIPLE_REDEEMS = 10000; // 10 seconds +export const BATCH_SIZE_FOR_CLAIM_MULTIPLE_REDEEMS = 25; diff --git a/backend/apps/relayerAutomaticRedeem/src/relayerAutomaticRedeem.module.ts b/backend/apps/relayerAutomaticRedeem/src/relayerAutomaticRedeem.module.ts new file mode 100644 index 00000000..265418b1 --- /dev/null +++ b/backend/apps/relayerAutomaticRedeem/src/relayerAutomaticRedeem.module.ts @@ -0,0 +1,12 @@ +import { Module } from "@nestjs/common"; +import { ConfigModule } from "@forge/config"; +import { PrismaModule } from "@forge/db"; +import { StarknetModule } from "@forge/starknet"; +import { RelayerAutomaticRedeemService } from "./relayerAutomaticRedeem.service"; + +@Module({ + imports: [ConfigModule, PrismaModule, StarknetModule], + providers: [RelayerAutomaticRedeemService], + exports: [RelayerAutomaticRedeemService], +}) +export class RelayerAutomaticRedeemModule {} \ No newline at end of file diff --git a/backend/apps/relayerAutomaticRedeem/src/relayerAutomaticRedeem.service.ts b/backend/apps/relayerAutomaticRedeem/src/relayerAutomaticRedeem.service.ts new file mode 100644 index 00000000..e2782c99 --- /dev/null +++ b/backend/apps/relayerAutomaticRedeem/src/relayerAutomaticRedeem.service.ts @@ -0,0 +1,234 @@ +import { Logger } from '@forge/logger'; +import { PrismaService } from '@forge/db'; +import { ConfigService } from '@forge/config'; +import { StarknetService } from '@forge/starknet'; +import { + SLEEP_TIME_AFTER_CLAIM_MULTIPLE_REDEEMS, + BATCH_SIZE_FOR_CLAIM_MULTIPLE_REDEEMS, +} from './relayerAutomaticRedeem.constants'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class RelayerAutomaticRedeemService { + private logger = Logger.create('RelayerAutomaticRedeem:Service'); + private isRunning = false; + + constructor( + private prismaService: PrismaService, + private starknetService: StarknetService, + private configService: ConfigService + ) {} + + async execute(): Promise { + if (this.isRunning) { + this.logger.debug('Auto-redeem job is already running, skipping execution'); + return; + } + + this.isRunning = true; + try { + await this.processAutoRedeem(); + } catch (error) { + this.logger.error('Auto-redeem job failed:', error); + } finally { + this.isRunning = false; + } + } + + private async processAutoRedeem(): Promise { + try { + this.logger.info('Starting auto-redeem process'); + + const indexerStatus = await this.prismaService.getIndexerStatus(); + if (!indexerStatus) { + this.logger.info('Indexer has not started yet, skipping auto-redeem'); + return; + } + + const currentBlock = await this.starknetService.getCurrentBlock(); + const blockDifference = currentBlock - indexerStatus.lastBlock; + + if (blockDifference > 10) { + this.logger.info('Indexer is not synced, waiting for synchronization', { + currentBlock, + lastIndexedBlock: indexerStatus.lastBlock, + blocksBehind: blockDifference, + }); + return; + } + + this.logger.info('Indexer is synced', { + currentBlock, + lastIndexedBlock: indexerStatus.lastBlock, + blockDifference, + }); + + const lastReport = await this.prismaService.fetchLastReport(); + + if (!lastReport) { + this.logger.info('No reports found, skipping auto-redeem'); + return; + } + + this.logger.info('Found last report', { + reportId: lastReport.id, + epoch: lastReport.newEpoch?.toString(), + handledEpochLen: lastReport.newHandledEpochLen?.toString(), + }); + + const pendingRedeems = await this.findPendingRedeems(lastReport); + + if (pendingRedeems.length === 0) { + this.logger.info('No pending redeems found for auto-redeem'); + return; + } + + this.logger.info(`Found ${pendingRedeems.length} pending redeems for processing`); + + await this.processPendingRedeemsBatches(pendingRedeems); + } catch (error) { + this.logger.error('Error in auto-redeem process:', error); + throw error; + } + } + + private async findPendingRedeems(lastReport: any): Promise { + try { + const pendingRedeems = await this.prismaService.redeemRequested.findMany({ + where: { + redeemId: { + notIn: await this.prismaService.redeemClaimed + .findMany({ + select: { redeemId: true }, + }) + .then((claims) => claims.map((c) => c.redeemId)), + }, + epoch: { + lte: lastReport.newHandledEpochLen || 0n, + }, + }, + orderBy: { redeemId: 'asc' }, + take: BATCH_SIZE_FOR_CLAIM_MULTIPLE_REDEEMS * 5, + }); + + this.logger.debug('Found pending redeems from database', { + count: pendingRedeems.length, + epochFilter: lastReport.newHandledEpochLen?.toString(), + }); + + return pendingRedeems; + } catch (error) { + this.logger.error('Error finding pending redeems:', error); + return []; + } + } + + private async processPendingRedeemsBatches(pendingRedeems: any[]): Promise { + const batches = this.chunkArray(pendingRedeems, BATCH_SIZE_FOR_CLAIM_MULTIPLE_REDEEMS); + + this.logger.info(`Processing ${batches.length} batches of redeems`); + + for (let i = 0; i < batches.length; i++) { + const batch = batches[i]; + try { + this.logger.info(`Processing batch ${i + 1}/${batches.length} with ${batch.length} redeems`); + + await this.processBatch(batch); + + if (i < batches.length - 1) { + this.logger.debug(`Sleeping for ${SLEEP_TIME_AFTER_CLAIM_MULTIPLE_REDEEMS}ms before next batch`); + await this.sleep(SLEEP_TIME_AFTER_CLAIM_MULTIPLE_REDEEMS); + } + } catch (error) { + this.logger.error(`Error processing batch ${i + 1}:`, error); + } + } + } + + private async processBatch(batch: any[]): Promise { + try { + const redeemIds = batch.map((redeem) => redeem.redeemId); + const vaultAddress = this.configService.get('VAULT_ADDRESS') as string; + const strategySymbol = (this.configService.get('STRATEGY_SYMBOL') as string) || 'default'; + + if (!vaultAddress) { + throw new Error('VAULT_ADDRESS configuration is required for auto-redeem'); + } + + this.logger.info('Processing redeem batch', { + vaultAddress, + strategySymbol, + redeemIds: redeemIds.map((id) => id.toString()), + batchSize: batch.length, + }); + + this.logger.info('Submitting claim redeem transaction to vault'); + const transactionHash = await this.starknetService.vault_claim_redeem(vaultAddress, redeemIds); + + this.logger.info('Transaction confirmed', { + transactionHash, + processedRedeemIds: redeemIds.map((id) => id.toString()), + }); + + await this.updateClaimRecords(batch, transactionHash); + + this.logger.info('Batch processed successfully', { + transactionHash, + processedCount: batch.length, + }); + } catch (error) { + this.logger.error('Error processing batch:', error); + throw error; + } + } + + private async updateClaimRecords(batch: any[], transactionHash: string): Promise { + try { + this.logger.info('Updating claim records in database', { + batchSize: batch.length, + transactionHash, + }); + + const currentTimestamp = Math.floor(Date.now() / 1000); + + const claimRecords = batch.map((redeem) => ({ + redeemId: redeem.redeemId, + receiver: redeem.receiver, + redeemRequestNominal: redeem.assets, + assets: redeem.assets, + epoch: redeem.epoch, + transactionHash: transactionHash, + timestamp: currentTimestamp, + blockNumber: 0, + })); + + await this.prismaService.redeemClaimed.createMany({ + data: claimRecords, + skipDuplicates: true, + }); + + this.logger.info('Successfully updated claim records', { + recordsCreated: claimRecords.length, + transactionHash, + }); + } catch (error) { + this.logger.error('Failed to update claim records in database', error, { + transactionHash, + batchSize: batch.length, + }); + this.logger.warn('Transaction was successful but database update failed - indexer will handle this'); + } + } + + private chunkArray(array: T[], chunkSize: number): T[][] { + const chunks: T[][] = []; + for (let i = 0; i < array.length; i += chunkSize) { + chunks.push(array.slice(i, i + chunkSize)); + } + return chunks; + } + + private async sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); + } +} diff --git a/backend/apps/relayerAutomaticRedeem/tsconfig.json b/backend/apps/relayerAutomaticRedeem/tsconfig.json new file mode 100644 index 00000000..997d1e4f --- /dev/null +++ b/backend/apps/relayerAutomaticRedeem/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "composite": false + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"] +} \ No newline at end of file diff --git a/backend/apps/relayerOnChainAum/nest-cli.json b/backend/apps/relayerOnChainAum/nest-cli.json new file mode 100644 index 00000000..5031e8a1 --- /dev/null +++ b/backend/apps/relayerOnChainAum/nest-cli.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true, + "watchAssets": true, + "assets": ["**/*.json", "**/*.md"] + }, + "entryFile": "apps/relayerOnChainAum/src/main" +} \ No newline at end of file diff --git a/backend/apps/relayerOnChainAum/package.json b/backend/apps/relayerOnChainAum/package.json new file mode 100644 index 00000000..635d5299 --- /dev/null +++ b/backend/apps/relayerOnChainAum/package.json @@ -0,0 +1,21 @@ +{ + "name": "relayerOnChainAum", + "version": "0.0.1", + "description": "On-chain AUM relayer for StarkNet Vault Kit", + "main": "dist/main.js", + "scripts": { + "build": "nest build", + "dev": "nest start --watch", + "start": "node dist/apps/relayerOnChainAum/src/main.js", + "lint": "eslint \"src/**/*.ts\" --fix", + "test": "jest" + }, + "dependencies": { + "@forge/config": "workspace:*", + "@forge/logger": "workspace:*", + "@forge/starknet": "workspace:*", + "@nestjs/core": "^10.0.0", + "@nestjs/common": "^10.0.0", + "@nestjs/config": "^4.0.0" + } +} \ No newline at end of file diff --git a/backend/apps/relayerOnChainAum/src/app.module.ts b/backend/apps/relayerOnChainAum/src/app.module.ts new file mode 100644 index 00000000..8b39083b --- /dev/null +++ b/backend/apps/relayerOnChainAum/src/app.module.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule as NestConfigModule } from '@nestjs/config'; +import { ConfigModule } from '@forge/config'; +import { StarknetModule } from '@forge/starknet'; +import { RelayerService } from './relayer.service'; + +@Module({ + imports: [ + NestConfigModule.forRoot({ + isGlobal: true, + }), + ConfigModule, + StarknetModule, + ], + providers: [RelayerService], +}) +export class AppModule {} \ No newline at end of file diff --git a/backend/apps/relayerOnChainAum/src/main.ts b/backend/apps/relayerOnChainAum/src/main.ts new file mode 100644 index 00000000..abc8bfa8 --- /dev/null +++ b/backend/apps/relayerOnChainAum/src/main.ts @@ -0,0 +1,24 @@ +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; +import { Logger, initializeLogger } from '@forge/logger'; +import { validateRelayerOnChainAumConfig } from '@forge/config'; + +async function bootstrap() { + initializeLogger(); + const logger = Logger.create('RelayerOnChainAum'); + + try { + validateRelayerOnChainAumConfig(process.env); + logger.info('Environment configuration validated successfully'); + } catch (error) { + logger.error('Environment validation failed', error); + process.exit(1); + } + await NestFactory.createApplicationContext(AppModule, { + logger, + }); + + logger.log('RelayerOnChainAum service started'); +} + +bootstrap(); \ No newline at end of file diff --git a/backend/apps/relayerOnChainAum/src/relayer.service.ts b/backend/apps/relayerOnChainAum/src/relayer.service.ts new file mode 100644 index 00000000..acdcf494 --- /dev/null +++ b/backend/apps/relayerOnChainAum/src/relayer.service.ts @@ -0,0 +1,92 @@ +import { Injectable, OnModuleInit } from '@nestjs/common'; +import { ConfigService } from '@forge/config'; +import { StarknetService } from '@forge/starknet'; +import { Logger } from '@forge/logger'; + +@Injectable() +export class RelayerService implements OnModuleInit { + private readonly logger = Logger.create('RelayerOnChainAum:Service'); + private vaultAddress: string; + private onChainAumProvider: string; + + private isRunning = false; + + constructor( + private configService: ConfigService, + private starknetService: StarknetService + ) {} + + async onModuleInit() { + this.vaultAddress = this.configService.get('VAULT_ADDRESS') as string; + this.onChainAumProvider = this.configService.get('ON_CHAIN_AUM_PROVIDER') as string; + + this.logger.info('RelayerOnChainAum service initialized', { + vaultAddress: this.vaultAddress, + onChainAumProvider: this.onChainAumProvider, + }); + + // Start the continuous monitoring loop + this.startMonitoring(); + } + + private async startMonitoring() { + if (this.isRunning) return; + this.isRunning = true; + + this.logger.info('Starting AUM reporting monitoring loop'); + + while (this.isRunning) { + try { + const nextReportTime = await this.getNextReportTime(); + const now = await this.starknetService.getCurrentBlockTimestamp(); + + if (nextReportTime <= now) { + this.logger.info('Report is ready, triggering AUM provider report'); + await this.starknetService.triggerAumProviderReport(this.onChainAumProvider); + + await this.sleep(60 * 1000); + } else { + const sleepTime = (nextReportTime - now) * 1000; // Convert to milliseconds + const sleepMinutes = Math.round(sleepTime / (60 * 1000)); + + this.logger.info(`Next report ready in ${sleepMinutes} minutes, sleeping until then`, { + nextReportTime, + currentTime: now, + sleepTimeMs: sleepTime, + }); + + await this.sleep(sleepTime); + } + } catch (error) { + this.logger.error('Error in monitoring loop, retrying in 5 minutes', error); + await this.sleep(5 * 60 * 1000); // Sleep 5 minutes on error + } + } + } + + private async getNextReportTime(): Promise { + const [lastReportTimestamp, reportDelay] = await Promise.all([ + this.starknetService.getLastReportTimestamp(this.vaultAddress), + this.starknetService.getReportDelay(this.vaultAddress), + ]); + + const nextReportTime = Number(lastReportTimestamp) + Number(reportDelay); + + this.logger.debug('Calculated next report time', { + lastReportTimestamp: lastReportTimestamp.toString(), + reportDelay: reportDelay.toString(), + nextReportTime, + }); + + return nextReportTime; + } + + private sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + + public stop() { + this.logger.info('Stopping AUM reporting monitoring'); + this.isRunning = false; + } +} diff --git a/backend/apps/relayerOnChainAum/tsconfig.json b/backend/apps/relayerOnChainAum/tsconfig.json new file mode 100644 index 00000000..1d63594f --- /dev/null +++ b/backend/apps/relayerOnChainAum/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": false, + "outDir": "./dist" + }, + "include": ["src/**/*"] +} \ No newline at end of file diff --git a/backend/docker-compose.dev.yml b/backend/docker-compose.dev.yml new file mode 100644 index 00000000..a53b97d8 --- /dev/null +++ b/backend/docker-compose.dev.yml @@ -0,0 +1,153 @@ +services: + postgres: + image: postgres:15-alpine + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: starknet_vault_kit + volumes: + - postgres_data:/var/lib/postgresql/data + ports: + - '5432:5432' + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U postgres'] + interval: 10s + timeout: 5s + retries: 5 + + api: + build: + context: . + dockerfile: Dockerfile.api.dev + environment: + - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/starknet_vault_kit + - RPC_URL=${RPC_URL} + - VAULT_ADDRESS=${VAULT_ADDRESS} + volumes: + - ./apps/api:/app/apps/api + - ./libs:/app/libs + ports: + - '3000:3000' + depends_on: + postgres: + condition: service_healthy + restart: unless-stopped + command: > + sh -c " + until nc -z postgres 5432; + do + echo '⏳ Waiting for Postgres...'; + sleep 2; + done; + echo '✅ Postgres is up! Generating Prisma client...'; + pnpm prisma generate --schema=libs/db/prisma/schema.prisma; + echo '✅ Prisma client generated! Running migrations...'; + pnpm prisma migrate deploy --schema=libs/db/prisma/schema.prisma; + echo '✅ Migrations done! Starting API with hot reload...'; + pnpm run dev:api + " + + indexer: + build: + context: . + dockerfile: Dockerfile.indexer.dev + environment: + - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/starknet_vault_kit + - RPC_URL=${RPC_URL:-https://starknet-mainnet.public.blastapi.io} + - APIBARA_TOKEN=${APIBARA_TOKEN} + - VAULT_ADDRESS=${VAULT_ADDRESS} + - START_BLOCK=${START_BLOCK} + - FORCE_START_BLOCK=${FORCE_START_BLOCK} + volumes: + - ./apps/indexer:/app/apps/indexer + - ./libs:/app/libs + depends_on: + api: + condition: service_started + restart: unless-stopped + command: > + sh -c " + until nc -z api 3000; + do + echo '⏳ Waiting for API (ready after Prisma)...'; + sleep 2; + done; + echo '✅ API is ready! Installing dependencies...'; + pnpm run dev:indexer + " + + relayer-automatic-redeem: + build: + context: . + dockerfile: Dockerfile.relayerAutomaticRedeem.dev + environment: + - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/starknet_vault_kit + - RPC_URL=${RPC_URL:-https://starknet-mainnet.public.blastapi.io} + - VAULT_ADDRESS=${VAULT_ADDRESS} + - RELAYER_ADDRESS=${RELAYER_ADDRESS} + - RELAYER_PRIVATE_KEY=${RELAYER_PRIVATE_KEY} + - CRON_SCHEDULE=${CRON_SCHEDULE:-*/5 * * * *} + volumes: + - ./apps/relayerAutomaticRedeem:/app/apps/relayerAutomaticRedeem + - ./libs:/app/libs + depends_on: + api: + condition: service_started + restart: unless-stopped + command: > + sh -c " + until nc -z api 3000; + do + echo '⏳ Waiting for API (ready after migration)...'; + sleep 2; + done; + echo '✅ API is ready! Installing dependencies...'; + pnpm run dev:relayerAutomaticRedeem + " + + relayer-on-chain-aum: + build: + context: . + dockerfile: Dockerfile.relayerOnChainAum.dev + environment: + - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/starknet_vault_kit + - RPC_URL=${RPC_URL:-https://starknet-mainnet.public.blastapi.io} + - VAULT_ADDRESS=${VAULT_ADDRESS} + - RELAYER_ADDRESS=${RELAYER_ADDRESS} + - RELAYER_PRIVATE_KEY=${RELAYER_PRIVATE_KEY} + - ON_CHAIN_AUM_PROVIDER=${ON_CHAIN_AUM_PROVIDER} + volumes: + - ./apps/relayerOnChainAum:/app/apps/relayerOnChainAum + - ./libs:/app/libs + depends_on: + api: + condition: service_started + restart: unless-stopped + command: > + sh -c " + until nc -z api 3000; + do + echo '⏳ Waiting for API (ready after migration)...'; + sleep 2; + done; + echo '✅ API is ready! Starting on-chain AUM relayer...'; + pnpm run dev:relayerOnChainAum + " + + pgadmin: + image: dpage/pgadmin4 + container_name: forge-pgadmin-dev + restart: unless-stopped + environment: + PGADMIN_DEFAULT_EMAIL: admin@admin.com + PGADMIN_DEFAULT_PASSWORD: admin + ports: + - '5050:80' + depends_on: + - postgres + volumes: + - pgadmin_data:/var/lib/pgadmin + +volumes: + postgres_data: + pgadmin_data: diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml new file mode 100644 index 00000000..0e327b67 --- /dev/null +++ b/backend/docker-compose.yml @@ -0,0 +1,82 @@ +services: + postgres: + image: postgres:15-alpine + container_name: vault_postgres + environment: + POSTGRES_DB: starknet_vault_kit + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_HOST_AUTH_METHOD: trust + ports: + - '5432:5432' + volumes: + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U postgres -d starknet_vault_kit'] + interval: 30s + timeout: 10s + retries: 5 + start_period: 30s + restart: unless-stopped + + api: + build: + context: .. + dockerfile: backend/Dockerfile.api + container_name: vault_api + ports: + - '3000:3000' + environment: + - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/starknet_vault_kit + - RPC_URL=${RPC_URL} + - VAULT_ADDRESS=${VAULT_ADDRESS} + - LOG_LEVEL=${LOG_LEVEL} + depends_on: + postgres: + condition: service_healthy + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + restart: unless-stopped + + indexer: + build: + context: .. + dockerfile: backend/Dockerfile.indexer + container_name: vault_indexer + environment: + - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/starknet_vault_kit + - RPC_URL=${RPC_URL} + - VAULT_ADDRESS=${VAULT_ADDRESS} + - APIBARA_TOKEN=${APIBARA_TOKEN} + - START_BLOCK=${START_BLOCK} + - LOG_LEVEL=${LOG_LEVEL} + depends_on: + postgres: + condition: service_healthy + restart: unless-stopped + + onchain-aum-provider: + build: + context: .. + dockerfile: backend/Dockerfile.relayerOnChainAum + container_name: vault_aum_provider + environment: + - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/starknet_vault_kit + - RPC_URL=${RPC_URL} + - VAULT_ADDRESS=${VAULT_ADDRESS} + - LOG_LEVEL=${LOG_LEVEL} + - ON_CHAIN_AUM_PROVIDER=${ON_CHAIN_AUM_PROVIDER} + - RELAYER_ADDRESS=${RELAYER_ADDRESS} + - RELAYER_PRIVATE_KEY=${RELAYER_PRIVATE_KEY} + depends_on: + postgres: + condition: service_healthy + restart: unless-stopped + +volumes: + pgdata: + driver: local diff --git a/backend/libs/config/package.json b/backend/libs/config/package.json new file mode 100644 index 00000000..46ba1574 --- /dev/null +++ b/backend/libs/config/package.json @@ -0,0 +1,17 @@ +{ + "name": "@forge/config", + "version": "0.0.1", + "description": "Configuration management for StarkNet Vault Kit", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "dev": "tsc --watch" + }, + "dependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/config": "^3.0.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0" + } +} \ No newline at end of file diff --git a/backend/libs/config/src/config.module.ts b/backend/libs/config/src/config.module.ts new file mode 100644 index 00000000..f52317f1 --- /dev/null +++ b/backend/libs/config/src/config.module.ts @@ -0,0 +1,14 @@ +import { Module } from "@nestjs/common"; +import { ConfigModule as NestConfigModule } from "@nestjs/config"; +import { ConfigService } from "./config.service"; + +@Module({ + imports: [ + NestConfigModule.forRoot({ + isGlobal: true, + }), + ], + providers: [ConfigService], + exports: [ConfigService], +}) +export class ConfigModule {} diff --git a/backend/libs/config/src/config.service.ts b/backend/libs/config/src/config.service.ts new file mode 100644 index 00000000..a1bb7298 --- /dev/null +++ b/backend/libs/config/src/config.service.ts @@ -0,0 +1,11 @@ +import { Injectable } from "@nestjs/common"; +import { ConfigService as NestConfigService } from "@nestjs/config"; + +@Injectable() +export class ConfigService { + constructor(private readonly configService: NestConfigService) {} + + public get(key: string): string | number | undefined { + return this.configService.get(key, { infer: true }); + } +} diff --git a/backend/libs/config/src/env.validation.ts b/backend/libs/config/src/env.validation.ts new file mode 100644 index 00000000..065da44e --- /dev/null +++ b/backend/libs/config/src/env.validation.ts @@ -0,0 +1,123 @@ +import { plainToClass, Transform } from "class-transformer"; +import { + IsNumber, + IsOptional, + validateSync, + IsString, + IsBoolean, +} from "class-validator"; + +// Base environment variables needed by all services +export class BaseEnvironmentVariables { + @IsString() + DATABASE_URL: string; +} + +// API-specific environment variables +export class ApiEnvironmentVariables extends BaseEnvironmentVariables { + @IsOptional() + @IsNumber() + @Transform(({ value }) => Number(value)) + PORT = 3000; + + @IsString() + RPC_URL: string; + + @IsString() + VAULT_ADDRESS: string; +} + +// Indexer-specific environment variables +export class IndexerEnvironmentVariables extends BaseEnvironmentVariables { + @IsString() + APIBARA_TOKEN: string; + + @IsString() + VAULT_ADDRESS: string; + + @IsOptional() + @IsNumber() + @Transform(({ value }) => Number(value)) + START_BLOCK?: number; + + @IsOptional() + @IsBoolean() + @Transform(({ value }) => value === "true" || value === true) + FORCE_START_BLOCK?: boolean; +} + +// RelayerAutomaticRedeem-specific environment variables +export class RelayerAutomaticRedeemEnvironmentVariables extends BaseEnvironmentVariables { + @IsString() + RPC_URL: string; + + @IsString() + VAULT_ADDRESS: string; + + @IsString() + RELAYER_ADDRESS: string; + + @IsString() + RELAYER_PRIVATE_KEY: string; + + @IsOptional() + @IsString() + CRON_SCHEDULE = "*/5 * * * *"; +} + +// RelayerOnChainAum-specific environment variables +export class RelayerOnChainAumEnvironmentVariables extends BaseEnvironmentVariables { + @IsString() + RPC_URL: string; + + @IsString() + VAULT_ADDRESS: string; + + @IsString() + RELAYER_ADDRESS: string; + + @IsString() + RELAYER_PRIVATE_KEY: string; + + @IsString() + ON_CHAIN_AUM_PROVIDER: string; +} + +// Generic validation function +function validateConfig( + EnvClass: new () => T, + config: Record +): T { + const validatedConfig = plainToClass(EnvClass, config); + + const validatorOptions = { skipMissingProperties: false }; + const errors = validateSync(validatedConfig, validatorOptions); + + if (errors.length > 0) { + console.error(errors.toString()); + process.exit(1); + } + + return validatedConfig; +} + +// Export specific validation functions for each service +export function validateApiConfig(config: Record) { + return validateConfig(ApiEnvironmentVariables, config); +} + +export function validateIndexerConfig(config: Record) { + return validateConfig(IndexerEnvironmentVariables, config); +} + +export function validateRelayerAutomaticRedeemConfig( + config: Record +) { + return validateConfig(RelayerAutomaticRedeemEnvironmentVariables, config); +} + +export function validateRelayerOnChainAumConfig( + config: Record +) { + return validateConfig(RelayerOnChainAumEnvironmentVariables, config); +} diff --git a/backend/libs/config/src/index.ts b/backend/libs/config/src/index.ts new file mode 100644 index 00000000..853b7711 --- /dev/null +++ b/backend/libs/config/src/index.ts @@ -0,0 +1,3 @@ +export * from './config.service'; +export * from './config.module'; +export * from './env.validation'; \ No newline at end of file diff --git a/backend/libs/config/tsconfig.json b/backend/libs/config/tsconfig.json new file mode 100644 index 00000000..33034f95 --- /dev/null +++ b/backend/libs/config/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/backend/libs/db/package.json b/backend/libs/db/package.json new file mode 100644 index 00000000..ca240dfd --- /dev/null +++ b/backend/libs/db/package.json @@ -0,0 +1,20 @@ +{ + "name": "@forge/db", + "version": "0.0.1", + "description": "Database client and DAL for StarkNet Vault Kit", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "prisma:generate": "prisma generate", + "prisma:migrate:dev": "prisma migrate dev", + "prisma:migrate:deploy": "prisma migrate deploy", + "prisma:studio": "prisma studio" + }, + "dependencies": { + "@prisma/client": "^6.15.0", + "prisma": "6.15.0", + "@nestjs/common": "^10.0.0" + } +} \ No newline at end of file diff --git a/backend/libs/db/prisma/migrations/20250826190954_init/migration.sql b/backend/libs/db/prisma/migrations/20250826190954_init/migration.sql new file mode 100644 index 00000000..2ddc1093 --- /dev/null +++ b/backend/libs/db/prisma/migrations/20250826190954_init/migration.sql @@ -0,0 +1,67 @@ +-- CreateTable +CREATE TABLE "Report" ( + "id" TEXT NOT NULL, + "blockNumber" INTEGER NOT NULL, + "timestamp" INTEGER NOT NULL, + "transactionHash" TEXT NOT NULL, + "newEpoch" BIGINT NOT NULL, + "newHandledEpochLen" BIGINT NOT NULL, + "totalSupply" BIGINT NOT NULL, + "totalAssets" BIGINT NOT NULL, + "managementFeeShares" BIGINT NOT NULL, + "performanceFeeShares" BIGINT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Report_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "RedeemRequested" ( + "id" TEXT NOT NULL, + "blockNumber" INTEGER NOT NULL, + "timestamp" INTEGER NOT NULL, + "transactionHash" TEXT NOT NULL, + "owner" TEXT NOT NULL, + "receiver" TEXT NOT NULL, + "shares" BIGINT NOT NULL, + "assets" BIGINT NOT NULL, + "redeemId" BIGINT NOT NULL, + "epoch" BIGINT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "RedeemRequested_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "RedeemClaimed" ( + "id" TEXT NOT NULL, + "blockNumber" INTEGER NOT NULL, + "timestamp" INTEGER NOT NULL, + "transactionHash" TEXT NOT NULL, + "receiver" TEXT NOT NULL, + "redeemRequestNominal" BIGINT NOT NULL, + "assets" BIGINT NOT NULL, + "redeemId" BIGINT NOT NULL, + "epoch" BIGINT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "RedeemClaimed_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IndexerStatus" ( + "id" INTEGER NOT NULL DEFAULT 1, + "currentBlock" INTEGER NOT NULL, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "IndexerStatus_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Report_newEpoch_key" ON "Report"("newEpoch"); + +-- CreateIndex +CREATE UNIQUE INDEX "RedeemRequested_redeemId_key" ON "RedeemRequested"("redeemId"); + +-- CreateIndex +CREATE UNIQUE INDEX "RedeemClaimed_redeemId_key" ON "RedeemClaimed"("redeemId"); diff --git a/backend/libs/db/prisma/migrations/20250904105108_init/migration.sql b/backend/libs/db/prisma/migrations/20250904105108_init/migration.sql new file mode 100644 index 00000000..ed5dadbc --- /dev/null +++ b/backend/libs/db/prisma/migrations/20250904105108_init/migration.sql @@ -0,0 +1,10 @@ +/* + Warnings: + + - You are about to drop the column `currentBlock` on the `IndexerStatus` table. All the data in the column will be lost. + - Added the required column `lastBlock` to the `IndexerStatus` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "public"."IndexerStatus" DROP COLUMN "currentBlock", +ADD COLUMN "lastBlock" INTEGER NOT NULL; diff --git a/backend/libs/db/prisma/migrations/migration_lock.toml b/backend/libs/db/prisma/migrations/migration_lock.toml new file mode 100644 index 00000000..044d57cd --- /dev/null +++ b/backend/libs/db/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" diff --git a/backend/libs/db/prisma/schema.prisma b/backend/libs/db/prisma/schema.prisma new file mode 100644 index 00000000..1c18163b --- /dev/null +++ b/backend/libs/db/prisma/schema.prisma @@ -0,0 +1,68 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model Report { + id String @id @default(uuid()) + blockNumber Int + timestamp Int + transactionHash String + + newEpoch BigInt + newHandledEpochLen BigInt + totalSupply BigInt + totalAssets BigInt + managementFeeShares BigInt + performanceFeeShares BigInt + + createdAt DateTime @default(now()) + + @@unique([newEpoch]) +} + + +model RedeemRequested { + id String @id @default(uuid()) + blockNumber Int + timestamp Int + transactionHash String + + owner String + receiver String + shares BigInt + assets BigInt + redeemId BigInt + epoch BigInt + + createdAt DateTime @default(now()) + + @@unique([redeemId]) +} + +model RedeemClaimed { + id String @id @default(uuid()) + blockNumber Int + timestamp Int + transactionHash String + + receiver String + redeemRequestNominal BigInt + assets BigInt + redeemId BigInt + epoch BigInt + + createdAt DateTime @default(now()) + + @@unique([redeemId]) +} + +model IndexerStatus { + id Int @id @default(1) + lastBlock Int + updatedAt DateTime @updatedAt +} diff --git a/backend/libs/db/src/index.ts b/backend/libs/db/src/index.ts new file mode 100644 index 00000000..8704fd9b --- /dev/null +++ b/backend/libs/db/src/index.ts @@ -0,0 +1,2 @@ +export * from './prisma.service'; +export * from './prisma.module'; \ No newline at end of file diff --git a/backend/libs/db/src/prisma.module.ts b/backend/libs/db/src/prisma.module.ts new file mode 100644 index 00000000..7856e928 --- /dev/null +++ b/backend/libs/db/src/prisma.module.ts @@ -0,0 +1,8 @@ +import { Module } from "@nestjs/common"; +import { PrismaService } from "./prisma.service"; + +@Module({ + providers: [PrismaService], + exports: [PrismaService], +}) +export class PrismaModule {} \ No newline at end of file diff --git a/backend/libs/db/src/prisma.service.ts b/backend/libs/db/src/prisma.service.ts new file mode 100644 index 00000000..7cf8c8ff --- /dev/null +++ b/backend/libs/db/src/prisma.service.ts @@ -0,0 +1,154 @@ +import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; +import { PrismaClient, RedeemRequested, Report, RedeemClaimed, IndexerStatus } from '@prisma/client'; + +@Injectable() +export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { + async onModuleInit() { + await this.$connect(); + } + + async onModuleDestroy() { + await this.$disconnect(); + } + + async fetchLastRedeemRequested(): Promise { + try { + const latest = await this.redeemRequested.findFirst({ + orderBy: { blockNumber: 'desc' }, + }); + return latest as RedeemRequested | undefined; + } catch (error) { + // Table doesn't exist yet, return undefined + return undefined; + } + } + + async fetchLastReport(): Promise { + try { + const latest = await this.report.findFirst({ + orderBy: { blockNumber: 'desc' }, + }); + return latest as Report | undefined; + } catch (error) { + // Table doesn't exist yet, return undefined + return undefined; + } + } + + async fetchLastReports(limit: number, offset?: number): Promise { + try { + const reports = await this.report.findMany({ + orderBy: { blockNumber: 'desc' }, + take: limit, + ...(offset && { skip: offset }), + }); + return reports as Report[]; + } catch (error) { + // Table doesn't exist yet, return empty array + return []; + } + } + + async fetchLastRedeemClaimed(): Promise { + try { + const latest = await this.redeemClaimed.findFirst({ + orderBy: { blockNumber: 'desc' }, + }); + return latest as RedeemClaimed | undefined; + } catch (error) { + // Table doesn't exist yet, return undefined + return undefined; + } + } + + async fetchLastRedeemRequestedIdForAddress(receiver: string): Promise { + try { + const latest = await this.redeemRequested.findFirst({ + where: { receiver }, + orderBy: { redeemId: 'desc' }, + select: { redeemId: true }, + }); + return latest?.redeemId !== undefined ? Number(latest.redeemId) : undefined; + } catch (error) { + // Table doesn't exist yet, return undefined + return undefined; + } + } + + async fetchLastRedeemClaimedIdForAddress(receiver: string): Promise { + try { + const latest = await this.redeemClaimed.findFirst({ + where: { receiver }, + orderBy: { redeemId: 'desc' }, + select: { redeemId: true }, + }); + return latest?.redeemId !== undefined ? Number(latest.redeemId) : undefined; + } catch (error) { + // Table doesn't exist yet, return undefined + return undefined; + } + } + + public async fetchPendingRedeemsForAddress( + receiver: string, + minRedeemId: number, + maxRedeemId: number, + limit?: number, + offset?: number + ): Promise { + try { + // Get all claimed redeem IDs for this address in the range + const claimedRedeemIds = await this.redeemClaimed.findMany({ + where: { + receiver, + redeemId: { + gt: minRedeemId, + lte: maxRedeemId, + }, + }, + select: { redeemId: true }, + }); + + const claimedIds = claimedRedeemIds.map((r) => r.redeemId); + + // Find all requested redeems in the range that are NOT in the claimed list + return this.redeemRequested.findMany({ + where: { + receiver, + redeemId: { + gt: minRedeemId, + lte: maxRedeemId, + notIn: claimedIds, + }, + }, + orderBy: { + redeemId: 'desc', + }, + ...(limit && { take: limit }), + ...(offset && { skip: offset }), + }); + } catch (error) { + // Tables don't exist yet, return empty array + return []; + } + } + + async updateIndexerStatus(lastBlock: number): Promise { + return this.indexerStatus.upsert({ + where: { id: 1 }, + update: { lastBlock }, + create: { id: 1, lastBlock }, + }); + } + + async getIndexerStatus(): Promise { + try { + return await this.indexerStatus.findUnique({ + where: { id: 1 }, + }); + } catch (error) { + // Table doesn't exist yet, return null + return null; + } + } +} diff --git a/backend/libs/db/tsconfig.json b/backend/libs/db/tsconfig.json new file mode 100644 index 00000000..33034f95 --- /dev/null +++ b/backend/libs/db/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/backend/libs/logger/package.json b/backend/libs/logger/package.json new file mode 100644 index 00000000..3ebfafeb --- /dev/null +++ b/backend/libs/logger/package.json @@ -0,0 +1,15 @@ +{ + "name": "@forge/logger", + "version": "0.0.1", + "description": "Logger factory for StarkNet Vault Kit", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "dev": "tsc --watch" + }, + "dependencies": { + "winston": "^3.11.0", + "winston-daily-rotate-file": "^4.7.1" + } +} \ No newline at end of file diff --git a/backend/libs/logger/src/index.ts b/backend/libs/logger/src/index.ts new file mode 100644 index 00000000..7a9a5761 --- /dev/null +++ b/backend/libs/logger/src/index.ts @@ -0,0 +1,3 @@ +export * from './logger'; +export * from './logger.config'; +export { Logger, LogLevel, LoggerConfig } from './logger'; \ No newline at end of file diff --git a/backend/libs/logger/src/logger.config.ts b/backend/libs/logger/src/logger.config.ts new file mode 100644 index 00000000..61417fc3 --- /dev/null +++ b/backend/libs/logger/src/logger.config.ts @@ -0,0 +1,22 @@ +import { LogLevel, LoggerConfig } from './logger'; + +export const getLoggerConfig = (): LoggerConfig => { + const isDevelopment = process.env.NODE_ENV === 'development'; + const logLevel = (process.env.LOG_LEVEL as LogLevel) || (isDevelopment ? LogLevel.DEBUG : LogLevel.INFO); + + return { + level: logLevel, + service: 'starknet-vault-kit', + enableConsole: true, + enableFile: process.env.ENABLE_FILE_LOGGING === 'true' || !isDevelopment, + logDir: process.env.LOG_DIR || 'logs', + maxFiles: '14d', + maxSize: '20m', + format: isDevelopment ? 'simple' : 'json', + }; +}; + +export const initializeLogger = (): void => { + const { Logger } = require('./logger'); + Logger.configure(getLoggerConfig()); +}; \ No newline at end of file diff --git a/backend/libs/logger/src/logger.ts b/backend/libs/logger/src/logger.ts new file mode 100644 index 00000000..3184320c --- /dev/null +++ b/backend/libs/logger/src/logger.ts @@ -0,0 +1,169 @@ +import * as winston from 'winston'; +import * as DailyRotateFile from 'winston-daily-rotate-file'; + +export enum LogLevel { + ERROR = 'error', + WARN = 'warn', + INFO = 'info', + DEBUG = 'debug', +} + +export interface LoggerConfig { + level?: LogLevel; + service?: string; + enableConsole?: boolean; + enableFile?: boolean; + logDir?: string; + maxFiles?: string; + maxSize?: string; + format?: 'json' | 'simple'; +} + +export class Logger { + private winston: winston.Logger; + private context: string; + private static instances: Map = new Map(); + + private constructor(context: string, config: LoggerConfig = {}) { + this.context = context; + this.winston = this.createWinstonLogger(config); + } + + private createWinstonLogger(config: LoggerConfig): winston.Logger { + const { + level = LogLevel.INFO, + service = 'starknet-vault-kit', + enableConsole = true, + enableFile = true, + logDir = 'logs', + maxFiles = '14d', + maxSize = '20m', + format = 'json', + } = config; + + const logFormat = format === 'json' + ? winston.format.combine( + winston.format.timestamp(), + winston.format.errors({ stack: true }), + winston.format.json(), + winston.format.printf(({ timestamp, level, message, service, context, ...meta }) => { + const baseLog = { + timestamp, + level, + service, + context, + message, + }; + return JSON.stringify(Object.keys(meta).length ? { ...baseLog, ...meta } : baseLog); + }) + ) + : winston.format.combine( + winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + winston.format.errors({ stack: true }), + winston.format.printf(({ timestamp, level, message, context, service, ...meta }) => { + const metaStr = Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : ''; + return `${timestamp} [${level.toUpperCase()}] [${service}:${context}] ${message}${metaStr}`; + }) + ); + + const transports: winston.transport[] = []; + + if (enableConsole) { + transports.push( + new winston.transports.Console({ + level, + format: winston.format.combine( + winston.format.colorize({ all: false, level: true }), + winston.format.timestamp({ format: 'HH:mm:ss' }), + winston.format.printf(({ timestamp, level, message, context, ...meta }) => { + const contextStr = context ? `[${context}]` : ''; + const metaStr = Object.keys(meta).length > 1 ? ` ${JSON.stringify( + Object.fromEntries( + Object.entries(meta).filter(([key]) => key !== 'service') + ), null, 2 + )}` : ''; + return `${timestamp} ${level} ${contextStr} ${message}${metaStr}`; + }) + ), + }) + ); + } + + if (enableFile) { + transports.push( + new DailyRotateFile({ + dirname: logDir, + filename: `${service}-%DATE%.log`, + datePattern: 'YYYY-MM-DD', + maxFiles, + maxSize, + level, + format: logFormat, + }), + new DailyRotateFile({ + dirname: logDir, + filename: `${service}-error-%DATE%.log`, + datePattern: 'YYYY-MM-DD', + level: 'error', + maxFiles, + maxSize, + format: logFormat, + }) + ); + } + + return winston.createLogger({ + level, + defaultMeta: { service, context: this.context }, + transports, + exitOnError: false, + }); + } + + info(message: string, meta?: any): void { + this.winston.info(message, meta); + } + + log(message: string, meta?: any): void { + this.winston.info(message, meta); + } + + error(message: string, error?: Error | any, meta?: any): void { + const errorMeta = error instanceof Error + ? { error: error.message, stack: error.stack, ...meta } + : { error, ...meta }; + + this.winston.error(message, errorMeta); + } + + warn(message: string, meta?: any): void { + this.winston.warn(message, meta); + } + + debug(message: string, meta?: any): void { + this.winston.debug(message, meta); + } + + child(childContext: string): Logger { + const fullContext = `${this.context}:${childContext}`; + return Logger.create(fullContext); + } + + static create(context: string = 'Application', config?: LoggerConfig): Logger { + if (!Logger.instances.has(context)) { + Logger.instances.set(context, new Logger(context, config)); + } + return Logger.instances.get(context)!; + } + + static configure(globalConfig: LoggerConfig): void { + Logger.instances.clear(); + Logger.defaultConfig = globalConfig; + } + + private static defaultConfig: LoggerConfig = {}; + + static getDefaultConfig(): LoggerConfig { + return Logger.defaultConfig; + } +} \ No newline at end of file diff --git a/backend/libs/logger/tsconfig.json b/backend/libs/logger/tsconfig.json new file mode 100644 index 00000000..33034f95 --- /dev/null +++ b/backend/libs/logger/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/backend/libs/starknet/package.json b/backend/libs/starknet/package.json new file mode 100644 index 00000000..48d4df1c --- /dev/null +++ b/backend/libs/starknet/package.json @@ -0,0 +1,17 @@ +{ + "name": "@forge/starknet", + "version": "1.0.0", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "watch": "tsc --watch", + "clean": "rm -rf dist" + }, + "dependencies": { + "@forge/config": "workspace:*", + "@forge/logger": "workspace:*", + "@nestjs/common": "^10.0.0", + "starknet": "^7.6.4" + } +} \ No newline at end of file diff --git a/backend/libs/starknet/src/abis/aumProvider.json b/backend/libs/starknet/src/abis/aumProvider.json new file mode 100644 index 00000000..30d91447 --- /dev/null +++ b/backend/libs/starknet/src/abis/aumProvider.json @@ -0,0 +1,99 @@ +[ + { + "type": "impl", + "name": "AumProvider4626Impl", + "interface_name": "vault::aum_provider::aum_provider_4626::interface::IAumProvider4626" + }, + { + "type": "interface", + "name": "vault::aum_provider::aum_provider_4626::interface::IAumProvider4626", + "items": [ + { + "type": "function", + "name": "get_strategy_4626", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "BaseAumProviderImpl", + "interface_name": "vault::aum_provider::interface::IBaseAumProvider" + }, + { + "type": "struct", + "name": "core::integer::u256", + "members": [ + { + "name": "low", + "type": "core::integer::u128" + }, + { + "name": "high", + "type": "core::integer::u128" + } + ] + }, + { + "type": "interface", + "name": "vault::aum_provider::interface::IBaseAumProvider", + "items": [ + { + "type": "function", + "name": "aum", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "report", + "inputs": [], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "vault", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "strategy4626", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "event", + "name": "vault::aum_provider::base_aum_provider::BaseAumProviderComponent::Event", + "kind": "enum", + "variants": [] + }, + { + "type": "event", + "name": "vault::aum_provider::aum_provider_4626::aum_provider_4626::AumProvider4626::Event", + "kind": "enum", + "variants": [ + { + "name": "BaseAumProviderEvent", + "type": "vault::aum_provider::base_aum_provider::BaseAumProviderComponent::Event", + "kind": "nested" + } + ] + } +] \ No newline at end of file diff --git a/backend/libs/starknet/src/abis/vault.json b/backend/libs/starknet/src/abis/vault.json new file mode 100644 index 00000000..131f20be --- /dev/null +++ b/backend/libs/starknet/src/abis/vault.json @@ -0,0 +1,1581 @@ +[ + { + "type": "impl", + "name": "UpgradeableImpl", + "interface_name": "openzeppelin_interfaces::upgrades::IUpgradeable" + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::upgrades::IUpgradeable", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "VaultMetadataImpl", + "interface_name": "openzeppelin_interfaces::token::erc20::IERC20Metadata" + }, + { + "type": "struct", + "name": "core::byte_array::ByteArray", + "members": [ + { + "name": "data", + "type": "core::array::Array::" + }, + { + "name": "pending_word", + "type": "core::felt252" + }, + { + "name": "pending_word_len", + "type": "core::integer::u32" + } + ] + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::token::erc20::IERC20Metadata", + "items": [ + { + "type": "function", + "name": "name", + "inputs": [], + "outputs": [ + { + "type": "core::byte_array::ByteArray" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "symbol", + "inputs": [], + "outputs": [ + { + "type": "core::byte_array::ByteArray" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "decimals", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u8" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "VaultImpl", + "interface_name": "vault::vault::interface::IVault" + }, + { + "type": "struct", + "name": "core::integer::u256", + "members": [ + { + "name": "low", + "type": "core::integer::u128" + }, + { + "name": "high", + "type": "core::integer::u128" + } + ] + }, + { + "type": "interface", + "name": "vault::vault::interface::IVault", + "items": [ + { + "type": "function", + "name": "register_redeem_request", + "inputs": [ + { + "name": "redeem_request", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "register_vault_allocator", + "inputs": [ + { + "name": "vault_allocator", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "request_redeem", + "inputs": [ + { + "name": "shares", + "type": "core::integer::u256" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "claim_redeem", + "inputs": [ + { + "name": "id", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "set_fees_config", + "inputs": [ + { + "name": "fees_recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "redeem_fees", + "type": "core::integer::u256" + }, + { + "name": "management_fees", + "type": "core::integer::u256" + }, + { + "name": "performance_fees", + "type": "core::integer::u256" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "set_report_delay", + "inputs": [ + { + "name": "report_delay", + "type": "core::integer::u64" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "set_max_delta", + "inputs": [ + { + "name": "max_delta", + "type": "core::integer::u256" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "report", + "inputs": [ + { + "name": "new_aum", + "type": "core::integer::u256" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "bring_liquidity", + "inputs": [ + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "pause", + "inputs": [], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "unpause", + "inputs": [], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "epoch", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "handled_epoch_len", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "buffer", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "aum", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "redeem_assets", + "inputs": [ + { + "name": "epoch", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "redeem_nominal", + "inputs": [ + { + "name": "epoch", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "fees_recipient", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "redeem_fees", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "management_fees", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "performance_fees", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "redeem_request", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "report_delay", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u64" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "vault_allocator", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "last_report_timestamp", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u64" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "max_delta", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "due_assets_from_id", + "inputs": [ + { + "name": "id", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "ERC4626Impl", + "interface_name": "openzeppelin_interfaces::token::erc4626::IERC4626" + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::token::erc4626::IERC4626", + "items": [ + { + "type": "function", + "name": "asset", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "total_assets", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "convert_to_shares", + "inputs": [ + { + "name": "assets", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "convert_to_assets", + "inputs": [ + { + "name": "shares", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "max_deposit", + "inputs": [ + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "preview_deposit", + "inputs": [ + { + "name": "assets", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "deposit", + "inputs": [ + { + "name": "assets", + "type": "core::integer::u256" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "max_mint", + "inputs": [ + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "preview_mint", + "inputs": [ + { + "name": "shares", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "mint", + "inputs": [ + { + "name": "shares", + "type": "core::integer::u256" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "max_withdraw", + "inputs": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "preview_withdraw", + "inputs": [ + { + "name": "assets", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "withdraw", + "inputs": [ + { + "name": "assets", + "type": "core::integer::u256" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "max_redeem", + "inputs": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "preview_redeem", + "inputs": [ + { + "name": "shares", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "redeem", + "inputs": [ + { + "name": "shares", + "type": "core::integer::u256" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "ERC20Impl", + "interface_name": "openzeppelin_interfaces::token::erc20::IERC20" + }, + { + "type": "enum", + "name": "core::bool", + "variants": [ + { + "name": "False", + "type": "()" + }, + { + "name": "True", + "type": "()" + } + ] + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::token::erc20::IERC20", + "items": [ + { + "type": "function", + "name": "total_supply", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "balance_of", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "allowance", + "inputs": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "spender", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "transfer", + "inputs": [ + { + "name": "recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "transfer_from", + "inputs": [ + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "approve", + "inputs": [ + { + "name": "spender", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "ERC20CamelOnlyImpl", + "interface_name": "openzeppelin_interfaces::token::erc20::IERC20CamelOnly" + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::token::erc20::IERC20CamelOnly", + "items": [ + { + "type": "function", + "name": "totalSupply", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "balanceOf", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "transferFrom", + "inputs": [ + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "AccessControlImpl", + "interface_name": "openzeppelin_interfaces::access::accesscontrol::IAccessControl" + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::access::accesscontrol::IAccessControl", + "items": [ + { + "type": "function", + "name": "has_role", + "inputs": [ + { + "name": "role", + "type": "core::felt252" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_role_admin", + "inputs": [ + { + "name": "role", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "grant_role", + "inputs": [ + { + "name": "role", + "type": "core::felt252" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "revoke_role", + "inputs": [ + { + "name": "role", + "type": "core::felt252" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "renounce_role", + "inputs": [ + { + "name": "role", + "type": "core::felt252" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "PausableImpl", + "interface_name": "openzeppelin_interfaces::security::pausable::IPausable" + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::security::pausable::IPausable", + "items": [ + { + "type": "function", + "name": "is_paused", + "inputs": [], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "name", + "type": "core::byte_array::ByteArray" + }, + { + "name": "symbol", + "type": "core::byte_array::ByteArray" + }, + { + "name": "underlying_asset", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "fees_recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "redeem_fees", + "type": "core::integer::u256" + }, + { + "name": "management_fees", + "type": "core::integer::u256" + }, + { + "name": "performance_fees", + "type": "core::integer::u256" + }, + { + "name": "report_delay", + "type": "core::integer::u64" + }, + { + "name": "max_delta", + "type": "core::integer::u256" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::erc20::ERC20Component::Transfer", + "kind": "struct", + "members": [ + { + "name": "from", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "to", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "value", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::erc20::ERC20Component::Approval", + "kind": "struct", + "members": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "spender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "value", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::erc20::ERC20Component::Event", + "kind": "enum", + "variants": [ + { + "name": "Transfer", + "type": "openzeppelin_token::erc20::erc20::ERC20Component::Transfer", + "kind": "nested" + }, + { + "name": "Approval", + "type": "openzeppelin_token::erc20::erc20::ERC20Component::Approval", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::extensions::erc4626::erc4626::ERC4626Component::Deposit", + "kind": "struct", + "members": [ + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "assets", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "shares", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::extensions::erc4626::erc4626::ERC4626Component::Withdraw", + "kind": "struct", + "members": [ + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "assets", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "shares", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::extensions::erc4626::erc4626::ERC4626Component::Event", + "kind": "enum", + "variants": [ + { + "name": "Deposit", + "type": "openzeppelin_token::erc20::extensions::erc4626::erc4626::ERC4626Component::Deposit", + "kind": "nested" + }, + { + "name": "Withdraw", + "type": "openzeppelin_token::erc20::extensions::erc4626::erc4626::ERC4626Component::Withdraw", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_introspection::src5::SRC5Component::Event", + "kind": "enum", + "variants": [] + }, + { + "type": "event", + "name": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleGranted", + "kind": "struct", + "members": [ + { + "name": "role", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleGrantedWithDelay", + "kind": "struct", + "members": [ + { + "name": "role", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "delay", + "type": "core::integer::u64", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleRevoked", + "kind": "struct", + "members": [ + { + "name": "role", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleAdminChanged", + "kind": "struct", + "members": [ + { + "name": "role", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "previous_admin_role", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "new_admin_role", + "type": "core::felt252", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::Event", + "kind": "enum", + "variants": [ + { + "name": "RoleGranted", + "type": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleGranted", + "kind": "nested" + }, + { + "name": "RoleGrantedWithDelay", + "type": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleGrantedWithDelay", + "kind": "nested" + }, + { + "name": "RoleRevoked", + "type": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleRevoked", + "kind": "nested" + }, + { + "name": "RoleAdminChanged", + "type": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleAdminChanged", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_upgrades::upgradeable::UpgradeableComponent::Upgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_upgrades::upgradeable::UpgradeableComponent::Event", + "kind": "enum", + "variants": [ + { + "name": "Upgraded", + "type": "openzeppelin_upgrades::upgradeable::UpgradeableComponent::Upgraded", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_security::pausable::PausableComponent::Paused", + "kind": "struct", + "members": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_security::pausable::PausableComponent::Unpaused", + "kind": "struct", + "members": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_security::pausable::PausableComponent::Event", + "kind": "enum", + "variants": [ + { + "name": "Paused", + "type": "openzeppelin_security::pausable::PausableComponent::Paused", + "kind": "nested" + }, + { + "name": "Unpaused", + "type": "openzeppelin_security::pausable::PausableComponent::Unpaused", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "vault::vault::vault::Vault::RedeemRequested", + "kind": "struct", + "members": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "shares", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "assets", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "id", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "epoch", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "vault::vault::vault::Vault::RedeemClaimed", + "kind": "struct", + "members": [ + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "redeem_request_nominal", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "assets", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "id", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "epoch", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "vault::vault::vault::Vault::Report", + "kind": "struct", + "members": [ + { + "name": "new_epoch", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "new_handled_epoch_len", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "total_supply", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "total_assets", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "management_fee_shares", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "performance_fee_shares", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "vault::vault::vault::Vault::Event", + "kind": "enum", + "variants": [ + { + "name": "ERC20Event", + "type": "openzeppelin_token::erc20::erc20::ERC20Component::Event", + "kind": "nested" + }, + { + "name": "ERC4626Event", + "type": "openzeppelin_token::erc20::extensions::erc4626::erc4626::ERC4626Component::Event", + "kind": "nested" + }, + { + "name": "SRC5Event", + "type": "openzeppelin_introspection::src5::SRC5Component::Event", + "kind": "nested" + }, + { + "name": "AccessControlEvent", + "type": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::Event", + "kind": "nested" + }, + { + "name": "UpgradeableEvent", + "type": "openzeppelin_upgrades::upgradeable::UpgradeableComponent::Event", + "kind": "nested" + }, + { + "name": "PausableEvent", + "type": "openzeppelin_security::pausable::PausableComponent::Event", + "kind": "nested" + }, + { + "name": "RedeemRequested", + "type": "vault::vault::vault::Vault::RedeemRequested", + "kind": "nested" + }, + { + "name": "RedeemClaimed", + "type": "vault::vault::vault::Vault::RedeemClaimed", + "kind": "nested" + }, + { + "name": "Report", + "type": "vault::vault::vault::Vault::Report", + "kind": "nested" + } + ] + } +] \ No newline at end of file diff --git a/backend/libs/starknet/src/index.ts b/backend/libs/starknet/src/index.ts new file mode 100644 index 00000000..dab36561 --- /dev/null +++ b/backend/libs/starknet/src/index.ts @@ -0,0 +1,2 @@ +export * from './starknet.module'; +export * from './starknet.service'; \ No newline at end of file diff --git a/backend/libs/starknet/src/starknet.module.ts b/backend/libs/starknet/src/starknet.module.ts new file mode 100644 index 00000000..87c6a58b --- /dev/null +++ b/backend/libs/starknet/src/starknet.module.ts @@ -0,0 +1,10 @@ +import { Module } from "@nestjs/common"; +import { StarknetService } from "./starknet.service"; +import { ConfigModule } from "@forge/config"; + +@Module({ + imports: [ConfigModule], + providers: [StarknetService], + exports: [StarknetService], +}) +export class StarknetModule {} \ No newline at end of file diff --git a/backend/libs/starknet/src/starknet.service.ts b/backend/libs/starknet/src/starknet.service.ts new file mode 100644 index 00000000..01d97643 --- /dev/null +++ b/backend/libs/starknet/src/starknet.service.ts @@ -0,0 +1,328 @@ +import { Injectable, OnModuleInit } from '@nestjs/common'; +import { ConfigService } from '@forge/config'; +import { Provider, RpcProvider, Contract, Call, uint256, Account } from 'starknet'; +import { Logger } from '@forge/logger'; +import * as VAULT_ABI from './abis/vault.json'; + +@Injectable() +export class StarknetService implements OnModuleInit { + private provider: Provider; + private readonly logger = Logger.create('Starknet:Service'); + + constructor(private configService: ConfigService) {} + + async onModuleInit() { + try { + const rpcUrl: string = + (this.configService.get('RPC_URL') as string) || 'https://starknet-mainnet.public.blastapi.io'; + + this.logger.info('Initializing StarkNet provider', { rpcUrl }); + this.provider = new RpcProvider({ nodeUrl: rpcUrl }); + this.logger.info('StarkNet provider initialized successfully'); + } catch (error) { + this.logger.error('Failed to initialize StarkNet provider', error); + throw error; + } + } + + getSigner() { + return new Account( + this.provider, + this.configService.get(`RELAYER_ADDRESS`) as string, + this.configService.get(`RELAYER_PRIVATE_KEY`) as string, + undefined, + '0x3' + ); + } + + async view(address: string, abi: any, functionName: string, calldata?: any[]) { + try { + this.logger.debug('Making contract view call', { + address, + functionName, + calldataLength: calldata?.length || 0, + }); + + const abiArray = Array.isArray(abi) ? abi : Object.values(abi); + const contract = new Contract(abiArray, address, this.provider); + + const result = calldata ? await contract.call(functionName, calldata) : await contract.call(functionName); + + this.logger.debug('Contract view call successful', { + address, + functionName, + resultType: typeof result, + }); + + return result; + } catch (error) { + this.logger.error('Contract view call failed', error, { + address, + functionName, + calldata, + }); + throw error; + } + } + + async vault_due_assets_from_id(address: string, id: number): Promise { + try { + this.logger.debug('Fetching due assets from vault', { address, id }); + + const due_assets = (await this.view(address, VAULT_ABI, 'due_assets_from_id', [id])) as any; + + this.logger.debug('Successfully fetched due assets', { + address, + id, + dueAssets: due_assets?.toString(), + }); + + return due_assets; + } catch (error) { + this.logger.error('Failed to fetch due assets from vault', error, { + address, + id, + }); + throw error; + } + } + + async vault_decimals(address: string): Promise { + try { + this.logger.debug('Fetching vault decimals', { address }); + + const decimals = (await this.provider.callContract({ + contractAddress: address, + entrypoint: 'decimals', + })) as any; + + this.logger.debug('Successfully fetched vault decimals', { + address, + decimals: decimals?.toString(), + }); + + return decimals; + } catch (error) { + this.logger.error('Failed to fetch vault decimals', error, { address }); + throw error; + } + } + + async vault_buffer(address: string): Promise { + try { + this.logger.debug('Fetching vault buffer', { address }); + + const buffer = (await this.provider.callContract({ + contractAddress: address, + entrypoint: 'buffer', + })) as any; + + this.logger.debug('Successfully fetched vault buffer', { + address, + buffer: buffer?.toString(), + }); + + return buffer; + } catch (error) { + this.logger.error('Failed to fetch vault buffer', error, { address }); + throw error; + } + } + + async vault_epoch(address: string): Promise { + try { + this.logger.debug('Fetching vault epoch', { address }); + + const epoch = (await this.provider.callContract({ + contractAddress: address, + entrypoint: 'epoch', + })) as any; + + this.logger.debug('Successfully fetched vault epoch', { + address, + epoch: epoch?.toString(), + }); + + return epoch; + } catch (error) { + this.logger.error('Failed to fetch vault epoch', error, { address }); + throw error; + } + } + + async vault_handled_epoch_len(address: string): Promise { + try { + this.logger.debug('Fetching vault handled epoch length', { address }); + + const handledEpochLen = (await this.provider.callContract({ + contractAddress: address, + entrypoint: 'handled_epoch_len', + })) as any; + + this.logger.debug('Successfully fetched vault handled epoch length', { + address, + handledEpochLen: handledEpochLen?.toString(), + }); + + return handledEpochLen; + } catch (error) { + this.logger.error('Failed to fetch vault handled epoch length', error, { + address, + }); + throw error; + } + } + + async vault_redeem_assets(address: string, epoch: number): Promise { + try { + this.logger.debug('Fetching vault redeem assets', { address, epoch }); + + const redeemAssets = (await this.provider.callContract({ + contractAddress: address, + entrypoint: 'redeem_assets', + calldata: [epoch], + })) as any; + + this.logger.debug('Successfully fetched vault redeem assets', { + address, + epoch, + redeemAssets: redeemAssets?.toString(), + }); + + return redeemAssets; + } catch (error) { + this.logger.error('Failed to fetch vault redeem assets', error, { + address, + epoch, + }); + throw error; + } + } + + async vault_claim_redeem(address: string, redeemIds: bigint[]) { + const functionName = 'claim_redeem'; + + const calls: Array = redeemIds.map((redeemId) => { + const redeemIdUint256 = uint256.bnToUint256(redeemId); + return { + contractAddress: address, + entrypoint: functionName, + calldata: [redeemIdUint256.low, redeemIdUint256.high], + }; + }); + + try { + const { transaction_hash } = await this.getSigner().execute(calls); + const txReceipt = await this.provider.waitForTransaction(transaction_hash); + if (txReceipt.isSuccess()) { + return transaction_hash; + } else { + throw new Error( + `tx failed, hash: ${transaction_hash}, status: ${txReceipt.isSuccess() ? 'SUCCESS' : 'FAILED'}` + ); + } + } catch (error) { + throw error; + } + } + + async getCurrentBlock(): Promise { + try { + const block = await this.provider.getBlock('latest'); + this.logger.debug('Fetched current block', { + blockNumber: block.block_number, + }); + return block.block_number; + } catch (error) { + this.logger.error('Failed to fetch current block', error); + throw error; + } + } + + async getCurrentBlockTimestamp(): Promise { + try { + const block = await this.provider.getBlock('latest'); + this.logger.debug('Fetched current block timestamp', { + blockNumber: block.timestamp, + }); + return block.timestamp; + } catch (error) { + this.logger.error('Failed to fetch current block timestamp', error); + throw error; + } + } + + async getLastReportTimestamp(vaultAddress: string): Promise { + try { + const result = await this.view(vaultAddress, VAULT_ABI, 'last_report_timestamp'); + this.logger.debug('Fetched last report timestamp', { + vaultAddress, + timestamp: result.toString(), + }); + return result as bigint; + } catch (error) { + this.logger.error('Failed to get last report timestamp', error, { + vaultAddress, + }); + throw error; + } + } + + async getReportDelay(vaultAddress: string): Promise { + try { + const result = await this.view(vaultAddress, VAULT_ABI, 'report_delay'); + this.logger.debug('Fetched report delay', { + vaultAddress, + delay: result.toString(), + }); + return result as bigint; + } catch (error) { + this.logger.error('Failed to get report delay', error, { + vaultAddress, + }); + throw error; + } + } + + async triggerAumProviderReport(aumProviderAddress: string): Promise { + try { + this.logger.info('Triggering AUM provider report', { + aumProviderAddress, + }); + + const calls = [ + { + contractAddress: aumProviderAddress, + entrypoint: 'report', + calldata: [], + }, + ]; + + const signer = this.getSigner(); + const { transaction_hash } = await signer.execute(calls); + + this.logger.info('Report transaction submitted', { + transactionHash: transaction_hash, + aumProviderAddress, + }); + + const txReceipt = await this.provider.waitForTransaction(transaction_hash); + + if (txReceipt.isSuccess()) { + this.logger.info('Report transaction successful', { + transactionHash: transaction_hash, + }); + return transaction_hash; + } else { + throw new Error( + `Report transaction failed: ${transaction_hash}, status: ${txReceipt.isSuccess() ? 'SUCCESS' : 'FAILED'}` + ); + } + } catch (error) { + this.logger.error('Failed to trigger AUM provider report', error, { + aumProviderAddress, + }); + throw error; + } + } +} diff --git a/backend/libs/starknet/tsconfig.json b/backend/libs/starknet/tsconfig.json new file mode 100644 index 00000000..997d1e4f --- /dev/null +++ b/backend/libs/starknet/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "composite": false + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"] +} \ No newline at end of file diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 00000000..84f91515 --- /dev/null +++ b/backend/package.json @@ -0,0 +1,71 @@ +{ + "name": "@forge/root", + "version": "0.0.1", + "description": "StarkNet Vault Kit Backend Monorepo", + "author": "", + "private": true, + "license": "UNLICENSED", + "workspaces": [ + "apps/*", + "libs/*" + ], + "scripts": { + "dev:api": "pnpm --filter api dev", + "dev:indexer": "pnpm --filter indexer dev", + "dev:relayerAutomaticRedeem": "pnpm --filter relayerAutomaticRedeem dev", + "dev:relayerOnChainAum": "pnpm --filter relayerOnChainAum dev", + "dev:all": "concurrently \"pnpm dev:api\" \"pnpm dev:indexer\" \"pnpm dev:relayerAutomaticRedeem\" \"pnpm dev:relayerOnChainAum\"", + "start:api": "pnpm --filter api start", + "start:indexer": "pnpm --filter indexer start", + "start:relayerAutomaticRedeem": "pnpm --filter relayerAutomaticRedeem start", + "start:relayerOnChainAum": "pnpm --filter relayerOnChainAum start", + "build": "pnpm --recursive --filter='!@forge/root' run build", + "build:api": "pnpm --filter api build", + "build:indexer": "pnpm --filter indexer build", + "build:relayerAutomaticRedeem": "pnpm --filter relayerAutomaticRedeem build", + "build:relayerOnChainAum": "pnpm --filter relayerOnChainAum build", + "lint": "pnpm --recursive --filter='!@forge/root' run lint", + "test": "pnpm --recursive --filter='!@forge/root' run test", + "prisma:generate": "pnpm --filter @forge/db prisma:generate", + "prisma:migrate:dev": "pnpm --filter @forge/db prisma:migrate:dev", + "prisma:migrate:deploy": "pnpm --filter @forge/db prisma:migrate:deploy", + "prisma:studio": "pnpm --filter @forge/db prisma:studio" + }, + "devDependencies": { + "@nestjs/cli": "^10.0.0", + "@nestjs/schematics": "^10.0.0", + "@nestjs/testing": "^10.0.0", + "@types/express": "^4.17.17", + "@types/jest": "^29.5.2", + "@types/node": "^20.3.1", + "@typescript-eslint/eslint-plugin": "^8.0.0", + "@typescript-eslint/parser": "^8.0.0", + "concurrently": "^8.0.0", + "eslint": "^8.42.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.0", + "jest": "^29.5.0", + "prettier": "^3.0.0", + "prisma": "6.15.0", + "source-map-support": "^0.5.21", + "supertest": "^7.0.0", + "ts-jest": "^29.1.0", + "ts-loader": "^9.4.3", + "ts-node": "^10.9.1", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.1.3" + }, + "dependencies": { + "@nestjs/axios": "^3.1.3", + "@nestjs/common": "^10.0.0", + "@nestjs/config": "^3.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "@nestjs/swagger": "^7.4.2", + "@nestjs/terminus": "^10.0.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0", + "reflect-metadata": "^0.2.2", + "rxjs": "^7.8.2" + } +} \ No newline at end of file diff --git a/backend/pnpm-lock.yaml b/backend/pnpm-lock.yaml new file mode 100644 index 00000000..b6215b70 --- /dev/null +++ b/backend/pnpm-lock.yaml @@ -0,0 +1,7282 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@nestjs/axios': + specifier: ^3.1.3 + version: 3.1.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2) + '@nestjs/common': + specifier: ^10.0.0 + version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/config': + specifier: ^3.0.0 + version: 3.3.0(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2) + '@nestjs/core': + specifier: ^10.0.0 + version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/platform-express': + specifier: ^10.0.0 + version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20) + '@nestjs/swagger': + specifier: ^7.4.2 + version: 7.4.2(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2) + '@nestjs/terminus': + specifier: ^10.0.0 + version: 10.3.0(@grpc/grpc-js@1.13.4)(@grpc/proto-loader@0.7.15)(@nestjs/axios@3.1.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2))(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@prisma/client@6.15.0(prisma@6.15.0(typescript@5.9.2))(typescript@5.9.2))(reflect-metadata@0.2.2)(rxjs@7.8.2) + class-transformer: + specifier: ^0.5.1 + version: 0.5.1 + class-validator: + specifier: ^0.14.0 + version: 0.14.2 + reflect-metadata: + specifier: ^0.2.2 + version: 0.2.2 + rxjs: + specifier: ^7.8.2 + version: 7.8.2 + devDependencies: + '@nestjs/cli': + specifier: ^10.0.0 + version: 10.4.9 + '@nestjs/schematics': + specifier: ^10.0.0 + version: 10.2.3(chokidar@3.6.0)(typescript@5.9.2) + '@nestjs/testing': + specifier: ^10.0.0 + version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20)) + '@types/express': + specifier: ^4.17.17 + version: 4.17.23 + '@types/jest': + specifier: ^29.5.2 + version: 29.5.14 + '@types/node': + specifier: ^20.3.1 + version: 20.19.12 + '@typescript-eslint/eslint-plugin': + specifier: ^8.0.0 + version: 8.42.0(@typescript-eslint/parser@8.42.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/parser': + specifier: ^8.0.0 + version: 8.42.0(eslint@8.57.1)(typescript@5.9.2) + concurrently: + specifier: ^8.0.0 + version: 8.2.2 + eslint: + specifier: ^8.42.0 + version: 8.57.1 + eslint-config-prettier: + specifier: ^9.0.0 + version: 9.1.2(eslint@8.57.1) + eslint-plugin-prettier: + specifier: ^5.0.0 + version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.6.2) + jest: + specifier: ^29.5.0 + version: 29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)) + prettier: + specifier: ^3.0.0 + version: 3.6.2 + prisma: + specifier: 6.15.0 + version: 6.15.0(typescript@5.9.2) + source-map-support: + specifier: ^0.5.21 + version: 0.5.21 + supertest: + specifier: ^7.0.0 + version: 7.1.4 + ts-jest: + specifier: ^29.1.0 + version: 29.4.1(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.3))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)))(typescript@5.9.2) + ts-loader: + specifier: ^9.4.3 + version: 9.5.4(typescript@5.9.2)(webpack@5.97.1) + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@types/node@20.19.12)(typescript@5.9.2) + tsconfig-paths: + specifier: ^4.2.0 + version: 4.2.0 + typescript: + specifier: ^5.1.3 + version: 5.9.2 + + apps/api: + dependencies: + '@forge/config': + specifier: workspace:* + version: link:../../libs/config + '@forge/db': + specifier: workspace:* + version: link:../../libs/db + '@forge/logger': + specifier: workspace:* + version: link:../../libs/logger + '@forge/starknet': + specifier: workspace:* + version: link:../../libs/starknet + '@nestjs/axios': + specifier: ^3.1.3 + version: 3.1.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2) + '@nestjs/common': + specifier: ^10.0.0 + version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': + specifier: ^10.4.20 + version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/platform-express': + specifier: ^10.0.0 + version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20) + '@nestjs/swagger': + specifier: ^7.4.2 + version: 7.4.2(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2) + '@nestjs/terminus': + specifier: ^11.0.0 + version: 11.0.0(@grpc/grpc-js@1.13.4)(@grpc/proto-loader@0.7.15)(@nestjs/axios@3.1.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2))(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@prisma/client@6.15.0(prisma@6.15.0(typescript@5.9.2))(typescript@5.9.2))(reflect-metadata@0.2.2)(rxjs@7.8.2) + decimal.js: + specifier: ^10.6.0 + version: 10.6.0 + starknet: + specifier: ^7.6.4 + version: 7.6.4 + + apps/indexer: + dependencies: + '@apibara/protocol': + specifier: ^0.4.9 + version: 0.4.9 + '@apibara/starknet': + specifier: ^0.5.0 + version: 0.5.0 + '@forge/config': + specifier: workspace:* + version: link:../../libs/config + '@forge/db': + specifier: workspace:* + version: link:../../libs/db + '@forge/logger': + specifier: workspace:* + version: link:../../libs/logger + '@nestjs/common': + specifier: ^10.0.0 + version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': + specifier: ^10.0.0 + version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2) + starknet: + specifier: ^7.6.4 + version: 7.6.4 + devDependencies: + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@types/node@20.19.12)(typescript@5.9.2) + + apps/relayerAutomaticRedeem: + dependencies: + '@forge/config': + specifier: workspace:* + version: link:../../libs/config + '@forge/db': + specifier: workspace:* + version: link:../../libs/db + '@forge/logger': + specifier: workspace:* + version: link:../../libs/logger + '@forge/starknet': + specifier: workspace:* + version: link:../../libs/starknet + '@nestjs/common': + specifier: ^10.0.0 + version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': + specifier: ^10.0.0 + version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2) + cron: + specifier: ^4.3.3 + version: 4.3.3 + devDependencies: + '@types/cron': + specifier: ^2.0.0 + version: 2.4.3 + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@types/node@20.19.12)(typescript@5.9.2) + + apps/relayerOnChainAum: + dependencies: + '@forge/config': + specifier: workspace:* + version: link:../../libs/config + '@forge/logger': + specifier: workspace:* + version: link:../../libs/logger + '@forge/starknet': + specifier: workspace:* + version: link:../../libs/starknet + '@nestjs/common': + specifier: ^10.0.0 + version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/config': + specifier: ^4.0.0 + version: 4.0.2(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2) + '@nestjs/core': + specifier: ^10.0.0 + version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2) + + libs/config: + dependencies: + '@nestjs/common': + specifier: ^10.0.0 + version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/config': + specifier: ^3.0.0 + version: 3.3.0(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2) + class-transformer: + specifier: ^0.5.1 + version: 0.5.1 + class-validator: + specifier: ^0.14.0 + version: 0.14.2 + + libs/db: + dependencies: + '@nestjs/common': + specifier: ^10.0.0 + version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@prisma/client': + specifier: ^6.15.0 + version: 6.15.0(prisma@6.15.0(typescript@5.9.2))(typescript@5.9.2) + prisma: + specifier: 6.15.0 + version: 6.15.0(typescript@5.9.2) + + libs/logger: + dependencies: + winston: + specifier: ^3.11.0 + version: 3.17.0 + winston-daily-rotate-file: + specifier: ^4.7.1 + version: 4.7.1(winston@3.17.0) + + libs/starknet: + dependencies: + '@forge/config': + specifier: workspace:* + version: link:../config + '@forge/logger': + specifier: workspace:* + version: link:../logger + '@nestjs/common': + specifier: ^10.0.0 + version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + starknet: + specifier: ^7.6.4 + version: 7.6.4 + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@angular-devkit/core@17.3.11': + resolution: {integrity: sha512-vTNDYNsLIWpYk2I969LMQFH29GTsLzxNk/0cLw5q56ARF0v5sIWfHYwGTS88jdDqIpuuettcSczbxeA7EuAmqQ==} + engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + chokidar: ^3.5.2 + peerDependenciesMeta: + chokidar: + optional: true + + '@angular-devkit/schematics-cli@17.3.11': + resolution: {integrity: sha512-kcOMqp+PHAKkqRad7Zd7PbpqJ0LqLaNZdY1+k66lLWmkEBozgq8v4ASn/puPWf9Bo0HpCiK+EzLf0VHE8Z/y6Q==} + engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + hasBin: true + + '@angular-devkit/schematics@17.3.11': + resolution: {integrity: sha512-I5wviiIqiFwar9Pdk30Lujk8FczEEc18i22A5c6Z9lbmhPQdTroDnEQdsfXjy404wPe8H62s0I15o4pmMGfTYQ==} + engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + + '@apibara/protocol@0.4.9': + resolution: {integrity: sha512-RTpZ9u8zmDHbgMYDO47lnJlGxUrfCjKUGZ10SmicoN7OkNyAWmVGrwXZYfQJMw81bSObet7vU76iQ1HznyiySA==} + + '@apibara/starknet@0.5.0': + resolution: {integrity: sha512-xzpXMlwdDeBb7IpY9CYKs7uBDz1uz9gb8rR3QgWVx7xipB/fQY6InHldyX5zfLIuBVblZOlenviNrBXOd7Wlsw==} + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.0': + resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.3': + resolution: {integrity: sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.3': + resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.3': + resolution: {integrity: sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.3': + resolution: {integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.27.1': + resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.27.1': + resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.27.1': + resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.28.3': + resolution: {integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.3': + resolution: {integrity: sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.2': + resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@borewit/text-codec@0.1.1': + resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==} + + '@colors/colors@1.5.0': + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + + '@colors/colors@1.6.0': + resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} + engines: {node: '>=0.1.90'} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@dabh/diagnostics@2.0.3': + resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} + + '@eslint-community/eslint-utils@4.8.0': + resolution: {integrity: sha512-MJQFqrZgcW0UNYLGOuQpey/oTN59vyWwplvCGZztn1cKz9agZPPYpJB7h2OMmuu7VLqkvEjN8feFZJmxNF9D+Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@grpc/grpc-js@1.13.4': + resolution: {integrity: sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==} + engines: {node: '>=12.10.0'} + + '@grpc/proto-loader@0.7.15': + resolution: {integrity: sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==} + engines: {node: '>=6'} + hasBin: true + + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + '@jest/console@29.7.0': + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/core@29.7.0': + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/environment@29.7.0': + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect-utils@29.7.0': + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect@29.7.0': + resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/fake-timers@29.7.0': + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/globals@29.7.0': + resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/reporters@29.7.0': + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/source-map@29.6.3': + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-result@29.7.0': + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-sequencer@29.7.0': + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/transform@29.7.0': + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.30': + resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@js-sdsl/ordered-map@4.4.2': + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + + '@ljharb/through@2.3.14': + resolution: {integrity: sha512-ajBvlKpWucBB17FuQYUShqpqy8GRgYEpJW0vWJbUu1CV9lWyrDCapy0lScU8T8Z6qn49sSwJB3+M+evYIdGg+A==} + engines: {node: '>= 0.4'} + + '@lukeed/csprng@1.1.0': + resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} + engines: {node: '>=8'} + + '@microsoft/tsdoc@0.15.1': + resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==} + + '@nestjs/axios@3.1.3': + resolution: {integrity: sha512-RZ/63c1tMxGLqyG3iOCVt7A72oy4x1eM6QEhd4KzCYpaVWW0igq0WSREeRoEZhIxRcZfDfIIkvsOMiM7yfVGZQ==} + peerDependencies: + '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + axios: ^1.3.1 + rxjs: ^6.0.0 || ^7.0.0 + + '@nestjs/cli@10.4.9': + resolution: {integrity: sha512-s8qYd97bggqeK7Op3iD49X2MpFtW4LVNLAwXFkfbRxKME6IYT7X0muNTJ2+QfI8hpbNx9isWkrLWIp+g5FOhiA==} + engines: {node: '>= 16.14'} + hasBin: true + peerDependencies: + '@swc/cli': ^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0 + '@swc/core': ^1.3.62 + peerDependenciesMeta: + '@swc/cli': + optional: true + '@swc/core': + optional: true + + '@nestjs/common@10.4.20': + resolution: {integrity: sha512-hxJxZF7jcKGuUzM9EYbuES80Z/36piJbiqmPy86mk8qOn5gglFebBTvcx7PWVbRNSb4gngASYnefBj/Y2HAzpQ==} + peerDependencies: + class-transformer: '*' + class-validator: '*' + reflect-metadata: ^0.1.12 || ^0.2.0 + rxjs: ^7.1.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true + + '@nestjs/config@3.3.0': + resolution: {integrity: sha512-pdGTp8m9d0ZCrjTpjkUbZx6gyf2IKf+7zlkrPNMsJzYZ4bFRRTpXrnj+556/5uiI6AfL5mMrJc2u7dB6bvM+VA==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + rxjs: ^7.1.0 + + '@nestjs/config@4.0.2': + resolution: {integrity: sha512-McMW6EXtpc8+CwTUwFdg6h7dYcBUpH5iUILCclAsa+MbCEvC9ZKu4dCHRlJqALuhjLw97pbQu62l4+wRwGeZqA==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + rxjs: ^7.1.0 + + '@nestjs/core@10.4.20': + resolution: {integrity: sha512-kRdtyKA3+Tu70N3RQ4JgmO1E3LzAMs/eppj7SfjabC7TgqNWoS4RLhWl4BqmsNVmjj6D5jgfPVtHtgYkU3AfpQ==} + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/microservices': ^10.0.0 + '@nestjs/platform-express': ^10.0.0 + '@nestjs/websockets': ^10.0.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + rxjs: ^7.1.0 + peerDependenciesMeta: + '@nestjs/microservices': + optional: true + '@nestjs/platform-express': + optional: true + '@nestjs/websockets': + optional: true + + '@nestjs/mapped-types@2.0.5': + resolution: {integrity: sha512-bSJv4pd6EY99NX9CjBIyn4TVDoSit82DUZlL4I3bqNfy5Gt+gXTa86i3I/i0iIV9P4hntcGM5GyO+FhZAhxtyg==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + class-transformer: ^0.4.0 || ^0.5.0 + class-validator: ^0.13.0 || ^0.14.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true + + '@nestjs/platform-express@10.4.20': + resolution: {integrity: sha512-rh97mX3rimyf4xLMLHuTOBKe6UD8LOJ14VlJ1F/PTd6C6ZK9Ak6EHuJvdaGcSFQhd3ZMBh3I6CuujKGW9pNdIg==} + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/core': ^10.0.0 + + '@nestjs/schematics@10.2.3': + resolution: {integrity: sha512-4e8gxaCk7DhBxVUly2PjYL4xC2ifDFexCqq1/u4TtivLGXotVk0wHdYuPYe1tHTHuR1lsOkRbfOCpkdTnigLVg==} + peerDependencies: + typescript: '>=4.8.2' + + '@nestjs/swagger@7.4.2': + resolution: {integrity: sha512-Mu6TEn1M/owIvAx2B4DUQObQXqo2028R2s9rSZ/hJEgBK95+doTwS0DjmVA2wTeZTyVtXOoN7CsoM5pONBzvKQ==} + peerDependencies: + '@fastify/static': ^6.0.0 || ^7.0.0 + '@nestjs/common': ^9.0.0 || ^10.0.0 + '@nestjs/core': ^9.0.0 || ^10.0.0 + class-transformer: '*' + class-validator: '*' + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + '@fastify/static': + optional: true + class-transformer: + optional: true + class-validator: + optional: true + + '@nestjs/terminus@10.3.0': + resolution: {integrity: sha512-vOJGCwt1OgrFuuxWQwPoaHqy9m9CfIk2qMUX2mosZLK5dFVJSEjHXrklkh3/Fw9PiUnfzvYFfiAdJRzUaxx+5Q==} + peerDependencies: + '@grpc/grpc-js': '*' + '@grpc/proto-loader': '*' + '@mikro-orm/core': '*' + '@mikro-orm/nestjs': '*' + '@nestjs/axios': ^1.0.0 || ^2.0.0 || ^3.0.0 + '@nestjs/common': ^9.0.0 || ^10.0.0 + '@nestjs/core': ^9.0.0 || ^10.0.0 + '@nestjs/microservices': ^9.0.0 || ^10.0.0 + '@nestjs/mongoose': ^9.0.0 || ^10.0.0 + '@nestjs/sequelize': ^9.0.0 || ^10.0.0 + '@nestjs/typeorm': ^9.0.0 || ^10.0.0 + '@prisma/client': '*' + mongoose: '*' + reflect-metadata: 0.1.x || 0.2.x + rxjs: 7.x + sequelize: '*' + typeorm: '*' + peerDependenciesMeta: + '@grpc/grpc-js': + optional: true + '@grpc/proto-loader': + optional: true + '@mikro-orm/core': + optional: true + '@mikro-orm/nestjs': + optional: true + '@nestjs/axios': + optional: true + '@nestjs/microservices': + optional: true + '@nestjs/mongoose': + optional: true + '@nestjs/sequelize': + optional: true + '@nestjs/typeorm': + optional: true + '@prisma/client': + optional: true + mongoose: + optional: true + sequelize: + optional: true + typeorm: + optional: true + + '@nestjs/terminus@11.0.0': + resolution: {integrity: sha512-c55LOo9YGovmQHtFUMa/vDaxGZ2cglMTZejqgHREaApt/GArTfgYYGwhRXPLq8ZwiQQlLuYB+79e9iA8mlDSLA==} + peerDependencies: + '@grpc/grpc-js': '*' + '@grpc/proto-loader': '*' + '@mikro-orm/core': '*' + '@mikro-orm/nestjs': '*' + '@nestjs/axios': ^2.0.0 || ^3.0.0 || ^4.0.0 + '@nestjs/common': ^10.0.0 || ^11.0.0 + '@nestjs/core': ^10.0.0 || ^11.0.0 + '@nestjs/microservices': ^10.0.0 || ^11.0.0 + '@nestjs/mongoose': ^11.0.0 + '@nestjs/sequelize': ^10.0.0 || ^11.0.0 + '@nestjs/typeorm': ^10.0.0 || ^11.0.0 + '@prisma/client': '*' + mongoose: '*' + reflect-metadata: 0.1.x || 0.2.x + rxjs: 7.x + sequelize: '*' + typeorm: '*' + peerDependenciesMeta: + '@grpc/grpc-js': + optional: true + '@grpc/proto-loader': + optional: true + '@mikro-orm/core': + optional: true + '@mikro-orm/nestjs': + optional: true + '@nestjs/axios': + optional: true + '@nestjs/microservices': + optional: true + '@nestjs/mongoose': + optional: true + '@nestjs/sequelize': + optional: true + '@nestjs/typeorm': + optional: true + '@prisma/client': + optional: true + mongoose: + optional: true + sequelize: + optional: true + typeorm: + optional: true + + '@nestjs/testing@10.4.20': + resolution: {integrity: sha512-nMkRDukDKskdPruM6EsgMq7yJua+CPZM6I6FrLP8yXw8BiVSPv9Nm0CtcGGwt3kgZF9hfxKjGqLjsvVBsv6Vfw==} + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/core': ^10.0.0 + '@nestjs/microservices': ^10.0.0 + '@nestjs/platform-express': ^10.0.0 + peerDependenciesMeta: + '@nestjs/microservices': + optional: true + '@nestjs/platform-express': + optional: true + + '@noble/curves@1.7.0': + resolution: {integrity: sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.6.0': + resolution: {integrity: sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@nuxtjs/opencollective@0.3.2': + resolution: {integrity: sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true + + '@paralleldrive/cuid2@2.2.2': + resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@pkgr/core@0.2.9': + resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@prisma/client@6.15.0': + resolution: {integrity: sha512-wR2LXUbOH4cL/WToatI/Y2c7uzni76oNFND7+23ypLllBmIS8e3ZHhO+nud9iXSXKFt1SoM3fTZvHawg63emZw==} + engines: {node: '>=18.18'} + peerDependencies: + prisma: '*' + typescript: '>=5.1.0' + peerDependenciesMeta: + prisma: + optional: true + typescript: + optional: true + + '@prisma/config@6.15.0': + resolution: {integrity: sha512-KMEoec9b2u6zX0EbSEx/dRpx1oNLjqJEBZYyK0S3TTIbZ7GEGoVyGyFRk4C72+A38cuPLbfQGQvgOD+gBErKlA==} + + '@prisma/debug@6.15.0': + resolution: {integrity: sha512-y7cSeLuQmyt+A3hstAs6tsuAiVXSnw9T55ra77z0nbNkA8Lcq9rNcQg6PI00by/+WnE/aMRJ/W7sZWn2cgIy1g==} + + '@prisma/engines-version@6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb': + resolution: {integrity: sha512-a/46aK5j6L3ePwilZYEgYDPrhBQ/n4gYjLxT5YncUTJJNRnTCVjPF86QdzUOLRdYjCLfhtZp9aum90W0J+trrg==} + + '@prisma/engines@6.15.0': + resolution: {integrity: sha512-opITiR5ddFJ1N2iqa7mkRlohCZqVSsHhRcc29QXeldMljOf4FSellLT0J5goVb64EzRTKcIDeIsJBgmilNcKxA==} + + '@prisma/fetch-engine@6.15.0': + resolution: {integrity: sha512-xcT5f6b+OWBq6vTUnRCc7qL+Im570CtwvgSj+0MTSGA1o9UDSKZ/WANvwtiRXdbYWECpyC3CukoG3A04VTAPHw==} + + '@prisma/get-platform@6.15.0': + resolution: {integrity: sha512-Jbb+Xbxyp05NSR1x2epabetHiXvpO8tdN2YNoWoA/ZsbYyxxu/CO/ROBauIFuMXs3Ti+W7N7SJtWsHGaWte9Rg==} + + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.4': + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + + '@protobufjs/eventemitter@1.1.0': + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + + '@protobufjs/fetch@1.1.0': + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.0': + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.0': + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + + '@scure/base@1.2.1': + resolution: {integrity: sha512-DGmGtC8Tt63J5GfHgfl5CuAXh96VF/LD8K9Hr/Gv0J2lAoRGlPOMpqMpMbCTOoOJMZCk2Xt+DskdDyn6dEFdzQ==} + + '@scure/starknet@1.1.0': + resolution: {integrity: sha512-83g3M6Ix2qRsPN4wqLDqiRZ2GBNbjVWfboJE/9UjfG+MHr6oDSu/CWgy8hsBSJejr09DkkL+l0Ze4KVrlCIdtQ==} + + '@sinclair/typebox@0.27.8': + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@10.3.0': + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + + '@starknet-io/types-js@0.7.10': + resolution: {integrity: sha512-1VtCqX4AHWJlRRSYGSn+4X1mqolI1Tdq62IwzoU2vUuEE72S1OlEeGhpvd6XsdqXcfHmVzYfj8k1XtKBQqwo9w==} + + '@starknet-io/types-js@0.8.4': + resolution: {integrity: sha512-0RZ3TZHcLsUTQaq1JhDSCM8chnzO4/XNsSCozwDET64JK5bjFDIf2ZUkta+tl5Nlbf4usoU7uZiDI/Q57kt2SQ==} + + '@tokenizer/inflate@0.2.7': + resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==} + engines: {node: '>=18'} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/cron@2.4.3': + resolution: {integrity: sha512-ViRBkoZD9Rk0hGeMdd2GHGaOaZuH9mDmwsE5/Zo53Ftwcvh7h9VJc8lIt2wdgEwS4EW5lbtTX6vlE0idCLPOyA==} + deprecated: This is a stub types definition. cron provides its own type definitions, so you do not need this installed. + + '@types/eslint-scope@3.7.7': + resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} + + '@types/eslint@9.6.1': + resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/express-serve-static-core@4.19.6': + resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} + + '@types/express@4.17.23': + resolution: {integrity: sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==} + + '@types/graceful-fs@4.1.9': + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/jest@29.5.14': + resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/luxon@3.7.1': + resolution: {integrity: sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==} + + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + + '@types/node@20.19.12': + resolution: {integrity: sha512-lSOjyS6vdO2G2g2CWrETTV3Jz2zlCXHpu1rcubLKpz9oj+z/1CceHlj+yq53W+9zgb98nSov/wjEKYDNauD+Hw==} + + '@types/qs@6.14.0': + resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/send@0.17.5': + resolution: {integrity: sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==} + + '@types/serve-static@1.15.8': + resolution: {integrity: sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/triple-beam@1.3.5': + resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} + + '@types/validator@13.15.3': + resolution: {integrity: sha512-7bcUmDyS6PN3EuD9SlGGOxM77F8WLVsrwkxyWxKnxzmXoequ6c7741QBrANq6htVRGOITJ7z72mTP6Z4XyuG+Q==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.33': + resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} + + '@typescript-eslint/eslint-plugin@8.42.0': + resolution: {integrity: sha512-Aq2dPqsQkxHOLfb2OPv43RnIvfj05nw8v/6n3B2NABIPpHnjQnaLo9QGMTvml+tv4korl/Cjfrb/BYhoL8UUTQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.42.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.42.0': + resolution: {integrity: sha512-r1XG74QgShUgXph1BYseJ+KZd17bKQib/yF3SR+demvytiRXrwd12Blnz5eYGm8tXaeRdd4x88MlfwldHoudGg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.42.0': + resolution: {integrity: sha512-vfVpLHAhbPjilrabtOSNcUDmBboQNrJUiNAGoImkZKnMjs2TIcWG33s4Ds0wY3/50aZmTMqJa6PiwkwezaAklg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.42.0': + resolution: {integrity: sha512-51+x9o78NBAVgQzOPd17DkNTnIzJ8T/O2dmMBLoK9qbY0Gm52XJcdJcCl18ExBMiHo6jPMErUQWUv5RLE51zJw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.42.0': + resolution: {integrity: sha512-kHeFUOdwAJfUmYKjR3CLgZSglGHjbNTi1H8sTYRYV2xX6eNz4RyJ2LIgsDLKf8Yi0/GL1WZAC/DgZBeBft8QAQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.42.0': + resolution: {integrity: sha512-9KChw92sbPTYVFw3JLRH1ockhyR3zqqn9lQXol3/YbI6jVxzWoGcT3AsAW0mu1MY0gYtsXnUGV/AKpkAj5tVlQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.42.0': + resolution: {integrity: sha512-LdtAWMiFmbRLNP7JNeY0SqEtJvGMYSzfiWBSmx+VSZ1CH+1zyl8Mmw1TT39OrtsRvIYShjJWzTDMPWZJCpwBlw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.42.0': + resolution: {integrity: sha512-ku/uYtT4QXY8sl9EDJETD27o3Ewdi72hcXg1ah/kkUgBvAYHLwj2ofswFFNXS+FL5G+AGkxBtvGt8pFBHKlHsQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.42.0': + resolution: {integrity: sha512-JnIzu7H3RH5BrKC4NoZqRfmjqCIS1u3hGZltDYJgkVdqAezl4L9d1ZLw+36huCujtSBSAirGINF/S4UxOcR+/g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.42.0': + resolution: {integrity: sha512-3WbiuzoEowaEn8RSnhJBrxSwX8ULYE9CXaPepS2C2W3NSA5NNIvBaslpBSBElPq0UGr0xVJlXFWOAKIkyylydQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@webassemblyjs/ast@1.14.1': + resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} + + '@webassemblyjs/floating-point-hex-parser@1.13.2': + resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} + + '@webassemblyjs/helper-api-error@1.13.2': + resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} + + '@webassemblyjs/helper-buffer@1.14.1': + resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} + + '@webassemblyjs/helper-numbers@1.13.2': + resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': + resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} + + '@webassemblyjs/helper-wasm-section@1.14.1': + resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} + + '@webassemblyjs/ieee754@1.13.2': + resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} + + '@webassemblyjs/leb128@1.13.2': + resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} + + '@webassemblyjs/utf8@1.13.2': + resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} + + '@webassemblyjs/wasm-edit@1.14.1': + resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} + + '@webassemblyjs/wasm-gen@1.14.1': + resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} + + '@webassemblyjs/wasm-opt@1.14.1': + resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} + + '@webassemblyjs/wasm-parser@1.14.1': + resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} + + '@webassemblyjs/wast-printer@1.14.1': + resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + + '@xtuc/ieee754@1.2.0': + resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + + '@xtuc/long@4.2.2': + resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + + abi-wan-kanabi@2.2.4: + resolution: {integrity: sha512-0aA81FScmJCPX+8UvkXLki3X1+yPQuWxEkqXBVKltgPAK79J+NB+Lp5DouMXa7L6f+zcRlIA/6XO7BN/q9fnvg==} + hasBin: true + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv-formats@2.1.1: + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-keywords@3.5.2: + resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + peerDependencies: + ajv: ^6.9.1 + + ajv-keywords@5.1.0: + resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} + peerDependencies: + ajv: ^8.8.2 + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ajv@8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.0: + resolution: {integrity: sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + ansicolors@0.3.2: + resolution: {integrity: sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + append-field@1.0.0: + resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + + array-timsort@1.0.3: + resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} + + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.11.0: + resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==} + + babel-jest@29.7.0: + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + + babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + + babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + babel-preset-current-node-syntax@1.2.0: + resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} + peerDependencies: + '@babel/core': ^7.0.0 || ^8.0.0-0 + + babel-preset-jest@29.6.3: + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + body-parser@1.20.3: + resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + boxen@5.1.2: + resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==} + engines: {node: '>=10'} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.25.4: + resolution: {integrity: sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} + + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + c12@3.1.0: + resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==} + peerDependencies: + magicast: ^0.3.5 + peerDependenciesMeta: + magicast: + optional: true + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001739: + resolution: {integrity: sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==} + + cardinal@2.1.1: + resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==} + hasBin: true + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.6.0: + resolution: {integrity: sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + + check-disk-space@3.4.0: + resolution: {integrity: sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==} + engines: {node: '>=16'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + chrome-trace-event@1.0.4: + resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} + engines: {node: '>=6.0'} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + + class-transformer@0.5.1: + resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} + + class-validator@0.14.2: + resolution: {integrity: sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==} + + cli-boxes@2.2.1: + resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} + engines: {node: '>=6'} + + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cli-table3@0.6.5: + resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} + engines: {node: 10.* || >= 12.*} + + cli-width@3.0.0: + resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} + engines: {node: '>= 10'} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + collect-v8-coverage@1.0.2: + resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@3.2.1: + resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==} + + colorspace@1.1.4: + resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + comment-json@4.2.5: + resolution: {integrity: sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==} + engines: {node: '>= 6'} + + component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} + + concurrently@8.2.2: + resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==} + engines: {node: ^14.13.0 || >=16.0.0} + hasBin: true + + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + + consola@2.15.3: + resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + + cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} + engines: {node: '>= 0.6'} + + cookiejar@2.1.4: + resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + + cosmiconfig@8.3.6: + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + create-jest@29.7.0: + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + cron@4.3.3: + resolution: {integrity: sha512-B/CJj5yL3sjtlun6RtYHvoSB26EmQ2NUmhq9ZiJSyKIM4K/fqfh9aelDFlIayD2YMeFZqWLi9hHV+c+pq2Djkw==} + engines: {node: '>=18.x'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} + engines: {node: '>=0.11'} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + dedent@1.7.0: + resolution: {integrity: sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge-ts@7.1.5: + resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} + engines: {node: '>=16.0.0'} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + + dezalgo@1.0.4: + resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dotenv-expand@10.0.0: + resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} + engines: {node: '>=12'} + + dotenv-expand@12.0.1: + resolution: {integrity: sha512-LaKRbou8gt0RNID/9RoI+J2rvXsBRPMV7p+ElHlPhcSARbCPDYcYG2s1TIzAfWv4YSgyY5taidWzzs31lNV3yQ==} + engines: {node: '>=12'} + + dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + + dotenv@16.4.7: + resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} + engines: {node: '>=12'} + + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + effect@3.16.12: + resolution: {integrity: sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg==} + + electron-to-chromium@1.5.214: + resolution: {integrity: sha512-TpvUNdha+X3ybfU78NoQatKvQEm1oq3lf2QbnmCEdw+Bd9RuIAY+hJTvq1avzHM0f7EJfnH3vbCnbzKzisc/9Q==} + + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + empathic@2.0.0: + resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} + engines: {node: '>=14'} + + enabled@2.0.0: + resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} + + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + enhanced-resolve@5.18.3: + resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} + engines: {node: '>=10.13.0'} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@9.1.2: + resolution: {integrity: sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-prettier@5.5.4: + resolution: {integrity: sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + exit@0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} + + expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + express@4.21.2: + resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} + engines: {node: '>= 0.10.0'} + + exsolve@1.0.7: + resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} + + external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + + fast-check@3.23.2: + resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} + engines: {node: '>=8.0.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + + fecha@4.2.3: + resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + + figures@3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + file-stream-rotator@0.6.1: + resolution: {integrity: sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==} + + file-type@20.4.1: + resolution: {integrity: sha512-hw9gNZXUfZ02Jo0uafWLaFVPter5/k2rfcrjFJJHX/77xtSDOfJuEFb6oKlFV86FLP1SuyHMW1PSk0U9M5tKkQ==} + engines: {node: '>=18'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@1.3.1: + resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} + engines: {node: '>= 0.8'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + fn.name@1.1.0: + resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fork-ts-checker-webpack-plugin@9.0.2: + resolution: {integrity: sha512-Uochze2R8peoN1XqlSi/rGUkDQpRogtLFocP9+PGu68zk1BDAKXfdeCdyVZpgTk8V8WFVQXdEz426VKjXLO1Gg==} + engines: {node: '>=12.13.0', yarn: '>=1.0.0'} + peerDependencies: + typescript: '>3.6.0' + webpack: ^5.11.0 + + form-data@4.0.4: + resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} + engines: {node: '>= 6'} + + formidable@3.5.4: + resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==} + engines: {node: '>=14.0.0'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fs-monkey@1.1.0: + resolution: {integrity: sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + giget@2.0.0: + resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} + hasBin: true + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + google-protobuf@3.21.4: + resolution: {integrity: sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} + hasBin: true + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-own-prop@2.0.0: + resolution: {integrity: sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} + hasBin: true + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + inquirer@8.2.6: + resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==} + engines: {node: '>=12.0.0'} + + inquirer@9.2.15: + resolution: {integrity: sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==} + engines: {node: '>=18'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + iterare@1.2.1: + resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==} + engines: {node: '>=6'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-cli@29.7.0: + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@29.7.0: + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-pnp-resolver@1.2.3: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-worker@27.5.1: + resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} + engines: {node: '>= 10.13.0'} + + jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest@29.7.0: + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jiti@2.5.1: + resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonc-parser@3.2.1: + resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==} + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + kuler@2.0.0: + resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + libphonenumber-js@1.12.15: + resolution: {integrity: sha512-TMDCtIhWUDHh91wRC+wFuGlIzKdPzaTUHHVrIZ3vPUEoNaXFLrsIQ1ZpAeZeXApIF6rvDksMTvjrIQlLKaYxqQ==} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + loader-runner@4.3.0: + resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} + engines: {node: '>=6.11.5'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + + lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + logform@2.7.0: + resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==} + engines: {node: '>= 12.0.0'} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + lossless-json@4.2.0: + resolution: {integrity: sha512-bsHH3x+7acZfqokfn9Ks/ej96yF/z6oGGw1aBmXesq4r3fAjhdG4uYuqzDgZMk5g1CZUd5w3kwwIp9K1LOYUiA==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + luxon@3.7.1: + resolution: {integrity: sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg==} + engines: {node: '>=12'} + + magic-string@0.30.8: + resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} + engines: {node: '>=12'} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + memfs@3.5.3: + resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} + engines: {node: '>= 4.0.0'} + + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + + moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + multer@2.0.2: + resolution: {integrity: sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==} + engines: {node: '>= 10.16.0'} + + mute-stream@0.0.8: + resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + + mute-stream@1.0.0: + resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + node-abort-controller@3.1.1: + resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + + node-emoji@1.11.0: + resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} + + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + nypm@0.6.1: + resolution: {integrity: sha512-hlacBiRiv1k9hZFiphPUkfSQ/ZfQzZDzC+8z0wL3lvDAOUu/2NnChkKuMoMjNur/9OpKuz2QsIeiPVN0xM5Q0w==} + engines: {node: ^14.16.0 || >=16.10.0} + hasBin: true + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@2.2.0: + resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==} + engines: {node: '>= 6'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + one-time@1.0.0: + resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} + + os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + pako@2.1.0: + resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-to-regexp@0.1.12: + resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + + path-to-regexp@3.3.0: + resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.1: + resolution: {integrity: sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==} + engines: {node: '>=12'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + prisma@6.15.0: + resolution: {integrity: sha512-E6RCgOt+kUVtjtZgLQDBJ6md2tDItLJNExwI0XJeBc1FKL+Vwb+ovxXxuok9r8oBgsOXBA33fGDuE/0qDdCWqQ==} + engines: {node: '>=18.18'} + hasBin: true + peerDependencies: + typescript: '>=5.1.0' + peerDependenciesMeta: + typescript: + optional: true + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + protobufjs@7.5.4: + resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} + engines: {node: '>=12.0.0'} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} + + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: '>=0.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + + rc9@2.1.2: + resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + redeyed@2.1.1: + resolution: {integrity: sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==} + + reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + + repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve.exports@2.0.3: + resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} + engines: {node: '>=10'} + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + run-async@2.4.1: + resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} + engines: {node: '>=0.12.0'} + + run-async@3.0.0: + resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} + engines: {node: '>=0.12.0'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + schema-utils@3.3.0: + resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} + engines: {node: '>= 10.13.0'} + + schema-utils@4.3.2: + resolution: {integrity: sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==} + engines: {node: '>= 10.13.0'} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + send@0.19.0: + resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} + engines: {node: '>= 0.8.0'} + + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + + serve-static@1.16.2: + resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} + engines: {node: '>= 0.8.0'} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.8.3: + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + engines: {node: '>= 0.4'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + spawn-command@0.0.2: + resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stack-trace@0.0.10: + resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + starknet@7.6.4: + resolution: {integrity: sha512-FB20IaLCDbh/XomkB+19f5jmNxG+RzNdRO7QUhm7nfH81UPIt2C/MyWAlHCYkbv2wznSEb73wpxbp9tytokTgQ==} + engines: {node: '>=22'} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strtok3@10.3.4: + resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} + engines: {node: '>=18'} + + superagent@10.2.3: + resolution: {integrity: sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==} + engines: {node: '>=14.18.0'} + + supertest@7.1.4: + resolution: {integrity: sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==} + engines: {node: '>=14.18.0'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + swagger-ui-dist@5.17.14: + resolution: {integrity: sha512-CVbSfaLpstV65OnSjbXfVd6Sta3q3F7Cj/yYuvHMp1P90LztOLs6PfUnKEVAeiIVQt9u2SaPwv0LiH/OyMjHRw==} + + symbol-observable@4.0.0: + resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} + engines: {node: '>=0.10'} + + synckit@0.11.11: + resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} + engines: {node: ^14.18.0 || >=16.0.0} + + tapable@2.2.3: + resolution: {integrity: sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==} + engines: {node: '>=6'} + + terser-webpack-plugin@5.3.14: + resolution: {integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + + terser@5.44.0: + resolution: {integrity: sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==} + engines: {node: '>=10'} + hasBin: true + + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + + text-hex@1.0.0: + resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + + tinyexec@1.0.1: + resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + + tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + token-types@6.1.1: + resolution: {integrity: sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==} + engines: {node: '>=14.16'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + triple-beam@1.4.1: + resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} + engines: {node: '>= 14.0.0'} + + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-jest@29.4.1: + resolution: {integrity: sha512-SaeUtjfpg9Uqu8IbeDKtdaS0g8lS6FT6OzM3ezrDfErPJPHNDo/Ey+VFGP1bQIDfagYDLyRpd7O15XpG1Es2Uw==} + engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/transform': ^29.0.0 || ^30.0.0 + '@jest/types': ^29.0.0 || ^30.0.0 + babel-jest: ^29.0.0 || ^30.0.0 + esbuild: '*' + jest: ^29.0.0 || ^30.0.0 + jest-util: ^29.0.0 || ^30.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/transform': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + jest-util: + optional: true + + ts-loader@9.5.4: + resolution: {integrity: sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==} + engines: {node: '>=12.0.0'} + peerDependencies: + typescript: '*' + webpack: ^5.0.0 + + ts-mixer@6.0.4: + resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==} + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + tsconfig-paths-webpack-plugin@4.2.0: + resolution: {integrity: sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==} + engines: {node: '>=10.13.0'} + + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + + typescript@5.7.2: + resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} + engines: {node: '>=14.17'} + hasBin: true + + typescript@5.9.2: + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} + engines: {node: '>=14.17'} + hasBin: true + + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + + uid@2.0.2: + resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} + engines: {node: '>=8'} + + uint8array-extras@1.5.0: + resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} + engines: {node: '>=18'} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + + validator@13.15.15: + resolution: {integrity: sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==} + engines: {node: '>= 0.10'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + + watchpack@2.4.4: + resolution: {integrity: sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==} + engines: {node: '>=10.13.0'} + + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + webpack-node-externals@3.0.0: + resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==} + engines: {node: '>=6'} + + webpack-sources@3.3.3: + resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==} + engines: {node: '>=10.13.0'} + + webpack@5.97.1: + resolution: {integrity: sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + widest-line@3.1.0: + resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} + engines: {node: '>=8'} + + winston-daily-rotate-file@4.7.1: + resolution: {integrity: sha512-7LGPiYGBPNyGHLn9z33i96zx/bd71pjBn9tqQzO3I4Tayv94WPmBNwKC7CO1wPHdP9uvu+Md/1nr6VSH9h0iaA==} + engines: {node: '>=8'} + peerDependencies: + winston: ^3 + + winston-transport@4.9.0: + resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==} + engines: {node: '>= 12.0.0'} + + winston@3.17.0: + resolution: {integrity: sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==} + engines: {node: '>= 12.0.0'} + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 + + '@angular-devkit/core@17.3.11(chokidar@3.6.0)': + dependencies: + ajv: 8.12.0 + ajv-formats: 2.1.1(ajv@8.12.0) + jsonc-parser: 3.2.1 + picomatch: 4.0.1 + rxjs: 7.8.1 + source-map: 0.7.4 + optionalDependencies: + chokidar: 3.6.0 + + '@angular-devkit/schematics-cli@17.3.11(chokidar@3.6.0)': + dependencies: + '@angular-devkit/core': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) + ansi-colors: 4.1.3 + inquirer: 9.2.15 + symbol-observable: 4.0.0 + yargs-parser: 21.1.1 + transitivePeerDependencies: + - chokidar + + '@angular-devkit/schematics@17.3.11(chokidar@3.6.0)': + dependencies: + '@angular-devkit/core': 17.3.11(chokidar@3.6.0) + jsonc-parser: 3.2.1 + magic-string: 0.30.8 + ora: 5.4.1 + rxjs: 7.8.1 + transitivePeerDependencies: + - chokidar + + '@apibara/protocol@0.4.9': + dependencies: + '@grpc/grpc-js': 1.13.4 + '@grpc/proto-loader': 0.7.15 + google-protobuf: 3.21.4 + long: 5.3.2 + protobufjs: 7.5.4 + + '@apibara/starknet@0.5.0': + dependencies: + google-protobuf: 3.21.4 + long: 5.3.2 + protobufjs: 7.5.4 + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.0': {} + + '@babel/core@7.28.3': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.3) + '@babel/helpers': 7.28.3 + '@babel/parser': 7.28.3 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 + convert-source-map: 2.0.0 + debug: 4.4.1 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.3': + dependencies: + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.25.4 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.27.1': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.3': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + + '@babel/parser@7.28.3': + dependencies: + '@babel/types': 7.28.2 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/runtime@7.28.3': {} + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 + + '@babel/traverse@7.28.3': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.3 + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.2': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@bcoe/v8-coverage@0.2.3': {} + + '@borewit/text-codec@0.1.1': {} + + '@colors/colors@1.5.0': + optional: true + + '@colors/colors@1.6.0': {} + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@dabh/diagnostics@2.0.3': + dependencies: + colorspace: 1.1.4 + enabled: 2.0.0 + kuler: 2.0.0 + + '@eslint-community/eslint-utils@4.8.0(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.12.6 + debug: 4.4.1 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.1': {} + + '@grpc/grpc-js@1.13.4': + dependencies: + '@grpc/proto-loader': 0.7.15 + '@js-sdsl/ordered-map': 4.4.2 + + '@grpc/proto-loader@0.7.15': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.5.4 + yargs: 17.7.2 + + '@humanwhocodes/config-array@0.13.0': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.1 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/load-nyc-config@1.1.0': + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.1 + resolve-from: 5.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jest/console@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.19.12 + chalk: 4.1.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + + '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2))': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.12 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + + '@jest/environment@29.7.0': + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.12 + jest-mock: 29.7.0 + + '@jest/expect-utils@29.7.0': + dependencies: + jest-get-type: 29.6.3 + + '@jest/expect@29.7.0': + dependencies: + expect: 29.7.0 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/fake-timers@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 20.19.12 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + '@jest/globals@29.7.0': + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/types': 29.6.3 + jest-mock: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/reporters@29.7.0': + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.30 + '@types/node': 20.19.12 + chalk: 4.1.2 + collect-v8-coverage: 1.0.2 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.2.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + jest-worker: 29.7.0 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - supports-color + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.8 + + '@jest/source-map@29.6.3': + dependencies: + '@jridgewell/trace-mapping': 0.3.30 + callsites: 3.1.0 + graceful-fs: 4.2.11 + + '@jest/test-result@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.2 + + '@jest/test-sequencer@29.7.0': + dependencies: + '@jest/test-result': 29.7.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + slash: 3.0.0 + + '@jest/transform@29.7.0': + dependencies: + '@babel/core': 7.28.3 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.30 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + micromatch: 4.0.8 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 20.19.12 + '@types/yargs': 17.0.33 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.30 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.30': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@js-sdsl/ordered-map@4.4.2': {} + + '@ljharb/through@2.3.14': + dependencies: + call-bind: 1.0.8 + + '@lukeed/csprng@1.1.0': {} + + '@microsoft/tsdoc@0.15.1': {} + + '@nestjs/axios@3.1.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + axios: 1.11.0 + rxjs: 7.8.2 + + '@nestjs/cli@10.4.9': + dependencies: + '@angular-devkit/core': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics-cli': 17.3.11(chokidar@3.6.0) + '@nestjs/schematics': 10.2.3(chokidar@3.6.0)(typescript@5.7.2) + chalk: 4.1.2 + chokidar: 3.6.0 + cli-table3: 0.6.5 + commander: 4.1.1 + fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.7.2)(webpack@5.97.1) + glob: 10.4.5 + inquirer: 8.2.6 + node-emoji: 1.11.0 + ora: 5.4.1 + tree-kill: 1.2.2 + tsconfig-paths: 4.2.0 + tsconfig-paths-webpack-plugin: 4.2.0 + typescript: 5.7.2 + webpack: 5.97.1 + webpack-node-externals: 3.0.0 + transitivePeerDependencies: + - esbuild + - uglify-js + - webpack-cli + + '@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + dependencies: + file-type: 20.4.1 + iterare: 1.2.1 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + tslib: 2.8.1 + uid: 2.0.2 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.2 + transitivePeerDependencies: + - supports-color + + '@nestjs/config@3.3.0(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + dotenv: 16.4.5 + dotenv-expand: 10.0.0 + lodash: 4.17.21 + rxjs: 7.8.2 + + '@nestjs/config@4.0.2(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + dotenv: 16.4.7 + dotenv-expand: 12.0.1 + lodash: 4.17.21 + rxjs: 7.8.2 + + '@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nuxtjs/opencollective': 0.3.2 + fast-safe-stringify: 2.1.1 + iterare: 1.2.1 + path-to-regexp: 3.3.0 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + tslib: 2.8.1 + uid: 2.0.2 + optionalDependencies: + '@nestjs/platform-express': 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20) + transitivePeerDependencies: + - encoding + + '@nestjs/mapped-types@2.0.5(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)': + dependencies: + '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + reflect-metadata: 0.2.2 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.2 + + '@nestjs/platform-express@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20)': + dependencies: + '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2) + body-parser: 1.20.3 + cors: 2.8.5 + express: 4.21.2 + multer: 2.0.2 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@nestjs/schematics@10.2.3(chokidar@3.6.0)(typescript@5.7.2)': + dependencies: + '@angular-devkit/core': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) + comment-json: 4.2.5 + jsonc-parser: 3.3.1 + pluralize: 8.0.0 + typescript: 5.7.2 + transitivePeerDependencies: + - chokidar + + '@nestjs/schematics@10.2.3(chokidar@3.6.0)(typescript@5.9.2)': + dependencies: + '@angular-devkit/core': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) + comment-json: 4.2.5 + jsonc-parser: 3.3.1 + pluralize: 8.0.0 + typescript: 5.9.2 + transitivePeerDependencies: + - chokidar + + '@nestjs/swagger@7.4.2(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)': + dependencies: + '@microsoft/tsdoc': 0.15.1 + '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/mapped-types': 2.0.5(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2) + js-yaml: 4.1.0 + lodash: 4.17.21 + path-to-regexp: 3.3.0 + reflect-metadata: 0.2.2 + swagger-ui-dist: 5.17.14 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.2 + + '@nestjs/terminus@10.3.0(@grpc/grpc-js@1.13.4)(@grpc/proto-loader@0.7.15)(@nestjs/axios@3.1.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2))(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@prisma/client@6.15.0(prisma@6.15.0(typescript@5.9.2))(typescript@5.9.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2) + boxen: 5.1.2 + check-disk-space: 3.4.0 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + optionalDependencies: + '@grpc/grpc-js': 1.13.4 + '@grpc/proto-loader': 0.7.15 + '@nestjs/axios': 3.1.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2) + '@prisma/client': 6.15.0(prisma@6.15.0(typescript@5.9.2))(typescript@5.9.2) + + '@nestjs/terminus@11.0.0(@grpc/grpc-js@1.13.4)(@grpc/proto-loader@0.7.15)(@nestjs/axios@3.1.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2))(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@prisma/client@6.15.0(prisma@6.15.0(typescript@5.9.2))(typescript@5.9.2))(reflect-metadata@0.2.2)(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2) + boxen: 5.1.2 + check-disk-space: 3.4.0 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + optionalDependencies: + '@grpc/grpc-js': 1.13.4 + '@grpc/proto-loader': 0.7.15 + '@nestjs/axios': 3.1.3(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.11.0)(rxjs@7.8.2) + '@prisma/client': 6.15.0(prisma@6.15.0(typescript@5.9.2))(typescript@5.9.2) + + '@nestjs/testing@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20))': + dependencies: + '@nestjs/common': 10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.2) + tslib: 2.8.1 + optionalDependencies: + '@nestjs/platform-express': 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.20) + + '@noble/curves@1.7.0': + dependencies: + '@noble/hashes': 1.6.0 + + '@noble/hashes@1.6.0': {} + + '@noble/hashes@1.8.0': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@nuxtjs/opencollective@0.3.2': + dependencies: + chalk: 4.1.2 + consola: 2.15.3 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + + '@paralleldrive/cuid2@2.2.2': + dependencies: + '@noble/hashes': 1.8.0 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@pkgr/core@0.2.9': {} + + '@prisma/client@6.15.0(prisma@6.15.0(typescript@5.9.2))(typescript@5.9.2)': + optionalDependencies: + prisma: 6.15.0(typescript@5.9.2) + typescript: 5.9.2 + + '@prisma/config@6.15.0': + dependencies: + c12: 3.1.0 + deepmerge-ts: 7.1.5 + effect: 3.16.12 + empathic: 2.0.0 + transitivePeerDependencies: + - magicast + + '@prisma/debug@6.15.0': {} + + '@prisma/engines-version@6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb': {} + + '@prisma/engines@6.15.0': + dependencies: + '@prisma/debug': 6.15.0 + '@prisma/engines-version': 6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb + '@prisma/fetch-engine': 6.15.0 + '@prisma/get-platform': 6.15.0 + + '@prisma/fetch-engine@6.15.0': + dependencies: + '@prisma/debug': 6.15.0 + '@prisma/engines-version': 6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb + '@prisma/get-platform': 6.15.0 + + '@prisma/get-platform@6.15.0': + dependencies: + '@prisma/debug': 6.15.0 + + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.4': {} + + '@protobufjs/eventemitter@1.1.0': {} + + '@protobufjs/fetch@1.1.0': + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.0': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.0': {} + + '@scure/base@1.2.1': {} + + '@scure/starknet@1.1.0': + dependencies: + '@noble/curves': 1.7.0 + '@noble/hashes': 1.6.0 + + '@sinclair/typebox@0.27.8': {} + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@10.3.0': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@standard-schema/spec@1.0.0': {} + + '@starknet-io/types-js@0.7.10': {} + + '@starknet-io/types-js@0.8.4': {} + + '@tokenizer/inflate@0.2.7': + dependencies: + debug: 4.4.1 + fflate: 0.8.2 + token-types: 6.1.1 + transitivePeerDependencies: + - supports-color + + '@tokenizer/token@0.3.0': {} + + '@tsconfig/node10@1.0.11': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.28.2 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.28.2 + + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 20.19.12 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 20.19.12 + + '@types/cron@2.4.3': + dependencies: + cron: 4.3.3 + + '@types/eslint-scope@3.7.7': + dependencies: + '@types/eslint': 9.6.1 + '@types/estree': 1.0.8 + + '@types/eslint@9.6.1': + dependencies: + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + + '@types/estree@1.0.8': {} + + '@types/express-serve-static-core@4.19.6': + dependencies: + '@types/node': 20.19.12 + '@types/qs': 6.14.0 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.5 + + '@types/express@4.17.23': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 4.19.6 + '@types/qs': 6.14.0 + '@types/serve-static': 1.15.8 + + '@types/graceful-fs@4.1.9': + dependencies: + '@types/node': 20.19.12 + + '@types/http-errors@2.0.5': {} + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/jest@29.5.14': + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + + '@types/json-schema@7.0.15': {} + + '@types/luxon@3.7.1': {} + + '@types/mime@1.3.5': {} + + '@types/node@20.19.12': + dependencies: + undici-types: 6.21.0 + + '@types/qs@6.14.0': {} + + '@types/range-parser@1.2.7': {} + + '@types/send@0.17.5': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 20.19.12 + + '@types/serve-static@1.15.8': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 20.19.12 + '@types/send': 0.17.5 + + '@types/stack-utils@2.0.3': {} + + '@types/triple-beam@1.3.5': {} + + '@types/validator@13.15.3': {} + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.33': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@typescript-eslint/eslint-plugin@8.42.0(@typescript-eslint/parser@8.42.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.42.0(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/scope-manager': 8.42.0 + '@typescript-eslint/type-utils': 8.42.0(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/utils': 8.42.0(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.42.0 + eslint: 8.57.1 + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.42.0(eslint@8.57.1)(typescript@5.9.2)': + dependencies: + '@typescript-eslint/scope-manager': 8.42.0 + '@typescript-eslint/types': 8.42.0 + '@typescript-eslint/typescript-estree': 8.42.0(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.42.0 + debug: 4.4.1 + eslint: 8.57.1 + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.42.0(typescript@5.9.2)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.42.0(typescript@5.9.2) + '@typescript-eslint/types': 8.42.0 + debug: 4.4.1 + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.42.0': + dependencies: + '@typescript-eslint/types': 8.42.0 + '@typescript-eslint/visitor-keys': 8.42.0 + + '@typescript-eslint/tsconfig-utils@8.42.0(typescript@5.9.2)': + dependencies: + typescript: 5.9.2 + + '@typescript-eslint/type-utils@8.42.0(eslint@8.57.1)(typescript@5.9.2)': + dependencies: + '@typescript-eslint/types': 8.42.0 + '@typescript-eslint/typescript-estree': 8.42.0(typescript@5.9.2) + '@typescript-eslint/utils': 8.42.0(eslint@8.57.1)(typescript@5.9.2) + debug: 4.4.1 + eslint: 8.57.1 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.42.0': {} + + '@typescript-eslint/typescript-estree@8.42.0(typescript@5.9.2)': + dependencies: + '@typescript-eslint/project-service': 8.42.0(typescript@5.9.2) + '@typescript-eslint/tsconfig-utils': 8.42.0(typescript@5.9.2) + '@typescript-eslint/types': 8.42.0 + '@typescript-eslint/visitor-keys': 8.42.0 + debug: 4.4.1 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.42.0(eslint@8.57.1)(typescript@5.9.2)': + dependencies: + '@eslint-community/eslint-utils': 4.8.0(eslint@8.57.1) + '@typescript-eslint/scope-manager': 8.42.0 + '@typescript-eslint/types': 8.42.0 + '@typescript-eslint/typescript-estree': 8.42.0(typescript@5.9.2) + eslint: 8.57.1 + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.42.0': + dependencies: + '@typescript-eslint/types': 8.42.0 + eslint-visitor-keys: 4.2.1 + + '@ungap/structured-clone@1.3.0': {} + + '@webassemblyjs/ast@1.14.1': + dependencies: + '@webassemblyjs/helper-numbers': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + + '@webassemblyjs/floating-point-hex-parser@1.13.2': {} + + '@webassemblyjs/helper-api-error@1.13.2': {} + + '@webassemblyjs/helper-buffer@1.14.1': {} + + '@webassemblyjs/helper-numbers@1.13.2': + dependencies: + '@webassemblyjs/floating-point-hex-parser': 1.13.2 + '@webassemblyjs/helper-api-error': 1.13.2 + '@xtuc/long': 4.2.2 + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': {} + + '@webassemblyjs/helper-wasm-section@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/wasm-gen': 1.14.1 + + '@webassemblyjs/ieee754@1.13.2': + dependencies: + '@xtuc/ieee754': 1.2.0 + + '@webassemblyjs/leb128@1.13.2': + dependencies: + '@xtuc/long': 4.2.2 + + '@webassemblyjs/utf8@1.13.2': {} + + '@webassemblyjs/wasm-edit@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/helper-wasm-section': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-opt': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + '@webassemblyjs/wast-printer': 1.14.1 + + '@webassemblyjs/wasm-gen@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 + + '@webassemblyjs/wasm-opt@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + + '@webassemblyjs/wasm-parser@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-api-error': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 + + '@webassemblyjs/wast-printer@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@xtuc/long': 4.2.2 + + '@xtuc/ieee754@1.2.0': {} + + '@xtuc/long@4.2.2': {} + + abi-wan-kanabi@2.2.4: + dependencies: + ansicolors: 0.3.2 + cardinal: 2.1.1 + fs-extra: 10.1.0 + yargs: 17.7.2 + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn-walk@8.3.4: + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv-formats@2.1.1(ajv@8.12.0): + optionalDependencies: + ajv: 8.12.0 + + ajv-formats@2.1.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + ajv-keywords@3.5.2(ajv@6.12.6): + dependencies: + ajv: 6.12.6 + + ajv-keywords@5.1.0(ajv@8.17.1): + dependencies: + ajv: 8.17.1 + fast-deep-equal: 3.1.3 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.12.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-align@3.0.1: + dependencies: + string-width: 4.2.3 + + ansi-colors@4.1.3: {} + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.1: {} + + ansicolors@0.3.2: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + append-field@1.0.0: {} + + arg@4.1.3: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + array-flatten@1.1.1: {} + + array-timsort@1.0.3: {} + + asap@2.0.6: {} + + async@3.2.6: {} + + asynckit@0.4.0: {} + + axios@1.11.0: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.4 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + babel-jest@29.7.0(@babel/core@7.28.3): + dependencies: + '@babel/core': 7.28.3 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.28.3) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-istanbul@6.1.1: + dependencies: + '@babel/helper-plugin-utils': 7.27.1 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@29.6.3: + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.28.0 + + babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.3): + dependencies: + '@babel/core': 7.28.3 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.3) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.3) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.3) + '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.3) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.3) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.3) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.3) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.3) + + babel-preset-jest@29.6.3(@babel/core@7.28.3): + dependencies: + '@babel/core': 7.28.3 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.3) + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + binary-extensions@2.3.0: {} + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + body-parser@1.20.3: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.13.0 + raw-body: 2.5.2 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + boxen@5.1.2: + dependencies: + ansi-align: 3.0.1 + camelcase: 6.3.0 + chalk: 4.1.2 + cli-boxes: 2.2.1 + string-width: 4.2.3 + type-fest: 0.20.2 + widest-line: 3.1.0 + wrap-ansi: 7.0.0 + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.25.4: + dependencies: + caniuse-lite: 1.0.30001739 + electron-to-chromium: 1.5.214 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.25.4) + + bs-logger@0.2.6: + dependencies: + fast-json-stable-stringify: 2.1.0 + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + buffer-from@1.1.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + + bytes@3.1.2: {} + + c12@3.1.0: + dependencies: + chokidar: 4.0.3 + confbox: 0.2.2 + defu: 6.1.4 + dotenv: 16.6.1 + exsolve: 1.0.7 + giget: 2.0.0 + jiti: 2.5.1 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 1.0.0 + pkg-types: 2.3.0 + rc9: 2.1.2 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001739: {} + + cardinal@2.1.1: + dependencies: + ansicolors: 0.3.2 + redeyed: 2.1.1 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.6.0: {} + + char-regex@1.0.2: {} + + chardet@0.7.0: {} + + check-disk-space@3.4.0: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + chrome-trace-event@1.0.4: {} + + ci-info@3.9.0: {} + + citty@0.1.6: + dependencies: + consola: 3.4.2 + + cjs-module-lexer@1.4.3: {} + + class-transformer@0.5.1: {} + + class-validator@0.14.2: + dependencies: + '@types/validator': 13.15.3 + libphonenumber-js: 1.12.15 + validator: 13.15.15 + + cli-boxes@2.2.1: {} + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cli-spinners@2.9.2: {} + + cli-table3@0.6.5: + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + + cli-width@3.0.0: {} + + cli-width@4.1.0: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone@1.0.4: {} + + co@4.6.0: {} + + collect-v8-coverage@1.0.2: {} + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + + color@3.2.1: + dependencies: + color-convert: 1.9.3 + color-string: 1.9.1 + + colorspace@1.1.4: + dependencies: + color: 3.2.1 + text-hex: 1.0.0 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@2.20.3: {} + + commander@4.1.1: {} + + comment-json@4.2.5: + dependencies: + array-timsort: 1.0.3 + core-util-is: 1.0.3 + esprima: 4.0.1 + has-own-prop: 2.0.0 + repeat-string: 1.6.1 + + component-emitter@1.3.1: {} + + concat-map@0.0.1: {} + + concat-stream@2.0.0: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + typedarray: 0.0.6 + + concurrently@8.2.2: + dependencies: + chalk: 4.1.2 + date-fns: 2.30.0 + lodash: 4.17.21 + rxjs: 7.8.2 + shell-quote: 1.8.3 + spawn-command: 0.0.2 + supports-color: 8.1.1 + tree-kill: 1.2.2 + yargs: 17.7.2 + + confbox@0.2.2: {} + + consola@2.15.3: {} + + consola@3.4.2: {} + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + convert-source-map@2.0.0: {} + + cookie-signature@1.0.6: {} + + cookie@0.7.1: {} + + cookiejar@2.1.4: {} + + core-util-is@1.0.3: {} + + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cosmiconfig@8.3.6(typescript@5.7.2): + dependencies: + import-fresh: 3.3.1 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 5.7.2 + + create-jest@29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + create-require@1.1.1: {} + + cron@4.3.3: + dependencies: + '@types/luxon': 3.7.1 + luxon: 3.7.1 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + date-fns@2.30.0: + dependencies: + '@babel/runtime': 7.28.3 + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + decimal.js@10.6.0: {} + + dedent@1.7.0: {} + + deep-is@0.1.4: {} + + deepmerge-ts@7.1.5: {} + + deepmerge@4.3.1: {} + + defaults@1.0.4: + dependencies: + clone: 1.0.4 + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + defu@6.1.4: {} + + delayed-stream@1.0.0: {} + + depd@2.0.0: {} + + destr@2.0.5: {} + + destroy@1.2.0: {} + + detect-newline@3.1.0: {} + + dezalgo@1.0.4: + dependencies: + asap: 2.0.6 + wrappy: 1.0.2 + + diff-sequences@29.6.3: {} + + diff@4.0.2: {} + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dotenv-expand@10.0.0: {} + + dotenv-expand@12.0.1: + dependencies: + dotenv: 16.6.1 + + dotenv@16.4.5: {} + + dotenv@16.4.7: {} + + dotenv@16.6.1: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + ee-first@1.1.1: {} + + effect@3.16.12: + dependencies: + '@standard-schema/spec': 1.0.0 + fast-check: 3.23.2 + + electron-to-chromium@1.5.214: {} + + emittery@0.13.1: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + empathic@2.0.0: {} + + enabled@2.0.0: {} + + encodeurl@1.0.2: {} + + encodeurl@2.0.0: {} + + enhanced-resolve@5.18.3: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.3 + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@2.0.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@9.1.2(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + + eslint-plugin-prettier@5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.6.2): + dependencies: + eslint: 8.57.1 + prettier: 3.6.2 + prettier-linter-helpers: 1.0.0 + synckit: 0.11.11 + optionalDependencies: + '@types/eslint': 9.6.1 + eslint-config-prettier: 9.1.2(eslint@8.57.1) + + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@8.57.1: + dependencies: + '@eslint-community/eslint-utils': 4.8.0(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.1 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.3.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.1 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + espree@9.6.1: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 3.4.3 + + esprima@4.0.1: {} + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@4.3.0: {} + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + events@3.3.0: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + exit@0.1.2: {} + + expect@29.7.0: + dependencies: + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + + express@4.21.2: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.3 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.1 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.1 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.12 + proxy-addr: 2.0.7 + qs: 6.13.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.0 + serve-static: 1.16.2 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + exsolve@1.0.7: {} + + external-editor@3.1.0: + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + + fast-check@3.23.2: + dependencies: + pure-rand: 6.1.0 + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-safe-stringify@2.1.1: {} + + fast-uri@3.1.0: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + + fecha@4.2.3: {} + + fflate@0.8.2: {} + + figures@3.2.0: + dependencies: + escape-string-regexp: 1.0.5 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + file-stream-rotator@0.6.1: + dependencies: + moment: 2.30.1 + + file-type@20.4.1: + dependencies: + '@tokenizer/inflate': 0.2.7 + strtok3: 10.3.4 + token-types: 6.1.1 + uint8array-extras: 1.5.0 + transitivePeerDependencies: + - supports-color + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@1.3.1: + dependencies: + debug: 2.6.9 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@3.2.0: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + rimraf: 3.0.2 + + flatted@3.3.3: {} + + fn.name@1.1.0: {} + + follow-redirects@1.15.11: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fork-ts-checker-webpack-plugin@9.0.2(typescript@5.7.2)(webpack@5.97.1): + dependencies: + '@babel/code-frame': 7.27.1 + chalk: 4.1.2 + chokidar: 3.6.0 + cosmiconfig: 8.3.6(typescript@5.7.2) + deepmerge: 4.3.1 + fs-extra: 10.1.0 + memfs: 3.5.3 + minimatch: 3.1.2 + node-abort-controller: 3.1.1 + schema-utils: 3.3.0 + semver: 7.7.2 + tapable: 2.2.3 + typescript: 5.7.2 + webpack: 5.97.1 + + form-data@4.0.4: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + formidable@3.5.4: + dependencies: + '@paralleldrive/cuid2': 2.2.2 + dezalgo: 1.0.4 + once: 1.4.0 + + forwarded@0.2.0: {} + + fresh@0.5.2: {} + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-monkey@1.1.0: {} + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-package-type@0.1.0: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@6.0.1: {} + + giget@2.0.0: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + defu: 6.1.4 + node-fetch-native: 1.6.7 + nypm: 0.6.1 + pathe: 2.0.3 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob-to-regexp@0.4.1: {} + + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + google-protobuf@3.21.4: {} + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + handlebars@4.7.8: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + + has-flag@4.0.0: {} + + has-own-prop@2.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + html-escaper@2.0.2: {} + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + human-signals@2.1.0: {} + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + inquirer@8.2.6: + dependencies: + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-width: 3.0.0 + external-editor: 3.1.0 + figures: 3.2.0 + lodash: 4.17.21 + mute-stream: 0.0.8 + ora: 5.4.1 + run-async: 2.4.1 + rxjs: 7.8.2 + string-width: 4.2.3 + strip-ansi: 6.0.1 + through: 2.3.8 + wrap-ansi: 6.2.0 + + inquirer@9.2.15: + dependencies: + '@ljharb/through': 2.3.14 + ansi-escapes: 4.3.2 + chalk: 5.6.0 + cli-cursor: 3.1.0 + cli-width: 4.1.0 + external-editor: 3.1.0 + figures: 3.2.0 + lodash: 4.17.21 + mute-stream: 1.0.0 + ora: 5.4.1 + run-async: 3.0.0 + rxjs: 7.8.2 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + + ipaddr.js@1.9.1: {} + + is-arrayish@0.2.1: {} + + is-arrayish@0.3.2: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-generator-fn@2.1.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-interactive@1.0.0: {} + + is-number@7.0.0: {} + + is-path-inside@3.0.3: {} + + is-stream@2.0.1: {} + + is-unicode-supported@0.1.0: {} + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@5.2.1: + dependencies: + '@babel/core': 7.28.3 + '@babel/parser': 7.28.3 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.28.3 + '@babel/parser': 7.28.3 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.7.2 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@4.0.1: + dependencies: + debug: 4.4.1 + istanbul-lib-coverage: 3.2.2 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + iterare@1.2.1: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jest-changed-files@29.7.0: + dependencies: + execa: 5.1.1 + jest-util: 29.7.0 + p-limit: 3.1.0 + + jest-circus@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.12 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.7.0 + is-generator-fn: 2.1.0 + jest-each: 29.7.0 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + p-limit: 3.1.0 + pretty-format: 29.7.0 + pure-rand: 6.1.0 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-cli@29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)) + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jest-config@29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)): + dependencies: + '@babel/core': 7.28.3 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.28.3) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.19.12 + ts-node: 10.9.2(@types/node@20.19.12)(typescript@5.9.2) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-diff@29.7.0: + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-docblock@29.7.0: + dependencies: + detect-newline: 3.1.0 + + jest-each@29.7.0: + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + jest-get-type: 29.6.3 + jest-util: 29.7.0 + pretty-format: 29.7.0 + + jest-environment-node@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.12 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + jest-get-type@29.6.3: {} + + jest-haste-map@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/graceful-fs': 4.1.9 + '@types/node': 20.19.12 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + jest-worker: 29.7.0 + micromatch: 4.0.8 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-leak-detector@29.7.0: + dependencies: + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-matcher-utils@29.7.0: + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-message-util@29.7.0: + dependencies: + '@babel/code-frame': 7.27.1 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.19.12 + jest-util: 29.7.0 + + jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): + optionalDependencies: + jest-resolve: 29.7.0 + + jest-regex-util@29.6.3: {} + + jest-resolve-dependencies@29.7.0: + dependencies: + jest-regex-util: 29.6.3 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + jest-resolve@29.7.0: + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + resolve: 1.22.10 + resolve.exports: 2.0.3 + slash: 3.0.0 + + jest-runner@29.7.0: + dependencies: + '@jest/console': 29.7.0 + '@jest/environment': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.12 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.11 + jest-docblock: 29.7.0 + jest-environment-node: 29.7.0 + jest-haste-map: 29.7.0 + jest-leak-detector: 29.7.0 + jest-message-util: 29.7.0 + jest-resolve: 29.7.0 + jest-runtime: 29.7.0 + jest-util: 29.7.0 + jest-watcher: 29.7.0 + jest-worker: 29.7.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + + jest-runtime@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/globals': 29.7.0 + '@jest/source-map': 29.6.3 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.12 + chalk: 4.1.2 + cjs-module-lexer: 1.4.3 + collect-v8-coverage: 1.0.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + + jest-snapshot@29.7.0: + dependencies: + '@babel/core': 7.28.3 + '@babel/generator': 7.28.3 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.3) + '@babel/types': 7.28.2 + '@jest/expect-utils': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.3) + chalk: 4.1.2 + expect: 29.7.0 + graceful-fs: 4.2.11 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + natural-compare: 1.4.0 + pretty-format: 29.7.0 + semver: 7.7.2 + transitivePeerDependencies: + - supports-color + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.19.12 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + + jest-validate@29.7.0: + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + + jest-watcher@29.7.0: + dependencies: + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.12 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.7.0 + string-length: 4.0.2 + + jest-worker@27.5.1: + dependencies: + '@types/node': 20.19.12 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest-worker@29.7.0: + dependencies: + '@types/node': 20.19.12 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest@29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)) + '@jest/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jiti@2.5.1: {} + + js-tokens@4.0.0: {} + + js-yaml@3.14.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jsonc-parser@3.2.1: {} + + jsonc-parser@3.3.1: {} + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kleur@3.0.3: {} + + kuler@2.0.0: {} + + leven@3.1.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + libphonenumber-js@1.12.15: {} + + lines-and-columns@1.2.4: {} + + loader-runner@4.3.0: {} + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.camelcase@4.3.0: {} + + lodash.memoize@4.1.2: {} + + lodash.merge@4.6.2: {} + + lodash@4.17.21: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + logform@2.7.0: + dependencies: + '@colors/colors': 1.6.0 + '@types/triple-beam': 1.3.5 + fecha: 4.2.3 + ms: 2.1.3 + safe-stable-stringify: 2.5.0 + triple-beam: 1.4.1 + + long@5.3.2: {} + + lossless-json@4.2.0: {} + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + luxon@3.7.1: {} + + magic-string@0.30.8: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + make-dir@4.0.0: + dependencies: + semver: 7.7.2 + + make-error@1.3.6: {} + + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + + math-intrinsics@1.1.0: {} + + media-typer@0.3.0: {} + + memfs@3.5.3: + dependencies: + fs-monkey: 1.1.0 + + merge-descriptors@1.0.3: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + methods@1.1.2: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: {} + + mime@2.6.0: {} + + mimic-fn@2.1.0: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + + moment@2.30.1: {} + + ms@2.0.0: {} + + ms@2.1.3: {} + + multer@2.0.2: + dependencies: + append-field: 1.0.0 + busboy: 1.6.0 + concat-stream: 2.0.0 + mkdirp: 0.5.6 + object-assign: 4.1.1 + type-is: 1.6.18 + xtend: 4.0.2 + + mute-stream@0.0.8: {} + + mute-stream@1.0.0: {} + + natural-compare@1.4.0: {} + + negotiator@0.6.3: {} + + neo-async@2.6.2: {} + + node-abort-controller@3.1.1: {} + + node-emoji@1.11.0: + dependencies: + lodash: 4.17.21 + + node-fetch-native@1.6.7: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-int64@0.4.0: {} + + node-releases@2.0.19: {} + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + nypm@0.6.1: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + pathe: 2.0.3 + pkg-types: 2.3.0 + tinyexec: 1.0.1 + + object-assign@4.1.1: {} + + object-hash@2.2.0: {} + + object-inspect@1.13.4: {} + + ohash@2.0.11: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + one-time@1.0.0: + dependencies: + fn.name: 1.1.0 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + ora@5.4.1: + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + + os-tmpdir@1.0.2: {} + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-try@2.2.0: {} + + package-json-from-dist@1.0.1: {} + + pako@2.1.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.27.1 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parseurl@1.3.3: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-to-regexp@0.1.12: {} + + path-to-regexp@3.3.0: {} + + path-type@4.0.0: {} + + pathe@2.0.3: {} + + perfect-debounce@1.0.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.1: {} + + pirates@4.0.7: {} + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.7 + pathe: 2.0.3 + + pluralize@8.0.0: {} + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + + prettier@3.6.2: {} + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + prisma@6.15.0(typescript@5.9.2): + dependencies: + '@prisma/config': 6.15.0 + '@prisma/engines': 6.15.0 + optionalDependencies: + typescript: 5.9.2 + transitivePeerDependencies: + - magicast + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + protobufjs@7.5.4: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 20.19.12 + long: 5.3.2 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + proxy-from-env@1.1.0: {} + + punycode@2.3.1: {} + + pure-rand@6.1.0: {} + + qs@6.13.0: + dependencies: + side-channel: 1.1.0 + + qs@6.14.0: + dependencies: + side-channel: 1.1.0 + + queue-microtask@1.2.3: {} + + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + + range-parser@1.2.1: {} + + raw-body@2.5.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + rc9@2.1.2: + dependencies: + defu: 6.1.4 + destr: 2.0.5 + + react-is@18.3.1: {} + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + readdirp@4.1.2: {} + + redeyed@2.1.1: + dependencies: + esprima: 4.0.1 + + reflect-metadata@0.2.2: {} + + repeat-string@1.6.1: {} + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve.exports@2.0.3: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + reusify@1.1.0: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + run-async@2.4.1: {} + + run-async@3.0.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + rxjs@7.8.1: + dependencies: + tslib: 2.8.1 + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + safe-buffer@5.2.1: {} + + safe-stable-stringify@2.5.0: {} + + safer-buffer@2.1.2: {} + + schema-utils@3.3.0: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + ajv-keywords: 3.5.2(ajv@6.12.6) + + schema-utils@4.3.2: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 8.17.1 + ajv-formats: 2.1.1(ajv@8.17.1) + ajv-keywords: 5.1.0(ajv@8.17.1) + + semver@6.3.1: {} + + semver@7.7.2: {} + + send@0.19.0: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 + + serve-static@1.16.2: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.0 + transitivePeerDependencies: + - supports-color + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shell-quote@1.8.3: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + + sisteransi@1.0.5: {} + + slash@3.0.0: {} + + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + source-map@0.7.4: {} + + source-map@0.7.6: {} + + spawn-command@0.0.2: {} + + sprintf-js@1.0.3: {} + + stack-trace@0.0.10: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + starknet@7.6.4: + dependencies: + '@noble/curves': 1.7.0 + '@noble/hashes': 1.6.0 + '@scure/base': 1.2.1 + '@scure/starknet': 1.1.0 + '@starknet-io/starknet-types-07': '@starknet-io/types-js@0.7.10' + '@starknet-io/starknet-types-08': '@starknet-io/types-js@0.8.4' + abi-wan-kanabi: 2.2.4 + lossless-json: 4.2.0 + pako: 2.1.0 + ts-mixer: 6.0.4 + + statuses@2.0.1: {} + + streamsearch@1.1.0: {} + + string-length@4.0.2: + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.2.0 + + strip-bom@3.0.0: {} + + strip-bom@4.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-json-comments@3.1.1: {} + + strtok3@10.3.4: + dependencies: + '@tokenizer/token': 0.3.0 + + superagent@10.2.3: + dependencies: + component-emitter: 1.3.1 + cookiejar: 2.1.4 + debug: 4.4.1 + fast-safe-stringify: 2.1.1 + form-data: 4.0.4 + formidable: 3.5.4 + methods: 1.1.2 + mime: 2.6.0 + qs: 6.14.0 + transitivePeerDependencies: + - supports-color + + supertest@7.1.4: + dependencies: + methods: 1.1.2 + superagent: 10.2.3 + transitivePeerDependencies: + - supports-color + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + swagger-ui-dist@5.17.14: {} + + symbol-observable@4.0.0: {} + + synckit@0.11.11: + dependencies: + '@pkgr/core': 0.2.9 + + tapable@2.2.3: {} + + terser-webpack-plugin@5.3.14(webpack@5.97.1): + dependencies: + '@jridgewell/trace-mapping': 0.3.30 + jest-worker: 27.5.1 + schema-utils: 4.3.2 + serialize-javascript: 6.0.2 + terser: 5.44.0 + webpack: 5.97.1 + + terser@5.44.0: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.15.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + + text-hex@1.0.0: {} + + text-table@0.2.0: {} + + through@2.3.8: {} + + tinyexec@1.0.1: {} + + tmp@0.0.33: + dependencies: + os-tmpdir: 1.0.2 + + tmpl@1.0.5: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + token-types@6.1.1: + dependencies: + '@borewit/text-codec': 0.1.1 + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + + tr46@0.0.3: {} + + tree-kill@1.2.2: {} + + triple-beam@1.4.1: {} + + ts-api-utils@2.1.0(typescript@5.9.2): + dependencies: + typescript: 5.9.2 + + ts-jest@29.4.1(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.3))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)))(typescript@5.9.2): + dependencies: + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + handlebars: 4.7.8 + jest: 29.7.0(@types/node@20.19.12)(ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2)) + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.7.2 + type-fest: 4.41.0 + typescript: 5.9.2 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.28.3 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.28.3) + jest-util: 29.7.0 + + ts-loader@9.5.4(typescript@5.9.2)(webpack@5.97.1): + dependencies: + chalk: 4.1.2 + enhanced-resolve: 5.18.3 + micromatch: 4.0.8 + semver: 7.7.2 + source-map: 0.7.6 + typescript: 5.9.2 + webpack: 5.97.1 + + ts-mixer@6.0.4: {} + + ts-node@10.9.2(@types/node@20.19.12)(typescript@5.9.2): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.19.12 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.9.2 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + tsconfig-paths-webpack-plugin@4.2.0: + dependencies: + chalk: 4.1.2 + enhanced-resolve: 5.18.3 + tapable: 2.2.3 + tsconfig-paths: 4.2.0 + + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-detect@4.0.8: {} + + type-fest@0.20.2: {} + + type-fest@0.21.3: {} + + type-fest@4.41.0: {} + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + typedarray@0.0.6: {} + + typescript@5.7.2: {} + + typescript@5.9.2: {} + + uglify-js@3.19.3: + optional: true + + uid@2.0.2: + dependencies: + '@lukeed/csprng': 1.1.0 + + uint8array-extras@1.5.0: {} + + undici-types@6.21.0: {} + + universalify@2.0.1: {} + + unpipe@1.0.0: {} + + update-browserslist-db@1.1.3(browserslist@4.25.4): + dependencies: + browserslist: 4.25.4 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + utils-merge@1.0.1: {} + + v8-compile-cache-lib@3.0.1: {} + + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.30 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + + validator@13.15.15: {} + + vary@1.1.2: {} + + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + + watchpack@2.4.4: + dependencies: + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + + webidl-conversions@3.0.1: {} + + webpack-node-externals@3.0.0: {} + + webpack-sources@3.3.3: {} + + webpack@5.97.1: + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.8 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.15.0 + browserslist: 4.25.4 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.18.3 + es-module-lexer: 1.7.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 3.3.0 + tapable: 2.2.3 + terser-webpack-plugin: 5.3.14(webpack@5.97.1) + watchpack: 2.4.4 + webpack-sources: 3.3.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + widest-line@3.1.0: + dependencies: + string-width: 4.2.3 + + winston-daily-rotate-file@4.7.1(winston@3.17.0): + dependencies: + file-stream-rotator: 0.6.1 + object-hash: 2.2.0 + triple-beam: 1.4.1 + winston: 3.17.0 + winston-transport: 4.9.0 + + winston-transport@4.9.0: + dependencies: + logform: 2.7.0 + readable-stream: 3.6.2 + triple-beam: 1.4.1 + + winston@3.17.0: + dependencies: + '@colors/colors': 1.6.0 + '@dabh/diagnostics': 2.0.3 + async: 3.2.6 + is-stream: 2.0.1 + logform: 2.7.0 + one-time: 1.0.0 + readable-stream: 3.6.2 + safe-stable-stringify: 2.5.0 + stack-trace: 0.0.10 + triple-beam: 1.4.1 + winston-transport: 4.9.0 + + word-wrap@1.2.5: {} + + wordwrap@1.0.0: {} + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + wrappy@1.0.2: {} + + write-file-atomic@4.0.2: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + + xtend@4.0.2: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yn@3.1.1: {} + + yocto-queue@0.1.0: {} diff --git a/backend/pnpm-workspace.yaml b/backend/pnpm-workspace.yaml new file mode 100644 index 00000000..f911ef41 --- /dev/null +++ b/backend/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - 'apps/*' + - 'libs/*' \ No newline at end of file diff --git a/backend/tsconfig.base.json b/backend/tsconfig.base.json new file mode 100644 index 00000000..095d9586 --- /dev/null +++ b/backend/tsconfig.base.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + "target": "ES2021", + "sourceMap": true, + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": false, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false, + "baseUrl": ".", + "paths": { + "@forge/db": ["libs/db/src"], + "@forge/db/*": ["libs/db/src/*"], + "@forge/config": ["libs/config/src"], + "@forge/config/*": ["libs/config/src/*"], + "@forge/logger": ["libs/logger/src"], + "@forge/logger/*": ["libs/logger/src/*"], + "@forge/starknet": ["libs/starknet/src"], + "@forge/starknet/*": ["libs/starknet/src/*"] + } + }, + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/backend/tsconfig.json b/backend/tsconfig.json new file mode 100644 index 00000000..b263fc33 --- /dev/null +++ b/backend/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist" + }, + "include": ["apps/**/*", "libs/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/export_merkle.sh b/export_merkle.sh new file mode 100755 index 00000000..138eb1c7 --- /dev/null +++ b/export_merkle.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +set -euo pipefail + +# --- Config --- +PKG="vault_allocator" +TEST="vault_allocator::test::creator::test_creator" +OUT_DIR="leafs" + +# Nom du fichier = premier argument (sinon "merkle") +NAME="${1:-merkle}" +OUT_PATH="$OUT_DIR/$NAME.json" +LOG_PATH="$OUT_DIR/$NAME.log" + +mkdir -p "$OUT_DIR" + +# --- Run test --- +snforge test -p "$PKG" "$TEST" 2>&1 | tee "$LOG_PATH" >/dev/null + +# --- Parse output --- +python3 - "$LOG_PATH" "$OUT_PATH" << 'PY' +import sys, re, json, pathlib +log_path, out_path = sys.argv[1], sys.argv[2] +s = pathlib.Path(log_path).read_text() + +def get(k): + m = re.search(rf"^{k}:\s*([0-9]+)\s*$", s, re.M) + return m.group(1) if m else "" + +blk = re.search(r"leaf_additional_data:\s*\[(.*)\]\s*tree:", s, re.S) +items = re.findall(r"ManageLeafAdditionalData\s*\{(.*?)\}", blk.group(1), re.S) if blk else [] +leafs = [] +for it in items: + g = lambda pat: (re.search(pat, it, re.S).group(1) if re.search(pat, it, re.S) else "") + argm = re.search(r"argument_addresses:\s*\[(.*?)\]", it, re.S) + args = re.findall(r"[0-9]+", argm.group(1)) if argm else [] + leafs.append({ + "decoder_and_sanitizer": g(r"decoder_and_sanitizer:\s*([0-9]+)"), + "target": g(r"target:\s*([0-9]+)"), + "selector": g(r"selector:\s*([0-9]+)"), + "argument_addresses": args, + "description": g(r'description:\s*"([^"]*)"'), + "leaf_index": int(g(r"leaf_index:\s*([0-9]+)") or 0), + "leaf_hash": g(r"leaf_hash:\s*([0-9]+)") + }) + +# tree via comptage de crochets +tree = [] +start = s.find("tree:") +if start != -1: + i = s.find("[", start) + if i != -1: + depth = 0; buf = "" + for ch in s[i:]: + buf += ch + if ch == "[": depth += 1 + elif ch == "]": depth -= 1 + if depth == 0: break + for row in re.findall(r"\[([0-9,\s]+)\]", buf): + tree.append(re.findall(r"[0-9]+", row)) + +doc = { + "metadata": { + "vault": get("vault"), + "vault_allocator": get("vault_allocator"), + "manager": get("manager"), + "decoder_and_sanitizer": get("decoder_and_sanitizer"), + "root": get("root"), + "tree_capacity": int(get("tree_capacity") or 0), + "leaf_used": int(get("leaf_used") or 0) + }, + "leafs": leafs, + "tree": tree +} + +pathlib.Path(out_path).write_text(json.dumps(doc, indent=2)) +print(f"Wrote {out_path} (log: {log_path})") +PY diff --git a/leafs/myDegenStrategy.json b/leafs/myDegenStrategy.json new file mode 100644 index 00000000..91304e73 --- /dev/null +++ b/leafs/myDegenStrategy.json @@ -0,0 +1,206 @@ +{ + "metadata": { + "vault": "5413914729363742859539197023059", + "vault_allocator": "5413914729363742859539197023059", + "manager": "5413914729363742859539197023059", + "decoder_and_sanitizer": "5413914729363742859539197023059", + "root": "2009410845371992160865623192489255279553238721469696220270717266578317727536", + "tree_capacity": 16, + "leaf_used": 12 + }, + "leafs": [ + { + "decoder_and_sanitizer": "5413914729363742859539197023059", + "target": "5413914729363742859539197023059", + "selector": "272736270449175597906601442304651169002384630994237278650276415012817394366", + "argument_addresses": [ + "5413914729363742859539197023059", + "154717502686997779505242937237748798500912348117963555524611254740330341259", + "0" + ], + "description": "Flash loan wstETH", + "leaf_index": 0, + "leaf_hash": "317606356264796957081494875492335589028851932487272643796381474436363864238" + }, + { + "decoder_and_sanitizer": "5413914729363742859539197023059", + "target": "154717502686997779505242937237748798500912348117963555524611254740330341259", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "3525041238689230877912143146602053624361984120479485588303103220764281888840" + ], + "description": "Approve vWSTETH to spend wstETH", + "leaf_index": 1, + "leaf_hash": "2382129687266469781982741342878431266787679357347247675491511646191972799740" + }, + { + "decoder_and_sanitizer": "5413914729363742859539197023059", + "target": "3525041238689230877912143146602053624361984120479485588303103220764281888840", + "selector": "352040181584456735608515580760888541466059565068553383579463728554843487745", + "argument_addresses": [ + "5413914729363742859539197023059" + ], + "description": "Deposit wstETH for vWSTETH", + "leaf_index": 2, + "leaf_hash": "678303738440072355040482306291323122231389812748699335636114100561821006738" + }, + { + "decoder_and_sanitizer": "5413914729363742859539197023059", + "target": "3525041238689230877912143146602053624361984120479485588303103220764281888840", + "selector": "602617684354587743731238934093348436560137034424203693318834094005509508215", + "argument_addresses": [ + "5413914729363742859539197023059", + "5413914729363742859539197023059" + ], + "description": "Withdraw wstETH from vWSTETH", + "leaf_index": 3, + "leaf_hash": "259042544756615804735847710915085572036254733492650027317560391082052917849" + }, + { + "decoder_and_sanitizer": "5413914729363742859539197023059", + "target": "3525041238689230877912143146602053624361984120479485588303103220764281888840", + "selector": "1329909728320632088402217562277154056711815095720684343816173432540100887380", + "argument_addresses": [ + "5413914729363742859539197023059" + ], + "description": "Mint vWSTETH from wstETH", + "leaf_index": 4, + "leaf_hash": "2020394736186607945703320019579876486318886267839428950131345419282806178769" + }, + { + "decoder_and_sanitizer": "5413914729363742859539197023059", + "target": "3525041238689230877912143146602053624361984120479485588303103220764281888840", + "selector": "1326975239452520649139862114866922847703907595552193666703522601892858398217", + "argument_addresses": [ + "5413914729363742859539197023059", + "5413914729363742859539197023059" + ], + "description": "Redeem vWSTETH for wstETH", + "leaf_index": 5, + "leaf_hash": "3042765462205175563536591820905994748649291322464868128752071183187879121549" + }, + { + "decoder_and_sanitizer": "5413914729363742859539197023059", + "target": "154717502686997779505242937237748798500912348117963555524611254740330341259", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "23945123541494432761473724903801722895187605313546851349842002150511018336" + ], + "description": "Approve singleton_4dc4f0ca6ea4961e4c8373265bfd5317678f4fe374d76f3fd7135f57763bf28 to spend wstETH", + "leaf_index": 6, + "leaf_hash": "2445852729353229310731046560318695011241265124708031712248225808303118455325" + }, + { + "decoder_and_sanitizer": "5413914729363742859539197023059", + "target": "3525041238689230877912143146602053624361984120479485588303103220764281888840", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "2205784456246912591591022705581145477202962715352127032183976536021813616724" + ], + "description": "Approve extension_pid_4dc4f0ca6ea4961e4c8373265bfd5317678f4fe374d76f3fd7135f57763bf28 to spend vWSTETH", + "leaf_index": 7, + "leaf_hash": "3464090403020451920057379067729769391029911523431888726114405517374847538715" + }, + { + "decoder_and_sanitizer": "5413914729363742859539197023059", + "target": "23945123541494432761473724903801722895187605313546851349842002150511018336", + "selector": "856336041243570760931994264144677337008736779890258883919015516521378842083", + "argument_addresses": [ + "2198503327643286920898110335698706244522220458610657370981979460625005526824", + "154717502686997779505242937237748798500912348117963555524611254740330341259", + "0", + "154717502686997779505242937237748798500912348117963555524611254740330341259", + "2087021424722619777119509474943472645767659996348769578120564519014510906823", + "2205784456246912591591022705581145477202962715352127032183976536021813616724", + "5413914729363742859539197023059", + "0", + "0" + ], + "description": "Transfer position extension_pid_4dc4f0ca6ea4961e4c8373265bfd5317678f4fe374d76f3fd7135f57763bf28 with collateral wstETH and debt ETH", + "leaf_index": 8, + "leaf_hash": "1819209779739977841040933056925283731595048071224895547241853612166795162382" + }, + { + "decoder_and_sanitizer": "5413914729363742859539197023059", + "target": "23945123541494432761473724903801722895187605313546851349842002150511018336", + "selector": "1631880094539101464511272129576525542256330665932643760479341072373928343757", + "argument_addresses": [ + "2198503327643286920898110335698706244522220458610657370981979460625005526824", + "154717502686997779505242937237748798500912348117963555524611254740330341259", + "2087021424722619777119509474943472645767659996348769578120564519014510906823", + "5413914729363742859539197023059", + "0" + ], + "description": "Modify position extension_pid_4dc4f0ca6ea4961e4c8373265bfd5317678f4fe374d76f3fd7135f57763bf28 with collateral wstETH and debt ETH", + "leaf_index": 9, + "leaf_hash": "2634592553496285009292376176295427889867467179651684925541942771847164440290" + }, + { + "decoder_and_sanitizer": "5413914729363742859539197023059", + "target": "2087021424722619777119509474943472645767659996348769578120564519014510906823", + "selector": "949021990203918389843157787496164629863144228991510976554585288817234167820", + "argument_addresses": [ + "5413914729363742859539197023059" + ], + "description": "Approve avnu_router to spend ETH", + "leaf_index": 10, + "leaf_hash": "2563898399473985563900934298259834363461328130575117797451263812333569086466" + }, + { + "decoder_and_sanitizer": "5413914729363742859539197023059", + "target": "5413914729363742859539197023059", + "selector": "493099248799488417046068732150865584992754802357103958402415916279525943907", + "argument_addresses": [ + "2087021424722619777119509474943472645767659996348769578120564519014510906823", + "154717502686997779505242937237748798500912348117963555524611254740330341259", + "5413914729363742859539197023059" + ], + "description": "Multi route swap ETH for wstETH", + "leaf_index": 11, + "leaf_hash": "1402948939338977525922864200314918151613509013497338423302729291119418422970" + } + ], + "tree": [ + [ + "317606356264796957081494875492335589028851932487272643796381474436363864238", + "2382129687266469781982741342878431266787679357347247675491511646191972799740", + "678303738440072355040482306291323122231389812748699335636114100561821006738", + "259042544756615804735847710915085572036254733492650027317560391082052917849", + "2020394736186607945703320019579876486318886267839428950131345419282806178769", + "3042765462205175563536591820905994748649291322464868128752071183187879121549", + "2445852729353229310731046560318695011241265124708031712248225808303118455325", + "3464090403020451920057379067729769391029911523431888726114405517374847538715", + "1819209779739977841040933056925283731595048071224895547241853612166795162382", + "2634592553496285009292376176295427889867467179651684925541942771847164440290", + "2563898399473985563900934298259834363461328130575117797451263812333569086466", + "1402948939338977525922864200314918151613509013497338423302729291119418422970", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691", + "3205128972529973231252252659884931471622226491573146866138760422619344340691" + ], + [ + "3423822769287180484021583579805443593830968587176109056997353008718099863284", + "1325742362178912360536139177417362640345586131374859855524232570863623463544", + "2211049742759667452469885564658058345369667690180880197147601523953370677968", + "2247797487024426298547368233360539032659181373376435894639190991549218093791", + "2408786195949745898962724526621934123371926682098616906200007259456869003567", + "1223523352300262617761383728743461619458442984187545862062756263135352083770", + "238030899799779168456945008849198713173740159230645696776744044218681768341", + "238030899799779168456945008849198713173740159230645696776744044218681768341" + ], + [ + "2654567653756178410357495042196036381127818862667901540690741603802233635829", + "2106748485588187450465230936666810982274765978487413121986630047764081007834", + "2083218962872988229484764095511349082585743934726234369242472763664387490033", + "2002344887409916074004609464034933685885954831475272208035150614502178175918" + ], + [ + "3081352858128957529930488752811556657193091015231923829060236869527770054267", + "738594670216921787531516715860696216564232173815946286788857865369399678411" + ], + [ + "2009410845371992160865623192489255279553238721469696220270717266578317727536" + ] + ] +} \ No newline at end of file diff --git a/packages/vault/src/aum_provider/aum_provider_4626/aum_provider_4626.cairo b/packages/vault/src/aum_provider/aum_provider_4626/aum_provider_4626.cairo new file mode 100644 index 00000000..88bb8011 --- /dev/null +++ b/packages/vault/src/aum_provider/aum_provider_4626/aum_provider_4626.cairo @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::contract] +pub mod AumProvider4626 { + use core::num::traits::Zero; + use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use openzeppelin::interfaces::erc4626::{IERC4626Dispatcher, IERC4626DispatcherTrait}; + use starknet::ContractAddress; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use vault::aum_provider::aum_provider_4626::errors::Errors; + use vault::aum_provider::aum_provider_4626::interface::IAumProvider4626; + use vault::aum_provider::base_aum_provider::BaseAumProviderComponent; + use vault::vault::interface::IVaultDispatcherTrait; + component!( + path: BaseAumProviderComponent, storage: base_aum_provider, event: BaseAumProviderEvent, + ); + + #[abi(embed_v0)] + impl BaseAumProviderImpl = + BaseAumProviderComponent::BaseAumProviderImpl; + impl BaseAumProviderInternalImpl = BaseAumProviderComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + base_aum_provider: BaseAumProviderComponent::Storage, + strategy4626: IERC4626Dispatcher, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + BaseAumProviderEvent: BaseAumProviderComponent::Event, + } + + #[abi(embed_v0)] + impl AumProvider4626Impl of IAumProvider4626 { + fn get_strategy_4626(self: @ContractState) -> ContractAddress { + self.strategy4626.read().contract_address + } + } + + #[constructor] + fn constructor(ref self: ContractState, vault: ContractAddress, strategy4626: ContractAddress) { + self.base_aum_provider.initializer(vault); + if (strategy4626.is_zero()) { + Errors::invalid_strategy4626_address(); + } + + self.strategy4626.write(IERC4626Dispatcher { contract_address: strategy4626 }); + } + + // --- AUM Trait Implementation --- + impl AumTrait of BaseAumProviderComponent::AumTrait { + fn get_aum(self: @BaseAumProviderComponent::ComponentState) -> u256 { + let contract_state = self.get_contract(); + let strategy = contract_state.strategy4626.read(); + let vault_allocator_address = contract_state + .base_aum_provider + .vault + .read() + .vault_allocator(); + + // .vault.vault_allocator(); + let underlying_asset = strategy.asset(); + let underlying_dispatcher = ERC20ABIDispatcher { contract_address: underlying_asset }; + let underlying_balance = underlying_dispatcher.balance_of(vault_allocator_address); + let strategy_shares = ERC20ABIDispatcher { contract_address: strategy.contract_address } + .balance_of(vault_allocator_address); + let strategy_assets = if strategy_shares > 0 { + strategy.convert_to_assets(strategy_shares) + } else { + 0 + }; + + underlying_balance + strategy_assets + } + } +} diff --git a/packages/vault/src/aum_provider/aum_provider_4626/errors.cairo b/packages/vault/src/aum_provider/aum_provider_4626/errors.cairo new file mode 100644 index 00000000..916972c5 --- /dev/null +++ b/packages/vault/src/aum_provider/aum_provider_4626/errors.cairo @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +pub mod Errors { + pub fn invalid_strategy4626_address() { + panic!("Invalid strategy4626 address"); + } +} diff --git a/packages/vault/src/aum_provider/aum_provider_4626/interface.cairo b/packages/vault/src/aum_provider/aum_provider_4626/interface.cairo new file mode 100644 index 00000000..c20e957a --- /dev/null +++ b/packages/vault/src/aum_provider/aum_provider_4626/interface.cairo @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use starknet::ContractAddress; + +#[starknet::interface] +pub trait IAumProvider4626 { + fn get_strategy_4626(self: @TContractState) -> ContractAddress; +} \ No newline at end of file diff --git a/packages/vault/src/aum_provider/base_aum_provider.cairo b/packages/vault/src/aum_provider/base_aum_provider.cairo new file mode 100644 index 00000000..6ebf107d --- /dev/null +++ b/packages/vault/src/aum_provider/base_aum_provider.cairo @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::component] +pub mod BaseAumProviderComponent { + use core::num::traits::Zero; + use starknet::ContractAddress; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use vault::aum_provider::errors::Errors; + use vault::aum_provider::interface::IBaseAumProvider; + use vault::vault::interface::{IVaultDispatcher, IVaultDispatcherTrait}; + #[storage] + pub struct Storage { + pub vault: IVaultDispatcher, + } + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + + pub trait AumTrait> { + fn get_aum(self: @ComponentState) -> u256; + } + + + #[embeddable_as(BaseAumProviderImpl)] + impl BaseAumProvider< + TContractState, +HasComponent, impl Aum: AumTrait, + > of IBaseAumProvider> { + fn aum(self: @ComponentState) -> u256 { + Aum::get_aum(self) + } + + fn report(ref self: ComponentState) { + self.vault.read().report(Aum::get_aum(@self)); + } + } + + #[generate_trait] + pub impl InternalImpl< + TContractState, +HasComponent, + > of InternalTrait { + fn initializer(ref self: ComponentState, vault: ContractAddress) { + if (vault.is_zero()) { + Errors::invalid_vault_address(); + } + self.vault.write(IVaultDispatcher { contract_address: vault }); + } + } +} diff --git a/packages/vault/src/aum_provider/errors.cairo b/packages/vault/src/aum_provider/errors.cairo new file mode 100644 index 00000000..a7c84ac7 --- /dev/null +++ b/packages/vault/src/aum_provider/errors.cairo @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +pub mod Errors { + pub fn invalid_vault_address() { + panic!("Invalid vault address"); + } +} diff --git a/packages/vault/src/aum_provider/interface.cairo b/packages/vault/src/aum_provider/interface.cairo new file mode 100644 index 00000000..6f218f13 --- /dev/null +++ b/packages/vault/src/aum_provider/interface.cairo @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::interface] +pub trait IBaseAumProvider { + fn aum(self: @T) -> u256; + fn report(ref self: T); +} diff --git a/packages/vault/src/lib.cairo b/packages/vault/src/lib.cairo index 31bd5d14..12aa3603 100644 --- a/packages/vault/src/lib.cairo +++ b/packages/vault/src/lib.cairo @@ -8,8 +8,17 @@ pub mod vault { pub mod vault; } -// waiting for openzeppelin to release vault with external assets -pub mod oz_4626; +pub mod aum_provider { + pub mod base_aum_provider; + pub mod errors; + pub mod interface; + pub mod aum_provider_4626 { + pub mod aum_provider_4626; + pub mod errors; + pub mod interface; + } +} + pub mod redeem_request { pub mod errors; diff --git a/packages/vault/src/oz_4626.cairo b/packages/vault/src/oz_4626.cairo deleted file mode 100644 index 0ce5d83f..00000000 --- a/packages/vault/src/oz_4626.cairo +++ /dev/null @@ -1,795 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v3.0.0-alpha.0 -// (token/src/erc20/extensions/erc4626/erc4626.cairo) - -/// # ERC4626 Component -/// -/// The ERC4626 component is an extension of ERC20 and provides an implementation of the IERC4626 -/// interface which allows the minting and burning of "shares" in exchange for an underlying -/// "asset". The component leverages traits to configure fees, limits, and decimals. -/// -/// CAUTION: In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen -/// through frontrunning with a "donation" to the vault that inflates the price of a share. This is -/// variously known as a donation or inflation attack and is essentially a problem of slippage. -/// Vault deployers can protect against this attack by making an initial deposit of a non-trivial -/// amount of the asset, such that price manipulation becomes infeasible. Withdrawals may similarly -/// be affected by slippage. Users can protect against this attack as well as unexpected slippage in -/// general by verifying the amount received is as expected, using a wrapper that performs these -/// checks. -/// -/// This implementation offers configurable virtual assets and shares to help developers mitigate -/// that risk. `ImmutableConfig::DECIMALS_OFFSET` corresponds to an offset in the decimal -/// representation between the underlying asset's decimals and vault decimals. This offset also -/// determines the rate of virtual shares to virtual assets in the vault, which itself determines -/// the initial exchange rate. While not fully preventing the attack, analysis shows that the -/// default offset (0) makes it non-profitable even if an attacker is able to capture value from -/// multiple user deposits, as a result of the value being captured by the virtual shares (out of -/// the attacker's donation) matching the attacker's expected gains. With a larger offset, the -/// attack becomes orders of magnitude more expensive than it is profitable. -/// -/// The drawback of this approach is that the virtual shares do capture (a very small) part of the -/// value being accrued to the vault. Also, if the vault experiences losses and users try to exit -/// the vault, the virtual shares and assets will cause the first exiting user to experience reduced -/// losses to the detriment to the last users who will experience bigger losses. -#[starknet::component] -pub mod ERC4626Component { - use core::num::traits::{Bounded, Pow, Zero}; - use openzeppelin::token::erc20::ERC20Component; - use openzeppelin::token::erc20::ERC20Component::InternalImpl as ERC20InternalImpl; - use openzeppelin::token::erc20::extensions::erc4626::interface::IERC4626; - use openzeppelin::token::erc20::interface::{IERC20, IERC20Metadata}; - use openzeppelin::utils::math; - use openzeppelin::utils::math::Rounding; - use starknet::ContractAddress; - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - - // The default values are only used when the DefaultConfig - // is in scope in the implementing contract. - pub const DEFAULT_UNDERLYING_DECIMALS: u8 = 18; - pub const DEFAULT_DECIMALS_OFFSET: u8 = 0; - - #[storage] - pub struct Storage { - pub ERC4626_asset: ContractAddress, - } - - #[event] - #[derive(Drop, PartialEq, starknet::Event)] - pub enum Event { - Deposit: Deposit, - Withdraw: Withdraw, - } - - /// Emitted when `sender` exchanges `assets` for `shares` and transfers those - /// `shares` to `owner`. - #[derive(Drop, PartialEq, starknet::Event)] - pub struct Deposit { - #[key] - pub sender: ContractAddress, - #[key] - pub owner: ContractAddress, - pub assets: u256, - pub shares: u256, - } - - /// Emitted when `sender` exchanges `shares`, owned by `owner`, for `assets` and transfers - /// those `assets` to `receiver`. - #[derive(Drop, PartialEq, starknet::Event)] - pub struct Withdraw { - #[key] - pub sender: ContractAddress, - #[key] - pub receiver: ContractAddress, - #[key] - pub owner: ContractAddress, - pub assets: u256, - pub shares: u256, - } - - pub mod Errors { - pub const EXCEEDED_MAX_DEPOSIT: felt252 = 'ERC4626: exceeds max deposit'; - pub const EXCEEDED_MAX_MINT: felt252 = 'ERC4626: exceeds max mint'; - pub const EXCEEDED_MAX_WITHDRAW: felt252 = 'ERC4626: exceeds max withdraw'; - pub const EXCEEDED_MAX_REDEEM: felt252 = 'ERC4626: exceeds max redeem'; - pub const TOKEN_TRANSFER_FAILED: felt252 = 'ERC4626: token transfer failed'; - pub const INVALID_ASSET_ADDRESS: felt252 = 'ERC4626: asset address set to 0'; - pub const DECIMALS_OVERFLOW: felt252 = 'ERC4626: decimals overflow'; - } - - /// Constants expected to be defined at the contract level which configure virtual - /// assets and shares. - /// - /// `UNDERLYING_DECIMALS` should match the underlying asset's decimals. The default - /// value is `18`. - /// - /// `DECIMALS_OFFSET` corresponds to the representational offset between `UNDERLYING_DECIMALS` - /// and the vault decimals. The greater the offset, the more expensive it is for attackers to - /// execute an inflation attack. - /// - /// Requirements: - /// - /// - `UNDERLYING_DECIMALS` + `DECIMALS_OFFSET` cannot exceed 255 (max u8). - pub trait ImmutableConfig { - const UNDERLYING_DECIMALS: u8; - const DECIMALS_OFFSET: u8; - - fn validate() { - assert( - Bounded::MAX - Self::UNDERLYING_DECIMALS >= Self::DECIMALS_OFFSET, - Errors::DECIMALS_OVERFLOW, - ) - } - } - - /// Adjustments for fees expected to be defined at the contract level. - /// Defaults to no entry or exit fees. - /// - /// NOTE: The FeeConfigTrait hooks directly into the preview methods of the ERC4626 component. - /// The preview methods must return as close to the exact amount of shares or assets as possible - /// if the actual (previewed) operation occurred in the same transaction (according to EIP-4626 - /// spec). - /// All operations use their corresponding preview method as the value of assets or shares being - /// moved. - /// Therefore, adjusting an operation's assets in FeeConfigTrait consequently adjusts the assets - /// (or assets to be converted into shares) in both the preview operation and the actual - /// operation. - /// - /// NOTE: To transfer fees, this trait needs to be coordinated with - /// `ERC4626Component::ERC4626Hooks`. - /// - /// See the ERC4626FeesMock example: - /// https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/test_common/src/mocks/erc4626.cairo - pub trait FeeConfigTrait> { - /// Adjusts deposits within `preview_deposit` to account for entry fees. - /// Entry fees should be transferred in the `after_deposit` hook. - fn adjust_deposit(self: @ComponentState, assets: u256) -> u256 { - assets - } - - /// Adjusts mints within `preview_mint` to account for entry fees. - /// Entry fees should be transferred in the `after_deposit` hook. - fn adjust_mint(self: @ComponentState, assets: u256) -> u256 { - assets - } - - /// Adjusts withdraws within `preview_withdraw` to account for exit fees. - /// Exit fees should be transferred in the `before_withdraw` hook. - fn adjust_withdraw(self: @ComponentState, assets: u256) -> u256 { - assets - } - - /// Adjusts redeems within `preview_redeem` to account for exit fees. - /// Exit fees should be transferred in the `before_withdraw` hook. - fn adjust_redeem(self: @ComponentState, assets: u256) -> u256 { - assets - } - } - - /// Sets limits to the target exchange type and is expected to be defined at the contract - /// level. - /// - /// It's important to note that these limits correspond directly to the `max_` - /// i.e. `deposit_limit` -> `max_deposit`. - /// - /// The EIP-4626 spec states that the `max_` methods must take into account all - /// global and user-specific limits. - /// If an operation is disabled (even temporarily), the corresponding limit MUST be `0` - /// and MUST NOT panic. - pub trait LimitConfigTrait> { - /// The max deposit allowed. - /// Defaults (`Option::None`) to 2 ** 256 - 1. - fn deposit_limit( - self: @ComponentState, receiver: ContractAddress, - ) -> Option { - Option::None - } - - /// The max mint allowed. - /// Defaults (`Option::None`) to 2 ** 256 - 1. - fn mint_limit( - self: @ComponentState, receiver: ContractAddress, - ) -> Option { - Option::None - } - - /// The max withdraw allowed. - /// Defaults (`Option::None`) to the full asset balance of `owner` converted from shares. - fn withdraw_limit( - self: @ComponentState, owner: ContractAddress, - ) -> Option { - Option::None - } - - /// The max redeem allowed. - /// Defaults (`Option::None`) to the full asset balance of `owner`. - fn redeem_limit( - self: @ComponentState, owner: ContractAddress, - ) -> Option { - Option::None - } - } - - /// Allows contracts to hook logic into deposit and withdraw transactions. - /// This is where contracts can transfer fees. - /// - /// NOTE: ERC4626 preview methods must be inclusive of any entry or exit fees. - /// The `AdjustFeesTrait` will adjust these values accordingly; therefore, - /// fees must be set in the `AdjustFeesTrait` if the using contract enforces - /// entry or exit fees. - /// - /// CAUTION: Special care must be taken when calling external contracts in these hooks. In - /// that case, consider implementing reentrancy protections. For example, in the - /// `withdraw` flow, the `withdraw_limit` is checked *before* the `before_withdraw` hook - /// is invoked. If this hook performs a reentrant call that invokes `withdraw` again, the - /// subsequent check on `withdraw_limit` will be done before the first withdrawal’s core logic - /// (e.g., burning shares and transferring assets) is executed. This could - /// lead to bypassing withdrawal constraints or draining funds. - /// - /// See the example: - /// https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/test_common/src/mocks/erc4626.cairo - pub trait ERC4626HooksTrait> { - /// Hooks into `InternalImpl::_withdraw`. - /// Executes logic before burning shares and transferring assets. - fn before_withdraw(ref self: ComponentState, assets: u256, shares: u256) {} - /// Hooks into `InternalImpl::_withdraw`. - /// Executes logic after burning shares and transferring assets. - fn after_withdraw(ref self: ComponentState, assets: u256, shares: u256) {} - /// Hooks into `InternalImpl::_deposit`. - /// Executes logic before transferring assets and minting shares. - fn before_deposit(ref self: ComponentState, assets: u256, shares: u256) {} - /// Hooks into `InternalImpl::_deposit`. - /// Executes logic after transferring assets and minting shares. - fn after_deposit(ref self: ComponentState, assets: u256, shares: u256) {} - } - - /// Defines how the ERC4626 vault manages its underlying assets. This trait provides the core - /// asset management functionality for the vault, abstracting the actual storage and transfer - /// mechanisms. - /// It enables two primary implementation patterns: - /// - /// 1. **Self-managed assets**: The vault contract holds assets directly on its own address. - /// This is the default behavior provided by `ERC4626SelfAssetsManagement` implementation. - /// - /// 2. **External vault**: Assets are managed by an external contract, allowing - /// for more complex asset management strategies. The exact implementation is expected to be - /// defined by the contract implementing the ERC4626 component. - /// - /// The trait methods are called during deposit, withdrawal, and total assets calculations, - /// ensuring that the vault's share pricing remains accurate regardless of the underlying - /// asset management strategy. - /// - /// CAUTION: Implementations must ensure that `get_total_assets` returns the actual amount - /// of assets that can be withdrawn by users. Inaccurate reporting can lead to incorrect - /// share valuations and potential economic attacks. - /// - /// See the examples: - /// - Self-managed vault: `ERC4626SelfAssetsManagement` at the end of the file. - /// - External vault: `ERC4626ExternalAssetsManagement` in `ERC4626ExternalVaultMock`. - pub trait AssetsManagementTrait> { - /// Returns the total amount of underlying assets under the vault's management. - /// Used for share price calculations and determining the vault's total value. - /// - /// This method should return the actual amount of assets that the vault controls - /// and that can be used to satisfy withdrawal requests. For self-managed vaults, - /// this is typically the vault contract's token balance. For external vaults, - /// this should include any assets deposited in external protocols, minus any - /// that are locked or unredeemable. - /// - /// The accuracy of this method is critical for proper vault operation: - /// - Overreporting can lead to share dilution and user losses. - /// - Underreporting can lead to share inflation and potential economic attacks. - fn get_total_assets(self: @ComponentState) -> u256; - - /// Transfers assets from an external address into the vault's management. - /// Called during `deposit` and `mint` operations. - /// - /// This method should handle the actual transfer of underlying assets from the `from` - /// address into the vault's control. For self-managed vaults, this typically means - /// transferring tokens to the vault contract's address. For external vaults, this - /// might involve transferring into an external contract. - /// - /// Requirements: - /// - /// - Must transfer exactly `assets` amount of the underlying token. - /// - Should revert if the transfer fails or insufficient allowance/balance. - fn transfer_assets_in( - ref self: ComponentState, from: ContractAddress, assets: u256, - ); - - /// Transfers assets from the vault's management to an external address. - /// Called during withdraw and redeem operations. - /// - /// This method should handle the actual transfer of underlying assets from the vault's - /// control to the `to` address. For self-managed vaults, this typically means - /// transferring tokens from the vault contract's address. For external vaults, this - /// might involve withdrawing from an external contract first. - /// - /// Requirements: - /// - /// - Must transfer exactly `assets` amount of the underlying token. - /// - Should revert if insufficient assets are available or transfer fails. - fn transfer_assets_out( - ref self: ComponentState, to: ContractAddress, assets: u256, - ); - } - - // - // External - // - - #[embeddable_as(ERC4626Impl)] - impl ERC4626< - TContractState, - +HasComponent, - impl Fee: FeeConfigTrait, - impl Limit: LimitConfigTrait, - impl Hooks: ERC4626HooksTrait, - impl Immutable: ImmutableConfig, - impl ERC20: ERC20Component::HasComponent, - impl Assets: AssetsManagementTrait, - +ERC20Component::ERC20HooksTrait, - +Drop, - > of IERC4626> { - /// Returns the address of the underlying token used for the Vault for accounting, - /// depositing, and withdrawing. - fn asset(self: @ComponentState) -> ContractAddress { - self.ERC4626_asset.read() - } - - /// Returns the total amount of the underlying asset that is “managed” by Vault. - fn total_assets(self: @ComponentState) -> u256 { - Assets::get_total_assets(self) - } - - /// Returns the amount of shares that the Vault would exchange for the amount of assets - /// provided irrespective of slippage or fees. - /// - /// NOTE: As per the EIP-4626 spec, this may panic _only_ if there's an overflow - /// from an unreasonably large input. - fn convert_to_shares(self: @ComponentState, assets: u256) -> u256 { - self._convert_to_shares(assets, Rounding::Floor) - } - - /// Returns the amount of assets that the Vault would exchange for the amount of shares - /// provided irrespective of slippage or fees. - /// - /// NOTE: As per the EIP-4626 spec, this may panic _only_ if there's an overflow - /// from an unreasonably large input. - fn convert_to_assets(self: @ComponentState, shares: u256) -> u256 { - self._convert_to_assets(shares, Rounding::Floor) - } - - /// Returns the maximum amount of the underlying asset that can be deposited into the Vault - /// for the receiver, through a deposit call. - /// - /// The default max deposit value is 2 ** 256 - 1. - /// This can be changed in the implementing contract by defining custom logic in - /// `LimitConfigTrait::deposit_limit`. - fn max_deposit(self: @ComponentState, receiver: ContractAddress) -> u256 { - Limit::deposit_limit(self, receiver).unwrap_or(Bounded::MAX) - } - - /// Allows an on-chain or off-chain user to simulate the effects of their deposit at the - /// current block, given current on-chain conditions. - /// - /// The default deposit preview value is the full amount of shares. - /// This can be changed to account for fees, for example, in the implementing contract by - /// defining custom logic in `FeeConfigTrait::adjust_deposit`. - /// - /// NOTE: `preview_deposit` must be inclusive of entry fees to be compliant with the - /// EIP-4626 spec. - fn preview_deposit(self: @ComponentState, assets: u256) -> u256 { - let adjusted_assets = Fee::adjust_deposit(self, assets); - self._convert_to_shares(adjusted_assets, Rounding::Floor) - } - - /// Mints Vault shares to `receiver` by depositing exactly `assets` of underlying tokens. - /// Returns the amount of newly-minted shares. - /// - /// Requirements: - /// - /// - `assets` is less than or equal to the max deposit amount for `receiver`. - /// - /// Emits a `Deposit` event. - fn deposit( - ref self: ComponentState, assets: u256, receiver: ContractAddress, - ) -> u256 { - let max_assets = self.max_deposit(receiver); - assert(assets <= max_assets, Errors::EXCEEDED_MAX_DEPOSIT); - - let shares = self.preview_deposit(assets); - let caller = starknet::get_caller_address(); - self._deposit(caller, receiver, assets, shares); - - shares - } - - /// Returns the maximum amount of the Vault shares that can be minted for `receiver` through - /// a `mint` call. - /// - /// The default max mint value is 2 ** 256 - 1. - /// This can be changed in the implementing contract by defining custom logic in - /// `LimitConfigTrait::mint_limit`. - fn max_mint(self: @ComponentState, receiver: ContractAddress) -> u256 { - Limit::mint_limit(self, receiver).unwrap_or(Bounded::MAX) - } - - /// Allows an on-chain or off-chain user to simulate the effects of their mint at the - /// current block, given current on-chain conditions. - /// - /// The default mint preview value is the full amount of assets. - /// This can be changed to account for fees, for example, in the implementing contract by - /// defining custom logic in `FeeConfigTrait::adjust_mint`. - /// - /// NOTE: `preview_mint` must be inclusive of entry fees to be compliant with the EIP-4626 - /// spec. - fn preview_mint(self: @ComponentState, shares: u256) -> u256 { - let full_assets = self._convert_to_assets(shares, Rounding::Ceil); - Fee::adjust_mint(self, full_assets) - } - - /// Mints exactly Vault `shares` to `receiver` by depositing amount of underlying tokens. - /// Returns the amount deposited assets. - /// - /// Requirements: - /// - /// - `shares` is less than or equal to the max shares amount for `receiver`. - /// - /// Emits a `Deposit` event. - fn mint( - ref self: ComponentState, shares: u256, receiver: ContractAddress, - ) -> u256 { - let max_shares = self.max_mint(receiver); - assert(shares <= max_shares, Errors::EXCEEDED_MAX_MINT); - - let assets = self.preview_mint(shares); - let caller = starknet::get_caller_address(); - self._deposit(caller, receiver, assets, shares); - - assets - } - - /// Returns the maximum amount of the underlying asset that can be withdrawn from the owner - /// balance in the Vault, through a `withdraw` call. - /// - /// The default max withdraw value is the full balance of assets for `owner` (converted from - /// shares). - /// This can be changed in the implementing contract by defining custom logic in - /// `LimitConfigTrait::withdraw_limit`. - /// Do note that with customized limits, the maximum withdraw amount will either be - /// the custom limit itself or ``owner``'s total asset balance, whichever value is less. - fn max_withdraw(self: @ComponentState, owner: ContractAddress) -> u256 { - let erc20_component = get_dep_component!(self, ERC20); - let owner_shares = erc20_component.balance_of(owner); - let total_owner_assets = self._convert_to_assets(owner_shares, Rounding::Floor); - - match Limit::withdraw_limit(self, owner) { - Option::Some(limit) => { - if total_owner_assets < limit { - total_owner_assets - } else { - limit - } - }, - Option::None => { total_owner_assets }, - } - } - - /// Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the - /// current block, given current on-chain conditions. - /// - /// The default withdraw preview value is the full amount of shares. - /// This can be changed to account for fees, for example, in the implementing contract by - /// defining custom logic in `FeeConfigTrait::adjust_withdraw`. - /// - /// NOTE: `preview_withdraw` must be inclusive of exit fees to be compliant with the - /// EIP-4626 spec. - fn preview_withdraw(self: @ComponentState, assets: u256) -> u256 { - let adjusted_assets = Fee::adjust_withdraw(self, assets); - self._convert_to_shares(adjusted_assets, Rounding::Ceil) - } - - /// Burns shares from `owner` and sends exactly `assets` of underlying tokens to `receiver`. - /// - /// Requirements: - /// - /// - `assets` is less than or equal to the max withdraw amount of `owner`. - /// - /// Emits a `Withdraw` event. - fn withdraw( - ref self: ComponentState, - assets: u256, - receiver: ContractAddress, - owner: ContractAddress, - ) -> u256 { - let max_assets = self.max_withdraw(owner); - assert(assets <= max_assets, Errors::EXCEEDED_MAX_WITHDRAW); - - let shares = self.preview_withdraw(assets); - let caller = starknet::get_caller_address(); - self._withdraw(caller, receiver, owner, assets, shares); - - shares - } - - /// Returns the maximum amount of Vault shares that can be redeemed from the owner balance - /// in the Vault, through a `redeem` call. - /// - /// The default max redeem value is the full balance of assets for `owner`. - /// This can be changed in the implementing contract by defining custom logic in - /// `LimitConfigTrait::redeem_limit`. - /// Do note that with customized limits, the maximum redeem amount will either be - /// the custom limit itself or ``owner``'s total asset balance, whichever value is less. - fn max_redeem(self: @ComponentState, owner: ContractAddress) -> u256 { - let erc20_component = get_dep_component!(self, ERC20); - let owner_shares = erc20_component.balance_of(owner); - - match Limit::redeem_limit(self, owner) { - Option::Some(limit) => { if owner_shares < limit { - owner_shares - } else { - limit - } }, - Option::None => { owner_shares }, - } - } - - /// Allows an on-chain or off-chain user to simulate the effects of their redemption at the - /// current block, given current on-chain conditions. - /// - /// The default redeem preview value is the full amount of assets. - /// This can be changed to account for fees, for example, in the implementing contract by - /// defining custom logic in `FeeConfigTrait::adjust_redeem`. - /// - /// NOTE: `preview_redeem` must be inclusive of exit fees to be compliant with the EIP-4626 - /// spec. - fn preview_redeem(self: @ComponentState, shares: u256) -> u256 { - let full_assets = self._convert_to_assets(shares, Rounding::Floor); - Fee::adjust_redeem(self, full_assets) - } - - /// Burns exactly `shares` from `owner` and sends assets of underlying tokens to `receiver`. - /// - /// Requirements: - /// - /// - `shares` is less than or equal to the max redeem amount of `owner`. - /// - /// Emits a `Withdraw` event. - fn redeem( - ref self: ComponentState, - shares: u256, - receiver: ContractAddress, - owner: ContractAddress, - ) -> u256 { - let max_shares = self.max_redeem(owner); - assert(shares <= max_shares, Errors::EXCEEDED_MAX_REDEEM); - - let assets = self.preview_redeem(shares); - let caller = starknet::get_caller_address(); - self._withdraw(caller, receiver, owner, assets, shares); - - assets - } - } - - #[embeddable_as(ERC4626MetadataImpl)] - impl ERC4626Metadata< - TContractState, - +HasComponent, - impl Immutable: ImmutableConfig, - impl ERC20: ERC20Component::HasComponent, - > of IERC20Metadata> { - /// Returns the name of the token. - fn name(self: @ComponentState) -> ByteArray { - let erc20_component = get_dep_component!(self, ERC20); - erc20_component.ERC20_name.read() - } - - /// Returns the ticker symbol of the token, usually a shorter version of the name. - fn symbol(self: @ComponentState) -> ByteArray { - let erc20_component = get_dep_component!(self, ERC20); - erc20_component.ERC20_symbol.read() - } - - /// Returns the cumulative number of decimals which includes both the underlying and offset - /// decimals. - /// Both of which must be defined in the `ImmutableConfig` inside the implementing contract. - fn decimals(self: @ComponentState) -> u8 { - Immutable::UNDERLYING_DECIMALS + Immutable::DECIMALS_OFFSET - } - } - - // - // Internal - // - - #[generate_trait] - pub impl InternalImpl< - TContractState, - +HasComponent, - impl Hooks: ERC4626HooksTrait, - impl Immutable: ImmutableConfig, - impl ERC20: ERC20Component::HasComponent, - impl Assets: AssetsManagementTrait, - +FeeConfigTrait, - +LimitConfigTrait, - +ERC20Component::ERC20HooksTrait, - +Drop, - > of InternalTrait { - /// Validates the `ImmutableConfig` constants and sets the `asset_address` to the vault. - /// This should be set in the contract's constructor. - /// - /// Requirements: - /// - /// - `asset_address` cannot be the zero address. - fn initializer(ref self: ComponentState, asset_address: ContractAddress) { - Immutable::validate(); - assert(asset_address.is_non_zero(), Errors::INVALID_ASSET_ADDRESS); - self.ERC4626_asset.write(asset_address); - } - - /// Internal logic for `deposit` and `mint`. - /// Transfers `assets` from `caller` to the Vault contract then mints `shares` to - /// `receiver`. - /// Fees can be transferred in the `ERC4626Hooks::after_deposit` hook which is executed - /// after assets are transferred and shares are minted. - /// - /// Requirements: - /// - /// - `ERC20::transfer_from` must return true. - /// - /// Emits two `ERC20::Transfer` events (`ERC20::mint` and `ERC20::transfer_from`). - /// Emits a `Deposit` event. - fn _deposit( - ref self: ComponentState, - caller: ContractAddress, - receiver: ContractAddress, - assets: u256, - shares: u256, - ) { - // Before deposit hook - Hooks::before_deposit(ref self, assets, shares); - - // Transfer assets first - Assets::transfer_assets_in(ref self, caller, assets); - - // Mint shares after transferring assets - let mut erc20_component = get_dep_component_mut!(ref self, ERC20); - erc20_component.mint(receiver, shares); - self.emit(Deposit { sender: caller, owner: receiver, assets, shares }); - - // After deposit hook - Hooks::after_deposit(ref self, assets, shares); - } - - /// Internal logic for `withdraw` and `redeem`. - /// Burns `shares` from `owner` and then transfers `assets` to `receiver`. - /// Fees can be transferred in the `ERC4626Hooks::before_withdraw` hook which is executed - /// before shares are burned and assets are transferred. - /// - /// Requirements: - /// - /// - `ERC20::transfer` must return true. - /// - /// Emits two `ERC20::Transfer` events (`ERC20::burn` and `ERC20::transfer`). - /// - /// Emits a `Withdraw` event. - fn _withdraw( - ref self: ComponentState, - caller: ContractAddress, - receiver: ContractAddress, - owner: ContractAddress, - assets: u256, - shares: u256, - ) { - // Before withdraw hook - Hooks::before_withdraw(ref self, assets, shares); - - // Burn shares first - let mut erc20_component = get_dep_component_mut!(ref self, ERC20); - if caller != owner { - erc20_component._spend_allowance(owner, caller, shares); - } - erc20_component.burn(owner, shares); - - // Transfer assets after burn - Assets::transfer_assets_out(ref self, receiver, assets); - - self.emit(Withdraw { sender: caller, receiver, owner, assets, shares }); - - // After withdraw hook - Hooks::after_withdraw(ref self, assets, shares); - } - - /// Internal conversion function (from assets to shares) with support for `rounding` - /// direction. - fn _convert_to_shares( - self: @ComponentState, assets: u256, rounding: Rounding, - ) -> u256 { - let erc20_component = get_dep_component!(self, ERC20); - let total_shares = erc20_component.total_supply(); - - math::u256_mul_div( - assets, - total_shares + 10_u256.pow(Immutable::DECIMALS_OFFSET.into()), - self.total_assets() + 1, - rounding, - ) - } - - /// Internal conversion function (from shares to assets) with support for `rounding` - /// direction. - fn _convert_to_assets( - self: @ComponentState, shares: u256, rounding: Rounding, - ) -> u256 { - let erc20_component = get_dep_component!(self, ERC20); - let total_shares = erc20_component.total_supply(); - - math::u256_mul_div( - shares, - self.total_assets() + 1, - total_shares + 10_u256.pow(Immutable::DECIMALS_OFFSET.into()), - rounding, - ) - } - } -} - -// -// Default (empty) traits -// - -use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; -use starknet::ContractAddress; -use starknet::storage::StoragePointerReadAccess; - -pub impl ERC4626HooksEmptyImpl< - TContractState, +ERC4626Component::HasComponent, -> of ERC4626Component::ERC4626HooksTrait {} - -pub impl ERC4626DefaultNoFees< - TContractState, +ERC4626Component::HasComponent, -> of ERC4626Component::FeeConfigTrait {} - -pub impl ERC4626DefaultLimits< - TContractState, +ERC4626Component::HasComponent, -> of ERC4626Component::LimitConfigTrait {} - -pub impl ERC4626SelfAssetsManagement< - TContractState, +ERC4626Component::HasComponent, -> of ERC4626Component::AssetsManagementTrait { - fn get_total_assets(self: @ERC4626Component::ComponentState) -> u256 { - let this = starknet::get_contract_address(); - let asset_dispatcher = IERC20Dispatcher { contract_address: self.ERC4626_asset.read() }; - asset_dispatcher.balance_of(this) - } - - fn transfer_assets_in( - ref self: ERC4626Component::ComponentState, - from: ContractAddress, - assets: u256, - ) { - let this = starknet::get_contract_address(); - let asset_dispatcher = IERC20Dispatcher { contract_address: self.ERC4626_asset.read() }; - assert( - asset_dispatcher.transfer_from(from, this, assets), - ERC4626Component::Errors::TOKEN_TRANSFER_FAILED, - ); - } - - fn transfer_assets_out( - ref self: ERC4626Component::ComponentState, - to: ContractAddress, - assets: u256, - ) { - let asset_dispatcher = IERC20Dispatcher { contract_address: self.ERC4626_asset.read() }; - assert( - asset_dispatcher.transfer(to, assets), ERC4626Component::Errors::TOKEN_TRANSFER_FAILED, - ); - } -} - -pub impl DefaultConfig of ERC4626Component::ImmutableConfig { - const UNDERLYING_DECIMALS: u8 = ERC4626Component::DEFAULT_UNDERLYING_DECIMALS; - const DECIMALS_OFFSET: u8 = ERC4626Component::DEFAULT_DECIMALS_OFFSET; -} diff --git a/packages/vault/src/redeem_request/redeem_request.cairo b/packages/vault/src/redeem_request/redeem_request.cairo index 43c8fa17..acabc1e0 100644 --- a/packages/vault/src/redeem_request/redeem_request.cairo +++ b/packages/vault/src/redeem_request/redeem_request.cairo @@ -4,12 +4,13 @@ #[starknet::contract] mod RedeemRequest { - use openzeppelin::access::accesscontrol::interface::{ + use openzeppelin::interfaces::accesscontrol::{ IAccessControlDispatcher, IAccessControlDispatcherTrait, }; + use openzeppelin::interfaces::upgrades::IUpgradeable; use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc721::extensions::ERC721EnumerableComponent; use openzeppelin::token::erc721::{ERC721Component, ERC721HooksEmptyImpl}; - use openzeppelin::upgrades::interface::IUpgradeable; use openzeppelin::upgrades::upgradeable::UpgradeableComponent; use starknet::storage::{ Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, @@ -22,10 +23,16 @@ mod RedeemRequest { component!(path: SRC5Component, storage: src5, event: SRC5Event); component!(path: ERC721Component, storage: erc721, event: ERC721Event); + component!( + path: ERC721EnumerableComponent, storage: erc721_enumerable, event: ERC721EnumerableEvent, + ); component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); #[abi(embed_v0)] impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl; + #[abi(embed_v0)] + impl ERC721EnumerableImpl = + ERC721EnumerableComponent::ERC721EnumerableImpl; impl ERC721InternalImpl = ERC721Component::InternalImpl; impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; @@ -37,6 +44,8 @@ mod RedeemRequest { #[substorage(v0)] erc721: ERC721Component::Storage, #[substorage(v0)] + erc721_enumerable: ERC721EnumerableComponent::Storage, + #[substorage(v0)] upgradeable: UpgradeableComponent::Storage, id_len: u256, id_to_info: Map, @@ -49,6 +58,8 @@ mod RedeemRequest { #[flat] ERC721Event: ERC721Component::Event, #[flat] + ERC721EnumerableEvent: ERC721EnumerableComponent::Event, + #[flat] SRC5Event: SRC5Component::Event, #[flat] UpgradeableEvent: UpgradeableComponent::Event, diff --git a/packages/vault/src/test/units/redeem_request.cairo b/packages/vault/src/test/units/redeem_request.cairo index c167c338..01e11b49 100644 --- a/packages/vault/src/test/units/redeem_request.cairo +++ b/packages/vault/src/test/units/redeem_request.cairo @@ -2,13 +2,12 @@ // Copyright (c) 2025 Starknet Vault Kit // Licensed under the MIT License. See LICENSE file for details. -use openzeppelin::access::accesscontrol::interface::{ +use openzeppelin::interfaces::accesscontrol::{ IAccessControlDispatcher, IAccessControlDispatcherTrait, }; -use openzeppelin::token::erc721::interface::{ERC721ABIDispatcher, ERC721ABIDispatcherTrait}; -use openzeppelin::upgrades::interface::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; -use snforge_std::{EventSpyAssertionsTrait, spy_events}; -use starknet::{ContractAddress, contract_address_const, get_caller_address}; +use openzeppelin::interfaces::erc721::{ERC721ABIDispatcher, ERC721ABIDispatcherTrait}; +use openzeppelin::interfaces::upgrades::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; +use starknet::ContractAddress; use vault::redeem_request::interface::{ IRedeemRequestDispatcher, IRedeemRequestDispatcherTrait, RedeemRequestInfo, }; @@ -63,7 +62,7 @@ fn test_id_to_info_nonexistent() { #[test] #[should_panic(expected: "Caller is not vault")] fn test_mint_not_vault() { - let (vault_address, redeem_request) = set_up(); + let (_, redeem_request) = set_up(); let redeem_info = RedeemRequestInfo { epoch: 1, nominal: 100 }; redeem_request.mint(DUMMY_ADDRESS(), redeem_info); } @@ -212,7 +211,7 @@ fn test_burn_one_of_multiple() { #[test] #[should_panic(expected: "Caller is not vault owner")] fn test_upgrade_not_vault_owner() { - let (vault_address, redeem_request) = set_up(); + let (_, redeem_request) = set_up(); let (_, counter_class_hash) = deploy_counter(); IUpgradeableDispatcher { contract_address: redeem_request.contract_address } @@ -221,7 +220,7 @@ fn test_upgrade_not_vault_owner() { #[test] fn test_upgrade_success() { - let (vault_address, redeem_request) = set_up(); + let (_, redeem_request) = set_up(); let (_, counter_class_hash) = deploy_counter(); cheat_caller_address_once(redeem_request.contract_address, OWNER()); diff --git a/packages/vault/src/test/units/vault.cairo b/packages/vault/src/test/units/vault.cairo index f1b83278..c02ad023 100644 --- a/packages/vault/src/test/units/vault.cairo +++ b/packages/vault/src/test/units/vault.cairo @@ -2,20 +2,18 @@ // Copyright (c) 2025 Starknet Vault Kit // Licensed under the MIT License. See LICENSE file for details. -use core::num::traits::Zero; -use openzeppelin::access::accesscontrol::interface::{ +use core::num::traits::{Bounded, Zero}; +use openzeppelin::interfaces::accesscontrol::{ IAccessControlDispatcher, IAccessControlDispatcherTrait, }; -use openzeppelin::security::interface::{IPausableDispatcher, IPausableDispatcherTrait}; -use openzeppelin::token::erc20::extensions::erc4626::interface::{ - IERC4626Dispatcher, IERC4626DispatcherTrait, -}; -use openzeppelin::token::erc20::interface::{ +use openzeppelin::interfaces::erc20::{ ERC20ABIDispatcher, ERC20ABIDispatcherTrait, IERC20MetadataDispatcher, IERC20MetadataDispatcherTrait, }; -use openzeppelin::token::erc721::interface::{ERC721ABIDispatcher, ERC721ABIDispatcherTrait}; -use openzeppelin::upgrades::interface::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; +use openzeppelin::interfaces::erc4626::{IERC4626Dispatcher, IERC4626DispatcherTrait}; +use openzeppelin::interfaces::erc721::{ERC721ABIDispatcher, ERC721ABIDispatcherTrait}; +use openzeppelin::interfaces::security::pausable::{IPausableDispatcher, IPausableDispatcherTrait}; +use openzeppelin::interfaces::upgrades::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; use openzeppelin::utils::math; use openzeppelin::utils::math::Rounding; use snforge_std::{ @@ -26,9 +24,9 @@ use starknet::{ContractAddress, get_block_timestamp}; use vault::redeem_request::interface::{IRedeemRequestDispatcher, IRedeemRequestDispatcherTrait}; use vault::test::utils::{ DUMMY_ADDRESS, FEES_RECIPIENT, MANAGEMENT_FEES, MAX_DELTA, ORACLE, OTHER_DUMMY_ADDRESS, OWNER, - PAUSER, PERFORMANCE_FEES, REDEEM_FEES, REPORT_DELAY, USER1, USER2, VAULT_ALLOCATOR, VAULT_NAME, - VAULT_SYMBOL, between, cheat_caller_address_once, deploy_counter, deploy_erc20_mock, - deploy_redeem_request, deploy_vault, + PERFORMANCE_FEES, REDEEM_FEES, REPORT_DELAY, VAULT_ALLOCATOR, VAULT_NAME, VAULT_SYMBOL, between, + cheat_caller_address_once, deploy_counter, deploy_erc20_mock, deploy_redeem_request, + deploy_vault, }; use vault::vault::interface::{IVaultDispatcher, IVaultDispatcherTrait}; use vault::vault::vault::Vault; @@ -1418,7 +1416,7 @@ fn test_report_when_vault_allocator_is_not_set() { let erc4626_dispatcher = IERC4626Dispatcher { contract_address: vault.contract_address }; cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); - let shares = erc4626_dispatcher.deposit(Vault::WAD, DUMMY_ADDRESS()); + erc4626_dispatcher.deposit(Vault::WAD, DUMMY_ADDRESS()); let mut cheat_calldata_vault_allocator = ArrayTrait::new(); cheat_calldata_vault_allocator.append(0); @@ -1454,7 +1452,7 @@ fn test_report_when_fees_recipient_is_not_set() { let erc4626_dispatcher = IERC4626Dispatcher { contract_address: vault.contract_address }; cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); - let shares = erc4626_dispatcher.deposit(Vault::WAD, DUMMY_ADDRESS()); + erc4626_dispatcher.deposit(Vault::WAD, DUMMY_ADDRESS()); let mut cheat_calldata_fees_recipient = ArrayTrait::new(); cheat_calldata_fees_recipient.append(0); @@ -1561,7 +1559,7 @@ fn setup_report_simple_deposit_epoch_0() -> ( let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_buffer + expected_aum - (expected_management_fees_assets) + 1, + expected_buffer + expected_aum - (expected_management_fees_assets + 0) + 1, Rounding::Floor, ); @@ -1665,16 +1663,17 @@ fn setup_report_simple_deposit_with_profit_epoch_1() -> ( * MANAGEMENT_FEES() * REPORT_DELAY().into()) / (Vault::WAD * Vault::YEAR.into()); + let net_profit_after_mgmt = profit_amount - expected_management_fees_assets; + let expected_performance_fee_assets = PERFORMANCE_FEES() * net_profit_after_mgmt / Vault::WAD; let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_total_assets - (expected_management_fees_assets) + 1, + expected_total_assets + - (expected_management_fees_assets + expected_performance_fee_assets) + + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; - - let net_profit_after_mgmt = profit_amount - expected_management_fees_assets; - let expected_performance_fee_assets = PERFORMANCE_FEES() * net_profit_after_mgmt / Vault::WAD; let performance_fee_shares = math::u256_mul_div( expected_performance_fee_assets, expected_total_supply + 1, @@ -1778,7 +1777,7 @@ fn setup_report_simple_deposit_with_loss_epoch_1() -> ( let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - (expected_total_assets - expected_management_fees_assets) + 1, + (expected_total_assets - (expected_management_fees_assets + 0)) + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; @@ -1895,19 +1894,21 @@ fn setup_report_simple_redeem_unhandled_with_profit_epoch_1() -> ( let expected_total_assets = liqudity_after - expected_redeem_assets_after_cut_epoch_1; + let management_fee_assets_for_shareholders = expected_management_fees_assets + - (expected_nominal - expected_redeem_assets_after_cut_epoch_1); + + let net_profit_after_mgmt = profit_amount - management_fee_assets_for_shareholders; + let expected_performance_fee_assets = PERFORMANCE_FEES() * net_profit_after_mgmt / Vault::WAD; + let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_total_assets - (expected_management_fees_assets) + 1, + expected_total_assets + - (expected_management_fees_assets + expected_performance_fee_assets) + + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; - - let management_fee_assets_for_shareholders = expected_management_fees_assets - - (expected_nominal - expected_redeem_assets_after_cut_epoch_1); - - let net_profit_after_mgmt = profit_amount - management_fee_assets_for_shareholders; - let expected_performance_fee_assets = PERFORMANCE_FEES() * net_profit_after_mgmt / Vault::WAD; let performance_fee_shares = math::u256_mul_div( expected_performance_fee_assets, expected_total_supply + 1, @@ -2046,19 +2047,21 @@ fn setup_report_simple_redeem_matched_with_profit_epoch_1() -> ( let expected_total_assets = liqudity_after - expected_redeem_assets_after_cut_epoch_1; + let management_fee_assets_for_shareholders = expected_management_fees_assets + - (expected_nominal - expected_redeem_assets_after_cut_epoch_1); + + let net_profit_after_mgmt = profit_amount - management_fee_assets_for_shareholders; + let expected_performance_fee_assets = PERFORMANCE_FEES() * net_profit_after_mgmt / Vault::WAD; + let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_total_assets - (expected_management_fees_assets) + 1, + expected_total_assets + - (expected_management_fees_assets + expected_performance_fee_assets) + + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; - - let management_fee_assets_for_shareholders = expected_management_fees_assets - - (expected_nominal - expected_redeem_assets_after_cut_epoch_1); - - let net_profit_after_mgmt = profit_amount - management_fee_assets_for_shareholders; - let expected_performance_fee_assets = PERFORMANCE_FEES() * net_profit_after_mgmt / Vault::WAD; let performance_fee_shares = math::u256_mul_div( expected_performance_fee_assets, expected_total_supply + 1, @@ -2192,19 +2195,21 @@ fn setup_report_simple_redeem_handled_with_bring_liquidity_with_profit_epoch_1() let expected_total_assets = liqudity_after - expected_redeem_assets_after_cut_epoch_1; + let management_fee_assets_for_shareholders = expected_management_fees_assets + - (expected_nominal - expected_redeem_assets_after_cut_epoch_1); + + let net_profit_after_mgmt = profit_amount - management_fee_assets_for_shareholders; + let expected_performance_fee_assets = PERFORMANCE_FEES() * net_profit_after_mgmt / Vault::WAD; + let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_total_assets - (expected_management_fees_assets) + 1, + expected_total_assets + - (expected_management_fees_assets + expected_performance_fee_assets) + + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; - - let management_fee_assets_for_shareholders = expected_management_fees_assets - - (expected_nominal - expected_redeem_assets_after_cut_epoch_1); - - let net_profit_after_mgmt = profit_amount - management_fee_assets_for_shareholders; - let expected_performance_fee_assets = PERFORMANCE_FEES() * net_profit_after_mgmt / Vault::WAD; let performance_fee_shares = math::u256_mul_div( expected_performance_fee_assets, expected_total_supply + 1, @@ -2338,19 +2343,21 @@ fn setup_report_simple_redeem_not_handled_with_bring_liquidity_with_profit_epoch let expected_total_assets = liqudity_after - expected_redeem_assets_after_cut_epoch_1; + let management_fee_assets_for_shareholders = expected_management_fees_assets + - (expected_nominal - expected_redeem_assets_after_cut_epoch_1); + + let net_profit_after_mgmt = profit_amount - management_fee_assets_for_shareholders; + let expected_performance_fee_assets = PERFORMANCE_FEES() * net_profit_after_mgmt / Vault::WAD; + let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_total_assets - (expected_management_fees_assets) + 1, + expected_total_assets + - (expected_management_fees_assets + expected_performance_fee_assets) + + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; - - let management_fee_assets_for_shareholders = expected_management_fees_assets - - (expected_nominal - expected_redeem_assets_after_cut_epoch_1); - - let net_profit_after_mgmt = profit_amount - management_fee_assets_for_shareholders; - let expected_performance_fee_assets = PERFORMANCE_FEES() * net_profit_after_mgmt / Vault::WAD; let performance_fee_shares = math::u256_mul_div( expected_performance_fee_assets, expected_total_supply + 1, @@ -2488,11 +2495,14 @@ fn setup_report_simple_redeem_unhandled_with_loss_epoch_1() -> ( - cut; let expected_total_assets = liqudity_after - expected_redeem_assets_after_cut_epoch_1; + let expected_performance_fee_assets = 0; let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_total_assets - (expected_management_fees_assets) + 1, + expected_total_assets + - (expected_management_fees_assets + expected_performance_fee_assets) + + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; @@ -2637,11 +2647,14 @@ fn setup_report_simple_redeem_unhandled_not_enough_buffer_with_loss_epoch_1() -> - cut; let expected_total_assets = liqudity_after - expected_redeem_assets_after_cut_epoch_1; + let expected_performance_fee_assets = 0; let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_total_assets - (expected_management_fees_assets) + 1, + expected_total_assets + - (expected_management_fees_assets + expected_performance_fee_assets) + + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; @@ -2822,11 +2835,14 @@ fn setup_report_simple_redeem_unhandled_not_enough_buffer_with_loss_epoch_2_hand let expected_total_assets = liqudity_after - (expected_redeem_assets_after_cut_epoch_1 + expected_redeem_assets_after_cut_epoch_2); + let expected_performance_fee_assets = 0; let management_fee_shares = math::u256_mul_div( expected_management_fees_assets, expected_total_supply + 1, - expected_total_assets - (expected_management_fees_assets) + 1, + expected_total_assets + - (expected_management_fees_assets + expected_performance_fee_assets) + + 1, Rounding::Floor, ); expected_total_supply = expected_total_supply + management_fee_shares; @@ -2880,3 +2896,144 @@ fn setup_report_simple_redeem_unhandled_not_enough_buffer_with_loss_epoch_2_hand fn test_report_simple_redeem_unhandled_not_enough_buffer_with_loss_epoch_2_handled_epoch_1() { setup_report_simple_redeem_unhandled_not_enough_buffer_with_loss_epoch_2_handled_epoch_1(); } + +#[test] +fn test_deposit_limit() { + let (underlying, vault, _) = set_up(); + let erc4626_dispatcher = IERC4626Dispatcher { contract_address: vault.contract_address }; + let deposit_cap = Vault::WAD * 100; + cheat_caller_address_once(vault.contract_address, OWNER()); + vault.set_deposit_limit(deposit_cap); + assert(vault.get_deposit_limit() == deposit_cap, 'Deposit limit not set'); + assert( + erc4626_dispatcher.max_deposit(DUMMY_ADDRESS()) == deposit_cap, 'Initial max deposit wrong', + ); + let first_deposit = Vault::WAD * 30; + cheat_caller_address_once(underlying, OWNER()); + ERC20ABIDispatcher { contract_address: underlying }.transfer(DUMMY_ADDRESS(), first_deposit); + cheat_caller_address_once(underlying, DUMMY_ADDRESS()); + ERC20ABIDispatcher { contract_address: underlying } + .approve(vault.contract_address, first_deposit); + cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); + erc4626_dispatcher.deposit(first_deposit, DUMMY_ADDRESS()); + let expected_remaining = deposit_cap - first_deposit; + assert( + erc4626_dispatcher.max_deposit(DUMMY_ADDRESS()) == expected_remaining, + 'Max deposit not reduced', + ); + cheat_caller_address_once(vault.contract_address, OWNER()); + vault.set_deposit_limit(Bounded::MAX); + assert(vault.get_deposit_limit() == Bounded::MAX, 'Unlimited not set'); + assert( + erc4626_dispatcher.max_deposit(DUMMY_ADDRESS()) == Bounded::MAX, + 'Max deposit not unlimited', + ); +} + +#[test] +fn test_mint_limit() { + let (underlying, vault, _) = set_up(); + let erc4626_dispatcher = IERC4626Dispatcher { contract_address: vault.contract_address }; + let deposit_cap = Vault::WAD * 100; + cheat_caller_address_once(vault.contract_address, OWNER()); + vault.set_deposit_limit(deposit_cap); + let mint_limit_config = Vault::WAD * 50; + cheat_caller_address_once(vault.contract_address, OWNER()); + vault.set_mint_limit(mint_limit_config); + assert(vault.get_mint_limit() == mint_limit_config, 'Mint limit not set'); + let initial_max_mint = erc4626_dispatcher.max_mint(DUMMY_ADDRESS()); + assert(initial_max_mint == deposit_cap, 'Initial max mint wrong'); + let first_deposit = Vault::WAD * 30; + cheat_caller_address_once(underlying, OWNER()); + ERC20ABIDispatcher { contract_address: underlying }.transfer(DUMMY_ADDRESS(), first_deposit); + cheat_caller_address_once(underlying, DUMMY_ADDRESS()); + ERC20ABIDispatcher { contract_address: underlying } + .approve(vault.contract_address, first_deposit); + cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); + erc4626_dispatcher.deposit(first_deposit, DUMMY_ADDRESS()); + let remaining_deposit_cap = deposit_cap - first_deposit; // 70 WAD remaining + let expected_max_mint = erc4626_dispatcher.convert_to_shares(remaining_deposit_cap); + let actual_max_mint = erc4626_dispatcher.max_mint(DUMMY_ADDRESS()); + assert(actual_max_mint == expected_max_mint, 'Max mint not adjusted'); + cheat_caller_address_once(vault.contract_address, OWNER()); + vault.set_deposit_limit(Bounded::MAX); + cheat_caller_address_once(vault.contract_address, OWNER()); + vault.set_mint_limit(Bounded::MAX); + + assert(erc4626_dispatcher.max_mint(DUMMY_ADDRESS()) == Bounded::MAX, 'Max mint not unlimited'); +} + + +#[test] +#[should_panic(expected: ('Caller is missing role',))] +fn test_set_deposit_limit_unauthorized() { + let (_, vault, _) = set_up(); + + cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); + vault.set_deposit_limit(Vault::WAD); +} + +#[test] +#[should_panic(expected: ('Caller is missing role',))] +fn test_set_mint_limit_unauthorized() { + let (_, vault, _) = set_up(); + + cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); + vault.set_mint_limit(Vault::WAD); +} + + +#[test] +fn test_deposit_with_limit() { + let (underlying, vault, _) = set_up(); + let erc4626_dispatcher = IERC4626Dispatcher { contract_address: vault.contract_address }; + + let deposit_limit = Vault::WAD; + cheat_caller_address_once(vault.contract_address, OWNER()); + vault.set_deposit_limit(deposit_limit); + + cheat_caller_address_once(underlying, OWNER()); + ERC20ABIDispatcher { contract_address: underlying }.transfer(DUMMY_ADDRESS(), Vault::WAD * 2); + cheat_caller_address_once(underlying, DUMMY_ADDRESS()); + ERC20ABIDispatcher { contract_address: underlying } + .approve(vault.contract_address, Vault::WAD * 2); + + cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); + erc4626_dispatcher.deposit(deposit_limit, DUMMY_ADDRESS()); + + assert( + ERC20ABIDispatcher { contract_address: vault.contract_address } + .balance_of(DUMMY_ADDRESS()) > 0, + 'Deposit failed', + ); +} + +#[test] +#[should_panic(expected: 'ERC4626: exceeds max deposit')] +fn test_deposit_exceeds_limit() { + let (underlying, vault, _) = set_up(); + let erc4626_dispatcher = IERC4626Dispatcher { contract_address: vault.contract_address }; + + let deposit_limit = Vault::WAD; + cheat_caller_address_once(vault.contract_address, OWNER()); + vault.set_deposit_limit(deposit_limit); + + cheat_caller_address_once(underlying, OWNER()); + ERC20ABIDispatcher { contract_address: underlying }.transfer(DUMMY_ADDRESS(), Vault::WAD * 2); + cheat_caller_address_once(underlying, DUMMY_ADDRESS()); + ERC20ABIDispatcher { contract_address: underlying } + .approve(vault.contract_address, Vault::WAD * 2); + + cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); + erc4626_dispatcher.deposit(deposit_limit + 1, DUMMY_ADDRESS()); +} + +#[test] +#[should_panic(expected: "Caller is not the vault allocator")] +fn test_bring_liquidity_unauthorized() { + let (_underlying, vault, _) = set_up(); + + // Try to call bring_liquidity from unauthorized address + cheat_caller_address_once(vault.contract_address, DUMMY_ADDRESS()); + vault.bring_liquidity(1000); +} diff --git a/packages/vault/src/vault/errors.cairo b/packages/vault/src/vault/errors.cairo index 01ee5f24..4d9d9bf9 100644 --- a/packages/vault/src/vault/errors.cairo +++ b/packages/vault/src/vault/errors.cairo @@ -66,4 +66,8 @@ pub mod Errors { pub fn invalid_new_aum(new_aum: u256) { panic!("Invalid new AUM: {}", new_aum); } + + pub fn caller_is_not_vault_allocator() { + panic!("Caller is not the vault allocator"); + } } diff --git a/packages/vault/src/vault/interface.cairo b/packages/vault/src/vault/interface.cairo index ebbc85e8..0afcf2cc 100644 --- a/packages/vault/src/vault/interface.cairo +++ b/packages/vault/src/vault/interface.cairo @@ -44,5 +44,13 @@ pub trait IVault { fn vault_allocator(self: @TContractState) -> ContractAddress; fn last_report_timestamp(self: @TContractState) -> u64; fn max_delta(self: @TContractState) -> u256; + fn due_assets_from_id(self: @TContractState, id: u256) -> u256; + fn due_assets_from_owner(self: @TContractState, owner: ContractAddress) -> u256; + + // Limit configuration functions + fn set_deposit_limit(ref self: TContractState, limit: u256); + fn set_mint_limit(ref self: TContractState, limit: u256); + fn get_deposit_limit(self: @TContractState) -> u256; + fn get_mint_limit(self: @TContractState) -> u256; } diff --git a/packages/vault/src/vault/vault.cairo b/packages/vault/src/vault/vault.cairo index d0b95a89..3963c0d5 100644 --- a/packages/vault/src/vault/vault.cairo +++ b/packages/vault/src/vault/vault.cairo @@ -19,18 +19,25 @@ #[starknet::contract] pub mod Vault { - use core::num::traits::Zero; + use core::num::traits::{Zero, Bounded}; use openzeppelin::access::accesscontrol::AccessControlComponent; + use openzeppelin::interfaces::erc20::{ + ERC20ABIDispatcher, ERC20ABIDispatcherTrait, IERC20Metadata, + }; + use openzeppelin::interfaces::erc721::{ + ERC721ABIDispatcher, ERC721ABIDispatcherTrait, IERC721EnumerableDispatcher, + IERC721EnumerableDispatcherTrait, + }; + use openzeppelin::interfaces::upgrades::IUpgradeable; use openzeppelin::introspection::src5::SRC5Component; use openzeppelin::security::pausable::PausableComponent; - use openzeppelin::token::erc20::interface::{ - ERC20ABIDispatcher, ERC20ABIDispatcherTrait, IERC20Metadata, + use openzeppelin::token::erc20::extensions::erc4626::ERC4626Component::Fee; + use openzeppelin::token::erc20::extensions::erc4626::{ + DefaultConfig, ERC4626Component, ERC4626DefaultNoFees, }; use openzeppelin::token::erc20::{ DefaultConfig as ERC20DefaultConfig, ERC20Component, ERC20HooksEmptyImpl, }; - use openzeppelin::token::erc721::interface::{ERC721ABIDispatcher, ERC721ABIDispatcherTrait}; - use openzeppelin::upgrades::interface::IUpgradeable; use openzeppelin::upgrades::upgradeable::UpgradeableComponent; use openzeppelin::utils::math; use openzeppelin::utils::math::Rounding; @@ -39,9 +46,6 @@ pub mod Vault { StoragePointerWriteAccess, }; use starknet::{ContractAddress, get_block_timestamp, get_caller_address}; - use vault::oz_4626::{ - DefaultConfig, ERC4626Component, ERC4626DefaultLimits, ERC4626DefaultNoFees, - }; use vault::redeem_request::interface::{ IRedeemRequestDispatcher, IRedeemRequestDispatcherTrait, RedeemRequestInfo, }; @@ -83,6 +87,72 @@ pub mod Vault { impl ERC4626Impl = ERC4626Component::ERC4626Impl; impl ERC4626InternalImpl = ERC4626Component::InternalImpl; + // --- Custom ERC4626 Limits Implementation --- + // Custom implementation of deposit/withdraw limits + // Uses max u256 as sentinel value for "unlimited" + impl ERC4626LimitConfigImpl of ERC4626Component::LimitConfigTrait { + /// The max deposit allowed based on cap. + /// Returns remaining capacity: cap - total_assets + /// Returns None for no limit (when limit equals max u256) + fn deposit_limit( + self: @ERC4626Component::ComponentState, receiver: ContractAddress, + ) -> Option { + let contract_state = self.get_contract(); + let limit = contract_state.deposit_limit.read(); + if limit == Bounded::MAX { + Option::None + } else { + let total_assets = self.get_total_assets(); + if total_assets >= limit { + Option::Some(0) + } else { + Option::Some(limit - total_assets) + } + } + } + + /// The max mint allowed based on cap. + /// Returns shares equivalent of remaining deposit capacity + /// Returns None for no limit (when limit equals max u256) + fn mint_limit( + self: @ERC4626Component::ComponentState, receiver: ContractAddress, + ) -> Option { + let contract_state = self.get_contract(); + let limit = contract_state.mint_limit.read(); + if limit == Bounded::MAX { + Option::None + } else { + let deposit_limit_opt = self.deposit_limit(receiver); + match deposit_limit_opt { + Option::None => Option::None, + Option::Some(deposit_remaining) => { + if deposit_remaining == 0 { + Option::Some(0) + } else { + let shares = self._convert_to_shares(deposit_remaining, Rounding::Floor); + Option::Some(shares) + } + } + } + } + } + + /// The max withdraw allowed. + /// Not implemented - withdrawals are disabled in this vault + fn withdraw_limit( + self: @ERC4626Component::ComponentState, owner: ContractAddress, + ) -> Option { + Option::None + } + + /// The max redeem allowed. + fn redeem_limit( + self: @ERC4626Component::ComponentState, owner: ContractAddress, + ) -> Option { + Option::None + } + } + // --- ERC20 Implementation --- // Share token functionality with standard and camelCase interfaces #[abi(embed_v0)] @@ -141,7 +211,11 @@ pub mod Vault { management_fees: u256, // Annual management fee rate (in WAD) performance_fees: u256, // Performance fee on profits (in WAD) // --- Redemption System --- - redeem_request: IRedeemRequestDispatcher // NFT contract for tracking redemption requests + redeem_request: IRedeemRequestDispatcher, // NFT contract for tracking redemption requests + // --- ERC4626 Limits --- + // Note: max u256 means unlimited, any other value (including 0) sets a specific limit + deposit_limit: u256, // Maximum deposit amount + mint_limit: u256, // Maximum mint amount } // --- Events --- @@ -158,7 +232,8 @@ pub mod Vault { // Vault-specific events RedeemRequested: RedeemRequested, // Emitted when a redemption is requested RedeemClaimed: RedeemClaimed, // Emitted when a redemption is claimed - Report: Report // Emitted when oracle reports new AUM + Report: Report, // Emitted when oracle reports new AUM + BringLiquidity: BringLiquidity // Emitted when liquidity is brought back from allocators } /// Event emitted when a user requests a redemption @@ -193,6 +268,16 @@ pub mod Vault { pub performance_fee_shares: u256 // Performance fee shares minted } + /// Event emitted when liquidity is brought back from allocators + #[derive(Drop, starknet::Event)] + pub struct BringLiquidity { + pub caller: ContractAddress, // Address that initiated the liquidity transfer + pub amount: u256, // Amount of assets brought back to vault + pub new_buffer: u256, // New buffer amount after the transfer + pub new_aum: u256, // New AUM amount after the transfer + pub epoch: u256 // Epoch when the liquidity was brought back + } + /// Initialize the vault with configuration parameters /// Sets up all components, roles, and fee structure #[constructor] @@ -238,6 +323,23 @@ pub mod Vault { // Initialize timestamp for fee calculations self.last_report_timestamp.write(get_block_timestamp()); + + // Initialize limits to max u256 (unlimited) + // max u256 is used as sentinel value for "no limit" + let max_limit: u256 = Bounded::MAX; + self.deposit_limit.write(max_limit); + self.mint_limit.write(max_limit); + self + .emit( + Report { + new_epoch: 0, + new_handled_epoch_len: 0, + total_supply: 0, + total_assets: 0, + management_fee_shares: 0, + performance_fee_shares: 0, + }, + ); } // --- Contract Upgradeability --- @@ -308,30 +410,55 @@ pub mod Vault { /// Hook executed before burning shares and transferring assets during withdrawal /// Direct withdrawals are not supported - users must use epoched redemption system fn before_withdraw( - ref self: ERC4626Component::ComponentState, assets: u256, shares: u256, + ref self: ERC4626Component::ComponentState, + caller: ContractAddress, + receiver: ContractAddress, + owner: ContractAddress, + assets: u256, + shares: u256, + fee: Option, ) { Errors::not_implemented(); // Withdrawals disabled - use request_redeem instead } + /// Hook executed after burning shares and transferring assets during withdrawal /// No additional logic needed since withdrawals are disabled fn after_withdraw( - ref self: ERC4626Component::ComponentState, assets: u256, shares: u256, + ref self: ERC4626Component::ComponentState, + caller: ContractAddress, + receiver: ContractAddress, + owner: ContractAddress, + assets: u256, + shares: u256, + fee: Option, ) {} + /// Hook executed before transferring assets and minting shares during deposit /// Ensures vault is not paused before accepting deposits fn before_deposit( - ref self: ERC4626Component::ComponentState, assets: u256, shares: u256, + ref self: ERC4626Component::ComponentState, + caller: ContractAddress, + receiver: ContractAddress, + assets: u256, + shares: u256, + fee: Option, ) { let mut contract_state = self.get_contract_mut(); contract_state.pausable.assert_not_paused(); // Prevent deposits when paused } + /// Hook executed after transferring assets and minting shares during deposit /// Updates buffer to track assets available for immediate use fn after_deposit( - ref self: ERC4626Component::ComponentState, assets: u256, shares: u256, + ref self: ERC4626Component::ComponentState, + caller: ContractAddress, + receiver: ContractAddress, + assets: u256, + shares: u256, + fee: Option, ) { let mut contract_state = self.get_contract_mut(); contract_state.buffer.write(contract_state.buffer.read() + assets); @@ -698,7 +825,7 @@ pub mod Vault { let management_fee_shares = math::u256_mul_div( management_fee_assets, total_supply + 1, - (total_assets - management_fee_assets) + 1, + (total_assets - (management_fee_assets + performance_fee_assets)) + 1, Rounding::Floor, ); if (management_fee_shares.is_non_zero()) { @@ -737,10 +864,24 @@ pub mod Vault { fn bring_liquidity( ref self: ContractState, amount: u256, ) { // Amount of assets to bring back + let caller = get_caller_address(); + // Only the registered vault allocator can bring liquidity + if (caller != self.vault_allocator.read()) { + Errors::caller_is_not_vault_allocator(); + } ERC20ABIDispatcher { contract_address: self.erc4626.asset() } - .transfer_from(get_caller_address(), starknet::get_contract_address(), amount); - self.buffer.write(self.buffer.read() + amount); // Increase buffer - self.aum.write(self.aum.read() - amount); // Decrease deployed AUM + .transfer_from(caller, starknet::get_contract_address(), amount); + let new_buffer = self.buffer.read() + amount; // Calculate new buffer + let new_aum = self.aum.read() - amount; // Calculate new AUM + self.buffer.write(new_buffer); // Increase buffer + self.aum.write(new_aum); // Decrease deployed AUM + + self + .emit( + BringLiquidity { + caller, amount, new_buffer, new_aum, epoch: self.epoch.read(), + }, + ); } // --- State Getter Functions --- @@ -821,6 +962,60 @@ pub mod Vault { fn max_delta(self: @ContractState) -> u256 { self.max_delta.read() } + + // --- Limit Configuration Functions --- + + /// Set the deposit limit (max u256 for unlimited, any other value including 0 for specific limit) + /// Only callable by owner + fn set_deposit_limit(ref self: ContractState, limit: u256) { + self.access_control.assert_only_role(OWNER_ROLE); + self.deposit_limit.write(limit); + } + + /// Set the mint limit (max u256 for unlimited, any other value including 0 for specific limit) + /// Only callable by owner + fn set_mint_limit(ref self: ContractState, limit: u256) { + self.access_control.assert_only_role(OWNER_ROLE); + self.mint_limit.write(limit); + } + + /// Get the current deposit limit (max u256 means unlimited) + fn get_deposit_limit(self: @ContractState) -> u256 { + self.deposit_limit.read() + } + + /// Get the current mint limit (max u256 means unlimited) + fn get_mint_limit(self: @ContractState) -> u256 { + self.mint_limit.read() + } + + fn due_assets_from_owner(self: @ContractState, owner: ContractAddress) -> u256 { + let balance = ERC721ABIDispatcher { + contract_address: self.redeem_request.read().contract_address, + } + .balance_of(owner); + let mut total_due_assets = 0; + for i in 0..balance { + total_due_assets += self + .due_assets_from_id( + IERC721EnumerableDispatcher { + contract_address: self.redeem_request.read().contract_address, + } + .token_of_owner_by_index(owner, i), + ); + } + total_due_assets + } + + + fn due_assets_from_id(self: @ContractState, id: u256) -> u256 { + let redeem_request_info = self.redeem_request.read().id_to_info(id); + let redeem_request_nominal = redeem_request_info + .nominal; // Original asset amount requested + let redeem_assets = self.redeem_assets.read(redeem_request_info.epoch); + let redeem_nominal = self.redeem_nominal.read(redeem_request_info.epoch); + (redeem_request_nominal * redeem_assets) / redeem_nominal + } } // --- Internal Helper Functions --- diff --git a/packages/vault_allocator/Scarb.toml b/packages/vault_allocator/Scarb.toml index 54f0bf30..1541c1fc 100644 --- a/packages/vault_allocator/Scarb.toml +++ b/packages/vault_allocator/Scarb.toml @@ -29,6 +29,13 @@ name = "SSCL" url = "https://starknet-mainnet.public.blastapi.io/rpc/v0_8" block_id.number = "1784627" +[[tool.snforge.fork]] +name = "SLLSE" +url = "https://starknet-mainnet.public.blastapi.io/rpc/v0_8" +block_id.number = "1794270" + + + @@ -39,7 +46,6 @@ block_id.number = "1784627" starknet.workspace = true openzeppelin.workspace = true alexandria_math.workspace = true -pragma_lib.workspace = true [dev-dependencies] diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/avnu_exchange_decoder_and_sanitizer/avnu_exchange_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/avnu_exchange_decoder_and_sanitizer/avnu_exchange_decoder_and_sanitizer.cairo index d891bb0e..da60c7b4 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/avnu_exchange_decoder_and_sanitizer/avnu_exchange_decoder_and_sanitizer.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/avnu_exchange_decoder_and_sanitizer/avnu_exchange_decoder_and_sanitizer.cairo @@ -9,9 +9,7 @@ pub mod AvnuExchangeDecoderAndSanitizerComponent { use vault_allocator::decoders_and_sanitizers::decoder_custom_types::Route; #[storage] - pub struct Storage { - pub vault_allocator: ContractAddress, - } + pub struct Storage {} #[event] #[derive(Drop, Debug, PartialEq, starknet::Event)] diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/base_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/base_decoder_and_sanitizer.cairo index 63304d20..62cc34bb 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/base_decoder_and_sanitizer.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/base_decoder_and_sanitizer.cairo @@ -28,18 +28,9 @@ pub mod BaseDecoderAndSanitizerComponent { serialized_struct.span() } - fn flash_loan( - self: @ComponentState, - receiver: ContractAddress, - asset: ContractAddress, - amount: u256, - is_legacy: bool, - data: Span, - ) -> Span { + + fn bring_liquidity(self: @ComponentState, amount: u256) -> Span { let mut serialized_struct: Array = ArrayTrait::new(); - receiver.serialize(ref serialized_struct); - asset.serialize(ref serialized_struct); - is_legacy.serialize(ref serialized_struct); serialized_struct.span() } } diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/decoder_custom_types.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/decoder_custom_types.cairo index 1a4b60a6..8cb7edf8 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/decoder_custom_types.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/decoder_custom_types.cairo @@ -39,21 +39,6 @@ pub struct UnsignedAmount { pub value: u256, } -#[derive(PartialEq, Copy, Drop, Serde)] -pub struct TransferPositionParams { - pub pool_id: felt252, - pub from_collateral_asset: ContractAddress, - pub from_debt_asset: ContractAddress, - pub to_collateral_asset: ContractAddress, - pub to_debt_asset: ContractAddress, - pub from_user: ContractAddress, - pub to_user: ContractAddress, - pub collateral: UnsignedAmount, - pub debt: UnsignedAmount, - pub from_data: Span, - pub to_data: Span, -} - #[derive(PartialEq, Copy, Drop, Serde, Default)] pub struct Amount { pub amount_type: AmountType, @@ -61,6 +46,12 @@ pub struct Amount { pub value: i257, } +#[derive(PartialEq, Copy, Drop, Serde, Default)] +pub struct AmountV2 { + pub denomination: AmountDenomination, + pub value: i257, +} + #[derive(PartialEq, Copy, Drop, Serde)] pub struct ModifyPositionParams { pub pool_id: felt252, @@ -71,3 +62,91 @@ pub struct ModifyPositionParams { pub debt: Amount, pub data: Span, } + +#[derive(PartialEq, Copy, Drop, Serde)] +pub struct ModifyPositionParamsV2 { + pub collateral_asset: ContractAddress, + pub debt_asset: ContractAddress, + pub user: ContractAddress, + pub collateral: AmountV2, + pub debt: AmountV2, +} + + +#[derive(Serde, Drop, Clone)] +pub enum ModifyLeverAction { + IncreaseLever: IncreaseLeverParams, + DecreaseLever: DecreaseLeverParams, +} + +#[derive(Serde, Drop, Clone)] +pub struct ModifyLeverParams { + pub action: ModifyLeverAction, +} + + +#[derive(Serde, Drop, Clone)] +pub struct IncreaseLeverParams { + pub pool_id: felt252, + pub collateral_asset: ContractAddress, + pub debt_asset: ContractAddress, + pub user: ContractAddress, + pub add_margin: u128, + pub margin_swap: Array, + pub margin_swap_limit_amount: u128, + pub lever_swap: Array, + pub lever_swap_limit_amount: u128, +} + +#[derive(Serde, Drop, Clone)] +pub struct DecreaseLeverParams { + pub pool_id: felt252, + pub collateral_asset: ContractAddress, + pub debt_asset: ContractAddress, + pub user: ContractAddress, + pub sub_margin: u128, + pub recipient: ContractAddress, + pub lever_swap: Array, + pub lever_swap_limit_amount: u128, + pub lever_swap_weights: Array, + pub withdraw_swap: Array, + pub withdraw_swap_limit_amount: u128, + pub withdraw_swap_weights: Array, + pub close_position: bool, +} + +#[derive(Serde, Drop, Clone)] +pub struct Swap { + pub route: Array, + pub token_amount: TokenAmount, +} + + +#[derive(Serde, Copy, Drop)] +pub struct RouteNode { + pub pool_key: PoolKey, + pub sqrt_ratio_limit: u256, + pub skip_ahead: u128, +} + + +#[derive(Serde, Copy, Drop)] +pub struct TokenAmount { + pub token: ContractAddress, + pub amount: i129, +} + +#[derive(Copy, Drop, Serde, PartialEq, Hash)] +pub struct PoolKey { + pub token0: ContractAddress, + pub token1: ContractAddress, + pub fee: u128, + pub tick_spacing: u128, + pub extension: ContractAddress, +} + +#[derive(Copy, Drop, Serde, Debug)] +pub struct i129 { + pub mag: u128, + pub sign: bool, +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/defi_spring_snf_style/defi_spring_snf_style_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/defi_spring_snf_style/defi_spring_snf_style_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..7ac5067b --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/defi_spring_snf_style/defi_spring_snf_style_decoder_and_sanitizer.cairo @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +// Helps claim rewards from Defi spring rewards and any possible rewards +// with similar claim contract structure +// This is called as SNFStyle because there can be other reward contracts (e.g. Ekubo) +// This is the default contract structure by SNF for rewards + +#[starknet::component] +pub mod DefiSpringSNFStyleDecoderAndSanitizerComponent { + use vault_allocator::decoders_and_sanitizers::defi_spring_snf_style::interface::IDefiSpringSNFStyleDecoderAndSanitizer; + + #[storage] + pub struct Storage {} + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + #[embeddable_as(DefiSpringSNFStyleDecoderAndSanitizerImpl)] + impl DefiSpringSNFStyleDecoderAndSanitizer< + TContractState, +HasComponent, + > of IDefiSpringSNFStyleDecoderAndSanitizer> { + fn claim( + self: @ComponentState, + amount: u128, + proof: Span, + ) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + serialized_struct.span() + } + } +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/defi_spring_snf_style/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/defi_spring_snf_style/interface.cairo new file mode 100644 index 00000000..92609cfe --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/defi_spring_snf_style/interface.cairo @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::interface] +pub trait IDefiSpringSNFStyleDecoderAndSanitizer { + fn claim( + self: @T, + amount: u128, + proof: Span, + ) -> Span; +} \ No newline at end of file diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/erc4626_decoder_and_sanitizer/erc4626_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/erc4626_decoder_and_sanitizer/erc4626_decoder_and_sanitizer.cairo index 0bafff05..3b4f812f 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/erc4626_decoder_and_sanitizer/erc4626_decoder_and_sanitizer.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/erc4626_decoder_and_sanitizer/erc4626_decoder_and_sanitizer.cairo @@ -8,9 +8,7 @@ pub mod Erc4626DecoderAndSanitizerComponent { use vault_allocator::decoders_and_sanitizers::erc4626_decoder_and_sanitizer::interface::IERC4626DecoderAndSanitizer; #[storage] - pub struct Storage { - pub vault_allocator: ContractAddress, - } + pub struct Storage {} #[event] #[derive(Drop, Debug, PartialEq, starknet::Event)] diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/interface.cairo index 7314c955..e5f266e1 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/interface.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/interface.cairo @@ -7,12 +7,5 @@ use starknet::ContractAddress; #[starknet::interface] pub trait IBaseDecoderAndSanitizer { fn approve(self: @T, spender: ContractAddress, amount: u256) -> Span; - fn flash_loan( - self: @T, - receiver: ContractAddress, - asset: ContractAddress, - amount: u256, - is_legacy: bool, - data: Span, - ) -> Span; + fn bring_liquidity(self: @T, amount: u256) -> Span; } diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/multiply_decoder_and_sanitizer/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/multiply_decoder_and_sanitizer/interface.cairo new file mode 100644 index 00000000..4110e379 --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/multiply_decoder_and_sanitizer/interface.cairo @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use vault_allocator::decoders_and_sanitizers::decoder_custom_types::ModifyLeverParams; + +#[starknet::interface] +pub trait IMultiplyDecoderAndSanitizer { + fn modify_lever(self: @T, modify_lever_params: ModifyLeverParams) -> Span; +} + diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/multiply_decoder_and_sanitizer/multiply_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/multiply_decoder_and_sanitizer/multiply_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..f096cd45 --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/multiply_decoder_and_sanitizer/multiply_decoder_and_sanitizer.cairo @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::component] +pub mod MultiplyDecoderAndSanitizerComponent { + use vault_allocator::decoders_and_sanitizers::decoder_custom_types::{ + ModifyLeverAction, ModifyLeverParams, + }; + use vault_allocator::decoders_and_sanitizers::multiply_decoder_and_sanitizer::interface::IMultiplyDecoderAndSanitizer; + + #[storage] + pub struct Storage {} + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + #[embeddable_as(MultiplyDecoderAndSanitizerImpl)] + impl MultiplyDecoderAndSanitizer< + TContractState, +HasComponent, + > of IMultiplyDecoderAndSanitizer> { + fn modify_lever( + self: @ComponentState, modify_lever_params: ModifyLeverParams, + ) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + match modify_lever_params.action { + ModifyLeverAction::IncreaseLever(params) => { + params.pool_id.serialize(ref serialized_struct); + params.collateral_asset.serialize(ref serialized_struct); + params.debt_asset.serialize(ref serialized_struct); + params.user.serialize(ref serialized_struct); + }, + ModifyLeverAction::DecreaseLever(params) => { + params.pool_id.serialize(ref serialized_struct); + params.collateral_asset.serialize(ref serialized_struct); + params.debt_asset.serialize(ref serialized_struct); + params.user.serialize(ref serialized_struct); + }, + } + + serialized_struct.span() + } + } +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/simple_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/simple_decoder_and_sanitizer.cairo index 857e206a..dc20d0f6 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/simple_decoder_and_sanitizer.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/simple_decoder_and_sanitizer.cairo @@ -7,7 +7,10 @@ pub mod SimpleDecoderAndSanitizer { use vault_allocator::decoders_and_sanitizers::avnu_exchange_decoder_and_sanitizer::avnu_exchange_decoder_and_sanitizer::AvnuExchangeDecoderAndSanitizerComponent; use vault_allocator::decoders_and_sanitizers::base_decoder_and_sanitizer::BaseDecoderAndSanitizerComponent; use vault_allocator::decoders_and_sanitizers::erc4626_decoder_and_sanitizer::erc4626_decoder_and_sanitizer::Erc4626DecoderAndSanitizerComponent; + use vault_allocator::decoders_and_sanitizers::multiply_decoder_and_sanitizer::multiply_decoder_and_sanitizer::MultiplyDecoderAndSanitizerComponent; + use vault_allocator::decoders_and_sanitizers::starknet_vault_kit_decoder_and_sanitizer::starknet_vault_kit_decoder_and_sanitizer::StarknetVaultKitDecoderAndSanitizerComponent; use vault_allocator::decoders_and_sanitizers::vesu_decoder_and_sanitizer::vesu_decoder_and_sanitizer::VesuDecoderAndSanitizerComponent; + use vault_allocator::decoders_and_sanitizers::defi_spring_snf_style::defi_spring_snf_style_decoder_and_sanitizer::DefiSpringSNFStyleDecoderAndSanitizerComponent; component!( path: BaseDecoderAndSanitizerComponent, @@ -20,6 +23,12 @@ pub mod SimpleDecoderAndSanitizer { event: Erc4626DecoderAndSanitizerEvent, ); + component!( + path: StarknetVaultKitDecoderAndSanitizerComponent, + storage: starknet_vault_kit_decoder_and_sanitizer, + event: StarknetVaultKitDecoderAndSanitizerEvent, + ); + component!( path: VesuDecoderAndSanitizerComponent, storage: vesu_decoder_and_sanitizer, @@ -32,6 +41,18 @@ pub mod SimpleDecoderAndSanitizer { event: AvnuExchangeDecoderAndSanitizerEvent, ); + component!( + path: MultiplyDecoderAndSanitizerComponent, + storage: multiply_decoder_and_sanitizer, + event: MultiplyDecoderAndSanitizerEvent, + ); + + component!( + path: DefiSpringSNFStyleDecoderAndSanitizerComponent, + storage: defi_spring_snf_style_decoder_and_sanitizer, + event: DefiSpringSNFStyleDecoderAndSanitizerEvent, + ); + #[abi(embed_v0)] impl BaseDecoderAndSanitizerImpl = BaseDecoderAndSanitizerComponent::BaseDecoderAndSanitizerImpl; @@ -50,6 +71,16 @@ pub mod SimpleDecoderAndSanitizer { ContractState, >; + #[abi(embed_v0)] + impl MultiplyDecoderAndSanitizerImpl = + MultiplyDecoderAndSanitizerComponent::MultiplyDecoderAndSanitizerImpl; + + #[abi(embed_v0)] + impl DefiSpringSNFStyleDecoderAndSanitizerImpl = + DefiSpringSNFStyleDecoderAndSanitizerComponent::DefiSpringSNFStyleDecoderAndSanitizerImpl< + ContractState, + >; + #[storage] pub struct Storage { #[substorage(v0)] @@ -60,6 +91,12 @@ pub mod SimpleDecoderAndSanitizer { pub vesu_decoder_and_sanitizer: VesuDecoderAndSanitizerComponent::Storage, #[substorage(v0)] pub avnu_exchange_decoder_and_sanitizer: AvnuExchangeDecoderAndSanitizerComponent::Storage, + #[substorage(v0)] + pub starknet_vault_kit_decoder_and_sanitizer: StarknetVaultKitDecoderAndSanitizerComponent::Storage, + #[substorage(v0)] + pub multiply_decoder_and_sanitizer: MultiplyDecoderAndSanitizerComponent::Storage, + #[substorage(v0)] + pub defi_spring_snf_style_decoder_and_sanitizer: DefiSpringSNFStyleDecoderAndSanitizerComponent::Storage, } #[event] @@ -73,5 +110,11 @@ pub mod SimpleDecoderAndSanitizer { VesuDecoderAndSanitizerEvent: VesuDecoderAndSanitizerComponent::Event, #[flat] AvnuExchangeDecoderAndSanitizerEvent: AvnuExchangeDecoderAndSanitizerComponent::Event, + #[flat] + StarknetVaultKitDecoderAndSanitizerEvent: StarknetVaultKitDecoderAndSanitizerComponent::Event, + #[flat] + MultiplyDecoderAndSanitizerEvent: MultiplyDecoderAndSanitizerComponent::Event, + #[flat] + DefiSpringSNFStyleDecoderAndSanitizerEvent: DefiSpringSNFStyleDecoderAndSanitizerComponent::Event, } } diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/starknet_vault_kit_decoder_and_sanitizer/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/starknet_vault_kit_decoder_and_sanitizer/interface.cairo new file mode 100644 index 00000000..837d592b --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/starknet_vault_kit_decoder_and_sanitizer/interface.cairo @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use starknet::ContractAddress; + +#[starknet::interface] +pub trait IStarknetVaultKitDecoderAndSanitizer { + fn request_redeem( + self: @T, shares: u256, receiver: ContractAddress, owner: ContractAddress, + ) -> Span; + + fn claim_redeem(self: @T, id: u256) -> Span; +} + diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/starknet_vault_kit_decoder_and_sanitizer/starknet_vault_kit_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/starknet_vault_kit_decoder_and_sanitizer/starknet_vault_kit_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..0abf72ce --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/starknet_vault_kit_decoder_and_sanitizer/starknet_vault_kit_decoder_and_sanitizer.cairo @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::component] +pub mod StarknetVaultKitDecoderAndSanitizerComponent { + use starknet::ContractAddress; + use vault_allocator::decoders_and_sanitizers::erc4626_decoder_and_sanitizer::erc4626_decoder_and_sanitizer::Erc4626DecoderAndSanitizerComponent; + use vault_allocator::decoders_and_sanitizers::erc4626_decoder_and_sanitizer::erc4626_decoder_and_sanitizer::Erc4626DecoderAndSanitizerComponent::Erc4626DecoderAndSanitizerImpl; + use vault_allocator::decoders_and_sanitizers::starknet_vault_kit_decoder_and_sanitizer::interface::IStarknetVaultKitDecoderAndSanitizer; + #[storage] + pub struct Storage {} + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + #[embeddable_as(VesuDecoderAndSanitizerImpl)] + impl StarknetVaultKitDecoderAndSanitizer< + TContractState, + +HasComponent, + +Erc4626DecoderAndSanitizerComponent::HasComponent, + > of IStarknetVaultKitDecoderAndSanitizer> { + fn request_redeem( + self: @ComponentState, + shares: u256, + receiver: ContractAddress, + owner: ContractAddress, + ) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + receiver.serialize(ref serialized_struct); + owner.serialize(ref serialized_struct); + serialized_struct.span() + } + + fn claim_redeem(self: @ComponentState, id: u256) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + id.serialize(ref serialized_struct); + serialized_struct.span() + } + } +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/uncap_decoder_and_sanitizer/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/uncap_decoder_and_sanitizer/interface.cairo new file mode 100644 index 00000000..b86d7bac --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/uncap_decoder_and_sanitizer/interface.cairo @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use starknet::ContractAddress; + +#[starknet::interface] +pub trait IUnCapDecoderAndSanitizer { + fn provide_to_sp(self: @T, top_up: u256, do_claim: bool) -> Span; + fn withdraw_from_sp(self: @T, amount: u256, do_claim: bool) -> Span; +} + diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/uncap_decoder_and_sanitizer/uncap_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/uncap_decoder_and_sanitizer/uncap_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..581fa73e --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/uncap_decoder_and_sanitizer/uncap_decoder_and_sanitizer.cairo @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::component] +pub mod UnCapDecoderAndSanitizerComponent { + use starknet::ContractAddress; + use vault_allocator::decoders_and_sanitizers::uncap_decoder_and_sanitizer::interface::IUnCapDecoderAndSanitizer; + + #[storage] + pub struct Storage { + pub vault_allocator: ContractAddress, + } + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + #[embeddable_as(UnCapDecoderAndSanitizerImpl)] + impl UnCapDecoderAndSanitizer< + TContractState, +HasComponent, + > of IUnCapDecoderAndSanitizer> { + fn provide_to_sp( + self: @ComponentState, top_up: u256, do_claim: bool, + ) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + serialized_struct.span() + } + + fn withdraw_from_sp( + self: @ComponentState, amount: u256, do_claim: bool, + ) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + serialized_struct.span() + } + } +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/vesu_decoder_and_sanitizer/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_decoder_and_sanitizer/interface.cairo index 9dd2c4a5..e8a25500 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/vesu_decoder_and_sanitizer/interface.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_decoder_and_sanitizer/interface.cairo @@ -2,13 +2,10 @@ // Copyright (c) 2025 Starknet Vault Kit // Licensed under the MIT License. See LICENSE file for details. -use vault_allocator::decoders_and_sanitizers::decoder_custom_types::{ - ModifyPositionParams, TransferPositionParams, -}; +use vault_allocator::decoders_and_sanitizers::decoder_custom_types::ModifyPositionParams; #[starknet::interface] pub trait IVesuDecoderAndSanitizer { - fn transfer_position(self: @T, params: TransferPositionParams) -> Span; fn modify_position(self: @T, params: ModifyPositionParams) -> Span; } diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/vesu_decoder_and_sanitizer/vesu_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_decoder_and_sanitizer/vesu_decoder_and_sanitizer.cairo index 56c1abd8..5dc1e578 100644 --- a/packages/vault_allocator/src/decoders_and_sanitizers/vesu_decoder_and_sanitizer/vesu_decoder_and_sanitizer.cairo +++ b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_decoder_and_sanitizer/vesu_decoder_and_sanitizer.cairo @@ -4,18 +4,11 @@ #[starknet::component] pub mod VesuDecoderAndSanitizerComponent { - use starknet::ContractAddress; - use vault_allocator::decoders_and_sanitizers::decoder_custom_types::{ - ModifyPositionParams, TransferPositionParams, - }; - use vault_allocator::decoders_and_sanitizers::erc4626_decoder_and_sanitizer::erc4626_decoder_and_sanitizer::Erc4626DecoderAndSanitizerComponent; - use vault_allocator::decoders_and_sanitizers::erc4626_decoder_and_sanitizer::erc4626_decoder_and_sanitizer::Erc4626DecoderAndSanitizerComponent::Erc4626DecoderAndSanitizerImpl; + use vault_allocator::decoders_and_sanitizers::decoder_custom_types::ModifyPositionParams; use vault_allocator::decoders_and_sanitizers::vesu_decoder_and_sanitizer::interface::IVesuDecoderAndSanitizer; #[storage] - pub struct Storage { - pub vault_allocator: ContractAddress, - } + pub struct Storage {} #[event] #[derive(Drop, Debug, PartialEq, starknet::Event)] @@ -23,33 +16,8 @@ pub mod VesuDecoderAndSanitizerComponent { #[embeddable_as(VesuDecoderAndSanitizerImpl)] impl VesuDecoderAndSanitizer< - TContractState, - +HasComponent, - +Erc4626DecoderAndSanitizerComponent::HasComponent, + TContractState, +HasComponent, > of IVesuDecoderAndSanitizer> { - fn transfer_position( - self: @ComponentState, params: TransferPositionParams, - ) -> Span { - let mut serialized_struct: Array = ArrayTrait::new(); - params.pool_id.serialize(ref serialized_struct); - params.from_collateral_asset.serialize(ref serialized_struct); - - // From debt asset, isn't it always 0 ? to confirm - params.from_debt_asset.serialize(ref serialized_struct); - params.to_collateral_asset.serialize(ref serialized_struct); - params.to_debt_asset.serialize(ref serialized_struct); - - //From user is in fact the extensin : to confirm - params.from_user.serialize(ref serialized_struct); - params.to_user.serialize(ref serialized_struct); - - //not sure to include those 2 fields : to confirm - params.from_data.serialize(ref serialized_struct); - params.to_data.serialize(ref serialized_struct); - serialized_struct.span() - } - - fn modify_position( self: @ComponentState, params: ModifyPositionParams, ) -> Span { @@ -58,10 +26,6 @@ pub mod VesuDecoderAndSanitizerComponent { params.collateral_asset.serialize(ref serialized_struct); params.debt_asset.serialize(ref serialized_struct); params.user.serialize(ref serialized_struct); - - // not sure to include this fields : to confirm - params.data.serialize(ref serialized_struct); - serialized_struct.span() } } diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_decoder_and_sanitizer/interface.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_decoder_and_sanitizer/interface.cairo new file mode 100644 index 00000000..0bf999c5 --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_decoder_and_sanitizer/interface.cairo @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use vault_allocator::decoders_and_sanitizers::decoder_custom_types::ModifyPositionParamsV2; + +#[starknet::interface] +pub trait IVesuV2DecoderAndSanitizer { + fn modify_position(self: @T, params: ModifyPositionParamsV2) -> Span; +} + diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_decoder_and_sanitizer/vesu_v2_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_decoder_and_sanitizer/vesu_v2_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..f4d10f31 --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_decoder_and_sanitizer/vesu_v2_decoder_and_sanitizer.cairo @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::component] +pub mod VesuV2DecoderAndSanitizerComponent { + use vault_allocator::decoders_and_sanitizers::decoder_custom_types::ModifyPositionParamsV2; + use vault_allocator::decoders_and_sanitizers::vesu_v2_decoder_and_sanitizer::interface::IVesuV2DecoderAndSanitizer; + + #[storage] + pub struct Storage {} + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event {} + + #[embeddable_as(VesuV2DecoderAndSanitizerImpl)] + impl VesuV2DecoderAndSanitizer< + TContractState, +HasComponent, + > of IVesuV2DecoderAndSanitizer> { + fn modify_position( + self: @ComponentState, params: ModifyPositionParamsV2, + ) -> Span { + let mut serialized_struct: Array = ArrayTrait::new(); + params.collateral_asset.serialize(ref serialized_struct); + params.debt_asset.serialize(ref serialized_struct); + params.user.serialize(ref serialized_struct); + serialized_struct.span() + } + } +} diff --git a/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_specific_decoder_and_sanitizer.cairo b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_specific_decoder_and_sanitizer.cairo new file mode 100644 index 00000000..3e36004d --- /dev/null +++ b/packages/vault_allocator/src/decoders_and_sanitizers/vesu_v2_specific_decoder_and_sanitizer.cairo @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::contract] +pub mod VesuV2SpecificDecoderAndSanitizer { + use vault_allocator::decoders_and_sanitizers::base_decoder_and_sanitizer::BaseDecoderAndSanitizerComponent; + use vault_allocator::decoders_and_sanitizers::vesu_v2_decoder_and_sanitizer::vesu_v2_decoder_and_sanitizer::VesuV2DecoderAndSanitizerComponent; + + component!( + path: BaseDecoderAndSanitizerComponent, + storage: base_decoder_and_sanitizer, + event: BaseDecoderAndSanitizerEvent, + ); + + component!( + path: VesuV2DecoderAndSanitizerComponent, + storage: vesu_v2_decoder_and_sanitizer, + event: VesuV2DecoderAndSanitizerEvent, + ); + + #[abi(embed_v0)] + impl BaseDecoderAndSanitizerImpl = + BaseDecoderAndSanitizerComponent::BaseDecoderAndSanitizerImpl; + + #[abi(embed_v0)] + impl VesuV2DecoderAndSanitizerImpl = + VesuV2DecoderAndSanitizerComponent::VesuV2DecoderAndSanitizerImpl; + + #[storage] + pub struct Storage { + #[substorage(v0)] + pub base_decoder_and_sanitizer: BaseDecoderAndSanitizerComponent::Storage, + #[substorage(v0)] + pub vesu_v2_decoder_and_sanitizer: VesuV2DecoderAndSanitizerComponent::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + BaseDecoderAndSanitizerEvent: BaseDecoderAndSanitizerComponent::Event, + #[flat] + VesuV2DecoderAndSanitizerEvent: VesuV2DecoderAndSanitizerComponent::Event, + } +} diff --git a/packages/vault_allocator/src/integration_interfaces/pragma.cairo b/packages/vault_allocator/src/integration_interfaces/pragma.cairo new file mode 100644 index 00000000..3d844d64 --- /dev/null +++ b/packages/vault_allocator/src/integration_interfaces/pragma.cairo @@ -0,0 +1,20 @@ +#[derive(Serde, Drop, Copy)] +pub struct PragmaPricesResponse { + pub price: u128, + pub decimals: u32, + pub last_updated_timestamp: u64, + pub num_sources_aggregated: u32, + pub expiration_timestamp: Option, +} + +#[derive(Drop, Copy, Serde)] +pub enum DataType { + SpotEntry: felt252, + FutureEntry: (felt252, u64), + GenericEntry: felt252, +} + +#[starknet::interface] +pub trait IPragmaABI { + fn get_data_median(self: @TContractState, data_type: DataType) -> PragmaPricesResponse; +} diff --git a/packages/vault_allocator/src/integration_interfaces/vesu.cairo b/packages/vault_allocator/src/integration_interfaces/vesu.cairo deleted file mode 100644 index 1f7d13b4..00000000 --- a/packages/vault_allocator/src/integration_interfaces/vesu.cairo +++ /dev/null @@ -1,397 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2025 Starknet Vault Kit -// Licensed under the MIT License. See LICENSE file for details. - -use starknet::ContractAddress; - -#[starknet::interface] -pub trait ISingletonV2< - TContractState, -> { // fn creator_nonce(self: @TContractState, creator: ContractAddress) -> felt252; - fn extension(self: @TContractState, pool_id: felt252) -> ContractAddress; - // fn asset_config_unsafe( - // self: @TContractState, pool_id: felt252, asset: ContractAddress, - // ) -> (AssetConfig, u256); - // fn asset_config( - // ref self: TContractState, pool_id: felt252, asset: ContractAddress, - // ) -> (AssetConfig, u256); - // fn ltv_config( - // self: @TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // ) -> LTVConfig; - // fn position_unsafe( - // self: @TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // user: ContractAddress, - // ) -> (Position, u256, u256); - // fn position( - // ref self: TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // user: ContractAddress, - // ) -> (Position, u256, u256); - // fn check_collateralization_unsafe( - // self: @TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // user: ContractAddress, - // ) -> (bool, u256, u256); - // fn check_collateralization( - // ref self: TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // user: ContractAddress, - // ) -> (bool, u256, u256); - // fn rate_accumulator_unsafe( - // self: @TContractState, pool_id: felt252, asset: ContractAddress, - // ) -> u256; - // fn rate_accumulator(ref self: TContractState, pool_id: felt252, asset: ContractAddress) -> - // u256; - // fn utilization_unsafe(self: @TContractState, pool_id: felt252, asset: ContractAddress) -> - // u256; - // fn utilization(ref self: TContractState, pool_id: felt252, asset: ContractAddress) -> u256; - // fn delegation( - // self: @TContractState, - // pool_id: felt252, - // delegator: ContractAddress, - // delegatee: ContractAddress, - // ) -> bool; - // fn calculate_pool_id( - // self: @TContractState, caller_address: ContractAddress, nonce: felt252, - // ) -> felt252; - // fn calculate_debt( - // self: @TContractState, nominal_debt: i257, rate_accumulator: u256, asset_scale: u256, - // ) -> u256; - // fn calculate_nominal_debt( - // self: @TContractState, debt: i257, rate_accumulator: u256, asset_scale: u256, - // ) -> u256; - // fn calculate_collateral_shares_unsafe( - // self: @TContractState, pool_id: felt252, asset: ContractAddress, collateral: i257, - // ) -> u256; - // fn calculate_collateral_shares( - // ref self: TContractState, pool_id: felt252, asset: ContractAddress, collateral: i257, - // ) -> u256; - // fn calculate_collateral_unsafe( - // self: @TContractState, pool_id: felt252, asset: ContractAddress, collateral_shares: i257, - // ) -> u256; - // fn calculate_collateral( - // ref self: TContractState, pool_id: felt252, asset: ContractAddress, collateral_shares: - // i257, - // ) -> u256; - // fn deconstruct_collateral_amount_unsafe( - // self: @TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // user: ContractAddress, - // collateral: Amount, - // ) -> (i257, i257); - // fn deconstruct_collateral_amount( - // ref self: TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // user: ContractAddress, - // collateral: Amount, - // ) -> (i257, i257); - // fn deconstruct_debt_amount_unsafe( - // self: @TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // user: ContractAddress, - // debt: Amount, - // ) -> (i257, i257); - // fn deconstruct_debt_amount( - // ref self: TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // user: ContractAddress, - // debt: Amount, - // ) -> (i257, i257); - // fn context_unsafe( - // self: @TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // user: ContractAddress, - // ) -> Context; - // fn context( - // ref self: TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // user: ContractAddress, - // ) -> Context; - // fn create_pool( - // ref self: TContractState, - // asset_params: Span, - // ltv_params: Span, - // extension: ContractAddress, - // ) -> felt252; - // fn modify_position( - // ref self: TContractState, params: ModifyPositionParams, - // ) -> UpdatePositionResponse; - // fn transfer_position(ref self: TContractState, params: TransferPositionParams); - // fn liquidate_position( - // ref self: TContractState, params: LiquidatePositionParams, - // ) -> UpdatePositionResponse; - fn flash_loan( - ref self: TContractState, - receiver: ContractAddress, - asset: ContractAddress, - amount: u256, - is_legacy: bool, - data: Span, - ); - // fn modify_delegation( -// ref self: TContractState, pool_id: felt252, delegatee: ContractAddress, delegation: bool, -// ); -// fn donate_to_reserve( -// ref self: TContractState, pool_id: felt252, asset: ContractAddress, amount: u256, -// ); -// fn retrieve_from_reserve( -// ref self: TContractState, -// pool_id: felt252, -// asset: ContractAddress, -// receiver: ContractAddress, -// amount: u256, -// ); -// fn set_asset_config(ref self: TContractState, pool_id: felt252, params: AssetParams); -// fn set_ltv_config( -// ref self: TContractState, -// pool_id: felt252, -// collateral_asset: ContractAddress, -// debt_asset: ContractAddress, -// ltv_config: LTVConfig, -// ); -// fn set_asset_parameter( -// ref self: TContractState, -// pool_id: felt252, -// asset: ContractAddress, -// parameter: felt252, -// value: u256, -// ); -// fn set_extension(ref self: TContractState, pool_id: felt252, extension: ContractAddress); -// fn set_extension_whitelist( -// ref self: TContractState, extension: ContractAddress, approved: bool, -// ); -// fn claim_fee_shares(ref self: TContractState, pool_id: felt252, asset: ContractAddress); - - // fn migrate_pool( -// ref self: TContractState, -// pool_id: felt252, -// extension: ContractAddress, -// creator: ContractAddress, -// asset_configs: Span<(ContractAddress, AssetConfig)>, -// ltv_configs: Span<(ContractAddress, ContractAddress, LTVConfig)>, -// ); -// fn migrate_position( -// ref self: TContractState, -// pool_id: felt252, -// collateral_asset: ContractAddress, -// debt_asset: ContractAddress, -// from: ContractAddress, -// to: ContractAddress, -// ); -// fn set_migrator(ref self: TContractState, migrator: ContractAddress); - - // fn upgrade_name(self: @TContractState) -> felt252; -// fn upgrade(ref self: TContractState, new_implementation: ClassHash); -} - -#[starknet::interface] -pub trait IDefaultExtensionPOV2< - TContractState, -> { // fn pool_name(self: @TContractState, pool_id: felt252) -> felt252; - // fn pool_owner(self: @TContractState, pool_id: felt252) -> ContractAddress; - // fn shutdown_mode_agent(self: @TContractState, pool_id: felt252) -> ContractAddress; - // fn pragma_oracle(self: @TContractState) -> ContractAddress; - // fn pragma_summary(self: @TContractState) -> ContractAddress; - // fn oracle_config( - // self: @TContractState, pool_id: felt252, asset: ContractAddress, - // ) -> OracleConfig; - // fn fee_config(self: @TContractState, pool_id: felt252) -> FeeConfig; - // fn debt_caps( - // self: @TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // ) -> u256; - // fn interest_rate_config( - // self: @TContractState, pool_id: felt252, asset: ContractAddress, - // ) -> InterestRateConfig; - // fn liquidation_config( - // self: @TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // ) -> LiquidationConfig; - // fn shutdown_config(self: @TContractState, pool_id: felt252) -> ShutdownConfig; - // fn shutdown_ltv_config( - // self: @TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // ) -> LTVConfig; - // fn shutdown_status( - // self: @TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // ) -> ShutdownStatus; - // fn pairs( - // self: @TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // ) -> Pair; - // fn violation_timestamp_for_pair( - // self: @TContractState, - // pool_id: felt252, - // collateral_asset: ContractAddress, - // debt_asset: ContractAddress, - // ) -> u64; - // fn violation_timestamp_count( - // self: @TContractState, pool_id: felt252, violation_timestamp: u64, - // ) -> u128; - // fn oldest_violation_timestamp(self: @TContractState, pool_id: felt252) -> u64; - // fn next_violation_timestamp( - // self: @TContractState, pool_id: felt252, violation_timestamp: u64, - // ) -> u64; - fn v_token_for_collateral_asset( - self: @TContractState, pool_id: felt252, collateral_asset: ContractAddress, - ) -> ContractAddress; - // fn collateral_asset_for_v_token( -// self: @TContractState, pool_id: felt252, v_token: ContractAddress, -// ) -> ContractAddress; -// fn create_pool( -// ref self: TContractState, -// name: felt252, -// asset_params: Span, -// v_token_params: Span, -// ltv_params: Span, -// interest_rate_configs: Span, -// pragma_oracle_params: Span, -// liquidation_params: Span, -// debt_caps: Span, -// shutdown_params: ShutdownParams, -// fee_params: FeeParams, -// owner: ContractAddress, -// ) -> felt252; -// fn add_asset( -// ref self: TContractState, -// pool_id: felt252, -// asset_params: AssetParams, -// v_token_params: VTokenParams, -// interest_rate_config: InterestRateConfig, -// pragma_oracle_params: PragmaOracleParams, -// ); -// fn set_asset_parameter( -// ref self: TContractState, -// pool_id: felt252, -// asset: ContractAddress, -// parameter: felt252, -// value: u256, -// ); -// fn set_debt_cap( -// ref self: TContractState, -// pool_id: felt252, -// collateral_asset: ContractAddress, -// debt_asset: ContractAddress, -// debt_cap: u256, -// ); -// fn set_interest_rate_parameter( -// ref self: TContractState, -// pool_id: felt252, -// asset: ContractAddress, -// parameter: felt252, -// value: u256, -// ); -// fn set_oracle_parameter( -// ref self: TContractState, -// pool_id: felt252, -// asset: ContractAddress, -// parameter: felt252, -// value: felt252, -// ); -// fn set_liquidation_config( -// ref self: TContractState, -// pool_id: felt252, -// collateral_asset: ContractAddress, -// debt_asset: ContractAddress, -// liquidation_config: LiquidationConfig, -// ); -// fn set_ltv_config( -// ref self: TContractState, -// pool_id: felt252, -// collateral_asset: ContractAddress, -// debt_asset: ContractAddress, -// ltv_config: LTVConfig, -// ); -// fn set_shutdown_config( -// ref self: TContractState, pool_id: felt252, shutdown_config: ShutdownConfig, -// ); -// fn set_shutdown_ltv_config( -// ref self: TContractState, -// pool_id: felt252, -// collateral_asset: ContractAddress, -// debt_asset: ContractAddress, -// shutdown_ltv_config: LTVConfig, -// ); -// fn set_shutdown_mode(ref self: TContractState, pool_id: felt252, shutdown_mode: -// ShutdownMode); -// fn set_pool_owner(ref self: TContractState, pool_id: felt252, owner: ContractAddress); -// fn set_shutdown_mode_agent( -// ref self: TContractState, pool_id: felt252, shutdown_mode_agent: ContractAddress, -// ); -// fn update_shutdown_status( -// ref self: TContractState, -// pool_id: felt252, -// collateral_asset: ContractAddress, -// debt_asset: ContractAddress, -// ) -> ShutdownMode; -// fn set_fee_config(ref self: TContractState, pool_id: felt252, fee_config: FeeConfig); -// fn claim_fees(ref self: TContractState, pool_id: felt252, collateral_asset: ContractAddress); - - // fn migrate_pool( -// ref self: TContractState, -// pool_id: felt252, -// name: felt252, -// v_token_configs: Span<(felt252, felt252, ContractAddress, ContractAddress)>, -// interest_rate_configs: Span<(ContractAddress, InterestRateConfig)>, -// pragma_oracle_configs: Span<(ContractAddress, OracleConfig)>, -// liquidation_configs: Span<(ContractAddress, ContractAddress, LiquidationConfig)>, -// pairs: Span<(ContractAddress, ContractAddress, Pair)>, -// debt_caps: Span<(ContractAddress, ContractAddress, u256)>, -// shutdown_ltv_configs: Span<(ContractAddress, ContractAddress, LTVConfig)>, -// shutdown_config: ShutdownConfig, -// fee_config: FeeConfig, -// owner: ContractAddress, -// ); -// fn set_migrator(ref self: TContractState, migrator: ContractAddress); - - // fn set_extension_utils_class_hash(ref self: TContractState, extension: felt252); -// // Upgrade -// fn upgrade_name(self: @TContractState) -> felt252; -// fn upgrade(ref self: TContractState, new_implementation: ClassHash); -} - -#[starknet::interface] -pub trait IFlashloanReceiver { - fn on_flash_loan( - ref self: TContractState, - sender: ContractAddress, - asset: ContractAddress, - amount: u256, - data: Span, - ); -} diff --git a/packages/vault_allocator/src/integration_interfaces/vesu_v1.cairo b/packages/vault_allocator/src/integration_interfaces/vesu_v1.cairo new file mode 100644 index 00000000..ffe96a60 --- /dev/null +++ b/packages/vault_allocator/src/integration_interfaces/vesu_v1.cairo @@ -0,0 +1,23 @@ +use starknet::ContractAddress; + +#[derive(PartialEq, Copy, Drop, Serde)] +pub struct Position { + collateral_shares: u256, + nominal_debt: u256, +} + +#[starknet::interface] +pub trait IV1Token { + fn pool_id(self: @TContractState) -> felt252; +} + +#[starknet::interface] +pub trait ISingletonV2 { + fn position( + ref self: TContractState, + pool_id: felt252, + collateral_asset: ContractAddress, + debt_asset: ContractAddress, + user: ContractAddress, + ) -> (Position, u256, u256); +} diff --git a/packages/vault_allocator/src/integration_interfaces/vesu_v2.cairo b/packages/vault_allocator/src/integration_interfaces/vesu_v2.cairo new file mode 100644 index 00000000..2ce1275c --- /dev/null +++ b/packages/vault_allocator/src/integration_interfaces/vesu_v2.cairo @@ -0,0 +1,28 @@ +use starknet::ContractAddress; +use vault_allocator::integration_interfaces::vesu_v1::Position; + +#[starknet::interface] +pub trait IV2Token { + fn pool_contract(self: @TContractState) -> ContractAddress; +} + +#[starknet::interface] +pub trait IPool { + fn position( + self: @TContractState, + collateral_asset: ContractAddress, + debt_asset: ContractAddress, + user: ContractAddress, + ) -> (Position, u256, u256); +} + + +#[starknet::interface] +pub trait IOracle { + fn price(self: @TContractState, asset: ContractAddress) -> AssetPrice; +} +#[derive(PartialEq, Copy, Drop, Serde, Default)] +pub struct AssetPrice { + pub value: u256, + pub is_valid: bool, +} diff --git a/packages/vault_allocator/src/lib.cairo b/packages/vault_allocator/src/lib.cairo index f67eaee9..53d69041 100644 --- a/packages/vault_allocator/src/lib.cairo +++ b/packages/vault_allocator/src/lib.cairo @@ -16,7 +16,9 @@ pub mod manager { pub mod integration_interfaces { pub mod avnu; - pub mod vesu; + pub mod pragma; + pub mod vesu_v1; + pub mod vesu_v2; } pub mod periphery { @@ -25,6 +27,11 @@ pub mod periphery { pub mod interface; pub mod price_router; } + pub mod price_router_vesu { + pub mod errors; + pub mod interface; + pub mod price_router_vesu; + } } pub mod middlewares { @@ -40,6 +47,7 @@ pub mod decoders_and_sanitizers { pub mod decoder_custom_types; pub mod interface; pub mod simple_decoder_and_sanitizer; + pub mod vesu_v2_specific_decoder_and_sanitizer; pub mod avnu_exchange_decoder_and_sanitizer { pub mod avnu_exchange_decoder_and_sanitizer; pub mod interface; @@ -52,18 +60,36 @@ pub mod decoders_and_sanitizers { pub mod interface; pub mod vesu_decoder_and_sanitizer; } + pub mod starknet_vault_kit_decoder_and_sanitizer { + pub mod interface; + pub mod starknet_vault_kit_decoder_and_sanitizer; + } + pub mod vesu_v2_decoder_and_sanitizer { + pub mod interface; + pub mod vesu_v2_decoder_and_sanitizer; + } + + pub mod multiply_decoder_and_sanitizer { + pub mod interface; + pub mod multiply_decoder_and_sanitizer; + } + + pub mod defi_spring_snf_style { + pub mod defi_spring_snf_style_decoder_and_sanitizer; + pub mod interface; + } } pub mod mocks { pub mod counter; pub mod erc20; pub mod erc4626; - pub mod flashloan; + pub mod vault; } #[cfg(test)] pub mod test { - pub mod register; + // pub mod creator; pub mod utils; pub mod units { pub mod manager; @@ -71,11 +97,23 @@ pub mod test { } pub mod integrations { pub mod avnu; - pub mod vesu; + pub mod vault_bring_liquidity; + pub mod vesu_v1; } - pub mod scenarios { pub mod stable_carry_loop; } } + +pub mod merkle_tree { + pub mod base; + pub mod registery; + pub mod integrations { + pub mod avnu; + pub mod erc4626; + pub mod starknet_vault_kit_strategies; + pub mod vesu_v1; + pub mod vesu_v2; + } +} diff --git a/packages/vault_allocator/src/manager/errors.cairo b/packages/vault_allocator/src/manager/errors.cairo index c232c949..8eb9aa4c 100644 --- a/packages/vault_allocator/src/manager/errors.cairo +++ b/packages/vault_allocator/src/manager/errors.cairo @@ -18,16 +18,4 @@ pub mod Errors { pub fn not_vault_allocator() { panic!("Not vault allocator"); } - - pub fn flash_loan_not_executed() { - panic!("Flash loan not executed"); - } - - pub fn not_vesu_singleton() { - panic!("Not vesu singleton"); - } - - pub fn bad_flash_loan_intent_hash() { - panic!("Bad flash loan intent hash"); - } } diff --git a/packages/vault_allocator/src/manager/interface.cairo b/packages/vault_allocator/src/manager/interface.cairo index 21e59cda..e5546961 100644 --- a/packages/vault_allocator/src/manager/interface.cairo +++ b/packages/vault_allocator/src/manager/interface.cairo @@ -6,7 +6,6 @@ use starknet::ContractAddress; #[starknet::interface] pub trait IManager { - fn vesu_singleton(self: @T) -> ContractAddress; fn vault_allocator(self: @T) -> ContractAddress; fn set_manage_root(ref self: T, target: ContractAddress, root: felt252); fn manage_root(self: @T, target: ContractAddress) -> felt252; @@ -20,14 +19,5 @@ pub trait IManager { selectors: Span, calldatas: Span>, ); - - fn flash_loan( - ref self: T, - recipient: ContractAddress, - asset: ContractAddress, - amount: u256, - is_legacy: bool, - data: Span, - ); } diff --git a/packages/vault_allocator/src/manager/manager.cairo b/packages/vault_allocator/src/manager/manager.cairo index ab6011de..c47d9d2c 100644 --- a/packages/vault_allocator/src/manager/manager.cairo +++ b/packages/vault_allocator/src/manager/manager.cairo @@ -8,28 +8,21 @@ pub mod Manager { pub const OWNER_ROLE: felt252 = selector!("OWNER_ROLE"); pub const PAUSER_ROLE: felt252 = selector!("PAUSER_ROLE"); use core::hash::HashStateTrait; - use core::num::traits::Zero; use core::pedersen::PedersenTrait; use openzeppelin::access::accesscontrol::AccessControlComponent; + use openzeppelin::interfaces::upgrades::IUpgradeable; use openzeppelin::introspection::src5::SRC5Component; use openzeppelin::merkle_tree::merkle_proof; use openzeppelin::security::pausable::PausableComponent; - use openzeppelin::token::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; - use openzeppelin::upgrades::interface::IUpgradeable; use openzeppelin::upgrades::upgradeable::UpgradeableComponent; use starknet::account::Call; use starknet::storage::{ Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, StoragePointerWriteAccess, }; - use starknet::{ContractAddress, get_caller_address, get_contract_address}; - use vault_allocator::integration_interfaces::vesu::{ - IFlashloanReceiver, ISingletonV2Dispatcher, ISingletonV2DispatcherTrait, - }; + use starknet::{ContractAddress, get_caller_address}; use vault_allocator::manager::errors::Errors; - use vault_allocator::manager::interface::{ - IManager, IManagerDispatcher, IManagerDispatcherTrait, - }; + use vault_allocator::manager::interface::IManager; use vault_allocator::vault_allocator::interface::{ IVaultAllocatorDispatcher, IVaultAllocatorDispatcherTrait, }; @@ -50,10 +43,7 @@ pub mod Manager { #[substorage(v0)] pausable: PausableComponent::Storage, vault_allocator: IVaultAllocatorDispatcher, - vesu_singleton: ISingletonV2Dispatcher, manage_root: Map, - flash_loan_intent_hash: felt252, - performing_flash_loan: bool, } #[event] @@ -68,10 +58,7 @@ pub mod Manager { #[constructor] fn constructor( - ref self: ContractState, - owner: ContractAddress, - vault_allocator: ContractAddress, - vesu_singleton: ContractAddress, + ref self: ContractState, owner: ContractAddress, vault_allocator: ContractAddress, ) { self.access_control.initializer(); self.access_control.set_role_admin(OWNER_ROLE, OWNER_ROLE); @@ -79,7 +66,6 @@ pub mod Manager { self.access_control._grant_role(OWNER_ROLE, owner); self.access_control._grant_role(PAUSER_ROLE, owner); self.vault_allocator.write(IVaultAllocatorDispatcher { contract_address: vault_allocator }); - self.vesu_singleton.write(ISingletonV2Dispatcher { contract_address: vesu_singleton }); } @@ -101,67 +87,9 @@ pub mod Manager { } } - #[abi(embed_v0)] - impl ManagerFlashloanReceiverImpl of IFlashloanReceiver { - fn on_flash_loan( - ref self: ContractState, - sender: ContractAddress, - asset: ContractAddress, - amount: u256, - data: Span, - ) { - let vesu_singleton = self.vesu_singleton.read().contract_address; - if (get_caller_address() != vesu_singleton) { - Errors::not_vesu_singleton(); - } - let intent_hash = self._get_flash_loan_intent_hash_from_span(data); - if (intent_hash != self.flash_loan_intent_hash.read()) { - Errors::bad_flash_loan_intent_hash(); - } - self.flash_loan_intent_hash.write(0); - - let vault_allocator = self.vault_allocator.read(); - let asset_dispatcher = ERC20ABIDispatcher { contract_address: asset }; - - asset_dispatcher.transfer(vault_allocator.contract_address, amount); - - let mut data = data.clone(); - let (proofs, decoder_and_sanitizers, targets, selectors, calldatas) = Serde::< - ( - Span>, - Span, - Span, - Span, - Span>, - ), - >::deserialize(ref data) - .unwrap(); - - let this = get_contract_address(); - IManagerDispatcher { contract_address: this } - .manage_vault_with_merkle_verification( - proofs, decoder_and_sanitizers, targets, selectors, calldatas, - ); - - let mut calldata = ArrayTrait::new(); - this.serialize(ref calldata); - amount.serialize(ref calldata); - - vault_allocator - .manage( - Call { to: asset, selector: selector!("transfer"), calldata: calldata.span() }, - ); - asset_dispatcher.approve(vesu_singleton, amount); - } - } - #[abi(embed_v0)] impl ManagerImpl of IManager { - fn vesu_singleton(self: @ContractState) -> ContractAddress { - self.vesu_singleton.read().contract_address - } - fn vault_allocator(self: @ContractState) -> ContractAddress { self.vault_allocator.read().contract_address } @@ -220,26 +148,6 @@ pub mod Manager { .manage(Call { to: target, selector: selector, calldata: calldata }); } } - - fn flash_loan( - ref self: ContractState, - recipient: ContractAddress, - asset: ContractAddress, - amount: u256, - is_legacy: bool, - data: Span, - ) { - if (get_caller_address() != self.vault_allocator.read().contract_address) { - Errors::not_vault_allocator(); - } - self.flash_loan_intent_hash.write(self._get_flash_loan_intent_hash_from_span(data)); - self.performing_flash_loan.write(true); - self.vesu_singleton.read().flash_loan(recipient, asset, amount, is_legacy, data); - self.performing_flash_loan.write(false); - if (self.flash_loan_intent_hash.read().is_non_zero()) { - Errors::flash_loan_not_executed(); - } - } } @@ -305,18 +213,5 @@ pub mod Manager { let leaf_hash = state.finalize(); merkle_proof::verify_pedersen(proof, root, leaf_hash) } - - fn _get_flash_loan_intent_hash_from_span( - self: @ContractState, data: Span, - ) -> felt252 { - let mut serialized_struct: Array = ArrayTrait::new(); - data.serialize(ref serialized_struct); - let first_element = serialized_struct.pop_front().unwrap(); - let mut state = PedersenTrait::new(first_element); - while let Some(value) = serialized_struct.pop_front() { - state = state.update(value); - } - state.finalize() - } } } diff --git a/packages/vault_allocator/src/merkle_tree/base.cairo b/packages/vault_allocator/src/merkle_tree/base.cairo new file mode 100644 index 00000000..7e0d681f --- /dev/null +++ b/packages/vault_allocator/src/merkle_tree/base.cairo @@ -0,0 +1,319 @@ +use core::hash::HashStateTrait; +use core::num::traits::Zero; +use core::pedersen::PedersenTrait; +use openzeppelin::interfaces::erc4626::{ERC4626ABIDispatcher, ERC4626ABIDispatcherTrait}; +use openzeppelin::merkle_tree::hashes::PedersenCHasher; +use starknet::ContractAddress; +use starknet::syscalls::call_contract_syscall; + + +#[derive(PartialEq, Drop, Serde, Debug, Clone)] +pub struct ManageLeaf { + pub decoder_and_sanitizer: ContractAddress, + pub target: ContractAddress, + pub selector: felt252, + pub argument_addresses: Span, + pub description: ByteArray, +} + + +pub fn get_leaf_hash(leaf: ManageLeaf) -> felt252 { + let mut serialized_struct: Array = ArrayTrait::new(); + leaf.decoder_and_sanitizer.serialize(ref serialized_struct); + leaf.target.serialize(ref serialized_struct); + leaf.selector.serialize(ref serialized_struct); + leaf.argument_addresses.serialize(ref serialized_struct); + let first_element = serialized_struct.pop_front().unwrap(); + let mut state = PedersenTrait::new(first_element); + while let Some(value) = serialized_struct.pop_front() { + state = state.update(value); + } + state.finalize() +} + +pub fn generate_merkle_tree(manage_leafs: Span) -> Array> { + let mut first_layer = ArrayTrait::new(); + let leafs_length = manage_leafs.len(); + for i in 0..leafs_length { + first_layer.append(get_leaf_hash(manage_leafs.at(i).clone())); + } + let mut leafs = ArrayTrait::new(); + leafs.append(first_layer); + _build_tree(leafs) +} + +pub fn _build_tree(merkle_tree_in: Array>) -> Array> { + let merkle_tree_in_length = merkle_tree_in.len(); + + let mut current_layer_index = merkle_tree_in_length - 1; + let current_layer_length = merkle_tree_in[current_layer_index].len(); + let mut next_layer_length = 0; + if (current_layer_length % 2 != 0) { + next_layer_length = (current_layer_length + 1) / 2; + } else { + next_layer_length = current_layer_length / 2; + } + let mut current_layer = ArrayTrait::new(); + let mut count = 0; + let mut i = 0; + while i < current_layer_length { + current_layer + .append( + PedersenCHasher::commutative_hash( + *merkle_tree_in[current_layer_index].at(i), + *merkle_tree_in[current_layer_index].at(i + 1), + ), + ); + count += 1; + i += 2; + } + + let mut merkle_tree_out = merkle_tree_in.clone(); + merkle_tree_out.append(current_layer); + + if (next_layer_length > 1) { + _build_tree(merkle_tree_out) + } else { + merkle_tree_out + } +} + +fn _next_power_of_two(x: u256) -> u256 { + let mut power = 1_u256; + while power < x { + power = power * 2_u256; + } + power +} + + +pub fn _pad_leafs_to_power_of_two(ref leafs: Array, ref leaf_index: u256) { + let target_len = if leaf_index < 4_u256 { + 4_u256 + } else { + _next_power_of_two(leaf_index) + }; + let padding_needed = target_len - leaf_index; + + let mut i: u256 = 0_u256; + while i < padding_needed { + leafs + .append( + ManageLeaf { + decoder_and_sanitizer: Zero::zero(), + target: Zero::zero(), + selector: Zero::zero(), + argument_addresses: ArrayTrait::new().span(), + description: "", + }, + ); + leaf_index += 1_u256; + i += 1_u256; + } +} + + +pub fn _get_proofs_using_tree( + leafs: Array, tree: Array>, +) -> Array> { + let mut proofs = ArrayTrait::new(); + for i in 0..leafs.len() { + let leaf = leafs.at(i); + let mut serialized_struct: Array = ArrayTrait::new(); + leaf.decoder_and_sanitizer.serialize(ref serialized_struct); + leaf.target.serialize(ref serialized_struct); + leaf.selector.serialize(ref serialized_struct); + leaf.argument_addresses.serialize(ref serialized_struct); + let first_element = serialized_struct.pop_front().unwrap(); + let mut state = PedersenTrait::new(first_element); + + while let Some(value) = serialized_struct.pop_front() { + state = state.update(value); + } + let leaf_hash = state.finalize(); + let proof = _generate_proof(leaf_hash, tree.clone()); + proofs.append(proof); + } + proofs +} + +pub fn _generate_proof(mut leaf: felt252, tree: Array>) -> Span { + let tree_length = tree.len(); + let mut proof = ArrayTrait::new(); + for i in 0..tree_length - 1 { + let tree_current_layer = tree.at(i); + let tree_current_layer_length = tree_current_layer.len(); + for j in 0..tree_current_layer_length { + if leaf == *tree_current_layer.at(j) { + let element_to_append = if j % 2 == 0 { + *tree_current_layer.at(j + 1) + } else { + *tree_current_layer.at(j - 1) + }; + leaf = PedersenCHasher::commutative_hash(leaf, element_to_append); + proof.append(element_to_append); + break; + } else { + assert(j != tree_current_layer_length - 1, 'leaf not found in tree'); + } + } + } + proof.span() +} + + +pub fn _add_vault_allocator_leafs( + ref leafs: Array, + ref leaf_index: u256, + vault_allocator: ContractAddress, + decoder_and_sanitizer: ContractAddress, + vault: ContractAddress, +) { + let underlying_asset = ERC4626ABIDispatcher { contract_address: vault }.asset(); + + // Approvals + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: underlying_asset, + selector: selector!("approve"), + argument_addresses: array![vault.into()].span(), + description: "Approve" + + " " + + get_symbol(vault) + + " " + + "to spend" + + " " + + get_symbol(underlying_asset), + }, + ); + leaf_index += 1; + + // Bring liquidity + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: vault, + selector: selector!("bring_liquidity"), + argument_addresses: array![].span(), + description: "Bring liquidity" + " " + get_symbol(vault), + }, + ); + leaf_index += 1; +} + + +pub fn _add_vesu_flash_loan_leafs( + ref leafs: Array, + ref leaf_index: u256, + vault: ContractAddress, + decoder_and_sanitizer: ContractAddress, + manager: ContractAddress, + asset: ContractAddress, + is_legacy: bool, +) { + let mut argument_addresses = ArrayTrait::new(); + manager.serialize(ref argument_addresses); + asset.serialize(ref argument_addresses); + is_legacy.serialize(ref argument_addresses); + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: manager, + selector: selector!("flash_loan"), + argument_addresses: argument_addresses.span(), + description: "Flash loan" + " " + get_symbol(asset), + }, + ); + leaf_index += 1; +} + + +pub fn _generate_trading_pairs_from_unique_assets( + ref unique_assets: Array, +) -> Array<(ContractAddress, ContractAddress)> { + let mut trading_pairs: Array<(ContractAddress, ContractAddress)> = ArrayTrait::new(); + loop { + match unique_assets.pop_front() { + Option::Some(asset) => { + for other_asset in @unique_assets { + trading_pairs.append((asset, *other_asset)) + } + }, + Option::None(_) => { break; }, + }; + } + trading_pairs +} + + +pub fn _append_unique_assets( + ref array_of_unique_assets: Array, + assets: Span, + consider_underlying: bool, +) { + for asset in assets { + let mut asset_to_consider = *asset; + if (consider_underlying) { + asset_to_consider = ERC4626ABIDispatcher { contract_address: *asset }.asset(); + } + if (!_contains_address(array_of_unique_assets.span(), asset_to_consider)) { + array_of_unique_assets.append(asset_to_consider); + } + } +} + + +pub fn _contains_address(span: Span, addr: ContractAddress) -> bool { + let mut i = 0; + while i < span.len() { + if *span.at(i) == addr { + return true; + } + i += 1; + } + false +} + + +pub fn get_symbol(contract_address: ContractAddress) -> ByteArray { + let ret_data = call_contract_syscall(contract_address, selector!("symbol"), array![].span()); + match ret_data { + Ok(res) => { + let res_len: u32 = res.len(); + if (res_len == 1) { + let symbol_felt = *res.at(0); + let mut symbol_byte_array: ByteArray = ""; + symbol_byte_array.append_word(symbol_felt, bytes_in_felt(symbol_felt)); + symbol_byte_array + } else { + let mut res_span = res; + Serde::::deserialize(ref res_span).unwrap() + } + }, + Err(revert_reason) => { panic!("revert_reason: {:?}", revert_reason); }, + } +} + + +fn bytes_in_felt(word: felt252) -> usize { + if word == 0 { + return 0; + } + let x: u256 = word.try_into().unwrap(); + + let mut p: u256 = 1_u256; + let mut bytes: usize = 0; + + while p <= x && bytes < 31 { + p = p * 256_u256; + bytes += 1; + } + + bytes +} diff --git a/packages/vault_allocator/src/merkle_tree/integrations/avnu.cairo b/packages/vault_allocator/src/merkle_tree/integrations/avnu.cairo new file mode 100644 index 00000000..f0182752 --- /dev/null +++ b/packages/vault_allocator/src/merkle_tree/integrations/avnu.cairo @@ -0,0 +1,72 @@ +use starknet::ContractAddress; +use vault_allocator::merkle_tree::base::{ManageLeaf, _contains_address, get_symbol}; + + +#[derive(PartialEq, Drop, Serde, Debug, Clone, starknet::Store)] +pub struct AvnuConfig { + pub sell_token: ContractAddress, + pub buy_token: ContractAddress, +} + + +pub fn _add_avnu_leafs( + ref leafs: Array, + ref leaf_index: u256, + vault_allocator: ContractAddress, + decoder_and_sanitizer: ContractAddress, + router: ContractAddress, + avnu_configs: Span, +) { + let mut seen_sells: Array = ArrayTrait::new(); + + for i in 0..avnu_configs.len() { + let avnu_config_elem = avnu_configs.at(i); + let sell_token_address = *avnu_config_elem.sell_token; + let buy_token_address = *avnu_config_elem.buy_token; + + if !_contains_address(seen_sells.span(), sell_token_address) { + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: sell_token_address, + selector: selector!("approve"), + argument_addresses: array![router.into()].span(), + description: "Approve" + + " " + + "avnu_router" + + " " + + "to spend" + + " " + + get_symbol(sell_token_address), + }, + ); + leaf_index += 1; + seen_sells.append(sell_token_address); + } + + // swap leaf à chaque paire + let mut argument_addresses = ArrayTrait::new(); + sell_token_address.serialize(ref argument_addresses); + buy_token_address.serialize(ref argument_addresses); + vault_allocator.serialize(ref argument_addresses); + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: router, + selector: selector!("multi_route_swap"), + argument_addresses: argument_addresses.span(), + description: "Multi route swap" + + " " + + get_symbol(sell_token_address) + + " " + + "for" + + " " + + get_symbol(buy_token_address), + }, + ); + leaf_index += 1; + } +} diff --git a/packages/vault_allocator/src/merkle_tree/integrations/erc4626.cairo b/packages/vault_allocator/src/merkle_tree/integrations/erc4626.cairo new file mode 100644 index 00000000..48917eb4 --- /dev/null +++ b/packages/vault_allocator/src/merkle_tree/integrations/erc4626.cairo @@ -0,0 +1,114 @@ +use openzeppelin::interfaces::erc4626::{IERC4626Dispatcher, IERC4626DispatcherTrait}; +use starknet::ContractAddress; +use vault_allocator::merkle_tree::base::{ManageLeaf, get_symbol}; + + +pub fn _add_erc4626_leafs( + ref leafs: Array, + ref leaf_index: u256, + vault_allocator: ContractAddress, + decoder_and_sanitizer: ContractAddress, + erc4626: ContractAddress, +) { + let erc4626_erc4646_disp = IERC4626Dispatcher { contract_address: erc4626 }; + let asset = erc4626_erc4646_disp.asset(); + + // Approvals + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: asset, + selector: selector!("approve"), + argument_addresses: array![erc4626.into()].span(), + description: "Approve" + + " " + + get_symbol(erc4626) + + " " + + "to spend" + + " " + + get_symbol(asset), + }, + ); + leaf_index += 1; + + // Deposits + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: erc4626, + selector: selector!("deposit"), + argument_addresses: array![vault_allocator.into()].span(), + description: "Deposit" + + " " + + get_symbol(asset) + + " " + + "for" + + " " + + get_symbol(erc4626), + }, + ); + leaf_index += 1; + + // Withdrawals + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: erc4626, + selector: selector!("withdraw"), + argument_addresses: array![vault_allocator.into(), vault_allocator.into()].span(), + description: "Withdraw" + + " " + + get_symbol(asset) + + " " + + "from" + + " " + + get_symbol(erc4626), + }, + ); + leaf_index += 1; + + // Minting + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: erc4626, + selector: selector!("mint"), + argument_addresses: array![vault_allocator.into()].span(), + description: "Mint" + + " " + + get_symbol(erc4626) + + " " + + "from" + + " " + + get_symbol(asset), + }, + ); + leaf_index += 1; + + // Redeeming + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: erc4626, + selector: selector!("redeem"), + argument_addresses: array![vault_allocator.into(), vault_allocator.into()].span(), + description: "Redeem" + + " " + + get_symbol(erc4626) + + " " + + "for" + + " " + + get_symbol(asset), + }, + ); + leaf_index += 1; +} diff --git a/packages/vault_allocator/src/merkle_tree/integrations/starknet_vault_kit_strategies.cairo b/packages/vault_allocator/src/merkle_tree/integrations/starknet_vault_kit_strategies.cairo new file mode 100644 index 00000000..4b3fa276 --- /dev/null +++ b/packages/vault_allocator/src/merkle_tree/integrations/starknet_vault_kit_strategies.cairo @@ -0,0 +1,102 @@ +use openzeppelin::interfaces::erc4626::{ERC4626ABIDispatcher, ERC4626ABIDispatcherTrait}; +use starknet::ContractAddress; +use vault_allocator::merkle_tree::base::{ManageLeaf, get_symbol}; + + +pub fn _add_starknet_vault_kit_strategies( + ref leafs: Array, + ref leaf_index: u256, + vault_allocator: ContractAddress, + decoder_and_sanitizer: ContractAddress, + starknet_vault_kit_strategy: ContractAddress, +) { + let starknet_vault_kit_strategy_erc4646_disp = ERC4626ABIDispatcher { + contract_address: starknet_vault_kit_strategy, + }; + let asset = starknet_vault_kit_strategy_erc4646_disp.asset(); + + // Approvals + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: asset, + selector: selector!("approve"), + argument_addresses: array![starknet_vault_kit_strategy.into()].span(), + description: "Approve" + + " " + + get_symbol(starknet_vault_kit_strategy) + + " " + + "to spend" + + " " + + get_symbol(asset), + }, + ); + leaf_index += 1; + + // Deposits + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: starknet_vault_kit_strategy, + selector: selector!("deposit"), + argument_addresses: array![vault_allocator.into()].span(), + description: "Deposit" + + " " + + get_symbol(asset) + + " " + + "for" + + " " + + get_symbol(starknet_vault_kit_strategy), + }, + ); + leaf_index += 1; + + // Minting + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: starknet_vault_kit_strategy, + selector: selector!("mint"), + argument_addresses: array![vault_allocator.into()].span(), + description: "Mint" + + " " + + get_symbol(starknet_vault_kit_strategy) + + " " + + "from" + + " " + + get_symbol(starknet_vault_kit_strategy), + }, + ); + leaf_index += 1; + + // Request Redeen + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: starknet_vault_kit_strategy, + selector: selector!("request_redeem"), + argument_addresses: array![vault_allocator.into(), vault_allocator.into()].span(), + description: "Request Redeem" + " " + get_symbol(starknet_vault_kit_strategy), + }, + ); + leaf_index += 1; + + // Claim Redeem + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: starknet_vault_kit_strategy, + selector: selector!("claim_redeem"), + argument_addresses: array![].span(), + description: "Claim Redeem" + " " + get_symbol(starknet_vault_kit_strategy), + }, + ); + leaf_index += 1; +} diff --git a/packages/vault_allocator/src/merkle_tree/integrations/vesu_v1.cairo b/packages/vault_allocator/src/merkle_tree/integrations/vesu_v1.cairo new file mode 100644 index 00000000..01c1d1d6 --- /dev/null +++ b/packages/vault_allocator/src/merkle_tree/integrations/vesu_v1.cairo @@ -0,0 +1,91 @@ +use core::to_byte_array::FormatAsByteArray; +use starknet::ContractAddress; +use vault_allocator::merkle_tree::base::{ManageLeaf, get_symbol}; +use vault_allocator::merkle_tree::registery::VESU_SINGLETON; + +#[derive(PartialEq, Drop, Serde, Debug, Clone)] +pub struct VesuV1Config { + pub pool_id: felt252, + pub collateral_asset: ContractAddress, + pub debt_assets: Span, +} + +pub fn _add_vesu_v1_leafs( + ref leafs: Array, + ref leaf_index: u256, + vault_allocator: ContractAddress, + decoder_and_sanitizer: ContractAddress, + vesu_v1_configs: Span, +) { + for i in 0..vesu_v1_configs.len() { + let vesu_v1_config_elem = vesu_v1_configs.at(i); + let pool_id = *vesu_v1_config_elem.pool_id; + let collateral_asset = *vesu_v1_config_elem.collateral_asset; + let debt_assets = *vesu_v1_config_elem.debt_assets; + let mut pool_id_str: ByteArray = FormatAsByteArray::format_as_byte_array(@pool_id, 16); + + // APPROVAL of collateral asset to the singleton + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: collateral_asset, + selector: selector!("approve"), + argument_addresses: array![VESU_SINGLETON().into()].span(), + description: "Approve" + + " " + + "singleton" + + "_" + + pool_id_str.clone() + + " " + + "to spend" + + " " + + get_symbol(collateral_asset), + }, + ); + leaf_index += 1; + + for j in 0..debt_assets.len() { + let debt_asset_elem = *debt_assets.at(j); + + // MODIFY POSITION + let mut argument_addresses_modify_position = ArrayTrait::new(); + + // pool_id + pool_id.serialize(ref argument_addresses_modify_position); + + // collateral_asset + collateral_asset.serialize(ref argument_addresses_modify_position); + + // debt_asset + debt_asset_elem.serialize(ref argument_addresses_modify_position); + + // user + vault_allocator.serialize(ref argument_addresses_modify_position); + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: VESU_SINGLETON(), + selector: selector!("modify_position"), + argument_addresses: argument_addresses_modify_position.span(), + description: "Modify position" + + " " + + "extension_pid" + + "_" + + pool_id_str.clone() + + " " + + "with collateral" + + " " + + get_symbol(collateral_asset) + + " " + + "and debt" + + " " + + get_symbol(debt_asset_elem), + }, + ); + leaf_index += 1; + } + } +} diff --git a/packages/vault_allocator/src/merkle_tree/integrations/vesu_v2.cairo b/packages/vault_allocator/src/merkle_tree/integrations/vesu_v2.cairo new file mode 100644 index 00000000..1335e298 --- /dev/null +++ b/packages/vault_allocator/src/merkle_tree/integrations/vesu_v2.cairo @@ -0,0 +1,95 @@ +use core::to_byte_array::FormatAsByteArray; +use starknet::ContractAddress; +use vault_allocator::merkle_tree::base::{ManageLeaf, get_symbol}; + + +#[derive(PartialEq, Drop, Serde, Debug, Clone)] +pub struct VesuV2Config { + pub pool_contract: ContractAddress, + pub collateral_asset: ContractAddress, + pub debt_assets: Span, +} + + +pub fn _add_vesu_v2_leafs( + ref leafs: Array, + ref leaf_index: u256, + vault_allocator: ContractAddress, + decoder_and_sanitizer: ContractAddress, + vesu_v2_configs: Span, +) { + for i in 0..vesu_v2_configs.len() { + let vesu_v2_config_elem = vesu_v2_configs.at(i); + let pool_contract = *vesu_v2_config_elem.pool_contract; + let collateral_asset = *vesu_v2_config_elem.collateral_asset; + let debt_assets = *vesu_v2_config_elem.debt_assets; + let pool_contract_felt: felt252 = pool_contract.into(); + let mut pool_contract_str: ByteArray = FormatAsByteArray::format_as_byte_array( + @pool_contract_felt, 16, + ); + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: collateral_asset, + selector: selector!("approve"), + argument_addresses: array![pool_contract.into()].span(), + description: "Approve" + + " " + + "pool contract" + + "_" + + pool_contract_str.clone() + + " " + + "to spend" + + " " + + get_symbol(collateral_asset), + }, + ); + leaf_index += 1; + + // debt mode + let debt_assets_len = debt_assets.len(); + for j in 0..debt_assets_len { + // APPROVAL of collateral asset to the pool contract + + let debt_asset = *debt_assets.at(j); + + // MODIFY POSITION + let mut argument_addresses_modify_position = ArrayTrait::new(); + + // collateral_asset + collateral_asset.serialize(ref argument_addresses_modify_position); + + // debt_asset + debt_asset.serialize(ref argument_addresses_modify_position); + + // user + vault_allocator.serialize(ref argument_addresses_modify_position); + + leafs + .append( + ManageLeaf { + decoder_and_sanitizer, + target: pool_contract, + selector: selector!("modify_position"), + argument_addresses: argument_addresses_modify_position.span(), + description: "Modify position" + + " " + + "extension_pid" + + "_" + + pool_contract_str.clone() + + " " + + "with collateral" + + " " + + get_symbol(collateral_asset) + + " " + + "and debt" + + " " + + get_symbol(debt_asset), + }, + ); + leaf_index += 1; + } + } +} diff --git a/packages/vault_allocator/src/test/register.cairo b/packages/vault_allocator/src/merkle_tree/registery.cairo similarity index 87% rename from packages/vault_allocator/src/test/register.cairo rename to packages/vault_allocator/src/merkle_tree/registery.cairo index 5a8cf769..2e385a05 100644 --- a/packages/vault_allocator/src/test/register.cairo +++ b/packages/vault_allocator/src/merkle_tree/registery.cairo @@ -43,6 +43,15 @@ pub fn VESU_SINGLETON() -> ContractAddress { pub const GENESIS_POOL_ID: felt252 = 2198503327643286920898110335698706244522220458610657370981979460625005526824; +pub fn VESU_GENESIS_POOL_V_TOKEN_WSTETH() -> ContractAddress { + 0x7cb1a46709214b94f51655be696a4ff6f9bdbbb6edb19418b6a55d190536048.try_into().unwrap() +} + +pub fn VESU_GENESIS_POOL_V_TOKEN_USDT() -> ContractAddress { + 0x40e480d202b47eb9335c31fc328ecda216231425dae74f87d1a97e6e7901dce.try_into().unwrap() +} + + // PRAGMA pub fn PRAGMA() -> ContractAddress { diff --git a/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo b/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo index f378aa95..13ee69ab 100644 --- a/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo +++ b/packages/vault_allocator/src/middlewares/avnu_middleware/avnu_middleware.cairo @@ -4,35 +4,49 @@ #[starknet::contract] pub mod AvnuMiddleware { - use alexandria_math::ed25519::c; + const BPS_SCALE: u16 = 10_000; + use core::num::traits::Zero; use openzeppelin::access::ownable::OwnableComponent; - use openzeppelin::token::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use openzeppelin::interfaces::upgrades::IUpgradeable; + use openzeppelin::upgrades::upgradeable::UpgradeableComponent; + use openzeppelin::utils::math; use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - use starknet::{ContractAddress, get_caller_address, get_contract_address}; + use starknet::{ContractAddress, get_block_timestamp, get_caller_address, get_contract_address}; use vault_allocator::decoders_and_sanitizers::decoder_custom_types::Route; use vault_allocator::integration_interfaces::avnu::{ IAvnuExchangeDispatcher, IAvnuExchangeDispatcherTrait, }; + use vault_allocator::merkle_tree::registery::AVNU_ROUTER; use vault_allocator::middlewares::avnu_middleware::errors::Errors; use vault_allocator::middlewares::avnu_middleware::interface::IAvnuMiddleware; use vault_allocator::periphery::price_router::interface::{ IPriceRouterDispatcher, IPriceRouterDispatcherTrait, }; + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); #[abi(embed_v0)] impl OwnableImpl = OwnableComponent::OwnableImpl; - impl InternalImpl = OwnableComponent::InternalImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; #[storage] struct Storage { #[substorage(v0)] ownable: OwnableComponent::Storage, - avnu_router: IAvnuExchangeDispatcher, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, price_router: IPriceRouterDispatcher, - slippage_tolerance_bps: u256, + vault_allocator: ContractAddress, + slippage: u16, + period: u64, + allowed_calls_per_period: u64, + current_window_id: u64, + window_call_count: u64, } #[event] @@ -40,13 +54,16 @@ pub mod AvnuMiddleware { pub enum Event { #[flat] OwnableEvent: OwnableComponent::Event, - SlippageUpdated: SlippageUpdated, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + ConfigUpdated: ConfigUpdated, } #[derive(Drop, starknet::Event)] - pub struct SlippageUpdated { - pub old_slippage: u256, - pub new_slippage: u256, + struct ConfigUpdated { + pub slippage: u16, + period: u64, + allowed_calls_per_period: u64, } @@ -54,43 +71,54 @@ pub mod AvnuMiddleware { fn constructor( ref self: ContractState, owner: ContractAddress, - avnu_router: ContractAddress, + vault_allocator: ContractAddress, price_router: ContractAddress, - initial_slippage_bps: u256, + slippage: u16, + period: u64, + allowed_calls_per_period: u64, ) { self.ownable.initializer(owner); - self.avnu_router.write(IAvnuExchangeDispatcher { contract_address: avnu_router }); self.price_router.write(IPriceRouterDispatcher { contract_address: price_router }); - self.slippage_tolerance_bps.write(initial_slippage_bps); + self.vault_allocator.write(vault_allocator); + self._set_config(slippage, period, allowed_calls_per_period) } + #[abi(embed_v0)] + impl UpgradeableImpl of IUpgradeable { + fn upgrade(ref self: ContractState, new_class_hash: starknet::ClassHash) { + self.ownable.assert_only_owner(); + self.upgradeable.upgrade(new_class_hash); + } + } #[abi(embed_v0)] impl AvnuMiddlewareViewImpl of IAvnuMiddleware { fn avnu_router(self: @ContractState) -> ContractAddress { - self.avnu_router.read().contract_address + AVNU_ROUTER() } fn price_router(self: @ContractState) -> ContractAddress { self.price_router.read().contract_address } - fn slippage_tolerance_bps(self: @ContractState) -> u256 { - self.slippage_tolerance_bps.read() + fn vault_allocator(self: @ContractState) -> ContractAddress { + self.vault_allocator.read() } - fn set_slippage_tolerance_bps(ref self: ContractState, new_slippage_bps: u256) { + fn config(self: @ContractState) -> (u16, u64, u64) { + (self.slippage.read(), self.period.read(), self.allowed_calls_per_period.read()) + } + fn set_config( + ref self: ContractState, slippage: u16, period: u64, allowed_calls_per_period: u64, + ) { self.ownable.assert_only_owner(); - if (new_slippage_bps > 10_000) { - Errors::slippage_exceeds_max(new_slippage_bps); - } - let old_slippage = self.slippage_tolerance_bps.read(); - self.slippage_tolerance_bps.write(new_slippage_bps); - self.emit(SlippageUpdated { old_slippage, new_slippage: new_slippage_bps }); + self._set_config(slippage, period, allowed_calls_per_period); + self.emit(ConfigUpdated { slippage, period, allowed_calls_per_period }); } + fn multi_route_swap( - self: @ContractState, + ref self: ContractState, sell_token_address: ContractAddress, sell_token_amount: u256, buy_token_address: ContractAddress, @@ -102,7 +130,13 @@ pub mod AvnuMiddleware { routes: Array, ) -> u256 { let caller = get_caller_address(); + self.enforce_rate_limit(caller); let this = get_contract_address(); + + if (sell_token_amount == Zero::zero()) { + return Zero::zero(); + } + if sell_token_address == buy_token_address { ERC20ABIDispatcher { contract_address: sell_token_address } .transfer_from(caller, beneficiary, sell_token_amount); @@ -110,7 +144,7 @@ pub mod AvnuMiddleware { } let sell = ERC20ABIDispatcher { contract_address: sell_token_address }; let buy = ERC20ABIDispatcher { contract_address: buy_token_address }; - let avnu = self.avnu_router.read(); + let avnu = IAvnuExchangeDispatcher { contract_address: AVNU_ROUTER() }; sell.transfer_from(caller, this, sell_token_amount); sell.approve(avnu.contract_address, sell_token_amount); let quote_out = self @@ -118,9 +152,13 @@ pub mod AvnuMiddleware { .read() .get_value(sell_token_address, sell_token_amount, buy_token_address); - let computed_min = quote_out - * (10_000_u256 - self.slippage_tolerance_bps.read()) - / 10_000_u256; + let computed_min = math::u256_mul_div( + quote_out, + (BPS_SCALE - self.slippage.read()).into(), + BPS_SCALE.into(), + math::Rounding::Ceil, + ); + let min_out = if buy_token_min_amount < computed_min { computed_min } else { @@ -133,11 +171,11 @@ pub mod AvnuMiddleware { sell_token_address, sell_token_amount, buy_token_address, - buy_token_amount, + Zero::zero(), min_out, this, - integrator_fee_amount_bps, - integrator_fee_recipient, + Zero::zero(), + Zero::zero(), routes, ); let buy_bal_1 = buy.balance_of(this); @@ -146,7 +184,53 @@ pub mod AvnuMiddleware { Errors::insufficient_output(out, min_out); } buy.transfer(beneficiary, out); - 0 + out + } + } + + + #[generate_trait] + pub impl InternalFunctions of InternalFunctionsTrait { + fn enforce_rate_limit(ref self: ContractState, caller: ContractAddress) { + if (caller != self.vault_allocator.read()) { + Errors::caller_not_vault_allocator(); + } + + let period = self.period.read(); + let ts: u64 = get_block_timestamp(); + let window_id: u64 = ts / period; + + if (window_id != self.current_window_id.read()) { + self.current_window_id.write(window_id); + self.window_call_count.write(0); + } + + let current = self.window_call_count.read(); + let next = current + 1; + let allowed = self.allowed_calls_per_period.read(); + + if (next > allowed) { + Errors::rate_limit_exceeded(next, allowed); + } + self.window_call_count.write(next); + } + + fn _set_config(ref self: ContractState, slippage: u16, period: u64, allowed: u64) { + if (slippage >= BPS_SCALE) { + Errors::slippage_exceeds_max(slippage); + } + if (period.is_zero()) { + Errors::period_zero(); + } + if (allowed.is_zero()) { + Errors::allowed_calls_per_period_zero(); + } + + self.slippage.write(slippage); + self.period.write(period); + self.allowed_calls_per_period.write(allowed); + self.current_window_id.write(0); + self.window_call_count.write(0); } } } diff --git a/packages/vault_allocator/src/middlewares/avnu_middleware/errors.cairo b/packages/vault_allocator/src/middlewares/avnu_middleware/errors.cairo index 0e62477c..cef66fda 100644 --- a/packages/vault_allocator/src/middlewares/avnu_middleware/errors.cairo +++ b/packages/vault_allocator/src/middlewares/avnu_middleware/errors.cairo @@ -7,7 +7,23 @@ pub mod Errors { panic!("Insufficient output: {} < {}", out, min); } - pub fn slippage_exceeds_max(slippage: u256) { + pub fn slippage_exceeds_max(slippage: u16) { panic!("Slippage exceeds max: {}", slippage); } + + pub fn rate_limit_exceeded(next: u64, allowed: u64) { + panic!("Rate limit exceeded: {} > {}", next, allowed); + } + + pub fn period_zero() { + panic!("Period is zero"); + } + + pub fn allowed_calls_per_period_zero() { + panic!("Allowed calls per period is zero"); + } + + pub fn caller_not_vault_allocator() { + panic!("Caller not vault allocator"); + } } diff --git a/packages/vault_allocator/src/middlewares/avnu_middleware/interface.cairo b/packages/vault_allocator/src/middlewares/avnu_middleware/interface.cairo index bca4ad9d..7b424486 100644 --- a/packages/vault_allocator/src/middlewares/avnu_middleware/interface.cairo +++ b/packages/vault_allocator/src/middlewares/avnu_middleware/interface.cairo @@ -9,11 +9,11 @@ use vault_allocator::decoders_and_sanitizers::decoder_custom_types::Route; pub trait IAvnuMiddleware { fn avnu_router(self: @T) -> ContractAddress; fn price_router(self: @T) -> ContractAddress; - fn slippage_tolerance_bps(self: @T) -> u256; - - + fn vault_allocator(self: @T) -> ContractAddress; + fn config(self: @T) -> (u16, u64, u64); + fn set_config(ref self: T, slippage: u16, period: u64, allowed_calls_per_period: u64); fn multi_route_swap( - self: @T, + ref self: T, sell_token_address: ContractAddress, sell_token_amount: u256, buy_token_address: ContractAddress, @@ -24,5 +24,4 @@ pub trait IAvnuMiddleware { integrator_fee_recipient: ContractAddress, routes: Array, ) -> u256; - fn set_slippage_tolerance_bps(ref self: T, new_slippage_bps: u256); } diff --git a/packages/vault_allocator/src/mocks/erc20.cairo b/packages/vault_allocator/src/mocks/erc20.cairo index 4578819d..9da89c8d 100644 --- a/packages/vault_allocator/src/mocks/erc20.cairo +++ b/packages/vault_allocator/src/mocks/erc20.cairo @@ -5,9 +5,9 @@ #[starknet::contract] pub mod Erc20Mock { use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::interfaces::upgrades::IUpgradeable; use openzeppelin::token::erc20::{DefaultConfig, ERC20Component, ERC20HooksEmptyImpl}; use openzeppelin::upgrades::UpgradeableComponent; - use openzeppelin::upgrades::interface::IUpgradeable; use starknet::{ClassHash, ContractAddress}; component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); diff --git a/packages/vault_allocator/src/mocks/erc4626.cairo b/packages/vault_allocator/src/mocks/erc4626.cairo index f3d2b4aa..66de550d 100644 --- a/packages/vault_allocator/src/mocks/erc4626.cairo +++ b/packages/vault_allocator/src/mocks/erc4626.cairo @@ -5,8 +5,8 @@ #[starknet::contract] pub mod Erc4626Mock { use openzeppelin::token::erc20::extensions::erc4626::{ - DefaultConfig, ERC4626Component, ERC4626DefaultLimits, ERC4626DefaultNoFees, - ERC4626HooksEmptyImpl, + DefaultConfig, ERC4626Component, ERC4626DefaultNoFees, ERC4626DefaultNoLimits, + ERC4626EmptyHooks, ERC4626SelfAssetsManagement, }; use openzeppelin::token::erc20::{ DefaultConfig as ERC20DefaultConfig, ERC20Component, ERC20HooksEmptyImpl, diff --git a/packages/vault_allocator/src/mocks/flashloan.cairo b/packages/vault_allocator/src/mocks/flashloan.cairo deleted file mode 100644 index 7829a13b..00000000 --- a/packages/vault_allocator/src/mocks/flashloan.cairo +++ /dev/null @@ -1,139 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2025 Starknet Vault Kit -// Licensed under the MIT License. See LICENSE file for details. -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; -use starknet::{ContractAddress, get_contract_address}; - -fn transfer_asset( - asset: ContractAddress, - sender: ContractAddress, - to: ContractAddress, - amount: u256, - is_legacy: bool, -) { - let erc20 = ERC20ABIDispatcher { contract_address: asset }; - if sender == get_contract_address() { - assert!(erc20.transfer(to, amount), "transfer-failed"); - } else if is_legacy { - assert!(erc20.transferFrom(sender, to, amount), "transferFrom-failed"); - } else { - assert!(erc20.transfer_from(sender, to, amount), "transfer-from-failed"); - } -} - -#[starknet::contract] -pub mod FlashLoanSingletonMock { - use core::num::traits::Zero; - use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - use starknet::{ContractAddress, get_caller_address}; - use vault_allocator::integration_interfaces::vesu::{ - IFlashloanReceiverDispatcher, IFlashloanReceiverDispatcherTrait, - }; - use super::{get_contract_address, transfer_asset}; - - - #[storage] - struct Storage { - do_nothing: bool, - i_did_something: bool, - do_wrong_callback: bool, - } - - #[constructor] - fn constructor(ref self: ContractState) {} - - #[abi(embed_v0)] - impl FlashLoanSingletonMockImpl of super::IFlashLoanSingletonMock { - fn do_wrong_callback(self: @ContractState) -> bool { - self.do_wrong_callback.read() - } - - fn set_do_wrong_callback(ref self: ContractState, do_wrong_callback: bool) { - self.do_wrong_callback.write(do_wrong_callback); - } - - fn flash_loan( - ref self: ContractState, - receiver: ContractAddress, - asset: ContractAddress, - amount: u256, - is_legacy: bool, - data: Span, - ) { - if (!self.do_nothing.read()) { - if (!self.do_wrong_callback.read()) { - transfer_asset(asset, get_contract_address(), receiver, amount, is_legacy); - IFlashloanReceiverDispatcher { contract_address: receiver } - .on_flash_loan(get_caller_address(), asset, amount, data); - transfer_asset(asset, receiver, get_contract_address(), amount, is_legacy); - } else { - let mut flash_loan_data_proofs: Array> = ArrayTrait::new(); - flash_loan_data_proofs.append(array![Zero::zero()].span()); - let mut flash_loan_data_decoder_and_sanitizer: Array = - ArrayTrait::new(); - flash_loan_data_decoder_and_sanitizer.append(Zero::zero()); - let mut flash_loan_data_target: Array = ArrayTrait::new(); - flash_loan_data_target.append(Zero::zero()); - let mut flash_loan_data_selector: Array = ArrayTrait::new(); - flash_loan_data_selector.append(Zero::zero()); - let mut flash_loan_data_calldata: Array> = ArrayTrait::new(); - flash_loan_data_calldata.append(array![Zero::zero()].span()); - let mut serialized_flash_loan_data = ArrayTrait::new(); - ( - flash_loan_data_proofs.span(), - flash_loan_data_decoder_and_sanitizer.span(), - flash_loan_data_target.span(), - flash_loan_data_selector.span(), - flash_loan_data_calldata.span(), - ) - .serialize(ref serialized_flash_loan_data); - - transfer_asset(asset, get_contract_address(), receiver, amount, is_legacy); - IFlashloanReceiverDispatcher { contract_address: receiver } - .on_flash_loan( - get_caller_address(), asset, amount, serialized_flash_loan_data.span(), - ); - transfer_asset(asset, receiver, get_contract_address(), amount, is_legacy); - } - } - } - - fn approve(ref self: ContractState, token: ContractAddress, amount: u256) { - transfer_asset(token, get_caller_address(), get_contract_address(), amount, true); - transfer_asset(token, get_contract_address(), get_caller_address(), amount, true); - self.i_did_something.write(true) - } - - fn i_did_something(self: @ContractState) -> bool { - self.i_did_something.read() - } - - fn do_nothing(self: @ContractState) -> bool { - self.do_nothing.read() - } - - fn set_do_nothing(ref self: ContractState, do_nothing: bool) { - self.do_nothing.write(do_nothing); - } - } -} - -#[starknet::interface] -pub trait IFlashLoanSingletonMock { - fn flash_loan( - ref self: TContractState, - receiver: ContractAddress, - asset: ContractAddress, - amount: u256, - is_legacy: bool, - data: Span, - ); - - fn approve(ref self: TContractState, token: ContractAddress, amount: u256); - - fn i_did_something(self: @TContractState) -> bool; - fn do_nothing(self: @TContractState) -> bool; - fn do_wrong_callback(self: @TContractState) -> bool; - fn set_do_nothing(ref self: TContractState, do_nothing: bool); - fn set_do_wrong_callback(ref self: TContractState, do_wrong_callback: bool); -} diff --git a/packages/vault_allocator/src/mocks/vault.cairo b/packages/vault_allocator/src/mocks/vault.cairo new file mode 100644 index 00000000..05a5aeb8 --- /dev/null +++ b/packages/vault_allocator/src/mocks/vault.cairo @@ -0,0 +1,179 @@ +#[starknet::contract] +pub mod MockVault { + use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use openzeppelin::token::erc20::extensions::erc4626::{ + DefaultConfig, ERC4626Component, ERC4626DefaultNoFees, ERC4626DefaultNoLimits, + }; + use openzeppelin::token::erc20::{ + DefaultConfig as ERC20DefaultConfig, ERC20Component, ERC20HooksEmptyImpl, + }; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use starknet::{ContractAddress, get_caller_address}; + + // --- OpenZeppelin Component Integrations --- + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + component!(path: ERC4626Component, storage: erc4626, event: ERC4626Event); + + // --- ERC4626 Implementation --- + #[abi(embed_v0)] + impl ERC4626Impl = ERC4626Component::ERC4626Impl; + impl ERC4626InternalImpl = ERC4626Component::InternalImpl; + + // --- ERC20 Implementation --- + #[abi(embed_v0)] + impl ERC20Impl = ERC20Component::ERC20Impl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + // --- ERC20 Metadata Implementation --- + #[abi(embed_v0)] + impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; + + #[storage] + pub struct Storage { + // --- Component Storage --- + #[substorage(v0)] + erc20: ERC20Component::Storage, + #[substorage(v0)] + erc4626: ERC4626Component::Storage, + // --- Vault State --- + buffer: u256, // Assets available in vault for instant redemption + aum: u256 // Assets under management (deployed to allocators) + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + ERC20Event: ERC20Component::Event, + ERC4626Event: ERC4626Component::Event, + } + + #[constructor] + fn constructor(ref self: ContractState, underlying_asset: ContractAddress) { + self.erc20.initializer("MockVault", "MVAULT"); + self.erc4626.initializer(underlying_asset); + } + + + /// Implementation of asset management for ERC4626 compatibility + impl MockVaultAssetsManagementImpl of ERC4626Component::AssetsManagementTrait { + fn get_total_assets(self: @ERC4626Component::ComponentState) -> u256 { + let contract_state = self.get_contract(); + contract_state.buffer.read() + contract_state.aum.read() + } + + fn transfer_assets_in( + ref self: ERC4626Component::ComponentState, + from: ContractAddress, + assets: u256, + ) { + let this = starknet::get_contract_address(); + let asset_dispatcher = ERC20ABIDispatcher { + contract_address: self.ERC4626_asset.read(), + }; + assert( + asset_dispatcher.transfer_from(from, this, assets), + ERC4626Component::Errors::TOKEN_TRANSFER_FAILED, + ); + } + + fn transfer_assets_out( + ref self: ERC4626Component::ComponentState, + to: ContractAddress, + assets: u256, + ) { + let asset_dispatcher = ERC20ABIDispatcher { + contract_address: self.ERC4626_asset.read(), + }; + assert( + asset_dispatcher.transfer(to, assets), + ERC4626Component::Errors::TOKEN_TRANSFER_FAILED, + ); + } + } + + pub impl MockVaultHooksImpl of ERC4626Component::ERC4626HooksTrait { + fn before_withdraw( + ref self: ERC4626Component::ComponentState, + caller: ContractAddress, + receiver: ContractAddress, + owner: ContractAddress, + assets: u256, + shares: u256, + fee: Option, + ) {} + + fn after_withdraw( + ref self: ERC4626Component::ComponentState, + caller: ContractAddress, + receiver: ContractAddress, + owner: ContractAddress, + assets: u256, + shares: u256, + fee: Option, + ) { + let mut contract_state = self.get_contract_mut(); + contract_state.buffer.write(contract_state.buffer.read() - assets); + } + + fn before_deposit( + ref self: ERC4626Component::ComponentState, + caller: ContractAddress, + receiver: ContractAddress, + assets: u256, + shares: u256, + fee: Option, + ) {} + + fn after_deposit( + ref self: ERC4626Component::ComponentState, + caller: ContractAddress, + receiver: ContractAddress, + assets: u256, + shares: u256, + fee: Option, + ) { + let mut contract_state = self.get_contract_mut(); + contract_state.buffer.write(contract_state.buffer.read() + assets); + } + } + + #[abi(embed_v0)] + impl MockVaultImpl of MockVaultTrait { + /// Bring assets back from allocators to vault buffer + fn bring_liquidity(ref self: ContractState, amount: u256) { + ERC20ABIDispatcher { contract_address: self.erc4626.asset() } + .transfer_from(get_caller_address(), starknet::get_contract_address(), amount); + self.buffer.write(self.buffer.read() + amount); + self.aum.write(self.aum.read() - amount); + } + + /// Get current buffer amount + fn buffer(self: @ContractState) -> u256 { + self.buffer.read() + } + + /// Get current assets under management + fn aum(self: @ContractState) -> u256 { + self.aum.read() + } + + /// Set buffer for testing purposes + fn set_buffer(ref self: ContractState, amount: u256) { + self.buffer.write(amount); + } + + /// Set AUM for testing purposes + fn set_aum(ref self: ContractState, amount: u256) { + self.aum.write(amount); + } + } + + #[starknet::interface] + pub trait MockVaultTrait { + fn bring_liquidity(ref self: TContractState, amount: u256); + fn buffer(self: @TContractState) -> u256; + fn aum(self: @TContractState) -> u256; + fn set_buffer(ref self: TContractState, amount: u256); + fn set_aum(ref self: TContractState, amount: u256); + } +} diff --git a/packages/vault_allocator/src/periphery/price_router/interface.cairo b/packages/vault_allocator/src/periphery/price_router/interface.cairo index f51d8b26..9590a07f 100644 --- a/packages/vault_allocator/src/periphery/price_router/interface.cairo +++ b/packages/vault_allocator/src/periphery/price_router/interface.cairo @@ -2,8 +2,9 @@ // Copyright (c) 2025 Starknet Vault Kit // Licensed under the MIT License. See LICENSE file for details. -use pragma_lib::types::PragmaPricesResponse; use starknet::ContractAddress; +use vault_allocator::integration_interfaces::pragma::PragmaPricesResponse; + #[starknet::interface] pub trait IPriceRouter { diff --git a/packages/vault_allocator/src/periphery/price_router/price_router.cairo b/packages/vault_allocator/src/periphery/price_router/price_router.cairo index f3a7fb32..46928f55 100644 --- a/packages/vault_allocator/src/periphery/price_router/price_router.cairo +++ b/packages/vault_allocator/src/periphery/price_router/price_router.cairo @@ -4,17 +4,18 @@ #[starknet::contract] pub mod PriceRouter { - use alexandria_math::ed25519::c; use core::num::traits::{Pow, Zero}; use openzeppelin::access::ownable::OwnableComponent; - use openzeppelin::token::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; - use pragma_lib::abi::{IPragmaABIDispatcher, IPragmaABIDispatcherTrait}; - use pragma_lib::types::{AggregationMode, DataType, PragmaPricesResponse}; + use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use openzeppelin::utils::math; use starknet::ContractAddress; use starknet::storage::{ Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, StoragePointerWriteAccess, }; + use vault_allocator::integration_interfaces::pragma::{ + DataType, IPragmaABIDispatcher, IPragmaABIDispatcherTrait, PragmaPricesResponse, + }; use vault_allocator::periphery::price_router::errors::Errors; use vault_allocator::periphery::price_router::interface::IPriceRouter; @@ -89,7 +90,7 @@ pub mod PriceRouter { let num: u256 = amount * base_price * scale_quote; let den: u256 = quote_price * scale_base; - num / den + math::u256_mul_div(num, 1, den, math::Rounding::Ceil) } fn asset_to_id(self: @ContractState, asset: ContractAddress) -> felt252 { self.asset_to_id.read(asset) diff --git a/packages/vault_allocator/src/periphery/price_router_vesu/errors.cairo b/packages/vault_allocator/src/periphery/price_router_vesu/errors.cairo new file mode 100644 index 00000000..0be8a340 --- /dev/null +++ b/packages/vault_allocator/src/periphery/price_router_vesu/errors.cairo @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +pub mod Errors { + use starknet::ContractAddress; + + pub fn invalid_price(asset: ContractAddress) { + panic!("Invalid price for asset: {:?}", asset); + } +} diff --git a/packages/vault_allocator/src/periphery/price_router_vesu/interface.cairo b/packages/vault_allocator/src/periphery/price_router_vesu/interface.cairo new file mode 100644 index 00000000..775165cf --- /dev/null +++ b/packages/vault_allocator/src/periphery/price_router_vesu/interface.cairo @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use starknet::ContractAddress; + + +#[starknet::interface] +pub trait IPriceRouter { + fn get_value( + self: @T, base_asset: ContractAddress, amount: u256, quote_asset: ContractAddress, + ) -> u256; + fn vesu_oracle_contract(self: @T) -> ContractAddress; +} + diff --git a/packages/vault_allocator/src/periphery/price_router_vesu/price_router_vesu.cairo b/packages/vault_allocator/src/periphery/price_router_vesu/price_router_vesu.cairo new file mode 100644 index 00000000..4651b32c --- /dev/null +++ b/packages/vault_allocator/src/periphery/price_router_vesu/price_router_vesu.cairo @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +#[starknet::contract] +pub mod PriceRouter { + use core::num::traits::Pow; + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; + use openzeppelin::utils::math; + use starknet::ContractAddress; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use vault_allocator::integration_interfaces::vesu_v2::{ + IOracleDispatcher, IOracleDispatcherTrait, + }; + use vault_allocator::periphery::price_router_vesu::errors::Errors; + use vault_allocator::periphery::price_router_vesu::interface::IPriceRouter; + + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + impl InternalImpl = OwnableComponent::InternalImpl; + + + #[storage] + struct Storage { + #[substorage(v0)] + ownable: OwnableComponent::Storage, + vesu_oracle: IOracleDispatcher, + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + } + + + #[constructor] + fn constructor(ref self: ContractState, owner: ContractAddress, vesu_oracle: ContractAddress) { + self.ownable.initializer(owner); + self.vesu_oracle.write(IOracleDispatcher { contract_address: vesu_oracle }); + } + + + #[abi(embed_v0)] + impl PriceRouterImpl of IPriceRouter { + fn get_value( + self: @ContractState, + base_asset: ContractAddress, + amount: u256, + quote_asset: ContractAddress, + ) -> u256 { + if base_asset == quote_asset { + return amount; + } + + let base_resp = self + .vesu_oracle + .read() + .price(base_asset); // USD price with its own decimals + if !base_resp.is_valid { + Errors::invalid_price(base_asset); + } + let quote_resp = self.vesu_oracle.read().price(quote_asset); + if !quote_resp.is_valid { + Errors::invalid_price(quote_asset); + } + + let base_price: u256 = base_resp.value; + let quote_price: u256 = quote_resp.value; + + let asset_decimals_base: u8 = ERC20ABIDispatcher { contract_address: base_asset } + .decimals(); + let asset_decimals_quote: u8 = ERC20ABIDispatcher { contract_address: quote_asset } + .decimals(); + + let scale_base: u256 = 10_u256.pow(asset_decimals_base.into()); + let scale_quote: u256 = 10_u256.pow(asset_decimals_quote.into()); + + let num: u256 = amount * base_price * scale_quote; + let den: u256 = quote_price * scale_base; + math::u256_mul_div(num, 1, den, math::Rounding::Ceil) + } + + fn vesu_oracle_contract(self: @ContractState) -> ContractAddress { + self.vesu_oracle.read().contract_address + } + } +} diff --git a/packages/vault_allocator/src/test/creator.cairo b/packages/vault_allocator/src/test/creator.cairo new file mode 100644 index 00000000..9f01c5c0 --- /dev/null +++ b/packages/vault_allocator/src/test/creator.cairo @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. +use alexandria_math::i257::I257Impl; +use starknet::ContractAddress; +use vault_allocator::merkle_tree::base::{ + ManageLeaf, _pad_leafs_to_power_of_two, generate_merkle_tree, get_leaf_hash, +}; +use vault_allocator::merkle_tree::integrations::avnu::{AvnuConfig, _add_avnu_leafs}; +use vault_allocator::merkle_tree::integrations::vesu_v1::{VesuV1Config, _add_vesu_v1_leafs}; +use vault_allocator::merkle_tree::registery::{ETH, GENESIS_POOL_ID, wstETH}; +use super::utils::DUMMY_ADDRESS; + + +#[derive(PartialEq, Drop, Serde, Debug, Clone)] +pub struct ManageLeafAdditionalData { + pub decoder_and_sanitizer: ContractAddress, + pub target: ContractAddress, + pub selector: felt252, + pub argument_addresses: Span, + pub description: ByteArray, + pub leaf_index: u32, + pub leaf_hash: felt252, +} +#[fork("MAINNET")] +#[test] +fn test_creator() { + let mut leafs: Array = ArrayTrait::new(); + let mut leaf_index: u256 = 0; + + // MANDATORY + let vault: ContractAddress = DUMMY_ADDRESS(); + let vault_allocator = DUMMY_ADDRESS(); + let manager = DUMMY_ADDRESS(); + let decoder_and_sanitizer = DUMMY_ADDRESS(); + let router = DUMMY_ADDRESS(); + + // INTEGRATIONS + + let pool_id = GENESIS_POOL_ID; + + _add_vesu_v1_leafs( + ref leafs, + ref leaf_index, + vault_allocator, + decoder_and_sanitizer, + array![ + VesuV1Config { pool_id, collateral_asset: wstETH(), debt_assets: array![ETH()].span() }, + ] + .span(), + ); + + let mut pairs_to_swap = ArrayTrait::new(); + pairs_to_swap.append((ETH(), wstETH())); + + _add_avnu_leafs( + ref leafs, + ref leaf_index, + vault_allocator, + decoder_and_sanitizer, + router, + array![AvnuConfig { sell_token: ETH(), buy_token: wstETH() }].span(), + ); + + let leaf_used = leafs.len(); + + // MERKLE TREE CREATION + _pad_leafs_to_power_of_two(ref leafs, ref leaf_index); + let tree_capacity = leafs.len(); + let tree = generate_merkle_tree(leafs.span()); + let root = *tree.at(tree.len() - 1).at(0); + + let mut leaf_additional_data = ArrayTrait::new(); + for i in 0..leaf_used { + leaf_additional_data + .append( + ManageLeafAdditionalData { + decoder_and_sanitizer: *leafs.at(i).decoder_and_sanitizer, + target: *leafs.at(i).target, + selector: *leafs.at(i).selector, + argument_addresses: *leafs.at(i).argument_addresses, + description: leafs.at(i).description.clone(), + leaf_index: i, + leaf_hash: get_leaf_hash(leafs.at(i).clone()), + }, + ); + } + + // PRINT + println!("vault: {:?}", vault); + println!("vault_allocator: {:?}", vault_allocator); + println!("manager: {:?}", manager); + println!("decoder_and_sanitizer: {:?}", decoder_and_sanitizer); + println!("root: {:?}", root); + println!("tree_capacity: {:?}", tree_capacity); + println!("leaf_used: {:?}", leaf_used); + println!("leaf_additional_data: {:?}", leaf_additional_data); + println!("tree: {:?}", tree); +} diff --git a/packages/vault_allocator/src/test/integrations/avnu.cairo b/packages/vault_allocator/src/test/integrations/avnu.cairo index 7cf5765f..57360edc 100644 --- a/packages/vault_allocator/src/test/integrations/avnu.cairo +++ b/packages/vault_allocator/src/test/integrations/avnu.cairo @@ -4,32 +4,35 @@ use alexandria_math::i257::I257Impl; use core::num::traits::Zero; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; use snforge_std::{map_entry_address, store}; use starknet::ContractAddress; use vault_allocator::decoders_and_sanitizers::decoder_custom_types::Route; use vault_allocator::manager::interface::IManagerDispatcherTrait; -use vault_allocator::middlewares::avnu_middleware::interface::{ - IAvnuMiddlewareDispatcher, IAvnuMiddlewareDispatcherTrait, +use vault_allocator::merkle_tree::base::{ + ManageLeaf, _get_proofs_using_tree, _pad_leafs_to_power_of_two, generate_merkle_tree, }; -use vault_allocator::test::register::{ETH, VESU_SINGLETON, wstETH}; +use vault_allocator::merkle_tree::integrations::avnu::{AvnuConfig, _add_avnu_leafs}; +use vault_allocator::merkle_tree::registery::{ETH, wstETH}; use vault_allocator::test::utils::{ - ManageLeaf, OWNER, STRATEGIST, WAD, _add_avnu_leafs, _get_proofs_using_tree, - _pad_leafs_to_power_of_two, cheat_caller_address_once, deploy_avnu_middleware, deploy_manager, + OWNER, STRATEGIST, WAD, cheat_caller_address_once, deploy_avnu_middleware, deploy_manager, deploy_price_router, deploy_simple_decoder_and_sanitizer, deploy_vault_allocator, - generate_merkle_tree, initialize_price_router, + initialize_price_router, }; use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcherTrait; + #[fork("AVNU")] #[test] fn test_manage_vault_with_merkle_verification_avnu() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); let price_router = deploy_price_router(); initialize_price_router(price_router); - let avnu_middleware = deploy_avnu_middleware(price_router); + let avnu_middleware = deploy_avnu_middleware( + vault_allocator.contract_address, price_router, 100, 100000, 1000000, + ); // 1% slippage let mut leafs: Array = ArrayTrait::new(); let mut leaf_index: u256 = 0; @@ -40,7 +43,7 @@ fn test_manage_vault_with_merkle_verification_avnu() { vault_allocator.contract_address, simple_decoder_and_sanitizer, avnu_middleware, - array![(wstETH(), ETH())], + array![AvnuConfig { sell_token: wstETH(), buy_token: ETH() }].span(), ); _pad_leafs_to_power_of_two(ref leafs, ref leaf_index); @@ -143,10 +146,6 @@ fn test_manage_vault_with_merkle_verification_avnu() { manage_leafs.append(leafs.at(0).clone()); manage_leafs.append(leafs.at(1).clone()); - cheat_caller_address_once(avnu_middleware, OWNER()); - IAvnuMiddlewareDispatcher { contract_address: avnu_middleware } - .set_slippage_tolerance_bps(100); // 1% slippage - let manage_proofs = _get_proofs_using_tree(manage_leafs, tree.clone()); cheat_caller_address_once(manager.contract_address, STRATEGIST()); diff --git a/packages/vault_allocator/src/test/integrations/vault_bring_liquidity.cairo b/packages/vault_allocator/src/test/integrations/vault_bring_liquidity.cairo new file mode 100644 index 00000000..acd56489 --- /dev/null +++ b/packages/vault_allocator/src/test/integrations/vault_bring_liquidity.cairo @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 Starknet Vault Kit +// Licensed under the MIT License. See LICENSE file for details. + +use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use openzeppelin::interfaces::erc4626::IERC4626Dispatcher; +use snforge_std::{map_entry_address, store}; +use vault_allocator::manager::interface::IManagerDispatcherTrait; +use vault_allocator::merkle_tree::base::{ + ManageLeaf, _add_vault_allocator_leafs, _get_proofs_using_tree, _pad_leafs_to_power_of_two, + generate_merkle_tree, +}; +use vault_allocator::mocks::vault::MockVault::MockVaultTraitDispatcherTrait; +use vault_allocator::test::utils::{ + OWNER, STRATEGIST, WAD, cheat_caller_address_once, deploy_erc20_mock, deploy_manager, + deploy_mock_vault, deploy_simple_decoder_and_sanitizer, deploy_vault_allocator, +}; +use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcherTrait; + +#[test] +fn test_manage_vault_with_merkle_verification_bring_liquidity() { + let vault_allocator = deploy_vault_allocator(); + let manager = deploy_manager(vault_allocator); + let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); + + let underlying_token = deploy_erc20_mock(); + let mock_vault = deploy_mock_vault(underlying_token); + + let mut leafs: Array = ArrayTrait::new(); + let mut leaf_index: u256 = 0; + + _add_vault_allocator_leafs( + ref leafs, + ref leaf_index, + vault_allocator.contract_address, + simple_decoder_and_sanitizer, + mock_vault.contract_address, + ); + + _pad_leafs_to_power_of_two(ref leafs, ref leaf_index); + + let tree = generate_merkle_tree(leafs.span()); + let root = *tree.at(tree.len() - 1).at(0); + + cheat_caller_address_once(vault_allocator.contract_address, OWNER()); + vault_allocator.set_manager(manager.contract_address); + + cheat_caller_address_once(manager.contract_address, OWNER()); + manager.set_manage_root(STRATEGIST(), root); + + // Set initial balance and liquidity for mock vault + let initial_liquidity: u256 = 10 * WAD; + let initial_buffer: u256 = 5 * WAD; + + // Add underlying token balance to vault allocator + let mut cheat_calldata = ArrayTrait::new(); + initial_liquidity.serialize(ref cheat_calldata); + store( + underlying_token, + map_entry_address( + selector!("ERC20_balances"), array![vault_allocator.contract_address.into()].span(), + ), + cheat_calldata.span(), + ); + + // Set initial buffer and liquidity in mock vault + cheat_caller_address_once(mock_vault.contract_address, OWNER()); + mock_vault.set_buffer(initial_buffer); + + cheat_caller_address_once(mock_vault.contract_address, OWNER()); + mock_vault.set_aum(initial_liquidity); + + let underlying_disp = ERC20ABIDispatcher { contract_address: underlying_token }; + assert( + underlying_disp.balance_of(vault_allocator.contract_address) == initial_liquidity, + 'underlying balance incorrect', + ); + + // Prepare to call bring_liquidity + let mut array_of_decoders_and_sanitizers = ArrayTrait::new(); + array_of_decoders_and_sanitizers.append(simple_decoder_and_sanitizer); + array_of_decoders_and_sanitizers.append(simple_decoder_and_sanitizer); + + let mut array_of_targets = ArrayTrait::new(); + array_of_targets.append(underlying_token); + array_of_targets.append(mock_vault.contract_address); + + let mut array_of_selectors = ArrayTrait::new(); + array_of_selectors.append(selector!("approve")); + array_of_selectors.append(selector!("bring_liquidity")); + + let mut array_of_calldatas = ArrayTrait::new(); + + let bring_liquidity_amount: u256 = 2 * WAD; + + // Approval calldata + let mut array_of_calldata_approve: Array = ArrayTrait::new(); + mock_vault.contract_address.serialize(ref array_of_calldata_approve); + bring_liquidity_amount.serialize(ref array_of_calldata_approve); + array_of_calldatas.append(array_of_calldata_approve.span()); + + // Bring liquidity calldata (empty for this function) + let mut array_of_calldata_bring_liquidity: Array = ArrayTrait::new(); + bring_liquidity_amount.serialize(ref array_of_calldata_bring_liquidity); + array_of_calldatas.append(array_of_calldata_bring_liquidity.span()); + + let mut manage_leafs: Array = ArrayTrait::new(); + manage_leafs.append(leafs.at(0).clone()); + manage_leafs.append(leafs.at(1).clone()); + + let manage_proofs = _get_proofs_using_tree(manage_leafs, tree.clone()); + + cheat_caller_address_once(manager.contract_address, STRATEGIST()); + manager + .manage_vault_with_merkle_verification( + manage_proofs.span(), + array_of_decoders_and_sanitizers.span(), + array_of_targets.span(), + array_of_selectors.span(), + array_of_calldatas.span(), + ); + + // Verify that bring_liquidity was called successfully + // The mock vault should have received some underlying tokens and updated its state + let new_liquidity = underlying_disp.balance_of(vault_allocator.contract_address); + assert( + new_liquidity == initial_liquidity - bring_liquidity_amount, 'tokens should be transferred', + ); + + // Check that the vault has increased liquidity + let new_buffer = mock_vault.buffer(); + let new_aum = mock_vault.aum(); + + assert(new_buffer == initial_buffer + bring_liquidity_amount, 'buffer should increase'); + assert(new_aum == initial_liquidity - bring_liquidity_amount, 'aum should decrease'); +} diff --git a/packages/vault_allocator/src/test/integrations/vesu.cairo b/packages/vault_allocator/src/test/integrations/vesu_v1.cairo similarity index 73% rename from packages/vault_allocator/src/test/integrations/vesu.cairo rename to packages/vault_allocator/src/test/integrations/vesu_v1.cairo index 4b582fed..4e72a03c 100644 --- a/packages/vault_allocator/src/test/integrations/vesu.cairo +++ b/packages/vault_allocator/src/test/integrations/vesu_v1.cairo @@ -3,26 +3,24 @@ // Licensed under the MIT License. See LICENSE file for details. use alexandria_math::i257::{I257Impl, I257Trait}; -use core::num::traits::Zero; -use openzeppelin::token::erc20::extensions::erc4626::interface::{ - IERC4626Dispatcher, IERC4626DispatcherTrait, -}; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use openzeppelin::interfaces::erc4626::{IERC4626Dispatcher, IERC4626DispatcherTrait}; use snforge_std::{map_entry_address, store}; -use starknet::ContractAddress; use vault_allocator::decoders_and_sanitizers::decoder_custom_types::{ - Amount, AmountDenomination, AmountType, UnsignedAmount, -}; -use vault_allocator::integration_interfaces::vesu::{ - IDefaultExtensionPOV2Dispatcher, IDefaultExtensionPOV2DispatcherTrait, ISingletonV2Dispatcher, - ISingletonV2DispatcherTrait, + Amount, AmountDenomination, AmountType, }; use vault_allocator::manager::interface::IManagerDispatcherTrait; -use vault_allocator::test::register::{ETH, GENESIS_POOL_ID, VESU_SINGLETON, wstETH}; +use vault_allocator::merkle_tree::base::{ + ManageLeaf, _get_proofs_using_tree, _pad_leafs_to_power_of_two, generate_merkle_tree, +}; +use vault_allocator::merkle_tree::integrations::erc4626::_add_erc4626_leafs; +use vault_allocator::merkle_tree::integrations::vesu_v1::{VesuV1Config, _add_vesu_v1_leafs}; +use vault_allocator::merkle_tree::registery::{ + ETH, GENESIS_POOL_ID, VESU_GENESIS_POOL_V_TOKEN_WSTETH, VESU_SINGLETON, wstETH, +}; use vault_allocator::test::utils::{ - ManageLeaf, OWNER, STRATEGIST, WAD, _add_vesu_flash_loan_leafs, _add_vesu_leafs, - _get_proofs_using_tree, _pad_leafs_to_power_of_two, cheat_caller_address_once, deploy_manager, - deploy_simple_decoder_and_sanitizer, deploy_vault_allocator, generate_merkle_tree, + OWNER, STRATEGIST, WAD, cheat_caller_address_once, deploy_manager, + deploy_simple_decoder_and_sanitizer, deploy_vault_allocator, }; use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcherTrait; @@ -30,20 +28,18 @@ use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcherTrait; #[test] fn test_manage_vault_with_merkle_verification_earn_mode() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); let mut leafs: Array = ArrayTrait::new(); let mut leaf_index: u256 = 0; - _add_vesu_leafs( + _add_erc4626_leafs( ref leafs, ref leaf_index, vault_allocator.contract_address, simple_decoder_and_sanitizer, - GENESIS_POOL_ID, - array![wstETH()].span(), - array![array![ETH()].span()].span(), + VESU_GENESIS_POOL_V_TOKEN_WSTETH(), ); _pad_leafs_to_power_of_two(ref leafs, ref leaf_index); @@ -82,11 +78,7 @@ fn test_manage_vault_with_merkle_verification_earn_mode() { let mut array_of_targets = ArrayTrait::new(); array_of_targets.append(wstETH()); - let v_token = IDefaultExtensionPOV2Dispatcher { - contract_address: ISingletonV2Dispatcher { contract_address: VESU_SINGLETON() } - .extension(GENESIS_POOL_ID), - } - .v_token_for_collateral_asset(GENESIS_POOL_ID, wstETH()); + let v_token = VESU_GENESIS_POOL_V_TOKEN_WSTETH(); array_of_targets.append(v_token); let mut array_of_selectors = ArrayTrait::new(); @@ -281,20 +273,25 @@ fn test_manage_vault_with_merkle_verification_earn_mode() { #[test] fn test_manage_vault_with_merkle_verification_debt_mode() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); let mut leafs: Array = ArrayTrait::new(); let mut leaf_index: u256 = 0; - _add_vesu_leafs( + _add_vesu_v1_leafs( ref leafs, ref leaf_index, vault_allocator.contract_address, simple_decoder_and_sanitizer, - GENESIS_POOL_ID, - array![wstETH()].span(), - array![array![ETH()].span()].span(), + array![ + VesuV1Config { + pool_id: GENESIS_POOL_ID, + collateral_asset: wstETH(), + debt_assets: array![ETH()].span(), + }, + ] + .span(), ); _pad_leafs_to_power_of_two(ref leafs, ref leaf_index); @@ -326,97 +323,27 @@ fn test_manage_vault_with_merkle_verification_debt_mode() { // first scenario is depositing wsteth to vesu genesis pool, transfer the position and borrow // ETH let deposit_amount: u256 = WAD; - let extension = ISingletonV2Dispatcher { contract_address: VESU_SINGLETON() } - .extension(GENESIS_POOL_ID); - let v_token = IDefaultExtensionPOV2Dispatcher { contract_address: extension } - .v_token_for_collateral_asset(GENESIS_POOL_ID, wstETH()); - let v_token_erc4626_disp = IERC4626Dispatcher { contract_address: v_token }; - let expected_shares_obtained = v_token_erc4626_disp.preview_deposit(deposit_amount); - let debt_amount: u256 = WAD / 4; // 25% of the deposit + + let debt_amount: u256 = WAD / 40; // 2.5% of the deposit let mut array_of_decoders_and_sanitizers = ArrayTrait::new(); array_of_decoders_and_sanitizers.append(simple_decoder_and_sanitizer); array_of_decoders_and_sanitizers.append(simple_decoder_and_sanitizer); - array_of_decoders_and_sanitizers.append(simple_decoder_and_sanitizer); - array_of_decoders_and_sanitizers.append(simple_decoder_and_sanitizer); - array_of_decoders_and_sanitizers.append(simple_decoder_and_sanitizer); let mut array_of_targets = ArrayTrait::new(); array_of_targets.append(wstETH()); - array_of_targets.append(v_token); - array_of_targets.append(v_token); - array_of_targets.append(VESU_SINGLETON()); array_of_targets.append(VESU_SINGLETON()); let mut array_of_selectors = ArrayTrait::new(); array_of_selectors.append(selector!("approve")); - array_of_selectors.append(selector!("deposit")); - array_of_selectors.append(selector!("approve")); - array_of_selectors.append(selector!("transfer_position")); array_of_selectors.append(selector!("modify_position")); let mut array_of_calldatas = ArrayTrait::new(); - let mut array_of_calldata_approve: Array = ArrayTrait::new(); - v_token.serialize(ref array_of_calldata_approve); - deposit_amount.serialize(ref array_of_calldata_approve); - array_of_calldatas.append(array_of_calldata_approve.span()); - let mut array_of_calldata_deposit: Array = ArrayTrait::new(); - deposit_amount.serialize(ref array_of_calldata_deposit); - vault_allocator.contract_address.serialize(ref array_of_calldata_deposit); - array_of_calldatas.append(array_of_calldata_deposit.span()); - - let mut array_of_calldata_approve_vtoken: Array = ArrayTrait::new(); - extension.serialize(ref array_of_calldata_approve_vtoken); - expected_shares_obtained.serialize(ref array_of_calldata_approve_vtoken); - array_of_calldatas.append(array_of_calldata_approve_vtoken.span()); - - let mut array_of_calldata_transfer_position: Array = ArrayTrait::new(); - // pool_id - GENESIS_POOL_ID.serialize(ref array_of_calldata_transfer_position); - - // from_collateral_asset - wstETH().serialize(ref array_of_calldata_transfer_position); - - // from_debt_asset - let from_debt_asset: ContractAddress = Zero::zero(); - from_debt_asset.serialize(ref array_of_calldata_transfer_position); - - // to_collateral_asset - wstETH().serialize(ref array_of_calldata_transfer_position); - - // to_debt_asset - ETH().serialize(ref array_of_calldata_transfer_position); - - // from_user - extension.serialize(ref array_of_calldata_transfer_position); - - // to_user - vault_allocator.contract_address.serialize(ref array_of_calldata_transfer_position); - - // collateral - let collateral: UnsignedAmount = UnsignedAmount { - amount_type: AmountType::Delta, - denomination: AmountDenomination::Native, - value: expected_shares_obtained, - }; - collateral.serialize(ref array_of_calldata_transfer_position); - - // debt - let debt: UnsignedAmount = UnsignedAmount { - amount_type: AmountType::Delta, denomination: AmountDenomination::Native, value: 0, - }; - debt.serialize(ref array_of_calldata_transfer_position); - - // from_data - let from_data: Span = array![].span(); - from_data.serialize(ref array_of_calldata_transfer_position); - - // to_data - let to_data: Span = array![].span(); - to_data.serialize(ref array_of_calldata_transfer_position); - - array_of_calldatas.append(array_of_calldata_transfer_position.span()); + let mut array_of_calldata_approve_token: Array = ArrayTrait::new(); + VESU_SINGLETON().serialize(ref array_of_calldata_approve_token); + deposit_amount.serialize(ref array_of_calldata_approve_token); + array_of_calldatas.append(array_of_calldata_approve_token.span()); let mut array_of_calldata_modify_position: Array = ArrayTrait::new(); // pool_id @@ -432,10 +359,10 @@ fn test_manage_vault_with_merkle_verification_debt_mode() { vault_allocator.contract_address.serialize(ref array_of_calldata_modify_position); // collateral - let value_for_collateral_modify_position = I257Trait::new(0, false); + let value_for_collateral_modify_position = I257Trait::new((deposit_amount), false); let collateral_modify_position: Amount = Amount { amount_type: AmountType::Delta, - denomination: AmountDenomination::Native, + denomination: AmountDenomination::Assets, value: value_for_collateral_modify_position, }; collateral_modify_position.serialize(ref array_of_calldata_modify_position); @@ -458,9 +385,6 @@ fn test_manage_vault_with_merkle_verification_debt_mode() { let mut manage_leafs: Array = ArrayTrait::new(); manage_leafs.append(leafs.at(0).clone()); manage_leafs.append(leafs.at(1).clone()); - manage_leafs.append(leafs.at(6).clone()); - manage_leafs.append(leafs.at(7).clone()); - manage_leafs.append(leafs.at(8).clone()); let manage_proofs = _get_proofs_using_tree(manage_leafs, tree.clone()); cheat_caller_address_once(manager.contract_address, STRATEGIST()); @@ -472,15 +396,11 @@ fn test_manage_vault_with_merkle_verification_debt_mode() { array_of_selectors.span(), array_of_calldatas.span(), ); - let new_underlying_balance = underlying_disp.balance_of(vault_allocator.contract_address); assert(new_underlying_balance == initial_wsteth_balance - deposit_amount, 'incorrect'); - let v_token_erc20_disp = ERC20ABIDispatcher { contract_address: v_token }; - let vault_shares_balance = v_token_erc20_disp.balance_of(vault_allocator.contract_address); - assert(vault_shares_balance == 0, 'incorrect'); - let debt_asset_disp = ERC20ABIDispatcher { contract_address: ETH() }; let debt_asset_balance = debt_asset_disp.balance_of(vault_allocator.contract_address); assert(debt_asset_balance == debt_amount, 'incorrect'); } + diff --git a/packages/vault_allocator/src/test/scenarios/stable_carry_loop.cairo b/packages/vault_allocator/src/test/scenarios/stable_carry_loop.cairo index c2d37344..47e6f9f9 100644 --- a/packages/vault_allocator/src/test/scenarios/stable_carry_loop.cairo +++ b/packages/vault_allocator/src/test/scenarios/stable_carry_loop.cairo @@ -3,26 +3,26 @@ // Licensed under the MIT License. See LICENSE file for details. use alexandria_math::i257::{I257Impl, I257Trait}; use core::num::traits::Zero; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; use snforge_std::{map_entry_address, store}; use starknet::ContractAddress; use vault_allocator::decoders_and_sanitizers::decoder_custom_types::{ - Amount, AmountDenomination, AmountType, Route, UnsignedAmount, -}; -use vault_allocator::integration_interfaces::vesu::{ - IDefaultExtensionPOV2Dispatcher, IDefaultExtensionPOV2DispatcherTrait, ISingletonV2Dispatcher, - ISingletonV2DispatcherTrait, + Amount, AmountDenomination, AmountType, Route, }; use vault_allocator::manager::interface::IManagerDispatcherTrait; -use vault_allocator::middlewares::avnu_middleware::interface::{ - IAvnuMiddlewareDispatcher, IAvnuMiddlewareDispatcherTrait, +use vault_allocator::merkle_tree::base::{ + ManageLeaf, _get_proofs_using_tree, _pad_leafs_to_power_of_two, generate_merkle_tree, +}; +use vault_allocator::merkle_tree::integrations::avnu::{AvnuConfig, _add_avnu_leafs}; +use vault_allocator::merkle_tree::integrations::erc4626::_add_erc4626_leafs; +use vault_allocator::merkle_tree::integrations::vesu_v1::{VesuV1Config, _add_vesu_v1_leafs}; +use vault_allocator::merkle_tree::registery::{ + ETH, GENESIS_POOL_ID, USDC, USDT, VESU_GENESIS_POOL_V_TOKEN_USDT, VESU_SINGLETON, }; -use vault_allocator::test::register::{ETH, GENESIS_POOL_ID, USDC, USDT, VESU_SINGLETON}; use vault_allocator::test::utils::{ - ManageLeaf, OWNER, STRATEGIST, WAD, _add_avnu_leafs, _add_vesu_leafs, _get_proofs_using_tree, - _pad_leafs_to_power_of_two, cheat_caller_address_once, deploy_avnu_middleware, deploy_manager, + OWNER, STRATEGIST, WAD, cheat_caller_address_once, deploy_avnu_middleware, deploy_manager, deploy_price_router, deploy_simple_decoder_and_sanitizer, deploy_vault_allocator, - generate_merkle_tree, initialize_price_router, + initialize_price_router, }; use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcherTrait; @@ -30,22 +30,40 @@ use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcherTrait; #[test] fn test_stable_carry_loop() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); let price_router = deploy_price_router(); initialize_price_router(price_router); - let avnu_middleware = deploy_avnu_middleware(price_router); + let avnu_middleware = deploy_avnu_middleware( + vault_allocator.contract_address, price_router, 100, 100000, 1000000, + ); let mut leafs: Array = ArrayTrait::new(); let mut leaf_index: u256 = 0; - _add_vesu_leafs( + + // add vesu v1 leafs + _add_vesu_v1_leafs( + ref leafs, + ref leaf_index, + vault_allocator.contract_address, + simple_decoder_and_sanitizer, + array![ + VesuV1Config { + pool_id: GENESIS_POOL_ID, + collateral_asset: ETH(), + debt_assets: array![USDC()].span(), + }, + ] + .span(), + ); + + // add erc4626 leafs for usdt v-token + _add_erc4626_leafs( ref leafs, ref leaf_index, vault_allocator.contract_address, simple_decoder_and_sanitizer, - GENESIS_POOL_ID, - array![ETH(), USDT()].span(), - array![array![USDC()].span(), array![].span()].span(), + VESU_GENESIS_POOL_V_TOKEN_USDT(), ); _add_avnu_leafs( @@ -54,7 +72,7 @@ fn test_stable_carry_loop() { vault_allocator.contract_address, simple_decoder_and_sanitizer, avnu_middleware, - array![(USDC(), USDT())], + array![AvnuConfig { sell_token: USDC(), buy_token: USDT() }].span(), ); _pad_leafs_to_power_of_two(ref leafs, ref leaf_index); @@ -109,12 +127,7 @@ fn test_stable_carry_loop() { array_of_targets.append(USDC()); array_of_targets.append(avnu_middleware); array_of_targets.append(USDT()); - let v_token = IDefaultExtensionPOV2Dispatcher { - contract_address: ISingletonV2Dispatcher { contract_address: VESU_SINGLETON() } - .extension(GENESIS_POOL_ID), - } - .v_token_for_collateral_asset(GENESIS_POOL_ID, USDT()); - array_of_targets.append(v_token); + array_of_targets.append(VESU_GENESIS_POOL_V_TOKEN_USDT()); let mut array_of_selectors = ArrayTrait::new(); array_of_selectors.append(selector!("approve")); @@ -214,7 +227,7 @@ fn test_stable_carry_loop() { // approve usdt to the v-token let mut array_of_calldata_approve_vtoken: Array = ArrayTrait::new(); - v_token.serialize(ref array_of_calldata_approve_vtoken); + VESU_GENESIS_POOL_V_TOKEN_USDT().serialize(ref array_of_calldata_approve_vtoken); earn_amount_from_swap.serialize(ref array_of_calldata_approve_vtoken); array_of_calldatas.append(array_of_calldata_approve_vtoken.span()); @@ -225,16 +238,12 @@ fn test_stable_carry_loop() { array_of_calldatas.append(array_of_calldata_deposit.span()); let mut manage_leafs: Array = ArrayTrait::new(); - manage_leafs.append(leafs.at(5).clone()); + manage_leafs.append(leafs.at(0).clone()); + manage_leafs.append(leafs.at(1).clone()); + manage_leafs.append(leafs.at(7).clone()); manage_leafs.append(leafs.at(8).clone()); - manage_leafs.append(leafs.at(14).clone()); - manage_leafs.append(leafs.at(15).clone()); - manage_leafs.append(leafs.at(9).clone()); - manage_leafs.append(leafs.at(10).clone()); - - cheat_caller_address_once(avnu_middleware, OWNER()); - IAvnuMiddlewareDispatcher { contract_address: avnu_middleware } - .set_slippage_tolerance_bps(100); // 1% slippage + manage_leafs.append(leafs.at(2).clone()); + manage_leafs.append(leafs.at(3).clone()); let manage_proofs = _get_proofs_using_tree(manage_leafs, tree.clone()); diff --git a/packages/vault_allocator/src/test/units/manager.cairo b/packages/vault_allocator/src/test/units/manager.cairo index 585ad674..4fc4c653 100644 --- a/packages/vault_allocator/src/test/units/manager.cairo +++ b/packages/vault_allocator/src/test/units/manager.cairo @@ -2,38 +2,30 @@ // Copyright (c) 2025 Starknet Vault Kit // Licensed under the MIT License. See LICENSE file for details. -use openzeppelin::access::accesscontrol::interface::{ +use openzeppelin::interfaces::accesscontrol::{ IAccessControlDispatcher, IAccessControlDispatcherTrait, }; -use openzeppelin::security::interface::{IPausableDispatcher, IPausableDispatcherTrait}; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; -use openzeppelin::upgrades::interface::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; -use starknet::ContractAddress; -use vault_allocator::integration_interfaces::vesu::{ - IFlashloanReceiverDispatcher, IFlashloanReceiverDispatcherTrait, -}; +use openzeppelin::interfaces::erc20::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use openzeppelin::interfaces::security::pausable::{IPausableDispatcher, IPausableDispatcherTrait}; +use openzeppelin::interfaces::upgrades::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; use vault_allocator::manager::interface::{IManagerDispatcher, IManagerDispatcherTrait}; use vault_allocator::manager::manager::Manager::{OWNER_ROLE, PAUSER_ROLE}; -use vault_allocator::mocks::counter::{ICounterDispatcher, ICounterDispatcherTrait}; -use vault_allocator::mocks::flashloan::{ - IFlashLoanSingletonMockDispatcher, IFlashLoanSingletonMockDispatcherTrait, +use vault_allocator::merkle_tree::base::{ + ManageLeaf, _get_proofs_using_tree, _pad_leafs_to_power_of_two, generate_merkle_tree, }; -use vault_allocator::test::register::VESU_SINGLETON; +use vault_allocator::merkle_tree::integrations::erc4626::_add_erc4626_leafs; +use vault_allocator::mocks::counter::{ICounterDispatcher, ICounterDispatcherTrait}; use vault_allocator::test::utils::{ - DUMMY_ADDRESS, ManageLeaf, OWNER, STRATEGIST, WAD, _add_erc4626_leafs, - _add_vesu_flash_loan_leafs, _get_proofs_using_tree, _pad_leafs_to_power_of_two, - cheat_caller_address_once, deploy_counter, deploy_erc20_mock, deploy_erc4626_mock, - deploy_flashloan_mock, deploy_manager, deploy_simple_decoder_and_sanitizer, - deploy_vault_allocator, generate_merkle_tree, -}; -use vault_allocator::vault_allocator::interface::{ - IVaultAllocatorDispatcher, IVaultAllocatorDispatcherTrait, + DUMMY_ADDRESS, OWNER, STRATEGIST, WAD, cheat_caller_address_once, deploy_counter, + deploy_erc20_mock, deploy_erc4626_mock, deploy_manager, deploy_simple_decoder_and_sanitizer, + deploy_vault_allocator, }; +use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcherTrait; #[test] fn test_constructor() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let access_control_dispatcher = IAccessControlDispatcher { contract_address: manager.contract_address, }; @@ -46,7 +38,6 @@ fn test_constructor() { let has_role = access_control_dispatcher.has_role(PAUSER_ROLE, OWNER()); assert(has_role, 'Pauser is not set correctly'); - assert(manager.vesu_singleton() == VESU_SINGLETON(), ' singleton is not set correctly'); assert( manager.vault_allocator() == vault_allocator.contract_address, 'allocator is not set correctly', @@ -57,7 +48,7 @@ fn test_constructor() { #[should_panic(expected: ('Caller is missing role',))] fn test_upgrade_not_owner() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let (_, counter_class_hash) = deploy_counter(); IUpgradeableDispatcher { contract_address: manager.contract_address } .upgrade(counter_class_hash); @@ -66,7 +57,7 @@ fn test_upgrade_not_owner() { #[test] fn test_upgrade() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let (_, counter_class_hash) = deploy_counter(); cheat_caller_address_once(manager.contract_address, OWNER()); IUpgradeableDispatcher { contract_address: manager.contract_address } @@ -77,7 +68,7 @@ fn test_upgrade() { #[test] fn test_set_manage_root() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let target = 0x123.try_into().unwrap(); let root = 0x456; @@ -93,7 +84,7 @@ fn test_set_manage_root() { #[should_panic(expected: ('Caller is missing role',))] fn test_set_manage_root_not_owner() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let target = 0x123.try_into().unwrap(); let root = 0x456; @@ -104,7 +95,7 @@ fn test_set_manage_root_not_owner() { #[test] fn test_pause() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let manager_dispatcher = IManagerDispatcher { contract_address: manager.contract_address }; let pausable_dispatcher = IPausableDispatcher { contract_address: manager.contract_address }; @@ -120,14 +111,14 @@ fn test_pause() { #[should_panic(expected: ('Caller is missing role',))] fn test_pause_not_pauser() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); manager.pause(); } #[test] fn test_unpause() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let manager_dispatcher = IManagerDispatcher { contract_address: manager.contract_address }; let pausable_dispatcher = IPausableDispatcher { contract_address: manager.contract_address }; @@ -145,7 +136,7 @@ fn test_unpause() { #[should_panic(expected: ('Caller is missing role',))] fn test_unpause_not_owner() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); cheat_caller_address_once(manager.contract_address, OWNER()); manager.pause(); manager.unpause(); @@ -155,7 +146,7 @@ fn test_unpause_not_owner() { #[should_panic(expected: "Inconsistent lengths")] fn test_manage_vault_with_merkle_verification_inconsistent_lengths_1() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); manager .manage_vault_with_merkle_verification( array![array![].span()].span(), @@ -170,7 +161,7 @@ fn test_manage_vault_with_merkle_verification_inconsistent_lengths_1() { #[should_panic(expected: "Inconsistent lengths")] fn test_manage_vault_with_merkle_verification_inconsistent_lengths_2() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); manager .manage_vault_with_merkle_verification( array![array![].span()].span(), @@ -185,7 +176,7 @@ fn test_manage_vault_with_merkle_verification_inconsistent_lengths_2() { #[should_panic(expected: "Inconsistent lengths")] fn test_manage_vault_with_merkle_verification_inconsistent_lengths_3() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); manager .manage_vault_with_merkle_verification( @@ -201,7 +192,7 @@ fn test_manage_vault_with_merkle_verification_inconsistent_lengths_3() { #[should_panic(expected: "Inconsistent lengths")] fn test_manage_vault_with_merkle_verification_inconsistent_lengths_4() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); manager .manage_vault_with_merkle_verification( @@ -217,7 +208,7 @@ fn test_manage_vault_with_merkle_verification_inconsistent_lengths_4() { #[should_panic(expected: "Invalid manage proof")] fn test_manage_vault_with_merkle_verification_invalid_proof() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); let underlying = deploy_erc20_mock(); let erc4626 = deploy_erc4626_mock(underlying); @@ -274,7 +265,7 @@ fn test_manage_vault_with_merkle_verification_invalid_proof() { #[test] fn test_manage_vault_with_merkle_verification_valid_proof() { let vault_allocator = deploy_vault_allocator(); - let manager = deploy_manager(vault_allocator, VESU_SINGLETON()); + let manager = deploy_manager(vault_allocator); let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); let underlying = deploy_erc20_mock(); let erc4626 = deploy_erc4626_mock(underlying); @@ -353,364 +344,3 @@ fn test_manage_vault_with_merkle_verification_valid_proof() { assert(underlying_balance == WAD * 9, 'Balance wrong'); } -#[test] -#[should_panic(expected: "Not vault allocator")] -fn test_flash_loan_not_vault_allocator() { - let vault_allocator = deploy_vault_allocator(); - let flashloan_mock = deploy_flashloan_mock(); - let manager = deploy_manager(vault_allocator, flashloan_mock); - manager - .flash_loan( - manager.contract_address, manager.contract_address, WAD, false, array!['0xdead'].span(), - ); -} - -#[test] -#[should_panic(expected: "Not vesu singleton")] -fn test_on_flash_loan_not_vesu_singleton() { - let vault_allocator = deploy_vault_allocator(); - let flashloan_mock = deploy_flashloan_mock(); - let manager = deploy_manager(vault_allocator, flashloan_mock); - let flash_loan_recipient_dispatcher = IFlashloanReceiverDispatcher { - contract_address: manager.contract_address, - }; - flash_loan_recipient_dispatcher - .on_flash_loan(DUMMY_ADDRESS(), DUMMY_ADDRESS(), WAD, array!['0xdead'].span()); -} - -#[test] -#[should_panic(expected: "Flash loan not executed")] -fn test_flash_loan_not_executed() { - let vault_allocator = deploy_vault_allocator(); - let flashloan_mock = deploy_flashloan_mock(); - let underlying = deploy_erc20_mock(); - let underlying_disp = ERC20ABIDispatcher { contract_address: underlying }; - cheat_caller_address_once(underlying, OWNER()); - underlying_disp.transfer(flashloan_mock, WAD * 10); - let manager = deploy_manager(vault_allocator, flashloan_mock); - let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); - - let mut leafs: Array = ArrayTrait::new(); - let mut leaf_index: u256 = 0; - - _add_vesu_flash_loan_leafs( - ref leafs, - ref leaf_index, - vault_allocator.contract_address, - simple_decoder_and_sanitizer, - manager.contract_address, - underlying, - false, - ); - - _pad_leafs_to_power_of_two(ref leafs, ref leaf_index); - - let tree = generate_merkle_tree(leafs.span()); - - let root = *tree.at(tree.len() - 1).at(0); - cheat_caller_address_once(vault_allocator.contract_address, OWNER()); - vault_allocator.set_manager(manager.contract_address); - - cheat_caller_address_once(manager.contract_address, OWNER()); - manager.set_manage_root(STRATEGIST(), root); - - // Since the manager calls to itself to fulfill the flashloan, we need to set its root. - cheat_caller_address_once(manager.contract_address, OWNER()); - manager.set_manage_root(manager.contract_address, root); - - let mut array_of_decoders_and_sanitizers = ArrayTrait::new(); - array_of_decoders_and_sanitizers.append(simple_decoder_and_sanitizer); - - let mut array_of_targets = ArrayTrait::new(); - array_of_targets.append(manager.contract_address); - - let mut array_of_selectors = ArrayTrait::new(); - array_of_selectors.append(selector!("flash_loan")); - - let mut array_of_calldatas = ArrayTrait::new(); - let mut array_of_calldatas_flash_loan = ArrayTrait::new(); - manager.contract_address.serialize(ref array_of_calldatas_flash_loan); - underlying.serialize(ref array_of_calldatas_flash_loan); - WAD.serialize(ref array_of_calldatas_flash_loan); - false.serialize(ref array_of_calldatas_flash_loan); - - let mut flash_loan_data_proofs: Array> = ArrayTrait::new(); - let mut flash_loan_data_decoder_and_sanitizer: Array = ArrayTrait::new(); - let mut flash_loan_data_target: Array = ArrayTrait::new(); - let mut flash_loan_data_selector: Array = ArrayTrait::new(); - let mut flash_loan_data_calldata: Array> = ArrayTrait::new(); - let mut serialized_flash_loan_data = ArrayTrait::new(); - ( - flash_loan_data_proofs.span(), - flash_loan_data_decoder_and_sanitizer.span(), - flash_loan_data_target.span(), - flash_loan_data_selector.span(), - flash_loan_data_calldata.span(), - ) - .serialize(ref serialized_flash_loan_data); - - serialized_flash_loan_data.span().serialize(ref array_of_calldatas_flash_loan); - array_of_calldatas.append(array_of_calldatas_flash_loan.span()); - - let mut manage_leafs: Array = ArrayTrait::new(); - manage_leafs.append(leafs.at(0).clone()); - - let proofs = _get_proofs_using_tree(manage_leafs, tree.clone()); - - IFlashLoanSingletonMockDispatcher { contract_address: flashloan_mock }.set_do_nothing(true); - - cheat_caller_address_once(manager.contract_address, STRATEGIST()); - manager - .manage_vault_with_merkle_verification( - proofs.span(), - array_of_decoders_and_sanitizers.span(), - array_of_targets.span(), - array_of_selectors.span(), - array_of_calldatas.span(), - ); -} - -#[test] -#[should_panic(expected: "Bad flash loan intent hash")] -fn test_flash_loan_bad_flash_loan_intent_hash() { - let vault_allocator = deploy_vault_allocator(); - let flashloan_mock = deploy_flashloan_mock(); - let underlying = deploy_erc20_mock(); - let underlying_disp = ERC20ABIDispatcher { contract_address: underlying }; - cheat_caller_address_once(underlying, OWNER()); - underlying_disp.transfer(flashloan_mock, WAD * 10); - let manager = deploy_manager(vault_allocator, flashloan_mock); - let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); - - let mut leafs: Array = ArrayTrait::new(); - let mut leaf_index: u256 = 0; - - _add_vesu_flash_loan_leafs( - ref leafs, - ref leaf_index, - vault_allocator.contract_address, - simple_decoder_and_sanitizer, - manager.contract_address, - underlying, - false, - ); - - _pad_leafs_to_power_of_two(ref leafs, ref leaf_index); - - let tree = generate_merkle_tree(leafs.span()); - - let root = *tree.at(tree.len() - 1).at(0); - cheat_caller_address_once(vault_allocator.contract_address, OWNER()); - vault_allocator.set_manager(manager.contract_address); - - cheat_caller_address_once(manager.contract_address, OWNER()); - manager.set_manage_root(STRATEGIST(), root); - - // Since the manager calls to itself to fulfill the flashloan, we need to set its root. - cheat_caller_address_once(manager.contract_address, OWNER()); - manager.set_manage_root(manager.contract_address, root); - - let mut array_of_decoders_and_sanitizers = ArrayTrait::new(); - array_of_decoders_and_sanitizers.append(simple_decoder_and_sanitizer); - - let mut array_of_targets = ArrayTrait::new(); - array_of_targets.append(manager.contract_address); - - let mut array_of_selectors = ArrayTrait::new(); - array_of_selectors.append(selector!("flash_loan")); - - let mut array_of_calldatas = ArrayTrait::new(); - let mut array_of_calldatas_flash_loan = ArrayTrait::new(); - manager.contract_address.serialize(ref array_of_calldatas_flash_loan); - underlying.serialize(ref array_of_calldatas_flash_loan); - WAD.serialize(ref array_of_calldatas_flash_loan); - false.serialize(ref array_of_calldatas_flash_loan); - - let mut flash_loan_data_proofs: Array> = ArrayTrait::new(); - let mut flash_loan_data_decoder_and_sanitizer: Array = ArrayTrait::new(); - let mut flash_loan_data_target: Array = ArrayTrait::new(); - let mut flash_loan_data_selector: Array = ArrayTrait::new(); - let mut flash_loan_data_calldata: Array> = ArrayTrait::new(); - let mut serialized_flash_loan_data = ArrayTrait::new(); - ( - flash_loan_data_proofs.span(), - flash_loan_data_decoder_and_sanitizer.span(), - flash_loan_data_target.span(), - flash_loan_data_selector.span(), - flash_loan_data_calldata.span(), - ) - .serialize(ref serialized_flash_loan_data); - - serialized_flash_loan_data.span().serialize(ref array_of_calldatas_flash_loan); - array_of_calldatas.append(array_of_calldatas_flash_loan.span()); - - let mut manage_leafs: Array = ArrayTrait::new(); - manage_leafs.append(leafs.at(0).clone()); - - let proofs = _get_proofs_using_tree(manage_leafs, tree.clone()); - - IFlashLoanSingletonMockDispatcher { contract_address: flashloan_mock } - .set_do_wrong_callback(true); - - cheat_caller_address_once(manager.contract_address, STRATEGIST()); - manager - .manage_vault_with_merkle_verification( - proofs.span(), - array_of_decoders_and_sanitizers.span(), - array_of_targets.span(), - array_of_selectors.span(), - array_of_calldatas.span(), - ); -} - -#[test] -fn test_flash_loan_bad_flash_loan() { - let vault_allocator = deploy_vault_allocator(); - let flashloan_mock = deploy_flashloan_mock(); - let underlying = deploy_erc20_mock(); - let underlying_disp = ERC20ABIDispatcher { contract_address: underlying }; - cheat_caller_address_once(underlying, OWNER()); - underlying_disp.transfer(flashloan_mock, WAD * 10); - let manager = deploy_manager(vault_allocator, flashloan_mock); - let simple_decoder_and_sanitizer = deploy_simple_decoder_and_sanitizer(); - - let mut leafs: Array = ArrayTrait::new(); - let mut leaf_index: u256 = 0; - - _add_vesu_flash_loan_leafs( - ref leafs, - ref leaf_index, - vault_allocator.contract_address, - simple_decoder_and_sanitizer, - manager.contract_address, - underlying, - false, - ); - - let amount_flash_loan = WAD; - - // approve the mock flashloan token to spend underlying - - let mut argument_addresses_approve = ArrayTrait::new(); - flashloan_mock.serialize(ref argument_addresses_approve); - leafs - .append( - ManageLeaf { - decoder_and_sanitizer: simple_decoder_and_sanitizer, - target: underlying, - selector: selector!("approve"), - argument_addresses: argument_addresses_approve.span(), - description: "", - }, - ); - - leaf_index += 1; - - // do something function called approve from the mock flashloan - let mut argument_addresses_fake_approve_func = ArrayTrait::new(); - underlying.serialize(ref argument_addresses_fake_approve_func); - leafs - .append( - ManageLeaf { - decoder_and_sanitizer: simple_decoder_and_sanitizer, - target: flashloan_mock, - selector: selector!("approve"), - argument_addresses: argument_addresses_fake_approve_func.span(), - description: "Approve", - }, - ); - leaf_index += 1; - - _pad_leafs_to_power_of_two(ref leafs, ref leaf_index); - - let tree = generate_merkle_tree(leafs.span()); - - let root = *tree.at(tree.len() - 1).at(0); - cheat_caller_address_once(vault_allocator.contract_address, OWNER()); - vault_allocator.set_manager(manager.contract_address); - - cheat_caller_address_once(manager.contract_address, OWNER()); - manager.set_manage_root(STRATEGIST(), root); - - // Since the manager calls to itself to fulfill the flashloan, we need to set its root. - cheat_caller_address_once(manager.contract_address, OWNER()); - manager.set_manage_root(manager.contract_address, root); - - let mut array_of_decoders_and_sanitizers = ArrayTrait::new(); - array_of_decoders_and_sanitizers.append(simple_decoder_and_sanitizer); - - let mut array_of_targets = ArrayTrait::new(); - array_of_targets.append(manager.contract_address); - - let mut array_of_selectors = ArrayTrait::new(); - array_of_selectors.append(selector!("flash_loan")); - - let mut array_of_calldatas = ArrayTrait::new(); - let mut array_of_calldatas_flash_loan = ArrayTrait::new(); - manager.contract_address.serialize(ref array_of_calldatas_flash_loan); - underlying.serialize(ref array_of_calldatas_flash_loan); - WAD.serialize(ref array_of_calldatas_flash_loan); - false.serialize(ref array_of_calldatas_flash_loan); - - /// construct the flash loan data to trigger do_something from the flashloan mock - let mut flash_loan_data_decoder_and_sanitizer: Array = ArrayTrait::new(); - flash_loan_data_decoder_and_sanitizer.append(simple_decoder_and_sanitizer); - flash_loan_data_decoder_and_sanitizer.append(simple_decoder_and_sanitizer); - - let mut flash_loan_data_target: Array = ArrayTrait::new(); - flash_loan_data_target.append(underlying); - flash_loan_data_target.append(flashloan_mock); - - let mut flash_loan_data_selector: Array = ArrayTrait::new(); - flash_loan_data_selector.append(selector!("approve")); - flash_loan_data_selector.append(selector!("approve")); - - let mut flash_loan_data_calldata: Array> = ArrayTrait::new(); - let mut flash_loan_data_calldata_approve = ArrayTrait::new(); - flashloan_mock.serialize(ref flash_loan_data_calldata_approve); - amount_flash_loan.serialize(ref flash_loan_data_calldata_approve); - - let mut flash_loan_data_calldata_approve_fake = ArrayTrait::new(); - underlying.serialize(ref flash_loan_data_calldata_approve_fake); - amount_flash_loan.serialize(ref flash_loan_data_calldata_approve_fake); - flash_loan_data_calldata.append(flash_loan_data_calldata_approve.span()); - flash_loan_data_calldata.append(flash_loan_data_calldata_approve_fake.span()); - - let mut flash_loan_manager_leafs: Array = ArrayTrait::new(); - flash_loan_manager_leafs.append(leafs.at(1).clone()); - flash_loan_manager_leafs.append(leafs.at(2).clone()); - - let mut flash_loan_proofs = _get_proofs_using_tree(flash_loan_manager_leafs, tree.clone()); - - let mut serialized_flash_loan_data = ArrayTrait::new(); - ( - flash_loan_proofs.span(), - flash_loan_data_decoder_and_sanitizer.span(), - flash_loan_data_target.span(), - flash_loan_data_selector.span(), - flash_loan_data_calldata.span(), - ) - .serialize(ref serialized_flash_loan_data); - - serialized_flash_loan_data.span().serialize(ref array_of_calldatas_flash_loan); - array_of_calldatas.append(array_of_calldatas_flash_loan.span()); - - let mut manage_leafs: Array = ArrayTrait::new(); - manage_leafs.append(leafs.at(0).clone()); - - let proofs = _get_proofs_using_tree(manage_leafs, tree.clone()); - - cheat_caller_address_once(manager.contract_address, STRATEGIST()); - manager - .manage_vault_with_merkle_verification( - proofs.span(), - array_of_decoders_and_sanitizers.span(), - array_of_targets.span(), - array_of_selectors.span(), - array_of_calldatas.span(), - ); - assert( - IFlashLoanSingletonMockDispatcher { contract_address: flashloan_mock }.i_did_something(), - 'i_did_something is not true', - ); -} diff --git a/packages/vault_allocator/src/test/units/vault_allocator.cairo b/packages/vault_allocator/src/test/units/vault_allocator.cairo index 9d38e572..89eaeaa4 100644 --- a/packages/vault_allocator/src/test/units/vault_allocator.cairo +++ b/packages/vault_allocator/src/test/units/vault_allocator.cairo @@ -2,8 +2,8 @@ // Copyright (c) 2025 Starknet Vault Kit // Licensed under the MIT License. See LICENSE file for details. -use openzeppelin::access::ownable::interface::{IOwnableDispatcher, IOwnableDispatcherTrait}; -use openzeppelin::upgrades::interface::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; +use openzeppelin::interfaces::ownable::{IOwnableDispatcher, IOwnableDispatcherTrait}; +use openzeppelin::interfaces::upgrades::{IUpgradeableDispatcher, IUpgradeableDispatcherTrait}; use starknet::ContractAddress; use starknet::account::Call; use vault_allocator::mocks::counter::{ICounterDispatcher, ICounterDispatcherTrait}; diff --git a/packages/vault_allocator/src/test/utils.cairo b/packages/vault_allocator/src/test/utils.cairo index 7f178d61..050deec2 100644 --- a/packages/vault_allocator/src/test/utils.cairo +++ b/packages/vault_allocator/src/test/utils.cairo @@ -2,35 +2,22 @@ // Copyright (c) 2025 Starknet Vault Kit // Licensed under the MIT License. See LICENSE file for details. -use core::hash::HashStateTrait; -use core::num::traits::Zero; -use core::pedersen::PedersenTrait; use openzeppelin::merkle_tree::hashes::PedersenCHasher; -use openzeppelin::token::erc20::extensions::erc4626::interface::{ - IERC4626Dispatcher, IERC4626DispatcherTrait, -}; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; use snforge_std::{CheatSpan, ContractClassTrait, DeclareResultTrait, cheat_caller_address, declare}; -use starknet::syscalls::call_contract_syscall; -use starknet::{ClassHash, ContractAddress, SyscallResultTrait}; -use vault_allocator::integration_interfaces::vesu::{ - IDefaultExtensionPOV2Dispatcher, IDefaultExtensionPOV2DispatcherTrait, ISingletonV2Dispatcher, - ISingletonV2DispatcherTrait, -}; +use starknet::{ClassHash, ContractAddress}; use vault_allocator::manager::interface::IManagerDispatcher; +use vault_allocator::merkle_tree::registery::{ + DAI, DAI_PRAGMA_ID, ETH, ETH_PRAGMA_ID, PRAGMA, STRK, STRK_PRAGMA_ID, USDC, USDC_PRAGMA_ID, + USDT, USDT_PRAGMA_ID, WBTC, WBTC_PRAGMA_ID, wstETH, wstETH_PRAGMA_ID, +}; use vault_allocator::mocks::counter::ICounterDispatcher; +use vault_allocator::mocks::vault::MockVault::MockVaultTraitDispatcher; use vault_allocator::periphery::price_router::interface::{ IPriceRouterDispatcher, IPriceRouterDispatcherTrait, }; -use vault_allocator::test::register::{ - AVNU_ROUTER, DAI, DAI_PRAGMA_ID, ETH, ETH_PRAGMA_ID, PRAGMA, STRK, STRK_PRAGMA_ID, USDC, - USDC_PRAGMA_ID, USDT, USDT_PRAGMA_ID, VESU_SINGLETON, WBTC, WBTC_PRAGMA_ID, wstETH, - wstETH_PRAGMA_ID, -}; use vault_allocator::vault_allocator::interface::IVaultAllocatorDispatcher; pub const WAD: u256 = 1_000_000_000_000_000_000; pub const INITIAL_SLIPPAGE_BPS: u256 = 100; // 1% -use core::to_byte_array::FormatAsByteArray; pub fn OWNER() -> ContractAddress { @@ -58,14 +45,11 @@ pub fn deploy_vault_allocator() -> IVaultAllocatorDispatcher { IVaultAllocatorDispatcher { contract_address: vault_allocator_address } } -pub fn deploy_manager( - vault_allocator: IVaultAllocatorDispatcher, vesu_singleton: ContractAddress, -) -> IManagerDispatcher { +pub fn deploy_manager(vault_allocator: IVaultAllocatorDispatcher) -> IManagerDispatcher { let manager = declare("Manager").unwrap().contract_class(); let mut calldata = ArrayTrait::new(); OWNER().serialize(ref calldata); vault_allocator.contract_address.serialize(ref calldata); - vesu_singleton.serialize(ref calldata); let (manager_address, _) = manager.deploy(@calldata).unwrap(); IManagerDispatcher { contract_address: manager_address } } @@ -79,12 +63,6 @@ pub fn deploy_counter() -> (ICounterDispatcher, ClassHash) { (ICounterDispatcher { contract_address: counter_address }, *counter.class_hash) } -pub fn deploy_flashloan_mock() -> ContractAddress { - let flashloan = declare("FlashLoanSingletonMock").unwrap().contract_class(); - let mut calldata = ArrayTrait::new(); - let (flashloan_address, _) = flashloan.deploy(@calldata).unwrap(); - flashloan_address -} pub fn deploy_erc4626_mock(underlying: ContractAddress) -> ContractAddress { let erc4626 = declare("Erc4626Mock").unwrap().contract_class(); @@ -104,6 +82,14 @@ pub fn deploy_erc20_mock() -> ContractAddress { erc20_address } +pub fn deploy_mock_vault(underlying: ContractAddress) -> MockVaultTraitDispatcher { + let mock_vault = declare("MockVault").unwrap().contract_class(); + let mut calldata = ArrayTrait::new(); + underlying.serialize(ref calldata); + let (mock_vault_address, _) = mock_vault.deploy(@calldata).unwrap(); + MockVaultTraitDispatcher { contract_address: mock_vault_address } +} + pub fn deploy_simple_decoder_and_sanitizer() -> ContractAddress { let simple_decoder_and_sanitizer = declare("SimpleDecoderAndSanitizer") .unwrap() @@ -148,13 +134,21 @@ pub fn initialize_price_router(price_router: ContractAddress) { price_router.set_asset_to_id(DAI(), DAI_PRAGMA_ID()); } -pub fn deploy_avnu_middleware(price_router: ContractAddress) -> ContractAddress { +pub fn deploy_avnu_middleware( + vault_allocator: ContractAddress, + price_router: ContractAddress, + slippage: u16, + period: u64, + allowed_calls_per_period: u64, +) -> ContractAddress { let avnu_middleware = declare("AvnuMiddleware").unwrap().contract_class(); let mut calldata = ArrayTrait::new(); OWNER().serialize(ref calldata); - AVNU_ROUTER().serialize(ref calldata); + vault_allocator.serialize(ref calldata); price_router.serialize(ref calldata); - INITIAL_SLIPPAGE_BPS.serialize(ref calldata); + slippage.serialize(ref calldata); + period.serialize(ref calldata); + allowed_calls_per_period.serialize(ref calldata); let (avnu_middleware_address, _) = avnu_middleware.deploy(@calldata).unwrap(); avnu_middleware_address } @@ -166,611 +160,3 @@ pub fn cheat_caller_address_once( cheat_caller_address(:contract_address, :caller_address, span: CheatSpan::TargetCalls(1)); } - -#[derive(PartialEq, Drop, Serde, Debug, Clone)] -pub struct ManageLeaf { - pub decoder_and_sanitizer: ContractAddress, - pub target: ContractAddress, - pub selector: felt252, - pub argument_addresses: Span, - pub description: ByteArray, -} - - -pub fn generate_merkle_tree(manage_leafs: Span) -> Array> { - let mut first_layer = ArrayTrait::new(); - let leafs_length = manage_leafs.len(); - for i in 0..leafs_length { - let mut serialized_struct: Array = ArrayTrait::new(); - manage_leafs[i].decoder_and_sanitizer.serialize(ref serialized_struct); - manage_leafs[i].target.serialize(ref serialized_struct); - manage_leafs[i].selector.serialize(ref serialized_struct); - manage_leafs[i].argument_addresses.serialize(ref serialized_struct); - let first_element = serialized_struct.pop_front().unwrap(); - let mut state = PedersenTrait::new(first_element); - - while let Some(value) = serialized_struct.pop_front() { - state = state.update(value); - } - let leaf_hash = state.finalize(); - first_layer.append(leaf_hash); - } - let mut leafs = ArrayTrait::new(); - leafs.append(first_layer); - _build_tree(leafs) -} - -pub fn _build_tree(merkle_tree_in: Array>) -> Array> { - let merkle_tree_in_length = merkle_tree_in.len(); - - let mut current_layer_index = merkle_tree_in_length - 1; - let current_layer_length = merkle_tree_in[current_layer_index].len(); - let mut next_layer_length = 0; - if (current_layer_length % 2 != 0) { - next_layer_length = (current_layer_length + 1) / 2; - } else { - next_layer_length = current_layer_length / 2; - } - let mut current_layer = ArrayTrait::new(); - let mut count = 0; - let mut i = 0; - while i < current_layer_length { - current_layer - .append( - PedersenCHasher::commutative_hash( - *merkle_tree_in[current_layer_index].at(i), - *merkle_tree_in[current_layer_index].at(i + 1), - ), - ); - count += 1; - i += 2; - } - - let mut merkle_tree_out = merkle_tree_in.clone(); - merkle_tree_out.append(current_layer); - - if (next_layer_length > 1) { - _build_tree(merkle_tree_out) - } else { - merkle_tree_out - } -} - -fn _next_power_of_two(x: u256) -> u256 { - let mut power = 1_u256; - while power < x { - power = power * 2_u256; - } - power -} -// pub fn _pad_leafs_to_power_of_two(ref leafs: Array, ref leaf_index: u256) { -// let next_power = _next_power_of_two(leaf_index); -// let padding_needed = next_power - leaf_index; - -// let default_leaf = ManageLeaf { -// decoder_and_sanitizer: Zero::zero(), -// target: Zero::zero(), -// selector: Zero::zero(), -// argument_addresses: ArrayTrait::new().span(), -// }; - -// let mut i = 0; -// while i < padding_needed { -// leafs.append(default_leaf); -// leaf_index += 1; -// i += 1; -// } -// } - -pub fn _pad_leafs_to_power_of_two(ref leafs: Array, ref leaf_index: u256) { - let target_len = if leaf_index < 4_u256 { - 4_u256 - } else { - _next_power_of_two(leaf_index) - }; - let padding_needed = target_len - leaf_index; - - let mut i: u256 = 0_u256; - while i < padding_needed { - leafs - .append( - ManageLeaf { - decoder_and_sanitizer: Zero::zero(), - target: Zero::zero(), - selector: Zero::zero(), - argument_addresses: ArrayTrait::new().span(), - description: "", - }, - ); - leaf_index += 1_u256; - i += 1_u256; - } -} - - -pub fn _get_proofs_using_tree( - leafs: Array, tree: Array>, -) -> Array> { - let mut proofs = ArrayTrait::new(); - for i in 0..leafs.len() { - let leaf = leafs.at(i); - let mut serialized_struct: Array = ArrayTrait::new(); - leaf.decoder_and_sanitizer.serialize(ref serialized_struct); - leaf.target.serialize(ref serialized_struct); - leaf.selector.serialize(ref serialized_struct); - leaf.argument_addresses.serialize(ref serialized_struct); - let first_element = serialized_struct.pop_front().unwrap(); - let mut state = PedersenTrait::new(first_element); - - while let Some(value) = serialized_struct.pop_front() { - state = state.update(value); - } - let leaf_hash = state.finalize(); - let proof = _generate_proof(leaf_hash, tree.clone()); - proofs.append(proof); - } - proofs -} - -pub fn _generate_proof(mut leaf: felt252, tree: Array>) -> Span { - let tree_length = tree.len(); - let mut proof = ArrayTrait::new(); - for i in 0..tree_length - 1 { - let tree_current_layer = tree.at(i); - let tree_current_layer_length = tree_current_layer.len(); - for j in 0..tree_current_layer_length { - if leaf == *tree_current_layer.at(j) { - let element_to_append = if j % 2 == 0 { - *tree_current_layer.at(j + 1) - } else { - *tree_current_layer.at(j - 1) - }; - leaf = PedersenCHasher::commutative_hash(leaf, element_to_append); - proof.append(element_to_append); - break; - } else { - assert(j != tree_current_layer_length - 1, 'leaf not found in tree'); - } - } - } - proof.span() -} - - -// ========================================= ERC4626 ========================================= - -pub fn _add_erc4626_leafs( - ref leafs: Array, - ref leaf_index: u256, - vault: ContractAddress, - decoder_and_sanitizer: ContractAddress, - erc4626: ContractAddress, -) { - let erc4626_erc4646_disp = IERC4626Dispatcher { contract_address: erc4626 }; - let asset = erc4626_erc4646_disp.asset(); - - // Approvals - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: asset, - selector: selector!("approve"), - argument_addresses: array![erc4626.into()].span(), - description: "Approve" - + " " - + get_symbol(erc4626) - + " " - + "to spend" - + " " - + get_symbol(asset), - }, - ); - leaf_index += 1; - - // Deposits - - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: erc4626, - selector: selector!("deposit"), - argument_addresses: array![vault.into()].span(), - description: "Deposit" - + " " - + get_symbol(asset) - + " " - + "for" - + " " - + get_symbol(erc4626), - }, - ); - leaf_index += 1; - - // Withdrawals - - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: erc4626, - selector: selector!("withdraw"), - argument_addresses: array![vault.into(), vault.into()].span(), - description: "Withdraw" - + " " - + get_symbol(asset) - + " " - + "from" - + " " - + get_symbol(erc4626), - }, - ); - leaf_index += 1; - - // Minting - - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: erc4626, - selector: selector!("mint"), - argument_addresses: array![vault.into()].span(), - description: "Mint" - + " " - + get_symbol(erc4626) - + " " - + "from" - + " " - + get_symbol(asset), - }, - ); - leaf_index += 1; - - // Redeeming - - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: erc4626, - selector: selector!("redeem"), - argument_addresses: array![vault.into(), vault.into()].span(), - description: "Redeem" - + " " - + get_symbol(erc4626) - + " " - + "for" - + " " - + get_symbol(asset), - }, - ); - leaf_index += 1; -} - - -// ========================================= VESU ========================================= - -pub fn _add_vesu_leafs( - ref leafs: Array, - ref leaf_index: u256, - vault: ContractAddress, - decoder_and_sanitizer: ContractAddress, - pool_id: felt252, - collateral_assets: Span, - debt_assets_per_collateral_asset: Span>, -) { - assert(collateral_assets.len() == debt_assets_per_collateral_asset.len(), 'inconsistent len'); - let singleton = ISingletonV2Dispatcher { contract_address: VESU_SINGLETON() }; - let pool_extension = singleton.extension(pool_id); - assert(pool_extension != Zero::zero(), 'pool extension not found'); - let pool_extension = IDefaultExtensionPOV2Dispatcher { contract_address: pool_extension }; - - for i in 0..collateral_assets.len() { - let collateral_asset = *collateral_assets.at(i); - let debt_assets = *debt_assets_per_collateral_asset.at(i); - - let v_token = pool_extension.v_token_for_collateral_asset(pool_id, collateral_asset); - assert(v_token != Zero::zero(), 'v token not found'); - - // earn mode - _add_erc4626_leafs(ref leafs, ref leaf_index, vault, decoder_and_sanitizer, v_token); - - // debt mode - let debt_assets_len = debt_assets.len(); - if debt_assets_len > 0 { - for j in 0..debt_assets_len { - let mut pool_id_str: ByteArray = FormatAsByteArray::format_as_byte_array( - @pool_id, 16, - ); - - // can be performed via transfering the v-token with transfer_position - // or can be performed via modify_position directly - - // APPROVAL of collateral asset to the singleton - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: collateral_asset, - selector: selector!("approve"), - argument_addresses: array![VESU_SINGLETON().into()].span(), - description: "Approve" - + " " - + "singleton" - + "_" - + pool_id_str.clone() - + " " - + "to spend" - + " " - + get_symbol(collateral_asset), - }, - ); - leaf_index += 1; - - // APPROVAL of v-token to the extension - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: v_token, - selector: selector!("approve"), - argument_addresses: array![pool_extension.contract_address.into()] - .span(), - description: "Approve" - + " " - + "extension_pid" - + "_" - + pool_id_str.clone() - + " " - + "to spend" - + " " - + get_symbol(v_token), - }, - ); - leaf_index += 1; - - let debt_asset = *debt_assets.at(j); - - // TRANSFER POSITION to create the pair collateral/debt - let mut argument_addresses_transfer_position = ArrayTrait::new(); - - // pool_id - pool_id.serialize(ref argument_addresses_transfer_position); - - // from_collateral_asset - collateral_asset.serialize(ref argument_addresses_transfer_position); - - // from_debt_asset - let from_debt_asset: ContractAddress = Zero::zero(); - from_debt_asset.serialize(ref argument_addresses_transfer_position); - - // to_collateral_asset - collateral_asset.serialize(ref argument_addresses_transfer_position); - - // to_debt_asset - debt_asset.serialize(ref argument_addresses_transfer_position); - - // from_user - pool_extension.contract_address.serialize(ref argument_addresses_transfer_position); - - // to_user - vault.serialize(ref argument_addresses_transfer_position); - - // from_data - let from_data: Span = array![].span(); - from_data.serialize(ref argument_addresses_transfer_position); - - // to_data - let to_data: Span = array![].span(); - to_data.serialize(ref argument_addresses_transfer_position); - - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: singleton.contract_address, - selector: selector!("transfer_position"), - argument_addresses: argument_addresses_transfer_position.span(), - description: "Transfer position" - + " " - + "extension_pid" - + "_" - + pool_id_str.clone() - + " " - + "with collateral" - + " " - + get_symbol(collateral_asset) - + " " - + "and debt" - + " " - + get_symbol(debt_asset), - }, - ); - leaf_index += 1; - - // MODIFY POSITION - let mut argument_addresses_modify_position = ArrayTrait::new(); - - // pool_id - pool_id.serialize(ref argument_addresses_modify_position); - - // collateral_asset - collateral_asset.serialize(ref argument_addresses_modify_position); - - // debt_asset - debt_asset.serialize(ref argument_addresses_modify_position); - - // user - vault.serialize(ref argument_addresses_modify_position); - - // data - let data: Span = array![].span(); - data.serialize(ref argument_addresses_modify_position); - - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: singleton.contract_address, - selector: selector!("modify_position"), - argument_addresses: argument_addresses_modify_position.span(), - description: "Modify position" - + " " - + "extension_pid" - + "_" - + pool_id_str - + " " - + "with collateral" - + " " - + get_symbol(collateral_asset) - + " " - + "and debt" - + " " - + get_symbol(debt_asset), - }, - ); - leaf_index += 1; - } - } - } -} - - -pub fn _add_vesu_flash_loan_leafs( - ref leafs: Array, - ref leaf_index: u256, - vault: ContractAddress, - decoder_and_sanitizer: ContractAddress, - manager: ContractAddress, - asset: ContractAddress, - is_legacy: bool, -) { - let mut argument_addresses = ArrayTrait::new(); - manager.serialize(ref argument_addresses); - asset.serialize(ref argument_addresses); - is_legacy.serialize(ref argument_addresses); - - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: manager, - selector: selector!("flash_loan"), - argument_addresses: argument_addresses.span(), - description: "Flash loan" + " " + get_symbol(asset), - }, - ); - leaf_index += 1; -} - -// ========================================= AVNU ========================================= -pub fn _add_avnu_leafs( - ref leafs: Array, - ref leaf_index: u256, - vault: ContractAddress, - decoder_and_sanitizer: ContractAddress, - router: ContractAddress, - sell_and_buy_token_address: Array<(ContractAddress, ContractAddress)>, -) { - let mut seen_sells: Array = ArrayTrait::new(); - - for i in 0..sell_and_buy_token_address.len() { - let (sell_token_address, buy_token_address) = *sell_and_buy_token_address.at(i); - - if !_contains_address(seen_sells.span(), sell_token_address) { - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: sell_token_address, - selector: selector!("approve"), - argument_addresses: array![router.into()].span(), - description: "Approve" - + " " - + "avnu_router" - + " " - + "to spend" - + " " - + get_symbol(sell_token_address), - }, - ); - leaf_index += 1; - seen_sells.append(sell_token_address); - } - - // swap leaf à chaque paire - let mut argument_addresses = ArrayTrait::new(); - sell_token_address.serialize(ref argument_addresses); - buy_token_address.serialize(ref argument_addresses); - vault.serialize(ref argument_addresses); - - leafs - .append( - ManageLeaf { - decoder_and_sanitizer, - target: router, - selector: selector!("multi_route_swap"), - argument_addresses: argument_addresses.span(), - description: "Multi route swap" - + " " - + get_symbol(sell_token_address) - + " " - + "for" - + " " - + get_symbol(buy_token_address), - }, - ); - leaf_index += 1; - } -} - -fn _contains_address(span: Span, addr: ContractAddress) -> bool { - let mut i = 0; - while i < span.len() { - if *span.at(i) == addr { - return true; - } - i += 1; - } - false -} - - -fn get_symbol(contract_address: ContractAddress) -> ByteArray { - let ret_data = call_contract_syscall(contract_address, selector!("symbol"), array![].span()); - match ret_data { - Ok(res) => { - let res_len: u32 = res.len(); - if (res_len == 1) { - let symbol_felt = *res.at(0); - let mut symbol_byte_array: ByteArray = ""; - symbol_byte_array.append_word(symbol_felt, bytes_in_felt(symbol_felt)); - symbol_byte_array - } else { - let mut res_span = res; - Serde::::deserialize(ref res_span).unwrap() - } - }, - Err(revert_reason) => { panic!("revert_reason: {:?}", revert_reason); }, - } -} - - -fn bytes_in_felt(word: felt252) -> usize { - if word == 0 { - return 0; - } - let x: u256 = word.try_into().unwrap(); - - let mut p: u256 = 1_u256; - let mut bytes: usize = 0; - - while p <= x && bytes < 31 { - p = p * 256_u256; - bytes += 1; - } - - bytes -} diff --git a/packages/vault_allocator/src/vault_allocator/vault_allocator.cairo b/packages/vault_allocator/src/vault_allocator/vault_allocator.cairo index ecc76293..54be9608 100644 --- a/packages/vault_allocator/src/vault_allocator/vault_allocator.cairo +++ b/packages/vault_allocator/src/vault_allocator/vault_allocator.cairo @@ -5,7 +5,7 @@ #[starknet::contract] pub mod VaultAllocator { use openzeppelin::access::ownable::OwnableComponent; - use openzeppelin::upgrades::interface::IUpgradeable; + use openzeppelin::interfaces::upgrades::IUpgradeable; use openzeppelin::upgrades::upgradeable::UpgradeableComponent; use starknet::account::Call; use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; @@ -31,6 +31,16 @@ pub mod VaultAllocator { pub enum Event { OwnableEvent: OwnableComponent::Event, UpgradeableEvent: UpgradeableComponent::Event, + CallPerformed: CallPerformed, + } + + /// Event emitted when a user requests a redemption + #[derive(Drop, starknet::Event)] + pub struct CallPerformed { + pub to: ContractAddress, + pub selector: felt252, + pub calldata: Span, + pub result: Span, } @@ -67,7 +77,7 @@ pub mod VaultAllocator { fn manage(ref self: ContractState, call: Call) -> Span { self._only_manager(); - call_contract_syscall(call.to, call.selector, call.calldata).unwrap_syscall() + self.call_contract(call.to, call.selector, call.calldata) } fn manage_multi(ref self: ContractState, calls: Array) -> Array> { @@ -76,11 +86,7 @@ pub mod VaultAllocator { let calls_len = calls.len(); for i in 0..calls_len { let call = *calls.at(i); - results - .append( - call_contract_syscall(call.to, call.selector, call.calldata) - .unwrap_syscall(), - ); + results.append(self.call_contract(call.to, call.selector, call.calldata)); } results } @@ -94,5 +100,16 @@ pub mod VaultAllocator { Errors::only_manager(); } } + + fn call_contract( + ref self: ContractState, + to: ContractAddress, + selector: felt252, + calldata: Span, + ) -> Span { + let result = call_contract_syscall(to, selector, calldata).unwrap_syscall(); + self.emit(CallPerformed { to, selector, calldata, result }); + result + } } } diff --git a/scripts/.env.example b/scripts/.env.example new file mode 100644 index 00000000..13e97ed2 --- /dev/null +++ b/scripts/.env.example @@ -0,0 +1,7 @@ +# StarkNet RPC URLs + +RPC=https://starknet-sepolia.public.blastapi.io + +# Account configuration +ACCOUNT_ADDRESS=0x... +ACCOUNT_PK=0x... \ No newline at end of file diff --git a/scripts/.gitignore b/scripts/.gitignore new file mode 100644 index 00000000..561b07d1 --- /dev/null +++ b/scripts/.gitignore @@ -0,0 +1,48 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Build outputs +dist/ +build/ +*.tsbuildinfo + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Temporary files +*.tmp +*.temp +*.log + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +ENV/ + +# Cairo/Starknet +target/ +*.sierra +*.json.gz \ No newline at end of file diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 00000000..371b377d --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,108 @@ +# StarkNet Vault Kit Scripts + +Collection of deployment and management scripts for the StarkNet Vault Kit. + +## Prerequisites + +- Node.js and pnpm installed +- StarkNet account with sufficient balance +- Environment variables configured in `.env` + +## Environment Setup + +Create a `.env` file in the project root with: + +```env +RPC= +ACCOUNT_ADDRESS= +ACCOUNT_PK= +``` + +## Available Scripts + +### Contract Declaration + +```bash +# Declare any contract +pnpm declare --contract + +# Declare specific contracts +pnpm declare:vault +pnpm declare:vault-allocator +pnpm declare:redeem-request +pnpm declare:avnu-middleware +pnpm declare:manager +pnpm declare:price-router +pnpm declare:decoder-sanitizer +``` + +### Contract Deployment + +```bash +# Deploy any contract +pnpm deploy --contract + +# Deploy vault with interactive setup +pnpm deploy:vault +``` + +### Configuration Management + +```bash +# Configure vault settings +pnpm vault:config + +# Configure manager settings +pnpm manager:config +``` + +## Script Details + +### `declareContract.ts` + +Declares Cairo contracts on StarkNet. Supports all vault ecosystem contracts. + +### `deployContract.ts` + +Deploys declared contracts with constructor parameters. + +### `deployVault.ts` + +Interactive vault deployment with automatic dependency setup. + +### `vaultConfig.ts` + +Interactive vault configuration management: + +- Set fees configuration (redeem, management, performance) +- Configure report delays +- Set max delta parameters + +### `managerConfig.ts` + +Interactive manager configuration: + +- Set manage root (merkle tree root for strategist targets) + +## Usage Examples + +```bash +# Full deployment workflow +pnpm declare:vault +pnpm declare:vault-allocator +pnpm deploy:vault + +# Configure deployed vault +pnpm vault:config + +# Set up manager permissions +pnpm manager:config +``` + +## Notes + +- All scripts use interactive prompts for safe parameter input +- Transaction hashes are displayed for verification +- Deployment addresses are saved to `deployments.json` +- Scripts validate inputs before execution +- Network detection is automatic based on RPC endpoint diff --git a/scripts/configs/config.json b/scripts/configs/config.json new file mode 100644 index 00000000..916eb6e7 --- /dev/null +++ b/scripts/configs/config.json @@ -0,0 +1,24 @@ +{ + "sepolia": { + "hash": {}, + "periphery": {} + }, + "mainnet": { + "hash": { + "Vault": "0x02c9453c0d1e52c38b9273801db7d44a3dd718f7d2152667f76ccf479afe854c", + "VaultAllocator": "0x00bf475be37c67c2b2b400dc4433f162ea516c40bd6ac4fc0bc5452a6a61539f", + "RedeemRequest": "0x076b42bba1d387b1d4a91f4b3e660365b25e5e6f1a0a27b9417283e45e7034ed", + "AvnuMiddleware": "0x061991d4c071181b102b97df406d2804c08c63d443cb011552fa4a93947de8e9", + "Manager": "0x0743ae018195fc5208d05471999a8ee519a4aac9a715a049b367389bac24214f", + "PriceRouter": "0x0388513a8239b53c9dba7a19049e9159f73a9d196dcb10745489bdc7d65839e5", + "SimpleDecoderAndSanitizer": "0x036c29d91a5f1cf1eb62acad4c39803061fd184b0d16a78721b7fcd725756625", + "AumProvider4626": "0x21aa3fcc7be5d6078d8551838e297e5e29f7e594c2f1338e3a27dbb11e127eb", + "VesuV2SpecificDecoderAndSanitizer": "0x02da0e885beb8200e0c1e2793cd5afecd4567c41b4f19940a962d77cd0b34f29" + }, + "periphery": { + "vesuSingleton": "0x000d8d6dfec4d33bfb6895de9f3852143a17c6f92fd2a21da3d6924d34870160", + "avnuRouter": "0x04270219d365d6b017231b52e92b3fb5d7c8378b05e9abc97724537a80e93b0f", + "pragma": "0x02a85bd616f912537c50a49a4076db02c00b29b2cdc8a197ce92ed1837fa875b" + } + } +} \ No newline at end of file diff --git a/scripts/configs/utils.ts b/scripts/configs/utils.ts new file mode 100644 index 00000000..3f34b10b --- /dev/null +++ b/scripts/configs/utils.ts @@ -0,0 +1,48 @@ +import fs from "fs"; +import path from "path"; + +const CONFIG_FILE = path.join(__dirname, "config.json"); + +export interface NetworkConfig { + [key: string]: any; +} + +export interface Config { + [networkName: string]: NetworkConfig; +} + +export function readConfigs(): Config { + try { + if (!fs.existsSync(CONFIG_FILE)) { + const defaultConfig: Config = { + sepolia: { + hash: {}, + periphery: {} + }, + mainnet: { + hash: {}, + periphery: {} + } + }; + writeConfigs(defaultConfig); + return defaultConfig; + } + + const configData = fs.readFileSync(CONFIG_FILE, "utf8"); + return JSON.parse(configData) as Config; + } catch (error) { + console.error("Error reading config file:", error); + throw new Error("Failed to read configuration"); + } +} + +export function writeConfigs(config: Config): void { + try { + const configData = JSON.stringify(config, null, 2); + fs.writeFileSync(CONFIG_FILE, configData, "utf8"); + console.log("Configuration saved successfully"); + } catch (error) { + console.error("Error writing config file:", error); + throw new Error("Failed to write configuration"); + } +} \ No newline at end of file diff --git a/scripts/declareContract.ts b/scripts/declareContract.ts new file mode 100644 index 00000000..a4299ea7 --- /dev/null +++ b/scripts/declareContract.ts @@ -0,0 +1,111 @@ +import { Account, json, RpcProvider, hash } from "starknet"; +import fs from "fs"; +import dotenv from "dotenv"; +import { readConfigs, writeConfigs } from "./configs/utils"; +import { getNetworkEnv } from "./utils"; + +dotenv.config({ path: __dirname + "/../.env" }); + +const provider = new RpcProvider({ nodeUrl: process.env.RPC }); + +const owner = new Account( + provider, + process.env.ACCOUNT_ADDRESS as string, + process.env.ACCOUNT_PK as string, + undefined, + "0x3" +); + +export async function declareContract( + envNetwork: string, + packageName: string, + name: string +) { + const config = readConfigs(); + const networkConfig = config[envNetwork]; + if (!networkConfig) { + throw new Error(`Configuration not found for network: ${envNetwork}`); + } + + const compiledContract = await json.parse( + fs + .readFileSync(`../target/dev/${packageName}_${name}.contract_class.json`) + .toString("ascii") + ); + const compiledSierraCasm = await json.parse( + fs + .readFileSync( + `../target/dev/${packageName}_${name}.compiled_contract_class.json` + ) + .toString("ascii") + ); + + try { + const declareResponse = await owner.declare({ + contract: compiledContract, + casm: compiledSierraCasm, + }); + + let classHash = declareResponse.class_hash; + console.log( + `Class Hash ${name}: ${classHash} deployed for network: ${envNetwork}` + ); + if (!networkConfig.hash) { + networkConfig.hash = {}; + } + networkConfig.hash[name] = classHash; + config[envNetwork] = networkConfig; + writeConfigs(config); + } catch (error) { + console.error(error); + } +} + +async function main() { + if (!process.argv[2] || !process.argv[3]) { + throw new Error("Missing --contract "); + } + + let envNetwork = await getNetworkEnv(provider); + switch (process.argv[3]) { + case "Vault": + await declareContract(envNetwork, "vault", "Vault"); + break; + case "VaultAllocator": + await declareContract(envNetwork, "vault_allocator", "VaultAllocator"); + break; + case "RedeemRequest": + await declareContract(envNetwork, "vault", "RedeemRequest"); + break; + case "AvnuMiddleware": + await declareContract(envNetwork, "vault_allocator", "AvnuMiddleware"); + break; + case "Manager": + await declareContract(envNetwork, "vault_allocator", "Manager"); + break; + case "PriceRouter": + await declareContract(envNetwork, "vault_allocator", "PriceRouter"); + break; + case "SimpleDecoderAndSanitizer": + await declareContract( + envNetwork, + "vault_allocator", + "SimpleDecoderAndSanitizer" + ); + break; + case "VesuV2SpecificDecoderAndSanitizer": + await declareContract( + envNetwork, + "vault_allocator", + "VesuV2SpecificDecoderAndSanitizer" + ); + break; + case "AumProvider4626": + await declareContract(envNetwork, "vault", "AumProvider4626"); + break; + default: + throw new Error("Error: Unknown contract"); + } +} + +main(); diff --git a/scripts/deployContract.ts b/scripts/deployContract.ts new file mode 100644 index 00000000..9897ad9c --- /dev/null +++ b/scripts/deployContract.ts @@ -0,0 +1,260 @@ +import { Account, RpcProvider, CallData, CairoUint256 } from "starknet"; +import dotenv from "dotenv"; +import { readConfigs } from "./configs/utils"; +import { getNetworkEnv } from "./utils"; +import { saveContractDeployment } from "./utils/deployment"; + +dotenv.config({ path: __dirname + "/../.env" }); + +const provider = new RpcProvider({ nodeUrl: process.env.RPC }); +const owner = new Account( + provider, + process.env.ACCOUNT_ADDRESS as string, + process.env.ACCOUNT_PK as string, + undefined, + "0x3" +); + +export async function deployContract( + envNetwork: string, + contractName: string, + constructorParams?: any[] +) { + const config = readConfigs(); + const networkConfig = config[envNetwork]; + if (!networkConfig) { + throw new Error(`Configuration not found for network: ${envNetwork}`); + } + + const classHash = networkConfig.hash?.[contractName]; + if (!classHash) { + throw new Error( + `${contractName} class hash not found for network: ${envNetwork}. Please declare the contract first.` + ); + } + + try { + const constructorCalldata = constructorParams + ? CallData.compile(constructorParams) + : []; + + console.log(`Deploying ${contractName} with constructor params:`); + if (constructorParams) { + constructorParams.forEach((param, index) => { + console.log(` Param ${index}: ${param}`); + }); + } else { + console.log(` No constructor parameters`); + } + + const deployResponse = await owner.deployContract({ + classHash: classHash, + constructorCalldata: constructorCalldata, + }); + + console.log(`${contractName} deployed successfully!`); + console.log(`Contract Address: ${deployResponse.contract_address}`); + console.log(`Transaction Hash: ${deployResponse.transaction_hash}`); + + saveContractDeployment( + envNetwork, + contractName, + deployResponse.contract_address, + deployResponse.transaction_hash + ); + + return deployResponse.contract_address; + } catch (error) { + console.error(`Error deploying ${contractName}:`, error); + throw error; + } +} + +export async function deploySimpleDecoderAndSanitizer(envNetwork: string) { + return await deployContract(envNetwork, "SimpleDecoderAndSanitizer", []); +} + +export async function deployPriceRouter(envNetwork: string) { + const config = readConfigs(); + const networkConfig = config[envNetwork]; + if (!networkConfig) { + throw new Error(`Configuration not found for network: ${envNetwork}`); + } + + const pragmaAddress = networkConfig.periphery?.pragma; + if (!pragmaAddress) { + throw new Error( + `pragma address not found for network: ${envNetwork}. Please add it to the config.` + ); + } + + return await deployContract(envNetwork, "PriceRouter", [ + owner.address, + pragmaAddress, + ]); +} + +export async function deployAvnuMiddleware( + envNetwork: string, + slippage_tolerance_bps: number +) { + const config = readConfigs(); + const networkConfig = config[envNetwork]; + if (!networkConfig) { + throw new Error(`Configuration not found for network: ${envNetwork}`); + } + + const avnuRouter = networkConfig.periphery?.avnuRouter; + if (!avnuRouter) { + throw new Error( + `avnuRouter address not found for network: ${envNetwork}. Please add it to the config.` + ); + } + + const priceRouter = networkConfig.pricerouter; + if (!priceRouter) { + throw new Error( + `priceRouter address not found for network: ${envNetwork}. Please add it to the config.` + ); + } + + const slippage_tolerance_bps_uint256 = new CairoUint256( + slippage_tolerance_bps + ); + + return await deployContract(envNetwork, "AvnuMiddleware", [ + owner.address, + avnuRouter, + priceRouter, + slippage_tolerance_bps_uint256, + ]); +} + +export async function deployAumProvider4626( + envNetwork: string, + vaultAddress: string, + strategy4626Address: string +) { + return await deployContract(envNetwork, "AumProvider4626", [ + vaultAddress, + strategy4626Address, + ]); +} + +function validateSlippageTolerancePercentage(slippage: string): number { + const num = parseFloat(slippage); + if (isNaN(num) || num < 0 || num > 100) { + throw new Error( + `Invalid slippage tolerance: ${slippage}%. Must be between 0 and 100%` + ); + } + return Math.floor(num * 100); +} + +function parseArguments(contractName: string, args: string[]) { + switch (contractName) { + case "SimpleDecoderAndSanitizer": + return {}; + + case "PriceRouter": + return {}; + + case "AvnuMiddleware": + if (args.length < 1) { + throw new Error( + "AvnuMiddleware requires: " + ); + } + return { + slippageToleranceBps: validateSlippageTolerancePercentage(args[0]), + }; + + case "AumProvider4626": + if (args.length < 2) { + throw new Error("AumProvider4626 requires: "); + } + return { + vaultAddress: args[0], + strategy4626Address: args[1], + }; + + default: + throw new Error(`Unknown contract: ${contractName}`); + } +} + +async function main() { + try { + const envNetwork = await getNetworkEnv(provider); + + if (!process.argv[2] || !process.argv[3]) { + console.log( + "Usage: npm run deploy:contract -- --contract [args...]" + ); + console.log("\nAvailable contracts:"); + console.log(" - SimpleDecoderAndSanitizer"); + console.log(" Usage: --contract SimpleDecoderAndSanitizer"); + console.log(" - PriceRouter"); + console.log(" Usage: --contract PriceRouter"); + console.log(" - AvnuMiddleware "); + console.log( + " Usage: --contract AvnuMiddleware " + ); + console.log( + " Note: slippage_tolerance_percentage should be between 0-100% (e.g., 2.5 for 2.5%)" + ); + console.log(" - AumProvider4626 "); + console.log( + " Usage: --contract AumProvider4626 " + ); + return; + } + + const contractName = process.argv[3]; + const contractArgs = process.argv.slice(4); + + console.log( + `\n🚀 Starting ${contractName} deployment on ${envNetwork}...\n` + ); + + const parsedArgs = parseArguments(contractName, contractArgs); + let deployedAddress: string; + + switch (contractName) { + case "SimpleDecoderAndSanitizer": + deployedAddress = await deploySimpleDecoderAndSanitizer(envNetwork); + break; + + case "PriceRouter": + deployedAddress = await deployPriceRouter(envNetwork); + break; + + case "AvnuMiddleware": + deployedAddress = await deployAvnuMiddleware( + envNetwork, + parsedArgs.slippageToleranceBps as number + ); + break; + + case "AumProvider4626": + deployedAddress = await deployAumProvider4626( + envNetwork, + parsedArgs.vaultAddress as string, + parsedArgs.strategy4626Address as string + ); + break; + + default: + throw new Error(`Unknown contract: ${contractName}`); + } + + console.log(`\n✅ ${contractName} deployment completed successfully!`); + console.log(`📍 Contract Address: ${deployedAddress}`); + console.log(`🌐 Network: ${envNetwork}`); + } catch (error) { + console.error(`\n❌ Deployment failed:`, error); + process.exit(1); + } +} + +main().catch(console.error); diff --git a/scripts/deployVault.ts b/scripts/deployVault.ts new file mode 100644 index 00000000..e93318b3 --- /dev/null +++ b/scripts/deployVault.ts @@ -0,0 +1,648 @@ +import { + Account, + byteArray, + CairoUint256, + CallData, + RpcProvider, + validateAndParseAddress, +} from "starknet"; +import dotenv from "dotenv"; +import { readConfigs } from "./configs/utils"; +import { getNetworkEnv, WAD } from "./utils"; +import { saveVaultDeployment } from "./utils/deployment"; +import { Decimal } from "decimal.js"; +import readline from "readline"; + +interface VaultDeploymentConfig { + name: string; + symbol: string; + underlyingAsset: string; + ownerAddress: string; + feesRecipient: string; + redeemFeesPercentage: string; + performanceFeePercentage: string; + managementFeePercentage: string; + reportDelay: string; + maxDeltaPercentage: string; +} + +interface VaultAllocatorDeploymentConfig { + vault: string; + manager: string; + paymentToken: string; +} + +interface DeploymentConfig { + vault?: VaultDeploymentConfig; + vaultAllocator?: VaultAllocatorDeploymentConfig; +} + +dotenv.config({ path: __dirname + "/../.env" }); + +const provider = new RpcProvider({ nodeUrl: process.env.RPC }); +const owner = new Account( + provider, + process.env.ACCOUNT_ADDRESS as string, + process.env.ACCOUNT_PK as string, + undefined, + "0x3" +); + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +function askQuestion(question: string): Promise { + return new Promise((resolve) => { + rl.question(question, (answer) => { + resolve(answer.trim()); + }); + }); +} + +const MAX_REDEEM_FEE_PERCENT = 0.1; +const MAX_MANAGEMENT_FEE_PERCENT = 2.0; +const MAX_PERFORMANCE_FEE_PERCENT = 20.0; +const MIN_REPORT_DELAY_SECONDS = 3600; + +function validatePercentage(value: string, fieldName: string): void { + const num = parseFloat(value); + if (isNaN(num) || num < 0) { + throw new Error(`${fieldName} must be a valid non-negative percentage`); + } +} + +function validateRedeemFeePercentage(value: string): void { + const num = parseFloat(value); + validatePercentage(value, "Redeem fees percentage"); + if (num > MAX_REDEEM_FEE_PERCENT) { + throw new Error( + `Redeem fees percentage must not exceed ${MAX_REDEEM_FEE_PERCENT}% (vault contract limit)` + ); + } +} + +function validateManagementFeePercentage(value: string): void { + const num = parseFloat(value); + validatePercentage(value, "Management fees percentage"); + if (num > MAX_MANAGEMENT_FEE_PERCENT) { + throw new Error( + `Management fees percentage must not exceed ${MAX_MANAGEMENT_FEE_PERCENT}% (vault contract limit)` + ); + } +} + +function validatePerformanceFeePercentage(value: string): void { + const num = parseFloat(value); + validatePercentage(value, "Performance fees percentage"); + if (num > MAX_PERFORMANCE_FEE_PERCENT) { + throw new Error( + `Performance fees percentage must not exceed ${MAX_PERFORMANCE_FEE_PERCENT}% (vault contract limit)` + ); + } +} + +function validateReportDelay(value: string): void { + const num = parseInt(value); + if (isNaN(num) || num < MIN_REPORT_DELAY_SECONDS) { + throw new Error( + `Report delay must be at least ${MIN_REPORT_DELAY_SECONDS} seconds (1 hour minimum from vault contract)` + ); + } +} + +function validateMaxDeltaPercentage(value: string): void { + const num = parseFloat(value); + if (isNaN(num) || num < 0 || num > 100) { + throw new Error("Max delta percentage must be between 0 and 100"); + } +} + +async function collectVaultParameters(): Promise { + console.log("🚀 Initializing vault deployment process...\n"); + console.log("Please provide the following parameters:\n"); + + const name = await askQuestion("Vault name: "); + const symbol = await askQuestion("Vault symbol: "); + + const underlyingAsset = await askQuestion("Underlying asset address: "); + try { + validateAndParseAddress(underlyingAsset); + } catch (error) { + throw new Error(`Invalid underlying asset address: ${underlyingAsset}`); + } + + const ownerAddress = owner.address; + + const feesRecipient = await askQuestion("Fees recipient address: "); + try { + validateAndParseAddress(feesRecipient); + } catch (error) { + throw new Error(`Invalid fees recipient address: ${feesRecipient}`); + } + + const redeemFeesPercentage = await askQuestion("Redeem fees percentage: "); + validateRedeemFeePercentage(redeemFeesPercentage); + + const performanceFeePercentage = await askQuestion( + "Performance fee percentage: " + ); + validatePerformanceFeePercentage(performanceFeePercentage); + + const managementFeePercentage = await askQuestion( + "Management fee percentage: " + ); + validateManagementFeePercentage(managementFeePercentage); + + const reportDelay = await askQuestion("Report delay (seconds): "); + validateReportDelay(reportDelay); + + const maxDeltaPercentage = await askQuestion("Max delta percentage: "); + validateMaxDeltaPercentage(maxDeltaPercentage); + + return { + name, + symbol, + underlyingAsset, + ownerAddress, + feesRecipient, + redeemFeesPercentage, + performanceFeePercentage, + managementFeePercentage, + reportDelay, + maxDeltaPercentage, + }; +} + +async function askCustodialType(): Promise<{ + isCustodial: boolean; + vaultAllocatorAddress?: string; +}> { + const custodialAnswer = await askQuestion( + "\nIs this vault custodial? (y/n): " + ); + + const normalizedAnswer = custodialAnswer.toLowerCase().trim(); + if (!["y", "yes", "n", "no"].includes(normalizedAnswer)) { + throw new Error( + `Invalid custodial type answer: '${custodialAnswer}'. Please enter 'y', 'yes', 'n', or 'no'.` + ); + } + + const isCustodial = normalizedAnswer === "y" || normalizedAnswer === "yes"; + + if (isCustodial) { + const vaultAllocatorAddress = await askQuestion( + "Vault allocator address: " + ); + + try { + validateAndParseAddress(vaultAllocatorAddress); + } catch (error) { + throw new Error( + `Invalid vault allocator address: ${vaultAllocatorAddress}` + ); + } + + return { isCustodial, vaultAllocatorAddress }; + } + + return { isCustodial }; +} + +export async function deployVault( + envNetwork: string, + nameString: string, + symbolString: string, + underlyingAsset: string, + ownerAddress: string, + feesRecipient: string, + redeemFeesPercentage: string, + performanceFeePercentage: string, + managementFeePercentage: string, + reportDelay: string, + maxDeltaPercentage: string +) { + const config = readConfigs(); + const networkConfig = config[envNetwork]; + + if (!networkConfig) { + throw new Error(`Configuration not found for network: ${envNetwork}`); + } + + const classHash = networkConfig.hash?.Vault; + if (!classHash) { + throw new Error( + `Vault class hash not found for network: ${envNetwork}. Please declare the contract first.` + ); + } + + const redeemFees = new CairoUint256( + new Decimal(redeemFeesPercentage).mul(WAD).div(100).toString() + ); + + const managementFees = new CairoUint256( + new Decimal(managementFeePercentage).mul(WAD).div(100).toString() + ); + + const performanceFees = new CairoUint256( + new Decimal(performanceFeePercentage).mul(WAD).div(100).toString() + ); + + const maxDelta = new CairoUint256( + new Decimal(maxDeltaPercentage).mul(WAD).div(100).toString() + ); + + const nameBytes = byteArray.byteArrayFromString(nameString); + const symbolBytes = byteArray.byteArrayFromString(symbolString); + + try { + let constructorCalldata = { + name: nameBytes, + symbol: symbolBytes, + underlying_asset: underlyingAsset, + owner: ownerAddress, + fees_recipient: feesRecipient, + redeem_fees: redeemFees, + management_fees: managementFees, + performance_fees: performanceFees, + report_delay: reportDelay, + max_delta: maxDelta, + }; + + console.log(constructorCalldata); + + console.log(`Deploying Vault with constructor params:`); + console.log(` Name: ${nameString}`); + console.log(` Symbol: ${symbolString}`); + console.log(` Asset: ${underlyingAsset}`); + console.log(` Owner: ${ownerAddress}`); + console.log(` Fees Recipient: ${feesRecipient}`); + console.log(` Redeem Fees: ${redeemFeesPercentage}`); + console.log(` Performance Fee: ${performanceFeePercentage}`); + console.log(` Management Fee: ${managementFeePercentage}`); + console.log(` Report Delay: ${reportDelay}`); + console.log(` Max Delta: ${maxDeltaPercentage}`); + + const deployResponse = await owner.deployContract({ + classHash: classHash, + constructorCalldata: constructorCalldata, + }); + + console.log(`Vault deployed successfully!`); + console.log(`Contract Address: ${deployResponse.contract_address}`); + console.log(`Transaction Hash: ${deployResponse.transaction_hash}`); + + saveVaultDeployment( + envNetwork, + symbolString, + "vault", + deployResponse.contract_address, + deployResponse.transaction_hash + ); + + return deployResponse.contract_address; + } catch (error) { + console.error("Error deploying Vault:", error); + throw error; + } +} + +export async function deployRedeemRequest( + envNetwork: string, + ownerAddress: string, + vaultAddress: string, + vaultSymbol?: string +) { + const config = readConfigs(); + const networkConfig = config[envNetwork]; + + if (!networkConfig) { + throw new Error(`Configuration not found for network: ${envNetwork}`); + } + + const classHash = networkConfig.hash?.RedeemRequest; + if (!classHash) { + throw new Error( + `RedeemRequest class hash not found for network: ${envNetwork}. Please declare the contract first.` + ); + } + + try { + console.log(`Deploying RedeemRequest with constructor params:`); + console.log(` Owner: ${owner.address}`); + console.log(` Vault: ${vaultAddress}`); + + const deployResponse = await owner.deployContract({ + classHash: classHash, + constructorCalldata: [owner.address, vaultAddress], + }); + + console.log(`RedeemRequest deployed successfully!`); + console.log(`Contract Address: ${deployResponse.contract_address}`); + console.log(`Transaction Hash: ${deployResponse.transaction_hash}`); + + if (vaultSymbol) { + saveVaultDeployment( + envNetwork, + vaultSymbol, + "redeemRequest", + deployResponse.contract_address, + deployResponse.transaction_hash + ); + } + + return deployResponse.contract_address; + } catch (error) { + console.error("Error deploying RedeemRequest:", error); + throw error; + } +} + +export async function linkRedeemRequestToVault( + vaultAddress: string, + redeemRequestAddress: string +) { + try { + console.log(`Linking RedeemRequest to Vault...`); + console.log(` Vault: ${vaultAddress}`); + console.log(` RedeemRequest: ${redeemRequestAddress}`); + + const response = await owner.execute({ + contractAddress: vaultAddress, + entrypoint: "register_redeem_request", + calldata: [redeemRequestAddress], + }); + + console.log(`RedeemRequest linked to Vault successfully!`); + console.log(`Transaction Hash: ${response.transaction_hash}`); + + return response.transaction_hash; + } catch (error) { + console.error("Error linking RedeemRequest to Vault:", error); + throw error; + } +} + +export async function deployVaultAllocator( + envNetwork: string, + vaultSymbol?: string +) { + const config = readConfigs(); + const networkConfig = config[envNetwork]; + + if (!networkConfig) { + throw new Error(`Configuration not found for network: ${envNetwork}`); + } + + const classHash = networkConfig.hash?.VaultAllocator; + if (!classHash) { + throw new Error( + `VaultAllocator class hash not found for network: ${envNetwork}. Please declare the contract first.` + ); + } + + try { + console.log(`Deploying VaultAllocator with constructor params:`); + console.log(` Owner: ${owner.address}`); + + const deployResponse = await owner.deployContract({ + classHash: classHash, + constructorCalldata: [owner.address], + }); + + console.log(`VaultAllocator deployed successfully!`); + console.log(`Contract Address: ${deployResponse.contract_address}`); + console.log(`Transaction Hash: ${deployResponse.transaction_hash}`); + + if (vaultSymbol) { + saveVaultDeployment( + envNetwork, + vaultSymbol, + "vaultAllocator", + deployResponse.contract_address, + deployResponse.transaction_hash + ); + } + + return deployResponse.contract_address; + } catch (error) { + console.error("Error deploying VaultAllocator:", error); + throw error; + } +} + +export async function deployManager( + envNetwork: string, + vaultAllocatorAddress: string, + vaultSymbol?: string +) { + const config = readConfigs(); + const networkConfig = config[envNetwork]; + + if (!networkConfig) { + throw new Error(`Configuration not found for network: ${envNetwork}`); + } + + const classHash = networkConfig.hash?.Manager; + if (!classHash) { + throw new Error( + `Manager class hash not found for network: ${envNetwork}. Please declare the contract first.` + ); + } + + const vesuSingleton = networkConfig.periphery?.vesuSingleton; + if (!vesuSingleton) { + throw new Error( + `Vesu Singleton address not found for network: ${envNetwork}.` + ); + } + + try { + const constructorCalldata = CallData.compile([ + owner.address, + vaultAllocatorAddress, + vesuSingleton, + ]); + + console.log(`Deploying Manager with constructor params:`); + console.log(` Owner: ${owner.address}`); + console.log(` Vault Allocator: ${vaultAllocatorAddress}`); + console.log(` Vesu Singleton: ${vesuSingleton}`); + + const deployResponse = await owner.deployContract({ + classHash: classHash, + constructorCalldata: constructorCalldata, + }); + + console.log(`Manager deployed successfully!`); + console.log(`Contract Address: ${deployResponse.contract_address}`); + console.log(`Transaction Hash: ${deployResponse.transaction_hash}`); + + if (vaultSymbol) { + saveVaultDeployment( + envNetwork, + vaultSymbol, + "manager", + deployResponse.contract_address, + deployResponse.transaction_hash + ); + } + + return deployResponse.contract_address; + } catch (error) { + console.error("Error deploying Manager:", error); + throw error; + } +} + +export async function attachVaultAllocatorToVault( + vaultAddress: string, + vaultAllocatorAddress: string +) { + try { + console.log(`Attaching VaultAllocator to Vault...`); + console.log(` Vault: ${vaultAddress}`); + console.log(` VaultAllocator: ${vaultAllocatorAddress}`); + + const response = await owner.execute({ + contractAddress: vaultAddress, + entrypoint: "register_vault_allocator", + calldata: [vaultAllocatorAddress], + }); + + console.log(`VaultAllocator attached to Vault successfully!`); + console.log(`Transaction Hash: ${response.transaction_hash}`); + + return response.transaction_hash; + } catch (error) { + console.error("Error attaching VaultAllocator to Vault:", error); + throw error; + } +} + +export async function setManagerInVaultAllocator( + vaultAllocatorAddress: string, + managerAddress: string +) { + try { + console.log(`Setting Manager in VaultAllocator...`); + console.log(` VaultAllocator: ${vaultAllocatorAddress}`); + console.log(` Manager: ${managerAddress}`); + + const response = await owner.execute({ + contractAddress: vaultAllocatorAddress, + entrypoint: "set_manager", + calldata: [managerAddress], + }); + + console.log(`Manager set in VaultAllocator successfully!`); + console.log(`Transaction Hash: ${response.transaction_hash}`); + + return response.transaction_hash; + } catch (error) { + console.error("Error setting Manager in VaultAllocator:", error); + throw error; + } +} + +async function main() { + try { + const envNetwork = await getNetworkEnv(provider); + + const vaultConfig = await collectVaultParameters(); + + const { isCustodial, vaultAllocatorAddress } = await askCustodialType(); + + console.log("\n📋 Deployment Summary:"); + console.log(`Network: ${envNetwork}`); + console.log(`Vault Type: ${isCustodial ? "Custodial" : "Non-custodial"}`); + if (isCustodial) { + console.log(`Vault Allocator: ${vaultAllocatorAddress}`); + } + console.log("\n🚀 Starting deployment process...\n"); + + console.log("📦 Deploying Vault..."); + const vaultAddress = await deployVault( + envNetwork, + vaultConfig.name, + vaultConfig.symbol, + vaultConfig.underlyingAsset, + vaultConfig.ownerAddress, + vaultConfig.feesRecipient, + vaultConfig.redeemFeesPercentage, + vaultConfig.performanceFeePercentage, + vaultConfig.managementFeePercentage, + vaultConfig.reportDelay, + vaultConfig.maxDeltaPercentage + ); + console.log("\n⏳ Waiting 3 seconds before deploying RedeemRequest..."); + await new Promise(resolve => setTimeout(resolve, 3000)); + + console.log("\n📦 Deploying RedeemRequest..."); + const redeemRequestAddress = await deployRedeemRequest( + envNetwork, + vaultConfig.ownerAddress, + vaultAddress, + vaultConfig.symbol + ); + + console.log("\n⏳ Waiting 3 seconds before linking RedeemRequest to Vault..."); + await new Promise(resolve => setTimeout(resolve, 3000)); + + console.log("\n🔗 Linking RedeemRequest to Vault..."); + await linkRedeemRequestToVault(vaultAddress, redeemRequestAddress); + + console.log("\n⏳ Waiting 3 seconds before vault allocator operations..."); + await new Promise(resolve => setTimeout(resolve, 3000)); + + if (isCustodial) { + console.log("\n🔗 Attaching existing VaultAllocator to Vault..."); + await attachVaultAllocatorToVault(vaultAddress, vaultAllocatorAddress!); + } else { + console.log("\n📦 Deploying new VaultAllocator..."); + + const newVaultAllocatorAddress = await deployVaultAllocator( + envNetwork, + vaultConfig.symbol + ); + + console.log("\n⏳ Waiting 3 seconds before attaching VaultAllocator to Vault..."); + await new Promise(resolve => setTimeout(resolve, 3000)); + + console.log("\n🔗 Attaching new VaultAllocator to Vault..."); + await attachVaultAllocatorToVault(vaultAddress, newVaultAllocatorAddress); + + console.log("\n⏳ Waiting 3 seconds before deploying Manager..."); + await new Promise(resolve => setTimeout(resolve, 3000)); + + console.log("\n📦 Deploying Manager..."); + const managerAddress = await deployManager( + envNetwork, + newVaultAllocatorAddress, + vaultConfig.symbol + ); + + console.log("\n⏳ Waiting 3 seconds before setting Manager in VaultAllocator..."); + await new Promise(resolve => setTimeout(resolve, 3000)); + + console.log("\n🔗 Setting Manager in VaultAllocator..."); + await setManagerInVaultAllocator( + newVaultAllocatorAddress, + managerAddress + ); + } + + console.log("\n✅ Deployment completed successfully!"); + console.log(`📍 Vault Address: ${vaultAddress}`); + console.log(`📍 RedeemRequest Address: ${redeemRequestAddress}`); + } catch (error) { + console.error("\n❌ Deployment failed:", error); + throw error; + } finally { + rl.close(); + } +} + +main().catch(console.error); diff --git a/scripts/managerConfig.ts b/scripts/managerConfig.ts new file mode 100644 index 00000000..5b8a5b65 --- /dev/null +++ b/scripts/managerConfig.ts @@ -0,0 +1,144 @@ +import { + Account, + RpcProvider, + validateAndParseAddress, +} from "starknet"; +import dotenv from "dotenv"; +import { getNetworkEnv } from "./utils"; +import readline from "readline"; + +dotenv.config({ path: __dirname + "/../.env" }); + +const provider = new RpcProvider({ nodeUrl: process.env.RPC }); +const owner = new Account( + provider, + process.env.ACCOUNT_ADDRESS as string, + process.env.ACCOUNT_PK as string, + undefined, + "0x3" +); + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +function askQuestion(question: string): Promise { + return new Promise((resolve) => { + rl.question(question, (answer) => { + resolve(answer.trim()); + }); + }); +} + +function validateMerkleRoot(value: string): void { + if (!value || value.length === 0) { + throw new Error("Merkle root cannot be empty"); + } + + if (!value.startsWith("0x")) { + throw new Error("Merkle root must start with '0x'"); + } + + const hexValue = value.slice(2); + if (!/^[0-9a-fA-F]+$/.test(hexValue)) { + throw new Error("Merkle root must be a valid hexadecimal value"); + } +} + +async function getManagerAddress(): Promise { + const managerAddress = await askQuestion("Enter manager contract address: "); + try { + validateAndParseAddress(managerAddress); + return managerAddress; + } catch (error) { + throw new Error(`Invalid manager address: ${managerAddress}`); + } +} + +async function showConfigMenu(): Promise { + console.log("\n📋 Manager Configuration Options:"); + console.log("1. Set Manage Root"); + console.log("2. Exit"); + + const choice = await askQuestion("\nSelect an option (1-2): "); + return choice.trim(); +} + +async function setManageRoot(managerAddress: string): Promise { + console.log("\n🌳 Set Manage Root"); + console.log("Configure merkle tree root for a strategist target"); + console.log(); + + const strategistAddress = await askQuestion("Strategist address (target): "); + try { + validateAndParseAddress(strategistAddress); + } catch (error) { + throw new Error(`Invalid strategist address: ${strategistAddress}`); + } + + const merkleRoot = await askQuestion("Merkle tree root (felt252): "); + validateMerkleRoot(merkleRoot); + + console.log(`\n📋 Configuration Summary:`); + console.log(` Manager: ${managerAddress}`); + console.log(` Strategist: ${strategistAddress}`); + console.log(` Merkle Root: ${merkleRoot}`); + + const confirm = await askQuestion("\nConfirm configuration? (y/n): "); + if (confirm.toLowerCase() !== "y" && confirm.toLowerCase() !== "yes") { + console.log("Configuration cancelled."); + return; + } + + try { + const response = await owner.execute({ + contractAddress: managerAddress, + entrypoint: "set_manage_root", + calldata: [strategistAddress, merkleRoot], + }); + + console.log("✅ Manage root updated successfully!"); + console.log(`Transaction Hash: ${response.transaction_hash}`); + } catch (error) { + console.error("❌ Error setting manage root:", error); + throw error; + } +} + +async function main() { + try { + const envNetwork = await getNetworkEnv(provider); + console.log(`🌐 Connected to network: ${envNetwork}`); + console.log(`👤 Using account: ${owner.address}`); + + const managerAddress = await getManagerAddress(); + + while (true) { + const choice = await showConfigMenu(); + + switch (choice) { + case "1": + await setManageRoot(managerAddress); + break; + + case "2": + console.log("👋 Goodbye!"); + return; + + default: + console.log("❌ Invalid choice. Please select 1-2."); + break; + } + + console.log("\n" + "=".repeat(50)); + } + } catch (error) { + console.error("\n❌ Configuration failed:", error); + throw error; + } finally { + rl.close(); + } +} + +main().catch(console.error); \ No newline at end of file diff --git a/scripts/package.json b/scripts/package.json new file mode 100644 index 00000000..c7d3a157 --- /dev/null +++ b/scripts/package.json @@ -0,0 +1,40 @@ +{ + "name": "starknet-vault-kit-scripts", + "version": "1.0.0", + "description": "StarkNet Vault Kit deployment and management scripts", + "main": "index.js", + "packageManager": "pnpm@9.0.0", + "scripts": { + "declare": "tsx declareContract.ts --contract", + "declare:vault": "tsx declareContract.ts --contract Vault", + "declare:vault-allocator": "tsx declareContract.ts --contract VaultAllocator", + "declare:redeem-request": "tsx declareContract.ts --contract RedeemRequest", + "declare:avnu-middleware": "tsx declareContract.ts --contract AvnuMiddleware", + "declare:manager": "tsx declareContract.ts --contract Manager", + "declare:price-router": "tsx declareContract.ts --contract PriceRouter", + "declare:simple-decoder-sanitizer": "tsx declareContract.ts --contract SimpleDecoderAndSanitizer", + "declare:vesu-v2-specific-decoder-sanitizer": "tsx declareContract.ts --contract VesuV2SpecificDecoderAndSanitizer", + "declare:aum-provider-4626": "tsx declareContract.ts --contract AumProvider4626", + "deploy:contract": "tsx deployContract.ts --contract", + "deploy:vault": "tsx deployVault.ts", + "vault:config": "tsx vaultConfig.ts", + "manager:config": "tsx managerConfig.ts" + }, + "dependencies": { + "dotenv": "^16.4.5", + "starknet": "7.6.4" + }, + "devDependencies": { + "@types/node": "^22.5.4", + "tsx": "^4.19.1", + "typescript": "^5.5.4" + }, + "keywords": [ + "starknet", + "cairo", + "vault", + "defi" + ], + "author": "", + "license": "ISC" +} \ No newline at end of file diff --git a/scripts/pnpm-lock.yaml b/scripts/pnpm-lock.yaml new file mode 100644 index 00000000..58ba7a08 --- /dev/null +++ b/scripts/pnpm-lock.yaml @@ -0,0 +1,613 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + dotenv: + specifier: ^16.4.5 + version: 16.6.1 + starknet: + specifier: 7.6.4 + version: 7.6.4 + devDependencies: + '@types/node': + specifier: ^22.5.4 + version: 22.18.0 + tsx: + specifier: ^4.19.1 + version: 4.20.5 + typescript: + specifier: ^5.5.4 + version: 5.9.2 + +packages: + + '@esbuild/aix-ppc64@0.25.9': + resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.9': + resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.9': + resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.9': + resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.9': + resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.9': + resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.9': + resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.9': + resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.9': + resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.9': + resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.9': + resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.9': + resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.9': + resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.9': + resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.9': + resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.9': + resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.9': + resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.9': + resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.9': + resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.9': + resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.9': + resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.9': + resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.9': + resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.9': + resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.9': + resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.9': + resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@noble/curves@1.7.0': + resolution: {integrity: sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.6.0': + resolution: {integrity: sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==} + engines: {node: ^14.21.3 || >=16} + + '@scure/base@1.2.1': + resolution: {integrity: sha512-DGmGtC8Tt63J5GfHgfl5CuAXh96VF/LD8K9Hr/Gv0J2lAoRGlPOMpqMpMbCTOoOJMZCk2Xt+DskdDyn6dEFdzQ==} + + '@scure/starknet@1.1.0': + resolution: {integrity: sha512-83g3M6Ix2qRsPN4wqLDqiRZ2GBNbjVWfboJE/9UjfG+MHr6oDSu/CWgy8hsBSJejr09DkkL+l0Ze4KVrlCIdtQ==} + + '@starknet-io/types-js@0.7.10': + resolution: {integrity: sha512-1VtCqX4AHWJlRRSYGSn+4X1mqolI1Tdq62IwzoU2vUuEE72S1OlEeGhpvd6XsdqXcfHmVzYfj8k1XtKBQqwo9w==} + + '@starknet-io/types-js@0.8.4': + resolution: {integrity: sha512-0RZ3TZHcLsUTQaq1JhDSCM8chnzO4/XNsSCozwDET64JK5bjFDIf2ZUkta+tl5Nlbf4usoU7uZiDI/Q57kt2SQ==} + + '@types/node@22.18.0': + resolution: {integrity: sha512-m5ObIqwsUp6BZzyiy4RdZpzWGub9bqLJMvZDD0QMXhxjqMHMENlj+SqF5QxoUwaQNFe+8kz8XM8ZQhqkQPTgMQ==} + + abi-wan-kanabi@2.2.4: + resolution: {integrity: sha512-0aA81FScmJCPX+8UvkXLki3X1+yPQuWxEkqXBVKltgPAK79J+NB+Lp5DouMXa7L6f+zcRlIA/6XO7BN/q9fnvg==} + hasBin: true + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansicolors@0.3.2: + resolution: {integrity: sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==} + + cardinal@2.1.1: + resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==} + hasBin: true + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + esbuild@0.25.9: + resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-tsconfig@4.10.1: + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + lossless-json@4.1.1: + resolution: {integrity: sha512-HusN80C0ohtT9kOHQH7EuUaqzRQsnekpa+2ot8OzvW0iC08dq/YtM/7uKwwajldQsCrHyC8q9fz3t3L+TmDltA==} + + pako@2.1.0: + resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} + + redeyed@2.1.1: + resolution: {integrity: sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + starknet@7.6.4: + resolution: {integrity: sha512-FB20IaLCDbh/XomkB+19f5jmNxG+RzNdRO7QUhm7nfH81UPIt2C/MyWAlHCYkbv2wznSEb73wpxbp9tytokTgQ==} + engines: {node: '>=22'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + ts-mixer@6.0.4: + resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==} + + tsx@4.20.5: + resolution: {integrity: sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==} + engines: {node: '>=18.0.0'} + hasBin: true + + typescript@5.9.2: + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + +snapshots: + + '@esbuild/aix-ppc64@0.25.9': + optional: true + + '@esbuild/android-arm64@0.25.9': + optional: true + + '@esbuild/android-arm@0.25.9': + optional: true + + '@esbuild/android-x64@0.25.9': + optional: true + + '@esbuild/darwin-arm64@0.25.9': + optional: true + + '@esbuild/darwin-x64@0.25.9': + optional: true + + '@esbuild/freebsd-arm64@0.25.9': + optional: true + + '@esbuild/freebsd-x64@0.25.9': + optional: true + + '@esbuild/linux-arm64@0.25.9': + optional: true + + '@esbuild/linux-arm@0.25.9': + optional: true + + '@esbuild/linux-ia32@0.25.9': + optional: true + + '@esbuild/linux-loong64@0.25.9': + optional: true + + '@esbuild/linux-mips64el@0.25.9': + optional: true + + '@esbuild/linux-ppc64@0.25.9': + optional: true + + '@esbuild/linux-riscv64@0.25.9': + optional: true + + '@esbuild/linux-s390x@0.25.9': + optional: true + + '@esbuild/linux-x64@0.25.9': + optional: true + + '@esbuild/netbsd-arm64@0.25.9': + optional: true + + '@esbuild/netbsd-x64@0.25.9': + optional: true + + '@esbuild/openbsd-arm64@0.25.9': + optional: true + + '@esbuild/openbsd-x64@0.25.9': + optional: true + + '@esbuild/openharmony-arm64@0.25.9': + optional: true + + '@esbuild/sunos-x64@0.25.9': + optional: true + + '@esbuild/win32-arm64@0.25.9': + optional: true + + '@esbuild/win32-ia32@0.25.9': + optional: true + + '@esbuild/win32-x64@0.25.9': + optional: true + + '@noble/curves@1.7.0': + dependencies: + '@noble/hashes': 1.6.0 + + '@noble/hashes@1.6.0': {} + + '@scure/base@1.2.1': {} + + '@scure/starknet@1.1.0': + dependencies: + '@noble/curves': 1.7.0 + '@noble/hashes': 1.6.0 + + '@starknet-io/types-js@0.7.10': {} + + '@starknet-io/types-js@0.8.4': {} + + '@types/node@22.18.0': + dependencies: + undici-types: 6.21.0 + + abi-wan-kanabi@2.2.4: + dependencies: + ansicolors: 0.3.2 + cardinal: 2.1.1 + fs-extra: 10.1.0 + yargs: 17.7.2 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansicolors@0.3.2: {} + + cardinal@2.1.1: + dependencies: + ansicolors: 0.3.2 + redeyed: 2.1.1 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + dotenv@16.6.1: {} + + emoji-regex@8.0.0: {} + + esbuild@0.25.9: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.9 + '@esbuild/android-arm': 0.25.9 + '@esbuild/android-arm64': 0.25.9 + '@esbuild/android-x64': 0.25.9 + '@esbuild/darwin-arm64': 0.25.9 + '@esbuild/darwin-x64': 0.25.9 + '@esbuild/freebsd-arm64': 0.25.9 + '@esbuild/freebsd-x64': 0.25.9 + '@esbuild/linux-arm': 0.25.9 + '@esbuild/linux-arm64': 0.25.9 + '@esbuild/linux-ia32': 0.25.9 + '@esbuild/linux-loong64': 0.25.9 + '@esbuild/linux-mips64el': 0.25.9 + '@esbuild/linux-ppc64': 0.25.9 + '@esbuild/linux-riscv64': 0.25.9 + '@esbuild/linux-s390x': 0.25.9 + '@esbuild/linux-x64': 0.25.9 + '@esbuild/netbsd-arm64': 0.25.9 + '@esbuild/netbsd-x64': 0.25.9 + '@esbuild/openbsd-arm64': 0.25.9 + '@esbuild/openbsd-x64': 0.25.9 + '@esbuild/openharmony-arm64': 0.25.9 + '@esbuild/sunos-x64': 0.25.9 + '@esbuild/win32-arm64': 0.25.9 + '@esbuild/win32-ia32': 0.25.9 + '@esbuild/win32-x64': 0.25.9 + + escalade@3.2.0: {} + + esprima@4.0.1: {} + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fsevents@2.3.3: + optional: true + + get-caller-file@2.0.5: {} + + get-tsconfig@4.10.1: + dependencies: + resolve-pkg-maps: 1.0.0 + + graceful-fs@4.2.11: {} + + is-fullwidth-code-point@3.0.0: {} + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + lossless-json@4.1.1: {} + + pako@2.1.0: {} + + redeyed@2.1.1: + dependencies: + esprima: 4.0.1 + + require-directory@2.1.1: {} + + resolve-pkg-maps@1.0.0: {} + + starknet@7.6.4: + dependencies: + '@noble/curves': 1.7.0 + '@noble/hashes': 1.6.0 + '@scure/base': 1.2.1 + '@scure/starknet': 1.1.0 + '@starknet-io/starknet-types-07': '@starknet-io/types-js@0.7.10' + '@starknet-io/starknet-types-08': '@starknet-io/types-js@0.8.4' + abi-wan-kanabi: 2.2.4 + lossless-json: 4.1.1 + pako: 2.1.0 + ts-mixer: 6.0.4 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + ts-mixer@6.0.4: {} + + tsx@4.20.5: + dependencies: + esbuild: 0.25.9 + get-tsconfig: 4.10.1 + optionalDependencies: + fsevents: 2.3.3 + + typescript@5.9.2: {} + + undici-types@6.21.0: {} + + universalify@2.0.1: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + y18n@5.0.8: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 diff --git a/scripts/utils.ts b/scripts/utils.ts new file mode 100644 index 00000000..d20645f8 --- /dev/null +++ b/scripts/utils.ts @@ -0,0 +1,28 @@ +import fs from "fs"; +import dotenv from "dotenv"; +import { RpcProvider, constants } from "starknet"; + +dotenv.config({ path: __dirname + "/../.env" }); + +export async function appendToEnv(name: string, address: string) { + fs.appendFile( + `${__dirname}/../.env`, + `\n${name}_ADDRESS=${address}`, + function (err) { + if (err) throw err; + } + ); +} + +export async function getNetworkEnv(provider: RpcProvider): Promise { + const chainIdFromRpc = await provider.getChainId(); + if (chainIdFromRpc == constants.StarknetChainId.SN_SEPOLIA) { + return "sepolia"; + } + if (chainIdFromRpc == constants.StarknetChainId.SN_MAIN) { + return "mainnet"; + } + throw new Error(`Unsupported network: ${chainIdFromRpc}`); +} + +export const WAD = "1000000000000000000"; diff --git a/scripts/utils/deployment.ts b/scripts/utils/deployment.ts new file mode 100644 index 00000000..d9caacb5 --- /dev/null +++ b/scripts/utils/deployment.ts @@ -0,0 +1,96 @@ +import fs from "fs"; +import path from "path"; + +interface DeploymentInfo { + address: string; + transactionHash: string; + timestamp: number; +} + +interface NetworkDeployments { + [contractName: string]: DeploymentInfo; + [symbol: string]: { + vault?: DeploymentInfo; + redeemRequest?: DeploymentInfo; + vaultAllocator?: DeploymentInfo; + manager?: DeploymentInfo; + }; +} + +interface DeploymentsJson { + [network: string]: NetworkDeployments; +} + +const DEPLOYMENTS_FILE = path.join(__dirname, "../deployments.json"); + +export function readDeployments(): DeploymentsJson { + try { + if (!fs.existsSync(DEPLOYMENTS_FILE)) { + return { sepolia: {}, mainnet: {} }; + } + const data = fs.readFileSync(DEPLOYMENTS_FILE, "utf8"); + return JSON.parse(data); + } catch (error) { + console.warn("Warning: Could not read deployments.json, creating new one"); + return { sepolia: {}, mainnet: {} }; + } +} + +export function writeDeployments(deployments: DeploymentsJson): void { + try { + fs.writeFileSync(DEPLOYMENTS_FILE, JSON.stringify(deployments, null, 2)); + } catch (error) { + console.error("Error writing deployments.json:", error); + throw error; + } +} + +export function saveContractDeployment( + network: string, + contractName: string, + address: string, + transactionHash: string +): void { + const deployments = readDeployments(); + + if (!deployments[network]) { + deployments[network] = {}; + } + + deployments[network][contractName] = { + address, + transactionHash, + timestamp: Date.now(), + }; + + writeDeployments(deployments); +} + +export function saveVaultDeployment( + network: string, + symbol: string, + contractType: "vault" | "redeemRequest" | "vaultAllocator" | "manager", + address: string, + transactionHash: string +): void { + const deployments = readDeployments(); + + if (!deployments[network]) { + deployments[network] = {}; + } + + if ( + !deployments[network][symbol] || + typeof deployments[network][symbol] !== "object" + ) { + deployments[network][symbol] = {} as any; + } + + (deployments[network][symbol] as any)[contractType] = { + address, + transactionHash, + timestamp: Date.now(), + }; + + writeDeployments(deployments); +} diff --git a/scripts/vaultConfig.ts b/scripts/vaultConfig.ts new file mode 100644 index 00000000..82eea705 --- /dev/null +++ b/scripts/vaultConfig.ts @@ -0,0 +1,340 @@ +import { + Account, + CairoUint256, + RpcProvider, + validateAndParseAddress, + hash, +} from "starknet"; +import dotenv from "dotenv"; +import { getNetworkEnv, WAD } from "./utils"; +import { Decimal } from "decimal.js"; +import readline from "readline"; + +dotenv.config({ path: __dirname + "/../.env" }); + +const provider = new RpcProvider({ nodeUrl: process.env.RPC }); +const owner = new Account( + provider, + process.env.ACCOUNT_ADDRESS as string, + process.env.ACCOUNT_PK as string, + undefined, + "0x3" +); + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +function askQuestion(question: string): Promise { + return new Promise((resolve) => { + rl.question(question, (answer) => { + resolve(answer.trim()); + }); + }); +} + +const MAX_REDEEM_FEE_PERCENT = 0.1; +const MAX_MANAGEMENT_FEE_PERCENT = 2.0; +const MAX_PERFORMANCE_FEE_PERCENT = 20.0; +const MIN_REPORT_DELAY_SECONDS = 3600; + +function validatePercentage(value: string, fieldName: string): void { + const num = parseFloat(value); + if (isNaN(num) || num < 0) { + throw new Error(`${fieldName} must be a valid non-negative percentage`); + } +} + +function validateRedeemFeePercentage(value: string): void { + const num = parseFloat(value); + validatePercentage(value, "Redeem fees percentage"); + if (num > MAX_REDEEM_FEE_PERCENT) { + throw new Error( + `Redeem fees percentage must not exceed ${MAX_REDEEM_FEE_PERCENT}%` + ); + } +} + +function validateManagementFeePercentage(value: string): void { + const num = parseFloat(value); + validatePercentage(value, "Management fees percentage"); + if (num > MAX_MANAGEMENT_FEE_PERCENT) { + throw new Error( + `Management fees percentage must not exceed ${MAX_MANAGEMENT_FEE_PERCENT}%` + ); + } +} + +function validatePerformanceFeePercentage(value: string): void { + const num = parseFloat(value); + validatePercentage(value, "Performance fees percentage"); + if (num > MAX_PERFORMANCE_FEE_PERCENT) { + throw new Error( + `Performance fees percentage must not exceed ${MAX_PERFORMANCE_FEE_PERCENT}%` + ); + } +} + +function validateReportDelay(value: string): void { + const num = parseInt(value); + if (isNaN(num) || num < MIN_REPORT_DELAY_SECONDS) { + throw new Error( + `Report delay must be at least ${MIN_REPORT_DELAY_SECONDS} seconds (1 hour minimum)` + ); + } +} + +function validateMaxDeltaPercentage(value: string): void { + const num = parseFloat(value); + if (isNaN(num) || num < 0 || num > 100) { + throw new Error("Max delta percentage must be between 0 and 100"); + } +} + +async function getVaultAddress(): Promise { + const vaultAddress = await askQuestion("Enter vault contract address: "); + try { + validateAndParseAddress(vaultAddress); + return vaultAddress; + } catch (error) { + throw new Error(`Invalid vault address: ${vaultAddress}`); + } +} + +async function showConfigMenu(): Promise { + console.log("\n📋 Vault Configuration Options:"); + console.log("1. Set Fees Configuration"); + console.log("2. Set Report Delay"); + console.log("3. Set Max Delta"); + console.log("4. Grant Oracle Role"); + console.log("5. Exit"); + + const choice = await askQuestion("\nSelect an option (1-5): "); + return choice.trim(); +} + +async function setFeesConfig(vaultAddress: string): Promise { + console.log("\n💰 Set Fees Configuration"); + console.log(` - Redeem fees: max ${MAX_REDEEM_FEE_PERCENT}%`); + console.log(` - Management fees: max ${MAX_MANAGEMENT_FEE_PERCENT}%`); + console.log(` - Performance fees: max ${MAX_PERFORMANCE_FEE_PERCENT}%`); + console.log(); + + const feesRecipient = await askQuestion("Fees recipient address: "); + try { + validateAndParseAddress(feesRecipient); + } catch (error) { + throw new Error(`Invalid fees recipient address: ${feesRecipient}`); + } + + const redeemFeesPercentage = await askQuestion("Redeem fees percentage: "); + validateRedeemFeePercentage(redeemFeesPercentage); + + const performanceFeePercentage = await askQuestion( + "Performance fee percentage: " + ); + validatePerformanceFeePercentage(performanceFeePercentage); + + const managementFeePercentage = await askQuestion( + "Management fee percentage: " + ); + validateManagementFeePercentage(managementFeePercentage); + + const redeemFees = new CairoUint256( + new Decimal(redeemFeesPercentage).mul(WAD).div(100).toString() + ); + + const managementFees = new CairoUint256( + new Decimal(managementFeePercentage).mul(WAD).div(100).toString() + ); + + const performanceFees = new CairoUint256( + new Decimal(performanceFeePercentage).mul(WAD).div(100).toString() + ); + + console.log(`\n📋 Configuration Summary:`); + console.log(` Vault: ${vaultAddress}`); + console.log(` Fees Recipient: ${feesRecipient}`); + console.log(` Redeem Fees: ${redeemFeesPercentage}%`); + console.log(` Management Fees: ${managementFeePercentage}%`); + console.log(` Performance Fees: ${performanceFeePercentage}%`); + + const confirm = await askQuestion("\nConfirm configuration? (y/n): "); + if (confirm.toLowerCase() !== "y" && confirm.toLowerCase() !== "yes") { + console.log("Configuration cancelled."); + return; + } + + try { + const response = await owner.execute({ + contractAddress: vaultAddress, + entrypoint: "set_fees_config", + calldata: [feesRecipient, redeemFees, managementFees, performanceFees], + }); + + console.log("✅ Fees configuration updated successfully!"); + console.log(`Transaction Hash: ${response.transaction_hash}`); + } catch (error) { + console.error("❌ Error setting fees configuration:", error); + throw error; + } +} + +async function setReportDelay(vaultAddress: string): Promise { + console.log("\n⏰ Set Report Delay"); + console.log(`Minimum delay: ${MIN_REPORT_DELAY_SECONDS} seconds (1 hour)`); + + const reportDelay = await askQuestion("Report delay (seconds): "); + validateReportDelay(reportDelay); + + console.log(`\n📋 Configuration Summary:`); + console.log(` Vault: ${vaultAddress}`); + console.log( + ` Report Delay: ${reportDelay} seconds (${Math.floor( + parseInt(reportDelay) / 3600 + )} hours)` + ); + + const confirm = await askQuestion("\nConfirm configuration? (y/n): "); + if (confirm.toLowerCase() !== "y" && confirm.toLowerCase() !== "yes") { + console.log("Configuration cancelled."); + return; + } + + try { + const response = await owner.execute({ + contractAddress: vaultAddress, + entrypoint: "set_report_delay", + calldata: [reportDelay], + }); + + console.log("✅ Report delay updated successfully!"); + console.log(`Transaction Hash: ${response.transaction_hash}`); + } catch (error) { + console.error("❌ Error setting report delay:", error); + throw error; + } +} + +async function setMaxDelta(vaultAddress: string): Promise { + console.log("\n📊 Set Max Delta"); + console.log("Max delta percentage range: 0-100%"); + + const maxDeltaPercentage = await askQuestion("Max delta percentage: "); + validateMaxDeltaPercentage(maxDeltaPercentage); + + const maxDelta = new CairoUint256( + new Decimal(maxDeltaPercentage).mul(WAD).div(100).toString() + ); + + console.log(`\n📋 Configuration Summary:`); + console.log(` Vault: ${vaultAddress}`); + console.log(` Max Delta: ${maxDeltaPercentage}%`); + + const confirm = await askQuestion("\nConfirm configuration? (y/n): "); + if (confirm.toLowerCase() !== "y" && confirm.toLowerCase() !== "yes") { + console.log("Configuration cancelled."); + return; + } + + try { + const response = await owner.execute({ + contractAddress: vaultAddress, + entrypoint: "set_max_delta", + calldata: [maxDelta], + }); + + console.log("✅ Max delta updated successfully!"); + console.log(`Transaction Hash: ${response.transaction_hash}`); + } catch (error) { + console.error("❌ Error setting max delta:", error); + throw error; + } +} + +async function grantOracleRole(vaultAddress: string): Promise { + console.log("\n🔮 Grant Oracle Role"); + console.log("This will grant the ORACLE_ROLE to a specified account."); + + const oracleAccount = await askQuestion("Oracle account address: "); + try { + validateAndParseAddress(oracleAccount); + } catch (error) { + throw new Error(`Invalid oracle account address: ${oracleAccount}`); + } + + console.log(`\n📋 Configuration Summary:`); + console.log(` Vault: ${vaultAddress}`); + console.log(` Oracle Account: ${oracleAccount}`); + console.log(` Role: ORACLE_ROLE`); + + const confirm = await askQuestion("\nConfirm granting oracle role? (y/n): "); + if (confirm.toLowerCase() !== "y" && confirm.toLowerCase() !== "yes") { + console.log("Operation cancelled."); + return; + } + + try { + const response = await owner.execute({ + contractAddress: vaultAddress, + entrypoint: "grant_role", + calldata: [hash.starknetKeccak("ORACLE_ROLE").toString(), oracleAccount], + }); + + console.log("✅ Oracle role granted successfully!"); + console.log(`Transaction Hash: ${response.transaction_hash}`); + } catch (error) { + console.error("❌ Error granting oracle role:", error); + throw error; + } +} + +async function main() { + try { + const envNetwork = await getNetworkEnv(provider); + console.log(`🌐 Connected to network: ${envNetwork}`); + console.log(`👤 Using account: ${owner.address}`); + + const vaultAddress = await getVaultAddress(); + + while (true) { + const choice = await showConfigMenu(); + + switch (choice) { + case "1": + await setFeesConfig(vaultAddress); + break; + + case "2": + await setReportDelay(vaultAddress); + break; + + case "3": + await setMaxDelta(vaultAddress); + break; + + case "4": + await grantOracleRole(vaultAddress); + break; + + case "5": + console.log("👋 Goodbye!"); + return; + + default: + console.log("❌ Invalid choice. Please select 1-5."); + break; + } + + console.log("\n" + "=".repeat(50)); + } + } catch (error) { + console.error("\n❌ Configuration failed:", error); + throw error; + } finally { + rl.close(); + } +} + +main().catch(console.error); diff --git a/sdk/.gitignore b/sdk/.gitignore new file mode 100644 index 00000000..bdfa6b81 --- /dev/null +++ b/sdk/.gitignore @@ -0,0 +1,3 @@ +.env +node_modules +dist \ No newline at end of file diff --git a/sdk/README.md b/sdk/README.md new file mode 100644 index 00000000..f90f19ad --- /dev/null +++ b/sdk/README.md @@ -0,0 +1,286 @@ +# Starknet Vault Kit SDK + +TypeScript SDK for interacting with Starknet Vault Kit contracts. Provides easy-to-use interfaces for both vault users and curators to interact with ERC-4626 compatible vaults with epoched redemption systems. + +## Features + +- **User Operations**: Deposit, mint, request redemptions, and claim redemptions +- **Curator Operations**: Report AUM, manage liquidity, configure fees, and pause/unpause +- **Calldata Generation**: Generate transaction calldata for all operations +- **State Queries**: Read vault state, balances, fees, and redemption information +- **Type Safety**: Full TypeScript support with proper types + +## Installation + +```bash +npm install @starknet-vault-kit/sdk +# or +yarn add @starknet-vault-kit/sdk +``` + +## Quick Start + +### User Operations + +```typescript +import { VaultUserSDK, VaultConfig } from '@starknet-vault-kit/sdk'; +import { RpcProvider } from 'starknet'; + +// Configure vault - only vault address is required +const vaultConfig: VaultConfig = { + vaultAddress: "0x...", +}; + +// Initialize SDK with provider +const provider = new RpcProvider({ nodeUrl: "https://starknet-mainnet.public.blastapi.io" }); +const userSDK = new VaultUserSDK(vaultConfig, provider); + +// Generate deposit calldata +const depositCalldata = userSDK.buildDepositCalldata({ + assets: "1000000", // 1 USDC (6 decimals) + receiver: "0x..." +}); + +// Generate deposit calldata WITH approval (async) +const depositWithApproval = await userSDK.buildDepositCalldataWithApproval({ + assets: "1000000", + receiver: "0x...", + includeApprove: true +}); +// Returns { transactions: [approveCalldata, depositCalldata] } + +// Get vault state +const vaultState = await userSDK.getVaultState(); +console.log("Current epoch:", vaultState.epoch); + +// Preview deposit +const expectedShares = await userSDK.previewDeposit("1000000"); +console.log("Expected shares:", expectedShares); +``` + +### Curator Operations + +```typescript +import { VaultCuratorSDK } from '@starknet-vault-kit/sdk'; + +const curatorSDK = new VaultCuratorSDK(vaultConfig, provider); + +// Generate report calldata +const reportCalldata = curatorSDK.buildReportCalldata({ + newAum: "5000000000" // New AUM value +}); + +// Check if report can be made +const canReport = await curatorSDK.canReport(); +console.log("Can report:", canReport); + +// Get pending redemption requirements +const pendingRedemptions = await curatorSDK.getPendingRedemptionRequirements(); +console.log("Total pending assets:", pendingRedemptions.totalPendingAssets); +``` + +## API Reference + +### VaultUserSDK + +#### Calldata Generation + +- `buildDepositCalldata(params)` - Generate deposit transaction calldata +- `buildDepositCalldataWithApproval(params)` - Generate deposit with approval (async) +- `buildMintCalldata(params)` - Generate mint transaction calldata +- `buildMintCalldataWithApproval(params)` - Generate mint with approval (async) +- `buildRequestRedeemCalldata(params)` - Generate redeem request calldata +- `buildClaimRedeemCalldata(params)` - Generate claim redemption calldata + +#### View Methods + +- `getVaultState()` - Get current vault state (epoch, buffer, AUM, etc.) +- `getUserShareBalance(address)` - Get user's share balance +- `previewDeposit(assets)` - Preview shares received for deposit +- `previewMint(shares)` - Preview assets needed for mint +- `previewRedeem(shares)` - Preview assets received for redemption +- `getDueAssetsFromId(id)` - Get expected assets for redemption NFT +- `convertToShares(assets)` - Convert assets to shares +- `convertToAssets(shares)` - Convert shares to assets +- `getUnderlyingAssetAddress()` - Get underlying asset contract address +- `getRedeemRequestAddress()` - Get redeem request NFT contract address + +### VaultCuratorSDK + +#### Calldata Generation + +- `buildReportCalldata(params)` - Generate AUM report calldata +- `buildBringLiquidityCalldata(params)` - Generate bring liquidity calldata +- `buildPauseCalldata()` - Generate pause calldata +- `buildUnpauseCalldata()` - Generate unpause calldata +- `buildSetFeesConfigCalldata(...)` - Generate fee configuration calldata +- `buildSetReportDelayCalldata(delay)` - Generate report delay calldata +- `buildSetMaxDeltaCalldata(delta)` - Generate max delta calldata + +#### View Methods + +- `getFeesConfig()` - Get current fee configuration +- `getReportDelay()` - Get minimum report delay +- `getMaxDelta()` - Get maximum AUM delta per report +- `getLastReportTimestamp()` - Get last report timestamp +- `canReport()` - Check if report can be made +- `getTimeUntilNextReport()` - Get time until next report allowed +- `getPendingRedemptionRequirements()` - Get pending redemption info +- `isPaused()` - Check if vault is paused +- `getCurrentEpoch()` - Get current epoch +- `getBuffer()` - Get current buffer amount +- `getAum()` - Get current AUM + +## Types + +### VaultConfig +```typescript +interface VaultConfig { + vaultAddress: string; // Only vault address is required - other addresses are fetched automatically +} +``` + +### CalldataResult +```typescript +interface CalldataResult { + contractAddress: string; + entrypoint: string; + calldata: string[]; +} +``` + +### MultiCalldataResult +```typescript +interface MultiCalldataResult { + transactions: CalldataResult[]; // Array of transactions to execute in order +} +``` + +### VaultState +```typescript +interface VaultState { + epoch: bigint; + handledEpochLen: bigint; + buffer: bigint; + aum: bigint; + totalSupply: bigint; + totalAssets: bigint; +} +``` + +## Examples + +### Complete User Flow + +```typescript +import { VaultUserSDK, VaultConfig } from '@starknet-vault-kit/sdk'; +import { Account, RpcProvider } from 'starknet'; + +const vaultConfig: VaultConfig = { + vaultAddress: "0x123...", +}; + +const provider = new RpcProvider({ nodeUrl: "your-node-url" }); +const account = new Account(provider, "0x...", "your-private-key"); +const userSDK = new VaultUserSDK(vaultConfig, provider); + +// 1. Check current vault state +const vaultState = await userSDK.getVaultState(); +console.log("Vault epoch:", vaultState.epoch); + +// 2. Preview deposit +const assetsToDeposit = "1000000"; // 1 USDC +const expectedShares = await userSDK.previewDeposit(assetsToDeposit); +console.log("Expected shares:", expectedShares); + +// 3. Generate and execute deposit +const depositCalldata = userSDK.buildDepositCalldata({ + assets: assetsToDeposit, + receiver: account.address +}); + +const tx = await account.execute([{ + contractAddress: depositCalldata.contractAddress, + entrypoint: depositCalldata.entrypoint, + calldata: depositCalldata.calldata +}]); + +console.log("Deposit tx:", tx.transaction_hash); + +// Alternative: Deposit with approval in one transaction batch +const depositWithApprovalCalldata = await userSDK.buildDepositCalldataWithApproval({ + assets: assetsToDeposit, + receiver: account.address, + includeApprove: true +}); + +const approvalTx = await account.execute( + depositWithApprovalCalldata.transactions.map(tx => ({ + contractAddress: tx.contractAddress, + entrypoint: tx.entrypoint, + calldata: tx.calldata + })) +); + +console.log("Approval + Deposit tx:", approvalTx.transaction_hash); + +// 4. Later, request redemption +const userShares = await userSDK.getUserShareBalance(account.address); +const redeemCalldata = userSDK.buildRequestRedeemCalldata({ + shares: userShares / 2n, // Redeem half + receiver: account.address, + owner: account.address +}); + +const redeemTx = await account.execute([{ + contractAddress: redeemCalldata.contractAddress, + entrypoint: redeemCalldata.entrypoint, + calldata: redeemCalldata.calldata +}]); + +console.log("Redeem request tx:", redeemTx.transaction_hash); +``` + +### Curator Operations + +```typescript +import { VaultCuratorSDK } from '@starknet-vault-kit/sdk'; + +const curatorSDK = new VaultCuratorSDK(vaultConfig, provider); + +// Check report status +const canReport = await curatorSDK.canReport(); +if (!canReport) { + const timeUntilReport = await curatorSDK.getTimeUntilNextReport(); + console.log(`Must wait ${timeUntilReport} seconds before next report`); + return; +} + +// Get current state for report +const [currentAum, buffer, pendingRedemptions] = await Promise.all([ + curatorSDK.getAum(), + curatorSDK.getBuffer(), + curatorSDK.getPendingRedemptionRequirements() +]); + +console.log("Current AUM:", currentAum); +console.log("Buffer:", buffer); +console.log("Pending redemptions:", pendingRedemptions.totalPendingAssets); + +// Generate report with new AUM +const newAum = "5500000000"; // Updated AUM from strategy +const reportCalldata = curatorSDK.buildReportCalldata({ newAum }); + +// Execute report +const reportTx = await curatorAccount.execute([{ + contractAddress: reportCalldata.contractAddress, + entrypoint: reportCalldata.entrypoint, + calldata: reportCalldata.calldata +}]); + +console.log("Report tx:", reportTx.transaction_hash); +``` + +## License + +MIT \ No newline at end of file diff --git a/sdk/examples/user-example.ts b/sdk/examples/user-example.ts new file mode 100644 index 00000000..2a570ab3 --- /dev/null +++ b/sdk/examples/user-example.ts @@ -0,0 +1,504 @@ +/** + * Example usage of VaultUserSDK + * Demonstrates how to interact with the vault as a regular user + */ + +import dotenv from "dotenv"; +import { VaultUserSDK, VaultConfig } from "../src"; +import { Account, RpcProvider } from "starknet"; +import * as readline from "readline"; + +dotenv.config(); + +const vaultConfig: VaultConfig = { + vaultAddress: + "0x006ec49bb6bbd9262423e5948159f2a6fcd6ffb0a5a8492d2eff0ee13d020b6c", +}; + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +function question(query: string): Promise { + return new Promise((resolve) => rl.question(query, resolve)); +} + +async function showMenu(): Promise { + console.log("\n=== Vault User Operations Menu ==="); + console.log("1. Get vault state"); + console.log("2. Get user balance"); + console.log("3. Preview deposit"); + console.log("4. Generate deposit calldata"); + console.log("5. Generate deposit calldata with approval"); + console.log("6. Execute deposit"); + console.log("7. Preview mint"); + console.log("8. Generate mint calldata"); + console.log("9. Generate mint calldata with approval"); + console.log("10. Execute mint"); + console.log("11. Preview redeem"); + console.log("12. Generate request redeem calldata"); + console.log("13. Execute request redeem"); + console.log("14. Generate claim redeem calldata"); + console.log("15. Execute claim redeem"); + console.log("16. Check due assets for NFT"); + console.log("17. Test conversion utilities"); + console.log("0. Exit"); + console.log("====================================="); + + const choice = await question("Enter your choice (0-17): "); + return choice.trim(); +} + +async function handleGetVaultState(userSDK: VaultUserSDK): Promise { + try { + console.log("\n--- Getting vault state ---"); + const vaultState = await userSDK.getVaultState(); + console.log(`Current epoch: ${vaultState.epoch}`); + console.log(`Total supply: ${vaultState.totalSupply}`); + console.log(`Total assets: ${vaultState.totalAssets}`); + console.log(`Buffer: ${vaultState.buffer}`); + console.log(`AUM: ${vaultState.aum}`); + } catch (error) { + console.error("Error getting vault state:", error); + } +} + +async function handleGetUserBalance( + userSDK: VaultUserSDK, + account: Account +): Promise { + try { + console.log("\n--- Getting user balance ---"); + const userBalance = await userSDK.getUserShareBalance(account.address); + console.log(`User share balance: ${userBalance}`); + } catch (error) { + console.error("Error getting user balance:", error); + } +} + +async function handlePreviewDeposit(userSDK: VaultUserSDK): Promise { + try { + const assetsInput = await question( + "Enter assets to deposit (e.g., 1000000 for 1 USDC): " + ); + const assetsToDeposit = assetsInput.trim(); + + console.log("\n--- Previewing deposit ---"); + console.log(`Assets to deposit: ${assetsToDeposit}`); + const expectedShares = await userSDK.previewDeposit(assetsToDeposit); + console.log(`Expected shares: ${expectedShares}`); + } catch (error) { + console.error("Error previewing deposit:", error); + } +} + +async function handleGenerateDepositCalldata( + userSDK: VaultUserSDK, + account: Account +): Promise { + try { + const assetsInput = await question("Enter assets to deposit: "); + const assetsToDeposit = assetsInput.trim(); + + console.log("\n--- Generating deposit calldata ---"); + const depositCalldata = userSDK.buildDepositCalldata({ + assets: assetsToDeposit, + receiver: account.address, + includeApprove: true, + }); + console.log(`Deposit calldata: ${depositCalldata}`); + } catch (error) { + console.error("Error generating deposit calldata:", error); + } +} + +async function handleGenerateDepositCalldataWithApproval( + userSDK: VaultUserSDK, + account: Account +): Promise { + try { + const assetsInput = await question("Enter assets to deposit: "); + const assetsToDeposit = assetsInput.trim(); + + console.log("\n--- Generating deposit calldata with approval ---"); + const depositWithApprovalCalldata = + await userSDK.buildDepositCalldataWithApproval({ + assets: assetsToDeposit, + receiver: account.address, + includeApprove: true, + }); + console.log( + `Number of transactions: ${depositWithApprovalCalldata.transactions.length}` + ); + console.log( + `Transaction 1 (Approve): ${depositWithApprovalCalldata.transactions[0].entrypoint}` + ); + console.log( + `Transaction 2 (Deposit): ${depositWithApprovalCalldata.transactions[1].entrypoint}` + ); + } catch (error) { + console.error("Error generating deposit calldata with approval:", error); + } +} + +async function handleExecuteDeposit( + userSDK: VaultUserSDK, + account: Account +): Promise { + try { + const assetsInput = await question("Enter assets to deposit: "); + const assetsToDeposit = assetsInput.trim(); + + console.log("\n--- Executing deposit ---"); + console.log(`Assets to deposit: ${assetsToDeposit}`); + + const depositWithApprovalCalldata = + await userSDK.buildDepositCalldataWithApproval({ + assets: assetsToDeposit, + receiver: account.address, + includeApprove: true, + }); + + console.log("Executing transaction..."); + const result = await account.execute( + depositWithApprovalCalldata.transactions + ); + console.log(`Transaction hash: ${result.transaction_hash}`); + console.log("Deposit executed successfully!"); + } catch (error) { + console.error("Error executing deposit:", error); + } +} + +async function handlePreviewMint(userSDK: VaultUserSDK): Promise { + try { + const sharesInput = await question("Enter shares to mint: "); + const sharesToMint = sharesInput.trim(); + + console.log("\n--- Previewing mint ---"); + console.log(`Shares to mint: ${sharesToMint}`); + const expectedAssets = await userSDK.previewMint(sharesToMint); + console.log(`Expected assets required: ${expectedAssets}`); + } catch (error) { + console.error("Error previewing mint:", error); + } +} + +async function handleGenerateMintCalldata( + userSDK: VaultUserSDK, + account: Account +): Promise { + try { + const sharesInput = await question("Enter shares to mint: "); + const sharesToMint = sharesInput.trim(); + + console.log("\n--- Generating mint calldata ---"); + const mintCalldata = userSDK.buildMintCalldata({ + shares: sharesToMint, + receiver: account.address, + }); + console.log(`Mint calldata: ${mintCalldata}`); + } catch (error) { + console.error("Error generating mint calldata:", error); + } +} + +async function handleGenerateMintCalldataWithApproval( + userSDK: VaultUserSDK, + account: Account +): Promise { + try { + const sharesInput = await question("Enter shares to mint: "); + const sharesToMint = sharesInput.trim(); + + console.log("\n--- Generating mint calldata with approval ---"); + const mintWithApprovalCalldata = + await userSDK.buildMintCalldataWithApproval({ + shares: sharesToMint, + receiver: account.address, + includeApprove: true, + }); + console.log( + `Number of transactions: ${mintWithApprovalCalldata.transactions.length}` + ); + console.log( + `Transaction 1 (Approve): ${mintWithApprovalCalldata.transactions[0].entrypoint}` + ); + console.log( + `Transaction 2 (Mint): ${mintWithApprovalCalldata.transactions[1].entrypoint}` + ); + } catch (error) { + console.error("Error generating mint calldata with approval:", error); + } +} + +async function handleExecuteMint( + userSDK: VaultUserSDK, + account: Account +): Promise { + try { + const sharesInput = await question("Enter shares to mint: "); + const sharesToMint = sharesInput.trim(); + + console.log("\n--- Executing mint ---"); + console.log(`Shares to mint: ${sharesToMint}`); + + const mintWithApprovalCalldata = + await userSDK.buildMintCalldataWithApproval({ + shares: sharesToMint, + receiver: account.address, + includeApprove: true, + }); + + console.log("Executing transaction..."); + const result = await account.execute(mintWithApprovalCalldata.transactions); + console.log(`Transaction hash: ${result.transaction_hash}`); + console.log("Mint executed successfully!"); + } catch (error) { + console.error("Error executing mint:", error); + } +} + +async function handlePreviewRedeem(userSDK: VaultUserSDK): Promise { + try { + const sharesInput = await question("Enter shares to redeem: "); + const sharesToRedeem = sharesInput.trim(); + + console.log("\n--- Previewing redeem ---"); + console.log(`Shares to redeem: ${sharesToRedeem}`); + const expectedAssets = await userSDK.previewRedeem(sharesToRedeem); + console.log(`Expected assets: ${expectedAssets}`); + } catch (error) { + console.error("Error previewing redeem:", error); + } +} + +async function handleGenerateRequestRedeemCalldata( + userSDK: VaultUserSDK, + account: Account +): Promise { + try { + const sharesInput = await question("Enter shares to redeem: "); + const sharesToRedeem = sharesInput.trim(); + + console.log("\n--- Generating request redeem calldata ---"); + const redeemCalldata = userSDK.buildRequestRedeemCalldata({ + shares: sharesToRedeem, + receiver: account.address, + owner: account.address, + }); + console.log(`Request redeem calldata: ${redeemCalldata}`); + } catch (error) { + console.error("Error generating request redeem calldata:", error); + } +} + +async function handleExecuteRequestRedeem( + userSDK: VaultUserSDK, + account: Account +): Promise { + try { + const sharesInput = await question("Enter shares to redeem: "); + const sharesToRedeem = sharesInput.trim(); + + console.log("\n--- Executing request redeem ---"); + console.log(`Shares to redeem: ${sharesToRedeem}`); + + const redeemCalldata = userSDK.buildRequestRedeemCalldata({ + shares: sharesToRedeem, + receiver: account.address, + owner: account.address, + }); + + console.log("Executing transaction..."); + const result = await account.execute(redeemCalldata); + console.log(`Transaction hash: ${result.transaction_hash}`); + console.log("Request redeem executed successfully!"); + } catch (error) { + console.error("Error executing request redeem:", error); + } +} + +async function handleGenerateClaimRedeemCalldata( + userSDK: VaultUserSDK +): Promise { + try { + const nftIdInput = await question("Enter NFT ID to claim: "); + const nftId = nftIdInput.trim(); + + console.log("\n--- Generating claim redeem calldata ---"); + const claimCalldata = userSDK.buildClaimRedeemCalldata({ + id: nftId, + }); + console.log(`NFT ID: ${nftId}`); + console.log(`Claim redeem calldata: ${claimCalldata}`); + } catch (error) { + console.error("Error generating claim redeem calldata:", error); + } +} + +async function handleExecuteClaimRedeem( + userSDK: VaultUserSDK, + account: Account +): Promise { + try { + const nftIdInput = await question("Enter NFT ID to claim: "); + const nftId = nftIdInput.trim(); + + console.log("\n--- Executing claim redeem ---"); + console.log(`NFT ID to claim: ${nftId}`); + + const claimCalldata = userSDK.buildClaimRedeemCalldata({ + id: nftId, + }); + + console.log("Executing transaction..."); + const result = await account.execute(claimCalldata); + console.log(`Transaction hash: ${result.transaction_hash}`); + console.log("Claim redeem executed successfully!"); + } catch (error) { + console.error("Error executing claim redeem:", error); + } +} + +async function handleCheckDueAssets(userSDK: VaultUserSDK): Promise { + try { + const nftIdInput = await question("Enter NFT ID to check: "); + const nftId = nftIdInput.trim(); + + console.log("\n--- Checking due assets for NFT ---"); + const dueAssets = await userSDK.getDueAssetsFromId(nftId); + console.log(`Due assets for NFT ${nftId}: ${dueAssets}`); + } catch (error) { + console.error(`Error checking due assets: ${error}`); + } +} + +async function handleConversionUtilities(userSDK: VaultUserSDK): Promise { + try { + const assetsInput = await question("Enter assets amount to convert: "); + const testAssets = assetsInput.trim(); + + console.log("\n--- Testing conversion utilities ---"); + const convertedShares = await userSDK.convertToShares(testAssets); + const convertedBackToAssets = await userSDK.convertToAssets( + convertedShares + ); + console.log(`${testAssets} assets = ${convertedShares} shares`); + console.log(`${convertedShares} shares = ${convertedBackToAssets} assets`); + } catch (error) { + console.error("Error with conversion utilities:", error); + } +} + +async function userExample() { + const provider = new RpcProvider({ + nodeUrl: "https://rpc.starknet.lava.build:443", + }); + + // Check for required environment variables + const accountAddress = process.env.ACCOUNT_ADDRESS; + const accountPK = process.env.ACCOUNT_PK; + + if (!accountAddress || !accountPK) { + console.error("Missing required environment variables:"); + if (!accountAddress) console.error("- ACCOUNT_ADDRESS"); + if (!accountPK) console.error("- ACCOUNT_PK"); + console.error("Please set these environment variables and try again."); + return; + } + + const account = new Account( + provider, + accountAddress, + accountPK, + undefined, + "0x3" + ); + + const userSDK = new VaultUserSDK(vaultConfig, provider); + + console.log("=== Vault User Operations Interactive Example ==="); + console.log(`Connected to vault: ${vaultConfig.vaultAddress}`); + console.log(`Using account: ${account.address}`); + + try { + let choice = ""; + while (choice !== "0") { + choice = await showMenu(); + + switch (choice) { + case "1": + await handleGetVaultState(userSDK); + break; + case "2": + await handleGetUserBalance(userSDK, account); + break; + case "3": + await handlePreviewDeposit(userSDK); + break; + case "4": + await handleGenerateDepositCalldata(userSDK, account); + break; + case "5": + await handleGenerateDepositCalldataWithApproval(userSDK, account); + break; + case "6": + await handleExecuteDeposit(userSDK, account); + break; + case "7": + await handlePreviewMint(userSDK); + break; + case "8": + await handleGenerateMintCalldata(userSDK, account); + break; + case "9": + await handleGenerateMintCalldataWithApproval(userSDK, account); + break; + case "10": + await handleExecuteMint(userSDK, account); + break; + case "11": + await handlePreviewRedeem(userSDK); + break; + case "12": + await handleGenerateRequestRedeemCalldata(userSDK, account); + break; + case "13": + await handleExecuteRequestRedeem(userSDK, account); + break; + case "14": + await handleGenerateClaimRedeemCalldata(userSDK); + break; + case "15": + await handleExecuteClaimRedeem(userSDK, account); + break; + case "16": + await handleCheckDueAssets(userSDK); + break; + case "17": + await handleConversionUtilities(userSDK); + break; + case "0": + console.log("Goodbye!"); + break; + default: + console.log("Invalid choice. Please enter a number between 0-17."); + break; + } + + if (choice !== "0") { + await question("\nPress Enter to continue..."); + } + } + } catch (error) { + console.error("Error in user example:", error); + } finally { + rl.close(); + } +} + +userExample(); + +export { userExample }; diff --git a/sdk/package.json b/sdk/package.json new file mode 100644 index 00000000..60d5e72b --- /dev/null +++ b/sdk/package.json @@ -0,0 +1,46 @@ +{ + "name": "@forge_yields/starknet_vault_kit_sdk", + "version": "0.1.0", + "description": "TypeScript SDK for Starknet Vault Kit - user and curator operations", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "test": "jest", + "lint": "eslint src/**/*.ts", + "clean": "rm -rf dist" + }, + "keywords": [ + "starknet", + "vault", + "defi", + "erc4626", + "sdk" + ], + "author": "Starknet Vault Kit Team", + "license": "MIT", + "dependencies": { + "dotenv": "^17.2.2", + "starknet": "7.6.4" + }, + "devDependencies": { + "@types/jest": "^29.0.0", + "@types/node": "^20.0.0", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "eslint": "^8.0.0", + "jest": "^29.0.0", + "ts-jest": "^29.0.0", + "ts-node": "^10.9.2", + "typescript": "^5.0.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/your-org/starknet-vault-kit.git", + "directory": "sdk" + } +} \ No newline at end of file diff --git a/sdk/src/abi/vault.json b/sdk/src/abi/vault.json new file mode 100644 index 00000000..131f20be --- /dev/null +++ b/sdk/src/abi/vault.json @@ -0,0 +1,1581 @@ +[ + { + "type": "impl", + "name": "UpgradeableImpl", + "interface_name": "openzeppelin_interfaces::upgrades::IUpgradeable" + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::upgrades::IUpgradeable", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "VaultMetadataImpl", + "interface_name": "openzeppelin_interfaces::token::erc20::IERC20Metadata" + }, + { + "type": "struct", + "name": "core::byte_array::ByteArray", + "members": [ + { + "name": "data", + "type": "core::array::Array::" + }, + { + "name": "pending_word", + "type": "core::felt252" + }, + { + "name": "pending_word_len", + "type": "core::integer::u32" + } + ] + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::token::erc20::IERC20Metadata", + "items": [ + { + "type": "function", + "name": "name", + "inputs": [], + "outputs": [ + { + "type": "core::byte_array::ByteArray" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "symbol", + "inputs": [], + "outputs": [ + { + "type": "core::byte_array::ByteArray" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "decimals", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u8" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "VaultImpl", + "interface_name": "vault::vault::interface::IVault" + }, + { + "type": "struct", + "name": "core::integer::u256", + "members": [ + { + "name": "low", + "type": "core::integer::u128" + }, + { + "name": "high", + "type": "core::integer::u128" + } + ] + }, + { + "type": "interface", + "name": "vault::vault::interface::IVault", + "items": [ + { + "type": "function", + "name": "register_redeem_request", + "inputs": [ + { + "name": "redeem_request", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "register_vault_allocator", + "inputs": [ + { + "name": "vault_allocator", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "request_redeem", + "inputs": [ + { + "name": "shares", + "type": "core::integer::u256" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "claim_redeem", + "inputs": [ + { + "name": "id", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "set_fees_config", + "inputs": [ + { + "name": "fees_recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "redeem_fees", + "type": "core::integer::u256" + }, + { + "name": "management_fees", + "type": "core::integer::u256" + }, + { + "name": "performance_fees", + "type": "core::integer::u256" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "set_report_delay", + "inputs": [ + { + "name": "report_delay", + "type": "core::integer::u64" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "set_max_delta", + "inputs": [ + { + "name": "max_delta", + "type": "core::integer::u256" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "report", + "inputs": [ + { + "name": "new_aum", + "type": "core::integer::u256" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "bring_liquidity", + "inputs": [ + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "pause", + "inputs": [], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "unpause", + "inputs": [], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "epoch", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "handled_epoch_len", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "buffer", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "aum", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "redeem_assets", + "inputs": [ + { + "name": "epoch", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "redeem_nominal", + "inputs": [ + { + "name": "epoch", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "fees_recipient", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "redeem_fees", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "management_fees", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "performance_fees", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "redeem_request", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "report_delay", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u64" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "vault_allocator", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "last_report_timestamp", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u64" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "max_delta", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "due_assets_from_id", + "inputs": [ + { + "name": "id", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "ERC4626Impl", + "interface_name": "openzeppelin_interfaces::token::erc4626::IERC4626" + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::token::erc4626::IERC4626", + "items": [ + { + "type": "function", + "name": "asset", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "total_assets", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "convert_to_shares", + "inputs": [ + { + "name": "assets", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "convert_to_assets", + "inputs": [ + { + "name": "shares", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "max_deposit", + "inputs": [ + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "preview_deposit", + "inputs": [ + { + "name": "assets", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "deposit", + "inputs": [ + { + "name": "assets", + "type": "core::integer::u256" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "max_mint", + "inputs": [ + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "preview_mint", + "inputs": [ + { + "name": "shares", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "mint", + "inputs": [ + { + "name": "shares", + "type": "core::integer::u256" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "max_withdraw", + "inputs": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "preview_withdraw", + "inputs": [ + { + "name": "assets", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "withdraw", + "inputs": [ + { + "name": "assets", + "type": "core::integer::u256" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "max_redeem", + "inputs": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "preview_redeem", + "inputs": [ + { + "name": "shares", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "redeem", + "inputs": [ + { + "name": "shares", + "type": "core::integer::u256" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "ERC20Impl", + "interface_name": "openzeppelin_interfaces::token::erc20::IERC20" + }, + { + "type": "enum", + "name": "core::bool", + "variants": [ + { + "name": "False", + "type": "()" + }, + { + "name": "True", + "type": "()" + } + ] + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::token::erc20::IERC20", + "items": [ + { + "type": "function", + "name": "total_supply", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "balance_of", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "allowance", + "inputs": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "spender", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "transfer", + "inputs": [ + { + "name": "recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "transfer_from", + "inputs": [ + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "approve", + "inputs": [ + { + "name": "spender", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "ERC20CamelOnlyImpl", + "interface_name": "openzeppelin_interfaces::token::erc20::IERC20CamelOnly" + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::token::erc20::IERC20CamelOnly", + "items": [ + { + "type": "function", + "name": "totalSupply", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "balanceOf", + "inputs": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "transferFrom", + "inputs": [ + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "AccessControlImpl", + "interface_name": "openzeppelin_interfaces::access::accesscontrol::IAccessControl" + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::access::accesscontrol::IAccessControl", + "items": [ + { + "type": "function", + "name": "has_role", + "inputs": [ + { + "name": "role", + "type": "core::felt252" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_role_admin", + "inputs": [ + { + "name": "role", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "grant_role", + "inputs": [ + { + "name": "role", + "type": "core::felt252" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "revoke_role", + "inputs": [ + { + "name": "role", + "type": "core::felt252" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "renounce_role", + "inputs": [ + { + "name": "role", + "type": "core::felt252" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "PausableImpl", + "interface_name": "openzeppelin_interfaces::security::pausable::IPausable" + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::security::pausable::IPausable", + "items": [ + { + "type": "function", + "name": "is_paused", + "inputs": [], + "outputs": [ + { + "type": "core::bool" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "name", + "type": "core::byte_array::ByteArray" + }, + { + "name": "symbol", + "type": "core::byte_array::ByteArray" + }, + { + "name": "underlying_asset", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "fees_recipient", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "redeem_fees", + "type": "core::integer::u256" + }, + { + "name": "management_fees", + "type": "core::integer::u256" + }, + { + "name": "performance_fees", + "type": "core::integer::u256" + }, + { + "name": "report_delay", + "type": "core::integer::u64" + }, + { + "name": "max_delta", + "type": "core::integer::u256" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::erc20::ERC20Component::Transfer", + "kind": "struct", + "members": [ + { + "name": "from", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "to", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "value", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::erc20::ERC20Component::Approval", + "kind": "struct", + "members": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "spender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "value", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::erc20::ERC20Component::Event", + "kind": "enum", + "variants": [ + { + "name": "Transfer", + "type": "openzeppelin_token::erc20::erc20::ERC20Component::Transfer", + "kind": "nested" + }, + { + "name": "Approval", + "type": "openzeppelin_token::erc20::erc20::ERC20Component::Approval", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::extensions::erc4626::erc4626::ERC4626Component::Deposit", + "kind": "struct", + "members": [ + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "assets", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "shares", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::extensions::erc4626::erc4626::ERC4626Component::Withdraw", + "kind": "struct", + "members": [ + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "assets", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "shares", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_token::erc20::extensions::erc4626::erc4626::ERC4626Component::Event", + "kind": "enum", + "variants": [ + { + "name": "Deposit", + "type": "openzeppelin_token::erc20::extensions::erc4626::erc4626::ERC4626Component::Deposit", + "kind": "nested" + }, + { + "name": "Withdraw", + "type": "openzeppelin_token::erc20::extensions::erc4626::erc4626::ERC4626Component::Withdraw", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_introspection::src5::SRC5Component::Event", + "kind": "enum", + "variants": [] + }, + { + "type": "event", + "name": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleGranted", + "kind": "struct", + "members": [ + { + "name": "role", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleGrantedWithDelay", + "kind": "struct", + "members": [ + { + "name": "role", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "delay", + "type": "core::integer::u64", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleRevoked", + "kind": "struct", + "members": [ + { + "name": "role", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "sender", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleAdminChanged", + "kind": "struct", + "members": [ + { + "name": "role", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "previous_admin_role", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "new_admin_role", + "type": "core::felt252", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::Event", + "kind": "enum", + "variants": [ + { + "name": "RoleGranted", + "type": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleGranted", + "kind": "nested" + }, + { + "name": "RoleGrantedWithDelay", + "type": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleGrantedWithDelay", + "kind": "nested" + }, + { + "name": "RoleRevoked", + "type": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleRevoked", + "kind": "nested" + }, + { + "name": "RoleAdminChanged", + "type": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::RoleAdminChanged", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_upgrades::upgradeable::UpgradeableComponent::Upgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_upgrades::upgradeable::UpgradeableComponent::Event", + "kind": "enum", + "variants": [ + { + "name": "Upgraded", + "type": "openzeppelin_upgrades::upgradeable::UpgradeableComponent::Upgraded", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_security::pausable::PausableComponent::Paused", + "kind": "struct", + "members": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_security::pausable::PausableComponent::Unpaused", + "kind": "struct", + "members": [ + { + "name": "account", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_security::pausable::PausableComponent::Event", + "kind": "enum", + "variants": [ + { + "name": "Paused", + "type": "openzeppelin_security::pausable::PausableComponent::Paused", + "kind": "nested" + }, + { + "name": "Unpaused", + "type": "openzeppelin_security::pausable::PausableComponent::Unpaused", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "vault::vault::vault::Vault::RedeemRequested", + "kind": "struct", + "members": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "shares", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "assets", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "id", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "epoch", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "vault::vault::vault::Vault::RedeemClaimed", + "kind": "struct", + "members": [ + { + "name": "receiver", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "redeem_request_nominal", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "assets", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "id", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "epoch", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "vault::vault::vault::Vault::Report", + "kind": "struct", + "members": [ + { + "name": "new_epoch", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "new_handled_epoch_len", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "total_supply", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "total_assets", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "management_fee_shares", + "type": "core::integer::u256", + "kind": "data" + }, + { + "name": "performance_fee_shares", + "type": "core::integer::u256", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "vault::vault::vault::Vault::Event", + "kind": "enum", + "variants": [ + { + "name": "ERC20Event", + "type": "openzeppelin_token::erc20::erc20::ERC20Component::Event", + "kind": "nested" + }, + { + "name": "ERC4626Event", + "type": "openzeppelin_token::erc20::extensions::erc4626::erc4626::ERC4626Component::Event", + "kind": "nested" + }, + { + "name": "SRC5Event", + "type": "openzeppelin_introspection::src5::SRC5Component::Event", + "kind": "nested" + }, + { + "name": "AccessControlEvent", + "type": "openzeppelin_access::accesscontrol::accesscontrol::AccessControlComponent::Event", + "kind": "nested" + }, + { + "name": "UpgradeableEvent", + "type": "openzeppelin_upgrades::upgradeable::UpgradeableComponent::Event", + "kind": "nested" + }, + { + "name": "PausableEvent", + "type": "openzeppelin_security::pausable::PausableComponent::Event", + "kind": "nested" + }, + { + "name": "RedeemRequested", + "type": "vault::vault::vault::Vault::RedeemRequested", + "kind": "nested" + }, + { + "name": "RedeemClaimed", + "type": "vault::vault::vault::Vault::RedeemClaimed", + "kind": "nested" + }, + { + "name": "Report", + "type": "vault::vault::vault::Vault::Report", + "kind": "nested" + } + ] + } +] \ No newline at end of file diff --git a/sdk/src/abi/vault_allocator.json b/sdk/src/abi/vault_allocator.json new file mode 100644 index 00000000..4506e322 --- /dev/null +++ b/sdk/src/abi/vault_allocator.json @@ -0,0 +1,294 @@ +[ + { + "type": "impl", + "name": "UpgradeableImpl", + "interface_name": "openzeppelin_interfaces::upgrades::IUpgradeable" + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::upgrades::IUpgradeable", + "items": [ + { + "type": "function", + "name": "upgrade", + "inputs": [ + { + "name": "new_class_hash", + "type": "core::starknet::class_hash::ClassHash" + } + ], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "VaultAllocatorImpl", + "interface_name": "vault_allocator::vault_allocator::interface::IVaultAllocator" + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "core::starknet::account::Call", + "members": [ + { + "name": "to", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "selector", + "type": "core::felt252" + }, + { + "name": "calldata", + "type": "core::array::Span::" + } + ] + }, + { + "type": "interface", + "name": "vault_allocator::vault_allocator::interface::IVaultAllocator", + "items": [ + { + "type": "function", + "name": "manager", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "set_manager", + "inputs": [ + { + "name": "manager", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "manage", + "inputs": [ + { + "name": "call", + "type": "core::starknet::account::Call" + } + ], + "outputs": [ + { + "type": "core::array::Span::" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "manage_multi", + "inputs": [ + { + "name": "calls", + "type": "core::array::Array::" + } + ], + "outputs": [ + { + "type": "core::array::Array::>" + } + ], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "OwnableImpl", + "interface_name": "openzeppelin_interfaces::access::ownable::IOwnable" + }, + { + "type": "interface", + "name": "openzeppelin_interfaces::access::ownable::IOwnable", + "items": [ + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "transfer_ownership", + "inputs": [ + { + "name": "new_owner", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "renounce_ownership", + "inputs": [], + "outputs": [], + "state_mutability": "external" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "owner", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_access::ownable::ownable::OwnableComponent::OwnershipTransferred", + "kind": "struct", + "members": [ + { + "name": "previous_owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "new_owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_access::ownable::ownable::OwnableComponent::OwnershipTransferStarted", + "kind": "struct", + "members": [ + { + "name": "previous_owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + }, + { + "name": "new_owner", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "key" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_access::ownable::ownable::OwnableComponent::Event", + "kind": "enum", + "variants": [ + { + "name": "OwnershipTransferred", + "type": "openzeppelin_access::ownable::ownable::OwnableComponent::OwnershipTransferred", + "kind": "nested" + }, + { + "name": "OwnershipTransferStarted", + "type": "openzeppelin_access::ownable::ownable::OwnableComponent::OwnershipTransferStarted", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_upgrades::upgradeable::UpgradeableComponent::Upgraded", + "kind": "struct", + "members": [ + { + "name": "class_hash", + "type": "core::starknet::class_hash::ClassHash", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "openzeppelin_upgrades::upgradeable::UpgradeableComponent::Event", + "kind": "enum", + "variants": [ + { + "name": "Upgraded", + "type": "openzeppelin_upgrades::upgradeable::UpgradeableComponent::Upgraded", + "kind": "nested" + } + ] + }, + { + "type": "event", + "name": "vault_allocator::vault_allocator::vault_allocator::VaultAllocator::CallPerformed", + "kind": "struct", + "members": [ + { + "name": "to", + "type": "core::starknet::contract_address::ContractAddress", + "kind": "data" + }, + { + "name": "selector", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "calldata", + "type": "core::array::Span::", + "kind": "data" + }, + { + "name": "result", + "type": "core::array::Span::", + "kind": "data" + } + ] + }, + { + "type": "event", + "name": "vault_allocator::vault_allocator::vault_allocator::VaultAllocator::Event", + "kind": "enum", + "variants": [ + { + "name": "OwnableEvent", + "type": "openzeppelin_access::ownable::ownable::OwnableComponent::Event", + "kind": "nested" + }, + { + "name": "UpgradeableEvent", + "type": "openzeppelin_upgrades::upgradeable::UpgradeableComponent::Event", + "kind": "nested" + }, + { + "name": "CallPerformed", + "type": "vault_allocator::vault_allocator::vault_allocator::VaultAllocator::CallPerformed", + "kind": "nested" + } + ] + } +] \ No newline at end of file diff --git a/sdk/src/curator/index.ts b/sdk/src/curator/index.ts new file mode 100644 index 00000000..1865fe0f --- /dev/null +++ b/sdk/src/curator/index.ts @@ -0,0 +1 @@ +export class VaultCuratorSDK {} diff --git a/sdk/src/index.ts b/sdk/src/index.ts new file mode 100644 index 00000000..3c737a92 --- /dev/null +++ b/sdk/src/index.ts @@ -0,0 +1,23 @@ +// Main SDK exports +export { VaultUserSDK } from "./user"; +export { VaultCuratorSDK } from "./curator"; +export { CalldataBuilder } from "./utils/calldata"; + +// Type exports +export type { + VaultConfig, + DepositParams, + MintParams, + RequestRedeemParams, + ClaimRedeemParams, + VaultState, + FeesConfig, + ReportParams, + BringLiquidityParams, + CalldataResult, + MultiCalldataResult, + Call +} from "./types"; + +// Re-export starknet types that users might need +export type { BigNumberish, Provider, Contract } from "starknet"; \ No newline at end of file diff --git a/sdk/src/types/index.ts b/sdk/src/types/index.ts new file mode 100644 index 00000000..8733e2fe --- /dev/null +++ b/sdk/src/types/index.ts @@ -0,0 +1,63 @@ +import { BigNumberish, Call } from "starknet"; + +export interface VaultConfig { + vaultAddress: string; +} + +export interface DepositParams { + assets: BigNumberish; + receiver: string; + includeApprove?: boolean; // Include approval transaction for underlying asset +} + +export interface MintParams { + shares: BigNumberish; + receiver: string; + includeApprove?: boolean; // Include approval transaction for underlying asset +} + +export interface RequestRedeemParams { + shares: BigNumberish; + receiver: string; + owner: string; +} + +export interface ClaimRedeemParams { + id: BigNumberish; +} + +export interface VaultState { + epoch: bigint; + handledEpochLen: bigint; + buffer: bigint; + aum: bigint; + totalSupply: bigint; + totalAssets: bigint; +} + +export interface FeesConfig { + feesRecipient: string; + redeemFees: bigint; + managementFees: bigint; + performanceFees: bigint; +} + +export interface ReportParams { + newAum: BigNumberish; +} + +export interface BringLiquidityParams { + amount: BigNumberish; +} + +export interface CalldataResult { + contractAddress: string; + entrypoint: string; + calldata: string[]; +} + +export interface MultiCalldataResult { + transactions: CalldataResult[]; +} + +export { Call }; diff --git a/sdk/src/user/index.ts b/sdk/src/user/index.ts new file mode 100644 index 00000000..1b1be9f1 --- /dev/null +++ b/sdk/src/user/index.ts @@ -0,0 +1,324 @@ +import { Provider, Contract, BigNumberish, uint256 } from "starknet"; +import { + VaultConfig, + DepositParams, + MintParams, + RequestRedeemParams, + ClaimRedeemParams, + CalldataResult, + MultiCalldataResult, + VaultState, +} from "../types"; +import { CalldataBuilder } from "../utils/calldata"; +import vaultAbi from "../abi/vault.json"; + +export class VaultUserSDK { + private vaultConfig: VaultConfig; + private provider?: Provider; + private vaultContract?: Contract; + private underlyingAssetAddress?: string; + + constructor(vaultConfig: VaultConfig, provider?: Provider) { + this.vaultConfig = vaultConfig; + this.provider = provider; + } + + /** + * Set the provider for reading vault state + */ + setProvider(provider: Provider): void { + this.provider = provider; + } + + /** + * Initialize vault contract for read operations + */ + private async initContract(): Promise { + if (!this.provider) { + throw new Error("Provider is required for contract operations"); + } + + if (!this.vaultContract) { + this.vaultContract = new Contract( + vaultAbi, + this.vaultConfig.vaultAddress, + this.provider + ); + + // Fetch underlying asset address if not cached + if (!this.underlyingAssetAddress) { + this.underlyingAssetAddress = await this.vaultContract.asset(); + } + } + } + + // === CALLDATA GENERATION METHODS === + + /** + * Generate calldata for deposit operation + * If includeApprove is true, returns multiple transactions including approval + */ + buildDepositCalldata( + params: DepositParams + ): CalldataResult | MultiCalldataResult { + const depositCalldata = CalldataBuilder.buildDepositCalldata( + this.vaultConfig.vaultAddress, + params.assets, + params.receiver + ); + + if (!params.includeApprove) { + return depositCalldata; + } + + if (!this.underlyingAssetAddress) { + throw new Error( + "Cannot build approval calldata: underlying asset address not loaded. Call a view method first or set includeApprove to false." + ); + } + + const approveCalldata = CalldataBuilder.buildApproveCalldata( + this.underlyingAssetAddress, + this.vaultConfig.vaultAddress, + params.assets + ); + + return { + transactions: [approveCalldata, depositCalldata], + }; + } + + /** + * Generate calldata for mint operation + * If includeApprove is true, returns multiple transactions including approval + */ + buildMintCalldata(params: MintParams): CalldataResult | MultiCalldataResult { + const mintCalldata = CalldataBuilder.buildMintCalldata( + this.vaultConfig.vaultAddress, + params.shares, + params.receiver + ); + + if (!params.includeApprove) { + return mintCalldata; + } + + if (!this.underlyingAssetAddress) { + throw new Error( + "Cannot build approval calldata: underlying asset address not loaded. Call a view method first or set includeApprove to false." + ); + } + + // For mint, we need to calculate required assets first + throw new Error( + "Cannot build mint with approval: requires async preview call. Use buildMintCalldataWithApproval() method instead." + ); + } + + /** + * Generate calldata for deposit operation with approval (async version) + * Loads underlying asset address and includes approval transaction + */ + async buildDepositCalldataWithApproval( + params: DepositParams + ): Promise { + if (!params.includeApprove) { + throw new Error( + "Use buildDepositCalldata() for deposit without approval" + ); + } + + await this.initContract(); + + const depositCalldata = CalldataBuilder.buildDepositCalldata( + this.vaultConfig.vaultAddress, + params.assets, + params.receiver + ); + + const approveCalldata = CalldataBuilder.buildApproveCalldata( + this.underlyingAssetAddress!, + this.vaultConfig.vaultAddress, + params.assets + ); + + return { + transactions: [approveCalldata, depositCalldata], + }; + } + + /** + * Generate calldata for mint operation with approval (async version) + * Calculates required assets and includes approval transaction + */ + async buildMintCalldataWithApproval( + params: MintParams + ): Promise { + if (!params.includeApprove) { + throw new Error("Use buildMintCalldata() for mint without approval"); + } + + await this.initContract(); + + // Calculate required assets for the mint + const requiredAssets = await this.previewMint(params.shares); + + const mintCalldata = CalldataBuilder.buildMintCalldata( + this.vaultConfig.vaultAddress, + params.shares, + params.receiver + ); + + const approveCalldata = CalldataBuilder.buildApproveCalldata( + this.underlyingAssetAddress!, + this.vaultConfig.vaultAddress, + requiredAssets + ); + + return { + transactions: [approveCalldata, mintCalldata], + }; + } + + /** + * Generate calldata for request redeem operation + */ + buildRequestRedeemCalldata(params: RequestRedeemParams): CalldataResult { + return CalldataBuilder.buildRequestRedeemCalldata( + this.vaultConfig.vaultAddress, + params.shares, + params.receiver, + params.owner + ); + } + + /** + * Generate calldata for claim redeem operation + */ + buildClaimRedeemCalldata(params: ClaimRedeemParams): CalldataResult { + return CalldataBuilder.buildClaimRedeemCalldata( + this.vaultConfig.vaultAddress, + params.id + ); + } + + // === VIEW METHODS === + + /** + * Get current vault state + */ + async getVaultState(): Promise { + await this.initContract(); + + const [epoch, handledEpochLen, buffer, aum, totalSupply, totalAssets] = + await Promise.all([ + this.vaultContract!.epoch(), + this.vaultContract!.handled_epoch_len(), + this.vaultContract!.buffer(), + this.vaultContract!.aum(), + this.vaultContract!.total_supply(), + this.vaultContract!.total_assets(), + ]); + + return { + epoch: BigInt(uint256.uint256ToBN(epoch).toString()), + handledEpochLen: BigInt(uint256.uint256ToBN(handledEpochLen).toString()), + buffer: BigInt(uint256.uint256ToBN(buffer).toString()), + aum: BigInt(uint256.uint256ToBN(aum).toString()), + totalSupply: BigInt(uint256.uint256ToBN(totalSupply).toString()), + totalAssets: BigInt(uint256.uint256ToBN(totalAssets).toString()), + }; + } + + /** + * Get user's share balance + */ + async getUserShareBalance(userAddress: string): Promise { + await this.initContract(); + const balance = await this.vaultContract!.balance_of(userAddress); + return BigInt(uint256.uint256ToBN(balance).toString()); + } + + /** + * Preview how many shares will be received for a deposit + */ + async previewDeposit(assets: BigNumberish): Promise { + await this.initContract(); + const shares = await this.vaultContract!.preview_deposit( + uint256.bnToUint256(assets.toString()) + ); + return BigInt(uint256.uint256ToBN(shares).toString()); + } + + /** + * Preview how many assets are needed to mint shares + */ + async previewMint(shares: BigNumberish): Promise { + await this.initContract(); + const assets = await this.vaultContract!.preview_mint( + uint256.bnToUint256(shares.toString()) + ); + return BigInt(uint256.uint256ToBN(assets).toString()); + } + + /** + * Preview how many assets will be received for redeeming shares + */ + async previewRedeem(shares: BigNumberish): Promise { + await this.initContract(); + const assets = await this.vaultContract!.preview_redeem( + uint256.bnToUint256(shares.toString()) + ); + return BigInt(uint256.uint256ToBN(assets).toString()); + } + + /** + * Get expected assets for a redemption NFT ID + */ + async getDueAssetsFromId(id: BigNumberish): Promise { + await this.initContract(); + const assets = await this.vaultContract!.due_assets_from_id( + uint256.bnToUint256(id.toString()) + ); + return BigInt(uint256.uint256ToBN(assets).toString()); + } + + /** + * Convert assets to shares + */ + async convertToShares(assets: BigNumberish): Promise { + await this.initContract(); + const shares = await this.vaultContract!.convert_to_shares( + uint256.bnToUint256(assets.toString()) + ); + return BigInt(uint256.uint256ToBN(shares).toString()); + } + + /** + * Convert shares to assets + */ + async convertToAssets(shares: BigNumberish): Promise { + await this.initContract(); + const assets = await this.vaultContract!.convert_to_assets( + uint256.bnToUint256(shares.toString()) + ); + return BigInt(uint256.uint256ToBN(assets).toString()); + } + + /** + * Get the underlying asset contract address + */ + async getUnderlyingAssetAddress(): Promise { + await this.initContract(); + return this.underlyingAssetAddress!; + } + + /** + * Get the redeem request NFT contract address + */ + async getRedeemRequestAddress(): Promise { + await this.initContract(); + const address = await this.vaultContract!.redeem_request(); + return address.toString(); + } +} diff --git a/sdk/src/utils/calldata.ts b/sdk/src/utils/calldata.ts new file mode 100644 index 00000000..77cfac2d --- /dev/null +++ b/sdk/src/utils/calldata.ts @@ -0,0 +1,226 @@ +import { BigNumberish, CallData, uint256 } from "starknet"; +import { CalldataResult } from "../types"; + +export class CalldataBuilder { + /** + * Build calldata for ERC20 approve operation + */ + static buildApproveCalldata( + tokenAddress: string, + spender: string, + amount: BigNumberish + ): CalldataResult { + const calldata = CallData.compile({ + spender: spender, + amount: uint256.bnToUint256(amount.toString()), + }); + + return { + contractAddress: tokenAddress, + entrypoint: "approve", + calldata, + }; + } + + /** + * Build calldata for deposit operation + */ + static buildDepositCalldata( + vaultAddress: string, + assets: BigNumberish, + receiver: string + ): CalldataResult { + const calldata = CallData.compile({ + assets: uint256.bnToUint256(assets.toString()), + receiver: receiver, + }); + + return { + contractAddress: vaultAddress, + entrypoint: "deposit", + calldata, + }; + } + + /** + * Build calldata for mint operation + */ + static buildMintCalldata( + vaultAddress: string, + shares: BigNumberish, + receiver: string + ): CalldataResult { + const calldata = CallData.compile({ + shares: uint256.bnToUint256(shares.toString()), + receiver: receiver, + }); + + return { + contractAddress: vaultAddress, + entrypoint: "mint", + calldata, + }; + } + + /** + * Build calldata for request_redeem operation + */ + static buildRequestRedeemCalldata( + vaultAddress: string, + shares: BigNumberish, + receiver: string, + owner: string + ): CalldataResult { + const calldata = CallData.compile({ + shares: uint256.bnToUint256(shares.toString()), + receiver: receiver, + owner: owner, + }); + + return { + contractAddress: vaultAddress, + entrypoint: "request_redeem", + calldata, + }; + } + + /** + * Build calldata for claim_redeem operation + */ + static buildClaimRedeemCalldata( + vaultAddress: string, + id: BigNumberish + ): CalldataResult { + const calldata = CallData.compile({ + id: uint256.bnToUint256(id.toString()), + }); + + return { + contractAddress: vaultAddress, + entrypoint: "claim_redeem", + calldata, + }; + } + + /** + * Build calldata for report operation (curator only) + */ + static buildReportCalldata( + vaultAddress: string, + newAum: BigNumberish + ): CalldataResult { + const calldata = CallData.compile({ + new_aum: uint256.bnToUint256(newAum.toString()), + }); + + return { + contractAddress: vaultAddress, + entrypoint: "report", + calldata, + }; + } + + /** + * Build calldata for bring_liquidity operation (curator only) + */ + static buildBringLiquidityCalldata( + vaultAddress: string, + amount: BigNumberish + ): CalldataResult { + const calldata = CallData.compile({ + amount: uint256.bnToUint256(amount.toString()), + }); + + return { + contractAddress: vaultAddress, + entrypoint: "bring_liquidity", + calldata, + }; + } + + /** + * Build calldata for pause operation (curator only) + */ + static buildPauseCalldata(vaultAddress: string): CalldataResult { + const calldata = CallData.compile({}); + + return { + contractAddress: vaultAddress, + entrypoint: "pause", + calldata, + }; + } + + /** + * Build calldata for unpause operation (curator only) + */ + static buildUnpauseCalldata(vaultAddress: string): CalldataResult { + const calldata = CallData.compile({}); + + return { + contractAddress: vaultAddress, + entrypoint: "unpause", + calldata, + }; + } + + /** + * Build calldata for set_fees_config operation (curator only) + */ + static buildSetFeesConfigCalldata( + vaultAddress: string, + feesRecipient: string, + redeemFees: BigNumberish, + managementFees: BigNumberish, + performanceFees: BigNumberish + ): CalldataResult { + const calldata = CallData.compile({ + fees_recipient: feesRecipient, + redeem_fees: uint256.bnToUint256(redeemFees.toString()), + management_fees: uint256.bnToUint256(managementFees.toString()), + performance_fees: uint256.bnToUint256(performanceFees.toString()), + }); + + return { + contractAddress: vaultAddress, + entrypoint: "set_fees_config", + calldata, + }; + } + + /** + * Build calldata for set_report_delay operation (curator only) + */ + static buildSetReportDelayCalldata( + vaultAddress: string, + reportDelay: BigNumberish + ): CalldataResult { + const calldata = CallData.compile({ + report_delay: reportDelay.toString(), + }); + + return { + contractAddress: vaultAddress, + entrypoint: "set_report_delay", + calldata, + }; + } + + /** + * Build calldata for set_max_delta operation (curator only) + */ + static buildSetMaxDeltaCalldata( + vaultAddress: string, + maxDelta: BigNumberish + ): CalldataResult { + const calldata = CallData.compile({ + max_delta: uint256.bnToUint256(maxDelta.toString()), + }); + + return { + contractAddress: vaultAddress, + entrypoint: "set_max_delta", + calldata, + }; + } +} \ No newline at end of file diff --git a/sdk/tsconfig.json b/sdk/tsconfig.json new file mode 100644 index 00000000..8702215c --- /dev/null +++ b/sdk/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020"], + "module": "commonjs", + "declaration": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist", + "**/*.test.ts" + ] +} \ No newline at end of file diff --git a/sdk/yarn.lock b/sdk/yarn.lock new file mode 100644 index 00000000..b80ab936 --- /dev/null +++ b/sdk/yarn.lock @@ -0,0 +1,2984 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + dependencies: + "@babel/helper-validator-identifier" "^7.27.1" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/compat-data@^7.27.2": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.4.tgz#96fdf1af1b8859c8474ab39c295312bfb7c24b04" + integrity sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.4.tgz#12a550b8794452df4c8b084f95003bce1742d496" + integrity sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-module-transforms" "^7.28.3" + "@babel/helpers" "^7.28.4" + "@babel/parser" "^7.28.4" + "@babel/template" "^7.27.2" + "@babel/traverse" "^7.28.4" + "@babel/types" "^7.28.4" + "@jridgewell/remapping" "^2.3.5" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.28.3", "@babel/generator@^7.7.2": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.3.tgz#9626c1741c650cbac39121694a0f2d7451b8ef3e" + integrity sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw== + dependencies: + "@babel/parser" "^7.28.3" + "@babel/types" "^7.28.2" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" + integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== + dependencies: + "@babel/compat-data" "^7.27.2" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + +"@babel/helper-module-imports@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" + integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-module-transforms@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz#a2b37d3da3b2344fe085dab234426f2b9a2fa5f6" + integrity sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@babel/traverse" "^7.28.3" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.8.0": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" + integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" + integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helpers@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.4.tgz#fe07274742e95bdf7cf1443593eeb8926ab63827" + integrity sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w== + dependencies: + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.4" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.27.2", "@babel/parser@^7.28.3", "@babel/parser@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.4.tgz#da25d4643532890932cc03f7705fe19637e03fa8" + integrity sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg== + dependencies: + "@babel/types" "^7.28.4" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz#34c017d54496f9b11b61474e7ea3dfd5563ffe07" + integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz#2f9beb5eff30fa507c5532d107daac7b888fa34c" + integrity sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz#5147d29066a793450f220c63fa3a9431b7e6dd18" + integrity sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/template@^7.27.2", "@babel/template@^7.3.3": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" + integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" + +"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.4.tgz#8d456101b96ab175d487249f60680221692b958b" + integrity sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.4" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.4" + debug "^4.3.1" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.28.2", "@babel/types@^7.28.4", "@babel/types@^7.3.3": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.4.tgz#0a4e618f4c60a7cd6c11cb2d48060e4dbe38ac3a" + integrity sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz#7308df158e064f0dd8b8fdb58aa14fa2a7f913b3" + integrity sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": + version "4.12.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.57.1": + version "8.57.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" + integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== + +"@humanwhocodes/config-array@^0.13.0": + version "0.13.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748" + integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw== + dependencies: + "@humanwhocodes/object-schema" "^2.0.3" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@noble/curves@1.7.0", "@noble/curves@~1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.7.0.tgz#0512360622439256df892f21d25b388f52505e45" + integrity sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw== + dependencies: + "@noble/hashes" "1.6.0" + +"@noble/hashes@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.6.0.tgz#d4bfb516ad6e7b5111c216a5cc7075f4cf19e6c5" + integrity sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ== + +"@noble/hashes@~1.6.0": + version "1.6.1" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.6.1.tgz#df6e5943edcea504bac61395926d6fd67869a0d5" + integrity sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@scure/base@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.2.1.tgz#dd0b2a533063ca612c17aa9ad26424a2ff5aa865" + integrity sha512-DGmGtC8Tt63J5GfHgfl5CuAXh96VF/LD8K9Hr/Gv0J2lAoRGlPOMpqMpMbCTOoOJMZCk2Xt+DskdDyn6dEFdzQ== + +"@scure/starknet@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@scure/starknet/-/starknet-1.1.0.tgz#d1902e053d98196e161b9b2c3996b20999094e7a" + integrity sha512-83g3M6Ix2qRsPN4wqLDqiRZ2GBNbjVWfboJE/9UjfG+MHr6oDSu/CWgy8hsBSJejr09DkkL+l0Ze4KVrlCIdtQ== + dependencies: + "@noble/curves" "~1.7.0" + "@noble/hashes" "~1.6.0" + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@starknet-io/starknet-types-07@npm:@starknet-io/types-js@~0.7.10": + version "0.7.10" + resolved "https://registry.yarnpkg.com/@starknet-io/types-js/-/types-js-0.7.10.tgz#d21dc973d0cd04d7b6293ce461f2f06a5873c760" + integrity sha512-1VtCqX4AHWJlRRSYGSn+4X1mqolI1Tdq62IwzoU2vUuEE72S1OlEeGhpvd6XsdqXcfHmVzYfj8k1XtKBQqwo9w== + +"@starknet-io/starknet-types-08@npm:@starknet-io/types-js@~0.8.4": + version "0.8.4" + resolved "https://registry.yarnpkg.com/@starknet-io/types-js/-/types-js-0.8.4.tgz#bbc07422e89cb5bac45da28e8457f0f17535950d" + integrity sha512-0RZ3TZHcLsUTQaq1JhDSCM8chnzO4/XNsSCozwDET64JK5bjFDIf2ZUkta+tl5Nlbf4usoU7uZiDI/Q57kt2SQ== + +"@tsconfig/node10@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" + integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74" + integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== + dependencies: + "@babel/types" "^7.28.2" + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^29.0.0": + version "29.5.14" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" + integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + +"@types/json-schema@^7.0.12": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/node@*": + version "24.5.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-24.5.2.tgz#52ceb83f50fe0fcfdfbd2a9fab6db2e9e7ef6446" + integrity sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ== + dependencies: + undici-types "~7.12.0" + +"@types/node@^20.0.0": + version "20.19.17" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.17.tgz#41b52697373aef8a43b3b92f33b43f329b2d674b" + integrity sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ== + dependencies: + undici-types "~6.21.0" + +"@types/semver@^7.5.0": + version "7.7.1" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.7.1.tgz#3ce3af1a5524ef327d2da9e4fd8b6d95c8d70528" + integrity sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA== + +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^6.0.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz#30830c1ca81fd5f3c2714e524c4303e0194f9cd3" + integrity sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA== + dependencies: + "@eslint-community/regexpp" "^4.5.1" + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/type-utils" "6.21.0" + "@typescript-eslint/utils" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.4" + natural-compare "^1.4.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/parser@^6.0.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.21.0.tgz#af8fcf66feee2edc86bc5d1cf45e33b0630bf35b" + integrity sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ== + dependencies: + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/typescript-estree" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz#ea8a9bfc8f1504a6ac5d59a6df308d3a0630a2b1" + integrity sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg== + dependencies: + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + +"@typescript-eslint/type-utils@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz#6473281cfed4dacabe8004e8521cee0bd9d4c01e" + integrity sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag== + dependencies: + "@typescript-eslint/typescript-estree" "6.21.0" + "@typescript-eslint/utils" "6.21.0" + debug "^4.3.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/types@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.21.0.tgz#205724c5123a8fef7ecd195075fa6e85bac3436d" + integrity sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg== + +"@typescript-eslint/typescript-estree@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz#c47ae7901db3b8bddc3ecd73daff2d0895688c46" + integrity sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ== + dependencies: + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + minimatch "9.0.3" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/utils@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.21.0.tgz#4714e7a6b39e773c1c8e97ec587f520840cd8134" + integrity sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/typescript-estree" "6.21.0" + semver "^7.5.4" + +"@typescript-eslint/visitor-keys@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz#87a99d077aa507e20e238b11d56cc26ade45fe47" + integrity sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A== + dependencies: + "@typescript-eslint/types" "6.21.0" + eslint-visitor-keys "^3.4.1" + +"@ungap/structured-clone@^1.2.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== + +abi-wan-kanabi@2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/abi-wan-kanabi/-/abi-wan-kanabi-2.2.4.tgz#47ebbafbb7f8df81773efbdcca60cdda8008c821" + integrity sha512-0aA81FScmJCPX+8UvkXLki3X1+yPQuWxEkqXBVKltgPAK79J+NB+Lp5DouMXa7L6f+zcRlIA/6XO7BN/q9fnvg== + dependencies: + ansicolors "^0.3.2" + cardinal "^2.1.1" + fs-extra "^10.0.0" + yargs "^17.7.2" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.1.1: + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" + +acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0: + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +ansicolors@^0.3.2, ansicolors@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" + integrity sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg== + +anymatch@^3.0.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz#20730d6cdc7dda5d89401cab10ac6a32067acde6" + integrity sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +baseline-browser-mapping@^2.8.3: + version "2.8.5" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.5.tgz#3147fe6b01a0c49ce1952daebcfc2057fc43fedb" + integrity sha512-TiU4qUT9jdCuh4aVOG7H1QozyeI2sZRqoRPdqBIaslfNt4WUSanRBueAwl2x5jt4rXBMim3lIN2x6yT8PDi24Q== + +brace-expansion@^1.1.7: + version "1.1.12" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" + integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" + integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.24.0: + version "4.26.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.26.2.tgz#7db3b3577ec97f1140a52db4936654911078cef3" + integrity sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A== + dependencies: + baseline-browser-mapping "^2.8.3" + caniuse-lite "^1.0.30001741" + electron-to-chromium "^1.5.218" + node-releases "^2.0.21" + update-browserslist-db "^1.1.3" + +bs-logger@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001741: + version "1.0.30001743" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz#50ff91a991220a1ee2df5af00650dd5c308ea7cd" + integrity sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw== + +cardinal@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/cardinal/-/cardinal-2.1.1.tgz#7cc1055d822d212954d07b085dea251cc7bc5505" + integrity sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw== + dependencies: + ansicolors "~0.3.2" + redeyed "~2.1.0" + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cjs-module-lexer@^1.0.0: + version "1.4.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" + integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +dedent@^1.0.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.7.0.tgz#c1f9445335f0175a96587be245a282ff451446ca" + integrity sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dotenv@^17.2.2: + version "17.2.2" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-17.2.2.tgz#4010cfe1c2be4fc0f46fd3d951afb424bc067ac6" + integrity sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q== + +electron-to-chromium@^1.5.218: + version "1.5.221" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.221.tgz#bd98014b2a247701c4ebd713080448d539545d79" + integrity sha512-/1hFJ39wkW01ogqSyYoA4goOXOtMRy6B+yvA1u42nnsEGtHzIzmk93aPISumVQeblj47JUHLC9coCjUxb1EvtQ== + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +error-ex@^1.3.1: + version "1.3.4" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.4.tgz#b3a8d8bb6f92eecc1629e3e27d3c8607a8a32414" + integrity sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ== + dependencies: + is-arrayish "^0.2.1" + +escalade@^3.1.1, escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8.0.0: + version "8.57.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" + integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.1" + "@humanwhocodes/config-array" "^0.13.0" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esprima@^4.0.0, esprima@~4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.2: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^29.0.0, expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.9: + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.8" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.19.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" + integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.3.3" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" + integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== + +fs-extra@^10.0.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +handlebars@^4.7.8: + version "4.7.8" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" + integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.2" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +ignore@^5.2.0, ignore@^5.2.4: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +import-fresh@^3.2.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-instrument@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz#cb4535162b5784aa623cee21a7252cf2c807ac93" + integrity sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + natural-compare "^1.4.0" + pretty-format "^29.7.0" + semver "^7.5.3" + +jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.0.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonfile@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62" + integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lossless-json@^4.0.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lossless-json/-/lossless-json-4.2.0.tgz#69841f29b673989980bfc0ce6e2b1db33533ce34" + integrity sha512-bsHH3x+7acZfqokfn9Ks/ej96yF/z6oGGw1aBmXesq4r3fAjhdG4uYuqzDgZMk5g1CZUd5w3kwwIp9K1LOYUiA== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-error@^1.1.1, make-error@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4, micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@9.0.3: + version "9.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.5: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.21: + version "2.0.21" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.21.tgz#f59b018bc0048044be2d4c4c04e4c8b18160894c" + integrity sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw== + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +pako@^2.0.4: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" + integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.7" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" + integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +pure-rand@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +redeyed@~2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/redeyed/-/redeyed-2.1.1.tgz#8984b5815d99cb220469c99eeeffe38913e6cc0b" + integrity sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ== + dependencies: + esprima "~4.0.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" + integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== + +resolve@^1.20.0: + version "1.22.10" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + dependencies: + is-core-module "^2.16.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.5.3, semver@^7.5.4, semver@^7.7.2: + version "7.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +starknet@7.6.4: + version "7.6.4" + resolved "https://registry.yarnpkg.com/starknet/-/starknet-7.6.4.tgz#8ca2f3decbecde6316e7561b39f6a296a7fa33b5" + integrity sha512-FB20IaLCDbh/XomkB+19f5jmNxG+RzNdRO7QUhm7nfH81UPIt2C/MyWAlHCYkbv2wznSEb73wpxbp9tytokTgQ== + dependencies: + "@noble/curves" "1.7.0" + "@noble/hashes" "1.6.0" + "@scure/base" "1.2.1" + "@scure/starknet" "1.1.0" + "@starknet-io/starknet-types-07" "npm:@starknet-io/types-js@~0.7.10" + "@starknet-io/starknet-types-08" "npm:@starknet-io/types-js@~0.8.4" + abi-wan-kanabi "2.2.4" + lossless-json "^4.0.1" + pako "^2.0.4" + ts-mixer "^6.0.3" + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +ts-api-utils@^1.0.1: + version "1.4.3" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" + integrity sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw== + +ts-jest@^29.0.0: + version "29.4.3" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.4.3.tgz#23264489bebb5b3e2c7966dbf6788e960f244f7c" + integrity sha512-KTWbK2Wot8VXargsLoxhSoEQ9OyMdzQXQoUDeIulWu2Tf7gghuBHeg+agZqVLdTOHhQHVKAaeuctBDRkhWE7hg== + dependencies: + bs-logger "^0.2.6" + fast-json-stable-stringify "^2.1.0" + handlebars "^4.7.8" + json5 "^2.2.3" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.7.2" + type-fest "^4.41.0" + yargs-parser "^21.1.1" + +ts-mixer@^6.0.3: + version "6.0.4" + resolved "https://registry.yarnpkg.com/ts-mixer/-/ts-mixer-6.0.4.tgz#1da39ceabc09d947a82140d9f09db0f84919ca28" + integrity sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA== + +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^4.41.0: + version "4.41.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" + integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== + +typescript@^5.0.0: + version "5.9.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6" + integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A== + +uglify-js@^3.1.4: + version "3.19.3" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" + integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +undici-types@~7.12.0: + version "7.12.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.12.0.tgz#15c5c7475c2a3ba30659529f5cdb4674b622fafb" + integrity sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ== + +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + +update-browserslist-db@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" + integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.3.1, yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==