fix escrow handling and fetching

This commit is contained in:
thesn10
2026-06-26 20:01:36 +02:00
parent 02c26e186f
commit 0c86751d51
7 changed files with 212 additions and 26 deletions

View File

@@ -0,0 +1,8 @@
import { use } from 'react'
import { EscrowDetail } from '@/components/EscrowDetail'
import type { Address } from '@solana/kit'
export default function EscrowPage({ params }: { params: Promise<{ pk: string }> }) {
const { pk } = use(params)
return <EscrowDetail pda={pk as Address} />
}

View File

@@ -0,0 +1,110 @@
'use client'
import { useRouter } from 'next/navigation'
import { isSome } from '@solana/kit'
import { escrowStateLabel } from '@descro/sdk'
import { EscrowState } from '@descro/sdk/src/generated/descro/src/generated/types/escrowState'
import { useEscrow } from '@/hooks/useEscrow'
import { useOrderByEscrow } from '@/hooks/useOrderByEscrow'
import { EscrowStateMachine } from '@/components/EscrowStateMachine'
import { FieldRow, MonoChip } from '@/components/ui/FieldRow'
import { Badge, STATUS_COLORS } from '@/components/ui/Badge'
import { abbrev, fmtSol } from '@/lib/format'
import type { Address } from '@solana/kit'
function escrowStatusKey(state: EscrowState): keyof typeof STATUS_COLORS {
switch (state) {
case EscrowState.AwaitingSellerConfirm: return 'awaitingConfirm'
case EscrowState.Active: return 'active'
case EscrowState.Disputed: return 'disputed'
case EscrowState.Complete: return 'complete'
default: return 'cancelled'
}
}
interface Props {
pda: Address
}
export function EscrowDetail({ pda }: Props) {
const router = useRouter()
const { data: escrow, isLoading, error } = useEscrow(pda)
const { data: order } = useOrderByEscrow(pda)
if (isLoading) return <div style={{ padding: 48, textAlign: 'center', color: 'var(--mut)' }}>Loading</div>
if (error || !escrow) return <div style={{ padding: 48, textAlign: 'center', color: 'var(--mut)' }}>Escrow account not found.</div>
const ed = escrow.data
const stateLabel = escrowStateLabel(ed.state)
const sc = STATUS_COLORS[escrowStatusKey(ed.state)]
const resolverPk = isSome(ed.disputeResolver) ? ed.disputeResolver.value : null
return (
<div>
<button
onClick={() => router.push('/escrows')}
style={{ display: 'inline-flex', alignItems: 'center', gap: 6, color: 'var(--mut)', fontSize: 13, fontWeight: 500, marginBottom: 18 }}
>
Escrows
</button>
<div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16, marginBottom: 24, flexWrap: 'wrap' }}>
<div>
<div style={{ fontSize: 11, fontWeight: 600, letterSpacing: '.14em', color: 'var(--mut)', marginBottom: 5 }}>
ESCROW ACCOUNT · #{String(ed.escrowId)}
</div>
<h1 style={{ margin: 0, fontSize: 26, fontWeight: 700, letterSpacing: '-.02em' }}>{abbrev(pda)}</h1>
</div>
<Badge color={sc.color} bg={sc.bg}>{stateLabel}</Badge>
</div>
<EscrowStateMachine state={ed.state} />
<div style={{ background: 'var(--bg2)', border: '1px solid var(--bd)', borderRadius: 'var(--radius)', padding: '8px 22px 16px' }}>
<div style={{ fontSize: 11, letterSpacing: '.13em', color: 'var(--mut)', fontWeight: 600, padding: '14px 0 4px' }}>
ESCROW ACCOUNT · descro
</div>
<FieldRow label="Address">
<MonoChip value={abbrev(pda)} onCopy={() => navigator.clipboard.writeText(pda)} />
</FieldRow>
<FieldRow label="State">
<Badge color={sc.color} bg={sc.bg}>{stateLabel}</Badge>
</FieldRow>
<FieldRow label="Escrow ID">
<span style={{ fontFamily: 'var(--font-mono)', fontSize: 12.5 }}>#{String(ed.escrowId)}</span>
</FieldRow>
<FieldRow label="Seller">
<MonoChip value={abbrev(ed.seller)} onCopy={() => navigator.clipboard.writeText(ed.seller)} />
</FieldRow>
<FieldRow label="Buyer">
<MonoChip value={abbrev(ed.buyer)} onCopy={() => navigator.clipboard.writeText(ed.buyer)} />
</FieldRow>
<FieldRow label="Amount">
<span style={{ fontSize: 13.5, fontWeight: 600 }}>{fmtSol(ed.amount)}</span>
</FieldRow>
{resolverPk && (
<FieldRow label="Resolver">
<MonoChip
value={abbrev(resolverPk)}
onClick={() => router.push(`/resolver/${resolverPk}`)}
onCopy={() => navigator.clipboard.writeText(resolverPk)}
/>
</FieldRow>
)}
<FieldRow label="Linked Order" last>
{order
? (
<MonoChip
value={abbrev(order.address)}
onClick={() => router.push(`/order/${order.address}`)}
onCopy={() => navigator.clipboard.writeText(order.address)}
/>
)
: <span style={{ fontSize: 13, color: 'var(--mut)' }}></span>
}
</FieldRow>
</div>
</div>
)
}

View File

@@ -137,7 +137,7 @@ export function EscrowsTable() {
return (
<div
key={e.pda}
onClick={() => router.push(`/search?q=${e.pda}`)}
onClick={() => router.push(`/escrow/${e.pda}`)}
style={{
display: 'grid',
gridTemplateColumns: '1.2fr 1.4fr 1.4fr .9fr 1.3fr .9fr',

View File

@@ -0,0 +1,21 @@
import { useQuery } from '@tanstack/react-query'
import { useCluster } from '@solana/connector/react'
import { createSolanaRpc } from '@solana/kit'
import { fetchEscrowAccount } from '@descro/sdk'
import type { Address } from '@solana/kit'
export function useEscrow(pda: Address) {
const { cluster } = useCluster()
return useQuery({
queryKey: ['escrow', pda, cluster?.id],
queryFn: () => {
const rpc = createSolanaRpc(cluster!.url)
return fetchEscrowAccount(
rpc as Parameters<typeof fetchEscrowAccount>[0],
pda,
).catch(() => null)
},
enabled: !!cluster && !!pda,
refetchInterval: 15_000,
})
}

View File

@@ -0,0 +1,18 @@
import { useQuery } from '@tanstack/react-query'
import { useCluster } from '@solana/connector/react'
import { createSolanaRpc } from '@solana/kit'
import { fetchOrderByEscrow } from '@solisting/sdk'
import type { Address } from '@solana/kit'
export function useOrderByEscrow(escrowPda: Address) {
const { cluster } = useCluster()
return useQuery({
queryKey: ['orderByEscrow', escrowPda, cluster?.id],
queryFn: () => {
const rpc = createSolanaRpc(cluster!.url)
return fetchOrderByEscrow(rpc, escrowPda)
},
enabled: !!cluster && !!escrowPda,
refetchInterval: 30_000,
})
}