otto-intel-mcp 0.1.2 → 0.1.4

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 (239) hide show
  1. package/README.md +76 -18
  2. package/dist/account-userop-policy-definition.d.ts +14 -0
  3. package/dist/account-userop-policy-definition.js +12 -0
  4. package/dist/account-userop-policy-relation.d.ts +371 -0
  5. package/dist/account-userop-policy-relation.js +87 -0
  6. package/dist/adapter/cdp-signer.d.ts +76 -9
  7. package/dist/adapter/cdp-signer.js +274 -33
  8. package/dist/adapter/evm-call-failure.d.ts +29 -0
  9. package/dist/adapter/evm-call-failure.js +109 -0
  10. package/dist/adapter/refusal.d.ts +1 -1
  11. package/dist/adapter/refusal.js +13 -0
  12. package/dist/adapter/route-simulator.d.ts +91 -0
  13. package/dist/adapter/route-simulator.js +143 -0
  14. package/dist/adapter-cdp-index.d.ts +1 -1
  15. package/dist/executable-equities.d.ts +55 -0
  16. package/dist/executable-equities.js +116 -0
  17. package/dist/executable-yield-markets.d.ts +261 -0
  18. package/dist/executable-yield-markets.js +154 -0
  19. package/dist/execution-account-gas-quote.d.ts +141 -0
  20. package/dist/execution-account-gas-quote.js +443 -0
  21. package/dist/execution-account-gas-review.d.ts +123 -0
  22. package/dist/execution-account-gas-review.js +104 -0
  23. package/dist/execution-account-gas-runtime.d.ts +33 -0
  24. package/dist/execution-account-gas-runtime.js +131 -0
  25. package/dist/execution-account-gas-view.d.ts +936 -0
  26. package/dist/execution-account-gas-view.js +56 -0
  27. package/dist/execution-config.d.ts +3 -0
  28. package/dist/execution-config.js +4 -0
  29. package/dist/execution-delegated-definition.d.ts +7 -6
  30. package/dist/execution-delegated-definition.js +13 -2
  31. package/dist/execution-delegation-admin-definition.d.ts +127 -5
  32. package/dist/execution-delegation-admin-definition.js +155 -5
  33. package/dist/execution-delegation-admin.d.ts +48 -2
  34. package/dist/execution-delegation-admin.js +422 -12
  35. package/dist/execution-delegation-cap-store.d.ts +386 -0
  36. package/dist/execution-delegation-cap-store.js +1150 -0
  37. package/dist/execution-delegation-exit-definition.d.ts +309 -0
  38. package/dist/execution-delegation-exit-definition.js +204 -0
  39. package/dist/execution-delegation-exit-store.d.ts +114 -0
  40. package/dist/execution-delegation-exit-store.js +671 -0
  41. package/dist/execution-delegation-exit.d.ts +4 -0
  42. package/dist/execution-delegation-exit.js +614 -0
  43. package/dist/execution-delegation-finality.d.ts +22 -0
  44. package/dist/execution-delegation-finality.js +90 -0
  45. package/dist/execution-delegation-operation-store.d.ts +129 -0
  46. package/dist/execution-delegation-operation-store.js +1107 -0
  47. package/dist/execution-delegation-operations-definition.d.ts +282 -0
  48. package/dist/execution-delegation-operations-definition.js +117 -0
  49. package/dist/execution-delegation-operations.d.ts +6 -0
  50. package/dist/execution-delegation-operations.js +282 -0
  51. package/dist/execution-delegation-policy.d.ts +154 -8
  52. package/dist/execution-delegation-policy.js +273 -40
  53. package/dist/execution-delegation.d.ts +51 -0
  54. package/dist/execution-delegation.js +441 -31
  55. package/dist/execution-errors.d.ts +1 -1
  56. package/dist/execution-errors.js +122 -0
  57. package/dist/execution-index.d.ts +7 -2
  58. package/dist/execution-index.js +7 -2
  59. package/dist/execution-journal-schema.d.ts +96 -0
  60. package/dist/execution-journal-schema.js +368 -0
  61. package/dist/execution-journal-tables.d.ts +14 -0
  62. package/dist/execution-journal-tables.js +227 -0
  63. package/dist/execution-policy-compatibility.d.ts +34 -0
  64. package/dist/execution-policy-compatibility.js +76 -0
  65. package/dist/execution-registration.d.ts +54 -2
  66. package/dist/execution-registration.js +151 -5
  67. package/dist/execution-sponsored-account-profile.d.ts +32 -0
  68. package/dist/execution-sponsored-account-profile.js +59 -0
  69. package/dist/execution-sponsored-account.d.ts +1519 -0
  70. package/dist/execution-sponsored-account.js +222 -0
  71. package/dist/execution-sponsored-definition.d.ts +1713 -0
  72. package/dist/execution-sponsored-definition.js +185 -0
  73. package/dist/execution-sponsored-gas-policy.d.ts +27 -0
  74. package/dist/execution-sponsored-gas-policy.js +110 -0
  75. package/dist/execution-sponsored-hosted.d.ts +3 -0
  76. package/dist/execution-sponsored-hosted.js +71 -0
  77. package/dist/execution-sponsored-index.d.ts +1322 -0
  78. package/dist/execution-sponsored-index.js +102 -0
  79. package/dist/execution-sponsored-mcp-definition.d.ts +1662 -0
  80. package/dist/execution-sponsored-mcp-definition.js +94 -0
  81. package/dist/execution-sponsored-receipt.d.ts +47 -0
  82. package/dist/execution-sponsored-receipt.js +74 -0
  83. package/dist/execution-sponsored-registration.d.ts +14 -0
  84. package/dist/execution-sponsored-registration.js +77 -0
  85. package/dist/execution-sponsored-runtime.d.ts +84 -0
  86. package/dist/execution-sponsored-runtime.js +149 -0
  87. package/dist/execution-sponsored-settlement.d.ts +15 -0
  88. package/dist/execution-sponsored-settlement.js +88 -0
  89. package/dist/execution-sponsored-store.d.ts +67 -0
  90. package/dist/execution-sponsored-store.js +517 -0
  91. package/dist/execution-sponsored-transport.d.ts +90 -0
  92. package/dist/execution-sponsored-transport.js +180 -0
  93. package/dist/execution-tool-definitions.d.ts +21 -21
  94. package/dist/execution-tool-definitions.js +0 -7
  95. package/dist/execution-tools.d.ts +53 -1
  96. package/dist/execution-tools.js +171 -13
  97. package/dist/execution-worker-guard.d.ts +28 -0
  98. package/dist/execution-worker-guard.js +124 -0
  99. package/dist/hybrid-yield-index.d.ts +10 -0
  100. package/dist/hybrid-yield-index.js +11 -0
  101. package/dist/hybrid-yield-markets.d.ts +96 -0
  102. package/dist/hybrid-yield-markets.js +124 -0
  103. package/dist/hybrid-yield.d.ts +411 -0
  104. package/dist/hybrid-yield.js +155 -0
  105. package/dist/hyperliquid-order-builder.js +4 -0
  106. package/dist/lifi-execution-client.d.ts +6 -0
  107. package/dist/lifi-execution-client.js +3 -0
  108. package/dist/lp-userop-account.d.ts +461 -0
  109. package/dist/lp-userop-account.js +417 -0
  110. package/dist/lp-userop-authority.d.ts +49 -0
  111. package/dist/lp-userop-authority.js +211 -0
  112. package/dist/lp-userop-definition.d.ts +233 -0
  113. package/dist/lp-userop-definition.js +85 -0
  114. package/dist/lp-userop-hosted.d.ts +3 -0
  115. package/dist/lp-userop-hosted.js +73 -0
  116. package/dist/lp-userop-index.d.ts +1569 -0
  117. package/dist/lp-userop-index.js +112 -0
  118. package/dist/lp-userop-mcp-definition.d.ts +1220 -0
  119. package/dist/lp-userop-mcp-definition.js +46 -0
  120. package/dist/lp-userop-operation.d.ts +1230 -0
  121. package/dist/lp-userop-operation.js +140 -0
  122. package/dist/lp-userop-registration.d.ts +14 -0
  123. package/dist/lp-userop-registration.js +70 -0
  124. package/dist/lp-userop-settlement.d.ts +89 -0
  125. package/dist/lp-userop-settlement.js +184 -0
  126. package/dist/lp-userop.d.ts +643 -0
  127. package/dist/lp-userop.js +343 -0
  128. package/dist/native-yield-account-definition.d.ts +1192 -0
  129. package/dist/native-yield-account-definition.js +232 -0
  130. package/dist/native-yield-account-gas.d.ts +58 -0
  131. package/dist/native-yield-account-gas.js +228 -0
  132. package/dist/native-yield-account-index.d.ts +23 -0
  133. package/dist/native-yield-account-index.js +28 -0
  134. package/dist/native-yield-account-policy.d.ts +168 -0
  135. package/dist/native-yield-account-policy.js +525 -0
  136. package/dist/native-yield-account-rpc.d.ts +8 -0
  137. package/dist/native-yield-account-rpc.js +128 -0
  138. package/dist/native-yield-account-runtime.d.ts +71 -0
  139. package/dist/native-yield-account-runtime.js +125 -0
  140. package/dist/native-yield-account-store.d.ts +40 -0
  141. package/dist/native-yield-account-store.js +307 -0
  142. package/dist/native-yield-account.d.ts +662 -0
  143. package/dist/native-yield-account.js +371 -0
  144. package/dist/native-yield-consent-store.d.ts +20 -0
  145. package/dist/native-yield-consent-store.js +317 -0
  146. package/dist/native-yield-definition.d.ts +288 -0
  147. package/dist/native-yield-definition.js +105 -0
  148. package/dist/native-yield-reader.d.ts +4 -0
  149. package/dist/native-yield-reader.js +137 -0
  150. package/dist/native-yield-settlement.d.ts +48 -0
  151. package/dist/native-yield-settlement.js +167 -0
  152. package/dist/native-yield-simulation.d.ts +18 -0
  153. package/dist/native-yield-simulation.js +45 -0
  154. package/dist/native-yield-userop-account.d.ts +675 -0
  155. package/dist/native-yield-userop-account.js +357 -0
  156. package/dist/native-yield-userop-authority.d.ts +214 -0
  157. package/dist/native-yield-userop-authority.js +296 -0
  158. package/dist/native-yield-userop-definition.d.ts +331 -0
  159. package/dist/native-yield-userop-definition.js +105 -0
  160. package/dist/native-yield-userop-hosted.d.ts +3 -0
  161. package/dist/native-yield-userop-hosted.js +71 -0
  162. package/dist/native-yield-userop-index.d.ts +1522 -0
  163. package/dist/native-yield-userop-index.js +142 -0
  164. package/dist/native-yield-userop-mcp-definition.d.ts +1356 -0
  165. package/dist/native-yield-userop-mcp-definition.js +50 -0
  166. package/dist/native-yield-userop-operation.d.ts +845 -0
  167. package/dist/native-yield-userop-operation.js +127 -0
  168. package/dist/native-yield-userop-policy.d.ts +23 -0
  169. package/dist/native-yield-userop-policy.js +25 -0
  170. package/dist/native-yield-userop-registration.d.ts +17 -0
  171. package/dist/native-yield-userop-registration.js +76 -0
  172. package/dist/native-yield-userop-settlement.d.ts +8 -0
  173. package/dist/native-yield-userop-settlement.js +79 -0
  174. package/dist/native-yield-userop-simulation.d.ts +18 -0
  175. package/dist/native-yield-userop-simulation.js +34 -0
  176. package/dist/native-yield-userop.d.ts +35 -0
  177. package/dist/native-yield-userop.js +156 -0
  178. package/dist/native-yield-verify.d.ts +30 -0
  179. package/dist/native-yield-verify.js +141 -0
  180. package/dist/native-yield.d.ts +17 -0
  181. package/dist/native-yield.js +119 -0
  182. package/dist/stdio-main.js +0 -0
  183. package/dist/stock-buy-definition.d.ts +338 -0
  184. package/dist/stock-buy-definition.js +127 -0
  185. package/dist/stock-buy-oracle.d.ts +8 -0
  186. package/dist/stock-buy-oracle.js +22 -0
  187. package/dist/stock-buy.d.ts +17 -0
  188. package/dist/stock-buy.js +164 -0
  189. package/dist/tool-definitions.d.ts +3 -1
  190. package/dist/tool-definitions.js +55 -2
  191. package/dist/vendor/lp-v3/lp-v3-atomic-contract.d.ts +445 -0
  192. package/dist/vendor/lp-v3/lp-v3-atomic-contract.js +90 -0
  193. package/dist/vendor/lp-v3/lp-v3-atomic-evidence.d.ts +14 -0
  194. package/dist/vendor/lp-v3/lp-v3-atomic-evidence.js +82 -0
  195. package/dist/vendor/lp-v3/lp-v3-atomic-index.d.ts +80 -0
  196. package/dist/vendor/lp-v3/lp-v3-atomic-index.js +19 -0
  197. package/dist/vendor/lp-v3/lp-v3-atomic-plan.d.ts +43 -0
  198. package/dist/vendor/lp-v3/lp-v3-atomic-plan.js +112 -0
  199. package/dist/vendor/lp-v3/lp-v3-atomic-read-contract.d.ts +312 -0
  200. package/dist/vendor/lp-v3/lp-v3-atomic-read-contract.js +41 -0
  201. package/dist/vendor/lp-v3/lp-v3-atomic-read-index.d.ts +3 -0
  202. package/dist/vendor/lp-v3/lp-v3-atomic-read-index.js +3 -0
  203. package/dist/vendor/lp-v3/lp-v3-atomic-read-rpc.d.ts +49 -0
  204. package/dist/vendor/lp-v3/lp-v3-atomic-read-rpc.js +88 -0
  205. package/dist/vendor/lp-v3/lp-v3-atomic-read.d.ts +17 -0
  206. package/dist/vendor/lp-v3/lp-v3-atomic-read.js +232 -0
  207. package/dist/vendor/lp-v3/lp-v3-atomic-settlement.d.ts +105 -0
  208. package/dist/vendor/lp-v3/lp-v3-atomic-settlement.js +162 -0
  209. package/dist/vendor/lp-v3/lp-v3-collect-protocol-contract.d.ts +606 -0
  210. package/dist/vendor/lp-v3/lp-v3-collect-protocol-contract.js +59 -0
  211. package/dist/vendor/lp-v3/lp-v3-collect-protocol-index.d.ts +168 -0
  212. package/dist/vendor/lp-v3/lp-v3-collect-protocol-index.js +23 -0
  213. package/dist/vendor/lp-v3/lp-v3-collect-protocol-observation.d.ts +32 -0
  214. package/dist/vendor/lp-v3/lp-v3-collect-protocol-observation.js +228 -0
  215. package/dist/vendor/lp-v3/lp-v3-collect-protocol-plan.d.ts +11 -0
  216. package/dist/vendor/lp-v3/lp-v3-collect-protocol-plan.js +91 -0
  217. package/dist/vendor/lp-v3/lp-v3-collect-protocol-reconcile.d.ts +9 -0
  218. package/dist/vendor/lp-v3/lp-v3-collect-protocol-reconcile.js +124 -0
  219. package/dist/vendor/lp-v3/lp-v3-contract.d.ts +538 -0
  220. package/dist/vendor/lp-v3/lp-v3-contract.js +137 -0
  221. package/dist/vendor/lp-v3/lp-v3-evidence.d.ts +25 -0
  222. package/dist/vendor/lp-v3/lp-v3-evidence.js +168 -0
  223. package/dist/vendor/lp-v3/lp-v3-plan.d.ts +22 -0
  224. package/dist/vendor/lp-v3/lp-v3-plan.js +135 -0
  225. package/dist/vendor/lp-v3/types.d.ts +131 -0
  226. package/dist/vendor/lp-v3/types.js +6 -0
  227. package/dist/vendor/lp-v3/v4-math.d.ts +18 -0
  228. package/dist/vendor/lp-v3/v4-math.js +90 -0
  229. package/dist/yield-deposit-definition.d.ts +374 -0
  230. package/dist/yield-deposit-definition.js +123 -0
  231. package/dist/yield-deposit.d.ts +16 -0
  232. package/dist/yield-deposit.js +149 -0
  233. package/dist/yield-route-semantics.d.ts +27 -0
  234. package/dist/yield-route-semantics.js +71 -0
  235. package/dist/yield-withdraw-definition.d.ts +357 -0
  236. package/dist/yield-withdraw-definition.js +90 -0
  237. package/dist/yield-withdraw.d.ts +11 -0
  238. package/dist/yield-withdraw.js +122 -0
  239. package/package.json +239 -7
