From e799c0be9d0a06fed339b17343f91cd92a47655f Mon Sep 17 00:00:00 2001 From: thesn10 <38666407+thesn10@users.noreply.github.com> Date: Sat, 27 Jun 2026 14:52:11 +0200 Subject: [PATCH] resolver picker --- app/src/app/listing/[pk]/page.tsx | 118 ++++++++++++++++-- .../src/instructions/create_order.rs | 17 +-- programs/solisting/src/lib.rs | 2 +- programs/solisting/src/state.rs | 2 +- programs/solisting/tests/common/mod.rs | 9 +- programs/solisting/tests/test_orders.rs | 4 +- .../accounts/solistingStateOrderAccount.ts | 12 +- .../src/generated/instructions/createOrder.ts | 12 +- sdk/src/order.ts | 12 +- target/idl/solisting.json | 8 +- 10 files changed, 155 insertions(+), 41 deletions(-) diff --git a/app/src/app/listing/[pk]/page.tsx b/app/src/app/listing/[pk]/page.tsx index 114a496..a42b229 100644 --- a/app/src/app/listing/[pk]/page.tsx +++ b/app/src/app/listing/[pk]/page.tsx @@ -6,6 +6,7 @@ import { useWallet, useKitTransactionSigner } from '@solana/connector/react' import { ListingDetail } from '@/components/ListingDetail' import { useTx } from '@/hooks/useTx' import { useListing } from '@/hooks/useListing' +import { useResolvers } from '@/hooks/useResolvers' import { getCloseListingInstruction, getCreateOrderInstructionAsync, @@ -13,6 +14,8 @@ import { deriveEscrowId, } from '@solisting/sdk' import { DESCRO_PROGRAM_ADDRESS } from '@descro/sdk' +import { ResolverType } from '@descro/sdk' +import { Button } from '@/components/ui/Button' import type { Address } from '@solisting/sdk' const SYSTEM_PROGRAM = '11111111111111111111111111111111' as Address<'11111111111111111111111111111111'> @@ -24,7 +27,10 @@ export default function ListingPage({ params }: { params: Promise<{ pk: string } const { signer } = useKitTransactionSigner() const sendTx = useTx() const { data: listing } = useListing(pk as Address) + const { data: resolvers = [] } = useResolvers() const [orderLoading, setOrderLoading] = useState(false) + const [showResolverPicker, setShowResolverPicker] = useState(false) + const [selectedResolver, setSelectedResolver] = useState
(null) async function handleClose() { if (!signer) return @@ -33,8 +39,14 @@ export default function ListingPage({ params }: { params: Promise<{ pk: string } router.push('/listings') } - async function handlePlaceOrder() { + function openPlaceOrder() { + setSelectedResolver(null) + setShowResolverPicker(true) + } + + async function handleConfirmOrder() { if (!signer || !account || !listing) return + setShowResolverPicker(false) setOrderLoading(true) try { const orderId = BigInt(Date.now()) @@ -50,7 +62,7 @@ export default function ListingPage({ params }: { params: Promise<{ pk: string } descroProgram: DESCRO_PROGRAM_ADDRESS, orderId, escrowId, - resolver: account as Address, + resolver: selectedResolver, paymentCurrency: { __kind: 'Sol' }, expectedAmount: listing.data.price, maxSlippageBps: 0, @@ -67,13 +79,101 @@ export default function ListingPage({ params }: { params: Promise<{ pk: string } } } + const acceptedResolvers = listing?.data.acceptedResolvers ?? [] + const visibleResolvers = acceptedResolvers.length > 0 + ? resolvers.filter((r) => acceptedResolvers.includes(r.pda as Address)) + : resolvers + const mustSelectResolver = acceptedResolvers.length > 0 + const confirmDisabled = mustSelectResolver && !selectedResolver + return ( - router.push(`/listing/create?edit=${pk}`)} - onClose={handleClose} - onPlaceOrder={orderLoading ? undefined : handlePlaceOrder} - /> + <> + router.push(`/listing/create?edit=${pk}`)} + onClose={handleClose} + onPlaceOrder={orderLoading ? undefined : openPlaceOrder} + /> + + {showResolverPicker && ( +
{ if (e.target === e.currentTarget) setShowResolverPicker(false) }} + > +
+
PLACE ORDER
+

Select Resolver

+

+ {mustSelectResolver + ? 'This listing requires one of the following resolvers for dispute resolution.' + : 'Choose a resolver for dispute resolution, or proceed without one.'} +

+ +
+ {!mustSelectResolver && ( + setSelectedResolver(null)} + label="No resolver" + sublabel="Disputes cannot be resolved on-chain" + /> + )} + {visibleResolvers.map((r) => ( + setSelectedResolver(r.pda as Address)} + label={`${r.pda.slice(0, 8)}…${r.pda.slice(-6)}`} + sublabel={ResolverType[r.data.resolverType as number] as string} + mono + /> + ))} + {visibleResolvers.length === 0 && mustSelectResolver && ( +
+ No registered resolvers found for this listing. +
+ )} +
+ +
+ + +
+
+
+ )} + + ) +} + +function ResolverOption({ selected, onClick, label, sublabel, mono }: { + selected: boolean + onClick: () => void + label: string + sublabel?: string + mono?: boolean +}) { + return ( + ) } diff --git a/programs/solisting/src/instructions/create_order.rs b/programs/solisting/src/instructions/create_order.rs index 61acaad..16e9a2b 100644 --- a/programs/solisting/src/instructions/create_order.rs +++ b/programs/solisting/src/instructions/create_order.rs @@ -4,7 +4,7 @@ use crate::state::{Currency, ListingAccount, OrderAccount}; use anchor_lang::prelude::*; #[derive(Accounts)] -#[instruction(order_id: u64, escrow_id: u64, resolver: Pubkey, payment_currency: Currency)] +#[instruction(order_id: u64, escrow_id: u64, resolver: Option, payment_currency: Currency)] pub struct CreateOrder<'info> { #[account(mut)] pub buyer: Signer<'info>, @@ -67,7 +67,7 @@ pub fn handler( ctx: Context, order_id: u64, escrow_id: u64, - resolver: Pubkey, + resolver: Option, payment_currency: Currency, expected_amount: u64, max_slippage_bps: u16, @@ -80,10 +80,13 @@ pub fn handler( let listing = &ctx.accounts.listing_account; if !listing.accepted_resolvers.is_empty() { - require!( - listing.accepted_resolvers.contains(&resolver), - SolistingError::ResolverNotAccepted - ); + match resolver { + Some(r) => require!( + listing.accepted_resolvers.contains(&r), + SolistingError::ResolverNotAccepted + ), + None => return err!(SolistingError::ResolverNotAccepted), + } } let amount = if payment_currency == listing.canonical_currency { @@ -143,7 +146,7 @@ pub fn handler( }, ), amount, - Some(resolver), + resolver, escrow_id, )?; diff --git a/programs/solisting/src/lib.rs b/programs/solisting/src/lib.rs index 17de565..45863e3 100644 --- a/programs/solisting/src/lib.rs +++ b/programs/solisting/src/lib.rs @@ -70,7 +70,7 @@ pub mod solisting { ctx: Context, order_id: u64, escrow_id: u64, - resolver: Pubkey, + resolver: Option, payment_currency: state::Currency, expected_amount: u64, max_slippage_bps: u16, diff --git a/programs/solisting/src/state.rs b/programs/solisting/src/state.rs index 47b90af..d7f111e 100644 --- a/programs/solisting/src/state.rs +++ b/programs/solisting/src/state.rs @@ -69,7 +69,7 @@ pub struct OrderAccount { pub listing: Pubkey, pub buyer: Pubkey, pub seller: Pubkey, - pub resolver: Pubkey, + pub resolver: Option, /// The currency the buyer chose to pay in. pub payment_currency: Currency, /// Actual amount paid (may differ from price when oracle-converted). diff --git a/programs/solisting/tests/common/mod.rs b/programs/solisting/tests/common/mod.rs index 4c4872b..ed8ffd5 100644 --- a/programs/solisting/tests/common/mod.rs +++ b/programs/solisting/tests/common/mod.rs @@ -195,7 +195,6 @@ pub fn ix_create_order( canonical_oracle: Option, target_oracle: Option, ) -> Instruction { - let resolver = Pubkey::new_unique(); ix_create_order_with_resolver( buyer, seller, @@ -205,7 +204,7 @@ pub fn ix_create_order( max_slippage_bps, canonical_oracle, target_oracle, - resolver, + Some(Pubkey::new_unique()), ) } @@ -219,7 +218,7 @@ pub fn ix_create_order_with_resolver( max_slippage_bps: u16, canonical_oracle: Option, target_oracle: Option, - resolver: Pubkey, + resolver: Option, ) -> Instruction { let listing = listing_pda(seller, listing_id); let order = order_pda(listing, *buyer, order_id); @@ -264,7 +263,7 @@ pub fn ix_accept_order( buyer: &Pubkey, order_id: u64, escrow_id: u64, - resolver: Pubkey, + resolver: Option, ) -> Instruction { let listing_account = listing_pda(seller, listing_id); let order_account = order_pda(listing_account, *buyer, order_id); @@ -276,7 +275,7 @@ pub fn ix_accept_order( &solisting::instruction::AcceptOrder {}.data(), solisting::accounts::AcceptOrder { seller: *seller, - resolver, + resolver: resolver.unwrap_or(system_program::ID), listing_account, order_account, escrow_account, diff --git a/programs/solisting/tests/test_orders.rs b/programs/solisting/tests/test_orders.rs index d2c6624..d9cf652 100644 --- a/programs/solisting/tests/test_orders.rs +++ b/programs/solisting/tests/test_orders.rs @@ -118,7 +118,7 @@ fn create_order_fails_if_resolver_not_accepted() { &mut svm, &[ix_create_order_with_resolver( &buyer.pubkey(), &seller.pubkey(), listing_id, 1, - Currency::Sol, 0, None, None, Pubkey::new_unique(), + Currency::Sol, 0, None, None, Some(Pubkey::new_unique()), )], &[&buyer], ); @@ -152,7 +152,7 @@ fn seller_accept_creates_active_descro_escrow() { ); let order_key = order_pda(listing_key, buyer.pubkey(), order_id); - assert!(svm.get_account(&order_key).is_none()); + assert!(svm.get_account(&order_key).is_some()); let listing = read_listing(&svm, &seller.pubkey(), listing_id); assert_eq!(listing.quantity_reserved, 0); diff --git a/sdk/src/generated/solisting/src/generated/accounts/solistingStateOrderAccount.ts b/sdk/src/generated/solisting/src/generated/accounts/solistingStateOrderAccount.ts index 33bb7fc..ad24cb2 100644 --- a/sdk/src/generated/solisting/src/generated/accounts/solistingStateOrderAccount.ts +++ b/sdk/src/generated/solisting/src/generated/accounts/solistingStateOrderAccount.ts @@ -21,6 +21,8 @@ import { getBytesEncoder, getI64Decoder, getI64Encoder, + getOptionDecoder, + getOptionEncoder, getStructDecoder, getStructEncoder, getU64Decoder, @@ -38,6 +40,8 @@ import { type FetchAccountsConfig, type MaybeAccount, type MaybeEncodedAccount, + type Option, + type OptionOrNullable, type ReadonlyUint8Array, } from "@solana/kit"; import { @@ -61,7 +65,7 @@ export type SolistingStateOrderAccount = { listing: Address; buyer: Address; seller: Address; - resolver: Address; + resolver: Option
; /** The currency the buyer chose to pay in. */ paymentCurrency: SolistingStateCurrency; /** Actual amount paid (may differ from price when oracle-converted). */ @@ -79,7 +83,7 @@ export type SolistingStateOrderAccountArgs = { listing: Address; buyer: Address; seller: Address; - resolver: Address; + resolver: OptionOrNullable
; /** The currency the buyer chose to pay in. */ paymentCurrency: SolistingStateCurrencyArgs; /** Actual amount paid (may differ from price when oracle-converted). */ @@ -101,7 +105,7 @@ export function getSolistingStateOrderAccountEncoder(): Encoder; paymentCurrency: SolistingStateCurrency; expectedAmount: bigint; maxSlippageBps: number; @@ -130,7 +134,7 @@ export type CreateOrderInstructionData = { export type CreateOrderInstructionDataArgs = { orderId: number | bigint; escrowId: number | bigint; - resolver: Address; + resolver: OptionOrNullable
; paymentCurrency: SolistingStateCurrencyArgs; expectedAmount: number | bigint; maxSlippageBps: number; @@ -142,7 +146,7 @@ export function getCreateOrderInstructionDataEncoder(): Encoder // 8: listing (32) // 40: buyer (32) // 72: seller (32) -// 104: resolver (32) -// 136: paymentCurrency — Sol variant = 1 byte tag + 0 data = 1 byte total -// 137: amount (8) -// 145: escrowAccount (32) +// 104: resolver Option = 1 byte tag + 32 bytes = 33 bytes +// 137: paymentCurrency — Sol variant = 1 byte tag + 0 data = 1 byte total +// 138: amount (8) +// 146: escrowAccount (32) const LISTING_OFFSET = 8n const BUYER_OFFSET = 40n // escrowAccount offset is only valid for Sol-currency orders. -// SPL orders (currently rejected by the program with SplNotImplemented) would sit at offset 178. -const ESCROW_ACCOUNT_OFFSET = 145n +// SPL orders (currently rejected by the program with SplNotImplemented) would sit at offset 179. +const ESCROW_ACCOUNT_OFFSET = 146n type RawGpaResult = Array<{ pubkey: Address diff --git a/target/idl/solisting.json b/target/idl/solisting.json index 4db8d51..1cca1c1 100644 --- a/target/idl/solisting.json +++ b/target/idl/solisting.json @@ -835,7 +835,9 @@ }, { "name": "resolver", - "type": "pubkey" + "type": { + "option": "pubkey" + } }, { "name": "payment_currency", @@ -1461,7 +1463,9 @@ }, { "name": "resolver", - "type": "pubkey" + "type": { + "option": "pubkey" + } }, { "name": "payment_currency",