diff --git a/.github/workflows/publish-protocol-core.yaml b/.github/workflows/publish-protocol-core.yaml new file mode 100644 index 0000000..71521c8 --- /dev/null +++ b/.github/workflows/publish-protocol-core.yaml @@ -0,0 +1,327 @@ +name: Publish Protocol Core + +# SECURITY: Uses Trusted Publishers (OIDC) — no long-lived tokens +# Requires NPM trusted publisher config: https://docs.npmjs.com/trusted-publishers +# Package: @quickswap-defi/protocol-core + +on: + workflow_dispatch: + inputs: + bump: + description: 'Version bump type' + required: true + type: choice + options: + - patch + - minor + - major + - prerelease + default: 'patch' + dry_run: + description: 'Dry run (test without publishing)' + required: false + type: boolean + default: false + + push: + tags: + - 'protocol-core/v*.*.*' + +permissions: + contents: read + +jobs: + # ============================================ + # JOB 1: Security Audit & Validation + # ============================================ + security-audit: + name: Security Audit + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + fetch-depth: 0 + + - name: Install pnpm + uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 + with: + version: 9 + + - name: Setup Node.js 20 LTS + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + with: + node-version: '20' + cache: 'pnpm' + + - name: Verify package integrity + run: | + echo "Verifying packages/protocol-core/package.json integrity..." + node -e "JSON.parse(require('fs').readFileSync('packages/protocol-core/package.json'))" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run security audit + run: pnpm audit --audit-level=high + + - name: Run linter + run: pnpm --filter @quickswap-defi/protocol-core lint + + # ============================================ + # JOB 2: Build & Test + # ============================================ + build-and-test: + name: Build & Test + runs-on: ubuntu-latest + needs: security-audit + timeout-minutes: 10 + + steps: + - name: Checkout repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Install pnpm + uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 + with: + version: 9 + + - name: Setup Node.js 20 LTS + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + with: + node-version: '20' + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run tests + run: pnpm --filter @quickswap-defi/protocol-core test + + - name: Validate addresses + run: pnpm --filter @quickswap-defi/protocol-core validate:addresses + + - name: Build package + run: pnpm --filter @quickswap-defi/protocol-core build + + - name: Validate build output + run: | + if [ ! -f "packages/protocol-core/dist/index.js" ] && [ ! -f "packages/protocol-core/dist/index.mjs" ]; then + echo "Build failed: packages/protocol-core/dist/ not found or empty" + exit 1 + fi + echo "Build validation passed" + + - name: Upload build artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: protocol-core-build + path: packages/protocol-core/dist/ + retention-days: 7 + + # ============================================ + # JOB 3: Version Bump (if manual trigger) + # ============================================ + version-bump: + name: Version Bump + runs-on: ubuntu-latest + needs: build-and-test + if: github.event_name == 'workflow_dispatch' && !inputs.dry_run + timeout-minutes: 5 + permissions: + contents: write + + outputs: + new_version: ${{ steps.bump.outputs.new_version }} + + steps: + - name: Verify branch is main + run: | + if [ "${{ github.ref }}" != "refs/heads/main" ]; then + echo "Error: Can only publish from main branch" + exit 1 + fi + + - name: Checkout repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + fetch-depth: 0 + + - name: Install pnpm + uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 + with: + version: 9 + + - name: Setup Node.js + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + with: + node-version: '20' + cache: 'pnpm' + + - name: Configure Git + run: | + git config user.name "quickswap-bot" + git config user.email "bot@quickswap.exchange" + + - name: Bump version + id: bump + run: | + cd packages/protocol-core + echo "Current version: $(node -p "require('./package.json').version")" + pnpm version ${{ inputs.bump }} --no-git-tag-version --no-commit-hooks + NEW_VERSION=$(node -p "require('./package.json').version") + echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT + echo "Version bumped to: $NEW_VERSION" + + - name: Commit and push version + run: | + git add packages/protocol-core/package.json + git diff --staged --quiet || git commit -m "chore(release): protocol-core v$(cd packages/protocol-core && node -p "require('./package.json').version") [skip ci]" + git tag "protocol-core/v$(cd packages/protocol-core && node -p "require('./package.json').version")" + git push origin HEAD --follow-tags + + # ============================================ + # JOB 4: Publish to NPM (Secure OIDC) + # ============================================ + publish-npm: + name: Publish to NPM + runs-on: ubuntu-latest + needs: [build-and-test, version-bump] + if: | + always() && + needs.build-and-test.result == 'success' && + ( + (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/protocol-core/v')) || + (github.event_name == 'workflow_dispatch' && !inputs.dry_run && needs.version-bump.result == 'success') + ) + timeout-minutes: 10 + permissions: + contents: write + id-token: write + + environment: + name: npm-protocol-core + url: https://www.npmjs.com/package/@quickswap-defi/protocol-core + + steps: + - name: Checkout repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + fetch-depth: 0 + + - name: Fetch version bump + if: github.event_name == 'workflow_dispatch' + run: git fetch origin --tags + + - name: Verify tag is on main branch + if: github.event_name == 'push' + run: | + git fetch origin main + if ! git merge-base --is-ancestor $GITHUB_SHA origin/main; then + echo "Error: Tag does not point to a commit on main branch" + exit 1 + fi + + - name: Install pnpm + uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 + with: + version: 9 + + - name: Setup Node.js with NPM registry + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + with: + node-version: '24' + registry-url: 'https://registry.npmjs.org/' + + - name: Update npm (required for OIDC) + run: | + npm install -g npm@11.5.1 + npm --version + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build package + run: pnpm --filter @quickswap-defi/protocol-core build + + - name: Pre-publish verification + run: | + cd packages/protocol-core + echo "Package details:" + npm pack --dry-run + echo "" + npm publish --dry-run + + - name: Check if version already exists + id: check_version + run: | + cd packages/protocol-core + PACKAGE_VERSION=$(node -p "require('./package.json').version") + echo "Checking if version $PACKAGE_VERSION already exists..." + if npm view "@quickswap-defi/protocol-core@$PACKAGE_VERSION" version > /dev/null 2>&1; then + echo "exists=true" >> $GITHUB_OUTPUT + echo "version=$PACKAGE_VERSION" >> $GITHUB_OUTPUT + else + echo "exists=false" >> $GITHUB_OUTPUT + echo "version=$PACKAGE_VERSION" >> $GITHUB_OUTPUT + fi + + - name: Publish to NPM + if: steps.check_version.outputs.exists == 'false' + run: cd packages/protocol-core && npm publish --access public --provenance + + - name: Skip publish (version exists) + if: steps.check_version.outputs.exists == 'true' + run: | + echo "Skipping: version ${{ steps.check_version.outputs.version }} already exists" + + - name: Get published version + id: version + run: | + VERSION=$(cd packages/protocol-core && node -p "require('./package.json').version") + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release create "protocol-core/v${{ steps.version.outputs.version }}" \ + --title "@quickswap-defi/protocol-core v${{ steps.version.outputs.version }}" \ + --notes "## NPM Package Published + **Package:** \`@quickswap-defi/protocol-core@${{ steps.version.outputs.version }}\` + **NPM:** https://www.npmjs.com/package/@quickswap-defi/protocol-core + ### Installation + \`\`\`bash + npm install @quickswap-defi/protocol-core@${{ steps.version.outputs.version }} + \`\`\`" \ + --generate-notes \ + ${{ contains(steps.version.outputs.version, '-') && '--prerelease' || '' }} + + # ============================================ + # JOB 5: Post-Publish Verification + # ============================================ + verify-publish: + name: Verify Publication + runs-on: ubuntu-latest + needs: publish-npm + if: needs.publish-npm.result == 'success' + timeout-minutes: 5 + + steps: + - name: Wait for NPM propagation + run: sleep 30 + + - name: Verify package + run: | + npm view @quickswap-defi/protocol-core version + npm view @quickswap-defi/protocol-core --json | jq '.provenance' + + - name: Test installation + run: | + mkdir -p /tmp/test-install && cd /tmp/test-install + npm init -y + npm install @quickswap-defi/protocol-core --ignore-scripts + echo "Package installation successful" diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish-sdk.yaml similarity index 59% rename from .github/workflows/publish.yaml rename to .github/workflows/publish-sdk.yaml index b3c8b3f..2d0bfce 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish-sdk.yaml @@ -1,11 +1,10 @@ -name: Secure NPM Publish +name: Publish SDK -# āš ļø SECURITY: This workflow uses Trusted Publishers (OIDC) -# No long-lived tokens needed - authentication via GitHub identity -# Requires NPM organization setup: https://docs.npmjs.com/trusted-publishers +# SECURITY: Uses Trusted Publishers (OIDC) — no long-lived tokens +# Requires NPM trusted publisher config: https://docs.npmjs.com/trusted-publishers +# Package: @quickswap-defi/sdk on: - # Manual trigger with version bump options workflow_dispatch: inputs: bump: @@ -24,77 +23,73 @@ on: type: boolean default: false - # Automatic publish on release tags push: tags: - - 'v*.*.*' + - 'sdk/v*.*.*' permissions: - contents: write - id-token: write - pull-requests: read + contents: read jobs: # ============================================ # JOB 1: Security Audit & Validation # ============================================ security-audit: - name: šŸ”’ Security Audit + name: Security Audit runs-on: ubuntu-latest timeout-minutes: 10 steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: fetch-depth: 0 - name: Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 with: version: 9 - name: Setup Node.js 20 LTS - uses: actions/setup-node@v4 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: '20' cache: 'pnpm' - name: Verify package integrity run: | - echo "šŸ“¦ Verifying package.json integrity..." - node -e "JSON.parse(require('fs').readFileSync('package.json'))" + echo "Verifying packages/sdk/package.json integrity..." + node -e "JSON.parse(require('fs').readFileSync('packages/sdk/package.json'))" - name: Install dependencies run: pnpm install --frozen-lockfile - name: Run security audit - run: pnpm audit --audit-level=moderate - continue-on-error: true + run: pnpm audit --audit-level=high - name: Run linter - run: pnpm run lint + run: pnpm --filter @quickswap-defi/sdk lint # ============================================ # JOB 2: Build & Test # ============================================ build-and-test: - name: šŸ—ļø Build & Test + name: Build & Test runs-on: ubuntu-latest needs: security-audit timeout-minutes: 10 steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 with: version: 9 - name: Setup Node.js 20 LTS - uses: actions/setup-node@v4 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: '20' cache: 'pnpm' @@ -103,54 +98,62 @@ jobs: run: pnpm install --frozen-lockfile - name: Run tests - run: pnpm run test - continue-on-error: true + run: pnpm --filter @quickswap-defi/sdk test - name: Build package - run: pnpm run build + run: pnpm --filter @quickswap-defi/sdk build - name: Validate build output run: | - if [ ! -f "dist/index.js" ] && [ ! -f "dist/index.mjs" ]; then - echo "āŒ Build failed: dist/ not found or empty" + if [ ! -f "packages/sdk/dist/index.js" ] && [ ! -f "packages/sdk/dist/index.mjs" ]; then + echo "Build failed: packages/sdk/dist/ not found or empty" exit 1 fi - echo "āœ… Build validation passed" + echo "Build validation passed" - name: Upload build artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: sdk-build - path: dist/ + path: packages/sdk/dist/ retention-days: 7 # ============================================ # JOB 3: Version Bump (if manual trigger) # ============================================ version-bump: - name: šŸ“ Version Bump + name: Version Bump runs-on: ubuntu-latest needs: build-and-test if: github.event_name == 'workflow_dispatch' && !inputs.dry_run timeout-minutes: 5 + permissions: + contents: write outputs: new_version: ${{ steps.bump.outputs.new_version }} steps: + - name: Verify branch is main + run: | + if [ "${{ github.ref }}" != "refs/heads/main" ]; then + echo "Error: Can only publish from main branch" + exit 1 + fi + - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: token: ${{ secrets.GITHUB_TOKEN }} fetch-depth: 0 - name: Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 with: version: 9 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: '20' cache: 'pnpm' @@ -163,83 +166,88 @@ jobs: - name: Bump version id: bump run: | + cd packages/sdk echo "Current version: $(node -p "require('./package.json').version")" pnpm version ${{ inputs.bump }} --no-git-tag-version --no-commit-hooks NEW_VERSION=$(node -p "require('./package.json').version") echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT - echo "āœ… Version bumped to: $NEW_VERSION" + echo "Version bumped to: $NEW_VERSION" - name: Commit and push version run: | - git add package.json pnpm-lock.yaml 2>/dev/null || true - git add package.json - git diff --staged --quiet || git commit -m "chore(release): v$(node -p "require('./package.json').version") [skip ci]" - git tag "v$(node -p "require('./package.json').version")" + git add packages/sdk/package.json + git diff --staged --quiet || git commit -m "chore(release): sdk v$(cd packages/sdk && node -p "require('./package.json').version") [skip ci]" + git tag "sdk/v$(cd packages/sdk && node -p "require('./package.json').version")" git push origin HEAD --follow-tags # ============================================ # JOB 4: Publish to NPM (Secure OIDC) # ============================================ publish-npm: - name: šŸš€ Publish to NPM + name: Publish to NPM runs-on: ubuntu-latest needs: [build-and-test, version-bump] if: | always() && needs.build-and-test.result == 'success' && ( - (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) || + (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/sdk/v')) || (github.event_name == 'workflow_dispatch' && !inputs.dry_run && needs.version-bump.result == 'success') ) timeout-minutes: 10 + permissions: + contents: write + id-token: write environment: - name: npm-production + name: npm-sdk url: https://www.npmjs.com/package/@quickswap-defi/sdk steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: fetch-depth: 0 - - name: Fetch latest (after version bump) + - name: Fetch version bump if: github.event_name == 'workflow_dispatch' + run: git fetch origin --tags + + - name: Verify tag is on main branch + if: github.event_name == 'push' run: | - git fetch origin --tags --force - git reset --hard origin/${{ github.ref_name }} + git fetch origin main + if ! git merge-base --is-ancestor $GITHUB_SHA origin/main; then + echo "Error: Tag does not point to a commit on main branch" + exit 1 + fi + + - name: Install pnpm + uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 + with: + version: 9 - name: Setup Node.js with NPM registry - uses: actions/setup-node@v4 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: '24' registry-url: 'https://registry.npmjs.org/' - name: Update npm (required for OIDC) run: | - npm install -g npm@latest + npm install -g npm@11.5.1 npm --version - - name: Download build artifact - uses: actions/download-artifact@v4 - with: - name: sdk-build - path: dist/ - - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9 - - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Rebuild (ensure consistency) - run: pnpm run build + - name: Build package + run: pnpm --filter @quickswap-defi/sdk build - name: Pre-publish verification run: | - echo "šŸ“‹ Package details:" + cd packages/sdk + echo "Package details:" npm pack --dry-run echo "" npm publish --dry-run @@ -247,6 +255,7 @@ jobs: - name: Check if version already exists id: check_version run: | + cd packages/sdk PACKAGE_VERSION=$(node -p "require('./package.json').version") echo "Checking if version $PACKAGE_VERSION already exists..." if npm view "@quickswap-defi/sdk@$PACKAGE_VERSION" version > /dev/null 2>&1; then @@ -259,41 +268,40 @@ jobs: - name: Publish to NPM if: steps.check_version.outputs.exists == 'false' - run: npm publish --access public --provenance + run: cd packages/sdk && npm publish --access public --provenance - name: Skip publish (version exists) if: steps.check_version.outputs.exists == 'true' run: | - echo "ā­ļø Skipping: version ${{ steps.check_version.outputs.version }} already exists" + echo "Skipping: version ${{ steps.check_version.outputs.version }} already exists" - name: Get published version id: version run: | - VERSION=$(node -p "require('./package.json').version") + VERSION=$(cd packages/sdk && node -p "require('./package.json').version") echo "version=$VERSION" >> $GITHUB_OUTPUT - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - tag_name: v${{ steps.version.outputs.version }} - name: v${{ steps.version.outputs.version }} - body: | - ## šŸ“¦ NPM Package Published - **Package:** `@quickswap-defi/sdk@${{ steps.version.outputs.version }}` - **NPM:** https://www.npmjs.com/package/@quickswap-defi/sdk - ### Installation - ```bash - npm install @quickswap-defi/sdk@${{ steps.version.outputs.version }} - ``` - draft: false - prerelease: ${{ contains(steps.version.outputs.version, '-') }} - generate_release_notes: true + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release create "sdk/v${{ steps.version.outputs.version }}" \ + --title "@quickswap-defi/sdk v${{ steps.version.outputs.version }}" \ + --notes "## NPM Package Published + **Package:** \`@quickswap-defi/sdk@${{ steps.version.outputs.version }}\` + **NPM:** https://www.npmjs.com/package/@quickswap-defi/sdk + ### Installation + \`\`\`bash + npm install @quickswap-defi/sdk@${{ steps.version.outputs.version }} + \`\`\`" \ + --generate-notes \ + ${{ contains(steps.version.outputs.version, '-') && '--prerelease' || '' }} # ============================================ # JOB 5: Post-Publish Verification # ============================================ verify-publish: - name: āœ… Verify Publication + name: Verify Publication runs-on: ubuntu-latest needs: publish-npm if: needs.publish-npm.result == 'success' @@ -313,4 +321,4 @@ jobs: mkdir -p /tmp/test-install && cd /tmp/test-install npm init -y npm install @quickswap-defi/sdk --ignore-scripts - echo "āœ… Package installation successful" + echo "Package installation successful" diff --git a/.gitignore b/.gitignore index aa32f7f..d3beeef 100755 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ dist node_modules -/packages \ No newline at end of file +**/dist +**/node_modules +**/coverage \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs index 67fad83..cdcd6cb 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -12,5 +12,5 @@ export default tseslint.config( }, }, }, - { ignores: ['dist/', 'node_modules/'] }, + { ignores: ['**/dist/', '**/node_modules/'] }, ) diff --git a/package.json b/package.json index b879c89..d4b8bdd 100755 --- a/package.json +++ b/package.json @@ -1,80 +1,22 @@ { - "name": "@quickswap-defi/sdk", - "license": "MIT", - "version": "1.0.1", - "description": "šŸ›  An SDK for building applications on top of Quickswap.", - "main": "dist/index.js", - "typings": "dist/index.d.ts", - "files": [ - "dist" - ], - "repository": "https://github.com/QuickSwap/QuickSwap-sdk", - "keywords": [ - "quickswap", - "matic", - "ethereum" - ], - "module": "dist/index.mjs", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "require": "./dist/index.js" - } - }, + "name": "quickswap-sdk-monorepo", + "private": true, "scripts": { - "lint": "eslint src test", - "build": "tsup", - "start": "tsup --watch", - "test": "vitest run", - "prepublishOnly": "tsup" - }, - "dependencies": { - "@uniswap/v2-core": "^1.0.0", - "big.js": "^5.2.2", - "decimal.js-light": "^2.5.0", - "jsbi": "^3.1.1", - "tiny-invariant": "^1.1.0", - "tiny-warning": "^1.0.3", - "toformat": "^2.0.0" - }, - "peerDependencies": { - "@ethersproject/address": "^5.0.0-beta", - "@ethersproject/contracts": "^5.0.0-beta", - "@ethersproject/networks": "^5.0.0-beta", - "@ethersproject/providers": "^5.0.0-beta", - "@ethersproject/solidity": "^5.0.0-beta" - }, - "devDependencies": { - "@ethersproject/address": "^5.0.2", - "@ethersproject/contracts": "^5.0.2", - "@ethersproject/networks": "^5.0.2", - "@ethersproject/providers": "^5.0.5", - "@ethersproject/solidity": "^5.0.2", - "@types/big.js": "^4.0.5", - "@eslint/js": "^9.0.0", - "eslint": "^9.0.0", - "tsup": "^8.0.0", - "typescript": "^5.5.0", - "typescript-eslint": "^8.0.0", - "vitest": "^2.0.0" - }, - "engines": { - "node": ">=16" - }, - "publishConfig": { - "access": "public", - "provenance": "true" - }, - "prettier": { - "printWidth": 120, - "semi": false, - "singleQuote": true + "build": "pnpm -r build", + "test": "pnpm -r test", + "lint": "pnpm -r lint", + "build:protocol-core": "pnpm --filter @quickswap-defi/protocol-core build", + "build:sdk": "pnpm --filter @quickswap-defi/sdk build", + "test:protocol-core": "pnpm --filter @quickswap-defi/protocol-core test", + "test:sdk": "pnpm --filter @quickswap-defi/sdk test" }, "pnpm": { "overrides": { "minimatch@<3.1.4": "^3.1.4", "form-data@<2.5.4": "^2.5.4" } + }, + "engines": { + "node": ">=16" } } diff --git a/packages/protocol-core/package.json b/packages/protocol-core/package.json new file mode 100644 index 0000000..49801d7 --- /dev/null +++ b/packages/protocol-core/package.json @@ -0,0 +1,41 @@ +{ + "name": "@quickswap-defi/protocol-core", + "version": "0.1.0", + "license": "MIT", + "description": "Pure protocol knowledge for QuickSwap DEX — chains, stablecoins, fees, schema detection", + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "files": ["dist"], + "scripts": { + "build": "tsup", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "lint": "eslint src/", + "validate:addresses": "tsx scripts/validate-addresses.ts", + "prepublishOnly": "pnpm test && pnpm build && pnpm validate:addresses" + }, + "devDependencies": { + "@noble/hashes": "^1.7.0", + "@vitest/coverage-v8": "^2.0.0", + "tsup": "^8.0.0", + "tsx": "^4.0.0", + "typescript": "^5.5.0", + "vitest": "^2.0.0" + }, + "publishConfig": { + "access": "public", + "provenance": true + }, + "engines": { + "node": ">=18" + } +} diff --git a/packages/protocol-core/scripts/validate-addresses.ts b/packages/protocol-core/scripts/validate-addresses.ts new file mode 100644 index 0000000..0746ee7 --- /dev/null +++ b/packages/protocol-core/scripts/validate-addresses.ts @@ -0,0 +1,81 @@ +import { keccak_256 } from '@noble/hashes/sha3' +import { getSupportedChainIds, getChain } from '../src/chains/registry' + +function toChecksumAddress(address: string): string { + const addr = address.toLowerCase().replace('0x', '') + const hashBytes = keccak_256(new TextEncoder().encode(addr)) + const hash = Array.from(hashBytes).map((b) => b.toString(16).padStart(2, '0')).join('') + + let checksummed = '0x' + for (let i = 0; i < addr.length; i++) { + if (parseInt(hash[i], 16) >= 8) { + checksummed += addr[i].toUpperCase() + } else { + checksummed += addr[i] + } + } + return checksummed +} + +function isValidChecksum(address: string): boolean { + if (!address.startsWith('0x') || address.length !== 42) return false + return address === toChecksumAddress(address) +} + +/** Matches any bare EVM address: 0x followed by exactly 40 hex chars */ +const EVM_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/ + +/** + * Recursively walks an object and collects all string values that look like + * EVM addresses, along with a human-readable path for error messages. + */ +function collectAddresses( + value: unknown, + path: string, + out: Array<{ address: string; path: string }>, +): void { + if (typeof value === 'string') { + if (EVM_ADDRESS_RE.test(value)) { + out.push({ address: value, path }) + } + return + } + if (Array.isArray(value)) { + value.forEach((item, i) => collectAddresses(item, `${path}[${i}]`, out)) + return + } + if (value !== null && typeof value === 'object') { + for (const [key, child] of Object.entries(value as Record)) { + collectAddresses(child, path ? `${path}.${key}` : key, out) + } + } +} + +const chainIds = getSupportedChainIds() +let total = 0 +const errors: string[] = [] + +for (const chainId of chainIds) { + const chain = getChain(chainId)! + const found: Array<{ address: string; path: string }> = [] + collectAddresses(chain, chain.name, found) + + for (const { address, path } of found) { + total++ + if (!isValidChecksum(address)) { + errors.push( + `${chain.name} (${chainId}) ${path}: ${address} → expected ${toChecksumAddress(address)}`, + ) + } + } +} + +if (errors.length > 0) { + console.error(`\nāŒ ${errors.length} invalid EIP-55 checksum(s) found:\n`) + errors.forEach((e) => console.error(` • ${e}`)) + console.error('') + process.exit(1) +} else { + console.log(`āœ“ ${total} addresses validated across ${chainIds.length} chains`) + process.exit(0) +} diff --git a/packages/protocol-core/src/__tests__/chains/chain-data.test.ts b/packages/protocol-core/src/__tests__/chains/chain-data.test.ts new file mode 100644 index 0000000..517c9a3 --- /dev/null +++ b/packages/protocol-core/src/__tests__/chains/chain-data.test.ts @@ -0,0 +1,170 @@ +import { describe, it, expect } from 'vitest' +import { POLYGON } from '../../chains/polygon' +import { BASE } from '../../chains/base' +import { MANTRA } from '../../chains/mantra' +import { MANTA } from '../../chains/manta' +import { SONEIUM } from '../../chains/soneium' +import { SOMNIA } from '../../chains/somnia' +import { IMX } from '../../chains/imx' +import { XLAYER } from '../../chains/xlayer' +import { ZKEVM } from '../../chains/zkevm' +import { DOGECHAIN } from '../../chains/dogechain' +import { ETHEREUM } from '../../chains/ethereum' +import type { ChainConfig } from '../../chains/types' + +const ALL_CHAINS: ChainConfig[] = [ + POLYGON, + BASE, + MANTRA, + MANTA, + SONEIUM, + SOMNIA, + IMX, + XLAYER, + ZKEVM, + DOGECHAIN, + ETHEREUM, +] + +describe('chain data integrity', () => { + describe('basic fields', () => { + it.each(ALL_CHAINS)('$name has positive chainId', (chain) => { + expect(chain.chainId).toBeGreaterThan(0) + }) + + it.each(ALL_CHAINS)('$name has non-empty name', (chain) => { + expect(chain.name).toBeTruthy() + expect(chain.name.length).toBeGreaterThan(0) + }) + + it.each(ALL_CHAINS)('$name has non-empty nativeSymbol', (chain) => { + expect(chain.nativeSymbol).toBeTruthy() + expect(chain.nativeSymbol.length).toBeGreaterThan(0) + }) + }) + + describe('wrappedNative decimals', () => { + it.each(ALL_CHAINS)('$name wrappedNative has 18 decimals', (chain) => { + expect(chain.wrappedNative.decimals).toBe(18) + }) + }) + + describe('stablecoin decimals', () => { + it.each(ALL_CHAINS)('$name stablecoins all have 6 or 18 decimals', (chain) => { + for (const coin of chain.stablecoins) { + expect([6, 18]).toContain(coin.decimals) + } + }) + }) + + describe('stablecoin counts', () => { + it('Polygon has 4 stablecoins', () => { + expect(POLYGON.stablecoins).toHaveLength(4) + }) + + it('Base has 2 stablecoins', () => { + expect(BASE.stablecoins).toHaveLength(2) + }) + + it('MANTRA has 4 stablecoins', () => { + expect(MANTRA.stablecoins).toHaveLength(4) + }) + + it('Manta has 3 stablecoins', () => { + expect(MANTA.stablecoins).toHaveLength(3) + }) + + it('Soneium has 2 stablecoins', () => { + expect(SONEIUM.stablecoins).toHaveLength(2) + }) + + it('Somnia has 2 stablecoins', () => { + expect(SOMNIA.stablecoins).toHaveLength(2) + }) + + it('IMX has 4 stablecoins', () => { + expect(IMX.stablecoins).toHaveLength(4) + }) + + it('X Layer has 3 stablecoins', () => { + expect(XLAYER.stablecoins).toHaveLength(3) + }) + + it('zkEVM has 3 stablecoins', () => { + expect(ZKEVM.stablecoins).toHaveLength(3) + }) + + it('Dogechain has 3 stablecoins', () => { + expect(DOGECHAIN.stablecoins).toHaveLength(3) + }) + + it('Ethereum has 3 stablecoins', () => { + expect(ETHEREUM.stablecoins).toHaveLength(3) + }) + }) + + describe('protocol entries', () => { + it('Ethereum has empty protocols (aggregation-only)', () => { + expect(ETHEREUM.protocols).toHaveLength(0) + }) + + it('Polygon protocols: v3 before v2', () => { + expect(POLYGON.protocols[0].version).toBe('v3') + expect(POLYGON.protocols[1].version).toBe('v2') + }) + + it('Base protocols: v4 before v2', () => { + expect(BASE.protocols[0].version).toBe('v4') + expect(BASE.protocols[1].version).toBe('v2') + }) + + it('Dogechain protocols: v3 before v2', () => { + expect(DOGECHAIN.protocols[0].version).toBe('v3') + expect(DOGECHAIN.protocols[1].version).toBe('v2') + }) + + it('zkEVM protocols: v3 before univ3', () => { + expect(ZKEVM.protocols[0].version).toBe('v3') + expect(ZKEVM.protocols[1].version).toBe('univ3') + }) + + it('MANTRA has v4 with exposeDynamicFee', () => { + expect(MANTRA.protocols).toHaveLength(1) + expect(MANTRA.protocols[0].version).toBe('v4') + expect(MANTRA.protocols[0].exposeDynamicFee).toBe(true) + }) + + it('Manta has univ3 without exposeDynamicFee', () => { + expect(MANTA.protocols).toHaveLength(1) + expect(MANTA.protocols[0].version).toBe('univ3') + expect(MANTA.protocols[0].exposeDynamicFee).toBe(false) + }) + }) + + describe('Ethereum stablecoins', () => { + it('Ethereum includes USDC', () => { + const symbols = ETHEREUM.stablecoins.map((s) => s.symbol) + expect(symbols).toContain('USDC') + }) + + it('Ethereum includes USDT', () => { + const symbols = ETHEREUM.stablecoins.map((s) => s.symbol) + expect(symbols).toContain('USDT') + }) + }) + + describe('Dogechain DAI isolation', () => { + const ZKEVM_DAI_ADDRESS = '0xC5015b9d9161Dca7e18e32f6f25C4aD850731Fd4' + + it('Dogechain DAI is NOT the zkEVM DAI address', () => { + const dogeDai = DOGECHAIN.stablecoins.find((s) => s.symbol === 'DAI') + expect(dogeDai).toBeDefined() + expect(dogeDai!.address).not.toBe(ZKEVM_DAI_ADDRESS) + }) + + it('Dogechain DAI address is the correct Dogechain-native DAI', () => { + const dogeDai = DOGECHAIN.stablecoins.find((s) => s.symbol === 'DAI') + expect(dogeDai!.address).toBe('0x639A647fbe20b6c8ac19E48E2de44ea792c62c5C') + }) + }) +}) diff --git a/packages/protocol-core/src/__tests__/chains/registry.test.ts b/packages/protocol-core/src/__tests__/chains/registry.test.ts new file mode 100644 index 0000000..2f02705 --- /dev/null +++ b/packages/protocol-core/src/__tests__/chains/registry.test.ts @@ -0,0 +1,117 @@ +import { describe, it, expect } from 'vitest' +import { CHAIN_REGISTRY, CHAIN_ID, getChain, getChainOrThrow, getSupportedChainIds } from '../../chains/registry' +import type { TokenInfo } from '../../chains/types' + +const EXPECTED_CHAIN_IDS = [137, 8453, 5888, 169, 1868, 5031, 13371, 196, 1101, 2000, 1] + +describe('chain registry', () => { + describe('getChain', () => { + it('returns MANTRA config for chainId 5888', () => { + const chain = getChain(5888) + expect(chain).toBeDefined() + expect(chain!.name).toBe('MANTRA') + expect(chain!.chainId).toBe(5888) + }) + + it('returns undefined for unknown chainId', () => { + expect(getChain(99999)).toBeUndefined() + }) + + it('returns Polygon config for chainId 137', () => { + const chain = getChain(137) + expect(chain).toBeDefined() + expect(chain!.nativeSymbol).toBe('POL') + }) + + it('returns Ethereum config for chainId 1', () => { + const chain = getChain(1) + expect(chain).toBeDefined() + expect(chain!.protocols).toHaveLength(0) + }) + }) + + describe('getSupportedChainIds', () => { + it('has length 11', () => { + expect(getSupportedChainIds()).toHaveLength(11) + }) + + it.each(EXPECTED_CHAIN_IDS)('contains chainId %i', (chainId) => { + expect(getSupportedChainIds()).toContain(chainId) + }) + }) + + describe('getChainOrThrow', () => { + it('returns config for known chain', () => { + const chain = getChainOrThrow(137) + expect(chain.name).toBe('Polygon PoS') + }) + + it('throws for unknown chain', () => { + expect(() => getChainOrThrow(99999)).toThrow('Unsupported chain: 99999') + }) + }) + + describe('CHAIN_ID', () => { + it('maps names to correct chain IDs', () => { + expect(CHAIN_ID.POLYGON).toBe(137) + expect(CHAIN_ID.BASE).toBe(8453) + expect(CHAIN_ID.MANTRA).toBe(5888) + expect(CHAIN_ID.MANTA).toBe(169) + expect(CHAIN_ID.SONEIUM).toBe(1868) + expect(CHAIN_ID.SOMNIA).toBe(5031) + expect(CHAIN_ID.IMX).toBe(13371) + expect(CHAIN_ID.XLAYER).toBe(196) + expect(CHAIN_ID.ZKEVM).toBe(1101) + expect(CHAIN_ID.DOGECHAIN).toBe(2000) + expect(CHAIN_ID.ETHEREUM).toBe(1) + }) + + it('has same keys as registry', () => { + expect(Object.keys(CHAIN_ID)).toHaveLength(getSupportedChainIds().length) + }) + + it('every CHAIN_ID value exists in registry', () => { + for (const id of Object.values(CHAIN_ID)) { + expect(getChain(id)).toBeDefined() + } + }) + }) + + describe('immutability', () => { + it('CHAIN_REGISTRY is frozen', () => { + expect(Object.isFrozen(CHAIN_REGISTRY)).toBe(true) + }) + + it('mutation of CHAIN_REGISTRY top-level has no effect', () => { + const before = CHAIN_REGISTRY[137] + try { + // @ts-expect-error intentional mutation attempt + CHAIN_REGISTRY[137] = undefined + } catch { + // strict mode throws — that's fine + } + expect(CHAIN_REGISTRY[137]).toBe(before) + }) + + it('nested stablecoins array is frozen (deep freeze)', () => { + const polygon = getChain(137)! + expect(Object.isFrozen(polygon.stablecoins)).toBe(true) + }) + + it('push to frozen stablecoins silently fails or throws', () => { + const polygon = getChain(137)! + const lengthBefore = polygon.stablecoins.length + try { + ;(polygon.stablecoins as TokenInfo[]).push({ address: '0x0', symbol: 'FAKE', decimals: 6 }) + } catch { + // strict mode throws — acceptable + } + expect(polygon.stablecoins).toHaveLength(lengthBefore) + }) + + it('wrappedNative object is frozen', () => { + const polygon = getChain(137)! + expect(Object.isFrozen(polygon.wrappedNative)).toBe(true) + }) + }) +}) diff --git a/packages/protocol-core/src/__tests__/chains/types.test.ts b/packages/protocol-core/src/__tests__/chains/types.test.ts new file mode 100644 index 0000000..8166779 --- /dev/null +++ b/packages/protocol-core/src/__tests__/chains/types.test.ts @@ -0,0 +1,64 @@ +import { describe, it, expect, expectTypeOf } from 'vitest' +import { + PROTOCOL_VERSIONS, + SCHEMA_VARIANTS, + type ProtocolVersion, + type SchemaVariant, + type TokenInfo, + type ChainProtocolEntry, + type ChainConfig, +} from '../../chains/types' + +describe('chains/types', () => { + describe('ProtocolVersion', () => { + it('accepts valid values', () => { + const v2: ProtocolVersion = PROTOCOL_VERSIONS.V2 + const v3: ProtocolVersion = PROTOCOL_VERSIONS.V3 + const v4: ProtocolVersion = PROTOCOL_VERSIONS.V4 + const univ3: ProtocolVersion = PROTOCOL_VERSIONS.UNIV3 + expect(v2).toBe('v2') + expect(v3).toBe('v3') + expect(v4).toBe('v4') + expect(univ3).toBe('univ3') + }) + + it('has exactly 4 entries', () => { + expect(Object.keys(PROTOCOL_VERSIONS)).toHaveLength(4) + }) + }) + + describe('SchemaVariant', () => { + it('accepts valid values', () => { + const v2: SchemaVariant = SCHEMA_VARIANTS.V2 + const concentrated: SchemaVariant = SCHEMA_VARIANTS.CONCENTRATED + expect(v2).toBe('v2') + expect(concentrated).toBe('concentrated') + }) + + it('has exactly 2 entries', () => { + expect(Object.keys(SCHEMA_VARIANTS)).toHaveLength(2) + }) + }) + + describe('TokenInfo', () => { + it('is a flat interface with 3 fields', () => { + const token: TokenInfo = { address: '0x123', symbol: 'TEST', decimals: 18 } + expect(Object.keys(token)).toHaveLength(3) + }) + }) + + describe('ChainConfig', () => { + it('has readonly protocols as ReadonlyArray', () => { + const config: ChainConfig = { + chainId: 1, + name: 'Test', + nativeSymbol: 'ETH', + wrappedNative: { address: '0x123', symbol: 'WETH', decimals: 18 }, + protocols: [], + stablecoins: [], + } + expectTypeOf(config.protocols).toEqualTypeOf>() + expectTypeOf(config.stablecoins).toEqualTypeOf>() + }) + }) +}) diff --git a/packages/protocol-core/src/__tests__/index.test.ts b/packages/protocol-core/src/__tests__/index.test.ts new file mode 100644 index 0000000..de49106 --- /dev/null +++ b/packages/protocol-core/src/__tests__/index.test.ts @@ -0,0 +1,94 @@ +import { describe, it, expect } from 'vitest' +import { + PROTOCOL_VERSIONS, + SCHEMA_VARIANTS, + CHAIN_REGISTRY, + CHAIN_ID, + getChain, + getChainOrThrow, + getSupportedChainIds, + POLYGON, + BASE, + MANTRA, + MANTA, + SONEIUM, + SOMNIA, + IMX, + XLAYER, + ZKEVM, + DOGECHAIN, + ETHEREUM, + getSchemaVariant, + getSupportedVersions, + getProtocolVersionLabel, + V2_FEE_BPS, + V2_FEE_RATE, + computeV2Fee, + getStablecoins, + getStablecoinAddresses, + isStablecoin, + getNativeToken, + getWrappedNative, +} from '../index' + +describe('barrel export (index.ts)', () => { + it('exports all type const objects', () => { + expect(PROTOCOL_VERSIONS.V2).toBe('v2') + expect(SCHEMA_VARIANTS.CONCENTRATED).toBe('concentrated') + }) + + it('exports chain registry functions', () => { + expect(typeof getChain).toBe('function') + expect(typeof getChainOrThrow).toBe('function') + expect(typeof getSupportedChainIds).toBe('function') + expect(CHAIN_REGISTRY).toBeDefined() + expect(CHAIN_ID).toBeDefined() + }) + + it('exports individual chain configs', () => { + expect(POLYGON.chainId).toBe(137) + expect(BASE.chainId).toBe(8453) + expect(MANTRA.chainId).toBe(5888) + expect(MANTA.chainId).toBe(169) + expect(SONEIUM.chainId).toBe(1868) + expect(SOMNIA.chainId).toBe(5031) + expect(IMX.chainId).toBe(13371) + expect(XLAYER.chainId).toBe(196) + expect(ZKEVM.chainId).toBe(1101) + expect(DOGECHAIN.chainId).toBe(2000) + expect(ETHEREUM.chainId).toBe(1) + }) + + it('exports protocol functions', () => { + expect(typeof getSchemaVariant).toBe('function') + expect(typeof getSupportedVersions).toBe('function') + expect(typeof getProtocolVersionLabel).toBe('function') + }) + + it('exports fee constants', () => { + expect(V2_FEE_BPS).toBe(30) + expect(V2_FEE_RATE).toBe(0.003) + expect(typeof computeV2Fee).toBe('function') + }) + + it('exports token functions', () => { + expect(typeof getStablecoins).toBe('function') + expect(typeof getStablecoinAddresses).toBe('function') + expect(typeof isStablecoin).toBe('function') + expect(typeof getNativeToken).toBe('function') + expect(typeof getWrappedNative).toBe('function') + }) + + it('has exactly 29 runtime exports (types excluded)', () => { + // 2 const objects + 5 registry + 11 chain configs + 3 protocol + 3 fee + 5 token = 29 + const allExports = { + PROTOCOL_VERSIONS, SCHEMA_VARIANTS, + CHAIN_REGISTRY, CHAIN_ID, getChain, getChainOrThrow, getSupportedChainIds, + POLYGON, BASE, MANTRA, MANTA, SONEIUM, SOMNIA, IMX, XLAYER, ZKEVM, DOGECHAIN, ETHEREUM, + getSchemaVariant, getSupportedVersions, getProtocolVersionLabel, + V2_FEE_BPS, V2_FEE_RATE, computeV2Fee, + getStablecoins, getStablecoinAddresses, isStablecoin, getNativeToken, getWrappedNative, + } + expect(Object.keys(allExports)).toHaveLength(29) + }) +}) diff --git a/packages/protocol-core/src/__tests__/protocol/fees.test.ts b/packages/protocol-core/src/__tests__/protocol/fees.test.ts new file mode 100644 index 0000000..16b3034 --- /dev/null +++ b/packages/protocol-core/src/__tests__/protocol/fees.test.ts @@ -0,0 +1,22 @@ +import { describe, it, expect } from 'vitest' +import { V2_FEE_BPS, V2_FEE_RATE, computeV2Fee } from '../../protocol/fees' + +describe('protocol/fees', () => { + it('V2_FEE_BPS is 30', () => { + expect(V2_FEE_BPS).toBe(30) + }) + + it('V2_FEE_RATE is 0.003', () => { + expect(V2_FEE_RATE).toBe(0.003) + }) + + describe('computeV2Fee', () => { + it('calculates correct fee', () => { + expect(computeV2Fee(1000)).toBeCloseTo(3, 10) + }) + + it('returns 0 for 0 input', () => { + expect(computeV2Fee(0)).toBe(0) + }) + }) +}) diff --git a/packages/protocol-core/src/__tests__/protocol/schema.test.ts b/packages/protocol-core/src/__tests__/protocol/schema.test.ts new file mode 100644 index 0000000..88112d3 --- /dev/null +++ b/packages/protocol-core/src/__tests__/protocol/schema.test.ts @@ -0,0 +1,20 @@ +import { describe, it, expect } from 'vitest' +import { getSchemaVariant } from '../../protocol/schema' + +describe('protocol/schema', () => { + it('maps v3 to concentrated', () => { + expect(getSchemaVariant('v3')).toBe('concentrated') + }) + + it('maps v4 to concentrated', () => { + expect(getSchemaVariant('v4')).toBe('concentrated') + }) + + it('maps univ3 to concentrated', () => { + expect(getSchemaVariant('univ3')).toBe('concentrated') + }) + + it('maps v2 to v2', () => { + expect(getSchemaVariant('v2')).toBe('v2') + }) +}) diff --git a/packages/protocol-core/src/__tests__/protocol/versions.test.ts b/packages/protocol-core/src/__tests__/protocol/versions.test.ts new file mode 100644 index 0000000..5e74c96 --- /dev/null +++ b/packages/protocol-core/src/__tests__/protocol/versions.test.ts @@ -0,0 +1,56 @@ +import { describe, it, expect } from 'vitest' +import { getSupportedVersions, getProtocolVersionLabel } from '../../protocol/versions' + +describe('protocol/versions', () => { + describe('getSupportedVersions', () => { + it('returns [v3, v2] for Polygon (137)', () => { + expect(getSupportedVersions(137)).toEqual(['v3', 'v2']) + }) + + it('returns [v4, v2] for Base (8453)', () => { + expect(getSupportedVersions(8453)).toEqual(['v4', 'v2']) + }) + + it('returns [v4] for MANTRA (5888)', () => { + expect(getSupportedVersions(5888)).toEqual(['v4']) + }) + + it('returns [univ3] for Manta (169)', () => { + expect(getSupportedVersions(169)).toEqual(['univ3']) + }) + + it('returns [] for Ethereum (1) — aggregation only', () => { + expect(getSupportedVersions(1)).toEqual([]) + }) + + it('returns [] for unknown chain', () => { + expect(getSupportedVersions(99999)).toEqual([]) + }) + }) + + describe('getProtocolVersionLabel', () => { + it('returns v4 for Base CL', () => { + expect(getProtocolVersionLabel(8453, true)).toBe('v4') + }) + + it('returns v3 for Polygon CL', () => { + expect(getProtocolVersionLabel(137, true)).toBe('v3') + }) + + it('returns univ3 for Manta CL', () => { + expect(getProtocolVersionLabel(169, true)).toBe('univ3') + }) + + it('returns v2 when not concentrated liquidity', () => { + expect(getProtocolVersionLabel(137, false)).toBe('v2') + }) + + it('returns v3 as fallback for unknown chain CL', () => { + expect(getProtocolVersionLabel(99999, true)).toBe('v3') + }) + + it('returns v3 fallback for Ethereum CL (no protocols)', () => { + expect(getProtocolVersionLabel(1, true)).toBe('v3') + }) + }) +}) diff --git a/packages/protocol-core/src/__tests__/tokens/native.test.ts b/packages/protocol-core/src/__tests__/tokens/native.test.ts new file mode 100644 index 0000000..ce72393 --- /dev/null +++ b/packages/protocol-core/src/__tests__/tokens/native.test.ts @@ -0,0 +1,59 @@ +import { describe, it, expect } from 'vitest' +import { getNativeToken, getWrappedNative } from '../../tokens/native' + +describe('tokens/native', () => { + describe('getNativeToken', () => { + it('returns POL info for Polygon', () => { + const result = getNativeToken(137) + expect(result).toBeDefined() + expect(result!.symbol).toBe('POL') + expect(result!.decimals).toBe(18) + }) + + it('returns ETH info for Base', () => { + const result = getNativeToken(8453) + expect(result).toBeDefined() + expect(result!.symbol).toBe('ETH') + }) + + it('returns SOMI info for Somnia', () => { + const result = getNativeToken(5031) + expect(result).toBeDefined() + expect(result!.symbol).toBe('SOMI') + }) + + it('returns undefined for unknown chain', () => { + expect(getNativeToken(99999)).toBeUndefined() + }) + }) + + describe('getWrappedNative', () => { + it('returns WPOL for Polygon', () => { + const result = getWrappedNative(137) + expect(result).toBeDefined() + expect(result!.symbol).toBe('WPOL') + expect(result!.decimals).toBe(18) + }) + + it('returns WETH for Base', () => { + const result = getWrappedNative(8453) + expect(result).toBeDefined() + expect(result!.symbol).toBe('WETH') + }) + + it('returns undefined for unknown chain', () => { + expect(getWrappedNative(99999)).toBeUndefined() + }) + }) + + describe('address equality', () => { + it('getNativeToken and getWrappedNative share the same address', () => { + const chains = [137, 8453, 5888, 169, 1868, 5031, 13371, 196, 1101, 2000, 1] + for (const chainId of chains) { + const native = getNativeToken(chainId) + const wrapped = getWrappedNative(chainId) + expect(native?.address).toBe(wrapped?.address) + } + }) + }) +}) diff --git a/packages/protocol-core/src/__tests__/tokens/stablecoins.test.ts b/packages/protocol-core/src/__tests__/tokens/stablecoins.test.ts new file mode 100644 index 0000000..3ddf8b1 --- /dev/null +++ b/packages/protocol-core/src/__tests__/tokens/stablecoins.test.ts @@ -0,0 +1,57 @@ +import { describe, it, expect } from 'vitest' +import { getStablecoins, getStablecoinAddresses, isStablecoin } from '../../tokens/stablecoins' + +describe('tokens/stablecoins', () => { + describe('getStablecoins', () => { + it('returns 4 stablecoins for MANTRA (5888)', () => { + const result = getStablecoins(5888) + expect(result).toHaveLength(4) + expect(result.map((s) => s.symbol)).toContain('mantraUSD') + }) + + it('returns 4 stablecoins for IMX (13371)', () => { + const result = getStablecoins(13371) + expect(result).toHaveLength(4) + expect(result.map((s) => s.symbol)).toContain('axlUSDC') + }) + + it('returns 3 stablecoins for Ethereum (1)', () => { + expect(getStablecoins(1)).toHaveLength(3) + }) + + it('returns [] for unknown chain', () => { + expect(getStablecoins(99999)).toEqual([]) + }) + }) + + describe('getStablecoinAddresses', () => { + it('returns lowercase addresses', () => { + const addresses = getStablecoinAddresses(137) + addresses.forEach((addr) => { + expect(addr).toBe(addr.toLowerCase()) + }) + }) + + it('returns 4 addresses for Polygon', () => { + expect(getStablecoinAddresses(137)).toHaveLength(4) + }) + }) + + describe('isStablecoin', () => { + it('returns true for known stablecoin (lowercase input)', () => { + expect(isStablecoin(137, '0x2791bca1f2de4661ed88a30c99a7a9449aa84174')).toBe(true) + }) + + it('returns true for known stablecoin (checksummed input)', () => { + expect(isStablecoin(137, '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174')).toBe(true) + }) + + it('returns false for non-stablecoin address', () => { + expect(isStablecoin(137, '0x0000000000000000000000000000000000000001')).toBe(false) + }) + + it('returns false for unknown chain', () => { + expect(isStablecoin(99999, '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174')).toBe(false) + }) + }) +}) diff --git a/packages/protocol-core/src/chains/base.ts b/packages/protocol-core/src/chains/base.ts new file mode 100644 index 0000000..05023fc --- /dev/null +++ b/packages/protocol-core/src/chains/base.ts @@ -0,0 +1,20 @@ +import type { ChainConfig } from './types' + +export const BASE: ChainConfig = { + chainId: 8453, + name: 'Base', + nativeSymbol: 'ETH', + wrappedNative: { + address: '0x4200000000000000000000000000000000000006', + symbol: 'WETH', + decimals: 18, + }, + protocols: [ + { version: 'v4', exposeDynamicFee: true }, + { version: 'v2', exposeDynamicFee: false }, + ], + stablecoins: [ + { address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', symbol: 'USDC', decimals: 6 }, + { address: '0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2', symbol: 'USDT', decimals: 6 }, + ], +} diff --git a/packages/protocol-core/src/chains/dogechain.ts b/packages/protocol-core/src/chains/dogechain.ts new file mode 100644 index 0000000..b9abc40 --- /dev/null +++ b/packages/protocol-core/src/chains/dogechain.ts @@ -0,0 +1,24 @@ +import type { ChainConfig } from './types' + +// NOTE: DAI address (0x639A647fbe20b6c8ac19E48E2de44ea792c62c5C) is Dogechain's own DAI token. +// Do NOT use zkEVM's DAI (0xC5015b9d9161Dca7e18e32f6f25C4aD850731Fd4) — interface-v3 has a bug +// referencing DAI[ChainId.ZKEVM] for Dogechain. +export const DOGECHAIN: ChainConfig = { + chainId: 2000, + name: 'Dogechain', + nativeSymbol: 'DOGE', + wrappedNative: { + address: '0xB7ddC6414bf4F5515b52D8BdD69973Ae205ff101', + symbol: 'WDOGE', + decimals: 18, + }, + protocols: [ + { version: 'v3', exposeDynamicFee: false }, + { version: 'v2', exposeDynamicFee: false }, + ], + stablecoins: [ + { address: '0x765277EebeCA2e31912C9946eAe1021199B39C61', symbol: 'USDC', decimals: 6 }, + { address: '0xE3F5a90F9cb311505cd691a46596599aA1A0AD7D', symbol: 'USDT', decimals: 6 }, + { address: '0x639A647fbe20b6c8ac19E48E2de44ea792c62c5C', symbol: 'DAI', decimals: 18 }, + ], +} diff --git a/packages/protocol-core/src/chains/ethereum.ts b/packages/protocol-core/src/chains/ethereum.ts new file mode 100644 index 0000000..8dfefcc --- /dev/null +++ b/packages/protocol-core/src/chains/ethereum.ts @@ -0,0 +1,19 @@ +import type { ChainConfig } from './types' + +export const ETHEREUM: ChainConfig = { + chainId: 1, + name: 'Ethereum', + nativeSymbol: 'ETH', + wrappedNative: { + address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + symbol: 'WETH', + decimals: 18, + }, + // No QuickSwap deployment — reference chain for cross-chain price feeds and stablecoin addresses + protocols: [], + stablecoins: [ + { address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', symbol: 'USDC', decimals: 6 }, + { address: '0xdAC17F958D2ee523a2206206994597C13D831ec7', symbol: 'USDT', decimals: 6 }, + { address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', symbol: 'DAI', decimals: 18 }, + ], +} diff --git a/packages/protocol-core/src/chains/imx.ts b/packages/protocol-core/src/chains/imx.ts new file mode 100644 index 0000000..cffdd04 --- /dev/null +++ b/packages/protocol-core/src/chains/imx.ts @@ -0,0 +1,21 @@ +import type { ChainConfig } from './types' + +export const IMX: ChainConfig = { + chainId: 13371, + name: 'Immutable zkEVM', + nativeSymbol: 'IMX', + wrappedNative: { + address: '0x3A0C2Ba54D6CBd3121F01b96dFd20e99D1696C9D', + symbol: 'WIMX', + decimals: 18, + }, + protocols: [ + { version: 'univ3', exposeDynamicFee: false }, + ], + stablecoins: [ + { address: '0x6de8aCC0D406837030CE4dd28e7c08C5a96a30d2', symbol: 'USDC', decimals: 6 }, + { address: '0x68bcc7F1190AF20e7b572BCfb431c3Ac10A936Ab', symbol: 'USDT', decimals: 6 }, + { address: '0x00000000eFE302BEAA2b3e6e1b18d08D69a9012a', symbol: 'AUSD', decimals: 6 }, + { address: '0xEB466342C4d449BC9f53A865D5Cb90586f405215', symbol: 'axlUSDC', decimals: 6 }, + ], +} diff --git a/packages/protocol-core/src/chains/manta.ts b/packages/protocol-core/src/chains/manta.ts new file mode 100644 index 0000000..0b9eec0 --- /dev/null +++ b/packages/protocol-core/src/chains/manta.ts @@ -0,0 +1,20 @@ +import type { ChainConfig } from './types' + +export const MANTA: ChainConfig = { + chainId: 169, + name: 'Manta Pacific', + nativeSymbol: 'ETH', + wrappedNative: { + address: '0x0Dc808adcE2099A9F62AA87D9670745AbA741746', + symbol: 'WETH', + decimals: 18, + }, + protocols: [ + { version: 'univ3', exposeDynamicFee: false }, + ], + stablecoins: [ + { address: '0xb73603C5d87fA094B7314C74ACE2e64D165016fb', symbol: 'USDC', decimals: 6 }, + { address: '0xf417F5A458eC102B90352F697D6e2Ac3A3d2851f', symbol: 'USDT', decimals: 6 }, + { address: '0x1c466b9371f8aBA0D7c458bE10a62192Fcb8Aa71', symbol: 'DAI', decimals: 18 }, + ], +} diff --git a/packages/protocol-core/src/chains/mantra.ts b/packages/protocol-core/src/chains/mantra.ts new file mode 100644 index 0000000..7e607a7 --- /dev/null +++ b/packages/protocol-core/src/chains/mantra.ts @@ -0,0 +1,21 @@ +import type { ChainConfig } from './types' + +export const MANTRA: ChainConfig = { + chainId: 5888, + name: 'MANTRA', + nativeSymbol: 'MANTRA', + wrappedNative: { + address: '0xE3047710EF6cB36Bcf1E58145529778eA7Cb5598', + symbol: 'WMANTRA', + decimals: 18, + }, + protocols: [ + { version: 'v4', exposeDynamicFee: true }, + ], + stablecoins: [ + { address: '0xd2b95283011E47257917770D28Bb3EE44c849f6F', symbol: 'mantraUSD', decimals: 18 }, + { address: '0x5E76be0F4e09057D75140216F70fd4cE3365bb29', symbol: 'USDC', decimals: 6 }, + { address: '0x680e8ECB908A2040232ef139A0A52cbE47b9F15B', symbol: 'USDT', decimals: 6 }, + { address: '0x3806640578b710d8480910bF51510bc538d2F51A', symbol: 'USDT_LUCID', decimals: 6 }, + ], +} diff --git a/packages/protocol-core/src/chains/polygon.ts b/packages/protocol-core/src/chains/polygon.ts new file mode 100644 index 0000000..41b6edf --- /dev/null +++ b/packages/protocol-core/src/chains/polygon.ts @@ -0,0 +1,22 @@ +import type { ChainConfig } from './types' + +export const POLYGON: ChainConfig = { + chainId: 137, + name: 'Polygon PoS', + nativeSymbol: 'POL', + wrappedNative: { + address: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270', + symbol: 'WPOL', + decimals: 18, + }, + protocols: [ + { version: 'v3', exposeDynamicFee: false }, + { version: 'v2', exposeDynamicFee: false }, + ], + stablecoins: [ + { address: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', symbol: 'USDC.e', decimals: 6 }, + { address: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', symbol: 'USDC', decimals: 6 }, + { address: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F', symbol: 'USDT', decimals: 6 }, + { address: '0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063', symbol: 'DAI', decimals: 18 }, + ], +} diff --git a/packages/protocol-core/src/chains/registry.ts b/packages/protocol-core/src/chains/registry.ts new file mode 100644 index 0000000..161fd80 --- /dev/null +++ b/packages/protocol-core/src/chains/registry.ts @@ -0,0 +1,68 @@ +import type { ChainConfig } from './types' +import { POLYGON } from './polygon' +import { BASE } from './base' +import { MANTRA } from './mantra' +import { MANTA } from './manta' +import { SONEIUM } from './soneium' +import { SOMNIA } from './somnia' +import { IMX } from './imx' +import { XLAYER } from './xlayer' +import { ZKEVM } from './zkevm' +import { DOGECHAIN } from './dogechain' +import { ETHEREUM } from './ethereum' + +function deepFreeze(obj: T): Readonly { + Object.freeze(obj) + for (const value of Object.values(obj)) { + if (typeof value === 'object' && value !== null && !Object.isFrozen(value)) { + deepFreeze(value) + } + } + return obj as Readonly +} + +const _registry: Record = { + [POLYGON.chainId]: POLYGON, + [BASE.chainId]: BASE, + [MANTRA.chainId]: MANTRA, + [MANTA.chainId]: MANTA, + [SONEIUM.chainId]: SONEIUM, + [SOMNIA.chainId]: SOMNIA, + [IMX.chainId]: IMX, + [XLAYER.chainId]: XLAYER, + [ZKEVM.chainId]: ZKEVM, + [DOGECHAIN.chainId]: DOGECHAIN, + [ETHEREUM.chainId]: ETHEREUM, +} + +export const CHAIN_REGISTRY: Readonly> = deepFreeze(_registry) + +export function getChain(chainId: number): ChainConfig | undefined { + return CHAIN_REGISTRY[chainId] +} + +export function getSupportedChainIds(): number[] { + return Object.keys(CHAIN_REGISTRY).map(Number) +} + +export function getChainOrThrow(chainId: number): ChainConfig { + const chain = CHAIN_REGISTRY[chainId] + if (!chain) { + throw new Error(`Unsupported chain: ${chainId}`) + } + return chain +} + +export const CHAIN_ID = { + POLYGON: POLYGON.chainId, + BASE: BASE.chainId, + MANTRA: MANTRA.chainId, + MANTA: MANTA.chainId, + SONEIUM: SONEIUM.chainId, + SOMNIA: SOMNIA.chainId, + IMX: IMX.chainId, + XLAYER: XLAYER.chainId, + ZKEVM: ZKEVM.chainId, + DOGECHAIN: DOGECHAIN.chainId, + ETHEREUM: ETHEREUM.chainId, +} as const diff --git a/packages/protocol-core/src/chains/somnia.ts b/packages/protocol-core/src/chains/somnia.ts new file mode 100644 index 0000000..4e9fb7c --- /dev/null +++ b/packages/protocol-core/src/chains/somnia.ts @@ -0,0 +1,19 @@ +import type { ChainConfig } from './types' + +export const SOMNIA: ChainConfig = { + chainId: 5031, + name: 'Somnia', + nativeSymbol: 'SOMI', + wrappedNative: { + address: '0x046EDe9564A72571df6F5e44d0405360c0f4dCab', + symbol: 'WSOMI', + decimals: 18, + }, + protocols: [ + { version: 'v4', exposeDynamicFee: true }, + ], + stablecoins: [ + { address: '0x28BEc7E30E6faee657a03e19Bf1128AaD7632A00', symbol: 'USDC', decimals: 6 }, + { address: '0x67B302E35Aef5EEE8c32D934F5856869EF428330', symbol: 'USDT', decimals: 6 }, + ], +} diff --git a/packages/protocol-core/src/chains/soneium.ts b/packages/protocol-core/src/chains/soneium.ts new file mode 100644 index 0000000..3225bdb --- /dev/null +++ b/packages/protocol-core/src/chains/soneium.ts @@ -0,0 +1,19 @@ +import type { ChainConfig } from './types' + +export const SONEIUM: ChainConfig = { + chainId: 1868, + name: 'Soneium', + nativeSymbol: 'ETH', + wrappedNative: { + address: '0x4200000000000000000000000000000000000006', + symbol: 'WETH', + decimals: 18, + }, + protocols: [ + { version: 'v4', exposeDynamicFee: true }, + ], + stablecoins: [ + { address: '0xbA9986D2381edf1DA03B0B9c1f8b00dc4AacC369', symbol: 'USDC', decimals: 6 }, + { address: '0x3A337a6adA9d885b6Ad95ec48F9b75f197b5AE35', symbol: 'USDT', decimals: 6 }, + ], +} diff --git a/packages/protocol-core/src/chains/types.ts b/packages/protocol-core/src/chains/types.ts new file mode 100644 index 0000000..a9ab6b7 --- /dev/null +++ b/packages/protocol-core/src/chains/types.ts @@ -0,0 +1,43 @@ +export const PROTOCOL_VERSIONS = { + V2: 'v2', + V3: 'v3', + V4: 'v4', + UNIV3: 'univ3', +} as const + +export type ProtocolVersion = (typeof PROTOCOL_VERSIONS)[keyof typeof PROTOCOL_VERSIONS] + +export const SCHEMA_VARIANTS = { + V2: 'v2', + CONCENTRATED: 'concentrated', +} as const + +export type SchemaVariant = (typeof SCHEMA_VARIANTS)[keyof typeof SCHEMA_VARIANTS] + +export interface TokenInfo { + readonly address: string + readonly symbol: string + readonly decimals: number +} + +export interface ChainProtocolEntry { + readonly version: ProtocolVersion + /** + * Whether the subgraph schema for this protocol version exposes a `dynamicFee` + * field on Pool entities. True for V4 (Algebra Integral), false for V3/V2/UniV3. + * + * This is a subgraph schema capability flag, NOT a protocol-level indicator of + * whether the AMM uses adaptive fees (Algebra V3 pools DO use dynamic fees at + * the protocol level, but the V3 subgraph schema does not expose the field). + */ + readonly exposeDynamicFee: boolean +} + +export interface ChainConfig { + readonly chainId: number + readonly name: string + readonly nativeSymbol: string + readonly wrappedNative: TokenInfo + readonly protocols: ReadonlyArray + readonly stablecoins: ReadonlyArray +} diff --git a/packages/protocol-core/src/chains/xlayer.ts b/packages/protocol-core/src/chains/xlayer.ts new file mode 100644 index 0000000..a53a480 --- /dev/null +++ b/packages/protocol-core/src/chains/xlayer.ts @@ -0,0 +1,20 @@ +import type { ChainConfig } from './types' + +export const XLAYER: ChainConfig = { + chainId: 196, + name: 'X-Layer', + nativeSymbol: 'OKB', + wrappedNative: { + address: '0xe538905cf8410324e03A5A23C1c177a474D59b2b', + symbol: 'WOKB', + decimals: 18, + }, + protocols: [ + { version: 'v3', exposeDynamicFee: false }, + ], + stablecoins: [ + { address: '0x1E4a5963aBFD975d8c9021ce480b42188849D41d', symbol: 'USDT', decimals: 6 }, + { address: '0x74b7F16337b8972027F6196A17a631aC6dE26d22', symbol: 'USDC', decimals: 6 }, + { address: '0xC5015b9d9161Dca7e18e32f6f25C4aD850731Fd4', symbol: 'DAI', decimals: 18 }, + ], +} diff --git a/packages/protocol-core/src/chains/zkevm.ts b/packages/protocol-core/src/chains/zkevm.ts new file mode 100644 index 0000000..0b06fe3 --- /dev/null +++ b/packages/protocol-core/src/chains/zkevm.ts @@ -0,0 +1,21 @@ +import type { ChainConfig } from './types' + +export const ZKEVM: ChainConfig = { + chainId: 1101, + name: 'Polygon zkEVM', + nativeSymbol: 'ETH', + wrappedNative: { + address: '0x4F9A0e7FD2Bf6067db6994CF12E4495Df938E6e9', + symbol: 'WETH', + decimals: 18, + }, + protocols: [ + { version: 'v3', exposeDynamicFee: false }, + { version: 'univ3', exposeDynamicFee: false }, + ], + stablecoins: [ + { address: '0xA8CE8aee21bC2A48a5EF670afCc9274C7bbbC035', symbol: 'USDC', decimals: 6 }, + { address: '0x1E4a5963aBFD975d8c9021ce480b42188849D41d', symbol: 'USDT', decimals: 6 }, + { address: '0xC5015b9d9161Dca7e18e32f6f25C4aD850731Fd4', symbol: 'DAI', decimals: 18 }, + ], +} diff --git a/packages/protocol-core/src/index.ts b/packages/protocol-core/src/index.ts new file mode 100644 index 0000000..9ca4a97 --- /dev/null +++ b/packages/protocol-core/src/index.ts @@ -0,0 +1,30 @@ +// Types +export type { ProtocolVersion, SchemaVariant, TokenInfo, ChainProtocolEntry, ChainConfig } from './chains/types' +export { PROTOCOL_VERSIONS, SCHEMA_VARIANTS } from './chains/types' + +// Chain Registry +export { CHAIN_REGISTRY, CHAIN_ID, getChain, getChainOrThrow, getSupportedChainIds } from './chains/registry' + +// Individual Chain Configs +export { POLYGON } from './chains/polygon' +export { BASE } from './chains/base' +export { MANTRA } from './chains/mantra' +export { MANTA } from './chains/manta' +export { SONEIUM } from './chains/soneium' +export { SOMNIA } from './chains/somnia' +export { IMX } from './chains/imx' +export { XLAYER } from './chains/xlayer' +export { ZKEVM } from './chains/zkevm' +export { DOGECHAIN } from './chains/dogechain' +export { ETHEREUM } from './chains/ethereum' + +// Protocol Functions +export { getSchemaVariant } from './protocol/schema' +export { getSupportedVersions, getProtocolVersionLabel } from './protocol/versions' + +// Fee Constants & Functions +export { V2_FEE_BPS, V2_FEE_RATE, computeV2Fee } from './protocol/fees' + +// Token Functions +export { getStablecoins, getStablecoinAddresses, isStablecoin } from './tokens/stablecoins' +export { getNativeToken, getWrappedNative } from './tokens/native' diff --git a/packages/protocol-core/src/protocol/fees.ts b/packages/protocol-core/src/protocol/fees.ts new file mode 100644 index 0000000..eb53e05 --- /dev/null +++ b/packages/protocol-core/src/protocol/fees.ts @@ -0,0 +1,21 @@ +/** V2 swap fee in basis points (0.30%) */ +export const V2_FEE_BPS = 30 + +/** V2 swap fee as a decimal rate (30 BPS = 0.003) */ +export const V2_FEE_RATE = 0.003 + +/** + * Compute the V2 fee amount for a given input amount. + * + * @param amountIn - The input token amount (as a plain JS number) + * @returns The fee amount as a JS number + * + * WARNING: This function uses standard IEEE-754 floating-point arithmetic. + * It is intended ONLY for display/analytics approximations (e.g., "~$0.03 fee"). + * Do NOT use the result for on-chain-accurate calculations — for that, use + * integer/BigInt arithmetic with the raw token amounts and apply the 0.30% fee + * in full-precision basis-point math (amountIn * 30 / 10_000). + */ +export function computeV2Fee(amountIn: number): number { + return amountIn * V2_FEE_RATE +} diff --git a/packages/protocol-core/src/protocol/schema.ts b/packages/protocol-core/src/protocol/schema.ts new file mode 100644 index 0000000..d06926a --- /dev/null +++ b/packages/protocol-core/src/protocol/schema.ts @@ -0,0 +1,7 @@ +import type { ProtocolVersion, SchemaVariant } from '../chains/types' + +const CONCENTRATED_VERSIONS = new Set(['v3', 'v4', 'univ3']) + +export function getSchemaVariant(version: ProtocolVersion): SchemaVariant { + return CONCENTRATED_VERSIONS.has(version) ? 'concentrated' : 'v2' +} diff --git a/packages/protocol-core/src/protocol/versions.ts b/packages/protocol-core/src/protocol/versions.ts new file mode 100644 index 0000000..262fd35 --- /dev/null +++ b/packages/protocol-core/src/protocol/versions.ts @@ -0,0 +1,16 @@ +import type { ProtocolVersion } from '../chains/types' +import { getChain } from '../chains/registry' + +export function getSupportedVersions(chainId: number): ProtocolVersion[] { + const chain = getChain(chainId) + if (!chain) return [] + return chain.protocols.map((p) => p.version) +} + +export function getProtocolVersionLabel(chainId: number, isConcentratedLiquidity: boolean): string { + if (!isConcentratedLiquidity) return 'v2' + const chain = getChain(chainId) + if (!chain) return 'v3' + const clProtocol = chain.protocols.find((p) => p.version !== 'v2') + return clProtocol?.version ?? 'v3' +} diff --git a/packages/protocol-core/src/tokens/native.ts b/packages/protocol-core/src/tokens/native.ts new file mode 100644 index 0000000..798198b --- /dev/null +++ b/packages/protocol-core/src/tokens/native.ts @@ -0,0 +1,37 @@ +import type { TokenInfo } from '../chains/types' +import { getChain } from '../chains/registry' + +/** Lazy cache: chainId → frozen TokenInfo for the native gas token */ +const _nativeTokenCache = new Map() + +/** + * Returns the native gas token info for a chain. + * + * The address is the WRAPPED native token address (e.g., WPOL for POL), + * which is the canonical on-chain address used in analytics, subgraph pricing, + * and pool lookups. + * + * NOTE: Some UI contexts expect the zero address (0x000...0) or a sentinel + * for native tokens. This function does NOT return those — + * use the wrapped address for all protocol/analytics operations. + * + * The returned object is frozen and cached — subsequent calls for the same + * chainId return the identical cached reference without re-allocating. + */ +export function getNativeToken(chainId: number): TokenInfo | undefined { + const cached = _nativeTokenCache.get(chainId) + if (cached !== undefined) return cached + const chain = getChain(chainId) + if (!chain) return undefined + const token = Object.freeze({ + address: chain.wrappedNative.address, + symbol: chain.nativeSymbol, + decimals: chain.wrappedNative.decimals, + }) + _nativeTokenCache.set(chainId, token) + return token +} + +export function getWrappedNative(chainId: number): TokenInfo | undefined { + return getChain(chainId)?.wrappedNative +} diff --git a/packages/protocol-core/src/tokens/stablecoins.ts b/packages/protocol-core/src/tokens/stablecoins.ts new file mode 100644 index 0000000..3fe9ccb --- /dev/null +++ b/packages/protocol-core/src/tokens/stablecoins.ts @@ -0,0 +1,26 @@ +import type { TokenInfo } from '../chains/types' +import { getChain } from '../chains/registry' + +export function getStablecoins(chainId: number): ReadonlyArray { + return getChain(chainId)?.stablecoins ?? [] +} + +export function getStablecoinAddresses(chainId: number): string[] { + return getStablecoins(chainId).map((t) => t.address.toLowerCase()) +} + +/** Lazy cache: chainId → Set of lowercase stablecoin addresses */ +const _stablecoinSetCache = new Map>() + +function getStablecoinSet(chainId: number): Set | undefined { + const cached = _stablecoinSetCache.get(chainId) + if (cached !== undefined) return cached + if (getChain(chainId) === undefined) return undefined + const set = new Set(getStablecoinAddresses(chainId)) + _stablecoinSetCache.set(chainId, set) + return set +} + +export function isStablecoin(chainId: number, address: string): boolean { + return getStablecoinSet(chainId)?.has(address.toLowerCase()) ?? false +} diff --git a/packages/protocol-core/tsconfig.build.json b/packages/protocol-core/tsconfig.build.json new file mode 100644 index 0000000..f9066ab --- /dev/null +++ b/packages/protocol-core/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.test.ts"] +} diff --git a/packages/protocol-core/tsconfig.json b/packages/protocol-core/tsconfig.json new file mode 100644 index 0000000..564a599 --- /dev/null +++ b/packages/protocol-core/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src"] +} diff --git a/packages/protocol-core/tsup.config.ts b/packages/protocol-core/tsup.config.ts new file mode 100644 index 0000000..0ac0af3 --- /dev/null +++ b/packages/protocol-core/tsup.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'tsup' + +export default defineConfig({ + entry: ['src/index.ts'], + format: ['cjs', 'esm'], + dts: true, + clean: true, + target: 'es2020', + splitting: false, + sourcemap: true, + minify: false, + treeshake: true, +}) diff --git a/packages/protocol-core/vitest.config.ts b/packages/protocol-core/vitest.config.ts new file mode 100644 index 0000000..6c9fde2 --- /dev/null +++ b/packages/protocol-core/vitest.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['src/**/*.test.ts'], + coverage: { + provider: 'v8', + include: ['src/**/*.ts'], + exclude: ['src/**/*.test.ts', 'src/index.ts'], + thresholds: { + lines: 95, + functions: 95, + branches: 95, + statements: 95, + }, + }, + }, +}) diff --git a/packages/sdk/package.json b/packages/sdk/package.json new file mode 100644 index 0000000..722dd57 --- /dev/null +++ b/packages/sdk/package.json @@ -0,0 +1,74 @@ +{ + "name": "@quickswap-defi/sdk", + "license": "MIT", + "version": "1.0.1", + "description": "šŸ›  An SDK for building applications on top of Quickswap.", + "main": "dist/index.js", + "typings": "dist/index.d.ts", + "files": [ + "dist" + ], + "repository": "https://github.com/QuickSwap/QuickSwap-sdk", + "keywords": [ + "quickswap", + "matic", + "ethereum" + ], + "module": "dist/index.mjs", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "scripts": { + "lint": "eslint src test", + "build": "tsup", + "start": "tsup --watch", + "test": "vitest run", + "prepublishOnly": "tsup" + }, + "dependencies": { + "@uniswap/v2-core": "^1.0.0", + "big.js": "^5.2.2", + "decimal.js-light": "^2.5.0", + "jsbi": "^3.1.1", + "tiny-invariant": "^1.1.0", + "tiny-warning": "^1.0.3", + "toformat": "^2.0.0" + }, + "peerDependencies": { + "@ethersproject/address": "^5.0.0-beta", + "@ethersproject/contracts": "^5.0.0-beta", + "@ethersproject/networks": "^5.0.0-beta", + "@ethersproject/providers": "^5.0.0-beta", + "@ethersproject/solidity": "^5.0.0-beta" + }, + "devDependencies": { + "@ethersproject/address": "^5.0.2", + "@ethersproject/contracts": "^5.0.2", + "@ethersproject/networks": "^5.0.2", + "@ethersproject/providers": "^5.0.5", + "@ethersproject/solidity": "^5.0.2", + "@types/big.js": "^4.0.5", + "@eslint/js": "^9.0.0", + "eslint": "^9.0.0", + "tsup": "^8.0.0", + "typescript": "^5.5.0", + "typescript-eslint": "^8.0.0", + "vitest": "^2.0.0" + }, + "engines": { + "node": ">=16" + }, + "publishConfig": { + "access": "public", + "provenance": true + }, + "prettier": { + "printWidth": 120, + "semi": false, + "singleQuote": true + } +} diff --git a/src/abis/ERC20.json b/packages/sdk/src/abis/ERC20.json similarity index 100% rename from src/abis/ERC20.json rename to packages/sdk/src/abis/ERC20.json diff --git a/src/constants.ts b/packages/sdk/src/constants.ts similarity index 100% rename from src/constants.ts rename to packages/sdk/src/constants.ts diff --git a/src/declarations.d.ts b/packages/sdk/src/declarations.d.ts similarity index 100% rename from src/declarations.d.ts rename to packages/sdk/src/declarations.d.ts diff --git a/src/entities/currency.ts b/packages/sdk/src/entities/currency.ts similarity index 100% rename from src/entities/currency.ts rename to packages/sdk/src/entities/currency.ts diff --git a/src/entities/fractions/currencyAmount.ts b/packages/sdk/src/entities/fractions/currencyAmount.ts similarity index 100% rename from src/entities/fractions/currencyAmount.ts rename to packages/sdk/src/entities/fractions/currencyAmount.ts diff --git a/src/entities/fractions/fraction.ts b/packages/sdk/src/entities/fractions/fraction.ts similarity index 100% rename from src/entities/fractions/fraction.ts rename to packages/sdk/src/entities/fractions/fraction.ts diff --git a/src/entities/fractions/index.ts b/packages/sdk/src/entities/fractions/index.ts similarity index 100% rename from src/entities/fractions/index.ts rename to packages/sdk/src/entities/fractions/index.ts diff --git a/src/entities/fractions/percent.ts b/packages/sdk/src/entities/fractions/percent.ts similarity index 100% rename from src/entities/fractions/percent.ts rename to packages/sdk/src/entities/fractions/percent.ts diff --git a/src/entities/fractions/price.ts b/packages/sdk/src/entities/fractions/price.ts similarity index 100% rename from src/entities/fractions/price.ts rename to packages/sdk/src/entities/fractions/price.ts diff --git a/src/entities/fractions/tokenAmount.ts b/packages/sdk/src/entities/fractions/tokenAmount.ts similarity index 100% rename from src/entities/fractions/tokenAmount.ts rename to packages/sdk/src/entities/fractions/tokenAmount.ts diff --git a/src/entities/index.ts b/packages/sdk/src/entities/index.ts similarity index 100% rename from src/entities/index.ts rename to packages/sdk/src/entities/index.ts diff --git a/src/entities/pair.ts b/packages/sdk/src/entities/pair.ts similarity index 100% rename from src/entities/pair.ts rename to packages/sdk/src/entities/pair.ts diff --git a/src/entities/route.ts b/packages/sdk/src/entities/route.ts similarity index 100% rename from src/entities/route.ts rename to packages/sdk/src/entities/route.ts diff --git a/src/entities/token.ts b/packages/sdk/src/entities/token.ts similarity index 100% rename from src/entities/token.ts rename to packages/sdk/src/entities/token.ts diff --git a/src/entities/trade.ts b/packages/sdk/src/entities/trade.ts similarity index 100% rename from src/entities/trade.ts rename to packages/sdk/src/entities/trade.ts diff --git a/src/errors.ts b/packages/sdk/src/errors.ts similarity index 100% rename from src/errors.ts rename to packages/sdk/src/errors.ts diff --git a/src/fetcher.ts b/packages/sdk/src/fetcher.ts similarity index 100% rename from src/fetcher.ts rename to packages/sdk/src/fetcher.ts diff --git a/src/index.ts b/packages/sdk/src/index.ts similarity index 100% rename from src/index.ts rename to packages/sdk/src/index.ts diff --git a/src/router.ts b/packages/sdk/src/router.ts similarity index 100% rename from src/router.ts rename to packages/sdk/src/router.ts diff --git a/src/utils.ts b/packages/sdk/src/utils.ts similarity index 100% rename from src/utils.ts rename to packages/sdk/src/utils.ts diff --git a/test/constants.test.ts b/packages/sdk/test/constants.test.ts similarity index 100% rename from test/constants.test.ts rename to packages/sdk/test/constants.test.ts diff --git a/test/data.test.ts b/packages/sdk/test/data.test.ts similarity index 100% rename from test/data.test.ts rename to packages/sdk/test/data.test.ts diff --git a/test/entities.test.ts b/packages/sdk/test/entities.test.ts similarity index 100% rename from test/entities.test.ts rename to packages/sdk/test/entities.test.ts diff --git a/test/fraction.test.ts b/packages/sdk/test/fraction.test.ts similarity index 100% rename from test/fraction.test.ts rename to packages/sdk/test/fraction.test.ts diff --git a/test/miscellaneous.test.ts b/packages/sdk/test/miscellaneous.test.ts similarity index 100% rename from test/miscellaneous.test.ts rename to packages/sdk/test/miscellaneous.test.ts diff --git a/test/pair.test.ts b/packages/sdk/test/pair.test.ts similarity index 100% rename from test/pair.test.ts rename to packages/sdk/test/pair.test.ts diff --git a/test/route.test.ts b/packages/sdk/test/route.test.ts similarity index 100% rename from test/route.test.ts rename to packages/sdk/test/route.test.ts diff --git a/test/router.test.ts b/packages/sdk/test/router.test.ts similarity index 100% rename from test/router.test.ts rename to packages/sdk/test/router.test.ts diff --git a/test/token.test.ts b/packages/sdk/test/token.test.ts similarity index 100% rename from test/token.test.ts rename to packages/sdk/test/token.test.ts diff --git a/test/trade.test.ts b/packages/sdk/test/trade.test.ts similarity index 100% rename from test/trade.test.ts rename to packages/sdk/test/trade.test.ts diff --git a/packages/sdk/tsconfig.json b/packages/sdk/tsconfig.json new file mode 100755 index 0000000..cb5ef20 --- /dev/null +++ b/packages/sdk/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src", "test"], + "compilerOptions": { + "target": "es2018", + "moduleResolution": "node", + "rootDir": "./", + "sourceMap": true, + "importHelpers": true, + "isolatedModules": false + } +} diff --git a/tsup.config.ts b/packages/sdk/tsup.config.ts similarity index 100% rename from tsup.config.ts rename to packages/sdk/tsup.config.ts diff --git a/vitest.config.ts b/packages/sdk/vitest.config.ts similarity index 100% rename from vitest.config.ts rename to packages/sdk/vitest.config.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 416cb92..1cc5597 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,7 +10,30 @@ overrides: importers: - .: + .: {} + + packages/protocol-core: + devDependencies: + '@noble/hashes': + specifier: ^1.7.0 + version: 1.8.0 + '@vitest/coverage-v8': + specifier: ^2.0.0 + version: 2.1.9(vitest@2.1.9(@types/node@24.10.0)(jsdom@11.12.0)) + tsup: + specifier: ^8.0.0 + version: 8.5.1(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3) + tsx: + specifier: ^4.0.0 + version: 4.21.0 + typescript: + specifier: ^5.5.0 + version: 5.9.3 + vitest: + specifier: ^2.0.0 + version: 2.1.9(@types/node@24.10.0)(jsdom@11.12.0) + + packages/sdk: dependencies: '@uniswap/v2-core': specifier: ^1.0.0 @@ -60,7 +83,7 @@ importers: version: 9.39.3 tsup: specifier: ^8.0.0 - version: 8.5.1(postcss@8.5.6)(typescript@5.9.3) + version: 8.5.1(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3) typescript: specifier: ^5.5.0 version: 5.9.3 @@ -73,6 +96,30 @@ importers: packages: + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -493,6 +540,14 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -506,6 +561,14 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + '@rollup/rollup-android-arm-eabi@4.59.0': resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} cpu: [arm] @@ -719,6 +782,15 @@ packages: resolution: {integrity: sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q==} engines: {node: '>=10'} + '@vitest/coverage-v8@2.1.9': + resolution: {integrity: sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==} + peerDependencies: + '@vitest/browser': 2.1.9 + vitest: 2.1.9 + peerDependenciesMeta: + '@vitest/browser': + optional: true + '@vitest/expect@2.1.9': resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} @@ -779,16 +851,25 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - ajv@6.14.0: resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -846,6 +927,9 @@ packages: brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@2.0.3: + resolution: {integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==} + brace-expansion@5.0.4: resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} engines: {node: 18 || 20 || >=22} @@ -969,12 +1053,21 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ecc-jsbn@0.1.2: resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} elliptic@6.6.1: resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -1118,6 +1211,10 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + forever-agent@0.6.1: resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} @@ -1141,6 +1238,9 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-tsconfig@4.13.7: + resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==} + getpass@0.1.7: resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} @@ -1148,6 +1248,11 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -1190,6 +1295,9 @@ packages: html-encoding-sniffer@1.0.2: resolution: {integrity: sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + http-signature@1.2.0: resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==} engines: {node: '>=0.8', npm: '>=1.3.7'} @@ -1221,6 +1329,10 @@ packages: 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-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -1234,6 +1346,25 @@ packages: isstream@0.1.2: resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} @@ -1315,9 +1446,19 @@ packages: loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -1343,6 +1484,14 @@ packages: minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + mlly@1.8.0: resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} @@ -1386,6 +1535,9 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -1401,6 +1553,10 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -1502,6 +1658,9 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + rollup@4.59.0: resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -1532,6 +1691,10 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -1559,6 +1722,22 @@ packages: resolution: {integrity: sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==} engines: {node: '>=0.10.0'} + 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'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -1575,6 +1754,10 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + test-exclude@7.0.2: + resolution: {integrity: sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==} + engines: {node: '>=18'} + thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -1652,6 +1835,11 @@ packages: typescript: optional: true + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} @@ -1791,6 +1979,14 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + 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'} + ws@5.2.4: resolution: {integrity: sha512-fFCejsuC8f9kOSu9FYaOw8CdO68O3h5v0lg4p74o8JqWpwTf9tniOD+nOB78aWoVSS6WptVUmDrp/KPsMVBWFQ==} peerDependencies: @@ -1823,6 +2019,26 @@ packages: snapshots: + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/parser@7.29.2': + dependencies: + '@babel/types': 7.29.0 + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bcoe/v8-coverage@0.2.3': {} + '@esbuild/aix-ppc64@0.21.5': optional: true @@ -2214,6 +2430,17 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/schema@0.1.3': {} + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2228,6 +2455,11 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@noble/hashes@1.8.0': {} + + '@pkgjs/parseargs@0.11.0': + optional: true + '@rollup/rollup-android-arm-eabi@4.59.0': optional: true @@ -2407,6 +2639,24 @@ snapshots: '@uniswap/v2-core@1.0.1': {} + '@vitest/coverage-v8@2.1.9(vitest@2.1.9(@types/node@24.10.0)(jsdom@11.12.0))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 0.2.3 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + magic-string: 0.30.21 + magicast: 0.3.5 + std-env: 3.10.0 + test-exclude: 7.0.2 + tinyrainbow: 1.2.0 + vitest: 2.1.9(@types/node@24.10.0)(jsdom@11.12.0) + transitivePeerDependencies: + - supports-color + '@vitest/expect@2.1.9': dependencies: '@vitest/spy': 2.1.9 @@ -2471,13 +2721,6 @@ snapshots: acorn@8.15.0: {} - 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@6.14.0: dependencies: fast-deep-equal: 3.1.3 @@ -2485,10 +2728,16 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 + ansi-styles@6.2.3: {} + any-promise@1.3.0: {} argparse@2.0.1: {} @@ -2540,6 +2789,10 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 + brace-expansion@2.0.3: + dependencies: + balanced-match: 1.0.2 + brace-expansion@5.0.4: dependencies: balanced-match: 4.0.4 @@ -2659,6 +2912,8 @@ snapshots: gopd: 1.2.0 optional: true + eastasianwidth@0.2.0: {} + ecc-jsbn@0.1.2: dependencies: jsbn: 0.1.1 @@ -2675,6 +2930,10 @@ snapshots: minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + es-define-property@1.0.1: optional: true @@ -2788,7 +3047,7 @@ snapshots: '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 - ajv: 6.12.6 + ajv: 6.14.0 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.3 @@ -2881,6 +3140,11 @@ snapshots: flatted@3.3.3: {} + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + forever-agent@0.6.1: optional: true @@ -2920,6 +3184,10 @@ snapshots: es-object-atoms: 1.1.1 optional: true + get-tsconfig@4.13.7: + dependencies: + resolve-pkg-maps: 1.0.0 + getpass@0.1.7: dependencies: assert-plus: 1.0.0 @@ -2929,6 +3197,15 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + globals@14.0.0: {} gopd@1.2.0: @@ -2974,6 +3251,8 @@ snapshots: whatwg-encoding: 1.0.5 optional: true + html-escaper@2.0.2: {} + http-signature@1.2.0: dependencies: assert-plus: 1.0.0 @@ -3001,6 +3280,8 @@ snapshots: is-extglob@2.1.1: {} + is-fullwidth-code-point@3.0.0: {} + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 @@ -3013,6 +3294,33 @@ snapshots: isstream@0.1.2: optional: true + istanbul-lib-coverage@3.2.2: {} + + 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@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + joycon@3.1.1: {} js-sha3@0.8.0: {} @@ -3117,10 +3425,22 @@ snapshots: loupe@3.2.1: {} + lru-cache@10.4.3: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + magicast@0.3.5: + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.7.3 + math-intrinsics@1.1.0: optional: true @@ -3144,6 +3464,12 @@ snapshots: dependencies: brace-expansion: 1.1.12 + minimatch@9.0.9: + dependencies: + brace-expansion: 2.0.3 + + minipass@7.1.3: {} + mlly@1.8.0: dependencies: acorn: 8.15.0 @@ -3198,6 +3524,8 @@ snapshots: dependencies: p-limit: 3.1.0 + package-json-from-dist@1.0.1: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -3209,6 +3537,11 @@ snapshots: path-key@3.1.1: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + pathe@1.1.2: {} pathe@2.0.3: {} @@ -3233,11 +3566,12 @@ snapshots: pn@1.1.0: optional: true - postcss-load-config@6.0.1(postcss@8.5.6): + postcss-load-config@6.0.1(postcss@8.5.6)(tsx@4.21.0): dependencies: lilconfig: 3.1.3 optionalDependencies: postcss: 8.5.6 + tsx: 4.21.0 postcss@8.5.6: dependencies: @@ -3304,6 +3638,8 @@ snapshots: resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} + rollup@4.59.0: dependencies: '@types/estree': 1.0.8 @@ -3354,6 +3690,8 @@ snapshots: siginfo@2.0.0: {} + signal-exit@4.1.0: {} + source-map-js@1.2.1: {} source-map@0.6.1: @@ -3381,6 +3719,26 @@ snapshots: stealthy-require@1.1.1: optional: true + 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.2.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + strip-json-comments@3.1.1: {} sucrase@3.35.1: @@ -3400,6 +3758,12 @@ snapshots: symbol-tree@3.2.4: optional: true + test-exclude@7.0.2: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.5.0 + minimatch: 10.2.4 + thenify-all@1.6.0: dependencies: thenify: 3.3.1 @@ -3448,7 +3812,7 @@ snapshots: ts-interface-checker@0.1.13: {} - tsup@8.5.1(postcss@8.5.6)(typescript@5.9.3): + tsup@8.5.1(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3): dependencies: bundle-require: 5.1.0(esbuild@0.27.3) cac: 6.7.14 @@ -3459,7 +3823,7 @@ snapshots: fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(postcss@8.5.6) + postcss-load-config: 6.0.1(postcss@8.5.6)(tsx@4.21.0) resolve-from: 5.0.0 rollup: 4.59.0 source-map: 0.7.6 @@ -3476,6 +3840,13 @@ snapshots: - tsx - yaml + tsx@4.21.0: + dependencies: + esbuild: 0.27.3 + get-tsconfig: 4.13.7 + optionalDependencies: + fsevents: 2.3.3 + tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 @@ -3629,6 +4000,18 @@ snapshots: word-wrap@1.2.5: {} + 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.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + ws@5.2.4: dependencies: async-limiter: 1.0.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..18ec407 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'packages/*' diff --git a/tsconfig.json b/tsconfig.base.json old mode 100755 new mode 100644 similarity index 68% rename from tsconfig.json rename to tsconfig.base.json index dbd86fd..c4b6b78 --- a/tsconfig.json +++ b/tsconfig.base.json @@ -1,12 +1,8 @@ { - "include": ["src", "test"], "compilerOptions": { - "target": "es2018", - "module": "esnext", - "importHelpers": true, - "declaration": true, - "sourceMap": true, - "rootDir": "./", + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", "strict": true, "noImplicitAny": true, "strictNullChecks": true, @@ -18,9 +14,10 @@ "noUnusedParameters": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, - "moduleResolution": "node", "esModuleInterop": true, "resolveJsonModule": true, - "skipLibCheck": true + "skipLibCheck": true, + "declaration": true, + "isolatedModules": true } }