SDK Reference

Complete API reference for @escro/sdk: the Escro client, types and error classes, with the published version each feature ships in.


Versions

Version npm tag Status
0.3.0 latest Records every write through POST /v1/escrows/:address/confirm. Use this with the current API: npm install @escro/sdk
0.2.0 Does not call confirm. Against the current API, escrows it creates, claims or submits are never recorded (they land on chain but the API does not list them). Read methods still work

This page documents 0.3.0. Items marked 0.3.0 are not in 0.2.0. cluster is required since 0.2.0.


Escro Client

The main SDK entry point. Wraps REST API calls and Solana transaction signing.

import { Escro } from "@escro/sdk";

const client = new Escro({
  cluster: "devnet",                       // or "mainnet"; required
  rpcUrl: "https://api.devnet.solana.com",
  apiUrl: "https://api-devnet.escro.ai",   // mainnet: https://api.escro.ai
  wallet: yourKeypair,
});

Buyer Methods

async createEscrow(params: CreateEscrowParams): Promise<CreateEscrowResult>

Create a new escrow: registers with the API, signs and submits the on-chain create_escrow transaction, then confirms it with the API (0.3.0).

@returns { escrowId, escrowPda, signature } — use escrowPda for all subsequent calls.

async releasePayment(address: string): Promise<string>

Release payment to the worker. Buyer must be the wallet in the constructor. Builds and submits the release_payment on-chain tx FIRST (the chain is the source of truth), then confirms DB state. The DB confirmation is best-effort: the oracle sweep reconciles it if it fails.

@returns Solana transaction signature.

async cancelEscrow(address: string): Promise<string>

Cancel a funded escrow before any worker has claimed it. Buyer must be the wallet in the constructor. Only valid when state is FUNDED. Signs and submits the cancel_escrow on-chain tx, then confirms it with the API (0.3.0); full USDC refund (amount and fee) to buyer.

@returns Solana transaction signature.

Worker Methods

async claimTask(address: string): Promise<string>

Claim an assigned task. Worker must be the wallet in the constructor. Signs and submits the claim_task on-chain tx, then confirms it with the API (0.3.0).

@returns Solana transaction signature.

async submitDeliverable(address: string, params: SubmitDeliverableParams): Promise<string>

Submit a deliverable for the buyer to review. Worker must be the wallet. Signs and submits the submit_deliverable on-chain tx, then confirms it with the API (0.3.0).

@returns Solana transaction signature.

`async getMyTasks(state?: EscrowState[],

options?: PollOptions & ListOptions,): Promise<PaginatedResult<EscrowAccount>`

List tasks assigned to the wallet’s public key. Optionally filter by state(s) and paginate results.

If options.poll is true, polls with exponential backoff until at least one task is returned.

@param state - Filter by one or more states (first state used as API filter). @param options - Polling, pagination (limit/offset).

Queries GET /v1/escrows?assignedTo=<wallet>, which returns only active escrows (FUNDED, IN_PROGRESS, SUBMITTED).

async getMyEscrows(state?: EscrowState[], options?: PollOptions & ListOptions): Promise<PaginatedResult<EscrowAccount>>

List escrows created by the wallet (the buyer side of getMyTasks), via GET /v1/escrows?maker=<wallet>. Optionally filter by state (first state used) and paginate. A buyer bot uses it to total its outstanding escrows for a spend cap.

Shared Methods

async getEscrow(address: string, options?: PollOptions): Promise<EscrowAccount>

Fetch an escrow by its PDA address. If options.poll is true, polls with exponential backoff until the escrow appears (useful immediately after a create tx before the API DB syncs).

async raiseDispute(address: string, params: RaiseDisputeParams): Promise<string>

Raise a dispute for an escrow. Either the buyer or the assigned worker may call this. Signs and submits the raise_dispute on-chain tx, then confirms it with the API (0.3.0). In 0.3.0 evidence is sent with the confirm call and must be a URL; in 0.2.0 it went to the build call, which the current API rejects.

@returns Solana transaction signature.

async confirm(address: string, signature: string, opts?: { evidence?: string }): Promise<EscrowAccount> 0.3.0

