Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add SPL-Governance Treasury Operations #184

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
48 changes: 48 additions & 0 deletions src/agent/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ import {
fetchPythPriceFeedID,
flashOpenTrade,
flashCloseTrade,
monitor_treasury_balances,
proposeTransaction,
executeApprovedTreasuryActions,
} from "../tools";
import {
CollectionDeployment,
Expand Down Expand Up @@ -655,4 +658,49 @@ export class SolanaAgentKit {
): Promise<string> {
return execute_transaction(this, transactionIndex);
}

async monitorTreasuryBalances(governancePubkey: PublicKey): Promise<string> {
const res = monitor_treasury_balances(this, governancePubkey);
return JSON.stringify(res);
}
async proposeTransaction(
realmId: PublicKey,
governanceId: PublicKey,
name: string,
descriptionLink: string,
options: string[],
voteType: string,
choiceType: string = "FullWeight",
useDenyOption: boolean = true,
): Promise<string> {
const res = proposeTransaction(
this,
realmId.toString(),
governanceId.toString(),
name,
descriptionLink,
options,
voteType,
choiceType,
useDenyOption,
);
return res.toString();
}

async executeApprovedTreasuryActions(
realmId: string,
governanceId: string,
proposalId: string,
transactionAddress: string,
transactionInstructions: any[],
): Promise<string> {
return executeApprovedTreasuryActions(
this,
realmId,
governanceId,
proposalId,
transactionAddress,
transactionInstructions,
);
}
}
139 changes: 139 additions & 0 deletions src/langchain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2688,6 +2688,142 @@ export class SolanaExecuteProposal2by2Multisig extends Tool {
}
}

export class SolanaProposeTransactionTool extends Tool {
name = "propose_transaction";
description = `Propose a transaction in a Solana DAO governance program.

Inputs (JSON string):
- realmId: string, the public key of the realm.
- governanceId: string, the public key of the governance account.
- name: string, the name of the proposal.
- descriptionLink: string, a link to the description of the proposal.
- options: string[], the options for the proposal (e.g., "yes", "no").
- voteType: string, the type of vote ("single" or "multi").
- choiceType: string, for multi-choice votes ("FullWeight" or "Weighted").
- useDenyOption: boolean (optional), whether to include a deny option.`;

constructor(private solanaKit: SolanaAgentKit) {
super();
}

protected async _call(input: string): Promise<string> {
try {
const {
realmId,
governanceId,
name,
descriptionLink,
options,
voteType,
choiceType,
useDenyOption,
} = JSON.parse(input);

const proposalPublicKey = await this.solanaKit.proposeTransaction(
realmId,
governanceId,
name,
descriptionLink,
options,
voteType,
choiceType,
useDenyOption,
);

return JSON.stringify({
status: "success",
proposalPublicKey,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "PROPOSE_TRANSACTION_ERROR",
});
}
}
}

export class SolanaMonitorTreasuryBalancesTool extends Tool {
name = "monitor_treasury_balances";
description = `Monitor the balances of treasuries associated with a Solana governance program.

Inputs (JSON string):
- governancePubkey: string, the public key of the governance account.`;

constructor(private solanaKit: SolanaAgentKit) {
super();
}

protected async _call(input: string): Promise<string> {
try {
const { governancePubkey } = JSON.parse(input);

const balances = await this.solanaKit.monitorTreasuryBalances(
new PublicKey(governancePubkey),
);

return JSON.stringify({
status: "success",
balances,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "MONITOR_TREASURY_BALANCES_ERROR",
});
}
}
}

export class SolanaExecuteApprovedTreasuryActionsTool extends Tool {
name = "execute_approved_treasury_actions";
description = `Execute an approved transaction from a proposal in a Solana DAO governance program.

Inputs (JSON string):
- realmId: string, the public key of the realm.
- governanceId: string, the public key of the governance account.
- proposalId: string, the public key of the proposal.
- transactionAddress: string, the public key of the transaction.
- transactionInstructions: InstructionData[], the instructions for the transaction.`;

constructor(private solanaKit: SolanaAgentKit) {
super();
}

protected async _call(input: string): Promise<string> {
try {
const {
realmId,
governanceId,
proposalId,
transactionAddress,
transactionInstructions,
} = JSON.parse(input);

const signature = await this.solanaKit.executeApprovedTreasuryActions(
realmId,
governanceId,
proposalId,
transactionAddress,
transactionInstructions,
);

return JSON.stringify({
status: "success",
signature,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "EXECUTE_APPROVED_TREASURY_ACTIONS_ERROR",
});
}
}
}