@@ -0,0 +1,127 @@
1
+ /**
2
+ * `otto_prepare_stock_buy` — a tokenized-stock BUY (Coinbase B20 on Base) as a thin constructor over
3
+ * `otto_prepare_swap` (Founder word: delegation beyond swaps, 2026-09-03).
4
+ *
5
+ * WHY IT IS A SWAP, PROVEN LIVE (li.quest, 2026-09-03, production params `integrator=ottowallet&fee=0.001`):
6
+ * $10 USDC → AAPLc quoted as a same-chain route whose transactionRequest targets the reviewed LI.FI
7
+ * diamond with selector 0x5fd9ae2e = `swapTokensMultipleV3ERC20ToERC20` — one of the two fenced
8
+ * GenericSwapV3 entry points — USDC is the v1 allow-listed input token, and the receiver is the signing
9
+ * account. A B20 buy is therefore INSIDE the delegation fence as it stands (v1/v2, unchanged); the SELL
10
+ * direction needs a B20-token approve to the diamond, which fence v2 grants only to the two vault share
11
+ * tokens — a sell is fence v3, sized in docs/0903_delegation_empire_scope.md, not shipped here.
12
+ *
13
+ * WHAT THIS TOOL PINS THAT A RAW SWAP DOES NOT:
14
+ * - the OUTPUT token is the registry row's B20 contract, never a caller input;
15
+ * - the row is the CURRENT registry record (caller's commitment must match the recomputed one);
16
+ * - chain 8453 / venue COINBASE_B20 / asset USDC, from the row, refused otherwise;
17
+ * - PRICE PARITY on the WORST PERMISSIBLE FILL: `amount / minTokens` (the floor the router
18
+ * enforces) may not exceed the row's own Chainlink total-return mark by more than the pinned
19
+ * tolerance, and the slippage band is capped — a thin or poisoned pool, or a wide floor, can
20
+ * never execute a delegated buy far over the venue's own mark;
21
+ * - ORACLE FRESHNESS: a frozen feed (> 24 h — the dashboard's own frozen threshold; feeds hold their
22
+ * last value off-hours) cannot anchor parity, so the buy is refused by name. Off-hours the pool may
23
+ * legitimately trade away from Friday's close; a delegated buy waits for a live reference.
24
+ * The generic `otto_prepare_swap` refuses every executable-equity token address in either direction
25
+ * (the same discipline as the yield share tokens): this constructor is the only delegated path.
26
+ */
27
+ import { z } from 'zod';
28
+ import { ACCOUNT_PROFILE_SCHEMA, EXECUTION_ENVELOPE_SCHEMA, PREPARE_SWAP_INPUT_SCHEMA, } from './execution-tool-definitions.js';
29
+ export const STOCK_BUY_TOOL_NAME = 'otto_prepare_stock_buy';
30
+ /**
31
+ * 🔴 PINNED PRICE-PARITY TOLERANCE, in basis points of the Chainlink mark, on the WORST PERMISSIBLE
32
+ * FILL (`fromAmount / minTokens` — the floor the router actually enforces on-chain, fees included).
33
+ *
34
+ * Gate G1: a guard on the QUOTE alone lets the permitted slippage carry the executed fill past the
35
+ * tolerance (quote at 0 bps + 1,000-bps floor = an 11% overpay the guard never saw). So parity is
36
+ * checked against the FLOOR, and the floor's own room is bounded: this constructor caps
37
+ * `slippage.maxBps` at 100 (the generic swap's 1..1000 range is not a stock buy's).
38
+ *
39
+ * Derivation (live li.quest quotes 2026-09-03/04, production params):
40
+ * - LI.FI fixed integrator fee 0.25% of the input → 25 bps
41
+ * - observed B20 pool spread over the oracle mark (~21 bps AAPLc · ~34 bps NVDAc
42
+ * at small size; thin pools move with size) → ~50 bps p95
43
+ * - the slippage floor this schema permits → ≤ 100 bps
44
+ * = 175 bps on the worst permissible fill: whatever routing and slippage do, a delegated buy can
45
+ * never execute more than 1.75% over the venue's own mark. A test pins this value; widening it
46
+ * must redden that test.
47
+ */
48
+ export const STOCK_BUY_PARITY_TOLERANCE_BPS = 175;
49
+ /** Gate G1: the tightest slippage band of any constructor — the tolerance above assumes it. */
50
+ export const STOCK_BUY_MAX_SLIPPAGE_BPS = 100;
51
+ /** The dashboard's frozen threshold (freshness.ts STALE_MAX_MIN): beyond this the feed cannot anchor parity. */
52
+ export const STOCK_BUY_MAX_ORACLE_AGE_SEC = 24 * 60 * 60;
53
+ const uintString = z
54
+ .string()
55
+ .regex(/^[1-9]\d*$/, 'must be a positive decimal integer')
56
+ .refine((value) => BigInt(value) <= (1n << 256n) - 1n, 'must fit uint256');
57
+ export const PREPARE_STOCK_BUY_INPUT_SCHEMA = z
58
+ .object({
59
+ accountProfile: ACCOUNT_PROFILE_SCHEMA,
60
+ executableEquityId: z
61
+ .string()
62
+ .min(1)
63
+ .max(120)
64
+ .describe('The executable equity id the READ half served, e.g. b20-nvda (Coinbase B20 on Base only).'),
65
+ registryCommitment: z
66
+ .string()
67
+ .regex(/^sha256:[0-9a-f]{64}$/, 'must be the sha256:… registry commitment the READ half served')
68
+ .describe("The registry commitment served with that row. It must equal the commitment this server recomputes over the CURRENT record; a stale or foreign row is refused by name."),
69
+ amount: uintString.describe('Exact USDC amount to spend, in atomic units (6 decimals).'),
70
+ slippage: z
71
+ .object({
72
+ maxBps: z.number().int().min(1).max(STOCK_BUY_MAX_SLIPPAGE_BPS),
73
+ })
74
+ .strict(),
75
+ client_ref: z.string().trim().min(1).max(128).optional(),
76
+ })
77
+ .strict();
78
+ const addressOut = z.string().regex(/^0x[0-9a-fA-F]{40}$/);
79
+ const uintOut = z.string().regex(/^\d+$/);
80
+ export const STOCK_BUY_PREVIEW_SCHEMA = z
81
+ .object({
82
+ equity_id: z.string(),
83
+ venue: z.literal('COINBASE_B20'),
84
+ chain_id: z.literal(8453),
85
+ underlying: z.string(),
86
+ symbol: z.string(),
87
+ token: addressOut,
88
+ asset: addressOut,
89
+ /** The USDC spent (atomic) — the cap's gross at submit is this same approve amount. */
90
+ amount: uintOut,
91
+ /** Tokens the route quotes for `amount` (8-decimal B20 units). */
92
+ expected_tokens: uintOut,
93
+ /** The on-chain floor the router enforces; what `prepareInput.slippage.minAmountOut` carries. */
94
+ min_tokens: uintOut,
95
+ /** The Chainlink total-return mark at prepare time (8 decimals) and its age. */
96
+ oracle_price: uintOut,
97
+ oracle_age_sec: z.number().int().min(0),
98
+ /** Premium of the WORST PERMISSIBLE FILL (amount / min_tokens) over the mark, in bps (gate G1). */
99
+ parity_premium_bps: z.number().int().min(0),
100
+ parity_tolerance_bps: z.literal(STOCK_BUY_PARITY_TOLERANCE_BPS),
101
+ /** The fee lines the router attached to the quote, as LI.FI names them (amount in USD). */
102
+ quoted_fees: z.array(z.object({ name: z.string(), amount_usd: z.string() }).strict()),
103
+ /** The route's own gas estimate for the call step, in USD, when LI.FI reported one. */
104
+ quoted_gas_usd: z.string().optional(),
105
+ })
106
+ .strict();
107
+ export const PREPARE_STOCK_BUY_OUTPUT_SCHEMA = z
108
+ .object({
109
+ /** The EXACT `otto_prepare_swap` envelope — submit it unchanged. */
110
+ envelope: EXECUTION_ENVELOPE_SCHEMA,
111
+ /**
112
+ * The EXACT `otto_prepare_swap` request that produced the envelope, with `slippage.minAmountOut`
113
+ * FILLED from the quote's floor — the pair `otto_submit_under_delegation` takes, verbatim.
114
+ */
115
+ prepareInput: PREPARE_SWAP_INPUT_SCHEMA,
116
+ preview: STOCK_BUY_PREVIEW_SCHEMA,
117
+ registry_commitment: z.string().regex(/^sha256:[0-9a-f]{64}$/),
118
+ })
119
+ .strict();
120
+ export const STOCK_BUY_TOOL_DEFINITION = Object.freeze({
121
+ name: STOCK_BUY_TOOL_NAME,
122
+ title: 'Prepare a tokenized-stock buy (Coinbase B20 on Base) as a delegable swap pair',
123
+ description: "Prepare the EXACT otto_prepare_swap pair (envelope + prepareInput) that buys one of the executable Coinbase B20 tokenized equities on Base with USDC, as a LI.FI route through the fenced diamond whose receiver is the signing account. The token comes from the registry row named by executableEquityId and is never an input; the caller's registryCommitment must equal the commitment recomputed over the current record. The WORST PERMISSIBLE FILL (amount over the route's own floor) is checked against the equity's own Chainlink total-return mark and refused when it exceeds the pinned tolerance, or when the feed is frozen (> 24 h). Returns the pair unchanged plus a preview; never signs or submits — submit the pair through otto_submit_under_delegation under the end user's per-swap cap.",
124
+ inputSchema: PREPARE_STOCK_BUY_INPUT_SCHEMA,
125
+ outputSchema: PREPARE_STOCK_BUY_OUTPUT_SCHEMA,
126
+ });
127
+ //# sourceMappingURL=stock-buy-definition.js.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The viem-backed `EquityOracleReader` — one `latestRoundData` read per prepare, over the same Base
3
+ * public client the delegated signer uses. In its own module (not stock-buy.ts) so execution-tools.ts
4
+ * can import it without a cycle, mirroring viemYieldVaultReader's placement.
5
+ */
6
+ import { type PublicClient } from 'viem';
7
+ import type { EquityOracleReader } from './stock-buy-definition.js';
8
+ export declare function viemEquityOracleReader(pub: PublicClient): EquityOracleReader;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The viem-backed `EquityOracleReader` — one `latestRoundData` read per prepare, over the same Base
3
+ * public client the delegated signer uses. In its own module (not stock-buy.ts) so execution-tools.ts
4
+ * can import it without a cycle, mirroring viemYieldVaultReader's placement.
5
+ */
6
+ import { parseAbi } from 'viem';
7
+ const AGGREGATOR_ABI = parseAbi([
8
+ 'function latestRoundData() view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)',
9
+ ]);
10
+ export function viemEquityOracleReader(pub) {
11
+ return Object.freeze({
12
+ async latestRoundData(feed) {
13
+ const [, answer, , updatedAt] = await pub.readContract({
14
+ address: feed,
15
+ abi: AGGREGATOR_ABI,
16
+ functionName: 'latestRoundData',
17
+ });
18
+ return { answer, updatedAt };
19
+ },
20
+ });
21
+ }
22
+ //# sourceMappingURL=stock-buy-oracle.js.map
@@ -0,0 +1,17 @@
1
+ import { type ExecutableEquity } from './executable-equities.js';
2
+ import { type ExecutionToolRuntime } from './execution-tools.js';
3
+ import { type PrepareStockBuyInput, type PrepareStockBuyOutput } from './stock-buy-definition.js';
4
+ /** Step 2: the registry row, pinned to the caller's commitment and to Base / B20 / USDC. */
5
+ export declare function resolveExecutableEquity(input: Pick<PrepareStockBuyInput, 'executableEquityId' | 'registryCommitment'>): ExecutableEquity;
6
+ /** Step 3, pure: the oracle answer is usable only when positive and within the frozen threshold. */
7
+ export declare function assertOracleUsable(answer: bigint, updatedAtSec: bigint, nowSec: number): number;
8
+ /**
9
+ * Step 5, pure and EXACT by cross-multiplication: the premium of a fill of `tokens` for `amountUsdc`
10
+ * over the mark, in bps (0 when at or under the mark) — called with the route's FLOOR (gate G1).
11
+ * Units: amountUsdc 6dp · tokens 8dp (B20) · oracle 8dp.
12
+ * implied = amountUsdc / tokens; refuse when implied > oracle × (1 + tol/10000):
13
+ * amountUsdc × 10^8 × 10^8 × 10000 > tokens × oracle × 10^6 × (10000 + tol)
14
+ * (both sides scaled to 6+8+8 decimal places plus the bps factor — no floats anywhere).
15
+ */
16
+ export declare function assertPriceParity(amountUsdc: bigint, quotedTokens: bigint, oraclePrice: bigint): number;
17
+ export declare function prepareStockBuy(runtime: ExecutionToolRuntime, rawInput: unknown, signal?: AbortSignal): Promise<PrepareStockBuyOutput>;
@@ -0,0 +1,164 @@
1
+ /**
2
+ * `otto_prepare_stock_buy` — the constructor. See `stock-buy-definition.ts` for WHY it is a swap
3
+ * and what it pins.
4
+ *
5
+ * Order of operations:
6
+ * 1. parse the strict input;
7
+ * 2. the registry row for `executableEquityId` (unknown → refused), the CURRENT commitment over the
8
+ * complete record must equal the caller's, chain 8453 / venue COINBASE_B20 / asset USDC from the
9
+ * row (anything else refused by name);
10
+ * 3. the row's Chainlink total-return mark (no reader / read failure / non-positive answer → refused;
11
+ * feed older than the pinned frozen threshold → refused: parity has no live anchor);
12
+ * 4. ONE LI.FI quote through `quoteSwap` for `USDC → row.tokenAddress` from the signing account;
13
+ * 5. PRICE PARITY, exact by cross-multiplication in atomic units, on the WORST PERMISSIBLE FILL
14
+ * (`amount / minTokens`, gate G1): the floor the router enforces may not exceed the mark by
15
+ * more than the pinned tolerance;
16
+ * 6. the swap prepareInput with `slippage.minAmountOut` = the quote's floor, then the envelope from
17
+ * THAT quote via `prepareSwapFromQuote` — the pair `otto_submit_under_delegation` takes verbatim.
18
+ *
19
+ * Nothing here signs, submits, or touches the delegation; the cap applies at submit, to the approve amount.
20
+ */
21
+ import { getAddress } from 'viem';
22
+ import { getExecutableEquity, getExecutableEquityCommitment, } from './executable-equities.js';
23
+ import { refuse } from './execution-errors.js';
24
+ import { PREPARE_SWAP_INPUT_SCHEMA } from './execution-tool-definitions.js';
25
+ import { prepareSwapFromQuote, quoteSwap } from './execution-tools.js';
26
+ import { PREPARE_STOCK_BUY_INPUT_SCHEMA, PREPARE_STOCK_BUY_OUTPUT_SCHEMA, STOCK_BUY_MAX_ORACLE_AGE_SEC, STOCK_BUY_PARITY_TOLERANCE_BPS, } from './stock-buy-definition.js';
27
+ /** Step 2: the registry row, pinned to the caller's commitment and to Base / B20 / USDC. */
28
+ export function resolveExecutableEquity(input) {
29
+ const equity = getExecutableEquity(input.executableEquityId);
30
+ if (!equity) {
31
+ return refuse('EQUITY_UNKNOWN', `${input.executableEquityId} is not an executable equity on this server`);
32
+ }
33
+ const current = getExecutableEquityCommitment(equity);
34
+ if (current !== input.registryCommitment) {
35
+ return refuse('EQUITY_REGISTRY_MISMATCH', `the registry commitment presented for ${equity.id} is not the current record's; re-read the equity and prepare again`);
36
+ }
37
+ if (equity.chainId !== 8453) {
38
+ return refuse('EQUITY_UNKNOWN', `${equity.id} is not on Base mainnet (8453)`);
39
+ }
40
+ if (equity.venue !== 'COINBASE_B20') {
41
+ return refuse('EQUITY_UNKNOWN', `${equity.id} is not a Coinbase B20 equity; only the B20 venue is executable`);
42
+ }
43
+ if (equity.asset.symbol !== 'USDC') {
44
+ return refuse('EQUITY_UNKNOWN', `${equity.id} does not buy with USDC`);
45
+ }
46
+ return equity;
47
+ }
48
+ /** Step 3, pure: the oracle answer is usable only when positive and within the frozen threshold. */
49
+ export function assertOracleUsable(answer, updatedAtSec, nowSec) {
50
+ if (answer <= 0n) {
51
+ return refuse('EQUITY_ORACLE_UNAVAILABLE', 'the Chainlink mark reads non-positive; parity has no anchor');
52
+ }
53
+ const age = nowSec - Number(updatedAtSec);
54
+ if (age < 0)
55
+ return 0; // clock skew clamps to 0, same as the dashboard's feedAge
56
+ if (age > STOCK_BUY_MAX_ORACLE_AGE_SEC) {
57
+ return refuse('EQUITY_ORACLE_FROZEN', `the Chainlink mark is ${Math.floor(age / 3600)} h old (frozen past ${STOCK_BUY_MAX_ORACLE_AGE_SEC / 3600} h — the feed holds its last value off-hours); a delegated buy waits for a live reference`);
58
+ }
59
+ return age;
60
+ }
61
+ /**
62
+ * Step 5, pure and EXACT by cross-multiplication: the premium of a fill of `tokens` for `amountUsdc`
63
+ * over the mark, in bps (0 when at or under the mark) — called with the route's FLOOR (gate G1).
64
+ * Units: amountUsdc 6dp · tokens 8dp (B20) · oracle 8dp.
65
+ * implied = amountUsdc / tokens; refuse when implied > oracle × (1 + tol/10000):
66
+ * amountUsdc × 10^8 × 10^8 × 10000 > tokens × oracle × 10^6 × (10000 + tol)
67
+ * (both sides scaled to 6+8+8 decimal places plus the bps factor — no floats anywhere).
68
+ */
69
+ export function assertPriceParity(amountUsdc, quotedTokens, oraclePrice) {
70
+ if (quotedTokens <= 0n) {
71
+ return refuse('STOCK_BUY_PARITY', 'the route quotes zero tokens for this amount; the buy is not prepared');
72
+ }
73
+ const lhs = amountUsdc * 10n ** 16n * 10000n; // 6dp → ×10^16 to the 6+8+8 plane, × bps base
74
+ const rhsBase = quotedTokens * oraclePrice * 10n ** 6n; // 8dp × 8dp → ×10^6 to the same plane
75
+ if (lhs > rhsBase * BigInt(10_000 + STOCK_BUY_PARITY_TOLERANCE_BPS)) {
76
+ const premiumBps = Number((lhs - rhsBase * 10000n) / rhsBase);
77
+ return refuse('STOCK_BUY_PARITY', `the route's gross implied price is ${premiumBps} bps over the equity's own Chainlink mark (tolerance ${STOCK_BUY_PARITY_TOLERANCE_BPS} bps); the buy is not prepared`);
78
+ }
79
+ const premium = lhs > rhsBase * 10000n ? Number((lhs - rhsBase * 10000n) / rhsBase) : 0;
80
+ return premium;
81
+ }
82
+ /** The fee lines LI.FI attached, as it names them — read for the preview; nothing is derived from them. */
83
+ function quotedFeeLines(quote) {
84
+ const estimate = quote.estimate;
85
+ const lines = Array.isArray(estimate.feeCosts) ? estimate.feeCosts : [];
86
+ return lines.flatMap((line) => {
87
+ const l = line;
88
+ return typeof l.name === 'string' && typeof l.amountUSD === 'string'
89
+ ? [{ name: l.name, amount_usd: l.amountUSD }]
90
+ : [];
91
+ });
92
+ }
93
+ function quotedGasUsd(quote) {
94
+ const estimate = quote.estimate;
95
+ const first = Array.isArray(estimate.gasCosts) ? estimate.gasCosts[0] : undefined;
96
+ return typeof first?.amountUSD === 'string' ? first.amountUSD : undefined;
97
+ }
98
+ export async function prepareStockBuy(runtime, rawInput, signal) {
99
+ const input = PREPARE_STOCK_BUY_INPUT_SCHEMA.parse(rawInput);
100
+ const equity = resolveExecutableEquity(input);
101
+ const reader = runtime.equityOracleReader;
102
+ if (!reader) {
103
+ return refuse('EQUITY_ORACLE_UNAVAILABLE', 'this server has no equity oracle reader, so price parity cannot be checked; nothing is prepared');
104
+ }
105
+ const token = getAddress(equity.tokenAddress);
106
+ const asset = getAddress(equity.asset.address);
107
+ const amount = BigInt(input.amount);
108
+ // 3. The mark, before any quote — a buy with no anchor is refused without touching LI.FI.
109
+ let round;
110
+ try {
111
+ round = await reader.latestRoundData(equity.feed);
112
+ }
113
+ catch {
114
+ return refuse('EQUITY_ORACLE_UNAVAILABLE', "the equity's Chainlink feed could not be read, so price parity cannot be checked; nothing is prepared");
115
+ }
116
+ // the runtime's clock, not the wall clock — freshness must be provable under a fixed now
117
+ const oracleAge = assertOracleUsable(round.answer, round.updatedAt, Math.floor(runtime.nowMs() / 1000));
118
+ // 4. The swap request, toToken PINNED to the registry token. Quoted once.
119
+ const quoteInput = PREPARE_SWAP_INPUT_SCHEMA.parse({
120
+ accountProfile: input.accountProfile,
121
+ chainId: equity.chainId,
122
+ fromToken: asset,
123
+ toToken: token,
124
+ fromAmount: input.amount,
125
+ slippage: { maxBps: input.slippage.maxBps },
126
+ ...(input.client_ref ? { client_ref: input.client_ref } : {}),
127
+ });
128
+ const quote = await quoteSwap(runtime, quoteInput, signal);
129
+ const expectedTokens = BigInt(quote.estimate.toAmount);
130
+ const minTokens = BigInt(quote.estimate.toAmountMin);
131
+ // 5. Parity against the venue's own mark, on the WORST PERMISSIBLE FILL (gate G1): the router
132
+ // enforces minTokens, not the quote — so the floor is what the tolerance must bound.
133
+ const premiumBps = assertPriceParity(amount, minTokens, round.answer);
134
+ // 6. The pair: the floor goes INTO prepareInput before the envelope is built, so the intent digest binds it.
135
+ const prepareInput = PREPARE_SWAP_INPUT_SCHEMA.parse({
136
+ ...quoteInput,
137
+ slippage: { maxBps: input.slippage.maxBps, minAmountOut: minTokens.toString() },
138
+ });
139
+ const envelope = prepareSwapFromQuote(runtime, prepareInput, quote);
140
+ return PREPARE_STOCK_BUY_OUTPUT_SCHEMA.parse({
141
+ envelope,
142
+ prepareInput,
143
+ preview: {
144
+ equity_id: equity.id,
145
+ venue: 'COINBASE_B20',
146
+ chain_id: 8453,
147
+ underlying: equity.underlying,
148
+ symbol: equity.symbol,
149
+ token,
150
+ asset,
151
+ amount: amount.toString(),
152
+ expected_tokens: expectedTokens.toString(),
153
+ min_tokens: minTokens.toString(),
154
+ oracle_price: round.answer.toString(),
155
+ oracle_age_sec: oracleAge,
156
+ parity_premium_bps: premiumBps,
157
+ parity_tolerance_bps: STOCK_BUY_PARITY_TOLERANCE_BPS,
158
+ quoted_fees: quotedFeeLines(quote),
159
+ ...(quotedGasUsd(quote) !== undefined ? { quoted_gas_usd: quotedGasUsd(quote) } : {}),
160
+ },
161
+ registry_commitment: input.registryCommitment,
162
+ });
163
+ }
164
+ //# sourceMappingURL=stock-buy.js.map
@@ -10,11 +10,13 @@ export interface IntelToolDefinition {
10
10
  readonly inputSchema: z.ZodObject;
11
11
  readonly priceUsd: string;
12
12
  readonly maxPaymentAtomic: bigint;
13
+ /** No compatible warm-only backend: use the public paid rail without spending the free grant. */
14
+ readonly paidOnly?: boolean;
13
15
  readonly toQuery: (input: ToolArguments) => Record<string, string>;
14
16
  }
