@provex/eas 1.9.2 → 1.10.0-rc.20260912192040.fdbb8fc

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
- import * as easSdkNamespace from '@ethereum-attestation-service/eas-sdk';
1
+ import type * as EasSdkModule from '@ethereum-attestation-service/eas-sdk';
2
2
  import type { Address, Hex, LocalAccount } from 'viem';
3
+ declare const easSdkNamespace: typeof EasSdkModule;
3
4
  type OffchainAttestationVersionEnum = (typeof easSdkNamespace)['OffchainAttestationVersion'][keyof (typeof easSdkNamespace)['OffchainAttestationVersion']];
4
5
  /**
5
6
  * @provex/eas — off-chain Ethereum Attestation Service attestation builder.
@@ -1,18 +1,9 @@
1
- // The EAS SDK has two interop quirks that make a single import shape
2
- // unworkable across our two test runners:
3
- // 1. The package ships CJS (`main`) + ESM (`module`), with no `"type"`
4
- // field at root and no `.js` extensions inside its ESM build.
5
- // 2. tsx (Node strict ESM) refuses the ESM build (extensionless paths)
6
- // and falls back to the CJS build, where the named exports live on
7
- // `default` — `* as` gives `{ default, 'module.exports' }`.
8
- // vitest (Vite resolver) loads the ESM build directly, where
9
- // `* as` gives a flat namespace of named exports.
10
- //
11
- // Resolve once, then reach into `default` if the flat namespace is empty.
12
- // This is the resolver-agnostic interop shape.
13
- import * as easSdkNamespace from '@ethereum-attestation-service/eas-sdk';
1
+ import easSdkNamespaceRaw from '#eas-sdk-namespace';
14
2
  import { zeroAddress } from 'viem';
15
3
  import { getAgeSchemaUid, getEasAddress, getReleaseSchemaUid, getSchemaUid, PROVEX_AGE_SCHEMA, PROVEX_IDENTITY_SCHEMA, PROVEX_RELEASE_SCHEMA, } from './eas.js';
4
+ // Runtime namespace, typed against the erased `import type` above so the shims
5
+ // below (`typeof easSdkNamespace`) keep their exact SDK types.
6
+ const easSdkNamespace = easSdkNamespaceRaw;
16
7
  const easSdkExports = (() => {
17
8
  const flat = easSdkNamespace;
18
9
  if (typeof flat.SchemaEncoder === 'function')
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Browser resolution of the Ethereum Attestation Service software development
3
+ * kit.
4
+ *
5
+ * A bundler (Vite/Rollup, esbuild in browser mode) compiles this build, and
6
+ * unlike plain Node it adds the missing file extensions throughout the kit's
7
+ * ECMAScript build as it inlines the code — so an ordinary namespace import
8
+ * works and produces the flat shape the browser bundle expects. The Node twin's
9
+ * `createRequire` has no browser equivalent (there is no `node:module`), which
10
+ * is the whole reason these two files exist separately; `#eas-sdk-namespace` in
11
+ * the package's `imports` map selects this one under the `browser` condition.
12
+ */
13
+ import * as easSdkNamespace from '@ethereum-attestation-service/eas-sdk';
14
+ export default easSdkNamespace;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Browser resolution of the Ethereum Attestation Service software development
3
+ * kit.
4
+ *
5
+ * A bundler (Vite/Rollup, esbuild in browser mode) compiles this build, and
6
+ * unlike plain Node it adds the missing file extensions throughout the kit's
7
+ * ECMAScript build as it inlines the code — so an ordinary namespace import
8
+ * works and produces the flat shape the browser bundle expects. The Node twin's
9
+ * `createRequire` has no browser equivalent (there is no `node:module`), which
10
+ * is the whole reason these two files exist separately; `#eas-sdk-namespace` in
11
+ * the package's `imports` map selects this one under the `browser` condition.
12
+ */
13
+ import * as easSdkNamespace from '@ethereum-attestation-service/eas-sdk';
14
+ export default easSdkNamespace;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Node resolution of the Ethereum Attestation Service software development kit.
3
+ *
4
+ * Plain Node's ECMAScript-module resolver rejects the kit's published
5
+ * ECMAScript build outright: its relative imports carry no file extension
6
+ * (`export ... from './eas'`), and so do a transitive dependency's subpaths
7
+ * (`lodash/isEqual`). Node's own resolver treats those as fatal —
8
+ * ERR_MODULE_NOT_FOUND — which crashed the gate's identity-verify route the
9
+ * instant it loaded `@provex/eas/build` at runtime (the built gate runs exactly
10
+ * that resolver, with this package left external).
11
+ *
12
+ * CommonJS `require` resolves every one of those extensionless paths
13
+ * transparently, across the whole dependency subtree, so this file forces the
14
+ * kit's `require` condition through createRequire. Callers load it lazily, so
15
+ * the kit still never touches the process boot path — the boot-safety property
16
+ * the gate depends on is preserved.
17
+ *
18
+ * The browser twin (`eas-sdk-namespace.browser.ts`) resolves the kit the other
19
+ * way; `#eas-sdk-namespace` in the package's `imports` map picks between them.
20
+ */
21
+ import type * as EasSdkModule from '@ethereum-attestation-service/eas-sdk';
22
+ declare const easSdkNamespace: typeof EasSdkModule;
23
+ export default easSdkNamespace;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Node resolution of the Ethereum Attestation Service software development kit.
3
+ *
4
+ * Plain Node's ECMAScript-module resolver rejects the kit's published
5
+ * ECMAScript build outright: its relative imports carry no file extension
6
+ * (`export ... from './eas'`), and so do a transitive dependency's subpaths
7
+ * (`lodash/isEqual`). Node's own resolver treats those as fatal —
8
+ * ERR_MODULE_NOT_FOUND — which crashed the gate's identity-verify route the
9
+ * instant it loaded `@provex/eas/build` at runtime (the built gate runs exactly
10
+ * that resolver, with this package left external).
11
+ *
12
+ * CommonJS `require` resolves every one of those extensionless paths
13
+ * transparently, across the whole dependency subtree, so this file forces the
14
+ * kit's `require` condition through createRequire. Callers load it lazily, so
15
+ * the kit still never touches the process boot path — the boot-safety property
16
+ * the gate depends on is preserved.
17
+ *
18
+ * The browser twin (`eas-sdk-namespace.browser.ts`) resolves the kit the other
19
+ * way; `#eas-sdk-namespace` in the package's `imports` map picks between them.
20
+ */
21
+ import { createRequire } from 'node:module';
22
+ const requireFromHere = createRequire(import.meta.url);
23
+ const easSdkNamespace = requireFromHere('@ethereum-attestation-service/eas-sdk');
24
+ export default easSdkNamespace;
@@ -78,6 +78,31 @@ export declare const isOffchainAttestationShape: (value: unknown) => value is Of
78
78
  * attestation."
