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,350 @@
|
|
|
1
|
+
import { decodeAbiParameters, decodeFunctionData, encodeAbiParameters, encodeFunctionResult, parseAbiParameters, keccak256, formatUnits } from 'viem';
|
|
2
|
+
import { contentHash, ResearchInputSchema, SupportedTokenResearcherManifestSchema, TokenResearchReportSchema, resolveResearchPolicy } from '../../contracts/src/index.js';
|
|
3
|
+
import { buildMarketSection, discoveryPlan, HOLDER_ACTIVITY_METHOD, MARKET_SELECTORS, marketCallInScope, marketTransferRequestInScope, marketVenuesFor, MULTICALL3_ABI, MULTICALL3_ADDRESS } from '../../contracts/src/token-market.js';
|
|
4
|
+
import { researchToken, EIP1967_SLOTS } from '../../research/src/index.js';
|
|
5
|
+
import { validateReceipt } from '../../runtime/src/checkpoints.js';
|
|
6
|
+
export const EVALUATION_SUITE_VERSION = 'token-researcher-local-fixtures-v1';
|
|
7
|
+
const CASES = ['standard-metadata', 'nonstandard-decimals', 'proxy-owner-gap', 'no-contract', 'provider-failure', 'chain-mismatch', 'snapshot-changed'];
|
|
8
|
+
const FIXED_TIME = '2026-01-01T00:00:00.000Z';
|
|
9
|
+
/**
|
|
10
|
+
* Robinhood Chain fixtures answer chain id 4663 (mainnet), or 46630 when this server is configured as a Robinhood testnet host. The fixture transport never
|
|
11
|
+
* leaves the process; the Robinhood identity settings below only satisfy the executor's configuration check with the documented values.
|
|
12
|
+
*/
|
|
13
|
+
const robinhoodEvaluationChainId = () => (process.env.ROBINHOOD_CHAIN_ID === '46630' ? 46630 : 4663);
|
|
14
|
+
const evaluationChainId = (chain) => chain === 'ethereum' ? '0x1' : chain === 'base' ? '0x2105' : `0x${robinhoodEvaluationChainId().toString(16)}`;
|
|
15
|
+
const evaluationEnv = () => ({ ETHEREUM_RPC_URL: 'https://evaluation.invalid', BASE_RPC_URL: 'https://evaluation.invalid', ROBINHOOD_RPC_URL: 'https://evaluation.invalid',
|
|
16
|
+
ROBINHOOD_CHAIN_ID: String(robinhoodEvaluationChainId()), ROBINHOOD_DOCUMENTATION_URL: 'https://docs.robinhood.com/chain/connecting/', ROBINHOOD_VERIFIED_AT: FIXED_TIME });
|
|
17
|
+
const ADDRESS = `0x${'1'.repeat(40)}`;
|
|
18
|
+
const BLOCK_HASH = `0x${'a'.repeat(64)}`;
|
|
19
|
+
const IMPLEMENTATION = `0x${'2'.repeat(40)}`;
|
|
20
|
+
const LARGE_SUPPLY = '90071992547409931234567890';
|
|
21
|
+
const descriptor = { version: EVALUATION_SUITE_VERSION, cases: CASES, fixedTime: FIXED_TIME, address: ADDRESS, blockHash: BLOCK_HASH,
|
|
22
|
+
implementation: IMPLEMENTATION, largeSupply: LARGE_SUPPLY, blockNumber: '100', baseDecimals: 6, nonstandardDecimals: 255,
|
|
23
|
+
expectedContracts: 'canonical-observed-metadata-v1;exact-supply;policy-skips;fail-closed-errors;rpc-ceilings;receipt-scope-v1' };
|
|
24
|
+
export const EVALUATION_SUITE_HASH = contentHash(descriptor);
|
|
25
|
+
/**
|
|
26
|
+
* Token Researcher 0.3.0 (market policy) suite. v1 above is unchanged, so every earlier evaluation keeps its hash. A version with `marketPolicy`
|
|
27
|
+
* runs the v1 cases plus market cases, and the report's market section must equal its derivation from the captured receipts.
|
|
28
|
+
*/
|
|
29
|
+
export const MARKET_EVALUATION_SUITE_VERSION = 'token-researcher-local-fixtures-v2';
|
|
30
|
+
const MARKET_CASES = ['multiple-pools', 'no-pools', 'recent-recipients-not-holders', 'holder-index-unavailable'];
|
|
31
|
+
const POOL_A = `0x${'4'.repeat(40)}`;
|
|
32
|
+
const POOL_B = `0x${'5'.repeat(40)}`;
|
|
33
|
+
const RECIPIENT = `0x${'6'.repeat(40)}`;
|
|
34
|
+
export const MARKET_EVALUATION_SUITE_HASH = contentHash({ ...descriptor, version: MARKET_EVALUATION_SUITE_VERSION, cases: [...CASES, ...MARKET_CASES], poolA: POOL_A, poolB: POOL_B, recipient: RECIPIENT,
|
|
35
|
+
expectedContracts: `${descriptor.expectedContracts};market-derived-from-receipts;listed-venues-only;recipients-not-holders;index-failure-partial;rpc-ceiling-16` });
|
|
36
|
+
/** Suite version and hash for a Token Researcher manifest: v2 when it declares a market policy, otherwise the unchanged v1. */
|
|
37
|
+
export const tokenResearchSuiteFor = (manifest) => manifest.marketPolicy !== undefined
|
|
38
|
+
? { version: MARKET_EVALUATION_SUITE_VERSION, hash: MARKET_EVALUATION_SUITE_HASH } : { version: EVALUATION_SUITE_VERSION, hash: EVALUATION_SUITE_HASH };
|
|
39
|
+
const encode = (type, value) => encodeAbiParameters(parseAbiParameters(type), [value]);
|
|
40
|
+
const object = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
41
|
+
/** This invariant is shared by local review and fixtures; callers still must verify the stored run/checkpoint binding. */
|
|
42
|
+
export function canonicalMetadataObserved(report) {
|
|
43
|
+
try {
|
|
44
|
+
TokenResearchReportSchema.parse(report);
|
|
45
|
+
for (const receipt of report.evidence)
|
|
46
|
+
validateReceipt(receipt, report.input);
|
|
47
|
+
return checkedCanonicalMetadata(report);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function checkedCanonicalMetadata(report) {
|
|
54
|
+
// New runs pin the latest block (`sequencer-confirmed`); creator tests captured before the instant-read rule keep `finalized`.
|
|
55
|
+
if (report.status === 'failed' || report.snapshot?.canonicality !== 'verified-at-completion' || (report.snapshot.finality !== 'sequencer-confirmed' && report.snapshot.finality !== 'finalized'))
|
|
56
|
+
return false;
|
|
57
|
+
if (report.input.snapshotBlock !== undefined && report.input.snapshotBlock !== report.snapshot.blockNumber)
|
|
58
|
+
return false;
|
|
59
|
+
const identity = report.evidence.findIndex(item => item.source.method === 'eth_chainId' && item.completeness === 'complete-for-request'
|
|
60
|
+
&& typeof item.result === 'string' && /^0x[0-9a-fA-F]+$/.test(item.result) && BigInt(item.result) === BigInt(report.chainId));
|
|
61
|
+
if (identity < 0)
|
|
62
|
+
return false;
|
|
63
|
+
const firstState = report.evidence.findIndex(item => ['eth_getCode', 'eth_call', 'eth_getStorageAt'].includes(item.source.method));
|
|
64
|
+
const head = report.evidence.some((item, index) => index > identity && index < firstState && item.source.method === 'eth_getBlockByNumber'
|
|
65
|
+
&& (item.request[0] === 'latest' || item.request[0] === 'finalized') && item.completeness === 'complete-for-request' && object(item.result) && typeof item.result.number === 'string'
|
|
66
|
+
&& /^0x[0-9a-fA-F]+$/.test(item.result.number) && BigInt(item.result.number) >= BigInt(report.snapshot.blockNumber));
|
|
67
|
+
const selected = report.evidence.some((item, index) => index < firstState && item.source.method === 'eth_getBlockByNumber'
|
|
68
|
+
&& item.completeness === 'complete-for-request' && object(item.result) && typeof item.result.number === 'string' && /^0x[0-9a-fA-F]+$/.test(item.result.number)
|
|
69
|
+
&& BigInt(item.result.number).toString() === report.snapshot.blockNumber && typeof item.result.hash === 'string'
|
|
70
|
+
&& item.result.hash.toLowerCase() === report.snapshot.blockHash.toLowerCase() && typeof item.result.timestamp === 'string'
|
|
71
|
+
&& /^0x[0-9a-fA-F]+$/.test(item.result.timestamp) && BigInt(item.result.timestamp).toString() === report.snapshot.timestamp);
|
|
72
|
+
if (!head || !selected)
|
|
73
|
+
return false;
|
|
74
|
+
const observations = [[report.metadata.name, '0x06fdde03'], [report.metadata.symbol, '0x95d89b41'], [report.metadata.decimals, '0x313ce567'],
|
|
75
|
+
[report.metadata.totalSupplyAtomic, '0x18160ddd'], [report.contract.codePresent, null], [report.contract.codeHash, null]];
|
|
76
|
+
if (report.contract.codePresent.status !== 'observed' || report.contract.codePresent.value !== true)
|
|
77
|
+
return false;
|
|
78
|
+
let last = -1;
|
|
79
|
+
const good = observations.every(([observation, selector]) => observation.status === 'observed' && observation.evidenceIds.length > 0 && observation.evidenceIds.every(id => {
|
|
80
|
+
const index = report.evidence.findIndex(receipt => receipt.id === id);
|
|
81
|
+
const item = report.evidence[index];
|
|
82
|
+
if (!item || item.chainId !== report.chainId || item.completeness !== 'complete-for-request' || item.snapshot?.blockNumber !== report.snapshot.blockNumber
|
|
83
|
+
|| item.snapshot.blockHash.toLowerCase() !== report.snapshot.blockHash.toLowerCase())
|
|
84
|
+
return false;
|
|
85
|
+
if (selector === null ? item.source.method !== 'eth_getCode' || typeof item.result !== 'string' || item.result === '0x'
|
|
86
|
+
: item.source.method !== 'eth_call' || !object(item.request[0]) || item.request[0].data !== selector)
|
|
87
|
+
return false;
|
|
88
|
+
if (typeof item.result !== 'string' || !/^0x(?:[a-fA-F0-9]{2})*$/.test(item.result))
|
|
89
|
+
return false;
|
|
90
|
+
const bytes = item.result;
|
|
91
|
+
const decoded = selector === '0x06fdde03' || selector === '0x95d89b41' ? decodeAbiParameters(parseAbiParameters('string'), bytes)[0]
|
|
92
|
+
: selector === '0x313ce567' ? decodeAbiParameters(parseAbiParameters('uint8'), bytes)[0]
|
|
93
|
+
: selector === '0x18160ddd' ? decodeAbiParameters(parseAbiParameters('uint256'), bytes)[0].toString()
|
|
94
|
+
: observation === report.contract.codePresent ? true : keccak256(bytes);
|
|
95
|
+
if (observation.value !== decoded)
|
|
96
|
+
return false;
|
|
97
|
+
if ((selector === '0x06fdde03' || selector === '0x95d89b41') && (typeof decoded !== 'string' || Buffer.byteLength(decoded, 'utf8') > 256
|
|
98
|
+
|| Array.from(decoded).some(character => character.charCodeAt(0) === 127 || (character.charCodeAt(0) < 32 && ![9, 10, 13].includes(character.charCodeAt(0))))))
|
|
99
|
+
return false;
|
|
100
|
+
last = Math.max(last, index);
|
|
101
|
+
return true;
|
|
102
|
+
}));
|
|
103
|
+
if (!good || report.metadata.decimals.status !== 'observed' || report.metadata.totalSupplyAtomic.status !== 'observed'
|
|
104
|
+
|| report.metadata.totalSupplyFormatted.status !== 'observed' || typeof report.metadata.decimals.value !== 'number'
|
|
105
|
+
|| !Number.isInteger(report.metadata.decimals.value) || report.metadata.decimals.value < 0 || report.metadata.decimals.value > 255
|
|
106
|
+
|| typeof report.metadata.totalSupplyAtomic.value !== 'string' || typeof report.metadata.totalSupplyFormatted.value !== 'string')
|
|
107
|
+
return false;
|
|
108
|
+
const formatIds = [...report.metadata.decimals.evidenceIds, ...report.metadata.totalSupplyAtomic.evidenceIds].sort();
|
|
109
|
+
if (contentHash([...report.metadata.totalSupplyFormatted.evidenceIds].sort()) !== contentHash(formatIds)
|
|
110
|
+
|| report.metadata.totalSupplyFormatted.value !== formatUnits(BigInt(report.metadata.totalSupplyAtomic.value), report.metadata.decimals.value))
|
|
111
|
+
return false;
|
|
112
|
+
for (const [index, item] of report.evidence.entries())
|
|
113
|
+
if (['eth_getCode', 'eth_call', 'eth_getStorageAt'].includes(item.source.method)) {
|
|
114
|
+
if (item.chainId !== report.chainId || item.snapshot?.blockNumber !== report.snapshot.blockNumber || item.snapshot.blockHash.toLowerCase() !== report.snapshot.blockHash.toLowerCase()
|
|
115
|
+
|| item.canonicality !== 'verified-at-completion')
|
|
116
|
+
return false;
|
|
117
|
+
last = Math.max(last, index);
|
|
118
|
+
}
|
|
119
|
+
return report.evidence.some((item, index) => index > last && item.chainId === report.chainId && item.source.method === 'eth_getBlockByNumber'
|
|
120
|
+
&& item.request[0] === `0x${BigInt(report.snapshot.blockNumber).toString(16)}` && item.completeness === 'complete-for-request'
|
|
121
|
+
&& object(item.result) && typeof item.result.hash === 'string' && item.result.hash.toLowerCase() === report.snapshot.blockHash.toLowerCase()
|
|
122
|
+
&& typeof item.result.number === 'string' && /^0x[0-9a-fA-F]+$/.test(item.result.number) && BigInt(item.result.number).toString() === report.snapshot.blockNumber);
|
|
123
|
+
}
|
|
124
|
+
export function validateReportBinding(raw, manifest, input) {
|
|
125
|
+
const report = TokenResearchReportSchema.parse(raw);
|
|
126
|
+
if (report.manifestHash !== contentHash(manifest) || report.agentVersion !== manifest.version || contentHash(report.input) !== contentHash(input))
|
|
127
|
+
throw new Error('Report identity differs from its immutable version or input.');
|
|
128
|
+
const policy = resolveResearchPolicy(manifest);
|
|
129
|
+
if (contentHash(resolveResearchPolicy(report)) !== contentHash(policy))
|
|
130
|
+
throw new Error('Report research policy differs from its immutable manifest.');
|
|
131
|
+
for (const receipt of report.evidence)
|
|
132
|
+
validateReceipt(receipt, input);
|
|
133
|
+
const selectors = ['0x06fdde03', '0x95d89b41', '0x313ce567', '0x18160ddd', ...(policy.readOwner ? ['0x8da5cb5b'] : [])];
|
|
134
|
+
const market = manifest.marketPolicy;
|
|
135
|
+
if (!market && (report.market !== undefined || report.marketPolicy !== undefined))
|
|
136
|
+
throw new Error('A report without a market policy cannot carry a market section.');
|
|
137
|
+
if (market && (!report.market || contentHash(report.marketPolicy ?? null) !== contentHash(market)))
|
|
138
|
+
throw new Error('Report market policy differs from its immutable manifest.');
|
|
139
|
+
for (const receipt of report.evidence) {
|
|
140
|
+
const marketRead = Boolean(market?.discoverPools || market?.recentHolderActivity) && receipt.source.method === 'eth_call' && report.chainId !== null && marketCallInScope(receipt.request[0], input.address, report.chainId);
|
|
141
|
+
if (receipt.source.method === 'eth_call' && !marketRead && (!object(receipt.request[0]) || typeof receipt.request[0].data !== 'string' || !selectors.includes(receipt.request[0].data)))
|
|
142
|
+
throw new Error('Report contains an unselected contract read.');
|
|
143
|
+
if (receipt.source.method === HOLDER_ACTIVITY_METHOD && (!market?.recentHolderActivity || !receipt.snapshot || !marketTransferRequestInScope(receipt.request, input.address, receipt.snapshot.blockNumber)))
|
|
144
|
+
throw new Error('Report contains an unselected index read.');
|
|
145
|
+
if (receipt.source.method === 'eth_getStorageAt' && (!policy.readProxySlots || !Object.values(EIP1967_SLOTS).includes(receipt.request[1])))
|
|
146
|
+
throw new Error('Report contains an unselected proxy slot read.');
|
|
147
|
+
}
|
|
148
|
+
if (market && report.market) {
|
|
149
|
+
// The market section must be exactly its derivation from this report's receipts. An unsupported section carries no receipt of its kind.
|
|
150
|
+
const unsupported = { pools: report.market.pools.status === 'unsupported' ? report.market.pools.reason : null, holders: report.market.recentHolderActivity.status === 'unsupported' ? report.market.recentHolderActivity.reason : null };
|
|
151
|
+
const multicalls = report.evidence.some((item) => item.source.method === 'eth_call' && object(item.request[0]) && String(item.request[0].to).toLowerCase() === MULTICALL3_ADDRESS.toLowerCase());
|
|
152
|
+
if ((unsupported.pools && marketVenuesFor(report.chainId) && multicalls && !unsupported.holders) || (unsupported.holders && report.evidence.some((item) => item.source.method === HOLDER_ACTIVITY_METHOD)))
|
|
153
|
+
throw new Error('An unsupported market section cannot carry receipts of that read.');
|
|
154
|
+
const derived = buildMarketSection({ policy: market, chainId: report.chainId, token: input.address, snapshotBlock: report.snapshot?.blockNumber ?? null, evidence: report.evidence, unsupported });
|
|
155
|
+
if (contentHash(derived) !== contentHash(report.market))
|
|
156
|
+
throw new Error('The market section differs from its derivation from the captured receipts.');
|
|
157
|
+
}
|
|
158
|
+
if (report.status !== 'failed') {
|
|
159
|
+
for (const [name, enabled, method, parameter] of [['owner', policy.readOwner, 'eth_call', '0x8da5cb5b'],
|
|
160
|
+
...Object.entries(EIP1967_SLOTS).map(([name, slot]) => [name, policy.readProxySlots, 'eth_getStorageAt', slot])]) {
|
|
161
|
+
const control = report.controls[name];
|
|
162
|
+
if (!enabled) {
|
|
163
|
+
if (control.status !== 'skipped' || control.evidenceIds.length)
|
|
164
|
+
throw new Error('Disabled research controls must remain explicitly skipped.');
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (control.status === 'skipped' || control.evidenceIds.length === 0)
|
|
168
|
+
throw new Error('A selected control has no captured read evidence.');
|
|
169
|
+
for (const reference of control.evidenceIds) {
|
|
170
|
+
const receipt = report.evidence.find(item => item.id === reference);
|
|
171
|
+
if (!receipt || receipt.source.method !== method || (method === 'eth_call' ? !object(receipt.request[0]) || receipt.request[0].data !== parameter : receipt.request[1] !== parameter))
|
|
172
|
+
throw new Error('Control observation references another operation.');
|
|
173
|
+
if (control.status === 'observed' || control.status === 'not-detected') {
|
|
174
|
+
if (receipt.completeness !== 'complete-for-request' || typeof receipt.result !== 'string' || !/^0x0{24}[a-fA-F0-9]{40}$/.test(receipt.result))
|
|
175
|
+
throw new Error('Control observation has invalid captured address bytes.');
|
|
176
|
+
const captured = `0x${receipt.result.slice(26)}`.toLowerCase();
|
|
177
|
+
if (control.status === 'observed' ? typeof control.value !== 'string' || control.value.toLowerCase() !== captured : method !== 'eth_getStorageAt' || captured !== `0x${'0'.repeat(40)}`)
|
|
178
|
+
throw new Error('Control observation differs from captured address bytes.');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (report.usage.rpcCalls > manifest.limits.maximumRpcCalls || report.usage.maximumRpcCalls !== Math.min(16, manifest.limits.maximumRpcCalls, ...manifest.tools.map(tool => tool.maximumCalls)))
|
|
184
|
+
throw new Error('Report usage differs from its immutable budget.');
|
|
185
|
+
return report;
|
|
186
|
+
}
|
|
187
|
+
/** Synthetic answers for the market reads in the v2 suite: pools on the first listed venues, bounded balances and one transfer page. */
|
|
188
|
+
function marketFixture(kind, chainId, method, params) {
|
|
189
|
+
if (method === HOLDER_ACTIVITY_METHOD) {
|
|
190
|
+
if (kind === 'holder-index-unavailable')
|
|
191
|
+
throw Object.assign(new Error('Synthetic index outage.'), { status: 503 });
|
|
192
|
+
const transfer = (to, block) => ({ blockNum: block, from: ADDRESS, to, rawContract: { address: ADDRESS, value: '0x1', decimal: '0x6' } });
|
|
193
|
+
return { transfers: kind === 'recent-recipients-not-holders' ? [transfer(POOL_A, '0x64'), transfer(RECIPIENT, '0x63'), transfer(RECIPIENT, '0x62')] : [] };
|
|
194
|
+
}
|
|
195
|
+
const request = params[0];
|
|
196
|
+
const { args } = decodeFunctionData({ abi: MULTICALL3_ABI, data: request.data });
|
|
197
|
+
const set = marketVenuesFor(chainId);
|
|
198
|
+
const plan = set ? discoveryPlan(set, ADDRESS) : [];
|
|
199
|
+
const pools = kind === 'multiple-pools' ? 2 : kind === 'recent-recipients-not-holders' ? 1 : 0;
|
|
200
|
+
const poolFor = (index) => {
|
|
201
|
+
const entry = plan[index];
|
|
202
|
+
if (!entry || !set)
|
|
203
|
+
return `0x${'0'.repeat(40)}`;
|
|
204
|
+
const first = plan.findIndex(item => item.venue === set.venues[0].id && item.quoteToken === set.quotes[0].address);
|
|
205
|
+
const second = plan.findIndex(item => item.venue === set.venues[1].id && item.quoteToken === set.quotes[0].address);
|
|
206
|
+
return pools >= 1 && index === first ? POOL_A : pools >= 2 && index === second ? POOL_B : `0x${'0'.repeat(40)}`;
|
|
207
|
+
};
|
|
208
|
+
const results = args[0].map((call) => {
|
|
209
|
+
const planned = plan.findIndex(item => item.callData === call.callData && item.factory.toLowerCase() === call.target.toLowerCase());
|
|
210
|
+
if (planned >= 0)
|
|
211
|
+
return { success: true, returnData: encode('address', poolFor(planned)) };
|
|
212
|
+
if (call.callData.startsWith(MARKET_SELECTORS.balanceOf))
|
|
213
|
+
return { success: true, returnData: encode('uint256', call.target.toLowerCase() === ADDRESS ? (call.callData.toLowerCase().endsWith(RECIPIENT.slice(2)) ? 42n : 1000000n) : 2000000000000000000n) };
|
|
214
|
+
if (call.callData === MARKET_SELECTORS.getReserves)
|
|
215
|
+
return { success: true, returnData: encodeAbiParameters(parseAbiParameters('uint112, uint112, uint32'), [1000000n, 2000000000000000000n, 1]) };
|
|
216
|
+
if (call.callData === MARKET_SELECTORS.slot0)
|
|
217
|
+
return { success: true, returnData: encodeAbiParameters(parseAbiParameters('uint160, int24, uint16, uint16, uint16, uint8, bool'), [79228162514264337593543950336n, 0, 1, 1, 1, 0, true]) };
|
|
218
|
+
if (call.callData === MARKET_SELECTORS.liquidity)
|
|
219
|
+
return { success: true, returnData: encode('uint128', 5000n) };
|
|
220
|
+
return { success: false, returnData: '0x' };
|
|
221
|
+
});
|
|
222
|
+
return encodeFunctionResult({ abi: MULTICALL3_ABI, functionName: 'aggregate3', result: results });
|
|
223
|
+
}
|
|
224
|
+
function marketExpectations(kind, report) {
|
|
225
|
+
const issues = [];
|
|
226
|
+
const market = report.market;
|
|
227
|
+
if (!market)
|
|
228
|
+
return ['A market-policy version must report its market section.'];
|
|
229
|
+
const pools = market.pools;
|
|
230
|
+
const holders = market.recentHolderActivity;
|
|
231
|
+
const venues = marketVenuesFor(report.chainId);
|
|
232
|
+
if (JSON.stringify(market).match(/\b(is|looks|appears)\s+safe\b|holder distribution:|concentration (is|of|:)\s*[0-9]|total liquidity (is|of|:)\s*[0-9]/i))
|
|
233
|
+
issues.push('The market section must not state a safety verdict, distribution, concentration or total liquidity.');
|
|
234
|
+
if (report.coverage.holders.status === 'observed' || report.coverage.liquidity.status === 'observed')
|
|
235
|
+
issues.push('Holder census and total liquidity must stay unknown.');
|
|
236
|
+
if (kind === 'multiple-pools' && venues && (pools.status !== 'observed' || pools.poolsFound !== 2 || pools.pools.some(pool => pool.tokenBalanceAtomic === null || pool.quoteBalanceAtomic === null)))
|
|
237
|
+
issues.push('Two listed-venue pools with observed balances were expected.');
|
|
238
|
+
if (kind === 'no-pools' && venues && (pools.status !== 'none-found' || !/not proof of no liquidity/.test(pools.reason)))
|
|
239
|
+
issues.push('No pools must be reported as none found on the listed venues, not as no liquidity.');
|
|
240
|
+
if (!venues && pools.status !== 'unsupported')
|
|
241
|
+
issues.push('A chain without listed venues must report pools as unsupported.');
|
|
242
|
+
if (kind === 'recent-recipients-not-holders' && (holders.status !== 'observed' || holders.basis !== 'recent-transfer-recipients-not-a-holder-list' || holders.accounts.length !== 2 || (venues && !holders.accounts[0]?.isDiscoveredPool) || holders.accounts[1]?.balanceAtomic !== '42'))
|
|
243
|
+
issues.push('Recent recipients must be listed once each, with a pool recipient flagged and exact balances.');
|
|
244
|
+
if (kind === 'holder-index-unavailable' && (holders.status !== 'error' || report.status === 'complete' || !holders.evidenceIds.length))
|
|
245
|
+
issues.push('An index failure must be an evidenced error and a partial report, never an empty list.');
|
|
246
|
+
return issues;
|
|
247
|
+
}
|
|
248
|
+
/** No creator-supplied fixture, executor, URL, time, or expected output is accepted. */
|
|
249
|
+
export async function evaluateManifest(manifest) {
|
|
250
|
+
const suite = tokenResearchSuiteFor(manifest);
|
|
251
|
+
const snapshot = { kind: 'deterministic_fixture_evaluation', visibility: 'local_only', suiteVersion: suite.version,
|
|
252
|
+
suiteHash: suite.hash, manifestHash: contentHash(manifest), status: 'failed', summary: { passed: 0, total: 0, issues: [] }, fixtures: [],
|
|
253
|
+
limitations: ['Platform-selected synthetic RPC responses only; this is not a live chain investigation.', 'No model execution or model evaluation is included.',
|
|
254
|
+
'Passing fixtures do not prove security, market suitability, complete holder/control coverage, or production publication readiness.'] };
|
|
255
|
+
const supported = SupportedTokenResearcherManifestSchema.safeParse(manifest);
|
|
256
|
+
if (!supported.success)
|
|
257
|
+
snapshot.summary.issues.push(...supported.error.issues.slice(0, 20).map(issue => issue.message.slice(0, 300)));
|
|
258
|
+
if (manifest.chains.some(chain => !['ethereum', 'base', 'robinhood'].includes(chain)))
|
|
259
|
+
snapshot.summary.issues.push('Only Ethereum, Base and Robinhood Chain have platform evaluation coverage.');
|
|
260
|
+
if (snapshot.summary.issues.length)
|
|
261
|
+
return snapshot;
|
|
262
|
+
const policy = resolveResearchPolicy(manifest);
|
|
263
|
+
const cases = manifest.marketPolicy ? [...CASES, ...MARKET_CASES] : CASES;
|
|
264
|
+
for (const chain of manifest.chains)
|
|
265
|
+
for (const kind of cases) {
|
|
266
|
+
let calls = 0;
|
|
267
|
+
const chainId = evaluationChainId(chain);
|
|
268
|
+
const EVALUATION_ENV = evaluationEnv();
|
|
269
|
+
const input = ResearchInputSchema.parse({ chain, address: ADDRESS });
|
|
270
|
+
const transport = { async request(method, params) {
|
|
271
|
+
calls++;
|
|
272
|
+
if (kind === 'provider-failure')
|
|
273
|
+
throw new Error('Synthetic provider outage.');
|
|
274
|
+
if (manifest.marketPolicy && (method === HOLDER_ACTIVITY_METHOD || (method === 'eth_call' && object(params[0]) && String(params[0].to).toLowerCase() === MULTICALL3_ADDRESS.toLowerCase())))
|
|
275
|
+
return marketFixture(kind, Number(chainId), method, params);
|
|
276
|
+
if (method === 'eth_chainId')
|
|
277
|
+
return kind === 'chain-mismatch' ? '0x7a69' : chainId;
|
|
278
|
+
if (method === 'eth_getBlockByNumber')
|
|
279
|
+
return { number: '0x64', timestamp: '0x65', hash: kind === 'snapshot-changed' && params[0] !== 'latest' ? `0x${'b'.repeat(64)}` : BLOCK_HASH };
|
|
280
|
+
if (method === 'eth_getCode')
|
|
281
|
+
return kind === 'no-contract' ? '0x' : '0x60806040';
|
|
282
|
+
if (method === 'eth_getStorageAt')
|
|
283
|
+
return kind === 'proxy-owner-gap' && params[1] === EIP1967_SLOTS.implementation ? `0x${'0'.repeat(24)}${IMPLEMENTATION.slice(2)}` : `0x${'0'.repeat(64)}`;
|
|
284
|
+
if (method === 'eth_call' && object(params[0])) {
|
|
285
|
+
if (params[0].data === '0x06fdde03')
|
|
286
|
+
return encode('string', 'Platform evaluation fixture');
|
|
287
|
+
if (params[0].data === '0x95d89b41')
|
|
288
|
+
return encode('string', 'FIX');
|
|
289
|
+
if (params[0].data === '0x313ce567')
|
|
290
|
+
return encode('uint256', kind === 'nonstandard-decimals' ? 255n : 6n);
|
|
291
|
+
if (params[0].data === '0x18160ddd')
|
|
292
|
+
return encode('uint256', BigInt(LARGE_SUPPLY));
|
|
293
|
+
if (params[0].data === '0x8da5cb5b') {
|
|
294
|
+
if (kind === 'proxy-owner-gap')
|
|
295
|
+
throw Object.assign(new Error('execution reverted'), { code: 3 });
|
|
296
|
+
return encode('address', ADDRESS);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
throw new Error('Unexpected platform fixture request.');
|
|
300
|
+
} };
|
|
301
|
+
let report = null;
|
|
302
|
+
const issues = [];
|
|
303
|
+
try {
|
|
304
|
+
report = validateReportBinding(await researchToken(input, { manifest, transport, now: () => new Date(FIXED_TIME),
|
|
305
|
+
env: EVALUATION_ENV }), manifest, input);
|
|
306
|
+
if (calls !== report.usage.rpcCalls || calls > manifest.limits.maximumRpcCalls || calls > manifest.tools[0].maximumCalls)
|
|
307
|
+
issues.push('RPC ceiling or captured usage invariant failed.');
|
|
308
|
+
const failure = { 'no-contract': 'no-contract', 'provider-failure': 'provider-unavailable', 'chain-mismatch': 'chain-mismatch', 'snapshot-changed': 'snapshot-changed' }[kind];
|
|
309
|
+
if (failure) {
|
|
310
|
+
if (report.status !== 'failed' || !report.errors.some(error => error.category === failure))
|
|
311
|
+
issues.push(`Expected fail-closed ${failure}.`);
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
if (!canonicalMetadataObserved(report))
|
|
315
|
+
issues.push('Required canonical metadata and code observations are missing.');
|
|
316
|
+
if (report.metadata.totalSupplyAtomic.status !== 'observed' || report.metadata.totalSupplyAtomic.value !== LARGE_SUPPLY)
|
|
317
|
+
issues.push('Atomic supply lost exact precision.');
|
|
318
|
+
if (report.metadata.name.status !== 'observed' || report.metadata.name.value !== 'Platform evaluation fixture'
|
|
319
|
+
|| report.metadata.symbol.status !== 'observed' || report.metadata.symbol.value !== 'FIX')
|
|
320
|
+
issues.push('Decoded token metadata differs from the platform fixture.');
|
|
321
|
+
const expectedSupply = kind === 'nonstandard-decimals' ? `0.${'0'.repeat(255 - LARGE_SUPPLY.length)}${LARGE_SUPPLY}`.replace(/0+$/, '') : '90071992547409931234.56789';
|
|
322
|
+
if (report.metadata.totalSupplyFormatted.status !== 'observed' || report.metadata.totalSupplyFormatted.value !== expectedSupply)
|
|
323
|
+
issues.push('Formatted supply differs from exact atomic conversion.');
|
|
324
|
+
if (report.contract.codeHash.status !== 'observed' || report.contract.codeHash.value !== keccak256('0x60806040'))
|
|
325
|
+
issues.push('Bytecode hash differs from the captured bytes.');
|
|
326
|
+
if (report.metadata.decimals.status !== 'observed' || report.metadata.decimals.value !== (kind === 'nonstandard-decimals' ? 255 : 6))
|
|
327
|
+
issues.push('Decimals do not match the platform fixture.');
|
|
328
|
+
const expectedStatus = (kind === 'proxy-owner-gap' && policy.readOwner) || (kind === 'holder-index-unavailable' && manifest.marketPolicy?.recentHolderActivity) ? 'partial' : 'complete';
|
|
329
|
+
if (report.status !== expectedStatus)
|
|
330
|
+
issues.push(`Expected ${expectedStatus} under the immutable read policy.`);
|
|
331
|
+
if (manifest.marketPolicy)
|
|
332
|
+
issues.push(...marketExpectations(kind, report));
|
|
333
|
+
if (!policy.readOwner && report.controls.owner.status !== 'skipped')
|
|
334
|
+
issues.push('Owner policy omission must be explicit.');
|
|
335
|
+
if (!policy.readProxySlots && ['implementation', 'admin', 'beacon'].some(key => report.controls[key].status !== 'skipped'))
|
|
336
|
+
issues.push('Proxy policy omissions must be explicit.');
|
|
337
|
+
if (kind === 'proxy-owner-gap' && policy.readProxySlots && (report.controls.implementation.status !== 'observed' || String(report.controls.implementation.value).toLowerCase() !== IMPLEMENTATION))
|
|
338
|
+
issues.push('Proxy fixture was not observed.');
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
issues.push('Executor or output validation failed under the immutable manifest limits and contract.');
|
|
343
|
+
}
|
|
344
|
+
snapshot.fixtures.push({ id: kind, chain, passed: issues.length === 0, issues, report, reportHash: report ? contentHash(report) : null, transportCalls: calls });
|
|
345
|
+
}
|
|
346
|
+
snapshot.summary.total = snapshot.fixtures.length;
|
|
347
|
+
snapshot.summary.passed = snapshot.fixtures.filter(result => result.passed).length;
|
|
348
|
+
snapshot.status = snapshot.summary.total > 0 && snapshot.summary.passed === snapshot.summary.total ? 'passed' : 'failed';
|
|
349
|
+
return snapshot;
|
|
350
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { type Hex } from 'viem';
|
|
2
|
+
import { type AttemptCompletion } from '../../providers/src/health.js';
|
|
3
|
+
import { type ReadConfirmation } from '../../providers/src/snapshot.js';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { type AgentManifest, type ChainKey, type EvidenceItem, type FailureCategory, type TokenResearchReport } from '../../contracts/src/index.js';
|
|
6
|
+
import { type SummaryFact } from '../../model/src/summary.js';
|
|
7
|
+
import type { TariffTable } from '../../model/src/pricing.js';
|
|
8
|
+
import { type ModelClient, type ModelUsageEntry } from '../../model/src/types.js';
|
|
9
|
+
export declare const EIP1967_SLOTS: {
|
|
10
|
+
readonly implementation: "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
|
|
11
|
+
readonly admin: "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103";
|
|
12
|
+
readonly beacon: "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50";
|
|
13
|
+
};
|
|
14
|
+
type Environment = Record<string, string | undefined>;
|
|
15
|
+
export interface RpcTransport {
|
|
16
|
+
request(method: string, params: readonly unknown[], signal: AbortSignal): Promise<unknown>;
|
|
17
|
+
/** The endpoint that answered the most recent successful request, when the transport knows it (evidence attribution). */
|
|
18
|
+
servedBy?(): {
|
|
19
|
+
id: string;
|
|
20
|
+
host: string;
|
|
21
|
+
} | null;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Evidence provider label for an RPC read: the configured label for the configured endpoint, and the actual provider when another one answered
|
|
25
|
+
* (the keyed Alchemy archive endpoint, the chain's official RPC or a public fallback), so a report never credits one provider with another's data.
|
|
26
|
+
*/
|
|
27
|
+
export declare function servedProviderLabel(chain: string, configured: string, served: {
|
|
28
|
+
id: string;
|
|
29
|
+
host: string;
|
|
30
|
+
} | null | undefined): string;
|
|
31
|
+
export interface ResearchOptions {
|
|
32
|
+
env?: Environment;
|
|
33
|
+
manifest?: AgentManifest;
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
onProgress?: (event: {
|
|
36
|
+
step: string;
|
|
37
|
+
message: string;
|
|
38
|
+
}) => void | Promise<void>;
|
|
39
|
+
/** Durable workers must atomically reserve this read against the run-wide budget and fencing token. */
|
|
40
|
+
/** Hidden provider steps (`provider-<purpose>`) may return a completion that settles their permit from the observed outcome. */
|
|
41
|
+
beforeRpc?: (operation: {
|
|
42
|
+
method: string;
|
|
43
|
+
step: string;
|
|
44
|
+
maximumRpcCalls: number;
|
|
45
|
+
}) => void | AttemptCompletion | Promise<void | AttemptCompletion>;
|
|
46
|
+
onEvidence?: (evidence: EvidenceItem) => void | Promise<void>;
|
|
47
|
+
/** Receipts loaded by the runtime after checking owner, version, input, fencing and envelope hashes. `analysis` holds checkpointed summary results. */
|
|
48
|
+
restore?: {
|
|
49
|
+
manifestHash: string;
|
|
50
|
+
inputHash: string;
|
|
51
|
+
evidence: EvidenceItem[];
|
|
52
|
+
analysis?: {
|
|
53
|
+
nodeId: string;
|
|
54
|
+
stored: unknown;
|
|
55
|
+
}[];
|
|
56
|
+
};
|
|
57
|
+
/** Server-only transport injection for fixtures. Never accepted in a buyer input. */
|
|
58
|
+
transport?: RpcTransport;
|
|
59
|
+
/** Server-only clock for deterministic evaluation timestamps. Hard deadlines use real time. */
|
|
60
|
+
now?: () => Date;
|
|
61
|
+
/**
|
|
62
|
+
* E05 check for the 0.3.0 market reads (coverage rows evm.dex-pool-discovery and evm.recent-holder-activity). Defaults to the coverage table with this
|
|
63
|
+
* run's env; with an injected fixture transport and no explicit check, both reads are admitted. A refusal marks that section unsupported, never empty.
|
|
64
|
+
*/
|
|
65
|
+
marketCoverage?: (chain: string, tool: string) => void;
|
|
66
|
+
/** Model client for the AI-assisted summary (refusal fallbacks off). Undefined: server configuration (AGENTEX_MODEL_ENABLED=true plus a key). null: disabled, reported unavailable without a request. */
|
|
67
|
+
modelClient?: ModelClient | null;
|
|
68
|
+
model?: string;
|
|
69
|
+
modelTariffs?: TariffTable;
|
|
70
|
+
/** Durable runtime permit for the one summary request. A throw coded RUN_MODEL_BUDGET_EXHAUSTED reports the summary unavailable; any other throw propagates. */
|
|
71
|
+
beforeModelRequest?: (operation: ResearchModelPermit) => void | Promise<void>;
|
|
72
|
+
/** Durable checkpoint of the summary outcome and metered usage, written once a request was sent. A throw propagates and the outcome is not used. */
|
|
73
|
+
onAnalysisResult?: (entry: ResearchAnalysisCheckpoint) => void | Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
/** Model checkpoint node id for the Token Researcher AI-assisted summary. */
|
|
76
|
+
export declare const RESEARCH_ANALYSIS_NODE_ID = "token-research-summary";
|
|
77
|
+
export interface ResearchModelPermit {
|
|
78
|
+
nodeId: string;
|
|
79
|
+
step: number;
|
|
80
|
+
maxOutputTokens: number;
|
|
81
|
+
worstCaseMicrousd: number;
|
|
82
|
+
model: string;
|
|
83
|
+
}
|
|
84
|
+
declare const StoredSummarySchema: z.ZodObject<{
|
|
85
|
+
status: z.ZodEnum<{
|
|
86
|
+
completed: "completed";
|
|
87
|
+
unavailable: "unavailable";
|
|
88
|
+
}>;
|
|
89
|
+
stopReason: z.ZodString;
|
|
90
|
+
reason: z.ZodNullable<z.ZodString>;
|
|
91
|
+
withheld: z.ZodNumber;
|
|
92
|
+
findings: z.ZodArray<z.ZodObject<{
|
|
93
|
+
claim: z.ZodString;
|
|
94
|
+
evidenceKeys: z.ZodArray<z.ZodString>;
|
|
95
|
+
confidence: z.ZodEnum<{
|
|
96
|
+
supported: "supported";
|
|
97
|
+
uncertain: "uncertain";
|
|
98
|
+
}>;
|
|
99
|
+
}, z.core.$strict>>;
|
|
100
|
+
usage: z.ZodObject<{
|
|
101
|
+
model: z.ZodString;
|
|
102
|
+
inputTokens: z.ZodNumber;
|
|
103
|
+
outputTokens: z.ZodNumber;
|
|
104
|
+
costMicrousd: z.ZodNumber;
|
|
105
|
+
costStatus: z.ZodOptional<z.ZodLiteral<"unresolved">>;
|
|
106
|
+
}, z.core.$strict>;
|
|
107
|
+
}, z.core.$strict>;
|
|
108
|
+
export type StoredResearchSummary = z.infer<typeof StoredSummarySchema>;
|
|
109
|
+
export interface ResearchAnalysisCheckpoint {
|
|
110
|
+
nodeId: string;
|
|
111
|
+
checkpointKey: string;
|
|
112
|
+
value: StoredResearchSummary;
|
|
113
|
+
/** Current-run evidence ids cited by the stored findings (informational; restore maps stable evidence keys). */
|
|
114
|
+
evidenceIds: string[];
|
|
115
|
+
usage: {
|
|
116
|
+
modelTokens: number;
|
|
117
|
+
costMicrousd: number;
|
|
118
|
+
};
|
|
119
|
+
modelUsage: ModelUsageEntry[];
|
|
120
|
+
stopReason: string;
|
|
121
|
+
modelServed: string;
|
|
122
|
+
}
|
|
123
|
+
declare class ResearchFailure extends Error {
|
|
124
|
+
readonly category: FailureCategory;
|
|
125
|
+
constructor(category: FailureCategory, message: string);
|
|
126
|
+
}
|
|
127
|
+
declare const ERROR_MESSAGES: Record<FailureCategory, string>;
|
|
128
|
+
declare function categoryOf(error: unknown, signal: AbortSignal): FailureCategory;
|
|
129
|
+
declare function chainConfiguration(chain: ChainKey, env: Environment): {
|
|
130
|
+
chain: "ethereum" | "base" | "robinhood";
|
|
131
|
+
chainId: number;
|
|
132
|
+
rpcUrl: string | undefined;
|
|
133
|
+
configured: boolean;
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* E05 enforcement for token research. The executor's reads are covered by these registry tools; every quote, acceptance,
|
|
137
|
+
* run, recording and listing admission refuses a chain unless all of them pass `assertToolCoverage`. This check applies
|
|
138
|
+
* even when a fixture executor is injected, because coverage is a property of the chain, not of the transport.
|
|
139
|
+
*/
|
|
140
|
+
export declare const RESEARCH_COVERAGE_TOOLS: readonly string[];
|
|
141
|
+
export { coverageSummary } from '../../providers/src/coverage.js';
|
|
142
|
+
export declare function researchCoverageDenial(chain: string, options?: {
|
|
143
|
+
env?: Environment;
|
|
144
|
+
tools?: readonly string[];
|
|
145
|
+
}): string | null;
|
|
146
|
+
/** True once the contracts owner's report schema accepts this confirmation level. Admission flips automatically when it does. */
|
|
147
|
+
export declare function researchReportAccepts(confirmation: ReadConfirmation): boolean;
|
|
148
|
+
export declare const researchCoveredChains: (chains: readonly string[], env?: Environment) => string[];
|
|
149
|
+
export declare function getResearchCoverage(env?: Environment): {
|
|
150
|
+
chain: "ethereum" | "base" | "robinhood";
|
|
151
|
+
chainId: number | null;
|
|
152
|
+
configured: boolean;
|
|
153
|
+
covered: boolean;
|
|
154
|
+
coverageDenial: string | null;
|
|
155
|
+
enabled: boolean;
|
|
156
|
+
verification: string;
|
|
157
|
+
capabilities: string[];
|
|
158
|
+
limitations: string[];
|
|
159
|
+
}[];
|
|
160
|
+
declare function makeTransport(url: string): RpcTransport;
|
|
161
|
+
declare function quantity(value: unknown): bigint;
|
|
162
|
+
declare function hexData(value: unknown): Hex;
|
|
163
|
+
declare function blockData(value: unknown): {
|
|
164
|
+
blockNumber: string;
|
|
165
|
+
blockHash: string;
|
|
166
|
+
timestamp: string;
|
|
167
|
+
};
|
|
168
|
+
/** Shared read-only EVM primitives for other first-party executors (for example the transaction inspector). Behaviour is unchanged. */
|
|
169
|
+
export { categoryOf as categorizeRpcFailure, ERROR_MESSAGES as FAILURE_MESSAGES, chainConfiguration as resolveChainConfiguration, makeTransport as createRpcTransport, ResearchFailure as EvmReadFailure, quantity as parseQuantity, hexData as parseHexData, blockData as parseBlockHeader };
|
|
170
|
+
/**
|
|
171
|
+
* Stable across recovery attempts (which renumber rpc-N ids): the read's method, request and pinned snapshot. Header re-reads are keyed by
|
|
172
|
+
* request only: the same block-number request is recorded before the snapshot is pinned during recovery and after it at completion.
|
|
173
|
+
*/
|
|
174
|
+
export declare const summaryEvidenceKey: (item: EvidenceItem) => string;
|
|
175
|
+
type KeyedFact = SummaryFact & {
|
|
176
|
+
key: string;
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* Citable facts for the summary: the pinned state reads and the snapshot header re-read at completion, each with the decoded
|
|
180
|
+
* observations that cite it. Volatile reads (chain identity, the moving latest head) are excluded so a recovery attempt rebuilds
|
|
181
|
+
* identical facts from its checkpointed receipts. Every string here is untrusted chain or provider data.
|
|
182
|
+
*/
|
|
183
|
+
export declare function summaryFacts(report: TokenResearchReport): {
|
|
184
|
+
facts: KeyedFact[];
|
|
185
|
+
context: Record<string, unknown>;
|
|
186
|
+
};
|
|
187
|
+
/** Purely read-only bounded inspection. Metadata is untrusted data and never instructions. */
|
|
188
|
+
export declare function researchToken(rawInput: unknown, options?: ResearchOptions): Promise<TokenResearchReport>;
|