otto-intel-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +102 -0
- package/dist/adapter/cdp-signer.d.ts +133 -0
- package/dist/adapter/cdp-signer.js +356 -0
- package/dist/adapter/cdp-signer.js.map +1 -0
- package/dist/adapter/chain.d.ts +36 -0
- package/dist/adapter/chain.js +65 -0
- package/dist/adapter/chain.js.map +1 -0
- package/dist/adapter/erc20.d.ts +39 -0
- package/dist/adapter/erc20.js +17 -0
- package/dist/adapter/erc20.js.map +1 -0
- package/dist/adapter/lifi-decode.d.ts +52 -0
- package/dist/adapter/lifi-decode.js +149 -0
- package/dist/adapter/lifi-decode.js.map +1 -0
- package/dist/adapter/refusal.d.ts +21 -0
- package/dist/adapter/refusal.js +55 -0
- package/dist/adapter/refusal.js.map +1 -0
- package/dist/adapter/sent-step.d.ts +15 -0
- package/dist/adapter/sent-step.js +6 -0
- package/dist/adapter/sent-step.js.map +1 -0
- package/dist/adapter/verify.d.ts +149 -0
- package/dist/adapter/verify.js +432 -0
- package/dist/adapter/verify.js.map +1 -0
- package/dist/adapter-cdp-index.d.ts +6 -0
- package/dist/adapter-cdp-index.js +7 -0
- package/dist/adapter-index.d.ts +15 -0
- package/dist/adapter-index.js +15 -0
- package/dist/artifact-id.d.ts +16 -0
- package/dist/artifact-id.js +60 -0
- package/dist/boot-redaction.d.ts +16 -0
- package/dist/boot-redaction.js +54 -0
- package/dist/data-source.d.ts +7 -0
- package/dist/data-source.js +2 -0
- package/dist/errors.d.ts +70 -0
- package/dist/errors.js +221 -0
- package/dist/execution-config.d.ts +232 -0
- package/dist/execution-config.js +443 -0
- package/dist/execution-delegated-definition.d.ts +165 -0
- package/dist/execution-delegated-definition.js +116 -0
- package/dist/execution-delegation-admin-definition.d.ts +208 -0
- package/dist/execution-delegation-admin-definition.js +170 -0
- package/dist/execution-delegation-admin.d.ts +74 -0
- package/dist/execution-delegation-admin.js +290 -0
- package/dist/execution-delegation-policy.d.ts +257 -0
- package/dist/execution-delegation-policy.js +279 -0
- package/dist/execution-delegation.d.ts +190 -0
- package/dist/execution-delegation.js +545 -0
- package/dist/execution-errors.d.ts +9 -0
- package/dist/execution-errors.js +128 -0
- package/dist/execution-index.d.ts +17 -0
- package/dist/execution-index.js +16 -0
- package/dist/execution-intent.d.ts +14 -0
- package/dist/execution-intent.js +36 -0
- package/dist/execution-registration.d.ts +113 -0
- package/dist/execution-registration.js +161 -0
- package/dist/execution-tool-definitions.d.ts +1103 -0
- package/dist/execution-tool-definitions.js +1051 -0
- package/dist/execution-tools.d.ts +85 -0
- package/dist/execution-tools.js +690 -0
- package/dist/execution-types.d.ts +274 -0
- package/dist/execution-types.js +157 -0
- package/dist/hyperliquid-info-client.d.ts +31 -0
- package/dist/hyperliquid-info-client.js +121 -0
- package/dist/hyperliquid-order-assertions.d.ts +89 -0
- package/dist/hyperliquid-order-assertions.js +325 -0
- package/dist/hyperliquid-order-builder.d.ts +47 -0
- package/dist/hyperliquid-order-builder.js +55 -0
- package/dist/lifi-bridge-assertions.d.ts +49 -0
- package/dist/lifi-bridge-assertions.js +303 -0
- package/dist/lifi-execution-client.d.ts +74 -0
- package/dist/lifi-execution-client.js +165 -0
- package/dist/lifi-fee-assertions.d.ts +32 -0
- package/dist/lifi-fee-assertions.js +103 -0
- package/dist/lifi-swap-assertions.d.ts +32 -0
- package/dist/lifi-swap-assertions.js +214 -0
- package/dist/local-config.d.ts +9 -0
- package/dist/local-config.js +45 -0
- package/dist/mcp-server.d.ts +4 -0
- package/dist/mcp-server.js +44 -0
- package/dist/polymarket-clob-client.d.ts +73 -0
- package/dist/polymarket-clob-client.js +186 -0
- package/dist/polymarket-order-assertions.d.ts +60 -0
- package/dist/polymarket-order-assertions.js +273 -0
- package/dist/polymarket-order-builder.d.ts +43 -0
- package/dist/polymarket-order-builder.js +74 -0
- package/dist/prepared-artifacts.d.ts +45 -0
- package/dist/prepared-artifacts.js +92 -0
- package/dist/stdio-main.d.ts +2 -0
- package/dist/stdio-main.js +20 -0
- package/dist/tool-definitions.d.ts +20 -0
- package/dist/tool-definitions.js +162 -0
- package/dist/x402-read-source.d.ts +11 -0
- package/dist/x402-read-source.js +77 -0
- package/package.json +162 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 otto AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# otto Intel MCP
|
|
2
|
+
|
|
3
|
+
Read-only pre-trade intelligence over MCP, from one shared tool catalog:
|
|
4
|
+
|
|
5
|
+
- Hosted Streamable HTTP at `https://mcp.ottoai.services`: one successful call free per observed network origin, then pay per call in USDC (x402) — your agent brings its wallet. Its `otto_catalog` storefront tool is always free.
|
|
6
|
+
- Local stdio package (`npx otto-intel-mcp`). The operator's dedicated payer wallet settles each allowlisted data call over x402.
|
|
7
|
+
|
|
8
|
+
The stdio transport exposes no fund-moving tool. The hosted transport is read-only by default; when the operator arms the execution tools (`OTTO_EXECUTION_MCP_ENABLED=true`) it also serves four **prepare-only** constructors (swap, bridge, Polymarket order, perp order — unsigned plans handed back to the caller's own custody), and when it additionally holds Otto's CDP developer credentials it serves ONE tool that submits: `otto_submit_under_delegation` (below), under the end user's own CDP delegation, server-to-server only. Nothing here holds a user key.
|
|
9
|
+
|
|
10
|
+
## Tool catalog
|
|
11
|
+
|
|
12
|
+
| Tool | Argument | x402 price |
|
|
13
|
+
| ------------------------------- | -------------------- | ---------: |
|
|
14
|
+
| `otto_tokenized_equities` | optional `thesis` | $0.001 |
|
|
15
|
+
| `otto_rh_season` | none | $0.001 |
|
|
16
|
+
| `otto_equity_intel` | `ticker` | $0.003 |
|
|
17
|
+
| `otto_insider_trades` | `ticker` | $0.003 |
|
|
18
|
+
| `otto_institutional_holdings` | `manager` CIK/ticker | $0.003 |
|
|
19
|
+
| `otto_equity_smart_money` | `ticker` | $0.006 |
|
|
20
|
+
| `otto_equity_smart_money_brief` | `ticker` | $0.10 |
|
|
21
|
+
| `otto_base_season` | none | $0.002 |
|
|
22
|
+
| `otto_pm_markets` | none | $0.001 |
|
|
23
|
+
| `otto_pm_crypto` | none | $0.001 |
|
|
24
|
+
|
|
25
|
+
The hosted transport also exposes `otto_catalog`. It derives the complete live x402 menu and count from `/.well-known/x402` plus `/llm.txt`; it never consumes the free intelligence call and never requires payment. The ten intelligence prices above mirror the canonical x402 catalog and are parity-tested.
|
|
26
|
+
|
|
27
|
+
The stdio client accepts only the official otto x402 origin, the Base network, canonical Base USDC, and a per-tool amount at or below the ceiling above. Redirects are disabled.
|
|
28
|
+
|
|
29
|
+
## Local stdio package
|
|
30
|
+
|
|
31
|
+
> Unpublished today (`"private": true`; see `publishGate` in `package.json`): the `npx` lines below describe the shape after the first publish. Until then run it from the repo (`npm run dev:stdio`) or from a local `npm pack` tarball.
|
|
32
|
+
|
|
33
|
+
Use a fresh payer wallet that holds only the Base USDC intended for data calls. Never use a primary wallet.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
export X402_PRIVATE_KEY=0x...
|
|
37
|
+
npx -y otto-intel-mcp
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Example MCP configuration:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"mcpServers": {
|
|
45
|
+
"otto-intel": {
|
|
46
|
+
"command": "npx",
|
|
47
|
+
"args": ["-y", "otto-intel-mcp"],
|
|
48
|
+
"env": {
|
|
49
|
+
"X402_PRIVATE_KEY": "${X402_PRIVATE_KEY}"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The private key stays in the local MCP process. It is used only to authorize bounded x402 data payments; it is never sent to otto.
|
|
57
|
+
|
|
58
|
+
## Hosted service
|
|
59
|
+
|
|
60
|
+
Connect a Streamable HTTP MCP client to `https://mcp.ottoai.services`. One successful intelligence call is free per observed network origin. Changing caller-supplied session IDs or client metadata does not create another grant; callers sharing one outbound address also share the courtesy call.
|
|
61
|
+
|
|
62
|
+
Later unpaid calls return the paid route's machine-actionable, recoverable Base-USDC EIP-3009 requirement. The wrapper also supplies an exact `command`, tool-specific `why_paid`, the shared `pricing_model`, honest `free_call` state, and an `otto_catalog` pointer. Retry the same tool with the signed payload in `x_payment`. The public x402 endpoint remains the verifier and settler, and the hosted process releases a paid payload only when that endpoint returns its settlement receipt.
|
|
63
|
+
|
|
64
|
+
If an input has no free result, the call returns promptly with the paid route's x402 requirement and the free grant remains available for an input that can be served free. The paid route covers inputs it has never served before. After an interrupted delivery, retry the exact same input with the same signed payload: the durable rail replays its settled response without a second settlement, or completes the attempt if it remained uncharged.
|
|
65
|
+
|
|
66
|
+
A successfully delivered or recovered paid call carries the route's settlement receipt in the result metadata under `com.ottoai/payment-response` — `{ header, value }`, verbatim, under the header name the paid route used.
|
|
67
|
+
|
|
68
|
+
The hosted process has no payer wallet or signing key.
|
|
69
|
+
|
|
70
|
+
For a self-contained transport demo with a mock data source:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm run demo:hosted
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Verification
|
|
77
|
+
|
|
78
|
+
> Build order: `services/otto-execute` resolves `otto-intel-mcp/adapter` and `otto-intel-mcp/execution` from this package's `dist/` — run `npm run build` here before typechecking or testing otto-execute. Also: `tsc --noEmit` on the FULL tsconfig needs the monorepo (two tests import the sibling `services/x402-router/src/catalog.ts`); the deploy build uses `tsconfig.build.json` (src only), so an isolated Railway build is unaffected.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npm run typecheck
|
|
82
|
+
npm test
|
|
83
|
+
npm run build
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Optional CDP SDK (the delegated-send path)
|
|
87
|
+
|
|
88
|
+
`@coinbase/cdp-sdk` is an **optional dependency**, loaded lazily in exactly two places — `openCdpRig` (`otto-intel-mcp/adapter-cdp`, for a consumer that delegates) and the hosted boot's `openDelegationServicesFromEnv` (`src/execution-delegation.ts`, only when the five delegation variables are set). Importing either adapter entry never resolves it, and a consumer that only verifies or signs with its own key never needs it. The hosted service installs it at its own root, where the `overrides` pin of the SDK's optional `@x402/*` peers to this package's `2.23.0` is honored.
|
|
89
|
+
|
|
90
|
+
A consumer installing this package gets the SDK too. As of 2026-09-01 this package's own `@x402/axios` + `@x402/evm` are `2.23.0`, which satisfies the SDK's optional `@x402/*@^2.19.0` peers directly — so the graph dedupes to a single `@x402/core@2.23.0` and `npm ls` reports **no** "invalid" marker, with or without the `overrides` pin (npm honors `overrides` in the root project only, so it never reached consumers anyway). The pin is kept, in lockstep, purely for root-install determinism. Install with `--omit=optional` if you do not delegate.
|
|
91
|
+
|
|
92
|
+
## Delegated send (hosted only) — `otto_submit_under_delegation`
|
|
93
|
+
|
|
94
|
+
Registers only when the hosted service holds Otto's CDP developer credentials — **five** variables, all or none (partial → boot refuses naming the MISSING names; the SDK absent while configured → named boot refusal; configured while `OTTO_EXECUTION_MCP_ENABLED` is off → boot refuses): `CDP_API_KEY_ID` · `CDP_API_KEY_SECRET` · `CDP_WALLET_SECRET` · `OTTO_DELEGATION_SERVER_SECRET` (≥ 32 chars; the caller presents it in the `x-otto-delegation-auth` request header, never in arguments) · `BASE_RPC_URL` (https). Values are set on Railway by hand, never on Vercel.
|
|
95
|
+
|
|
96
|
+
**MUST NEVER SCALE ABOVE ONE REPLICA** (`railway.json` `numReplicas: 1`): the fence read-back, the per-user in-flight lock, the per-artifact idempotency record and the signer's rig lock are all process-local. A second replica would allow cross-replica duplicate submissions, nonce races and unsynchronised revocation.
|
|
97
|
+
|
|
98
|
+
Idempotency: one record per artifact for its freshness window (+10 min grace) — a retry of a SENT artifact replays the stored result (`replayed: true`, nothing re-sent); a HALTED artifact is never retried on this server (the same refusal replays; prepare again); a refusal before any send leaves no record. A record answers only the SAME request: it is bound to a fingerprint over the end user, the envelope's RECOMPUTED whole-envelope commitment (the `artifact_digest` field is never trusted) and the canonical prepare input, so a tampered envelope under a known id is refused (`ARTIFACT_NOT_PREPARED_HERE`) and the same envelope with another user, account or intent is refused (`DELEGATION_REPLAY_MISMATCH`) — a stored success never attests to a plan that was not the one sent. The record is in-process, so the tool submits ONLY artifacts THIS process prepared (`src/prepared-artifacts.ts`: every hosted `otto_prepare_swap` records its `artifact_id`; any other id is refused `ARTIFACT_NOT_PREPARED_HERE` before anything is read). The entry binds the id AND the envelope's whole-envelope commitment, so a re-IDed envelope prepared elsewhere is refused too. That holds across a restart AND across a rolling replacement in which the old and the new process serve at once — the process that did not prepare an artifact cannot know whether the other already sent it, so it refuses. Live entries are never evicted (an issued artifact must always find its record); the registry is bounded by refusing a NEW prepare at 100,000 live entries (`PREPARED_ARTIFACT_CAPACITY`). That refusal is unreachable in practice — an entry lives for the artifact's freshness window plus the 10-minute grace (≈ 15 minutes), so 100,000 live entries means ≈ 110 sustained prepares per second on this service against a per-identity prepare rate limit of 10 per minute by default; it exists so the bound is honest, and it must NEVER be "fixed" into evicting a live entry, which would refuse an issued artifact instead of replaying it. Consequence, by design: an agent whose prepare → submit spans a deploy is refused and prepares again (rare; fail-closed). Refusals carry cause classes, never upstream error text; boot failures print fixed text with URLs and configured secret values redacted (`src/boot-redaction.ts`).
|
|
99
|
+
|
|
100
|
+
**Delegation admin tools (hosted only, same header, same admission):** `otto_delegation_fence` — the ONLY writer of the Model-B project policy: ensure-by-digest + read-back. Rules, each mutation-proved: EXACTLY ONE project-scope policy or refuse (`DELEGATION_FENCE_AMBIGUOUS` — Coinbase allows one; more is an anomalous snapshot, never judged, never written to; the send tool's read-back refuses it too); a policy is Otto's own only when its description matches `^OTTO model b swap fence v<N>$` exactly — N ≤ this server's version with stale rules → updated IN PLACE, N newer → refused (`DELEGATION_FENCE_NEWER`, never downgraded), anything else → refused BY NAME (`DELEGATION_FENCE_FOREIGN`; never overwritten, never deleted); a create Coinbase refuses as a duplicate is never retried (re-read once, judge what is there); before an update the policy is re-read by id and must still be exactly the snapshot; after any write the proof is a read-back of exactly one policy whose id IS the written id and which is v-current (a bounded poll, then fail-closed). **Operational premise (no conditional update in `@coinbase/cdp-sdk` 1.54.0 — `UpdatePolicyOptions` carries no revision/ETag):** AT MOST ONE ENSURE WRITES PROJECT-WIDE AT A TIME, ACROSS VERSIONS — the ensure is an ops action run by one operator (the seat or the Founder, with the server secret), never during a rolling deploy (two versions of the tool running at once are two writers and the older one would win a race and downgrade), never from the dApp (PR-2's mint gate reads `fence.present` from `otto_delegation_status`), and nobody edits the policy in the CDP portal while it runs. Under that premise the pre-write re-read is exact — without it, "never overwrite foreign" and "never downgrade" narrow to "…what was foreign / newer one round-trip ago". `otto_delegation_status` — `{ accessToken }` only: the server resolves the end user through `cdp.endUser.validateAccessToken`; a client-sent user id is refused; the output is bound to that user. `otto_delegation_revoke` — `{ accessToken }` (user path) OR `{ userId, support_reason }` (support path, for an operator holding the server secret; the reason is logged through the boot redactor — URLs and configured secret values never land in the log — a token never is); developer-side revoke confirmed by read-back, and this server submits nothing for the user afterwards even if the read-back failed. `otto_delegation_fence_status` — PUBLIC and read-only, no header: the fence's public promise (`present`, versioned name, rules digest, per-swap cap USD; a `reason` class when absent) — never a user, a grant, an address or a policy's contents; `otto-execute delegate` reads it before minting. With delegation configured the hosted tool count is 21.
|
|
101
|
+
|
|
102
|
+
Residual (spec-level, not this tool's): the MCP SDK requires `params.arguments` to be PRESENT and an OBJECT before any handler runs (the request schema, then the registered object schema), so an absent `arguments`, `null`, an array or a primitive is answered by the SDK's `InvalidParams` error whether or not the caller is authenticated — an objectness/presence oracle every MCP server has. The tool's own field schema is validated only after the caller header is accepted; the SDK's error carries no secret and no field name (asserted in `test/hosted-delegation.test.ts`). The fence and its residual are stated in `src/execution-delegation-policy.ts`.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cdp-signer.ts — the CDP-delegated capability provider, product-shaped.
|
|
3
|
+
*
|
|
4
|
+
* The end user has ALREADY signed in and minted a time-boxed delegation somewhere else (a dApp, the
|
|
5
|
+
* proof kit's OTP flow, …). This adapter holds Otto's developer-side CDP access and submits the
|
|
6
|
+
* user's OWN transactions under that delegation: user-authenticated, Otto-governed (the project policy
|
|
7
|
+
* is the fence), Otto-revocable. It never holds a user key, never mints a grant, and submits ONLY a
|
|
8
|
+
* plan that `assertPlanSignableBy` accepts for THIS user.
|
|
9
|
+
*
|
|
10
|
+
* RUNTIME privacy, not TypeScript privacy: the raw "send this unsigned transaction under the user's
|
|
11
|
+
* delegation" capability lives in a module-private WeakMap keyed by the rig object. A `CdpRig` value
|
|
12
|
+
* exposes only chain reads, delegation read-back and revocation; the signer reaches the raw send
|
|
13
|
+
* through the WeakMap, and its own submission path is a `#private` method. Nothing reachable from a
|
|
14
|
+
* rig or a signer can submit an unverified transaction.
|
|
15
|
+
*
|
|
16
|
+
* Lifecycle serialization lives on the RIG, not on the signer: `sendPlan`, its allowance clear, the
|
|
17
|
+
* signer's `revokeDeveloperSide` AND the rig's public `revoke` all run through one module-private lock
|
|
18
|
+
* per rig, so a revocation issued by anyone holding the rig can never race an in-flight plan or its
|
|
19
|
+
* cleanup. Revocation is a FAIL-CLOSED per-user transition: the lock is held through the raw revoke AND
|
|
20
|
+
* its read-back, and once revocation has been REQUESTED for a user on a rig, no plan for that user is
|
|
21
|
+
* ever submitted on that rig again — confirmed, unconfirmed or failed. A halted plan's clear is
|
|
22
|
+
* confirmed by an `allowance` read-back, never inferred from a receipt.
|
|
23
|
+
*
|
|
24
|
+
* Nonce, gas and serialization are adapter-owned (spec §6): the unsigned EIP-1559 transaction is
|
|
25
|
+
* assembled here from chain reads and handed to CDP to sign and broadcast.
|
|
26
|
+
*/
|
|
27
|
+
import { type Address, type Hex, type PublicClient } from 'viem';
|
|
28
|
+
import { type SealedWaitOptions } from './chain.js';
|
|
29
|
+
import type { SentStep } from './sent-step.js';
|
|
30
|
+
import { type SigningCapability, type VerifiedSwap } from './verify.js';
|
|
31
|
+
export declare const CDP_CAPABILITIES: readonly SigningCapability[];
|
|
32
|
+
export declare const CHAIN_ID: 8453;
|
|
33
|
+
export declare const NETWORK: "base";
|
|
34
|
+
export interface CdpDeveloperCredentials {
|
|
35
|
+
readonly apiKeyId: string;
|
|
36
|
+
readonly apiKeySecret: string;
|
|
37
|
+
readonly walletSecret: string;
|
|
38
|
+
}
|
|
39
|
+
export interface CdpEndUser {
|
|
40
|
+
readonly userId: string;
|
|
41
|
+
readonly address: Address;
|
|
42
|
+
}
|
|
43
|
+
/** The PUBLIC face of a rig: chain reads, delegation read-back, revocation. No submission surface. */
|
|
44
|
+
export interface CdpRig {
|
|
45
|
+
readonly pub: PublicClient;
|
|
46
|
+
readDelegation(userId: string): Promise<{
|
|
47
|
+
expiresAt: string;
|
|
48
|
+
} | undefined>;
|
|
49
|
+
/**
|
|
50
|
+
* Request revocation and confirm it by read-back, serialized with every plan on this rig. Resolves
|
|
51
|
+
* only once the grant no longer reads back; rejects if it still does. Either way the user is marked
|
|
52
|
+
* revocation-requested on this rig and no later plan for them is submitted (fail-closed).
|
|
53
|
+
*/
|
|
54
|
+
revoke(userId: string): Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
/** The raw capabilities a rig is built from. Held privately by this module once the rig exists. */
|
|
57
|
+
export interface CdpRawOperations {
|
|
58
|
+
readonly pub: PublicClient;
|
|
59
|
+
/** Sign + broadcast an unsigned serialized EIP-1559 transaction under the user's delegation. Returns the tx hash. */
|
|
60
|
+
submit(user: CdpEndUser, unsignedTransaction: Hex): Promise<Hex>;
|
|
61
|
+
revoke(userId: string): Promise<void>;
|
|
62
|
+
readDelegation(userId: string): Promise<{
|
|
63
|
+
expiresAt: string;
|
|
64
|
+
} | undefined>;
|
|
65
|
+
}
|
|
66
|
+
/** True once `rig.revoke(userId)` has been called on this rig, whatever it returned. */
|
|
67
|
+
export declare function revocationRequested(rig: CdpRig, userId: string): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Why a delegated plan halted — a closed vocabulary the signer sets from STRUCTURED facts only (the CDP
|
|
70
|
+
* `errorType`, an error's class, the receipt status, its own checks). Message text never selects it, so
|
|
71
|
+
* upstream text cannot steer the class a consumer reports.
|
|
72
|
+
*/
|
|
73
|
+
export type DelegatedHaltCause = 'policy_violation' | 'delegation_not_active' | 'artifact_no_longer_signable' | 'reverted' | 'receipt_not_sealed' | 'upstream_failure';
|
|
74
|
+
/**
|
|
75
|
+
* A delegated plan halted; `cause` is the structured class above, `allowanceCleared` says whether the
|
|
76
|
+
* router allowance is known to be zero again. The message is for the operator; a consumer that must not
|
|
77
|
+
* relay upstream text reads `cause` and the two fields only.
|
|
78
|
+
*/
|
|
79
|
+
export declare class DelegatedPlanHaltedError extends Error {
|
|
80
|
+
readonly failedStep: number;
|
|
81
|
+
readonly allowanceCleared: boolean;
|
|
82
|
+
readonly cause: DelegatedHaltCause;
|
|
83
|
+
readonly clearTransactionHash?: Hex | undefined;
|
|
84
|
+
constructor(message: string, failedStep: number, allowanceCleared: boolean, cause: DelegatedHaltCause, clearTransactionHash?: Hex | undefined);
|
|
85
|
+
}
|
|
86
|
+
/** Thrown by the signer itself once revocation was requested for the user on this rig (fail-closed). */
|
|
87
|
+
export declare class RevocationRequestedError extends Error {
|
|
88
|
+
constructor(userId: string);
|
|
89
|
+
}
|
|
90
|
+
/** Classify a failure from its STRUCTURE (class, `errorType`) — never from its message text. */
|
|
91
|
+
export declare function haltCauseOf(error: unknown): DelegatedHaltCause;
|
|
92
|
+
/**
|
|
93
|
+
* Build a rig from raw operations. The raw operations are captured into the module-private registry;
|
|
94
|
+
* the returned object exposes only reads and revocation. (Callers who hold a CDP client already have
|
|
95
|
+
* the raw capability — this function does not create it, it fences it.)
|
|
96
|
+
*/
|
|
97
|
+
export declare function createCdpRig(raw: CdpRawOperations): CdpRig;
|
|
98
|
+
/** Open a rig with Otto's developer credentials against Base mainnet. */
|
|
99
|
+
export declare function openCdpRig(credentials: CdpDeveloperCredentials, rpcUrl?: string): Promise<CdpRig>;
|
|
100
|
+
/**
|
|
101
|
+
* Revoke by `userId` alone, with read-back — usable even when the delegation request's response was
|
|
102
|
+
* lost (the grant may have committed remotely). Resolves only once the grant no longer reads back.
|
|
103
|
+
* This IS `rig.revoke`: serialized on the rig, lock held through the read-back, fail-closed afterwards.
|
|
104
|
+
*/
|
|
105
|
+
export declare function revokeDelegationByUserId(rig: CdpRig, userId: string): Promise<void>;
|
|
106
|
+
export declare class CdpDelegatedSigner {
|
|
107
|
+
#private;
|
|
108
|
+
readonly capabilities: readonly SigningCapability[];
|
|
109
|
+
readonly chainId: 8453;
|
|
110
|
+
constructor(rig: CdpRig, user: CdpEndUser, options?: {
|
|
111
|
+
readonly sealedWait?: SealedWaitOptions;
|
|
112
|
+
});
|
|
113
|
+
get address(): Address;
|
|
114
|
+
get user(): CdpEndUser;
|
|
115
|
+
/**
|
|
116
|
+
* `send_evm_transaction`, under delegation: only a plan `assertPlanSignableBy` accepts for THIS user,
|
|
117
|
+
* re-checked before every step AND again immediately before each hand-off to CDP (after the nonce /
|
|
118
|
+
* fee / gas round trips, so an artifact that expires during preparation is never submitted). The plan
|
|
119
|
+
* halts at the first step that does not succeed; after ANY failure a bounded `approve(spender, 0)` is
|
|
120
|
+
* attempted under the same policy and its real outcome — confirmed by an allowance read-back — is
|
|
121
|
+
* reported. A failed reset may leave a pre-existing allowance, so step 0 is not exempt.
|
|
122
|
+
*/
|
|
123
|
+
sendPlan(plan: VerifiedSwap): Promise<SentStep[]>;
|
|
124
|
+
/**
|
|
125
|
+
* The Otto-side kill switch, with read-back. Serialized on the rig (waits for any in-flight plan and
|
|
126
|
+
* its cleanup, holds the lock through the read-back) and fail-closed: no later plan for this user
|
|
127
|
+
* is submitted on this rig, whatever the read-back said.
|
|
128
|
+
*/
|
|
129
|
+
revokeDeveloperSide(): Promise<void>;
|
|
130
|
+
delegation(): Promise<{
|
|
131
|
+
expiresAt: string;
|
|
132
|
+
} | undefined>;
|
|
133
|
+
}
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cdp-signer.ts — the CDP-delegated capability provider, product-shaped.
|
|
3
|
+
*
|
|
4
|
+
* The end user has ALREADY signed in and minted a time-boxed delegation somewhere else (a dApp, the
|
|
5
|
+
* proof kit's OTP flow, …). This adapter holds Otto's developer-side CDP access and submits the
|
|
6
|
+
* user's OWN transactions under that delegation: user-authenticated, Otto-governed (the project policy
|
|
7
|
+
* is the fence), Otto-revocable. It never holds a user key, never mints a grant, and submits ONLY a
|
|
8
|
+
* plan that `assertPlanSignableBy` accepts for THIS user.
|
|
9
|
+
*
|
|
10
|
+
* RUNTIME privacy, not TypeScript privacy: the raw "send this unsigned transaction under the user's
|
|
11
|
+
* delegation" capability lives in a module-private WeakMap keyed by the rig object. A `CdpRig` value
|
|
12
|
+
* exposes only chain reads, delegation read-back and revocation; the signer reaches the raw send
|
|
13
|
+
* through the WeakMap, and its own submission path is a `#private` method. Nothing reachable from a
|
|
14
|
+
* rig or a signer can submit an unverified transaction.
|
|
15
|
+
*
|
|
16
|
+
* Lifecycle serialization lives on the RIG, not on the signer: `sendPlan`, its allowance clear, the
|
|
17
|
+
* signer's `revokeDeveloperSide` AND the rig's public `revoke` all run through one module-private lock
|
|
18
|
+
* per rig, so a revocation issued by anyone holding the rig can never race an in-flight plan or its
|
|
19
|
+
* cleanup. Revocation is a FAIL-CLOSED per-user transition: the lock is held through the raw revoke AND
|
|
20
|
+
* its read-back, and once revocation has been REQUESTED for a user on a rig, no plan for that user is
|
|
21
|
+
* ever submitted on that rig again — confirmed, unconfirmed or failed. A halted plan's clear is
|
|
22
|
+
* confirmed by an `allowance` read-back, never inferred from a receipt.
|
|
23
|
+
*
|
|
24
|
+
* Nonce, gas and serialization are adapter-owned (spec §6): the unsigned EIP-1559 transaction is
|
|
25
|
+
* assembled here from chain reads and handed to CDP to sign and broadcast.
|
|
26
|
+
*/
|
|
27
|
+
import { createPublicClient, encodeFunctionData, http, serializeTransaction, } from 'viem';
|
|
28
|
+
import { base } from 'viem/chains';
|
|
29
|
+
import { SealedReceiptTimeoutError, waitForSealedReceipt } from './chain.js';
|
|
30
|
+
import { ERC20_APPROVE_ABI, readAllowance } from './erc20.js';
|
|
31
|
+
import { isAdapterRefusal } from './refusal.js';
|
|
32
|
+
import { assertPlanSignableBy } from './verify.js';
|
|
33
|
+
export const CDP_CAPABILITIES = Object.freeze(['send_evm_transaction']);
|
|
34
|
+
export const CHAIN_ID = 8453;
|
|
35
|
+
export const NETWORK = 'base';
|
|
36
|
+
const RAW = new WeakMap();
|
|
37
|
+
/** One lifecycle lock PER RIG — shared by every signer over it and by the rig's own public `revoke`. */
|
|
38
|
+
const LOCKS = new WeakMap();
|
|
39
|
+
/** Per rig: the users whose revocation has been REQUESTED (set before the raw revoke is issued; never cleared). */
|
|
40
|
+
const REVOKE_REQUESTED = new WeakMap();
|
|
41
|
+
/** True once `rig.revoke(userId)` has been called on this rig, whatever it returned. */
|
|
42
|
+
export function revocationRequested(rig, userId) {
|
|
43
|
+
return REVOKE_REQUESTED.get(rig)?.has(userId) ?? false;
|
|
44
|
+
}
|
|
45
|
+
/** Run `operation` after everything previously queued on this rig has settled; nothing else starts until it settles. */
|
|
46
|
+
function serializedOn(rig, operation) {
|
|
47
|
+
const lock = LOCKS.get(rig);
|
|
48
|
+
if (!lock)
|
|
49
|
+
throw new Error('rig was not created by openCdpRig/createCdpRig');
|
|
50
|
+
const run = lock.chain.then(operation, operation);
|
|
51
|
+
lock.chain = run.catch(() => undefined);
|
|
52
|
+
return run;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A delegated plan halted; `cause` is the structured class above, `allowanceCleared` says whether the
|
|
56
|
+
* router allowance is known to be zero again. The message is for the operator; a consumer that must not
|
|
57
|
+
* relay upstream text reads `cause` and the two fields only.
|
|
58
|
+
*/
|
|
59
|
+
export class DelegatedPlanHaltedError extends Error {
|
|
60
|
+
failedStep;
|
|
61
|
+
allowanceCleared;
|
|
62
|
+
cause;
|
|
63
|
+
clearTransactionHash;
|
|
64
|
+
constructor(message, failedStep, allowanceCleared, cause, clearTransactionHash) {
|
|
65
|
+
super(message);
|
|
66
|
+
this.failedStep = failedStep;
|
|
67
|
+
this.allowanceCleared = allowanceCleared;
|
|
68
|
+
this.cause = cause;
|
|
69
|
+
this.clearTransactionHash = clearTransactionHash;
|
|
70
|
+
this.name = 'DelegatedPlanHaltedError';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** Thrown by the signer itself once revocation was requested for the user on this rig (fail-closed). */
|
|
74
|
+
export class RevocationRequestedError extends Error {
|
|
75
|
+
constructor(userId) {
|
|
76
|
+
super(`refusing to submit: delegation revocation was requested for ${userId} on this rig`);
|
|
77
|
+
this.name = 'RevocationRequestedError';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const ABSENT_DELEGATION_ERROR_TYPES = ['delegation_not_found', 'delegation_revoked', 'delegation_expired'];
|
|
81
|
+
/** Classify a failure from its STRUCTURE (class, `errorType`) — never from its message text. */
|
|
82
|
+
export function haltCauseOf(error) {
|
|
83
|
+
if (error instanceof RevocationRequestedError)
|
|
84
|
+
return 'delegation_not_active';
|
|
85
|
+
if (error instanceof SealedReceiptTimeoutError)
|
|
86
|
+
return 'receipt_not_sealed';
|
|
87
|
+
if (isAdapterRefusal(error))
|
|
88
|
+
return 'artifact_no_longer_signable';
|
|
89
|
+
const errorType = error?.errorType;
|
|
90
|
+
if (errorType === 'policy_violation')
|
|
91
|
+
return 'policy_violation';
|
|
92
|
+
if (typeof errorType === 'string' && ABSENT_DELEGATION_ERROR_TYPES.includes(errorType))
|
|
93
|
+
return 'delegation_not_active';
|
|
94
|
+
return 'upstream_failure';
|
|
95
|
+
}
|
|
96
|
+
function isAbsentDelegation(error) {
|
|
97
|
+
const e = error;
|
|
98
|
+
return e.statusCode === 404 || ABSENT_DELEGATION_ERROR_TYPES.includes(e.errorType ?? '');
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Build a rig from raw operations. The raw operations are captured into the module-private registry;
|
|
102
|
+
* the returned object exposes only reads and revocation. (Callers who hold a CDP client already have
|
|
103
|
+
* the raw capability — this function does not create it, it fences it.)
|
|
104
|
+
*/
|
|
105
|
+
export function createCdpRig(raw) {
|
|
106
|
+
const requested = new Set();
|
|
107
|
+
const rig = Object.freeze({
|
|
108
|
+
pub: raw.pub,
|
|
109
|
+
readDelegation: (userId) => raw.readDelegation(userId),
|
|
110
|
+
// The public revocation goes through the rig's lifecycle lock and HOLDS it through the read-back:
|
|
111
|
+
// it lands only after any in-flight plan on this rig and its allowance clear have settled, and a
|
|
112
|
+
// plan queued behind it runs only after the read-back — where it is refused, because the user was
|
|
113
|
+
// marked revocation-requested BEFORE the raw revoke was issued.
|
|
114
|
+
revoke: (userId) => serializedOn(rig, async () => {
|
|
115
|
+
requested.add(userId);
|
|
116
|
+
try {
|
|
117
|
+
await raw.revoke(userId);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// A grant that never existed (or is already gone) reads back as absent below; other failures surface there.
|
|
121
|
+
}
|
|
122
|
+
const after = await raw.readDelegation(userId);
|
|
123
|
+
if (after)
|
|
124
|
+
throw new Error(`delegation for ${userId} still reads back after revoke (expires ${after.expiresAt})`);
|
|
125
|
+
}),
|
|
126
|
+
});
|
|
127
|
+
RAW.set(rig, raw);
|
|
128
|
+
LOCKS.set(rig, { chain: Promise.resolve() });
|
|
129
|
+
REVOKE_REQUESTED.set(rig, requested);
|
|
130
|
+
return rig;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* `@coinbase/cdp-sdk` is an OPTIONAL dependency of this package, loaded here and only here: an install
|
|
134
|
+
* that never delegates does not need it, and a consumer that does gets a named failure instead of a
|
|
135
|
+
* module-resolution stack when it is missing.
|
|
136
|
+
*/
|
|
137
|
+
async function loadCdpSdk() {
|
|
138
|
+
try {
|
|
139
|
+
return await import('@coinbase/cdp-sdk');
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
143
|
+
throw new Error(`@coinbase/cdp-sdk is not installed: the delegated signer needs the optional dependency (${reason})`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/** Open a rig with Otto's developer credentials against Base mainnet. */
|
|
147
|
+
export async function openCdpRig(credentials, rpcUrl) {
|
|
148
|
+
const { CdpClient } = await loadCdpSdk();
|
|
149
|
+
const cdp = new CdpClient({
|
|
150
|
+
apiKeyId: credentials.apiKeyId,
|
|
151
|
+
apiKeySecret: credentials.apiKeySecret,
|
|
152
|
+
walletSecret: credentials.walletSecret,
|
|
153
|
+
});
|
|
154
|
+
const pub = createPublicClient({ chain: base, transport: http(rpcUrl) });
|
|
155
|
+
let chainId;
|
|
156
|
+
try {
|
|
157
|
+
chainId = await pub.getChainId();
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// FIXED text: viem's own message carries the RPC URL (a key may ride in its path); it is never rethrown.
|
|
161
|
+
throw new Error('the RPC endpoint did not answer eth_chainId; refusing to open the rig');
|
|
162
|
+
}
|
|
163
|
+
if (chainId !== CHAIN_ID)
|
|
164
|
+
throw new Error(`RPC reports chainId ${chainId}, expected ${CHAIN_ID} (Base mainnet)`);
|
|
165
|
+
return createCdpRig({
|
|
166
|
+
pub,
|
|
167
|
+
submit: async (user, transaction) => {
|
|
168
|
+
const result = await cdp.endUser.sendEvmTransaction({ userId: user.userId, address: user.address, transaction, network: NETWORK });
|
|
169
|
+
return result.transactionHash;
|
|
170
|
+
},
|
|
171
|
+
revoke: async (userId) => {
|
|
172
|
+
await cdp.endUser.revokeDelegationForEndUser({ userId });
|
|
173
|
+
},
|
|
174
|
+
readDelegation: async (userId) => {
|
|
175
|
+
try {
|
|
176
|
+
return await cdp.endUser.getDelegationForEndUser({ userId });
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
if (isAbsentDelegation(error))
|
|
180
|
+
return undefined;
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Revoke by `userId` alone, with read-back — usable even when the delegation request's response was
|
|
188
|
+
* lost (the grant may have committed remotely). Resolves only once the grant no longer reads back.
|
|
189
|
+
* This IS `rig.revoke`: serialized on the rig, lock held through the read-back, fail-closed afterwards.
|
|
190
|
+
*/
|
|
191
|
+
export function revokeDelegationByUserId(rig, userId) {
|
|
192
|
+
return rig.revoke(userId);
|
|
193
|
+
}
|
|
194
|
+
async function buildUnsignedTransaction(pub, from, step) {
|
|
195
|
+
const [nonce, fees] = await Promise.all([
|
|
196
|
+
pub.getTransactionCount({ address: from, blockTag: 'pending' }),
|
|
197
|
+
pub.estimateFeesPerGas(),
|
|
198
|
+
]);
|
|
199
|
+
let gas;
|
|
200
|
+
try {
|
|
201
|
+
gas = ((await pub.estimateGas({ account: from, to: step.to, value: step.value, data: step.data })) * 12n) / 10n;
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// An out-of-policy transaction is still a valid transaction; a node-side estimate failure must not
|
|
205
|
+
// be confused with the policy engine refusing. Keep a generous static limit.
|
|
206
|
+
gas = 400000n;
|
|
207
|
+
}
|
|
208
|
+
return serializeTransaction({
|
|
209
|
+
type: 'eip1559',
|
|
210
|
+
chainId: CHAIN_ID,
|
|
211
|
+
nonce,
|
|
212
|
+
to: step.to,
|
|
213
|
+
value: step.value,
|
|
214
|
+
data: step.data,
|
|
215
|
+
gas,
|
|
216
|
+
maxFeePerGas: fees.maxFeePerGas,
|
|
217
|
+
maxPriorityFeePerGas: fees.maxPriorityFeePerGas,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
export class CdpDelegatedSigner {
|
|
221
|
+
capabilities = CDP_CAPABILITIES;
|
|
222
|
+
chainId = CHAIN_ID;
|
|
223
|
+
#rig;
|
|
224
|
+
#raw;
|
|
225
|
+
#user;
|
|
226
|
+
#sealedWait;
|
|
227
|
+
constructor(rig, user, options = {}) {
|
|
228
|
+
const raw = RAW.get(rig);
|
|
229
|
+
if (!raw)
|
|
230
|
+
throw new Error('CdpDelegatedSigner needs a rig created by openCdpRig/createCdpRig');
|
|
231
|
+
this.#rig = rig;
|
|
232
|
+
this.#raw = raw;
|
|
233
|
+
this.#user = Object.freeze({ userId: user.userId, address: user.address });
|
|
234
|
+
this.#sealedWait = options.sealedWait;
|
|
235
|
+
}
|
|
236
|
+
get address() {
|
|
237
|
+
return this.#user.address;
|
|
238
|
+
}
|
|
239
|
+
get user() {
|
|
240
|
+
return this.#user;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Assemble the unsigned transaction from chain reads, run `beforeSubmit` (the plan's freshness /
|
|
244
|
+
* binding re-check, AFTER the RPC round trips and immediately before the hand-off), then submit.
|
|
245
|
+
*/
|
|
246
|
+
async #submit(step, beforeSubmit) {
|
|
247
|
+
const transaction = await buildUnsignedTransaction(this.#raw.pub, this.#user.address, step);
|
|
248
|
+
beforeSubmit?.();
|
|
249
|
+
return this.#raw.submit(this.#user, transaction);
|
|
250
|
+
}
|
|
251
|
+
/** Fail-closed: once revocation was requested for this user on this rig, nothing is submitted for them again. */
|
|
252
|
+
#assertNotRevoked() {
|
|
253
|
+
if (revocationRequested(this.#rig, this.#user.userId))
|
|
254
|
+
throw new RevocationRequestedError(this.#user.userId);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* `send_evm_transaction`, under delegation: only a plan `assertPlanSignableBy` accepts for THIS user,
|
|
258
|
+
* re-checked before every step AND again immediately before each hand-off to CDP (after the nonce /
|
|
259
|
+
* fee / gas round trips, so an artifact that expires during preparation is never submitted). The plan
|
|
260
|
+
* halts at the first step that does not succeed; after ANY failure a bounded `approve(spender, 0)` is
|
|
261
|
+
* attempted under the same policy and its real outcome — confirmed by an allowance read-back — is
|
|
262
|
+
* reported. A failed reset may leave a pre-existing allowance, so step 0 is not exempt.
|
|
263
|
+
*/
|
|
264
|
+
sendPlan(plan) {
|
|
265
|
+
return serializedOn(this.#rig, async () => {
|
|
266
|
+
// Entry, inside the lock: a plan queued behind a revocation lands here AFTER the revoke and its
|
|
267
|
+
// read-back, and is refused before any step — nothing to clear.
|
|
268
|
+
this.#assertNotRevoked();
|
|
269
|
+
assertPlanSignableBy(plan, this, Date.now());
|
|
270
|
+
const out = [];
|
|
271
|
+
for (const [index, step] of plan.steps.entries()) {
|
|
272
|
+
try {
|
|
273
|
+
this.#assertNotRevoked();
|
|
274
|
+
assertPlanSignableBy(plan, this, Date.now());
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
throw await this.#halt(plan, index, `plan no longer signable before step ${index}: ${error instanceof Error ? error.message : String(error)}`, haltCauseOf(error));
|
|
278
|
+
}
|
|
279
|
+
let hash;
|
|
280
|
+
let receipt;
|
|
281
|
+
try {
|
|
282
|
+
hash = await this.#submit(step, () => {
|
|
283
|
+
this.#assertNotRevoked();
|
|
284
|
+
assertPlanSignableBy(plan, this, Date.now());
|
|
285
|
+
});
|
|
286
|
+
await this.#raw.pub.waitForTransactionReceipt({ hash }); // first sight (may be a preconfirmation) …
|
|
287
|
+
// … then only the SEALED, CANONICAL receipt counts: the next step is estimated against sealed state,
|
|
288
|
+
// and a preconfirmation omitted from the sealed block is never reported as success.
|
|
289
|
+
receipt = await waitForSealedReceipt(this.#raw.pub, hash, this.#sealedWait);
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
// The CAUSE is classified from the error's structure (`haltCauseOf`); the message carries the upstream
|
|
293
|
+
// text for the operator and is never what a consumer classifies on.
|
|
294
|
+
const errorType = error.errorType;
|
|
295
|
+
throw await this.#halt(plan, index, `delegated step ${index} (${step.kind}) failed: ${typeof errorType === 'string' && errorType ? `${errorType} | ` : ''}${error instanceof Error ? error.message : String(error)}`, haltCauseOf(error));
|
|
296
|
+
}
|
|
297
|
+
if (receipt.status !== 'success') {
|
|
298
|
+
throw await this.#halt(plan, index, `delegated step ${index} (${step.kind}) reverted on-chain: ${hash}`, 'reverted');
|
|
299
|
+
}
|
|
300
|
+
out.push(Object.freeze({
|
|
301
|
+
index,
|
|
302
|
+
kind: step.kind,
|
|
303
|
+
to: step.to,
|
|
304
|
+
transaction_hash: hash,
|
|
305
|
+
status: 'success',
|
|
306
|
+
block_number: receipt.blockNumber.toString(),
|
|
307
|
+
gas_used: receipt.gasUsed.toString(),
|
|
308
|
+
}));
|
|
309
|
+
}
|
|
310
|
+
return out;
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* After ANY failure: attempt `approve(spender, 0)` under the delegation, then READ the allowance back.
|
|
315
|
+
* `allowanceCleared` is true only when the read-back is zero — a mined clear whose read-back is not
|
|
316
|
+
* zero, a reverted clear, a failed submission or a failed read all report false with the hand-off.
|
|
317
|
+
*/
|
|
318
|
+
async #halt(plan, failedStep, message, cause) {
|
|
319
|
+
const handOff = `clear approve(${plan.spender}, 0) on ${plan.token} BEFORE revoking the delegation`;
|
|
320
|
+
let hash;
|
|
321
|
+
try {
|
|
322
|
+
hash = await this.#submit({
|
|
323
|
+
to: plan.token,
|
|
324
|
+
value: 0n,
|
|
325
|
+
data: encodeFunctionData({ abi: ERC20_APPROVE_ABI, functionName: 'approve', args: [plan.spender, 0n] }),
|
|
326
|
+
});
|
|
327
|
+
await this.#raw.pub.waitForTransactionReceipt({ hash });
|
|
328
|
+
// The read-back below must see the clear: only its sealed canonical receipt counts.
|
|
329
|
+
const clearReceipt = await waitForSealedReceipt(this.#raw.pub, hash, this.#sealedWait);
|
|
330
|
+
if (clearReceipt.status !== 'success') {
|
|
331
|
+
return new DelegatedPlanHaltedError(`${message}; allowance clear ${hash} reverted — ${handOff}`, failedStep, false, cause, hash);
|
|
332
|
+
}
|
|
333
|
+
const remaining = await readAllowance(this.#raw.pub, plan.token, this.#user.address, plan.spender);
|
|
334
|
+
if (remaining !== 0n) {
|
|
335
|
+
return new DelegatedPlanHaltedError(`${message}; allowance clear ${hash} was mined but ${remaining} still reads back — ${handOff}`, failedStep, false, cause, hash);
|
|
336
|
+
}
|
|
337
|
+
return new DelegatedPlanHaltedError(`${message}; allowance cleared in ${hash} (read-back 0)`, failedStep, true, cause, hash);
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
341
|
+
return new DelegatedPlanHaltedError(`${message}; allowance clear ${hash ? `${hash} could not be confirmed` : 'FAILED'} (${reason}) — ${handOff}`, failedStep, false, cause, hash);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* The Otto-side kill switch, with read-back. Serialized on the rig (waits for any in-flight plan and
|
|
346
|
+
* its cleanup, holds the lock through the read-back) and fail-closed: no later plan for this user
|
|
347
|
+
* is submitted on this rig, whatever the read-back said.
|
|
348
|
+
*/
|
|
349
|
+
revokeDeveloperSide() {
|
|
350
|
+
return this.#rig.revoke(this.#user.userId);
|
|
351
|
+
}
|
|
352
|
+
delegation() {
|
|
353
|
+
return this.#rig.readDelegation(this.#user.userId);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
//# sourceMappingURL=cdp-signer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cdp-signer.js","sourceRoot":"","sources":["../../src/adapter/cdp-signer.ts"],"names":[],"mappings":"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"}
|