79
79
  */
80
80
  export declare const liftWireOffchainAttestation: (wire: Record<string, unknown>) => OffchainEasAttestation | null;
81
+ /**
82
+ * Wire form of an off-chain attestation: identical to
83
+ * {@link OffchainEasAttestation} except `message.time` and
84
+ * `message.expirationTime` are base-10 strings rather than `bigint`.
85
+ */
86
+ export type WireOffchainAttestation = Omit<OffchainEasAttestation, 'message'> & {
87
+ message: Omit<OffchainEasAttestation['message'], 'time' | 'expirationTime'> & {
88
+ time: string;
89
+ expirationTime: string;
90
+ };
91
+ };
92
+ /**
93
+ * Lower a signed off-chain attestation to its wire form, so it survives
94
+ * `JSON.stringify` — which throws outright on `bigint`.
95
+ *
96
+ * This is the exact inverse of {@link liftWireOffchainAttestation}, and the two
97
+ * are pinned as a round-trip pair by a test. Every server returning an
98
+ * attestation to a client must go through here: a raw `res.json(attestation)`
99
+ * does not fail gracefully, it throws `TypeError: Do not know how to serialize
100
+ * a BigInt` and turns a successful issuance into a 500.
101
+ *
102
+ * Only the two bigint fields are touched — the signature and every hashed field
103
+ * are copied verbatim, so lowering can never invalidate the attestation.
104
+ */
105
+ export declare const toWireOffchainAttestation: (attestation: OffchainEasAttestation) => WireOffchainAttestation;
81
106
  export type OffchainSignatureCheck = {
82
107
  /** Signature recovered cleanly and (when an expected signer was given) matched. */
83
108
  valid: boolean;
@@ -170,6 +170,27 @@ export const liftWireOffchainAttestation = (wire) => {
170
170
  },
171
171
  };
