Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion biome.jsonc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.2.3/schema.json",
"$schema": "https://biomejs.dev/schemas/2.2.4/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
Expand Down
25 changes: 21 additions & 4 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"": {
"name": "@macalinao/coda-monorepo",
"devDependencies": {
"@biomejs/biome": "^2.2.3",
"@changesets/cli": "^2.29.6",
"@biomejs/biome": "^2.2.4",
"@changesets/cli": "^2.29.7",
"husky": "^9.1.7",
"lint-staged": "^16.1.6",
"turbo": "^2.5.6",
Expand Down Expand Up @@ -82,6 +82,21 @@
"@solana/kit": "*",
},
},
"clients/spl-governance": {
"name": "@macalinao/clients-spl-governance",
"version": "0.1.0",
"devDependencies": {
"@macalinao/coda": "workspace:*",
"@macalinao/eslint-config": "catalog:",
"@macalinao/tsconfig": "catalog:",
"@solana/kit": "catalog:",
"eslint": "catalog:",
"typescript": "catalog:",
},
"peerDependencies": {
"@solana/kit": "*",
},
},
"clients/token-metadata": {
"name": "@macalinao/clients-token-metadata",
"version": "0.4.0",
Expand Down Expand Up @@ -473,6 +488,8 @@

"@macalinao/clients-quarry": ["@macalinao/clients-quarry@workspace:clients/quarry"],

"@macalinao/clients-spl-governance": ["@macalinao/clients-spl-governance@workspace:clients/spl-governance"],

"@macalinao/clients-token-metadata": ["@macalinao/clients-token-metadata@workspace:clients/token-metadata"],

"@macalinao/coda": ["@macalinao/coda@workspace:packages/coda"],
Expand Down Expand Up @@ -735,7 +752,7 @@

"@tybys/wasm-util": ["@tybys/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="],

"@types/bun": ["@types/[email protected].22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="],
"@types/bun": ["@types/[email protected].23", "", { "dependencies": { "bun-types": "1.2.23" } }, "sha512-le8ueOY5b6VKYf19xT3McVbXqLqmxzPXHsQT/q9JHgikJ2X22wyTW3g3ohz2ZMnp7dod6aduIiq8A14Xyimm0A=="],

"@types/debug": ["@types/[email protected]", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],

Expand Down Expand Up @@ -887,7 +904,7 @@

"braces": ["[email protected]", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],

"bun-types": ["[email protected].22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="],
"bun-types": ["[email protected].23", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-R9f0hKAZXgFU3mlrA0YpE/fiDvwV0FT9rORApt2aQVWSuJDzZOyB5QLc0N/4HF57CS8IXJ6+L5E4W1bW6NS2Aw=="],

"call-bind": ["[email protected]", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],

Expand Down
55 changes: 55 additions & 0 deletions clients/spl-governance/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# @macalinao/clients-spl-governance

[![npm version](https://img.shields.io/npm/v/@macalinao/clients-spl-governance.svg)](https://www.npmjs.com/package/@macalinao/clients-spl-governance)

TypeScript client for the SPL Governance program, generated using Coda with full ESM support.

IDL changes:

- `RealmConfigArgs` is renamed to `RealmConfigParams`
- `GoverningTokenConfigArgs` is renamed to `GoverningTokenConfigParams`

## Installation

```bash
bun add @macalinao/clients-spl-governance
```

## Development

This client is generated from the SPL Governance IDL using Coda CLI:

```bash
# Generate the client from idls/spl_governance.json
bun run codegen

# Build the TypeScript
bun run build
```

### Configuration

The `coda.config.mjs` file defines custom PDAs for the SPL Governance program, including:

- Realm identified by its name
- Token holding accounts for community and council tokens
- Governance accounts and proposals
- Vote records and signatory records
- Treasury accounts and transaction instructions

## Usage

```typescript
import { createRealm, getRealmPda } from "@macalinao/clients-spl-governance";

// Create a new realm
const realmPda = getRealmPda({ name: "my-dao" });

// Use the generated client functions
```

## License

Copyright © 2025 Ian Macalinao

Licensed under the Apache License, Version 2.0
263 changes: 263 additions & 0 deletions clients/spl-governance/coda.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
import {
accountValueNode,
addPdasVisitor,
constantPdaSeedNodeFromString,
defineConfig,
numberTypeNode,
pdaLinkNode,
pdaSeedValueNode,
pdaValueNode,
publicKeyTypeNode,
publicKeyValueNode,
setInstructionAccountDefaultValuesVisitor,
stringTypeNode,
updateAccountsVisitor,
variablePdaSeedNode,
} from "@macalinao/coda";

// PDAs are based on the original SPL Governance SDK:
// https://raw.githubusercontent.com/Mythic-Project/governance-sdk/refs/heads/main/src/pda.ts
const addCustomPDAsVisitor = addPdasVisitor({
splGovernance: [
// Realm
{
name: "realm",
docs: ["Realm account identified by its name"],
seeds: [
constantPdaSeedNodeFromString("utf8", "governance"),
variablePdaSeedNode("name", stringTypeNode("utf8")),
],
},
// Community Token Holding
{
name: "communityTokenHolding",
docs: ["Community token holding account of a realm"],
seeds: [
constantPdaSeedNodeFromString("utf8", "governance"),
variablePdaSeedNode("realm", publicKeyTypeNode()),
variablePdaSeedNode("communityMint", publicKeyTypeNode()),
],
},
// Council Token Holding
{
name: "councilTokenHolding",
docs: ["Council token holding account of a realm"],
seeds: [
constantPdaSeedNodeFromString("utf8", "governance"),
variablePdaSeedNode("realm", publicKeyTypeNode()),
variablePdaSeedNode("councilMint", publicKeyTypeNode()),
],
},
// Realm Config
{
name: "realmConfig",
docs: ["Configuration of a realm"],
seeds: [
constantPdaSeedNodeFromString("utf8", "realm-config"),
variablePdaSeedNode("realm", publicKeyTypeNode()),
],
},
// Token Owner Record
{
name: "tokenOwnerRecord",
docs: ["Token owner's record within a realm"],
seeds: [
constantPdaSeedNodeFromString("utf8", "governance"),
variablePdaSeedNode("realm", publicKeyTypeNode()),
variablePdaSeedNode("governingTokenMint", publicKeyTypeNode()),
variablePdaSeedNode("governingTokenOwner", publicKeyTypeNode()),
],
},
// Governing Token Holding
{
name: "governingTokenHolding",
docs: ["Governing token holding account"],
seeds: [
constantPdaSeedNodeFromString("utf8", "governance"),
variablePdaSeedNode("realm", publicKeyTypeNode()),
variablePdaSeedNode("governingTokenMint", publicKeyTypeNode()),
],
},
// Governance
{
name: "governance",
docs: ["Governance account within a realm"],
seeds: [
constantPdaSeedNodeFromString("utf8", "account-governance"),
variablePdaSeedNode("realm", publicKeyTypeNode()),
variablePdaSeedNode("seed", publicKeyTypeNode()),
],
},
// Native Treasury
{
name: "nativeTreasury",
docs: ["Governance's native SOL treasury account"],
seeds: [
constantPdaSeedNodeFromString("utf8", "native-treasury"),
variablePdaSeedNode("governance", publicKeyTypeNode()),
],
},
// Proposal
{
name: "proposal",
docs: ["Governance proposal"],
seeds: [
constantPdaSeedNodeFromString("utf8", "governance"),
variablePdaSeedNode("governance", publicKeyTypeNode()),
variablePdaSeedNode("governingTokenMint", publicKeyTypeNode()),
variablePdaSeedNode("proposalSeed", publicKeyTypeNode()),
],
},
// Proposal Deposit
{
name: "proposalDeposit",
docs: ["Proposal deposit made by a specific payer"],
seeds: [
constantPdaSeedNodeFromString("utf8", "proposal-deposit"),
variablePdaSeedNode("proposal", publicKeyTypeNode()),
variablePdaSeedNode("depositPayer", publicKeyTypeNode()),
],
},
// Signatory Record
{
name: "signatoryRecord",
docs: ["Signatory's record on a proposal"],
seeds: [
constantPdaSeedNodeFromString("utf8", "governance"),
variablePdaSeedNode("proposal", publicKeyTypeNode()),
variablePdaSeedNode("signatory", publicKeyTypeNode()),
],
},
// Proposal Transaction
{
name: "proposalTransaction",
docs: ["Transaction within a proposal option"],
seeds: [
constantPdaSeedNodeFromString("utf8", "governance"),
variablePdaSeedNode("proposal", publicKeyTypeNode()),
variablePdaSeedNode("optionIndex", numberTypeNode("u8")),
variablePdaSeedNode("index", numberTypeNode("u16")),
],
},
// Vote Record
{
name: "voteRecord",
docs: ["Vote record on a proposal"],
seeds: [
constantPdaSeedNodeFromString("utf8", "governance"),
variablePdaSeedNode("proposal", publicKeyTypeNode()),
variablePdaSeedNode("tokenOwnerRecord", publicKeyTypeNode()),
],
},
// Required Signatory
{
name: "requiredSignatory",
docs: ["Required signatory on a governance"],
seeds: [
constantPdaSeedNodeFromString("utf8", "required-signatory"),
variablePdaSeedNode("governance", publicKeyTypeNode()),
variablePdaSeedNode("signatory", publicKeyTypeNode()),
],
},
],
});

export default defineConfig({
outputDir: "./src/generated",
docs: {
npmPackageName: "@macalinao/clients-spl-governance",
},
visitors: [
updateAccountsVisitor({
// Realm accounts
realmV1: {
pda: pdaLinkNode("realm"),
},
realmV2: {
pda: pdaLinkNode("realm"),
},
realmConfigAccount: {
pda: pdaLinkNode("realmConfig"),
},

// Governance accounts
governanceV1: {
pda: pdaLinkNode("governance"),
},
governanceV2: {
pda: pdaLinkNode("governance"),
},

// Proposal accounts
proposalV1: {
pda: pdaLinkNode("proposal"),
},
proposalV2: {
pda: pdaLinkNode("proposal"),
},
proposalDeposit: {
pda: pdaLinkNode("proposalDeposit"),
},
proposalInstructionV1: {
pda: pdaLinkNode("proposalTransaction"),
},
proposalTransactionV2: {
pda: pdaLinkNode("proposalTransaction"),
},

// Token owner records
tokenOwnerRecordV1: {
pda: pdaLinkNode("tokenOwnerRecord"),
},
tokenOwnerRecordV2: {
pda: pdaLinkNode("tokenOwnerRecord"),
},
legacyTokenOwnerRecord: {
pda: pdaLinkNode("tokenOwnerRecord"),
},

// Signatory records
signatoryRecordV1: {
pda: pdaLinkNode("signatoryRecord"),
},
signatoryRecordV2: {
pda: pdaLinkNode("signatoryRecord"),
},
requiredSignatory: {
pda: pdaLinkNode("requiredSignatory"),
},

// Vote records
voteRecordV1: {
pda: pdaLinkNode("voteRecord"),
},
voteRecordV2: {
pda: pdaLinkNode("voteRecord"),
},
}),

setInstructionAccountDefaultValuesVisitor([
{
account: "systemProgram",
defaultValue: publicKeyValueNode("11111111111111111111111111111111"),
},
// Create token owner record defaults
{
account: "tokenOwnerRecord",
instruction: "createTokenOwnerRecord",
defaultValue: pdaValueNode(pdaLinkNode("tokenOwnerRecord"), [
pdaSeedValueNode("realm", accountValueNode("realmAccount")),
pdaSeedValueNode(
"governingTokenMint",
accountValueNode("governingTokenMint"),
),
pdaSeedValueNode(
"governingTokenOwner",
accountValueNode("governingTokenOwnerAccount"),
),
]),
},
]),
addCustomPDAsVisitor,
],
});
Loading