/** * Attestation marketplace — willingness-to-attest, sold. * * Templates publish a voice. Listings publish a callable. An **attestation * listing publishes a willingness-to-attest**: an attester offers to review * evidence and, if satisfied, sign one specific class of claim at a price. * A buyer purchases a *grant* against a subject identity; the attester * reviews, asks for the exact short-lived digest, signs it, and delivers. * Settlement writes a row in `identity.attestations` and releases escrow with * the take-rate split. * * What is being sold is **review and issuance**, not trust. Payment proves * settlement and the signature proves which key made the claim; neither proves * the claim is true, that the issuer is accredited, or that any relying agent * should believe it. The legacy identity trust field stays neutral at `0`. * * Two signatures could be confused here and they are NOT interchangeable: * * - `identity-attestation/v1` (identity.ts) is an ordinary unpaid * attestation. It names no grant and authorizes no payment. * - `attestation-issue/v1` (here) additionally binds one grant, one escrow, * the buyer/subject/attester identities and DIDs, both wallets, the active * signing key, the deterministic evidence hash, the current fee split, and * both expiry terms. Only this authorizes money to move. * * The attester therefore must never sign the server's `signed_payload_b64` * blindly. {@link AttestationMarketplaceClient.signingPayload} recomputes the * digest from the named `fields` the server returned and refuses if the two * disagree, so what gets signed is what the attester can read. And because * `evidence_sha256` is the one field that says *what was reviewed*, * {@link attestationEvidenceSha256} lets the attester tie that hash back to * the evidence body on the grant row before signing. * * Walls the server holds (surfaced here as refusal codes): * - `self_purchase_not_allowed` — the buyer cannot be the listing's * attester. Buyer may equal subject; attester may equal subject. * - `not_listing_owner` (403) — only the listing's project may issue or * decline; only the buyer's project may cancel. * - `signature_invalid` / `signing_key_revoked` / * `signing_key_does_not_belong_to_attester` (401). * - `attestation_replay` (409) — an exact signature is single-use. * - `grant_sla_expired`, `grant_state_invalid: status=…`, * `*_terms_changed` (409) — the bound terms drifted before settlement. * - `insufficient_balance` (402) on purchase. No partial payments. * * Doctrine: docs/MARKETPLACE.md §"Attestation marketplace" · * docs/CANONICAL-BYTES.md §attestation-issue/v1. */ import { type HttpConfig } from "./_http.js"; /** Domain tag. Byte-identical to the API service's constant. */ export declare const ATTESTATION_ISSUE_SIGNATURE_CONTEXT = "attestation-issue/v1"; /** The authorization the server prepares lives five minutes. Pinned so a * caller can reason about the window without a round trip. */ export declare const ATTESTATION_ISSUE_AUTHORIZATION_TTL_SECONDS: number; /** Wire order is part of the v1 contract. It changes only with a new context. */ export declare const ATTESTATION_ISSUE_FIELD_ORDER: readonly ["listing_id", "grant_id", "escrow_id", "buyer_identity_id", "buyer_did", "buyer_project_id", "buyer_wallet_id", "subject_identity_id", "subject_did", "attester_identity_id", "attester_did", "attester_project_id", "signing_key_id", "claim", "evidence_sha256", "attester_wallet_id", "grant_gross", "grant_currency", "take_rate_bps", "platform_fee", "attester_net", "validity_seconds", "attestation_expires_at", "authorization_expires_at"]; export interface AttestationIssueFields { listing_id: string; grant_id: string; escrow_id: string; buyer_identity_id: string; buyer_did: string; buyer_project_id: string; buyer_wallet_id: string; subject_identity_id: string; subject_did: string; attester_identity_id: string; attester_did: string; attester_project_id: string; signing_key_id: string; claim: string; /** 64 lowercase hex characters — see {@link attestationEvidenceSha256}. */ evidence_sha256: string; attester_wallet_id: string; grant_gross: number; grant_currency: string; take_rate_bps: number; platform_fee: number; attester_net: number; /** Null for an attestation that never expires. Paired with the next field. */ validity_seconds: number | null; /** Null exactly when `validity_seconds` is null. Encoded as text `null`. */ attestation_expires_at: string | null; authorization_expires_at: string; } /** * The 32-byte digest a paid attestation issuance covers. * * sha256(utf8("attestation-issue/v1") || 0x00 || utf8(field_1) || …) * * Byte-identical to * `api/src/services/marketplace/attestation-issue-sig.ts#canonicalAttestationIssueBytes`. * The field guards are the server's guards: a non-lowercase-UUID identifier, * an empty or NUL-bearing text field, a malformed evidence hash, a negative or * unsafe amount, a rate above 100%, a fee split that does not add up, a * validity/expiry pair that disagrees about nullness, or a non-canonical * timestamp all refuse here exactly as they refuse there. */ export declare function canonicalAttestationIssueBytes(fields: AttestationIssueFields): Uint8Array; /** * Sorted-key, no-whitespace JSON — the exact spelling the server hashes to * produce `evidence_sha256`. * * Byte-identical to * `api/src/services/marketplace/attestation-issue-sig.ts#canonicalAttestationEvidenceJson`. * Anything that is not JSON — `undefined`, a non-finite number, a class * instance, a `Map` — is refused rather than coerced, because a coerced * evidence body would hash to something the attester never read. */ export declare function canonicalAttestationEvidenceJson(value: unknown): string; /** * The `evidence_sha256` the server binds into the digest. * * Read the grant's `evidence` and pass it here. If the result does not equal * the `evidence_sha256` in the signing payload, the terms describe a review of * something other than what you read — do not sign. A grant with no evidence * hashes `null`, which is a real value and not an absence. */ export declare function attestationEvidenceSha256(value: unknown): string; export interface SignAttestationIssueOpts { fields: AttestationIssueFields; /** The attester's ed25519 signing seed (32 bytes) for `signing_key_id`. */ signing_key: Uint8Array; } /** Sign a paid attestation issuance. Returns base64 of 64 raw bytes — * the exact spelling `POST /:id/issue` accepts in its `signature` field. */ export declare function signAttestationIssue(opts: SignAttestationIssueOpts): string; export type AttestationListingVisibility = "private" | "public"; export type AttestationListingStatus = "active" | "paused" | "archived"; export type AttestationGrantStatus = "pending" | "issued" | "refunded" | "failed"; export type AttestationGrantRole = "buyer" | "attester" | "subject"; export interface AttestationIssueSigningPayload { signature_context: string; field_order: string[]; fields: AttestationIssueFields; /** Base64 of the 32-byte digest. Verified locally before it is returned. */ signed_payload_b64: string; authorization_expires_at: string; } export interface CreateAttestationListingOptions { attester_identity_id: string; name: string; claim: string; price_amount: number; price_currency: string; attester_wallet_id: string; description?: string | null; capability_tags?: string[]; /** Published buyer guidance. The purchase route does NOT validate evidence * against it — it is a contract between agents, not a server wall. */ evidence_schema?: Record | null; validity_seconds?: number | null; sla_seconds?: number | null; visibility?: AttestationListingVisibility; metadata?: Record; } export interface ListAttestationListingsOptions { attester_id?: string; claim?: string; status?: AttestationListingStatus; /** `true` returns only this project's rows — the management view. The * default collection is your own rows plus other projects' public ones. */ mine?: boolean; limit?: number; } export interface PatchAttestationListingOptions { name?: string; description?: string | null; capability_tags?: string[]; evidence_schema?: Record | null; price_amount?: number; price_currency?: string; attester_wallet_id?: string; validity_seconds?: number | null; sla_seconds?: number | null; visibility?: AttestationListingVisibility; status?: AttestationListingStatus; metadata?: Record; } export interface PurchaseAttestationGrantOptions { buyer_identity_id: string; buyer_wallet_id: string; /** Who the claim is about. May be the buyer, or someone else entirely. */ subject_identity_id: string; /** Stored plaintext and hashed into the signed digest. Omitting it is not * the same as `null`: an absent key sends no field, and the server then * stores `null`, which hashes as the JSON text `null`. */ evidence?: Record | null; } export interface ListAttestationGrantsOptions { role?: AttestationGrantRole; status?: AttestationGrantStatus; limit?: number; } /** `GET /v1/attestation-grants/:id` answers with the role you hold in it. * A project unrelated to the grant gets a 404, not an empty role. */ export interface AttestationGrantView { grant: Record; role: "buyer" | "attester"; } export interface IssueAttestationGrantOptions { /** Base64 ed25519 over {@link canonicalAttestationIssueBytes}. The server * requires canonical standard base64 of exactly 64 bytes. */ signature: string; signing_key_id: string; /** Echo the payload's expiry exactly; it is inside the signed bytes and the * server rechecks the binding. */ authorization_expires_at: string; } /** * Client for `/v1/attestation-listings` and `/v1/attestation-grants`. * * @example * ```ts * // Attester side: review, verify the terms, sign, issue. * const payload = await at.attestationMarketplace.signingPayload(grantId, { * signing_key_id: keyId, * }); * const { grant } = await at.attestationMarketplace.getGrant(grantId); * // The one field that says WHAT you reviewed — check it yourself. * if (attestationEvidenceSha256(grant.evidence ?? null) !== * payload.fields.evidence_sha256) throw new Error("not what I read"); * await at.attestationMarketplace.issue(grantId, { * signature: signAttestationIssue({ * fields: payload.fields, * signing_key: attesterSeed, * }), * signing_key_id: keyId, * authorization_expires_at: payload.authorization_expires_at, * }); * ``` */ export declare class AttestationMarketplaceClient { private readonly http; /** @internal */ constructor(http: HttpConfig); /** Publish a willingness-to-attest listing. The attester identity and * wallet must be active, owned by this project, and share the listing's * currency. */ createListing(options: CreateAttestationListingOptions): Promise>; /** Browse the shelf. Default: this project's rows plus other projects' * public ones. `mine: true` narrows to a strictly owned management view. * A private listing looks absent from outside its project; that is the * intended answer, not an error. */ listListings(options?: ListAttestationListingsOptions): Promise[]>; /** Read one listing. 404 when it is private and not yours. */ getListing(listingId: string): Promise>; /** Amend your own listing. Only the keys you pass are touched; the price, * claim wording, and payout wallet of already-purchased grants are frozen * in those grants' own signed terms. */ patchListing(listingId: string, options: PatchAttestationListingOptions): Promise>; /** Buy a grant against an active public listing. This is the ONLY mounted * grant-creation operation — `POST /v1/attestation-grants` does not exist. * Atomically debits the buyer wallet and funds escrow. * * Refused with `self_purchase_not_allowed` when the buyer is the listing's * attester, and with `insufficient_balance` (402) when the wallet cannot * cover the gross. */ purchase(listingId: string, options: PurchaseAttestationGrantOptions): Promise>; /** List grants, scoped to the role you hold in them. Defaults to `buyer`; * `attester` is the issuance queue. */ listGrants(options?: ListAttestationGrantsOptions): Promise[]>; /** Read one grant and the role you hold in it. The server tries buyer scope * first, then attester scope, and 404s if you are neither — so `role` is * the server's answer, not an echo of your request. */ getGrant(grantId: string): Promise; /** * Ask for the exact short-lived terms to authorize issuance. * * The response's `fields` are the readable terms; `signed_payload_b64` is * their digest. This method recomputes the digest locally from those fields * and REFUSES if the two disagree, so an attester never signs a blob whose * meaning it did not derive itself. That check is the whole reason the * canonical bytes live in the SDK rather than only on the server. * * The authorization lives five minutes. Sign and submit within it. * * @throws AgentToolError `signing_payload_mismatch` when the server's digest * does not equal the digest of the terms it printed alongside it. */ signingPayload(grantId: string, options: { signing_key_id: string; }): Promise; /** * Submit the attester's authorization and settle the grant. * * The server relocks and rechecks every bound term, recomputes the current * fee split and evidence hash, verifies the signature against * `attestation-issue/v1`, writes the `identity.attestations` receipt, * credits the attester the net, releases the bound escrow, and records the * take-rate. There is no legacy fallback signature shape. * * Unlike memory-witness issuance, nothing here mutates a project's * constitution, so this route takes no root authority proof. */ issue(grantId: string, options: IssueAttestationGrantOptions): Promise>; /** Refuse a grant as its attester. Declining is always available: no * attester is obliged to sign anything. The buyer is refunded in full and * the platform earns no fee. */ decline(grantId: string): Promise>; /** Withdraw a grant as its buyer, while it is still `pending`. Once the * attester issues, the claim is signed and the window is closed. */ cancel(grantId: string): Promise>; /** Serialize once, transmit those same bytes. */ private fetch; }