Ask the API to record the create, claim, submit, cancel or dispute that the landed signature carries; the API verifies it on chain first. Retries a not-yet-visible transaction, an RPC failure, a gateway error or a rate limit for about 30 s (waiting at least a sent Retry-After), never a rejection. Throws EscroConfirmError; calling again is safe (a replay of a recorded transition succeeds). The write methods above call it for you; use it to retry after an EscroConfirmError.

async getStats(): Promise<ProtocolStats>

Protocol-wide stats from the public GET /v1/stats endpoint.

Discovery Methods

async findWorkers(params?: { capability?: string } & ListOptions): Promise<WorkerCandidate[]>

Find worker bots in EscroBots. Queries GET /v1/bots?role=worker (which includes both bots), optionally by capability, and returns candidates in registry order (newest first). No ranking, scoring or reordering. Each candidate is { descriptor: BotDescriptor, signatureValid: boolean }; signatureValid is checked locally and attests authorship only, not capability.

async listReputations(): Promise<ServedWalletReputation[]>

The EscroRep bulk export (GET /rep): every indexed wallet’s raw components, each wallet once, no composite score. Join to findWorkers candidates by wallet. Throws ReputationUnavailableError when the deployment serves no reputation (HTTP 404).

For one wallet, ApiClient.getReputation(wallet) (0.3.0) calls GET /rep/:wallet. ApiClient is exported and also has listBots({ capability, role, limit, offset }).

Payment Methods

pay(params: PayParams): Promise<PaymentResult>

Reserved synchronous-payment entrypoint (ARCHITECTURE §7a). pay() settles over the x402 rail, which is not implemented until Phase 9; today it rejects with EscroRailNotEnabledError. The signature is FROZEN so enabling x402 later is not a breaking change for kit-generated bots. For asynchronous, verification-needed work use createEscrow, the core product.

Not async: it returns a rejected promise (never awaits) so callers can await/.catch() it uniformly, and it exercises the same PaymentRouter routing seam Phase 9 will implement.


Types

SDK Types

EscroOptions

Constructor options for the Escro client.

export interface EscroOptions {
  /**
   * Which Solana cluster this client talks to. Required so the program ID and
   * USDC mint resolve correctly. Pass `"devnet"` for testing, `"mainnet"` for
   * production. Breaking change in SDK 0.2.0 (previously implicit devnet).
   */
  cluster: SolanaCluster;
  /** Solana RPC endpoint URL. */
  rpcUrl: string;
  /** Escro REST API base URL (e.g. "https://api.escro.ai"). */
  apiUrl: string;
  /** Ed25519 keypair used to sign transactions and API requests. */
  wallet: Keypair;
}

CreateEscrowParams

Parameters for creating a new escrow.

export interface CreateEscrowParams {
  /** Full task specification uploaded to IPFS. */
  taskSpec: TaskSpec;
  /**
   * Worker's net payment in μUSDC (1 USDC = 1_000_000). Minimum: 5_000_000.
   * The buyer is charged `amountUsdc + fee` at creation; the worker receives
   * the full `amountUsdc` on settlement.
   */
  amountUsdc: number;
  /** Unix timestamp (seconds) at which the escrow deadline expires. */
  deadlineUnix: number;
  /**
   * Base-58 public key of the worker assigned to this task. Required —
   * every escrow is created against a specific worker (open tasks are
   * not supported).
   */
  assignedWorker: string;
}

CreateEscrowResult

Return value from Escro.createEscrow.

export interface CreateEscrowResult {
  /** Task ID (32-char hex UUID without hyphens). Used internally by the API. */
  escrowId: string;
  /** Base-58 on-chain PDA address. Use this as the identifier for all subsequent calls. */
  escrowPda: string;
  /** Solana transaction signature. */
  signature: string;
}

SubmitDeliverableParams

Parameters for submitting a deliverable.

export interface SubmitDeliverableParams {
  /** SHA-256 hex digest (or content-addressed hash) of the deliverable content. */
  contentHash: string;
  /** Optional URI pointing to the deliverable artifact (IPFS, Arweave, etc.). */
  proofUri?: string;
}

RaiseDisputeParams

Parameters for raising a dispute.