172
172
  };
173
+ /**
174
+ * Lower a signed off-chain attestation to its wire form, so it survives
175
+ * `JSON.stringify` — which throws outright on `bigint`.
176
+ *
177
+ * This is the exact inverse of {@link liftWireOffchainAttestation}, and the two
178
+ * are pinned as a round-trip pair by a test. Every server returning an
179
+ * attestation to a client must go through here: a raw `res.json(attestation)`
180
+ * does not fail gracefully, it throws `TypeError: Do not know how to serialize
181
+ * a BigInt` and turns a successful issuance into a 500.
182
+ *
183
+ * Only the two bigint fields are touched — the signature and every hashed field
184
+ * are copied verbatim, so lowering can never invalidate the attestation.
185
+ */
186
+ export const toWireOffchainAttestation = (attestation) => ({
187
+ ...attestation,
188
+ message: {
189
+ ...attestation.message,
190
+ time: attestation.message.time.toString(),
191
+ expirationTime: attestation.message.expirationTime.toString(),
192
+ },
193
+ });
173
194
  /**
174
195
  * Recover the signing address from an off-chain EAS attestation and
175
196
  * report whether the signature is cryptographically valid.
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict';
3
3
  import { privateKeyToAccount } from 'viem/accounts';
4
4
  import { buildOffchainEasAttestation, encodeIdentityClaim, encodeReleaseClaim, } from './eas-builder.js';
5
5
  import { EAS_MAINNET_ADDRESS, PROVEX_IDENTITY_SCHEMA_UID } from './eas.js';
6
- import { decodeIdentityClaim, decodeReleaseClaim, liftWireOffchainAttestation, recoverOffchainAttestationSigner, } from './offchain-verify.js';
6
+ import { decodeIdentityClaim, decodeReleaseClaim, liftWireOffchainAttestation, recoverOffchainAttestationSigner, toWireOffchainAttestation, } from './offchain-verify.js';
7
7
  const TEST_KEY = '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d';
8
8
  const SAMPLE_CLAIM = {
9
9
  platformHash: ('0x' + '11'.repeat(32)),
@@ -51,6 +51,36 @@ describe('liftWireOffchainAttestation', () => {
51
51
  assert.equal(liftWireOffchainAttestation({ not: 'an attestation' }), null);
52
52
  });
53
53
  });
54
+ describe('toWireOffchainAttestation', () => {
55
+ it('produces output JSON.stringify accepts, which the raw attestation does not', async () => {
56
+ const signed = await signedSample();
57
+ // The whole reason this function exists: the raw form is unserializable.
58
+ assert.throws(() => JSON.stringify(signed), TypeError);
59
+ assert.doesNotThrow(() => JSON.stringify(toWireOffchainAttestation(signed)));
60
+ });
61
+ it('agrees with the bigint JSON replacer the UI already consumes', async () => {
62
+ const signed = await signedSample();
63
+ // `toWire` is an independent implementation (a JSON replacer, matching the
64
+ // notary-gate's existing convention). Agreement proves the new producer
65
+ // emits the exact shape every existing consumer is already parsing —
66
+ // otherwise this helper would silently fork the wire format.
67
+ assert.deepEqual(JSON.parse(JSON.stringify(toWireOffchainAttestation(signed))), toWire(signed));
68
+ });
69
+ it('round-trips through liftWireOffchainAttestation unchanged', async () => {
70
+ const signed = await signedSample();
71
+ const lifted = liftWireOffchainAttestation(JSON.parse(JSON.stringify(toWireOffchainAttestation(signed))));
72
+ assert.ok(lifted);
73
+ assert.deepEqual(lifted, signed);
74
+ });
75
+ it('preserves the signature, so lowering cannot invalidate an attestation', async () => {
76
+ const signed = await signedSample();
77
+ const lifted = liftWireOffchainAttestation(JSON.parse(JSON.stringify(toWireOffchainAttestation(signed))));
78
+ assert.ok(lifted);
79
+ const check = await recoverOffchainAttestationSigner(lifted);
80
+ assert.equal(check.valid, true);
81
+ assert.equal(check.signer?.toLowerCase(), privateKeyToAccount(TEST_KEY).address.toLowerCase());
82
+ });
83
+ });
54
84
  describe('recoverOffchainAttestationSigner', () => {
55
85
  it('recovers the gate signer from a genuine attestation', async () => {
56
86
  const attester = privateKeyToAccount(TEST_KEY);
@@ -69,11 +69,18 @@ describe('viem-only invariant for @provex/eas/verify', () => {
69
69
  const reached = reachablePackageSpecifiers(resolve(HERE, 'offchain-verify.ts'));
70
70
  assert.deepEqual([...reached].sort(), ['viem']);
71
71
  });
72
- it('confirms the builder DOES reach the software development kit, proving the walker works', () => {
72
+ it('confirms the software development kit IS reached where used, proving the walker works', () => {
73
73
  // WHY: a graph walker that silently resolves nothing would make the two
74
- // assertions above vacuously pass. `eas-builder.ts` is the known-positive
75
- // control — it must import the software development kit at runtime.
76
- const reached = reachablePackageSpecifiers(resolve(HERE, 'eas-builder.ts'));
74
+ // assertions above vacuously pass, so it needs a known-positive control —
75
+ // a file that genuinely imports the software development kit at runtime.
76
+ //
77
+ // `eas-builder.ts` reaches the kit only transitively now, through the
78
+ // `#eas-sdk-namespace` conditional import (a bare specifier the walker
79
+ // treats as a leaf, exactly as it treats `viem`). The direct static import
80
+ // lives in `eas-sdk-namespace.browser.ts` — the browser half of that
81
+ // conditional — so that is the control. This still proves the walker
82
+ // detects the kit, and pins that the browser namespace path pulls it in.
83
+ const reached = reachablePackageSpecifiers(resolve(HERE, 'eas-sdk-namespace.browser.ts'));
77
84
  assert.ok(reached.has(EAS_SOFTWARE_DEVELOPMENT_KIT));
78
85
  });
79
86
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@provex/eas",
3
- "version": "1.9.2",
3
+ "version": "1.10.0-rc.20260912192040.fdbb8fc",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -9,6 +9,15 @@
9
9
  "url": "git+https://github.com/provex-tech/monorepo.git",
10
10
  "directory": "packages/eas"
11
11
  },
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "imports": {
16
+ "#eas-sdk-namespace": {
17
+ "browser": "./dist/eas-sdk-namespace.browser.js",
18
+ "default": "./dist/eas-sdk-namespace.node.js"
19
+ }
20
+ },
12
21
  "exports": {
13
22
  ".": {
14
23
  "import": "./dist/index.js",
@@ -56,4 +65,4 @@
56
65
  "typescript": "^5.9.2",
57
66
  "viem": "^2.38.0"
58
67
  }
59
- }
68
+ }