Skip to content

Commit 2524bba

Browse files
authored
Merge pull request #35 from macalinao/igm/spl-governance
Adds SPL governance client
2 parents d78d94e + 76e1dcd commit 2524bba

File tree

115 files changed

+22007
-5
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

115 files changed

+22007
-5
lines changed

biome.jsonc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"$schema": "https://biomejs.dev/schemas/2.2.3/schema.json",
2+
"$schema": "https://biomejs.dev/schemas/2.2.4/schema.json",
33
"vcs": {
44
"enabled": true,
55
"clientKind": "git",

bun.lock

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
"": {
55
"name": "@macalinao/coda-monorepo",
66
"devDependencies": {
7-
"@biomejs/biome": "^2.2.3",
8-
"@changesets/cli": "^2.29.6",
7+
"@biomejs/biome": "^2.2.4",
8+
"@changesets/cli": "^2.29.7",
99
"husky": "^9.1.7",
1010
"lint-staged": "^16.1.6",
1111
"turbo": "^2.5.6",
@@ -82,6 +82,21 @@
8282
"@solana/kit": "*",
8383
},
8484
},
85+
"clients/spl-governance": {
86+
"name": "@macalinao/clients-spl-governance",
87+
"version": "0.1.0",
88+
"devDependencies": {
89+
"@macalinao/coda": "workspace:*",
90+
"@macalinao/eslint-config": "catalog:",
91+
"@macalinao/tsconfig": "catalog:",
92+
"@solana/kit": "catalog:",
93+
"eslint": "catalog:",
94+
"typescript": "catalog:",
95+
},
96+
"peerDependencies": {
97+
"@solana/kit": "*",
98+
},
99+
},
85100
"clients/token-metadata": {
86101
"name": "@macalinao/clients-token-metadata",
87102
"version": "0.4.0",
@@ -473,6 +488,8 @@
473488

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

491+
"@macalinao/clients-spl-governance": ["@macalinao/clients-spl-governance@workspace:clients/spl-governance"],
492+
476493
"@macalinao/clients-token-metadata": ["@macalinao/clients-token-metadata@workspace:clients/token-metadata"],
477494

478495
"@macalinao/coda": ["@macalinao/coda@workspace:packages/coda"],
@@ -735,7 +752,7 @@
735752

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

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

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

@@ -887,7 +904,7 @@
887904

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

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

892909
"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=="],
893910

clients/spl-governance/README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# @macalinao/clients-spl-governance
2+
3+
[![npm version](https://img.shields.io/npm/v/@macalinao/clients-spl-governance.svg)](https://www.npmjs.com/package/@macalinao/clients-spl-governance)
4+
5+
TypeScript client for the SPL Governance program, generated using Coda with full ESM support.
6+
7+
IDL changes:
8+
9+
- `RealmConfigArgs` is renamed to `RealmConfigParams`
10+
- `GoverningTokenConfigArgs` is renamed to `GoverningTokenConfigParams`
11+
12+
## Installation
13+
14+
```bash
15+
bun add @macalinao/clients-spl-governance
16+
```
17+
18+
## Development
19+
20+
This client is generated from the SPL Governance IDL using Coda CLI:
21+
22+
```bash
23+
# Generate the client from idls/spl_governance.json
24+
bun run codegen
25+
26+
# Build the TypeScript
27+
bun run build
28+
```
29+
30+
### Configuration
31+
32+
The `coda.config.mjs` file defines custom PDAs for the SPL Governance program, including:
33+
34+
- Realm identified by its name
35+
- Token holding accounts for community and council tokens
36+
- Governance accounts and proposals
37+
- Vote records and signatory records
38+
- Treasury accounts and transaction instructions
39+
40+
## Usage
41+
42+
```typescript
43+
import { createRealm, getRealmPda } from "@macalinao/clients-spl-governance";
44+
45+
// Create a new realm
46+
const realmPda = getRealmPda({ name: "my-dao" });
47+
48+
// Use the generated client functions
49+
```
50+
51+
## License
52+
53+
Copyright © 2025 Ian Macalinao
54+
55+
Licensed under the Apache License, Version 2.0
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
import {
2+
accountValueNode,
3+
addPdasVisitor,
4+
constantPdaSeedNodeFromString,
5+
defineConfig,
6+
numberTypeNode,
7+
pdaLinkNode,
8+
pdaSeedValueNode,
9+
pdaValueNode,
10+
publicKeyTypeNode,
11+
publicKeyValueNode,
12+
setInstructionAccountDefaultValuesVisitor,
13+
stringTypeNode,
14+
updateAccountsVisitor,
15+
variablePdaSeedNode,
16+
} from "@macalinao/coda";
17+
18+
// PDAs are based on the original SPL Governance SDK:
19+
// https://raw.githubusercontent.com/Mythic-Project/governance-sdk/refs/heads/main/src/pda.ts
20+
const addCustomPDAsVisitor = addPdasVisitor({
21+
splGovernance: [
22+
// Realm
23+
{
24+
name: "realm",
25+
docs: ["Realm account identified by its name"],
26+
seeds: [
27+
constantPdaSeedNodeFromString("utf8", "governance"),
28+
variablePdaSeedNode("name", stringTypeNode("utf8")),
29+
],
30+
},
31+
// Community Token Holding
32+
{
33+
name: "communityTokenHolding",
34+
docs: ["Community token holding account of a realm"],
35+
seeds: [
36+
constantPdaSeedNodeFromString("utf8", "governance"),
37+
variablePdaSeedNode("realm", publicKeyTypeNode()),
38+
variablePdaSeedNode("communityMint", publicKeyTypeNode()),
39+
],
40+
},
41+
// Council Token Holding
42+
{
43+
name: "councilTokenHolding",
44+
docs: ["Council token holding account of a realm"],
45+
seeds: [
46+
constantPdaSeedNodeFromString("utf8", "governance"),
47+
variablePdaSeedNode("realm", publicKeyTypeNode()),
48+
variablePdaSeedNode("councilMint", publicKeyTypeNode()),
49+
],
50+
},
51+
// Realm Config
52+
{
53+
name: "realmConfig",
54+
docs: ["Configuration of a realm"],
55+
seeds: [
56+
constantPdaSeedNodeFromString("utf8", "realm-config"),
57+
variablePdaSeedNode("realm", publicKeyTypeNode()),
58+
],
59+
},
60+
// Token Owner Record
61+
{
62+
name: "tokenOwnerRecord",
63+
docs: ["Token owner's record within a realm"],
64+
seeds: [
65+
constantPdaSeedNodeFromString("utf8", "governance"),
66+
variablePdaSeedNode("realm", publicKeyTypeNode()),
67+
variablePdaSeedNode("governingTokenMint", publicKeyTypeNode()),
68+
variablePdaSeedNode("governingTokenOwner", publicKeyTypeNode()),
69+
],
70+
},
71+
// Governing Token Holding
72+
{
73+
name: "governingTokenHolding",
74+
docs: ["Governing token holding account"],
75+
seeds: [
76+
constantPdaSeedNodeFromString("utf8", "governance"),
77+
variablePdaSeedNode("realm", publicKeyTypeNode()),
78+
variablePdaSeedNode("governingTokenMint", publicKeyTypeNode()),
79+
],
80+
},
81+
// Governance
82+
{
83+
name: "governance",
84+
docs: ["Governance account within a realm"],
85+
seeds: [
86+
constantPdaSeedNodeFromString("utf8", "account-governance"),
87+
variablePdaSeedNode("realm", publicKeyTypeNode()),
88+
variablePdaSeedNode("seed", publicKeyTypeNode()),
89+
],
90+
},
91+
// Native Treasury
92+
{
93+
name: "nativeTreasury",
94+
docs: ["Governance's native SOL treasury account"],
95+
seeds: [
96+
constantPdaSeedNodeFromString("utf8", "native-treasury"),
97+
variablePdaSeedNode("governance", publicKeyTypeNode()),
98+
],
99+
},
100+
// Proposal
101+
{
102+
name: "proposal",
103+
docs: ["Governance proposal"],
104+
seeds: [
105+
constantPdaSeedNodeFromString("utf8", "governance"),
106+
variablePdaSeedNode("governance", publicKeyTypeNode()),
107+
variablePdaSeedNode("governingTokenMint", publicKeyTypeNode()),
108+
variablePdaSeedNode("proposalSeed", publicKeyTypeNode()),
109+
],
110+
},
111+
// Proposal Deposit
112+
{
113+
name: "proposalDeposit",
114+
docs: ["Proposal deposit made by a specific payer"],
115+
seeds: [
116+
constantPdaSeedNodeFromString("utf8", "proposal-deposit"),
117+
variablePdaSeedNode("proposal", publicKeyTypeNode()),
118+
variablePdaSeedNode("depositPayer", publicKeyTypeNode()),
119+
],
120+
},
121+
// Signatory Record
122+
{
123+
name: "signatoryRecord",
124+
docs: ["Signatory's record on a proposal"],
125+
seeds: [
126+
constantPdaSeedNodeFromString("utf8", "governance"),
127+
variablePdaSeedNode("proposal", publicKeyTypeNode()),
128+
variablePdaSeedNode("signatory", publicKeyTypeNode()),
129+
],
130+
},
131+
// Proposal Transaction
132+
{
133+
name: "proposalTransaction",
134+
docs: ["Transaction within a proposal option"],
135+
seeds: [
136+
constantPdaSeedNodeFromString("utf8", "governance"),
137+
variablePdaSeedNode("proposal", publicKeyTypeNode()),
138+
variablePdaSeedNode("optionIndex", numberTypeNode("u8")),
139+
variablePdaSeedNode("index", numberTypeNode("u16")),
140+
],
141+
},
142+
// Vote Record
143+
{
144+
name: "voteRecord",
145+
docs: ["Vote record on a proposal"],
146+
seeds: [
147+
constantPdaSeedNodeFromString("utf8", "governance"),
148+
variablePdaSeedNode("proposal", publicKeyTypeNode()),
149+
variablePdaSeedNode("tokenOwnerRecord", publicKeyTypeNode()),
150+
],
151+
},
152+
// Required Signatory
153+
{
154+
name: "requiredSignatory",
155+
docs: ["Required signatory on a governance"],
156+
seeds: [
157+
constantPdaSeedNodeFromString("utf8", "required-signatory"),
158+
variablePdaSeedNode("governance", publicKeyTypeNode()),
159+
variablePdaSeedNode("signatory", publicKeyTypeNode()),
160+
],
161+
},
162+
],
163+
});
164+
165+
export default defineConfig({
166+
outputDir: "./src/generated",
167+
docs: {
168+
npmPackageName: "@macalinao/clients-spl-governance",
169+
},
170+
visitors: [
171+
updateAccountsVisitor({
172+
// Realm accounts
173+
realmV1: {
174+
pda: pdaLinkNode("realm"),
175+
},
176+
realmV2: {
177+
pda: pdaLinkNode("realm"),
178+
},
179+
realmConfigAccount: {
180+
pda: pdaLinkNode("realmConfig"),
181+
},
182+
183+
// Governance accounts
184+
governanceV1: {
185+
pda: pdaLinkNode("governance"),
186+
},
187+
governanceV2: {
188+
pda: pdaLinkNode("governance"),
189+
},
190+
191+
// Proposal accounts
192+
proposalV1: {
193+
pda: pdaLinkNode("proposal"),
194+
},
195+
proposalV2: {
196+
pda: pdaLinkNode("proposal"),
197+
},
198+
proposalDeposit: {
199+
pda: pdaLinkNode("proposalDeposit"),
200+
},
201+
proposalInstructionV1: {
202+
pda: pdaLinkNode("proposalTransaction"),
203+
},
204+
proposalTransactionV2: {
205+
pda: pdaLinkNode("proposalTransaction"),
206+
},
207+
208+
// Token owner records
209+
tokenOwnerRecordV1: {
210+
pda: pdaLinkNode("tokenOwnerRecord"),
211+
},
212+
tokenOwnerRecordV2: {
213+
pda: pdaLinkNode("tokenOwnerRecord"),
214+
},
215+
legacyTokenOwnerRecord: {
216+
pda: pdaLinkNode("tokenOwnerRecord"),
217+
},
218+
219+
// Signatory records
220+
signatoryRecordV1: {
221+
pda: pdaLinkNode("signatoryRecord"),
222+
},
223+
signatoryRecordV2: {
224+
pda: pdaLinkNode("signatoryRecord"),
225+
},
226+
requiredSignatory: {
227+
pda: pdaLinkNode("requiredSignatory"),
228+
},
229+
230+
// Vote records
231+
voteRecordV1: {
232+
pda: pdaLinkNode("voteRecord"),
233+
},
234+
voteRecordV2: {
235+
pda: pdaLinkNode("voteRecord"),
236+
},
237+
}),
238+
239+
setInstructionAccountDefaultValuesVisitor([
240+
{
241+
account: "systemProgram",
242+
defaultValue: publicKeyValueNode("11111111111111111111111111111111"),
243+
},
244+
// Create token owner record defaults
245+
{
246+
account: "tokenOwnerRecord",
247+
instruction: "createTokenOwnerRecord",
248+
defaultValue: pdaValueNode(pdaLinkNode("tokenOwnerRecord"), [
249+
pdaSeedValueNode("realm", accountValueNode("realmAccount")),
250+
pdaSeedValueNode(
251+
"governingTokenMint",
252+
accountValueNode("governingTokenMint"),
253+
),
254+
pdaSeedValueNode(
255+
"governingTokenOwner",
256+
accountValueNode("governingTokenOwnerAccount"),
257+
),
258+
]),
259+
},
260+
]),
261+
addCustomPDAsVisitor,
262+
],
263+
});

0 commit comments

Comments
 (0)