cascrow-sdk 0.2.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,32 @@
1
+ # Changelog
2
+
3
+ ## 0.7.0 — unreleased (owner publishes)
4
+
5
+ - **`joinContract()` returns `{ contractId, status, activated }`**, and the new export `joinedLine(result)` renders it: `activated: true` — a $0 contract, active now; `false` — wait for the payer's MilestoneEscrowV3 offer and accept it with your own key.
6
+ - **`reviewContract()` returns `{ status }`** — the `txHash` in its type never existed: a review completes a verification-only milestone and moves no money.
7
+ - **New stream action `NOT_COMPLETABLE_WITHOUT_ESCROW`** (with `error` and `remedy`): the panel approved a milestone that carries an amount without a MilestoneEscrowV3 escrow, so nothing completed it and no money moved — contact support, do not resubmit. `mcpSubmit` throws the server's 409 with that code and its hint; `watchVerification` / `waitForProof` return on the first poll with `notCompletableWithoutEscrow: true`. An exhaustive `switch` over `action` will ask for the new arm — that is intended.
8
+ - **Breaking: `releaseHoldUntil` is removed** from `VerificationResult`, `ProofStatus`, `WatchResult` and the `mcpSubmit` result, with the v2 release window it reported.
9
+ - New hint for `RELEASE_HOLD_REMOVED` (the server refuses `releaseHoldHours` on contract creation).
10
+ - **Breaking: `fundMilestone()` is removed** (cascrow Phase 6 — the v2 RLUSD escrow is gone). A contract whose milestones have `amountUSD` 0 is active by itself once the builder has joined; there is nothing to fund. A milestone with an amount above 0 is a MilestoneEscrowV3 escrow in native XRP: the payer offers terms with `client.v3.offer`, the builder accepts with its own key, and the payer funds it with one transaction from its own machine — `npx cascrow-signer@0.1.0 fund --milestone <id>`. The SDK signs nothing.
11
+ - `ReputationResult.stats.totalReleasedUsd` (the value released over every escrow version) and `totalXrpReleased` (native XRP released by MilestoneEscrowV3 escrows). `totalRlusdReleased` is deprecated — the retired v2 escrow only, same value — and goes at the next major.
12
+ - Doc comments no longer say "funds released" for a COMPLETED milestone: a verification-only milestone held no money, and a MilestoneEscrowV3 milestone is paid out on chain by its parties.
13
+
14
+ ## 0.6.0 — unreleased (owner publishes)
15
+
16
+ - `client.v3.offer / acceptance / submitAcceptance / prepare / reportTx / attestation / claims` — the MilestoneEscrowV3 remote calls. The SDK signs nothing and has no keystore or file access; signing is the separate, pinned `cascrow-signer` package (`CASCROW_SIGNER_PIN`). `reportTx` accepts a transaction hash only.
17
+ - `V3Signer` is deliberately NOT part of the SDK: the one implementation of the signer's checks lives in `cascrow-signer` (import its API directly, `import { createSigner } from "cascrow-signer"`), so a browser bundle of this SDK can never pull in a keystore.
18
+ - The verification stream's `action` may be `V3_RECOMMENDATION` (a MilestoneEscrowV3 verdict — a recommendation that moves no money); `mcpSubmit` returns `verdict: "v3_recommendation"` with `decision` and `manifestHash`. Errors with a V3 code carry `.hint` (who can act, and how), also appended to the message.
19
+
20
+ ## 0.5.0 — unreleased (owner publishes)
21
+
22
+ - **Breaking: `escrowFund()` is removed.** It sent the agent's EVM private key to cascrow, which signed the deposit server-side; cascrow no longer accepts, stores or signs with anyone's key. Fund a v2 escrow by signing locally: `GET /api/agent/escrow-prepare` returns the two unsigned transactions, you sign and broadcast them with your own wallet, then `POST /api/escrow/sync`. Funding a MilestoneEscrowV3 contract from an API client is not available yet. **If you ever passed a private key to `escrowFund`, treat that key as exposed and move its funds to a new wallet.**
23
+ - `register()` returns `wallet: null` and `walletNote`: cascrow no longer generates a wallet at registration. Link your own with `GET /api/user/wallet/challenge` → sign → `POST /api/user/wallet`.
24
+
25
+ ## 0.4.0 — 2026-09-25
26
+
27
+ - `waitForProof(proofId, { timeoutMs, intervalMs })` — wait for a proof already submitted. Upload starts verification by itself, so this, not `verify()`, is the call after `submitProof`. Stops by throwing on 401 / 403 / 404 (waiting does not change them), returns `{ pending: true, interrupted: true }` when the run died, and honours `Retry-After` on 429 / 503.
28
+ - `watchVerification` is `submitProof` + `waitForProof`, with the same stop rules (it used to swallow every error and poll out its full timeout).
29
+ - Errors carry `.code` (the API's stable code, or `UPSTREAM_NON_JSON` for a non-JSON answer such as a platform error page) and `.retryAfterSeconds`; a 409 `VERIFICATION_IN_PROGRESS` also carries `.phase`, `.watchUrl` and `.expectedDoneInSeconds`.
30
+ - `submitProof` has a deadline (120 s); before, a stalled upload never returned.
31
+ - `MilestoneInput` takes `verificationCriteria` and `verificationChecklist` — the bar the AI panel judges against — normalised client-side (a bad item is refused with its index before any request).
32
+ - `getProofStatus` types `claimPhase` and `expectedDoneInSeconds`.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # cascrow-sdk
2
2
 
3
- TypeScript client for [Cascrow](https://cascrow.com) — agentic escrow and verification on the XRP Ledger.
3
+ TypeScript client for [Cascrow](https://cascrow.com) — trust infrastructure for the agent economy: AI verification, on-chain reputation, and — for paid work — the remote half of a non-custodial milestone escrow (MilestoneEscrowV3, native XRP).
4
4
 
5
5
  For AI agents connecting through Claude, Cursor, or Codex, use [`cascrow-mcp`](https://www.npmjs.com/package/cascrow-mcp) instead — it's the same API, exposed as MCP tools. This package is for plain application code (backends, scripts, other platforms) that want to call Cascrow directly without going through an LLM tool call.
6
6
 
@@ -18,7 +18,19 @@ import { CascrowClient } from "cascrow-sdk";
18
18
  const client = new CascrowClient({ apiKey: "csk_..." });
19
19
 
20
20
  const { contractId, inviteLink } = await client.createContract({
21
- milestones: [{ title: "Ship the landing page", amountUSD: 500, daysUntilDeadline: 7 }],
21
+ milestones: [
22
+ {
23
+ title: "Ship the landing page", // printed on the certificate — keep it short
24
+ // 0 / absent = verification only, active once the builder joins. Above 0 = a
25
+ // MilestoneEscrowV3 escrow in native XRP (see "Agent-to-agent flow" below).
26
+ amountUSD: 0,
27
+ daysUntilDeadline: 7,
28
+ // The bar the AI panel judges the proof against:
29
+ verificationCriteria: "Responsive down to 320px, LCP under 2 s on mobile, CTA links to /signup",
30
+ verificationChecklist: [{ title: "Mobile layout", severity: "HIGH" }, { title: "Signup CTA" }],
31
+ },
32
+ ],
33
+ agentReviewMode: "MANUAL", // what happens when the panel is unsure (60–85 % confidence)
22
34
  });
23
35
 
24
36
  console.log(`Contract created: ${contractId}, invite: ${inviteLink}`);
@@ -28,7 +40,7 @@ Get an API key via `client.register({ email, password })` (no CAPTCHA, no email
28
40
 
29
41
  ## Sandbox
30
42
 
31
- Escrow currently settles on the XRPL EVM Testnet (chain 1449000) using MockRLUSD, a freely mintable test token — every `amountUSD` you see in the examples below moves test funds, not real money. AI verification is real (live calls to the 5-model panel), and NFT certificates are issued on XRP Ledger Mainnet. See the [Guide](https://cascrow.com/guide) for wallet setup and getting test RLUSD from the faucet.
43
+ MilestoneEscrowV3 runs on the XRPL EVM Sidechain **testnet** (chain 1449000) with native test XRP from the faucet — an `amountUSD` above 0 in the examples below moves test XRP, not real money. The escrow is a smart contract with no platform role: cascrow holds no key, never signs and is never the arbiter. AI verification is real (live calls to the 5-model panel), and NFT certificates are issued on XRP Ledger Mainnet. See the [Guide](https://cascrow.com/guide) for wallet setup and test XRP.
32
44
 
33
45
  ## Verification without escrow
34
46
 
@@ -47,6 +59,8 @@ console.log(result.decision, result.confidence, result.reportUrl);
47
59
 
48
60
  Verification is rate-limited to 3 per IP per 24h. Passing an `apiKey` attributes each result to your account (so it shows up in your usage) but does not currently raise that allowance — account-based verification credits are not yet purchasable.
49
61
 
62
+ Passing an `apiKey` also anchors the verdict on the XRP Ledger mainnet — a content-free commitment hash written into an on-chain memo, independently recomputable from the report (see `GET /api/verify/result/{hash}` in the [API reference](https://cascrow.com/api-docs)). Anonymous calls are never anchored. `result.id`, `result.reportUrl`, and the anchor fields you'd poll for (`anchorHash`, `xrplTxHash`, `anchoredAt`) live at that report URL, not in the `verifyWork()` response — anchoring happens asynchronously after the call returns.
63
+
50
64
  ## Agent-to-agent flow
51
65
 
52
66
  ```ts
@@ -56,14 +70,26 @@ const builder = new CascrowClient({ apiKey: builderKey });
56
70
  const { contractId, inviteLink } = await requester.createContract({
57
71
  milestones: [{ title: "Build the API", amountUSD: 1000 }],
58
72
  });
59
- await requester.escrowFund({ contractId, agentPrivateKey: requesterEvmKey, amountUSD: 1000 }); // testnet MockRLUSD, not real funds — see Sandbox above
60
-
61
73
  await builder.joinContract(inviteLink!);
62
- const { proofId } = await builder.submitProof({ milestoneId, content: "Done — see PR #12, all tests pass." });
63
- const result = await builder.verify(proofId);
64
74
 
65
- if (result.action === "VERIFIED") {
66
- console.log("Funds released automatically:", result.txHash);
75
+ // MilestoneEscrowV3 (native XRP, testnet): the payer offers terms, the builder accepts with
76
+ // its own key, the payer funds with ONE transaction from its own machine. The SDK signs
77
+ // nothing — signing is the separate, pinned local signer (npx cascrow-signer@0.1.0):
78
+ await requester.v3.offer({ milestoneId, reviewPeriodSec: 72 * 3600, watcherDeclared: false });
79
+ // builder: npx cascrow-signer@0.1.0 accept --milestone <milestoneId>
80
+ // requester: npx cascrow-signer@0.1.0 fund --milestone <milestoneId>
81
+ // cascrow never accepts, creates, stores or signs with a private key (escrowFund(), which
82
+ // took one, was removed in 0.5.0 — if you ever passed a key to it, treat that key as exposed).
83
+ // A contract with amountUSD 0 needs none of this: it is active once the builder has joined.
84
+
85
+ const { proofId } = await builder.submitProof({ milestoneId, content: "Done — see PR #12, all tests pass." });
86
+ // Upload starts verification by itself — wait for it, don't start it again.
87
+ const result = await builder.waitForProof(proofId);
88
+ // For a MilestoneEscrowV3 milestone the AI verdict is a signed, non-binding recommendation:
89
+ // the builder claims on chain (npx cascrow-signer@0.1.0 submit-claim --milestone <id> --proof-hash <manifestHash>),
90
+ // the payer releases or objects before the objection deadline.
91
+ if (!result.pending && result.milestoneStatus === "COMPLETED") {
92
+ console.log("Milestone complete.");
67
93
  }
68
94
  ```
69
95
 
@@ -79,14 +105,16 @@ if (result.action === "VERIFIED") {
79
105
  | `getContract` | `GET /api/contracts/{id}` |
80
106
  | `listMyContracts` | `GET /api/agent/my-contracts` |
81
107
  | `joinContract` | `POST /api/contracts/join` |
82
- | `reviewContract` | `POST /api/contracts/review` |
108
+ | `reviewContract` | `POST /api/contracts/review` — pass `milestoneId` when more than one milestone is pending review |
109
+ | `reviewQueue` | `GET /api/agent/review-queue` — the contracts on which you, as funder, still owe a verdict. The pull behind the `manual_review.required` webhook: verification-only ($0) contracts; an undecided milestone is completed automatically after 48 h under review mode `MANUAL_AUTO` or `AUTO`, 14 days otherwise. Moves no money |
110
+ | `v3Claims` | `GET /api/agent/v3-claims` — as payer of a MilestoneEscrowV3 escrow, the claims that release it unless your own wallet calls `object(escrowId)` before `objectionDeadline`. Throws `V3_INDEX_STALE` (503) while the index is not current: read the chain then |
111
+ | `v3.offer` / `v3.acceptance` / `v3.submitAcceptance` / `v3.prepare` / `v3.reportTx` / `v3.attestation` / `v3.claims` | MilestoneEscrowV3, remote half. `prepare` returns an UNSIGNED transaction — rebuild `data` and check `to`/`chainId` yourself before signing; `reportTx` takes a hash only. This SDK signs nothing: use the separate local signer, pinned — `npx cascrow-signer@0.1.0` (or `import { createSigner } from "cascrow-signer"` in Node). There is deliberately no `V3Signer` in this package. |
83
112
  | `checkInvites` | `GET /api/agent/pending-invites` |
84
113
  | `handoff` | `POST /api/agent/handoff` |
85
- | `fundMilestone` | `POST /api/agent/fund-milestone` |
86
- | `escrowFund` | `POST /api/agent/escrow-fund` |
87
114
  | `submitProof` | `POST /api/proof/upload` |
88
115
  | `getProofStatus` | `GET /api/agent/proof-status/{proofId}` |
89
- | `verify` | `POST /api/verify` (streamed) |
116
+ | `waitForProof` | poll `getProofStatus` until the proof settles — the call to make after `submitProof` |
117
+ | `verify` | `POST /api/verify` (streamed) — start or re-run a verification by hand |
90
118
  | `mcpSubmit` | `POST /api/mcp/submit` |
91
119
  | `verifyWork` | `POST /api/verify/standalone` |
92
120
  | `watchVerification` | `submitProof` + poll |
@@ -96,7 +124,9 @@ if (result.action === "VERIFIED") {
96
124
 
97
125
  ## Errors
98
126
 
99
- Failed requests throw an `Error` with `.status` (HTTP status code) and `.body` (parsed JSON error response) attached.
127
+ Failed requests throw an `Error` with `.status` (HTTP status code), `.body` (parsed JSON error response), `.code` (the stable error code — branch on this, not on the message; `UPSTREAM_NON_JSON` when the server answered with something that is not JSON) and `.retryAfterSeconds` (how long the server asked you to wait) attached.
128
+
129
+ `verify(proofId)` on a proof whose verification is already running (usually the one upload started) throws `code: "VERIFICATION_IN_PROGRESS"` with `phase: "running"` — call `waitForProof(proofId)`. Its `retryAfterSeconds` is when an abandoned run could be taken over (about an hour), not a backoff.
100
130
 
101
131
  ```ts
102
132
  try {
package/dist/client.d.ts CHANGED
@@ -1,20 +1,114 @@
1
- import type { Contract, CreateContractInput, CreateContractResult, DiscoverAgentsResult, MyContractSummary, RegisterInput, RegisterResult, ReputationResult, VerificationResult, VerifyWorkInput, VerifyWorkResult } from "./types.js";
1
+ import type { Contract, CreateContractInput, CreateContractResult, DiscoverAgentsResult, MyContractSummary, ReviewQueueResult, V3ClaimsResult, RegisterInput, RegisterResult, ReputationResult, VerificationResult, VerifyWorkInput, VerifyWorkResult } from "./types.js";
2
+ import type { V3AcceptanceData, V3Action, V3Attestation, V3OfferInput, V3PreparedTx } from "./v3.js";
2
3
  export interface CascrowClientOptions {
3
4
  /** API key starting with csk_. Omit only for endpoints that support anonymous use (register, getReputation, verifyWork). */
4
5
  apiKey?: string;
5
6
  /** Override the API base URL. Defaults to https://cascrow.com. */
6
7
  baseUrl?: string;
7
8
  }
8
- /**
9
- * Typed client for the Cascrow API. Wraps the same REST endpoints used by
10
- * the cascrow-mcp server and the Cascrow web app — this is the plain-code
11
- * equivalent for applications that don't go through an MCP/LLM tool call.
12
- */
9
+ /** What `watchVerification` and `waitForProof` resolve to. */
10
+ export type WatchResult = {
11
+ pending: false;
12
+ /** The verdict on this proof, or `null` when the route reported none. */
13
+ decision: "YES" | "NO" | null;
14
+ confidence: number | null;
15
+ /** `"COMPLETED"`: the milestone is complete (see `escrowVersion` for whether money was held). */
16
+ milestoneStatus?: string;
17
+ /**
18
+ * The milestone carries an amount without a MilestoneEscrowV3 escrow, so no
19
+ * verdict can complete it (cascrow has a case open; contact support, do not
20
+ * resubmit). Read it BEFORE `decision`: a YES here is not done.
21
+ */
22
+ notCompletableWithoutEscrow: boolean;
23
+ /**
24
+ * The milestone is TERMINAL (`terminalStatus`: EXPIRED or COMPLETED) and this
25
+ * proof's verdict cannot move it. Read this BEFORE `decision`.
26
+ */
27
+ milestoneClosed: boolean;
28
+ terminalStatus: string | null;
29
+ /** See `getProofStatus` — `"RELEASED_ON_CHAIN"` means the builder WAS paid on-chain. */
30
+ terminalReason: string | null;
31
+ /**
32
+ * The milestone is VERIFIED or COMPLETED, but not on a YES from this proof —
33
+ * another proof won it, or the funder approved it in review. `decision` is
34
+ * this proof's `"NO"` or `null`; the payout, if any, is not this proof's.
35
+ */
36
+ approvedWithoutThisVerdict: boolean;
37
+ /**
38
+ * `3` for a MilestoneEscrowV3 milestone, `2` otherwise (absent on an older server).
39
+ * On `3` the verdict is a RECOMMENDATION and moves no money: read
40
+ * `submissionVerdict` / `claimReady`, and claim on chain with `manifestHash`
41
+ * (`npx cascrow-signer submit-claim --proof-hash <manifestHash>`).
42
+ */
43
+ escrowVersion?: 2 | 3;
44
+ /** V3 only: the sealed submission this proof belongs to (one evidence request = one submission). */
45
+ submissionId?: string | null;
46
+ /** V3 only: the submission's manifest hash — the proofHash a claim names on chain. */
47
+ manifestHash?: string | null;
48
+ /** V3 only: the SUBMISSION's write-once verdict — YES only when every proof in it has a definite yes. */
49
+ submissionVerdict?: "YES" | "NO" | "PENDING" | null;
50
+ /** V3 only: `submissionVerdict === "YES"` — the claim cascrow recommends. */
51
+ claimReady?: boolean;
52
+ reasoning?: string;
53
+ } | {
54
+ pending: true;
55
+ proofId: string;
56
+ /**
57
+ * Set when the panel produced no verdict and the milestone is at
58
+ * `PENDING_REVIEW`: a human decides, and polling will not change that.
59
+ */
60
+ awaitingHumanReview?: true;
61
+ /** The run that held this proof died; nothing is running. Submitting the proof again always works; `verify()` can restart it once its claim is an hour old. */
62
+ interrupted?: true;
63
+ };
13
64
  export declare class CascrowClient {
14
65
  private readonly apiKey?;
15
66
  private readonly baseUrl;
16
67
  constructor(options?: CascrowClientOptions);
68
+ /**
69
+ * MilestoneEscrowV3 — the remote half. cascrow prepares and reads; YOU sign and
70
+ * send with your own key. This SDK never signs: use the separate, pinned
71
+ * `cascrow-signer` package (`npx cascrow-signer@<pin> …`, see CASCROW_SIGNER_PIN),
72
+ * which checks every prepared transaction against its own chain and contract.
73
+ */
74
+ readonly v3: {
75
+ /** As PAYER: offer terms for a milestone. 409 `OFFER_EXISTS` names the open offer. */
76
+ offer: (input: V3OfferInput) => Promise<Record<string, unknown>>;
77
+ /** As PAYEE: the frozen offer to check and sign (EIP-712). 409 `V3_NOT_OFFERED` before the payer offered. */
78
+ acceptance: (milestoneId: string) => Promise<V3AcceptanceData>;
79
+ /** As PAYEE: hand over the acceptance signature — binding until acceptBy. */
80
+ submitAcceptance: (offerId: string, signature: string) => Promise<{
81
+ escrowId: string;
82
+ acceptBy: string;
83
+ }>;
84
+ /** An UNSIGNED transaction. Rebuild `data` and check `to`/`chainId` yourself before signing. */
85
+ prepare: (milestoneId: string, action: V3Action, extra?: {
86
+ proofHash?: string;
87
+ reasonHash?: string;
88
+ newDeadline?: string | number;
89
+ }) => Promise<V3PreparedTx>;
90
+ /** After YOU broadcast: the hash as a hint to re-read the chain. Only a hash — never a signed transaction. */
91
+ reportTx: (milestoneId: string, txHash: string) => Promise<Record<string, unknown>>;
92
+ /** cascrow's signed, NON-BINDING AI recommendation for a claim. 409 `ATTESTATION_PENDING` while the panel runs. */
93
+ attestation: (escrowId: string, claimIndex: number) => Promise<V3Attestation>;
94
+ /** As PAYER: open claims you can still object to (same as `v3Claims()`). */
95
+ claims: () => Promise<V3ClaimsResult>;
96
+ };
17
97
  private authHeaders;
98
+ /**
99
+ * Every non-streaming call carries a deadline (SEC-073).
100
+ *
101
+ * Before this, the only `AbortController` in the SDK was inside `verify()`, the
102
+ * streaming method — so `verifyWork()`, which waits on a route the platform
103
+ * kills at 120 s, waited forever and then surfaced a torn connection. The
104
+ * default is the longest cap any route in this API declares (240 s), so it
105
+ * cannot cut a legitimately slow on-chain call short; `verifyWork` passes the
106
+ * tighter one because its own route is capped lower.
107
+ *
108
+ * An `AbortError` is re-thrown as a sentence, not as the DOM error: a caller
109
+ * cannot tell "we stopped waiting" from "the panel said no" out of an
110
+ * exception whose message is "This operation was aborted" (F-72).
111
+ */
18
112
  private request;
19
113
  /** Self-register an agent account. No CAPTCHA, no email verification. Returns an API key — save it, it is shown only once. */
20
114
  register(input: RegisterInput): Promise<RegisterResult>;
@@ -33,15 +127,80 @@ export declare class CascrowClient {
33
127
  contracts: MyContractSummary[];
34
128
  total: number;
35
129
  }>;
36
- /** Join an existing contract as the Builder using an invite code. */
130
+ /**
131
+ * Join an existing contract as the Builder using an invite code.
132
+ *
133
+ * `activated: true` — a $0 verification contract: it is active (`status: "FUNDED"`)
134
+ * and you can submit proof now. `activated: false` — the milestones carry an amount:
135
+ * wait for the payer's MilestoneEscrowV3 offer and accept it with your own wallet key
136
+ * (`npx cascrow-signer@0.1.0 accept --milestone <id>`, or `client.v3.acceptance` +
137
+ * `client.v3.submitAcceptance`); proof is accepted once the payer has funded it.
138
+ * `joinedLine(result)` renders that as one sentence.
139
+ */
37
140
  joinContract(inviteCode: string): Promise<{
38
141
  contractId: string;
142
+ status: string;
143
+ activated: boolean;
39
144
  }>;
40
- /** Approve or reject a borderline (PENDING_REVIEW) contract. Only valid when agentReviewMode is MANUAL or MANUAL_AUTO. */
41
- reviewContract(contractId: string, decision: "APPROVE" | "REJECT"): Promise<{
145
+ /**
146
+ * Approve or reject a borderline (PENDING_REVIEW) MILESTONE. Only valid when
147
+ * agentReviewMode is MANUAL or MANUAL_AUTO.
148
+ *
149
+ * The CONTRACT row need not be PENDING_REVIEW: funding a later milestone
150
+ * overwrites that column, and such a contract is still reviewable. Only a
151
+ * terminal contract (COMPLETED, EXPIRED, DECLINED) is refused.
152
+ *
153
+ * `milestoneId` is optional while exactly one milestone of the contract is
154
+ * pending review and REQUIRED once more than one is — the API answers 400
155
+ * "Multiple milestones are pending review — specify milestoneId to
156
+ * disambiguate". Until this parameter existed an SDK caller had no way to
157
+ * comply with that refusal, which is exactly the case SEC-076 is about: a
158
+ * multi-milestone contract where a second proof lands while the first is
159
+ * still under review (SEC-120, F-72).
160
+ *
161
+ * Additive optional third argument — every existing call site still compiles.
162
+ */
163
+ reviewContract(contractId: string, decision: "APPROVE" | "REJECT", milestoneId?: string): Promise<{
164
+ /**
165
+ * The contract's status after the decision. APPROVE completes a verification-only
166
+ * ($0) milestone — no money moves; there is no transaction. A MilestoneEscrowV3
167
+ * milestone is refused here: its payer releases or objects on chain.
168
+ */
42
169
  status: string;
43
- txHash: string | null;
44
170
  }>;
171
+ /**
172
+ * The contracts on which THIS agent, as funder, still owes a manual verdict.
173
+ *
174
+ * The pull behind the `manual_review.required` webhook. A webhook that does
175
+ * not arrive — the agent was down, the endpoint 500'd, the subscription was
176
+ * never made — is a decision that never gets made, and `cron/cancel-expired`
177
+ * then completes the milestone after 48 h (agent contracts in MANUAL_AUTO or
178
+ * AUTO) or 14 days (every other contract) — verification-only contracts, so
179
+ * no money moves, and nobody decided it. The whole point of the 60–85 confidence
180
+ * band is that somebody looks.
181
+ *
182
+ * Every browser surface the funder has for this asks
183
+ * `funderReviewQueueWhere()`; so does this route, so the queue an agent polls
184
+ * and the queue `reviewContract` accepts a verdict on cannot come apart.
185
+ *
186
+ * `reviewMilestoneId` is what to pass to {@link reviewContract}: non-null when
187
+ * exactly one milestone of that contract is pending, `null` for the legacy
188
+ * contract-level population that is reviewed by `contractId` alone. When
189
+ * `pendingMilestones` holds more than one entry the milestone has to be chosen
190
+ * — `POST /api/contracts/review` answers 400 otherwise.
191
+ *
192
+ * `truncated` says the list is capped while `total` is not, so a poller does
193
+ * not read a short list as "that was all".
194
+ */
195
+ reviewQueue(): Promise<ReviewQueueResult>;
196
+ /**
197
+ * As the PAYER of a MilestoneEscrowV3 escrow: the claims that release it to the
198
+ * builder unless your own wallet calls `object(escrowId)` before
199
+ * `objectionDeadline` — the pull behind the `claim.submitted` webhook. cascrow
200
+ * cannot object for you. Throws with code `V3_INDEX_STALE` (503) while the index
201
+ * is not current; read the chain directly then.
202
+ */
203
+ v3Claims(): Promise<V3ClaimsResult>;
45
204
  /** Check for pending contract invites sent to this agent. */
46
205
  checkInvites(): Promise<{
47
206
  invites: unknown[];
@@ -55,26 +214,11 @@ export declare class CascrowClient {
55
214
  }): Promise<{
56
215
  handoffId: string;
57
216
  }>;
58
- /** Fund (activate) a milestone via MetaMask/browser flow so it can receive proof. */
59
- fundMilestone(contractId: string, milestoneId?: string): Promise<{
60
- milestoneId: string;
61
- contractId: string;
62
- status: string;
63
- }>;
64
- /** Fund a milestone with real on-chain RLUSD escrow using the agent's own EVM private key. */
65
- escrowFund(input: {
66
- contractId: string;
67
- agentPrivateKey: string;
68
- amountUSD: number;
69
- milestoneId?: string;
70
- }): Promise<{
71
- milestoneId: string;
72
- contractId: string;
73
- txHash: string;
74
- amountUSD: number;
75
- status: string;
76
- }>;
77
- /** Submit a text proof report for a milestone. Automatically triggers AI verification. */
217
+ /**
218
+ * Submit a text proof report for a milestone. Verification starts by itself —
219
+ * wait for the verdict with `waitForProof(proofId)`; do not call `verify()` for it,
220
+ * that run is already going.
221
+ */
78
222
  submitProof(input: {
79
223
  milestoneId: string;
80
224
  content: string;
@@ -85,11 +229,73 @@ export declare class CascrowClient {
85
229
  /** Check the AI verification result for a submitted proof (poll after submitProof). */
86
230
  getProofStatus(proofId: string): Promise<{
87
231
  pending?: boolean;
232
+ /**
233
+ * `true` while the verification panel is running on this proof right now. A running
234
+ * proof is never final, whatever `milestoneStatus` says: a `PENDING_REVIEW` a sibling
235
+ * proof set, or a `VERIFIED` that a failed release or a review hands back, can still
236
+ * be decided by it. `pending` without `running` is the other cause: not claimed yet,
237
+ * or a panel that produced no verdict. Absent on a server that predates the field.
238
+ */
239
+ running?: boolean;
240
+ /**
241
+ * `"running"` — a live run holds the proof (wait for it); `"orphaned"` — the run
242
+ * died, nothing is running; `"unclaimed"` / `"decided"`. Absent on an older server.
243
+ */
244
+ claimPhase?: "unclaimed" | "running" | "orphaned" | "decided";
245
+ /** While `claimPhase` is `"running"`: roughly when the verdict is due. */
246
+ expectedDoneInSeconds?: number;
247
+ /** The verdict on THIS PROOF. It does not say whether the money moved. */
88
248
  decision?: "YES" | "NO";
89
249
  confidence?: number | null;
90
250
  contractStatus?: string;
91
251
  milestoneTitle?: string;
252
+ /**
253
+ * The MILESTONE's state, which is the question most callers are actually asking.
254
+ * `"COMPLETED"`: the milestone is complete — a verification-only milestone held no
255
+ * money; a MilestoneEscrowV3 milestone was paid out on chain by its parties.
256
+ */
92
257
  milestoneStatus?: string;
258
+ /**
259
+ * `true`: this proof's milestone carries an amount without a MilestoneEscrowV3
260
+ * escrow, so no verdict can complete it — the verdict doors answer 409
261
+ * `NOT_COMPLETABLE_WITHOUT_ESCROW` and cascrow has a case open. Stop polling,
262
+ * contact support, do not resubmit. (Replaces `releaseHoldUntil`, which reported
263
+ * the removed v2 release window.)
264
+ */
265
+ notCompletableWithoutEscrow?: boolean;
266
+ /**
267
+ * `true` when the milestone (or, for a legacy contract-level proof, the contract)
268
+ * is TERMINAL — `EXPIRED` or `COMPLETED` — so no verdict on this proof can move it
269
+ * any more. Check it BEFORE `decision` and `pending`: a milestone the funder
270
+ * cancelled, or the deadline cron closed, while the panel ran stays `EXPIRED` with
271
+ * this proof's `decision` still recorded (YES or NO) — or with `pending: true` for
272
+ * good, when the panel could not reach a verdict. A YES here moved no funds.
273
+ */
274
+ milestoneClosed?: boolean;
275
+ /** `"EXPIRED"` or `"COMPLETED"` when `milestoneClosed`, else null. */
276
+ terminalStatus?: string | null;
277
+ /**
278
+ * Why an `EXPIRED` milestone ended, when recorded. `"RELEASED_ON_CHAIN"` means the
279
+ * chain shows the builder WAS paid — just not by this verification; the other
280
+ * values (`DEADLINE_EXPIRED`, `CANCELLED_AFTER_APPROVAL`, …) mean the funder was
281
+ * refunded or no escrow existed. Null when not recorded.
282
+ */
283
+ terminalReason?: string | null;
284
+ /**
285
+ * `3` for a MilestoneEscrowV3 milestone, `2` otherwise (absent on an older server).
286
+ * On `3` the verdict is a RECOMMENDATION and moves no money: read
287
+ * `submissionVerdict` / `claimReady`, and claim on chain with `manifestHash`
288
+ * (`npx cascrow-signer submit-claim --proof-hash <manifestHash>`).
289
+ */
290
+ escrowVersion?: 2 | 3;
291
+ /** V3 only: the sealed submission this proof belongs to (one evidence request = one submission). */
292
+ submissionId?: string | null;
293
+ /** V3 only: the submission's manifest hash — the proofHash a claim names on chain. */
294
+ manifestHash?: string | null;
295
+ /** V3 only: the SUBMISSION's write-once verdict — YES only when every proof in it has a definite yes. */
296
+ submissionVerdict?: "YES" | "NO" | "PENDING" | null;
297
+ /** V3 only: `submissionVerdict === "YES"` — the claim cascrow recommends. */
298
+ claimReady?: boolean;
93
299
  reasoning?: string;
94
300
  }>;
95
301
  /**
@@ -107,7 +313,9 @@ export declare class CascrowClient {
107
313
  }): Promise<VerificationResult>;
108
314
  /**
109
315
  * Submit proof and get an immediate verdict in one call — runs the full
110
- * 5-model pipeline and, on approval, releases funds and mints the NFT.
316
+ * 5-model pipeline. On approval a verification-only milestone completes and its NFT
317
+ * is minted; for a MilestoneEscrowV3 milestone the verdict is a recommendation
318
+ * (`verdict: "v3_recommendation"`) and moves no money.
111
319
  */
112
320
  mcpSubmit(input: {
113
321
  contractId: string;
@@ -120,11 +328,50 @@ export declare class CascrowClient {
120
328
  customFields?: Record<string, unknown>;
121
329
  };
122
330
  }): Promise<{
123
- verdict: "approved" | "rejected" | "pending_review";
124
- confidence: number;
331
+ /**
332
+ * SEC-059's shape at a fourth consumer, found by deriving the set of readers from
333
+ * what the route SENDS rather than from the two MCP servers a finding compared.
334
+ *
335
+ * The route has two 200 responses that carry NO `verdict` key at all, and this
336
+ * union claimed three values:
337
+ *
338
+ * - `decided_elsewhere` — the milestone was settled by a concurrent request or by
339
+ * the investor's review while this proof was being verified. The route answers
340
+ * `{ ok: true, decided: "elsewhere" }`; `verdict` was simply `undefined` here,
341
+ * outside its own declared type, and a consumer treating it as falsy read it as
342
+ * a rejection.
343
+ * - `panel_unavailable` — fewer than three of the five models answered. OUR
344
+ * outage, not a judgement of the work, and resubmitting will not help.
345
+ *
346
+ * An approved milestone that nothing can complete (an amount without a
347
+ * MilestoneEscrowV3 escrow) is not a 200: the route answers 409
348
+ * `NOT_COMPLETABLE_WITHOUT_ESCROW`, thrown as a `CascrowApiError` with its hint.
349
+ */
350
+ /**
351
+ * `v3_recommendation`: the milestone is a MilestoneEscrowV3 escrow and the panel's
352
+ * verdict is a RECOMMENDATION — it moves no money. `decision` carries it; the payee
353
+ * claims on chain with the same evidence (`manifestHash`), the payer releases or objects.
354
+ */
355
+ verdict: "approved" | "rejected" | "pending_review" | "decided_elsewhere" | "panel_unavailable" | "v3_recommendation";
356
+ /** With `v3_recommendation`: the panel's YES/NO on this one file — NOT the claim verdict; read `submissionVerdict`. */
357
+ decision?: string | null;
358
+ /** With `v3_recommendation`: the sealed submission's manifest hash (the claim's proofHash). */
359
+ manifestHash?: string | null;
360
+ /** With `v3_recommendation`: the submission's verdict — YES only when every file has a definite AI yes. */
361
+ submissionVerdict?: "YES" | "NO" | "PENDING";
362
+ /** With `v3_recommendation`: true exactly when `submissionVerdict` is YES. */
363
+ claimReady?: boolean;
364
+ confidence: number | null;
125
365
  reasoning: string;
126
366
  onChainUrl: string | null;
127
367
  proofId: string | null;
368
+ /**
369
+ * With `decided_elsewhere`: the TERMINAL status the milestone was already in
370
+ * (`"EXPIRED"`, `"COMPLETED"`) — it was closed before this submission could settle
371
+ * it, e.g. by the funder's cancel or the deadline, and resubmitting is pointless.
372
+ * Null when it was decided by the investor's review or a concurrent submission.
373
+ */
374
+ settledAs: string | null;
128
375
  }>;
129
376
  /**
130
377
  * Verify AI-generated work with no escrow contract — a task description
@@ -134,25 +381,84 @@ export declare class CascrowClient {
134
381
  * yet, so do not treat a key as a way to get more runs.
135
382
  */
136
383
  verifyWork(input: VerifyWorkInput): Promise<VerifyWorkResult>;
384
+ /**
385
+ * True when a thrown `CascrowApiError` means "cascrow could not run the panel",
386
+ * not "the work was not verified" (SEC-128).
387
+ *
388
+ * `verifyWork` resolves ONLY when a verdict exists. When fewer than three of the
389
+ * five models answer, the endpoint answers 503 with `panelUnavailable: true` and
390
+ * carries NO decision and NO confidence, so `request` throws — deliberately, and
391
+ * that is the whole repair: the previous behaviour was a 200 carrying
392
+ * `decision: "NO", confidence: 65`, which every consumer rendered as a rejection
393
+ * of the submitted work. An outage is not a verdict, and a retry is the right
394
+ * next move rather than a resubmission with "stronger" evidence.
395
+ *
396
+ * A thrown 500 from the same endpoint is NOT this: it reports `false` here,
397
+ * because "the models did not answer" is a claim about the panel and a crash is
398
+ * not evidence for it.
399
+ */
400
+ static isPanelUnavailable(err: unknown): boolean;
137
401
  /**
138
402
  * Submit a proof and wait for AI verification to complete — combines
139
403
  * submitProof + polling into a single call. Times out after ~100s and
140
404
  * returns { pending: true, proofId } so you can poll manually.
405
+ *
406
+ * Returns early, with `milestoneClosed: true`, when the milestone is already
407
+ * terminal — including when no verdict was recorded, which on a closed milestone
408
+ * will never change. Check `milestoneClosed` before `decision`: a `"YES"` on a
409
+ * closed, `EXPIRED` milestone is the panel's opinion of the proof and moved no funds.
410
+ *
411
+ * Also returns early, with `approvedWithoutThisVerdict: true`, when the milestone is
412
+ * VERIFIED or COMPLETED without a YES from this proof (another proof won it, or the
413
+ * funder approved it in review): `decision` is then `"NO"` or `null`, and the
414
+ * milestone's outcome is NOT this proof's.
415
+ *
416
+ * A proof parked for a human (no verdict, milestone at `PENDING_REVIEW`) returns
417
+ * `{ pending: true, proofId, awaitingHumanReview: true }` on the first poll instead of
418
+ * after the full timeout: no poll will change it, the funder decides.
419
+ *
420
+ * Neither early return is taken while the server reports `running: true` on a
421
+ * VERIFIED or PENDING_REVIEW milestone: this proof's panel is still running and can
422
+ * still decide the row, so the loop keeps polling. A server that predates `running`
423
+ * cannot say so, and there the early returns stay as described.
141
424
  */
142
425
  watchVerification(input: {
143
426
  milestoneId: string;
144
427
  content: string;
145
428
  filename?: string;
146
- }): Promise<{
147
- pending: false;
148
- decision: "YES" | "NO";
149
- confidence: number | null;
150
- reasoning?: string;
151
- } | {
152
- pending: true;
153
- proofId: string;
154
- }>;
155
- /** Look up any agent's on-chain reputation by EVM wallet address. No auth required. */
429
+ }): Promise<WatchResult>;
430
+ /**
431
+ * Wait for the verification of a proof that is already submitted — the call to
432
+ * make after `submitProof`, and after `verify()` threw a 409 VERIFICATION_IN_PROGRESS
433
+ * with `phase: "running"` (T01-7). Same results as `watchVerification`, which is
434
+ * this plus the upload.
435
+ *
436
+ * Stops early, by throwing, when waiting cannot help: 401 / 403 (the key or the
437
+ * account does not change by waiting) and 404 (permanent — upload answers only
438
+ * after the proof exists and proof-status reads the primary database, so a 404
439
+ * means deleted or erased). Returns `{ pending: true, interrupted: true }` when the
440
+ * run that held the proof died (`claimPhase: "orphaned"`). On 429 / 503 it waits at
441
+ * least the server's Retry-After, never past `timeoutMs`; network errors and other
442
+ * 5xx are transient and it polls again.
443
+ */
444
+ waitForProof(proofId: string, options?: {
445
+ timeoutMs?: number;
446
+ intervalMs?: number;
447
+ }): Promise<WatchResult>;
448
+ /**
449
+ * Look up any agent's on-chain reputation by EVM wallet address. No auth required.
450
+ *
451
+ * `summary` reaches you RAW, and so do `name` and `skills` from `discoverAgents`
452
+ * below. The route deliberately does not mark them (the browser profile page renders
453
+ * the same fields) and both MCP servers do mark them — so this SDK is a machine
454
+ * boundary that does not, exactly like `verify()`.
455
+ *
456
+ * The DECISION is the same and so is its reason: a library cannot know whether its
457
+ * return value ends in a model's context or on a page, where a marker is noise.
458
+ * What was missing here was the REASON, stated at `verify()` and nowhere
459
+ * else, which made this door look like an oversight rather than a choice (SEC-120).
460
+ * If you feed these fields to a model, treat them as data and not as instructions.
461
+ */
156
462
  getReputation(walletAddress: string): Promise<ReputationResult>;
157
463
  /** Search for discoverable Builder agents by skill tag and minimum success rate. */
158
464
  discoverAgents(options?: {