Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ build/
*.egg-info/
greenfloor-native/target/
greenfloor-signer-pyo3/target/
greenfloor-signer-pyo3/python/
greenfloor-signer/target/
.coverage
.coverage.*
Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Severity tags:
- `[MUST]` `greenfloor/core/coin_ops/`: coin-op deterministic policy (plan, fee budget, inventory, min-amount guard) shared by CLI and daemon.
- `[MUST]` `greenfloor/config`: parse/validate config, resolve paths, resolve quote assets.
- `[MUST]` `greenfloor/* adapters`: side effects only (network, filesystem, wallet, notifications).
- `[MUST]` `greenfloor/signing.py`: legacy Python signing entry point during Rust migration (see ADR 0006).
- `[MUST]` Signing/execution path is adapter -> canonical Rust kernel (`greenfloor-signer` / `greenfloor_kernel` PyO3); legacy `greenfloor/signing.py` re-exports adapters only.
- `[MUST]` `greenfloor-signer/`: canonical vault KMS signing implementation; new vault spend/offer logic lands here first.
- `[MUST]` `greenfloor/cli/manager.py`: operator CLI router (argparse + dispatch).
- `[MUST]` `greenfloor/cli/coin_ops_list.py`, `coin_ops_split.py`, `coin_ops_combine.py`: coin list/split/combine CLI commands (`coin_ops.py` re-exports).
Expand All @@ -57,7 +57,7 @@ Severity tags:
## Design Constraints

- `[MUST]` Prefer direct function calls within the package; do not spawn subprocesses for same-env Python calls unless isolation/security is documented in `docs/decisions/`.
- `[MUST]` Signing/execution path is adapter -> canonical signer (`greenfloor-signer` for vault KMS; `greenfloor/signing.py` until cutover).
- `[MUST]` Signing/execution path is adapter -> canonical Rust kernel (`greenfloor-signer` crate, `greenfloor_kernel` PyO3 module).
- `[MUST]` Avoid unnecessary indirection layers (`executor`, `worker`, `engine`, etc.).
- `[MUST]` Keep one distinct responsibility per file; merge pass-through modules into functions.
- `[MUST]` Eliminate duplicated logic blocks (>10 lines) by extracting shared helpers.
Expand Down
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ GreenFloor is a long-running Python application for Chia CAT market making.

## Components

- `greenfloor-manager`: manager CLI for config validation, key onboarding, cloud-wallet coin inventory/reshaping, offer building/posting, and operational checks.
- `greenfloor-manager`: manager CLI for config validation, key onboarding, coin inventory/reshaping, offer building/posting, and operational checks.
- `greenfloord`: daemon process that evaluates configured markets, executes offers, and emits low-inventory alerts.

## V1 Plan
Expand Down Expand Up @@ -63,7 +63,7 @@ greenfloor-manager build-and-post-offer --pair ECO.181.2022:xch --size-base-unit
greenfloor-manager build-and-post-offer --pair TDBX:txch --size-base-units 1 --network testnet11
```

Cloud Wallet vault operations:
Vault KMS / signer operations:

```bash
# List vault inventory (XCH + CAT)
Expand Down Expand Up @@ -120,12 +120,14 @@ Operator overrides (all optional):
- `GREENFLOOR_COINSET_BASE_URL` — custom Coinset API base URL for coin queries and `push_tx`; when unset, `CoinsetAdapter` defaults to mainnet and can be forced to testnet11 by network selection.
- `coin_ops.minimum_fee_mojos` (in program config) — fallback minimum fee for coin operations when Coinset advice is unavailable (default `10000000` mojos / `0.00001 XCH`; can be set to `0`).

Cloud Wallet program config contract (`program.yaml`):
Signer program config contract (`program.yaml`):

- `cloud_wallet.base_url` — GraphQL API root URL.
- `cloud_wallet.user_key_id` — user auth key id used in `chia-user-key-id`.
- `cloud_wallet.private_key_pem_path` — PEM private key path for RSA-SHA256 header signatures.
- `cloud_wallet.vault_id` — target wallet/vault ID for coin and offer operations.
- `signer.kms_key_id` — AWS KMS key for vault member signing.
- `signer.kms_region` — AWS region for KMS calls.
- `vault.launcher_id` — vault singleton launcher id (hex).
- `vault.custody_keys` / `vault.recovery_keys` — member public keys for the vault puzzle.

Legacy `cloud_wallet:` blocks are rejected at config load; use `signer:` + `vault:` instead.

CI secret for optional live testnet workflow:

Expand Down
25 changes: 18 additions & 7 deletions docs/decisions/0011-offer-request-python-import-boundaries.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,24 @@ without growing `policy_bridge.py` into a flat FFI catalog.

### Python modules (import from here, not `policy_bridge`)

