diff --git a/.dev.vars.example b/.dev.vars.example new file mode 100644 index 0000000..10ea9d0 --- /dev/null +++ b/.dev.vars.example @@ -0,0 +1,7 @@ +INVISIBLE_COORDINATOR_WS_URL=wss://coordinator.example/ws-noise +INVISIBLE_REQUIRED_MODE=dev +INVISIBLE_RELEASE_MRTD=000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +INVISIBLE_INTEL_ROOT_FINGERPRINT=0000000000000000000000000000000000000000000000000000000000000000 +INVISIBLE_ALLOW_MISSING_DCAP_COLLATERAL=false +DEMO_DESTINATION_ADDRESS=11111111111111111111111111111111 +INVISIBLE_WORKER_API_KEY= diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1e68201 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + +jobs: + verify: + runs-on: ubuntu-latest + permissions: + contents: read + packages: read + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-node@v6 + with: + node-version: "24" + cache: npm + - run: npm ci + - run: npm run typecheck + - run: npm run build + - run: npm run check:contract + - run: npm run simulate + - run: npm run verify:sdk + - run: npm run audit:high diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..95e4910 --- /dev/null +++ b/.npmrc @@ -0,0 +1,5 @@ +@invisible:registry=https://npm.pkg.github.com +@invisible-labs:registry=https://npm.pkg.github.com +//npm.pkg.github.com/:_authToken=${NPM_TOKEN} +save-exact=true +ignore-scripts=true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f37758e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,13 @@ +# Contributing + +Keep this Worker example small and copy-pasteable. + +```bash +npm ci +npm run typecheck +npm run build +npm run simulate +npm run audit:high +``` + +Do not commit secrets, wallet private keys, recovery codes, FROST material, nonces, signatures, or real user data. diff --git a/README.md b/README.md index 6ac306c..bcb6e3a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,53 @@ # Invisible Worker -Cloudflare Worker server example for Invisible private transfers. +Cloudflare Worker example for server-side Invisible private transfers. -Implementation lives in pull requests. +```bash +npm ci +npm run dev +``` + +Routes: + +```txt +GET /health +GET /sdk +POST /private-transfer +POST /lp +``` + +Set secrets with Wrangler. Do not commit secrets. + +Required runtime config: + +```txt +INVISIBLE_COORDINATOR_WS_URL= +INVISIBLE_RELEASE_MRTD= +INVISIBLE_INTEL_ROOT_FINGERPRINT= +INVISIBLE_WORKER_API_KEY= +``` + +The SDK requires DCAP collateral by default. Production examples need a coordinator that emits `dcap_collateral` or a compatible attestation manifest. For legacy previews only, set `INVISIBLE_ALLOW_MISSING_DCAP_COLLATERAL=true`; this maps to `releasePin.allowMissingDcapCollateral` and is rejected in `prod` mode. + +If the configured coordinator is still on an older wire contract, this preview can fail before a transfer is created. Once the matching coordinator and wire-contract rollout is live, the SDK package and coordinator will speak the same message names and payload shapes. + +`POST /private-transfer` requires `Authorization: Bearer `. +`POST /lp` requires the same bearer token and accepts: + +```json +{ "action": "create" } +``` + +For existing positions, pass `positionCode` with `recover`, `complete-dkg`, +`prepare-funding`, `reconcile-funding`, `refill`, or `withdraw`. `withdraw` +also requires `destinationAddress`. + +When `@invisible/sdk` is available: + +```bash +INVISIBLE_SDK_PACKAGE=npm:@invisible-labs/sdk@0.1.0-dev.1.2 npm run verify:sdk +npm run prepare:sdk +npm run deploy +``` + +`npm run verify:sdk` installs `@invisible/sdk@npm:@invisible-labs/sdk@0.1.0-dev.1.2` in a temporary consumer project when `INVISIBLE_SDK_PACKAGE` is unset. It allows freshly published private dev packages for that temporary install only, and must pass from the published or packaged SDK artifact, not from local monorepo paths or generated FROST files. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..e269dfb --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,5 @@ +# Security + +Report security issues privately to the maintainers. + +This example must never log or persist coordinator secrets, wallet private keys, recovery codes, FROST shares, nonces, signatures, or raw decrypted payloads. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..7181336 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1064 @@ +{ + "name": "invisible-worker", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "invisible-worker", + "version": "0.1.0", + "dependencies": { + "@invisible/sdk": "npm:@invisible-labs/sdk@0.1.0-dev.1.2" + }, + "devDependencies": { + "@cloudflare/workers-types": "4.20260612.1", + "@types/node": "24.10.3", + "tsx": "4.21.0", + "typescript": "5.9.3" + }, + "engines": { + "node": ">=24.0.0" + }, + "optionalDependencies": { + "@invisible/sdk": "npm:@invisible-labs/sdk@0.1.0-dev.1.2" + } + }, + "node_modules/@cloudflare/workers-types": { + "version": "4.20260612.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260612.1.tgz", + "integrity": "sha512-PMQI7XP/wrMhxyjseUHoHj6XFqkHaf4utWQ/hhefVY8oMK2LJ730oeQ7H/nZSVMexZe39DzsdOx7sf1PqMr7+Q==", + "dev": true, + "license": "MIT OR Apache-2.0" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@invisible/sdk": { + "name": "@invisible-labs/sdk", + "version": "0.1.0-dev.1.2", + "resolved": "https://npm.pkg.github.com/download/@invisible-labs/sdk/0.1.0-dev.1.2/62e7af1fd4461c07e229b4abe6109764a8ee677d", + "integrity": "sha512-B1pHdobtm0osKHbrFGbQqja4pGT3JXJHfo/OWB0vfOtl98jWE7oaRvQgWgzKGy3zdZNR8RngOtGXlyw2HBCIyw==", + "optional": true, + "dependencies": { + "@peculiar/x509": "1.14.3", + "@stablelib/chacha20poly1305": "2.0.1", + "@stablelib/hmac": "2.0.1", + "@stablelib/sha256": "2.0.1", + "@stablelib/x25519": "2.0.1", + "ajv": "8.20.0" + } + }, + "node_modules/@peculiar/asn1-cms": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.8.0.tgz", + "integrity": "sha512-NgekZOrSJFSBFLFoLfwePguAWAx7z1+f2TEsWFUMyiqqfntZ4+S/S5hzqME3q4pCA0iOsFKdwiQ35dwY24eVqA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "@peculiar/asn1-x509-attr": "^2.8.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-csr": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.8.0.tgz", + "integrity": "sha512-akbF8+uvleHs8sejNPQxwmVFuInAg6FMNHOwMILXfP518YfFJwdR3jr6oNUPOaEJfuEhn/vkNOCIT6ASUd4mbg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-ecc": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.8.0.tgz", + "integrity": "sha512-ohwlk+u9Rv2NOAY1c6MfHj45ATVF8R1DUN/WCgABiRtLi2ZftlZWZX7KvpAbU8v9xPcmoILfELeEABj/rn18AQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pfx": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.8.0.tgz", + "integrity": "sha512-5yof1ytoB++RQtaFbqSUJ8pxDJtZT6vbVqZ8XoJ61ph7UjNVvfFwAilnCodqkNsAodpy13gDhoxZXw00pghnyg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@peculiar/asn1-cms": "^2.8.0", + "@peculiar/asn1-pkcs8": "^2.8.0", + "@peculiar/asn1-rsa": "^2.8.0", + "@peculiar/asn1-schema": "^2.8.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.8.0.tgz", + "integrity": "sha512-qAKXtLpBEw9LqhKpjw3ajZSXlBur+ipW+y2ivVBQAG6F6qRx94yO+1ZR4mvw+YaCfKSaOzLeYEzsPaBp4SJELA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.8.0.tgz", + "integrity": "sha512-b5nDWCnkV60+cQ141D6sVVwK9nz64R5n3zSVnklGd+ECdkW2Ol3U1a6yYFlalpSOaD557yuJB64A+q42jG7lUQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@peculiar/asn1-cms": "^2.8.0", + "@peculiar/asn1-pfx": "^2.8.0", + "@peculiar/asn1-pkcs8": "^2.8.0", + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "@peculiar/asn1-x509-attr": "^2.8.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-rsa": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.8.0.tgz", + "integrity": "sha512-zHEUlCqB2mk7x2lxDwHHJy7hWZOPdGHVlsmITWKB5/PbQo61atbu9PJ/0r9dQNMwFzbKPXZ8uK8/91eUhRznSg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.8.0.tgz", + "integrity": "sha512-7YT0U/ze0tF2QOBbE15gKZwy5tvgGyLRiRHLzhlbOpf7BT032oBSd0haZqXn5W6l26WLlu3dyxzjM+2638/z2Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.8.0.tgz", + "integrity": "sha512-N0CMuhWUzsWEVq6F1q9X6+VKUnWzSW+cSVg+aPaGGwDdbFoFWTYgin5MHwXgpWd6y9COMBxnfy/Qc+Xc7F0Zwg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.8.0.tgz", + "integrity": "sha512-tHjkfS/qhMnmrlB2J9NhflQlQ7In3khO3CfmVrriOlpTeErY9ZIKOso1hQ5JQiyrJ7ShvqVPk7E5fQmbclkSKA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.8.0", + "@peculiar/asn1-x509": "^2.8.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/x509": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.3.tgz", + "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@stablelib/aead": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@stablelib/aead/-/aead-2.0.0.tgz", + "integrity": "sha512-U/RMANRxbT/ahIpYsPSiFwDFNjADHdnCFfmo09MO1ai2XmerPAOPtMl0qmX7XVvygnACC6ijKDyHBoT2rGyElg==", + "license": "MIT", + "optional": true + }, + "node_modules/@stablelib/binary": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/binary/-/binary-2.0.1.tgz", + "integrity": "sha512-U9iAO8lXgEDONsA0zPPSgcf3HUBNAqHiJmSHgZz62OvC3Hi2Bhc5kTnQ3S1/L+sthDTHtCMhcEiklmIly6uQ3w==", + "license": "MIT", + "optional": true, + "dependencies": { + "@stablelib/int": "^2.0.1" + } + }, + "node_modules/@stablelib/bytes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/bytes/-/bytes-2.0.1.tgz", + "integrity": "sha512-QIzI6V7nkJA5CjOZ7GoceBd4CIKrJoC471VaI6jh1xPQ2cMhkhQK4HddyzCXOR2y+fBF3/5B2HO3FXXI9C+Xzg==", + "license": "MIT", + "optional": true + }, + "node_modules/@stablelib/chacha": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/chacha/-/chacha-2.0.1.tgz", + "integrity": "sha512-lS1FqtNqofxe2vLkRsLli2m3x/XanUyAYRphLhdHumKeIsLbjbCXdCq3Pf/eWiO7G3QlSG5ViqnoVjktzfLWMg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@stablelib/binary": "^2.0.1", + "@stablelib/wipe": "^2.0.1" + } + }, + "node_modules/@stablelib/chacha20poly1305": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/chacha20poly1305/-/chacha20poly1305-2.0.1.tgz", + "integrity": "sha512-kOoBsXbDPVRlelzXl+5WViycgM19lD7lF3Bc3KWI+DzId0Stc2HlxAfSc+Xpn3RgqSCl1ZNTXr33LegqBhBBaw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@stablelib/aead": "^2.0.0", + "@stablelib/binary": "^2.0.1", + "@stablelib/chacha": "^2.0.1", + "@stablelib/constant-time": "^2.0.1", + "@stablelib/poly1305": "^2.0.1", + "@stablelib/wipe": "^2.0.1" + } + }, + "node_modules/@stablelib/constant-time": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/constant-time/-/constant-time-2.0.1.tgz", + "integrity": "sha512-0NWPogffRm+UWBH0+iM5otZmNrVe5OHFIvyoNIVankMAYOQzMwcdVALOVPrB5Ho0dST+Oc3H8/hPh65Z8R/uew==", + "license": "MIT", + "optional": true + }, + "node_modules/@stablelib/hash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@stablelib/hash/-/hash-2.0.0.tgz", + "integrity": "sha512-u3WPSqGido8lwJuMcrBgM5K54LrPGhkWAdtsyccf7dGsLixAZUds77zOAbu7bvKPwQlmoByH0txBi5rTmEKuHg==", + "license": "MIT", + "optional": true + }, + "node_modules/@stablelib/hmac": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/hmac/-/hmac-2.0.1.tgz", + "integrity": "sha512-YDMEmogYNDWESU8ggMiTi4VDMfHt2XG+pgVskJCHuKVlmWwXxYKZ6bn5dq24/jU3vzZSajwKzRCU6iafT2OKUA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@stablelib/constant-time": "^2.0.1", + "@stablelib/hash": "^2.0.0", + "@stablelib/wipe": "^2.0.1" + } + }, + "node_modules/@stablelib/int": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/int/-/int-2.0.1.tgz", + "integrity": "sha512-Ht63fQp3wz/F8U4AlXEPb7hfJOIILs8Lq55jgtD7KueWtyjhVuzcsGLSTAWtZs3XJDZYdF1WcSKn+kBtbzupww==", + "license": "MIT", + "optional": true + }, + "node_modules/@stablelib/keyagreement": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/keyagreement/-/keyagreement-2.0.1.tgz", + "integrity": "sha512-2+tWBLCMtWlHQ7GqjD5L+lQRyWtun4Lou0IOdTML8zuTuAS0EgihnHFx+4uMZwYU1In40J/WlpyKSLidHfStRQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@stablelib/bytes": "^2.0.1" + } + }, + "node_modules/@stablelib/poly1305": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/poly1305/-/poly1305-2.0.1.tgz", + "integrity": "sha512-D8xfZcL/5zeVARJ9I2fZOUCnZvsJx8G4JDQvD+ecsnaCildPtqZDwqqjtOc4cfHAdScrMjOzUVsssjXUTBw3YQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@stablelib/constant-time": "^2.0.1", + "@stablelib/wipe": "^2.0.1" + } + }, + "node_modules/@stablelib/random": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/random/-/random-2.0.1.tgz", + "integrity": "sha512-W6GAtXEEs7r+dSbuBsvoFmlyL3gLxle41tQkjKu17dDWtDdjhVUbtRfRCQcCUeczwkgjQxMPopgwYEvxXtHXGw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@stablelib/binary": "^2.0.1", + "@stablelib/wipe": "^2.0.1" + } + }, + "node_modules/@stablelib/sha256": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/sha256/-/sha256-2.0.1.tgz", + "integrity": "sha512-LA1PaLDc6Lv72ppA4PEZ7abDE741KfG7k7QhBiUyIfViMqrwWv8HqQQFPeuPfS4k2OxFv++IAgc8HlvdBatD+w==", + "license": "MIT", + "optional": true, + "dependencies": { + "@stablelib/binary": "^2.0.1", + "@stablelib/hash": "^2.0.0", + "@stablelib/wipe": "^2.0.1" + } + }, + "node_modules/@stablelib/wipe": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/wipe/-/wipe-2.0.1.tgz", + "integrity": "sha512-1eU2K9EgOcV4qc9jcP6G72xxZxEm5PfeI5H55l08W95b4oRJaqhmlWRc4xZAm6IVSKhVNxMi66V67hCzzuMTAg==", + "license": "MIT", + "optional": true + }, + "node_modules/@stablelib/x25519": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/x25519/-/x25519-2.0.1.tgz", + "integrity": "sha512-qi04HS2puHaBf50kM/kes5QcZFGsx8yF0YmCjLCOa/LPmnBaKEKX9ZR82OnnCwMn72YH13R/bBZgr/UP0aPFfA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@stablelib/keyagreement": "^2.0.1", + "@stablelib/random": "^2.0.1", + "@stablelib/wipe": "^2.0.1" + } + }, + "node_modules/@types/node": { + "version": "24.10.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.3.tgz", + "integrity": "sha512-gqkrWUsS8hcm0r44yn7/xZeV1ERva/nLgrLxFRUGb7aoNMIJfZJ3AC261zDQuOAKC7MiXai1WCpYc48jAHoShQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "optional": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/asn1js": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT", + "optional": true + }, + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT", + "optional": true + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/tsyringe/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a87fd32 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "invisible-worker", + "version": "0.1.0", + "private": true, + "type": "module", + "engines": { + "node": ">=24.0.0" + }, + "scripts": { + "dev": "npx wrangler dev", + "deploy": "npx wrangler deploy", + "typecheck": "tsc --noEmit", + "build": "tsc --noEmit", + "simulate": "tsx scripts/simulate.ts", + "check:contract": "tsx scripts/check-contract.ts", + "prepare:sdk": "tsx scripts/prepare-sdk-imports.ts", + "verify:sdk": "tsx scripts/verify-sdk.ts", + "cf-typegen": "npx wrangler types", + "audit:high": "npm audit --audit-level=high" + }, + "optionalDependencies": { + "@invisible/sdk": "npm:@invisible-labs/sdk@0.1.0-dev.1.2" + }, + "devDependencies": { + "@cloudflare/workers-types": "4.20260612.1", + "@types/node": "24.10.3", + "tsx": "4.21.0", + "typescript": "5.9.3" + } +} diff --git a/scripts/check-contract.ts b/scripts/check-contract.ts new file mode 100644 index 0000000..f1767e9 --- /dev/null +++ b/scripts/check-contract.ts @@ -0,0 +1,52 @@ +import { buildCoordinatorPool, type WorkerEnv } from "../src/sdk.js"; + +const env: WorkerEnv = { + INVISIBLE_COORDINATOR_WS_URL: "wss://coordinator.example/ws-noise", + INVISIBLE_REQUIRED_MODE: "dev", + INVISIBLE_RELEASE_MRTD: + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + INVISIBLE_INTEL_ROOT_FINGERPRINT: + "0000000000000000000000000000000000000000000000000000000000000000", + DEMO_DESTINATION_ADDRESS: "11111111111111111111111111111111", +}; + +const pool = buildCoordinatorPool(env); +const pin = pool.endpoints[0]?.releasePin; +if (!pin?.mrtd || !pin.intelRootFingerprint) { + throw new Error("coordinator release pin must include mrtd and intelRootFingerprint"); +} +if ("allowMissingDcapCollateral" in pin) { + throw new Error("missing DCAP collateral escape hatch must be disabled by default"); +} + +const legacyDevPin = buildCoordinatorPool({ + ...env, + INVISIBLE_ALLOW_MISSING_DCAP_COLLATERAL: "true", +}).endpoints[0]?.releasePin; +if (legacyDevPin?.allowMissingDcapCollateral !== true) { + throw new Error("non-prod legacy DCAP collateral escape hatch must be explicit"); +} + +try { + buildCoordinatorPool({ + ...env, + INVISIBLE_REQUIRED_MODE: "prod", + INVISIBLE_ALLOW_MISSING_DCAP_COLLATERAL: "true", + }); + throw new Error("prod mode must reject missing DCAP collateral escape hatch"); +} catch (error) { + if (!(error instanceof Error) || !error.message.includes("DCAP")) { + throw error; + } +} + +try { + buildCoordinatorPool({ ...env, INVISIBLE_REQUIRED_MODE: "" }); + throw new Error("attestation mode must fail closed"); +} catch (error) { + if (!(error instanceof Error) || !error.message.includes("INVISIBLE_REQUIRED_MODE")) { + throw error; + } +} + +console.log("worker contract ok"); diff --git a/scripts/prepare-sdk-imports.ts b/scripts/prepare-sdk-imports.ts new file mode 100644 index 0000000..ca5238c --- /dev/null +++ b/scripts/prepare-sdk-imports.ts @@ -0,0 +1,38 @@ +import { access, writeFile } from "node:fs/promises"; + +const output = new URL("../src/sdk-imports.generated.ts", import.meta.url); + +try { + await access(new URL("../node_modules/@invisible/sdk/package.json", import.meta.url)); + await writeGeneratedLoader(true); + console.log("generated static @invisible/sdk imports"); +} catch { + await writeGeneratedLoader(false); + console.log("generated SDK-missing stub"); +} + +async function writeGeneratedLoader(usePackage: boolean): Promise { + const source = usePackage + ? [ + 'import * as root from "@invisible/sdk";', + 'import * as user from "@invisible/sdk/user";', + 'import * as lp from "@invisible/sdk/lp";', + 'import * as storage from "@invisible/sdk/storage";', + 'import type { SdkBundle } from "./sdk-types.js";', + "", + "export async function loadSdkBundle(): Promise {", + " return { root, user, storage, lp };", + "}", + "", + ] + : [ + 'import type { SdkBundle } from "./sdk-types.js";', + "", + "export async function loadSdkBundle(): Promise {", + " return null;", + "}", + "", + ]; + + await writeFile(output, source.join("\n")); +} diff --git a/scripts/simulate.ts b/scripts/simulate.ts new file mode 100644 index 0000000..2ac6e8f --- /dev/null +++ b/scripts/simulate.ts @@ -0,0 +1,57 @@ +import worker from "../src/index.js"; +import type { WorkerEnv } from "../src/sdk.js"; + +const env: WorkerEnv = { + INVISIBLE_COORDINATOR_WS_URL: "wss://coordinator.example/ws-noise", + INVISIBLE_REQUIRED_MODE: "dev", + INVISIBLE_RELEASE_MRTD: + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + INVISIBLE_INTEL_ROOT_FINGERPRINT: + "0000000000000000000000000000000000000000000000000000000000000000", + DEMO_DESTINATION_ADDRESS: "11111111111111111111111111111111", + INVISIBLE_WORKER_API_KEY: "test-key", +}; + +const health = await worker.fetch(new Request("https://worker.example/health"), env); +if (health.status !== 200) throw new Error("health route failed"); + +const transfer = await worker.fetch( + new Request("https://worker.example/private-transfer", { + method: "POST", + headers: { authorization: "Bearer test-key" }, + body: JSON.stringify({ amountLamports: 1000 }), + }), + env, +); +if (transfer.status !== 503) throw new Error("private transfer should fail closed without SDK"); + +const lp = await worker.fetch( + new Request("https://worker.example/lp", { + method: "POST", + headers: { authorization: "Bearer test-key" }, + body: JSON.stringify({ action: "recover", positionCode: "lp-code" }), + }), + env, +); +if (lp.status !== 503) throw new Error("LP route should fail closed without SDK"); + +const unauthorized = await worker.fetch( + new Request("https://worker.example/private-transfer", { + method: "POST", + body: JSON.stringify({ amountLamports: 1000 }), + }), + env, +); +if (unauthorized.status !== 401) throw new Error("private transfer route must require auth"); + +const tooLarge = await worker.fetch( + new Request("https://worker.example/private-transfer", { + method: "POST", + headers: { authorization: "Bearer test-key" }, + body: JSON.stringify({ amountLamports: 1000, memo: "x".repeat(5000) }), + }), + env, +); +if (tooLarge.status !== 413) throw new Error("private transfer route must enforce body limits"); + +console.log("worker simulation ok"); diff --git a/scripts/verify-sdk.ts b/scripts/verify-sdk.ts new file mode 100644 index 0000000..a21d12a --- /dev/null +++ b/scripts/verify-sdk.ts @@ -0,0 +1,68 @@ +import { mkdir, rm, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; +import { spawnSync } from "node:child_process"; + +const packageSpec = process.env.INVISIBLE_SDK_PACKAGE ?? "npm:@invisible-labs/sdk@0.1.0-dev.1.2"; +const privateSdkInstallArgs = ["install", "--omit=dev", "--min-release-age=0"]; +const required = + process.env.VERIFY_SDK_REQUIRED === "1" || + Boolean(process.env.INVISIBLE_SDK_PACKAGE) || + Boolean(process.env.NPM_TOKEN); +const workspace = join(tmpdir(), `invisible-sdk-verify-${process.pid}`); + +await mkdir(workspace, { recursive: true }); + +try { + await writeFile( + join(workspace, "package.json"), + JSON.stringify({ type: "module", dependencies: { "@invisible/sdk": packageSpec } }, null, 2), + ); + await writeFile( + join(workspace, ".npmrc"), + [ + "@invisible:registry=https://npm.pkg.github.com", + "@invisible-labs:registry=https://npm.pkg.github.com", + "//npm.pkg.github.com/:_authToken=${NPM_TOKEN}", + "ignore-scripts=true", + "", + ].join("\n"), + ); + + const install = spawnSync("npm", privateSdkInstallArgs, { + cwd: workspace, + encoding: "utf8", + stdio: "pipe", + }); + if (install.status !== 0) { + if (required) { + process.stderr.write(install.stderr); + process.exit(install.status ?? 1); + } + skipUnavailable(); + } + + const verify = spawnSync( + process.execPath, + [ + "--input-type=module", + "--eval", + "await import('@invisible/sdk'); await import('@invisible/sdk/user'); await import('@invisible/sdk/lp'); await import('@invisible/sdk/storage'); console.log('sdk imports ok')", + ], + { cwd: workspace, encoding: "utf8", stdio: "pipe" }, + ); + if (verify.status !== 0) { + if (!required) skipUnavailable(); + process.stderr.write(verify.stderr); + process.exit(verify.status ?? 1); + } + process.stdout.write(verify.stdout); +} finally { + await rm(workspace, { force: true, recursive: true }); +} + +function skipUnavailable(): never { + console.log("sdk verification skipped: package is not available yet"); + process.exit(0); + throw new Error("unreachable"); +} diff --git a/src/http.ts b/src/http.ts new file mode 100644 index 0000000..5807cca --- /dev/null +++ b/src/http.ts @@ -0,0 +1,57 @@ +export function json(data: unknown, init: ResponseInit = {}): Response { + return new Response(JSON.stringify(data), { + ...init, + headers: { + "content-type": "application/json; charset=utf-8", + "cache-control": "no-store", + ...init.headers, + }, + }); +} + +export class HttpError extends Error { + constructor( + readonly status: number, + message: string, + ) { + super(message); + this.name = "HttpError"; + } +} + +export async function readJsonBody(request: Request, maxBytes = 4096): Promise { + const contentLength = request.headers.get("content-length"); + if (contentLength && Number(contentLength) > maxBytes) { + throw new HttpError(413, "Request body is too large."); + } + + if (!request.body) { + throw new HttpError(400, "Request body is required."); + } + + const reader = request.body.getReader(); + const chunks: Uint8Array[] = []; + let total = 0; + + for (;;) { + const { done, value } = await reader.read(); + if (done) break; + total += value.byteLength; + if (total > maxBytes) { + await reader.cancel("request body too large"); + throw new HttpError(413, "Request body is too large."); + } + chunks.push(value); + } + + const bytes = new Uint8Array(total); + let offset = 0; + for (const chunk of chunks) { + bytes.set(chunk, offset); + offset += chunk.byteLength; + } + + const body = new TextDecoder().decode(bytes); + if (!body.trim()) throw new HttpError(400, "Request body is required."); + return JSON.parse(body) as T; +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..630b13b --- /dev/null +++ b/src/index.ts @@ -0,0 +1,107 @@ +import { HttpError, json, readJsonBody } from "./http.js"; +import { + runLpAction, + sdkStatus, + startPrivateTransfer, + type LpActionRequest, + type PrivateTransferRequest, + type WorkerEnv, +} from "./sdk.js"; + +export default { + async fetch(request: Request, env: WorkerEnv): Promise { + const url = new URL(request.url); + + try { + if (request.method === "GET" && url.pathname === "/health") { + return json({ ok: true }); + } + + if (request.method === "GET" && url.pathname === "/sdk") { + return json(await sdkStatus()); + } + + if (request.method === "POST" && url.pathname === "/private-transfer") { + await requireBearerToken(request, env); + const input = await readJsonBody(request); + const result = await startPrivateTransfer(env, input); + return json(result, { status: statusForTransfer(result.status) }); + } + + if (request.method === "POST" && url.pathname === "/lp") { + await requireBearerToken(request, env); + const input = await readJsonBody(request); + const result = await runLpAction(env, input); + return json(result, { status: statusForLp(result.status) }); + } + + return json({ error: "not_found" }, { status: 404 }); + } catch (error) { + if (error instanceof HttpError) { + return json({ error: error.message }, { status: error.status }); + } + return json( + { error: error instanceof Error ? error.message : "request failed" }, + { status: 400 }, + ); + } + }, +}; + +async function requireBearerToken(request: Request, env: WorkerEnv): Promise { + const expected = env.INVISIBLE_WORKER_API_KEY?.trim(); + if (!expected) throw new HttpError(503, "Worker API key is not configured."); + + const header = request.headers.get("authorization") ?? ""; + const provided = header.startsWith("Bearer ") ? header.slice("Bearer ".length).trim() : ""; + if (!provided || !(await timingSafeEqual(provided, expected))) { + throw new HttpError(401, "Unauthorized."); + } +} + +async function timingSafeEqual(a: string, b: string): Promise { + const encoder = new TextEncoder(); + const [left, right] = await Promise.all([ + crypto.subtle.digest("SHA-256", encoder.encode(a)), + crypto.subtle.digest("SHA-256", encoder.encode(b)), + ]); + const leftBytes = new Uint8Array(left); + const rightBytes = new Uint8Array(right); + let diff = leftBytes.length ^ rightBytes.length; + for (let i = 0; i < Math.max(leftBytes.length, rightBytes.length); i += 1) { + diff |= (leftBytes[i] ?? 0) ^ (rightBytes[i] ?? 0); + } + return diff === 0; +} + +function statusForTransfer(status: string): number { + switch (status) { + case "accepted": + return 202; + case "sdk_missing": + return 503; + case "sdk_not_ready": + return 501; + case "failed": + return 502; + default: + return 500; + } +} + +function statusForLp(status: string): number { + switch (status) { + case "position": + case "funding": + case "withdrawal": + return 202; + case "sdk_missing": + return 503; + case "sdk_not_ready": + return 501; + case "failed": + return 502; + default: + return 500; + } +} diff --git a/src/sdk-imports.generated.ts b/src/sdk-imports.generated.ts new file mode 100644 index 0000000..630d908 --- /dev/null +++ b/src/sdk-imports.generated.ts @@ -0,0 +1,5 @@ +import type { SdkBundle } from "./sdk-types.js"; + +export async function loadSdkBundle(): Promise { + return null; +} diff --git a/src/sdk-types.ts b/src/sdk-types.ts new file mode 100644 index 0000000..b561a1e --- /dev/null +++ b/src/sdk-types.ts @@ -0,0 +1,70 @@ +export type SdkSession = { readonly attested?: boolean }; + +export type SdkRoot = { + createSession(options: { coordinator: unknown; storage?: unknown }): Promise; + closeSession?(session: SdkSession): void; + normalizeError?(error: unknown, fallback?: string): string; +}; + +export type SdkUser = { + contractRequest( + session: SdkSession, + args: { + amountLamports: number; + payoutPolicy: unknown; + sync?: boolean; + }, + ): Promise<{ + accepted?: boolean; + requestId?: string; + request_id?: string; + }>; +}; + +export type SdkStorage = { + inMemoryStorage?(): unknown; +}; + +export type SdkLpPosition = { + id: string; + status: string; + targetShardCount: number; + shards: Array<{ status: string }>; + committedLamports: number; + earnedLamports: number; +}; + +export type SdkLp = { + createPosition( + session: SdkSession, + args?: { committedLamports?: number; shardCount?: number }, + ): Promise<{ positionId: string; lpPositionCode: string; position?: SdkLpPosition }>; + recoverPosition(session: SdkSession, args: { code: string }): Promise; + completeDkgBatch(session: SdkSession, positionId: string): Promise; + prepareInitialFunding( + session: SdkSession, + positionId: string, + ): Promise<{ + address: string; + requiredLamports: number; + qrPayload: string; + position?: SdkLpPosition; + } | null>; + reconcileFunding(session: SdkSession, positionId: string): Promise; + refill(session: SdkSession, positionId: string): Promise; + withdrawPosition( + session: SdkSession, + positionId: string, + args: { destinationAddresses: string[]; allowManyToOne: boolean }, + ): Promise<{ + execution: { withdrawalId: string; txSignatures: string[] }; + position: SdkLpPosition; + }>; +}; + +export type SdkBundle = { + root: SdkRoot; + user: SdkUser; + storage: SdkStorage; + lp: SdkLp; +}; diff --git a/src/sdk.ts b/src/sdk.ts new file mode 100644 index 0000000..f1767c8 --- /dev/null +++ b/src/sdk.ts @@ -0,0 +1,370 @@ +import { loadSdkBundle } from "./sdk-imports.generated.js"; +import type { SdkBundle, SdkLpPosition, SdkRoot } from "./sdk-types.js"; + +export type WorkerEnv = { + INVISIBLE_COORDINATOR_WS_URL: string; + INVISIBLE_REQUIRED_MODE: string; + INVISIBLE_RELEASE_MRTD: string; + INVISIBLE_INTEL_ROOT_FINGERPRINT: string; + INVISIBLE_ALLOW_MISSING_DCAP_COLLATERAL?: string; + DEMO_DESTINATION_ADDRESS: string; + INVISIBLE_WORKER_API_KEY?: string; +}; + +export type CoordinatorPoolConfig = { + endpoints: Array<{ + wsUrl: string; + expectedHostname: string; + requiredMode: "dev" | "prod" | "auto"; + releasePin: { + mrtd: string; + intelRootFingerprint: string; + allowMissingDcapCollateral?: boolean; + }; + }>; + allowedRoles?: string[]; + preferLeader?: boolean; +}; + +export type PayoutPolicy = { + destinations: Array<{ + address: string; + sharePercent: number; + }>; +}; + +export type AcceptanceReceipt = { + accepted?: boolean; + requestId?: string; + request_id?: string; +}; + +export type PrivateTransferRequest = { + amountLamports: number; + destinationAddress?: string; +}; + +export type LpAction = + | "create" + | "recover" + | "complete-dkg" + | "prepare-funding" + | "reconcile-funding" + | "refill" + | "withdraw"; + +export type LpActionRequest = { + action: LpAction; + positionCode?: string; + destinationAddress?: string; +}; + +export type LpActionResult = + | { + status: "position"; + action: LpAction; + message: string; + position: LpPositionSummary; + lpPositionCode?: string; + } + | { + status: "funding"; + action: "prepare-funding"; + message: string; + position: LpPositionSummary; + address: string; + requiredLamports: number; + qrPayload: string; + } + | { + status: "withdrawal"; + action: "withdraw"; + message: string; + position: LpPositionSummary; + withdrawalId: string; + txSignatures: string[]; + } + | { status: "sdk_missing"; message: string } + | { status: "sdk_not_ready"; message: string } + | { status: "failed"; message: string }; + +export type LpPositionSummary = { + id: string; + status: string; + targetShardCount: number; + shardCount: number; + availableShardCount: number; + fundingQueuedShardCount: number; + pregeneratedShardCount: number; + committedLamports: number; + earnedLamports: number; +}; + +const LP_DEFAULT_TARGET_SHARDS = 200; +const LP_INITIAL_FUNDING_LAMPORTS = 101_000_000; +const LP_WITHDRAWAL_ALLOW_MANY_TO_ONE = true; + +export async function sdkStatus(): Promise<{ packageAvailable: boolean }> { + return { packageAvailable: (await loadSdk()) !== null }; +} + +export async function startPrivateTransfer(env: WorkerEnv, input: PrivateTransferRequest) { + const sdk = await loadSdk(); + if (!sdk) { + return { + status: "sdk_missing", + message: "Install @invisible/sdk from the private package registry before running this Worker.", + }; + } + + try { + const session = await sdk.root.createSession({ + coordinator: buildCoordinatorPool(env), + storage: sdk.storage.inMemoryStorage?.(), + }); + try { + const receipt = await sdk.user.contractRequest(session, { + amountLamports: assertLamports(input.amountLamports), + payoutPolicy: singleDestinationPolicy(input.destinationAddress ?? env.DEMO_DESTINATION_ADDRESS), + sync: true, + }); + + return { + status: "accepted", + requestId: receipt.requestId ?? receipt.request_id ?? "accepted", + }; + } finally { + sdk.root.closeSession?.(session); + } + } catch (error) { + const message = normalizeSdkError(sdk.root, error); + if (message.includes("NOT_ATTESTED") || message.includes("not attested")) { + return { + status: "sdk_not_ready", + message: "SDK connected, but this package build has not completed coordinator attestation yet.", + }; + } + if (message.includes("NOT_IMPLEMENTED") || message.includes("not implemented")) { + return { + status: "sdk_not_ready", + message: "SDK package is installed, but this command is still preview-only in the current build.", + }; + } + return { status: "failed", message }; + } +} + +export async function runLpAction(env: WorkerEnv, input: LpActionRequest): Promise { + const sdk = await loadSdk(); + if (!sdk) { + return { + status: "sdk_missing", + message: "Install @invisible/sdk from the private package registry before running LP actions.", + }; + } + + try { + const session = await sdk.root.createSession({ + coordinator: buildCoordinatorPool(env), + storage: sdk.storage.inMemoryStorage?.(), + }); + + try { + if (input.action === "create") { + const result = await sdk.lp.createPosition(session, { + committedLamports: LP_INITIAL_FUNDING_LAMPORTS, + shardCount: LP_DEFAULT_TARGET_SHARDS, + }); + const position = + result.position ?? (await sdk.lp.recoverPosition(session, { code: result.lpPositionCode })); + return { + status: "position", + action: input.action, + message: "LP position created. Store the LP Position Code before continuing.", + position: summarizeLpPosition(position), + lpPositionCode: result.lpPositionCode, + }; + } + + const recovered = await recoverLpPositionForAction(sdk, session, input.positionCode); + if (input.action === "recover") { + return { + status: "position", + action: input.action, + message: "LP position recovered.", + position: summarizeLpPosition(recovered), + }; + } + + if (input.action === "complete-dkg") { + const position = await sdk.lp.completeDkgBatch(session, recovered.id); + return { + status: "position", + action: input.action, + message: "LP DKG batch completed or reconciled.", + position: summarizeLpPosition(position), + }; + } + + if (input.action === "prepare-funding") { + const plan = await sdk.lp.prepareInitialFunding(session, recovered.id); + if (plan === null) { + return { + status: "position", + action: input.action, + message: "No LP_DKG_0 funding action is currently available.", + position: summarizeLpPosition(recovered), + }; + } + return { + status: "funding", + action: input.action, + message: "Fund only LP_DKG_0 with the exact required amount.", + position: summarizeLpPosition(plan.position ?? recovered), + address: plan.address, + requiredLamports: plan.requiredLamports, + qrPayload: plan.qrPayload, + }; + } + + if (input.action === "reconcile-funding") { + const position = await sdk.lp.reconcileFunding(session, recovered.id); + return { + status: "position", + action: input.action, + message: "LP funding reconciled from coordinator state.", + position: summarizeLpPosition(position), + }; + } + + if (input.action === "refill") { + const position = await sdk.lp.refill(session, recovered.id); + return { + status: "position", + action: input.action, + message: "LP refill requested through the SDK lifecycle.", + position: summarizeLpPosition(position), + }; + } + + const destination = input.destinationAddress?.trim(); + if (!destination) throw new Error("destinationAddress is required for withdraw."); + const result = await sdk.lp.withdrawPosition(session, recovered.id, { + destinationAddresses: [destination], + allowManyToOne: LP_WITHDRAWAL_ALLOW_MANY_TO_ONE, + }); + return { + status: "withdrawal", + action: input.action, + message: "LP withdrawal requested. Reconciliation stays SDK-owned.", + position: summarizeLpPosition(result.position), + withdrawalId: result.execution.withdrawalId, + txSignatures: result.execution.txSignatures, + }; + } finally { + sdk.root.closeSession?.(session); + } + } catch (error) { + const message = normalizeSdkError(sdk.root, error); + if (message.includes("NOT_ATTESTED") || message.includes("not attested")) { + return { + status: "sdk_not_ready", + message: "SDK connected, but this package build has not completed coordinator attestation yet.", + }; + } + return { status: "failed", message }; + } +} + +export function buildCoordinatorPool(env: WorkerEnv): CoordinatorPoolConfig { + const endpoint = new URL(env.INVISIBLE_COORDINATOR_WS_URL); + if (endpoint.protocol !== "wss:" && endpoint.protocol !== "ws:") { + throw new Error("INVISIBLE_COORDINATOR_WS_URL must use ws:// or wss://"); + } + const requiredMode = readRequiredMode(env.INVISIBLE_REQUIRED_MODE); + const allowMissingDcapCollateral = readBoolean( + env.INVISIBLE_ALLOW_MISSING_DCAP_COLLATERAL, + false, + ); + if (requiredMode === "prod" && allowMissingDcapCollateral) { + throw new Error("INVISIBLE_ALLOW_MISSING_DCAP_COLLATERAL is only allowed outside prod mode"); + } + + return { + endpoints: [ + { + wsUrl: env.INVISIBLE_COORDINATOR_WS_URL, + expectedHostname: endpoint.hostname, + requiredMode, + releasePin: { + mrtd: env.INVISIBLE_RELEASE_MRTD, + intelRootFingerprint: env.INVISIBLE_INTEL_ROOT_FINGERPRINT, + ...(allowMissingDcapCollateral ? { allowMissingDcapCollateral: true } : {}), + }, + }, + ], + allowedRoles: ["leader"], + preferLeader: true, + }; +} + +function singleDestinationPolicy(destinationAddress: string): PayoutPolicy { + return { + destinations: [{ address: destinationAddress.trim(), sharePercent: 100 }], + }; +} + +function assertLamports(value: number): number { + if (!Number.isSafeInteger(value) || value <= 0) { + throw new Error("amountLamports must be a positive safe integer."); + } + return value; +} + +function readRequiredMode(value: string): "dev" | "prod" | "auto" { + if (value === "dev" || value === "prod" || value === "auto") return value; + throw new Error("INVISIBLE_REQUIRED_MODE must be dev, prod, or auto"); +} + +function readBoolean(value: string | undefined, fallback: boolean): boolean { + if (value === undefined) return fallback; + return value === "true" || value === "1"; +} + +async function loadSdk() { + return loadSdkBundle(); +} + +async function recoverLpPositionForAction( + sdk: SdkBundle, + session: Awaited>, + positionCode: string | undefined, +): Promise { + const code = positionCode?.trim(); + if (!code) throw new Error("positionCode is required for this LP action."); + return sdk.lp.recoverPosition(session, { code }); +} + +function summarizeLpPosition(position: SdkLpPosition): LpPositionSummary { + return { + id: position.id, + status: position.status, + targetShardCount: position.targetShardCount, + shardCount: position.shards.length, + availableShardCount: countLpShards(position, "AVAILABLE"), + fundingQueuedShardCount: countLpShards(position, "FUNDING_QUEUED"), + pregeneratedShardCount: countLpShards(position, "PREGENERATED"), + committedLamports: position.committedLamports, + earnedLamports: position.earnedLamports, + }; +} + +function countLpShards(position: SdkLpPosition, status: string): number { + return position.shards.filter((shard) => shard.status === status).length; +} + +function normalizeSdkError(sdk: SdkRoot, error: unknown): string { + if (sdk.normalizeError) return sdk.normalizeError(error, "Invisible transfer failed."); + if (error instanceof Error) return error.message; + return "Invisible transfer failed."; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..329ea09 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "types": ["@cloudflare/workers-types", "node"], + "skipLibCheck": true + }, + "include": ["src/**/*.ts", "scripts/**/*.ts", "worker-configuration.d.ts"] +} diff --git a/wrangler.jsonc b/wrangler.jsonc new file mode 100644 index 0000000..17f9d61 --- /dev/null +++ b/wrangler.jsonc @@ -0,0 +1,18 @@ +{ + "name": "invisible-worker", + "main": "src/index.ts", + "compatibility_date": "2026-06-19", + "compatibility_flags": ["nodejs_compat"], + "observability": { + "enabled": true, + "head_sampling_rate": 1 + }, + "vars": { + "INVISIBLE_COORDINATOR_WS_URL": "wss://coordinator.example/ws-noise", + "INVISIBLE_REQUIRED_MODE": "dev", + "INVISIBLE_RELEASE_MRTD": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "INVISIBLE_INTEL_ROOT_FINGERPRINT": "0000000000000000000000000000000000000000000000000000000000000000", + "INVISIBLE_ALLOW_MISSING_DCAP_COLLATERAL": "false", + "DEMO_DESTINATION_ADDRESS": "11111111111111111111111111111111" + } +}