Skip to content

SDK reference

This page is generated from the primedelta package docstrings at build time, so it can't drift from the installed SDK. For task-oriented walkthroughs, start with the SDK guides.

Client

PrimeDelta

PrimeDelta(
    private_key: Optional[str] = None,
    web3_provider_url: Optional[str] = None,
    network: str = "dev",
    signer: Optional[Signer] = None,
    auto_relogin: bool = True,
    on_login: Optional[Callable[[PrimeDelta], None]] = None,
)

craft

craft(action: Callable[[], Any]) -> list[dict[str, Any]]

Run a trading action WITHOUT broadcasting and return the unsigned transaction(s) it would have sent, for an external wallet to sign.

Non-custodial flow (per Web3/MCP security guidance): this SDK builds the calldata but never signs a fund-moving transaction here — the returned dicts go to the user's own wallet (MetaMask / hardware) to review and sign. action is any zero-arg callable that would normally send a transaction::

txs = pd.craft(
    lambda: pd.swap_exact_input(
        "AMMT1", SwapSide.STABLECOIN_TO_STOCK, Decimal("10"), Decimal("0")
    )
)

Each returned dict is {from, to, value, data, chainId}. Gas and nonce are intentionally omitted for the signing wallet to fill. A multi-step action (e.g. approve -> swap) returns one dict per transaction, in send order.

The action still reads the chain and backend to build calldata (pool addresses, allowances, signed prices, deadlines). For oracle-priced instruments the signed price and deadline baked into the calldata expire — sign and broadcast promptly.

Scope: craft captures ON-CHAIN transactions — swap, LP, native transfer, token approve, and custodial deposit/claim (all go through the signed send path). Backend REST actions (limit/market orders, withdrawal requests, order cancels) produce no on-chain tx here and raise CannotCraft under craft rather than silently executing for real.

Not thread-safe: crafting toggles an instance-level flag, so do not share one client across threads while a craft is in flight — a concurrent fund-moving call on the same client would be captured instead of broadcast. Use a separate client per thread, or don't overlap them.

export_session

export_session() -> list[dict[str, Any]]

Serialize the authenticated session cookies so a caller can persist the login (e.g. across process restarts) instead of re-running SIWE. Pair with import_session. Returns an empty list when not logged in.

import_session

import_session(cookies: list[dict[str, Any]]) -> None

Restore a session previously captured with export_session. The session is not re-validated; a stale one raises NotLoggedIn on the next authed call, which auto-relogin (when enabled) then retries. Arm auto-relogin so a stale restore recovers cleanly.

register_ai_account

register_ai_account(
    agent_name: str, main_wallet_address: str
) -> None

Register the logged-in wallet as an AI subaccount of main_wallet_address.

Call this from the SUBACCOUNT's session (a fresh wallet); the main account must then confirm it before the subaccount becomes active.

get_pending_ai_agents

get_pending_ai_agents() -> list[PendingAIAgent]

List AI subaccounts awaiting this (main) account's confirmation.

confirm_ai_agent

confirm_ai_agent(sub_wallet_address: str) -> None

Confirm a pending AI subaccount registered under this main account.

reject_ai_agent

reject_ai_agent(sub_wallet_address: str) -> None

Reject a pending AI subaccount registered under this main account.

verification_url

verification_url() -> str

Return the URL of the web page where the user completes KYC.

Verification (uploading ID, taking a selfie, etc.) is performed in the web app, not via the SDK. After verification completes there, the backend marks the account as VERIFIED and the SDK can call claim_digital_identity() to mint the on-chain DID NFT.

open_verification_page

open_verification_page() -> str

Open the verification web page in the user's default browser.

Returns the URL that was opened, so callers can fall back to printing it if webbrowser.open returned False (headless environments).

get_onchain_stablecoin_balance

get_onchain_stablecoin_balance() -> Decimal

Read stablecoin balance from chain (bypasses backend indexer lag).

get_onchain_stock_balance

get_onchain_stock_balance(symbol: str) -> Decimal

