fix errors
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -24,8 +24,20 @@ export {
|
||||
} from "./generated/descro/src/generated/instructions/complete";
|
||||
export {
|
||||
getCancelInstruction,
|
||||
getCancelInstructionAsync,
|
||||
type CancelInput,
|
||||
type CancelAsyncInput,
|
||||
} from "./generated/descro/src/generated/instructions/cancel";
|
||||
export {
|
||||
getBuyerCreateEscrowInstructionAsync,
|
||||
getBuyerCreateEscrowInstruction,
|
||||
type BuyerCreateEscrowAsyncInput,
|
||||
type BuyerCreateEscrowInput,
|
||||
} from "./generated/descro/src/generated/instructions/buyerCreateEscrow";
|
||||
export {
|
||||
getSellerConfirmInstruction,
|
||||
type SellerConfirmInput,
|
||||
} from "./generated/descro/src/generated/instructions/sellerConfirm";
|
||||
export {
|
||||
getDisputeInstruction,
|
||||
type DisputeInput,
|
||||
@@ -43,4 +55,5 @@ export {
|
||||
type RegisterResolverInput,
|
||||
} from "./generated/descro_ext_resolvers/src/generated/instructions/registerResolver";
|
||||
export { Winner } from "./generated/descro/src/generated/types/winner";
|
||||
export { ResolverType } from "./generated/descro_ext_resolvers/src/generated/types/resolverType";
|
||||
export { ResolverType } from "./generated/descro_ext_resolvers/src/generated/types/resolverType";
|
||||
export { AcceptancePolicy } from "./generated/descro_ext_resolvers/src/generated/types/acceptancePolicy";
|
||||
@@ -32,6 +32,9 @@ export type ResolverEntryWithPda = { pda: Address; account: ResolverEntry };
|
||||
export function isAwaitingDeposit(state: EscrowState): boolean {
|
||||
return state === EscrowState.AwaitingDeposit;
|
||||
}
|
||||
export function isAwaitingSellerConfirm(state: EscrowState): boolean {
|
||||
return state === EscrowState.AwaitingSellerConfirm;
|
||||
}
|
||||
export function isActive(state: EscrowState): boolean {
|
||||
return state === EscrowState.Active;
|
||||
}
|
||||
@@ -48,6 +51,7 @@ export function isCancelled(state: EscrowState): boolean {
|
||||
export function escrowStateLabel(state: EscrowState): string {
|
||||
switch (state) {
|
||||
case EscrowState.AwaitingDeposit: return "AwaitingDeposit";
|
||||
case EscrowState.AwaitingSellerConfirm: return "AwaitingSellerConfirm";
|
||||
case EscrowState.Active: return "Active";
|
||||
case EscrowState.Disputed: return "Disputed";
|
||||
case EscrowState.Complete: return "Complete";
|
||||
|
||||
Reference in New Issue
Block a user