otto-intel-mcp 0.1.2 → 0.1.3

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 (51) hide show
  1. package/README.md +4 -2
  2. package/dist/adapter/cdp-signer.d.ts +44 -5
  3. package/dist/adapter/cdp-signer.js +168 -18
  4. package/dist/adapter/evm-call-failure.d.ts +29 -0
  5. package/dist/adapter/evm-call-failure.js +109 -0
  6. package/dist/adapter/refusal.d.ts +1 -1
  7. package/dist/adapter/refusal.js +13 -0
  8. package/dist/adapter/route-simulator.d.ts +91 -0
  9. package/dist/adapter/route-simulator.js +143 -0
  10. package/dist/executable-equities.d.ts +55 -0
  11. package/dist/executable-equities.js +116 -0
  12. package/dist/executable-yield-markets.d.ts +261 -0
  13. package/dist/executable-yield-markets.js +154 -0
  14. package/dist/execution-delegated-definition.d.ts +1 -0
  15. package/dist/execution-delegated-definition.js +13 -2
  16. package/dist/execution-delegation-admin-definition.d.ts +126 -5
  17. package/dist/execution-delegation-admin-definition.js +154 -5
  18. package/dist/execution-delegation-admin.d.ts +48 -2
  19. package/dist/execution-delegation-admin.js +314 -12
  20. package/dist/execution-delegation-cap-store.d.ts +369 -0
  21. package/dist/execution-delegation-cap-store.js +1083 -0
  22. package/dist/execution-delegation-policy.d.ts +153 -6
  23. package/dist/execution-delegation-policy.js +260 -16
  24. package/dist/execution-delegation.d.ts +35 -0
  25. package/dist/execution-delegation.js +224 -9
  26. package/dist/execution-errors.d.ts +1 -1
  27. package/dist/execution-errors.js +98 -0
  28. package/dist/execution-index.d.ts +3 -2
  29. package/dist/execution-index.js +3 -2
  30. package/dist/execution-registration.d.ts +51 -2
  31. package/dist/execution-registration.js +45 -2
  32. package/dist/execution-tools.d.ts +44 -1
  33. package/dist/execution-tools.js +157 -13
  34. package/dist/lifi-execution-client.d.ts +6 -0
  35. package/dist/lifi-execution-client.js +3 -0
  36. package/dist/stock-buy-definition.d.ts +338 -0
  37. package/dist/stock-buy-definition.js +127 -0
  38. package/dist/stock-buy-oracle.d.ts +8 -0
  39. package/dist/stock-buy-oracle.js +22 -0
  40. package/dist/stock-buy.d.ts +17 -0
  41. package/dist/stock-buy.js +164 -0
  42. package/dist/tool-definitions.js +1 -1
  43. package/dist/yield-deposit-definition.d.ts +323 -0
  44. package/dist/yield-deposit-definition.js +121 -0
  45. package/dist/yield-deposit.d.ts +16 -0
  46. package/dist/yield-deposit.js +141 -0
  47. package/dist/yield-withdraw-definition.d.ts +306 -0
  48. package/dist/yield-withdraw-definition.js +88 -0
  49. package/dist/yield-withdraw.d.ts +21 -0
  50. package/dist/yield-withdraw.js +120 -0
  51. package/package.json +28 -4
@@ -31,6 +31,20 @@
31
31
  *
32
32
  * Nothing here logs the header, the secret, or a credential. Refusals are machine-readable codes; upstream
33
33
  * error bodies are never relayed.
34
+ *
35
+ * ══════════════════════════════════════════════════════════════════════════════════════════════════
36
+ * 🔴 THE THREAT MODEL THE PER-SWAP CAP GATE (step 5b) ENFORCES (Founder ruling, 2026-09-03, verbatim)
37
+ * ══════════════════════════════════════════════════════════════════════════════════════════════════
38
+ * "The per-person delegation cap protects every delegated send that goes through Otto's page
39
+ * (/app/delegate) and Otto's CLI (otto-execute delegate). A permission a person mints or revokes
40
+ * directly against Coinbase with their own login, outside Otto's page and CLI, is OUT OF SCOPE: Otto
41
+ * cannot distinguish it (CDP returns no grant identity, only an expiry) and does not defend against it;
42
+ * the CDP project-scope $5,000 ceiling remains the hard backstop for every permission. The delegated
43
+ * agent never holds the person's login."
44
+ *
45
+ * Note the direction that holds even out of the model: a grant Otto has no CONFIRMED record for
46
+ * authorises NOTHING here — it is refused by name, never admitted at the shared ceiling. An
47
+ * out-of-model actor can therefore shorten what Otto may do for them, never widen it.
34
48
  */
35
49
  import { createHash, timingSafeEqual } from 'node:crypto';
36
50
  import { getAddress } from 'viem';
@@ -38,7 +52,9 @@ import { CDP_CAPABILITIES, CdpDelegatedSigner, DelegatedPlanHaltedError, createC
38
52
  import { isAdapterRefusal } from './adapter/refusal.js';
39
53
  import { defaultAdapterPolicy, verifySwapEnvelope } from './adapter/verify.js';
40
54
  import { DELEGATION_AUTH_HEADER, SUBMIT_UNDER_DELEGATION_INPUT_SCHEMA, } from './execution-delegated-definition.js';
41
- import { assertWithinModelBFence, MODEL_B_CHAIN_ID, MODEL_B_DELEGATION_EXPIRY_MAX_DAYS, MODEL_B_PER_SWAP_CAP_USD, MODEL_B_POLICY_NAME_V1, readBackModelBFence, } from './execution-delegation-policy.js';
55
+ import { MODEL_B_CAP_RULESET_VERSION } from './execution-delegation-cap-store.js';
56
+ import { assertWithinModelBFence, assertWithinUserPerSwapCap, MODEL_B_CHAIN_ID, MODEL_B_DELEGATION_EXPIRY_MAX_DAYS, MODEL_B_PER_SWAP_CAP_USD, MODEL_B_POLICY_VERSION, assertWithdrawWithinModelBFence, modelBWithdrawToken, MODEL_B_INPUT_TOKENS_V1, readBackModelBFence, } from './execution-delegation-policy.js';
57
+ import { createDelegationCapStore, resolveDelegationMintEnabled, } from './execution-delegation-cap-store.js';
42
58
  import { EXECUTION_REFUSAL_CODES, ExecutionRefusal, isExecutionRefusal, refuse, } from './execution-errors.js';
43
59
  import { artifactCommitmentDigest, canonicalJson, } from './execution-types.js';
44
60
  import { DELEGATED_SUBMISSION_GRACE_MS, preparedHere } from './prepared-artifacts.js';
@@ -357,15 +373,32 @@ function verifiedResult(input, user, verified, sent, fence, expiresAt) {
357
373
  block_number: step.block_number,
358
374
  gas_used: step.gas_used,
359
375
  })),
376
+ /**
377
+ * 🔴 TWO NUMBERS, BECAUSE THERE ARE TWO FACTS. `per_swap_cap_usd` is the ENGINE's — the constant
378
+ * compiled into the one project-scope policy, the same for every delegated user, and the only one
379
+ * Coinbase enforces. `user_cap_usd` is what THIS person chose when they minted, enforced by this
380
+ * server alone. Reporting only the second would describe the CDP fence as something it is not;
381
+ * reporting only the first would hide the number that actually bounded this send.
382
+ */
360
383
  fence: Object.freeze({
361
384
  policy_id: fence.id,
362
- policy_name: MODEL_B_POLICY_NAME_V1,
385
+ policy_name: fence.name,
363
386
  rules_digest: fence.rulesDigest,
364
387
  per_swap_cap_usd: MODEL_B_PER_SWAP_CAP_USD,
388
+ user_cap_usd: fence.userCapUsd,
365
389
  }),
366
390
  delegation: Object.freeze({ expires_at: expiresAt }),