| Module | Use for |
| ---------------------------------------- | ----------------------------------------------------------------------------------------- |
| `greenfloor.core.offer_request_bridge` | Direct kernel access to offer-request symbols (internal bridge). |
| `greenfloor.core.offer_bootstrap_bridge` | **Stable runtime imports** — bootstrap DTOs, planner, and phase kernel wrappers. |
| `greenfloor.core.offer_bootstrap_policy` | Backward-compatible re-export of `offer_bootstrap_bridge` (no logic). |
| `greenfloor.core.offer_policy` | **Stable runtime/daemon/BLS imports** — re-exports leg math + Dexie/publish helpers. |
| `greenfloor.core.signer_offer_request` | `SignerCreateOfferRequest`, `SignerOfferLegAmounts`, `build_signer_create_offer_request`. |
| Module | Use for |
| ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `greenfloor.core.offer_request_bridge` | Direct kernel access to offer-request symbols (internal bridge). |
| `greenfloor.core.offer_bootstrap_bridge` | **Stable runtime imports** — bootstrap DTOs, planner, and phase kernel wrappers. |
| `greenfloor.core.offer_bootstrap_policy` | Backward-compatible re-export of `offer_bootstrap_bridge` (no logic). |
| `greenfloor.core.offer_policy` | **Stable runtime/daemon/BLS imports** — re-exports leg math + Dexie/publish helpers. |
| `greenfloor.core.signer_offer_request` | Low-level `SignerCreateOfferRequest` / `signer_create_offer_request_from_fields` (KMS plan-dict spends only). |
| `greenfloor.core.offer_action` | **Canonical offer create** — typed action request/result, pure shaping, create-phase outcome mapping. |
| `greenfloor.runtime.offer_action_build` | Build action requests from `OfferBuildContext` plus local/signer runtime orchestration (asset resolution + BLS create). |
| `greenfloor.adapters.offer_action` | Kernel IO only (`build_*_offer_for_action`). |

### Offer-action create path (2026-05)

- **New market-action offer creation** must use `core/offer_action` + `adapters/offer_action`
(signer) or `runtime/offer_action_build` (local BLS). Do not add call sites to
`rust_signer.build_vault_cat_offer` for that flow.
- Local BLS resolves ticker symbols via `resolve_action_assets_for_build_context` before kernel
dispatch when ids are not already canonical.

### `policy_bridge.py` role

Expand Down
2 changes: 1 addition & 1 deletion greenfloor-signer-pyo3/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"

[lib]
name = "greenfloor_signer"
name = "greenfloor_kernel"
crate-type = ["cdylib"]

[dependencies]
Expand Down
5 changes: 4 additions & 1 deletion greenfloor-signer-pyo3/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ requires = ["maturin>=1.8,<2.0"]
build-backend = "maturin"

[project]
name = "greenfloor_signer"
name = "greenfloor_kernel"
version = "0.1.0"
requires-python = ">=3.11"

[tool.maturin]
module-name = "greenfloor_kernel"
6 changes: 4 additions & 2 deletions greenfloor-signer-pyo3/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
//! PyO3 bindings for the GreenFloor Rust kernel (`greenfloor-signer` crate).
//!
//! The extension module is still exported as `greenfloor_signer` (ADR 0010). Python
//! The extension module is exported as `greenfloor_kernel` (ADR 0010). Python
//! callers should import through `greenfloor.core.kernel_bridge.import_kernel`.
extern crate greenfloor_signer as signer_core;

mod coin_ops_py;
mod cycle;
mod execution_py;
mod offer_action_py;
mod hex_py;
mod notifications_py;
mod offer_bootstrap_py;
Expand Down Expand Up @@ -411,7 +412,7 @@ fn coinset_get_conservative_fee_estimate_py(
}

#[pymodule]
fn greenfloor_signer(m: &Bound<'_, PyModule>) -> PyResult<()> {
fn greenfloor_kernel(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(resolve_vault_context_py, m)?)?;
m.add_function(wrap_pyfunction!(build_vault_cat_offer_py, m)?)?;
m.add_function(wrap_pyfunction!(build_mixed_split_py, m)?)?;
Expand All @@ -435,6 +436,7 @@ fn greenfloor_signer(m: &Bound<'_, PyModule>) -> PyResult<()> {
coinset_get_conservative_fee_estimate_py,
m
)?)?;
offer_action_py::register(m)?;
coin_ops_py::register(m)?;
cycle::register(m)?;
hex_py::register(m)?;
Expand Down
73 changes: 73 additions & 0 deletions greenfloor-signer-pyo3/src/offer_action_py.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyModule};
use signer_core::offer::action::{
build_bls_offer_for_action, build_signer_offer_for_action, BuildOfferForActionRequest,
};
use signer_core::{load_bls_master_secret_key, load_signer_config};

use crate::py_utils::{dict_from_json_value, request_dict_to_json, to_py_err};
use crate::{block_on_signer, parse_master_sk_bytes, runtime};

