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: booleanand never moves funds whenfalse. Every method returns a result withsecrets_exposed: falseand 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_receipts2. 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.