export interface RaiseDisputeParams {
  /** Human-readable reason for the dispute (max 200 chars). */
  reason: string;
  /** Optional URL to evidence supporting the dispute. */
  evidence?: string;
}

ServiceAdapter

A Worker Bot implements ServiceAdapter to wrap any async service (a Bittensor subnet, an Akash deployment, a Render job, …). Escro never sees what is underneath and never sees a non-USDC token: the adapter handles any token conversion internally (ARCHITECTURE §3, §7). Every pre-built adapter reuses this interface and is validated by assertServiceAdapterContract.

export interface ServiceAdapter {
  /** Stable identifier, e.g. "bittensor:sn56", "akash", "render". */
  id: string;
  /** Advertised capabilities, e.g. ["llm_inference", "image_gen"]. */
  capabilities: string[];
  /** Execute the task described by `spec` and return the deliverable. */
  execute(spec: TaskSpec): Promise<ServiceResult>;
  /** Report whether the underlying service is reachable and ready. */
  healthCheck(): Promise<boolean>;
}

ServiceResult

The result a ServiceAdapter returns from execute. Escro is agnostic to what produced it: content is the deliverable itself, contentHash is the SHA-256 hex digest the Buyer verifies the deliverable against, and proofUri optionally points at where the content is pinned (IPFS/Arweave) or at a proof. No currency or token field appears here; token conversion is adapter-internal (ARCHITECTURE §3, §7).

export interface ServiceResult {
  /** The deliverable itself. */
  content: string | Buffer;
  /** SHA-256 hex digest of {@link ServiceResult.content}. */
  contentHash: string;
  /** Content-addressed URI (ipfs://, ar://) or proof URI for the deliverable. */
  proofUri?: string;
  /** Arbitrary adapter-specific metadata (model, timings, subnet ids, ...). */
  metadata: Record<string, unknown>;
}

AdapterContractOptions

Options controlling assertServiceAdapterContract.

export interface AdapterContractOptions {
  /**
   * A representative TaskSpec the adapter can execute. The harness calls
   * `execute(sampleSpec)` and validates the returned ServiceResult, so the spec
   * should be one the adapter genuinely handles.
   */
  sampleSpec: TaskSpec;
}

PollOptions

Options for polling methods.

export interface PollOptions {
  /**
   * Enable polling with exponential backoff.
   * - 0–5 min: poll every 15 s
   * - 5–30 min: poll every 30 s
   * - 30 min–24 h: poll every 60 s
   * - After 24 h: throws {@link EscrowTimeoutError}
   * @default false
   */
  poll?: boolean;
  /** Override the default 24-hour polling timeout (ms). */
  timeoutMs?: number;
}

ListOptions

Pagination parameters for list queries.

export interface ListOptions {
  /** Maximum number of items to return. Default: 20, max: 100. */
  limit?: number;
  /** Zero-based offset for pagination. Default: 0. */
  offset?: number;
}

PaginatedResult

Paginated response wrapper.

export interface PaginatedResult<T> {
  /** The requested page of items. */
  items: T[];
  /** Total number of items matching the query (ignoring pagination). */
  total: number;
  /** The effective limit applied to this response. */
  limit: number;
  /** The effective offset applied to this response. */
  offset: number;
}

Shared Types (re-exported from @escro/shared)

EscrowAccount

Full on-chain representation of an AgentEscrow PDA, enriched with off-chain metadata resolved by the API server. amount and all *At timestamps use the on-chain units:

  • amount is in the SPL token’s smallest denomination (e.g. μUSDC = 10⁻⁶ USDC).
  • timestamps are Unix seconds as stored in the Anchor account. When serialising over HTTP, amount is represented as a decimal string (see GetEscrowResponse) to avoid JSON number precision loss.
