294 lines
14 KiB
Markdown
294 lines
14 KiB
Markdown
# 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 solisting `accept_order`)
|
||
- `AwaitingSellerConfirm` → `Cancelled`: seller rejects, buyer cancels (via solisting or directly on descro)
|
||
- `Active` → `Complete`: buyer calls `complete` on descro (marks delivery received, vault goes to seller)
|
||
- `Active` → `Disputed`: buyer or seller raises dispute on descro
|
||
- `Disputed` → `Complete` 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 `OrderAccount`s 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 `ListingAccount`s where `seller == connected_wallet`. Quick stats per listing. Buttons: Update, Close, + a link to its orders.
|
||
|
||
My Orders: all `OrderAccount`s 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 `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.
|