Read a stock token balance from chain (bypasses backend indexer lag).

Useful immediately after a swap when the backend hasn't synced yet.

get_onchain_balances

get_onchain_balances() -> dict[str, Decimal]

All on-chain token balances for this wallet in a SINGLE Multicall3 call: dUSD (6 decimals) plus every token the DEX router lists (18 decimals). Returns the held balances (> 0) plus dUSD. Falls back to one read per token when the network has no Multicall3 configured.

get_native_del_balance

get_native_del_balance() -> Decimal

Read native DEL balance from chain.

tx_status

tx_status(tx_hash: str) -> Optional[TxStatus]

Look up the mined receipt for a transaction hash.

Returns None if the tx is not yet mined / unknown to the node. succeeded is False for a reverted (status 0) transaction. The send methods already block on the receipt and raise on failure, so this is for polling a hash you hold (e.g. one broadcast externally after craft, or a hash returned by a prior send).

wrap_del

wrap_del(amount: Decimal) -> str

Wrap native DEL → WDEL by sending msg.value to WDEL.deposit().

The resulting WDEL is an AMM ERC20 — feed it into the regular swap methods using the symbol "WDEL".

unwrap_del

unwrap_del(amount: Decimal) -> str

Unwrap WDEL → native DEL via WDEL.withdraw(amount).

prices_stream

prices_stream(
    symbols: Optional[list[str]] = None,
) -> Iterator[Price]

Stream real-time price updates.

When logged in, uses the broker's authenticated price stream. When not logged in, uses Pyth Hermes API for public price feeds.

Parameters:

Name Type Description Default
symbols Optional[list[str]]

List of stock symbols to stream prices for. Only used when not logged in (Pyth stream). If None, streams all available stocks.

None

Raises:

Type Description
AccountNotVerified

If logged in but account is not verified. Use pyth_prices_stream() or verify at https://app.primedelta.io

pyth_prices_stream

pyth_prices_stream(
    symbols: Optional[list[str]] = None,
) -> Iterator[Price]

Stream prices from Pyth Hermes API.

This method does not require authentication and can be used when not logged in.

Parameters:

Name Type Description Default
symbols Optional[list[str]]

List of stock symbols to stream prices for. If None, streams all available stocks.

None

swap_token_to_token_exact_input

swap_token_to_token_exact_input(
    input_symbol: str,
    output_symbol: str,
    amount_in: Decimal,
    min_amount_out: Decimal,
    deadline_seconds: int = 600,
    update_fee: int = 0,
) -> str

Trade one non-dUSD token for another (routed through dUSD on chain).

Use for AMM↔AMM, AMM↔stock, and stock↔stock swaps. For dUSD↔token swaps, use swap_exact_input with SwapSide.

swap_token_to_token_exact_output

swap_token_to_token_exact_output(
    input_symbol: str,
    output_symbol: str,
    amount_out: Decimal,
    max_amount_in: Decimal,
    deadline_seconds: int = 600,
    update_fee: int = 0,
) -> str

Exact-output cross-dex swap. See swap_token_to_token_exact_input.

preview_fees

preview_fees(position_id: int) -> tuple[Decimal, Decimal]

Preview an AMM (V3) position's currently collectable amounts as (stock, stablecoin) in human units, via a static collect simulation. Read-only (no login) but only for positions owned by this wallet — the NPM gates collect on ownership, so a third party's position_id reverts.

lp_positions

lp_positions() -> list[int]

Return all AMM (V3) position NFT token IDs owned by the wallet.

lp_position

lp_position(position_id: int) -> LPPosition

Read AMM (V3) position info for a given NFT token ID.

instrument_kind

instrument_kind(symbol: str) -> str

Classify a tradable symbol as "amm" (a 24/7 Uniswap-V3 AMM pool) or "oracle" (a price-feed pool, tradable only in US market hours). Agents should gate oracle-instrument swaps on is_market_open().

min_out_from_quote staticmethod

