docs: add 4-part Solisting Explorer implementation plan

This commit is contained in:
thesn10
2026-06-24 20:34:13 +02:00
parent 7c91d2c52b
commit e326bfee51
4 changed files with 3587 additions and 0 deletions

View File

@@ -0,0 +1,488 @@
# 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<ProgramDerivedAddress> {
return findListingAccountPda({ seller, listingId })
}
export async function findOrderPda(
listingAccount: Address,
buyer: Address,
orderId: bigint,
): Promise<ProgramDerivedAddress> {
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<ListingAccount>
type Rpc = Parameters<typeof fetchAllListingAccounts>[0]
export async function fetchAllListings(rpc: Rpc): Promise<ListingAccountWithPda[]> {
return fetchAllListingAccounts(rpc)
}
export async function fetchListingsBySeller(
rpc: Rpc,
seller: Address,
): Promise<ListingAccountWithPda[]> {
const all = await fetchAllListingAccounts(rpc)
return all.filter((l) => l.data.seller === seller)
}
export async function fetchListing(
rpc: Rpc,
address: Address,
): Promise<ListingAccountWithPda | null> {
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<OrderAccount>
type Rpc = Parameters<typeof fetchAllOrderAccounts>[0]
export async function fetchOrdersForListing(
rpc: Rpc,
listingPk: Address,
): Promise<OrderAccountWithPda[]> {
const all = await fetchAllOrderAccounts(rpc)
return all.filter((o) => o.data.listingAccount === listingPk)
}
export async function fetchOrdersByBuyer(
rpc: Rpc,
buyer: Address,
): Promise<OrderAccountWithPda[]> {
const all = await fetchAllOrderAccounts(rpc)
return all.filter((o) => o.data.buyer === buyer)
}
export async function fetchOrder(
rpc: Rpc,
address: Address,
): Promise<OrderAccountWithPda | null> {
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`