From 8ce23f34ca94f7add91a990f1c93a1b9536395cb Mon Sep 17 00:00:00 2001 From: mac Date: Wed, 26 Feb 2025 01:21:51 +0530 Subject: [PATCH 1/5] everything is done --- package.json | 1 + pnpm-lock.yaml | 16 ++ src/actions/governance/council.ts | 332 +++++++++++++++++++++++++++ src/actions/governance/governance.ts | 186 +++++++++++++++ src/actions/governance/monitor.ts | 102 ++++++++ src/agent/index.ts | 180 ++++++++++++++- src/langchain/index.ts | 12 + src/tools/governance/governance.ts | 201 ++++++++++++++++ src/types/index.ts | 47 +++- 9 files changed, 1061 insertions(+), 16 deletions(-) create mode 100644 src/actions/governance/council.ts create mode 100644 src/actions/governance/governance.ts create mode 100644 src/actions/governance/monitor.ts create mode 100644 src/tools/governance/governance.ts diff --git a/package.json b/package.json index 2b431b4b..3501d86a 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "ethers": "^6.13.5", "flash-sdk": "^2.24.3", "form-data": "^4.0.1", + "governance-idl-sdk": "^0.0.4", "langchain": "^0.3.8", "openai": "^4.77.0", "tiktoken": "^1.0.18", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bfe78d3f..3dca43ca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -167,6 +167,9 @@ importers: form-data: specifier: ^4.0.1 version: 4.0.1 + governance-idl-sdk: + specifier: ^0.0.4 + version: 0.0.4(bufferutil@4.0.9)(utf-8-validate@5.0.10) langchain: specifier: ^0.3.8 version: 0.3.9(@langchain/core@0.3.27(openai@4.77.3(zod@3.24.1)))(@langchain/groq@0.1.2(@langchain/core@0.3.27(openai@4.77.3(zod@3.24.1))))(axios@1.7.9)(openai@4.77.3(zod@3.24.1)) @@ -2963,6 +2966,9 @@ packages: resolution: {integrity: sha512-1qd54GLxvVgzuidFmw9ze9umxS3rzhdBH6Wt6BTYrTQUXTN01vGGYXwzLzYLowNx8HBH3/c7kRyvx90fh13i7Q==} engines: {node: '>=0.10.0 <7'} + governance-idl-sdk@0.0.4: + resolution: {integrity: sha512-90B5lZBxEnraiK74jHWIYbMec7Y0aQEyPz/MF7KeRCGc2ImcIa6xwWvscVxyZhtU7dys9FBJaUN/EZj9TET32Q==} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -9922,6 +9928,16 @@ snapshots: unzip-response: 1.0.2 url-parse-lax: 1.0.0 + governance-idl-sdk@0.0.4(bufferutil@4.0.9)(utf-8-validate@5.0.10): + dependencies: + '@coral-xyz/anchor': 0.29.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@solana/web3.js': 1.98.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + bn.js: 5.2.1 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + graceful-fs@4.2.11: optional: true diff --git a/src/actions/governance/council.ts b/src/actions/governance/council.ts new file mode 100644 index 00000000..7b9a5031 --- /dev/null +++ b/src/actions/governance/council.ts @@ -0,0 +1,332 @@ +import { PublicKey, Transaction } from "@solana/web3.js"; +import { SplGovernance } from "governance-idl-sdk"; +import { BN } from "@coral-xyz/anchor"; +import { SolanaAgentKit } from "../../agent"; + +export interface CouncilConfig { + minTokensToCreateGovernance: number; + minVotingThreshold: number; + minTransactionHoldUpTime: number; + maxVotingTime: number; + voteTipping: "strict" | "early" | "disabled"; +} + +export interface CouncilMemberConfig { + member: PublicKey; + tokenAmount: number; +} + +export async function configureCouncilSettings( + agent: SolanaAgentKit, + realm: PublicKey, + config: CouncilConfig, +): Promise { + const splGovernance = new SplGovernance(agent.connection); + + // Get the governance account for the realm + const governanceAccounts = + await splGovernance.getGovernanceAccountsByRealm(realm); + + if (!governanceAccounts.length) { + throw new Error("No governance account found for realm"); + } + + const governance = governanceAccounts[0]; + + // Create config instruction + const configureIx = + await splGovernance.program.instruction.setGovernanceConfig( + { + communityVoteThreshold: { value: new BN(config.minVotingThreshold) }, + minCommunityWeightToCreateProposal: new BN( + config.minTokensToCreateGovernance, + ), + minTransactionHoldUpTime: new BN(config.minTransactionHoldUpTime), + maxVotingTime: new BN(config.maxVotingTime), + votingBaseTime: new BN(0), + votingCoolOffTime: new BN(0), + depositExemptProposalCount: 0, + communityVoteTipping: config.voteTipping, + councilVoteTipping: config.voteTipping, + minCouncilWeightToCreateProposal: new BN(1), + councilVoteThreshold: { value: new BN(1) }, + councilVetoVoteThreshold: { value: new BN(0) }, + communityVetoVoteThreshold: { value: new BN(0) }, + }, + { + accounts: { + governanceAccount: governance.publicKey, + }, + }, + ); + + const transaction = new Transaction().add(configureIx); + const signature = await agent.connection.sendTransaction(transaction, [ + agent.wallet, + ]); + + return signature; +} + +export async function addCouncilMember( + agent: SolanaAgentKit, + realm: PublicKey, + councilMint: PublicKey, + memberConfig: CouncilMemberConfig, +): Promise { + const splGovernance = new SplGovernance(agent.connection); + + // Get all realm configs + const allRealmConfigs = await splGovernance.getAllRealmConfigs(); + const realmConfig = allRealmConfigs.find((config: { realm: PublicKey }) => + config.realm.equals(realm), + ); + if (!realmConfig) { + throw new Error("Realm config not found"); + } + + // Get token owner record + const tokenOwnerRecords = await splGovernance.getTokenOwnerRecordsForOwner( + memberConfig.member, + ); + const tokenOwnerRecord = tokenOwnerRecords.find( + (record) => + record.realm.equals(realm) && + record.governingTokenMint.equals(councilMint), + ); + + if (!tokenOwnerRecord) { + throw new Error("Token owner record not found"); + } + + // Create mint tokens instruction for council member + const mintTokensIx = + await splGovernance.program.instruction.depositGoverningTokens( + new BN(memberConfig.tokenAmount), + { + accounts: { + realmAccount: realm, + realmConfigAccount: realmConfig.publicKey, + governingTokenHoldingAccount: councilMint, + governingTokenOwnerAccount: memberConfig.member, + governingTokenSourceAccount: agent.wallet.publicKey, + governingTokenSourceAccountAuthority: agent.wallet.publicKey, + tokenOwnerRecord: tokenOwnerRecord.publicKey, + payer: agent.wallet.publicKey, + tokenProgram: new PublicKey( + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + ), + systemProgram: new PublicKey("11111111111111111111111111111111"), + }, + }, + ); + + const transaction = new Transaction().add(mintTokensIx); + const signature = await agent.connection.sendTransaction(transaction, [ + agent.wallet, + ]); + + return signature; +} + +export async function removeCouncilMember( + agent: SolanaAgentKit, + realm: PublicKey, + councilMint: PublicKey, + member: PublicKey, +): Promise { + const splGovernance = new SplGovernance(agent.connection); + + // Get all realm configs + const allRealmConfigs = await splGovernance.getAllRealmConfigs(); + const realmConfigForBurn = allRealmConfigs.find( + (config: { realm: PublicKey }) => config.realm.equals(realm), + ); + if (!realmConfigForBurn) { + throw new Error("Realm config not found"); + } + + // Get token owner record for the member + const tokenOwnerRecords = + await splGovernance.getTokenOwnerRecordsForOwner(member); + const councilRecord = tokenOwnerRecords.find( + (record) => + record.realm.equals(realm) && + record.governingTokenMint.equals(councilMint), + ); + + if (!councilRecord) { + throw new Error("Council member record not found"); + } + + // Create withdraw tokens instruction + const withdrawTokensIx = + await splGovernance.program.instruction.withdrawGoverningTokens( + councilRecord.governingTokenDepositAmount, + { + accounts: { + realmAccount: realm, + realmConfigAccount: realmConfigForBurn.publicKey, + governingTokenHoldingAccount: councilMint, + governingTokenOwnerAccount: member, + governingTokenDestinationAccount: agent.wallet.publicKey, + tokenOwnerRecord: councilRecord.publicKey, + tokenProgram: new PublicKey( + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + ), + }, + }, + ); + + const transaction = new Transaction().add(withdrawTokensIx); + const signature = await agent.connection.sendTransaction(transaction, [ + agent.wallet, + ]); + + return signature; +} + +export async function updateCouncilMemberWeight( + agent: SolanaAgentKit, + realm: PublicKey, + councilMint: PublicKey, + memberConfig: CouncilMemberConfig, +): Promise { + const splGovernance = new SplGovernance(agent.connection); + + // Get current token owner record + const tokenOwnerRecords = await splGovernance.getTokenOwnerRecordsForOwner( + memberConfig.member, + ); + + const councilRecord = tokenOwnerRecords.find( + (record) => + record.realm.equals(realm) && + record.governingTokenMint.equals(councilMint), + ); + + if (!councilRecord) { + throw new Error("Council member record not found"); + } + + const currentAmount = councilRecord.governingTokenDepositAmount; + const targetAmount = new BN(memberConfig.tokenAmount); + + const transaction = new Transaction(); + + if (targetAmount.gt(currentAmount)) { + // Mint additional tokens + const mintAmount = targetAmount.sub(currentAmount); + + // Get realm config + const allRealmConfigs = await splGovernance.getAllRealmConfigs(); + const realmConfig = allRealmConfigs.find((config: { realm: PublicKey }) => + config.realm.equals(realm), + ); + if (!realmConfig) { + throw new Error("Realm config not found"); + } + + // Get token owner record + const tokenOwnerRecords = await splGovernance.getTokenOwnerRecordsForOwner( + memberConfig.member, + ); + const tokenOwnerRecord = tokenOwnerRecords.find( + (record) => + record.realm.equals(realm) && + record.governingTokenMint.equals(councilMint), + ); + + if (!tokenOwnerRecord) { + throw new Error("Token owner record not found"); + } + + // Get realm config account + const realmConfigPda = splGovernance.pda.realmConfigAccount({ + realmAccount: realm, + }).publicKey; + + // Get token owner record + const tokenOwnerRecordPda = splGovernance.pda.tokenOwnerRecordAccount({ + realmAccount: realm, + governingTokenMintAccount: councilMint, + governingTokenOwner: memberConfig.member, + }).publicKey; + + // Create mint tokens instruction + const mintTokensIx = + await splGovernance.program.instruction.depositGoverningTokens( + mintAmount, + { + accounts: { + realmAccount: realm, + realmConfigAccount: realmConfigPda, + governingTokenHoldingAccount: councilMint, + governingTokenOwnerAccount: memberConfig.member, + governingTokenSourceAccount: agent.wallet.publicKey, + governingTokenSourceAccountAuthority: agent.wallet.publicKey, + tokenOwnerRecord: tokenOwnerRecordPda, + payer: agent.wallet.publicKey, + tokenProgram: new PublicKey( + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + ), + systemProgram: new PublicKey("11111111111111111111111111111111"), + }, + }, + ); + transaction.add(mintTokensIx); + } else if (targetAmount.lt(currentAmount)) { + // Burn excess tokens + const burnAmount = currentAmount.sub(targetAmount); + + // Get realm config + const allRealmConfigs = await splGovernance.getAllRealmConfigs(); + const realmConfig = allRealmConfigs.find((config: { realm: PublicKey }) => + config.realm.equals(realm), + ); + if (!realmConfig) { + throw new Error("Realm config not found"); + } + + // Get token owner record + const tokenOwnerRecords = await splGovernance.getTokenOwnerRecordsForOwner( + memberConfig.member, + ); + const tokenOwnerRecord = tokenOwnerRecords.find( + (record) => + record.realm.equals(realm) && + record.governingTokenMint.equals(councilMint), + ); + + if (!tokenOwnerRecord) { + throw new Error("Token owner record not found"); + } + + const burnTokensIx = + await splGovernance.program.instruction.withdrawGoverningTokens( + burnAmount, + { + accounts: { + realmAccount: realm, + realmConfigAccount: realmConfig.publicKey, + governingTokenHoldingAccount: councilMint, + governingTokenOwnerAccount: memberConfig.member, + governingTokenDestinationAccount: agent.wallet.publicKey, + tokenOwnerRecord: tokenOwnerRecord.publicKey, + tokenProgram: new PublicKey( + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + ), + }, + }, + ); + transaction.add(burnTokensIx); + } else { + throw new Error("No weight change needed"); + } + + const signature = await agent.connection.sendTransaction(transaction, [ + agent.wallet, + ]); + + return signature; +} \ No newline at end of file diff --git a/src/actions/governance/governance.ts b/src/actions/governance/governance.ts new file mode 100644 index 00000000..b0f24ace --- /dev/null +++ b/src/actions/governance/governance.ts @@ -0,0 +1,186 @@ +import { Connection, PublicKey, Transaction } from "@solana/web3.js"; +import { SplGovernance } from "governance-idl-sdk"; +import { BN } from "@coral-xyz/anchor"; +import { + RealmConfig, + ProposalConfig, + VoteConfig, + VoteType, + Vote, + VoteChoice, +} from "../../types"; +import { SolanaAgentKit } from "../../agent"; + +export async function createNewRealm( + agent: SolanaAgentKit, + config: RealmConfig, +): Promise { + const splGovernance = new SplGovernance(agent.connection); + + const createRealmIx = await splGovernance.createRealmInstruction( + config.name, + config.communityMint, + config.minCommunityTokensToCreateGovernance, + agent.wallet.publicKey, + undefined, // communityMintMaxVoterWeightSource + config.councilMint, + config.communityTokenConfig?.tokenType || "liquid", + "membership", // councilTokenType + ); + + const transaction = new Transaction().add(createRealmIx); + const signature = await agent.connection.sendTransaction(transaction, [ + agent.wallet, + ]); + + return new PublicKey(signature); +} + +export async function createNewProposal( + agent: SolanaAgentKit, + realm: PublicKey, + config: ProposalConfig, +): Promise { + const splGovernance = new SplGovernance(agent.connection); + + // Get the governance account for the realm + const governanceAccounts = + await splGovernance.getGovernanceAccountsByRealm(realm); + if (!governanceAccounts.length) { + throw new Error("No governance account found for realm"); + } + + const governance = governanceAccounts[0]; + + // Get token owner record + const tokenOwnerRecords = await splGovernance.getTokenOwnerRecordsForOwner( + agent.wallet.publicKey, + ); + const tokenOwnerRecord = tokenOwnerRecords.find( + (record) => + record.realm.equals(realm) && + record.governingTokenMint.equals(config.governingTokenMint), + ); + + if (!tokenOwnerRecord) { + throw new Error("Token owner record not found"); + } + + const voteType: VoteType = { + choiceType: config.voteType === "single-choice" ? "single" : "multi", + multiChoiceOptions: + config.voteType === "multiple-choice" + ? { + choiceType: "fullWeight", + minVoterOptions: 1, + maxVoterOptions: config.options.length, + maxWinningOptions: 1, + } + : null, + }; + + const createProposalIx = await splGovernance.createProposalInstruction( + config.name, + config.description, + voteType, + [config.options[0]], // Only support single option for now + true, // useDenyOption + realm, + governance.publicKey, + tokenOwnerRecord.publicKey, + config.governingTokenMint, + agent.wallet.publicKey, + agent.wallet.publicKey, // payer + ); + + const transaction = new Transaction().add(createProposalIx); + const signature = await agent.connection.sendTransaction(transaction, [ + agent.wallet, + ]); + + return new PublicKey(signature); +} + +export async function castVoteOnProposal( + agent: SolanaAgentKit, + config: VoteConfig, +): Promise { + const splGovernance = new SplGovernance(agent.connection); + + // Get token owner record + const tokenOwnerRecords = await splGovernance.getTokenOwnerRecordsForOwner( + config.tokenOwner || agent.wallet.publicKey, + ); + const tokenOwnerRecord = tokenOwnerRecords.find( + (record) => + record.realm.equals(config.realm) && + record.governingTokenMint.equals(config.governingTokenMint), + ); + + if (!tokenOwnerRecord) { + throw new Error("Token owner record not found"); + } + + const proposal = await splGovernance.getProposalByPubkey(config.proposal); + if (!proposal) { + throw new Error("Proposal not found"); + } + + const voteChoice: VoteChoice = { rank: 0, weightPercentage: 100 }; + const vote = { + kind: "enum", + variant: + config.choice === 0 + ? "Approve" + : config.choice === 1 + ? "Deny" + : "Abstain", + fields: config.choice === 0 ? [[voteChoice]] : [], + } as unknown as Vote; + + const castVoteIx = await splGovernance.castVoteInstruction( + vote, + config.realm, + config.governance, + config.proposal, + tokenOwnerRecord.publicKey, // proposalOwnerTokenOwnerRecord + tokenOwnerRecord.publicKey, // voterTokenOwnerRecord + agent.wallet.publicKey, // governanceAuthority + config.governingTokenMint, + agent.wallet.publicKey, // payer + ); + + const transaction = new Transaction().add(castVoteIx); + const signature = await agent.connection.sendTransaction(transaction, [ + agent.wallet, + ]); + + return signature; +} + +export async function getRealmInfo(agent: SolanaAgentKit, realmPk: PublicKey) { + const splGovernance = new SplGovernance(agent.connection); + const realm = await splGovernance.getRealmByPubkey(realmPk); + return realm; +} + +export async function getTokenOwnerRecord( + agent: SolanaAgentKit, + realm: PublicKey, + governingTokenMint: PublicKey, + governingTokenOwner: PublicKey, +) { + const splGovernance = new SplGovernance(agent.connection); + const records = + await splGovernance.getTokenOwnerRecordsForOwner(governingTokenOwner); + return records.find( + (record) => + record.realm.equals(realm) && + record.governingTokenMint.equals(governingTokenMint), + ); +} + +export async function getVoterHistory(agent: SolanaAgentKit, voter: PublicKey) { + const splGovernance = new SplGovernance(agent.connection); + return await splGovernance.getVoteRecordsForUser(voter); +} \ No newline at end of file diff --git a/src/actions/governance/monitor.ts b/src/actions/governance/monitor.ts new file mode 100644 index 00000000..a72415aa --- /dev/null +++ b/src/actions/governance/monitor.ts @@ -0,0 +1,102 @@ +import { Connection, PublicKey } from "@solana/web3.js"; +import { SplGovernance } from "governance-idl-sdk"; +import type { TokenOwnerRecord } from "governance-idl-sdk"; +import { SolanaAgentKit } from "../../agent"; + +export type MembershipChangeCallback = ( + tokenOwnerRecord: TokenOwnerRecord, + isNew: boolean, +) => void; + +export type VotingPowerChangeCallback = ( + owner: PublicKey, + oldBalance: number, + newBalance: number, +) => void; + +export class GovernanceMonitor { + private membershipSubscriptionId?: number | undefined; + private votingPowerSubscriptionId?: number | undefined; + private lastKnownBalances: Map = new Map(); + private splGovernance: SplGovernance; + + constructor( + private agent: SolanaAgentKit, + private realm: PublicKey, + private governingTokenMint: PublicKey, + ) { + this.splGovernance = new SplGovernance(agent.connection); + } + + async monitorMembershipChanges( + callback: MembershipChangeCallback, + ): Promise { + this.membershipSubscriptionId = + this.agent.connection.onProgramAccountChange( + new PublicKey("GovER5Lthms3bLBqWub97yVrMmEogzX7xNjdXpPPCVZw"), + async (accountInfo) => { + try { + const records = + await this.splGovernance.getTokenOwnerRecordsForOwner( + new PublicKey(accountInfo.accountInfo.owner), + ); + + const tokenOwnerRecord = records.find( + (record) => + record.realm.equals(this.realm) && + record.governingTokenMint.equals(this.governingTokenMint), + ); + + if (tokenOwnerRecord) { + const isNew = + accountInfo.accountInfo.lamports > 0 && + accountInfo.accountInfo.data.length === 0; + callback(tokenOwnerRecord, isNew); + } + } catch (error) { + console.error("Error processing membership change:", error); + } + }, + ); + } + + async monitorVotingPowerChanges( + callback: VotingPowerChangeCallback, + ): Promise { + this.votingPowerSubscriptionId = + this.agent.connection.onProgramAccountChange( + this.governingTokenMint, + async (accountInfo) => { + try { + const owner = new PublicKey(accountInfo.accountInfo.owner); + const tokenBalance = accountInfo.accountInfo.lamports; + const oldBalance = + this.lastKnownBalances.get(owner.toBase58()) || 0; + + if (tokenBalance !== oldBalance) { + callback(owner, oldBalance, tokenBalance); + this.lastKnownBalances.set(owner.toBase58(), tokenBalance); + } + } catch (error) { + console.error("Error processing voting power change:", error); + } + }, + ); + } + + async stopMonitoring(): Promise { + if (this.membershipSubscriptionId !== undefined) { + await this.agent.connection.removeAccountChangeListener( + this.membershipSubscriptionId, + ); + this.membershipSubscriptionId = undefined; + } + + if (this.votingPowerSubscriptionId !== undefined) { + await this.agent.connection.removeAccountChangeListener( + this.votingPowerSubscriptionId, + ); + this.votingPowerSubscriptionId = undefined; + } + } +} \ No newline at end of file diff --git a/src/agent/index.ts b/src/agent/index.ts index 09beb9b7..9342e6b4 100644 --- a/src/agent/index.ts +++ b/src/agent/index.ts @@ -7,6 +7,12 @@ import { CreateSingleOptions, StoreInitOptions, } from "@3land/listings-sdk/dist/types/implementation/implementationTypes"; +import { SplGovernance } from "governance-idl-sdk"; +import type { + TokenOwnerRecord, + VoteRecord, + RealmV2 as Realm, +} from "governance-idl-sdk"; import { DEFAULT_OPTIONS } from "../constants"; import { deploy_collection, @@ -162,8 +168,11 @@ import { deBridgeOrderResponse, deBridgeOrderStatusResponse, deBridgeTokensInfoResponse, - SplAuthorityInput, + VoteConfig, + ProposalConfig, + RealmConfig } from "../types"; + import { DasApiAsset, DasApiAssetList, @@ -181,6 +190,31 @@ import { getSmartTwitterAccountStats, } from "../tools/elfa_ai"; +import { + createNewRealm, + createNewProposal, + castVoteOnProposal, + getRealmInfo, + getTokenOwnerRecord, + getVoterHistory, +} from "../actions/governance/governance"; + +import { + GovernanceMonitor, + MembershipChangeCallback, + VotingPowerChangeCallback, +} from "../actions/governance/monitor"; +import { + configureCouncilSettings, + addCouncilMember, + removeCouncilMember, + updateCouncilMemberWeight, + CouncilConfig, + CouncilMemberConfig, +} from "../actions/governance/council"; + + + /** * Main class for interacting with Solana blockchain * Provides a unified interface for token operations, NFT management, trading and more @@ -192,10 +226,29 @@ import { * @property {Config} config - Configuration object */ export class SolanaAgentKit { + // createRealm(arg0: { name: string; communityMint: PublicKey; minCommunityTokensToCreateGovernance: number; councilMint: PublicKey | undefined; }) { + // throw new Error("Method not implemented."); + // } + // createProposal(arg0: PublicKey, arg1: ProposalConfig) { + // throw new Error("Method not implemented."); + // } + // castVote(args: VoteConfig) { + // throw new Error("Method not implemented."); + // } + // getRealm(arg0: PublicKey) { + // throw new Error("Method not implemented."); + // } + // getTokenOwnerRecord(arg0: PublicKey, arg1: PublicKey, arg2: PublicKey) { + // throw new Error("Method not implemented."); + // } + // getVoterHistory(arg0: PublicKey) { + // throw new Error("Method not implemented."); + // } public connection: Connection; public wallet: Keypair; public wallet_address: PublicKey; public config: Config; + governanceMonitors: any; /** * @deprecated Using openai_api_key directly in constructor is deprecated. @@ -240,18 +293,9 @@ export class SolanaAgentKit { uri: string, symbol: string, decimals: number = DEFAULT_OPTIONS.TOKEN_DECIMALS, - authority: SplAuthorityInput, initialSupply?: number, ): Promise<{ mint: PublicKey }> { - return deploy_token( - this, - name, - uri, - symbol, - decimals, - authority, - initialSupply, - ); + return deploy_token(this, name, uri, symbol, decimals, initialSupply); } async deployCollection( @@ -670,6 +714,8 @@ export class SolanaAgentKit { ); } + + async manifestCreateMarket( baseMint: PublicKey, quoteMint: PublicKey, @@ -785,6 +831,118 @@ export class SolanaAgentKit { return `Transaction: ${tx}`; } + + async createRealm(config: RealmConfig): Promise { + return createNewRealm(this, config); + } + + async createProposal( + realm: PublicKey, + config: ProposalConfig, + ): Promise { + return createNewProposal(this, realm, config); + } + async castVote(config: VoteConfig): Promise { + return castVoteOnProposal(this, config); + } + async getRealm(realm: PublicKey): Promise { + return getRealmInfo(this, realm); + } + async getTokenOwnerRecord( + realm: PublicKey, + governingTokenMint: PublicKey, + governingTokenOwner: PublicKey, + ): Promise { + return getTokenOwnerRecord( + this, + realm, + governingTokenMint, + governingTokenOwner, + ); + } + async getVoterHistory(voter: PublicKey): Promise { + return getVoterHistory(this, voter); + } + + async monitorRealmMembership( + realm: PublicKey, + governingTokenMint: PublicKey, + callback: MembershipChangeCallback, + ): Promise { + const key = `${realm.toBase58()}-${governingTokenMint.toBase58()}`; + let monitor = this.governanceMonitors.get(key); + + if (!monitor) { + monitor = new GovernanceMonitor(this, realm, governingTokenMint); + this.governanceMonitors.set(key, monitor); + } + + await monitor.monitorMembershipChanges(callback); + } + + async monitorVotingPower( + realm: PublicKey, + governingTokenMint: PublicKey, + callback: VotingPowerChangeCallback, + ): Promise { + const key = `${realm.toBase58()}-${governingTokenMint.toBase58()}`; + let monitor = this.governanceMonitors.get(key); + + if (!monitor) { + monitor = new GovernanceMonitor(this, realm, governingTokenMint); + this.governanceMonitors.set(key, monitor); + } + + await monitor.monitorVotingPowerChanges(callback); + } + + async stopMonitoring( + realm: PublicKey, + governingTokenMint: PublicKey, + ): Promise { + const key = `${realm.toBase58()}-${governingTokenMint.toBase58()}`; + const monitor = this.governanceMonitors.get(key); + + if (monitor) { + await monitor.stopMonitoring(); + this.governanceMonitors.delete(key); + } + } + + async configureCouncil( + realm: PublicKey, + config: CouncilConfig, + ): Promise { + return configureCouncilSettings(this, realm, config); + } + + async addCouncilMember( + realm: PublicKey, + councilMint: PublicKey, + memberConfig: CouncilMemberConfig, + ): Promise { + return addCouncilMember(this, realm, councilMint, memberConfig); + } + + async removeCouncilMember( + realm: PublicKey, + councilMint: PublicKey, + member: PublicKey, + ): Promise { + return removeCouncilMember(this, realm, councilMint, member); + } + + async updateCouncilMemberWeight( + realm: PublicKey, + councilMint: PublicKey, + memberConfig: CouncilMemberConfig, + ): Promise { + return updateCouncilMemberWeight(this, realm, councilMint, memberConfig); + } + + + + async create3LandNft( collectionAccount: string, createItemOptions: CreateSingleOptions, diff --git a/src/langchain/index.ts b/src/langchain/index.ts index 8ede830a..70335d39 100644 --- a/src/langchain/index.ts +++ b/src/langchain/index.ts @@ -160,6 +160,13 @@ import { SolanaFluxbeamCreatePoolTool, } from "./index"; +import { + SolanaCreateRealmTool, + SolanaCreateProposalTool, + SolanaCastVoteTool, + SolanaGetRealmInfoTool, +} from "../tools/governance/governance" + export function createSolanaTools(solanaKit: SolanaAgentKit) { return [ new SolanaGetInfoTool(solanaKit), @@ -282,5 +289,10 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) { new ElfaGetTopMentionsTool(solanaKit), new ElfaAccountSmartStatsTool(solanaKit), new SolanaFluxbeamCreatePoolTool(solanaKit), + new SolanaCreateRealmTool(solanaKit), + new SolanaCreateProposalTool(solanaKit), + new SolanaCastVoteTool(solanaKit), + new SolanaGetRealmInfoTool(solanaKit), + new SolanaApproveProposal2by2Multisig(solanaKit), ]; } diff --git a/src/tools/governance/governance.ts b/src/tools/governance/governance.ts new file mode 100644 index 00000000..a0452ae9 --- /dev/null +++ b/src/tools/governance/governance.ts @@ -0,0 +1,201 @@ +import { PublicKey } from "@solana/web3.js"; +import { z } from "zod"; +import { Tool } from "@langchain/core/tools"; +import { SolanaAgentKit } from "../../agent"; +import { VoteConfig, ProposalConfig } from "../../types/"; + +export class SolanaCreateRealmTool extends Tool { + name = "create_realm"; + description = "Create a new DAO realm with specified configuration"; + + override schema = z + .object({ + input: z.string().optional(), + }) + .transform((args) => args.input); + + constructor(private agent: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const args = JSON.parse(input || "{}") as { + name: string; + communityMint: string; + minCommunityTokens: number; + councilMint?: string; + }; + + const result = await this.agent.createRealm({ + name: args.name, + communityMint: new PublicKey(args.communityMint), + minCommunityTokensToCreateGovernance: args.minCommunityTokens, + councilMint: args.councilMint + ? new PublicKey(args.councilMint) + : undefined, + }); + return `Successfully created realm with address: ${result.toString()}`; + } catch (error) { + throw new Error(`Failed to create realm: ${error}`); + } + } +} + +export class SolanaCreateProposalTool extends Tool { + name = "create_proposal"; + description = "Create a new proposal in a DAO realm"; + + override schema = z + .object({ + input: z.string().optional(), + }) + .transform((args) => args.input); + + constructor(private agent: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const args = JSON.parse(input || "{}") as { + realm: string; + name: string; + description: string; + governingTokenMint: string; + voteType: "single-choice" | "multiple-choice"; + }; + + const result = await this.agent.createProposal( + new PublicKey(args.realm), + { + name: args.name, + description: args.description, + governingTokenMint: new PublicKey(args.governingTokenMint), + voteType: args.voteType, + options: ["Approve", "Deny"], + } as ProposalConfig, + ); + return `Successfully created proposal with address: ${result.toString()}`; + } catch (error) { + throw new Error(`Failed to create proposal: ${error}`); + } + } +} + +export class SolanaCastVoteTool extends Tool { + name = "cast_vote"; + description = "Cast a vote on a proposal"; + + override schema = z + .object({ + input: z.string().optional(), + }) + .transform((args) => args.input); + + constructor(private agent: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const args = JSON.parse(input || "{}") as VoteConfig; + const result = await this.agent.castVote(args); + return `Successfully cast vote on proposal: ${result.toString()}`; + } catch (error) { + throw new Error(`Failed to cast vote: ${error}`); + } + } +} + +export class SolanaGetRealmInfoTool extends Tool { + name = "get_realm_info"; + description = "Get information about a DAO realm"; + + override schema = z + .object({ + input: z.string().optional(), + }) + .transform((args) => args.input); + + constructor(private agent: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const args = JSON.parse(input || "{}") as { + realm: string; + }; + + const result = await this.agent.getRealm(new PublicKey(args.realm)); + return JSON.stringify(result); + } catch (error) { + throw new Error(`Failed to get realm info: ${error}`); + } + } +} + +export class SolanaGetTokenOwnerRecordTool extends Tool { + name = "get_token_owner_record"; + description = "Get token owner record for a member in a DAO realm"; + + override schema = z + .object({ + input: z.string().optional(), + }) + .transform((args) => args.input); + + constructor(private agent: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const args = JSON.parse(input || "{}") as { + realm: string; + governingTokenMint: string; + governingTokenOwner: string; + }; + + const result = await this.agent.getTokenOwnerRecord( + new PublicKey(args.realm), + new PublicKey(args.governingTokenMint), + new PublicKey(args.governingTokenOwner), + ); + return JSON.stringify(result); + } catch (error) { + throw new Error(`Failed to get token owner record: ${error}`); + } + } +} + +export class SolanaGetVoterHistoryTool extends Tool { + name = "get_voter_history"; + description = "Get voting history for a specific voter"; + + override schema = z + .object({ + input: z.string().optional(), + }) + .transform((args) => args.input); + + constructor(private agent: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const args = JSON.parse(input || "{}") as { + voter: string; + }; + + const result = await this.agent.getVoterHistory( + new PublicKey(args.voter), + ); + return JSON.stringify(result); + } catch (error) { + throw new Error(`Failed to get voter history: ${error}`); + } + } +} \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts index 9933ab96..5fcb22b0 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -2,6 +2,8 @@ import { PublicKey } from "@solana/web3.js"; import { SolanaAgentKit } from "../agent"; import { z } from "zod"; import { AlloraInference, AlloraTopic } from "@alloralabs/allora-sdk"; +import { Vote, VoteChoice } from "governance-idl-sdk"; + export interface Config { OPENAI_API_KEY?: string; @@ -507,9 +509,44 @@ export interface TransformedResponse { quote: Quote; } -export interface SplAuthorityInput { - mintAuthority?: PublicKey | undefined | null; - freezeAuthority?: PublicKey | undefined | null; - updateAuthority?: PublicKey | undefined; - isMutable?: boolean; +export { Vote, VoteChoice }; + +export interface RealmConfig { + name: string; + councilMint?: PublicKey | undefined; + communityMint: PublicKey; + minCommunityTokensToCreateGovernance: number; + communityTokenConfig?: { + tokenType: "liquid" | "membership" | "dormant"; + maxVotingPower?: number; + }; } + +export interface ProposalConfig { + name: string; + description: string; + governingTokenMint: PublicKey; + voteType: "single-choice" | "multiple-choice"; + options: string[]; + executionTime?: number; +} + +export interface VoteConfig { + realm: PublicKey; + proposal: PublicKey; + choice: number; + tokenAmount?: number; + governingTokenMint: PublicKey; + tokenOwner?: PublicKey; + governance: PublicKey; +} + +export type VoteType = { + choiceType: "single" | "multi"; + multiChoiceOptions: { + choiceType: "fullWeight" | "weighted"; + minVoterOptions: number; + maxVoterOptions: number; + maxWinningOptions: number; + } | null; +}; \ No newline at end of file From 09e98406aa5274102f9de80d0a7c86c2a1de9334 Mon Sep 17 00:00:00 2001 From: mac Date: Wed, 26 Feb 2025 02:00:02 +0530 Subject: [PATCH 2/5] done --- src/agent/index.ts | 40 +++++++++++++++------------------------- src/langchain/index.ts | 1 + src/types/index.ts | 10 +++++++++- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/agent/index.ts b/src/agent/index.ts index 9342e6b4..692eceaa 100644 --- a/src/agent/index.ts +++ b/src/agent/index.ts @@ -168,11 +168,11 @@ import { deBridgeOrderResponse, deBridgeOrderStatusResponse, deBridgeTokensInfoResponse, + SplAuthorityInput, VoteConfig, ProposalConfig, - RealmConfig + RealmConfig, } from "../types"; - import { DasApiAsset, DasApiAssetList, @@ -213,8 +213,6 @@ import { CouncilMemberConfig, } from "../actions/governance/council"; - - /** * Main class for interacting with Solana blockchain * Provides a unified interface for token operations, NFT management, trading and more @@ -226,24 +224,6 @@ import { * @property {Config} config - Configuration object */ export class SolanaAgentKit { - // createRealm(arg0: { name: string; communityMint: PublicKey; minCommunityTokensToCreateGovernance: number; councilMint: PublicKey | undefined; }) { - // throw new Error("Method not implemented."); - // } - // createProposal(arg0: PublicKey, arg1: ProposalConfig) { - // throw new Error("Method not implemented."); - // } - // castVote(args: VoteConfig) { - // throw new Error("Method not implemented."); - // } - // getRealm(arg0: PublicKey) { - // throw new Error("Method not implemented."); - // } - // getTokenOwnerRecord(arg0: PublicKey, arg1: PublicKey, arg2: PublicKey) { - // throw new Error("Method not implemented."); - // } - // getVoterHistory(arg0: PublicKey) { - // throw new Error("Method not implemented."); - // } public connection: Connection; public wallet: Keypair; public wallet_address: PublicKey; @@ -283,6 +263,8 @@ export class SolanaAgentKit { } } + + // Tool methods async requestFaucetFunds() { return request_faucet_funds(this); @@ -293,9 +275,18 @@ export class SolanaAgentKit { uri: string, symbol: string, decimals: number = DEFAULT_OPTIONS.TOKEN_DECIMALS, + authority: SplAuthorityInput, initialSupply?: number, ): Promise<{ mint: PublicKey }> { - return deploy_token(this, name, uri, symbol, decimals, initialSupply); + return deploy_token( + this, + name, + uri, + symbol, + decimals, + authority, + initialSupply, + ); } async deployCollection( @@ -714,8 +705,6 @@ export class SolanaAgentKit { ); } - - async manifestCreateMarket( baseMint: PublicKey, quoteMint: PublicKey, @@ -832,6 +821,7 @@ export class SolanaAgentKit { } + async createRealm(config: RealmConfig): Promise { return createNewRealm(this, config); } diff --git a/src/langchain/index.ts b/src/langchain/index.ts index 70335d39..03b7437d 100644 --- a/src/langchain/index.ts +++ b/src/langchain/index.ts @@ -167,6 +167,7 @@ import { SolanaGetRealmInfoTool, } from "../tools/governance/governance" + export function createSolanaTools(solanaKit: SolanaAgentKit) { return [ new SolanaGetInfoTool(solanaKit), diff --git a/src/types/index.ts b/src/types/index.ts index 5fcb22b0..6fd1dc2b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -3,7 +3,7 @@ import { SolanaAgentKit } from "../agent"; import { z } from "zod"; import { AlloraInference, AlloraTopic } from "@alloralabs/allora-sdk"; import { Vote, VoteChoice } from "governance-idl-sdk"; - + export interface Config { OPENAI_API_KEY?: string; @@ -509,6 +509,14 @@ export interface TransformedResponse { quote: Quote; } +export interface SplAuthorityInput { + mintAuthority?: PublicKey | undefined | null; + freezeAuthority?: PublicKey | undefined | null; + updateAuthority?: PublicKey | undefined; + isMutable?: boolean; +} + + export { Vote, VoteChoice }; export interface RealmConfig { From 8323701c6b99bd9d0282a5f1d3a8df9b0ae1266b Mon Sep 17 00:00:00 2001 From: mac Date: Fri, 28 Feb 2025 16:32:43 +0530 Subject: [PATCH 3/5] file structured --- src/agent/index.ts | 39 ++-- src/langchain/index.ts | 10 +- src/langchain/realm-governance/cast-vote.ts | 29 +++ .../realm-governance/create-proposal.ts | 46 ++++ .../realm-governance/create-realm.ts | 42 ++++ src/langchain/realm-governance/index.ts | 7 + .../realm-governance/owner-record.ts | 38 ++++ src/langchain/realm-governance/realm-info.ts | 32 +++ .../realm-governance/voter-history.ts | 34 +++ src/tools/governance/governance.ts | 201 ------------------ src/tools/index.ts | 2 + .../realm-governance}/council.ts | 0 .../realm-governance}/governance.ts | 0 src/tools/realm-governance/index.ts | 3 + .../realm-governance}/monitor.ts | 0 15 files changed, 255 insertions(+), 228 deletions(-) create mode 100644 src/langchain/realm-governance/cast-vote.ts create mode 100644 src/langchain/realm-governance/create-proposal.ts create mode 100644 src/langchain/realm-governance/create-realm.ts create mode 100644 src/langchain/realm-governance/index.ts create mode 100644 src/langchain/realm-governance/owner-record.ts create mode 100644 src/langchain/realm-governance/realm-info.ts create mode 100644 src/langchain/realm-governance/voter-history.ts delete mode 100644 src/tools/governance/governance.ts rename src/{actions/governance => tools/realm-governance}/council.ts (100%) rename src/{actions/governance => tools/realm-governance}/governance.ts (100%) create mode 100644 src/tools/realm-governance/index.ts rename src/{actions/governance => tools/realm-governance}/monitor.ts (100%) diff --git a/src/agent/index.ts b/src/agent/index.ts index 692eceaa..ca4b240c 100644 --- a/src/agent/index.ts +++ b/src/agent/index.ts @@ -147,6 +147,21 @@ import { getTopGainers, getTrendingPools, getTrendingTokens, + createNewRealm, + createNewProposal, + castVoteOnProposal, + getRealmInfo, + getTokenOwnerRecord, + getVoterHistory, + GovernanceMonitor, + MembershipChangeCallback, + VotingPowerChangeCallback, + configureCouncilSettings, + addCouncilMember, + removeCouncilMember, + updateCouncilMemberWeight, + CouncilConfig, + CouncilMemberConfig, } from "../tools"; import { Config, @@ -190,29 +205,9 @@ import { getSmartTwitterAccountStats, } from "../tools/elfa_ai"; -import { - createNewRealm, - createNewProposal, - castVoteOnProposal, - getRealmInfo, - getTokenOwnerRecord, - getVoterHistory, -} from "../actions/governance/governance"; - -import { - GovernanceMonitor, - MembershipChangeCallback, - VotingPowerChangeCallback, -} from "../actions/governance/monitor"; -import { - configureCouncilSettings, - addCouncilMember, - removeCouncilMember, - updateCouncilMemberWeight, - CouncilConfig, - CouncilMemberConfig, -} from "../actions/governance/council"; + + /** * Main class for interacting with Solana blockchain * Provides a unified interface for token operations, NFT management, trading and more diff --git a/src/langchain/index.ts b/src/langchain/index.ts index 03b7437d..6f5dd83a 100644 --- a/src/langchain/index.ts +++ b/src/langchain/index.ts @@ -35,6 +35,8 @@ export * from "./switchboard"; export * from "./elfa_ai"; export * from "./debridge"; export * from "./fluxbeam"; +export * from "./governance"; + import type { SolanaAgentKit } from "../agent"; import { @@ -158,14 +160,12 @@ import { ElfaGetTopMentionsTool, ElfaAccountSmartStatsTool, SolanaFluxbeamCreatePoolTool, -} from "./index"; - -import { SolanaCreateRealmTool, SolanaCreateProposalTool, SolanaCastVoteTool, - SolanaGetRealmInfoTool, -} from "../tools/governance/governance" + SolanaGetRealmInfoTool, +} from "./index"; + export function createSolanaTools(solanaKit: SolanaAgentKit) { diff --git a/src/langchain/realm-governance/cast-vote.ts b/src/langchain/realm-governance/cast-vote.ts new file mode 100644 index 00000000..56b377d6 --- /dev/null +++ b/src/langchain/realm-governance/cast-vote.ts @@ -0,0 +1,29 @@ +import { Tool } from "langchain/tools"; +import { z } from "zod"; +import { SolanaAgentKit } from "../../agent"; +import { VoteConfig } from "../../types"; + +export class SolanaCastVoteTool extends Tool { + name = "cast_vote"; + description = "Cast a vote on a proposal"; + + override schema = z + .object({ + input: z.string().optional(), + }) + .transform((args) => args.input); + + constructor(private agent: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const args = JSON.parse(input || "{}") as VoteConfig; + const result = await this.agent.castVote(args); + return `Successfully cast vote on proposal: ${result.toString()}`; + } catch (error) { + throw new Error(`Failed to cast vote: ${error}`); + } + } +} \ No newline at end of file diff --git a/src/langchain/realm-governance/create-proposal.ts b/src/langchain/realm-governance/create-proposal.ts new file mode 100644 index 00000000..56b2f225 --- /dev/null +++ b/src/langchain/realm-governance/create-proposal.ts @@ -0,0 +1,46 @@ +import { PublicKey } from "@solana/web3.js"; +import { Tool } from "langchain/tools"; +import { z } from "zod"; +import { SolanaAgentKit } from "../../agent"; +import { ProposalConfig } from "../../types"; + +export class SolanaCreateProposalTool extends Tool { + name = "create_proposal"; + description = "Create a new proposal in a DAO realm"; + + override schema = z + .object({ + input: z.string().optional(), + }) + .transform((args) => args.input); + + constructor(private agent: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const args = JSON.parse(input || "{}") as { + realm: string; + name: string; + description: string; + governingTokenMint: string; + voteType: "single-choice" | "multiple-choice"; + }; + + const result = await this.agent.createProposal( + new PublicKey(args.realm), + { + name: args.name, + description: args.description, + governingTokenMint: new PublicKey(args.governingTokenMint), + voteType: args.voteType, + options: ["Approve", "Deny"], + } as ProposalConfig, + ); + return `Successfully created proposal with address: ${result.toString()}`; + } catch (error) { + throw new Error(`Failed to create proposal: ${error}`); + } + } +} \ No newline at end of file diff --git a/src/langchain/realm-governance/create-realm.ts b/src/langchain/realm-governance/create-realm.ts new file mode 100644 index 00000000..4abc7cd5 --- /dev/null +++ b/src/langchain/realm-governance/create-realm.ts @@ -0,0 +1,42 @@ +import { PublicKey } from "@solana/web3.js"; +import { Tool } from "langchain/tools"; +import { z } from "zod"; +import { SolanaAgentKit } from "../../agent"; + +export class SolanaCreateRealmTool extends Tool { + name = "create_realm"; + description = "Create a new DAO realm with specified configuration"; + + override schema = z + .object({ + input: z.string().optional(), + }) + .transform((args) => args.input); + + constructor(private agent: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const args = JSON.parse(input || "{}") as { + name: string; + communityMint: string; + minCommunityTokens: number; + councilMint?: string; + }; + + const result = await this.agent.createRealm({ + name: args.name, + communityMint: new PublicKey(args.communityMint), + minCommunityTokensToCreateGovernance: args.minCommunityTokens, + councilMint: args.councilMint + ? new PublicKey(args.councilMint) + : undefined, + }); + return `Successfully created realm with address: ${result.toString()}`; + } catch (error) { + throw new Error(`Failed to create realm: ${error}`); + } + } +} \ No newline at end of file diff --git a/src/langchain/realm-governance/index.ts b/src/langchain/realm-governance/index.ts new file mode 100644 index 00000000..dd3fb5ff --- /dev/null +++ b/src/langchain/realm-governance/index.ts @@ -0,0 +1,7 @@ + +export * from "./owner-record"; +export * from "./realm-info"; +export * from "./create-realm"; +export * from "./create-proposal"; +export * from "./cast-vote"; +export * from "./voter-history"; \ No newline at end of file diff --git a/src/langchain/realm-governance/owner-record.ts b/src/langchain/realm-governance/owner-record.ts new file mode 100644 index 00000000..13467e92 --- /dev/null +++ b/src/langchain/realm-governance/owner-record.ts @@ -0,0 +1,38 @@ +import { PublicKey } from "@solana/web3.js"; +import { Tool } from "langchain/tools"; +import { z } from "zod"; +import { SolanaAgentKit } from "../../agent"; + +export class SolanaGetTokenOwnerRecordTool extends Tool { + name = "get_token_owner_record"; + description = "Get token owner record for a member in a DAO realm"; + + override schema = z + .object({ + input: z.string().optional(), + }) + .transform((args) => args.input); + + constructor(private agent: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const args = JSON.parse(input || "{}") as { + realm: string; + governingTokenMint: string; + governingTokenOwner: string; + }; + + const result = await this.agent.getTokenOwnerRecord( + new PublicKey(args.realm), + new PublicKey(args.governingTokenMint), + new PublicKey(args.governingTokenOwner), + ); + return JSON.stringify(result); + } catch (error) { + throw new Error(`Failed to get token owner record: ${error}`); + } + } +} \ No newline at end of file diff --git a/src/langchain/realm-governance/realm-info.ts b/src/langchain/realm-governance/realm-info.ts new file mode 100644 index 00000000..493325d7 --- /dev/null +++ b/src/langchain/realm-governance/realm-info.ts @@ -0,0 +1,32 @@ +import { PublicKey } from "@solana/web3.js"; +import { Tool } from "langchain/tools"; +import { z } from "zod"; +import { SolanaAgentKit } from "../../agent"; + +export class SolanaGetRealmInfoTool extends Tool { + name = "get_realm_info"; + description = "Get information about a DAO realm"; + + override schema = z + .object({ + input: z.string().optional(), + }) + .transform((args) => args.input); + + constructor(private agent: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const args = JSON.parse(input || "{}") as { + realm: string; + }; + + const result = await this.agent.getRealm(new PublicKey(args.realm)); + return JSON.stringify(result); + } catch (error) { + throw new Error(`Failed to get realm info: ${error}`); + } + } +} \ No newline at end of file diff --git a/src/langchain/realm-governance/voter-history.ts b/src/langchain/realm-governance/voter-history.ts new file mode 100644 index 00000000..c2d3c1ae --- /dev/null +++ b/src/langchain/realm-governance/voter-history.ts @@ -0,0 +1,34 @@ +import { PublicKey } from "@solana/web3.js"; +import { Tool } from "langchain/tools"; +import { z } from "zod"; +import { SolanaAgentKit } from "../../agent"; + +export class SolanaGetVoterHistoryTool extends Tool { + name = "get_voter_history"; + description = "Get voting history for a specific voter"; + + override schema = z + .object({ + input: z.string().optional(), + }) + .transform((args) => args.input); + + constructor(private agent: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const args = JSON.parse(input || "{}") as { + voter: string; + }; + + const result = await this.agent.getVoterHistory( + new PublicKey(args.voter), + ); + return JSON.stringify(result); + } catch (error) { + throw new Error(`Failed to get voter history: ${error}`); + } + } + } \ No newline at end of file diff --git a/src/tools/governance/governance.ts b/src/tools/governance/governance.ts deleted file mode 100644 index a0452ae9..00000000 --- a/src/tools/governance/governance.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { PublicKey } from "@solana/web3.js"; -import { z } from "zod"; -import { Tool } from "@langchain/core/tools"; -import { SolanaAgentKit } from "../../agent"; -import { VoteConfig, ProposalConfig } from "../../types/"; - -export class SolanaCreateRealmTool extends Tool { - name = "create_realm"; - description = "Create a new DAO realm with specified configuration"; - - override schema = z - .object({ - input: z.string().optional(), - }) - .transform((args) => args.input); - - constructor(private agent: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const args = JSON.parse(input || "{}") as { - name: string; - communityMint: string; - minCommunityTokens: number; - councilMint?: string; - }; - - const result = await this.agent.createRealm({ - name: args.name, - communityMint: new PublicKey(args.communityMint), - minCommunityTokensToCreateGovernance: args.minCommunityTokens, - councilMint: args.councilMint - ? new PublicKey(args.councilMint) - : undefined, - }); - return `Successfully created realm with address: ${result.toString()}`; - } catch (error) { - throw new Error(`Failed to create realm: ${error}`); - } - } -} - -export class SolanaCreateProposalTool extends Tool { - name = "create_proposal"; - description = "Create a new proposal in a DAO realm"; - - override schema = z - .object({ - input: z.string().optional(), - }) - .transform((args) => args.input); - - constructor(private agent: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const args = JSON.parse(input || "{}") as { - realm: string; - name: string; - description: string; - governingTokenMint: string; - voteType: "single-choice" | "multiple-choice"; - }; - - const result = await this.agent.createProposal( - new PublicKey(args.realm), - { - name: args.name, - description: args.description, - governingTokenMint: new PublicKey(args.governingTokenMint), - voteType: args.voteType, - options: ["Approve", "Deny"], - } as ProposalConfig, - ); - return `Successfully created proposal with address: ${result.toString()}`; - } catch (error) { - throw new Error(`Failed to create proposal: ${error}`); - } - } -} - -export class SolanaCastVoteTool extends Tool { - name = "cast_vote"; - description = "Cast a vote on a proposal"; - - override schema = z - .object({ - input: z.string().optional(), - }) - .transform((args) => args.input); - - constructor(private agent: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const args = JSON.parse(input || "{}") as VoteConfig; - const result = await this.agent.castVote(args); - return `Successfully cast vote on proposal: ${result.toString()}`; - } catch (error) { - throw new Error(`Failed to cast vote: ${error}`); - } - } -} - -export class SolanaGetRealmInfoTool extends Tool { - name = "get_realm_info"; - description = "Get information about a DAO realm"; - - override schema = z - .object({ - input: z.string().optional(), - }) - .transform((args) => args.input); - - constructor(private agent: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const args = JSON.parse(input || "{}") as { - realm: string; - }; - - const result = await this.agent.getRealm(new PublicKey(args.realm)); - return JSON.stringify(result); - } catch (error) { - throw new Error(`Failed to get realm info: ${error}`); - } - } -} - -export class SolanaGetTokenOwnerRecordTool extends Tool { - name = "get_token_owner_record"; - description = "Get token owner record for a member in a DAO realm"; - - override schema = z - .object({ - input: z.string().optional(), - }) - .transform((args) => args.input); - - constructor(private agent: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const args = JSON.parse(input || "{}") as { - realm: string; - governingTokenMint: string; - governingTokenOwner: string; - }; - - const result = await this.agent.getTokenOwnerRecord( - new PublicKey(args.realm), - new PublicKey(args.governingTokenMint), - new PublicKey(args.governingTokenOwner), - ); - return JSON.stringify(result); - } catch (error) { - throw new Error(`Failed to get token owner record: ${error}`); - } - } -} - -export class SolanaGetVoterHistoryTool extends Tool { - name = "get_voter_history"; - description = "Get voting history for a specific voter"; - - override schema = z - .object({ - input: z.string().optional(), - }) - .transform((args) => args.input); - - constructor(private agent: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const args = JSON.parse(input || "{}") as { - voter: string; - }; - - const result = await this.agent.getVoterHistory( - new PublicKey(args.voter), - ); - return JSON.stringify(result); - } catch (error) { - throw new Error(`Failed to get voter history: ${error}`); - } - } -} \ No newline at end of file diff --git a/src/tools/index.ts b/src/tools/index.ts index 913ad862..b8afac0b 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -35,3 +35,5 @@ export * from "./switchboard"; export * from "./elfa_ai"; export * from "./fluxbeam"; export * from "./coingecko"; +export * from "./realm-governance"; + diff --git a/src/actions/governance/council.ts b/src/tools/realm-governance/council.ts similarity index 100% rename from src/actions/governance/council.ts rename to src/tools/realm-governance/council.ts diff --git a/src/actions/governance/governance.ts b/src/tools/realm-governance/governance.ts similarity index 100% rename from src/actions/governance/governance.ts rename to src/tools/realm-governance/governance.ts diff --git a/src/tools/realm-governance/index.ts b/src/tools/realm-governance/index.ts new file mode 100644 index 00000000..6ea83303 --- /dev/null +++ b/src/tools/realm-governance/index.ts @@ -0,0 +1,3 @@ +export * from "./council"; +export * from "./governance"; +export * from "./monitor"; diff --git a/src/actions/governance/monitor.ts b/src/tools/realm-governance/monitor.ts similarity index 100% rename from src/actions/governance/monitor.ts rename to src/tools/realm-governance/monitor.ts From 717381ee1ab93c4cd4f4452824659b2329b93b94 Mon Sep 17 00:00:00 2001 From: mac Date: Fri, 28 Feb 2025 18:00:37 +0530 Subject: [PATCH 4/5] actions added / testing reamins --- src/actions/index.ts | 17 ++ src/actions/realm-governance/cast-vote.ts | 69 ++++++ .../realm-governance/create-proposal.ts | 75 +++++++ src/actions/realm-governance/create-realm.ts | 77 +++++++ src/actions/realm-governance/index.ts | 1 + src/actions/realm-governance/owner-record.ts | 71 +++++++ src/actions/realm-governance/realm-info.ts | 59 +++++ src/actions/realm-governance/voter-history.ts | 61 ++++++ src/agent/index.ts | 1 - src/langchain/index.ts | 4 +- src/langchain/realm-governance/cast-vote.ts | 29 --- .../realm-governance/create-proposal.ts | 46 ---- .../realm-governance/create-realm.ts | 42 ---- src/langchain/realm-governance/index.ts | 8 +- .../realm-governance/owner-record.ts | 38 ---- .../realm-governance/realm-governance.ts | 201 ++++++++++++++++++ src/langchain/realm-governance/realm-info.ts | 32 --- .../realm-governance/voter-history.ts | 34 --- src/types/index.ts | 3 +- 19 files changed, 637 insertions(+), 231 deletions(-) create mode 100644 src/actions/realm-governance/cast-vote.ts create mode 100644 src/actions/realm-governance/create-proposal.ts create mode 100644 src/actions/realm-governance/create-realm.ts create mode 100644 src/actions/realm-governance/index.ts create mode 100644 src/actions/realm-governance/owner-record.ts create mode 100644 src/actions/realm-governance/realm-info.ts create mode 100644 src/actions/realm-governance/voter-history.ts delete mode 100644 src/langchain/realm-governance/cast-vote.ts delete mode 100644 src/langchain/realm-governance/create-proposal.ts delete mode 100644 src/langchain/realm-governance/create-realm.ts delete mode 100644 src/langchain/realm-governance/owner-record.ts create mode 100644 src/langchain/realm-governance/realm-governance.ts delete mode 100644 src/langchain/realm-governance/realm-info.ts delete mode 100644 src/langchain/realm-governance/voter-history.ts diff --git a/src/actions/index.ts b/src/actions/index.ts index 736a1aa9..150d31d3 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -109,6 +109,16 @@ import getCoingeckoTokenPriceDataAction from "./coingecko/getCoingeckoTokenPrice import getCoingeckoTopGainersAction from "./coingecko/getCoingeckoTopGainers"; import getCoingeckoTrendingPoolsAction from "./coingecko/getCoingeckoTrendingPools"; import getCoingeckoTrendingTokensAction from "./coingecko/getCoingeckoTrendingTokens"; +import castVoteAction from "./realm-governance/cast-vote"; +import getRealmInfoAction from "./realm-governance/realm-info"; +import createRealmAction from "./realm-governance/create-realm"; +import createProposalAction from "./realm-governance/create-proposal"; +import getVoterHistoryAction from "./realm-governance/voter-history"; +import getTokenOwnerRecordAction from "./realm-governance/owner-record"; + + + + export const ACTIONS = { GET_INFO_ACTION: getInfoAction, @@ -226,6 +236,13 @@ export const ACTIONS = { GET_COINGECKO_TOP_GAINERS_ACTION: getCoingeckoTopGainersAction, GET_COINGECKO_TRENDING_POOLS_ACTION: getCoingeckoTrendingPoolsAction, GET_COINGECKO_TRENDING_TOKENS_ACTION: getCoingeckoTrendingTokensAction, + CAST_VOTE_ACTION: castVoteAction, + GET_REALM_INFO_ACTION: getRealmInfoAction, + CREATE_REALM_ACTION: createRealmAction, + CREATE_PROPOSAL_ACTION: createProposalAction, + GET_VOTER_HISTORY_ACTION: getVoterHistoryAction, + GET_TOKEN_OWNER_RECORD_ACTION: getTokenOwnerRecordAction, + }; export type { Action, ActionExample, Handler } from "../types/action"; diff --git a/src/actions/realm-governance/cast-vote.ts b/src/actions/realm-governance/cast-vote.ts new file mode 100644 index 00000000..ffc6bf1b --- /dev/null +++ b/src/actions/realm-governance/cast-vote.ts @@ -0,0 +1,69 @@ +import { PublicKey } from "@solana/web3.js"; +import { z } from "zod"; +import { SolanaAgentKit } from "../../agent"; +import { Action, VoteConfig } from "../../types"; + +const castVoteAction: Action = { + name: "SPL_CAST_VOTE", + similes: [ + "vote on proposal", + "cast dao vote", + "submit governance vote", + "vote on dao motion", + "participate in dao governance", + "cast ballot on proposal", + ], + description: "Cast a vote on an existing proposal in a DAO", + examples: [ + [ + { + input: { + proposal: "2ZE7Rz...", + tokenOwnerRecord: "8gYZR...", + }, + output: { + status: "success", + voteRecordAddress: "5PmxV...", + message: "Successfully cast vote on proposal", + }, + explanation: "Cast a vote on an existing proposal", + }, + ], + ], + schema: z.object({ + proposal: z.string().min(1).describe("Address of the proposal to vote on"), + tokenOwnerRecord: z + .string() + .min(1) + .describe("Token owner record address for voting"), + + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + try { + + const voteConfig: VoteConfig = { + proposal: new PublicKey(input.proposal), + tokenOwnerRecord: new PublicKey(input.tokenOwnerRecord), + realm: new PublicKey("11111111111111111111111111111111"), + choice: 0, + governingTokenMint: new PublicKey("11111111111111111111111111111111"), + governance: new PublicKey("11111111111111111111111111111111") + }; + + const result = await agent.castVote(voteConfig); + + return { + status: "success", + voteRecordAddress: result.toString(), + message: "Successfully cast vote on proposal", + }; + } catch (error: any) { + return { + status: "error", + message: `Failed to cast vote: ${error.message}`, + }; + } + }, +}; + +export default castVoteAction; \ No newline at end of file diff --git a/src/actions/realm-governance/create-proposal.ts b/src/actions/realm-governance/create-proposal.ts new file mode 100644 index 00000000..61c085ee --- /dev/null +++ b/src/actions/realm-governance/create-proposal.ts @@ -0,0 +1,75 @@ +import { PublicKey } from "@solana/web3.js"; +import { z } from "zod"; +import { SolanaAgentKit } from "../../agent"; +import { Action } from "../../types"; + +const createProposalAction: Action = { + name: "SPL_CREATE_PROPOSAL", + similes: [ + "create dao proposal", + "submit governance proposal", + "initiate dao vote", + "propose dao action", + "start governance vote", + "submit dao motion", + ], + description: + "Create a new proposal in a DAO realm for community or council voting", + examples: [ + [ + { + input: { + realm: "7nxQB...", + name: "Treasury Funding Allocation", + description: "Proposal to allocate 1000 tokens to the development team", + governingTokenMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + voteType: "single-choice", + }, + output: { + status: "success", + proposalAddress: "2ZE7Rz...", + message: "Successfully created proposal for community voting", + }, + explanation: + "Create a single-choice proposal for community members to vote on", + }, + ], + ], + schema: z.object({ + realm: z.string().min(1).describe("Address of the DAO realm"), + name: z.string().min(1).describe("Name of the proposal"), + description: z.string().min(1).describe("Description of the proposal"), + governingTokenMint: z + .string() + .min(1) + .describe("Token mint address for voting (community or council)"), + voteType: z + .enum(["single-choice", "multiple-choice"]) + .describe("Type of voting mechanism for the proposal"), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + try { + const result = await agent.createProposal(new PublicKey(input.realm), { + name: input.name, + description: input.description, + governingTokenMint: new PublicKey(input.governingTokenMint), + voteType: input.voteType, + options: ["Approve", "Deny"], + }); + + return { + status: "success", + proposalAddress: result.toString(), + message: `Successfully created proposal: ${input.name}`, + }; + } catch (error: any) { + return { + status: "error", + message: `Failed to create proposal: ${error.message}`, + }; + } + }, +}; + + +export default createProposalAction; \ No newline at end of file diff --git a/src/actions/realm-governance/create-realm.ts b/src/actions/realm-governance/create-realm.ts new file mode 100644 index 00000000..7ed5b222 --- /dev/null +++ b/src/actions/realm-governance/create-realm.ts @@ -0,0 +1,77 @@ +import { PublicKey } from "@solana/web3.js"; +import { z } from "zod"; +import { SolanaAgentKit } from "../../agent"; +import { Action } from "../../types"; + +const createRealmAction: Action = { + name: "SPL_CREATE_REALM", + similes: [ + "create dao realm", + "initialize governance realm", + "setup dao", + "create governance entity", + "establish dao space", + "initialize dao organization", + ], + description: + "Create a new DAO realm with specified configuration for on-chain governance", + examples: [ + [ + { + input: { + name: "My Community DAO", + communityMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + minCommunityTokens: 1000, + councilMint: "So11111111111111111111111111111111111111112", + }, + output: { + status: "success", + realmAddress: "7nxQB...", + message: "Successfully created DAO realm", + }, + explanation: + "Create a new DAO realm with community and council tokens for governance", + }, + ], + ], + schema: z.object({ + name: z.string().min(1).describe("Name of the DAO realm"), + communityMint: z + .string() + .min(1) + .describe("Address of the community token mint"), + minCommunityTokens: z + .number() + .positive() + .describe("Minimum community tokens required to create governance"), + councilMint: z + .string() + .optional() + .describe("Optional address of the council token mint"), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + try { + const result = await agent.createRealm({ + name: input.name, + communityMint: new PublicKey(input.communityMint), + minCommunityTokensToCreateGovernance: input.minCommunityTokens, + councilMint: input.councilMint + ? new PublicKey(input.councilMint) + : undefined, + }); + + return { + status: "success", + realmAddress: result.toString(), + message: `Successfully created realm: ${input.name}`, + }; + } catch (error: any) { + return { + status: "error", + message: `Failed to create realm: ${error.message}`, + }; + } + }, +}; + +export default createRealmAction; \ No newline at end of file diff --git a/src/actions/realm-governance/index.ts b/src/actions/realm-governance/index.ts new file mode 100644 index 00000000..0519ecba --- /dev/null +++ b/src/actions/realm-governance/index.ts @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/actions/realm-governance/owner-record.ts b/src/actions/realm-governance/owner-record.ts new file mode 100644 index 00000000..c064ed2f --- /dev/null +++ b/src/actions/realm-governance/owner-record.ts @@ -0,0 +1,71 @@ +import { PublicKey } from "@solana/web3.js"; +import { z } from "zod"; +import { SolanaAgentKit } from "../../agent"; +import { Action } from "../../types"; + +const getTokenOwnerRecordAction: Action = { + name: "SPL_GET_TOKEN_OWNER_RECORD", + similes: [ + "check dao membership status", + "get governance token holdings", + "view dao member record", + "check voting power", + "verify dao participation rights", + "view governance token record", + ], + description: "Get token owner record for a member in a DAO realm", + examples: [ + [ + { + input: { + realm: "7nxQB...", + governingTokenMint: "EPjF...", + governingTokenOwner: "DqYm...", + }, + output: { + status: "success", + tokenOwnerRecord: { + governingTokenOwner: "DqYm...", + tokenBalance: 5000, + // other member data + }, + message: "Successfully retrieved token owner record", + }, + explanation: "Retrieve a member's voting power and participation record", + }, + ], + ], + schema: z.object({ + realm: z.string().min(1).describe("Address of the DAO realm"), + governingTokenMint: z + .string() + .min(1) + .describe("Token mint address for voting (community or council)"), + governingTokenOwner: z + .string() + .min(1) + .describe("Address of the token owner/member"), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + try { + const result = await agent.getTokenOwnerRecord( + new PublicKey(input.realm), + new PublicKey(input.governingTokenMint), + new PublicKey(input.governingTokenOwner) + ); + + return { + status: "success", + tokenOwnerRecord: result, + message: "Successfully retrieved token owner record", + }; + } catch (error: any) { + return { + status: "error", + message: `Failed to get token owner record: ${error.message}`, + }; + } + }, +}; + +export default getTokenOwnerRecordAction; \ No newline at end of file diff --git a/src/actions/realm-governance/realm-info.ts b/src/actions/realm-governance/realm-info.ts new file mode 100644 index 00000000..274dbe58 --- /dev/null +++ b/src/actions/realm-governance/realm-info.ts @@ -0,0 +1,59 @@ +import { PublicKey } from "@solana/web3.js"; +import { z } from "zod"; +import { SolanaAgentKit } from "../../agent"; +import { Action } from "../../types"; +import { e } from "@raydium-io/raydium-sdk-v2/lib/api-0eb57ba2"; + +const getRealmInfoAction: Action = { + name: "SPL_GET_REALM_INFO", + similes: [ + "view dao information", + "check realm status", + "get dao details", + "retrieve governance realm info", + "fetch dao configuration", + "lookup realm data", + ], + description: "Get detailed information about a DAO realm", + examples: [ + [ + { + input: { + realm: "7nxQB...", + }, + output: { + status: "success", + realmInfo: { + name: "My Community DAO", + communityMint: "EPjF...", + councilMint: "So11...", + // other realm data + }, + message: "Successfully retrieved realm information", + }, + explanation: "Retrieve configuration details for a DAO realm", + }, + ], + ], + schema: z.object({ + realm: z.string().min(1).describe("Address of the DAO realm"), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + try { + const result = await agent.getRealm(new PublicKey(input.realm)); + + return { + status: "success", + realmInfo: result, + message: "Successfully retrieved realm information", + }; + } catch (error: any) { + return { + status: "error", + message: `Failed to get realm info: ${error.message}`, + }; + } + }, +}; + +export default getRealmInfoAction; \ No newline at end of file diff --git a/src/actions/realm-governance/voter-history.ts b/src/actions/realm-governance/voter-history.ts new file mode 100644 index 00000000..5c902b6e --- /dev/null +++ b/src/actions/realm-governance/voter-history.ts @@ -0,0 +1,61 @@ +import { PublicKey } from "@solana/web3.js"; +import { z } from "zod"; +import { SolanaAgentKit } from "../../agent"; +import { Action } from "../../types"; + +const getVoterHistoryAction: Action = { + name: "SPL_GET_VOTER_HISTORY", + similes: [ + "check voting history", + "view past votes", + "retrieve voter record", + "get dao participation history", + "review governance activity", + "check vote records", + ], + description: "Get voting history for a specific voter across proposals", + examples: [ + [ + { + input: { + voter: "DqYm...", + }, + output: { + status: "success", + voterHistory: [ + { + proposal: "2ZE7Rz...", + vote: "Approve", + timestamp: 1672531200, + }, + + ], + message: "Successfully retrieved voter history", + }, + explanation: "Retrieve a complete voting record for a DAO participant", + }, + ], + ], + schema: z.object({ + voter: z.string().min(1).describe("Address of the voter"), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + try { + const result = await agent.getVoterHistory(new PublicKey(input.voter)); + + return { + status: "success", + voterHistory: result, + message: "Successfully retrieved voter history", + }; + } catch (error: any) { + return { + status: "error", + message: `Failed to get voter history: ${error.message}`, + }; + } + }, +}; + + +export default getVoterHistoryAction; \ No newline at end of file diff --git a/src/agent/index.ts b/src/agent/index.ts index ca4b240c..6b8df262 100644 --- a/src/agent/index.ts +++ b/src/agent/index.ts @@ -7,7 +7,6 @@ import { CreateSingleOptions, StoreInitOptions, } from "@3land/listings-sdk/dist/types/implementation/implementationTypes"; -import { SplGovernance } from "governance-idl-sdk"; import type { TokenOwnerRecord, VoteRecord, diff --git a/src/langchain/index.ts b/src/langchain/index.ts index 6f5dd83a..82adc50d 100644 --- a/src/langchain/index.ts +++ b/src/langchain/index.ts @@ -35,7 +35,8 @@ export * from "./switchboard"; export * from "./elfa_ai"; export * from "./debridge"; export * from "./fluxbeam"; -export * from "./governance"; +export * from "./realm-governance"; + import type { SolanaAgentKit } from "../agent"; @@ -290,6 +291,7 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) { new ElfaGetTopMentionsTool(solanaKit), new ElfaAccountSmartStatsTool(solanaKit), new SolanaFluxbeamCreatePoolTool(solanaKit), + new SolanaApproveProposal2by2Multisig(solanaKit), new SolanaCreateRealmTool(solanaKit), new SolanaCreateProposalTool(solanaKit), new SolanaCastVoteTool(solanaKit), diff --git a/src/langchain/realm-governance/cast-vote.ts b/src/langchain/realm-governance/cast-vote.ts deleted file mode 100644 index 56b377d6..00000000 --- a/src/langchain/realm-governance/cast-vote.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Tool } from "langchain/tools"; -import { z } from "zod"; -import { SolanaAgentKit } from "../../agent"; -import { VoteConfig } from "../../types"; - -export class SolanaCastVoteTool extends Tool { - name = "cast_vote"; - description = "Cast a vote on a proposal"; - - override schema = z - .object({ - input: z.string().optional(), - }) - .transform((args) => args.input); - - constructor(private agent: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const args = JSON.parse(input || "{}") as VoteConfig; - const result = await this.agent.castVote(args); - return `Successfully cast vote on proposal: ${result.toString()}`; - } catch (error) { - throw new Error(`Failed to cast vote: ${error}`); - } - } -} \ No newline at end of file diff --git a/src/langchain/realm-governance/create-proposal.ts b/src/langchain/realm-governance/create-proposal.ts deleted file mode 100644 index 56b2f225..00000000 --- a/src/langchain/realm-governance/create-proposal.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { PublicKey } from "@solana/web3.js"; -import { Tool } from "langchain/tools"; -import { z } from "zod"; -import { SolanaAgentKit } from "../../agent"; -import { ProposalConfig } from "../../types"; - -export class SolanaCreateProposalTool extends Tool { - name = "create_proposal"; - description = "Create a new proposal in a DAO realm"; - - override schema = z - .object({ - input: z.string().optional(), - }) - .transform((args) => args.input); - - constructor(private agent: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const args = JSON.parse(input || "{}") as { - realm: string; - name: string; - description: string; - governingTokenMint: string; - voteType: "single-choice" | "multiple-choice"; - }; - - const result = await this.agent.createProposal( - new PublicKey(args.realm), - { - name: args.name, - description: args.description, - governingTokenMint: new PublicKey(args.governingTokenMint), - voteType: args.voteType, - options: ["Approve", "Deny"], - } as ProposalConfig, - ); - return `Successfully created proposal with address: ${result.toString()}`; - } catch (error) { - throw new Error(`Failed to create proposal: ${error}`); - } - } -} \ No newline at end of file diff --git a/src/langchain/realm-governance/create-realm.ts b/src/langchain/realm-governance/create-realm.ts deleted file mode 100644 index 4abc7cd5..00000000 --- a/src/langchain/realm-governance/create-realm.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { PublicKey } from "@solana/web3.js"; -import { Tool } from "langchain/tools"; -import { z } from "zod"; -import { SolanaAgentKit } from "../../agent"; - -export class SolanaCreateRealmTool extends Tool { - name = "create_realm"; - description = "Create a new DAO realm with specified configuration"; - - override schema = z - .object({ - input: z.string().optional(), - }) - .transform((args) => args.input); - - constructor(private agent: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const args = JSON.parse(input || "{}") as { - name: string; - communityMint: string; - minCommunityTokens: number; - councilMint?: string; - }; - - const result = await this.agent.createRealm({ - name: args.name, - communityMint: new PublicKey(args.communityMint), - minCommunityTokensToCreateGovernance: args.minCommunityTokens, - councilMint: args.councilMint - ? new PublicKey(args.councilMint) - : undefined, - }); - return `Successfully created realm with address: ${result.toString()}`; - } catch (error) { - throw new Error(`Failed to create realm: ${error}`); - } - } -} \ No newline at end of file diff --git a/src/langchain/realm-governance/index.ts b/src/langchain/realm-governance/index.ts index dd3fb5ff..4d5d819a 100644 --- a/src/langchain/realm-governance/index.ts +++ b/src/langchain/realm-governance/index.ts @@ -1,7 +1 @@ - -export * from "./owner-record"; -export * from "./realm-info"; -export * from "./create-realm"; -export * from "./create-proposal"; -export * from "./cast-vote"; -export * from "./voter-history"; \ No newline at end of file + export * from "./realm-governance"; \ No newline at end of file diff --git a/src/langchain/realm-governance/owner-record.ts b/src/langchain/realm-governance/owner-record.ts deleted file mode 100644 index 13467e92..00000000 --- a/src/langchain/realm-governance/owner-record.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { PublicKey } from "@solana/web3.js"; -import { Tool } from "langchain/tools"; -import { z } from "zod"; -import { SolanaAgentKit } from "../../agent"; - -export class SolanaGetTokenOwnerRecordTool extends Tool { - name = "get_token_owner_record"; - description = "Get token owner record for a member in a DAO realm"; - - override schema = z - .object({ - input: z.string().optional(), - }) - .transform((args) => args.input); - - constructor(private agent: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const args = JSON.parse(input || "{}") as { - realm: string; - governingTokenMint: string; - governingTokenOwner: string; - }; - - const result = await this.agent.getTokenOwnerRecord( - new PublicKey(args.realm), - new PublicKey(args.governingTokenMint), - new PublicKey(args.governingTokenOwner), - ); - return JSON.stringify(result); - } catch (error) { - throw new Error(`Failed to get token owner record: ${error}`); - } - } -} \ No newline at end of file diff --git a/src/langchain/realm-governance/realm-governance.ts b/src/langchain/realm-governance/realm-governance.ts new file mode 100644 index 00000000..9b308063 --- /dev/null +++ b/src/langchain/realm-governance/realm-governance.ts @@ -0,0 +1,201 @@ +import { PublicKey } from "@solana/web3.js"; +import { z } from "zod"; +import { Tool } from "@langchain/core/tools"; +import { SolanaAgentKit } from "../../agent"; +import { VoteConfig, ProposalConfig } from "../../types"; + +export class SolanaCreateRealmTool extends Tool { + name = "create_realm"; + description = "Create a new DAO realm with specified configuration"; + + override schema = z + .object({ + input: z.string().optional(), + }) + .transform((args) => args.input); + + constructor(private agent: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const args = JSON.parse(input || "{}") as { + name: string; + communityMint: string; + minCommunityTokens: number; + councilMint?: string; + }; + + const result = await this.agent.createRealm({ + name: args.name, + communityMint: new PublicKey(args.communityMint), + minCommunityTokensToCreateGovernance: args.minCommunityTokens, + councilMint: args.councilMint + ? new PublicKey(args.councilMint) + : undefined, + }); + return `Successfully created realm with address: ${result.toString()}`; + } catch (error) { + throw new Error(`Failed to create realm: ${error}`); + } + } +} + +export class SolanaCreateProposalTool extends Tool { + name = "create_proposal"; + description = "Create a new proposal in a DAO realm"; + + override schema = z + .object({ + input: z.string().optional(), + }) + .transform((args) => args.input); + + constructor(private agent: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const args = JSON.parse(input || "{}") as { + realm: string; + name: string; + description: string; + governingTokenMint: string; + voteType: "single-choice" | "multiple-choice"; + }; + + const result = await this.agent.createProposal( + new PublicKey(args.realm), + { + name: args.name, + description: args.description, + governingTokenMint: new PublicKey(args.governingTokenMint), + voteType: args.voteType, + options: ["Approve", "Deny"], + } as ProposalConfig, + ); + return `Successfully created proposal with address: ${result.toString()}`; + } catch (error) { + throw new Error(`Failed to create proposal: ${error}`); + } + } +} + +export class SolanaCastVoteTool extends Tool { + name = "cast_vote"; + description = "Cast a vote on a proposal"; + + override schema = z + .object({ + input: z.string().optional(), + }) + .transform((args) => args.input); + + constructor(private agent: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const args = JSON.parse(input || "{}") as VoteConfig; + const result = await this.agent.castVote(args); + return `Successfully cast vote on proposal: ${result.toString()}`; + } catch (error) { + throw new Error(`Failed to cast vote: ${error}`); + } + } +} + +export class SolanaGetRealmInfoTool extends Tool { + name = "get_realm_info"; + description = "Get information about a DAO realm"; + + override schema = z + .object({ + input: z.string().optional(), + }) + .transform((args) => args.input); + + constructor(private agent: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const args = JSON.parse(input || "{}") as { + realm: string; + }; + + const result = await this.agent.getRealm(new PublicKey(args.realm)); + return JSON.stringify(result); + } catch (error) { + throw new Error(`Failed to get realm info: ${error}`); + } + } +} + +export class SolanaGetTokenOwnerRecordTool extends Tool { + name = "get_token_owner_record"; + description = "Get token owner record for a member in a DAO realm"; + + override schema = z + .object({ + input: z.string().optional(), + }) + .transform((args) => args.input); + + constructor(private agent: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const args = JSON.parse(input || "{}") as { + realm: string; + governingTokenMint: string; + governingTokenOwner: string; + }; + + const result = await this.agent.getTokenOwnerRecord( + new PublicKey(args.realm), + new PublicKey(args.governingTokenMint), + new PublicKey(args.governingTokenOwner), + ); + return JSON.stringify(result); + } catch (error) { + throw new Error(`Failed to get token owner record: ${error}`); + } + } +} + +export class SolanaGetVoterHistoryTool extends Tool { + name = "get_voter_history"; + description = "Get voting history for a specific voter"; + + override schema = z + .object({ + input: z.string().optional(), + }) + .transform((args) => args.input); + + constructor(private agent: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const args = JSON.parse(input || "{}") as { + voter: string; + }; + + const result = await this.agent.getVoterHistory( + new PublicKey(args.voter), + ); + return JSON.stringify(result); + } catch (error) { + throw new Error(`Failed to get voter history: ${error}`); + } + } +} \ No newline at end of file diff --git a/src/langchain/realm-governance/realm-info.ts b/src/langchain/realm-governance/realm-info.ts deleted file mode 100644 index 493325d7..00000000 --- a/src/langchain/realm-governance/realm-info.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { PublicKey } from "@solana/web3.js"; -import { Tool } from "langchain/tools"; -import { z } from "zod"; -import { SolanaAgentKit } from "../../agent"; - -export class SolanaGetRealmInfoTool extends Tool { - name = "get_realm_info"; - description = "Get information about a DAO realm"; - - override schema = z - .object({ - input: z.string().optional(), - }) - .transform((args) => args.input); - - constructor(private agent: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const args = JSON.parse(input || "{}") as { - realm: string; - }; - - const result = await this.agent.getRealm(new PublicKey(args.realm)); - return JSON.stringify(result); - } catch (error) { - throw new Error(`Failed to get realm info: ${error}`); - } - } -} \ No newline at end of file diff --git a/src/langchain/realm-governance/voter-history.ts b/src/langchain/realm-governance/voter-history.ts deleted file mode 100644 index c2d3c1ae..00000000 --- a/src/langchain/realm-governance/voter-history.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { PublicKey } from "@solana/web3.js"; -import { Tool } from "langchain/tools"; -import { z } from "zod"; -import { SolanaAgentKit } from "../../agent"; - -export class SolanaGetVoterHistoryTool extends Tool { - name = "get_voter_history"; - description = "Get voting history for a specific voter"; - - override schema = z - .object({ - input: z.string().optional(), - }) - .transform((args) => args.input); - - constructor(private agent: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const args = JSON.parse(input || "{}") as { - voter: string; - }; - - const result = await this.agent.getVoterHistory( - new PublicKey(args.voter), - ); - return JSON.stringify(result); - } catch (error) { - throw new Error(`Failed to get voter history: ${error}`); - } - } - } \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts index 6fd1dc2b..88475a23 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -541,12 +541,13 @@ export interface ProposalConfig { export interface VoteConfig { realm: PublicKey; - proposal: PublicKey; choice: number; tokenAmount?: number; governingTokenMint: PublicKey; tokenOwner?: PublicKey; governance: PublicKey; + proposal: PublicKey; + tokenOwnerRecord: PublicKey; } export type VoteType = { From 14ac4e592f4c72b2823505106cd9bc3cb9ac30bd Mon Sep 17 00:00:00 2001 From: mac Date: Tue, 4 Mar 2025 02:22:56 +0530 Subject: [PATCH 5/5] transaction complete --- src/actions/index.ts | 3 --- src/actions/realm-governance/index.ts | 1 - src/agent/index.ts | 2 -- src/langchain/index.ts | 3 --- src/tools/index.ts | 3 +-- 5 files changed, 1 insertion(+), 11 deletions(-) delete mode 100644 src/actions/realm-governance/index.ts diff --git a/src/actions/index.ts b/src/actions/index.ts index 150d31d3..b0d34d2f 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -117,9 +117,6 @@ import getVoterHistoryAction from "./realm-governance/voter-history"; import getTokenOwnerRecordAction from "./realm-governance/owner-record"; - - - export const ACTIONS = { GET_INFO_ACTION: getInfoAction, WALLET_ADDRESS_ACTION: getWalletAddressAction, diff --git a/src/actions/realm-governance/index.ts b/src/actions/realm-governance/index.ts deleted file mode 100644 index 0519ecba..00000000 --- a/src/actions/realm-governance/index.ts +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/agent/index.ts b/src/agent/index.ts index 6b8df262..ff3d4f85 100644 --- a/src/agent/index.ts +++ b/src/agent/index.ts @@ -205,8 +205,6 @@ import { } from "../tools/elfa_ai"; - - /** * Main class for interacting with Solana blockchain * Provides a unified interface for token operations, NFT management, trading and more diff --git a/src/langchain/index.ts b/src/langchain/index.ts index 82adc50d..92783ff8 100644 --- a/src/langchain/index.ts +++ b/src/langchain/index.ts @@ -36,8 +36,6 @@ export * from "./elfa_ai"; export * from "./debridge"; export * from "./fluxbeam"; export * from "./realm-governance"; - - import type { SolanaAgentKit } from "../agent"; import { @@ -168,7 +166,6 @@ import { } from "./index"; - export function createSolanaTools(solanaKit: SolanaAgentKit) { return [ new SolanaGetInfoTool(solanaKit), diff --git a/src/tools/index.ts b/src/tools/index.ts index b8afac0b..074e0516 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -35,5 +35,4 @@ export * from "./switchboard"; export * from "./elfa_ai"; export * from "./fluxbeam"; export * from "./coingecko"; -export * from "./realm-governance"; - +export * from "./realm-governance"; \ No newline at end of file