{"_id":"@3flabs/guardian","_rev":"9-f9d1585ad837ffc1b0721d27dc7ba51d","name":"@3flabs/guardian","dist-tags":{"latest":"0.5.0"},"versions":{"0.1.0":{"name":"@3flabs/guardian","version":"0.1.0","license":"MIT","_id":"@3flabs/guardian@0.1.0","maintainers":[{"name":"maxencerb","email":"maxenceraballand00@gmail.com"}],"homepage":"https://github.com/3FLabs/3f-guardian#readme","bugs":{"url":"https://github.com/3FLabs/3f-guardian/issues"},"dist":{"shasum":"f2f52dbcee9f7a8e13b69057077cd0c05aea716e","tarball":"https://registry.npmjs.org/@3flabs/guardian/-/guardian-0.1.0.tgz","fileCount":187,"integrity":"sha512-fNcxGwZNYybzQcGcDDHNA13pYdBSrTQwsqIZwV9MxWqFWfhRoppUMPEyptPTQ5E/P2ey47+S7P30a7JFGJZCjQ==","signatures":[{"sig":"MEYCIQDXGxHzRE0GwjyHpyD093lrZrmU5ymyQMpppT3LeWYiwgIhAMY66vuo240I6YU7fPPtJsWnTAu2/7qCg/+OZc4ng+6C","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":379729},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"8f1d8b43392fd933975c1015d2eaccd80bcb36b4","scripts":{"dev":"bun run --watch src/index.ts","test":"vitest run","build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit","test:watch":"vitest","build:clean":"rm -rf dist && bun run build"},"_npmUser":{"name":"maxencerb","email":"maxenceraballand00@gmail.com"},"repository":{"url":"git+https://github.com/3FLabs/3f-guardian.git","type":"git","directory":"packages/guardian"},"_npmVersion":"11.6.2","description":"HTTP shell for the Guardian off-chain signer service. Hosts plug in policy & signing via a typed abstractions contract.","directories":{},"sideEffects":false,"_nodeVersion":"24.11.1","dependencies":{"zod":"^4.0.0","viem":"^2.48.4","elysia":"^1.4.28","@noble/hashes":"^2.2.0","better-result":"^2.9.0","@elysiajs/openapi":"^1.4.15"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.1.5","typescript":"^5.6.0"},"_npmOperationalInternal":{"tmp":"tmp/guardian_0.1.0_1778068830960_0.5960073249908142","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@3flabs/guardian","version":"0.2.0","license":"MIT","_id":"@3flabs/guardian@0.2.0","maintainers":[{"name":"maxencerb","email":"maxenceraballand00@gmail.com"}],"homepage":"https://github.com/3FLabs/3f-guardian#readme","bugs":{"url":"https://github.com/3FLabs/3f-guardian/issues"},"dist":{"shasum":"a75e44df42381917966c748b00df215f97b7fe35","tarball":"https://registry.npmjs.org/@3flabs/guardian/-/guardian-0.2.0.tgz","fileCount":187,"integrity":"sha512-7pHrZFM31z3yQ5dAtdC+zvFyIPxFFK/ZZ/TUE7HBq8QfS25vU3EhzjDRGwyxzlaRbWUxx1NT3CaXFkA+rHgiuA==","signatures":[{"sig":"MEUCIFDmqRLZ9QbHvNQ507DgDPCnOMFOCQM/chWXz01qDFCbAiEAmyulX1K0B6ZMO9drhdyAqNGKrdKdnbmSVAJOyXQXUxc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@3flabs%2fguardian@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":379745},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"ae8288300ba22c2d8ca9591482f977f891ab0b49","scripts":{"dev":"bun run --watch src/index.ts","test":"vitest run","build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit","test:watch":"vitest","build:clean":"rm -rf dist && bun run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:a546441f-45bc-42c1-b338-e67f76e0d7b4"}},"repository":{"url":"git+https://github.com/3FLabs/3f-guardian.git","type":"git","directory":"packages/guardian"},"_npmVersion":"11.11.0","description":"HTTP shell for the Guardian off-chain signer service. Hosts plug in policy & signing via a typed abstractions contract.","directories":{},"sideEffects":false,"_nodeVersion":"24.14.1","dependencies":{"zod":"^4.0.0","viem":"^2.48.4","elysia":"^1.4.28","@noble/hashes":"^2.2.0","better-result":"^2.9.0","@elysiajs/openapi":"^1.4.15"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.1.5","typescript":"^5.6.0"},"_npmOperationalInternal":{"tmp":"tmp/guardian_0.2.0_1778158306162_0.9274639212199549","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@3flabs/guardian","version":"0.2.1","license":"MIT","_id":"@3flabs/guardian@0.2.1","maintainers":[{"name":"maxencerb","email":"maxenceraballand00@gmail.com"}],"homepage":"https://github.com/3FLabs/3f-guardian#readme","bugs":{"url":"https://github.com/3FLabs/3f-guardian/issues"},"dist":{"shasum":"ba52676da35b4142085bc93af2773a0868c14c46","tarball":"https://registry.npmjs.org/@3flabs/guardian/-/guardian-0.2.1.tgz","fileCount":187,"integrity":"sha512-KeWj5+v7mvC1K4lj4TBo5mLtxSP4GHSnLHjqMNrcwKDixr/PXIkfrN0L9lNK3deE5W8tP3zsI+cdSUt25qpWow==","signatures":[{"sig":"MEYCIQCnfiMx1n61SntNfBOZAvh5En9PYHXRrEU+rSC0mNaj/QIhANW2F833i4o24vfP15+26aLupSerQF/dIUQjRZjFR42B","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@3flabs%2fguardian@0.2.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":381812},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"78bbdaa2eb85994bf9dbf8de6cd13aee2150bead","scripts":{"dev":"bun run --watch src/index.ts","test":"vitest run","build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit","test:watch":"vitest","build:clean":"rm -rf dist && bun run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:a546441f-45bc-42c1-b338-e67f76e0d7b4"}},"repository":{"url":"git+https://github.com/3FLabs/3f-guardian.git","type":"git","directory":"packages/guardian"},"_npmVersion":"11.11.0","description":"HTTP shell for the Guardian off-chain signer service. Hosts plug in policy & signing via a typed abstractions contract.","directories":{},"sideEffects":false,"_nodeVersion":"24.14.1","dependencies":{"zod":"^4.0.0","viem":"^2.48.4","elysia":"^1.4.28","@noble/hashes":"^2.2.0","better-result":"^2.9.0","@elysiajs/openapi":"^1.4.15"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.1.5","typescript":"^5.6.0"},"_npmOperationalInternal":{"tmp":"tmp/guardian_0.2.1_1778161886514_0.5138420828000161","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@3flabs/guardian","version":"0.3.0","license":"MIT","_id":"@3flabs/guardian@0.3.0","maintainers":[{"name":"maxencerb","email":"maxenceraballand00@gmail.com"}],"homepage":"https://github.com/3FLabs/3f-guardian#readme","bugs":{"url":"https://github.com/3FLabs/3f-guardian/issues"},"dist":{"shasum":"6513e718eea15a2695c0a521625f684e2ae4489c","tarball":"https://registry.npmjs.org/@3flabs/guardian/-/guardian-0.3.0.tgz","fileCount":227,"integrity":"sha512-MEl8wqxMbjy9m0/lgZY/bVB5wT0fiVAsFnHLC1M1DBlCv5c6xbsdnw5d1A2RtnL7sDuY2fm0wSFdlenbOjEXGA==","signatures":[{"sig":"MEQCIEjjuv9oIcfw1YGjGPJhXQa+y/N387aWB7jiR2a9nU6/AiBb28oQ3ZRfGOWeg5C27TJhBoFI4L/rFjk+it8gaUWMYg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@3flabs%2fguardian@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":527742},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"baf9dfde730768318f3f4442bed24debf2221cce","scripts":{"dev":"bun run --watch src/index.ts","test":"vitest run","build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit","test:watch":"vitest","build:clean":"rm -rf dist && bun run build","test:integration":"vitest run --config vitest.integration.config.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:a546441f-45bc-42c1-b338-e67f76e0d7b4"}},"repository":{"url":"git+https://github.com/3FLabs/3f-guardian.git","type":"git","directory":"packages/guardian"},"_npmVersion":"11.11.0","description":"HTTP shell for the Guardian off-chain signer service. Hosts plug in policy & signing via a typed abstractions contract.","directories":{},"sideEffects":false,"_nodeVersion":"24.14.1","dependencies":{"zod":"^4.0.0","viem":"^2.48.4","elysia":"^1.4.28","@noble/hashes":"^2.2.0","better-result":"^2.9.0","@elysiajs/openapi":"^1.4.15"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.1.5","typescript":"^5.6.0","@3flabs/guardian-test-fixtures":"0.0.0"},"_npmOperationalInternal":{"tmp":"tmp/guardian_0.3.0_1778176449748_0.6696214191096046","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"name":"@3flabs/guardian","version":"0.3.1","license":"MIT","_id":"@3flabs/guardian@0.3.1","maintainers":[{"name":"maxencerb","email":"maxenceraballand00@gmail.com"}],"homepage":"https://github.com/3FLabs/3f-guardian#readme","bugs":{"url":"https://github.com/3FLabs/3f-guardian/issues"},"dist":{"shasum":"fdd0939a66525797676275b08f9e801add68637b","tarball":"https://registry.npmjs.org/@3flabs/guardian/-/guardian-0.3.1.tgz","fileCount":227,"integrity":"sha512-Fcn9YoyWswyb8Ofm/XxF3PTyEgdUBCiJ5TOP3N99HMHd6v4TSqLoleBS6SKU4R5TWW6LU1xGmD83lsWwDS5YRg==","signatures":[{"sig":"MEQCIE9qmbAf96O+ymTn1X9OL2hMBo8t0jCvfVi3So08Sz6gAiA1+Tz4yEABBpkIPqfX1i3OmMw4QQfsyk8F0S3JIbVbyA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@3flabs%2fguardian@0.3.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":553898},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"95904f2cc82e6b8946d9706108eafc66192c172e","scripts":{"dev":"bun run --watch src/index.ts","test":"vitest run","build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit","test:watch":"vitest","build:clean":"rm -rf dist && bun run build","test:integration":"vitest run --config vitest.integration.config.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:a546441f-45bc-42c1-b338-e67f76e0d7b4"}},"repository":{"url":"git+https://github.com/3FLabs/3f-guardian.git","type":"git","directory":"packages/guardian"},"_npmVersion":"11.11.0","description":"HTTP shell for the Guardian off-chain signer service. Hosts plug in policy & signing via a typed abstractions contract.","directories":{},"sideEffects":false,"_nodeVersion":"24.14.1","dependencies":{"zod":"^4.0.0","viem":"^2.48.4","elysia":"^1.4.28","@noble/hashes":"^2.2.0","better-result":"^2.9.0","@elysiajs/openapi":"^1.4.15"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.1.5","typescript":"^5.6.0","@3flabs/guardian-test-fixtures":"0.0.0"},"_npmOperationalInternal":{"tmp":"tmp/guardian_0.3.1_1778178507409_0.9772560525065355","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@3flabs/guardian","version":"0.4.0","license":"MIT","_id":"@3flabs/guardian@0.4.0","maintainers":[{"name":"maxencerb","email":"maxenceraballand00@gmail.com"}],"homepage":"https://github.com/3FLabs/3f-guardian#readme","bugs":{"url":"https://github.com/3FLabs/3f-guardian/issues"},"dist":{"shasum":"dd95e6749a3206b2ba1a5b42a3ad044e3ed82d51","tarball":"https://registry.npmjs.org/@3flabs/guardian/-/guardian-0.4.0.tgz","fileCount":232,"integrity":"sha512-szSeqb1jozGuhfFYI+9VQbfI7TdhUGGl0Of+b4XH2wPFHrRfgzvRe24ZeZaGZLuu2qQ34sH2C2zARo5KzVvtoQ==","signatures":[{"sig":"MEUCIQCu5g/Plp9wK/rr9Fk6civtHeoFSD7OBTNNe0K2Sza36AIgciRgFRppmIBJhujSJ/y60ewf/mpQbAY2dzLwnQkfoA4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@3flabs%2fguardian@0.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":687520},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"0d1a85e9e7b7550c7bcd38b51d9e0ca21ec1b547","scripts":{"dev":"bun run --watch src/index.ts","test":"vitest run","build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit","test:watch":"vitest","build:clean":"rm -rf dist && bun run build","test:integration":"vitest run --config vitest.integration.config.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:a546441f-45bc-42c1-b338-e67f76e0d7b4"}},"repository":{"url":"git+https://github.com/3FLabs/3f-guardian.git","type":"git","directory":"packages/guardian"},"_npmVersion":"11.13.0","description":"HTTP shell for the Guardian off-chain signer service. Hosts plug in policy & signing via a typed abstractions contract.","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","dependencies":{"zod":"^4.0.0","viem":"^2.52.2","elysia":"^1.4.28","@noble/hashes":"^2.2.0","better-result":"^2.9.2","@elysiajs/openapi":"^1.4.15"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.1.8","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/guardian_0.4.0_1781179298107_0.6055883699879132","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@3flabs/guardian","version":"0.5.0","license":"MIT","_id":"@3flabs/guardian@0.5.0","maintainers":[{"name":"maxencerb","email":"maxenceraballand00@gmail.com"}],"homepage":"https://github.com/3FLabs/3f-guardian#readme","bugs":{"url":"https://github.com/3FLabs/3f-guardian/issues"},"dist":{"shasum":"ea37c7a4282a767d8527fc7412505902ab34456f","tarball":"https://registry.npmjs.org/@3flabs/guardian/-/guardian-0.5.0.tgz","fileCount":232,"integrity":"sha512-xpE8Sap4fmh4vu2ET+Nn5YaNMX6m+yAQdhJ4MtMlwQeW/z3lPw8aqBfBIEFdfHwaQllEjQRWnYNKoLfdTsMicA==","signatures":[{"sig":"MEUCICOYfLwL2D4iXZISjdiGweGNl7kRtD7flfiBLV+Cvw5vAiEA+O5JDZdSuCaXjlTL6/KtaoaDBnZ42I5XndqDHQ+y1KY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@3flabs%2fguardian@0.5.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":688403},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"ed1bdf49697ffe9cd9113eb98e6066c224797adf","scripts":{"dev":"bun run --watch src/index.ts","test":"vitest run","build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit","test:watch":"vitest","build:clean":"rm -rf dist && bun run build","test:integration":"vitest run --config vitest.integration.config.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:a546441f-45bc-42c1-b338-e67f76e0d7b4"}},"repository":{"url":"git+https://github.com/3FLabs/3f-guardian.git","type":"git","directory":"packages/guardian"},"_npmVersion":"11.13.0","description":"HTTP shell for the Guardian off-chain signer service. Hosts plug in policy & signing via a typed abstractions contract.","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","dependencies":{"zod":"^4.0.0","viem":"^2.52.2","elysia":"^1.4.28","@noble/hashes":"^2.2.0","better-result":"^2.9.2","@elysiajs/openapi":"^1.4.15"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.1.8","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/guardian_0.5.0_1782133817074_0.33719840652024824","host":"s3://npm-registry-packages-npm-production"}}},"time":{"created":"2026-05-06T12:00:30.898Z","modified":"2026-07-24T13:32:37.707Z","0.1.0":"2026-05-06T12:00:31.077Z","0.2.0":"2026-05-07T12:51:46.328Z","0.2.1":"2026-05-07T13:51:26.665Z","0.3.0":"2026-05-07T17:54:09.916Z","0.3.1":"2026-05-07T18:28:27.575Z","0.4.0":"2026-06-11T12:01:38.241Z","0.5.0":"2026-06-22T13:10:17.211Z"},"bugs":{"url":"https://github.com/3FLabs/3f-guardian/issues"},"license":"MIT","homepage":"https://github.com/3FLabs/3f-guardian#readme","repository":{"url":"git+https://github.com/3FLabs/3f-guardian.git","type":"git","directory":"packages/guardian"},"description":"HTTP shell for the Guardian off-chain signer service. Hosts plug in policy & signing via a typed abstractions contract.","maintainers":[{"email":"me@maxencerb.com","name":"maxencerb"}],"readme":"# @3flabs/guardian\n\nHTTP shell for the **Guardian** off-chain signer service — the v1 contract specified at\n[gist b50021fb3ee2c059d228b4dcddbb577e](https://gist.github.com/maxencerb/b50021fb3ee2c059d228b4dcddbb577e).\n\nThe package never sees a private key, an HMAC secret, or a chain RPC URL. Hosts implement\na typed `GuardianAbstractions` and pass it to `buildGuardianServer(abs)`. Default building\nblocks (logger, rate limiter, cache, Appendix-A check builders, on-chain ABIs) live in the\ncompanion [`@3flabs/guardian-defaults`](https://npmjs.com/package/@3flabs/guardian-defaults).\n\n## Contents\n\n- [Install](#install)\n- [Quickstart — running a signer on a private key](#quickstart--running-a-signer-on-a-private-key)\n- [`GuardianAbstractions` at a glance](#guardianabstractions-at-a-glance)\n- [Timeouts, body cap & upstream probe](#timeouts-body-cap--upstream-probe)\n- [EIP-712 typed-data builders](#eip-712-typed-data-builders)\n- [`SigningContext`](#signingcontext)\n- [Endpoints](#endpoints)\n  - [Required request headers (§6.2)](#required-request-headers-62)\n- [Error envelope (§6.5)](#error-envelope-65)\n- [Running integration tests](#running-integration-tests)\n\n## Install\n\n```bash\nbun add @3flabs/guardian @3flabs/guardian-defaults viem better-result\n# or\nnpm install @3flabs/guardian @3flabs/guardian-defaults viem better-result\n```\n\nPeer runtime: any ESM-compatible Node ≥ 22 / Bun ≥ 1.1.\n\n## Quickstart — running a signer on a private key\n\nThe shortest fully-functional Guardian. Wires a dev private-key signer, an in-memory\nbearer-token directory, viem RPC clients per chain, the four §A check runners from\n`@3flabs/guardian-defaults`, and the `makeSign*` orchestrators from this package.\n\n```ts\n// src/server.ts\nimport { Result } from \"better-result\";\nimport { http, createPublicClient, type Hex, type PublicClient } from \"viem\";\nimport { mainnet, base } from \"viem/chains\";\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport { z } from \"zod\";\n\nimport {\n  buildGuardianServer,\n  ENDPOINT_SCOPES,\n  makeSignIntentFundBinding,\n  makeSignIntentRequestBinding,\n  makeSignIntentSwap,\n  makeSignRequestWhitelisting,\n  privateKeyToSignTypedData,\n  UnauthenticatedError,\n  UnsupportedChainError,\n  type GuardianAbstractions,\n  type TokenInfo,\n} from \"@3flabs/guardian\";\nimport { pinoLogger } from \"@3flabs/guardian-defaults/logger\";\nimport { inMemoryRateLimiter } from \"@3flabs/guardian-defaults/rate-limit\";\nimport { inMemoryCache } from \"@3flabs/guardian-defaults/cache\";\nimport {\n  buildIntentFundBindingChecks,\n  buildIntentRequestBindingChecks,\n  buildIntentSwapChecks,\n  buildRequestWhitelistingChecks,\n  zA1OnChainData,\n  type IntentFundBindingPolicy,\n  type IntentRequestBindingPolicy,\n  type IntentSwapPolicy,\n  type RequestWhitelistingPolicy,\n} from \"@3flabs/guardian-defaults/checks\";\n\n// 1. Signer (dev only — production uses KMS/HSM-backed `SignTypedData`).\nconst PRIVATE_KEY = process.env.GUARDIAN_SIGNER_KEY as Hex | undefined;\nif (!PRIVATE_KEY) throw new Error(\"Missing GUARDIAN_SIGNER_KEY\");\nconst guardianSigner = privateKeyToAccount(PRIVATE_KEY).address;\nconst signTypedData = privateKeyToSignTypedData(PRIVATE_KEY);\n\n// 2. Per-chain viem clients. Cast each to the generic `PublicClient`\n//    so chain-narrowed types (e.g. base's deposit-tx formatter) widen\n//    to the shape `getChainClient` expects.\nconst clients: Record<number, PublicClient> = {\n  1: createPublicClient({\n    chain: mainnet,\n    transport: http(process.env.RPC_MAINNET),\n  }) as PublicClient,\n  8453: createPublicClient({\n    chain: base,\n    transport: http(process.env.RPC_BASE),\n  }) as PublicClient,\n};\nconst supportedChains = Object.keys(clients).map(Number);\n\n// 3. Bearer-token directory.\nconst TOKENS = new Map<string, TokenInfo>([\n  [\n    process.env.DEV_BEARER_TOKEN ?? \"dev-token\",\n    { tokenId: \"dev\", scopes: new Set(ENDPOINT_SCOPES), requiresHmac: false },\n  ],\n]);\n\n// 4. Caches: ERC-5267 domain (long TTL) + §A.1/§A.4 on-chain reads (short TTL).\n//    Each cache takes a schema (any Standard Schema — zod here) that\n//    re-validates every hit on read; a value that fails to parse is a miss.\nconst eip712DomainCache = inMemoryCache(z.object({ name: z.string(), version: z.string() }), {\n  defaultTtlMs: 24 * 60 * 60_000,\n  maxEntries: 256,\n});\nconst a1OnChainCache = inMemoryCache(zA1OnChainData, {\n  defaultTtlMs: 5 * 60_000,\n  maxEntries: 1024,\n});\n\n// 5. Policies. Empty Sets cause the runner to fail every check —\n//    populate with the factory / owner / puller / consumer / fund /\n//    position-manager addresses your deployment trusts. See the\n//    `@3flabs/guardian-defaults` README for full type docs.\nconst requestBindingPolicy: IntentRequestBindingPolicy = {\n  maxDeadlineSecondsAhead: 600,\n  acceptedRequestFactories: new Map([[1, new Set<string>(/* \"0xRequestFactory\" */)]]),\n  acceptedOwners: new Map([[1, new Set<string>(/* \"0xOwner\" */)]]),\n  acceptedPullers: new Map([[1, new Set<string>(/* \"0xPuller\" */)]]),\n  acceptedConsumers: new Map([[1, new Set<string>(/* \"0xConsumer\" */)]]),\n  eventScanBlockRange: 10_000n,\n  eventScanMaxLookbackBlocks: 1_000_000n,\n};\nconst fundBindingPolicy: IntentFundBindingPolicy = {\n  maxDeadlineSecondsAhead: 600,\n  acceptedFunds: new Map([[1, new Set<string>(/* \"0xFund\" */)]]),\n  acceptedOwners: new Map([[1, new Set<string>(/* \"0xOwner\" */)]]),\n};\nconst swapPolicy: IntentSwapPolicy = {\n  maxDeadlineSecondsAhead: 600,\n  acceptedPmFactories: new Map([[1, new Set<string>(/* \"0xPmFactory\" */)]]),\n  acceptedPmOwners: new Map([[1, new Set<string>(/* \"0xPmOwner\" */)]]),\n  swapPriceToleranceBps: 1,\n};\nconst whitelistingPolicy: RequestWhitelistingPolicy = {\n  ...requestBindingPolicy,\n  maxNonceAboveFloor: 100n,\n};\n\n// 6. Wire the abstractions and listen.\nconst abs: GuardianAbstractions = {\n  metadata: {\n    build: process.env.BUILD_ID ?? \"0.1.0\",\n    guardianSigner,\n    supportedChains,\n  },\n  logger: pinoLogger({ level: \"info\", bindings: { service: \"guardian\" } }),\n  liveness: async () => Result.ok(),\n  getChainClient: (chainId) => {\n    const client = clients[chainId];\n    return client\n      ? Result.ok(client)\n      : Result.err(new UnsupportedChainError({ message: \"unsupported\", chainId }));\n  },\n  authenticate: async (token) => {\n    const info = TOKENS.get(token);\n    return info\n      ? Result.ok(info)\n      : Result.err(new UnauthenticatedError({ message: \"Unknown token\" }));\n  },\n  signTypedData,\n  accountRateLimit: inMemoryRateLimiter({ limit: 60, windowSeconds: 60 }),\n\n  // Each `makeSign*` factory wires a defaults-package check runner into a\n  // ready-to-use validate-and-sign abstraction: the runner evaluates §A\n  // on-chain checks, this package fetches the EIP-712 domain\n  // (`eip712Domain()` per ERC-5267) and signs the matching typed-data.\n  signIntentRequestBinding: makeSignIntentRequestBinding({\n    checks: buildIntentRequestBindingChecks({\n      policy: requestBindingPolicy,\n      cache: a1OnChainCache,\n    }),\n    guardianSigner,\n    cache: eip712DomainCache,\n  }),\n  signIntentFundBinding: makeSignIntentFundBinding({\n    checks: buildIntentFundBindingChecks({ policy: fundBindingPolicy }),\n    guardianSigner,\n    cache: eip712DomainCache,\n  }),\n  signIntentSwap: makeSignIntentSwap({\n    checks: buildIntentSwapChecks({ policy: swapPolicy }),\n    guardianSigner,\n    cache: eip712DomainCache,\n  }),\n  signRequestWhitelisting: makeSignRequestWhitelisting({\n    checks: buildRequestWhitelistingChecks({\n      policy: whitelistingPolicy,\n      guardianSigner,\n      cache: a1OnChainCache,\n    }),\n    guardianSigner,\n    cache: eip712DomainCache,\n  }),\n};\n\nbuildGuardianServer(abs).listen(3000);\n```\n\n`buildGuardianServer` returns a vanilla [Elysia](https://elysiajs.com/) instance, so callers\nretain full access to `.listen()`, `.handle()` (for tests), and `.use()` for further\ncomposition. Composition caveats — parts of guardian's request pipeline reach beyond the\nroutes it defines:\n\n- the §6.1 JSON-only content-type guard on POST / PUT (415 otherwise), guardian's body\n  parser for `application/json` and `text/*` bodies, and its `maxBodyBytes` cap (413 past\n  the cap) apply to **every route of the entire composed application** — any app, at any\n  depth, that `.use()`s the guardian instance — not just routes added to the returned\n  instance. Other content types (multipart, urlencoded, …) are left untouched for Elysia's\n  own parser chain everywhere, and the cap is enforced only on the content types guardian\n  buffers;\n- the `requestId` / `logger` / `rawBody` context keys shadow any same-named keys the host\n  derives.\n\nFor the per-subsystem deep dive (logger options, rate-limiter store interface, cache TTL\nsemantics, full policy type tables), see the\n[`@3flabs/guardian-defaults` README](https://npmjs.com/package/@3flabs/guardian-defaults).\n\n## `GuardianAbstractions` at a glance\n\nRequired:\n\n- `metadata: GuardianMetadata` — static `{ build, guardianSigner, supportedChains }` exposed at GET `/version`.\n- `liveness: () => Promise<Result<void, GuardianError>>` — readiness probe (§7.1).\n- `getChainClient(chainId): Result<PublicClient, UnsupportedChainError>` — viem client per chain.\n- `authenticate(token): Promise<Result<TokenInfo, UnauthenticatedError>>` — bearer → token info.\n- `signTypedData: SignTypedData` — host-owned EIP-712 signer (use `privateKeyToSignTypedData(hex)` for dev, KMS/HSM in prod).\n- `signIntentRequestBinding`, `signIntentFundBinding`, `signIntentSwap`, `signRequestWhitelisting` — the four §7.3-7.6 validate-and-sign abstractions.\n\nOptional:\n\n- `logger?: Logger` — pino-compatible. Falls back to `noopLogger`. Use `pinoLogger()` from `@3flabs/guardian-defaults`.\n- `accountRateLimit?(tokenInfo, options?): Promise<Result<RateLimitWindow, GuardianError>>` — populates §6.3 `X-RateLimit-*` headers; `Err(RateLimitedError)` yields 429 with `Retry-After`. Use `inMemoryRateLimiter()` from `@3flabs/guardian-defaults`.\n- `probeUpstream?(): Result<void, UpstreamUnavailableError>` — consulted at the top of every signing request; `Err` short-circuits with the probe's own 502/503 `upstream_unavailable` before any chain access.\n- `timeouts?: GuardianTimeouts` — per-call deadlines for the async abstractions. See [Timeouts, body cap & upstream probe](#timeouts-body-cap--upstream-probe).\n- `maxBodyBytes?: number` — cap (bytes) on buffered request bodies. Default 1 MiB (`DEFAULT_MAX_BODY_BYTES`).\n\n`TokenInfo`:\n\n```ts\ntype TokenInfo = {\n  readonly tokenId: string;\n  readonly scopes: ReadonlySet<EndpointScope>;   // see ENDPOINT_SCOPES\n  readonly requiresHmac: boolean;\n  readonly hmacSecret?: string;                   // required when requiresHmac is true\n};\n```\n\n## Timeouts, body cap & upstream probe\n\nEvery host abstraction call is bounded by a per-call deadline. `GuardianAbstractions.timeouts`\noverrides the defaults (exported as `DEFAULT_GUARDIAN_TIMEOUTS`):\n\n| Field | Bounds | Default |\n|---|---|---|\n| `authenticateMs` | `authenticate(token)` | 2 000 ms |\n| `rateLimitMs` | `accountRateLimit(tokenInfo)` | 1 000 ms |\n| `livenessMs` | `liveness()` (GET `/health`) | 5 000 ms |\n| `signMs` | a whole `sign*` validate-and-sign call | 6 000 ms |\n\nOn expiry the shell responds `503 upstream_unavailable` and logs the abstraction name at\nerror level. The deadlines apply sequentially on one signing request\n(`authenticate` → `accountRateLimit` → `sign*`), so the worst-case sum\n`authenticateMs + rateLimitMs + signMs` (9 s with the defaults) must stay below the HTTP\nserver's idle timeout (`Bun.serve` defaults to 10 s), otherwise the socket is reset before\nthe 503 envelope can be written. Hosts raising any deadline past that budget must also pass\na larger `idleTimeout` to `.listen()` (`Bun.serve` accepts up to 255 s). Overrides are\nvalidated at construction: any non-finite or ≤ 0 value throws a `TypeError`, so\nmisconfiguration fails loudly at startup instead of turning every call into an instant 503.\n\n`authenticate`, `accountRateLimit` and `liveness` receive an optional `{ signal: AbortSignal }`\nargument (type `AbstractionCallOptions`), and `SigningContext` carries an optional `signal` —\neach aborts when the corresponding deadline expires. Implementations SHOULD forward the\nsignal to their I/O so a timed-out call stops consuming resources; ignoring it is safe (the\nshell races the call against the deadline regardless).\n\n`maxBodyBytes` (default 1 MiB, exported as `DEFAULT_MAX_BODY_BYTES`) caps the request body\nthe shell buffers for §5.4 HMAC verification and JSON parsing. Bodies past the cap are\nrejected with 413 (`bad_request` envelope code, thrown as the exported `PayloadTooLargeError`)\nbefore authentication. Hosts deploying via `Bun.serve` SHOULD also set `maxRequestBodySize`\nas a transport-level backstop.\n\n`probeUpstream`, when supplied, is consulted at the top of every signing request and\nshort-circuits with the probe's own 502/503 `upstream_unavailable` before any chain access —\nuseful when the host has already detected that its upstream is down.\n\n## EIP-712 typed-data builders\n\nEach of the four signing surfaces ships:\n\n- `build*TypedData(...)` — pure builder returning `{ typedData, payloadHash }`. Useful for tests and bespoke signers.\n- `makeSign*(...)` — orchestrator that composes a host-supplied check runner, the typed-data builder, ERC-5267 `eip712Domain()` resolution, `ctx.signTypedData`, and the §6.4 SigningSuccess assembly.\n\n| Endpoint | Builder | Orchestrator | Verifying contract |\n|---|---|---|---|\n| `intent-request-bindings` (§A.1) | `buildIntentRequestBindingTypedData` | `makeSignIntentRequestBinding` | `body.facility` |\n| `intent-fund-bindings` (§A.2)    | `buildIntentFundBindingTypedData`    | `makeSignIntentFundBinding`    | `body.facility` |\n| `intent-swaps` (§A.3)            | `buildIntentSwapTypedData`           | `makeSignIntentSwap`           | `body.facility` |\n| `request-whitelistings` (§A.4)   | `buildWhitelistRequestTypedData` / `buildUnwhitelistRequestTypedData` | `makeSignRequestWhitelisting` | `body.whitelistBook` |\n\nTypehashes mirror the on-chain verifiers (grunt's `Facility*` and the `RequestWhitelist` repo); `domainName` and `version` are read from `eip712Domain()` per ERC-5267. The `tests/typed-data/typehashes.test.ts` suite cross-checks each typehash against the literal pinned in the contract.\n\nEvery builder throws if `domain.verifyingContract` does not equal (case-insensitively) the\nbody's verifying contract — `body.facility` for the three intent builders,\n`body.whitelistBook` for the whitelist / unwhitelist builders.\n\nFor `intent-swaps`, `canonicaliseSwapLegs` sorts legs by ascending intent id (asset address as tie-break) so `[A, B]` and `[B, A]` produce byte-identical `signature` and `payloadHash` (§7.5).\n\nThe orchestrators accept an optional `Eip712DomainCache` (structurally compatible with `AsyncCache<{ name; version }>` from `@3flabs/guardian-defaults/cache`) so the per-contract `eip712Domain()` read is amortised across signing calls. `fetchEip712DomainNameVersion` is exported separately for callers that compose their own runners.\n\nA caller-supplied verifying contract (`facility` / `whitelistBook`) with no code, no\nERC-5267 support, or a reverting `eip712Domain()` yields `404 not_found`;\n`503 upstream_unavailable` is reserved for transport-level RPC failures, and neither error\nmessage embeds raw viem error text.\n\n## `SigningContext`\n\nPassed to every `sign*` abstraction:\n\n- `chainId`, `client` — viem `PublicClient` for that chain\n- `requestId`, `tokenId` — for log correlation\n- `now` — wall-clock snapshotted at request entry (deadline checks rely on this)\n- `signTypedData` — host EIP-712 signer\n- `logger` — child-bound to `{ requestId, tokenId, chainId }`\n- `signal` — optional; aborts when the `signMs` deadline expires (forward it to on-chain\n  reads / KMS calls so a timed-out request stops consuming resources)\n\n## Endpoints\n\n| Method | Path | Scope | 409? |\n|--------|------|-------|------|\n| GET    | `/health`                                  | (unauthenticated) | — |\n| GET    | `/version`                                 | (unauthenticated) | — |\n| POST   | `/v1/facility/intent-request-bindings`     | `facility:intent-request-bindings`     | no  |\n| POST   | `/v1/facility/intent-fund-bindings`        | `facility:intent-fund-bindings`        | yes |\n| POST   | `/v1/facility/intent-swaps`                | `facility:intent-swaps`                | no  |\n| POST   | `/v1/whitelist-book/request-whitelistings` | `whitelist-book:request-whitelistings` | no  |\n\nOpenAPI spec at `/openapi/json`; Scalar UI at `/openapi`. The document declares the 413 and\n415 responses on every POST route, and response hex fields (`guardian`, `signature`,\n`payloadHash`, `address`) carry the lower-case wire patterns actually emitted (e.g.\n`^0x[0-9a-f]{40}$`); request bodies keep accepting any case.\n\nRequest-shape notes:\n\n- Unknown routes and method mismatches return `404 {\"error\":\"not_found\",\"message\":\"Unknown route\"}`.\n- JSON content types are matched case-insensitively per RFC 7231\n  (`Application/Json; charset=utf-8` is accepted); non-JSON content types on POST yield 415.\n- A malformed JSON body returns `400 bad_request` (message `\"Malformed JSON: …\"`).\n  Parse-phase rejections (400 / 413) are emitted before authentication; validation-phase\n  body 400s still come after 401 / 403.\n- Decimal uint256 string fields (`intent.id`, swap-leg `amount`, whitelisting `nonce`)\n  reject strings longer than 78 digits and any non-numeric input as a clean field-level 400.\n\n### Required request headers (§6.2)\n\n`X-Client-Name` (1-64 chars of `[A-Za-z0-9._-]`) and `X-Client-Version` (Semantic Version\n2.0.0) are **required on every protected `/v1/*` route** and rendered as OpenAPI\nparameters; a missing or malformed value yields `400 bad_request` (auth runs first, so\n401 / 403 still preempt the header 400). `X-Request-Id` and the `X-Guardian-*` HMAC headers\nare documented but not schema-validated: a malformed request id is replaced per §3.7, and\nevery §5.4 HMAC violation maps to 401 per §5.4.3.\n\n## Error envelope (§6.5)\n\nAll non-2xx responses share:\n\n```json\n{\n  \"error\": \"<machine_readable_code>\",\n  \"message\": \"<human_readable_description>\",\n  \"requestId\": \"<uuid>\",\n  \"details\": { /* optional */ },\n  \"checks\": [ /* present for validation_failed, MAY be present for state_conflict */ ]\n}\n```\n\nTagged error classes (`UnauthenticatedError`, `ForbiddenError`, `BadRequestError`,\n`UnsupportedMediaTypeError`, `PayloadTooLargeError`, `UnsupportedChainError`,\n`NotFoundError`, `StateConflictError`, `ValidationFailedError`, `RateLimitedError`,\n`InternalError`, `UpstreamUnavailableError`) are exported so abstraction implementations\ncan construct them directly. The `SigningError` and `GuardianError` discriminated unions\nhelp typing custom runners.\n\n`UpstreamUnavailableError` accepts an optional `retryAfterSeconds`; when set, the 502/503\nresponse carries a `Retry-After` header. Unexpected (non-tagged) thrown values always\nproduce a 500 envelope with the fixed message `\"Unexpected Guardian-side failure\"` — the\nreal error is only visible in server-side logs.\n\n## Running integration tests\n\nThe package ships an integration suite that boots a real anvil per vitest worker (via\n[prool](https://github.com/wevm/prool)), deploys the Guardian-relevant slice of the 3F\nprotocol, and exercises the four `makeSign*` orchestrators end-to-end:\n\n- `tests/integration/eip712-domain.integration.test.ts` — verifies\n  `fetchEip712DomainNameVersion` reads `{ name: \"3F\", version: \"1\" }` from the deployed\n  Facility and `{ name: \"3fRequestWhitelist\", version: \"1\" }` from the RequestWhitelist\n  proxy, plus cache amortisation.\n- `tests/integration/sign-runners.integration.test.ts` — proves each `makeSign*` produces\n  signatures that recover to the configured signer. The `makeSignIntentRequestBinding`\n  case additionally submits the signature on-chain via `Facility.setRequest(…)` from a\n  facilitator-roled account and asserts the receipt is `success` — which verifies the\n  contract accepts the off-chain digest.\n\nPrerequisite: `anvil` on `PATH`. From the repo root:\n\n```bash\nbun run test:integration\n```\n\nThe shared anvil pool, deployment script, and foundry artifacts live in the private\n`@3flabs/guardian-test-fixtures` workspace package and are never published.\n\n## License\n\nMIT.\n","readmeFilename":"README.md"}