@lawreneliang/atel-sdk 0.1.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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +221 -0
  3. package/dist/anchor/base.d.ts +21 -0
  4. package/dist/anchor/base.js +26 -0
  5. package/dist/anchor/bsc.d.ts +20 -0
  6. package/dist/anchor/bsc.js +25 -0
  7. package/dist/anchor/evm.d.ts +67 -0
  8. package/dist/anchor/evm.js +176 -0
  9. package/dist/anchor/index.d.ts +173 -0
  10. package/dist/anchor/index.js +165 -0
  11. package/dist/anchor/mock.d.ts +43 -0
  12. package/dist/anchor/mock.js +100 -0
  13. package/dist/anchor/solana.d.ts +62 -0
  14. package/dist/anchor/solana.js +190 -0
  15. package/dist/gateway/index.d.ts +278 -0
  16. package/dist/gateway/index.js +520 -0
  17. package/dist/graph/index.d.ts +215 -0
  18. package/dist/graph/index.js +524 -0
  19. package/dist/identity/index.d.ts +114 -0
  20. package/dist/identity/index.js +178 -0
  21. package/dist/index.d.ts +14 -0
  22. package/dist/index.js +16 -0
  23. package/dist/orchestrator/index.d.ts +190 -0
  24. package/dist/orchestrator/index.js +297 -0
  25. package/dist/policy/index.d.ts +100 -0
  26. package/dist/policy/index.js +206 -0
  27. package/dist/proof/index.d.ts +220 -0
  28. package/dist/proof/index.js +541 -0
  29. package/dist/rollback/index.d.ts +76 -0
  30. package/dist/rollback/index.js +91 -0
  31. package/dist/schema/capability-schema.json +52 -0
  32. package/dist/schema/index.d.ts +128 -0
  33. package/dist/schema/index.js +163 -0
  34. package/dist/schema/task-schema.json +69 -0
  35. package/dist/score/index.d.ts +132 -0
  36. package/dist/score/index.js +202 -0
  37. package/dist/service/index.d.ts +34 -0
  38. package/dist/service/index.js +273 -0
  39. package/dist/service/server.d.ts +7 -0
  40. package/dist/service/server.js +22 -0
  41. package/dist/trace/index.d.ts +217 -0
  42. package/dist/trace/index.js +446 -0
  43. package/dist/trust/index.d.ts +84 -0
  44. package/dist/trust/index.js +107 -0
  45. package/dist/trust-sync/index.d.ts +30 -0
  46. package/dist/trust-sync/index.js +57 -0
  47. package/package.json +59 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ATEL
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,221 @@
1
+ # ATEL SDK
2
+
3
+ **Agent Trust & Economics Layer** — a TypeScript protocol SDK for building trustworthy, auditable multi-agent systems.
4
+
5
+ ATEL provides the cryptographic primitives and protocol building blocks that let AI agents collaborate safely: identity verification, scoped consent, policy enforcement, tamper-evident execution traces, Merkle-tree proofs, and reputation scoring.
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ # Install dependencies
11
+ npm install
12
+
13
+ # Build
14
+ npm run build
15
+
16
+ # Run the end-to-end demo
17
+ npx tsx demo/full-demo.ts
18
+ ```
19
+
20
+ Phase 0.5 commands:
21
+
22
+ ```bash
23
+ # Real external API e2e demo
24
+ npm run demo:real
25
+
26
+ # Testnet anchoring smoke (requires chain env vars)
27
+ npm run smoke:anchor
28
+
29
+ # Performance baseline
30
+ npm run test:perf
31
+
32
+ # 5-10 agent continuous run (Phase 0.5)
33
+ npm run run:cluster
34
+
35
+ # deploy acceptance (service + sync + proof + anchor)
36
+ npm run acceptance:deploy
37
+ ```
38
+
39
+ The demo simulates two agents collaborating on a flight search task, exercising the end-to-end trust workflow.
40
+
41
+ ## Architecture
42
+
43
+ ATEL is organized into 13 composable modules:
44
+
45
+ ```
46
+ ┌──────────────────────────────────────────────────────────────┐
47
+ │ ATEL SDK │
48
+ ├──────────┬──────────┬──────────┬──────────┬─────────────────┤
49
+ │ Identity │ Schema │ Policy │ Gateway │ Trace │
50
+ ├──────────┴──────────┴──────────┴──────────┴─────────────────┤
51
+ │ Proof │ Score │ Graph │ TrustManager │ Rollback │ Anchor │
52
+ ├───────────────────────────────┬──────────────────────────────┤
53
+ │ Orchestrator │ Trust Score Service │
54
+ └───────────────────────────────┴──────────────────────────────┘
55
+ ```
56
+
57
+ | Module | Description |
58
+ |--------|-------------|
59
+ | **Identity** | Ed25519 key pairs, DID creation (`did:atel:*`), signing & verification |
60
+ | **Schema** | Task and Capability JSON schemas, validation, factory functions, matching |
61
+ | **Policy** | Consent tokens (scoped, time-limited), policy engine with call tracking |
62
+ | **Gateway** | Central tool invocation gateway with policy enforcement and deterministic hashing |
63
+ | **Trace** | Append-only, hash-chained execution log with auto-checkpoints |
64
+ | **Proof** | Merkle-tree proof bundles with multi-check verification |
65
+ | **Score** | Local trust-score computation based on execution history |
66
+ | **Graph** | Multi-dimensional trust graph, direct/indirect/composite trust |
67
+ | **TrustManager** | Unified score + graph submission and trust query API |
68
+ | **Rollback** | Compensation and rollback execution manager |
69
+ | **Anchor** | Multi-chain proof anchoring (Base/BSC/Solana/Mock) |
70
+ | **Orchestrator** | High-level API wiring task delegation/execution/verify |
71
+ | **Service** | HTTP API for score + graph queries with JSON persistence |
72
+
73
+ ## Trust Modes
74
+
75
+ ATEL supports two deployment modes that can coexist:
76
+
77
+ - `Local Mode` (default): execute, prove, verify, and score locally with no network dependency.
78
+ - `Network Mode` (optional): keep local trust updates, and additionally sync summaries to a shared trust service.
79
+
80
+ This means local capability is never removed when network trust is enabled.
81
+
82
+ ## API Reference
83
+
84
+ Detailed API guide: `docs/API.md`
85
+ Start here (one-page onboarding): `docs/START-HERE.md`
86
+ 5-minute quickstart: `docs/QUICKSTART-5MIN.md`
87
+ Phase 0.5 runbook: `docs/PHASE-0.5.md`
88
+ Service deployment: `docs/SERVICE-DEPLOY.md`
89
+
90
+ ATEL skill package: `skills/atel/SKILL.md`
91
+
92
+ ### Module 1: Identity
93
+
94
+ ```typescript
95
+ import { AgentIdentity, generateKeyPair, createDID, sign, verify } from '@lawreneliang/atel-sdk';
96
+
97
+ const agent = new AgentIdentity();
98
+ console.log(agent.did); // "did:atel:..."
99
+ const sig = agent.sign(payload);
100
+ const ok = agent.verify(payload, sig);
101
+ ```
102
+
103
+ ### Module 2: Schema
104
+
105
+ ```typescript
106
+ import { createTask, createCapability, matchTaskToCapability } from '@lawreneliang/atel-sdk';
107
+
108
+ const task = createTask({
109
+ issuer: agent.did,
110
+ intent: { type: 'flight_search', goal: 'Find flights SIN→HND' },
111
+ risk: { level: 'medium' },
112
+ });
113
+
114
+ const cap = createCapability({
115
+ provider: executor.did,
116
+ capabilities: [{ type: 'flight_search', description: '...' }],
117
+ });
118
+
119
+ const match = matchTaskToCapability(task, cap);
120
+ ```
121
+
122
+ ### Module 3: Policy
123
+
124
+ ```typescript
125
+ import { mintConsentToken, verifyConsentToken, PolicyEngine } from '@lawreneliang/atel-sdk';
126
+
127
+ const token = mintConsentToken(
128
+ issuer.did, executor.did,
129
+ ['tool:http:get', 'data:public_web:read'],
130
+ { max_calls: 10, ttl_sec: 3600 },
131
+ 'medium',
132
+ issuer.secretKey,
133
+ );
134
+
135
+ verifyConsentToken(token, issuer.publicKey);
136
+
137
+ const engine = new PolicyEngine(token);
138
+ const decision = engine.evaluate(action); // 'allow' | 'deny' | 'needs_confirm'
139
+ ```
140
+
141
+ ### Module 4: Gateway
142
+
143
+ ```typescript
144
+ import { ToolGateway } from '@lawreneliang/atel-sdk';
145
+
146
+ const gateway = new ToolGateway(policyEngine);
147
+ gateway.registerTool('http.get', async (input) => { /* ... */ });
148
+
149
+ const result = await gateway.callTool({
150
+ tool: 'http.get',
151
+ input: { url: '...' },
152
+ consentToken: '...',
153
+ });
154
+ // result.output, result.input_hash, result.output_hash
155
+ ```
156
+
157
+ ### Module 5: Trace
158
+
159
+ ```typescript
160
+ import { ExecutionTrace } from '@lawreneliang/atel-sdk';
161
+
162
+ const trace = new ExecutionTrace(taskId, agentIdentity);
163
+ trace.append('TASK_ACCEPTED', { ... });
164
+ trace.append('TOOL_CALL', { ... });
165
+ trace.finalize(result);
166
+
167
+ const { valid, errors } = trace.verify();
168
+ ```
169
+
170
+ ### Module 6: Proof
171
+
172
+ ```typescript
173
+ import { ProofGenerator, ProofVerifier } from '@lawreneliang/atel-sdk';
174
+
175
+ const gen = new ProofGenerator(trace, identity);
176
+ const bundle = gen.generate(policyRef, consentRef, resultRef);
177
+
178
+ const report = ProofVerifier.verify(bundle, { trace });
179
+ // report.valid, report.checks, report.summary
180
+ ```
181
+
182
+ ### Module 7: Score
183
+
184
+ ```typescript
185
+ import { TrustScoreClient } from '@lawreneliang/atel-sdk';
186
+
187
+ const client = new TrustScoreClient();
188
+ client.submitExecutionSummary({ executor: did, task_id, success: true, ... });
189
+
190
+ const report = client.getAgentScore(did);
191
+ // report.trust_score (0-100), report.risk_flags, etc.
192
+ ```
193
+
194
+ ## Trust Score Formula
195
+
196
+ ```
197
+ base = success_rate × 60
198
+ volume = min(total_tasks / 100, 1) × 15
199
+ risk_bonus = (high_risk_successes / total) × 15
200
+ consistency = (1 − violation_rate) × 10
201
+ ─────────────────────────────────────────
202
+ score = base + volume + risk_bonus + consistency (clamped 0–100)
203
+ ```
204
+
205
+ ## Current Status (2026-02-13)
206
+
207
+ - [x] **Phase 0 MVP complete** — 13 modules implemented, core trust workflow end-to-end
208
+ - [x] **241 tests in suite** — unit/integration coverage across modules
209
+ - [x] **Demo coverage** — success path + failure scenarios
210
+ - [ ] **Phase 0.5 validation** — real agents + real external tools + real testnet anchoring
211
+
212
+ ## Roadmap
213
+
214
+ - [ ] **Phase 0.5** — Internal multi-agent cluster with real API/tool workloads
215
+ - [ ] **Phase 1** — Enterprise pilot + external integration hardening
216
+ - [ ] **Phase 2** — Open SDK access + Trust Score/Graph network rollout
217
+ - [ ] **Phase 3+** — Discovery/Directory and Router/Marketplace layers
218
+
219
+ ## License
220
+
221
+ MIT
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Base Chain Anchor Provider.
3
+ *
4
+ * Extends {@link EvmAnchorProvider} with Base-specific defaults.
5
+ * Base is an EVM-compatible L2 built on the OP Stack.
6
+ *
7
+ * Default RPC: https://mainnet.base.org
8
+ */
9
+ import { EvmAnchorProvider, type EvmAnchorConfig } from './evm.js';
10
+ /**
11
+ * Anchor provider for the Base chain.
12
+ */
13
+ export declare class BaseAnchorProvider extends EvmAnchorProvider {
14
+ /** Default Base mainnet RPC URL */
15
+ static readonly DEFAULT_RPC_URL = "https://mainnet.base.org";
16
+ /**
17
+ * @param config - RPC URL and optional private key.
18
+ * If `rpcUrl` is omitted, the Base mainnet default is used.
19
+ */
20
+ constructor(config?: Partial<EvmAnchorConfig>);
21
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Base Chain Anchor Provider.
3
+ *
4
+ * Extends {@link EvmAnchorProvider} with Base-specific defaults.
5
+ * Base is an EVM-compatible L2 built on the OP Stack.
6
+ *
7
+ * Default RPC: https://mainnet.base.org
8
+ */
9
+ import { EvmAnchorProvider } from './evm.js';
10
+ /**
11
+ * Anchor provider for the Base chain.
12
+ */
13
+ export class BaseAnchorProvider extends EvmAnchorProvider {
14
+ /** Default Base mainnet RPC URL */
15
+ static DEFAULT_RPC_URL = 'https://mainnet.base.org';
16
+ /**
17
+ * @param config - RPC URL and optional private key.
18
+ * If `rpcUrl` is omitted, the Base mainnet default is used.
19
+ */
20
+ constructor(config) {
21
+ super('Base', 'base', {
22
+ rpcUrl: config?.rpcUrl ?? BaseAnchorProvider.DEFAULT_RPC_URL,
23
+ privateKey: config?.privateKey,
24
+ });
25
+ }
26
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * BSC (BNB Smart Chain) Anchor Provider.
3
+ *
4
+ * Extends {@link EvmAnchorProvider} with BSC-specific defaults.
5
+ *
6
+ * Default RPC: https://bsc-dataseed.binance.org
7
+ */
8
+ import { EvmAnchorProvider, type EvmAnchorConfig } from './evm.js';
9
+ /**
10
+ * Anchor provider for the BSC chain.
11
+ */
12
+ export declare class BSCAnchorProvider extends EvmAnchorProvider {
13
+ /** Default BSC mainnet RPC URL */
14
+ static readonly DEFAULT_RPC_URL = "https://bsc-dataseed.binance.org";
15
+ /**
16
+ * @param config - RPC URL and optional private key.
17
+ * If `rpcUrl` is omitted, the BSC mainnet default is used.
18
+ */
19
+ constructor(config?: Partial<EvmAnchorConfig>);
20
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * BSC (BNB Smart Chain) Anchor Provider.
3
+ *
4
+ * Extends {@link EvmAnchorProvider} with BSC-specific defaults.
5
+ *
6
+ * Default RPC: https://bsc-dataseed.binance.org
7
+ */
8
+ import { EvmAnchorProvider } from './evm.js';
9
+ /**
10
+ * Anchor provider for the BSC chain.
11
+ */
12
+ export class BSCAnchorProvider extends EvmAnchorProvider {
13
+ /** Default BSC mainnet RPC URL */
14
+ static DEFAULT_RPC_URL = 'https://bsc-dataseed.binance.org';
15
+ /**
16
+ * @param config - RPC URL and optional private key.
17
+ * If `rpcUrl` is omitted, the BSC mainnet default is used.
18
+ */
19
+ constructor(config) {
20
+ super('BSC', 'bsc', {
21
+ rpcUrl: config?.rpcUrl ?? BSCAnchorProvider.DEFAULT_RPC_URL,
22
+ privateKey: config?.privateKey,
23
+ });
24
+ }
25
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * EVM Anchor Provider — shared base class for EVM-compatible chains.
3
+ *
4
+ * Anchors a hash by sending a zero-value transaction to the signer's
5
+ * own address with the hash encoded in the `data` field. This is the
6
+ * simplest on-chain timestamping approach — no contract deployment needed.
7
+ *
8
+ * Both {@link BaseAnchorProvider} and {@link BSCAnchorProvider} extend this class.
9
+ *
10
+ * @remarks
11
+ * ⚠️ SECURITY: The `privateKey` is used to sign transactions.
12
+ * Never hard-code private keys in source code. Use environment variables
13
+ * or a secure vault in production.
14
+ */
15
+ import { ethers } from 'ethers';
16
+ import type { AnchorProvider, AnchorRecord, AnchorVerification } from './index.js';
17
+ /** Configuration for an EVM-based anchor provider */
18
+ export interface EvmAnchorConfig {
19
+ /** JSON-RPC endpoint URL */
20
+ rpcUrl: string;
21
+ /**
22
+ * Hex-encoded private key for signing transactions.
23
+ * Optional — if omitted the provider can only verify / lookup.
24
+ *
25
+ * ⚠️ SECURITY: Keep this value secret. Load from env vars or a vault.
26
+ */
27
+ privateKey?: string;
28
+ }
29
+ /**
30
+ * Abstract EVM anchor provider.
31
+ *
32
+ * Subclasses only need to supply `name`, `chain`, and a default RPC URL.
33
+ */
34
+ export declare class EvmAnchorProvider implements AnchorProvider {
35
+ readonly name: string;
36
+ readonly chain: string;
37
+ /** Ethers JSON-RPC provider (read-only access) */
38
+ protected readonly provider: ethers.JsonRpcProvider;
39
+ /** Wallet for signing (undefined when no private key is supplied) */
40
+ protected readonly wallet?: ethers.Wallet;
41
+ /**
42
+ * @param name - Human-readable provider name.
43
+ * @param chain - Chain identifier.
44
+ * @param config - RPC URL and optional private key.
45
+ */
46
+ constructor(name: string, chain: string, config: EvmAnchorConfig);
47
+ /**
48
+ * Encode a hash string into the hex data payload for a transaction.
49
+ *
50
+ * Format: `0x` + hex(ATEL_ANCHOR:<hash>)
51
+ */
52
+ static encodeData(hash: string): string;
53
+ /**
54
+ * Decode the hash from a transaction data field.
55
+ *
56
+ * @returns The decoded hash, or `null` if the data doesn't match the expected format.
57
+ */
58
+ static decodeData(data: string): string | null;
59
+ /** @inheritdoc */
60
+ anchor(hash: string, metadata?: Record<string, unknown>): Promise<AnchorRecord>;
61
+ /** @inheritdoc */
62
+ verify(hash: string, txHash: string): Promise<AnchorVerification>;
63
+ /** @inheritdoc */
64
+ lookup(hash: string): Promise<AnchorRecord[]>;
65
+ /** @inheritdoc */
66
+ isAvailable(): Promise<boolean>;
67
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * EVM Anchor Provider — shared base class for EVM-compatible chains.
3
+ *
4
+ * Anchors a hash by sending a zero-value transaction to the signer's
5
+ * own address with the hash encoded in the `data` field. This is the
6
+ * simplest on-chain timestamping approach — no contract deployment needed.
7
+ *
8
+ * Both {@link BaseAnchorProvider} and {@link BSCAnchorProvider} extend this class.
9
+ *
10
+ * @remarks
11
+ * ⚠️ SECURITY: The `privateKey` is used to sign transactions.
12
+ * Never hard-code private keys in source code. Use environment variables
13
+ * or a secure vault in production.
14
+ */
15
+ import { ethers } from 'ethers';
16
+ /**
17
+ * Prefix prepended to the hash in the transaction data field
18
+ * so anchored transactions are easily identifiable.
19
+ */
20
+ const ANCHOR_PREFIX = 'ATEL_ANCHOR:';
21
+ /**
22
+ * Abstract EVM anchor provider.
23
+ *
24
+ * Subclasses only need to supply `name`, `chain`, and a default RPC URL.
25
+ */
26
+ export class EvmAnchorProvider {
27
+ name;
28
+ chain;
29
+ /** Ethers JSON-RPC provider (read-only access) */
30
+ provider;
31
+ /** Wallet for signing (undefined when no private key is supplied) */
32
+ wallet;
33
+ /**
34
+ * @param name - Human-readable provider name.
35
+ * @param chain - Chain identifier.
36
+ * @param config - RPC URL and optional private key.
37
+ */
38
+ constructor(name, chain, config) {
39
+ this.name = name;
40
+ this.chain = chain;
41
+ this.provider = new ethers.JsonRpcProvider(config.rpcUrl);
42
+ if (config.privateKey) {
43
+ // ⚠️ SECURITY: The wallet holds the private key in memory.
44
+ this.wallet = new ethers.Wallet(config.privateKey, this.provider);
45
+ }
46
+ }
47
+ /**
48
+ * Encode a hash string into the hex data payload for a transaction.
49
+ *
50
+ * Format: `0x` + hex(ATEL_ANCHOR:<hash>)
51
+ */
52
+ static encodeData(hash) {
53
+ return ethers.hexlify(ethers.toUtf8Bytes(`${ANCHOR_PREFIX}${hash}`));
54
+ }
55
+ /**
56
+ * Decode the hash from a transaction data field.
57
+ *
58
+ * @returns The decoded hash, or `null` if the data doesn't match the expected format.
59
+ */
60
+ static decodeData(data) {
61
+ try {
62
+ const text = ethers.toUtf8String(data);
63
+ if (text.startsWith(ANCHOR_PREFIX)) {
64
+ return text.slice(ANCHOR_PREFIX.length);
65
+ }
66
+ return null;
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ }
72
+ /** @inheritdoc */
73
+ async anchor(hash, metadata) {
74
+ if (!this.wallet) {
75
+ throw new Error(`${this.name}: Cannot anchor without a private key`);
76
+ }
77
+ const data = EvmAnchorProvider.encodeData(hash);
78
+ try {
79
+ const tx = await this.wallet.sendTransaction({
80
+ to: this.wallet.address,
81
+ value: 0n,
82
+ data,
83
+ });
84
+ const receipt = await tx.wait();
85
+ if (!receipt) {
86
+ throw new Error('Transaction receipt is null — tx may have been dropped');
87
+ }
88
+ return {
89
+ hash,
90
+ txHash: receipt.hash,
91
+ chain: this.chain,
92
+ timestamp: Date.now(),
93
+ blockNumber: receipt.blockNumber,
94
+ metadata,
95
+ };
96
+ }
97
+ catch (err) {
98
+ const message = err instanceof Error ? err.message : String(err);
99
+ throw new Error(`${this.name} anchor failed: ${message}`);
100
+ }
101
+ }
102
+ /** @inheritdoc */
103
+ async verify(hash, txHash) {
104
+ try {
105
+ const tx = await this.provider.getTransaction(txHash);
106
+ if (!tx) {
107
+ return {
108
+ valid: false,
109
+ hash,
110
+ txHash,
111
+ chain: this.chain,
112
+ detail: 'Transaction not found',
113
+ };
114
+ }
115
+ const decoded = EvmAnchorProvider.decodeData(tx.data);
116
+ if (decoded === null) {
117
+ return {
118
+ valid: false,
119
+ hash,
120
+ txHash,
121
+ chain: this.chain,
122
+ detail: 'Transaction data does not contain a valid anchor',
123
+ };
124
+ }
125
+ const valid = decoded === hash;
126
+ // Try to get block timestamp
127
+ let blockTimestamp;
128
+ if (tx.blockNumber) {
129
+ try {
130
+ const block = await this.provider.getBlock(tx.blockNumber);
131
+ blockTimestamp = block ? block.timestamp * 1000 : undefined;
132
+ }
133
+ catch {
134
+ // Non-critical — skip timestamp
135
+ }
136
+ }
137
+ return {
138
+ valid,
139
+ hash,
140
+ txHash,
141
+ chain: this.chain,
142
+ blockTimestamp,
143
+ detail: valid
144
+ ? 'Hash matches on-chain data'
145
+ : `Hash mismatch: expected "${hash}", found "${decoded}"`,
146
+ };
147
+ }
148
+ catch (err) {
149
+ const message = err instanceof Error ? err.message : String(err);
150
+ return {
151
+ valid: false,
152
+ hash,
153
+ txHash,
154
+ chain: this.chain,
155
+ detail: `Verification error: ${message}`,
156
+ };
157
+ }
158
+ }
159
+ /** @inheritdoc */
160
+ async lookup(hash) {
161
+ // On-chain lookup without an indexer is not feasible for EVM chains.
162
+ // In production, integrate with a subgraph or event indexer.
163
+ // For now, return an empty array — local records are managed by AnchorManager.
164
+ return [];
165
+ }
166
+ /** @inheritdoc */
167
+ async isAvailable() {
168
+ try {
169
+ await this.provider.getBlockNumber();
170
+ return true;
171
+ }
172
+ catch {
173
+ return false;
174
+ }
175
+ }
176
+ }