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.
Files changed (97) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/LICENSE +21 -0
  3. package/README.md +195 -0
  4. package/dist/packages/contracts/src/deployer-investigation.d.ts +1015 -0
  5. package/dist/packages/contracts/src/deployer-investigation.js +101 -0
  6. package/dist/packages/contracts/src/index.d.ts +1701 -0
  7. package/dist/packages/contracts/src/index.js +380 -0
  8. package/dist/packages/contracts/src/indexed-activity.d.ts +684 -0
  9. package/dist/packages/contracts/src/indexed-activity.js +71 -0
  10. package/dist/packages/contracts/src/indexed-agents.d.ts +299 -0
  11. package/dist/packages/contracts/src/indexed-agents.js +131 -0
  12. package/dist/packages/contracts/src/inspection.d.ts +906 -0
  13. package/dist/packages/contracts/src/inspection.js +114 -0
  14. package/dist/packages/contracts/src/kinds.d.ts +5398 -0
  15. package/dist/packages/contracts/src/kinds.js +156 -0
  16. package/dist/packages/contracts/src/report-presentation.d.ts +346 -0
  17. package/dist/packages/contracts/src/report-presentation.js +120 -0
  18. package/dist/packages/contracts/src/solana-inspection.d.ts +451 -0
  19. package/dist/packages/contracts/src/solana-inspection.js +94 -0
  20. package/dist/packages/contracts/src/token-market.d.ts +193 -0
  21. package/dist/packages/contracts/src/token-market.js +335 -0
  22. package/dist/packages/contracts/src/wallet-analysis.d.ts +866 -0
  23. package/dist/packages/contracts/src/wallet-analysis.js +89 -0
  24. package/dist/packages/contracts/src/watchtower.d.ts +1141 -0
  25. package/dist/packages/contracts/src/watchtower.js +196 -0
  26. package/dist/packages/contracts/src/workflow.d.ts +1568 -0
  27. package/dist/packages/contracts/src/workflow.js +651 -0
  28. package/dist/packages/inspector/src/decode.d.ts +23 -0
  29. package/dist/packages/inspector/src/decode.js +150 -0
  30. package/dist/packages/inspector/src/scope.d.ts +87 -0
  31. package/dist/packages/inspector/src/scope.js +64 -0
  32. package/dist/packages/model/src/analysis.d.ts +149 -0
  33. package/dist/packages/model/src/analysis.js +387 -0
  34. package/dist/packages/model/src/pricing.d.ts +38 -0
  35. package/dist/packages/model/src/pricing.js +49 -0
  36. package/dist/packages/model/src/retry.d.ts +20 -0
  37. package/dist/packages/model/src/retry.js +31 -0
  38. package/dist/packages/model/src/schema.d.ts +10 -0
  39. package/dist/packages/model/src/schema.js +51 -0
  40. package/dist/packages/model/src/summary.d.ts +91 -0
  41. package/dist/packages/model/src/summary.js +177 -0
  42. package/dist/packages/model/src/types.d.ts +81 -0
  43. package/dist/packages/model/src/types.js +19 -0
  44. package/dist/packages/monitoring/src/delivery.d.ts +32 -0
  45. package/dist/packages/monitoring/src/delivery.js +53 -0
  46. package/dist/packages/providers/src/chain-transport.d.ts +42 -0
  47. package/dist/packages/providers/src/chain-transport.js +57 -0
  48. package/dist/packages/providers/src/coverage.d.ts +105 -0
  49. package/dist/packages/providers/src/coverage.js +260 -0
  50. package/dist/packages/providers/src/health.d.ts +273 -0
  51. package/dist/packages/providers/src/health.js +505 -0
  52. package/dist/packages/providers/src/keyed.d.ts +96 -0
  53. package/dist/packages/providers/src/keyed.js +240 -0
  54. package/dist/packages/providers/src/snapshot.d.ts +61 -0
  55. package/dist/packages/providers/src/snapshot.js +77 -0
  56. package/dist/packages/publication/src/fixtures.d.ts +45 -0
  57. package/dist/packages/publication/src/fixtures.js +350 -0
  58. package/dist/packages/research/src/index.d.ts +188 -0
  59. package/dist/packages/research/src/index.js +829 -0
  60. package/dist/packages/runtime/src/checkpoints.d.ts +65 -0
  61. package/dist/packages/runtime/src/checkpoints.js +214 -0
  62. package/dist/packages/runtime/src/policy.d.ts +57 -0
  63. package/dist/packages/runtime/src/policy.js +296 -0
  64. package/dist/packages/sdk/src/bin/agentex-buyer.d.ts +2 -0
  65. package/dist/packages/sdk/src/bin/agentex-buyer.js +4 -0
  66. package/dist/packages/sdk/src/bin/agentex.d.ts +2 -0
  67. package/dist/packages/sdk/src/bin/agentex.js +3 -0
  68. package/dist/packages/sdk/src/buyer-cli.d.ts +10 -0
  69. package/dist/packages/sdk/src/buyer-cli.js +210 -0
  70. package/dist/packages/sdk/src/buyer.d.ts +534 -0
  71. package/dist/packages/sdk/src/buyer.js +441 -0
  72. package/dist/packages/sdk/src/cli.d.ts +14 -0
  73. package/dist/packages/sdk/src/cli.js +149 -0
  74. package/dist/packages/sdk/src/errors.d.ts +31 -0
  75. package/dist/packages/sdk/src/errors.js +24 -0
  76. package/dist/packages/sdk/src/index.d.ts +236 -0
  77. package/dist/packages/sdk/src/index.js +150 -0
  78. package/dist/packages/sdk/src/local.d.ts +11 -0
  79. package/dist/packages/sdk/src/local.js +110 -0
  80. package/dist/packages/sdk/src/rails.d.ts +59 -0
  81. package/dist/packages/sdk/src/rails.js +96 -0
  82. package/dist/packages/sdk/src/report.d.ts +121 -0
  83. package/dist/packages/sdk/src/report.js +114 -0
  84. package/dist/packages/sdk/src/version.d.ts +2 -0
  85. package/dist/packages/sdk/src/version.js +2 -0
  86. package/dist/packages/watchtower/src/index.d.ts +150 -0
  87. package/dist/packages/watchtower/src/index.js +786 -0
  88. package/dist/packages/workflow/src/registry.d.ts +61 -0
  89. package/dist/packages/workflow/src/registry.js +76 -0
  90. package/examples/README.md +34 -0
  91. package/examples/cli-usage.sh +30 -0
  92. package/examples/fixtures/base-weth-input.json +4 -0
  93. package/examples/focused-researcher.json +127 -0
  94. package/examples/pay-with-eth-robinhood.mts +37 -0
  95. package/examples/pay-with-usdc.mts +41 -0
  96. package/examples/quickstart.mts +73 -0
  97. package/package.json +50 -0
