diff --git a/.eslintrc.json b/.eslintrc.json index 30fa837..ebc2900 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -2,7 +2,8 @@ "parser": "@typescript-eslint/parser", "parserOptions": { "project": [ - "./tsconfig.json" + "./tsconfig.json", + "./packages/*/tsconfig.json" ], "tsconfigRootDir": ".", "sourceType": "module" @@ -16,7 +17,8 @@ }, "ignorePatterns": [ "node_modules/", - "dist/" + "dist/", + "**/example/**" ], "rules": { "@typescript-eslint/interface-name-prefix": "off", diff --git a/.github/workflows/release-to-npm.yaml b/.github/workflows/release-auco.yaml similarity index 100% rename from .github/workflows/release-to-npm.yaml rename to .github/workflows/release-auco.yaml diff --git a/.github/workflows/release-create-auco.yaml b/.github/workflows/release-create-auco.yaml new file mode 100644 index 0000000..217ae67 --- /dev/null +++ b/.github/workflows/release-create-auco.yaml @@ -0,0 +1,112 @@ +name: Version Bump and Publish Create Auco + +on: + pull_request: + types: [closed] + branches: [main] + +jobs: + check_commit: + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + outputs: + SHOULD_RUN: ${{ steps.check_commit.outputs.SHOULD_RUN }} + steps: + - name: Checkout Files + uses: actions/checkout@v4 + with: + token: ${{ secrets.ORG_GITHUB_TOKEN }} + fetch-depth: 0 # Need full git history for logs + + - name: check if skip ci + id: check_commit + run: | + COMMIT_MESSAGE=$(git log -1 --pretty=%B) + if [[ "$COMMIT_MESSAGE" == *"[skip ci]"* ]]; then + echo "SHOULD_RUN=false" >> "$GITHUB_OUTPUT" + else + echo "SHOULD_RUN=true" >> "$GITHUB_OUTPUT" + fi + + version-bump-create-auco: + needs: check_commit + if: ${{ needs.check_commit.outputs.SHOULD_RUN != 'false' }} + runs-on: ubuntu-22.04 + defaults: + run: + working-directory: packages/create-auco + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + token: ${{ secrets.ORG_GITHUB_TOKEN }} + fetch-depth: 0 # Need full git history for version bumping + + - name: Determine version bump type + id: version + run: | + commit_message=$(git log -1 --pretty=%B) + if [[ "$commit_message" == *"[major]"* ]]; then + echo "type=major" >> "$GITHUB_ENV" + elif [[ "$commit_message" == *"[minor]"* ]]; then + echo "type=minor" >> "$GITHUB_ENV" + elif [[ "$commit_message" == *"[prerelease]"* ]]; then + echo "type=prerelease --preid=rc" >> "$GITHUB_ENV" + else + echo "type=patch" >> "$GITHUB_ENV" + fi + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + registry-url: 'https://registry.npmjs.org/' + + - name: Install dependencies + working-directory: . + run: npm install + + - name: Bump version and commit (create-auco) + run: | + # Ensure we are on main and up to date + git reset --hard HEAD + git pull origin main --no-rebase --strategy=ort --no-edit + + # Configure git user + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + + # Bump version for create-auco package + new_version=$(npm version ${{ env.type }} -m "chore(release:create-auco): %s [skip ci]") + echo "NEW_VERSION=${new_version}" >> "$GITHUB_ENV" + + # Push the version bump commit and tag + git push origin main --follow-tags + + - name: Build create-auco + run: npm run build + + - name: Publish create-auco to NPM + run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Notify Slack on Success + if: success() + uses: slackapi/slack-github-action@v1.26.0 + with: + channel-id: ${{ secrets.SLACK_CHANNEL_ID }} + slack-message: '✅ Version bump & publish successful for create-auco: ${{ env.NEW_VERSION }}' + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + + - name: Notify Slack on Failure + if: failure() + uses: slackapi/slack-github-action@v1.26.0 + with: + channel-id: ${{ secrets.SLACK_CHANNEL_ID }} + slack-message: '❌ Version bump or publish failed for create-auco on main: ${{ github.ref_name }}' + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + + diff --git a/.gitignore b/.gitignore index 1d6e6ad..1e0e242 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ node_modules/ -dist +dist/ +**/dist/ .env *.log -*.db \ No newline at end of file +*.db +*.tsbuildinfo +**/*.tsbuildinfo \ No newline at end of file diff --git a/README.md b/README.md index 85f328e..87f89d8 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,61 @@ Read [our documentation](https://scaffoldstark.com/auco) ## Installation -### From npm (Recommended) +### Create a New Project + +The quickest way to create a new Auco project is using the create command: + +```bash +# Using npm (recommended - works once published) +npm create auco + +# Using yarn +yarn create auco + +# Using pnpm +pnpm create auco + +# Using bun +bun create auco + +# Using npx (also works once published) +npx create-auco +``` + +On installation, you'll see a few prompts: +- ✓ What's the name of your project? › my-starknet-indexer +- ✓ Which template would you like to use? › Default +- ✓ Installed packages +- ✓ Initialized git repository + +You can skip prompts by using the `--yes` flag: +```bash +npm create auco my-project --yes +# or +npx create-auco my-project --yes +``` + +**For Local Development/Testing:** + +If you're developing the `create-auco` script locally, you can test it using: + +```bash +# Option 1: Run directly from the built file +node dist/scripts/create-auco.js my-project + +# Option 2: Link the package globally first, then use npx +npm link +npx create-auco my-project + +# Option 3: Use npm create with the local package +npm create auco@file:. +``` + +**Note:** For `npm create auco` / `yarn create auco` to work for end users, the package needs to be published as `create-auco` on npm (in addition to the main `auco` package). + +### Install as a Library + +If you want to use Auco as a library in an existing project: ```bash npm install auco diff --git a/package-lock.json b/package-lock.json index 5074937..4790e57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,27 +1,15 @@ { - "name": "auco", + "name": "auco-workspace", "version": "0.1.7", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "auco", - "version": "0.1.7", + "name": "auco-workspace", "hasInstallScript": true, - "dependencies": { - "@types/better-sqlite3": "^7.6.13", - "abi-wan-kanabi": "2.2.4", - "better-sqlite3": "^12.2.0", - "commander": "^11.1.0", - "mysql2": "^3.14.1", - "patch-package": "^8.0.0", - "pg": "^8.11.0", - "starknet": "7.6.2", - "terminal-size": "^4.0.0" - }, - "bin": { - "abi-downloader": "dist/scripts/download-abi.js" - }, + "workspaces": [ + "packages/*" + ], "devDependencies": { "@types/jest": "^29.5.3", "@types/pg": "^8.10.2", @@ -33,11 +21,18 @@ "husky": "^9.1.7", "jest": "^29.7.0", "lint-staged": "^15.5.2", + "patch-package": "^8.0.0", "prettier": "^3.5.3", "ts-jest": "^29.1.1", "typescript": "^5.0.0" } }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", + "license": "MIT" + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -1377,14 +1372,80 @@ } }, "node_modules/@scure/base": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.1.tgz", - "integrity": "sha512-DGmGtC8Tt63J5GfHgfl5CuAXh96VF/LD8K9Hr/Gv0J2lAoRGlPOMpqMpMbCTOoOJMZCk2Xt+DskdDyn6dEFdzQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", "license": "MIT", "funding": { "url": "https://paulmillr.com/funding/" } }, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@scure/starknet": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@scure/starknet/-/starknet-1.1.0.tgz", @@ -1425,18 +1486,36 @@ "@sinonjs/commons": "^3.0.0" } }, - "node_modules/@starknet-io/starknet-types-07": { + "node_modules/@starknet-io/get-starknet-wallet-standard": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@starknet-io/get-starknet-wallet-standard/-/get-starknet-wallet-standard-5.0.0.tgz", + "integrity": "sha512-isDNGDlp16W24HE4IuweYXLDRZN0JbsDnazAieeKXE87Mn+jqhsjgTsMxcwWTjX7v906Bjz39FiDjGUddnr36g==", + "license": "MIT", + "dependencies": { + "@starknet-io/types-js": "^0.7.10", + "@wallet-standard/base": "^1.1.0", + "@wallet-standard/features": "^1.1.0", + "ox": "^0.4.4" + } + }, + "node_modules/@starknet-io/starknet-types-010": { "name": "@starknet-io/types-js", - "version": "0.7.10", - "resolved": "https://registry.npmjs.org/@starknet-io/types-js/-/types-js-0.7.10.tgz", - "integrity": "sha512-1VtCqX4AHWJlRRSYGSn+4X1mqolI1Tdq62IwzoU2vUuEE72S1OlEeGhpvd6XsdqXcfHmVzYfj8k1XtKBQqwo9w==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@starknet-io/types-js/-/types-js-0.10.0.tgz", + "integrity": "sha512-7ALSydz6pq3YIOpq5a7OkkxqwJciMc9Nlph0OGjhcC3xX0xH30XgizmziLyYVN10oO9+BJk8M9KbJjpzdbtRSw==", "license": "MIT" }, - "node_modules/@starknet-io/starknet-types-08": { + "node_modules/@starknet-io/starknet-types-09": { "name": "@starknet-io/types-js", - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/@starknet-io/types-js/-/types-js-0.8.4.tgz", - "integrity": "sha512-0RZ3TZHcLsUTQaq1JhDSCM8chnzO4/XNsSCozwDET64JK5bjFDIf2ZUkta+tl5Nlbf4usoU7uZiDI/Q57kt2SQ==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@starknet-io/types-js/-/types-js-0.9.2.tgz", + "integrity": "sha512-vWOc0FVSn+RmabozIEWcEny1I73nDGTvOrLYJsR1x7LGA3AZmqt4i/aW69o/3i2NN5CVP8Ok6G1ayRQJKye3Wg==", + "license": "MIT" + }, + "node_modules/@starknet-io/types-js": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@starknet-io/types-js/-/types-js-0.7.10.tgz", + "integrity": "sha512-1VtCqX4AHWJlRRSYGSn+4X1mqolI1Tdq62IwzoU2vUuEE72S1OlEeGhpvd6XsdqXcfHmVzYfj8k1XtKBQqwo9w==", "license": "MIT" }, "node_modules/@types/babel__core": { @@ -1828,10 +1907,32 @@ "dev": true, "license": "ISC" }, + "node_modules/@wallet-standard/base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@wallet-standard/base/-/base-1.1.0.tgz", + "integrity": "sha512-DJDQhjKmSNVLKWItoKThJS+CsJQjR9AOBOirBVT1F9YpRyC9oYHE+ZnSf8y8bxUphtKqdQMPVQ2mHohYdRvDVQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=16" + } + }, + "node_modules/@wallet-standard/features": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@wallet-standard/features/-/features-1.1.0.tgz", + "integrity": "sha512-hiEivWNztx73s+7iLxsuD1sOJ28xtRix58W7Xnz4XzzA/pF0+aicnWgjOdA10doVDEDZdUuZCIIqG96SFNlDUg==", + "license": "Apache-2.0", + "dependencies": { + "@wallet-standard/base": "^1.1.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true, "license": "BSD-2-Clause" }, "node_modules/abi-wan-kanabi": { @@ -1849,6 +1950,27 @@ "generate": "dist/generate.js" } }, + "node_modules/abitype": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz", + "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -1972,11 +2094,16 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, "license": "ISC", "engines": { "node": ">= 4.0.0" } }, + "node_modules/auco": { + "resolved": "packages/auco-indexer", + "link": true + }, "node_modules/aws-ssl-profiles": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", @@ -2132,6 +2259,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, "license": "MIT" }, "node_modules/base64-js": { @@ -2202,6 +2330,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -2302,6 +2431,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.0", @@ -2320,6 +2450,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2333,6 +2464,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -2419,6 +2551,7 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, "funding": [ { "type": "github", @@ -2606,6 +2739,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, "license": "MIT" }, "node_modules/convert-source-map": { @@ -2615,6 +2749,10 @@ "dev": true, "license": "MIT" }, + "node_modules/create-auco": { + "resolved": "packages/create-auco", + "link": true + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -2657,6 +2795,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -2745,6 +2884,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -2813,6 +2953,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -2901,6 +3042,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2910,6 +3052,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2919,6 +3062,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -3237,7 +3381,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "dev": true, "license": "MIT" }, "node_modules/execa": { @@ -3423,6 +3566,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -3452,6 +3596,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "micromatch": "^4.0.2" @@ -3503,6 +3648,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, "license": "ISC" }, "node_modules/fsevents": { @@ -3524,6 +3670,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3574,6 +3721,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -3608,6 +3756,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -3641,6 +3790,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -3674,6 +3824,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -3684,6 +3835,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -3712,6 +3864,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3737,6 +3890,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3746,6 +3900,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -3758,6 +3913,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3770,6 +3926,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -3905,6 +4062,7 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -3934,6 +4092,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, "license": "MIT", "bin": { "is-docker": "cli.js" @@ -3995,6 +4154,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -4033,6 +4193,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, "license": "MIT", "dependencies": { "is-docker": "^2.0.0" @@ -4045,12 +4206,14 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -5049,6 +5212,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz", "integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -5100,6 +5264,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "dev": true, "license": "Public Domain", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5119,6 +5284,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.1.11" @@ -5534,9 +5700,9 @@ "license": "Apache-2.0" }, "node_modules/lossless-json": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lossless-json/-/lossless-json-4.1.1.tgz", - "integrity": "sha512-HusN80C0ohtT9kOHQH7EuUaqzRQsnekpa+2ot8OzvW0iC08dq/YtM/7uKwwajldQsCrHyC8q9fz3t3L+TmDltA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lossless-json/-/lossless-json-4.3.0.tgz", + "integrity": "sha512-ToxOC+SsduRmdSuoLZLYAr5zy1Qu7l5XhmPWM3zefCZ5IcrzW/h108qbJUKfOlDlhvhjUK84+8PSVX0kxnit0g==", "license": "MIT" }, "node_modules/lru-cache": { @@ -5601,6 +5767,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5627,6 +5794,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -5816,6 +5984,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5850,6 +6019,7 @@ "version": "7.4.2", "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, "license": "MIT", "dependencies": { "is-docker": "^2.0.0", @@ -5884,11 +6054,41 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/ox": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.4.4.tgz", + "integrity": "sha512-oJPEeCDs9iNiPs6J0rTx+Y0KGeCGyCAA3zo94yZhm8G5WpOxrwUtn2Ie/Y8IyARSqqY/j9JTKA3Fc1xs1DvFnw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.10.1", + "@noble/curves": "^1.6.0", + "@noble/hashes": "^1.5.0", + "@scure/bip32": "^1.5.0", + "@scure/bip39": "^1.4.0", + "abitype": "^1.0.6", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -5973,6 +6173,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz", "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==", + "dev": true, "license": "MIT", "dependencies": { "@yarnpkg/lockfile": "^1.1.0", @@ -6003,6 +6204,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -6018,6 +6220,7 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, "license": "MIT", "dependencies": { "at-least-node": "^1.0.0", @@ -6034,6 +6237,7 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, "license": "ISC", "dependencies": { "glob": "^7.1.3" @@ -6046,6 +6250,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -6065,6 +6270,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6074,6 +6280,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6187,6 +6394,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -6784,6 +6992,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -6801,6 +7010,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -6813,6 +7023,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6987,19 +7198,20 @@ } }, "node_modules/starknet": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/starknet/-/starknet-7.6.2.tgz", - "integrity": "sha512-IoXUtzrtG+IPfvnZzIYcbp2lDSCb8VKFyOtgvuJhlfRWUJDxbe27ZAsXJfo9rSFS7kbKny5KdiZgO6RlxcmXvg==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/starknet/-/starknet-9.3.0.tgz", + "integrity": "sha512-XtdeESdcy4nXDCFyCqowjGENiBpD5i6ClgvYDqnpvx3SW5PXtKVHRUQVcdkJ6ckz/tQFaH1a1zi7x8ObOoIBdA==", "license": "MIT", "dependencies": { - "@noble/curves": "1.7.0", - "@noble/hashes": "1.6.0", - "@scure/base": "1.2.1", + "@noble/curves": "~1.7.0", + "@noble/hashes": "~1.6.0", + "@scure/base": "~1.2.1", "@scure/starknet": "1.1.0", - "@starknet-io/starknet-types-07": "npm:@starknet-io/types-js@~0.7.10", - "@starknet-io/starknet-types-08": "npm:@starknet-io/types-js@~0.8.4", + "@starknet-io/get-starknet-wallet-standard": "^5.0.0", + "@starknet-io/starknet-types-010": "npm:@starknet-io/types-js@0.10.0", + "@starknet-io/starknet-types-09": "npm:@starknet-io/types-js@~0.9.1", "abi-wan-kanabi": "2.2.4", - "lossless-json": "^4.0.1", + "lossless-json": "^4.2.0", "pako": "^2.0.4", "ts-mixer": "^6.0.3" }, @@ -7137,6 +7349,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -7263,6 +7476,7 @@ "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, "license": "MIT", "dependencies": { "os-tmpdir": "~1.0.2" @@ -7282,6 +7496,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -7427,7 +7642,7 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "peer": true, "bin": { @@ -7529,6 +7744,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -7651,6 +7867,7 @@ "version": "2.8.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "dev": true, "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -7746,6 +7963,32 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "packages/auco-indexer": { + "name": "auco", + "version": "0.1.7", + "dependencies": { + "@types/better-sqlite3": "^7.6.13", + "abi-wan-kanabi": "2.2.4", + "better-sqlite3": "^12.2.0", + "commander": "^11.1.0", + "mysql2": "^3.14.1", + "pg": "^8.11.0", + "starknet": "9.3.0", + "terminal-size": "^4.0.0" + }, + "bin": { + "auco-download-abi": "dist/scripts/download-abi.js" + } + }, + "packages/create-auco": { + "version": "0.1.7", + "dependencies": { + "commander": "^11.1.0" + }, + "bin": { + "create-auco": "dist/create-auco.js" + } } } } diff --git a/package.json b/package.json index c95d826..739af0b 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,17 @@ { - "name": "auco", - "version": "0.1.7", - "main": "dist/index.js", - "types": "dist/index.d.ts", + "name": "auco-workspace", + "private": true, + "workspaces": [ + "packages/*" + ], "scripts": { "chain": "starknet-devnet --state-archive-capacity full --seed 0", - "build": "tsc --skipLibCheck", + "build": "npm -ws run build --if-present", + "test": "npm -ws run test --if-present", "lint": "eslint . --ext .ts", "lint:fix": "eslint . --ext .ts --fix", "format": "prettier --write \"**/*.{ts,js,json,md}\"", - "postinstall": "npx patch-package", - "test": "jest --passWithNoTests --forceExit --config ./jest.config.js" + "postinstall": "npx patch-package" }, "overrides": { "chalk": "4.1.2", @@ -22,18 +23,8 @@ "error-ex": "1.3.4", "has-ansi": "6.0.2" }, - "dependencies": { - "commander": "^11.1.0", - "@types/better-sqlite3": "^7.6.13", - "abi-wan-kanabi": "2.2.4", - "better-sqlite3": "^12.2.0", - "mysql2": "^3.14.1", - "patch-package": "^8.0.0", - "pg": "^8.11.0", - "starknet": "7.6.2", - "terminal-size": "^4.0.0" - }, "devDependencies": { + "patch-package": "^8.0.0", "@types/jest": "^29.5.3", "@types/pg": "^8.10.2", "@typescript-eslint/eslint-plugin": "^8.32.0", @@ -56,8 +47,5 @@ "*.{json,md}": [ "prettier --write" ] - }, - "bin": { - "abi-downloader": "./dist/scripts/download-abi.js" } } diff --git a/packages/auco-indexer/LICENSE b/packages/auco-indexer/LICENSE new file mode 100644 index 0000000..d77f0d7 --- /dev/null +++ b/packages/auco-indexer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Auco Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/auco-indexer/README.md b/packages/auco-indexer/README.md new file mode 100644 index 0000000..87f89d8 --- /dev/null +++ b/packages/auco-indexer/README.md @@ -0,0 +1,398 @@ +[![CI](https://github.com/Quantum3-Labs/auco/actions/workflows/ci.yaml/badge.svg)](https://github.com/Quantum3-Labs/auco/actions/workflows/ci.yaml) +[![npm version](https://badge.fury.io/js/auco.svg)](https://badge.fury.io/js/auco) +[![TypeScript](https://img.shields.io/badge/TypeScript-5.0-blue.svg)](https://www.typescriptlang.org/) +[![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE) + +# Auco + +A TypeScript/Node.js indexer for Starknet events, supporting PostgreSQL, SQLite, and real-time event handling via WebSocket and RPC. + +Read [our documentation](https://scaffoldstark.com/auco) + +## Features + +- 🔄 **Real-time Event Indexing**: Listen to Starknet events from specified contracts +- 💾 **Data Persistence**: Store events and block data in PostgreSQL +- 🔄 **Chain Reorg Handling**: Detect and handle blockchain reorganizations gracefully +- 🔌 **Extensible Architecture**: Register custom event handlers for any contract +- 🛡️ **Type Safety**: Full TypeScript support with type-safe event handling +- 🔄 **Retry Logic**: Automatic retry mechanisms with exponential backoff +- 📊 **Historical Processing**: Process historical blocks with seamless real-time transition +- 🔌 **Multiple DB Support**: Supports PostgreSQL, SQLite, and MySQL out of the box. +- ⚡ **High Performance**: Optimized for speed with parallel processing and efficient data handling + +## Installation + +### Create a New Project + +The quickest way to create a new Auco project is using the create command: + +```bash +# Using npm (recommended - works once published) +npm create auco + +# Using yarn +yarn create auco + +# Using pnpm +pnpm create auco + +# Using bun +bun create auco + +# Using npx (also works once published) +npx create-auco +``` + +On installation, you'll see a few prompts: +- ✓ What's the name of your project? › my-starknet-indexer +- ✓ Which template would you like to use? › Default +- ✓ Installed packages +- ✓ Initialized git repository + +You can skip prompts by using the `--yes` flag: +```bash +npm create auco my-project --yes +# or +npx create-auco my-project --yes +``` + +**For Local Development/Testing:** + +If you're developing the `create-auco` script locally, you can test it using: + +```bash +# Option 1: Run directly from the built file +node dist/scripts/create-auco.js my-project + +# Option 2: Link the package globally first, then use npx +npm link +npx create-auco my-project + +# Option 3: Use npm create with the local package +npm create auco@file:. +``` + +**Note:** For `npm create auco` / `yarn create auco` to work for end users, the package needs to be published as `create-auco` on npm (in addition to the main `auco` package). + +### Install as a Library + +If you want to use Auco as a library in an existing project: + +```bash +npm install auco +``` + +### From source + +```bash +git clone https://github.com/Quantum3-Labs/auco.git +cd auco +npm install +npm run build +``` + +## Requirements + +- **Node.js**: Version 18 or higher +- **PostgreSQL**: Version 12 or higher (can be switched with SQLite or MySQL) +- **WebSocket endpoint**: Starknet node with WebSocket support (e.g., Infura, local node with WS enabled) +- **Starknet node spec version 0.8 or above**: Compatible with Starknet nodes running spec version 0.8+ +- **How to install a Starknet node?** See the quick guide in [CONTRIBUTING.md](CONTRIBUTING.md#prerequisites). + +## Quick Start + +### PostgreSQL Example + +See [`example/index.ts`](./example/index.ts) for a PostgreSQL-based example. + +```typescript +import { StarknetIndexer, LogLevel } from 'auco'; +import { abi as myContractAbi } from './myContractAbi'; // Provide your contract ABI + +const indexer = new StarknetIndexer({ + rpcNodeUrl: 'https://starknet-mainnet.infura.io/v3/YOUR_KEY', + wsNodeUrl: 'wss://starknet-mainnet.infura.io/ws/v3/YOUR_KEY', + database: { + type: 'postgres', + config: { + connectionString: 'postgresql://user:password@localhost:5432/mydb', + }, + }, + logLevel: LogLevel.INFO, +}); + +indexer.onEvent({ + contractAddress: '0x...', + abi: myContractAbi, + eventName: 'Transfer', + handler: async (event, client, indexer) => { + console.log('Received event:', event); + // Custom logic here + }, +}); + +indexer.start(); +``` + +### SQLite Example + +1. **Configure your environment**: + - Edit `example/sqlite.ts` with your node URLs and contract address + - Update your database config to SQLite + + ```typescript + import Database from 'better-sqlite3'; + + const indexer = new StarknetIndexer({ + rpcNodeUrl: 'https://starknet-sepolia-rpc.publicnode.com', + wsNodeUrl: 'wss://starknet-sepolia-rpc.publicnode.com', + database: { + type: 'sqlite', + config: { + dbInstance: new Database('starknet_indexer.db'), + }, + }, + logLevel: LogLevel.INFO, + startingBlockNumber: 'latest', + }); + ``` + +2. **Run the example**: + ```bash + npx ts-node example/sqlite.ts + ``` + +### MySQL Example + +1. **Configure your environment**: + - Edit `example/mysql.ts` with your node URLs and contract address + - Update your database config to MySQL + + ```typescript + const indexer = new StarknetIndexer({ + rpcNodeUrl: 'https://starknet-sepolia-rpc.publicnode.com', + wsNodeUrl: 'wss://starknet-sepolia-rpc.publicnode.com', + database: { + type: 'mysql', + config: { + connectionString: 'mysql://root:root@localhost:3306/starknet_indexer', + }, + }, + logLevel: LogLevel.INFO, + startingBlockNumber: 'latest', + }); + ``` + +2. **Run the example**: + ```bash + npx ts-node example/mysql.ts + ``` + +## ABI Downloader CLI + +You can download contract ABIs directly from Starknet nodes using the built-in CLI script. This is useful for generating TypeScript ABI files for your project. + +### Usage + +Run the script with `npx` (recommended) or directly with `ts-node`/`node`: + +#### Single Contract + +Download the ABI for a single contract: + +```bash +npx ts-node src/scripts/download-abi.ts single \ + --rpc-url \ + --address \ + --name \ + [--output ] +``` + +- `--rpc-url` (required): Starknet RPC endpoint URL +- `--address` (required): Contract address +- `--name` (required): Name for the generated ABI file (no extension) +- `--output` (optional): Output directory (default: `generated/abis`) + +**Example:** +```bash +npx ts-node src/scripts/download-abi.ts single \ + --rpc-url https://starknet-mainnet.infura.io/v3/YOUR_KEY \ + --address 0x123...abc \ + --name MyContract +``` + +#### Batch Mode + +Download ABIs for multiple contracts defined in a JSON file: + +1. Create a JSON file (e.g., `contracts.json`) with the following format: + ```json + { + "MyContract": "0x123...abc", + "AnotherContract": "0x456...def" + } + ``` + +2. Run: + ```bash + npx ts-node src/scripts/download-abi.ts batch \ + --rpc-url \ + --file contracts.json \ + [--output ] + ``` + +- `--file` (required): Path to the JSON file mapping contract names to addresses +- Other options as above + +**Example:** +```bash +npx ts-node src/scripts/download-abi.ts batch \ + --rpc-url https://starknet-mainnet.infura.io/v3/YOUR_KEY \ + --file contracts.json +``` + +The script will generate TypeScript ABI files in the specified output directory and create an `index.ts` exporting all ABIs. + +--- + +## Performance + +AUCO is designed for high-performance event indexing with optimized parallel processing and efficient data handling. Our benchmarks demonstrate significant performance advantages: + +### Benchmark Results: Transfer Events on Starknet Mainnet + +| Metric | AUCO | APIBARA | Improvement | +|--------|------|---------|-------------| +| **Sync Time** | 147.63s | 270.50s | **83% faster** | +| **Events Processed** | 159,885 | 152,604 | **4.8% more events** | + +**Benchmark Parameters:** +- **Block Range**: 1,500,000 to 1,505,000 (5,000 blocks) +- **Event Type**: Starknet Transfer events +- **Total Events**: ~150,000 events processed +- **Device**: MacBook M1 Pro (16GB RAM) + +## Configuration + +| Option | Type | Required | Description | +| --------------------- | -------------------- | -------- | ------------------------------------------------------------- | +| `rpcNodeUrl` | `string` | ✅ | Starknet RPC endpoint | +| `wsNodeUrl` | `string` | ✅ | Starknet WebSocket endpoint | +| `database` | `object` | ✅ | Database configuration object with type and config properties | +| `logLevel` | `LogLevel` | ❌ | Log verbosity (DEBUG, INFO, WARN, ERROR) | +| `startingBlockNumber` | `number \| 'latest'` | ❌ | Starting block number for indexing | + +## Handling Chain Reorgs + +The indexer automatically detects and handles chain reorganizations: + +```typescript +indexer.onReorg({ + handler: async (forkedBlock) => { + console.log(`Reorg detected at block ${forkedBlock.block_number}`); + // Implement your reorg handling logic + // The indexer automatically rolls back non-canonical data + }, +}); +``` + +**Important**: You must implement your own business logic for handling reorgs. The indexer provides the hook and basic database cleanup. + +## Development + +### Running Tests + +```bash +npm test +``` + +### Code Quality + +```bash +npm run lint # Check code style +npm run lint:fix # Fix code style issues +npm run format # Format code with Prettier +``` + +### Building + +```bash +npm run build # Compile TypeScript +``` + +## Roadmap + +### 📋 Planned + +- [x] Additional database (MongoDB, MySQL, etc.) ✅ +- [ ] Built-in monitoring and health checks +- [ ] Advanced caching layer +- [ ] Docker containerization +- [ ] Event filtering by multiple criteria +- [ ] Instant event processing via WebSocket + +## Contributing + +We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details. + +### Development Setup + +1. **Fork and clone** the repository +2. **Install dependencies**: + + ```bash + npm install + ``` + +3. **Setup development environment**: + + ```bash + # Start local PostgreSQL (if using Docker) + docker run --name postgres-dev -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres:13 + + # Start local Starknet devnet + npm run chain + ``` + +4. **Run tests**: + + ```bash + npm test + ``` + +5. **Submit a pull request** with your changes + +### Code Style + +- Use TypeScript for all new code +- Follow the existing code style (enforced by ESLint and Prettier) +- Write tests for new functionality +- Update documentation as needed + +## Troubleshooting + +### Common Issues + +| Issue | Solution | +| ---------------------------- | ----------------------------------------------- | +| PostgreSQL connection failed | Ensure PostgreSQL is running and accessible | +| WebSocket connection failed | Verify your node supports WebSocket connections | +| ABI parsing errors | Check your ABI file and event names | +| Memory usage high | Consider processing events in smaller batches | + +### Getting Help + +- 📖 **Documentation**: Check the [API Reference](docs/API.md) +- 🐛 **Issues**: Report bugs on [GitHub Issues](https://github.com/Quantum3-Labs/auco/issues) +- 💬 **Discussions**: Join our [GitHub Discussions](https://github.com/Quantum3-Labs/auco/discussions) + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## Acknowledgments + +- Built with [Starknet.js](https://github.com/0xs34n/starknet.js) +- Type-safe ABI parsing with [abi-wan-kanabi](https://github.com/keep-starknet-strange/abi-wan-kanabi) +- Database operations with [node-postgres](https://github.com/brianc/node-postgres), [mysql2](https://github.com/sidorares/node-mysql2), and [better-sqlite3](https://github.com/WiseLibs/better-sqlite3) diff --git a/example/abi/ABI.ts b/packages/auco-indexer/example/abi/ABI.ts similarity index 100% rename from example/abi/ABI.ts rename to packages/auco-indexer/example/abi/ABI.ts diff --git a/example/index.ts b/packages/auco-indexer/example/index.ts similarity index 95% rename from example/index.ts rename to packages/auco-indexer/example/index.ts index eac3144..3d14366 100644 --- a/example/index.ts +++ b/packages/auco-indexer/example/index.ts @@ -16,6 +16,9 @@ const indexer = new StarknetIndexer({ logLevel: LogLevel.INFO, startingBlockNumber: 'latest', + devMode: { + resetOnStart: true, + } }); indexer.onEvent({ diff --git a/example/mysql.ts b/packages/auco-indexer/example/mysql.ts similarity index 100% rename from example/mysql.ts rename to packages/auco-indexer/example/mysql.ts diff --git a/example/reorg.ts b/packages/auco-indexer/example/reorg.ts similarity index 93% rename from example/reorg.ts rename to packages/auco-indexer/example/reorg.ts index b646b54..dfba770 100644 --- a/example/reorg.ts +++ b/packages/auco-indexer/example/reorg.ts @@ -102,10 +102,16 @@ async function runReorgExample(): Promise { }); // Set up contract with first devnet account - const strkContract = new Contract(universalErc20Abi, CONFIG.CONTRACT_ADDRESS, provider); - strkContract.connect( - new Account(provider, CONFIG.DEVNET_ACCOUNT_1.ADDRESS, CONFIG.DEVNET_ACCOUNT_1.PRIVATE_KEY) - ); + const account = new Account({ + provider, + address: CONFIG.DEVNET_ACCOUNT_1.ADDRESS, + signer: CONFIG.DEVNET_ACCOUNT_1.PRIVATE_KEY, + }); + const strkContract = new Contract({ + abi: universalErc20Abi, + address: CONFIG.CONTRACT_ADDRESS, + providerOrAccount: account, + }); // Perform initial transfers console.log('Performing initial transfers...'); diff --git a/example/sqlite.ts b/packages/auco-indexer/example/sqlite.ts similarity index 100% rename from example/sqlite.ts rename to packages/auco-indexer/example/sqlite.ts diff --git a/jest.config.js b/packages/auco-indexer/jest.config.js similarity index 100% rename from jest.config.js rename to packages/auco-indexer/jest.config.js diff --git a/jest.setup.js b/packages/auco-indexer/jest.setup.js similarity index 100% rename from jest.setup.js rename to packages/auco-indexer/jest.setup.js diff --git a/packages/auco-indexer/package.json b/packages/auco-indexer/package.json new file mode 100644 index 0000000..fcb6258 --- /dev/null +++ b/packages/auco-indexer/package.json @@ -0,0 +1,30 @@ +{ + "name": "auco", + "version": "0.1.7", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc -p tsconfig.json --skipLibCheck", + "test": "jest --passWithNoTests --forceExit --config ./jest.config.js" + }, + "dependencies": { + "@types/better-sqlite3": "^7.6.13", + "abi-wan-kanabi": "2.2.4", + "better-sqlite3": "^12.2.0", + "commander": "^11.1.0", + "mysql2": "^3.14.1", + "pg": "^8.11.0", + "starknet": "9.3.0", + "terminal-size": "^4.0.0" + }, + "bin": { + "auco-download-abi": "./dist/scripts/download-abi.js" + }, + "files": [ + "dist", + "README.md", + "LICENSE" + ] +} + + diff --git a/src/core/__tests__/indexer.test.ts b/packages/auco-indexer/src/core/__tests__/indexer.test.ts similarity index 100% rename from src/core/__tests__/indexer.test.ts rename to packages/auco-indexer/src/core/__tests__/indexer.test.ts diff --git a/src/core/indexer.ts b/packages/auco-indexer/src/core/indexer.ts similarity index 96% rename from src/core/indexer.ts rename to packages/auco-indexer/src/core/indexer.ts index 49d85c3..3b9e293 100644 --- a/src/core/indexer.ts +++ b/packages/auco-indexer/src/core/indexer.ts @@ -8,6 +8,7 @@ import { EmittedEvent, WebSocketChannel, Subscription, + createAbiParser, } from 'starknet'; import { @@ -91,7 +92,7 @@ export class StarknetIndexer { } try { - this.provider = new RpcProvider({ nodeUrl: config.rpcNodeUrl, specVersion: '0.8.1' }); + this.provider = new RpcProvider({ nodeUrl: config.rpcNodeUrl }); } catch (error) { this.logger.error('Failed to initialize RPC provider:', error); } @@ -100,6 +101,16 @@ export class StarknetIndexer { this.MAX_HISTORICAL_BLOCK_CONCURRENT_REQUESTS = config.maxHistoricalBlockConcurrentRequests ?? 5; + // Warn if dev mode reset is enabled + if (config.devMode?.resetOnStart) { + this.logger.warn( + '⚠️ WARNING: Development mode reset is enabled! All indexed data (blocks, events, and cursor state) will be deleted on indexer start.' + ); + this.logger.warn( + '⚠️ This should ONLY be used in development. Do NOT enable this in production!' + ); + } + this.setupEventHandlers(); } @@ -313,6 +324,16 @@ export class StarknetIndexer { // in blocks table, composite primary key with both number and hash await this.dbHandler.initializeDb(); + // Reset indexer state if dev mode reset is enabled + if (this.config.devMode?.resetOnStart) { + this.logger.warn('🔄 Resetting indexer state (dev mode: resetOnStart enabled)...'); + await this.dbHandler.resetIndexerState(this.config.cursorKey); + this.logger.info( + '✅ Indexer state reset complete. All blocks, events, and cursor state have been cleared.' + ); + this.hasExistingState = false; + } + const result = await this.dbHandler.getIndexerState(this.config.cursorKey); if (!result) { @@ -926,6 +947,7 @@ export class StarknetIndexer { block_number: event.block_number, block_hash: event.block_hash || '', transaction_hash: event.transaction_hash, + transaction_index: event.transaction_index ?? 0, from_address: fromAddress, event_index: eventIndex, keys: event.keys, @@ -954,8 +976,15 @@ export class StarknetIndexer { const abiEvents = events.getAbiEvents(abi); const abiStructs = CallData.getAbiStruct(abi); const abiEnums = CallData.getAbiEnum(abi); - - const parsedEvents = events.parseEvents([eventObj], abiEvents, abiStructs, abiEnums); + const parser = createAbiParser(abi); + + const parsedEvents = events.parseEvents( + [eventObj], + abiEvents, + abiStructs, + abiEnums, + parser + ); if (parsedEvents && parsedEvents.length > 0) { // Get the first key of the parsed event (the event name) @@ -966,6 +995,7 @@ export class StarknetIndexer { block_number: eventObj.block_number, block_hash: eventObj.block_hash, transaction_hash: eventObj.transaction_hash, + transaction_index: eventObj.transaction_index, from_address: fromAddress, event_index: eventObj.event_index, keys: eventObj.keys, @@ -975,7 +1005,7 @@ export class StarknetIndexer { parsedEvent = parsedEventWithOriginal; // this.logger.debug(`Parsed event values:`, parsedValues); const eventName = eventKey; - this.progressStats.updateEvent(event.block_number, eventName, fromAddress); + this.progressStats.updateEvent(event.block_number ?? 0, eventName, fromAddress); } } catch (error) { this.logger.error(`Error parsing event from contract ${fromAddress}:`, error); diff --git a/src/core/progressStats.ts b/packages/auco-indexer/src/core/progressStats.ts similarity index 100% rename from src/core/progressStats.ts rename to packages/auco-indexer/src/core/progressStats.ts diff --git a/src/index.ts b/packages/auco-indexer/src/index.ts similarity index 87% rename from src/index.ts rename to packages/auco-indexer/src/index.ts index 0020f68..e6e646d 100644 --- a/src/index.ts +++ b/packages/auco-indexer/src/index.ts @@ -1,9 +1,8 @@ export { StarknetIndexer } from './core/indexer'; -export { - LogLevel, +export { LogLevel, ConsoleLogger } from './types/indexer'; +export type { Logger, - ConsoleLogger, IndexerConfig, StarknetEvent, EventHandler, diff --git a/src/scripts/download-abi.ts b/packages/auco-indexer/src/scripts/download-abi.ts similarity index 96% rename from src/scripts/download-abi.ts rename to packages/auco-indexer/src/scripts/download-abi.ts index 9183562..47a80f4 100644 --- a/src/scripts/download-abi.ts +++ b/packages/auco-indexer/src/scripts/download-abi.ts @@ -18,7 +18,6 @@ async function downloadAbi(options: DownloadAbiOptions) { // Initialize RPC provider const provider = new RpcProvider({ nodeUrl: rpcUrl, - specVersion: '0.8.1', }); try { @@ -94,7 +93,10 @@ async function downloadAbisFromFile(rpcUrl: string, contractsFile: string, outpu } // CLI setup -program.name('abi-downloader').description('Download ABIs from Starknet contracts').version('1.0.0'); +program + .name('abi-downloader') + .description('Download ABIs from Starknet contracts') + .version('1.0.0'); program .command('single') diff --git a/src/types/abi-wan-helpers.ts b/packages/auco-indexer/src/types/abi-wan-helpers.ts similarity index 100% rename from src/types/abi-wan-helpers.ts rename to packages/auco-indexer/src/types/abi-wan-helpers.ts diff --git a/src/types/db-handler.ts b/packages/auco-indexer/src/types/db-handler.ts similarity index 94% rename from src/types/db-handler.ts rename to packages/auco-indexer/src/types/db-handler.ts index 80e6d76..96d444f 100644 --- a/src/types/db-handler.ts +++ b/packages/auco-indexer/src/types/db-handler.ts @@ -1,4 +1,4 @@ -import Database from "better-sqlite3"; +import Database from 'better-sqlite3'; export interface BlockData { block_number: number; diff --git a/src/types/indexer.ts b/packages/auco-indexer/src/types/indexer.ts similarity index 92% rename from src/types/indexer.ts rename to packages/auco-indexer/src/types/indexer.ts index 747d24f..2a44f99 100644 --- a/src/types/indexer.ts +++ b/packages/auco-indexer/src/types/indexer.ts @@ -110,6 +110,19 @@ export interface IndexerConfig { /** Enable UI progress bar for tracking indexer progress */ enableUiProgress?: boolean; + + /** + * Development mode options + * WARNING: These options are for development only and will clear all indexed data! + */ + devMode?: { + /** + * If true, reset all indexed data (blocks, events, and cursor state) on indexer start. + * This is useful for development to quickly reset state without manually clearing the database. + * WARNING: This will delete all indexed data! + */ + resetOnStart?: boolean; + }; } export type StarknetEvent = { diff --git a/src/ui/patch.ts b/packages/auco-indexer/src/ui/patch.ts similarity index 100% rename from src/ui/patch.ts rename to packages/auco-indexer/src/ui/patch.ts diff --git a/src/ui/progress.ts b/packages/auco-indexer/src/ui/progress.ts similarity index 100% rename from src/ui/progress.ts rename to packages/auco-indexer/src/ui/progress.ts diff --git a/src/utils/__tests__/blockUtils.test.ts b/packages/auco-indexer/src/utils/__tests__/blockUtils.test.ts similarity index 98% rename from src/utils/__tests__/blockUtils.test.ts rename to packages/auco-indexer/src/utils/__tests__/blockUtils.test.ts index 61cb43d..865aec0 100644 --- a/src/utils/__tests__/blockUtils.test.ts +++ b/packages/auco-indexer/src/utils/__tests__/blockUtils.test.ts @@ -5,7 +5,6 @@ describe('findContractDeploymentBlock', () => { // Use public Starknet Sepolia RPC endpoint for testing const provider = new RpcProvider({ nodeUrl: 'https://starknet-sepolia-rpc.publicnode.com', - specVersion: '0.8.1', }); // Well-known contract on Sepolia testnet diff --git a/src/utils/blockUtils.ts b/packages/auco-indexer/src/utils/blockUtils.ts similarity index 97% rename from src/utils/blockUtils.ts rename to packages/auco-indexer/src/utils/blockUtils.ts index 8636342..823417c 100644 --- a/src/utils/blockUtils.ts +++ b/packages/auco-indexer/src/utils/blockUtils.ts @@ -1,4 +1,4 @@ -import { BlockNumber, RpcProvider } from 'starknet'; +import { RpcProvider } from 'starknet'; type BlockRange = { from: number; to: number }; diff --git a/src/utils/db/base-db-handler.ts b/packages/auco-indexer/src/utils/db/base-db-handler.ts similarity index 86% rename from src/utils/db/base-db-handler.ts rename to packages/auco-indexer/src/utils/db/base-db-handler.ts index ef5a733..60b660a 100644 --- a/src/utils/db/base-db-handler.ts +++ b/packages/auco-indexer/src/utils/db/base-db-handler.ts @@ -38,4 +38,10 @@ export abstract class BaseDbHandler { abstract cleanup(): Promise; abstract healthCheck(): Promise; + + /** + * Reset all indexer state by clearing blocks, events, and indexer state. + * WARNING: This will delete all indexed data! + */ + abstract resetIndexerState(cursorKey?: string): Promise; } diff --git a/src/utils/db/helpers/initialize-db-handler.ts b/packages/auco-indexer/src/utils/db/helpers/initialize-db-handler.ts similarity index 100% rename from src/utils/db/helpers/initialize-db-handler.ts rename to packages/auco-indexer/src/utils/db/helpers/initialize-db-handler.ts diff --git a/src/utils/db/mysql-db-handler.ts b/packages/auco-indexer/src/utils/db/mysql-db-handler.ts similarity index 95% rename from src/utils/db/mysql-db-handler.ts rename to packages/auco-indexer/src/utils/db/mysql-db-handler.ts index 8690ea9..32464aa 100644 --- a/src/utils/db/mysql-db-handler.ts +++ b/packages/auco-indexer/src/utils/db/mysql-db-handler.ts @@ -366,4 +366,20 @@ export class MysqlDbHandler extends BaseDbHandler { throw error; } } + + async resetIndexerState(cursorKey?: string): Promise { + if (!this.connection) { + throw new Error('Database connection not initialized'); + } + + // Delete all blocks (events will be deleted via CASCADE) + await this.execute('DELETE FROM blocks'); + + // Delete indexer state for the given cursor key + if (cursorKey) { + await this.execute('DELETE FROM indexer_state WHERE cursor_key = ?', [cursorKey]); + } else { + await this.execute('DELETE FROM indexer_state'); + } + } } diff --git a/src/utils/db/postgres-db-handler.ts b/packages/auco-indexer/src/utils/db/postgres-db-handler.ts similarity index 94% rename from src/utils/db/postgres-db-handler.ts rename to packages/auco-indexer/src/utils/db/postgres-db-handler.ts index e8e517e..cb01a8a 100644 --- a/src/utils/db/postgres-db-handler.ts +++ b/packages/auco-indexer/src/utils/db/postgres-db-handler.ts @@ -335,4 +335,20 @@ export class PostgresDbHandler extends BaseDbHandler { throw error; } } + + async resetIndexerState(cursorKey?: string): Promise { + if (!this.client) { + throw new Error('Database client not initialized'); + } + + // Delete all blocks (events will be deleted via CASCADE) + await this.client.query('DELETE FROM blocks'); + + // Delete indexer state for the given cursor key + if (cursorKey) { + await this.client.query('DELETE FROM indexer_state WHERE cursor_key = $1', [cursorKey]); + } else { + await this.client.query('DELETE FROM indexer_state'); + } + } } diff --git a/src/utils/db/sqlite-db-handler.ts b/packages/auco-indexer/src/utils/db/sqlite-db-handler.ts similarity index 95% rename from src/utils/db/sqlite-db-handler.ts rename to packages/auco-indexer/src/utils/db/sqlite-db-handler.ts index 2495654..005fd6a 100644 --- a/src/utils/db/sqlite-db-handler.ts +++ b/packages/auco-indexer/src/utils/db/sqlite-db-handler.ts @@ -374,4 +374,21 @@ export class SqliteDbHandler extends BaseDbHandler { throw error; } } + + async resetIndexerState(cursorKey?: string): Promise { + if (!this.db) { + throw new Error('Database not initialized'); + } + + // Delete all blocks (events will be deleted via CASCADE) + this.db.exec('DELETE FROM blocks'); + + // Delete indexer state for the given cursor key + if (cursorKey) { + const stmt = this.db.prepare('DELETE FROM indexer_state WHERE cursor_key = ?'); + stmt.run(cursorKey); + } else { + this.db.exec('DELETE FROM indexer_state'); + } + } } diff --git a/test-utils/constants.ts b/packages/auco-indexer/test-utils/constants.ts similarity index 100% rename from test-utils/constants.ts rename to packages/auco-indexer/test-utils/constants.ts diff --git a/packages/auco-indexer/tsconfig.json b/packages/auco-indexer/tsconfig.json new file mode 100644 index 0000000..6280b84 --- /dev/null +++ b/packages/auco-indexer/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "node16", + "moduleResolution": "node16", + "declaration": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "noImplicitAny": false, + "resolveJsonModule": true, + "composite": true, + "isolatedModules": true + }, + "include": ["src/**/*"], + "exclude": [ + "node_modules", + "dist", + "test-utils/**/*", + "**/__tests__/**/*", + "**/*.test.ts", + "**/*.spec.ts" + ] +} diff --git a/packages/create-auco/LICENSE b/packages/create-auco/LICENSE new file mode 100644 index 0000000..d77f0d7 --- /dev/null +++ b/packages/create-auco/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Auco Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/create-auco/README.md b/packages/create-auco/README.md new file mode 100644 index 0000000..17118c4 --- /dev/null +++ b/packages/create-auco/README.md @@ -0,0 +1,19 @@ +# create-auco + +Scaffold a new Auco-based Starknet indexer project. + +## Usage + +```bash +npm create auco +# or +npx create-auco +``` + +Skip prompts: + +```bash +npm create auco my-project --yes +``` + + diff --git a/packages/create-auco/package.json b/packages/create-auco/package.json new file mode 100644 index 0000000..0ae1f58 --- /dev/null +++ b/packages/create-auco/package.json @@ -0,0 +1,22 @@ +{ + "name": "create-auco", + "version": "0.1.7", + "bin": { + "create-auco": "./dist/create-auco.js" + }, + "main": "dist/create-auco.js", + "types": "dist/create-auco.d.ts", + "scripts": { + "build": "tsc -p tsconfig.json --skipLibCheck" + }, + "dependencies": { + "commander": "^11.1.0" + }, + "files": [ + "dist", + "README.md", + "LICENSE" + ] +} + + diff --git a/packages/create-auco/src/create-auco.ts b/packages/create-auco/src/create-auco.ts new file mode 100644 index 0000000..45c3a39 --- /dev/null +++ b/packages/create-auco/src/create-auco.ts @@ -0,0 +1,718 @@ +#!/usr/bin/env node + +import * as fs from 'fs'; +import * as path from 'path'; +import { program } from 'commander'; +import * as readline from 'readline'; + +interface CreateOptions { + name?: string; + skipPrompts?: boolean; +} + +// ANSI color codes +const colors = { + reset: '\x1b[0m', + bright: '\x1b[1m', + dim: '\x1b[2m', + cyan: '\x1b[36m', + green: '\x1b[32m', + yellow: '\x1b[33m', + red: '\x1b[31m', + magenta: '\x1b[35m', +}; + +function log(message: string) { + console.log(message); +} + +function success(message: string) { + console.log(`${colors.green}✓${colors.reset} ${message}`); +} + +function info(message: string) { + console.log(`${colors.cyan}ℹ${colors.reset} ${message}`); +} + +function warn(message: string) { + console.log(`${colors.yellow}⚠${colors.reset} ${message}`); +} + +function error(message: string) { + console.log(`${colors.red}✗${colors.reset} ${message}`); +} + +async function prompt(question: string, defaultValue?: string): Promise { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + return new Promise((resolve) => { + const defaultHint = defaultValue ? ` ${colors.dim}(${defaultValue})${colors.reset}` : ''; + rl.question(`${colors.cyan}?${colors.reset} ${question}${defaultHint}: `, (answer) => { + rl.close(); + resolve(answer.trim() || defaultValue || ''); + }); + }); +} + +function getPackageJsonTemplate(projectName: string): string { + return JSON.stringify( + { + name: projectName, + version: '0.1.0', + private: true, + scripts: { + dev: 'npx ts-node src/index.ts', + build: 'tsc', + start: 'node dist/index.js', + 'db:up': 'docker compose up -d', + 'db:down': 'docker compose down', + 'db:reset': 'docker compose down -v && docker compose up -d', + 'download-abi': 'npx auco-download-abi', + }, + dependencies: { + auco: '^0.1.4', + express: '^4.21.0', + dotenv: '^16.4.5', + }, + devDependencies: { + '@types/express': '^4.17.21', + '@types/node': '^20.11.0', + 'ts-node': '^10.9.2', + typescript: '^5.0.0', + }, + }, + null, + 2 + ); +} + +function getTsConfigTemplate(): string { + return JSON.stringify( + { + compilerOptions: { + target: 'ES2020', + module: 'commonjs', + lib: ['ES2020'], + outDir: './dist', + rootDir: './src', + strict: true, + esModuleInterop: true, + skipLibCheck: true, + forceConsistentCasingInFileNames: true, + resolveJsonModule: true, + declaration: true, + declarationMap: true, + sourceMap: true, + }, + include: ['src/**/*'], + exclude: ['node_modules', 'dist'], + }, + null, + 2 + ); +} + +function getDockerComposeTemplate(): string { + return `services: + postgres: + image: postgres:16-alpine + container_name: auco-postgres + restart: unless-stopped + ports: + - "5432:5432" + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: starknet_indexer + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 + +volumes: + postgres_data: +`; +} + +function getEnvTemplate(): string { + return `# Starknet RPC Configuration +RPC_URL=https://starknet-sepolia-rpc.publicnode.com +WS_URL=wss://starknet-sepolia-rpc.publicnode.com + +# Database Configuration +DATABASE_URL=postgresql://postgres:postgres@localhost:5432/starknet_indexer + +# Server Configuration +PORT=3000 + +# Indexer Configuration +STARTING_BLOCK=latest +LOG_LEVEL=info +`; +} + +function getEnvExampleTemplate(): string { + return `# Starknet RPC Configuration +RPC_URL=https://starknet-sepolia-rpc.publicnode.com +WS_URL=wss://starknet-sepolia-rpc.publicnode.com + +# Database Configuration +DATABASE_URL=postgresql://postgres:postgres@localhost:5432/starknet_indexer + +# Server Configuration +PORT=3000 + +# Indexer Configuration +# Use 'latest' to start from the latest block, or a specific block number +STARTING_BLOCK=latest +LOG_LEVEL=info +`; +} + +function getGitignoreTemplate(): string { + return `# Dependencies +node_modules/ + +# Build output +dist/ + +# Environment files +.env +.env.local +.env.*.local + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log +npm-debug.log* + +# Database +*.db +*.sqlite +`; +} + +function getExampleAbiTemplate(): string { + return `// Example ABI for STRK Token on Starknet +// Contract Address: 0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d +// You can download ABIs using: npm run download-abi single -r -a -n + +export const StrkTokenAbi = [ + { + type: 'struct', + name: 'core::integer::u256', + members: [ + { name: 'low', type: 'core::integer::u128' }, + { name: 'high', type: 'core::integer::u128' }, + ], + }, + { + type: 'event', + name: 'src::strk::erc20_lockable::ERC20Lockable::Transfer', + kind: 'struct', + members: [ + { name: 'from', type: 'core::starknet::contract_address::ContractAddress', kind: 'data' }, + { name: 'to', type: 'core::starknet::contract_address::ContractAddress', kind: 'data' }, + { name: 'value', type: 'core::integer::u256', kind: 'data' }, + ], + }, + { + type: 'event', + name: 'src::strk::erc20_lockable::ERC20Lockable::Approval', + kind: 'struct', + members: [ + { name: 'owner', type: 'core::starknet::contract_address::ContractAddress', kind: 'data' }, + { name: 'spender', type: 'core::starknet::contract_address::ContractAddress', kind: 'data' }, + { name: 'value', type: 'core::integer::u256', kind: 'data' }, + ], + }, + { + type: 'event', + name: 'src::strk::erc20_lockable::ERC20Lockable::Event', + kind: 'enum', + variants: [ + { name: 'Transfer', type: 'src::strk::erc20_lockable::ERC20Lockable::Transfer', kind: 'nested' }, + { name: 'Approval', type: 'src::strk::erc20_lockable::ERC20Lockable::Approval', kind: 'nested' }, + ], + }, +] as const; + +export default StrkTokenAbi; +`; +} + +function getIndexerTemplate(): string { + return `import 'dotenv/config'; +import express, { Request, Response } from 'express'; +import { StarknetIndexer, LogLevel } from 'auco'; +import { StrkTokenAbi } from './abi/StrkToken'; + +// ═══════════════════════════════════════════════════════════════════════════════ +// Configuration +// ═══════════════════════════════════════════════════════════════════════════════ + +const config = { + rpcUrl: process.env.RPC_URL || 'https://starknet-sepolia-rpc.publicnode.com', + wsUrl: process.env.WS_URL || 'wss://starknet-sepolia-rpc.publicnode.com', + databaseUrl: process.env.DATABASE_URL || 'postgresql://postgres:postgres@localhost:5432/starknet_indexer', + port: parseInt(process.env.PORT || '3000', 10), + startingBlock: process.env.STARTING_BLOCK === 'latest' + ? 'latest' as const + : parseInt(process.env.STARTING_BLOCK || '0', 10), + logLevel: (process.env.LOG_LEVEL as LogLevel) || LogLevel.INFO, +}; + +// ═══════════════════════════════════════════════════════════════════════════════ +// In-Memory Store (for demo purposes - use database in production) +// ═══════════════════════════════════════════════════════════════════════════════ + +interface TransferEvent { + blockNumber: number; + blockHash: string; + transactionHash: string; + from: string; + to: string; + value: string; + timestamp: Date; +} + +const recentTransfers: TransferEvent[] = []; +const MAX_STORED_TRANSFERS = 100; + +// ═══════════════════════════════════════════════════════════════════════════════ +// Initialize Indexer +// ═══════════════════════════════════════════════════════════════════════════════ + +const indexer = new StarknetIndexer({ + rpcNodeUrl: config.rpcUrl, + wsNodeUrl: config.wsUrl, + database: { + type: 'postgres', + config: { + connectionString: config.databaseUrl, + }, + }, + logLevel: config.logLevel, + startingBlockNumber: config.startingBlock, +}); + +// ═══════════════════════════════════════════════════════════════════════════════ +// Event Handlers +// ═══════════════════════════════════════════════════════════════════════════════ + +// STRK Token contract address on Sepolia +const STRK_TOKEN_ADDRESS = '0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d'; + +indexer.onEvent({ + contractAddress: STRK_TOKEN_ADDRESS, + abi: StrkTokenAbi, + eventName: 'src::strk::erc20_lockable::ERC20Lockable::Transfer', + handler: async (event, dbHandler, indexerInstance) => { + const transfer: TransferEvent = { + blockNumber: event.block_number, + blockHash: event.block_hash, + transactionHash: event.transaction_hash, + from: String(event.parsed.from), + to: String(event.parsed.to), + value: String(event.parsed.value), + timestamp: new Date(), + }; + + // Store in memory for API access + recentTransfers.unshift(transfer); + if (recentTransfers.length > MAX_STORED_TRANSFERS) { + recentTransfers.pop(); + } + + console.log(\`[Transfer] Block \${event.block_number}: \${transfer.from} → \${transfer.to} (\${transfer.value})\`); + + // You can also store in the database using dbHandler + // Example: await dbHandler.query('INSERT INTO transfers ...', [...]) + }, +}); + +// ═══════════════════════════════════════════════════════════════════════════════ +// Express.js API Server +// ═══════════════════════════════════════════════════════════════════════════════ + +const app = express(); +app.use(express.json()); + +// Health check endpoint +app.get('/health', async (req: Request, res: Response) => { + try { + const health = await indexer.healthCheck(); + const status = health.database && health.ws && health.rpc ? 'healthy' : 'degraded'; + res.json({ + status, + services: health, + timestamp: new Date().toISOString(), + }); + } catch (error) { + res.status(503).json({ + status: 'unhealthy', + error: String(error), + timestamp: new Date().toISOString(), + }); + } +}); + +// Get recent transfers +app.get('/api/transfers', (req: Request, res: Response) => { + const limit = Math.min(parseInt(req.query.limit as string) || 10, MAX_STORED_TRANSFERS); + res.json({ + transfers: recentTransfers.slice(0, limit), + total: recentTransfers.length, + }); +}); + +// Get transfer by transaction hash +app.get('/api/transfers/:txHash', (req: Request, res: Response) => { + const transfer = recentTransfers.find(t => t.transactionHash === req.params.txHash); + if (transfer) { + res.json(transfer); + } else { + res.status(404).json({ error: 'Transfer not found' }); + } +}); + +// Get transfers by address (sender or recipient) +app.get('/api/address/:address/transfers', (req: Request, res: Response) => { + const address = req.params.address.toLowerCase(); + const transfers = recentTransfers.filter( + t => t.from.toLowerCase() === address || t.to.toLowerCase() === address + ); + res.json({ + address: req.params.address, + transfers, + total: transfers.length, + }); +}); + +// ═══════════════════════════════════════════════════════════════════════════════ +// Start Services +// ═══════════════════════════════════════════════════════════════════════════════ + +async function main() { + console.log(''); + console.log('╔═══════════════════════════════════════════════════════════════╗'); + console.log('║ 🚀 Auco Indexer ║'); + console.log('╚═══════════════════════════════════════════════════════════════╝'); + console.log(''); + + // Start Express server + app.listen(config.port, () => { + console.log(\`[Server] API running at http://localhost:\${config.port}\`); + console.log(\`[Server] Health check: http://localhost:\${config.port}/health\`); + console.log(\`[Server] Transfers API: http://localhost:\${config.port}/api/transfers\`); + console.log(''); + }); + + // Start the indexer + console.log('[Indexer] Starting...'); + console.log(\`[Indexer] RPC: \${config.rpcUrl}\`); + console.log(\`[Indexer] Starting block: \${config.startingBlock}\`); + console.log(''); + + await indexer.start(); +} + +// Handle graceful shutdown +process.on('SIGINT', async () => { + console.log('\\n[Shutdown] Received SIGINT, shutting down gracefully...'); + await indexer.stop(); + process.exit(0); +}); + +process.on('SIGTERM', async () => { + console.log('\\n[Shutdown] Received SIGTERM, shutting down gracefully...'); + await indexer.stop(); + process.exit(0); +}); + +main().catch((err) => { + console.error('[Fatal] Failed to start:', err); + process.exit(1); +}); +`; +} + +function getReadmeTemplate(projectName: string): string { + return `# ${projectName} + +A Starknet indexer built with [Auco](https://github.com/auco/starknet-js-indexer). + +## Quick Start + +### 1. Install Dependencies + +\`\`\`bash +npm install +\`\`\` + +### 2. Start PostgreSQL + +\`\`\`bash +npm run db:up +\`\`\` + +This starts a PostgreSQL instance using Docker Compose. + +### 3. Configure Environment + +Copy the example environment file and adjust as needed: + +\`\`\`bash +cp .env.example .env +\`\`\` + +### 4. Run the Indexer + +\`\`\`bash +npm run dev +\`\`\` + +The indexer will start and: +- Connect to Starknet Sepolia +- Listen for Transfer events from the STRK token +- Expose an API at http://localhost:3000 + +## API Endpoints + +| Endpoint | Description | +|----------|-------------| +| \`GET /health\` | Health check with service status | +| \`GET /api/transfers\` | Get recent transfers (query: \`?limit=10\`) | +| \`GET /api/transfers/:txHash\` | Get transfer by transaction hash | +| \`GET /api/address/:address/transfers\` | Get transfers for an address | + +## Project Structure + +\`\`\` +${projectName}/ +├── src/ +│ ├── index.ts # Main entry point with indexer + Express server +│ └── abi/ +│ └── StrkToken.ts # Example ABI (STRK token) +├── docker-compose.yml # PostgreSQL configuration +├── .env # Environment variables +├── package.json +└── tsconfig.json +\`\`\` + +## Commands + +| Command | Description | +|---------|-------------| +| \`npm run dev\` | Start the indexer in development mode | +| \`npm run build\` | Build TypeScript to JavaScript | +| \`npm start\` | Start the built application | +| \`npm run db:up\` | Start PostgreSQL container | +| \`npm run db:down\` | Stop PostgreSQL container | +| \`npm run db:reset\` | Reset database (delete all data) | + +## Customization + +### Adding New Event Handlers + +Edit \`src/index.ts\` to add handlers for additional contracts/events: + +\`\`\`typescript +import { YourContractAbi } from './abi/YourContract'; + +indexer.onEvent({ + contractAddress: 'YOUR_CONTRACT_ADDRESS', + abi: YourContractAbi, + eventName: 'YourEventName', + handler: async (event, dbHandler, indexer) => { + // Process the event + console.log('Event received:', event.parsed); + }, +}); +\`\`\` + +### Downloading ABIs + +Use the built-in ABI downloader: + +\`\`\`bash +npx auco-download-abi single -r https://starknet-sepolia-rpc.publicnode.com -a -n +\`\`\` + +## Configuration + +Environment variables: + +| Variable | Description | Default | +|----------|-------------|---------| +| \`RPC_URL\` | Starknet RPC endpoint | Sepolia public node | +| \`WS_URL\` | Starknet WebSocket endpoint | Sepolia public node | +| \`DATABASE_URL\` | PostgreSQL connection string | localhost:5432 | +| \`PORT\` | API server port | 3000 | +| \`STARTING_BLOCK\` | Block to start indexing from | latest | +| \`LOG_LEVEL\` | Log verbosity (debug/info/warn/error) | info | + +## License + +MIT +`; +} + +async function createProject(projectPath: string, projectName: string) { + log(''); + log(`${colors.bright}Creating Auco project in ${colors.cyan}${projectPath}${colors.reset}`); + log(''); + + // Create directories + const directories = [ + projectPath, + path.join(projectPath, 'src'), + path.join(projectPath, 'src', 'abi'), + ]; + + for (const dir of directories) { + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + } + + // Write files + const files: Array<{ path: string; content: string; name: string }> = [ + { + path: path.join(projectPath, 'package.json'), + content: getPackageJsonTemplate(projectName), + name: 'package.json', + }, + { + path: path.join(projectPath, 'tsconfig.json'), + content: getTsConfigTemplate(), + name: 'tsconfig.json', + }, + { + path: path.join(projectPath, 'docker-compose.yml'), + content: getDockerComposeTemplate(), + name: 'docker-compose.yml', + }, + { path: path.join(projectPath, '.env'), content: getEnvTemplate(), name: '.env' }, + { + path: path.join(projectPath, '.env.example'), + content: getEnvExampleTemplate(), + name: '.env.example', + }, + { + path: path.join(projectPath, '.gitignore'), + content: getGitignoreTemplate(), + name: '.gitignore', + }, + { + path: path.join(projectPath, 'README.md'), + content: getReadmeTemplate(projectName), + name: 'README.md', + }, + { + path: path.join(projectPath, 'src', 'index.ts'), + content: getIndexerTemplate(), + name: 'src/index.ts', + }, + { + path: path.join(projectPath, 'src', 'abi', 'StrkToken.ts'), + content: getExampleAbiTemplate(), + name: 'src/abi/StrkToken.ts', + }, + ]; + + for (const file of files) { + fs.writeFileSync(file.path, file.content); + success(`Created ${file.name}`); + } + + log(''); + success(`${colors.bright}Project created successfully!${colors.reset}`); + log(''); + log(`${colors.bright}Next steps:${colors.reset}`); + log(''); + log(` ${colors.cyan}cd${colors.reset} ${projectName}`); + log(` ${colors.cyan}npm${colors.reset} install`); + log(` ${colors.cyan}npm run${colors.reset} db:up`); + log(` ${colors.cyan}npm run${colors.reset} dev`); + log(''); + log(`${colors.dim}The indexer will start on http://localhost:3000${colors.reset}`); + log(''); +} + +async function run(options: CreateOptions) { + log(''); + log( + `${colors.bright}${colors.magenta}╔═══════════════════════════════════════════════════════════════╗${colors.reset}` + ); + log( + `${colors.bright}${colors.magenta}║${colors.reset} ${colors.bright}🔮 Create Auco - Starknet Indexer${colors.reset} ${colors.bright}${colors.magenta}║${colors.reset}` + ); + log( + `${colors.bright}${colors.magenta}╚═══════════════════════════════════════════════════════════════╝${colors.reset}` + ); + log(''); + + let projectName = options.name; + + if (!projectName && !options.skipPrompts) { + projectName = await prompt('Project name', 'my-starknet-indexer'); + } + + if (!projectName) { + projectName = 'my-starknet-indexer'; + } + + // Validate project name + const validNameRegex = /^[a-zA-Z0-9-_]+$/; + if (!validNameRegex.test(projectName)) { + error('Project name can only contain letters, numbers, hyphens, and underscores'); + process.exit(1); + } + + const projectPath = path.join(process.cwd(), projectName); + + // Check if directory already exists + if (fs.existsSync(projectPath)) { + const files = fs.readdirSync(projectPath); + if (files.length > 0) { + error(`Directory ${projectName} already exists and is not empty`); + process.exit(1); + } + } + + await createProject(projectPath, projectName); +} + +// CLI setup +program + .name('create-auco') + .description('Create a new Auco Starknet indexer project') + .version('0.1.0') + .argument('[name]', 'Project name') + .option('-y, --yes', 'Skip prompts and use defaults') + .action(async (name?: string, opts?: { yes?: boolean }) => { + try { + await run({ name, skipPrompts: opts?.yes }); + } catch (err) { + error(`Failed to create project: ${err}`); + process.exit(1); + } + }); + +program.parse(); diff --git a/packages/create-auco/tsconfig.json b/packages/create-auco/tsconfig.json new file mode 100644 index 0000000..c76d28c --- /dev/null +++ b/packages/create-auco/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "node16", + "moduleResolution": "node16", + "declaration": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "noImplicitAny": false, + "resolveJsonModule": true, + "composite": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} + + diff --git a/tsconfig.json b/tsconfig.json index e8e4337..2665459 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,23 +1,9 @@ { - "compilerOptions": { - "target": "es2020", - "module": "node16", - "moduleResolution": "node16", - "declaration": true, - "outDir": "./dist", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "noImplicitAny": false, - "resolveJsonModule": true - }, - "include": ["src/**/*", "example/index.ts"], - "exclude": [ - "node_modules", - "dist", - "example/**/*", - "test-utils/**/*", - "**/*.test.ts", - "**/*.spec.ts" + "files": [], + "references": [ + { "path": "./packages/auco-indexer" }, + { "path": "./packages/create-auco" } ] } + +