367
391
  verification: Object.freeze({
368
- checks: verified.checks.map((check) => check.id),
392
+ /**
393
+ * The verifier's checks, plus the ROUTE the constructor recorded in the envelope (`route_tool:<lifi
394
+ * tool>` and `route_simulated:<true|false>` assertion ids), so the receipt says which venue executed.
395
+ * Carried as check strings because the receipt's wire shape is validated `.strict()` by the shipped
396
+ * dApp until cut #3 — a new field would break the page that is live today.
397
+ */
398
+ checks: [
399
+ ...verified.checks.map((check) => check.id),
400
+ ...input.envelope.assertions.map((a) => a.id).filter((id) => id.startsWith('route_')),
401
+ ],
369
402
  gross: verified.gross.toString(),
370
403
  min_amount_out: verified.minAmountOut.toString(),
371
404
  otto_fee_atomic: verified.ottoFee.toString(),
@@ -427,13 +460,19 @@ export async function submitUnderDelegation(runtime, rawInput, caller, _signal)
427
460
  const fence = readBackModelBFence(await services.rig.listProjectPolicies());
428
461
  if (!fence.present)
429
462
  return refuse('DELEGATION_FENCE_ABSENT', `the Model-B project policy is not in force (${fence.reason}): ${fence.detail}`);
430
- // 3. GRANT active past the envelope's own expiry; fail-closed once revocation was ever requested here.
431
- if (revocationRequested(services.rig.rig, input.userId)) {
432
- return refuse('DELEGATION_NOT_ACTIVE', 'revocation was requested for this end user on this server; nothing is submitted for them again');
433
- }
463
+ // 3. GRANT active past the envelope's own expiry; fail-closed once revocation was requested for IT.
434
464
  const grant = await services.rig.rig.readDelegation(input.userId);
435
465
  if (!grant)
436
466
  return refuse('DELEGATION_NOT_ACTIVE', 'no active delegation reads back for this end user');
467
+ /**
468
+ * 🔴 PER GRANT, NOT PER USER (gate finding, HIGH — round 3). The marker used to be a sticky set of
469
+ * user ids, so once revoke was offered in every screen state a Stop press on a screen with no grant
470
+ * bricked every later permission that person minted. It names the grant now, so a revoke stops
471
+ * exactly what it was aimed at.
472
+ */
473
+ if (revocationRequested(services.rig.rig, input.userId, grant.expiresAt)) {
474
+ return refuse('DELEGATION_NOT_ACTIVE', 'revocation was requested for this delegation on this server; nothing is submitted under it');
475
+ }
437
476
  const expiresAtMs = Date.parse(grant.expiresAt);
438
477
  const validUntilMs = Date.parse(input.envelope.valid_until);
439
478
  if (!Number.isFinite(expiresAtMs) ||
@@ -500,10 +539,176 @@ export async function submitUnderDelegation(runtime, rawInput, caller, _signal)
500
539
  throw refusal;
501
540
  throw error;
502
541
  }
503
- assertWithinModelBFence(verified, services.fenceBound?.perSwapCapUsd, services.fenceBound?.inputTokens);
542
+ /**
543
+ * 🔴 v2 — TWO SHAPES THROUGH ONE GATE. A delegated plan spends either an allow-listed INPUT token (a swap,
544
+ * or a Yield Copilot DEPOSIT, which is a swap into vault shares) — capped in USD by the engine fence and
545
+ * by the person's own recorded cap — or a registry vault SHARE token (a Yield Copilot WITHDRAW: shares →
546
+ * USDC, back to the signing account). A withdraw returns the person's own assets to the person and never
547
+ * increases exposure, so the person's per-swap cap does not apply to it: the cap checks below run with a
548
+ * gross of ZERO (which still validates the recorded cap's sanity). What bounds a withdraw is the engine's
549
+ * share cap (`assertWithdrawWithinModelBFence`) and the verifier's receiver pin, and it is admitted ONLY
550
+ * under fence v2 and ONLY for a permission minted under cap ruleset v2 (the person consented to withdraws).
551
+ */
552
+ const inputTokens = services.fenceBound?.inputTokens ?? MODEL_B_INPUT_TOKENS_V1;
553
+ // A withdraw is recognised by its INPUT being a registry share token; anything else takes the swap path
554
+ // (and a token outside the allow-list is refused there, by the original message).
555
+ const isWithdraw = modelBWithdrawToken(verified.token) !== undefined;
556
+ let capGross;
557
+ let capToken;
558
+ if (isWithdraw) {
559
+ if (fence.version < 2) {
560
+ return refuse('DELEGATION_OUTSIDE_FENCE', `a delegated vault withdraw needs fence v${MODEL_B_POLICY_VERSION}; the project fence is v${fence.version} — nothing is submitted`);
561
+ }
562
+ assertWithdrawWithinModelBFence({
563
+ token: verified.token,
564
+ spender: verified.spender,
565
+ gross: verified.gross,
566
+ outputToken: verified.outputToken,
567
+ });
568
+ capGross = 0n;
569
+ capToken = MODEL_B_INPUT_TOKENS_V1[0];
570
+ }
571
+ else {
572
+ const bound = assertWithinModelBFence(verified, services.fenceBound?.perSwapCapUsd, inputTokens);
573
+ capGross = verified.gross;
574
+ capToken = bound.token;
575
+ }
576
+ /**
577
+ * 5b. THE SEND GATE (Founder word `delegate cap choice`; ROUND 4, built to the codex gate's own
578
+ * prescription after it NO-GO'd rounds 1–3).
579
+ *
580
+ * ══════════════════════════════════════════════════════════════════════════════════════════════
581
+ * 🔴 WHAT THIS REFUSES, AND WHY IT IS A CONFIRMED RECORD RATHER THAN A ROW
582
+ * ══════════════════════════════════════════════════════════════════════════════════════════════
583
+ * Round 3 read a durable row keyed by `(userId, grant.expiresAt)`. The gate's two CRITICALs:
584
+ *
585
+ * "The durable row still authenticates an expiry, not a grant … it does not require a successful
586
+ * confirmation, the reservation's mintKey, or an active-grant pointer."
587
+ * "Confirmation is not an enforcement gate. Reserve writes the complete enforceable capability;
588
+ * confirm writes no state at all."
589
+ *
590
+ * So a reservation was a bearer capability: anyone who could ask CDP for a grant at that instant —
591
+ * the agent CLI, an SDK client, a second tab — inherited the cap without confirming.
592
+ *
593
+ * The gate now asks for the CONFIRMED-ACTIVE record and nothing else. A reserved, superseded,
594
+ * revoked or expired record is not returned by `readActive` at all, so:
595
+ *
596
+ * · a grant minted at a reserved instant WITHOUT confirming is refused `DELEGATION_CAP_UNCONFIRMED`;
597
+ * · a grant that is not the one the confirmed record names — an old expiry reused, a permission
598
+ * minted elsewhere — is refused `DELEGATION_CAP_MISMATCH`;
599
+ * · a record from a NEWER cap ruleset than this worker implements is refused
600
+ * `DELEGATION_CAP_RULESET_STALE`, which is what stops a rolling deploy's older binary executing
601
+ * a new permission at the shared ceiling.
602
+ *
603
+ * None of them fall back to the engine's $5,000: "missing means the ceiling" would turn every one of
604
+ * these into a silent widening of somebody's permission.
605
+ *
606
+ * 🔴 AND IT IS RE-DERIVED FROM CURRENT DURABLE STATE ON EVERY READ, never from a boot-time map, so a
607
+ * process that missed another's confirmation still sees it — and a store that cannot be read THROWS
608
+ * rather than answering "nothing is confirmed", because those two have opposite consequences.
609
+ */
610
+ const readActiveCap = async () => {
611
+ if (!services.capStore.available) {
612
+ return refuse('DELEGATION_CAP_STORE_UNAVAILABLE', `the per-swap cap for this delegation cannot be read, so what the user agreed to is unknown; nothing is submitted (${services.capStore.unavailableReason ?? 'store unavailable'})`);
613
+ }
614
+ let active;
615
+ try {
616
+ active = await services.capStore.readActive(input.userId, runtime.nowMs());
617
+ }
618
+ catch {
619
+ return refuse('DELEGATION_CAP_STORE_UNAVAILABLE', 'the per-swap cap for this delegation could not be read, so what the user agreed to is unknown; nothing is submitted');
620
+ }
621
+ if (!active) {
622
+ return refuse('DELEGATION_CAP_UNCONFIRMED', 'no confirmed permission stands for this end user on this server, so what they agreed to is unknown; nothing is submitted — mint the permission again on the delegation page');
623
+ }
624
+ if (active.requiredVersion > MODEL_B_CAP_RULESET_VERSION) {
625
+ return refuse('DELEGATION_CAP_RULESET_STALE', `this permission requires cap ruleset v${active.requiredVersion} and this server implements v${MODEL_B_CAP_RULESET_VERSION}; nothing is submitted until the newer build is serving`);
626
+ }
627
+ /**
628
+ * 🔴 THE CONFIRMED RECORD MUST NAME THE GRANT THAT IS ACTUALLY LIVE. Without this the record would
629
+ * be a standing authorisation for whatever grant the user happens to hold, which is the round-3
630
+ * defect one layer up.
631
+ */
632
+ if (Date.parse(active.expiresAt) !== expiresAtMs) {
633
+ return refuse('DELEGATION_CAP_MISMATCH', 'the delegation that reads back is not the one this permission was confirmed for; nothing is submitted — mint the permission again on the delegation page');
634
+ }
635
+ return {
636
+ capUsd: active.perSwapCapUsd,
637
+ expiresAt: active.expiresAt,
638
+ requiredVersion: active.requiredVersion,
639
+ };
640
+ };
641
+ const active = await readActiveCap();
642
+ const userCapUsd = active.capUsd;
643
+ if (isWithdraw && active.requiredVersion < 2) {
644
+ return refuse('DELEGATION_WITHDRAW_UNCONSENTED', `this permission was minted under cap ruleset v${active.requiredVersion}, before vault withdraws existed; nothing is submitted — mint the permission again on the delegation page`);
645
+ }
646
+ assertWithinUserPerSwapCap(capGross, userCapUsd, capToken);
647
+ /**
648
+ * 🔴 AND IT IS RE-CHECKED BEFORE EVERY STEP, AND AGAIN AFTER THE TRANSACTION IS BUILT.
649
+ *
650
+ * The plan is three transactions. `beforeEachStep` re-reads the grant and the confirmed record
651
+ * between them, which catches a revoke-and-re-mint from another tab. The gate found the remaining
652
+ * window: "the final asynchronous grant/cap check still happens before transaction construction …
653
+ * only a synchronous abort check precedes raw submission. A revoke/re-mint to a narrower cap during
654
+ * transaction building therefore escapes the last authoritative check."
655
+ *
656
+ * So the async re-read stores what it saw, and a SYNCHRONOUS assertion over that value runs after
657
+ * the nonce/fee/gas round trips and immediately before the hand-off. It cannot await — awaiting is
658
+ * what opened the window — so what it closes is the interval between the last read and the send,
659
+ * using the freshest value that exists. The irreducible residual is the network round trip itself,
660
+ * which no vendor CAS is offered to close.
661
+ */
662
+ let lastSeenCapUsd = userCapUsd;
663
+ let lastSeenRequiredVersion = active.requiredVersion;
664
+ let lastSeenGrantOk = true;
665
+ /** Re-read the grant and the confirmed record; record what was seen. Used by both hooks below. */
666
+ const refreshSeen = async () => {
667
+ const current = await services.rig.rig.readDelegation(input.userId);
668
+ lastSeenGrantOk = current !== undefined && Date.parse(current.expiresAt) === expiresAtMs;
669
+ if (!lastSeenGrantOk)
670
+ return;
671
+ const seen = await readActiveCap();
672
+ lastSeenCapUsd = seen.capUsd;
673
+ lastSeenRequiredVersion = seen.requiredVersion;
674
+ };
675
+ /** A withdraw's consent is re-asserted wherever the cap is (codex gate r1: true at all three points). */
676
+ const assertWithdrawConsent = () => {
677
+ if (isWithdraw && lastSeenRequiredVersion < 2) {
678
+ return refuse('DELEGATION_WITHDRAW_UNCONSENTED', 'the confirmed permission changed to one minted before vault withdraws existed; the remaining steps are not submitted');
679
+ }
680
+ };
681
+ /** Between steps: the plan must still be admissible before the next one is even built. */
682
+ const beforeEachStep = async () => {
683
+ await refreshSeen();
684
+ if (!lastSeenGrantOk) {
685
+ return refuse('DELEGATION_NOT_ACTIVE', 'the delegation changed while the plan was in flight; the remaining steps are not submitted');
686
+ }
687
+ assertWithdrawConsent();
688
+ assertWithinUserPerSwapCap(capGross, lastSeenCapUsd, capToken);
689
+ };
690
+ /**
691
+ * 🔴 AND AGAIN AFTER THE TRANSACTION IS BUILT — the window the gate named. The nonce, fee and gas
692
+ * round trips take real time, and a revoke or a narrower re-mint landing inside them was invisible
693
+ * to the check between steps. This one runs after construction; the synchronous assertion below then
694
+ * acts on what it found, with nothing awaited between it and the hand-off.
695
+ */
696
+ const afterBuild = refreshSeen;
697
+ const beforeSubmit = () => {
698
+ if (!lastSeenGrantOk) {
699
+ return refuse('DELEGATION_NOT_ACTIVE', 'the delegation changed while this transaction was being built; it is not submitted');
700
+ }
701
+ assertWithdrawConsent();
702
+ assertWithinUserPerSwapCap(capGross, lastSeenCapUsd, capToken);
703
+ };
504
704
  // 6. SEND.
505
705
  const signer = new CdpDelegatedSigner(services.rig.rig, user, {
506
706
  sealedWait: services.sealedWait,
707
+ beforeEachStep,
708
+ afterBuild,
709
+ beforeSubmit,
710
+ // The grant this plan is bound to: the revocation marker is per grant, so the signer names one.
711
+ grantExpiresAt: grant.expiresAt,
507
712
  });
508
713
  let sent;
509
714
  try {
@@ -516,7 +721,7 @@ export async function submitUnderDelegation(runtime, rawInput, caller, _signal)
516
721
  submission.state = { kind: 'halted', refusal };
517
722
  throw refusal;
518
723
  }
519
- const result = verifiedResult(input, user, verified, sent, fence, grant.expiresAt);
724
+ const result = verifiedResult(input, user, verified, sent, { id: fence.id, rulesDigest: fence.rulesDigest, userCapUsd, name: fence.name }, grant.expiresAt);
520
725
  submission.state = { kind: 'sent', result };
521
726
  return result;
522
727
  }
@@ -576,7 +781,17 @@ export async function openDelegationServicesFromEnv(env) {
576
781
  }
577
782
  return Object.freeze({
578
783
  rig: createDelegationRig(cdp, pub),
784
+ pub,
579
785
  serverSecret: env.serverSecret,
786
+ // 🔴 An unconfigured store does NOT stop the boot — it opens UNAVAILABLE, and every delegated send
787
+ // then refuses `DELEGATION_CAP_STORE_UNAVAILABLE` by name. Refusing to boot would also take down the
788
+ // read-only tools (status, revoke, the public fence promise) and, in particular, the REVOKE that lets
789
+ // a person withdraw authority — the one control that must never be collateral damage.
790
+ capStore: await createDelegationCapStore(),
791
+ // Default FALSE in code: an operator who never sets the variable gets the paused surface, never an
792
+ // unfenced first rollout. Read once at boot, so a mid-life environment change needs a redeploy —
793
+ // which is exactly what the rollout runbook does.
794
+ mintEnabled: resolveDelegationMintEnabled(),
580
795
  ...(env.publicProjectId ? { publicProjectId: env.publicProjectId } : {}),
581
796
  });
582
797
  }
