Metadata-Version: 2.5
Name: olive-solana-sdk
Version: 0.1.1
Summary: Async Python SDK for market makers quoting Olive Solana options
Project-URL: Documentation, https://olive.ag/docs
Project-URL: Repository, https://github.com/macols77/olive
Project-URL: Issues, https://github.com/macols77/olive/issues
Author: Olive
License-Expression: MIT
License-File: LICENSE
Keywords: market-maker,olive,options,rfq,solana
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: jsonschema<5,>=4.23
Requires-Dist: solders<1,>=0.26
Requires-Dist: websockets<18,>=15
Provides-Extra: dev
Requires-Dist: build<2,>=1.2; extra == 'dev'
Requires-Dist: mypy<2,>=1.15; extra == 'dev'
Requires-Dist: psycopg[binary,pool]<4,>=3.2; extra == 'dev'
Requires-Dist: pytest-asyncio<2,>=0.25; extra == 'dev'
Requires-Dist: pytest<10,>=8.3; extra == 'dev'
Requires-Dist: ruff<1,>=0.11; extra == 'dev'
Requires-Dist: types-jsonschema<5,>=4.23; extra == 'dev'
Provides-Extra: postgres
Requires-Dist: psycopg[binary,pool]<4,>=3.2; extra == 'postgres'
Description-Content-Type: text/markdown

# Olive Solana Python SDK

The supported async Python client for market makers quoting Olive Solana options.
It implements only the native `/maker/v1/ws` protocol—there is no EVM or legacy
compatibility layer.

`ManagedMaker` is the recommended API. Maker code supplies entry premium or exit
amount and funding source; the SDK validates requests, binds canonical quote
fields, allocates durable quote IDs and entry nonces, signs through a local/HSM callback,
persists before submission, reconnects, and durably routes lifecycle events.

## Install

```bash
# From this repository:
pip install -e "packages/python-sdk[postgres]"

# After the distribution is published:
pip install "olive-solana-sdk[postgres]"
```

Python 3.11 or newer is required. Apply the packaged
`schemas/postgres-maker-store-v1.sql` with the maker's migration role and use
`schema_mode="verify"` in production. The default migration mode is intended for
development. `InMemoryMakerStore` is test-only.

The exact migration is also available programmatically with
`postgres_maker_store_schema()` so deployment tooling does not need to locate
the installed wheel's data directory.

## Managed maker quickstart

```python
import asyncio
import os

from olive_solana import (
    DeploymentIdentity,
    LocalKeypairMakerSigner,
    ManagedMaker,
    ManagedMakerEventHandlers,
    ManagedMakerOptions,
    PostgresMakerStore,
)


async def main() -> None:
    signer = LocalKeypairMakerSigner.from_json_file(
        os.environ["OLIVE_QUOTE_SIGNER_KEYPAIR"]
    )
    store = PostgresMakerStore(
        os.environ["DATABASE_URL"], schema_mode="verify"
    )

    async def entry(rfq):
        premium = await price_entry(rfq.request)
        return (
            rfq.decline("outside_risk_limits")
            if premium is None
            else rfq.quote(premium_usdc=premium)
        )

    async def exit_(rfq):
        amount = await price_exit(rfq.request)
        return (
            rfq.decline("outside_risk_limits")
            if amount is None
            else rfq.quote(
                exit_amount_usdc=amount,
                usdc_source=os.environ["OLIVE_EXIT_USDC_SOURCE"],
            )
        )

    maker = ManagedMaker(
        ManagedMakerOptions(
            url=os.environ["OLIVE_MAKER_WS_URL"],
            maker_config=os.environ["OLIVE_MAKER_CONFIG"],
            expected_deployment=DeploymentIdentity(
                program_id=os.environ["OLIVE_PROGRAM_ID"],
                genesis_hash=os.environ["OLIVE_GENESIS_HASH"],
                chain_tag=os.environ["OLIVE_CHAIN_TAG"],
            ),
            signer=signer,
            store=store,
            topics=("btc", "sol"),
            products=(0, 1),
            on_entry_rfq=entry,
            on_exit_rfq=exit_,
            events=ManagedMakerEventHandlers(
                # Selection is feedback, not execution.
                on_entry_selected=record_selection,
                # Only finalized fills trigger hedging/reconciliation.
                on_entry_filled=hedge_finalized_entry,
                on_exit_filled=reconcile_finalized_exit,
                on_funding_required=alert_funding,
                on_default=page_risk,
                on_settlement=record_settlement,
            ),
        )
    )
    try:
        await maker.run()
    finally:
        await store.close()


asyncio.run(main())
```