15
17
  /**
16
18
  * The single v1 catalog consumed by both the hosted and stdio transports.
17
- * Every entry is quote/data-only and backed by the data-endpoints service.
19
+ * Every entry is quote/data-only. Entries outside data-endpoints' warm-only backend are paid-only.
18
20
  */
19
21
  export declare const TOOL_DEFINITIONS: readonly IntelToolDefinition[];
20
22
  export declare const TOOL_DEFINITION_BY_NAME: Map<`otto_${string}`, IntelToolDefinition>;
@@ -33,6 +33,28 @@ const managerSchema = z
33
33
  .describe('SEC manager CIK, such as 1067983, or listed manager ticker, such as BLK.'),
34
34
  })
35
35
  .strict();
36
+ /**
37
+ * Network-qualified stock identity for `/stock-pools`. VENDORED grammar: a Railway build reads one
38
+ * service dir, so this cannot import data-endpoints' `normalizeStockPoolsKey` — the source-side lock
39
+ * test (`services/data-endpoints/src/endpoints/stock-pools-mcp-lock.test.ts`) fails when the two
40
+ * drift. A BARE ticker is rejected on purpose: the same ticker is tokenized on several chains, and
41
+ * guessing one would answer a question the caller did not ask.
42
+ */
43
+ const stockPoolsSchema = z
44
+ .object({
45
+ stock: z
46
+ .string()
47
+ .trim()
48
+ .min(3)
49
+ .max(64)
50
+ // Case-insensitive chain and an optional leading `$` — the SAME spellings the endpoint's
51
+ // normalizer accepts. Codex r1 seam: the two rails previously disagreed, so `$MU` and
52
+ // `ROBINHOOD:MU` were accepted on x402 and rejected here; the lock test now pins a shared
53
+ // accepted/rejected table rather than the regex's shape.
54
+ .regex(/^\$?(robinhood|base|bsc|solana):[A-Za-z0-9._-]{1,50}$/i, 'stock must be network-qualified, for example robinhood:MU')
55
+ .describe('Network-qualified tokenized-stock identity: "<network>:<TICKER>" or "<network>:<token address>", where network is robinhood, base, bsc or solana. Example: robinhood:MU. A bare ticker is rejected, and so is a ticker that names two deployments on one chain — give the token address for those.'),
56
+ })
57
+ .strict();
36
58
  function noQuery() {
37
59
  return {};
38
60
  }
