From 9b7391677cba9a99108006294bceb270e50b5624 Mon Sep 17 00:00:00 2001
From: thesn10 <38666407+thesn10@users.noreply.github.com>
Date: Sat, 23 May 2026 14:45:49 +0200
Subject: [PATCH] update app to use solana kit
---
app/.gitignore | 1 +
app/package.json | 3 +-
app/src/app/page.tsx | 4 +-
app/src/components/CreateEscrowForm.tsx | 101 ++++++++++--------
app/src/components/EscrowCard.tsx | 13 +--
app/src/components/EscrowDetail.tsx | 133 +++++++++++++-----------
app/src/components/ResolverPanel.tsx | 110 +++++++++++---------
app/src/hooks/useEscrowDetail.ts | 37 +++----
app/src/hooks/useEscrows.ts | 31 ++----
app/tsconfig.json | 2 +-
sdk/src/index.ts | 43 +++++++-
sdk/src/registry.ts | 50 ++++++++-
yarn.lock | 48 +--------
13 files changed, 320 insertions(+), 256 deletions(-)
diff --git a/app/.gitignore b/app/.gitignore
index 33600ed..dc1f287 100644
--- a/app/.gitignore
+++ b/app/.gitignore
@@ -2,3 +2,4 @@
node_modules/
public/tamagui.generated.css
.env*.local
+tsconfig.tsbuildinfo
\ No newline at end of file
diff --git a/app/package.json b/app/package.json
index 1c7f159..16eb775 100644
--- a/app/package.json
+++ b/app/package.json
@@ -7,10 +7,9 @@
"start": "next start"
},
"dependencies": {
- "@coral-xyz/anchor": "^0.32.1",
"@descro/sdk": "workspace:*",
"@solana/connector": "^0.2.4",
- "@solana/web3.js": "^1.98.4",
+ "@solana/kit": "^6.0.0",
"@tamagui/config": "^2.0.0-rc.42",
"@tamagui/next-theme": "^2.0.0-rc.42",
"next": "^16.2.6",
diff --git a/app/src/app/page.tsx b/app/src/app/page.tsx
index b0a7070..c32f434 100644
--- a/app/src/app/page.tsx
+++ b/app/src/app/page.tsx
@@ -124,9 +124,9 @@ export default function PlaygroundPage() {
{escrows.map((item) => (
setSelected(item)}
/>
))}
diff --git a/app/src/components/CreateEscrowForm.tsx b/app/src/components/CreateEscrowForm.tsx
index a40f5ea..90b6ba9 100644
--- a/app/src/components/CreateEscrowForm.tsx
+++ b/app/src/components/CreateEscrowForm.tsx
@@ -1,21 +1,32 @@
'use client'
-import { useState, useMemo } from 'react'
+import { useState } from 'react'
import { YStack, XStack, Text, Button, Input, Spinner } from 'tamagui'
-import { PublicKey, Transaction, Connection } from '@solana/web3.js'
-import { AnchorProvider } from '@coral-xyz/anchor'
-import { useTransactionSigner, useDisconnectWallet, useCluster } from '@solana/connector/react'
-import { useWalletAdapterCompat } from '@solana/connector/compat'
-import BN from 'bn.js'
-import { EscrowClient } from '@descro/sdk'
+import {
+ createSolanaRpc,
+ createSolanaRpcSubscriptions,
+ pipe,
+ createTransactionMessage,
+ setTransactionMessageFeePayerSigner,
+ setTransactionMessageLifetimeUsingBlockhash,
+ appendTransactionMessageInstructions,
+ signTransactionMessageWithSigners,
+ sendAndConfirmTransactionFactory,
+ assertIsTransactionWithBlockhashLifetime,
+ getSignatureFromTransaction,
+ address as kitAddress,
+} from '@solana/kit'
+import { useKitTransactionSigner, useWallet, useCluster } from '@solana/connector/react'
+import { getCreateEscrowInstructionAsync } from '@descro/sdk'
+import type { Address } from '@descro/sdk'
-function getNextEscrowId(walletPubkey: string): BN {
- const key = `descro_escrow_id_${walletPubkey}`
+function getNextEscrowId(walletAddress: string): bigint {
+ const key = `descro_escrow_id_${walletAddress}`
const stored = localStorage.getItem(key)
const current = stored ? parseInt(stored, 10) : 0
const next = current + 1
localStorage.setItem(key, next.toString())
- return new BN(next)
+ return BigInt(next)
}
interface CreateEscrowFormProps {
@@ -23,17 +34,10 @@ interface CreateEscrowFormProps {
}
export function CreateEscrowForm({ onCreated }: CreateEscrowFormProps) {
- const { signer } = useTransactionSigner()
- const { disconnect } = useDisconnectWallet()
+ const { signer } = useKitTransactionSigner()
+ const { account: address } = useWallet()
const { cluster } = useCluster()
- const walletAdapter = useWalletAdapterCompat(signer, disconnect)
-
- const connection = useMemo(
- () => (cluster?.url ? new Connection(cluster.url, 'confirmed') : null),
- [cluster?.url],
- )
-
- const publicKey = walletAdapter.publicKey ? new PublicKey(walletAdapter.publicKey.toString()) : null
+ const rpcUrl = cluster?.url ?? null
const [buyer, setBuyer] = useState('')
const [amountSol, setAmountSol] = useState('')
@@ -47,29 +51,30 @@ export function CreateEscrowForm({ onCreated }: CreateEscrowFormProps) {
setError(null)
setSuccess(null)
- if (!publicKey || !connection) {
+ if (!signer || !address || !rpcUrl) {
setError('Connect your wallet first.')
return
}
- let buyerPk: PublicKey
+ let buyerAddress: Address
try {
- buyerPk = new PublicKey(buyer)
+ buyerAddress = kitAddress(buyer)
} catch {
setError('Invalid buyer public key.')
return
}
- const amountLamports = parseFloat(amountSol)
- if (isNaN(amountLamports) || amountLamports <= 0) {
+ const amountSolNum = parseFloat(amountSol)
+ if (isNaN(amountSolNum) || amountSolNum <= 0) {
setError('Invalid SOL amount.')
return
}
+ const amountLamports = BigInt(Math.round(amountSolNum * 1e9))
- let resolverPk: PublicKey | null = null
+ let resolverAddress: Address | null = null
if (resolver.trim()) {
try {
- resolverPk = new PublicKey(resolver.trim())
+ resolverAddress = kitAddress(resolver.trim())
} catch {
setError('Invalid resolver public key.')
return
@@ -78,29 +83,33 @@ export function CreateEscrowForm({ onCreated }: CreateEscrowFormProps) {
setLoading(true)
try {
- const provider = new AnchorProvider(
- connection,
- { publicKey, signTransaction: walletAdapter.signTransaction!.bind(walletAdapter) } as never,
- {},
- )
- const client = new EscrowClient(provider)
- const escrowId = getNextEscrowId(publicKey.toBase58())
- const amount = new BN(Math.round(amountLamports * 1e9))
+ const rpc = createSolanaRpc(rpcUrl)
+ const rpcSubscriptions = createSolanaRpcSubscriptions(rpcUrl.replace('http', 'ws'))
+ const escrowId = getNextEscrowId(address)
- const ix = await client.buildCreateEscrow({
- seller: publicKey,
- buyer: buyerPk,
- amount,
- disputeResolver: resolverPk,
+ const ix = await getCreateEscrowInstructionAsync({
+ seller: signer,
+ buyer: buyerAddress,
+ amount: amountLamports,
+ disputeResolver: resolverAddress,
escrowId,
})
- const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash()
- const tx = new Transaction({ blockhash, lastValidBlockHeight, feePayer: publicKey }).add(ix)
- const sig = await walletAdapter.sendTransaction(tx, connection)
- await connection.confirmTransaction({ signature: sig, blockhash, lastValidBlockHeight }, 'confirmed')
+ const { value: latestBlockhash } = await rpc.getLatestBlockhash().send()
- setSuccess(`Escrow #${escrowId.toString()} created! Tx: ${sig.slice(0, 16)}…`)
+ const txMsg = pipe(
+ createTransactionMessage({ version: 0 }),
+ (tx) => setTransactionMessageFeePayerSigner(signer, tx),
+ (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
+ (tx) => appendTransactionMessageInstructions([ix as never], tx),
+ )
+
+ const signed = await signTransactionMessageWithSigners(txMsg)
+ assertIsTransactionWithBlockhashLifetime(signed)
+ await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signed, { commitment: 'confirmed' })
+ const sig = getSignatureFromTransaction(signed)
+
+ setSuccess(`Escrow #${escrowId} created! Tx: ${sig.slice(0, 16)}…`)
setBuyer('')
setAmountSol('')
setResolver('')
@@ -164,7 +173,7 @@ export function CreateEscrowForm({ onCreated }: CreateEscrowFormProps) {
)
diff --git a/app/src/components/EscrowDetail.tsx b/app/src/components/EscrowDetail.tsx
index b091ad0..91fd9c7 100644
--- a/app/src/components/EscrowDetail.tsx
+++ b/app/src/components/EscrowDetail.tsx
@@ -1,13 +1,36 @@
'use client'
-import { useState, useMemo } from 'react'
+import { useState } from 'react'
import { YStack, XStack, Text, Button, Spinner, Separator } from 'tamagui'
-import { PublicKey, Transaction, Connection } from '@solana/web3.js'
-import { AnchorProvider } from '@coral-xyz/anchor'
-import { useTransactionSigner, useDisconnectWallet, useCluster } from '@solana/connector/react'
-import { useWalletAdapterCompat } from '@solana/connector/compat'
+import {
+ createSolanaRpc,
+ createSolanaRpcSubscriptions,
+ pipe,
+ createTransactionMessage,
+ setTransactionMessageFeePayerSigner,
+ setTransactionMessageLifetimeUsingBlockhash,
+ appendTransactionMessageInstructions,
+ signTransactionMessageWithSigners,
+ sendAndConfirmTransactionFactory,
+ assertIsTransactionWithBlockhashLifetime,
+ getSignatureFromTransaction,
+ isSome,
+} from '@solana/kit'
+import { useKitTransactionSigner, useWallet, useCluster } from '@solana/connector/react'
import { StatusBadge } from './StatusBadge'
-import { EscrowClient, isAwaitingDeposit, isActive, isDisputed } from '@descro/sdk'
+import {
+ isAwaitingDeposit,
+ isActive,
+ isDisputed,
+ getDepositInstructionAsync,
+ getCancelInstruction,
+ getCompleteInstructionAsync,
+ getDisputeInstruction,
+ getResolveInstructionAsync,
+ findResolverEntryPda,
+ DESCRO_EXT_RESOLVERS_PROGRAM_ADDRESS,
+ Winner,
+} from '@descro/sdk'
import type { EscrowAccountWithPda } from '@descro/sdk'
import { useEscrowDetail } from '@/hooks/useEscrowDetail'
@@ -16,41 +39,28 @@ interface EscrowDetailProps {
onAction?: () => void
}
-function fullPubkey(pk: PublicKey): string {
- return pk.toBase58()
-}
-
export function EscrowDetail({ item, onAction }: EscrowDetailProps) {
const { pda } = item
const { account, loading: detailLoading } = useEscrowDetail(pda)
const escrow = account ?? item.account
- const { signer } = useTransactionSigner()
- const { disconnect } = useDisconnectWallet()
+ const { signer } = useKitTransactionSigner()
+ const { account: address } = useWallet()
const { cluster } = useCluster()
- const walletAdapter = useWalletAdapterCompat(signer, disconnect)
-
- const connection = useMemo(
- () => (cluster?.url ? new Connection(cluster.url, 'confirmed') : null),
- [cluster?.url],
- )
-
- const publicKey = walletAdapter.publicKey ? new PublicKey(walletAdapter.publicKey.toString()) : null
+ const rpcUrl = cluster?.url ?? null
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const [success, setSuccess] = useState(null)
- const solAmount = (Number(escrow.amount.toString()) / 1e9).toFixed(4)
- const isSeller = publicKey?.equals(escrow.seller) ?? false
- const isBuyer = publicKey?.equals(escrow.buyer) ?? false
- const isResolver =
- escrow.disputeResolver != null && publicKey?.equals(escrow.disputeResolver) === true
+ const solAmount = (Number(escrow.amount) / 1e9).toFixed(4)
+ const isSeller = address != null && address === escrow.seller
+ const isBuyer = address != null && address === escrow.buyer
+ const resolverAddr = isSome(escrow.disputeResolver) ? escrow.disputeResolver.value : null
+ const isResolver = address != null && resolverAddr != null && address === resolverAddr
- async function runTx(
- buildFn: (client: EscrowClient) => Promise,
- ) {
- if (!publicKey || !connection || !walletAdapter.signTransaction) {
+ async function runTx(buildIx: () => Promise) {
+ if (!signer || !address || !rpcUrl) {
setError('Connect your wallet first.')
return
}
@@ -58,17 +68,20 @@ export function EscrowDetail({ item, onAction }: EscrowDetailProps) {
setError(null)
setSuccess(null)
try {
- const provider = new AnchorProvider(
- connection,
- { publicKey, signTransaction: walletAdapter.signTransaction.bind(walletAdapter) } as never,
- {},
+ const rpc = createSolanaRpc(rpcUrl)
+ const rpcSubscriptions = createSolanaRpcSubscriptions(rpcUrl.replace('http', 'ws'))
+ const ix = await buildIx()
+ const { value: latestBlockhash } = await rpc.getLatestBlockhash().send()
+ const txMsg = pipe(
+ createTransactionMessage({ version: 0 }),
+ (tx) => setTransactionMessageFeePayerSigner(signer, tx),
+ (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
+ (tx) => appendTransactionMessageInstructions([ix as never], tx),
)
- const client = new EscrowClient(provider)
- const ix = await buildFn(client)
- const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash()
- const tx = new Transaction({ blockhash, lastValidBlockHeight, feePayer: publicKey }).add(ix)
- const sig = await walletAdapter.sendTransaction(tx, connection)
- await connection.confirmTransaction({ signature: sig, blockhash, lastValidBlockHeight }, 'confirmed')
+ const signed = await signTransactionMessageWithSigners(txMsg)
+ assertIsTransactionWithBlockhashLifetime(signed)
+ await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signed, { commitment: 'confirmed' })
+ const sig = getSignatureFromTransaction(signed)
setSuccess(`Done! Tx: ${sig.slice(0, 20)}…`)
onAction?.()
} catch (err: unknown) {
@@ -79,31 +92,33 @@ export function EscrowDetail({ item, onAction }: EscrowDetailProps) {
}
const handleDeposit = () =>
- runTx((client) => client.buildDeposit({ buyer: publicKey!, escrowPda: pda }))
+ runTx(() => getDepositInstructionAsync({ buyer: signer!, escrowAccount: pda }))
const handleCancel = () =>
- runTx((client) => client.buildCancel({ seller: publicKey!, escrowPda: pda }))
+ runTx(async () => getCancelInstruction({ seller: signer!, escrowAccount: pda }))
const handleComplete = () =>
- runTx((client) =>
- client.buildComplete({ buyer: publicKey!, seller: escrow.seller, escrowPda: pda }),
+ runTx(() =>
+ getCompleteInstructionAsync({ buyer: signer!, seller: escrow.seller, escrowAccount: pda }),
)
const handleDispute = () =>
- runTx((client) => client.buildDispute({ initiator: publicKey!, escrowPda: pda }))
+ runTx(async () => getDisputeInstruction({ initiator: signer!, escrowAccount: pda }))
const handleResolve = (winner: 'buyer' | 'seller') => {
- const winnerPubkey = winner === 'buyer' ? escrow.buyer : escrow.seller
- runTx((client) =>
- client.buildResolve({
- resolver: publicKey!,
- winner,
- winnerPubkey,
+ if (!resolverAddr) return
+ runTx(async () => {
+ const [resolverEntryAddr] = await findResolverEntryPda({ authority: resolverAddr })
+ return getResolveInstructionAsync({
+ resolver: signer!,
+ winner: winner === 'buyer' ? escrow.buyer : escrow.seller,
seller: escrow.seller,
- escrowPda: pda,
- disputeResolver: escrow.disputeResolver,
- }),
- )
+ escrowAccount: pda,
+ resolverEntry: resolverEntryAddr,
+ registryProgram: DESCRO_EXT_RESOLVERS_PROGRAM_ADDRESS,
+ winnerArg: winner === 'buyer' ? Winner.Buyer : Winner.Seller,
+ })
+ })
}
void detailLoading
@@ -121,14 +136,14 @@ export function EscrowDetail({ item, onAction }: EscrowDetailProps) {
-
-
+
+
-
+
diff --git a/app/src/components/ResolverPanel.tsx b/app/src/components/ResolverPanel.tsx
index bc30a12..c85c24b 100644
--- a/app/src/components/ResolverPanel.tsx
+++ b/app/src/components/ResolverPanel.tsx
@@ -1,39 +1,47 @@
'use client'
-import React, { useState, useEffect, useMemo } from 'react'
+import React, { useState, useEffect } from 'react'
import { YStack, XStack, Text, Button, Input, Spinner, Separator } from 'tamagui'
-import { PublicKey, Transaction, Connection } from '@solana/web3.js'
-import { AnchorProvider } from '@coral-xyz/anchor'
-import { useTransactionSigner, useDisconnectWallet, useCluster } from '@solana/connector/react'
-import { useWalletAdapterCompat } from '@solana/connector/compat'
-import { RegistryClient, resolverTypeLabel } from '@descro/sdk'
-import type { ResolverEntryWithPda, ResolverType } from '@descro/sdk'
+import {
+ createSolanaRpc,
+ createSolanaRpcSubscriptions,
+ pipe,
+ createTransactionMessage,
+ setTransactionMessageFeePayerSigner,
+ setTransactionMessageLifetimeUsingBlockhash,
+ appendTransactionMessageInstructions,
+ signTransactionMessageWithSigners,
+ sendAndConfirmTransactionFactory,
+ assertIsTransactionWithBlockhashLifetime,
+ getSignatureFromTransaction,
+ address as kitAddress,
+} from '@solana/kit'
+import { useKitTransactionSigner, useWallet, useCluster } from '@solana/connector/react'
+import {
+ fetchAllResolvers,
+ getRegisterResolverInstructionAsync,
+ resolverTypeLabel,
+ ResolverType,
+} from '@descro/sdk'
+import type { ResolverEntryWithPda } from '@descro/sdk'
const RESOLVER_TYPE_OPTIONS: { label: string; value: ResolverType }[] = [
- { label: 'CentralAuthority', value: { centralAuthority: {} } },
- { label: 'JuryDAO', value: { juryDAO: {} } },
- { label: 'MAD', value: { mad: {} } },
- { label: 'Algorithmic', value: { algorithmic: {} } },
- { label: 'Multisig', value: { multisig: {} } },
+ { label: 'CentralAuthority', value: ResolverType.CentralAuthority },
+ { label: 'JuryDAO', value: ResolverType.JuryDAO },
+ { label: 'MAD', value: ResolverType.MAD },
+ { label: 'Algorithmic', value: ResolverType.Algorithmic },
+ { label: 'Multisig', value: ResolverType.Multisig },
]
-function truncPk(pk: PublicKey): string {
- const s = pk.toBase58()
- return `${s.slice(0, 4)}…${s.slice(-4)}`
+function truncAddr(addr: string): string {
+ return `${addr.slice(0, 4)}…${addr.slice(-4)}`
}
export function ResolverPanel() {
- const { signer } = useTransactionSigner()
- const { disconnect } = useDisconnectWallet()
+ const { signer } = useKitTransactionSigner()
+ const { account: address } = useWallet()
const { cluster } = useCluster()
- const walletAdapter = useWalletAdapterCompat(signer, disconnect)
-
- const connection = useMemo(
- () => (cluster?.url ? new Connection(cluster.url, 'confirmed') : null),
- [cluster?.url],
- )
-
- const publicKey = walletAdapter.publicKey ? new PublicKey(walletAdapter.publicKey.toString()) : null
+ const rpcUrl = cluster?.url ?? null
const [resolvers, setResolvers] = useState([])
const [resolversLoading, setResolversLoading] = useState(false)
@@ -50,12 +58,11 @@ export function ResolverPanel() {
const [success, setSuccess] = useState(null)
async function loadResolvers() {
- if (!connection) return
+ if (!rpcUrl) return
setResolversLoading(true)
try {
- const provider = new AnchorProvider(connection, {} as never, {})
- const client = new RegistryClient(provider)
- const all = await client.fetchAllResolvers()
+ const rpc = createSolanaRpc(rpcUrl)
+ const all = await fetchAllResolvers(rpc)
setResolvers(all)
} catch (err) {
console.error('Failed to load resolvers', err)
@@ -67,22 +74,22 @@ export function ResolverPanel() {
useEffect(() => {
loadResolvers()
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [connection])
+ }, [rpcUrl])
async function handleRegister(e: React.FormEvent) {
e.preventDefault()
setError(null)
setSuccess(null)
- if (!publicKey || !connection || !walletAdapter.signTransaction) {
+ if (!signer || !address || !rpcUrl) {
setError('Connect your wallet first.')
return
}
- let recipientPk: PublicKey
- const recipientStr = feeRecipient.trim() || publicKey.toBase58()
+ let recipientAddr
+ const recipientStr = feeRecipient.trim() || address
try {
- recipientPk = new PublicKey(recipientStr)
+ recipientAddr = kitAddress(recipientStr)
} catch {
setError('Invalid fee recipient public key.')
return
@@ -101,28 +108,31 @@ export function ResolverPanel() {
setFormLoading(true)
try {
- const provider = new AnchorProvider(
- connection,
- { publicKey, signTransaction: walletAdapter.signTransaction.bind(walletAdapter) } as never,
- {},
- )
- const client = new RegistryClient(provider)
+ const rpc = createSolanaRpc(rpcUrl)
+ const rpcSubscriptions = createSolanaRpcSubscriptions(rpcUrl.replace('http', 'ws'))
const resolverType = RESOLVER_TYPE_OPTIONS[resolverTypeIdx].value
- const ix = await client.buildRegisterResolver({
- authority: publicKey,
+ const ix = await getRegisterResolverInstructionAsync({
+ authority: signer,
resolverType,
name: name.trim(),
description: description.trim(),
feeBps: fee,
- feeRecipient: recipientPk,
+ feeRecipient: recipientAddr,
metadataUri: metadataUri.trim(),
})
- const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash()
- const tx = new Transaction({ blockhash, lastValidBlockHeight, feePayer: publicKey }).add(ix)
- const sig = await walletAdapter.sendTransaction(tx, connection)
- await connection.confirmTransaction({ signature: sig, blockhash, lastValidBlockHeight }, 'confirmed')
+ const { value: latestBlockhash } = await rpc.getLatestBlockhash().send()
+ const txMsg = pipe(
+ createTransactionMessage({ version: 0 }),
+ (tx) => setTransactionMessageFeePayerSigner(signer, tx),
+ (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
+ (tx) => appendTransactionMessageInstructions([ix as never], tx),
+ )
+ const signed = await signTransactionMessageWithSigners(txMsg)
+ assertIsTransactionWithBlockhashLifetime(signed)
+ await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signed, { commitment: 'confirmed' })
+ const sig = getSignatureFromTransaction(signed)
setSuccess(`Registered! Tx: ${sig.slice(0, 20)}…`)
setName('')
@@ -156,7 +166,7 @@ export function ResolverPanel() {
{resolvers.map(({ pda, account }) => (
S: {account.ruledForSeller.toString()}
- {truncPk(account.authority)}
+ {truncAddr(account.authority)}
))}
@@ -241,7 +251,7 @@ export function ResolverPanel() {