fix errors

This commit is contained in:
thesn10
2026-05-26 21:45:35 +02:00
parent f97792f190
commit 5da7fb2b97
6 changed files with 156 additions and 26 deletions

View File

@@ -14,11 +14,13 @@ import {
getSignatureFromTransaction,
address as kitAddress,
} from '@solana/kit'
import { useKitTransactionSigner, useWallet, useCluster, useSolanaClient } from '@solana/connector/react'
import { getCreateEscrowInstructionAsync } from '@descro/sdk'
import { useKitTransactionSigner, useWallet, useSolanaClient } from '@solana/connector/react'
import { getCreateEscrowInstructionAsync, getBuyerCreateEscrowInstructionAsync, findResolverEntryPda } from '@descro/sdk'
import { lamportsFromSol } from '@/util/lamports'
import type { Address } from '@descro/sdk'
const SYSTEM_PROGRAM_ADDRESS = '11111111111111111111111111111111' as Address
function getNextEscrowId(walletAddress: string): bigint {
const key = `descro_escrow_id_${walletAddress}`
const stored = localStorage.getItem(key)
@@ -28,6 +30,15 @@ function getNextEscrowId(walletAddress: string): bigint {
return BigInt(next)
}
function getNextBuyerEscrowId(buyerAddress: string, sellerAddress: string): bigint {
const key = `descro_buyer_escrow_id_${buyerAddress}_${sellerAddress}`
const stored = localStorage.getItem(key)
const current = stored ? parseInt(stored, 10) : 0
const next = current + 1
localStorage.setItem(key, next.toString())
return BigInt(next)
}
interface CreateEscrowFormProps {
onCreated?: () => void
}
@@ -37,7 +48,8 @@ export function CreateEscrowForm({ onCreated }: CreateEscrowFormProps) {
const { account: address } = useWallet()
const { client } = useSolanaClient()
const [buyer, setBuyer] = useState('')
const [mode, setMode] = useState<'seller' | 'buyer'>('seller')
const [counterparty, setCounterparty] = useState('')
const [amountSol, setAmountSol] = useState('')
const [resolver, setResolver] = useState('')
const [loading, setLoading] = useState(false)
@@ -54,11 +66,11 @@ export function CreateEscrowForm({ onCreated }: CreateEscrowFormProps) {
return
}
let buyerAddress: Address
let counterpartyAddress: Address
try {
buyerAddress = kitAddress(buyer)
counterpartyAddress = kitAddress(counterparty)
} catch {
setError('Invalid buyer public key.')
setError(`Invalid ${mode === 'seller' ? 'buyer' : 'seller'} public key.`)
return
}
@@ -68,7 +80,6 @@ export function CreateEscrowForm({ onCreated }: CreateEscrowFormProps) {
return
}
const amountLamports = lamportsFromSol(amountSolNum)
// Vault is a 0-byte PDA funded entirely by the deposit; must be rent-exempt (~890,880 lamports)
if (amountLamports < 890_880n) {
setError('Amount too small: minimum is ~0.00089 SOL (vault must be rent-exempt).')
return
@@ -87,15 +98,37 @@ export function CreateEscrowForm({ onCreated }: CreateEscrowFormProps) {
setLoading(true)
try {
const { rpc, rpcSubscriptions } = client
const escrowId = getNextEscrowId(address)
const ix = await getCreateEscrowInstructionAsync({
seller: signer,
buyer: buyerAddress,
amount: amountLamports,
disputeResolver: resolverAddress,
escrowId,
})
let ix: unknown
let escrowIdLabel: bigint
if (mode === 'seller') {
const escrowId = getNextEscrowId(address)
escrowIdLabel = escrowId
const resolverAcct = resolverAddress ?? SYSTEM_PROGRAM_ADDRESS
const [resolverEntryAcct] = resolverAddress
? await findResolverEntryPda({ authority: resolverAddress })
: [SYSTEM_PROGRAM_ADDRESS]
ix = await getCreateEscrowInstructionAsync({
seller: signer,
buyer: counterpartyAddress,
resolver: resolverAcct,
resolverEntry: resolverEntryAcct,
amount: amountLamports,
disputeResolver: resolverAddress,
escrowId,
})
} else {
const escrowId = getNextBuyerEscrowId(address, counterpartyAddress)
escrowIdLabel = escrowId
ix = await getBuyerCreateEscrowInstructionAsync({
buyer: signer,
seller: counterpartyAddress,
amount: amountLamports,
disputeResolver: resolverAddress,
escrowId,
})
}
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send()
@@ -108,11 +141,11 @@ export function CreateEscrowForm({ onCreated }: CreateEscrowFormProps) {
const signed = await signTransactionMessageWithSigners(txMsg)
assertIsTransactionWithBlockhashLifetime(signed)
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signed, { commitment: 'confirmed' })
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions: rpcSubscriptions as never })(signed, { commitment: 'confirmed' })
const sig = getSignatureFromTransaction(signed)
setSuccess(`Escrow #${escrowId} created! Tx: ${sig.slice(0, 16)}`)
setBuyer('')
setSuccess(`Escrow #${escrowIdLabel} created! Tx: ${sig.slice(0, 16)}`)
setCounterparty('')
setAmountSol('')
setResolver('')
onCreated?.()
@@ -123,16 +156,46 @@ export function CreateEscrowForm({ onCreated }: CreateEscrowFormProps) {
}
}
const counterpartyLabel = mode === 'seller' ? 'Buyer Public Key' : 'Seller Public Key'
const buttonLabel = mode === 'seller' ? 'Create Escrow' : 'Create & Fund Escrow'
return (
<YStack gap="$3">
<Text fontSize={16} fontWeight="700">Create Escrow</Text>
<XStack gap="$2">
<Button
size="$2"
theme={mode === 'seller' ? 'active' : undefined}
onPress={() => { setMode('seller'); setCounterparty(''); setError(null); setSuccess(null) }}
flex={1}
>
I am Seller
</Button>
<Button
size="$2"
theme={mode === 'buyer' ? 'active' : undefined}
onPress={() => { setMode('buyer'); setCounterparty(''); setError(null); setSuccess(null) }}
flex={1}
>
I am Buyer
</Button>
</XStack>
{mode === 'buyer' && (
<XStack backgroundColor="$purple3" padding="$2" borderRadius="$2">
<Text color="$purple9" fontSize={12}>
Buyer flow: you create and fund the escrow immediately. The seller must confirm before the escrow becomes active.
</Text>
</XStack>
)}
<form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
<YStack gap="$1">
<Text fontSize={13} color="$color11">Buyer Public Key *</Text>
<Text fontSize={13} color="$color11">{counterpartyLabel} *</Text>
<Input
value={buyer}
onChangeText={setBuyer}
value={counterparty}
onChangeText={setCounterparty}
placeholder="Pubkey..."
style={{ fontFamily: 'monospace' }}
fontSize={13}
@@ -178,7 +241,7 @@ export function CreateEscrowForm({ onCreated }: CreateEscrowFormProps) {
disabled={loading || !address}
icon={loading ? <Spinner /> : undefined}
>
{loading ? 'Creating…' : 'Create Escrow'}
{loading ? 'Creating…' : buttonLabel}
</Button>
</form>
</YStack>

View File

@@ -18,13 +18,15 @@ import { useKitTransactionSigner, useWallet, useSolanaClient } from '@solana/con
import { StatusBadge } from './StatusBadge'
import {
isAwaitingDeposit,
isAwaitingSellerConfirm,
isActive,
isDisputed,
getDepositInstructionAsync,
getCancelInstruction,
getCancelInstructionAsync,
getCompleteInstructionAsync,
getDisputeInstruction,
getResolveInstructionAsync,
getSellerConfirmInstruction,
findResolverEntryPda,
DESCRO_EXT_RESOLVERS_PROGRAM_ADDRESS,
Winner,
@@ -93,7 +95,22 @@ export function EscrowDetail({ item, onAction }: EscrowDetailProps) {
runTx(() => getDepositInstructionAsync({ buyer: signer!, escrowAccount: pda }))
const handleCancel = () =>
runTx(async () => getCancelInstruction({ seller: signer!, escrowAccount: pda }))
runTx(() => getCancelInstructionAsync({ canceller: signer!, buyer: escrow.buyer, escrowAccount: pda }))
const handleSellerConfirm = () =>
runTx(async () => {
const SYSTEM_PROGRAM = '11111111111111111111111111111111' as Parameters<typeof getSellerConfirmInstruction>[0]['resolver']
const resolver = resolverAddr ?? SYSTEM_PROGRAM
const [resolverEntryAddr] = resolverAddr
? await findResolverEntryPda({ authority: resolverAddr })
: [SYSTEM_PROGRAM]
return getSellerConfirmInstruction({
seller: signer!,
resolver,
escrowAccount: pda,
resolverEntry: resolverEntryAddr,
})
})
const handleComplete = () =>
runTx(() =>
@@ -152,7 +169,17 @@ export function EscrowDetail({ item, onAction }: EscrowDetailProps) {
Deposit
</Button>
)}
{isAwaitingDeposit(escrow.state) && isSeller && (
{isAwaitingDeposit(escrow.state) && (isSeller || isBuyer) && (
<Button theme="red" onPress={handleCancel} disabled={loading} icon={loading ? <Spinner /> : undefined}>
Cancel
</Button>
)}
{isAwaitingSellerConfirm(escrow.state) && isSeller && (
<Button theme="green" onPress={handleSellerConfirm} disabled={loading} icon={loading ? <Spinner /> : undefined}>
Confirm Escrow
</Button>
)}
{isAwaitingSellerConfirm(escrow.state) && (isSeller || isBuyer) && (
<Button theme="red" onPress={handleCancel} disabled={loading} icon={loading ? <Spinner /> : undefined}>
Cancel
</Button>

View File

@@ -20,6 +20,7 @@ import {
getRegisterResolverInstructionAsync,
resolverTypeLabel,
ResolverType,
AcceptancePolicy,
} from '@descro/sdk'
import type { ResolverEntryWithPda } from '@descro/sdk'
@@ -49,6 +50,13 @@ export function ResolverPanel() {
const [feeRecipient, setFeeRecipient] = useState('')
const [metadataUri, setMetadataUri] = useState('')
const [resolverTypeIdx, setResolverTypeIdx] = useState(0)
const [acceptancePolicyIdx, setAcceptancePolicyIdx] = useState(0)
const ACCEPTANCE_POLICY_OPTIONS: { label: string; value: AcceptancePolicy }[] = [
{ label: 'Open (anyone can assign you)', value: AcceptancePolicy.Open },
{ label: 'SignatureGated (you must co-sign)', value: AcceptancePolicy.SignatureGated },
{ label: 'ProgramGated', value: AcceptancePolicy.ProgramGated },
]
const [formLoading, setFormLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
@@ -110,6 +118,7 @@ export function ResolverPanel() {
const ix = await getRegisterResolverInstructionAsync({
authority: signer,
resolverType,
acceptancePolicy: ACCEPTANCE_POLICY_OPTIONS[acceptancePolicyIdx].value,
name: name.trim(),
description: description.trim(),
feeBps: fee,
@@ -217,6 +226,19 @@ export function ResolverPanel() {
</select>
</YStack>
<YStack gap="$1">
<Text fontSize={13} color="$color11">Acceptance Policy</Text>
<select
value={acceptancePolicyIdx}
onChange={(e) => setAcceptancePolicyIdx(parseInt(e.target.value, 10))}
style={{ padding: '8px', borderRadius: 6, border: '1px solid #ccc', fontSize: 13 }}
>
{ACCEPTANCE_POLICY_OPTIONS.map((opt, i) => (
<option key={i} value={i}>{opt.label}</option>
))}
</select>
</YStack>
<YStack gap="$1">
<Text fontSize={13} color="$color11">Fee (BPS)</Text>
<Input value={feeBps} onChangeText={setFeeBps} placeholder="0" keyboardType="numeric" fontSize={13} />

View File

@@ -7,6 +7,7 @@ import type { EscrowState } from '@descro/sdk'
const STATE_COLORS: Record<string, string> = {
AwaitingDeposit: '$blue9',
AwaitingSellerConfirm: '$purple9',
Active: '$green9',
Disputed: '$orange9',
Complete: '$gray9',