solisting explorer
This commit is contained in:
293
docs/solisting-explorer.md
Normal file
293
docs/solisting-explorer.md
Normal file
@@ -0,0 +1,293 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user