14 KiB
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:
AwaitingSellerConfirm→Active: seller accepts (via solistingaccept_order)AwaitingSellerConfirm→Cancelled: seller rejects, buyer cancels (via solisting or directly on descro)Active→Complete: buyer callscompleteon descro (marks delivery received, vault goes to seller)Active→Disputed: buyer or seller raises dispute on descroDisputed→CompleteorCancelled: resolver callsresolve(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 = Noneis 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" (whenActive), "Dispute" (whenActive) - 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.
Global search
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
decimalsstored in theCurrency::Splvariant. 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
ResolverEntryexists, show name. Otherwise show abbreviated pubkey. metadata_uri: show as a clickable link. Optionally fetch the JSON and show name/description/image from it.