From c92e71fe81fd0e0994663d8e13b04989caf62b33 Mon Sep 17 00:00:00 2001 From: thesn10 <38666407+thesn10@users.noreply.github.com> Date: Sat, 23 May 2026 13:29:13 +0200 Subject: [PATCH] use codama in sdk --- sdk/package.json | 3 +- sdk/src/escrow.ts | 241 +++++++++++--------------------------------- sdk/src/index.ts | 16 +-- sdk/src/listener.ts | 86 +++++++--------- sdk/src/pda.ts | 69 ++++--------- sdk/src/registry.ts | 85 +--------------- sdk/src/types.ts | 117 +++++++++------------ 7 files changed, 167 insertions(+), 450 deletions(-) diff --git a/sdk/package.json b/sdk/package.json index 36afcd3..0442a68 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -8,7 +8,8 @@ "generate": "codama run js --config codama.descro.json && codama run js --config codama.registry.json" }, "dependencies": { - "@solana/kit": "^6.0.0" + "@solana/kit": "^6.0.0", + "@solana/program-client-core": "^6.4.0" }, "devDependencies": { "@codama/nodes-from-anchor": "^1.4.1", diff --git a/sdk/src/escrow.ts b/sdk/src/escrow.ts index 9823746..b4c6ee2 100644 --- a/sdk/src/escrow.ts +++ b/sdk/src/escrow.ts @@ -1,193 +1,68 @@ +import type { Address, GetProgramAccountsApi, Lamports, Rpc } from "@solana/kit"; import { - PublicKey, - SystemProgram, - TransactionInstruction, -} from "@solana/web3.js"; -import { AnchorProvider, Program, BN } from "@coral-xyz/anchor"; -import type { Idl } from "@coral-xyz/anchor"; -import descroIdl from "./idl/descro.json"; -import { - REGISTRY_PROGRAM_ID, - deriveEscrowPda, - deriveVaultPda, - deriveEscrowAuthorityPda, - deriveResolverEntryPda, -} from "./pda"; -import type { EscrowAccount, EscrowAccountWithPda, Winner } from "./types"; + fetchMaybeEscrowAccount, + fetchEscrowAccount, + decodeEscrowAccount, + getEscrowAccountDiscriminatorBytes, +} from "./generated/descro/src/generated/accounts/escrowAccount"; +import { DESCRO_PROGRAM_ADDRESS } from "./generated/descro/src/generated/programs/descro"; +import type { EscrowAccountWithPda } from "./types"; -export class EscrowClient { - readonly program: Program; - readonly provider: AnchorProvider; +export { fetchEscrowAccount, fetchMaybeEscrowAccount, decodeEscrowAccount }; - constructor(provider: AnchorProvider) { - this.provider = provider; - this.program = new Program(descroIdl as Idl, provider); - } +export async function fetchEscrowsForWallet( + rpc: Rpc, + walletAddress: Address, +): Promise { + const discriminatorBase64 = btoa( + String.fromCharCode(...getEscrowAccountDiscriminatorBytes()), + ) as never; - async buildCreateEscrow(params: { - seller: PublicKey; - buyer: PublicKey; - amount: BN; - disputeResolver: PublicKey | null; - escrowId: BN; - }): Promise { - const { seller, buyer, amount, disputeResolver, escrowId } = params; - const [escrowPda] = deriveEscrowPda(seller, escrowId); - const [vaultPda] = deriveVaultPda(escrowPda); + const walletBytes = walletAddress as never; - return await (this.program.methods as any) - .createEscrow(amount, disputeResolver, escrowId) - .accounts({ - seller, - buyer, - escrowAccount: escrowPda, - vault: vaultPda, - systemProgram: SystemProgram.programId, + const [asSeller, asBuyer] = await Promise.all([ + rpc + .getProgramAccounts(DESCRO_PROGRAM_ADDRESS, { + filters: [ + { memcmp: { offset: 0n, bytes: discriminatorBase64, encoding: "base64" } }, + { memcmp: { offset: 8n, bytes: walletBytes, encoding: "base58" } }, + ], + encoding: "base64", }) - .instruction(); - } - - async buildDeposit(params: { - buyer: PublicKey; - escrowPda: PublicKey; - }): Promise { - const { buyer, escrowPda } = params; - const [vaultPda] = deriveVaultPda(escrowPda); - - return await (this.program.methods as any) - .deposit() - .accounts({ - buyer, - escrowAccount: escrowPda, - vault: vaultPda, - systemProgram: SystemProgram.programId, + .send(), + rpc + .getProgramAccounts(DESCRO_PROGRAM_ADDRESS, { + filters: [ + { memcmp: { offset: 0n, bytes: discriminatorBase64, encoding: "base64" } }, + { memcmp: { offset: 40n, bytes: walletBytes, encoding: "base58" } }, + ], + encoding: "base64", }) - .instruction(); + .send(), + ]); + + const seen = new Set(); + const results: EscrowAccountWithPda[] = []; + + for (const item of [...asSeller, ...asBuyer]) { + const pda = item.pubkey; + if (seen.has(pda)) continue; + seen.add(pda); + + const [base64Data] = item.account.data; + const rawBytes = Uint8Array.from(atob(base64Data), (c) => c.charCodeAt(0)); + + const decoded = decodeEscrowAccount({ + address: pda, + data: rawBytes, + executable: item.account.executable, + lamports: item.account.lamports as Lamports, + programAddress: item.account.owner, + space: item.account.space, + exists: true, + }); + if (decoded.exists) results.push({ pda, account: decoded.data }); } - async buildComplete(params: { - buyer: PublicKey; - seller: PublicKey; - escrowPda: PublicKey; - }): Promise { - const { buyer, seller, escrowPda } = params; - const [vaultPda] = deriveVaultPda(escrowPda); - - return await (this.program.methods as any) - .complete() - .accounts({ - buyer, - seller, - escrowAccount: escrowPda, - vault: vaultPda, - systemProgram: SystemProgram.programId, - }) - .instruction(); - } - - async buildDispute(params: { - initiator: PublicKey; - escrowPda: PublicKey; - }): Promise { - const { initiator, escrowPda } = params; - - return await (this.program.methods as any) - .dispute() - .accounts({ - initiator, - escrowAccount: escrowPda, - }) - .instruction(); - } - - async buildCancel(params: { - seller: PublicKey; - escrowPda: PublicKey; - }): Promise { - const { seller, escrowPda } = params; - - return await (this.program.methods as any) - .cancel() - .accounts({ - seller, - escrowAccount: escrowPda, - systemProgram: SystemProgram.programId, - }) - .instruction(); - } - - async buildResolve(params: { - resolver: PublicKey; - winner: "buyer" | "seller"; - winnerPubkey: PublicKey; - seller: PublicKey; - escrowPda: PublicKey; - disputeResolver: PublicKey | null; - }): Promise { - const { resolver, winner, winnerPubkey, seller, escrowPda, disputeResolver } = params; - const [vaultPda] = deriveVaultPda(escrowPda); - const [escrowAuthority] = deriveEscrowAuthorityPda(); - - const registryProgram = disputeResolver ? REGISTRY_PROGRAM_ID : SystemProgram.programId; - const resolverEntryAuthority = disputeResolver ?? resolver; - const [resolverEntry] = deriveResolverEntryPda(resolverEntryAuthority); - - const winnerArg: Winner = - winner === "buyer" ? { buyer: {} } : { seller: {} }; - - return await (this.program.methods as any) - .resolve(winnerArg) - .accounts({ - resolver, - winner: winnerPubkey, - seller, - escrowAccount: escrowPda, - vault: vaultPda, - resolverEntry, - escrowAuthority, - registryProgram, - systemProgram: SystemProgram.programId, - }) - .instruction(); - } - - async fetchEscrow(pda: PublicKey): Promise { - try { - const raw = await (this.program.account as any).escrowAccount.fetch(pda); - return raw as EscrowAccount; - } catch { - return null; - } - } - - async fetchAllEscrows(): Promise { - const accounts = await (this.program.account as any).escrowAccount.all(); - return accounts.map((a: { publicKey: PublicKey; account: EscrowAccount }) => ({ - pda: a.publicKey, - account: a.account as EscrowAccount, - })); - } - - async fetchEscrowsForWallet(wallet: PublicKey): Promise { - const asSeller = await (this.program.account as any).escrowAccount.all([ - { memcmp: { offset: 8, bytes: wallet.toBase58() } }, - ]); - - const asBuyer = await (this.program.account as any).escrowAccount.all([ - { memcmp: { offset: 8 + 32, bytes: wallet.toBase58() } }, - ]); - - const seen = new Set(); - const results: EscrowAccountWithPda[] = []; - - for (const a of [...asSeller, ...asBuyer]) { - const key = a.publicKey.toBase58(); - if (!seen.has(key)) { - seen.add(key); - results.push({ pda: a.publicKey, account: a.account as EscrowAccount }); - } - } - - return results; - } + return results; } diff --git a/sdk/src/index.ts b/sdk/src/index.ts index e49d37f..8db0b28 100644 --- a/sdk/src/index.ts +++ b/sdk/src/index.ts @@ -2,18 +2,4 @@ export * from "./types"; export * from "./pda"; export * from "./escrow"; export * from "./registry"; -export * from "./listener"; - -import { AnchorProvider } from "@coral-xyz/anchor"; -import { EscrowClient } from "./escrow"; -import { RegistryClient } from "./registry"; - -export class DescroSdk { - readonly escrow: EscrowClient; - readonly registry: RegistryClient; - - constructor(provider: AnchorProvider) { - this.escrow = new EscrowClient(provider); - this.registry = new RegistryClient(provider); - } -} +export * from "./listener"; \ No newline at end of file diff --git a/sdk/src/listener.ts b/sdk/src/listener.ts index 18edf8c..53d75e1 100644 --- a/sdk/src/listener.ts +++ b/sdk/src/listener.ts @@ -1,53 +1,45 @@ -import { Connection, PublicKey, AccountInfo } from "@solana/web3.js"; -import { AnchorProvider, Program } from "@coral-xyz/anchor"; -import type { Idl } from "@coral-xyz/anchor"; -import descroIdl from "./idl/descro.json"; -import { ESCROW_PROGRAM_ID } from "./pda"; -import type { EscrowAccount } from "./types"; - -function decodeEscrowAccount( - program: Program, - info: AccountInfo -): EscrowAccount | null { - try { - return (program.coder.accounts as any).decode("EscrowAccount", info.data) as EscrowAccount; - } catch { - return null; - } -} +import type { Address, Lamports } from "@solana/kit"; +import type { AccountNotificationsApi } from "@solana/rpc-subscriptions-api"; +import type { RpcSubscriptions } from "@solana/rpc-subscriptions-spec"; +import { decodeEscrowAccount } from "./generated/descro/src/generated/accounts/escrowAccount"; +import type { EscrowAccount } from "./generated/descro/src/generated/accounts/escrowAccount"; export function subscribeEscrow( - connection: Connection, - pda: PublicKey, - cb: (account: EscrowAccount | null) => void + rpcSubscriptions: RpcSubscriptions, + address: Address, + cb: (account: EscrowAccount | null) => void, ): () => void { - const provider = new AnchorProvider(connection, {} as never, {}); - const program = new Program(descroIdl as Idl, new PublicKey(ESCROW_PROGRAM_ID), provider); + const abortController = new AbortController(); - const subId = connection.onAccountChange(pda, (info) => { - cb(decodeEscrowAccount(program, info as AccountInfo)); - }); + (async () => { + try { + const notifications = await rpcSubscriptions + .accountNotifications(address, { encoding: "base64" }) + .subscribe({ abortSignal: abortController.signal }); - return () => { - connection.removeAccountChangeListener(subId).catch(() => undefined); - }; -} - -export function subscribeAll( - connection: Connection, - programId: PublicKey = ESCROW_PROGRAM_ID, - cb: (pda: PublicKey, account: EscrowAccount | null) => void -): () => void { - const provider = new AnchorProvider(connection, {} as never, {}); - const program = new Program(descroIdl as Idl, new PublicKey(programId), provider); - - const subId = connection.onProgramAccountChange(programId, (keyedAccountInfo) => { - const pda = keyedAccountInfo.accountId; - const info = keyedAccountInfo.accountInfo; - cb(pda, decodeEscrowAccount(program, info as AccountInfo)); - }); - - return () => { - connection.removeProgramAccountChangeListener(subId).catch(() => undefined); - }; + for await (const notification of notifications) { + const info = notification.value; + const [base64Data] = info.data; + const rawBytes = Uint8Array.from(atob(base64Data), (c) => + c.charCodeAt(0), + ); + + const decoded = decodeEscrowAccount({ + address, + data: rawBytes, + executable: info.executable, + lamports: info.lamports as Lamports, + programAddress: info.owner, + space: info.space, + exists: true, + }); + + cb(decoded.exists ? decoded.data : null); + } + } catch { + // subscription was aborted or connection closed + } + })(); + + return () => abortController.abort(); } diff --git a/sdk/src/pda.ts b/sdk/src/pda.ts index 8cb591d..816facd 100644 --- a/sdk/src/pda.ts +++ b/sdk/src/pda.ts @@ -1,52 +1,17 @@ -import { PublicKey } from "@solana/web3.js"; -import BN from "bn.js"; - -export const ESCROW_PROGRAM_ID = new PublicKey( - "DjVR4EuYV6USMJFfsGZwhZ3y8rtWsmG8EvDY96GTqqi3" -); -export const REGISTRY_PROGRAM_ID = new PublicKey( - "GwUPAKs3HHzCpj8uhet4NAnxk9GWNwfrYbpihu5DyFp" -); - -export function deriveEscrowPda( - seller: PublicKey, - escrowId: BN | bigint | number, - programId: PublicKey = ESCROW_PROGRAM_ID -): [PublicKey, number] { - const id = new BN(escrowId.toString()); - const idBuf = Buffer.alloc(8); - idBuf.writeBigUInt64LE(BigInt(id.toString())); - return PublicKey.findProgramAddressSync( - [Buffer.from("escrow"), seller.toBuffer(), idBuf], - programId - ); -} - -export function deriveVaultPda( - escrowPda: PublicKey, - programId: PublicKey = ESCROW_PROGRAM_ID -): [PublicKey, number] { - return PublicKey.findProgramAddressSync( - [Buffer.from("vault"), escrowPda.toBuffer()], - programId - ); -} - -export function deriveEscrowAuthorityPda( - programId: PublicKey = ESCROW_PROGRAM_ID -): [PublicKey, number] { - return PublicKey.findProgramAddressSync( - [Buffer.from("escrow_authority")], - programId - ); -} - -export function deriveResolverEntryPda( - authority: PublicKey, - registryProgramId: PublicKey = REGISTRY_PROGRAM_ID -): [PublicKey, number] { - return PublicKey.findProgramAddressSync( - [Buffer.from("resolver"), authority.toBuffer()], - registryProgramId - ); -} +export { + findEscrowAccountPda, + type EscrowAccountSeeds, +} from "./generated/descro/src/generated/pdas/escrowAccount"; +export { + findVaultPda, + type VaultSeeds, +} from "./generated/descro/src/generated/pdas/vault"; +export { + findEscrowAuthorityPda, +} from "./generated/descro/src/generated/pdas/escrowAuthority"; +export { + findResolverEntryPda, + type ResolverEntrySeeds, +} from "./generated/descro_ext_resolvers/src/generated/pdas/resolverEntry"; +export { DESCRO_PROGRAM_ADDRESS } from "./generated/descro/src/generated/programs/descro"; +export { DESCRO_EXT_RESOLVERS_PROGRAM_ADDRESS } from "./generated/descro_ext_resolvers/src/generated/programs/descroExtResolvers"; diff --git a/sdk/src/registry.ts b/sdk/src/registry.ts index df91741..448500e 100644 --- a/sdk/src/registry.ts +++ b/sdk/src/registry.ts @@ -1,84 +1 @@ -import { PublicKey, TransactionInstruction, SystemProgram } from "@solana/web3.js"; -import { AnchorProvider, Program } from "@coral-xyz/anchor"; -import type { Idl } from "@coral-xyz/anchor"; -import registryIdl from "./idl/descro_ext_resolvers.json"; -import { deriveResolverEntryPda } from "./pda"; -import type { ResolverEntry, ResolverEntryWithPda, ResolverType } from "./types"; - -export class RegistryClient { - readonly program: Program; - readonly provider: AnchorProvider; - - constructor(provider: AnchorProvider) { - this.provider = provider; - this.program = new Program(registryIdl as Idl, provider); - } - - async buildRegisterResolver(params: { - authority: PublicKey; - resolverType: ResolverType; - name: string; - description: string; - feeBps: number; - feeRecipient: PublicKey; - metadataUri: string; - }): Promise { - const { authority, resolverType, name, description, feeBps, feeRecipient, metadataUri } = params; - const [resolverEntry] = deriveResolverEntryPda(authority); - - return await (this.program.methods as any) - .registerResolver(resolverType, name, description, feeBps, feeRecipient, metadataUri) - .accounts({ - authority, - resolverEntry, - systemProgram: SystemProgram.programId, - }) - .instruction(); - } - - async buildUpdateResolver(params: { - authority: PublicKey; - name: string; - description: string; - feeBps: number; - metadataUri: string; - }): Promise { - const { authority, name, description, feeBps, metadataUri } = params; - const [resolverEntry] = deriveResolverEntryPda(authority); - - return await (this.program.methods as any) - .updateResolver(name, description, feeBps, metadataUri) - .accounts({ - authority, - resolverEntry, - }) - .instruction(); - } - - async fetchResolver(authority: PublicKey): Promise { - try { - const [pda] = deriveResolverEntryPda(authority); - const raw = await (this.program.account as any).resolverEntry.fetch(pda); - return raw as ResolverEntry; - } catch { - return null; - } - } - - async fetchResolverByPda(pda: PublicKey): Promise { - try { - const raw = await (this.program.account as any).resolverEntry.fetch(pda); - return raw as ResolverEntry; - } catch { - return null; - } - } - - async fetchAllResolvers(): Promise { - const accounts = await (this.program.account as any).resolverEntry.all(); - return accounts.map((a: { publicKey: PublicKey; account: ResolverEntry }) => ({ - pda: a.publicKey, - account: a.account as ResolverEntry, - })); - } -} +export * from "./generated/descro_ext_resolvers/src/generated/accounts"; diff --git a/sdk/src/types.ts b/sdk/src/types.ts index 115d1f4..ae363f0 100644 --- a/sdk/src/types.ts +++ b/sdk/src/types.ts @@ -1,87 +1,68 @@ -import { PublicKey } from "@solana/web3.js"; -import BN from "bn.js"; +export type { + EscrowAccount, + EscrowAccountArgs, +} from "./generated/descro/src/generated/accounts/escrowAccount"; +export type { + EscrowState, + EscrowStateArgs, +} from "./generated/descro/src/generated/types/escrowState"; +export type { + Winner, + WinnerArgs, +} from "./generated/descro/src/generated/types/winner"; +export type { + ResolverEntry, + ResolverEntryArgs, +} from "./generated/descro_ext_resolvers/src/generated/accounts/resolverEntry"; +export type { + ResolverType, + ResolverTypeArgs, +} from "./generated/descro_ext_resolvers/src/generated/types/resolverType"; +export type { Address } from "@solana/kit"; -export type EscrowState = - | { awaitingDeposit: Record } - | { active: Record } - | { disputed: Record } - | { complete: Record } - | { cancelled: Record }; +import { EscrowState } from "./generated/descro/src/generated/types/escrowState"; +import { ResolverType } from "./generated/descro_ext_resolvers/src/generated/types/resolverType"; +import type { EscrowAccount } from "./generated/descro/src/generated/accounts/escrowAccount"; +import type { ResolverEntry } from "./generated/descro_ext_resolvers/src/generated/accounts/resolverEntry"; +import type { Address } from "@solana/kit"; -export type Winner = { buyer: Record } | { seller: Record }; - -export type ResolverType = - | { centralAuthority: Record } - | { juryDAO: Record } - | { mad: Record } - | { algorithmic: Record } - | { multisig: Record }; - -export interface EscrowAccount { - seller: PublicKey; - buyer: PublicKey; - amount: BN; - disputeResolver: PublicKey | null; - state: EscrowState; - bump: number; - vaultBump: number; - escrowId: BN; -} - -export interface ResolverEntry { - authority: PublicKey; - resolverType: ResolverType; - name: string; - description: string; - feeBps: number; - feeRecipient: PublicKey; - metadataUri: string; - totalResolved: BN; - ruledForBuyer: BN; - ruledForSeller: BN; - registeredAt: BN; -} - -export interface EscrowAccountWithPda { - pda: PublicKey; - account: EscrowAccount; -} - -export interface ResolverEntryWithPda { - pda: PublicKey; - account: ResolverEntry; -} +export type EscrowAccountWithPda = { pda: Address; account: EscrowAccount }; +export type ResolverEntryWithPda = { pda: Address; account: ResolverEntry }; export function isAwaitingDeposit(state: EscrowState): boolean { - return "awaitingDeposit" in state; + return state === EscrowState.AwaitingDeposit; } export function isActive(state: EscrowState): boolean { - return "active" in state; + return state === EscrowState.Active; } export function isDisputed(state: EscrowState): boolean { - return "disputed" in state; + return state === EscrowState.Disputed; } export function isComplete(state: EscrowState): boolean { - return "complete" in state; + return state === EscrowState.Complete; } export function isCancelled(state: EscrowState): boolean { - return "cancelled" in state; + return state === EscrowState.Cancelled; } export function escrowStateLabel(state: EscrowState): string { - if (isAwaitingDeposit(state)) return "AwaitingDeposit"; - if (isActive(state)) return "Active"; - if (isDisputed(state)) return "Disputed"; - if (isComplete(state)) return "Complete"; - if (isCancelled(state)) return "Cancelled"; - return "Unknown"; + switch (state) { + case EscrowState.AwaitingDeposit: return "AwaitingDeposit"; + case EscrowState.Active: return "Active"; + case EscrowState.Disputed: return "Disputed"; + case EscrowState.Complete: return "Complete"; + case EscrowState.Cancelled: return "Cancelled"; + default: return "Unknown"; + } } export function resolverTypeLabel(rt: ResolverType): string { - if ("centralAuthority" in rt) return "CentralAuthority"; - if ("juryDAO" in rt) return "JuryDAO"; - if ("mad" in rt) return "MAD"; - if ("algorithmic" in rt) return "Algorithmic"; - if ("multisig" in rt) return "Multisig"; - return "Unknown"; + switch (rt) { + case ResolverType.CentralAuthority: return "CentralAuthority"; + case ResolverType.JuryDAO: return "JuryDAO"; + case ResolverType.MAD: return "MAD"; + case ResolverType.Algorithmic: return "Algorithmic"; + case ResolverType.Multisig: return "Multisig"; + default: return "Unknown"; + } }