@@ -1,4 +1,4 @@
1
- export declare const EXECUTION_REFUSAL_CODES: readonly ["ATTRIBUTION_MISSING", "LIFI_FEE_CAP_EXCEEDED", "ACCOUNT_BINDING_MISMATCH", "DIGEST_MISMATCH", "ARTIFACT_INVALID", "STALE_ARTIFACT", "EXECUTION_RATE_LIMITED", "EXECUTION_CONCURRENCY_FULL", "UNSUPPORTED_ACCOUNT_PROFILE", "LIFI_QUOTE_FAILED", "LIFI_QUOTE_INVALID", "UNSUPPORTED_LIFI_ROUTE", "POLYMARKET_METADATA_FAILED", "POLYMARKET_METADATA_INVALID", "UNSUPPORTED_POLYMARKET_MARKET", "POLYMARKET_ORDER_INVALID", "POLYMARKET_BUILDER_FEE_UNRULED", "HYPERLIQUID_META_FAILED", "HYPERLIQUID_META_INVALID", "UNSUPPORTED_HYPERLIQUID_MARKET", "HYPERLIQUID_ORDER_INVALID", "DELEGATION_UNAVAILABLE", "DELEGATION_UNAUTHORIZED", "DELEGATION_INPUT_INVALID", "DELEGATION_BUSY", "DELEGATION_ARTIFACT_IN_FLIGHT", "ARTIFACT_NOT_PREPARED_HERE", "PREPARED_ARTIFACT_CAPACITY", "ARTIFACT_ID_COLLISION", "DELEGATION_REPLAY_MISMATCH", "DELEGATION_FENCE_ABSENT", "DELEGATION_FENCE_FOREIGN", "DELEGATION_FENCE_AMBIGUOUS", "DELEGATION_FENCE_NEWER", "DELEGATION_TOKEN_INVALID", "DELEGATION_REVOKE_NOT_CONFIRMED", "DELEGATION_NOT_ACTIVE", "DELEGATION_EXPIRY_EXCEEDS_BOUND", "DELEGATION_USER_UNKNOWN", "DELEGATION_OUTSIDE_FENCE", "DELEGATED_PLAN_REFUSED", "DELEGATED_PLAN_HALTED", "DELEGATION_UPSTREAM_FAILED"];
1
+ export declare const EXECUTION_REFUSAL_CODES: readonly ["ATTRIBUTION_MISSING", "LIFI_FEE_CAP_EXCEEDED", "ACCOUNT_BINDING_MISMATCH", "DIGEST_MISMATCH", "ARTIFACT_INVALID", "STALE_ARTIFACT", "EXECUTION_RATE_LIMITED", "EXECUTION_CONCURRENCY_FULL", "UNSUPPORTED_ACCOUNT_PROFILE", "LIFI_QUOTE_FAILED", "LIFI_QUOTE_INVALID", "UNSUPPORTED_LIFI_ROUTE", "POLYMARKET_METADATA_FAILED", "POLYMARKET_METADATA_INVALID", "UNSUPPORTED_POLYMARKET_MARKET", "POLYMARKET_ORDER_INVALID", "POLYMARKET_BUILDER_FEE_UNRULED", "HYPERLIQUID_META_FAILED", "HYPERLIQUID_META_INVALID", "UNSUPPORTED_HYPERLIQUID_MARKET", "HYPERLIQUID_ORDER_INVALID", "DELEGATION_UNAVAILABLE", "DELEGATION_UNAUTHORIZED", "DELEGATION_INPUT_INVALID", "DELEGATION_BUSY", "DELEGATION_ARTIFACT_IN_FLIGHT", "ARTIFACT_NOT_PREPARED_HERE", "PREPARED_ARTIFACT_CAPACITY", "ARTIFACT_ID_COLLISION", "DELEGATION_REPLAY_MISMATCH", "DELEGATION_FENCE_ABSENT", "DELEGATION_FENCE_FOREIGN", "DELEGATION_FENCE_AMBIGUOUS", "DELEGATION_FENCE_NEWER", "DELEGATION_TOKEN_INVALID", "DELEGATION_REVOKE_NOT_CONFIRMED", "DELEGATION_NOT_ACTIVE", "DELEGATION_EXPIRY_EXCEEDS_BOUND", "DELEGATION_USER_UNKNOWN", "DELEGATION_OUTSIDE_FENCE", "DELEGATION_ABOVE_USER_CAP", "DELEGATION_CAP_UNRECORDED", "DELEGATION_CAP_GRANT_CHANGED", "DELEGATION_CAP_EXPIRY_UNAVAILABLE", "DELEGATION_CAP_UNCONFIRMED", "DELEGATION_CAP_MISMATCH", "DELEGATION_CAP_RULESET_STALE", "DELEGATION_CAP_SUPERSEDED", "DELEGATION_CAP_STORE_UNAVAILABLE", "DELEGATION_MINT_PAUSED", "DELEGATION_CAP_CANCELLED", "DELEGATED_PLAN_REFUSED", "DELEGATED_PLAN_HALTED", "DELEGATION_UPSTREAM_FAILED", "YIELD_MARKET_UNKNOWN", "YIELD_REGISTRY_MISMATCH", "YIELD_PREVIEW_UNAVAILABLE", "YIELD_DEPOSIT_PARITY", "YIELD_WITHDRAW_PARITY", "DELEGATION_WITHDRAW_UNCONSENTED", "EQUITY_UNKNOWN", "EQUITY_REGISTRY_MISMATCH", "EQUITY_ORACLE_UNAVAILABLE", "EQUITY_ORACLE_FROZEN", "STOCK_BUY_PARITY"];
2
2
  export type ExecutionRefusalCode = (typeof EXECUTION_REFUSAL_CODES)[number];