min_out_from_quote(
    quote: Decimal, slippage_bps: int = 100
) -> Decimal

Compute a safe min_amount_out from a quote_swap result and a slippage budget in basis points (default 100 = 1%). Never pass a raw Decimal("0") to a swap in production — derive the bound from a quote.

halt

halt() -> None

Engage the kill switch: every subsequent on-chain send raises TradingHalted until resume(). Reads are unaffected.

resume

resume() -> None

Release the kill switch engaged by halt().

oracle_price

oracle_price(symbol: str) -> Optional[Decimal]

Current signed oracle USD price for an oracle (price-feed) stock — the reference AAPL-class stocks lack an on-chain quote for. Decoded from the same signed update the swap would submit (feedId, price:int64, expo:int32, ... — FIOracle layout), so it is the price the pool values against. Returns None when none is available (e.g. the market is closed).

This is a REFERENCE price, not a fee-adjusted amount-out: the oracle pool applies a dynamic, reserve-dependent fee on top, so budget slippage to cover it when deriving a swap's min_amount_out. Needs a logged-in session.

simulate_swap

simulate_swap(
    symbol: str,
    side: SwapSide,
    amount_in: Decimal,
    slippage_bps: int = 100,
) -> SwapSimulation

Paper-trade an exact-input swap without touching account state.

Runs entirely on static reads (the on-chain Quoter) — no allowance, balance, or signature — so it returns a result before you have approved or funded anything, and on thin pools where a live trade might revert. Gives the expected output, a slippage-bounded min_amount_out ready to pass straight to swap_exact_input, the current spot price, and the pool fee tier. AMM pools only, like quote_swap.

Signers

A signer is the boundary of what the SDK can do on your behalf. See Signing modes for how to choose.

Signer

Bases: Protocol

Wallet seam the SDK signs and broadcasts through.

fills_gas_and_nonce is False for signers where the SDK builds a fully specified transaction (nonce, gas, gasPrice) and the signer only signs and broadcasts it, and True for wallet signers that fill nonce/gas and broadcast themselves (e.g. a browser extension).

KmsSigner

KmsSigner(
    key_id: str,
    kms_client: Any = None,
    region_name: Any = None,
)

Signer whose key stays inside AWS KMS — only 32-byte digests are sent out.

Requires the optional primedelta[kms] extra (boto3). Pass an existing KMS client as kms_client, or let the signer build one for region_name. The key must be an asymmetric secp256k1 signing key (KeySpec ECC_SECG_P256K1, KeyUsage SIGN_VERIFY).

LocalAccountSigner

LocalAccountSigner(account: LocalAccount)

BrowserSigner

BrowserSigner(
    *,
    chain: Optional[dict[str, Any]] = None,
    timeout: float = 180.0
)

Sign through a browser wallet (MetaMask, …) via a one-shot local bridge.

Each operation opens a single-use page on 127.0.0.1 that discovers the wallet (EIP-6963), performs eth_requestAccounts / personal_sign / eth_sendTransaction, and posts the result back over a one-time state token; the loopback server then shuts down. Pass chain (a wallet_addEthereumChain params dict) to switch/add the network before signing.

RemoteBrowserSigner

RemoteBrowserSigner(
    *,
    base_url: str,
    deliver: Callable[[str], None],
    chain: Optional[dict[str, Any]] = None,
    timeout: float = 180.0
)

Sign through the user's own browser wallet reached at a HOSTED HTTPS origin — for a hosted/remote MCP that can't open the user's local browser.

It reuses BrowserSigner's one-shot page and one-time state token, but the hosting app serves the page from base_url and decides how to send the user there via the deliver callback (e.g. an MCP url-mode elicitation). The hosting app must: - serve GET /sign?state=<token> -> :meth:render_page - serve POST /result?state=<token> -> :meth:resolve The URL carries only the opaque token; the tx/message stays server-side. Non-custodial: no fund-moving key lives here — the user's wallet signs.

render_page

render_page(state: str) -> str

HTML for the pending op behind state (serve at GET /sign?state=).

