Skip to main content
Glama
dao.ts14.6 kB
import { Wallet } from '@midnight-ntwrk/wallet-api'; import { Resource } from '@midnight-ntwrk/wallet'; import { createLogger } from '../logger/index.js'; import { configureProviders, joinDaoVotingContract, openElection, closeElection, castVote, fundTreasury, payoutApprovedProposal, getElectionStatus, displayDaoVotingState, VoteType, type DaoVotingProviders, type DeployedDaoVotingContract, type ElectionStatus } from '../integrations/dao/index.js'; import { getDaoConfigFromEnv, type DaoConfig } from './dao-config.js'; import { tokenType } from '@midnight-ntwrk/compact-runtime'; import { randomBytes } from 'crypto'; import { CoinInfo } from '../types/wallet.js'; const logger = createLogger('dao-service'); /** * Helper function to serialize BigInt values to strings for JSON serialization */ function serializeBigInts(obj: any): any { if (obj === null || obj === undefined) { return obj; } if (typeof obj === 'bigint') { return obj.toString(); } if (Array.isArray(obj)) { return obj.map(serializeBigInts); } if (typeof obj === 'object') { const result: any = {}; for (const [key, value] of Object.entries(obj)) { result[key] = serializeBigInts(value); } return result; } return obj; } export interface DaoOperationResult { success: boolean; message?: string; data?: any; error?: string; } // Use the standard CoinInfo interface from types/wallet.ts // export interface VoteCoinInfo { // nonce: string; // color: string; // value: string; // } /** * Helper function to pad string to specified length (required for token type generation) * pad(n, s): UTF-8 bytes of s followed by 0x00 up to length n */ function padBytes(n: number, s: string): Uint8Array { const bytes = new TextEncoder().encode(s); if (bytes.length > n) throw new Error('String too long for pad length'); const out = new Uint8Array(n); out.set(bytes); return out; } /** * Generate vote coin color using the same logic as the working DAO code * @param voteTokenContractAddress Vote token contract address * @returns Vote coin color as hex string */ function generateVoteCoinColor(voteTokenContractAddress: string): string { const domainSep = padBytes(32, 'dega_dao_vote'); return tokenType(domainSep, voteTokenContractAddress); } /** * Generate fund coin color using the same logic as the working DAO code * @param fundTokenContractAddress Fund token contract address * @returns Fund coin color as hex string */ function generateFundCoinColor(fundTokenContractAddress: string): string { const domainSep = padBytes(32, 'dega_funding_token'); return tokenType(domainSep, fundTokenContractAddress); } export class DaoService { private wallet: Wallet & Resource; private providers: DaoVotingProviders | null = null; private daoConfig: DaoConfig | null = null; constructor(wallet: Wallet & Resource) { this.wallet = wallet; this.loadDaoConfig(); } /** * Load DAO configuration from environment variables */ private loadDaoConfig(): void { const configResult = getDaoConfigFromEnv(); if (configResult.success && configResult.config) { this.daoConfig = configResult.config; logger.info('DAO configuration loaded successfully', this.daoConfig); } else { logger.warn('No DAO configuration found in environment variables', configResult.error); } } /** * Get DAO configuration */ private getDaoConfig(): DaoConfig { if (!this.daoConfig) { throw new Error('DAO configuration not found. Please set DAO environment variable.'); } return this.daoConfig; } /** * Initialize DAO providers */ private async initializeProviders(): Promise<DaoVotingProviders> { if (!this.providers) { this.providers = await configureProviders(this.wallet); } return this.providers; } /** * Get DAO providers (initialize if needed) */ private async getDaoProviders(): Promise<DaoVotingProviders> { return await this.initializeProviders(); } /** * Get DAO voting contract instance */ private async getDaoVotingContract(): Promise<DeployedDaoVotingContract> { const config = this.getDaoConfig(); const providers = await this.initializeProviders(); return await joinDaoVotingContract(providers, config.contractAddress); } /** * Open a new election in the DAO voting contract */ public async openDaoElection(electionId: string): Promise<DaoOperationResult> { try { const config = this.getDaoConfig(); logger.info(`Opening DAO election: ${electionId} for contract: ${config.contractAddress}`); const daoVotingContract = await this.getDaoVotingContract(); const result = await openElection(daoVotingContract, electionId); // Serialize BigInt values for JSON response const serializedResult = serializeBigInts(result); logger.info(`DAO election opened successfully: ${electionId}`); return { success: true, message: `Election ${electionId} opened successfully`, data: serializedResult }; } catch (error) { logger.error('Error opening DAO election:', error); return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } } /** * Close the current election in the DAO voting contract */ public async closeDaoElection(): Promise<DaoOperationResult> { try { const config = this.getDaoConfig(); logger.info(`Closing DAO election for contract: ${config.contractAddress}`); const daoVotingContract = await this.getDaoVotingContract(); const result = await closeElection(daoVotingContract); // Serialize BigInt values for JSON response const serializedResult = serializeBigInts(result); logger.info(`DAO election closed successfully for contract: ${config.contractAddress}`); return { success: true, message: 'Election closed successfully', data: serializedResult }; } catch (error) { logger.error('Error closing DAO election:', error); return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } } /** * Convert natural language vote string to VoteType enum */ private convertVoteStringToType(voteString: string): VoteType { const normalizedVote = voteString.toLowerCase().trim(); switch (normalizedVote) { case 'yes': return VoteType.YES; // 0 case 'no': return VoteType.NO; // 1 case 'absence': case 'absent': return VoteType.ABSENT; // 2 default: throw new Error(`Invalid vote type: ${voteString}. Must be 'yes', 'no', or 'absence'`); } } /** * Cast a vote in the DAO election */ public async castDaoVote(voteType: string): Promise<DaoOperationResult> { try { const config = this.getDaoConfig(); logger.info(`Casting DAO vote: ${voteType} for contract: ${config.contractAddress}`); // Convert natural language vote string to VoteType enum const convertedVoteType = this.convertVoteStringToType(voteType); // Get the DAO contract state to retrieve the correct vote coin color const daoVotingContract = await this.getDaoVotingContract(); const providers = await this.getDaoProviders(); const { state } = await displayDaoVotingState(providers, daoVotingContract); if (!state) { throw new Error('Failed to retrieve DAO contract state'); } // Use the actual vote coin color from the contract state const voteCoinColor = state.dao_vote_coin_color; logger.info(`Using DAO vote coin color from contract: ${Buffer.from(voteCoinColor).toString('hex')}`); const voteCoin: CoinInfo = { nonce: randomBytes(32), // Generate random nonce for uniqueness (Uint8Array) color: voteCoinColor, // Use the exact color from contract state (already Uint8Array) value: BigInt(config.voteCoinValue) // Convert string to bigint }; const result = await castVote(daoVotingContract, convertedVoteType, voteCoin); // Serialize BigInt values for JSON response const serializedResult = serializeBigInts(result); logger.info(`DAO vote cast successfully: ${voteType}`); return { success: true, message: `Vote ${voteType} cast successfully`, data: serializedResult }; } catch (error) { logger.error('Error casting DAO vote:', error); return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } } /** * Fund the DAO treasury with tokens */ public async fundDaoTreasury(amount: string): Promise<DaoOperationResult> { try { const config = this.getDaoConfig(); logger.info(`Funding DAO treasury for contract: ${config.contractAddress} with amount: ${amount}`); // Get the DAO contract state to retrieve the correct treasury coin color const daoVotingContract = await this.getDaoVotingContract(); const providers = await this.getDaoProviders(); const { state } = await displayDaoVotingState(providers, daoVotingContract); if (!state) { throw new Error('Failed to retrieve DAO contract state'); } // Use the actual treasury coin color from the contract state const treasuryCoinColor = state.treasury.color; logger.info(`Using DAO treasury coin color from contract: ${Buffer.from(treasuryCoinColor).toString('hex')}`); const fundCoin: CoinInfo = { nonce: randomBytes(32), // Generate random nonce for uniqueness (Uint8Array) color: treasuryCoinColor, // Use the exact color from contract state (already Uint8Array) value: BigInt(amount) // Convert string to bigint }; const result = await fundTreasury(daoVotingContract, fundCoin); // Serialize BigInt values for JSON response const serializedResult = serializeBigInts(result); logger.info(`DAO treasury funded successfully for contract: ${config.contractAddress}`); return { success: true, message: 'Treasury funded successfully', data: serializedResult }; } catch (error) { logger.error('Error funding DAO treasury:', error); return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } } /** * Payout an approved proposal from the DAO treasury */ public async payoutDaoProposal(): Promise<DaoOperationResult> { try { const config = this.getDaoConfig(); logger.info(`Paying out DAO proposal for contract: ${config.contractAddress}`); // Get the DAO contract state to check conditions before payout const daoVotingContract = await this.getDaoVotingContract(); const providers = await this.getDaoProviders(); const { state } = await displayDaoVotingState(providers, daoVotingContract); if (!state) { throw new Error('Failed to retrieve DAO contract state'); } // Check if election is closed (payout typically requires closed election) if (state.election_open) { logger.warn('Election is still open. Payout may require election to be closed first.'); } // Check if treasury has funds if (state.treasury.value === 0n) { logger.warn('Treasury has no funds to payout.'); } const result = await payoutApprovedProposal(daoVotingContract); // Serialize BigInt values for JSON response const serializedResult = serializeBigInts(result); logger.info(`DAO proposal paid out successfully for contract: ${config.contractAddress}`); return { success: true, message: 'Proposal paid out successfully', data: serializedResult }; } catch (error) { logger.error('Error paying out DAO proposal:', error); logger.error('Error details:', { message: error instanceof Error ? error.message : 'Unknown error', stack: error instanceof Error ? error.stack : undefined, name: error instanceof Error ? error.name : undefined }); return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } } /** * Get the current status of the DAO election */ public async getDaoElectionStatus(): Promise<DaoOperationResult> { try { const config = this.getDaoConfig(); logger.info(`Getting DAO election status for contract: ${config.contractAddress}`); const providers = await this.initializeProviders(); const result = await getElectionStatus(providers, config.contractAddress); // Serialize BigInt values for JSON response const serializedResult = serializeBigInts(result); logger.info(`DAO election status retrieved for contract: ${config.contractAddress}`); return { success: true, message: 'Election status retrieved successfully', data: serializedResult }; } catch (error) { logger.error('Error getting DAO election status:', error); return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } } /** * Get the full state of the DAO voting contract */ public async getDaoState(): Promise<DaoOperationResult> { try { const config = this.getDaoConfig(); logger.info(`Getting DAO state for contract: ${config.contractAddress}`); const providers = await this.initializeProviders(); const daoVotingContract = await this.getDaoVotingContract(); const result = await displayDaoVotingState(providers, daoVotingContract); // Serialize BigInt values for JSON response const serializedResult = serializeBigInts(result); logger.info(`DAO state retrieved for contract: ${config.contractAddress}`); return { success: true, message: 'DAO state retrieved successfully', data: serializedResult }; } catch (error) { logger.error('Error getting DAO state:', error); return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/evilpixi/pixi-midnight-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server