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
7 changes: 7 additions & 0 deletions .changeset/proud-dragons-spend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@macalinao/clients-voter-stake-registry": patch
"@macalinao/coda-visitors": patch
"@macalinao/coda": patch
---

Adds coda-visitors package and clients-voter-stake-registry
41 changes: 41 additions & 0 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,21 @@
"@solana/kit": "*",
},
},
"clients/voter-stake-registry": {
"name": "@macalinao/clients-voter-stake-registry",
"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": "*",
},
},
"packages/coda": {
"name": "@macalinao/coda",
"version": "0.4.0",
Expand All @@ -122,6 +137,7 @@
"dependencies": {
"@codama/nodes-from-anchor": "catalog:",
"@codama/renderers-rust": "^1.2.7",
"@macalinao/coda-visitors": "workspace:*",
"@macalinao/codama-nodes-from-anchor-x": "workspace:*",
"@macalinao/codama-rename-visitor": "workspace:*",
"@macalinao/codama-renderers-js-esm": "workspace:*",
Expand All @@ -138,6 +154,19 @@
"typescript": "catalog:",
},
},
"packages/coda-visitors": {
"name": "@macalinao/coda-visitors",
"version": "0.1.0",
"dependencies": {
"codama": "catalog:",
},
"devDependencies": {
"@macalinao/eslint-config": "catalog:",
"@macalinao/tsconfig": "catalog:",
"eslint": "catalog:",
"typescript": "catalog:",
},
},
"packages/codama-instruction-accounts-dedupe-visitor": {
"name": "@macalinao/codama-instruction-accounts-dedupe-visitor",
"version": "0.4.0",
Expand Down Expand Up @@ -495,8 +524,12 @@

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

"@macalinao/clients-voter-stake-registry": ["@macalinao/clients-voter-stake-registry@workspace:clients/voter-stake-registry"],

"@macalinao/coda": ["@macalinao/coda@workspace:packages/coda"],

"@macalinao/coda-visitors": ["@macalinao/coda-visitors@workspace:packages/coda-visitors"],

"@macalinao/codama-instruction-accounts-dedupe-visitor": ["@macalinao/codama-instruction-accounts-dedupe-visitor@workspace:packages/codama-instruction-accounts-dedupe-visitor"],

"@macalinao/codama-nodes-from-anchor-x": ["@macalinao/codama-nodes-from-anchor-x@workspace:packages/codama-nodes-from-anchor-x"],
Expand Down Expand Up @@ -1929,12 +1962,20 @@

"@isaacs/cliui/wrap-ansi": ["[email protected]", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],

"@macalinao/clients-voter-stake-registry/typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],

"@macalinao/coda/typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],

"@macalinao/coda-visitors/typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],

"@macalinao/codama-instruction-accounts-dedupe-visitor/typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],

"@macalinao/codama-nodes-from-anchor-x/typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],

"@macalinao/codama-rename-visitor/typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],

"@macalinao/codama-renderers-js-esm/typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],

"@macalinao/codama-renderers-markdown/typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],

"@manypkg/find-root/@types/node": ["@types/[email protected]", "", {}, "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="],
Expand Down
65 changes: 65 additions & 0 deletions clients/voter-stake-registry/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# @macalinao/clients-voter-stake-registry

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

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

## Installation

```bash
bun add @macalinao/clients-voter-stake-registry
```

## Development

This client is generated from the Voter Stake Registry IDL using Coda CLI:

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

# Build the TypeScript
bun run build
```

### Configuration

The `coda.config.mjs` file defines custom PDAs for the Voter Stake Registry program, including:

- **Registrar**: The voting registrar account - one per governance realm and governing mint
- **Voter**: Individual voter accounts tied to a registrar and voter authority
- **Voter Weight Record**: The account shown to spl-governance to prove vote weight

## Usage

```typescript
import {
findRegistrarPda,
findVoterPda,
findVoterWeightRecordPda
} from "@macalinao/clients-voter-stake-registry";

// Get the registrar PDA
const registrarPda = await findRegistrarPda({
realm: realmPublicKey,
realmGoverningTokenMint: mintPublicKey,
});

// Get a voter PDA
const voterPda = await findVoterPda({
registrar: registrarPublicKey,
voterAuthority: authorityPublicKey,
});

// Get a voter weight record PDA
const voterWeightRecordPda = await findVoterWeightRecordPda({
registrar: registrarPublicKey,
voterAuthority: authorityPublicKey,
});
```

## License

Copyright © 2025 Ian Macalinao

Licensed under the Apache License, Version 2.0
197 changes: 197 additions & 0 deletions clients/voter-stake-registry/coda.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import {
accountNode,
accountValueNode,
addNodesVisitor,
addPdasVisitor,
bytesTypeNode,
bytesValueNode,
constantPdaSeedNodeFromString,
defineConfig,
fieldDiscriminatorNode,
fixedSizeTypeNode,
numberTypeNode,
optionTypeNode,
payerValueNode,
pdaLinkNode,
pdaSeedValueNode,
pdaValueNode,
publicKeyTypeNode,
publicKeyValueNode,
setInstructionAccountDefaultValuesVisitor,
structFieldTypeNode,
structTypeNode,
updateAccountsVisitor,
variablePdaSeedNode,
} from "@macalinao/coda";

const addCustomPDAsVisitor = addPdasVisitor({
voterStakeRegistry: [
{
name: "registrar",
docs: [
"The voting registrar. There can only be a single registrar",
"per governance realm and governing mint.",
],
seeds: [
variablePdaSeedNode("realm", publicKeyTypeNode()),
constantPdaSeedNodeFromString("utf8", "registrar"),
variablePdaSeedNode("realmGoverningTokenMint", publicKeyTypeNode()),
],
},
{
name: "voter",
docs: [
"The voter account for a given voter authority.",
"Each voter authority has a unique voter account per registrar.",
],
seeds: [
variablePdaSeedNode("registrar", publicKeyTypeNode()),
constantPdaSeedNodeFromString("utf8", "voter"),
variablePdaSeedNode("voterAuthority", publicKeyTypeNode()),
],
},
{
name: "voterWeightRecord",
docs: [
"The voter weight record is the account that will be shown to spl-governance",
"to prove how much vote weight the voter has. See update_voter_weight_record.",
],
seeds: [
variablePdaSeedNode("registrar", publicKeyTypeNode()),
constantPdaSeedNodeFromString("utf8", "voter-weight-record"),
variablePdaSeedNode("voterAuthority", publicKeyTypeNode()),
],
},
],
});

export default defineConfig({
outputDir: "./src/generated",
docs: {
npmPackageName: "@macalinao/clients-voter-stake-registry",
},
visitors: [
addNodesVisitor({
voterStakeRegistry: {
accounts: [
// See: https://github.com/Mythic-Project/oyster/blob/main/packages/governance-sdk/src/addins/serialisation.ts
accountNode({
name: "voterWeightRecord",
discriminators: [fieldDiscriminatorNode("discriminator", 0)],
data: structTypeNode([
structFieldTypeNode({
name: "discriminator",
defaultValueStrategy: "omitted",
type: fixedSizeTypeNode(bytesTypeNode(), 8),
defaultValue: bytesValueNode("base16", "3265663939623462"),
}),
structFieldTypeNode({
name: "realm",
type: publicKeyTypeNode(),
}),
structFieldTypeNode({
name: "governingTokenMint",
type: publicKeyTypeNode(),
}),
structFieldTypeNode({
name: "governingTokenOwner",
type: publicKeyTypeNode(),
}),
structFieldTypeNode({
name: "voterWeight",
type: numberTypeNode("u64"),
}),
structFieldTypeNode({
name: "voterWeightExpiry",
type: optionTypeNode(numberTypeNode("u64")),
}),
structFieldTypeNode({
name: "weightAction",
type: optionTypeNode(numberTypeNode("u8")),
}),
structFieldTypeNode({
name: "weightActionTarget",
type: optionTypeNode(publicKeyTypeNode()),
}),
// ['realm', 'pubkey'],
// ['governingTokenMint', 'pubkey'],
// ['governingTokenOwner', 'pubkey'],
// ['voterWeight', 'u64'],
// ['voterWeightExpiry', { kind: 'option', type: 'u64' }],
// ['weightAction', { kind: 'option', type: 'u8' }],
// ['weightActionTarget', { kind: 'option', type: 'pubkey' }],
]),
pda: pdaLinkNode("voterWeightRecord"),
}),
],
},
}),
updateAccountsVisitor({
registrar: {
pda: pdaLinkNode("registrar"),
},
voter: {
pda: pdaLinkNode("voter"),
},
}),

setInstructionAccountDefaultValuesVisitor([
{
account: "tokenProgram",
defaultValue: publicKeyValueNode(
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
),
},
{
account: "associatedTokenProgram",
defaultValue: publicKeyValueNode(
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL",
),
},
{
account: "systemProgram",
defaultValue: publicKeyValueNode("11111111111111111111111111111111"),
},
{
account: "rent",
defaultValue: publicKeyValueNode(
"SysvarRent111111111111111111111111111111111",
),
},
{
account: "instructions",
defaultValue: publicKeyValueNode(
"Sysvar1nstructions1111111111111111111111111",
),
},
{
account: "payer",
defaultValue: payerValueNode(),
},

{
account: "voter",
instruction: "createVoter",
defaultValue: pdaValueNode(pdaLinkNode("voter"), [
pdaSeedValueNode("registrar", accountValueNode("registrar")),
pdaSeedValueNode(
"voterAuthority",
accountValueNode("voterAuthority"),
),
]),
},
{
account: "voterWeightRecord",
instruction: "createVoter",
defaultValue: pdaValueNode(pdaLinkNode("voterWeightRecord"), [
pdaSeedValueNode("registrar", accountValueNode("registrar")),
pdaSeedValueNode(
"voterAuthority",
accountValueNode("voterAuthority"),
),
]),
},
]),
addCustomPDAsVisitor,
],
});
31 changes: 31 additions & 0 deletions clients/voter-stake-registry/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { configs } from "@macalinao/eslint-config";

export default [
...configs.fast,
{
languageOptions: {
parserOptions: {
tsconfigRootDir: import.meta.dirname,
},
},
},
{
files: ["src/generated/**/*.ts"],
rules: {
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/prefer-nullish-coalescing": "off",
},
},
{
files: [
"src/generated/instructions/*.ts",
"src/generated/types/*.ts",
"src/generated/errors/*.ts",
],
rules: {
"@typescript-eslint/no-unnecessary-condition": "off",
"no-constant-condition": "off",
"@typescript-eslint/no-empty-object-type": "off",
},
},
];
Loading