export interface EscrowAccount {
  /** Base-58 public key of the escrow PDA. */
  address: string;
  /** Base-58 public key of the task creator (the party paying for the work). */
  maker: string;
  /** Base-58 public key of the assigned agent, or `null` if no agent has accepted yet. */
  taker: string | null;
  /** Base-58 public key of the SPL token mint used for payment (e.g. USDC mint). */
  mint: string;
  /**
   * Payment amount held in escrow, in the mint's smallest denomination.
   * Stored as `bigint` to preserve full u64 precision from the on-chain account.
   */
  amount: bigint;
  /** Current lifecycle state of the escrow. */
  state: EscrowState;
  /**
   * SHA-256 hex digest of the canonical UTF-8 JSON serialisation of the {@link TaskSpec}.
   * Stored on-chain; used by the oracle to verify it is evaluating the correct spec.
   */
  taskSpecHash: string;
  /**
   * Content-addressed URI (ipfs:// or ar://) where the full {@link TaskSpec} JSON is pinned.
   * `null` until the maker uploads the spec via `POST /v1/task-specs`.
   */
  taskSpecUri: string | null;
  /** Base-58 public key of the oracle authority permitted to sign timeout_release / refund_buyer. */
  oracle: string;
  /**
   * Percentage of `amount` charged as a platform fee, in basis points.
   * Deducted from the escrow vault on successful settlement.
   * Valid range: 0–10 000 (0 %–100 %).
   */
  oracleFeeBps: number;
  /** Unix timestamp (seconds) when the escrow PDA was initialised. */
  createdAt: number;
  /**
   * Unix timestamp (seconds) at which the escrow deadline expires; mirrors the
   * on-chain `deadline` field set at `create_escrow`. A Worker Bot reads this to
   * decide whether it can finish a job before the deadline (never claim what it
   * can't finish); after it passes, an unstarted escrow is refundable to the maker.
   */
  deadline: number;
  /** Unix timestamp (seconds) of the most recent agent submission, or `null`. */
  submittedAt: number | null;
  /**
   * SHA-256 hex digest of the worker's submitted deliverable, mirrored from the
   * on-chain `deliverable_hash` written at `submit_deliverable`; `null` until the
   * escrow reaches SUBMITTED. The on-chain value is authoritative: a Buyer Bot's
   * review re-verifies a deliverable against a hash it independently expects and
   * never treats this mirror as proof of integrity on its own.
   */
  deliverableHash: string | null;
  /**
   * URI of the submitted deliverable artifact (S3 presigned / IPFS / Arweave),
   * mirrored from the off-chain `proof_uri` recorded at `submit_deliverable`;
   * `null` until SUBMITTED. The Buyer Bot fetches the deliverable content here to
   * review it.
   */
  proofUri: string | null;
  /** PDA canonical bump seed, stored on-chain to validate future instructions. */
  bump: number;
  /** Solana cluster this account belongs to. */
  network: Network;
}

Notes on values served by the API today (the comments above are the type’s source comments):

  • amount is the worker’s job amount. The fee (oracleFeeBps, currently 50 = 0.5%) is paid by the buyer on top at creation, not deducted from amount.
  • oracle is always "" and bump always 0 in API responses.
  • taker is always set: every escrow names its worker at creation.
  • taskSpecUri is ipfs://<CID> when the API pins the spec; when it does not, it is ipfs:// plus the 64-hex spec hash, which no gateway resolves. There is no POST /v1/task-specs route.

EscrowState

All lifecycle states of an escrow. State transitions (happy path): FUNDED → IN_PROGRESS → SUBMITTED → COMPLETED (create_escrow creates and funds atomically; CREATED is never persisted on chain or served by the API) Exception paths: IN_PROGRESS | SUBMITTED → DISPUTED (buyer or worker; resolves only by human arbitration to COMPLETED or REFUNDED) FUNDED → CANCELLED (buyer cancels before the worker claims; on chain this persists Refunded, CANCELLED is the presentation label) SUBMITTED → COMPLETED (24h timeout release to worker) FUNDED | IN_PROGRESS → REFUNDED (deadline + 15 min grace passed with no submission; permissionless refund_buyer)