@@ -0,0 +1,380 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { isAddress } from 'viem';
3
+ import { z } from 'zod';
4
+ export const ChainKeySchema = z.enum(['ethereum', 'base', 'robinhood']);
5
+ export const AddressSchema = z.string().refine((value) => isAddress(value), 'Expected a valid EVM address (with a valid checksum when mixed case)');
6
+ export const AtomicAmountSchema = z.string().regex(/^(0|[1-9][0-9]{0,77})$/, 'Expected unsigned atomic units, without decimals or exponent notation').refine((value) => /^(0|[1-9][0-9]{0,77})$/.test(value) && BigInt(value) < 2n ** 256n, 'Amount exceeds uint256');
7
+ export const AssetAmountSchema = z.strictObject({ chainId: z.number().int().positive(), asset: z.union([z.literal('native'), AddressSchema]), decimals: z.number().int().min(0).max(255), atomic: AtomicAmountSchema });
8
+ export const ResearchInputSchema = z.strictObject({
9
+ chain: ChainKeySchema,
10
+ address: AddressSchema,
11
+ scope: z.literal('metadata-and-controls').default('metadata-and-controls'),
12
+ snapshotBlock: AtomicAmountSchema.optional(),
13
+ });
14
+ const Identifier = z.string().regex(/^[a-z][a-z0-9._-]{0,63}$/);
15
+ const PropertyIdentifier = z.string().regex(/^[a-z][a-zA-Z0-9_]{0,63}$/).refine((value) => !['constructor', 'prototype'].includes(value), 'Reserved property name');
16
+ const Hash = z.string().regex(/^[a-f0-9]{64}$/);
17
+ const FieldSchema = z.strictObject({ type: z.enum(['string', 'number', 'integer', 'boolean', 'array']), description: z.string().max(1000), enum: z.array(z.union([z.string(), z.number(), z.boolean()])).max(100).optional() });
18
+ export const ObjectSchemaDefinition = z.strictObject({ type: z.literal('object'), properties: z.record(PropertyIdentifier, FieldSchema), required: z.array(PropertyIdentifier).max(64), additionalProperties: z.literal(false) }).superRefine((schema, ctx) => {
19
+ for (const key of schema.required)
20
+ if (!Object.hasOwn(schema.properties, key))
21
+ ctx.addIssue({ code: 'custom', message: `Required property ${key} is not declared` });
22
+ });
23
+ const BindingSchema = z.discriminatedUnion('source', [
24
+ z.strictObject({ source: z.literal('input'), path: PropertyIdentifier }),
25
+ z.strictObject({ source: z.literal('node'), nodeId: Identifier, path: PropertyIdentifier }),
26
+ ]);
27
+ const NodeBase = { id: Identifier, dependsOn: z.array(Identifier).max(32) };
28
+ export const WorkflowNodeSchema = z.discriminatedUnion('type', [
29
+ z.strictObject({ ...NodeBase, type: z.literal('input') }),
30
+ z.strictObject({ ...NodeBase, type: z.literal('tool'), tool: Identifier, arguments: z.record(PropertyIdentifier, BindingSchema) }),
31
+ z.strictObject({ ...NodeBase, type: z.literal('condition'), field: BindingSchema, operator: z.enum(['equals', 'exists']), value: z.union([z.string(), z.boolean(), z.number()]).optional(), whenTrue: Identifier, whenFalse: Identifier }),
32
+ z.strictObject({ ...NodeBase, type: z.literal('output'), from: Identifier }),
33
+ ]);
34
+ /** Output schema ids double as the explicit executable agent kind. Enum parsing keeps stored manifest hashes unchanged. */
35
+ export const OutputSchemaIdSchema = z.enum(['agentex.token-research.v1', 'agentex.transaction-inspection.v1', 'agentex.watchtower-window.v1']);
36
+ /** input-address: reads bound to one input contract. input-transaction: reads bound to a proposed payload's sender, target and decoded grantees. input-watch-scope: log reads bound to declared rule contracts inside one bounded block window. */
37
+ const ToolSchema = z.strictObject({ id: Identifier, version: z.string().regex(/^\d+\.\d+\.\d+$/), capability: z.literal('evm.read'), chains: z.array(ChainKeySchema).min(1).max(3), addressScope: z.enum(['input-address', 'input-transaction', 'input-watch-scope']), maximumCalls: z.number().int().min(1).max(24) });
38
+ export const ResearchPolicySchema = z.strictObject({ readOwner: z.boolean(), readProxySlots: z.boolean() });
39
+ /**
40
+ * Token Researcher 0.3.0 market reads (P01): pools on listed venues by factory lookup, and balances of recent transfer recipients from an
41
+ * index. Absent: no market read, report section or hash change (0.1.0 and 0.2.0 manifests omit it). See token-market.ts.
42
+ */
43
+ export const MarketPolicySchema = z.strictObject({ discoverPools: z.boolean(), recentHolderActivity: z.boolean() });
44
+ /**
45
+ * Optional bounded AI-assisted summary for Token Researcher manifests: one model request over evidence the deterministic reads already
46
+ * captured. Absent: the manifest hash, run behaviour and report shape are unchanged. The cost cap is the quoted model maximum in micro-USD.
47
+ */
48
+ export const AnalysisPolicySchema = z.strictObject({ mode: z.literal('summary'), maximumOutputTokens: z.number().int().min(64).max(2000), maximumCostMicrousd: z.number().int().min(1).max(200000) });
49
+ export const ManifestPolicySchema = z.strictObject({ analysis: AnalysisPolicySchema });
50
+ export const AgentManifestSchema = z.strictObject({
51
+ schemaVersion: z.literal('1.0'), id: Identifier, version: z.string().regex(/^\d+\.\d+\.\d+$/), name: z.string().min(1).max(100), description: z.string().min(1).max(2000),
52
+ author: z.strictObject({ id: Identifier, name: z.string().min(1).max(100), ownership: z.enum(['first-party', 'independent']) }),
53
+ license: z.string().min(1).max(100), visibility: z.enum(['private', 'source-visible', 'source-private']),
54
+ chains: z.array(ChainKeySchema).min(1).max(3),
55
+ inputSchema: ObjectSchemaDefinition, outputSchemaId: OutputSchemaIdSchema,
56
+ tools: z.array(ToolSchema).min(1).max(16),
57
+ modelPolicy: z.strictObject({ mode: z.literal('deterministic'), maximumModelTokens: z.literal(0) }),
58
+ // Do not add a schema default here: absent policy must preserve immutable legacy hashes.
59
+ researchPolicy: ResearchPolicySchema.optional(),
60
+ // Do not add a schema default here either: absent policy must preserve immutable legacy hashes.
61
+ policy: ManifestPolicySchema.optional(),
62
+ // Token Researcher 0.3.0 market reads; absent on every earlier manifest (no default, hashes unchanged).
63
+ marketPolicy: MarketPolicySchema.optional(),
64
+ limits: z.strictObject({ maximumRpcCalls: z.number().int().min(1).max(24), maximumDurationMs: z.number().int().min(1000).max(120000), maximumOutputBytes: z.number().int().min(1024).max(262144), maximumGraphDepth: z.number().int().min(1).max(16), maximumPages: z.literal(0), maximumExecutionAtomic: AtomicAmountSchema }),
65
+ workflow: z.array(WorkflowNodeSchema).min(3).max(32),
66
+ testCases: z.array(Identifier).min(1).max(100),
67
+ }).superRefine((manifest, ctx) => {
68
+ const issue = (message) => ctx.addIssue({ code: 'custom', message });
69
+ const nodes = new Map(manifest.workflow.map((node) => [node.id, node]));
70
+ const tools = new Map(manifest.tools.map((tool) => [tool.id, tool]));
71
+ if (nodes.size !== manifest.workflow.length)
72
+ issue('Duplicate workflow node id');
73
+ if (tools.size !== manifest.tools.length)
74
+ issue('Duplicate tool id');
75
+ if (new Set(manifest.chains).size !== manifest.chains.length)
76
+ issue('Duplicate chain');
77
+ const expectedScope = manifest.outputSchemaId === 'agentex.token-research.v1' ? 'input-address' : manifest.outputSchemaId === 'agentex.watchtower-window.v1' ? 'input-watch-scope' : 'input-transaction';
78
+ if (manifest.tools.some((tool) => tool.addressScope !== expectedScope))
79
+ issue(`Tools for ${manifest.outputSchemaId} must use the ${expectedScope} read grant`);
80
+ if (manifest.researchPolicy !== undefined && manifest.outputSchemaId !== 'agentex.token-research.v1')
81
+ issue('researchPolicy applies only to Token Researcher manifests');
82
+ if (manifest.policy !== undefined && manifest.outputSchemaId !== 'agentex.token-research.v1')
83
+ issue('policy.analysis applies only to Token Researcher manifests');
84
+ if (manifest.marketPolicy !== undefined && manifest.outputSchemaId !== 'agentex.token-research.v1')
85
+ issue('marketPolicy applies only to Token Researcher manifests');
86
+ if (manifest.workflow.filter((node) => node.type === 'input').length !== 1)
87
+ issue('Workflow must have exactly one input node');
88
+ if (manifest.workflow.filter((node) => node.type === 'output').length !== 1)
89
+ issue('Workflow must have exactly one output node');
90
+ for (const tool of manifest.tools) {
91
+ if (!manifest.chains.every((chain) => tool.chains.includes(chain)))
92
+ issue(`Tool ${tool.id} cannot execute on every declared chain`);
93
+ if (tool.maximumCalls > manifest.limits.maximumRpcCalls)
94
+ issue(`Tool ${tool.id} exceeds the workflow call budget`);
95
+ }
96
+ for (const node of manifest.workflow) {
97
+ for (const dependency of node.dependsOn)
98
+ if (!nodes.has(dependency))
99
+ issue(`Node ${node.id} references missing dependency ${dependency}`);
100
+ if (node.type === 'input' && node.dependsOn.length)
101
+ issue('Input node cannot have dependencies');
102
+ if (node.type !== 'input' && !node.dependsOn.length)
103
+ issue(`Node ${node.id} is disconnected from input`);
104
+ if (node.type === 'tool' && !tools.has(node.tool))
105
+ issue(`Undeclared tool ${node.tool}`);
106
+ if (node.type === 'output' && !node.dependsOn.includes(node.from))
107
+ issue('Output source must be an explicit dependency');
108
+ if (node.type === 'condition') {
109
+ if (!nodes.has(node.whenTrue) || !nodes.has(node.whenFalse))
110
+ issue('Condition branch target does not exist');
111
+ if (node.operator === 'equals' && node.value === undefined)
112
+ issue('Equality condition needs a value');
113
+ for (const target of [node.whenTrue, node.whenFalse])
114
+ if (!nodes.get(target)?.dependsOn.includes(node.id))
115
+ issue('Condition targets must depend on the condition node');
116
+ }
117
+ const bindings = node.type === 'tool' ? Object.values(node.arguments) : node.type === 'condition' ? [node.field] : [];
118
+ for (const binding of bindings) {
119
+ if (binding.source === 'node' && !node.dependsOn.includes(binding.nodeId))
120
+ issue('Node binding must name an explicit dependency');
121
+ if (binding.source === 'input' && !Object.hasOwn(manifest.inputSchema.properties, binding.path))
122
+ issue(`Unknown input property ${binding.path}`);
123
+ }
124
+ }
125
+ const visiting = new Set();
126
+ const depths = new Map();
127
+ const visit = (id) => {
128
+ if (visiting.has(id)) {
129
+ issue('Workflow contains a cycle');
130
+ return manifest.limits.maximumGraphDepth + 1;
131
+ }
132
+ const cached = depths.get(id);
133
+ if (cached !== undefined)
134
+ return cached;
135
+ visiting.add(id);
136
+ const depth = 1 + Math.max(0, ...(nodes.get(id)?.dependsOn ?? []).map(visit));
137
+ visiting.delete(id);
138
+ depths.set(id, depth);
139
+ if (depth > manifest.limits.maximumGraphDepth)
140
+ issue('Workflow exceeds maximum graph depth');
141
+ return depth;
142
+ };
143
+ for (const id of nodes.keys())
144
+ visit(id);
145
+ const output = manifest.workflow.find((node) => node.type === 'output');
146
+ const used = new Set();
147
+ const ancestors = (id) => { if (used.has(id))
148
+ return; used.add(id); for (const dependency of nodes.get(id)?.dependsOn ?? [])
149
+ ancestors(dependency); };
150
+ if (output)
151
+ ancestors(output.id);
152
+ if (used.size !== nodes.size)
153
+ issue('Every node must contribute to the output');
154
+ });
155
+ export function canonicalJson(value) {
156
+ if (value === null || typeof value === 'string' || typeof value === 'boolean')
157
+ return JSON.stringify(value);
158
+ if (typeof value === 'number' && Number.isFinite(value))
159
+ return JSON.stringify(value);
160
+ if (Array.isArray(value))
161
+ return `[${value.map(canonicalJson).join(',')}]`;
162
+ if (typeof value === 'object' && Object.getPrototypeOf(value) === Object.prototype)
163
+ return `{${Object.entries(value).filter(([, entry]) => entry !== undefined).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`).join(',')}}`;
164
+ throw new TypeError('Only finite JSON data can be hashed');
165
+ }
166
+ export const contentHash = (value) => createHash('sha256').update(canonicalJson(value)).digest('hex');
167
+ function deepFreeze(value) { if (value !== null && typeof value === 'object') {
168
+ for (const child of Object.values(value))
169
+ deepFreeze(child);
170
+ Object.freeze(value);
171
+ } return value; }
172
+ export function publishManifest(input) { const manifest = AgentManifestSchema.parse(input); return deepFreeze({ manifest, hash: contentHash(manifest) }); }
173
+ export const TOKEN_RESEARCHER_MANIFEST = publishManifest({
174
+ schemaVersion: '1.0', id: 'token-researcher', version: '0.1.0', name: 'Token Researcher',
175
+ description: 'Read ERC-20 metadata, bytecode and standard EIP-1967 proxy slots at one verified EVM snapshot. Holder, pool and exhaustive authority indexes are not included.',
176
+ author: { id: 'agentex', name: 'AGENTEX', ownership: 'first-party' }, license: 'Proprietary', visibility: 'source-visible', chains: ['ethereum', 'base'],
177
+ inputSchema: { type: 'object', additionalProperties: false, properties: { chain: { type: 'string', description: 'Explicit analysis network', enum: ['ethereum', 'base'] }, address: { type: 'string', description: 'Token contract address' }, scope: { type: 'string', description: 'Bounded inspection scope', enum: ['metadata-and-controls'] }, snapshotBlock: { type: 'string', description: 'Optional finalized block number' } }, required: ['chain', 'address'] },
178
+ outputSchemaId: 'agentex.token-research.v1', tools: [{ id: 'evm.inspect-token', version: '0.1.0', capability: 'evm.read', chains: ['ethereum', 'base'], addressScope: 'input-address', maximumCalls: 16 }],
179
+ modelPolicy: { mode: 'deterministic', maximumModelTokens: 0 }, limits: { maximumRpcCalls: 16, maximumDurationMs: 60000, maximumOutputBytes: 131072, maximumGraphDepth: 4, maximumPages: 0, maximumExecutionAtomic: '0' },
180
+ workflow: [{ id: 'input', type: 'input', dependsOn: [] }, { id: 'inspect', type: 'tool', tool: 'evm.inspect-token', dependsOn: ['input'], arguments: { chain: { source: 'input', path: 'chain' }, address: { source: 'input', path: 'address' }, scope: { source: 'input', path: 'scope' }, snapshotBlock: { source: 'input', path: 'snapshotBlock' } } }, { id: 'report', type: 'output', dependsOn: ['inspect'], from: 'inspect' }],
181
+ testCases: ['standard-metadata', 'nonstandard-decimals', 'proxy-slots', 'no-contract', 'provider-failure', 'budget-stop'],
182
+ }).manifest;
183
+ /**
184
+ * Token Researcher 0.2.0: the same workflow, tools, limits and output schema as 0.1.0, with buyer-visible text for the instant-read rule
185
+ * (latest block, sequencer-confirmed). 0.1.0 above stays byte-identical (hash 5784e6b0…) for stored versions, evidence and replay;
186
+ * new drafts, the SDK scaffold and `GET /v1/templates` start from 0.2.0.
187
+ */
188
+ export const TOKEN_RESEARCHER_MANIFEST_V2 = publishManifest({
189
+ ...structuredClone(TOKEN_RESEARCHER_MANIFEST), version: '0.2.0',
190
+ description: 'Read ERC-20 metadata, bytecode and standard EIP-1967 proxy slots at the latest block (sequencer-confirmed), pinned for the whole run and rechecked at completion. Holder, pool and exhaustive authority indexes are not included.',
191
+ inputSchema: { ...structuredClone(TOKEN_RESEARCHER_MANIFEST.inputSchema), properties: { ...structuredClone(TOKEN_RESEARCHER_MANIFEST.inputSchema.properties), snapshotBlock: { type: 'string', description: 'Optional block number (defaults to the latest block)' } } },
192
+ }).manifest;
193
+ /**
194
+ * Token Researcher 0.3.0 (P01): the 0.2.0 graph, tool and output schema across Ethereum, Base and Robinhood Chain, plus `marketPolicy`: pools on listed
195
+ * venues found by factory lookup with observed pool token balances, and current balances of recent transfer recipients from a provider index.
196
+ * 0.1.0 and 0.2.0 stay byte-identical. At most 15 reads at the latest block (16 with an explicit snapshot).
197
+ */
198
+ export const TOKEN_RESEARCHER_MANIFEST_V3 = publishManifest({
199
+ ...structuredClone(TOKEN_RESEARCHER_MANIFEST_V2), version: '0.3.0',
200
+ description: 'Read ERC-20 metadata, bytecode, standard EIP-1967 proxy slots and owner() at the latest block (sequencer-confirmed), pinned for the whole run and rechecked at completion. It also finds pools on listed Uniswap-compatible venues by factory lookup, with the token balances each pool holds, and reads current balances of recent transfer recipients from a provider index. It is not a holder census, a total liquidity figure or a safety verdict.',
201
+ chains: ['ethereum', 'base', 'robinhood'],
202
+ inputSchema: { ...structuredClone(TOKEN_RESEARCHER_MANIFEST_V2.inputSchema), properties: { ...structuredClone(TOKEN_RESEARCHER_MANIFEST_V2.inputSchema.properties), chain: { type: 'string', description: 'Explicit analysis network', enum: ['ethereum', 'base', 'robinhood'] } } },
203
+ tools: [{ ...structuredClone(TOKEN_RESEARCHER_MANIFEST_V2.tools[0]), chains: ['ethereum', 'base', 'robinhood'] }],
204
+ // The market receipts (one transfer page, two multicalls) need the 256 KiB report budget the other first-party agents already declare.
205
+ limits: { ...structuredClone(TOKEN_RESEARCHER_MANIFEST_V2.limits), maximumOutputBytes: 262144 },
206
+ marketPolicy: { discoverPools: true, recentHolderActivity: true },
207
+ testCases: [...TOKEN_RESEARCHER_MANIFEST_V2.testCases, 'multiple-pools', 'no-pools', 'recent-recipients-not-holders', 'holder-index-unavailable'],
208
+ }).manifest;
209
+ /** True when `inputSchema` equals one template version's input declaration with the manifest's own chains selected. */
210
+ export function matchesTemplateInput(manifest, templates) {
211
+ const actual = contentHash(manifest.inputSchema);
212
+ return templates.some((template) => { const expected = structuredClone(template.inputSchema); expected.properties.chain.enum = manifest.chains; return contentHash(expected) === actual; });
213
+ }
214
+ const DEFAULT_RESEARCH_POLICY = Object.freeze({ readOwner: true, readProxySlots: true });
215
+ export function resolveResearchPolicy(manifest) {
216
+ return manifest.researchPolicy === undefined ? DEFAULT_RESEARCH_POLICY : Object.freeze(ResearchPolicySchema.parse(manifest.researchPolicy));
217
+ }
218
+ /** The validated AI-assisted summary policy, or null when the manifest declares none (or declares an invalid one). */
219
+ export function resolveAnalysisPolicy(manifest) {
220
+ const raw = manifest !== null && typeof manifest === 'object' ? manifest.policy?.analysis : undefined;
221
+ if (raw === undefined)
222
+ return null;
223
+ const parsed = AnalysisPolicySchema.safeParse(raw);
224
+ return parsed.success ? Object.freeze(parsed.data) : null;
225
+ }
226
+ /** Pure quote helper: the maximum model spend a run of this manifest may incur, in micro-USD. 0 when no AI-assisted summary is declared. */
227
+ export function analysisCostCap(manifest) {
228
+ return resolveAnalysisPolicy(manifest)?.maximumCostMicrousd ?? 0;
229
+ }
230
+ /** Executable manifest subset shared by publication review and the actual researcher. */
231
+ export const SupportedTokenResearcherManifestSchema = AgentManifestSchema.superRefine((manifest, ctx) => {
232
+ if (manifest.outputSchemaId !== 'agentex.token-research.v1')
233
+ ctx.addIssue({ code: 'custom', path: ['outputSchemaId'], message: 'Only agentex.token-research.v1 manifests are Token Researcher packages.' });
234
+ if (contentHash(manifest.workflow) !== contentHash(TOKEN_RESEARCHER_MANIFEST.workflow))
235
+ ctx.addIssue({ code: 'custom', path: ['workflow'], message: 'The executor supports only the bounded Token Researcher graph; select controls with researchPolicy.' });
236
+ if (!matchesTemplateInput(manifest, [TOKEN_RESEARCHER_MANIFEST, TOKEN_RESEARCHER_MANIFEST_V2]))
237
+ ctx.addIssue({ code: 'custom', path: ['inputSchema'], message: 'The input declaration must match the supported chain/address/scope/snapshot inputs.' });
238
+ const tool = manifest.tools[0];
239
+ if (manifest.tools.length !== 1 || tool?.id !== 'evm.inspect-token' || tool.version !== '0.1.0' || tool.capability !== 'evm.read' || tool.addressScope !== 'input-address')
240
+ ctx.addIssue({ code: 'custom', path: ['tools'], message: 'Only evm.inspect-token@0.1.0 with the read-only input-address grant is executable.' });
241
+ });
242
+ /** Nominal fresh-run RPC attempts. Provider failures may stop early; retry receipts stay charged. */
243
+ export function getTokenResearcherRpcPlan(raw) {
244
+ const manifest = SupportedTokenResearcherManifestSchema.parse(raw);
245
+ const policy = resolveResearchPolicy(manifest);
246
+ const market = manifest.marketPolicy;
247
+ // Market reads: one discovery multicall, one indexed transfer page, and one balances multicall when either section is on.
248
+ const marketCalls = market ? (market.discoverPools ? 1 : 0) + (market.recentHolderActivity ? 1 : 0) + (market.discoverPools || market.recentHolderActivity ? 1 : 0) : 0;
249
+ const latestSnapshotCalls = 8 + (policy.readOwner ? 1 : 0) + (policy.readProxySlots ? 3 : 0) + marketCalls;
250
+ return Object.freeze({ policy, marketPolicy: market ?? null, latestSnapshotCalls, explicitSnapshotCalls: latestSnapshotCalls + 1, maximumRpcCalls: Math.min(16, manifest.limits.maximumRpcCalls, ...manifest.tools.map((tool) => tool.maximumCalls)) });
251
+ }
252
+ export const FailureCategorySchema = z.enum(['provider-unavailable', 'history-unavailable', 'rate-limited', 'timeout', 'cancelled', 'unsupported', 'contract-reverted', 'invalid-response', 'chain-mismatch', 'snapshot-changed', 'limit-exceeded', 'unconfigured', 'no-contract']);
253
+ export const ResearchErrorSchema = z.strictObject({ category: FailureCategorySchema, message: z.string().max(1000), step: z.string().max(100), evidenceId: z.string().optional() });
254
+ /** Shared EVM evidence envelope, bound to exactly one executor tool version. */
255
+ export const evmEvidenceItemSchema = (tool) => z.strictObject({
256
+ id: z.string(), tool: z.literal(tool), source: z.strictObject({ kind: z.literal('rpc'), provider: z.string(), method: z.string(), documentationUrl: z.url() }),
257
+ chain: ChainKeySchema, chainId: z.number().int().positive(), request: z.array(z.json()),
258
+ snapshot: z.strictObject({ blockNumber: AtomicAmountSchema, blockHash: z.string().regex(/^0x[a-fA-F0-9]{64}$/) }).nullable(),
259
+ // `sequencer-confirmed`: a state read at the pinned latest block (instant-read rule). `finalized` parses receipts captured before the rule.
260
+ observedAt: z.iso.datetime(), finality: z.enum(['finalized', 'sequencer-confirmed', 'unverified']), canonicality: z.enum(['verified-at-completion', 'unchecked', 'changed']),
261
+ completeness: z.enum(['complete-for-request', 'error']), result: z.json(), contentHash: Hash,
262
+ privacy: z.literal('public-chain-data'),
263
+ });
264
+ export const EvidenceItemSchema = evmEvidenceItemSchema('evm.inspect-token@0.1.0');
265
+ export const ObservationSchema = z.discriminatedUnion('status', [
266
+ z.strictObject({ status: z.literal('observed'), value: z.union([z.string(), z.number(), z.boolean()]), evidenceIds: z.array(z.string()).min(1), note: z.string().optional() }),
267
+ z.strictObject({ status: z.enum(['unknown', 'error', 'not-detected', 'not-applicable', 'unsupported']), reason: z.string(), evidenceIds: z.array(z.string()) }),
268
+ z.strictObject({ status: z.literal('skipped'), reason: z.string().min(1).max(1000), evidenceIds: z.array(z.string()).length(0) }),
269
+ ]);
270
+ export const ANALYSIS_LABEL = 'AI-assisted summary';
271
+ /** Model-written section, present only when the manifest declares policy.analysis. Deterministic observations are never part of it. */
272
+ export const ResearchAnalysisSchema = z.strictObject({
273
+ label: z.literal(ANALYSIS_LABEL), status: z.enum(['completed', 'unavailable', 'skipped']), model: z.string().min(1).max(64),
274
+ findings: z.array(z.strictObject({ claim: z.string().min(1).max(600), evidenceIds: z.array(z.string().min(1).max(64)).min(1).max(8), confidence: z.enum(['supported', 'uncertain']) })).max(12),
275
+ unavailableReason: z.string().min(1).max(500).optional(),
276
+ });
277
+ /**
278
+ * Metered summary usage. `costStatus: 'unresolved'` means an earlier attempt sent the request but its usage was never recorded: `costMicrousd`
279
+ * is then the reserved worst case (an upper bound for settlement), not metered spend, and token counts are unknown (0). Absent: metered.
280
+ */
281
+ export const ModelUsageSchema = z.strictObject({ model: z.string().min(1).max(64), inputTokens: z.number().int().nonnegative(), outputTokens: z.number().int().nonnegative(), costMicrousd: z.number().int().nonnegative(), costStatus: z.literal('unresolved').optional() });
282
+ const MarketAddress = z.string().regex(/^0x[0-9a-fA-F]{40}$/);
283
+ const MarketStatusSchema = z.enum(['observed', 'none-found', 'skipped', 'unsupported', 'not-read', 'error']);
284
+ const MarketVenueFields = { venue: z.string().regex(/^[a-z0-9-]{1,32}$/), protocol: z.enum(['uniswap-v2', 'uniswap-v3', 'aerodrome']), factory: MarketAddress, quoteToken: MarketAddress,
285
+ quoteSymbol: z.string().regex(/^[A-Z0-9]{1,12}$/), quoteDecimals: z.number().int().min(0).max(255), feeTier: z.number().int().min(1).max(1_000_000).nullable(), stable: z.boolean().nullable() };
286
+ const NullableAtomic = AtomicAmountSchema.nullable();
287
+ /**
288
+ * Token Researcher 0.3.0 market section. Every value is derived from captured receipts by `buildMarketSection` (token-market.ts). Pools are the
289
+ * listed venues' factory lookups only; recent recipients are an index page of recent transfers, never a holder list. No totals, prices or scores.
290
+ */
291
+ export const TokenMarketSchema = z.strictObject({
292
+ pools: z.strictObject({
293
+ status: MarketStatusSchema, reason: z.string().min(1).max(1000),
294
+ venuesChecked: z.array(z.strictObject({ ...MarketVenueFields, lookup: z.enum(['pool', 'no-pool', 'call-failed']), pool: MarketAddress.nullable() })).max(32),
295
+ pools: z.array(z.strictObject({ ...MarketVenueFields, address: MarketAddress, token0: MarketAddress, tokenBalanceAtomic: NullableAtomic, quoteBalanceAtomic: NullableAtomic, reserve0Atomic: NullableAtomic, reserve1Atomic: NullableAtomic,
296
+ sqrtPriceX96: NullableAtomic, tick: z.number().int().min(-887272).max(887272).nullable(), activeLiquidity: NullableAtomic, evidenceIds: z.array(z.string()).min(1).max(2) })).max(8),
297
+ poolsFound: z.number().int().min(0).max(32), truncated: z.boolean(), evidenceIds: z.array(z.string()).max(1),
298
+ }),
299
+ recentHolderActivity: z.strictObject({
300
+ status: MarketStatusSchema, reason: z.string().min(1).max(1000), basis: z.literal('recent-transfer-recipients-not-a-holder-list'), transfersExamined: z.number().int().min(0).max(25), truncated: z.boolean(),
301
+ accounts: z.array(z.strictObject({ address: MarketAddress, latestReceiptBlock: AtomicAmountSchema, balanceAtomic: NullableAtomic, isDiscoveredPool: z.boolean(), evidenceIds: z.array(z.string()).min(1).max(2) })).max(10),
302
+ evidenceIds: z.array(z.string()).max(1),
303
+ }),
304
+ limitations: z.array(z.string().min(1).max(1000)).max(8),
305
+ });
306
+ export const TokenResearchReportSchema = z.strictObject({
307
+ schemaVersion: z.literal('agentex.token-research.v1'), agentVersion: z.string().regex(/^\d+\.\d+\.\d+$/), manifestHash: Hash,
308
+ researchPolicy: ResearchPolicySchema.optional(),
309
+ // 0.3.0 reports only: the resolved market policy and its section are present together.
310
+ marketPolicy: MarketPolicySchema.optional(), market: TokenMarketSchema.optional(),
311
+ input: ResearchInputSchema, status: z.enum(['complete', 'partial', 'failed']), generatedAt: z.iso.datetime(),
312
+ chainId: z.number().int().positive().nullable(), snapshot: z.strictObject({ blockNumber: AtomicAmountSchema, blockHash: z.string(), timestamp: AtomicAmountSchema, finality: z.enum(['finalized', 'sequencer-confirmed']), canonicality: z.enum(['verified-at-completion', 'unchecked', 'changed']) }).nullable(),
313
+ metadata: z.strictObject({ name: ObservationSchema, symbol: ObservationSchema, decimals: ObservationSchema, totalSupplyAtomic: ObservationSchema, totalSupplyFormatted: ObservationSchema }),
314
+ contract: z.strictObject({ codePresent: ObservationSchema, codeHash: ObservationSchema, sourceVerification: ObservationSchema }),
315
+ controls: z.strictObject({ implementation: ObservationSchema, admin: ObservationSchema, beacon: ObservationSchema, owner: ObservationSchema, mintAuthority: ObservationSchema, freezeAuthority: ObservationSchema, upgradeability: ObservationSchema }),
316
+ coverage: z.strictObject({ holders: ObservationSchema, liquidity: ObservationSchema, deployment: ObservationSchema, historicalActivity: ObservationSchema }),
317
+ findings: z.array(z.strictObject({ code: z.string(), message: z.string(), evidenceIds: z.array(z.string()) })),
318
+ limitations: z.array(z.string()), errors: z.array(ResearchErrorSchema), evidence: z.array(EvidenceItemSchema),
319
+ analysis: ResearchAnalysisSchema.optional(),
320
+ // modelTokens stays 0: it describes the deterministic executor. Metered AI-assisted summary usage is reported in usage.model.
321
+ 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'), model: ModelUsageSchema.optional() }),
322
+ }).superRefine((report, ctx) => {
323
+ if ((report.analysis === undefined) !== (report.usage.model === undefined))
324
+ ctx.addIssue({ code: 'custom', path: ['analysis'], message: 'An AI-assisted summary section and usage.model must be reported together.' });
325
+ if (report.analysis) {
326
+ const completed = report.analysis.status === 'completed';
327
+ if (completed === (report.analysis.unavailableReason !== undefined))
328
+ ctx.addIssue({ code: 'custom', path: ['analysis', 'unavailableReason'], message: 'Only a summary that did not complete states an unavailable or skipped reason.' });
329
+ if (!completed && report.analysis.findings.length)
330
+ ctx.addIssue({ code: 'custom', path: ['analysis', 'findings'], message: 'Only a completed AI-assisted summary can contain findings.' });
331
+ if (report.analysis.status === 'skipped' && report.usage.model && (report.usage.model.inputTokens || report.usage.model.outputTokens || report.usage.model.costMicrousd))
332
+ ctx.addIssue({ code: 'custom', path: ['usage', 'model'], message: 'A skipped AI-assisted summary records no model usage.' });
333
+ if (report.usage.model?.costStatus === 'unresolved' && (report.analysis.status !== 'unavailable' || report.analysis.findings.length || report.usage.model.inputTokens || report.usage.model.outputTokens || report.usage.model.costMicrousd < 1))
334
+ ctx.addIssue({ code: 'custom', path: ['usage', 'model', 'costStatus'], message: 'An unresolved model cost belongs to an unavailable summary without findings and states its reserved maximum, never zero.' });
335
+ const evidenceIds = new Set(report.evidence.map((item) => item.id));
336
+ for (const finding of report.analysis.findings)
337
+ for (const id of finding.evidenceIds)
338
+ if (!evidenceIds.has(id))
339
+ ctx.addIssue({ code: 'custom', path: ['analysis', 'findings'], message: `AI-assisted finding cites unknown evidence ${id}` });
340
+ }
341
+ const policy = resolveResearchPolicy(report);
342
+ const selectable = { owner: policy.readOwner, implementation: policy.readProxySlots, admin: policy.readProxySlots, beacon: policy.readProxySlots };
343
+ for (const [control, enabled] of Object.entries(selectable)) {
344
+ const observation = report.controls[control];
345
+ if (enabled === (observation.status === 'skipped'))
346
+ ctx.addIssue({ code: 'custom', path: ['controls', control], message: 'Skipped control status must match the resolved immutable research policy.' });
347
+ }
348
+ const requiredObservations = [...Object.values(report.metadata), ...Object.values(report.contract), ...Object.values(report.coverage), report.controls.mintAuthority, report.controls.freezeAuthority, report.controls.upgradeability];
349
+ if (requiredObservations.some((item) => item.status === 'skipped'))
350
+ ctx.addIssue({ code: 'custom', message: 'Only the selectable owner and EIP-1967 slot controls can be intentionally skipped.' });
351
+ if (!policy.readProxySlots && report.controls.upgradeability.status !== 'unknown')
352
+ ctx.addIssue({ code: 'custom', path: ['controls', 'upgradeability'], message: 'Upgradeability remains unknown when proxy inspection is disabled.' });
353
+ if (!policy.readProxySlots && (report.evidence.some((item) => item.source.method === 'eth_getStorageAt') || report.findings.some((item) => item.code.startsWith('eip1967-'))))
354
+ ctx.addIssue({ code: 'custom', message: 'Disabled proxy checks cannot have RPC receipts or proxy-slot findings.' });
355
+ if (!policy.readOwner && report.evidence.some((item) => item.source.method === 'eth_call' && item.request[0] !== null && typeof item.request[0] === 'object' && !Array.isArray(item.request[0]) && typeof item.request[0].data === 'string' && /^0x8da5cb5b/i.test(item.request[0].data)))
356
+ ctx.addIssue({ code: 'custom', message: 'A disabled owner check cannot have an owner() RPC receipt.' });
357
+ const ids = new Set(report.evidence.map((item) => item.id));
358
+ if (ids.size !== report.evidence.length)
359
+ ctx.addIssue({ code: 'custom', message: 'Duplicate evidence id' });
360
+ if ((report.market === undefined) !== (report.marketPolicy === undefined))
361
+ ctx.addIssue({ code: 'custom', path: ['market'], message: 'A market section and its market policy must be reported together.' });
362
+ if (report.market && report.marketPolicy) {
363
+ if ((report.market.pools.status === 'skipped') === report.marketPolicy.discoverPools)
364
+ ctx.addIssue({ code: 'custom', path: ['market', 'pools'], message: 'Skipped pool discovery must match the market policy.' });
365
+ if ((report.market.recentHolderActivity.status === 'skipped') === report.marketPolicy.recentHolderActivity)
366
+ ctx.addIssue({ code: 'custom', path: ['market', 'recentHolderActivity'], message: 'Skipped holder activity must match the market policy.' });
367
+ }
368
+ const marketReferences = report.market ? [report.market.pools, ...report.market.pools.pools, report.market.recentHolderActivity, ...report.market.recentHolderActivity.accounts] : [];
369
+ const references = [...Object.values(report.metadata), ...Object.values(report.contract), ...Object.values(report.controls), ...Object.values(report.coverage), ...report.findings, ...marketReferences];
370
+ for (const item of references)
371
+ for (const id of item.evidenceIds)
372
+ if (!ids.has(id))
373
+ ctx.addIssue({ code: 'custom', message: `Unknown evidence reference ${id}` });
374
+ for (const error of report.errors)
375
+ if (error.evidenceId && !ids.has(error.evidenceId))
376
+ ctx.addIssue({ code: 'custom', message: `Unknown error evidence reference ${error.evidenceId}` });
377
+ for (const item of report.evidence)
378
+ if (contentHash(item.result) !== item.contentHash)
379
+ ctx.addIssue({ code: 'custom', message: 'Evidence content hash does not match its captured result' });
380
+ });