resolve

resolve(
    state: str,
    value: Any = None,
    error: Optional[str] = None,
) -> bool

Deliver the wallet's result for state and unblock the waiting call (call from POST /result?state=). Returns False for an unknown/expired token.

Liquidity & swap parameters

SwapSide

Bases: str, Enum

PoolType

Bases: str, Enum

SwapSimulation dataclass

SwapSimulation(
    symbol: str,
    side: SwapSide,
    amount_in: Decimal,
    expected_amount_out: Decimal,
    min_amount_out: Decimal,
    slippage_bps: int,
    spot_price: Decimal,
    fee_tier: int,
)

AMMAddLiquidity dataclass

AMMAddLiquidity(
    symbol: str,
    tick_lower: int,
    tick_upper: int,
    amount_stock_desired: Decimal,
    amount_stablecoin_desired: Decimal,
    amount_stock_min: Decimal,
    amount_stablecoin_min: Decimal,
)

AMMRemoveLiquidity dataclass

AMMRemoveLiquidity(
    position_id: int,
    liquidity: int,
    amount_stock_min: Decimal,
    amount_stablecoin_min: Decimal,
)

PriceFeedAddLiquidity dataclass

PriceFeedAddLiquidity(
    symbol: str,
    liquidity_amount: Decimal,
    max_stock_amount: Decimal,
    max_stablecoin_amount: Decimal,
)

PriceFeedRemoveLiquidity dataclass

PriceFeedRemoveLiquidity(
    symbol: str, liquidity_amount: Decimal
)

Exceptions

NotLoggedIn

Bases: Exception

AccountNotVerified

Bases: Exception

NotEnoughFunds

Bases: Exception

MarketClosed

MarketClosed(
    function_name: str,
    reason: str,
    tx_hash: Optional[str] = None,
    to: Optional[str] = None,
    data: Optional[str] = None,
    trace: Optional[Any] = None,
)

Bases: TransactionFailed

An oracle/price-feed swap reverted because the signed equity price was stale or absent — the US market is closed (or the price was withheld). A subclass of TransactionFailed, so existing handlers still catch it, but agents can catch it specifically to distinguish 'market closed' from a bug.

TradingHalted

Bases: Exception

Raised when a transaction is attempted while the client's kill switch is engaged (halt()); call resume() to re-enable.

TransactionFailed

TransactionFailed(
    function_name: str,
    reason: str,
    tx_hash: Optional[str] = None,
    to: Optional[str] = None,
    data: Optional[str] = None,
    trace: Optional[Any] = None,
)

Bases: Exception

A transaction submitted by the SDK reverted or failed to mine.

Attributes:

Name Type Description
function_name

Solidity function the SDK tried to call.

reason

Decoded revert reason if available (Error(string) or Panic(uint256)), otherwise the raw return data or original message.

tx_hash

Hex-encoded tx hash if the transaction was submitted. None when the failure happened during pre-submit gas estimation.

to

Target contract address (when known) — useful for replay via cast call <to> <data> --from <sender> --rpc-url ....

data

ABI-encoded calldata (when known) — pair with to to replay the failing call in a debugger / block explorer.

trace

Best-effort debug_traceCall output if the node supports it.

CannotCraft

Bases: Exception

Raised when a backend REST action (limit/market order, withdrawal request, order cancel) is invoked inside craft. Those actions do not produce an on-chain transaction the SDK can capture, so crafting cannot intercept them — calling one would execute it for real.

DigitalIdentityAlreadyClaimed

Bases: Exception

WdelNotConfigured

Bases: Exception

Raised when a DEL/WDEL helper is called on a network whose config has no wdel address. Add it to networks/<name>.json to enable.

BackendUnavailable

Bases: Exception

The backend could not be reached, or errored transiently — a dropped connection, a timeout that survived the automatic retries, or a 5xx. Kept distinct from NotLoggedIn / AuthorizationError / APIError so a caller (or the MCP layer) can back off and retry rather than crash on a raw requests traceback.