Files
solisting/docs/solisting-explorer.md
2026-06-24 23:02:54 +02:00

14 KiB
Raw Permalink Blame History

Solisting Explorer — Frontend Design Brief

Build a dark, polished blockchain explorer frontend for the Solisting Solana program ecosystem — similar in style to Solscan or Jupiter (dark backgrounds, colored state badges, card-based layout). The primary accent color is Solana purple (#9945FF) with green (#14F195) for active/success states.

The app is called Solisting Explorer. It's a debug/inspection tool like mempool.space or Etherscan, but specifically for the solisting + descro program ecosystem. Users can browse all on-chain data and, if they connect their wallet, execute instructions they're authorized for.


What Solisting Is

Solisting is a Solana on-chain program (Anchor framework) that provides a coordination layer for product listings and order consent. It sits on top of a separate escrow program called descro.

  • Sellers create Listings advertising goods or services with a price in SOL (or SPL tokens).
  • Buyers create Orders against a listing. This triggers the creation of a descro Escrow — a trustless SOL vault holding the buyer's funds until the seller confirms.
  • The seller then accepts or rejects. Disputes are resolved by a Resolver (a registered third party).
  • Solisting never holds funds itself — all money flows through descro's vault accounts.

Programs and IDs

Program Role Program ID
solisting Listings + Orders DzwUAbpRvqcbA8QsEkRbZeXG4TEho5782cMySodrUHBU
descro Escrow vault engine DjVR4EuYV6USMJFfsGZwhZ3y8rtWsmG8EvDY96GTqqi3
descro_ext_resolvers Resolver registry GwUPAKs3HHzCpj8uhet4NAnxk9GWNwfrYbpihu5DyFp

The app needs a network selector dropdown in the top navigation: localnet / devnet / mainnet-beta. The selected network determines the RPC endpoint for all account fetches and transaction submissions.


On-Chain Accounts

ListingAccount (solisting program)

PDA seeds: ["listing", seller_pubkey_bytes, listing_id_as_u64_le_bytes]

seller: Pubkey                         // wallet that created the listing
listing_id: u64                        // unique ID chosen by seller
canonical_currency: Currency           // Sol | Spl { mint: Pubkey, decimals: u8 }
price: u64                             // in smallest unit of canonical_currency
canonical_oracle: Option<Pubkey>       // Pyth V2 feed (TOKEN/USD). None = stablecoin ($1)
alt_currencies: Vec<AltCurrencyConfig> // max 3. each has { currency, usd_oracle: Option<Pubkey> }
accepted_resolvers: Vec<Pubkey>        // max 4. empty = any resolver is accepted
quantity: u32                          // total units (includes reserved)
quantity_reserved: u32                 // locked by pending orders
metadata_uri: String                   // max 256 chars. IPFS or HTTPS link to listing metadata JSON
is_active: bool                        // whether new orders are accepted
bump: u8

Derived: available = quantity quantity_reserved

OrderAccount (solisting program)

PDA seeds: ["order", listing_account_pubkey_bytes, buyer_pubkey_bytes, order_id_as_u64_le_bytes]

listing: Pubkey           // the ListingAccount this order belongs to
buyer: Pubkey
seller: Pubkey            // copied from listing at creation
resolver: Pubkey          // chosen dispute resolver
payment_currency: Currency
amount: u64               // final payment amount (oracle-converted if paying in alt currency)
escrow_account: Pubkey    // linked descro EscrowAccount PDA
escrow_id: u64            // first 8 bytes of this order's PDA address as u64 little-endian
order_id: u64
created_at: i64           // unix timestamp
bump: u8

EscrowAccount (descro program)

PDA seeds: ["escrow", seller_pubkey_bytes, escrow_id_as_u64_le_bytes]

seller: Pubkey
buyer: Pubkey
amount: u64                    // lamports held in the vault
dispute_resolver: Option<Pubkey>
state: EscrowState             // see lifecycle below
escrow_id: u64
dispute_raised_at: Option<i64> // unix timestamp, set when dispute is raised
bump: u8
vault_bump: u8

The vault is a separate bare system account (no data, just holds SOL):
PDA seeds: ["vault", escrow_account_pubkey_bytes] (in the descro program)

EscrowState lifecycle

AwaitingSellerConfirm  →  buyer deposited, waiting for seller
Active                 →  seller confirmed, delivery in progress
Disputed               →  dispute raised by buyer or seller
Complete               →  funds released to seller
Cancelled              →  funds refunded to buyer

State transitions:

  • AwaitingSellerConfirmActive: seller accepts (via solisting accept_order)
  • AwaitingSellerConfirmCancelled: seller rejects, buyer cancels (via solisting or directly on descro)
  • ActiveComplete: buyer calls complete on descro (marks delivery received, vault goes to seller)
  • ActiveDisputed: buyer or seller raises dispute on descro
  • DisputedComplete or Cancelled: resolver calls resolve(Winner) on descro

State badge color suggestions: AwaitingSellerConfirm = amber, Active = green, Disputed = red/orange, Complete = blue/teal, Cancelled = gray.

ResolverEntry (descro_ext_resolvers program)

PDA seeds: ["resolver", resolver_pubkey_bytes]

authority: Pubkey
resolver_type: ResolverType        // CentralAuthority | JuryDAO | MAD | Algorithmic | Multisig
acceptance_policy: AcceptancePolicy // Open | SignatureGated | ProgramGated
name: String                        // max 64 chars
description: String                 // max 256 chars
fee_bps: u16                        // fee in basis points (100 bps = 1%)
fee_recipient: Pubkey
metadata_uri: String                // max 256 chars
total_resolved: u64
ruled_for_buyer: u64
ruled_for_seller: u64
registered_at: i64

Instructions

Solisting: create_listing

Signer: seller
Creates a new ListingAccount.
Params: listing_id, canonical_currency, price, canonical_oracle, alt_currencies (max 3), accepted_resolvers (max 4), quantity, metadata_uri

Solisting: update_listing

Signer: seller (must own the listing, listing must be active)
Updates all mutable fields on an existing ListingAccount (not the listing_id).

Solisting: close_listing

Signer: seller
Closes the ListingAccount. Rent returned to seller.

Solisting: create_order

Signer: buyer
Creates an OrderAccount and CPIs into descro to create + fund an escrow vault with the buyer's SOL.
Params: order_id, escrow_id (= first 8 bytes of order PDA as u64 LE), resolver, payment_currency, expected_amount, max_slippage_bps
Constraints: listing must be active, must have available quantity, currency must be in listing's accepted currencies, resolver must be in listing's accepted resolvers (if list is non-empty).
Note: only Currency::Sol works currently. SPL token payment is not yet implemented (SplNotImplemented error).

Solisting: accept_order

Signer: seller
CPIs descro::seller_confirm → escrow moves to Active. Decrements quantity_reserved and quantity. Closes OrderAccount (rent to seller).

Solisting: reject_order

Signer: seller
Defensive: reads escrow state first; only CPIs descro::cancel if escrow is still AwaitingSellerConfirm. Decrements quantity_reserved. Closes OrderAccount. Buyer gets vault refund.

Solisting: cancel_order

Signer: buyer
Same as reject_order but initiated by the buyer. Closes OrderAccount (rent to buyer).

Solisting: close_stale_order

Signer: anyone (permissionless cleanup)
Closes an OrderAccount whose linked descro escrow is already in a terminal state (Complete or Cancelled) or has already been closed (account data empty). Rent goes to the caller.

Descro: complete

Signer: buyer
Constraint: escrow must be Active.
Releases vault funds to seller. Escrow → Complete. Closes escrow account.

Descro: dispute

Signer: buyer or seller
Constraint: escrow must be Active.
Escrow → Disputed. Records dispute_raised_at timestamp.

Descro: resolve

Signer: resolver (must match escrow.dispute_resolver)
Constraint: escrow must be Disputed.
Param: winner: Winner (Buyer or Seller).
Releases vault to winner. Escrow → Complete.

Descro: cancel (direct)

Signer: buyer or seller
Constraint: escrow must be AwaitingDeposit or AwaitingSellerConfirm.
Refunds vault to buyer. Closes escrow.


Error Codes (Solisting)

Name Message
ListingNotActive Listing is not active
OutOfStock No quantity available
Unauthorized Signer is not authorized
CurrencyNotAccepted Currency not accepted by this listing
ResolverNotAccepted Resolver not accepted by this listing
OracleRequired Oracle account required but not provided
OracleMismatch Oracle key doesn't match listing
OraclePriceUnavailable Oracle price stale or confidence too wide
SlippageExceeded Oracle rate moved unfavorably
SplNotImplemented SPL payment not yet implemented
EscrowStateUnexpected Escrow not in expected state
InvalidDescroProgram Descro program address mismatch

Oracle / Price Conversion

Solisting uses Pyth V2 price feeds for multi-currency support.

  • Prices must be no older than 60 seconds.
  • Conversion formula (canonical currency → alt currency): target = price × canonical_price_raw / target_price_raw × 10^(canonical_exponent target_exponent + target_decimals canonical_decimals)
  • A currency with usd_oracle = None is treated as a $1.00 USD stablecoin.
  • Slippage check: |computed expected| ≤ expected × max_slippage_bps / 10_000

Relationships Between Accounts

ResolverEntry (descro_ext_resolvers)
    ↑ pubkey referenced in
ListingAccount.accepted_resolvers[]    ←── one listing has many orders
    ↑ pubkey stored in
OrderAccount.listing
OrderAccount.resolver                  → can look up ResolverEntry for display

OrderAccount.escrow_account            → EscrowAccount (descro)
EscrowAccount                          → Vault (bare SOL account, descro)

Pages and Features

Listings page (no wallet required)

Browse all ListingAccount PDAs on-chain. Show: listing ID, seller (abbreviated address), canonical price + currency, available qty / total qty, number of pending orders, active/inactive badge. Filterable by active status and seller address. Clicking a row goes to the listing detail page.

Listing detail page (no wallet required)

Show all fields of the ListingAccount. Quantity displayed as a gauge or three numbers (available / reserved / total). Alt currencies listed with their oracle addresses. Accepted resolvers listed — if a ResolverEntry exists for that pubkey, show the resolver name. Metadata URI shown with a link and optional fetch of the JSON to show name/description/image. Below: table of all OrderAccounts for this listing with their escrow state.

If connected wallet = seller: show "Update Listing" and "Close Listing" buttons.
If connected wallet is not the seller and has no open order: show "Place Order" button.

Order detail page (no wallet required)

Show all fields of the OrderAccount. Show the linked EscrowAccount data inline: state badge, vault balance in SOL (raw lamports on hover), dispute_raised_at if applicable. Show the escrow state machine with the current state highlighted.

Action buttons depend on the connected wallet's role:

  • Seller: "Accept Order" and "Reject Order" (when escrow is AwaitingSellerConfirm)
  • Buyer: "Cancel Order" (when escrow is AwaitingSellerConfirm), "Complete" (when Active), "Dispute" (when Active)
  • Anyone: "Close Stale Order" (when escrow is terminal or closed)

All escrows page (no wallet required)

Browse all descro::EscrowAccount PDAs. Filterable by state. Columns: escrow ID, seller, buyer, amount in SOL, state badge, resolver (if any).

Resolver registry page (no wallet required)

Browse all ResolverEntry PDAs. Show name, type, acceptance policy, fee %, total disputes, buyer win %, seller win %. Clicking goes to resolver detail page.

Resolver detail page (no wallet required)

All fields of a ResolverEntry. Show dispute stats as numbers and ratios.

My Dashboard (wallet required)

Two tabs: "My Listings" and "My Orders (as Buyer)".

My Listings: all ListingAccounts where seller == connected_wallet. Quick stats per listing. Buttons: Update, Close, + a link to its orders.

My Orders: all OrderAccounts where buyer == connected_wallet. Show current escrow state per order with available action buttons.

Prominent "Create Listing" button on this page.

Create / Edit Listing form (wallet required)

Fields: canonical currency (SOL or SPL mint address), price, canonical oracle pubkey (optional), up to 3 alt currencies (each: currency + oracle pubkey), up to 4 accepted resolver pubkeys, quantity, metadata URI.

Search box in the top nav. Input any pubkey. The frontend determines what account type it is and redirects to the relevant detail page (listing, order, escrow, resolver, or wallet).


Display Conventions

  • Addresses: always show as ABCD...WXYZ (first 4 + last 4 chars). Copy-to-clipboard button. Link to the relevant detail page within the explorer.
  • SOL amounts: show as SOL (divide lamports by 1,000,000,000). Show raw lamport value on hover.
  • SPL token amounts: show in token units using the decimals stored in the Currency::Spl variant. Optionally show token symbol if resolvable via token metadata.
  • Timestamps: show as relative time ("2 hours ago"). Show exact UTC datetime on hover.
  • quantity_reserved: always show all three numbers — available / reserved / total.
  • Resolver pubkeys in listings: if a ResolverEntry exists, show name. Otherwise show abbreviated pubkey.
  • metadata_uri: show as a clickable link. Optionally fetch the JSON and show name/description/image from it.