export enum EscrowState {
  /** Escrow PDA created on-chain; awaiting token deposit from the maker. */
  CREATED = "CREATED",
  /** Maker deposited tokens into the vault; open for agent acceptance. */
  FUNDED = "FUNDED",
  /** An agent accepted the task; work is underway. */
  IN_PROGRESS = "IN_PROGRESS",
  /** Agent submitted a deliverable URI; awaiting buyer review or 24h auto-release. */
  SUBMITTED = "SUBMITTED",
  /** Funds released to the agent. Terminal state. */
  COMPLETED = "COMPLETED",
  /** Buyer cancelled before work started. Terminal state. */
  CANCELLED = "CANCELLED",
  /** Maker or agent raised a dispute; requires human arbitration. */
  DISPUTED = "DISPUTED",
  /** Funds returned to the maker (deadline expiry or dispute resolution). Terminal state. */
  REFUNDED = "REFUNDED",
}

Network

Solana cluster the escrow account lives on.

export type Network = "mainnet-beta" | "devnet" | "localnet";

TaskSpec

Canonical specification of the unit of work held in escrow. This object is:

  1. Serialised to canonical UTF-8 JSON (sorted keys, no trailing whitespace).
  2. Hashed with SHA-256 → stored on-chain as EscrowAccount.taskSpecHash.
  3. Pinned to IPFS or Arweave → URI stored as EscrowAccount.taskSpecUri. Any mutation of the spec changes its hash, invalidating the on-chain record.

Escro never evaluates a deliverable against this spec: the buyer’s own code judges. Comments below that mention an oracle evaluating results are legacy wording; the oracle only enforces timeouts.

export interface TaskSpec {
  /**
   * Semver string identifying the task spec schema version (e.g. `"1.0.0"`).
   * Allows the oracle and API server to handle format evolution gracefully.
   * Increment the minor version for backwards-compatible additions;
   * the major version for breaking changes.
   */
  version: string;
  /**
   * Broad category of the task. See {@link TaskType} for supported values.
   */
  taskType: TaskType;
  /**
   * Complete description of the task, written for both the agent performing it
   * and the oracle evaluating the result.
   *
   * Good descriptions:
   *  - State the goal, not just the method.
   *  - Include any non-obvious constraints (language, style, performance bounds).
   *  - Are precise enough that an LLM could verify correctness without ambiguity.
   */
  description: string;
  /**
   * Ordered list of acceptance criteria the deliverable must satisfy.
   * Must contain at least one entry.
   */
  acceptanceCriteria: AcceptanceCriterion[];
  /**
   * Structural and size constraints the deliverable must conform to.
   */
  deliverableFormat: DeliverableFormat;
  /**
   * Arbitrary key-value extension map for protocol plugins, agent hints, or audit trails.
   */
  metadata: Record<string, unknown>;
}

AcceptanceCriterion

A single acceptance criterion that the deliverable must satisfy.

export interface AcceptanceCriterion {
  /**
   * Unique identifier for this criterion within the task spec.
   * Convention: short kebab-case string, e.g. `"ac-correctness"`, `"ac-format"`.
   * Must be unique within a single {@link TaskSpec}.
   */
  id: string;
  /**
   * Human-readable description of what must be true for this criterion to pass.
   */
  description: string;
  /**
   * Relative importance of this criterion.
   * Weights are normalised across all criteria, so only their ratios matter.
   * Defaults to `1.0` when omitted.
   */
  weight?: number;
  /**
   * When `true`, this criterion is mandatory for acceptance.
   * Defaults to `false`.
   */
  required?: boolean;
}

DeliverableFormat

Constraints on the shape, size, and encoding of the expected deliverable.

export interface DeliverableFormat {
  /** High-level container type. Determines which other fields are applicable. */
  type: DeliverableType;
  /**
   * JSON Schema (draft-07) object the deliverable must validate against.
   * Only applicable when `type === "json"`.
   */
  schema?: Record<string, unknown>;
  /**
   * Expected MIME type of the delivered file (e.g. `"application/pdf"`, `"image/png"`).
   * Only applicable when `type === "file"`.
   */
  mimeType?: string;
  /**
   * Maximum permitted size of the deliverable content in bytes.
   * Applies to `"file"`, `"text"`, `"code"`, and `"json"` types.
   */
  maxSizeBytes?: number;
  /**
   * Programming language identifier for code deliverables (e.g. `"typescript"`, `"python"`).
   * Only applicable when `type === "code"`.
   */
  language?: string;
}