#[pyfunction]
#[pyo3(name = "build_signer_offer_for_action")]
fn build_signer_offer_for_action_py(
config_path: &str,
request: &Bound<'_, PyDict>,
) -> PyResult<Py<PyAny>> {
let config = load_signer_config(std::path::Path::new(config_path)).map_err(to_py_err)?;
let payload = request_dict_to_json(request)?;
let offer_request: BuildOfferForActionRequest =
serde_json::from_value(payload).map_err(to_py_err)?;
let result = runtime()
.block_on(build_signer_offer_for_action(config, offer_request))
.map_err(to_py_err)?;
Python::attach(|py| dict_from_json_value(py, serde_json::to_value(&result).map_err(to_py_err)?))
}

#[pyfunction]
#[pyo3(name = "build_bls_offer_for_action_key")]
fn build_bls_offer_for_action_key_py(
network: &str,
key_id: &str,
request: &Bound<'_, PyDict>,
) -> PyResult<Py<PyAny>> {
let master_sk = load_bls_master_secret_key(key_id.trim()).map_err(to_py_err)?;
let payload = request_dict_to_json(request)?;
let offer_request: BuildOfferForActionRequest =
serde_json::from_value(payload).map_err(to_py_err)?;
let result = block_on_signer(build_bls_offer_for_action(
network,
&master_sk,
offer_request,
))
.map_err(to_py_err)?;
Python::attach(|py| dict_from_json_value(py, serde_json::to_value(&result).map_err(to_py_err)?))
}

/// Internal/test entry: build a BLS action offer from raw master secret key bytes.
#[pyfunction]
#[pyo3(name = "build_bls_offer_for_action_sk")]
fn build_bls_offer_for_action_sk_py(
network: &str,
master_sk_bytes: &[u8],
request: &Bound<'_, PyDict>,
) -> PyResult<Py<PyAny>> {
let master_sk = parse_master_sk_bytes(master_sk_bytes)?;
let payload = request_dict_to_json(request)?;
let offer_request: BuildOfferForActionRequest =
serde_json::from_value(payload).map_err(to_py_err)?;
let result = block_on_signer(build_bls_offer_for_action(
network,
&master_sk,
offer_request,
))
.map_err(to_py_err)?;
Python::attach(|py| dict_from_json_value(py, serde_json::to_value(&result).map_err(to_py_err)?))
}

pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(build_signer_offer_for_action_py, m)?)?;
m.add_function(wrap_pyfunction!(build_bls_offer_for_action_key_py, m)?)?;
m.add_function(wrap_pyfunction!(build_bls_offer_for_action_sk_py, m)?)?;
Ok(())
}
4 changes: 3 additions & 1 deletion greenfloor-signer/src/bls/offer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ pub struct BlsOfferRequest {
pub request_amount: u64,
#[serde(default)]
pub offer_coin_ids: Vec<String>,
#[serde(default)]
pub expires_at: Option<u64>,
}

#[derive(Debug, Clone, Serialize)]
Expand Down Expand Up @@ -134,7 +136,7 @@ pub async fn build_bls_offer_spend_bundle(
offer_amount: request.offer_amount,
request_asset_id: request.request_asset_id.trim().to_lowercase(),
request_amount: request.request_amount,
expires_at: None,
expires_at: request.expires_at,
};

// Assertion nodes for requested payments only (not merged into the driver spend graph).
Expand Down
4 changes: 1 addition & 3 deletions greenfloor-signer/src/coinset/msp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,7 @@ pub async fn resolve_offer_asset_ids(
&& !is_xch_like_asset(&resolved_base)
&& !is_xch_like_asset(&resolved_quote)
{
return Err(SignerError::Other(
"resolved_assets_collide_for_non_xch_pair".to_string(),
));
return Err(SignerError::ResolvedAssetsCollideForNonXchPair);
}
Ok((resolved_base, resolved_quote))
}
Expand Down
3 changes: 3 additions & 0 deletions greenfloor-signer/src/error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ pub enum SignerError {
#[error("invalid_offer_amount")]
InvalidOfferAmount,

#[error("resolved offer assets collide for non-xch pair")]
ResolvedAssetsCollideForNonXchPair,

#[error("{0}")]
Other(String),
}
Expand Down
6 changes: 5 additions & 1 deletion greenfloor-signer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,11 @@ pub use offer::request::{
compute_signer_offer_leg_amounts, normalize_offer_asset_id, normalize_offer_side,
quote_mojos_for_base_size, signer_split_asset_id, SignerOfferLegAmounts,
};
pub use offer::{build_vault_cat_offer, CreateOfferRequest, CreateOfferResult};
pub use offer::{
build_bls_offer_for_action, build_signer_offer_for_action, build_vault_cat_offer,
expires_at_unix_from_pricing, BuildOfferForActionRequest, BuildOfferForActionResult,
CreateOfferRequest, CreateOfferResult,
};
pub use vault::{
build_and_optionally_broadcast_vault_cat_mixed_split, MixedSplitRequest, MixedSplitResult,
};
Expand Down
Loading
Loading