otto-execute 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,151 +1,137 @@
1
1
  # otto-execute
2
2
 
3
- The **user's side** of Otto Model B.
4
-
5
- > **Agent path to a delegation — `otto-execute delegate`** (`otto-execute/delegate` entry): sign in as the end user by email OTP (typed on the terminal, never echoed, never written), mint a time-boxed CDP delegation to Otto's project (default 90 days, max 180), read it back, revoke it all from the agent's own process, end-user-side (no Otto credential is ever in it). Before minting it reads the hosted MCP's PUBLIC `otto_delegation_fence_status` and refuses when the Model-B fence is not present (and fails closed when the MCP cannot be reached). **This path is not documented or supported by Coinbase and can break on an SDK update; the browser page is the supported path.** `@coinbase/cdp-core` is an optional peer PINNED to `0.0.120` (the version proven live); a canary test drives its real web build on Node against a loopback fake on every dependency bump, and the shim breaking is a named refusal (`CDP_CORE_NODE_PATH_BROKEN`), never a silent failure. The fence source (Otto's MCP, `https://mcp.ottoai.services/mcp`) and the CDP SDK endpoint are **fixed** — no flag or environment variable redirects either, and the mint binds to the public project id the fence itself publishes (there is no `--project-id`). The command also refuses to run with any Otto credential or 64-hex value in its environment, and keeps the OTP out of argv by every spelling. **Threat model:** the residual after all of the above is a **platform universal**, not a property of this CLI — a caller who controls the process environment has pre-entry arbitrary code execution in ANY Node program (via `NODE_OPTIONS=--import=…`, `LD_PRELOAD`, or PATH-shadowing the `node` binary), so environment control is equivalent to process control. **An attacker with environment control of your shell already owns your OTP and every CLI on the machine.** Run the delegate path only in an environment you trust.
6
-
7
- Otto's hosted MCP (`otto-intel-mcp`, `https://mcp.ottoai.services`) constructs transactions
8
- **prepare-only**: `otto_prepare_swap` returns an unsigned, digest-committed envelope carrying a LI.FI
9
- swap with Otto's fee attribution baked in. Nothing in it is signed. This package is what **your**
10
- signer runs before and while signing one:
11
-
12
- 1. **Verify** — integrity (the constructor's own schema / digest / commitment / freshness / binding
13
- assertions), then **your intent** (input token and exact amount, the deposit legs pulling exactly
14
- that, the output token, and your own positive minimum output), then policy (reviewed router,
15
- `_receiver` == you, the fee leg decoded independently and matched to the ruled 10 bps to Otto's
16
- recipient through a reviewed FeeForwarder deployment, aggregate under the cap). Twelve named checks
17
- (+ `intent_digest` when you pass the exact request you sent). A verified plan is deep-frozen and
18
- registered by identity; signers additionally require the plan to be bound to THEIR address and chain, to
19
- cover their capabilities, and to be fresh at signing time — re-checked before every live step.
20
- 2. **Sign / submit** —
21
- - **BYO/EOA** (`EoaSigner`): your key signs **and** submits, one ordered transaction per step,
22
- halting and clearing a live allowance on any uncertain failure (the clear is confirmed by an
23
- `allowance` read-back, never inferred from a receipt). Freshness and binding are re-asserted
24
- immediately before every signature, after the nonce / fee / gas round trips. `signPlanDryRun`
25
- signs, decodes back and recovers without broadcasting.
26
- - **CDP-delegated** (`CdpDelegatedSigner`, from the `otto-execute/cdp` entry): you have already signed
27
- in and minted a time-boxed delegation; Otto's server submits your own transactions under it —
28
- user-authenticated, Otto-governed (project policy), Otto-revocable. No raw-submission surface exists
29
- on the signer; submission, allowance clearing and revocation are serialized on one lock per rig.
30
- - **x402** (`validateX402Challenge` + `EoaSigner.signX402Payment`): a 402 is validated against an
31
- exact-match table (resource, recipient, exact amount, exact 300 s window, pinned EIP-712 domain),
32
- revalidated immediately before signing, and the produced authorization is checked against the
33
- table row. The `table` parameter is public on purpose: it defaults to the built-in `X402_TABLE`
34
- (Otto's routes), and a caller may pass a narrower or newer table — the per-payment spend cap below
35
- bounds what any table can authorize, so a wrong table cannot sign for more than `$1`.
36
-
37
- Otto never holds your keys or funds on either path.
38
-
39
- ## Install
3
+ The user's side of Otto's execution seam. [Otto Intel MCP](https://www.npmjs.com/package/otto-intel-mcp) builds a swap as an unsigned, digest-committed **envelope** for an address you supply; `otto-execute` checks that envelope against **your own intent**, then signs and submits it with **your** key (or under a delegation you minted). CLI and library, one install.
4
+
5
+ - npm: `npm install otto-execute` — 19 packages including itself (`@x402/core`, `@x402/evm`, `viem`, `zod`), 0 `npm audit` findings.
6
+ - Docs: https://docs.useotto.xyz/acp-swarm/execution-seam
7
+ - Requires Node 22 or newer.
8
+
9
+ ## Quick start (under a minute)
10
+
11
+ **1. Get an envelope** — ask the hosted MCP (`https://mcp.ottoai.services`, free) for a swap on your address:
12
+
13
+ ```json
14
+ {
15
+ "name": "otto_prepare_swap",
16
+ "arguments": {
17
+ "accountProfile": { "kind": "eoa", "address": "0xYourAddress", "chainId": 8453 },
18
+ "chainId": 8453,
19
+ "fromToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
20
+ "toToken": "0x4200000000000000000000000000000000000006",
21
+ "fromAmount": "1000000",
22
+ "slippage": { "maxBps": 50, "minAmountOut": "200000000000000" },
23
+ "client_ref": "my-trade-1"
24
+ }
25
+ }
26
+ ```
27
+
28
+ Any MCP client works (connection snippets: the [otto-intel-mcp README](https://www.npmjs.com/package/otto-intel-mcp)); the tool result is one text part containing JSON — save that JSON object (it starts with `schema_version`, `artifact_id`, …) as `envelope.json`. It is valid for **5 minutes** from construction (`valid_until`; the hosted server's window — the tool description states it), and `submit` re-checks freshness before each of its three signatures, so don't pace on the window: verify, then submit, or fetch a fresh envelope. v1 builds Base (chain 8453) swaps.
29
+
30
+ **2. Write your intent** — what *you* meant, in atomic token units (USDC has 6 decimals: `1000000` = 1 USDC; WETH has 18: `200000000000000` = 0.0002 WETH). `minAmountOut` is your own floor from a price source you trust:
31
+
32
+ ```json
33
+ { "fromToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "toToken": "0x4200000000000000000000000000000000000006", "fromAmount": "1000000", "minAmountOut": "200000000000000" }
34
+ ```
35
+
36
+ **3. Verify, sign, submit:**
40
37
 
41
38
  ```bash
42
- npm install otto-execute
39
+ npx otto-execute verify --envelope envelope.json --intent intent.json --account 0xYourAddress # no key needed
40
+ OTTO_EXECUTE_PRIVATE_KEY=0x… npx otto-execute sign --envelope envelope.json --intent intent.json # dry run: signs + decodes back, broadcasts nothing
41
+ OTTO_EXECUTE_PRIVATE_KEY=0x… npx otto-execute submit --envelope envelope.json --intent intent.json --i-understand-mainnet
42
+ ```
43
+
44
+ `verify` on a real envelope prints (captured 2026-09-02):
45
+
46
+ ```json
47
+ {
48
+ "verified": {
49
+ "account": "0xF7270569486Ae94945360107721273C60dFdD825",
50
+ "checks": ["schema", "payload_digest", "commitment", "freshness", "account_binding", "capabilities", "chain", "plan_shape", "approvals", "swap_decode", "min_output", "fee_attribution"],
51
+ "token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
52
+ "output_token": "0x4200000000000000000000000000000000000006",
53
+ "gross": "1000000",
54
+ "otto_fee": "1000",
55
+ "min_amount_out": "415211826902997",
56
+ "fee_distributions": [
57
+ { "recipient": "0xC06ebbefD94032B85424D51906e2A335EFAe264B", "amount": "1500" },
58
+ { "recipient": "0x0E84dDEdAaE6A779c462C22a59F301EC31B6b808", "amount": "1000" }
59
+ ],
60
+ "valid_until": "2026-09-02T09:00:49.613Z",
61
+ "seconds_remaining": 287,
62
+ "artifact_id": "01M1GN9T3DH6470V0Y8968QXRX"
63
+ }
64
+ }
43
65
  ```
44
66
 
45
- That installs this package plus `@x402/core`, `@x402/evm`, `viem` and `zod` 18 packages. The parts of
46
- `otto-intel-mcp` this package runs at runtime (`otto-intel-mcp/execution`, `/adapter`, `/adapter-cdp` —
47
- the envelope schema, the verifier, the signing adapters) are **vendored** into `dist/vendor/otto-intel-mcp/`
48
- at build time as file-for-file copies of the published `otto-intel-mcp` release, so a consumer does not
49
- install that package (it is also an MCP server and would cost ~240 packages). The copy is byte-verified:
50
- `dist/vendor/otto-intel-mcp/VENDORED.json` records the version, the registry integrity hash and a sha256 per
51
- file; `npm run vendor:check` re-derives it (and runs in `prepack`). A change in the seam is therefore a
52
- visible `otto-intel-mcp` version bump + re-vendor here, never silent drift. `@coinbase/cdp-sdk` stays an
53
- optional peer, loaded only by the `otto-execute/cdp` entry.
67
+ `seconds_remaining` is the budget you have left to `submit` (the whole plan must clear before `valid_until`).
54
68
 
55
- Working on it inside the monorepo:
69
+ A plan that does not hold is refused by name, exit code 2 — for example an envelope past its window:
56
70
 
57
- ```bash
58
- cd services/otto-execute && npm ci
59
- npm run verify # typecheck → build (tsc + vendor-seam) → tests; the prepublishOnly gate
60
- npm run fixtures:check # monorepo-only: the committed envelope fixtures still match the sibling constructor
61
- npm run smoke:install # pack + clean install outside the repo + a real verify + the consumer package count
71
+ ```json
72
+ { "refused": { "code": "STALE_ARTIFACT", "message": "[STALE_ARTIFACT] execution artifact has expired" } }
62
73
  ```
63
74
 
64
- The test suite is **hermetic**: it resolves `otto-intel-mcp/*` from `node_modules` (the published package,
65
- an exact devDependency) and runs on captured envelope fixtures under `test/fixtures/envelopes/` — genuine
66
- `otto_prepare_swap` outputs built by the constructor's own code over its real LI.FI captures and committed by
67
- `scripts/capture-envelopes.ts` (the one file that touches `../otto-intel-mcp`, run only in the monorepo).
68
-
69
- **Sealed-state rule (Base Flashblocks).** A receipt can arrive from a preconfirmation while `latest` — the
70
- state gas estimates and reads run against — still predates it, and a preconfirmation is not final (a
71
- Flashblock reorg = a preconfirmed transaction omitted from the sealed block). Both signers therefore count a
72
- transaction as mined ONLY once `waitForSealedReceipt` has refetched its receipt and the sealed canonical
73
- block at that height carries the receipt's block hash — before the next estimate and before the allowance
74
- read-back after a clear; if that never happens within the bound, the step fails closed. Observed live
75
- 2026-08-28: an approve preconfirmed, the swap call estimated against `latest`, `TRANSFER_FROM_FAILED` on an
76
- allowance already granted. The wait is what a user's wallet observes.
77
-
78
- `@coinbase/cdp-sdk` is an OPTIONAL peer: only the CDP-delegated signer needs it, and that signer lives on its
79
- own entry — `import { CdpDelegatedSigner } from 'otto-execute/cdp'`. The root entry (`otto-execute`: verifier,
80
- BYO/EOA signer, x402 table) and the CLI never resolve the SDK; `test/root-without-cdp.test.ts` loads the root
81
- with the peer made unresolvable to prove it. The dev-only `.npmrc` (`legacy-peer-deps`) is **gone**: it existed
82
- only because the SDK's optional `@x402` peers want `^2.19` while this package pinned `2.16.0`, and the
83
- reconcile to `2.23.0` satisfies that range natively — `npm install` now succeeds with no flag.
84
-
85
- > **`@x402/*` reconciled to 2.23.0 (2026-09-01)**, matching what `otto-intel-mcp@0.1.0` ships, so one
86
- > `@x402/core` instance serves both packages.
87
- >
88
- > **`spendControls` is set explicitly, and KEPT.** `@x402/core` ≥ 2.23 seeds `spendControls = {}` in the
89
- > bare `x402Client` constructor, which the SDK reads as *default assets only, capped at `$1` per payment*.
90
- > `signX402Payment` states that cap in our own source (`PER_PAYMENT_SPEND_CAP = '$1'`) instead of inheriting
91
- > it, so the number is visible here and a future SDK default cannot move it silently.
92
- >
93
- > It is deliberately **not** disabled. The table checks more *fields*, but it is **not an independent
94
- > authority**: `table` is a parameter, and even the default table's `amount_atomic` values are hand-edited
95
- > constants that nothing else cross-checks. A price typo (`5000` → `5000000`) leaves the table agreeing with
96
- > itself perfectly, and only a cap derived from the requirement amount rather than from the table stands
97
- > between that typo and a signature for 1000× the intended amount. Field coverage is not a second opinion.
98
- >
99
- > The SDK's rejection is translated into this package's own vocabulary as
100
- > **`X402_SPEND_LIMIT_EXCEEDED`** — a distinct condition from `X402_AMOUNT_EXCEEDED` (which means the
101
- > *challenge* disagreed with the table; this one means the *table* asked for more than the budget). Only
102
- > that rejection is translated; every other error rethrows untouched.
103
- >
104
- > Three tests pin it, mutation-proved in both directions: a `$5` typo row is never signed (flip the cap to
105
- > `false` and it signs; delete the translation and a raw SDK error escapes instead of an `X402Refusal`), a
106
- > live-priced row still signs through the same path, and a challenge that disagrees with the table is still
107
- > `X402_AMOUNT_EXCEEDED`.
108
-
109
- ## Library
75
+ Exit codes: 0 verified/sent, 2 refused, 1 usage or I/O error. Keys come from the environment only (`OTTO_EXECUTE_PRIVATE_KEY`); a key-looking value in argv aborts; `sign` and `submit` take the account from the key, so `--account` is for `verify` only. `submit` sends up to three transactions and needs a little ETH on Base for gas; `--rpc <https url>` sets your own Base RPC (default: the public Base RPC — fine for `verify`/`sign`, pass your own for `submit`). `--prepare-input p.json` (the exact `arguments` you sent) adds the `intent_digest` check.
76
+
77
+ ## Quick start (library)
110
78
 
111
79
  ```ts
80
+ import { readFileSync } from 'node:fs';
112
81
  import { verifySwapEnvelope, defaultAdapterPolicy, EoaSigner } from 'otto-execute';
113
82
 
114
- const signer = EoaSigner.fromPrivateKey(process.env.MY_KEY as `0x${string}`, RPC_URL);
115
- const verified = verifySwapEnvelope(envelopeFromMcp, {
83
+ const USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
84
+ const WETH = '0x4200000000000000000000000000000000000006';
85
+ const envelope = JSON.parse(readFileSync('envelope.json', 'utf8')); // the otto_prepare_swap result
86
+
87
+ const signer = EoaSigner.fromPrivateKey(process.env.OTTO_EXECUTE_PRIVATE_KEY as `0x${string}`, 'https://your-base-rpc');
88
+ const verified = verifySwapEnvelope(envelope, {
116
89
  profile: { kind: 'eoa', address: signer.address, chainId: 8453 },
117
- intent: { fromToken: USDC, toToken: WETH, fromAmount: 2_000_000n, minAmountOut: myOwnFloor },
90
+ intent: { fromToken: USDC, toToken: WETH, fromAmount: 1_000_000n, minAmountOut: 200_000_000_000_000n },
118
91
  capabilities: signer.capabilities,
119
92
  policy: defaultAdapterPolicy(8453),
120
- prepareInput: theExactInputISentToOttoPrepareSwap, // optional: binds intent_digest to your request
93
+ // prepareInput: theArgumentsYouSent, // optional: also binds intent_digest to your exact request
121
94
  });
122
95
  await signer.signPlanDryRun(verified); // proves what would be signed; broadcasts nothing
123
- await signer.sendPlan(verified); // Base mainnet, real funds
96
+ await signer.sendPlan(verified); // Base mainnet
124
97
  ```
125
98
 
126
- `minAmountOut` is **yours**: derive it from a price source you trust. It is also the on-chain fence
127
- for the swap's internal DEX legs (LI.FI's router reverts unless you receive at least it), so it must
128
- be positive.
99
+ `minAmountOut` is yours to derive and must be positive: it is also the on-chain floor for the swap's DEX legs (LI.FI's router reverts unless you receive at least it).
129
100
 
130
- ## CLI
101
+ ## What you get
131
102
 
132
- ```bash
133
- otto-execute verify --envelope e.json --intent intent.json --account 0x… # no key needed
134
- OTTO_EXECUTE_PRIVATE_KEY=0x… otto-execute sign --envelope e.json --intent intent.json # dry: sign + decode back
135
- OTTO_EXECUTE_PRIVATE_KEY=0x… otto-execute submit --envelope e.json --intent intent.json --i-understand-mainnet
136
- otto-execute x402 --challenge c.json # validate a 402 against the table
137
- OTTO_EXECUTE_PRIVATE_KEY=0x… otto-execute x402 --challenge c.json --sign # emit x_payment
138
- ```
103
+ | Command / API | Does |
104
+ | --- | --- |
105
+ | `verify` · `verifySwapEnvelope()` | Re-checks the envelope's own assertions (schema, payload digest, commitment, freshness, account binding), then **your intent** (input token + exact amount, every deposit leg pulling exactly that, the output token, your minimum output), then policy (reviewed router, receiver = you, every distribution leg decoded independently and matched to the reviewed, versioned set through a reviewed FeeForwarder deployment, aggregate under the cap). Twelve named checks; a verified plan is frozen and registered by identity. |
106
+ | `sign` · `signPlanDryRun()` | Signs every step with your key, decodes each back and recovers the signer — nothing is broadcast. |
107
+ | `submit` · `sendPlan()` | Sends the steps in order (allowance reset → approval → swap), each waited to a sealed Base block; on any uncertain failure it halts and clears the live allowance, confirmed by an allowance read-back. Freshness and binding are re-checked immediately before every signature. |
108
+ | `x402 --challenge c.json [--sign]` · `validateX402Challenge()` + `signX402Payment()` | For the hosted MCP's **paid intelligence tools** (the constructors are free): when a tool answers `payment_required`, save that object as `c.json`; this validates it against an exact-match table of Otto's routes (resource, recipient, amount, 300 s window, EIP-712 domain) and, with `--sign`, prints the `x_payment` string — retry the same tool with it in the `x_payment` argument. A per-payment cap of `$1` is enforced regardless of the table. |
109
+ | `delegate --email you@example.com [--expires-days 90]` · `otto-execute/delegate` | Mints a time-boxed CDP delegation (default 90 days, max 180) to Otto's public project so Otto's server can submit your own swaps under it; `delegation status\|revoke` read it back / revoke it. Reads the public `otto_delegation_fence_status` first and refuses when the fence is absent. Runs end-user-side: sign-in by email OTP typed on the terminal; no Otto credential in the process. Not a Coinbase-supported path — it drives the pinned `@coinbase/cdp-core` 0.0.120 web build on Node and can break on an SDK update (a canary test names the break). |
110
+ | `otto-execute/cdp` · `CdpDelegatedSigner` | Library only: the same verify → submit over a delegation you minted with `delegate` (needs the optional `@coinbase/cdp-sdk` peer; the CLI's `submit` is the EOA path). |
111
+
112
+ Every refusal is a named code (`AdapterRefusal` / `X402Refusal` / `DelegateRefusal`), never upstream error text.
113
+
114
+ ## File shapes
115
+
116
+ - `envelope.json` — the `otto_prepare_swap` result, verbatim.
117
+ - `intent.json` — `{ fromToken, toToken, fromAmount, minAmountOut }`, addresses `0x` + 40 hex, amounts as strings in atomic units.
118
+ - `--prepare-input p.json` — the exact `arguments` object you sent to `otto_prepare_swap` (optional; adds the `intent_digest` check).
119
+ - `x402 --challenge c.json` — the `payment_required` object a hosted tool returned.
120
+
121
+ ## Design notes (for reviewers)
139
122
 
140
- Keys come from the environment only; a key value found in argv aborts. Refusals exit 2 with
141
- `{ "refused": { "code", "message" } }`.
123
+ - **Vendored seam.** The parts of `otto-intel-mcp` this package runs (`/execution`, `/adapter`, `/adapter-cdp`) are copied file-for-file from the published `otto-intel-mcp` release into `dist/vendor/` at build time; `dist/vendor/otto-intel-mcp/VENDORED.json` records the version, the registry integrity hash and a sha256 per file, re-derived by `npm run vendor:check` in `prepack`. A seam change is a visible version bump + re-vendor, never silent drift.
124
+ - **Sealed-state rule (Base Flashblocks).** A receipt can arrive from a preconfirmation while `latest` still predates it, and a preconfirmation can be reorged. Both signers count a transaction as mined only once the sealed canonical block at that height carries the receipt's block hash — before the next estimate and before the allowance read-back. Observed live 2026-08-28.
125
+ - **`spendControls` kept on.** `@x402/core` ≥ 2.23 caps a payment at `$1` by default; this package states that cap explicitly (`PER_PAYMENT_SPEND_CAP`) and translates the SDK's rejection to `X402_SPEND_LIMIT_EXCEEDED`. The `table` parameter of `validateX402Challenge` is public and defaults to `X402_TABLE`; whatever table a caller passes, the cap bounds what can be signed.
126
+ - **Optional peers.** `@coinbase/cdp-sdk` (the `/cdp` entry) and `@coinbase/cdp-core` (the `/delegate` entry) are optional; the root entry and the CLI's verify/sign/submit/x402 never load them, and a consumer without them typechecks (`test/root-without-cdp.test.ts`, and the isolated-install smoke compiles a consumer with `skipLibCheck: false`).
127
+ - **Hermetic tests.** `npm test` runs on captured genuine envelopes (`test/fixtures/envelopes/`, built by the constructor's own code over its real LI.FI captures) and resolves `otto-intel-mcp/*` from `node_modules`; `scripts/capture-envelopes.ts` (monorepo-only) re-captures and `npm run fixtures:check` fails when they are stale. The 24-row mutation matrix (`MUTATION_CASES`) is the one the Model-B proof kit shipped with, which consumes this package.
128
+ - Not in this package by design: a DEX allowlist (the on-chain minimum-output bind is the fence); bridge / Polymarket / Hyperliquid adapters (their constructors are live on the MCP; adapters follow).
142
129
 
143
- ## What is proven, and where
130
+ Working in the monorepo: `npm ci && npm run verify` (typecheck → build incl. vendoring → tests), `npm run smoke:install` (pack + clean install outside the repo + real verify + TypeScript consumer compile + package count), `npm run pack:audit` (the privacy gate, also in `prepack`).
144
131
 
145
- The consistency matrix (`MUTATION_CASES`, 24 rows) and the tests are the ones the Model-B proof kit
146
- (`scripts/probes/model-b-proof/`, PR #1918, codex gate GO r5/5) shipped with; the kit now consumes this
147
- package. Live sittings run from the kit's runbook (`docs/0826_model_b_proof_runbook.md`).
132
+ ## Links
148
133
 
149
- Not in this package by design: an allowlist of DEX contracts (the on-chain minimum-output bind is the
150
- fence), user-side revocation claims (exists per CDP's API; unobserved), bridge / Polymarket / Hyperliquid
151
- adapters (their constructors are merged; consumers follow).
134
+ - Execution seam docs (the envelope field by field, the checks, delegation): https://docs.useotto.xyz/acp-swarm/execution-seam
135
+ - Otto Intel MCP (the constructors + intelligence tools): https://www.npmjs.com/package/otto-intel-mcp · https://docs.useotto.xyz/acp-swarm/otto-intel-mcp
136
+ - Otto AI: https://useotto.xyz · X: [@useOttoAI](https://x.com/useOttoAI)
137
+ - Changelog: 0.1.2 — `verify` prints `seconds_remaining` (the submit budget) and `secondsRemaining()` is exported; envelope window documented as 5 minutes; 0.1.1 — README rewritten for first-time users and agents (quick start, captured real output, file shapes); 0.1.0 — first release.
package/dist/cli.js CHANGED
@@ -18,6 +18,7 @@ import { readFileSync } from 'node:fs';
18
18
  import { createInterface } from 'node:readline';
19
19
  import { getAddress } from 'viem';
20
20
  import { privateKeyToAccount } from 'viem/accounts';
21
+ import { secondsRemaining } from './window.js';
21
22
  import { DEFAULT_OTTO_MCP_URL, DelegateRefusal, UNSUPPORTED_PATH_NOTE, assertFencePresent, initEndUserSdk, isDelegateRefusal, mintDelegation, readDelegationAsUser, readPublicFenceStatus, redactSecrets, resolveDelegateEndpoints, revokeDelegationAsUser, signInWithEmailOtp, } from './delegate.js';
22
23
  import { EoaSigner } from './eoa-signer.js';
23
24
  import { isAdapterRefusal } from './refusal.js';
@@ -381,6 +382,8 @@ async function main() {
381
382
  min_amount_out: verified.minAmountOut,
382
383
  fee_distributions: verified.fees.distributions,
383
384
  valid_until: verified.artifact.valid_until,
385
+ // The budget left to submit the WHOLE plan (freshness is re-checked before every signature).
386
+ seconds_remaining: secondsRemaining(verified.artifact.valid_until),
384
387
  artifact_id: verified.artifact.artifact_id,
385
388
  };
386
389
  if (command === 'verify') {
package/dist/index.d.ts CHANGED
@@ -14,3 +14,4 @@ export { waitForSealedReceipt, type SealedClient, type SealedWaitOptions } from
14
14
  export { EOA_CAPABILITIES, EoaSigner, PlanHaltedError, type DryRunSignedStep, type SentStep, type SignedX402Payment, } from './eoa-signer.js';
15
15
  export { BASE_USDC, OTTO_X402_PAY_TO, X402_REFUSAL_CODES, X402_TABLE, X402_TABLE_VERSION, X402Refusal, canonicalResourceUrl, requirementMatchesRow, validateX402Challenge, type PaymentRequirementLike, type ValidatedX402Challenge, type X402ExpectedRow, type X402RefusalCode, type X402Table, } from './x402-table.js';
16
16
  export { FOREIGN_ADDRESS, FOREIGN_TOKEN, MUTATION_CASES, applyMutation, recommit, recommitCommitmentOnly, recommitPayloadOnly, type CatchingLayer, type MutationCase, type MutationContext, } from './mutations.js';
17
+ export { secondsRemaining } from './window.js';
package/dist/index.js CHANGED
@@ -16,4 +16,5 @@ export { EOA_CAPABILITIES, EoaSigner, PlanHaltedError, } from './eoa-signer.js';
16
16
  // optional `@coinbase/cdp-sdk` peer (loaded lazily); this root never references the SDK at all.
17
17
  export { BASE_USDC, OTTO_X402_PAY_TO, X402_REFUSAL_CODES, X402_TABLE, X402_TABLE_VERSION, X402Refusal, canonicalResourceUrl, requirementMatchesRow, validateX402Challenge, } from './x402-table.js';
18
18
  export { FOREIGN_ADDRESS, FOREIGN_TOKEN, MUTATION_CASES, applyMutation, recommit, recommitCommitmentOnly, recommitPayloadOnly, } from './mutations.js';
19
+ export { secondsRemaining } from './window.js';
19
20
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * window.ts — the freshness budget a caller has left on an envelope. The constructor stamps
3
+ * `valid_until` (5 minutes from construction on the hosted server by default; the tool description
4
+ * states the exact window) and the verifier and both signers refuse a stale envelope before every
5
+ * signature — so the WHOLE plan (allowance reset → approval → swap, each waited to a sealed block)
6
+ * must clear before `valid_until`. `verify` prints this so a user sees the budget instead of
7
+ * discovering it as STALE_ARTIFACT mid-plan.
8
+ */
9
+ /** Whole seconds until `validUntil` (an ISO-8601 instant), never negative; `NaN` in → 0. */
10
+ export declare function secondsRemaining(validUntil: string, nowMs?: number): number;
package/dist/window.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * window.ts — the freshness budget a caller has left on an envelope. The constructor stamps
3
+ * `valid_until` (5 minutes from construction on the hosted server by default; the tool description
4
+ * states the exact window) and the verifier and both signers refuse a stale envelope before every
5
+ * signature — so the WHOLE plan (allowance reset → approval → swap, each waited to a sealed block)
6
+ * must clear before `valid_until`. `verify` prints this so a user sees the budget instead of
7
+ * discovering it as STALE_ARTIFACT mid-plan.
8
+ */
9
+ /** Whole seconds until `validUntil` (an ISO-8601 instant), never negative; `NaN` in → 0. */
10
+ export function secondsRemaining(validUntil, nowMs = Date.now()) {
11
+ const until = Date.parse(validUntil);
12
+ if (!Number.isFinite(until))
13
+ return 0;
14
+ return Math.max(0, Math.floor((until - nowMs) / 1000));
15
+ }
16
+ //# sourceMappingURL=window.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "otto-execute",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "The user's side of Otto Model B: verify a prepare-only execution artifact from the Otto Intel MCP against your own intent and the ruled attribution, then sign and submit it with your own key (or under a delegation you minted). Otto never holds your keys or funds.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://docs.useotto.xyz",