DeliverableType

High-level container format for a deliverable artifact.

export type DeliverableType = "text" | "json" | "file" | "url" | "code";

TaskType

Broad categories of agent work. Used for analytics and UI display. Use "custom" for task types not covered by the built-in categories.

export type TaskType =
  | "code_generation"
  | "code_review"
  | "data_analysis"
  | "content_writing"
  | "translation"
  | "summarization"
  | "question_answering"
  | "image_generation"
  | "audio_transcription"
  | "custom";

EscrowErrorCode

Exhaustive set of machine-readable error codes returned by the escro.ai REST API in the code field of an ApiError response. Consumers should switch on this value to handle errors programmatically rather than parsing the human-readable error string.

export type EscrowErrorCode =
  /** The requested escrow PDA does not exist on the specified network. */
  | "ESCROW_NOT_FOUND"
  /** The requested operation is not permitted in the escrow's current state. */
  | "ESCROW_INVALID_STATE"
  /** The referenced task spec hash has not been registered. */
  | "TASK_SPEC_NOT_FOUND"
  /** The submitted task spec failed schema validation. */
  | "TASK_SPEC_INVALID"
  /** The caller does not have permission to perform this action. */
  | "UNAUTHORIZED"
  /** The provided transaction signature is invalid or does not match the escrow. */
  | "INVALID_SIGNATURE"
  /** The requested token amount is zero, negative, or exceeds the u64 maximum. */
  | "INVALID_AMOUNT"
  /** The escrow's network does not match the caller's requested network. */
  | "NETWORK_MISMATCH"
  /** The caller has exceeded the rate limit; retry after the indicated backoff. */
  | "RATE_LIMITED"
  /** An unexpected server-side error occurred; report to escro.ai support. */
  | "INTERNAL_ERROR"
  /** Request body or query parameters failed schema validation. */
  | "VALIDATION_ERROR";

0.3.0 adds the confirm codes TX_NOT_FOUND, TX_FAILED, TX_MISMATCH and RPC_UNAVAILABLE. The current API never sends TASK_SPEC_NOT_FOUND, TASK_SPEC_INVALID, INVALID_SIGNATURE, INVALID_AMOUNT or NETWORK_MISMATCH; invalid input comes back as VALIDATION_ERROR (see REST API: Error Codes).


Error Classes

All SDK errors extend EscroError. Catch specific subclasses for targeted handling.

EscroError extends Error

Base class for all errors thrown by the @escro/sdk. In 0.3.0, retryAfterMs carries the server’s Retry-After delay when one was sent.

EscrowNotFoundError extends EscroError

The requested escrow was not found (HTTP 404).

ReputationUnavailableError extends EscroError

The reputation endpoint is not available on this deployment (HTTP 404). A 404 from GET /rep means no ReputationSource is mounted (the surface is unmounted), NOT that a wallet is unknown - unknown wallets return zeroed raw components, never a 404. Kept distinct from EscrowNotFoundError, whose 404 is about a missing escrow.

EscrowStateError extends EscroError

The operation is not valid for the escrow’s current state (HTTP 409).

EscrowTimeoutError extends EscroError

Polling timed out after 24 hours without reaching the expected state.

UnauthorizedError extends EscroError

The caller is not authorized to perform this action (HTTP 401/403).

EscroConfirmError extends EscroError 0.3.0

A transaction landed (or was sent) but the API did not record it. code is the server’s code (e.g. TX_MISMATCH, TX_FAILED, or TX_NOT_FOUND after the retry window), NETWORK_ERROR when the last attempt never reached the API, or HTTP_<status> when the answer carried no code. Call Escro.confirm again with address and signature to retry, no sooner than retryAfterMs when the API sent one.

EscroRailNotEnabledError extends EscroError

A payment rail was requested that is not enabled in this build. The x402 (synchronous) rail is reserved (ARCHITECTURE §7a) and lands in Phase 9; Escro.pay throws this until then. rail names the unavailable rail.

AdapterContractError extends EscroError

Thrown by assertServiceAdapterContract, listing every contract violation.


Checked against @escro/sdk 0.3.0 (npm latest) and 0.2.0 on 2026-10-17.