Skip to content

07 — Reference interfaces (the SDK surface)

These are the interfaces a dev codes against. They are vendor-neutral; Privy and Virtuals are adapters behind them. Two language targets: TypeScript (@oaw/core + adapters) and Python (oaw). The shapes mirror the verified Betvax services so they are real, not aspirational.

Convention: every money-moving method takes an explicit execute: boolean and never moves funds when false. Every method returns a result with secrets_exposed: false and never embeds secret material.


1. Provider (wallet) adapter

ts
// @oaw/core — what a wallet provider must implement (Privy, Virtuals, external)
export type WalletProvider = "privy" | "virtuals" | "external";
export type AuthoritySource =
  | "provider_managed" | "virtuals_linked" | "external_imported" | "unknown";
export type ProofState = "verified" | "pending" | "unverified" | "not_applicable";
export type SignerStatus = "ready" | "required" | "pending" | "unsupported" | "unknown";

export interface AgentAuthority {
  id: string;
  userId: string;
  agentId: string | null;
  environment: string;
  authoritySource: AuthoritySource;
  walletProvider: WalletProvider | "unknown";
  walletAddress: string | null;
  providerWalletId: string | null;     // e.g. privy_agent_wallet_id — opaque, non-secret
  virtualsAgentId: string | null;
  proofState: ProofState;
  signerStatus: SignerStatus;
  supportedChains: string[];           // ["base","arbitrum","polygon","solana", …]
  policyRef: string | null;
  metadata: Record<string, unknown>;   // non-secret
}

export interface WalletProviderAdapter {
  readonly provider: WalletProvider;

  /** Mode A: create-or-load a programmatic agent wallet. Idempotent. */
  ensureAgentWallet(input: {
    user: UserRef; agentId?: string; environment?: string; idempotencyKey?: string;
  }): Promise<AgentAuthority>;

  /** Mode B: link an existing Virtuals/EconomyOS agent. Verification ≠ ownership. */
  linkExistingAgent(input: {
    user: UserRef; virtualsAgentId: string;
    ownershipProof: OwnershipProof; expectedWalletAddress?: string;
    switchAuthority?: boolean;          // never overwrites a ready Mode A without proof
  }): Promise<LinkResult>;

  getProfile(user: UserRef, environment?: string): Promise<AgentAuthority | null>;

  /** Non-secret capability snapshot (caps, agentmail, card, ACP, tokenization). */
  capabilities(authority: AgentAuthority): Promise<ProviderCapabilities>;

  /** Resolve how this authority signs for a venue (the L4 routing decision). */
  executionRoute(authority: AgentAuthority, venue: string): Promise<ExecutionRoute>;

  // Mode C / Mode D — OPTIONAL, only if the provider exposes them (see 09).
  createManagedAgentWallet?(input: CreateManagedInput): Promise<AgentAuthority>;       // Mode C
  registerExternalWallet?(input: RegisterExternalInput): Promise<AgentAuthority>;      // Mode D
}
python
# oaw (Python) — Protocol, mirrors economyos_agent_wallet_service
from typing import Optional, Protocol, Any

class WalletProviderAdapter(Protocol):
    provider: str  # "privy" | "virtuals" | "external"

    async def ensure_agent_wallet(self, db, *, user, agent_id=None,
                                  environment=None, idempotency_key=None) -> dict: ...
    async def link_existing_agent(self, db, *, user, virtuals_agent_id,
                                  ownership_proof, expected_wallet_address=None,
                                  switch_authority: bool = False) -> dict: ...
    async def get_profile(self, db, *, user_id, environment=None) -> Optional[dict]: ...
    async def capabilities(self, profile) -> dict: ...
    async def execution_route(self, profile, venue: str) -> dict: ...
    # Optional Mode C / D:
    # async def create_managed_agent_wallet(self, db, *, user, ...) -> dict: ...
    # async def register_external_wallet(self, db, *, user, wallet, ...) -> dict: ...

The Virtuals adapter's verified ACP operations

The Virtuals adapter wraps these (and does not expose a create_agent until Mode C exists — see 09):

verify_identity · signer_status · verify_authority_proof · wallet_balance
create_job · create_fund_transfer_job · fund_job · complete_job · reject_job
send_message · submit_deliverable · drain_events · get_job · list_resources
dgclaw_join · dgclaw_activate_unified · dgclaw_add_api_wallet · dgclaw_deposit
dgclaw_withdraw · dgclaw_trade · dgclaw_status · dgclaw_receipts

2. Venue capability contract + adapter

ts
export interface VenueCapabilityContract {
  venueId: string;
  label: string;
  aliases: string[];
  supportedIntents: string[];           // ["trade","fund","status"]
  supportedMarketClasses: string[];     // ["perp","spot","prediction","token"]
  lifecycleModes: string[];             // ["create_account","link_existing"]
  authModel: string;                    // "economyos_agent_wallet_plus_acp" | "cex_api_key" | …
  signingModel: string;                 // "provider_agent_wallet" | "eip1271" | "api_wallet" | …
  perUserAuthorityRequired: boolean;    // ← gates the pre-bind check
  managedWalletSupport: boolean;
  delegatedSignerSupport: boolean;
  browserSessionRequired: boolean;      // owner/OAuth/dashboard handoff needed
  fundingRequirements: string[];
  readinessRequirements: string[];
  approvalRequirements: string[];
  geoComplianceGates: string[];
  bindingSupported: boolean;            // a promise the backend can service it
  bindingRequiredFields: string[];
  acpNative: boolean;                   // derived: virtuals/degenclaw
}

