fix errors
This commit is contained in:
@@ -14,11 +14,13 @@ import {
|
|||||||
getSignatureFromTransaction,
|
getSignatureFromTransaction,
|
||||||
address as kitAddress,
|
address as kitAddress,
|
||||||
} from '@solana/kit'
|
} from '@solana/kit'
|
||||||
import { useKitTransactionSigner, useWallet, useCluster, useSolanaClient } from '@solana/connector/react'
|
import { useKitTransactionSigner, useWallet, useSolanaClient } from '@solana/connector/react'
|
||||||
import { getCreateEscrowInstructionAsync } from '@descro/sdk'
|
import { getCreateEscrowInstructionAsync, getBuyerCreateEscrowInstructionAsync, findResolverEntryPda } from '@descro/sdk'
|
||||||
import { lamportsFromSol } from '@/util/lamports'
|
import { lamportsFromSol } from '@/util/lamports'
|
||||||
import type { Address } from '@descro/sdk'
|
import type { Address } from '@descro/sdk'
|
||||||
|
|
||||||
|
const SYSTEM_PROGRAM_ADDRESS = '11111111111111111111111111111111' as Address
|
||||||
|
|
||||||
function getNextEscrowId(walletAddress: string): bigint {
|
function getNextEscrowId(walletAddress: string): bigint {
|
||||||
const key = `descro_escrow_id_${walletAddress}`
|
const key = `descro_escrow_id_${walletAddress}`
|
||||||
const stored = localStorage.getItem(key)
|
const stored = localStorage.getItem(key)
|
||||||
@@ -28,6 +30,15 @@ function getNextEscrowId(walletAddress: string): bigint {
|
|||||||
return BigInt(next)
|
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 {
|
interface CreateEscrowFormProps {
|
||||||
onCreated?: () => void
|
onCreated?: () => void
|
||||||
}
|
}
|
||||||
@@ -37,7 +48,8 @@ export function CreateEscrowForm({ onCreated }: CreateEscrowFormProps) {
|
|||||||
const { account: address } = useWallet()
|
const { account: address } = useWallet()
|
||||||
const { client } = useSolanaClient()
|
const { client } = useSolanaClient()
|
||||||
|
|
||||||
const [buyer, setBuyer] = useState('')
|
const [mode, setMode] = useState<'seller' | 'buyer'>('seller')
|
||||||
|
const [counterparty, setCounterparty] = useState('')
|
||||||
const [amountSol, setAmountSol] = useState('')
|
const [amountSol, setAmountSol] = useState('')
|
||||||
const [resolver, setResolver] = useState('')
|
const [resolver, setResolver] = useState('')
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
@@ -54,11 +66,11 @@ export function CreateEscrowForm({ onCreated }: CreateEscrowFormProps) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let buyerAddress: Address
|
let counterpartyAddress: Address
|
||||||
try {
|
try {
|
||||||
buyerAddress = kitAddress(buyer)
|
counterpartyAddress = kitAddress(counterparty)
|
||||||
} catch {
|
} catch {
|
||||||
setError('Invalid buyer public key.')
|
setError(`Invalid ${mode === 'seller' ? 'buyer' : 'seller'} public key.`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +80,6 @@ export function CreateEscrowForm({ onCreated }: CreateEscrowFormProps) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const amountLamports = lamportsFromSol(amountSolNum)
|
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) {
|
if (amountLamports < 890_880n) {
|
||||||
setError('Amount too small: minimum is ~0.00089 SOL (vault must be rent-exempt).')
|
setError('Amount too small: minimum is ~0.00089 SOL (vault must be rent-exempt).')
|
||||||
return
|
return
|
||||||
@@ -87,15 +98,37 @@ export function CreateEscrowForm({ onCreated }: CreateEscrowFormProps) {
|
|||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
const { rpc, rpcSubscriptions } = client
|
const { rpc, rpcSubscriptions } = client
|
||||||
const escrowId = getNextEscrowId(address)
|
|
||||||
|
|
||||||
const ix = await getCreateEscrowInstructionAsync({
|
let ix: unknown
|
||||||
seller: signer,
|
let escrowIdLabel: bigint
|
||||||
buyer: buyerAddress,
|
|
||||||
amount: amountLamports,
|
if (mode === 'seller') {
|
||||||
disputeResolver: resolverAddress,
|
const escrowId = getNextEscrowId(address)
|
||||||
escrowId,
|
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()
|
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send()
|
||||||
|
|
||||||
@@ -108,11 +141,11 @@ export function CreateEscrowForm({ onCreated }: CreateEscrowFormProps) {
|
|||||||
|
|
||||||
const signed = await signTransactionMessageWithSigners(txMsg)
|
const signed = await signTransactionMessageWithSigners(txMsg)
|
||||||
assertIsTransactionWithBlockhashLifetime(signed)
|
assertIsTransactionWithBlockhashLifetime(signed)
|
||||||
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signed, { commitment: 'confirmed' })
|
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions: rpcSubscriptions as never })(signed, { commitment: 'confirmed' })
|
||||||
const sig = getSignatureFromTransaction(signed)
|
const sig = getSignatureFromTransaction(signed)
|
||||||
|
|
||||||
setSuccess(`Escrow #${escrowId} created! Tx: ${sig.slice(0, 16)}…`)
|
setSuccess(`Escrow #${escrowIdLabel} created! Tx: ${sig.slice(0, 16)}…`)
|
||||||
setBuyer('')
|
setCounterparty('')
|
||||||
setAmountSol('')
|
setAmountSol('')
|
||||||
setResolver('')
|
setResolver('')
|
||||||
onCreated?.()
|
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 (
|
return (
|
||||||
<YStack gap="$3">
|
<YStack gap="$3">
|
||||||
<Text fontSize={16} fontWeight="700">Create Escrow</Text>
|
<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 }}>
|
<form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||||
<YStack gap="$1">
|
<YStack gap="$1">
|
||||||
<Text fontSize={13} color="$color11">Buyer Public Key *</Text>
|
<Text fontSize={13} color="$color11">{counterpartyLabel} *</Text>
|
||||||
<Input
|
<Input
|
||||||
value={buyer}
|
value={counterparty}
|
||||||
onChangeText={setBuyer}
|
onChangeText={setCounterparty}
|
||||||
placeholder="Pubkey..."
|
placeholder="Pubkey..."
|
||||||
style={{ fontFamily: 'monospace' }}
|
style={{ fontFamily: 'monospace' }}
|
||||||
fontSize={13}
|
fontSize={13}
|
||||||
@@ -178,7 +241,7 @@ export function CreateEscrowForm({ onCreated }: CreateEscrowFormProps) {
|
|||||||
disabled={loading || !address}
|
disabled={loading || !address}
|
||||||
icon={loading ? <Spinner /> : undefined}
|
icon={loading ? <Spinner /> : undefined}
|
||||||
>
|
>
|
||||||
{loading ? 'Creating…' : 'Create Escrow'}
|
{loading ? 'Creating…' : buttonLabel}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</YStack>
|
</YStack>
|
||||||
|
|||||||
@@ -18,13 +18,15 @@ import { useKitTransactionSigner, useWallet, useSolanaClient } from '@solana/con
|
|||||||
import { StatusBadge } from './StatusBadge'
|
import { StatusBadge } from './StatusBadge'
|
||||||
import {
|
import {
|
||||||
isAwaitingDeposit,
|
isAwaitingDeposit,
|
||||||
|
isAwaitingSellerConfirm,
|
||||||
isActive,
|
isActive,
|
||||||
isDisputed,
|
isDisputed,
|
||||||
getDepositInstructionAsync,
|
getDepositInstructionAsync,
|
||||||
getCancelInstruction,
|
getCancelInstructionAsync,
|
||||||
getCompleteInstructionAsync,
|
getCompleteInstructionAsync,
|
||||||
getDisputeInstruction,
|
getDisputeInstruction,
|
||||||
getResolveInstructionAsync,
|
getResolveInstructionAsync,
|
||||||
|
getSellerConfirmInstruction,
|
||||||
findResolverEntryPda,
|
findResolverEntryPda,
|
||||||
DESCRO_EXT_RESOLVERS_PROGRAM_ADDRESS,
|
DESCRO_EXT_RESOLVERS_PROGRAM_ADDRESS,
|
||||||
Winner,
|
Winner,
|
||||||
@@ -93,7 +95,22 @@ export function EscrowDetail({ item, onAction }: EscrowDetailProps) {
|
|||||||
runTx(() => getDepositInstructionAsync({ buyer: signer!, escrowAccount: pda }))
|
runTx(() => getDepositInstructionAsync({ buyer: signer!, escrowAccount: pda }))
|
||||||
|
|
||||||
const handleCancel = () =>
|
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 = () =>
|
const handleComplete = () =>
|
||||||
runTx(() =>
|
runTx(() =>
|
||||||
@@ -152,7 +169,17 @@ export function EscrowDetail({ item, onAction }: EscrowDetailProps) {
|
|||||||
Deposit
|
Deposit
|
||||||
</Button>
|
</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}>
|
<Button theme="red" onPress={handleCancel} disabled={loading} icon={loading ? <Spinner /> : undefined}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
getRegisterResolverInstructionAsync,
|
getRegisterResolverInstructionAsync,
|
||||||
resolverTypeLabel,
|
resolverTypeLabel,
|
||||||
ResolverType,
|
ResolverType,
|
||||||
|
AcceptancePolicy,
|
||||||
} from '@descro/sdk'
|
} from '@descro/sdk'
|
||||||
import type { ResolverEntryWithPda } from '@descro/sdk'
|
import type { ResolverEntryWithPda } from '@descro/sdk'
|
||||||
|
|
||||||
@@ -49,6 +50,13 @@ export function ResolverPanel() {
|
|||||||
const [feeRecipient, setFeeRecipient] = useState('')
|
const [feeRecipient, setFeeRecipient] = useState('')
|
||||||
const [metadataUri, setMetadataUri] = useState('')
|
const [metadataUri, setMetadataUri] = useState('')
|
||||||
const [resolverTypeIdx, setResolverTypeIdx] = useState(0)
|
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 [formLoading, setFormLoading] = useState(false)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
@@ -110,6 +118,7 @@ export function ResolverPanel() {
|
|||||||
const ix = await getRegisterResolverInstructionAsync({
|
const ix = await getRegisterResolverInstructionAsync({
|
||||||
authority: signer,
|
authority: signer,
|
||||||
resolverType,
|
resolverType,
|
||||||
|
acceptancePolicy: ACCEPTANCE_POLICY_OPTIONS[acceptancePolicyIdx].value,
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
description: description.trim(),
|
description: description.trim(),
|
||||||
feeBps: fee,
|
feeBps: fee,
|
||||||
@@ -217,6 +226,19 @@ export function ResolverPanel() {
|
|||||||
</select>
|
</select>
|
||||||
</YStack>
|
</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">
|
<YStack gap="$1">
|
||||||
<Text fontSize={13} color="$color11">Fee (BPS)</Text>
|
<Text fontSize={13} color="$color11">Fee (BPS)</Text>
|
||||||
<Input value={feeBps} onChangeText={setFeeBps} placeholder="0" keyboardType="numeric" fontSize={13} />
|
<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> = {
|
const STATE_COLORS: Record<string, string> = {
|
||||||
AwaitingDeposit: '$blue9',
|
AwaitingDeposit: '$blue9',
|
||||||
|
AwaitingSellerConfirm: '$purple9',
|
||||||
Active: '$green9',
|
Active: '$green9',
|
||||||
Disputed: '$orange9',
|
Disputed: '$orange9',
|
||||||
Complete: '$gray9',
|
Complete: '$gray9',
|
||||||
|
|||||||
@@ -24,8 +24,20 @@ export {
|
|||||||
} from "./generated/descro/src/generated/instructions/complete";
|
} from "./generated/descro/src/generated/instructions/complete";
|
||||||
export {
|
export {
|
||||||
getCancelInstruction,
|
getCancelInstruction,
|
||||||
|
getCancelInstructionAsync,
|
||||||
type CancelInput,
|
type CancelInput,
|
||||||
|
type CancelAsyncInput,
|
||||||
} from "./generated/descro/src/generated/instructions/cancel";
|
} 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 {
|
export {
|
||||||
getDisputeInstruction,
|
getDisputeInstruction,
|
||||||
type DisputeInput,
|
type DisputeInput,
|
||||||
@@ -43,4 +55,5 @@ export {
|
|||||||
type RegisterResolverInput,
|
type RegisterResolverInput,
|
||||||
} from "./generated/descro_ext_resolvers/src/generated/instructions/registerResolver";
|
} from "./generated/descro_ext_resolvers/src/generated/instructions/registerResolver";
|
||||||
export { Winner } from "./generated/descro/src/generated/types/winner";
|
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 {
|
export function isAwaitingDeposit(state: EscrowState): boolean {
|
||||||
return state === EscrowState.AwaitingDeposit;
|
return state === EscrowState.AwaitingDeposit;
|
||||||
}
|
}
|
||||||
|
export function isAwaitingSellerConfirm(state: EscrowState): boolean {
|
||||||
|
return state === EscrowState.AwaitingSellerConfirm;
|
||||||
|
}
|
||||||
export function isActive(state: EscrowState): boolean {
|
export function isActive(state: EscrowState): boolean {
|
||||||
return state === EscrowState.Active;
|
return state === EscrowState.Active;
|
||||||
}
|
}
|
||||||
@@ -48,6 +51,7 @@ export function isCancelled(state: EscrowState): boolean {
|
|||||||
export function escrowStateLabel(state: EscrowState): string {
|
export function escrowStateLabel(state: EscrowState): string {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case EscrowState.AwaitingDeposit: return "AwaitingDeposit";
|
case EscrowState.AwaitingDeposit: return "AwaitingDeposit";
|
||||||
|
case EscrowState.AwaitingSellerConfirm: return "AwaitingSellerConfirm";
|
||||||
case EscrowState.Active: return "Active";
|
case EscrowState.Active: return "Active";
|
||||||
case EscrowState.Disputed: return "Disputed";
|
case EscrowState.Disputed: return "Disputed";
|
||||||
case EscrowState.Complete: return "Complete";
|
case EscrowState.Complete: return "Complete";
|
||||||
|
|||||||
Reference in New Issue
Block a user