import {CardanoTypes} from '../types' import {GovernanceApi} from './api' import {convertHexKeyHashToBech32Format, parseDrepId} from './helpers' import {StakingKeyState} from './types' import {App, Chain} from '@yoroi/types' export type Config = { network: Chain.SupportedNetworks walletId: string cardano: CardanoTypes.Wasm storage: App.Storage api: GovernanceApi } export type VoteKind = 'abstain' | 'no-confidence' export type GovernanceAction = | { kind: 'delegate-to-drep' hash: string type: 'script' | 'key' txID: string } | { kind: 'vote' vote: VoteKind txID: string } export type GovernanceManager = { readonly network: Chain.Network validateDRepID: (drepID: string) => Promise createDelegationCertificate: ( hash: string, type: 'script' | 'key', stakingKey: CardanoTypes.PublicKey, ) => Promise createLedgerDelegationPayload: ( hash: string, type: 'script' | 'key', stakingKey: CardanoTypes.PublicKey, ) => Promise createVotingCertificate: ( vote: VoteKind, stakingKey: CardanoTypes.PublicKey, ) => Promise createLedgerVotingPayload: ( vote: VoteKind, stakingKey: CardanoTypes.PublicKey, ) => Promise createStakeRegistrationCertificate: ( stakingKey: CardanoTypes.PublicKey, ) => Promise // latest governance action to be used only to check the "pending" transaction that is not yet confirmed on the blockchain setLatestGovernanceAction: (action: GovernanceAction | null) => Promise getLatestGovernanceAction: () => Promise getStakingKeyState: (stakeKeyHash: string) => Promise convertHexKeyHashToBech32Format: (hexKeyHash: string) => Promise } export const governanceManagerMaker = (config: Config): GovernanceManager => { return new Manager(config) } class Manager implements GovernanceManager { readonly network: Chain.Network constructor(private config: Config) { this.network = config.network } async convertHexKeyHashToBech32Format(hexKeyHash: string): Promise { return await convertHexKeyHashToBech32Format( hexKeyHash, this.config.cardano, ) } async getStakingKeyState(stakeKeyHash: string) { const {api} = this.config const response = await api.getStakingKeyState(stakeKeyHash) if (response.drepDelegation) { if (response.drepDelegation.drep === 'no_confidence') { const {tx, slot, epoch} = response.drepDelegation return { drepDelegation: {action: 'no-confidence', tx, slot, epoch}, } as const } if (response.drepDelegation.drep === 'abstain') { const {tx, slot, epoch} = response.drepDelegation return { drepDelegation: {action: 'abstain', tx, slot, epoch}, } as const } const {tx, slot, epoch, drep, drepKind} = response.drepDelegation return { drepDelegation: { action: 'drep', tx, slot, epoch, hash: drep, type: drepKind === 'scripthash' ? 'script' : 'key', }, } as const } return {} } async createDelegationCertificate( hash: string, type: 'script' | 'key', stakingKey: CardanoTypes.PublicKey, ): Promise { const { Certificate, Ed25519KeyHash, Credential, VoteDelegation, DRep, ScriptHash, } = this.config.cardano const stakingCredential = await Credential.fromKeyhash( await stakingKey.hash(), ) const votingDelegation = type === 'key' ? await DRep.newKeyHash( await Ed25519KeyHash.fromBytes(Buffer.from(hash, 'hex')), ) : await DRep.newScriptHash( await ScriptHash.fromBytes(Buffer.from(hash, 'hex')), ) return await Certificate.newVoteDelegation( await VoteDelegation.new(stakingCredential, votingDelegation), ) } async createStakeRegistrationCertificate( stakingKey: CardanoTypes.PublicKey, ): Promise { const {Certificate, Credential, StakeRegistration} = this.config.cardano const stakingCredential = await Credential.fromKeyhash( await stakingKey.hash(), ) return await Certificate.newStakeRegistration( await StakeRegistration.new(stakingCredential), ) } async validateDRepID(drepId: string): Promise { const {hash} = await parseDrepId(drepId, this.config.cardano) const drepStatus = await this.config.api.getDRepById(hash) if (!drepStatus || !drepStatus.epoch) { throw new Error('DRep ID not registered') } } async createLedgerDelegationPayload( _drepID: string, _type: 'script' | 'key', _stakingKey: CardanoTypes.PublicKey, ): Promise { throw new Error('Not implemented') } async createVotingCertificate( vote: VoteKind, stakingKey: CardanoTypes.PublicKey, ): Promise { const {Certificate, Credential, VoteDelegation, DRep} = this.config.cardano const stakingCredential = await Credential.fromKeyhash( await stakingKey.hash(), ) if (vote === 'abstain') { return await Certificate.newVoteDelegation( await VoteDelegation.new( stakingCredential, await DRep.newAlwaysAbstain(), ), ) } if (vote === 'no-confidence') { return await Certificate.newVoteDelegation( await VoteDelegation.new( stakingCredential, await DRep.newAlwaysNoConfidence(), ), ) } throw new Error('Invalid vote') } async createLedgerVotingPayload( _vote: VoteKind, _stakingKey: CardanoTypes.PublicKey, ): Promise { throw new Error('Not implemented') } async setLatestGovernanceAction( action: GovernanceAction | null, ): Promise { if (!action) { await this.config.storage .join(`${this.config.network}/`) .removeItem(LATEST_ACTION_KEY) return } await this.config.storage .join(`${this.config.network}/`) .setItem(LATEST_ACTION_KEY, action) } async getLatestGovernanceAction(): Promise { try { return await this.config.storage .join(`${this.config.network}/`) .getItem(LATEST_ACTION_KEY) } catch { return null } } } const LATEST_ACTION_KEY = 'latest-action-v2'