export interface VenueAdapter {
  readonly contract: VenueCapabilityContract;
  setup(authority: AgentAuthority, binding: VenueBinding, params: object): Promise<SetupResult>;
  readiness(authority: AgentAuthority, binding: VenueBinding): Promise<ReadinessResult>;
  fundingTarget(authority: AgentAuthority, binding: VenueBinding): Promise<FundingTarget>;
  executeOrPlan(authority: AgentAuthority, binding: VenueBinding,
                action: VenueAction, opts: { execute: boolean }): Promise<ExecutionResult>;
}

3. The pre-bind gate (L2 keystone)

ts
export type PrebindStatus =
  | "authority_ready" | "agent_wallet_created"        // ok
  | "agent_wallet_required" | "virtuals_identity_required"
  | "virtuals_signer_required" | "owner_handoff_required"
  | "authority_reconciliation_required" | "unsupported_authority_source"
  | "venue_contract_missing";

export interface PrebindResult {
  ok: boolean;
  status: PrebindStatus;
  venue: string;
  authoritySource: AuthoritySource | null;
  walletProvider: WalletProvider | null;
  proofState: ProofState | null;
  authority: ExecutionRoute | null;
  blockers: string[];
  nextAction: string | null;
  safeUserMessage: string | null;
  noLiveVenueAction: true;
  secretsExposed: false;
}

export interface PrebindGate {
  /** Resolve parent authority BEFORE any venue setup. allow_auto_create=true only on write intent. */
  ensure(input: {
    user?: UserRef; userId?: string; venue: string; action: string;
    allowAutoCreate?: boolean; environment?: string; idempotencyKey?: string;
  }): Promise<PrebindResult>;
  /** Same, but throws VenuePrebindBlocked(result) when !ok. */
  require(input: { /* …same… */ }): Promise<PrebindResult>;
}

4. Funding router (L3)

ts
export type FundingStatus =
  | "ready" | "funding_required" | "funding_target_unresolved"
  | "funding_route_unsupported" | "funding_venue_unsupported"
  | "deposit_addresses_ready" | "ready_but_live_locked" | "requires_approval";

export interface FundingPlan {
  venue: string;
  fundingTarget: { chain: string; address: string | null };
  availableFundingUsd: string;
  supportedSourceChains: string[];
  route: {
    kind: "chain_agnostic_deposit_address" | "venue_native_deposit_bridge" | "cross_chain_stablecoin_transfer";
    targetChain: string;
    acceptsAnyChainUsdc: boolean;
    detail: string;
  };
  status: FundingStatus;
  noLiveVenueAction: boolean;
  secretsExposed: false;
}

export interface FundingRouter {
  resolve(input: { user: UserRef; venue: string; amountUsd: number }): Promise<FundingPlan>;
  execute(input: {
    user: UserRef; venue: string; amountUsd: number;
    execute: boolean;                 // false ⇒ preview only, never moves funds
    approval?: ApprovalRef; idempotencyKey?: string; sourceChain?: string;
  }): Promise<FundingResult>;          // gated behind VENUE_FUNDING_EXECUTE_ENABLED + policy + approval
}

5. Intent classifier (L5)

ts
export type Capability =
  | "bind" | "fund" | "trade" | "credit" | "intel" | "treasury" | "degen" | "status" | "none";

export interface IntentResult {
  capability: Capability;
  isAction: boolean;                  // question/negation/hypothetical ⇒ false
  venue: string | null;
  amountUsd: number | null;
  mode: "create" | "link_existing" | "status" | "explain" | null;
  confidence: number;                 // 0..1
  ambiguous: boolean;
  clarifyingQuestion: string | null;
}

export interface IntentClassifier {
  /** Cheap, high-recall, NON-DECIDING. Decides only whether to spend a model call. */
  shouldClassify(message: string, capability: Capability): boolean;
  /** ONE structured-JSON model call. Fail-safe: returns null/abstain on any error. */
  classify(message: string, opts?: { context?: object }): Promise<IntentResult | null>;
}

6. The top-level facade

ts
export interface OAW {
  authority: {
    ensure(user: UserRef, opts?: { allowAutoCreate?: boolean }): Promise<AgentAuthority>;
    linkVirtuals(user: UserRef, virtualsAgentId: string, proof: OwnershipProof): Promise<LinkResult>;
    get(user: UserRef): Promise<AgentAuthority | null>;
  };
  venues: {
    bind(user: UserRef, venue: string, params: object, opts?: { allowAutoCreate?: boolean }): Promise<VenueBinding>;
    readiness(user: UserRef, venue: string): Promise<ReadinessResult>;
    execute(user: UserRef, venue: string, action: VenueAction, opts: { execute: boolean }): Promise<ExecutionResult>;
  };
  funding: FundingRouter;
  intent: IntentClassifier;
  registerProvider(adapter: WalletProviderAdapter): void;
  registerVenue(adapter: VenueAdapter): void;
}

A complete dev flow (the north-star command)

ts
const oaw = createOAW({ provider: privyAdapter });
oaw.registerVenue(polymarketAdapter);

// "Bind Polymarket, fund it with $50, buy YES on <market>"
const intent = await oaw.intent.classify(userMessage);
if (intent.capability === "bind" && intent.isAction) {
  await oaw.venues.bind(user, "polymarket", {}, { allowAutoCreate: true });  // Mode A minted if needed
}
const plan = await oaw.funding.resolve(user, "polymarket", { amountUsd: 50 });
await oaw.funding.execute({ user, venue: "polymarket", amountUsd: 50, execute: false }); // deposit addresses
const result = await oaw.venues.execute(user, "polymarket",
  { kind: "trade", side: "YES", market, amountUsd: 50 }, { execute: confirmed });
// L4: agent_authority-aware, policy/funding/live-flag gated, receipted. Brain held no keys.

These interfaces are the contract for 08 (what Privy should expose) and 09 (what Virtuals should expose) to make the optional methods real.

Released under the Apache-2.0 License.