The local keypair in this quickstart is for laptop tests or a tightly controlled
key-file deployment. Production remote/HSM integrations use
`CallbackMakerSigner` and never pass private-key bytes to the maker process:

```python
from olive_solana import CallbackMakerSigner, SignerContext


async def sign_with_remote_provider(
    digest: bytes,
    context: SignerContext,
) -> bytes:
    if len(digest) != 32:
        raise ValueError("expected a 32-byte Olive digest")
    signature = await remote_provider.sign_ed25519(
        key_id="olive-quote-signer",
        message=digest,
        audit_context={"purpose": context.purpose},
    )
    if len(signature) != 64:
        raise ValueError("expected a 64-byte Ed25519 signature")
    return signature


signer = CallbackMakerSigner(
    public_key="<remote Ed25519 public key in canonical base58>",
    callback=sign_with_remote_provider,
)
```

The callback signs the supplied raw 32-byte `SHA-256(preimage)` digest with
ordinary Ed25519 and returns the raw 64-byte signature. Do not hash again, sign
encoded text/the full preimage, or use Ed25519ph. The remote public key may be
the finalized on-chain `MakerConfig.quoteSigner`.

An empty topic or product collection subscribes to everything authorized by
`maker.hello`. Keep `takeover_existing_session=False` for ordinary starts; enable
it only for an intentional failover.

## Operational preflight

The doctor authenticates without subscribing, taking over a live maker, or
submitting quotes. Its convenience CLI intentionally uses a local test keypair:

```bash
olive-maker-doctor --topics btc,sol --products 0,1 \
  --exit-sources "$OLIVE_EXIT_USDC_SOURCE"

olive-maker-doctor --json > maker-doctor.json
```

Connection and deployment values default from `OLIVE_MAKER_WS_URL`,
`OLIVE_MAKER_CONFIG`, `OLIVE_PROGRAM_ID`, `OLIVE_GENESIS_HASH`,
`OLIVE_CHAIN_TAG`, and `OLIVE_QUOTE_SIGNER_KEYPAIR`.

Remote/HSM deployments MUST NOT export their private key to use the CLI. Call
`run_maker_doctor(MakerDoctorOptions(..., signer=signer))` programmatically with
the same `CallbackMakerSigner` used by the managed maker, and retain
`report.to_dict()` as the machine-readable preflight record.

## Wire types and schema

The complete required/optional DTO catalog is published at
[`maker-websocket-v1.schema.json`](https://olive.ag/docs/schemas/maker-websocket-v1.schema.json)
and included in the wheel at
`olive_solana/schemas/maker-websocket-v1.schema.json`. Python exports typed RFQ,
quote, selected, filled, decline, and error/result shapes from `olive_solana`.
Missing required fields fail before maker callbacks run; unknown additive server
fields are preserved, while outbound quotes remain strict. The normative
semantics and signing preimages are in the
[Maker WebSocket API v1](https://olive.ag/docs/developers/maker-websocket-spec).

## Reliability contract

- Wire integers remain canonical decimal strings.
- Entry nonces are durable; V6 exit quotes have no wire nonce and are replay-safe
  because a successful exit closes the bound position.
- Complete quote, digest, and signature are committed before submission.
- Restart retries only the exact stored quote and signature.
- Durable events are stored before `event.ack`; local handler completion is
  persisted independently.
- `quote.selected` and `exit_quote.selected` are not fills. Only finalized
  `trade.filled` and `exit.filled` are canonical execution signals.
- Signatures and private signer data are never included in SDK status events.

The low-level `MakerWsClient` remains available for makers requiring custom
orchestration. Releases must pass the shared fixture/digest suite and all
fail-closed maker conformance scenarios.

## Development

```bash
python -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/ruff check .
.venv/bin/mypy src
.venv/bin/pytest
.venv/bin/python -m build
```

## Release

PyPI releases use trusted publishing from
`.github/workflows/publish-python-sdk.yml`; no long-lived API token is stored in
GitHub. Configure the PyPI publisher for repository `macols77/olive`, workflow
`publish-python-sdk.yml`, and environment `pypi`. Pushing a tag that exactly
matches `python-sdk-v<pyproject version>` builds, checks, and uploads both the
wheel and source distribution. Protect the `pypi` environment with required
reviewer approval. A failed release can be retried from GitHub Actions with
**Run workflow** by supplying the same existing tag; the workflow checks out
that tag and verifies it exactly matches the package version before publishing.
