use solana connector

This commit is contained in:
thesn10
2026-05-23 01:51:34 +02:00
parent b56bada0fa
commit b4cea41a93
14 changed files with 915 additions and 5466 deletions

5
.gitignore vendored
View File

@@ -9,4 +9,7 @@ test-ledger
.vscode .vscode
spec.md spec.md
app_old app_old
connectorkit
create-solana-dapp-template

View File

@@ -4,7 +4,6 @@ const nextConfig: NextConfig = {
transpilePackages: [ transpilePackages: [
'@tamagui/lucide-icons-2', '@tamagui/lucide-icons-2',
'@descro/sdk', '@descro/sdk',
'@solana/wallet-adapter-react-ui',
], ],
turbopack: { turbopack: {
resolveAlias: { resolveAlias: {

View File

@@ -9,10 +9,7 @@
"dependencies": { "dependencies": {
"@coral-xyz/anchor": "^0.32.1", "@coral-xyz/anchor": "^0.32.1",
"@descro/sdk": "workspace:*", "@descro/sdk": "workspace:*",
"@solana/wallet-adapter-base": "^0.9.27", "@solana/connector": "^0.2.4",
"@solana/wallet-adapter-react": "^0.15.39",
"@solana/wallet-adapter-react-ui": "^0.9.39",
"@solana/wallet-adapter-wallets": "^0.19.38",
"@solana/web3.js": "^1.98.4", "@solana/web3.js": "^1.98.4",
"@tamagui/config": "^2.0.0-rc.42", "@tamagui/config": "^2.0.0-rc.42",
"@tamagui/next-theme": "^2.0.0-rc.42", "@tamagui/next-theme": "^2.0.0-rc.42",

View File

@@ -1,54 +1,37 @@
'use client' 'use client'
import React, { useCallback, useMemo, useState } from 'react' import type { ReactNode } from 'react'
import { ConnectionProvider, WalletProvider } from '@solana/wallet-adapter-react' import { useMemo } from 'react'
import { WalletAdapterNetwork, type WalletError } from '@solana/wallet-adapter-base' import { AppProvider } from '@solana/connector/react'
import { WalletModalProvider } from '@solana/wallet-adapter-react-ui' import { getDefaultConfig } from '@solana/connector/headless'
import { clusterApiUrl } from '@solana/web3.js'
import { UnsafeBurnerWalletAdapter } from '@solana/wallet-adapter-wallets'
export type Network = 'devnet' | 'localnet' function getOrigin() {
if (typeof window !== 'undefined') return window.location.origin
interface NetworkContextValue { return 'http://localhost:3000'
network: WalletAdapterNetwork
setNetwork: (n: WalletAdapterNetwork) => void
} }
export const NetworkContext = React.createContext<NetworkContextValue>({ export function WalletProviders({ children }: { children: ReactNode }) {
network: WalletAdapterNetwork.Devnet, const connectorConfig = useMemo(
setNetwork: () => undefined,
})
export function WalletProviders({ children }: { children: React.ReactNode }) {
const [network, setNetwork] = useState<WalletAdapterNetwork>(WalletAdapterNetwork.Devnet)
const endpoint = useMemo(
() => () =>
(network as string) === 'localnet' getDefaultConfig({
? 'http://localhost:8899' appName: 'Descro',
: clusterApiUrl(network), appUrl: getOrigin(),
[network], autoConnect: true,
); clusters: [
{
const wallets = useMemo( id: 'solana:devnet' as const,
() => network as string === 'localnet' ? [new UnsafeBurnerWalletAdapter()] : [], label: 'Devnet',
// eslint-disable-next-line react-hooks/exhaustive-deps url: 'https://api.devnet.solana.com',
[network] },
); {
id: 'solana:localnet' as const,
const onError = useCallback((error: WalletError) => { label: 'Localnet',
console.warn('[wallet]', error.name, error.message, error.error) url: 'http://localhost:8899',
}, []) },
],
return ( }),
<NetworkContext.Provider value={{ network, setNetwork }}> [],
<ConnectionProvider endpoint={endpoint}>
<WalletProvider wallets={wallets} autoConnect onError={onError}>
<WalletModalProvider>
{children}
</WalletModalProvider>
</WalletProvider>
</ConnectionProvider>
</NetworkContext.Provider>
) )
return <AppProvider connectorConfig={connectorConfig}>{children}</AppProvider>
} }

View File

@@ -1,5 +1,4 @@
import '../../public/tamagui.generated.css' import '../../public/tamagui.generated.css'
import '@solana/wallet-adapter-react-ui/styles.css'
import { Metadata } from 'next' import { Metadata } from 'next'
import { NextTamaguiProvider } from './NextTamaguiProvider' import { NextTamaguiProvider } from './NextTamaguiProvider'
import { WalletProviders } from './WalletProviders' import { WalletProviders } from './WalletProviders'

View File

@@ -1,6 +1,6 @@
'use client' 'use client'
import { useContext, useState } from 'react' import { useState } from 'react'
import { YStack, XStack, Text, Button, Spinner, Separator } from 'tamagui' import { YStack, XStack, Text, Button, Spinner, Separator } from 'tamagui'
import { WalletButton } from '@/components/WalletButton' import { WalletButton } from '@/components/WalletButton'
import { CreateEscrowForm } from '@/components/CreateEscrowForm' import { CreateEscrowForm } from '@/components/CreateEscrowForm'
@@ -8,14 +8,12 @@ import { EscrowCard } from '@/components/EscrowCard'
import { EscrowDetail } from '@/components/EscrowDetail' import { EscrowDetail } from '@/components/EscrowDetail'
import { ResolverPanel } from '@/components/ResolverPanel' import { ResolverPanel } from '@/components/ResolverPanel'
import { useEscrows } from '@/hooks/useEscrows' import { useEscrows } from '@/hooks/useEscrows'
import { NetworkContext } from './WalletProviders' import { useWallet, useCluster } from '@solana/connector/react'
import type { Network } from './WalletProviders'
import type { EscrowAccountWithPda } from '@descro/sdk' import type { EscrowAccountWithPda } from '@descro/sdk'
import { useWallet } from '@solana/wallet-adapter-react'
export default function PlaygroundPage() { export default function PlaygroundPage() {
const { network, setNetwork } = useContext(NetworkContext) const { cluster, clusters, setCluster, isDevnet } = useCluster()
const { publicKey } = useWallet() const { account } = useWallet()
const { escrows, loading, error, refresh } = useEscrows() const { escrows, loading, error, refresh } = useEscrows()
const [selected, setSelected] = useState<EscrowAccountWithPda | null>(null) const [selected, setSelected] = useState<EscrowAccountWithPda | null>(null)
const [showResolvers, setShowResolvers] = useState(false) const [showResolvers, setShowResolvers] = useState(false)
@@ -43,8 +41,8 @@ export default function PlaygroundPage() {
Network: Network:
</Text> </Text>
<select <select
value={network} value={cluster?.id ?? ''}
onChange={(e) => setNetwork(e.target.value as Network)} onChange={(e) => setCluster(e.target.value as never)}
style={{ style={{
padding: '6px 10px', padding: '6px 10px',
borderRadius: 6, borderRadius: 6,
@@ -53,12 +51,15 @@ export default function PlaygroundPage() {
cursor: 'pointer', cursor: 'pointer',
}} }}
> >
<option value="devnet">Devnet</option> {clusters.map((c) => (
<option value="localnet">Localnet</option> <option key={c.id} value={c.id}>
{c.label}
</option>
))}
</select> </select>
</XStack> </XStack>
{network === 'devnet' && ( {isDevnet && (
<a <a
href="https://faucet.solana.com/" href="https://faucet.solana.com/"
target="_blank" target="_blank"
@@ -100,7 +101,7 @@ export default function PlaygroundPage() {
</Button> </Button>
</XStack> </XStack>
{!publicKey && ( {!account && (
<Text color="$color10" fontSize={13}> <Text color="$color10" fontSize={13}>
Connect your wallet to see escrows. Connect your wallet to see escrows.
</Text> </Text>
@@ -114,7 +115,7 @@ export default function PlaygroundPage() {
</XStack> </XStack>
)} )}
{publicKey && !loading && escrows.length === 0 && ( {account && !loading && escrows.length === 0 && (
<Text color="$color10" fontSize={13}> <Text color="$color10" fontSize={13}>
No escrows found for your wallet. No escrows found for your wallet.
</Text> </Text>

View File

@@ -1,10 +1,11 @@
'use client' 'use client'
import React, { useState } from 'react' import { useState, useMemo } from 'react'
import { YStack, XStack, Text, Button, Input, Spinner } from 'tamagui' import { YStack, XStack, Text, Button, Input, Spinner } from 'tamagui'
import { useWallet, useConnection } from '@solana/wallet-adapter-react' import { PublicKey, Transaction, Connection } from '@solana/web3.js'
import { PublicKey, Transaction } from '@solana/web3.js'
import { AnchorProvider } from '@coral-xyz/anchor' 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 BN from 'bn.js'
import { EscrowClient } from '@descro/sdk' import { EscrowClient } from '@descro/sdk'
@@ -22,8 +23,17 @@ interface CreateEscrowFormProps {
} }
export function CreateEscrowForm({ onCreated }: CreateEscrowFormProps) { export function CreateEscrowForm({ onCreated }: CreateEscrowFormProps) {
const { publicKey, sendTransaction, signTransaction } = useWallet() const { signer } = useTransactionSigner()
const { connection } = useConnection() const { disconnect } = useDisconnectWallet()
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 [buyer, setBuyer] = useState('') const [buyer, setBuyer] = useState('')
const [amountSol, setAmountSol] = useState('') const [amountSol, setAmountSol] = useState('')
@@ -37,7 +47,7 @@ export function CreateEscrowForm({ onCreated }: CreateEscrowFormProps) {
setError(null) setError(null)
setSuccess(null) setSuccess(null)
if (!publicKey) { if (!publicKey || !connection) {
setError('Connect your wallet first.') setError('Connect your wallet first.')
return return
} }
@@ -70,8 +80,8 @@ export function CreateEscrowForm({ onCreated }: CreateEscrowFormProps) {
try { try {
const provider = new AnchorProvider( const provider = new AnchorProvider(
connection, connection,
{ publicKey, signTransaction: signTransaction! } as any, { publicKey, signTransaction: walletAdapter.signTransaction!.bind(walletAdapter) } as never,
{} {},
) )
const client = new EscrowClient(provider) const client = new EscrowClient(provider)
const escrowId = getNextEscrowId(publicKey.toBase58()) const escrowId = getNextEscrowId(publicKey.toBase58())
@@ -85,9 +95,10 @@ export function CreateEscrowForm({ onCreated }: CreateEscrowFormProps) {
escrowId, escrowId,
}) })
const tx = new Transaction().add(ix) const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash()
const sig = await sendTransaction(tx, connection) const tx = new Transaction({ blockhash, lastValidBlockHeight, feePayer: publicKey }).add(ix)
await connection.confirmTransaction(sig, 'confirmed') const sig = await walletAdapter.sendTransaction(tx, connection)
await connection.confirmTransaction({ signature: sig, blockhash, lastValidBlockHeight }, 'confirmed')
setSuccess(`Escrow #${escrowId.toString()} created! Tx: ${sig.slice(0, 16)}`) setSuccess(`Escrow #${escrowId.toString()} created! Tx: ${sig.slice(0, 16)}`)
setBuyer('') setBuyer('')
@@ -112,7 +123,7 @@ export function CreateEscrowForm({ onCreated }: CreateEscrowFormProps) {
value={buyer} value={buyer}
onChangeText={setBuyer} onChangeText={setBuyer}
placeholder="Pubkey..." placeholder="Pubkey..."
fontFamily="monospace" style={{ fontFamily: 'monospace' }}
fontSize={13} fontSize={13}
/> />
</YStack> </YStack>
@@ -134,7 +145,7 @@ export function CreateEscrowForm({ onCreated }: CreateEscrowFormProps) {
value={resolver} value={resolver}
onChangeText={setResolver} onChangeText={setResolver}
placeholder="Pubkey..." placeholder="Pubkey..."
fontFamily="monospace" style={{ fontFamily: 'monospace' }}
fontSize={13} fontSize={13}
/> />
</YStack> </YStack>

View File

@@ -1,17 +1,13 @@
'use client' 'use client'
import React, { useState } from 'react' import { useState, useMemo } from 'react'
import { YStack, XStack, Text, Button, Spinner, Separator } from 'tamagui' import { YStack, XStack, Text, Button, Spinner, Separator } from 'tamagui'
import { useWallet, useConnection } from '@solana/wallet-adapter-react' import { PublicKey, Transaction, Connection } from '@solana/web3.js'
import { PublicKey, Transaction } from '@solana/web3.js'
import { AnchorProvider } from '@coral-xyz/anchor' import { AnchorProvider } from '@coral-xyz/anchor'
import { useTransactionSigner, useDisconnectWallet, useCluster } from '@solana/connector/react'
import { useWalletAdapterCompat } from '@solana/connector/compat'
import { StatusBadge } from './StatusBadge' import { StatusBadge } from './StatusBadge'
import { import { EscrowClient, isAwaitingDeposit, isActive, isDisputed } from '@descro/sdk'
EscrowClient,
isAwaitingDeposit,
isActive,
isDisputed,
} from '@descro/sdk'
import type { EscrowAccountWithPda } from '@descro/sdk' import type { EscrowAccountWithPda } from '@descro/sdk'
import { useEscrowDetail } from '@/hooks/useEscrowDetail' import { useEscrowDetail } from '@/hooks/useEscrowDetail'
@@ -29,8 +25,18 @@ export function EscrowDetail({ item, onAction }: EscrowDetailProps) {
const { account, loading: detailLoading } = useEscrowDetail(pda) const { account, loading: detailLoading } = useEscrowDetail(pda)
const escrow = account ?? item.account const escrow = account ?? item.account
const { publicKey, sendTransaction, signTransaction } = useWallet() const { signer } = useTransactionSigner()
const { connection } = useConnection() const { disconnect } = useDisconnectWallet()
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 [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const [success, setSuccess] = useState<string | null>(null) const [success, setSuccess] = useState<string | null>(null)
@@ -39,13 +45,12 @@ export function EscrowDetail({ item, onAction }: EscrowDetailProps) {
const isSeller = publicKey?.equals(escrow.seller) ?? false const isSeller = publicKey?.equals(escrow.seller) ?? false
const isBuyer = publicKey?.equals(escrow.buyer) ?? false const isBuyer = publicKey?.equals(escrow.buyer) ?? false
const isResolver = const isResolver =
escrow.disputeResolver != null && escrow.disputeResolver != null && publicKey?.equals(escrow.disputeResolver) === true
publicKey?.equals(escrow.disputeResolver) === true
async function runTx( async function runTx(
buildFn: (client: EscrowClient) => Promise<import('@solana/web3.js').TransactionInstruction> buildFn: (client: EscrowClient) => Promise<import('@solana/web3.js').TransactionInstruction>,
) { ) {
if (!publicKey || !signTransaction) { if (!publicKey || !connection || !walletAdapter.signTransaction) {
setError('Connect your wallet first.') setError('Connect your wallet first.')
return return
} }
@@ -55,14 +60,15 @@ export function EscrowDetail({ item, onAction }: EscrowDetailProps) {
try { try {
const provider = new AnchorProvider( const provider = new AnchorProvider(
connection, connection,
{ publicKey, signTransaction } as any, { publicKey, signTransaction: walletAdapter.signTransaction.bind(walletAdapter) } as never,
{} {},
) )
const client = new EscrowClient(provider) const client = new EscrowClient(provider)
const ix = await buildFn(client) const ix = await buildFn(client)
const tx = new Transaction().add(ix) const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash()
const sig = await sendTransaction(tx, connection) const tx = new Transaction({ blockhash, lastValidBlockHeight, feePayer: publicKey }).add(ix)
await connection.confirmTransaction(sig, 'confirmed') const sig = await walletAdapter.sendTransaction(tx, connection)
await connection.confirmTransaction({ signature: sig, blockhash, lastValidBlockHeight }, 'confirmed')
setSuccess(`Done! Tx: ${sig.slice(0, 20)}`) setSuccess(`Done! Tx: ${sig.slice(0, 20)}`)
onAction?.() onAction?.()
} catch (err: unknown) { } catch (err: unknown) {
@@ -72,25 +78,21 @@ export function EscrowDetail({ item, onAction }: EscrowDetailProps) {
} }
} }
function handleDeposit() { const handleDeposit = () =>
runTx((client) => client.buildDeposit({ buyer: publicKey!, escrowPda: pda })) runTx((client) => client.buildDeposit({ buyer: publicKey!, escrowPda: pda }))
}
function handleCancel() { const handleCancel = () =>
runTx((client) => client.buildCancel({ seller: publicKey!, escrowPda: pda })) runTx((client) => client.buildCancel({ seller: publicKey!, escrowPda: pda }))
}
function handleComplete() { const handleComplete = () =>
runTx((client) => runTx((client) =>
client.buildComplete({ buyer: publicKey!, seller: escrow.seller, escrowPda: pda }) client.buildComplete({ buyer: publicKey!, seller: escrow.seller, escrowPda: pda }),
) )
}
function handleDispute() { const handleDispute = () =>
runTx((client) => client.buildDispute({ initiator: publicKey!, escrowPda: pda })) runTx((client) => client.buildDispute({ initiator: publicKey!, escrowPda: pda }))
}
function handleResolve(winner: 'buyer' | 'seller') { const handleResolve = (winner: 'buyer' | 'seller') => {
const winnerPubkey = winner === 'buyer' ? escrow.buyer : escrow.seller const winnerPubkey = winner === 'buyer' ? escrow.buyer : escrow.seller
runTx((client) => runTx((client) =>
client.buildResolve({ client.buildResolve({
@@ -100,18 +102,14 @@ export function EscrowDetail({ item, onAction }: EscrowDetailProps) {
seller: escrow.seller, seller: escrow.seller,
escrowPda: pda, escrowPda: pda,
disputeResolver: escrow.disputeResolver, disputeResolver: escrow.disputeResolver,
}) }),
) )
} }
void detailLoading
return ( return (
<YStack <YStack padding="$4" borderRadius="$4" borderWidth={1} borderColor="$borderColor" gap="$3">
padding="$4"
borderRadius="$4"
borderWidth={1}
borderColor="$borderColor"
gap="$3"
>
<XStack justifyContent="space-between" alignItems="center"> <XStack justifyContent="space-between" alignItems="center">
<Text fontSize={18} fontWeight="700"> <Text fontSize={18} fontWeight="700">
Escrow #{escrow.escrowId.toString()} Escrow #{escrow.escrowId.toString()}

View File

@@ -1,10 +1,11 @@
'use client' 'use client'
import React, { useState, useEffect } from 'react' import React, { useState, useEffect, useMemo } from 'react'
import { YStack, XStack, Text, Button, Input, Spinner, Separator } from 'tamagui' import { YStack, XStack, Text, Button, Input, Spinner, Separator } from 'tamagui'
import { useWallet, useConnection } from '@solana/wallet-adapter-react' import { PublicKey, Transaction, Connection } from '@solana/web3.js'
import { PublicKey, Transaction } from '@solana/web3.js'
import { AnchorProvider } from '@coral-xyz/anchor' 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 { RegistryClient, resolverTypeLabel } from '@descro/sdk'
import type { ResolverEntryWithPda, ResolverType } from '@descro/sdk' import type { ResolverEntryWithPda, ResolverType } from '@descro/sdk'
@@ -22,8 +23,17 @@ function truncPk(pk: PublicKey): string {
} }
export function ResolverPanel() { export function ResolverPanel() {
const { publicKey, sendTransaction, signTransaction } = useWallet() const { signer } = useTransactionSigner()
const { connection } = useConnection() const { disconnect } = useDisconnectWallet()
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 [resolvers, setResolvers] = useState<ResolverEntryWithPda[]>([]) const [resolvers, setResolvers] = useState<ResolverEntryWithPda[]>([])
const [resolversLoading, setResolversLoading] = useState(false) const [resolversLoading, setResolversLoading] = useState(false)
@@ -40,9 +50,10 @@ export function ResolverPanel() {
const [success, setSuccess] = useState<string | null>(null) const [success, setSuccess] = useState<string | null>(null)
async function loadResolvers() { async function loadResolvers() {
if (!connection) return
setResolversLoading(true) setResolversLoading(true)
try { try {
const provider = new AnchorProvider(connection, {} as any, {}) const provider = new AnchorProvider(connection, {} as never, {})
const client = new RegistryClient(provider) const client = new RegistryClient(provider)
const all = await client.fetchAllResolvers() const all = await client.fetchAllResolvers()
setResolvers(all) setResolvers(all)
@@ -63,7 +74,7 @@ export function ResolverPanel() {
setError(null) setError(null)
setSuccess(null) setSuccess(null)
if (!publicKey || !signTransaction) { if (!publicKey || !connection || !walletAdapter.signTransaction) {
setError('Connect your wallet first.') setError('Connect your wallet first.')
return return
} }
@@ -92,8 +103,8 @@ export function ResolverPanel() {
try { try {
const provider = new AnchorProvider( const provider = new AnchorProvider(
connection, connection,
{ publicKey, signTransaction } as any, { publicKey, signTransaction: walletAdapter.signTransaction.bind(walletAdapter) } as never,
{} {},
) )
const client = new RegistryClient(provider) const client = new RegistryClient(provider)
const resolverType = RESOLVER_TYPE_OPTIONS[resolverTypeIdx].value const resolverType = RESOLVER_TYPE_OPTIONS[resolverTypeIdx].value
@@ -108,9 +119,10 @@ export function ResolverPanel() {
metadataUri: metadataUri.trim(), metadataUri: metadataUri.trim(),
}) })
const tx = new Transaction().add(ix) const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash()
const sig = await sendTransaction(tx, connection) const tx = new Transaction({ blockhash, lastValidBlockHeight, feePayer: publicKey }).add(ix)
await connection.confirmTransaction(sig, 'confirmed') const sig = await walletAdapter.sendTransaction(tx, connection)
await connection.confirmTransaction({ signature: sig, blockhash, lastValidBlockHeight }, 'confirmed')
setSuccess(`Registered! Tx: ${sig.slice(0, 20)}`) setSuccess(`Registered! Tx: ${sig.slice(0, 20)}`)
setName('') setName('')
@@ -164,7 +176,7 @@ export function ResolverPanel() {
<Text fontSize={12} color="$green9">B: {account.ruledForBuyer.toString()}</Text> <Text fontSize={12} color="$green9">B: {account.ruledForBuyer.toString()}</Text>
<Text fontSize={12} color="$orange9">S: {account.ruledForSeller.toString()}</Text> <Text fontSize={12} color="$orange9">S: {account.ruledForSeller.toString()}</Text>
</XStack> </XStack>
<Text fontSize={11} fontFamily="monospace" color="$color10"> <Text fontSize={11} color="$color10" style={{ fontFamily: 'monospace' }}>
{truncPk(account.authority)} {truncPk(account.authority)}
</Text> </Text>
</YStack> </YStack>
@@ -207,7 +219,7 @@ export function ResolverPanel() {
<YStack gap="$1"> <YStack gap="$1">
<Text fontSize={13} color="$color11">Fee Recipient (default: your wallet)</Text> <Text fontSize={13} color="$color11">Fee Recipient (default: your wallet)</Text>
<Input value={feeRecipient} onChangeText={setFeeRecipient} placeholder="Pubkey or leave blank" fontFamily="monospace" fontSize={13} /> <Input value={feeRecipient} onChangeText={setFeeRecipient} placeholder="Pubkey or leave blank" fontSize={13} />
</YStack> </YStack>
<YStack gap="$1"> <YStack gap="$1">

View File

@@ -1,22 +1,79 @@
'use client' 'use client'
import { useState, useEffect } from 'react' import { useState } from 'react'
import { WalletMultiButton } from '@solana/wallet-adapter-react-ui' import { useConnector, useConnectWallet, useDisconnectWallet, useWalletConnectors } from '@solana/connector/react'
export function WalletButton() { export function WalletButton() {
const [mounted, setMounted] = useState(false) const { isConnected, isConnecting, account, connector } = useConnector()
useEffect(() => setMounted(true), []) const { connect } = useConnectWallet()
if (!mounted) return null const { disconnect } = useDisconnectWallet()
const connectors = useWalletConnectors()
const [showList, setShowList] = useState(false)
const baseStyle: React.CSSProperties = {
backgroundColor: '#512da8',
color: 'white',
border: 'none',
borderRadius: 8,
fontSize: 14,
fontWeight: 600,
padding: '8px 16px',
cursor: 'pointer',
}
if (isConnected && account) {
const short = `${account.slice(0, 4)}${account.slice(-4)}`
return (
<button style={{ ...baseStyle, backgroundColor: '#2e7d32' }} onClick={() => disconnect()}>
{connector?.icon && (
<img src={connector.icon} width={16} height={16} alt="" style={{ marginRight: 6, verticalAlign: 'middle', borderRadius: 4 }} />
)}
{short} · Disconnect
</button>
)
}
if (showList) {
return (
<div style={{ position: 'relative' }}>
<div style={{
position: 'absolute', top: 0, right: 0, zIndex: 100,
backgroundColor: 'white', border: '1px solid #ddd', borderRadius: 8,
padding: 8, minWidth: 200, boxShadow: '0 4px 16px rgba(0,0,0,0.15)',
}}>
{connectors.length === 0 && (
<div style={{ fontSize: 13, color: '#666', padding: '8px 12px' }}>No wallets detected</div>
)}
{connectors.map((w) => (
<button
key={w.id}
onClick={() => { connect(w.id); setShowList(false) }}
style={{
display: 'flex', alignItems: 'center', gap: 8, width: '100%',
padding: '8px 12px', border: 'none', background: 'none',
cursor: 'pointer', fontSize: 13, borderRadius: 6,
}}
onMouseEnter={(e) => { (e.currentTarget as HTMLButtonElement).style.backgroundColor = '#f5f5f5' }}
onMouseLeave={(e) => { (e.currentTarget as HTMLButtonElement).style.backgroundColor = 'transparent' }}
>
{w.icon && <img src={w.icon} width={20} height={20} alt="" style={{ borderRadius: 4 }} />}
{w.name}
</button>
))}
<button
onClick={() => setShowList(false)}
style={{ width: '100%', padding: '4px', border: 'none', background: 'none', cursor: 'pointer', fontSize: 12, color: '#999', marginTop: 4 }}
>
Cancel
</button>
</div>
</div>
)
}
return ( return (
<WalletMultiButton <button style={baseStyle} onClick={() => setShowList(true)} disabled={isConnecting}>
style={{ {isConnecting ? 'Connecting…' : 'Connect Wallet'}
backgroundColor: '#512da8', </button>
borderRadius: 8,
fontSize: 14,
fontWeight: 600,
padding: '8px 16px',
}}
/>
) )
} }

View File

@@ -1,30 +1,35 @@
'use client' 'use client'
import { useState, useEffect, useRef } from 'react' import { useState, useEffect, useRef, useMemo } from 'react'
import { PublicKey } from '@solana/web3.js' import { PublicKey, Connection } from '@solana/web3.js'
import { useConnection } from '@solana/wallet-adapter-react' import { useCluster } from '@solana/connector/react'
import { AnchorProvider } from '@coral-xyz/anchor' import { AnchorProvider } from '@coral-xyz/anchor'
import { EscrowClient, subscribeEscrow } from '@descro/sdk' import { EscrowClient, subscribeEscrow } from '@descro/sdk'
import type { EscrowAccount } from '@descro/sdk' import type { EscrowAccount } from '@descro/sdk'
export function useEscrowDetail(pda: PublicKey | null) { export function useEscrowDetail(pda: PublicKey | null) {
const { connection } = useConnection() const { cluster } = useCluster()
const connection = useMemo(
() => (cluster?.url ? new Connection(cluster.url) : null),
[cluster?.url],
)
const [account, setAccount] = useState<EscrowAccount | null>(null) const [account, setAccount] = useState<EscrowAccount | null>(null)
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const unsubRef = useRef<(() => void) | null>(null) const unsubRef = useRef<(() => void) | null>(null)
useEffect(() => { useEffect(() => {
if (!pda) { if (!pda || !connection) {
setAccount(null) setAccount(null)
return return
} }
let cancelled = false let cancelled = false
setLoading(true) setLoading(true)
const provider = new AnchorProvider(connection, {} as any, {}) const provider = new AnchorProvider(connection, {} as never, {})
const client = new EscrowClient(provider) const client = new EscrowClient(provider)
client client
.fetchEscrow(pda) .fetchEscrow(pda)
.then((acc) => { .then((acc) => {

View File

@@ -1,7 +1,8 @@
'use client' 'use client'
import { useState, useEffect, useCallback, useRef } from 'react' import { useState, useEffect, useCallback, useRef, useMemo } from 'react'
import { useWallet, useConnection } from '@solana/wallet-adapter-react' import { Connection, PublicKey } from '@solana/web3.js'
import { useTransactionSigner, useCluster } from '@solana/connector/react'
import { AnchorProvider } from '@coral-xyz/anchor' import { AnchorProvider } from '@coral-xyz/anchor'
import { EscrowClient } from '@descro/sdk' import { EscrowClient } from '@descro/sdk'
import type { EscrowAccountWithPda } from '@descro/sdk' import type { EscrowAccountWithPda } from '@descro/sdk'
@@ -9,24 +10,31 @@ import type { EscrowAccountWithPda } from '@descro/sdk'
const POLL_INTERVAL_MS = 5000 const POLL_INTERVAL_MS = 5000
export function useEscrows() { export function useEscrows() {
const { publicKey } = useWallet() const { address } = useTransactionSigner()
const { connection } = useConnection() const { cluster } = useCluster()
const publicKey = useMemo(() => (address ? new PublicKey(address) : null), [address])
const connection = useMemo(
() => (cluster?.url ? new Connection(cluster.url) : null),
[cluster?.url],
)
const [escrows, setEscrows] = useState<EscrowAccountWithPda[]>([]) const [escrows, setEscrows] = useState<EscrowAccountWithPda[]>([])
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null) const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
const fetchEscrows = useCallback(async () => { const fetchEscrows = useCallback(async () => {
if (!publicKey) { if (!publicKey || !connection) {
setEscrows([]) setEscrows([])
return return
} }
try { try {
const provider = new AnchorProvider(connection, {} as any, {}) const provider = new AnchorProvider(connection, {} as never, {})
const client = new EscrowClient(provider) const client = new EscrowClient(provider)
const results = await client.fetchEscrowsForWallet(publicKey) const results = await client.fetchEscrowsForWallet(publicKey)
results.sort((a, b) => results.sort(
Number(b.account.escrowId.toString()) - Number(a.account.escrowId.toString()) (a, b) => Number(b.account.escrowId.toString()) - Number(a.account.escrowId.toString()),
) )
setEscrows(results) setEscrows(results)
setError(null) setError(null)
@@ -38,9 +46,7 @@ export function useEscrows() {
useEffect(() => { useEffect(() => {
setLoading(true) setLoading(true)
fetchEscrows().finally(() => setLoading(false)) fetchEscrows().finally(() => setLoading(false))
intervalRef.current = setInterval(fetchEscrows, POLL_INTERVAL_MS) intervalRef.current = setInterval(fetchEscrows, POLL_INTERVAL_MS)
return () => { return () => {
if (intervalRef.current !== null) clearInterval(intervalRef.current) if (intervalRef.current !== null) clearInterval(intervalRef.current)
} }

View File

@@ -10,7 +10,6 @@
"skipLibCheck": true, "skipLibCheck": true,
"declaration": true, "declaration": true,
"declarationMap": true, "declarationMap": true,
"outDir": "./dist"
}, },
"include": ["src/**/*"] "include": ["src/**/*"]
} }

5985
yarn.lock

File diff suppressed because it is too large Load Diff