export function createSolanaTools(solanaKit: SolanaAgentKit) {
return [
new SolanaBalanceTool(solanaKit),
Expand Down Expand Up @@ -2755,5 +2891,8 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
new SolanaApproveProposal2by2Multisig(solanaKit),
new SolanaRejectProposal2by2Multisig(solanaKit),
new SolanaExecuteProposal2by2Multisig(solanaKit),
new SolanaProposeTransactionTool(solanaKit),
new SolanaMonitorTreasuryBalancesTool(solanaKit),
new SolanaExecuteApprovedTreasuryActionsTool(solanaKit),
];
}
76 changes: 76 additions & 0 deletions src/tools/execute_Approved_Treasury_Actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {
PublicKey,
Transaction,
sendAndConfirmTransaction,
TransactionInstruction,
} from "@solana/web3.js";
import {
InstructionData,
withExecuteTransaction,
getGovernanceProgramVersion,
} from "@solana/spl-governance";
import { SolanaAgentKit } from "../agent";

/**
* Execute a transaction from an approved proposal.
*
* @param agent The SolanaAgentKit instance.
* @param realmId The public key of the realm as a string.
* @param governanceId The public key of the governance as a string.
* @param proposalId The public key of the proposal as a string.
* @param transactionAddress The public key of the transaction as a string.
* @param transactionInstructions The instructions of the transaction.
* @returns The signature of the transaction.
*/
export async function executeApprovedTreasuryActions(
agent: SolanaAgentKit,
realmId: string,
governanceId: string,
proposalId: string,
transactionAddress: string,
transactionInstructions: InstructionData[],
): Promise<string> {
const connection = agent.connection;
const realmPublicKey = new PublicKey(realmId);
const governancePublicKey = new PublicKey(governanceId);
const proposalPublicKey = new PublicKey(proposalId);
const transactionPublicKey = new PublicKey(transactionAddress);
const governanceProgramId = new PublicKey(
"GovER5Lthms3bLBqWub97yVrMmEogzX7xNjdXpPPCVZw",
);

try {
// Fetch the program version
const programVersion = await getGovernanceProgramVersion(
connection,
governanceProgramId,
);

// Prepare transaction and instructions
const transaction = new Transaction();
const instructions: TransactionInstruction[] = [];

await withExecuteTransaction(
instructions,
governanceProgramId,
programVersion,
governancePublicKey,
proposalPublicKey,
transactionPublicKey,
transactionInstructions,
);

transaction.add(...instructions);

// Send and confirm the transaction
const signature = await sendAndConfirmTransaction(connection, transaction, [
agent.wallet,
]);

return signature;
} catch (error: any) {
throw new Error(
`Failed to execute approved treasury actions: ${error.message}`,
);
}
}
3 changes: 3 additions & 0 deletions src/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,6 @@ export * from "./flash_open_trade";
export * from "./flash_close_trade";

export * from "./create_3land_collectible";
export * from "./execute_Approved_Treasury_Actions";
export * from "./monitor_treasury_balances";
export * from "./propose_transaction";
78 changes: 78 additions & 0 deletions src/tools/monitor_treasury_balances.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { PublicKey } from "@solana/web3.js";
import {
getGovernanceAccounts,
getNativeTreasuryAddress,
Governance,
} from "@solana/spl-governance";
import { SolanaAgentKit } from "../agent";

/**
* Monitors the balances of all treasuries associated with a governance.
*
* @param agent The {@link SolanaAgentKit} instance.
* @param governancePubkey The public key of the governance.
* @returns The balances of all treasuries associated with the governance.
* Each balance object contains the `account` public key, the `solBalance` in SOL,
* and the `splTokens` balance of SPL tokens associated with the treasury.
* The `splTokens` property is an array of objects with `mint` and `balance` properties.
* The `mint` property is the mint address of the SPL token, and the `balance` property is the balance of the SPL token in UI units.
*/
export async function monitor_treasury_balances(
agent: SolanaAgentKit,
governancePubkey: PublicKey,
): Promise<
{
account: PublicKey;
solBalance: number;
splTokens: { mint: string; balance: number }[];
}[]
> {
try {
const connection = agent.connection;
// Fetch all governance accounts
const governanceAccounts = await getGovernanceAccounts(
connection,
governancePubkey,
Governance,
);

const balances = [];

// Iterate over governance accounts to fetch treasury balances
for (const governance of governanceAccounts) {
// Compute the native treasury address
const treasuryAddress = await getNativeTreasuryAddress(
governancePubkey,
governance.pubkey,
);

// Fetch the SOL balance of the treasury
const solBalance = (await connection.getBalance(treasuryAddress)) / 1e9;

// Fetch SPL token balances
const tokenAccounts = await connection.getParsedTokenAccountsByOwner(
treasuryAddress,
{
programId: new PublicKey(
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
),
},
);

const splTokens = tokenAccounts.value.map((tokenAccount) => {
const tokenAmount = tokenAccount.account.data.parsed.info.tokenAmount;
return {
mint: tokenAccount.account.data.parsed.info.mint,
balance: tokenAmount.uiAmount,
};
});

balances.push({ account: treasuryAddress, solBalance, splTokens });
}

return balances;
} catch (error) {
console.error("Failed to monitor treasury balances:", error);
throw error;
}
}
Loading