3
3
  /** A fail-closed, machine-readable refusal. No upstream response bodies or secrets are retained. */
4
4
  export declare class ExecutionRefusal extends Error {
@@ -109,12 +109,110 @@ export const EXECUTION_REFUSAL_CODES = [
109
109
  'DELEGATION_USER_UNKNOWN',
110
110
  /** A verified plan the fence would refuse anyway: input token not allow-listed, spender not the reviewed diamond, gross over the per-swap cap. */
111
111
  'DELEGATION_OUTSIDE_FENCE',
112
+ /**
113
+ * The plan is inside the ENGINE's fence but over the per-swap cap THIS USER chose when they minted.
114
+ * Deliberately distinct from `DELEGATION_OUTSIDE_FENCE`: that code means "Coinbase would refuse this
115
+ * too"; this one means "the engine would have signed it and Otto did not, because the person set a
116
+ * lower number." Conflating them would make the user's own choice indistinguishable from the shared
117
+ * ceiling, and no test could tell whether the chosen cap was ever consulted.
118
+ */
119
+ 'DELEGATION_ABOVE_USER_CAP',
120
+ /**
121
+ * The grant that reads back from CDP carries NO recorded per-swap cap on this server, so what the person
122
+ * agreed to is unknown. Fail-closed: nothing is submitted, and the remedy is a fresh mint (which records
123
+ * one). Never defaulted to the ceiling — a grant minted for $10 must not become a $5,000 grant because
124
+ * its record could not be found.
125
+ */
126
+ 'DELEGATION_CAP_UNRECORDED',
127
+ /**
128
+ * CONFIRM FAILED: the instant CDP reports for the live grant is not the instant this mint RESERVED.
129
+ *
130
+ * Round 3 makes `(userId, expiry)` a grant identity by construction — the server picks a whole second no
131
+ * durable row for that user occupies, writes the cap against it, and the page mints with exactly that
132
+ * second. This code is what happens when that construction does not hold: either another actor's grant
133
+ * is what reads back (replaced between the reservation and the mint), or CDP stored something other than
134
+ * what was asked for. Both mean the live permission is not the one this cap was chosen for, so nothing is
135
+ * blessed and the fresh grant is REVOKED rather than left standing with a cap it was never matched to.
136
+ */
137
+ 'DELEGATION_CAP_GRANT_CHANGED',
138
+ /**
139
+ * The offset ladder found no free whole-second instant at or before the requested expiry within its
140
+ * bound. Nothing is reserved and nothing is minted: handing out an instant a reservation already holds
141
+ * would put two permissions on one enforcement key, which is the collision this design removes.
142
+ */
143
+ 'DELEGATION_CAP_EXPIRY_UNAVAILABLE',
144
+ /**
145
+ * 🔴 THE SEND GATE (round 4). No CONFIRMED-ACTIVE record stands for this end user, so no permission
146
+ * has completed `reserved → confirmed_active` on this server. A reservation on its own authorises
147
+ * NOTHING: that is what stops a grant minted at a reserved instant by any client — the agent CLI, an
148
+ * SDK caller, a second tab — from inheriting a cap it never confirmed. Never defaulted to the ceiling.
149
+ */
150
+ 'DELEGATION_CAP_UNCONFIRMED',
151
+ /**
152
+ * A confirmed-active record stands, but the grant CDP reports right now is not the one it names. The
153
+ * live permission is a different one — replaced, or minted elsewhere — so nothing is submitted under
154
+ * it. Distinct from `DELEGATION_CAP_UNCONFIRMED`: that means nothing was ever confirmed; this means
155
+ * something was, and it is not what is live.
156
+ */
157
+ 'DELEGATION_CAP_MISMATCH',
158
+ /**
159
+ * The record requires a NEWER cap ruleset than this worker implements. A rolling deploy runs two
160
+ * binaries at once, and the Coinbase fence is identical on both — so an older worker that cannot
161
+ * honour a per-user cap would execute a new permission at the shared ceiling. It refuses instead.
162
+ */
163
+ 'DELEGATION_CAP_RULESET_STALE',
164
+ /**
165
+ * The reservation was confirmed once and a later confirmation replaced it. It is never revived: a
166
+ * superseded permission returning to active is a replaced cap coming back, and on a wider number that
167
+ * is the widening this unit exists to prevent.
168
+ */
169
+ 'DELEGATION_CAP_SUPERSEDED',
170
+ /**
171
+ * The per-swap cap store itself is unavailable — no durable path is configured, or it could not be read.
172
+ * Every delegated send refuses while that is true, because an unreadable store is indistinguishable from
173
+ * an empty one and the difference decides whether a user's cap is honoured.
174
+ */
175
+ 'DELEGATION_CAP_STORE_UNAVAILABLE',
176
+ /**
177
+ * This worker is not creating NEW permissions right now (`DELEGATION_MINT_ENABLED` is not set true) —
178
+ * the staged first deployment. It gates the RESERVE only: a permission that already stands keeps
179
+ * working under its own cap, an in-flight mint can still be confirmed, and revoke always works.
180
+ */
181
+ 'DELEGATION_MINT_PAUSED',
182
+ /**
183
+ * The person pressed Stop while this permission was being created, so the reservation it was minted
184
+ * against was cancelled. The fresh grant is revoked and nothing is confirmed for it. Never a retry:
185
+ * a Stop that leaves a permission behind is not a Stop.
186
+ */
187
+ 'DELEGATION_CAP_CANCELLED',
112
188
  /** The user-side verifier refused the envelope with an adapter code the constructor does not share; the message carries `[CODE]`. */
113
189
  'DELEGATED_PLAN_REFUSED',
114
190
  /** The plan halted mid-way; the message reports the failed step and whether the allowance clear read back zero. */
115
191
  'DELEGATED_PLAN_HALTED',
116
192
  /** A CDP or chain read/write failed outright; no upstream body is relayed. */
117
193
  'DELEGATION_UPSTREAM_FAILED',
194
+ /** `otto_prepare_yield_deposit`: the executableMarketId names no executable Morpho market in this server's registry. */
195
+ 'YIELD_MARKET_UNKNOWN',
196
+ /** The caller's registryCommitment is not the commitment recomputed over the CURRENT registry record (stale or foreign row). */
197
+ 'YIELD_REGISTRY_MISMATCH',
198
+ /** The vault's own previewDeposit could not be read, so deposit parity cannot be checked; nothing is prepared. */
199
+ 'YIELD_PREVIEW_UNAVAILABLE',
200
+ /** The route quotes fewer shares than the vault's own previewDeposit by more than the pinned tolerance. */
201
+ 'YIELD_DEPOSIT_PARITY',
202
+ /** `otto_prepare_yield_withdraw`: the route quotes less USDC than the vault's own previewRedeem by more than the pinned tolerance. */
203
+ 'YIELD_WITHDRAW_PARITY',
204
+ /** A delegated vault withdraw under a permission minted before cap ruleset v2 (no consent to withdraws); re-mint. */
205
+ 'DELEGATION_WITHDRAW_UNCONSENTED',
206
+ /** `otto_prepare_stock_buy`: the executableEquityId names no executable equity in this server's registry (or the row fails its own pins). */
207
+ 'EQUITY_UNKNOWN',
208
+ /** The caller's registryCommitment is not the commitment recomputed over the CURRENT equity record (stale or foreign row). */
209
+ 'EQUITY_REGISTRY_MISMATCH',
210
+ /** The equity's Chainlink feed could not be read (or reads non-positive), so price parity cannot be checked; nothing is prepared. */
211
+ 'EQUITY_ORACLE_UNAVAILABLE',
212
+ /** The equity's Chainlink mark is older than the pinned frozen threshold (feeds hold their last value off-hours); a delegated buy waits for a live reference. */
213
+ 'EQUITY_ORACLE_FROZEN',
214
+ /** The route's gross implied price exceeds the equity's own Chainlink mark by more than the pinned tolerance. */
215
+ 'STOCK_BUY_PARITY',
118
216
  ];
119
217
  /** A fail-closed, machine-readable refusal. No upstream response bodies or secrets are retained. */
120
218
  export class ExecutionRefusal extends Error {
@@ -11,7 +11,8 @@ export { swapIntentDigestInput } from './execution-intent.js';
11
11
  export { artifactCommitmentDigest, assertArtifactAccountBinding, assertArtifactCommitment, assertArtifactEnvelope, assertArtifactFresh, assertArtifactPayloadDigest, executionDigest, MAX_ARTIFACT_TTL_MS, } from './execution-types.js';
12
12
  export type { AccountProfile, ExecutionArtifact, ExecutionEnvelope, ExecutionPayload, } from './execution-types.js';
13
13
  export { ExecutionRefusal, EXECUTION_REFUSAL_CODES } from './execution-errors.js';
14
- export { MODEL_B_DELEGATION_EXPIRY_DEFAULT_DAYS, MODEL_B_DELEGATION_EXPIRY_MAX_DAYS, MODEL_B_INPUT_TOKENS_V1, MODEL_B_PER_SWAP_CAP_USD, boundedDelegationExpiry, MODEL_B_POLICY_DIGEST_V1, MODEL_B_POLICY_NAME_V1, modelBProjectPolicyBody, modelBProjectPolicyRules, policyRulesDigest, readBackModelBFence, type DelegationPolicyRecord, type ModelBFenceReadBack, } from './execution-delegation-policy.js';
14
+ export { MODEL_B_DELEGATION_EXPIRY_DEFAULT_DAYS, MODEL_B_DELEGATION_EXPIRY_MAX_DAYS, MODEL_B_DELEGATION_EXPIRY_MIN_DAYS, MODEL_B_INPUT_TOKENS_V1, MODEL_B_MINT_EXPIRY_FILLER_MS, mintExpiryFor, MODEL_B_PER_SWAP_CAP_MIN_USD, MODEL_B_PER_SWAP_CAP_USD, boundedDelegationExpiry, boundedPerSwapCap, assertWithinUserPerSwapCap, MODEL_B_POLICY_DIGEST_V1, MODEL_B_POLICY_NAME_V1, MODEL_B_POLICY_NAME_V2, MODEL_B_POLICY_NAME, MODEL_B_POLICY_DIGEST_V2, MODEL_B_POLICY_DIGEST, MODEL_B_POLICY_DIGEST_BY_NAME, MODEL_B_POLICY_ACCEPTED_VERSIONS, MODEL_B_WITHDRAW_TOKENS_V2, MODEL_B_WITHDRAW_SHARE_CAP_ATOMIC, assertWithdrawWithinModelBFence, fenceIsCurrent, modelBProjectPolicyBody, modelBProjectPolicyRules, policyRulesDigest, readBackModelBFence, type DelegationPolicyRecord, type ModelBFenceReadBack, } from './execution-delegation-policy.js';
15
15
  export { DELEGATED_EXECUTION_TOOL_NAME, DELEGATION_AUTH_HEADER, SUBMIT_UNDER_DELEGATION_INPUT_SCHEMA, DELEGATED_SEND_RESULT_SCHEMA, type DelegatedSendResult, type SubmitUnderDelegationInput, } from './execution-delegated-definition.js';
16
16
  export { DEFAULT_LIFI_AGGREGATE_FEE_CAP_RATE, DEFAULT_LIFI_FEE_BPS, DEFAULT_LIFI_FEE_CONFIG_VERSION, DEFAULT_LIFI_PLATFORM_FEE_RECIPIENT, DEFAULT_OTTO_LIFI_FEE_RECIPIENT, LIFI_DIAMOND_ADDRESS, LIFI_FEE_FORWARDERS_BY_CHAIN, LIFI_FEE_FORWARDER_V1, LIFI_FEE_FORWARDER_V1_MAINNET, LIFI_FEE_FORWARDER_V2, LIFI_INTEGRATOR, isReviewedLiFiFeeForwarder, reviewedLiFiFeeForwarders, unreviewedFeeForwarderMessage, } from './execution-config.js';
17
- export { DELEGATION_ADMIN_TOOL_NAMES, DELEGATION_FENCE_INPUT_SCHEMA, DELEGATION_FENCE_RESULT_SCHEMA, DELEGATION_FENCE_STATUS_INPUT_SCHEMA, DELEGATION_FENCE_STATUS_RESULT_SCHEMA, DELEGATION_FENCE_STATUS_TOOL_NAME, DELEGATION_FENCE_TOOL_NAME, DELEGATION_REVOKE_INPUT_SCHEMA, DELEGATION_REVOKE_RESULT_SCHEMA, DELEGATION_REVOKE_TOOL_NAME, DELEGATION_STATUS_INPUT_SCHEMA, DELEGATION_STATUS_RESULT_SCHEMA, DELEGATION_STATUS_TOOL_NAME, type DelegationFenceResult, type DelegationFenceStatusResult, type DelegationRevokeResult, type DelegationStatusResult, } from './execution-delegation-admin-definition.js';
17
+ export { DELEGATION_ADMIN_TOOL_NAMES, DELEGATION_CAP_INPUT_SCHEMA, DELEGATION_CAP_RESULT_SCHEMA, DELEGATION_CAP_TOOL_NAME, DELEGATION_FENCE_INPUT_SCHEMA, DELEGATION_FENCE_RESULT_SCHEMA, DELEGATION_FENCE_STATUS_INPUT_SCHEMA, DELEGATION_FENCE_STATUS_RESULT_SCHEMA, DELEGATION_FENCE_STATUS_TOOL_NAME, DELEGATION_FENCE_TOOL_NAME, DELEGATION_REVOKE_INPUT_SCHEMA, DELEGATION_REVOKE_RESULT_SCHEMA, DELEGATION_REVOKE_TOOL_NAME, DELEGATION_STATUS_INPUT_SCHEMA, DELEGATION_STATUS_RESULT_SCHEMA, DELEGATION_STATUS_TOOL_NAME, type DelegationCapResult, type DelegationFenceResult, type DelegationFenceStatusResult, type DelegationRevokeResult, type DelegationStatusResult, } from './execution-delegation-admin-definition.js';
18
+ export { CAP_EXPIRY_MAX_OFFSET_STEPS, DELEGATION_CAP_STORE_PATH_ENV, DelegationCapExpiryUnavailableError, MINT_KEY_PATTERN, createDelegationCapStore, firstFreeExpiryMs, inMemoryDelegationCapStore, resolveDelegationCapStorePath, unavailableDelegationCapStore, wholeSecondMs, type DelegationCapRecord, type DelegationCapReservation, type DelegationCapStore, } from './execution-delegation-cap-store.js';
@@ -9,8 +9,9 @@ export { EXECUTION_ARTIFACT_SCHEMA, EXECUTION_ENVELOPE_SCHEMA, PREPARE_SWAP_INPU
9
9
  export { swapIntentDigestInput } from './execution-intent.js';
10
10
  export { artifactCommitmentDigest, assertArtifactAccountBinding, assertArtifactCommitment, assertArtifactEnvelope, assertArtifactFresh, assertArtifactPayloadDigest, executionDigest, MAX_ARTIFACT_TTL_MS, } from './execution-types.js';
11
11
  export { ExecutionRefusal, EXECUTION_REFUSAL_CODES } from './execution-errors.js';
12
- export { MODEL_B_DELEGATION_EXPIRY_DEFAULT_DAYS, MODEL_B_DELEGATION_EXPIRY_MAX_DAYS, MODEL_B_INPUT_TOKENS_V1, MODEL_B_PER_SWAP_CAP_USD, boundedDelegationExpiry, MODEL_B_POLICY_DIGEST_V1, MODEL_B_POLICY_NAME_V1, modelBProjectPolicyBody, modelBProjectPolicyRules, policyRulesDigest, readBackModelBFence, } from './execution-delegation-policy.js';
12
+ export { MODEL_B_DELEGATION_EXPIRY_DEFAULT_DAYS, MODEL_B_DELEGATION_EXPIRY_MAX_DAYS, MODEL_B_DELEGATION_EXPIRY_MIN_DAYS, MODEL_B_INPUT_TOKENS_V1, MODEL_B_MINT_EXPIRY_FILLER_MS, mintExpiryFor, MODEL_B_PER_SWAP_CAP_MIN_USD, MODEL_B_PER_SWAP_CAP_USD, boundedDelegationExpiry, boundedPerSwapCap, assertWithinUserPerSwapCap, MODEL_B_POLICY_DIGEST_V1, MODEL_B_POLICY_NAME_V1, MODEL_B_POLICY_NAME_V2, MODEL_B_POLICY_NAME, MODEL_B_POLICY_DIGEST_V2, MODEL_B_POLICY_DIGEST, MODEL_B_POLICY_DIGEST_BY_NAME, MODEL_B_POLICY_ACCEPTED_VERSIONS, MODEL_B_WITHDRAW_TOKENS_V2, MODEL_B_WITHDRAW_SHARE_CAP_ATOMIC, assertWithdrawWithinModelBFence, fenceIsCurrent, modelBProjectPolicyBody, modelBProjectPolicyRules, policyRulesDigest, readBackModelBFence, } from './execution-delegation-policy.js';
13
13
  export { DELEGATED_EXECUTION_TOOL_NAME, DELEGATION_AUTH_HEADER, SUBMIT_UNDER_DELEGATION_INPUT_SCHEMA, DELEGATED_SEND_RESULT_SCHEMA, } from './execution-delegated-definition.js';
14
14
  export { DEFAULT_LIFI_AGGREGATE_FEE_CAP_RATE, DEFAULT_LIFI_FEE_BPS, DEFAULT_LIFI_FEE_CONFIG_VERSION, DEFAULT_LIFI_PLATFORM_FEE_RECIPIENT, DEFAULT_OTTO_LIFI_FEE_RECIPIENT, LIFI_DIAMOND_ADDRESS, LIFI_FEE_FORWARDERS_BY_CHAIN, LIFI_FEE_FORWARDER_V1, LIFI_FEE_FORWARDER_V1_MAINNET, LIFI_FEE_FORWARDER_V2, LIFI_INTEGRATOR, isReviewedLiFiFeeForwarder, reviewedLiFiFeeForwarders, unreviewedFeeForwarderMessage, } from './execution-config.js';
15
- export { DELEGATION_ADMIN_TOOL_NAMES, DELEGATION_FENCE_INPUT_SCHEMA, DELEGATION_FENCE_RESULT_SCHEMA, DELEGATION_FENCE_STATUS_INPUT_SCHEMA, DELEGATION_FENCE_STATUS_RESULT_SCHEMA, DELEGATION_FENCE_STATUS_TOOL_NAME, DELEGATION_FENCE_TOOL_NAME, DELEGATION_REVOKE_INPUT_SCHEMA, DELEGATION_REVOKE_RESULT_SCHEMA, DELEGATION_REVOKE_TOOL_NAME, DELEGATION_STATUS_INPUT_SCHEMA, DELEGATION_STATUS_RESULT_SCHEMA, DELEGATION_STATUS_TOOL_NAME, } from './execution-delegation-admin-definition.js';
15
+ export { DELEGATION_ADMIN_TOOL_NAMES, DELEGATION_CAP_INPUT_SCHEMA, DELEGATION_CAP_RESULT_SCHEMA, DELEGATION_CAP_TOOL_NAME, DELEGATION_FENCE_INPUT_SCHEMA, DELEGATION_FENCE_RESULT_SCHEMA, DELEGATION_FENCE_STATUS_INPUT_SCHEMA, DELEGATION_FENCE_STATUS_RESULT_SCHEMA, DELEGATION_FENCE_STATUS_TOOL_NAME, DELEGATION_FENCE_TOOL_NAME, DELEGATION_REVOKE_INPUT_SCHEMA, DELEGATION_REVOKE_RESULT_SCHEMA, DELEGATION_REVOKE_TOOL_NAME, DELEGATION_STATUS_INPUT_SCHEMA, DELEGATION_STATUS_RESULT_SCHEMA, DELEGATION_STATUS_TOOL_NAME, } from './execution-delegation-admin-definition.js';
16
+ export { CAP_EXPIRY_MAX_OFFSET_STEPS, DELEGATION_CAP_STORE_PATH_ENV, DelegationCapExpiryUnavailableError, MINT_KEY_PATTERN, createDelegationCapStore, firstFreeExpiryMs, inMemoryDelegationCapStore, resolveDelegationCapStorePath, unavailableDelegationCapStore, wholeSecondMs, } from './execution-delegation-cap-store.js';
16
17
  //# sourceMappingURL=execution-index.js.map
@@ -1,6 +1,6 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
3
- import { ensureDelegationFence, readDelegationStatus, revokeDelegation } from './execution-delegation-admin.js';
3
+ import { ensureDelegationFence, readDelegationStatus, reserveDelegationCap, revokeDelegation } from './execution-delegation-admin.js';
4
4
  import { type ExecutionToolDefinition } from './execution-tool-definitions.js';
5
5
  import { type ExecutionToolRuntime } from './execution-tools.js';
6
6
  export declare const EXECUTION_TOOL_ANNOTATIONS: Readonly<{
@@ -11,6 +11,7 @@ export declare const EXECUTION_TOOL_ANNOTATIONS: Readonly<{
11
11
  }>;
12
12
  export declare function executionErrorResult(error: unknown): CallToolResult;
13
13
  export declare function callExecutionDefinition(runtime: ExecutionToolRuntime, definition: ExecutionToolDefinition, rawInput: unknown, signal?: AbortSignal): Promise<CallToolResult>;
14
+ export declare function callYieldDefinition(prepare: (runtime: ExecutionToolRuntime, rawInput: unknown, signal?: AbortSignal) => Promise<unknown>, runtime: ExecutionToolRuntime, rawInput: unknown, signal?: AbortSignal): Promise<CallToolResult>;
14
15
  /** A failure that is not a refusal is an upstream fault; nothing of it is relayed. */
15
16
  export declare function delegatedErrorResult(error: unknown): CallToolResult;
16
17
  /** The caller comes ONLY from the request headers; a value placed in the arguments is never consulted. */
@@ -47,7 +48,7 @@ declare const DELEGATION_ADMIN_TOOLS: readonly [{
47
48
  readonly definition: Readonly<{
48
49
  name: "otto_delegation_status";
49
50
  title: "Read one end user's delegation status (token-bound)";
50
- description: `Read back, for the end user identified by a CDP access token, their CDP accounts, whether the Model-B fence is present, and whether an active delegation reads back (with its expiry). Arguments: { accessToken } \u2014 the server resolves the user from the token; a client-sent user id is refused. Output is bound to that user; nothing about any other user is returned. ${string}`;
51
+ description: `Read back, for the end user identified by a CDP access token, their CDP accounts, whether the Model-B fence is present, and whether an active delegation reads back (with its expiry and the per-swap cap recorded for it). Arguments: { accessToken } \u2014 the server resolves the user from the token; a client-sent user id is refused. delegation.per_swap_cap_usd is the number THIS user chose at mint time; it is ABSENT when no cap is recorded for the grant, which means the delegated send will refuse it \u2014 say so rather than showing the shared ceiling. fence.per_swap_cap_usd is that shared ceiling and is never a per-user number. Output is bound to that user; nothing about any other user is returned. ${string}`;
51
52
  inputSchema: import("zod").ZodObject<{}, import("zod/v4/core").$loose>;
52
53
  strictInputSchema: import("zod").ZodObject<{
53
54
  accessToken: import("zod").ZodString;
@@ -64,8 +65,11 @@ declare const DELEGATION_ADMIN_TOOLS: readonly [{
64
65
  delegation: import("zod").ZodObject<{
65
66
  active: import("zod").ZodBoolean;
66
67
  expires_at: import("zod").ZodOptional<import("zod").ZodString>;
68
+ per_swap_cap_usd: import("zod").ZodOptional<import("zod").ZodNumber>;
67
69
  }, import("zod/v4/core").$strict>;
68
70
  revocation_requested_here: import("zod").ZodBoolean;
71
+ ruleset_version: import("zod").ZodNumber;
72
+ mint_enabled: import("zod").ZodBoolean;
69
73
  }, import("zod/v4/core").$strict>;
70
74
  annotations: Readonly<{
71
75
  readOnlyHint: true;
@@ -104,6 +108,51 @@ declare const DELEGATION_ADMIN_TOOLS: readonly [{
104
108
  }>;
105
109
  }>;
106
110
  readonly run: typeof revokeDelegation;
111
+ }, {
112
+ readonly definition: Readonly<{
113
+ name: "otto_delegation_cap";
114
+ title: "Reserve the expiry a delegation is minted with, record the chosen per-swap cap against it, then confirm the mint";
115
+ description: `TWO PHASES, both header authed, both resolving the end user from the access token. { phase: "reserve", accessToken, perSwapCapUsd, requestedDays, mintKey } picks a whole-second expiry instant at or BEFORE the requested duration that no durable row for this user already occupies, writes the chosen cap (whole USD, 1\u20265000) against it, and answers { expires_at, mint_expires_at, per_swap_cap_usd, recorded }. Call createDelegation with mint_expires_at EXACTLY as given \u2014 it is the reserved instant plus a one-millisecond filler, because CDP refuses an expiry ending .000 and truncates the filler away \u2014 and with mintKey as the idempotencyKey. Then { phase: "confirm", accessToken, mintKey } re-reads the grant from CDP and refuses (DELEGATION_CAP_GRANT_CHANGED) unless the instant CDP reports IS the reserved one, revoking the fresh grant when it is not. Why this shape: Coinbase issues no grant id and a send can learn nothing about a live grant except its expiry, so the expiry is made an identity BY CONSTRUCTION \u2014 reserved server-side, recorded before the mint, asserted after it. Until a reservation exists, otto_submit_under_delegation refuses that delegation (DELEGATION_CAP_UNRECORDED) rather than falling back to the 5000 USD ceiling. A reservation is IMMUTABLE per mint key \u2014 pressing again returns the same instant and the cap already in force, so raising a cap means minting a new permission (which the user is told replaces the old one). Coinbase's engine cannot hold this number: end-user accounts are fenced by ONE project-scope policy shared by every delegated user, so a lower per-user cap is enforced by Otto's server alone. ${string}`;
116
+ inputSchema: import("zod").ZodObject<{}, import("zod/v4/core").$loose>;
117
+ strictInputSchema: import("zod").ZodDiscriminatedUnion<[import("zod").ZodObject<{
118
+ phase: import("zod").ZodLiteral<"reserve">;
119
+ accessToken: import("zod").ZodString;
120
+ perSwapCapUsd: import("zod").ZodNumber;
121
+ requestedDays: import("zod").ZodNumber;
122
+ mintKey: import("zod").ZodString;
123
+ }, import("zod/v4/core").$strict>, import("zod").ZodObject<{
124
+ phase: import("zod").ZodLiteral<"confirm">;
125
+ accessToken: import("zod").ZodString;
126
+ mintKey: import("zod").ZodString;
127
+ }, import("zod/v4/core").$strict>], "phase">;
128
+ outputSchema: import("zod").ZodObject<{
129
+ phase: import("zod").ZodEnum<{
130
+ reserve: "reserve";
131
+ confirm: "confirm";
132
+ }>;
133
+ user_id: import("zod").ZodString;
134
+ expires_at: import("zod").ZodString;
135
+ mint_expires_at: import("zod").ZodOptional<import("zod").ZodString>;
136
+ per_swap_cap_usd: import("zod").ZodNumber;
137
+ state: import("zod").ZodEnum<{
138
+ legacy: "legacy";
139
+ reserved: "reserved";
140
+ confirmed_active: "confirmed_active";
141
+ superseded: "superseded";
142
+ revoked: "revoked";
143
+ expired: "expired";
144
+ }>;
145
+ ruleset_version: import("zod").ZodNumber;
146
+ recorded: import("zod").ZodBoolean;
147
+ }, import("zod/v4/core").$strict>;
148
+ annotations: Readonly<{
149
+ readOnlyHint: false;
150
+ destructiveHint: false;
151
+ idempotentHint: true;
152
+ openWorldHint: true;
153
+ }>;
154
+ }>;
155
+ readonly run: typeof reserveDelegationCap;
107
156
  }];
108
157
  export declare function callDelegationAdminDefinition(runtime: ExecutionToolRuntime, run: (typeof DELEGATION_ADMIN_TOOLS)[number]['run'], rawInput: unknown, headers: Readonly<Record<string, string | string[] | undefined>> | undefined): Promise<CallToolResult>;
109
158
  export interface ExecutionCallAdmission {
@@ -1,9 +1,15 @@
1
1
  import { ExecutionRefusal, isExecutionRefusal } from './execution-errors.js';
2
2
  import { DELEGATED_EXECUTION_TOOL_DEFINITION, DELEGATED_TOOL_ANNOTATIONS, } from './execution-delegated-definition.js';
3
3
  import { callerFromHeaders, submitUnderDelegation } from './execution-delegation.js';
4
- import { DELEGATION_FENCE_STATUS_TOOL_DEFINITION, DELEGATION_FENCE_TOOL_DEFINITION, DELEGATION_REVOKE_TOOL_DEFINITION, DELEGATION_STATUS_TOOL_DEFINITION, } from './execution-delegation-admin-definition.js';
5
- import { ensureDelegationFence, readDelegationFenceStatus, readDelegationStatus, revokeDelegation, } from './execution-delegation-admin.js';
4
+ import { DELEGATION_CAP_TOOL_DEFINITION, DELEGATION_FENCE_STATUS_TOOL_DEFINITION, DELEGATION_FENCE_TOOL_DEFINITION, DELEGATION_REVOKE_TOOL_DEFINITION, DELEGATION_STATUS_TOOL_DEFINITION, } from './execution-delegation-admin-definition.js';
5
+ import { ensureDelegationFence, readDelegationFenceStatus, readDelegationStatus, reserveDelegationCap, revokeDelegation, } from './execution-delegation-admin.js';
6
6
  import { EXECUTION_TOOL_DEFINITIONS, } from './execution-tool-definitions.js';
7
+ import { YIELD_DEPOSIT_TOOL_DEFINITION } from './yield-deposit-definition.js';
8
+ import { prepareYieldDeposit } from './yield-deposit.js';
9
+ import { STOCK_BUY_TOOL_DEFINITION } from './stock-buy-definition.js';
10
+ import { prepareStockBuy } from './stock-buy.js';
11
+ import { YIELD_WITHDRAW_TOOL_DEFINITION } from './yield-withdraw-definition.js';
12
+ import { prepareYieldWithdraw } from './yield-withdraw.js';
7
13
  import { prepareBridge, preparePerpOrder, preparePolymarketOrder, prepareSwap, } from './execution-tools.js';
8
14
  export const EXECUTION_TOOL_ANNOTATIONS = Object.freeze({
9
15
  readOnlyHint: true,
@@ -46,6 +52,18 @@ export async function callExecutionDefinition(runtime, definition, rawInput, sig
46
52
  return executionErrorResult(error);
47
53
  }
48
54
  }
55
+ export async function callYieldDefinition(prepare, runtime, rawInput, signal) {
56
+ try {
57
+ const output = await prepare(runtime, rawInput, signal);
58
+ return {
59
+ content: [{ type: 'text', text: stringify(output) }],
60
+ structuredContent: output,
61
+ };
62
+ }
63
+ catch (error) {
64
+ return executionErrorResult(error);
65
+ }
66
+ }
49
67
  function delegatedSuccessResult(result) {
50
68
  return {
51
69
  content: [{ type: 'text', text: stringify(result) }],
@@ -72,6 +90,7 @@ const DELEGATION_ADMIN_TOOLS = [
72
90
  { definition: DELEGATION_FENCE_TOOL_DEFINITION, run: ensureDelegationFence },
73
91
  { definition: DELEGATION_STATUS_TOOL_DEFINITION, run: readDelegationStatus },
74
92
  { definition: DELEGATION_REVOKE_TOOL_DEFINITION, run: revokeDelegation },
93
+ { definition: DELEGATION_CAP_TOOL_DEFINITION, run: reserveDelegationCap },
75
94
  ];
76
95
  export async function callDelegationAdminDefinition(runtime, run, rawInput, headers) {
77
96
  try {
@@ -107,6 +126,30 @@ export function registerExecutionTools(server, runtime, admission) {
107
126
  return admission ? admission.run(call) : call();
108
127
  });
109
128
  }
129
+ // The Yield Copilot deposit constructor: registered where the delegated services are (it needs the
130
+ // rig's Base client for the vault's previewDeposit), read-only like every other prepare tool.
131
+ // Registered wherever the delegated set is; without a vault reader the tool refuses by name.
132
+ if (runtime.delegation) {
133
+ for (const yieldTool of [
134
+ { definition: YIELD_DEPOSIT_TOOL_DEFINITION, prepare: prepareYieldDeposit },
135
+ { definition: YIELD_WITHDRAW_TOOL_DEFINITION, prepare: prepareYieldWithdraw },
136
+ // The tokenized-stock buy constructor rides the same registration block: it needs the rig's
137
+ // Base client for the equity's Chainlink mark; without a reader it refuses by name.
138
+ { definition: STOCK_BUY_TOOL_DEFINITION, prepare: prepareStockBuy },
139
+ ]) {
140
+ const { definition, prepare } = yieldTool;
141
+ server.registerTool(definition.name, {
142
+ title: definition.title,
143
+ description: definition.description + freshness,
144
+ inputSchema: definition.inputSchema,
145
+ outputSchema: definition.outputSchema,
146
+ annotations: EXECUTION_TOOL_ANNOTATIONS,
147
+ }, (input, extra) => {
148
+ const call = () => callYieldDefinition(prepare, runtime, input, extra.signal);
149
+ return admission ? admission.run(call) : call();
150
+ });
151
+ }
152
+ }
110
153
  // The one tool that submits: registered only when the runtime carries the delegated services, with
111
154
  // its own (non-read-only) annotations, through the same per-identity admission as the prepare tools.
112
155
  if (runtime.delegation) {