use solana connector
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -9,4 +9,7 @@ test-ledger
|
|||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
spec.md
|
spec.md
|
||||||
app_old
|
app_old
|
||||||
|
|
||||||
|
connectorkit
|
||||||
|
create-solana-dapp-template
|
||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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()}
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"declarationMap": true,
|
"declarationMap": true,
|
||||||
"outDir": "./dist"
|
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"]
|
"include": ["src/**/*"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user