diff --git a/docs/superpowers/plans/2026-05-19-solisting.md b/docs/superpowers/plans/2026-05-19-solisting.md index c10100b..0a261fe 100644 --- a/docs/superpowers/plans/2026-05-19-solisting.md +++ b/docs/superpowers/plans/2026-05-19-solisting.md @@ -9,7 +9,7 @@ **Goal:** Build the `solisting` Anchor program — a coordination and discovery layer for product listings and bilateral order consent, orchestrating the full flow from buyer intent → bilateral consent → active `descro` escrow without ever touching the descro vault directly. **Architecture:** -- `ListingAccount` holds a single canonical price (`canonical_currency` + `canonical_amount`) as the sole source of truth. Alternative currencies (`alt_currencies: Vec`) are always converted from canonical at `create_order` time via on-chain Pyth oracles — no independent price per currency, no arbitrage possible. +- `ListingAccount` holds a single canonical price (`canonical_currency` + `price`) as the sole source of truth. Alternative currencies (`alt_currencies: Vec`) are always converted from canonical at `create_order` time via on-chain Pyth oracles — no independent price per currency, no arbitrage possible. - Each `AltCurrencyConfig` carries its own `usd_oracle: Option` (the Pyth TOKEN/USD feed for that token; `None` = treat as a $1 USD stablecoin). The canonical currency has a parallel `canonical_oracle: Option`. `Currency::Spl` stores `decimals: u8` so the program never needs to look up the mint. - `OrderAccount` records the bilateral consent: which currency the buyer chose, the oracle-computed amount, and a pointer to the descro escrow. - The oracle module converts between any two currencies through USD as an intermediate (using up to two Pyth feeds): `canonical_usd = amount × canonical_price`, `target_amount = canonical_usd ÷ target_price`. Both sides can independently be a stablecoin (`None` oracle = $1 peg, no account needed). @@ -176,7 +176,7 @@ pub struct ListingAccount { /// Sole source of truth for price. All alt_currencies are derived from this via oracle. pub canonical_currency: Currency, /// Price in canonical_currency's smallest unit. - pub canonical_amount: u64, + pub price: u64, /// Pyth V2 PriceFeedAccount for the canonical currency priced in USD. /// `None` = canonical is a $1.00 USD stablecoin. /// For Sol canonical: set to the SOL/USD Pyth feed. @@ -208,7 +208,7 @@ pub struct OrderAccount { pub resolver: Pubkey, /// The currency the buyer chose to pay in. pub payment_currency: Currency, - /// Actual amount paid (may differ from canonical_amount when oracle-converted). + /// Actual amount paid (may differ from price when oracle-converted). pub amount: u64, /// The descro EscrowAccount PDA for this order. pub escrow_account: Pubkey, @@ -264,9 +264,9 @@ verifies that the passed account key matches the stored key (done in `create_ord Math (using USD as pivot): ``` -canonical_usd = canonical_amount × (cp_raw × 10^cp_expo) / 10^cd +canonical_usd = price × (cp_raw × 10^cp_expo) / 10^cd target_amount = canonical_usd × 10^td / (tp_raw × 10^tp_expo) - = canonical_amount × cp_raw / tp_raw × 10^(cp_expo − tp_expo + td − cd) + = price × cp_raw / tp_raw × 10^(cp_expo − tp_expo + td − cd) let shift = cp_expo − tp_expo + td − cd (can be positive or negative) ``` @@ -313,7 +313,7 @@ fn usd_price(oracle: Option<&AccountInfo>) -> Result<(i64, i32)> { } } -/// Converts `canonical_amount` (smallest units of `canonical_currency`) to the equivalent +/// Converts `price` (smallest units of `canonical_currency`) to the equivalent /// amount in `target_currency` smallest units, using USD as an intermediate. /// /// `canonical_oracle` / `target_oracle`: pass `None` when the currency is a USD stablecoin. @@ -324,7 +324,7 @@ fn usd_price(oracle: Option<&AccountInfo>) -> Result<(i64, i32)> { pub fn convert_with_slippage( canonical_oracle: Option<&AccountInfo>, canonical_currency: &Currency, - canonical_amount: u64, + price: u64, target_oracle: Option<&AccountInfo>, target_currency: &Currency, expected_amount: u64, @@ -339,9 +339,9 @@ pub fn convert_with_slippage( // shift = cp_expo − tp_expo + td − cd let shift: i32 = cp_expo - tp_expo + td - cd; - // target = canonical_amount × cp_raw / tp_raw × 10^shift + // target = price × cp_raw / tp_raw × 10^shift // Use u128 to avoid overflow. tp_raw and cp_raw are always positive (checked above). - let numerator = (canonical_amount as u128) + let numerator = (price as u128) .checked_mul(cp_raw as u128) .ok_or(error!(SolistingError::OraclePriceUnavailable))?; @@ -418,7 +418,7 @@ fn seller_can_create_sol_only_listing() { let listing = read_listing(&svm, &seller.pubkey(), listing_id); assert_eq!(listing.canonical_currency, Currency::Sol); - assert_eq!(listing.canonical_amount, price); + assert_eq!(listing.price, price); assert_eq!(listing.alt_currencies.len(), 0); assert_eq!(listing.canonical_oracle, None); assert_eq!(listing.quantity, 10); @@ -502,7 +502,7 @@ fn seller_can_update_listing() { send(&mut svm, &[ix_update], &[&seller]); let listing = read_listing(&svm, &seller.pubkey(), listing_id); - assert_eq!(listing.canonical_amount, 2_000_000_000); + assert_eq!(listing.price, 2_000_000_000); assert_eq!(listing.quantity, 20); } @@ -558,7 +558,7 @@ pub fn handler( ctx: Context, listing_id: u64, canonical_currency: Currency, - canonical_amount: u64, + price: u64, canonical_oracle: Option, alt_currencies: Vec, accepted_resolvers: Vec, @@ -568,7 +568,7 @@ pub fn handler( let listing = &mut ctx.accounts.listing_account; listing.seller = ctx.accounts.seller.key(); listing.canonical_currency = canonical_currency; - listing.canonical_amount = canonical_amount; + listing.price = price; listing.canonical_oracle = canonical_oracle; listing.alt_currencies = alt_currencies; listing.accepted_resolvers = accepted_resolvers; @@ -606,7 +606,7 @@ pub struct UpdateListing<'info> { pub fn handler( ctx: Context, canonical_currency: Currency, - canonical_amount: u64, + price: u64, canonical_oracle: Option, alt_currencies: Vec, accepted_resolvers: Vec, @@ -615,7 +615,7 @@ pub fn handler( ) -> Result<()> { let listing = &mut ctx.accounts.listing_account; listing.canonical_currency = canonical_currency; - listing.canonical_amount = canonical_amount; + listing.price = price; listing.canonical_oracle = canonical_oracle; listing.alt_currencies = alt_currencies; listing.accepted_resolvers = accepted_resolvers; @@ -702,7 +702,7 @@ pub fn ix_create_listing( seller: &Pubkey, listing_id: u64, canonical_currency: Currency, - canonical_amount: u64, + price: u64, canonical_oracle: Option, alt_currencies: Vec, accepted_resolvers: Vec, @@ -716,7 +716,7 @@ pub fn ix_update_listing( seller: &Pubkey, listing_id: u64, canonical_currency: Currency, - canonical_amount: u64, + price: u64, canonical_oracle: Option, alt_currencies: Vec, accepted_resolvers: Vec, @@ -771,7 +771,7 @@ pub mod solisting { ctx: Context, listing_id: u64, canonical_currency: state::Currency, - canonical_amount: u64, + price: u64, canonical_oracle: Option, alt_currencies: Vec, accepted_resolvers: Vec, @@ -779,7 +779,7 @@ pub mod solisting { metadata_uri: String, ) -> Result<()> { create_listing::handler( - ctx, listing_id, canonical_currency, canonical_amount, + ctx, listing_id, canonical_currency, price, canonical_oracle, alt_currencies, accepted_resolvers, quantity, metadata_uri, ) } @@ -787,7 +787,7 @@ pub mod solisting { pub fn update_listing( ctx: Context, canonical_currency: state::Currency, - canonical_amount: u64, + price: u64, canonical_oracle: Option, alt_currencies: Vec, accepted_resolvers: Vec, @@ -795,7 +795,7 @@ pub mod solisting { metadata_uri: String, ) -> Result<()> { update_listing::handler( - ctx, canonical_currency, canonical_amount, + ctx, canonical_currency, price, canonical_oracle, alt_currencies, accepted_resolvers, quantity, metadata_uri, ) } @@ -844,7 +844,7 @@ fn buyer_can_create_order_canonical_sol() { // Buyer creates order paying in Sol // Assert: OrderAccount exists with payment_currency=Sol, amount=0.1 SOL in lamports // Assert: descro EscrowAccount exists, state == AwaitingSellerConfirm - // Assert: descro vault has listing.canonical_amount lamports + // Assert: descro vault has listing.price lamports // Assert: listing.quantity_reserved == 1 let (mut svm, seller, buyer) = setup(); let listing_id = 1u64; @@ -1037,7 +1037,7 @@ pub fn handler( // Determine amount in payment_currency let amount = if payment_currency == listing.canonical_currency { // Paying in canonical currency — no oracle needed - listing.canonical_amount + listing.price } else { // Must be a configured alt_currency let alt_cfg = listing.alt_currencies @@ -1065,7 +1065,7 @@ pub fn handler( oracle::convert_with_slippage( c_oracle, &listing.canonical_currency, - listing.canonical_amount, + listing.price, t_oracle, &payment_currency, expected_amount, @@ -1745,12 +1745,12 @@ declare_id!("So1istingProgramID11111111111111111111111111"); // replace with: so pub mod solisting { use super::*; - pub fn create_listing(ctx: Context, listing_id: u64, canonical_currency: state::Currency, canonical_amount: u64, canonical_oracle: Option, alt_currencies: Vec, accepted_resolvers: Vec, quantity: u32, metadata_uri: String) -> Result<()> { - create_listing::handler(ctx, listing_id, canonical_currency, canonical_amount, canonical_oracle, alt_currencies, accepted_resolvers, quantity, metadata_uri) + pub fn create_listing(ctx: Context, listing_id: u64, canonical_currency: state::Currency, price: u64, canonical_oracle: Option, alt_currencies: Vec, accepted_resolvers: Vec, quantity: u32, metadata_uri: String) -> Result<()> { + create_listing::handler(ctx, listing_id, canonical_currency, price, canonical_oracle, alt_currencies, accepted_resolvers, quantity, metadata_uri) } - pub fn update_listing(ctx: Context, canonical_currency: state::Currency, canonical_amount: u64, canonical_oracle: Option, alt_currencies: Vec, accepted_resolvers: Vec, quantity: u32, metadata_uri: String) -> Result<()> { - update_listing::handler(ctx, canonical_currency, canonical_amount, canonical_oracle, alt_currencies, accepted_resolvers, quantity, metadata_uri) + pub fn update_listing(ctx: Context, canonical_currency: state::Currency, price: u64, canonical_oracle: Option, alt_currencies: Vec, accepted_resolvers: Vec, quantity: u32, metadata_uri: String) -> Result<()> { + update_listing::handler(ctx, canonical_currency, price, canonical_oracle, alt_currencies, accepted_resolvers, quantity, metadata_uri) } pub fn close_listing(ctx: Context) -> Result<()> {