From 5da7fb2b971ffb064d5ff7a516a84cce11b72d0f Mon Sep 17 00:00:00 2001
From: thesn10 <38666407+thesn10@users.noreply.github.com>
Date: Tue, 26 May 2026 21:45:35 +0200
Subject: [PATCH] fix errors
---
app/src/components/CreateEscrowForm.tsx | 107 +++++++++++++++++++-----
app/src/components/EscrowDetail.tsx | 33 +++++++-
app/src/components/ResolverPanel.tsx | 22 +++++
app/src/components/StatusBadge.tsx | 1 +
sdk/src/index.ts | 15 +++-
sdk/src/types.ts | 4 +
6 files changed, 156 insertions(+), 26 deletions(-)
diff --git a/app/src/components/CreateEscrowForm.tsx b/app/src/components/CreateEscrowForm.tsx
index 7edcac2..cfc20d8 100644
--- a/app/src/components/CreateEscrowForm.tsx
+++ b/app/src/components/CreateEscrowForm.tsx
@@ -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 (
Create Escrow
+
+
+
+
+
+ {mode === 'buyer' && (
+
+
+ Buyer flow: you create and fund the escrow immediately. The seller must confirm before the escrow becomes active.
+
+
+ )}
+
diff --git a/app/src/components/EscrowDetail.tsx b/app/src/components/EscrowDetail.tsx
index d73b773..59c7a99 100644
--- a/app/src/components/EscrowDetail.tsx
+++ b/app/src/components/EscrowDetail.tsx
@@ -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[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
)}
- {isAwaitingDeposit(escrow.state) && isSeller && (
+ {isAwaitingDeposit(escrow.state) && (isSeller || isBuyer) && (
+ : undefined}>
+ Cancel
+
+ )}
+ {isAwaitingSellerConfirm(escrow.state) && isSeller && (
+ : undefined}>
+ Confirm Escrow
+
+ )}
+ {isAwaitingSellerConfirm(escrow.state) && (isSeller || isBuyer) && (
: undefined}>
Cancel
diff --git a/app/src/components/ResolverPanel.tsx b/app/src/components/ResolverPanel.tsx
index b0a5958..485664d 100644
--- a/app/src/components/ResolverPanel.tsx
+++ b/app/src/components/ResolverPanel.tsx
@@ -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(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() {
+
+ Acceptance Policy
+
+
+
Fee (BPS)
diff --git a/app/src/components/StatusBadge.tsx b/app/src/components/StatusBadge.tsx
index b66cea9..de031c4 100644
--- a/app/src/components/StatusBadge.tsx
+++ b/app/src/components/StatusBadge.tsx
@@ -7,6 +7,7 @@ import type { EscrowState } from '@descro/sdk'
const STATE_COLORS: Record = {
AwaitingDeposit: '$blue9',
+ AwaitingSellerConfirm: '$purple9',
Active: '$green9',
Disputed: '$orange9',
Complete: '$gray9',
diff --git a/sdk/src/index.ts b/sdk/src/index.ts
index 7036b72..a3b1157 100644
--- a/sdk/src/index.ts
+++ b/sdk/src/index.ts
@@ -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";
\ No newline at end of file
+export { ResolverType } from "./generated/descro_ext_resolvers/src/generated/types/resolverType";
+export { AcceptancePolicy } from "./generated/descro_ext_resolvers/src/generated/types/acceptancePolicy";
\ No newline at end of file
diff --git a/sdk/src/types.ts b/sdk/src/types.ts
index ae363f0..3029e2b 100644
--- a/sdk/src/types.ts
+++ b/sdk/src/types.ts
@@ -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";