Skip to main content
This is the canonical developer path for Cobo Agentic Wallet.

5-minute outcome

In one run, you will:
  1. Submit a pact requesting scoped onchain permissions
  2. Wait for owner approval
  3. Execute one allowed onchain action using the pact-scoped API key
  4. Trigger one policy denial and inspect the structured error
  5. Verify in audit logs

Prerequisites

  • Python 3.11+
  • A wallet already onboarded and paired via caw onboard (see CLI quickstart)
  • Testnet tokens on your wallet address (use caw faucet deposit to request Sepolia ETH)
  • Your agent’s API key and wallet UUID set as environment variables

Step 1: Install SDK

pip install cobo-agentic-wallet

Step 2: Set environment variables

Set AGENT_WALLET_API_URL to your CAW API endpoint.
export AGENT_WALLET_API_URL=https://api.agenticwallet.cobo.com
export AGENT_WALLET_API_KEY=your-agent-api-key
export AGENT_WALLET_WALLET_ID=your-wallet-uuid

Step 3: Run the end-to-end script

quickstart.py
import asyncio
import json
import os

from cobo_agentic_wallet.client import WalletAPIClient

CHAIN_ID = "SETH"
TOKEN_ID = "SETH"
DESTINATION = "0x1111111111111111111111111111111111111111"
ALLOWED_AMOUNT = "0.001"
DENIED_AMOUNT = "0.005"
DENY_THRESHOLD = "0.002"


def parse_api_error(exc: Exception) -> dict:
    body = getattr(exc, "body", None)
    if not body:
        return {}
    try:
        payload = json.loads(body)
    except (TypeError, ValueError):
        return {}
    return payload if isinstance(payload, dict) else {}


async def main() -> None:
    api_url = os.environ["AGENT_WALLET_API_URL"]
    api_key = os.environ["AGENT_WALLET_API_KEY"]
    wallet_id = os.environ["AGENT_WALLET_WALLET_ID"]

    client = WalletAPIClient(base_url=api_url, api_key=api_key)

    try:
        # Step 1: Submit a pact requesting transfer permissions for 24 hours.
        pact_resp = await client.submit_pact(
            wallet_id=wallet_id,
            intent="Transfer tokens for integration testing",
            spec={
                "policies": [
                    {
                        "name": "max-tx-limit",
                        "type": "transfer",
                        "rules": {
                            "effect": "allow",
                            "when": {"chain_in": [CHAIN_ID], "token_in": [{"chain_id": CHAIN_ID, "token_id": TOKEN_ID}]},
                            "deny_if": {"amount_gt": DENY_THRESHOLD},
                        },
                    }
                ],
                "completion_conditions": [
                    {"type": "time_elapsed", "threshold": "86400"}
                ],
            },
        )
        pact_id = pact_resp["pact_id"]
        print(f"PACT SUBMITTED: {pact_id}")

        # Step 2: Poll until the owner approves the pact.
        print("Waiting for owner approval in Cobo Agentic Wallet app...")
        while True:
            pact = await client.get_pact(pact_id)
            status = pact.get("status", "")
            print(f"  pact status: {status}")
            if status == "active":
                break
            if status in ("rejected", "expired", "revoked", "completed"):
                raise RuntimeError(f"Pact reached terminal status before use: {status}")
            await asyncio.sleep(5)

        print("PACT ACTIVE")

        # Step 3: Use the pact-scoped API key for all subsequent calls.
        pact_api_key = pact["api_key"]
        pact_client = WalletAPIClient(base_url=api_url, api_key=pact_api_key)

        try:
            # Step 4: Execute an allowed transfer (within the deny threshold).
            allowed = await pact_client.transfer_tokens(
                wallet_id,
                chain_id=CHAIN_ID,
                dst_addr=DESTINATION,
                token_id=TOKEN_ID,
                amount=ALLOWED_AMOUNT,
            )
            print("ALLOWED:", allowed.get("status", "unknown"))

            # Step 5: Trigger a policy denial (amount exceeds the deny threshold).
            try:
                await pact_client.transfer_tokens(
                    wallet_id,
                    chain_id=CHAIN_ID,
                    dst_addr=DESTINATION,
                    token_id=TOKEN_ID,
                    amount=DENIED_AMOUNT,
                )
            except Exception as exc:
                payload = parse_api_error(exc)
                err = payload.get("error") or {}
                print(f"DENIED: code={err.get('code', '-')} reason={err.get('reason', '-')}")
                suggestion = payload.get("suggestion")
                if suggestion:
                    print(f"SUGGESTION: {suggestion}")

        finally:
            await pact_client.close()

        # Step 6: Verify allowed and denied events in audit logs.
        logs = await client.list_audit_logs(wallet_id=wallet_id, limit=20)
        items = logs.get("items", []) if isinstance(logs, dict) else []
        allowed_count = sum(1 for item in items if item.get("result") == "allowed")
        denied_count = sum(1 for item in items if item.get("result") == "denied")
        print(f"AUDIT: allowed={allowed_count}, denied={denied_count}")

    finally:
        await client.close()


if __name__ == "__main__":
    asyncio.run(main())

Step 4: Validate output

You should see:
  • PACT SUBMITTED: <pact-id>
  • PACT ACTIVE
  • ALLOWED: ...
  • DENIED: code=... reason=...
  • SUGGESTION: ...
  • AUDIT: allowed=..., denied=...

Broaden beyond transfers

The same pact-and-policy flow also applies to smart-contract interaction and payments. Once the transfer hello-world works, the next canonical expansion is contract_call plus durable tracking by request_id:
fee = await pact_client.estimate_contract_call_fee(
    wallet_id,
    chain_id="BASE_ETH",
    contract_addr="0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
    calldata="0x38ed1739...",
    value="0",
)
print("CALL FEE:", fee)

call_result = await pact_client.contract_call(
    wallet_id,
    chain_id="BASE_ETH",
    contract_addr="0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
    calldata="0x38ed1739...",
    value="0",
    request_id="swap-2026-001",
)
print("CALL:", call_result.get("status", "unknown"))
Use Contract Calls when your runtime automates swaps, staking, vault deposits, or other protocol interaction.

Go further

WalletAPIClient is a direct API wrapper — it gives you full control. You can build on top of it in several ways:
  • Write custom tool functions — wrap WalletAPIClient calls in domain-specific functions that encode your business logic (e.g. transfer_with_price_check, batch_payout), then expose those as agent tools.
  • Use framework integrationsAgentWalletToolkit exposes the widened runtime toolkit as native LangChain, OpenAI Agents SDK, Agno, or CrewAI tools. Use it for the canonical wallet/pact/onchain surface, and add your own custom tools alongside it for extended functionality.
  • Pair with the CLI — use caw for onboarding, debugging, and one-off operations; use WalletAPIClient for programmatic agent logic. They share the same config and credentials.
  • Design around presets — even if you later expose CAW through an agent framework, keep your runtime logic split into Pact Drafting, Execution, and Observer responsibilities.

Handle Policy Denial

Production retry loop patterns and structured denial fields.

LangChain

Add wallet tools to a LangChain agent with one toolkit call.

CLI

Pair with CLI for onboarding and debugging.