@@ -45,6 +67,9 @@ function managerQuery(input) {
45
67
  function thesisQuery(input) {
46
68
  return typeof input.thesis === 'string' ? { thesis: input.thesis } : {};
47
69
  }
70
+ function stockPoolsQuery(input) {
71
+ return { stock: String(input.stock).replace(/^\$/, '') };
72
+ }
48
73
  function usdcAtomic(priceUsd) {
49
74
  if (!/^\d+\.\d{1,6}$/.test(priceUsd)) {
50
75
  throw new Error(`invalid USDC price: ${priceUsd}`);
@@ -61,13 +86,32 @@ function defineTool(definition) {
61
86
  }
62
87
  /**
63
88
  * The single v1 catalog consumed by both the hosted and stdio transports.
64
- * Every entry is quote/data-only and backed by the data-endpoints service.
89
+ * Every entry is quote/data-only. Entries outside data-endpoints' warm-only backend are paid-only.
65
90
  */
66
91
  export const TOOL_DEFINITIONS = Object.freeze([
92
+ defineTool({
93
+ name: 'otto_crypto_news',
94
+ title: 'Otto crypto news',
95
+ description: 'Read importance-ranked crypto headlines with source links and publication times, plus a sentiment-scored market brief. The hourly snapshot reports its analysis window and freshness; missing source links remain unknown. Paid-only, including the first call: this tool preserves the hosted free intelligence allowance. Market context only; not a recommendation.',
96
+ endpointId: 'crypto-news',
97
+ inputSchema: noArgumentsSchema,
98
+ priceUsd: '0.001',
99
+ paidOnly: true,
100
+ toQuery: noQuery,
101
+ }),
102
+ defineTool({
103
+ name: 'otto_news_recaps',
104
+ title: 'Otto crypto market recap',
105
+ description: 'Read a concise crypto market recap with ranked stories, source links where available, analysis window and freshness. A cached result can use the hosted free allowance; a cold or unavailable read returns a payment challenge without generating a recap for free. Stale-but-servable cached results retain their upstream freshness and degraded indicators. Market context only; not a recommendation.',
106
+ endpointId: 'news-recaps',
107
+ inputSchema: noArgumentsSchema,
108
+ priceUsd: '0.003',
109
+ toQuery: noQuery,
110
+ }),
67
111
  defineTool({
68
112
  name: 'otto_tokenized_equities',
69
113
  title: 'Otto tokenized equities',
70
- description: 'Scan the live registry of US equities and ETFs tokenized on Robinhood Chain (4663): symbol, canonical token address, decimals, reference price, and verifiable tradability signal. An optional thesis returns a relevance-ranked shortlist with a match reason per row. Data only; not a recommendation or executable quote.',
114
+ description: 'Scan the live registry of US equities and ETFs tokenized on Robinhood Chain (4663): symbol, canonical token address, decimals, the executable buy price a live route returns, the catalog reference price and the deviation between them, plus a verifiable tradability signal. An optional thesis returns a relevance-ranked shortlist with a match reason per row. Data only; not a recommendation or executable quote.',
71
115
  endpointId: 'tokenized-equities',
72
116
  inputSchema: thesisSchema,
73
117
  priceUsd: '0.001',
@@ -157,6 +201,15 @@ export const TOOL_DEFINITIONS = Object.freeze([
157
201
  priceUsd: '0.001',
158
202
  toQuery: noQuery,
159
203
  }),
204
+ defineTool({
205
+ name: 'otto_stock_pools',
206
+ title: 'Otto tokenized-stock pool discovery',
207
+ description: 'Find the DEX pools a tokenized US stock or ETF is actually a member of, on Robinhood Chain, Base, BNB Chain or Solana. Returns each pool id (Uniswap v4 pool ids kept distinct from v3 pool addresses), DEX and version, which side the asset is on, the counterpart token with an evidence-backed classification (verified stock, verified stablecoin, or unclassified), reported USD liquidity and 24h volume, 24h transaction count, pool creation time, and the source coverage limits. Membership discovery only: a provider-covered observation rather than a complete census, no APR and no execution support, and the asset\u2019s own 24h return is reported only from pools where it is the base token. Identify the asset by address when a ticker names two deployments on one chain \u2014 that spelling is refused rather than resolved to one of them. Not a pairing recommendation.',
208
+ endpointId: 'stock-pools',
209
+ inputSchema: stockPoolsSchema,
210
+ priceUsd: '0.001',
211
+ toQuery: stockPoolsQuery,
212
+ }),
160
213
  ]);
161
214
  export const TOOL_DEFINITION_BY_NAME = new Map(TOOL_DEFINITIONS.map((definition) => [definition.name, definition]));
162
215
  //# sourceMappingURL=tool-definitions.js.map