# Solisting SDK Implementation Plan (Part 1 of 4) > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Generate the Anchor IDL, run Codama to produce TypeScript bindings, and write hand-crafted SDK helpers for the `@solisting/sdk` yarn workspace package. **Architecture:** The SDK mirrors the `@descro/sdk` pattern: Codama renders the IDL into typed account decoders and instruction builders under `src/generated/`, while hand-written files in `src/` add PDA helpers, filtering utilities, and the critical `deriveEscrowId` function. `@descro/sdk` is referenced as a local `file:` path (not npm). **Tech Stack:** Anchor IDL build, `codama` + `@codama/renderers-js`, `@solana/kit ^6`, `@solana/program-client-core`, vitest for unit tests. --- ## File Map | Path | Purpose | |---|---| | `package.json` (root) | Add `"workspaces": ["sdk","app"]` | | `sdk/package.json` | Workspace package `@solisting/sdk` | | `sdk/tsconfig.json` | TypeScript config | | `sdk/codama.solisting.json` | Codama render config | | `sdk/src/idl/solisting.json` | Anchor-generated IDL (copied from `target/idl/`) | | `sdk/src/generated/solisting/` | Codama output — do NOT edit manually | | `sdk/src/pda.ts` | `findListingPda`, `findOrderPda`, `deriveEscrowId` | | `sdk/src/listing.ts` | `fetchAllListings`, `fetchListingsBySeller` | | `sdk/src/order.ts` | `fetchOrdersForListing`, `fetchOrdersByBuyer` | | `sdk/src/index.ts` | Public re-exports | | `sdk/src/__tests__/pda.test.ts` | Unit tests | --- ### Task 1: Generate the Anchor IDL **Files:** - Read: `programs/solisting/Cargo.toml` (confirm `idl-build` feature exists) - Output: `target/idl/solisting.json` - [ ] **Step 1: Verify anchor-cli is installed** ```bash anchor --version ``` Expected: `anchor-cli 0.30.x` or `1.x` - [ ] **Step 2: Generate the IDL (host-target compilation, not SBF)** ```bash anchor idl build --program-name solisting ``` If this fails, use the full build (slower): ```bash anchor build ``` Expected: `target/idl/solisting.json` created - [ ] **Step 3: Confirm IDL structure** ```bash cat target/idl/solisting.json | python3 -m json.tool | head -60 ``` Expected: JSON with `"name": "solisting"`, `"accounts"`, `"instructions"`, `"types"` keys. Confirm accounts named `listingAccount` and `orderAccount`, and 8 instructions. - [ ] **Step 4: Commit** ```bash git add target/idl/solisting.json git commit -m "chore: generate solisting IDL" ``` --- ### Task 2: SDK Package Setup **Files:** - Modify: `package.json` (root) - Create: `sdk/package.json` - Create: `sdk/tsconfig.json` - Create: `sdk/codama.solisting.json` - Create: `sdk/src/idl/solisting.json` - [ ] **Step 1: Update root package.json to add workspaces** Replace `package.json` at repo root with: ```json { "name": "solisting", "license": "ISC", "private": true, "workspaces": ["sdk", "app"], "scripts": { "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" }, "devDependencies": { "prettier": "^3.8.3" } } ``` - [ ] **Step 2: Create `sdk/package.json`** ```json { "name": "@solisting/sdk", "version": "0.1.0", "private": true, "main": "./src/index.ts", "types": "./src/index.ts", "scripts": { "generate": "codama run js --config codama.solisting.json", "test": "vitest run", "typecheck": "tsc --noEmit" }, "dependencies": { "@descro/sdk": "file:../../descro/sdk", "@solana/kit": "^6.0.0", "@solana/program-client-core": "^6.4.0" }, "devDependencies": { "@codama/nodes-from-anchor": "^1.4.1", "@codama/renderers-js": "^2.2.0", "codama": "^1.6.0", "typescript": "^6.0.3", "vitest": "^3.0.0" } } ``` - [ ] **Step 3: Create `sdk/tsconfig.json`** ```json { "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", "strict": true, "skipLibCheck": true, "outDir": "./dist" }, "include": ["src"], "exclude": ["node_modules", "dist"] } ``` - [ ] **Step 4: Create `sdk/codama.solisting.json`** ```json { "idl": "./src/idl/solisting.json", "scripts": { "js": [ { "from": "@codama/renderers-js", "args": ["./src/generated/solisting"] } ] } } ``` - [ ] **Step 5: Copy the IDL into the SDK** ```bash mkdir -p sdk/src/idl cp target/idl/solisting.json sdk/src/idl/solisting.json ``` - [ ] **Step 6: Install dependencies** ```bash yarn install ``` Expected: `sdk/node_modules/` populated, `@codama/renderers-js` available. - [ ] **Step 7: Commit** ```bash git add sdk/package.json sdk/tsconfig.json sdk/codama.solisting.json sdk/src/idl/solisting.json package.json git commit -m "feat: add @solisting/sdk package scaffold" ``` --- ### Task 3: Run Codama **Files:** - Create: `sdk/src/generated/solisting/` (entire directory, auto-generated) - [ ] **Step 1: Run codama** ```bash cd sdk && yarn generate ``` Expected: `sdk/src/generated/solisting/` created with subdirectories: ``` src/generated/solisting/ ├── accounts/ │ ├── index.ts │ ├── listingAccount.ts │ └── orderAccount.ts ├── instructions/ │ ├── index.ts │ ├── createListing.ts │ ├── updateListing.ts │ ├── closeListing.ts │ ├── createOrder.ts │ ├── acceptOrder.ts │ ├── rejectOrder.ts │ ├── cancelOrder.ts │ └── closeStaleOrder.ts ├── pdas/ │ ├── index.ts │ ├── listingAccount.ts │ └── orderAccount.ts ├── types/ │ ├── index.ts │ ├── currency.ts │ └── altCurrencyConfig.ts ├── errors/ │ └── solisting.ts ├── programs/ │ └── solisting.ts └── index.ts ``` - [ ] **Step 2: Inspect the generated account types** ```bash cat sdk/src/generated/solisting/accounts/listingAccount.ts | head -40 ``` Note the exact field names (camelCase in TS, e.g. `quantityReserved`, `isActive`, `canonicalCurrency`, `metadataUri`). These must match what you use in the hand-written helpers and components. - [ ] **Step 3: Inspect the generated PDA functions** ```bash cat sdk/src/generated/solisting/pdas/listingAccount.ts cat sdk/src/generated/solisting/pdas/orderAccount.ts ``` Note the exact seed parameter names (e.g. `{ seller, listingId }` or `{ seller, listingIdLeBytes }`). Required in Task 4. - [ ] **Step 4: Inspect the generated instruction builders** ```bash cat sdk/src/generated/solisting/instructions/createListing.ts | head -50 ``` Note all required and optional account + data fields. These will be used in Plan 4 (write transactions). - [ ] **Step 5: Add generated directory to git** ```bash git add sdk/src/generated/ git commit -m "feat: generate solisting codama bindings" ``` --- ### Task 4: SDK Hand-Written Helpers **Files:** - Create: `sdk/src/pda.ts` - Create: `sdk/src/listing.ts` - Create: `sdk/src/order.ts` - Create: `sdk/src/__tests__/pda.test.ts` - [ ] **Step 1: Write `sdk/src/pda.ts`** ```ts import { getAddressEncoder, type Address, type ProgramDerivedAddress } from '@solana/kit' import { findListingAccountPda, findOrderAccountPda } from './generated/solisting' // Convenience wrappers so callers don't need to pass the program address. export async function findListingPda( seller: Address, listingId: bigint, ): Promise { return findListingAccountPda({ seller, listingId }) } export async function findOrderPda( listingAccount: Address, buyer: Address, orderId: bigint, ): Promise { return findOrderAccountPda({ listingAccount, buyer, orderId }) } /** * Replicates the program's escrow_id derivation: * u64::from_le_bytes(order_pda.to_bytes()[0..8]) * Must match exactly — used when building create_order instructions. */ export function deriveEscrowId(orderPda: Address): bigint { const bytes = getAddressEncoder().encode(orderPda) // 32-byte Uint8Array const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength) return view.getBigUint64(0, true) // little-endian, first 8 bytes } ``` Note: If codama's `findListingAccountPda` seed parameters differ (inspect in Task 3 Step 3), adjust accordingly. The seeds from CLAUDE.md are `[b"listing", seller, listing_id_le]`. - [ ] **Step 2: Write `sdk/src/listing.ts`** ```ts import type { Account, Address } from '@solana/kit' import { fetchAllListingAccounts, fetchListingAccount, type ListingAccount, } from './generated/solisting' export type ListingAccountWithPda = Account type Rpc = Parameters[0] export async function fetchAllListings(rpc: Rpc): Promise { return fetchAllListingAccounts(rpc) } export async function fetchListingsBySeller( rpc: Rpc, seller: Address, ): Promise { const all = await fetchAllListingAccounts(rpc) return all.filter((l) => l.data.seller === seller) } export async function fetchListing( rpc: Rpc, address: Address, ): Promise { return fetchListingAccount(rpc, address).catch(() => null) } ``` - [ ] **Step 3: Write `sdk/src/order.ts`** ```ts import type { Account, Address } from '@solana/kit' import { fetchAllOrderAccounts, fetchOrderAccount, type OrderAccount, } from './generated/solisting' import { deriveEscrowId } from './pda' export type OrderAccountWithPda = Account type Rpc = Parameters[0] export async function fetchOrdersForListing( rpc: Rpc, listingPk: Address, ): Promise { const all = await fetchAllOrderAccounts(rpc) return all.filter((o) => o.data.listingAccount === listingPk) } export async function fetchOrdersByBuyer( rpc: Rpc, buyer: Address, ): Promise { const all = await fetchAllOrderAccounts(rpc) return all.filter((o) => o.data.buyer === buyer) } export async function fetchOrder( rpc: Rpc, address: Address, ): Promise { return fetchOrderAccount(rpc, address).catch(() => null) } export { deriveEscrowId } ``` - [ ] **Step 4: Write unit tests `sdk/src/__tests__/pda.test.ts`** ```ts import { describe, it, expect } from 'vitest' import { address } from '@solana/kit' import { deriveEscrowId, findListingPda } from '../pda' const SELLER = address('11111111111111111111111111111112') describe('deriveEscrowId', () => { it('returns a bigint from the first 8 bytes of the order PDA', () => { // We test the determinism: same input → same output const fakeOrderPda = address('So11111111111111111111111111111111111111112') const id1 = deriveEscrowId(fakeOrderPda) const id2 = deriveEscrowId(fakeOrderPda) expect(id1).toBe(id2) expect(typeof id1).toBe('bigint') }) it('returns different ids for different PDAs', () => { const pda1 = address('So11111111111111111111111111111111111111112') const pda2 = address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA') expect(deriveEscrowId(pda1)).not.toBe(deriveEscrowId(pda2)) }) }) describe('findListingPda', () => { it('derives a deterministic PDA for a given seller + listingId', async () => { const [pda1] = await findListingPda(SELLER, 1001n) const [pda2] = await findListingPda(SELLER, 1001n) expect(pda1).toBe(pda2) expect(pda1).toHaveLength(44) // base58 encoded 32-byte pubkey }) it('produces different PDAs for different listing ids', async () => { const [pda1] = await findListingPda(SELLER, 1001n) const [pda2] = await findListingPda(SELLER, 1002n) expect(pda1).not.toBe(pda2) }) }) ``` - [ ] **Step 5: Run tests** ```bash cd sdk && yarn test ``` Expected: 4 passing tests. If `findListingPda` seed params differ from what's in `pda.ts`, fix the wrapper signature based on what Task 3 Step 3 revealed. - [ ] **Step 6: Type-check** ```bash cd sdk && yarn typecheck ``` Expected: No errors. --- ### Task 5: SDK `index.ts` + Final Check **Files:** - Create: `sdk/src/index.ts` - [ ] **Step 1: Write `sdk/src/index.ts`** ```ts // Generated bindings — accounts, instructions, pdas, types, errors, program id export * from './generated/solisting' // Hand-written helpers export * from './pda' export * from './listing' export * from './order' // Re-export Address for consumers export type { Address, Account } from '@solana/kit' ``` - [ ] **Step 2: Type-check once more** ```bash cd sdk && yarn typecheck ``` Expected: No errors. - [ ] **Step 3: Run tests again to confirm nothing regressed** ```bash cd sdk && yarn test ``` Expected: 4 passing. - [ ] **Step 4: Commit** ```bash git add sdk/src/ git commit -m "feat: add @solisting/sdk helpers, tests, and index exports" ``` --- **Next:** Proceed to Part 2 — `2026-06-22-solisting-app-foundation.md`