capacity-attest 0.6.0 → 0.7.0
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.
- package/README.md +103 -93
- package/README.nl.md +204 -0
- package/dist/bench-helpers.d.ts +12 -0
- package/dist/bench-helpers.d.ts.map +1 -0
- package/dist/bench-helpers.js +51 -0
- package/dist/bench-helpers.js.map +1 -0
- package/dist/discovery.bench.d.ts +2 -0
- package/dist/discovery.bench.d.ts.map +1 -0
- package/dist/discovery.bench.js +30 -0
- package/dist/discovery.bench.js.map +1 -0
- package/dist/discovery.d.ts +2 -0
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +57 -2
- package/dist/discovery.js.map +1 -1
- package/dist/eas.bench.d.ts +2 -0
- package/dist/eas.bench.d.ts.map +1 -0
- package/dist/eas.bench.js +40 -0
- package/dist/eas.bench.js.map +1 -0
- package/dist/eas.d.ts +89 -7
- package/dist/eas.d.ts.map +1 -1
- package/dist/eas.js +151 -34
- package/dist/eas.js.map +1 -1
- package/dist/http-source.bench.d.ts +2 -0
- package/dist/http-source.bench.d.ts.map +1 -0
- package/dist/http-source.bench.js +65 -0
- package/dist/http-source.bench.js.map +1 -0
- package/dist/http-source.d.ts +28 -0
- package/dist/http-source.d.ts.map +1 -0
- package/dist/http-source.js +154 -0
- package/dist/http-source.js.map +1 -0
- package/dist/schema.d.ts +1 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +5 -1
- package/dist/schema.js.map +1 -1
- package/package.json +3 -1
package/dist/eas.d.ts
CHANGED
|
@@ -4,10 +4,44 @@ import { verifyClaim } from "./signing.js";
|
|
|
4
4
|
import type { ClaimSource } from "./discovery.js";
|
|
5
5
|
export declare const EAS_ADDRESS = "0x4200000000000000000000000000000000000021";
|
|
6
6
|
export declare const SCHEMA_REGISTRY_ADDRESS = "0x4200000000000000000000000000000000000020";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
/** One chain's EAS deployment: both contract addresses, plus a human-facing explorer base URL. */
|
|
8
|
+
export interface EasDeployment {
|
|
9
|
+
chainId: number;
|
|
10
|
+
name: string;
|
|
11
|
+
easAddress: string;
|
|
12
|
+
schemaRegistryAddress: string;
|
|
13
|
+
explorer: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Known EAS deployments, keyed by chainId. Every address was independently
|
|
17
|
+
* verified (EAS's own deployment records AND a live eth_getCode call)
|
|
18
|
+
* before being added here -- the OP-Stack-predeploy pattern is CONFIRMED
|
|
19
|
+
* true for Optimism specifically below, never applied to a new chain just
|
|
20
|
+
* because it sounds plausible.
|
|
21
|
+
*/
|
|
22
|
+
export declare const EAS_DEPLOYMENTS: Record<number, EasDeployment>;
|
|
23
|
+
/** Backward-compatible: existing callers (examples/eas-live-demo.ts) look up an explorer URL by chainId this way. Derived FROM EAS_DEPLOYMENTS so there is exactly one source of truth, not two tables that could quietly drift apart. */
|
|
24
|
+
export declare const EAS_EXPLORER: Record<number, string>;
|
|
25
|
+
interface AttestRequest {
|
|
26
|
+
schema: string;
|
|
27
|
+
data: {
|
|
28
|
+
recipient: string;
|
|
29
|
+
expirationTime: bigint;
|
|
30
|
+
revocable: boolean;
|
|
31
|
+
refUID: string;
|
|
32
|
+
data: string;
|
|
33
|
+
value: bigint;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
interface EasWriteContract {
|
|
37
|
+
attest(request: AttestRequest): Promise<ethers.ContractTransactionResponse>;
|
|
38
|
+
}
|
|
39
|
+
interface SchemaRegistryContract {
|
|
40
|
+
getSchema(uid: string): Promise<{
|
|
41
|
+
uid: string;
|
|
42
|
+
}>;
|
|
43
|
+
register(schema: string, resolver: string, revocable: boolean): Promise<ethers.ContractTransactionResponse>;
|
|
44
|
+
}
|
|
11
45
|
export declare const SCHEMA_STRING = "bytes32 claimId,string claim";
|
|
12
46
|
/** Deterministic schema UID: keccak256(abi.encodePacked(schema, resolver, revocable)). */
|
|
13
47
|
export declare function computeSchemaUID(schema?: string, resolver?: string, revocable?: boolean): string;
|
|
@@ -22,18 +56,36 @@ export declare function encodeClaimData(claim: DeliveryClaim): string;
|
|
|
22
56
|
* own claimId, so a mismatch is dropped here as malformed.
|
|
23
57
|
*/
|
|
24
58
|
export declare function decodeClaimData(data: string): DeliveryClaim | null;
|
|
59
|
+
/**
|
|
60
|
+
* Builds the SchemaRegistry write contract. Split out as an injection seam
|
|
61
|
+
* (same DI pattern as erc8004.ts's ContractFactory and this file's own
|
|
62
|
+
* AttestationReader below) so ensureSchema's CHAIN-TARGETING logic --
|
|
63
|
+
* "does it use the address I told it to, for a chain that is not Base" --
|
|
64
|
+
* is unit-testable without a live signer or network, not just assumed.
|
|
65
|
+
*/
|
|
66
|
+
export type SchemaRegistryContractFactory = (schemaRegistryAddress: string, signer: ethers.Signer) => SchemaRegistryContract;
|
|
25
67
|
/**
|
|
26
68
|
* Register our schema if it does not already exist on this chain. The schema
|
|
27
69
|
* UID is deterministic, so a duplicate register() reverts with AlreadyExists;
|
|
28
70
|
* we check first via getSchema and only register when absent. Requires a
|
|
29
71
|
* funded signer. Returns the schema UID.
|
|
72
|
+
*
|
|
73
|
+
* `opts.schemaRegistryAddress` defaults to SCHEMA_REGISTRY_ADDRESS (Base's,
|
|
74
|
+
* unchanged from before this function took an opts argument at all): pass
|
|
75
|
+
* `EAS_DEPLOYMENTS[1].schemaRegistryAddress` for Ethereum mainnet, or any
|
|
76
|
+
* other chain's own address, to target that chain instead.
|
|
30
77
|
*/
|
|
31
|
-
export declare function ensureSchema(signer: ethers.Signer
|
|
78
|
+
export declare function ensureSchema(signer: ethers.Signer, opts?: {
|
|
79
|
+
schemaRegistryAddress?: string;
|
|
80
|
+
contractFactory?: SchemaRegistryContractFactory;
|
|
81
|
+
}): Promise<string>;
|
|
32
82
|
export interface PublishResult {
|
|
33
83
|
uid: string;
|
|
34
84
|
txHash: string;
|
|
35
85
|
recipient: string;
|
|
36
86
|
}
|
|
87
|
+
/** Same injection-seam purpose as SchemaRegistryContractFactory above, for the EAS write contract itself. */
|
|
88
|
+
export type EasWriteContractFactory = (easAddress: string, signer: ethers.Signer) => EasWriteContract;
|
|
37
89
|
/**
|
|
38
90
|
* Publish one claim to EAS as an attestation with recipient = sellerAddress, so
|
|
39
91
|
* it is discoverable by that seller. `signer` only pays gas and writes the
|
|
@@ -43,8 +95,16 @@ export interface PublishResult {
|
|
|
43
95
|
* (easSource, discoverDeliveryHistory) ever looks at who published the
|
|
44
96
|
* attestation, only at what the claim itself proves. Requires a funded signer.
|
|
45
97
|
* Returns the new attestation UID and tx hash.
|
|
98
|
+
*
|
|
99
|
+
* `opts.easAddress` defaults to EAS_ADDRESS (Base's, unchanged from before
|
|
100
|
+
* this function took an opts argument at all): pass
|
|
101
|
+
* `EAS_DEPLOYMENTS[1].easAddress` for Ethereum mainnet, or any other
|
|
102
|
+
* chain's own address, to target that chain instead.
|
|
46
103
|
*/
|
|
47
|
-
export declare function publishClaim(signer: ethers.Signer, claim: DeliveryClaim
|
|
104
|
+
export declare function publishClaim(signer: ethers.Signer, claim: DeliveryClaim, opts?: {
|
|
105
|
+
easAddress?: string;
|
|
106
|
+
contractFactory?: EasWriteContractFactory;
|
|
107
|
+
}): Promise<PublishResult>;
|
|
48
108
|
/**
|
|
49
109
|
* The minimal on-chain read surface easSource needs. Split out as an injection
|
|
50
110
|
* seam so the decode/aggregation path is unit-testable without a live chain
|
|
@@ -56,11 +116,30 @@ export interface AttestationReader {
|
|
|
56
116
|
uidsForSeller(sellerAddress: string): Promise<string[]>;
|
|
57
117
|
/** The raw ABI-encoded `data` field of one attestation. */
|
|
58
118
|
dataForUid(uid: string): Promise<string>;
|
|
119
|
+
/** Max concurrent dataForUid calls easSource should keep in flight. Readers that do not set this get DEFAULT_CONCURRENCY. */
|
|
120
|
+
concurrency?: number;
|
|
59
121
|
}
|
|
60
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* Pure block-range windowing, split out so the chunking math is unit-testable
|
|
124
|
+
* without a real or mocked RPC provider. [fromBlock, latest] inclusive,
|
|
125
|
+
* chunked into <= blockRange-wide, non-overlapping, gap-free windows in
|
|
126
|
+
* ascending order. Returns [] when fromBlock > latest (nothing to scan).
|
|
127
|
+
*/
|
|
128
|
+
export declare function blockWindows(fromBlock: number, latest: number, blockRange: number): Array<[number, number]>;
|
|
129
|
+
/**
|
|
130
|
+
* Build a real AttestationReader over a JSON-RPC endpoint (getLogs +
|
|
131
|
+
* getAttestation). `opts.easAddress` defaults to EAS_ADDRESS (Base's,
|
|
132
|
+
* unchanged from before WP2): pass `EAS_DEPLOYMENTS[1].easAddress` for
|
|
133
|
+
* Ethereum mainnet, or any other chain's own address, to read that chain
|
|
134
|
+
* instead. rpcUrl itself already told this function which CHAIN to talk to;
|
|
135
|
+
* easAddress tells it WHERE on that chain the EAS contract lives.
|
|
136
|
+
*/
|
|
61
137
|
export declare function rpcAttestationReader(rpcUrl: string, opts?: {
|
|
62
138
|
fromBlock?: number;
|
|
63
139
|
schemaUID?: string;
|
|
140
|
+
blockRange?: number;
|
|
141
|
+
concurrency?: number;
|
|
142
|
+
easAddress?: string;
|
|
64
143
|
}): AttestationReader;
|
|
65
144
|
/**
|
|
66
145
|
* An untrusted ClaimSource backed by EAS. fetchForSeller reads the seller's
|
|
@@ -76,6 +155,9 @@ export declare function easSourceFromRpc(rpcUrl: string, opts?: {
|
|
|
76
155
|
fromBlock?: number;
|
|
77
156
|
schemaUID?: string;
|
|
78
157
|
name?: string;
|
|
158
|
+
blockRange?: number;
|
|
159
|
+
concurrency?: number;
|
|
160
|
+
easAddress?: string;
|
|
79
161
|
}): ClaimSource;
|
|
80
162
|
export { verifyClaim };
|
|
81
163
|
//# sourceMappingURL=eas.d.ts.map
|
package/dist/eas.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eas.d.ts","sourceRoot":"","sources":["../src/eas.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"eas.d.ts","sourceRoot":"","sources":["../src/eas.ts"],"names":[],"mappings":"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"}
|
package/dist/eas.js
CHANGED
|
@@ -1,27 +1,57 @@
|
|
|
1
1
|
// eas.ts — publish a delivery claim to, and discover it from, the Ethereum
|
|
2
|
-
// Attestation Service (EAS)
|
|
2
|
+
// Attestation Service (EAS). This is the concrete, host-independent
|
|
3
3
|
// substrate D-005/D-012 pointed at: a public chain anyone can read, so a buyer
|
|
4
4
|
// on one installation can find a claim another buyer published, and verify it
|
|
5
5
|
// locally (discovery.ts re-verifies every claim, so EAS is just an untrusted
|
|
6
6
|
// ClaimSource like any other).
|
|
7
7
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
8
|
+
// Originally wired to Base only. Generalized here (WP2, see
|
|
9
|
+
// docs/NLNET-SELFBUILD-PLAN-2026-09-28.md) to support any EAS deployment:
|
|
10
|
+
// discovery.ts's own header comment always described the substrate as "EAS
|
|
11
|
+
// on Base", not "EAS, forever only Base". Optimism and Ethereum mainnet are
|
|
12
|
+
// equally real EAS deployments, just with different contract addresses:
|
|
13
|
+
// Optimism, being an OP-Stack chain like Base, happens to share Base's exact
|
|
14
|
+
// predeploy addresses; Ethereum mainnet does not, since it is the underlying
|
|
15
|
+
// L1, not an OP-Stack chain. That distinction was verified, not assumed --
|
|
16
|
+
// see EAS_DEPLOYMENTS below.
|
|
17
|
+
//
|
|
18
|
+
// Plain ethers only, no eas-sdk dependency. Everything here is
|
|
19
|
+
// caller-parameterised (rpcUrl, signer, and now chain addresses too): this
|
|
11
20
|
// package never bundles an RPC, a key, or gas, and never becomes the index.
|
|
12
21
|
//
|
|
13
|
-
//
|
|
14
|
-
// SchemaRegistry.sol) and the base / base-sepolia deployment
|
|
22
|
+
// Base's addresses verified against the EAS contract sources (IEAS.sol,
|
|
23
|
+
// Common.sol, SchemaRegistry.sol) and the base / base-sepolia deployment
|
|
24
|
+
// artifacts, 2026-09-06. Optimism and Ethereum mainnet addresses verified
|
|
25
|
+
// 2026-09-28 against EAS's own official deployment records
|
|
26
|
+
// (github.com/ethereum-attestation-service/eas-contracts, the
|
|
27
|
+
// deployments/optimism and deployments/mainnet JSON files, cross-checked
|
|
28
|
+
// against docs.attest.org's own contracts table) AND independently
|
|
29
|
+
// re-confirmed with a direct eth_getCode call against each chain's real,
|
|
30
|
+
// live RPC: real bytecode present at every address below, not just "found
|
|
31
|
+
// in a document".
|
|
15
32
|
import { ethers } from "ethers";
|
|
16
33
|
import { DeliveryClaimSchema } from "./schema.js";
|
|
17
34
|
import { verifyClaim } from "./signing.js";
|
|
18
|
-
// OP-Stack predeploys:
|
|
35
|
+
// OP-Stack predeploys: identical on every OP-Stack chain (Base, Optimism).
|
|
36
|
+
// Kept as the module-level default for every function below, so a caller who
|
|
37
|
+
// passes nothing still gets exactly today's, unchanged, Base behavior.
|
|
19
38
|
export const EAS_ADDRESS = "0x4200000000000000000000000000000000000021";
|
|
20
39
|
export const SCHEMA_REGISTRY_ADDRESS = "0x4200000000000000000000000000000000000020";
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Known EAS deployments, keyed by chainId. Every address was independently
|
|
42
|
+
* verified (EAS's own deployment records AND a live eth_getCode call)
|
|
43
|
+
* before being added here -- the OP-Stack-predeploy pattern is CONFIRMED
|
|
44
|
+
* true for Optimism specifically below, never applied to a new chain just
|
|
45
|
+
* because it sounds plausible.
|
|
46
|
+
*/
|
|
47
|
+
export const EAS_DEPLOYMENTS = {
|
|
48
|
+
8453: { chainId: 8453, name: "Base", easAddress: EAS_ADDRESS, schemaRegistryAddress: SCHEMA_REGISTRY_ADDRESS, explorer: "https://base.easscan.org" },
|
|
49
|
+
84532: { chainId: 84532, name: "Base Sepolia", easAddress: EAS_ADDRESS, schemaRegistryAddress: SCHEMA_REGISTRY_ADDRESS, explorer: "https://base-sepolia.easscan.org" },
|
|
50
|
+
10: { chainId: 10, name: "Optimism", easAddress: EAS_ADDRESS, schemaRegistryAddress: SCHEMA_REGISTRY_ADDRESS, explorer: "https://optimism.easscan.org" },
|
|
51
|
+
1: { chainId: 1, name: "Ethereum", easAddress: "0xA1207F3BBa224E2c9c3c6D5aF63D0eb1582Ce587", schemaRegistryAddress: "0xA7b39296258348C78294F95B872b282326A97BDF", explorer: "https://easscan.org" },
|
|
24
52
|
};
|
|
53
|
+
/** Backward-compatible: existing callers (examples/eas-live-demo.ts) look up an explorer URL by chainId this way. Derived FROM EAS_DEPLOYMENTS so there is exactly one source of truth, not two tables that could quietly drift apart. */
|
|
54
|
+
export const EAS_EXPLORER = Object.fromEntries(Object.entries(EAS_DEPLOYMENTS).map(([chainId, d]) => [chainId, d.explorer]));
|
|
25
55
|
const EAS_ABI = [
|
|
26
56
|
"function attest((bytes32 schema,(address recipient,uint64 expirationTime,bool revocable,bytes32 refUID,bytes data,uint256 value) data) request) payable returns (bytes32)",
|
|
27
57
|
"function getAttestation(bytes32 uid) view returns ((bytes32 uid,bytes32 schema,uint64 time,uint64 expirationTime,uint64 revocationTime,bytes32 refUID,address recipient,address attester,bool revocable,bytes data))",
|
|
@@ -75,14 +105,22 @@ export function decodeClaimData(data) {
|
|
|
75
105
|
return null;
|
|
76
106
|
return parsed;
|
|
77
107
|
}
|
|
108
|
+
const defaultSchemaRegistryContractFactory = (schemaRegistryAddress, signer) => new ethers.Contract(schemaRegistryAddress, SCHEMA_REGISTRY_ABI, signer);
|
|
78
109
|
/**
|
|
79
110
|
* Register our schema if it does not already exist on this chain. The schema
|
|
80
111
|
* UID is deterministic, so a duplicate register() reverts with AlreadyExists;
|
|
81
112
|
* we check first via getSchema and only register when absent. Requires a
|
|
82
113
|
* funded signer. Returns the schema UID.
|
|
114
|
+
*
|
|
115
|
+
* `opts.schemaRegistryAddress` defaults to SCHEMA_REGISTRY_ADDRESS (Base's,
|
|
116
|
+
* unchanged from before this function took an opts argument at all): pass
|
|
117
|
+
* `EAS_DEPLOYMENTS[1].schemaRegistryAddress` for Ethereum mainnet, or any
|
|
118
|
+
* other chain's own address, to target that chain instead.
|
|
83
119
|
*/
|
|
84
|
-
export async function ensureSchema(signer) {
|
|
85
|
-
const
|
|
120
|
+
export async function ensureSchema(signer, opts = {}) {
|
|
121
|
+
const schemaRegistryAddress = opts.schemaRegistryAddress ?? SCHEMA_REGISTRY_ADDRESS;
|
|
122
|
+
const contractFactory = opts.contractFactory ?? defaultSchemaRegistryContractFactory;
|
|
123
|
+
const registry = contractFactory(schemaRegistryAddress, signer);
|
|
86
124
|
const existing = await registry.getSchema(SCHEMA_UID);
|
|
87
125
|
// getSchema returns a zero-uid struct when the schema is not registered.
|
|
88
126
|
if (existing && existing.uid && existing.uid.toLowerCase() === SCHEMA_UID.toLowerCase()) {
|
|
@@ -92,6 +130,7 @@ export async function ensureSchema(signer) {
|
|
|
92
130
|
await tx.wait();
|
|
93
131
|
return SCHEMA_UID;
|
|
94
132
|
}
|
|
133
|
+
const defaultEasWriteContractFactory = (easAddress, signer) => new ethers.Contract(easAddress, EAS_ABI, signer);
|
|
95
134
|
/**
|
|
96
135
|
* Publish one claim to EAS as an attestation with recipient = sellerAddress, so
|
|
97
136
|
* it is discoverable by that seller. `signer` only pays gas and writes the
|
|
@@ -101,9 +140,16 @@ export async function ensureSchema(signer) {
|
|
|
101
140
|
* (easSource, discoverDeliveryHistory) ever looks at who published the
|
|
102
141
|
* attestation, only at what the claim itself proves. Requires a funded signer.
|
|
103
142
|
* Returns the new attestation UID and tx hash.
|
|
143
|
+
*
|
|
144
|
+
* `opts.easAddress` defaults to EAS_ADDRESS (Base's, unchanged from before
|
|
145
|
+
* this function took an opts argument at all): pass
|
|
146
|
+
* `EAS_DEPLOYMENTS[1].easAddress` for Ethereum mainnet, or any other
|
|
147
|
+
* chain's own address, to target that chain instead.
|
|
104
148
|
*/
|
|
105
|
-
export async function publishClaim(signer, claim) {
|
|
106
|
-
const
|
|
149
|
+
export async function publishClaim(signer, claim, opts = {}) {
|
|
150
|
+
const easAddress = opts.easAddress ?? EAS_ADDRESS;
|
|
151
|
+
const contractFactory = opts.contractFactory ?? defaultEasWriteContractFactory;
|
|
152
|
+
const eas = contractFactory(easAddress, signer);
|
|
107
153
|
const tx = await eas.attest({
|
|
108
154
|
schema: SCHEMA_UID,
|
|
109
155
|
data: {
|
|
@@ -134,27 +180,83 @@ export async function publishClaim(signer, claim) {
|
|
|
134
180
|
}
|
|
135
181
|
return { uid, txHash: receipt.hash, recipient: claim.sellerAddress };
|
|
136
182
|
}
|
|
137
|
-
|
|
183
|
+
// Most public RPC providers cap a single eth_getLogs call to a bounded block
|
|
184
|
+
// range (2,000-10,000 blocks is typical; some reject an unbounded fromBlock=0
|
|
185
|
+
// outright). A single call from genesis to "latest" therefore either errors
|
|
186
|
+
// out or silently degrades depending on the provider, and degrades further as
|
|
187
|
+
// the chain ages and the range grows. Default window chosen conservatively
|
|
188
|
+
// below the tightest commonly-seen provider limit; callers on a more
|
|
189
|
+
// permissive RPC can raise it via opts.blockRange.
|
|
190
|
+
const DEFAULT_BLOCK_RANGE = 2_000;
|
|
191
|
+
// Bound how many attestation bodies rpcAttestationReader fetches CONCURRENTLY
|
|
192
|
+
// per uidsForSeller/dataForUid fan-out. discoverDeliveryHistory's own
|
|
193
|
+
// MAX_CLAIMS_PER_SOURCE bounds how many claims are ultimately ACCEPTED from a
|
|
194
|
+
// source; this bounds how many in-flight RPC requests a single source's read
|
|
195
|
+
// can open at once, so a seller with many attestations does not open
|
|
196
|
+
// thousands of simultaneous connections against the RPC endpoint.
|
|
197
|
+
const DEFAULT_CONCURRENCY = 10;
|
|
198
|
+
/**
|
|
199
|
+
* Pure block-range windowing, split out so the chunking math is unit-testable
|
|
200
|
+
* without a real or mocked RPC provider. [fromBlock, latest] inclusive,
|
|
201
|
+
* chunked into <= blockRange-wide, non-overlapping, gap-free windows in
|
|
202
|
+
* ascending order. Returns [] when fromBlock > latest (nothing to scan).
|
|
203
|
+
*/
|
|
204
|
+
export function blockWindows(fromBlock, latest, blockRange) {
|
|
205
|
+
if (blockRange < 1)
|
|
206
|
+
throw new Error(`blockRange must be >= 1, got ${blockRange}`);
|
|
207
|
+
const windows = [];
|
|
208
|
+
for (let from = fromBlock; from <= latest; from += blockRange) {
|
|
209
|
+
windows.push([from, Math.min(from + blockRange - 1, latest)]);
|
|
210
|
+
}
|
|
211
|
+
return windows;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Build a real AttestationReader over a JSON-RPC endpoint (getLogs +
|
|
215
|
+
* getAttestation). `opts.easAddress` defaults to EAS_ADDRESS (Base's,
|
|
216
|
+
* unchanged from before WP2): pass `EAS_DEPLOYMENTS[1].easAddress` for
|
|
217
|
+
* Ethereum mainnet, or any other chain's own address, to read that chain
|
|
218
|
+
* instead. rpcUrl itself already told this function which CHAIN to talk to;
|
|
219
|
+
* easAddress tells it WHERE on that chain the EAS contract lives.
|
|
220
|
+
*/
|
|
138
221
|
export function rpcAttestationReader(rpcUrl, opts = {}) {
|
|
139
222
|
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
140
|
-
const
|
|
223
|
+
const easAddress = opts.easAddress ?? EAS_ADDRESS;
|
|
224
|
+
const eas = new ethers.Contract(easAddress, EAS_ABI, provider);
|
|
141
225
|
const schemaUID = opts.schemaUID ?? SCHEMA_UID;
|
|
142
226
|
const attestedTopic0 = ethers.id("Attested(address,address,bytes32,bytes32)");
|
|
227
|
+
const blockRange = opts.blockRange ?? DEFAULT_BLOCK_RANGE;
|
|
228
|
+
const concurrency = opts.concurrency ?? DEFAULT_CONCURRENCY;
|
|
143
229
|
return {
|
|
144
230
|
uidsForSeller: async (sellerAddress) => {
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
//
|
|
152
|
-
|
|
231
|
+
const latest = await provider.getBlockNumber();
|
|
232
|
+
const windows = blockWindows(opts.fromBlock ?? 0, latest, blockRange);
|
|
233
|
+
const uids = [];
|
|
234
|
+
// Walk the chain in bounded windows instead of one fromBlock=0..latest
|
|
235
|
+
// call. A window that itself errors (provider-side limit stricter than
|
|
236
|
+
// blockRange) is not swallowed here: it propagates, same as today's
|
|
237
|
+
// single-call behaviour, so a caller who needs resilience against a
|
|
238
|
+
// flaky/stricter provider passes a smaller blockRange, not a silent
|
|
239
|
+
// partial result.
|
|
240
|
+
for (const [from, to] of windows) {
|
|
241
|
+
const logs = await provider.getLogs({
|
|
242
|
+
address: easAddress,
|
|
243
|
+
topics: [attestedTopic0, ethers.zeroPadValue(sellerAddress, 32), null, schemaUID],
|
|
244
|
+
fromBlock: from,
|
|
245
|
+
toBlock: to,
|
|
246
|
+
});
|
|
247
|
+
// uid is the only non-indexed value: it sits in log.data.
|
|
248
|
+
for (const l of logs)
|
|
249
|
+
uids.push(l.data);
|
|
250
|
+
}
|
|
251
|
+
return uids;
|
|
153
252
|
},
|
|
154
253
|
dataForUid: async (uid) => {
|
|
155
254
|
const att = await eas.getAttestation(uid);
|
|
156
255
|
return att.data;
|
|
157
256
|
},
|
|
257
|
+
// Exposed so easSource's fan-out can bound concurrency without
|
|
258
|
+
// hardcoding a number that belongs to the reader's own configuration.
|
|
259
|
+
concurrency,
|
|
158
260
|
};
|
|
159
261
|
}
|
|
160
262
|
/**
|
|
@@ -170,19 +272,34 @@ export function easSource(reader, name = "eas") {
|
|
|
170
272
|
name,
|
|
171
273
|
fetchForSeller: async (sellerAddress) => {
|
|
172
274
|
const uids = await reader.uidsForSeller(sellerAddress);
|
|
275
|
+
const concurrency = Math.max(1, reader.concurrency ?? DEFAULT_CONCURRENCY);
|
|
173
276
|
const claims = [];
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
277
|
+
// Bounded-concurrency fan-out: at most `concurrency` dataForUid calls
|
|
278
|
+
// in flight at once, instead of either one-at-a-time (slow for a
|
|
279
|
+
// seller with many attestations) or fully unbounded (many simultaneous
|
|
280
|
+
// requests against one RPC endpoint). Order of `claims` does not need
|
|
281
|
+
// to match `uids`; discoverDeliveryHistory sorts the final aggregate by
|
|
282
|
+
// timestamp regardless of source order.
|
|
283
|
+
let next = 0;
|
|
284
|
+
async function worker() {
|
|
285
|
+
for (;;) {
|
|
286
|
+
const i = next++;
|
|
287
|
+
if (i >= uids.length)
|
|
288
|
+
return;
|
|
289
|
+
const uid = uids[i];
|
|
290
|
+
let data;
|
|
291
|
+
try {
|
|
292
|
+
data = await reader.dataForUid(uid);
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
continue; // unreadable attestation, skip
|
|
296
|
+
}
|
|
297
|
+
const claim = decodeClaimData(data);
|
|
298
|
+
if (claim)
|
|
299
|
+
claims.push(claim);
|
|
181
300
|
}
|
|
182
|
-
const claim = decodeClaimData(data);
|
|
183
|
-
if (claim)
|
|
184
|
-
claims.push(claim);
|
|
185
301
|
}
|
|
302
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, uids.length) }, worker));
|
|
186
303
|
return claims;
|
|
187
304
|
},
|
|
188
305
|
};
|
package/dist/eas.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eas.js","sourceRoot":"","sources":["../src/eas.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,
|
|
1
|
+
{"version":3,"file":"eas.js","sourceRoot":"","sources":["../src/eas.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-source.bench.d.ts","sourceRoot":"","sources":["../src/http-source.bench.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// http-source.bench.ts — quantifies two things for the new httpClaimSource
|
|
2
|
+
// (WP1's second, non-EAS production-grade source):
|
|
3
|
+
// 1. Real end-to-end throughput through discoverDeliveryHistory at volume,
|
|
4
|
+
// the same question discovery.bench.ts already answers for the
|
|
5
|
+
// aggregation layer itself. (eas.bench.ts answers a narrower, different
|
|
6
|
+
// question: it benchmarks easSource.fetchForSeller() in isolation, at a
|
|
7
|
+
// fixed small volume, to compare concurrency=1 vs 10 vs 25 -- the PR
|
|
8
|
+
// #15 fan-out fix -- not throughput through discoverDeliveryHistory.)
|
|
9
|
+
// 2. Whether the byte-bounded streaming read (readBounded, the defense
|
|
10
|
+
// against an oversized/hostile response) costs anything meaningful
|
|
11
|
+
// compared to a naive "buffer everything, then check the size"
|
|
12
|
+
// implementation. readBounded is module-private, so this compares two
|
|
13
|
+
// full fetchForSeller() calls with IDENTICAL post-read work (JSON.parse
|
|
14
|
+
// included in both arms) and only the read strategy itself differing --
|
|
15
|
+
// an earlier version of this benchmark compared readBounded's full
|
|
16
|
+
// fetchForSeller() against a bare response.text() with no JSON.parse,
|
|
17
|
+
// which bundled parse cost into only one arm and overstated the
|
|
18
|
+
// streaming mechanism's true overhead.
|
|
19
|
+
// Run with: npx vitest bench src/http-source.bench.ts
|
|
20
|
+
import { bench, describe } from "vitest";
|
|
21
|
+
import { httpClaimSource } from "./http-source.js";
|
|
22
|
+
import { discoverDeliveryHistory } from "./discovery.js";
|
|
23
|
+
import { generateClaims, BENCH_SELLER } from "./bench-helpers.js";
|
|
24
|
+
const CLAIM_COUNT = 1_000;
|
|
25
|
+
const claims = await generateClaims(CLAIM_COUNT);
|
|
26
|
+
const body = JSON.stringify(claims);
|
|
27
|
+
function fixedResponseFetch() {
|
|
28
|
+
return (async () => new Response(body, { status: 200 }));
|
|
29
|
+
}
|
|
30
|
+
describe(`discoverDeliveryHistory via httpClaimSource, ${CLAIM_COUNT} claims in one response`, () => {
|
|
31
|
+
bench("end to end (fetch + bounded read + JSON.parse + verify all)", async () => {
|
|
32
|
+
const source = httpClaimSource("https://example.invalid/claims", { fetchImpl: fixedResponseFetch() });
|
|
33
|
+
await discoverDeliveryHistory(BENCH_SELLER, [source]);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
// A deliberately naive re-implementation of exactly what httpClaimSource does,
|
|
37
|
+
// EXCEPT the read step is `await response.text()` (buffer everything, then
|
|
38
|
+
// check the length) instead of readBounded's stream-and-cap-as-you-go. Same
|
|
39
|
+
// URL building, same JSON.parse, same Array.isArray check, same cast -- so
|
|
40
|
+
// the ONLY thing this isolates against the real httpClaimSource is the read
|
|
41
|
+
// strategy itself, not an unrelated extra JSON.parse.
|
|
42
|
+
async function naiveFetchForSeller(sellerAddress) {
|
|
43
|
+
const response = await (fixedResponseFetch())("https://example.invalid/claims", {});
|
|
44
|
+
const text = await response.text();
|
|
45
|
+
if (Buffer.byteLength(text, "utf-8") > body.length * 2)
|
|
46
|
+
throw new Error("too large");
|
|
47
|
+
const parsed = JSON.parse(text);
|
|
48
|
+
if (!Array.isArray(parsed))
|
|
49
|
+
throw new Error("not an array");
|
|
50
|
+
void sellerAddress;
|
|
51
|
+
return parsed;
|
|
52
|
+
}
|
|
53
|
+
describe(`read strategy only: readBounded (streams+caps) vs naive response.text()+length-check, same ${CLAIM_COUNT}-claim payload (${(body.length / 1024).toFixed(0)} KiB), identical JSON.parse in both arms`, () => {
|
|
54
|
+
bench("naive: response.text() then check length, then JSON.parse", async () => {
|
|
55
|
+
await naiveFetchForSeller(BENCH_SELLER);
|
|
56
|
+
});
|
|
57
|
+
bench("readBounded via httpClaimSource: stream + cap-as-you-go, then JSON.parse", async () => {
|
|
58
|
+
const source = httpClaimSource("https://example.invalid/claims", {
|
|
59
|
+
fetchImpl: fixedResponseFetch(),
|
|
60
|
+
maxResponseBytes: body.length * 2, // generous, so this measures the mechanism's overhead, not a refusal
|
|
61
|
+
});
|
|
62
|
+
await source.fetchForSeller(BENCH_SELLER);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
//# sourceMappingURL=http-source.bench.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-source.bench.js","sourceRoot":"","sources":["../src/http-source.bench.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ClaimSource } from "./discovery.js";
|
|
2
|
+
export declare class HttpSourceError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
export interface HttpClaimSourceOptions {
|
|
6
|
+
name?: string;
|
|
7
|
+
/** Per-request timeout in ms. Independent of, and typically tighter than, discoverDeliveryHistory's own sourceTimeoutMs. */
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
/** Refuse a response body larger than this many bytes, checked WHILE streaming, before JSON.parse ever runs. */
|
|
10
|
+
maxResponseBytes?: number;
|
|
11
|
+
/** Injection seam for tests: a fetch-shaped function. Defaults to the global fetch. */
|
|
12
|
+
fetchImpl?: typeof fetch;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A ClaimSource over a plain HTTP endpoint. GETs
|
|
16
|
+
* `${endpointUrl}?seller=<sellerAddress>` and expects a JSON array back.
|
|
17
|
+
* Deliberately does NO verification and NO per-entry shape checking itself
|
|
18
|
+
* (same division of responsibility as staticSource/easSource):
|
|
19
|
+
* discoverDeliveryHistory re-verifies every claim from every source
|
|
20
|
+
* uniformly. A malformed sellerAddress, a non-2xx status, a request that
|
|
21
|
+
* times out or fails at the network level, a body over the size cap, or a
|
|
22
|
+
* body that is not JSON or not a JSON array, all become a thrown
|
|
23
|
+
* HttpSourceError — which discoverDeliveryHistory's existing per-source
|
|
24
|
+
* try/catch already turns into a SourceReport.error, never a crash of the
|
|
25
|
+
* whole aggregation.
|
|
26
|
+
*/
|
|
27
|
+
export declare function httpClaimSource(endpointUrl: string, opts?: HttpClaimSourceOptions): ClaimSource;
|
|
28
|
+
//# sourceMappingURL=http-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-source.d.ts","sourceRoot":"","sources":["../src/http-source.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGlD,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AA8BD,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4HAA4H;IAC5H,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gHAAgH;IAChH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,uFAAuF;IACvF,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AA4BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,GAAE,sBAA2B,GAAG,WAAW,CAwDnG"}
|