/** * Direct Data Controller — the server-side facade for the two-tab Data * Portability flow. * * @remarks * One controller owns an app's private key, source, scopes, app identity, and * payment flow. It exposes the three methods the builder guide documents: * * - {@link DirectDataController.createAccessRequest} — start an approval request. * - {@link DirectDataController.getAccessRequestStatus} — poll while the Vana tab is open. * - {@link DirectDataController.readApprovedData} — read from the Personal Server, * handling 402 Payment Required. * * Access requests are created through the Vana Account access-request API; the * Personal Server read uses Web3Signed auth; and payment uses the DPv2 escrow * surface (`protocol/escrow`) — when a read returns `402`, the controller signs * a `GenericPayment` with the app key, settles it through the escrow gateway, * and retries. * * @category Direct * @module direct/controller */ import { type FetchLike } from "./access-request-client.js"; import { type EscrowPaymentConfig } from "./escrow-payment.js"; import { type PersonalServerFetch, type PersonalServerTransportRetryOptions } from "./personal-server-read.js"; import type { AccessRequest, AccessRequestClient, AccessRequestQuestion, AccessRequestStatus, ApprovedDataResult, AppIdentity, DirectAppConfig, DirectEnv, DirectNetwork, DirectServiceEndpoints, ForegroundDelivery, MultiScopeDataResult } from "./types.js"; /** Configuration for {@link createDirectDataController}. */ export interface DirectDataControllerConfig { /** Target environment. Defaults to `"production"`. */ env?: DirectEnv; /** * Target Vana network for chain-aware defaults. Defaults to the selected * environment's historical network (`mainnet` for production, `moksha` for * dev). Use `network: "moksha"` with the default production env for * production app/API URLs on testnet. */ network?: DirectNetwork; /** * The app private key (`0x`-prefixed, 32 bytes). Server-side only — this key * is the app's on-chain identity and is never exposed to the browser. */ appPrivateKey?: string; /** * @deprecated Use {@link DirectDataControllerConfig.appPrivateKey}. Accepted as * a backwards-compatible alias; if both are set, `appPrivateKey` wins. */ builderPrivateKey?: string; /** App identity advertised during approval. */ app: DirectAppConfig; /** Data source key (e.g. `"icloud_notes"`). */ source: string; /** * Grant scope entries to request. At least one required. * * Each entry is `[operation:]scope` (see `parseScopeEntry`): a bare entry * such as `"icloud_notes.notes"` requests read, and `"write:coach.weekly"` * requests write. The entries are carried through to the access request * verbatim and become the grant's `scopes`, so a request can mix both * (`["oura.sleep", "coach.weekly", "write:coach.weekly"]`). * * The scope part must be a concrete `{source}.{category}[.{subcategory}]` * scope: this flow reads approved scopes back one by one, so wildcard * patterns (`chatgpt.*`, `write:chatgpt.*`) are not accepted here for * either operation. */ scopes: string[]; /** * Override the resolved service endpoints (partial). Useful for pointing at a * non-standard deployment. */ endpoints?: Partial; /** * Client for the Vana Account access-request API. Defaults to a client against * the resolved Vana Account endpoints; inject your own to point at a custom * deployment or to supply a test double. */ accessRequestClient?: AccessRequestClient; /** * Escrow settlement config used when a Personal Server read returns `402`. * * @remarks * Wires the DPv2 escrow gateway (`protocol/escrow`). The controller supplies * the EIP-712 `signTypedData` from the app key automatically. * * When omitted (or partially omitted), the SDK derives defaults from the * per-network endpoints table and the contract registry: * - `client` defaults to a gateway client at `endpoints.escrowGatewayUrl` * - `escrowContract` defaults to `CONTRACTS.DataPortabilityEscrow.addresses[chainId]` * - `chainId` defaults to the controller's resolved chain id * * Provide this field only to override a specific default. */ escrow?: Partial; /** `fetch` used by the default access-request client. Defaults to `globalThis.fetch`. */ fetchFn?: FetchLike; /** `fetch` used for the Personal Server read. Defaults to `globalThis.fetch`. */ personalServerFetch?: PersonalServerFetch; /** * Transport-retry knobs for the Personal Server read * ({@link PersonalServerTransportRetryOptions}). Defaults to 3 attempts with * exponential backoff. Retries fire only when fetch throws (the browser-PS * relay reconnect window), never on a received HTTP status, and never * re-sign a payment. */ personalServerTransportRetry?: PersonalServerTransportRetryOptions; } /** * Controller-level escrow config — the {@link EscrowPaymentConfig} minus the * `signTypedData` and `chainId` the controller injects itself. */ export interface DirectEscrowConfig extends Omit { /** * Chain id for the EIP-712 domain. Defaults to the controller's environment * (1480 for mainnet, 14800 for moksha). */ chainId?: number; } /** * Server-side controller for the direct Data Portability flow. * * @typeParam T - Shape of the data returned by {@link DirectDataController.readApprovedData}. */ export interface DirectDataController { /** The on-chain address of the app, derived from `appPrivateKey`. */ readonly appAddress: string; /** * The app's on-chain address — the address to fund and inspect in the Builder * activity report. Equivalent to {@link DirectDataController.appAddress}. * * @returns The app's `0x`-prefixed address. */ getAppAddress(): string; /** * The app's full identity: its configured id/name/homepage plus the derived * on-chain address. Useful for telling builders which app address to fund or * look up. * * @returns `{ id, name, homepageUrl, address }`. */ getAppIdentity(): AppIdentity; /** * Create an access request the user can approve. * * @param input - The post-approval return URL and optional create retry key. * @returns The request id, HTTPS approval URL, and — for a pending deep Direct * request on mobile — an optional HTTPS `mobileContinuationUrl`. */ createAccessRequest(input: { returnUrl: string; /** Optional foreground mobile delivery callback. */ foregroundDelivery?: ForegroundDelivery; /** * Derivative questions to carry on the request (1 to 4). Each question * asks the user's Personal Server to compute its `derivedScope` from * `sourceScopes` the app never reads; the derived scope must also appear * in the controller's `scopes` as a bare read entry. Validated eagerly * against the configured scopes before the request is sent. See * {@link AccessRequestQuestion}. */ questions?: AccessRequestQuestion[]; /** * Stable retry key when the caller retries after an uncertain response. * Each create without one gets its own generated key. */ idempotencyKey?: string; }): Promise; /** * Fetch the current status of an access request. * * @param requestId - The `dcr_*` id from {@link DirectDataController.createAccessRequest}. * @returns `{ status, personalServerUrl?, grantId?, scope?, scopes? }`. */ getAccessRequestStatus(requestId: string): Promise; /** * Read the approved data from the user's Personal Server. * * @remarks * Resolves the request to its grant + Personal Server and performs a Web3Signed * read. Hides the `402 Payment Required` flow by default: if a read needs * payment, it signs the Personal Server's payment challenge, retries with * `X-PAYMENT`, and attaches shape-validated but unauthenticated * {@link DirectPaymentResponseMetadata} under `payment` when the Personal * Server returns it. After a successful read, the controller acknowledges * the DCR so Vana Web can close/redirect the approval tab. * * A request can approve several scopes. This reads **one** of them — `scope` * when given, otherwise the first approved scope. Use * {@link DirectDataController.readAllApprovedData} to read them all. * * Acknowledging moves the DCR to `completed`, which is terminal and no longer * read-ready. To read several scopes with your own loop, pass * `acknowledge: false` on every call but the last. * * @param input - The `dcr_*` request id, the optional `scope` to read, and an * optional `acknowledge` flag (default `true`). * @returns `{ scope, data, payment? }`. * @throws {@link AccessNotApprovedError} if the request is not approved. * @throws {@link ScopeNotApprovedError} if `scope` is not an approved scope. * @throws {@link PaymentRequiredError} if payment is required but unsettled. */ readApprovedData(input: { requestId: string; scope?: string; acknowledge?: boolean; }): Promise>; /** * Read every scope the user approved on a request. * * @remarks * Reads the scopes in approval order, then acknowledges the DCR **once**, * after the last read — acknowledging earlier would move the request to * `completed` and make the remaining scopes unreadable. * * Each scope is a separate Personal Server read that settles its own * `data_access` fee from escrow, so reading N scopes costs N times a * single-scope read. The one-off registration fee is charged per grant, not * per scope. * * A scope that fails does not abort the rest: successes land in `results` and * failures in `errors`, because the fees for earlier scopes are already spent. * If any scope fails the request is left unacknowledged, so the scopes that * failed stay retryable — read them with `readApprovedData({ scope })` and * acknowledge on the last one. * * @param input - The `dcr_*` request id to read. * @returns `{ results, errors }`, both keyed by scope. * @throws {@link AccessNotApprovedError} if the request is not approved. */ readAllApprovedData(input: { requestId: string; }): Promise>; } /** * Create a {@link DirectDataController}. * * @param config - Controller configuration (env, key, app identity, source, scopes). * @returns A ready-to-use controller. * @throws {@link DirectConfigError} when the key is missing or malformed, when * `scopes` is empty, or when no escrow contract can be resolved. * @throws InvalidScopeEntryError when a `scopes` entry does not fit the * `[operation:]scope` grammar (an unknown operation prefix such as `delete:`). * @throws ZodError when the scope part of an entry is not a valid scope. */ export declare function createDirectDataController(config: DirectDataControllerConfig): DirectDataController;