agentex-creator-sdk 1.0.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/CHANGELOG.md +36 -0
- package/LICENSE +21 -0
- package/README.md +195 -0
- package/dist/packages/contracts/src/deployer-investigation.d.ts +1015 -0
- package/dist/packages/contracts/src/deployer-investigation.js +101 -0
- package/dist/packages/contracts/src/index.d.ts +1701 -0
- package/dist/packages/contracts/src/index.js +380 -0
- package/dist/packages/contracts/src/indexed-activity.d.ts +684 -0
- package/dist/packages/contracts/src/indexed-activity.js +71 -0
- package/dist/packages/contracts/src/indexed-agents.d.ts +299 -0
- package/dist/packages/contracts/src/indexed-agents.js +131 -0
- package/dist/packages/contracts/src/inspection.d.ts +906 -0
- package/dist/packages/contracts/src/inspection.js +114 -0
- package/dist/packages/contracts/src/kinds.d.ts +5398 -0
- package/dist/packages/contracts/src/kinds.js +156 -0
- package/dist/packages/contracts/src/report-presentation.d.ts +346 -0
- package/dist/packages/contracts/src/report-presentation.js +120 -0
- package/dist/packages/contracts/src/solana-inspection.d.ts +451 -0
- package/dist/packages/contracts/src/solana-inspection.js +94 -0
- package/dist/packages/contracts/src/token-market.d.ts +193 -0
- package/dist/packages/contracts/src/token-market.js +335 -0
- package/dist/packages/contracts/src/wallet-analysis.d.ts +866 -0
- package/dist/packages/contracts/src/wallet-analysis.js +89 -0
- package/dist/packages/contracts/src/watchtower.d.ts +1141 -0
- package/dist/packages/contracts/src/watchtower.js +196 -0
- package/dist/packages/contracts/src/workflow.d.ts +1568 -0
- package/dist/packages/contracts/src/workflow.js +651 -0
- package/dist/packages/inspector/src/decode.d.ts +23 -0
- package/dist/packages/inspector/src/decode.js +150 -0
- package/dist/packages/inspector/src/scope.d.ts +87 -0
- package/dist/packages/inspector/src/scope.js +64 -0
- package/dist/packages/model/src/analysis.d.ts +149 -0
- package/dist/packages/model/src/analysis.js +387 -0
- package/dist/packages/model/src/pricing.d.ts +38 -0
- package/dist/packages/model/src/pricing.js +49 -0
- package/dist/packages/model/src/retry.d.ts +20 -0
- package/dist/packages/model/src/retry.js +31 -0
- package/dist/packages/model/src/schema.d.ts +10 -0
- package/dist/packages/model/src/schema.js +51 -0
- package/dist/packages/model/src/summary.d.ts +91 -0
- package/dist/packages/model/src/summary.js +177 -0
- package/dist/packages/model/src/types.d.ts +81 -0
- package/dist/packages/model/src/types.js +19 -0
- package/dist/packages/monitoring/src/delivery.d.ts +32 -0
- package/dist/packages/monitoring/src/delivery.js +53 -0
- package/dist/packages/providers/src/chain-transport.d.ts +42 -0
- package/dist/packages/providers/src/chain-transport.js +57 -0
- package/dist/packages/providers/src/coverage.d.ts +105 -0
- package/dist/packages/providers/src/coverage.js +260 -0
- package/dist/packages/providers/src/health.d.ts +273 -0
- package/dist/packages/providers/src/health.js +505 -0
- package/dist/packages/providers/src/keyed.d.ts +96 -0
- package/dist/packages/providers/src/keyed.js +240 -0
- package/dist/packages/providers/src/snapshot.d.ts +61 -0
- package/dist/packages/providers/src/snapshot.js +77 -0
- package/dist/packages/publication/src/fixtures.d.ts +45 -0
- package/dist/packages/publication/src/fixtures.js +350 -0
- package/dist/packages/research/src/index.d.ts +188 -0
- package/dist/packages/research/src/index.js +829 -0
- package/dist/packages/runtime/src/checkpoints.d.ts +65 -0
- package/dist/packages/runtime/src/checkpoints.js +214 -0
- package/dist/packages/runtime/src/policy.d.ts +57 -0
- package/dist/packages/runtime/src/policy.js +296 -0
- package/dist/packages/sdk/src/bin/agentex-buyer.d.ts +2 -0
- package/dist/packages/sdk/src/bin/agentex-buyer.js +4 -0
- package/dist/packages/sdk/src/bin/agentex.d.ts +2 -0
- package/dist/packages/sdk/src/bin/agentex.js +3 -0
- package/dist/packages/sdk/src/buyer-cli.d.ts +10 -0
- package/dist/packages/sdk/src/buyer-cli.js +210 -0
- package/dist/packages/sdk/src/buyer.d.ts +534 -0
- package/dist/packages/sdk/src/buyer.js +441 -0
- package/dist/packages/sdk/src/cli.d.ts +14 -0
- package/dist/packages/sdk/src/cli.js +149 -0
- package/dist/packages/sdk/src/errors.d.ts +31 -0
- package/dist/packages/sdk/src/errors.js +24 -0
- package/dist/packages/sdk/src/index.d.ts +236 -0
- package/dist/packages/sdk/src/index.js +150 -0
- package/dist/packages/sdk/src/local.d.ts +11 -0
- package/dist/packages/sdk/src/local.js +110 -0
- package/dist/packages/sdk/src/rails.d.ts +59 -0
- package/dist/packages/sdk/src/rails.js +96 -0
- package/dist/packages/sdk/src/report.d.ts +121 -0
- package/dist/packages/sdk/src/report.js +114 -0
- package/dist/packages/sdk/src/version.d.ts +2 -0
- package/dist/packages/sdk/src/version.js +2 -0
- package/dist/packages/watchtower/src/index.d.ts +150 -0
- package/dist/packages/watchtower/src/index.js +786 -0
- package/dist/packages/workflow/src/registry.d.ts +61 -0
- package/dist/packages/workflow/src/registry.js +76 -0
- package/examples/README.md +34 -0
- package/examples/cli-usage.sh +30 -0
- package/examples/fixtures/base-weth-input.json +4 -0
- package/examples/focused-researcher.json +127 -0
- package/examples/pay-with-eth-robinhood.mts +37 -0
- package/examples/pay-with-usdc.mts +41 -0
- package/examples/quickstart.mts +73 -0
- package/package.json +50 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AddressSchema, AgentManifestSchema, AtomicAmountSchema, ChainKeySchema, ResearchErrorSchema, contentHash, evmEvidenceItemSchema, matchesTemplateInput, publishManifest } from './index.js';
|
|
3
|
+
/** Watchtower window contracts (P04 package interface). Kept outside index.ts so modules initialise without a circular import. */
|
|
4
|
+
export const WATCH_SCHEMA_ID = 'agentex.watchtower-window.v1';
|
|
5
|
+
export const WATCH_TOOL = 'evm.watch-logs@0.1.0';
|
|
6
|
+
export const WATCH_MAX_WINDOW_BLOCKS = 2000;
|
|
7
|
+
export const WATCH_MAX_LOGS = 100;
|
|
8
|
+
export const WATCH_MAX_RULES = 8;
|
|
9
|
+
export const WATCH_RPC_CEILING = 16;
|
|
10
|
+
export const WATCH_RPC_METHODS = ['eth_chainId', 'eth_getBlockByNumber', 'eth_getLogs'];
|
|
11
|
+
export const AUTHORITY_EVENTS = ['Upgraded', 'AdminChanged', 'BeaconUpgraded', 'OwnershipTransferred'];
|
|
12
|
+
/** Watchtower 0.3.0: OpenZeppelin/Compound Governor, OpenZeppelin TimelockController and Compound Timelock events. */
|
|
13
|
+
export const GOVERNANCE_EVENTS = ['ProposalCreated', 'ProposalQueued', 'ProposalExecuted', 'ProposalCanceled', 'CallScheduled', 'CallExecuted', 'Cancelled', 'MinDelayChange',
|
|
14
|
+
'QueueTransaction', 'ExecuteTransaction', 'CancelTransaction', 'NewDelay', 'NewAdmin', 'NewPendingAdmin'];
|
|
15
|
+
/** Watchtower 0.3.0: OpenZeppelin AccessControl and Pausable events. */
|
|
16
|
+
export const CONTROL_EVENTS = ['RoleGranted', 'RoleRevoked', 'RoleAdminChanged', 'Paused', 'Unpaused'];
|
|
17
|
+
/** Watchtower 0.3.0: pool events of Uniswap V2-compatible, Uniswap V3-compatible and Aerodrome (Solidly) pools. */
|
|
18
|
+
export const POOL_EVENTS = ['Swap', 'Mint', 'Burn'];
|
|
19
|
+
export const POOL_DEXES = ['uniswap-v2', 'uniswap-v3', 'aerodrome'];
|
|
20
|
+
/** Rule types added in Watchtower 0.3.0. Versions 0.1.0 and 0.2.0 refuse them, so a pinned monitor never gains a rule its version did not declare. */
|
|
21
|
+
export const WATCH_RULES_V3 = ['wallet-token-flow', 'pool-activity', 'governance-change', 'control-change'];
|
|
22
|
+
const WATCH_RULE_TYPES = ['erc20-transfer', 'erc20-approval', 'authority-change', ...WATCH_RULES_V3];
|
|
23
|
+
const Hash = z.string().regex(/^[a-f0-9]{64}$/);
|
|
24
|
+
const Hex32 = z.string().regex(/^0x[a-fA-F0-9]{64}$/);
|
|
25
|
+
export const BlockNumberSchema = z.string().regex(/^(0|[1-9][0-9]{0,15})$/, 'Expected a decimal block number');
|
|
26
|
+
export const WatchRuleIdSchema = z.string().regex(/^[a-z][a-z0-9-]{0,39}$/, 'Rule ids use lowercase letters, digits and hyphens (at most 40 characters)');
|
|
27
|
+
/** Rule types that would need indexes the platform does not have. They fail validation instead of silently matching nothing. */
|
|
28
|
+
export const UNSUPPORTED_WATCH_RULES = Object.freeze({
|
|
29
|
+
'liquidity-threshold': 'Liquidity threshold rules need a verified DEX pool index, which is not connected. This rule type is unsupported.',
|
|
30
|
+
'wallet-interaction': 'Wallet-interacted-with-contract rules need a transaction or trace index, which is not connected. This rule type is unsupported.',
|
|
31
|
+
'contract-interaction': 'Contract interaction rules need a transaction or trace index, which is not connected. This rule type is unsupported.',
|
|
32
|
+
'native-transfer': 'Native value transfers emit no logs and need a trace index, which is not connected. This rule type is unsupported.',
|
|
33
|
+
});
|
|
34
|
+
export const SupportedWatchRuleSchema = z.discriminatedUnion('type', [
|
|
35
|
+
z.strictObject({ id: WatchRuleIdSchema, type: z.literal('erc20-transfer'), token: AddressSchema, from: AddressSchema.optional(), to: AddressSchema.optional(), minimumAtomic: AtomicAmountSchema.optional() }),
|
|
36
|
+
z.strictObject({ id: WatchRuleIdSchema, type: z.literal('erc20-approval'), token: AddressSchema, owner: AddressSchema, spender: AddressSchema.optional() }),
|
|
37
|
+
z.strictObject({ id: WatchRuleIdSchema, type: z.literal('authority-change'), contract: AddressSchema, events: z.array(z.enum(AUTHORITY_EVENTS)).min(1).max(4).optional() }),
|
|
38
|
+
// 0.3.0: a wallet's ERC-20 movements in one direction across up to eight tokens, read with one address-list log filter.
|
|
39
|
+
z.strictObject({ id: WatchRuleIdSchema, type: z.literal('wallet-token-flow'), wallet: AddressSchema, tokens: z.array(AddressSchema).min(1).max(8), direction: z.enum(['in', 'out']) })
|
|
40
|
+
.refine((rule) => new Set(rule.tokens.map((token) => token.toLowerCase())).size === rule.tokens.length, 'Tokens must be distinct'),
|
|
41
|
+
// 0.3.0: swaps and liquidity added or removed on one supplied pool, optionally above exact per-token minimums.
|
|
42
|
+
z.strictObject({ id: WatchRuleIdSchema, type: z.literal('pool-activity'), pool: AddressSchema, dex: z.enum(POOL_DEXES), events: z.array(z.enum(POOL_EVENTS)).min(1).max(3).optional(),
|
|
43
|
+
minimumAmount0Atomic: AtomicAmountSchema.optional(), minimumAmount1Atomic: AtomicAmountSchema.optional() }),
|
|
44
|
+
z.strictObject({ id: WatchRuleIdSchema, type: z.literal('governance-change'), contract: AddressSchema, events: z.array(z.enum(GOVERNANCE_EVENTS)).min(1).max(GOVERNANCE_EVENTS.length).optional() }),
|
|
45
|
+
z.strictObject({ id: WatchRuleIdSchema, type: z.literal('control-change'), contract: AddressSchema, events: z.array(z.enum(CONTROL_EVENTS)).min(1).max(CONTROL_EVENTS.length).optional() }),
|
|
46
|
+
]);
|
|
47
|
+
export const WatchRuleSchema = z.preprocess((value, ctx) => {
|
|
48
|
+
const type = value !== null && typeof value === 'object' && !Array.isArray(value) ? value.type : undefined;
|
|
49
|
+
if (typeof type === 'string' && Object.hasOwn(UNSUPPORTED_WATCH_RULES, type))
|
|
50
|
+
ctx.addIssue({ code: 'custom', message: UNSUPPORTED_WATCH_RULES[type] });
|
|
51
|
+
return value;
|
|
52
|
+
}, SupportedWatchRuleSchema);
|
|
53
|
+
/** One bounded, inclusive block window at or below the latest head. Monitors create consecutive windows; a manual run can inspect one. */
|
|
54
|
+
export const WatchtowerWindowInputSchema = z.strictObject({
|
|
55
|
+
chain: ChainKeySchema,
|
|
56
|
+
fromBlock: BlockNumberSchema,
|
|
57
|
+
toBlock: BlockNumberSchema,
|
|
58
|
+
rules: z.array(WatchRuleSchema).min(1).max(WATCH_MAX_RULES),
|
|
59
|
+
maximumLogs: z.number().int().min(1).max(WATCH_MAX_LOGS).default(50),
|
|
60
|
+
}).superRefine((input, ctx) => {
|
|
61
|
+
const from = BigInt(input.fromBlock);
|
|
62
|
+
const to = BigInt(input.toBlock);
|
|
63
|
+
if (from > to)
|
|
64
|
+
ctx.addIssue({ code: 'custom', path: ['toBlock'], message: 'toBlock must not precede fromBlock' });
|
|
65
|
+
else if (to - from + 1n > BigInt(WATCH_MAX_WINDOW_BLOCKS))
|
|
66
|
+
ctx.addIssue({ code: 'custom', path: ['toBlock'], message: `A window covers at most ${WATCH_MAX_WINDOW_BLOCKS} blocks` });
|
|
67
|
+
if (new Set(input.rules.map((rule) => rule.id)).size !== input.rules.length)
|
|
68
|
+
ctx.addIssue({ code: 'custom', path: ['rules'], message: 'Rule ids must be unique' });
|
|
69
|
+
});
|
|
70
|
+
/** Worst-case RPC reads for one window: identity, latest head, from/to headers, one eth_getLogs per rule, a covered-through header and the final recheck. */
|
|
71
|
+
export const watchWindowRpcEstimate = (rules) => 6 + rules;
|
|
72
|
+
export const WATCHTOWER_MANIFEST = publishManifest({
|
|
73
|
+
schemaVersion: '1.0', id: 'watchtower', version: '0.1.0', name: 'Watchtower',
|
|
74
|
+
description: 'Scan one bounded finalized block window for declared ERC-20 Transfer/Approval events and proxy or ownership authority changes, with evidence and explicit coverage. Monitors schedule consecutive windows. Unsupported data sources are refused, never reported as quiet.',
|
|
75
|
+
author: { id: 'agentex', name: 'AGENTEX', ownership: 'first-party' }, license: 'Proprietary', visibility: 'source-visible', chains: ['ethereum', 'base'],
|
|
76
|
+
inputSchema: { type: 'object', additionalProperties: false, properties: { chain: { type: 'string', description: 'Explicit analysis network', enum: ['ethereum', 'base'] }, fromBlock: { type: 'string', description: 'First finalized block, inclusive' }, toBlock: { type: 'string', description: 'Last finalized block, inclusive; at most 2000 blocks per window' }, rules: { type: 'array', description: 'One to eight supported log rules' }, maximumLogs: { type: 'integer', description: 'Maximum logs examined in the window (1-100)' } }, required: ['chain', 'fromBlock', 'toBlock', 'rules'] },
|
|
77
|
+
outputSchemaId: WATCH_SCHEMA_ID, tools: [{ id: 'evm.watch-logs', version: '0.1.0', capability: 'evm.read', chains: ['ethereum', 'base'], addressScope: 'input-watch-scope', maximumCalls: 16 }],
|
|
78
|
+
modelPolicy: { mode: 'deterministic', maximumModelTokens: 0 }, limits: { maximumRpcCalls: 16, maximumDurationMs: 60000, maximumOutputBytes: 262144, maximumGraphDepth: 4, maximumPages: 0, maximumExecutionAtomic: '0' },
|
|
79
|
+
workflow: [{ id: 'input', type: 'input', dependsOn: [] }, { id: 'watch', type: 'tool', tool: 'evm.watch-logs', dependsOn: ['input'], arguments: { chain: { source: 'input', path: 'chain' }, fromBlock: { source: 'input', path: 'fromBlock' }, toBlock: { source: 'input', path: 'toBlock' }, rules: { source: 'input', path: 'rules' }, maximumLogs: { source: 'input', path: 'maximumLogs' } } }, { id: 'report', type: 'output', dependsOn: ['watch'], from: 'watch' }],
|
|
80
|
+
testCases: ['transfer-filters', 'approval-owner', 'authority-events', 'mismatched-topics', 'log-cap-truncation', 'provider-unavailable', 'reorged-window', 'unsupported-rules'],
|
|
81
|
+
}).manifest;
|
|
82
|
+
/** Watchtower 0.2.0: identical graph, tool, limits and output schema to 0.1.0 (kept byte-identical, hash f5ad5883…); instant-read wording for new drafts. */
|
|
83
|
+
export const WATCHTOWER_MANIFEST_V2 = publishManifest({
|
|
84
|
+
...structuredClone(WATCHTOWER_MANIFEST), version: '0.2.0',
|
|
85
|
+
description: 'Scan one bounded block window, read up to the latest block (sequencer-confirmed), for declared ERC-20 Transfer/Approval events and proxy or ownership authority changes, with evidence and explicit coverage. A reorged window fails its end-hash recheck and is retracted and rescanned. Monitors schedule consecutive windows. Unsupported data sources are refused, never reported as quiet.',
|
|
86
|
+
inputSchema: { ...structuredClone(WATCHTOWER_MANIFEST.inputSchema), properties: { ...structuredClone(WATCHTOWER_MANIFEST.inputSchema.properties),
|
|
87
|
+
fromBlock: { type: 'string', description: 'First block, inclusive' }, toBlock: { type: 'string', description: 'Last block, inclusive, at or before the latest block; at most 2000 blocks per window' } } },
|
|
88
|
+
}).manifest;
|
|
89
|
+
/**
|
|
90
|
+
* Watchtower 0.3.0: the same bounded window graph, tool and output schema, plus wallet token flows across a token list, pool swaps and
|
|
91
|
+
* liquidity changes, governance and timelock events and access-control or pause events, and Robinhood Chain. New drafts start from it.
|
|
92
|
+
*/
|
|
93
|
+
export const WATCHTOWER_MANIFEST_V3 = publishManifest({
|
|
94
|
+
...structuredClone(WATCHTOWER_MANIFEST_V2), version: '0.3.0', chains: ['ethereum', 'base', 'robinhood'],
|
|
95
|
+
description: 'Scan one bounded block window, read up to the latest block (sequencer-confirmed), for declared on-chain events: ERC-20 transfers and approvals, a wallet\'s token movements across up to eight tokens, swaps and liquidity changes on a supplied Uniswap V2/V3 or Aerodrome pool, governor proposals and timelock operations, proxy, ownership, role and pause changes. Every event carries evidence and explicit coverage; a reorged window is retracted and rescanned. Monitors schedule consecutive windows. Unsupported data sources are refused, never reported as quiet.',
|
|
96
|
+
inputSchema: { ...structuredClone(WATCHTOWER_MANIFEST_V2.inputSchema), properties: { ...structuredClone(WATCHTOWER_MANIFEST_V2.inputSchema.properties),
|
|
97
|
+
chain: { type: 'string', description: 'Explicit analysis network', enum: ['ethereum', 'base', 'robinhood'] }, rules: { type: 'array', description: 'One to eight supported event rules' } } },
|
|
98
|
+
tools: [{ ...structuredClone(WATCHTOWER_MANIFEST_V2.tools[0]), chains: ['ethereum', 'base', 'robinhood'] }],
|
|
99
|
+
testCases: [...WATCHTOWER_MANIFEST_V2.testCases, 'wallet-token-flow', 'pool-activity', 'governance-events', 'control-events'],
|
|
100
|
+
}).manifest;
|
|
101
|
+
const versionAtLeast = (version, minimum) => { const a = version.split('.').map(Number); const b = minimum.split('.').map(Number); for (let index = 0; index < 3; index++) {
|
|
102
|
+
if (a[index] !== b[index])
|
|
103
|
+
return a[index] > b[index];
|
|
104
|
+
} return true; };
|
|
105
|
+
/** The rule types an exact Watchtower version declares. Returns the refusal for the first rule its version cannot run, or null. */
|
|
106
|
+
export function watchRuleRefusal(version, rules) {
|
|
107
|
+
if (versionAtLeast(version, '0.3.0'))
|
|
108
|
+
return null;
|
|
109
|
+
const rule = rules.find((item) => WATCH_RULES_V3.includes(item.type));
|
|
110
|
+
return rule ? `Rule ${rule.id} (${rule.type}) needs Watchtower 0.3.0 or later; version ${version} watches ERC-20 transfers, approvals and authority changes only.` : null;
|
|
111
|
+
}
|
|
112
|
+
export const SupportedWatchtowerManifestSchema = AgentManifestSchema.superRefine((manifest, ctx) => {
|
|
113
|
+
if (manifest.outputSchemaId !== WATCH_SCHEMA_ID)
|
|
114
|
+
ctx.addIssue({ code: 'custom', path: ['outputSchemaId'], message: 'Only agentex.watchtower-window.v1 manifests are Watchtower packages.' });
|
|
115
|
+
if (contentHash(manifest.workflow) !== contentHash(WATCHTOWER_MANIFEST.workflow))
|
|
116
|
+
ctx.addIssue({ code: 'custom', path: ['workflow'], message: 'The executor supports only the bounded Watchtower window graph.' });
|
|
117
|
+
if (!matchesTemplateInput(manifest, [WATCHTOWER_MANIFEST, WATCHTOWER_MANIFEST_V2, WATCHTOWER_MANIFEST_V3]))
|
|
118
|
+
ctx.addIssue({ code: 'custom', path: ['inputSchema'], message: 'The input declaration must match the supported chain/window/rules/maximumLogs inputs.' });
|
|
119
|
+
if (manifest.chains.includes('robinhood') && !versionAtLeast(manifest.version, '0.3.0'))
|
|
120
|
+
ctx.addIssue({ code: 'custom', path: ['chains'], message: 'Robinhood Chain windows need Watchtower 0.3.0 or later.' });
|
|
121
|
+
const tool = manifest.tools[0];
|
|
122
|
+
if (manifest.tools.length !== 1 || tool?.id !== 'evm.watch-logs' || tool.version !== '0.1.0' || tool.capability !== 'evm.read' || tool.addressScope !== 'input-watch-scope')
|
|
123
|
+
ctx.addIssue({ code: 'custom', path: ['tools'], message: 'Only evm.watch-logs@0.1.0 with the read-only input-watch-scope grant is executable.' });
|
|
124
|
+
if (manifest.limits.maximumRpcCalls < watchWindowRpcEstimate(1))
|
|
125
|
+
ctx.addIssue({ code: 'custom', path: ['limits', 'maximumRpcCalls'], message: 'A Watchtower window needs at least seven RPC calls.' });
|
|
126
|
+
});
|
|
127
|
+
export const WatchEvidenceItemSchema = evmEvidenceItemSchema(WATCH_TOOL);
|
|
128
|
+
export const WatchIgnoredReasonSchema = z.enum(['address-mismatch', 'topic-mismatch', 'data-mismatch', 'filter-mismatch', 'below-minimum', 'removed', 'outside-coverage']);
|
|
129
|
+
export const WatchEventSchema = z.strictObject({
|
|
130
|
+
eventKey: Hash, ruleId: WatchRuleIdSchema, ruleType: z.enum(WATCH_RULE_TYPES), event: z.enum(['Transfer', 'Approval', ...AUTHORITY_EVENTS, ...POOL_EVENTS, ...GOVERNANCE_EVENTS, ...CONTROL_EVENTS]),
|
|
131
|
+
blockNumber: BlockNumberSchema, blockHash: Hex32, transactionHash: Hex32, logIndex: z.number().int().nonnegative(), address: AddressSchema,
|
|
132
|
+
fields: z.record(z.string().regex(/^[a-zA-Z][a-zA-Z0-9]{0,31}$/), z.string().max(200)), explanation: z.string().max(600), evidenceIds: z.array(z.string()).min(1),
|
|
133
|
+
});
|
|
134
|
+
export const WatchRuleCoverageSchema = z.strictObject({
|
|
135
|
+
id: WatchRuleIdSchema, type: z.enum(WATCH_RULE_TYPES), coverage: z.enum(['complete', 'truncated', 'unavailable', 'not-read']),
|
|
136
|
+
logsReturned: z.number().int().nonnegative(), logsMatched: z.number().int().nonnegative(), ignored: z.partialRecord(WatchIgnoredReasonSchema, z.number().int().positive()),
|
|
137
|
+
coveredThroughBlock: BlockNumberSchema.nullable(), reason: z.string().max(500), evidenceIds: z.array(z.string()),
|
|
138
|
+
});
|
|
139
|
+
export const WatchtowerWindowReportSchema = z.strictObject({
|
|
140
|
+
schemaVersion: z.literal(WATCH_SCHEMA_ID), agentVersion: z.string().regex(/^\d+\.\d+\.\d+$/), manifestHash: Hash,
|
|
141
|
+
input: WatchtowerWindowInputSchema, status: z.enum(['complete', 'partial', 'failed']), generatedAt: z.iso.datetime(), chainId: z.number().int().positive().nullable(),
|
|
142
|
+
window: z.strictObject({
|
|
143
|
+
fromBlock: BlockNumberSchema, toBlock: BlockNumberSchema, fromBlockHash: Hex32.nullable(), toBlockHash: Hex32.nullable(), toBlockTimestamp: AtomicAmountSchema.nullable(),
|
|
144
|
+
// New windows are read up to the latest head (`sequencer-confirmed`); a reorg is caught by the end-hash recheck and retracted. `finalized`,
|
|
145
|
+
// `finalizedBlock` and `finalityLagBlocks` only parse windows captured before the instant-read rule (new windows leave them null).
|
|
146
|
+
finality: z.enum(['finalized', 'sequencer-confirmed']), canonicality: z.enum(['verified-at-completion', 'unchecked', 'changed']), coverage: z.enum(['complete', 'partial', 'unavailable']),
|
|
147
|
+
coveredThroughBlock: BlockNumberSchema.nullable(), coveredThroughBlockHash: Hex32.nullable(), finalizedBlock: BlockNumberSchema.nullable(),
|
|
148
|
+
finalityLagBlocks: z.number().int().nonnegative().nullable(), dataLagSeconds: z.number().int().nonnegative().nullable(),
|
|
149
|
+
/** Informational: the latest head read when the window was scanned, and how many blocks the window end trailed it. */
|
|
150
|
+
headBlock: BlockNumberSchema.nullable().optional(), headLagBlocks: z.number().int().nonnegative().nullable().optional(),
|
|
151
|
+
}),
|
|
152
|
+
rules: z.array(WatchRuleCoverageSchema).min(1).max(WATCH_MAX_RULES), events: z.array(WatchEventSchema).max(WATCH_MAX_LOGS),
|
|
153
|
+
unknowns: z.array(z.string().max(500)).max(32), limitations: z.array(z.string()), errors: z.array(ResearchErrorSchema), evidence: z.array(WatchEvidenceItemSchema).max(WATCH_RPC_CEILING * 2),
|
|
154
|
+
usage: z.strictObject({ rpcCalls: z.number().int().nonnegative(), maximumRpcCalls: z.number().int().positive(), elapsedMs: z.number().int().nonnegative(), modelTokens: z.literal(0), providerCostAtomic: z.null(), providerCostStatus: z.literal('unpriced') }),
|
|
155
|
+
}).superRefine((report, ctx) => {
|
|
156
|
+
const issue = (message) => ctx.addIssue({ code: 'custom', message });
|
|
157
|
+
const ids = new Set(report.evidence.map((item) => item.id));
|
|
158
|
+
if (ids.size !== report.evidence.length)
|
|
159
|
+
issue('Duplicate evidence id');
|
|
160
|
+
for (const item of report.evidence) {
|
|
161
|
+
if (contentHash(item.result) !== item.contentHash)
|
|
162
|
+
issue('Evidence content hash does not match its captured result');
|
|
163
|
+
if (!WATCH_RPC_METHODS.includes(item.source.method))
|
|
164
|
+
issue(`Unsupported Watchtower RPC method ${item.source.method}`);
|
|
165
|
+
}
|
|
166
|
+
for (const item of [...report.rules, ...report.events])
|
|
167
|
+
for (const id of item.evidenceIds)
|
|
168
|
+
if (!ids.has(id))
|
|
169
|
+
issue(`Unknown evidence reference ${id}`);
|
|
170
|
+
for (const error of report.errors)
|
|
171
|
+
if (error.evidenceId && !ids.has(error.evidenceId))
|
|
172
|
+
issue(`Unknown error evidence reference ${error.evidenceId}`);
|
|
173
|
+
const { window } = report;
|
|
174
|
+
if (window.fromBlock !== report.input.fromBlock || window.toBlock !== report.input.toBlock)
|
|
175
|
+
issue('The report window must equal the accepted input window.');
|
|
176
|
+
const ruleIds = new Set(report.input.rules.map((rule) => rule.id));
|
|
177
|
+
if (report.rules.length !== report.input.rules.length || report.rules.some((rule, index) => rule.id !== report.input.rules[index]?.id))
|
|
178
|
+
issue('Rule coverage must describe every accepted rule in order.');
|
|
179
|
+
if (window.coverage === 'complete' && (window.coveredThroughBlock !== window.toBlock || report.rules.some((rule) => rule.coverage !== 'complete') || window.canonicality !== 'verified-at-completion'))
|
|
180
|
+
issue('Complete coverage requires every rule read through the verified window end.');
|
|
181
|
+
if (window.coverage === 'unavailable' && (window.coveredThroughBlock !== null || report.events.length > 0))
|
|
182
|
+
issue('An unavailable window cannot claim coverage or events.');
|
|
183
|
+
if (report.status !== 'failed' && window.canonicality !== 'verified-at-completion')
|
|
184
|
+
issue('Only a canonical rechecked window can be accepted.');
|
|
185
|
+
const keys = new Set();
|
|
186
|
+
for (const event of report.events) {
|
|
187
|
+
if (!ruleIds.has(event.ruleId))
|
|
188
|
+
issue(`Event references unknown rule ${event.ruleId}`);
|
|
189
|
+
if (keys.has(event.eventKey))
|
|
190
|
+
issue('Duplicate event key');
|
|
191
|
+
keys.add(event.eventKey);
|
|
192
|
+
const block = BigInt(event.blockNumber);
|
|
193
|
+
if (block < BigInt(window.fromBlock) || window.coveredThroughBlock === null || block > BigInt(window.coveredThroughBlock))
|
|
194
|
+
issue('Events must lie inside the covered part of the window.');
|
|
195
|
+
}
|
|
196
|
+
});
|