mango-token-sale/models/accounts.ts

655 lines
16 KiB
TypeScript

import { PublicKey } from '@solana/web3.js'
import BN from 'bn.js'
/// Seed prefix for Governance Program PDAs
export const GOVERNANCE_PROGRAM_SEED = 'governance'
export enum GovernanceAccountType {
Uninitialized = 0,
Realm = 1,
TokenOwnerRecord = 2,
AccountGovernance = 3,
ProgramGovernance = 4,
Proposal = 5,
SignatoryRecord = 6,
VoteRecord = 7,
ProposalInstruction = 8,
MintGovernance = 9,
TokenGovernance = 10,
}
export interface GovernanceAccount {
accountType: GovernanceAccountType
}
export type GovernanceAccountClass =
| typeof Realm
| typeof TokenOwnerRecord
| typeof Governance
| typeof Proposal
| typeof SignatoryRecord
| typeof VoteRecord
| typeof ProposalInstruction
export function getAccountTypes(accountClass: GovernanceAccountClass) {
switch (accountClass) {
case Realm:
return [GovernanceAccountType.Realm]
case TokenOwnerRecord:
return [GovernanceAccountType.TokenOwnerRecord]
case Proposal:
return [GovernanceAccountType.Proposal]
case SignatoryRecord:
return [GovernanceAccountType.SignatoryRecord]
case VoteRecord:
return [GovernanceAccountType.VoteRecord]
case ProposalInstruction:
return [GovernanceAccountType.ProposalInstruction]
case Governance:
return [
GovernanceAccountType.AccountGovernance,
GovernanceAccountType.ProgramGovernance,
GovernanceAccountType.MintGovernance,
GovernanceAccountType.TokenGovernance,
]
default:
throw Error(`${accountClass} account is not supported`)
}
}
export enum VoteThresholdPercentageType {
YesVote = 0,
Quorum = 1,
}
export class VoteThresholdPercentage {
type = VoteThresholdPercentageType.YesVote
value: number
constructor(args: { value: number }) {
this.value = args.value
}
}
export enum VoteWeightSource {
Deposit,
Snapshot,
}
export enum InstructionExecutionStatus {
None,
Success,
Error,
}
export enum InstructionExecutionFlags {
None,
Ordered,
UseTransaction,
}
export enum MintMaxVoteWeightSourceType {
SupplyFraction = 0,
Absolute = 1,
}
export class MintMaxVoteWeightSource {
type = MintMaxVoteWeightSourceType.SupplyFraction
value: BN
constructor(args: { value: BN }) {
this.value = args.value
}
static SUPPLY_FRACTION_BASE = new BN(10000000000)
static SUPPLY_FRACTION_DECIMALS = 10
static FULL_SUPPLY_FRACTION = new MintMaxVoteWeightSource({
value: MintMaxVoteWeightSource.SUPPLY_FRACTION_BASE,
})
isFullSupply() {
return (
this.type === MintMaxVoteWeightSourceType.SupplyFraction &&
this.value.cmp(MintMaxVoteWeightSource.SUPPLY_FRACTION_BASE) === 0
)
}
getSupplyFraction() {
if (this.type !== MintMaxVoteWeightSourceType.SupplyFraction) {
throw new Error('Max vote weight is not fraction')
}
return this.value
}
}
export class RealmConfigArgs {
useCouncilMint: boolean
communityMintMaxVoteWeightSource: MintMaxVoteWeightSource
minCommunityTokensToCreateGovernance: BN
constructor(args: {
useCouncilMint: boolean
communityMintMaxVoteWeightSource: MintMaxVoteWeightSource
minCommunityTokensToCreateGovernance: BN
}) {
this.useCouncilMint = !!args.useCouncilMint
this.communityMintMaxVoteWeightSource =
args.communityMintMaxVoteWeightSource
this.minCommunityTokensToCreateGovernance =
args.minCommunityTokensToCreateGovernance
}
}
export class RealmConfig {
councilMint: PublicKey | undefined
communityMintMaxVoteWeightSource: MintMaxVoteWeightSource
minCommunityTokensToCreateGovernance: BN
reserved: Uint8Array
constructor(args: {
councilMint: PublicKey | undefined
communityMintMaxVoteWeightSource: MintMaxVoteWeightSource
minCommunityTokensToCreateGovernance: BN
reserved: Uint8Array
}) {
this.councilMint = args.councilMint
this.communityMintMaxVoteWeightSource =
args.communityMintMaxVoteWeightSource
this.minCommunityTokensToCreateGovernance =
args.minCommunityTokensToCreateGovernance
this.reserved = args.reserved
}
}
export class Realm {
accountType = GovernanceAccountType.Realm
communityMint: PublicKey
config: RealmConfig
reserved: Uint8Array
authority: PublicKey | undefined
name: string
constructor(args: {
communityMint: PublicKey
reserved: Uint8Array
config: RealmConfig
authority: PublicKey | undefined
name: string
}) {
this.communityMint = args.communityMint
this.config = args.config
this.reserved = args.reserved
this.authority = args.authority
this.name = args.name
}
}
export async function getTokenHoldingAddress(
programId: PublicKey,
realm: PublicKey,
governingTokenMint: PublicKey
) {
const [tokenHoldingAddress] = await PublicKey.findProgramAddress(
[
Buffer.from(GOVERNANCE_PROGRAM_SEED),
realm.toBuffer(),
governingTokenMint.toBuffer(),
],
programId
)
return tokenHoldingAddress
}
export class GovernanceConfig {
voteThresholdPercentage: VoteThresholdPercentage
minCommunityTokensToCreateProposal: BN
minInstructionHoldUpTime: number
maxVotingTime: number
voteWeightSource: VoteWeightSource
proposalCoolOffTime: number
minCouncilTokensToCreateProposal: BN
constructor(args: {
voteThresholdPercentage: VoteThresholdPercentage
minCommunityTokensToCreateProposal: BN
minInstructionHoldUpTime: number
maxVotingTime: number
voteWeightSource?: VoteWeightSource
proposalCoolOffTime?: number
minCouncilTokensToCreateProposal: BN
}) {
this.voteThresholdPercentage = args.voteThresholdPercentage
this.minCommunityTokensToCreateProposal =
args.minCommunityTokensToCreateProposal
this.minInstructionHoldUpTime = args.minInstructionHoldUpTime
this.maxVotingTime = args.maxVotingTime
this.voteWeightSource = args.voteWeightSource ?? VoteWeightSource.Deposit
this.proposalCoolOffTime = args.proposalCoolOffTime ?? 0
this.minCouncilTokensToCreateProposal =
args.minCouncilTokensToCreateProposal
}
}
export class Governance {
accountType: GovernanceAccountType
realm: PublicKey
governedAccount: PublicKey
config: GovernanceConfig
proposalCount: number
reserved?: Uint8Array
constructor(args: {
realm: PublicKey
governedAccount: PublicKey
accountType: number
config: GovernanceConfig
reserved?: Uint8Array
proposalCount: number
}) {
this.accountType = args.accountType
this.realm = args.realm
this.governedAccount = args.governedAccount
this.config = args.config
this.reserved = args.reserved
this.proposalCount = args.proposalCount
}
isProgramGovernance() {
return this.accountType === GovernanceAccountType.ProgramGovernance
}
isAccountGovernance() {
return this.accountType === GovernanceAccountType.AccountGovernance
}
isMintGovernance() {
return this.accountType === GovernanceAccountType.MintGovernance
}
isTokenGovernance() {
return this.accountType === GovernanceAccountType.TokenGovernance
}
}
export class TokenOwnerRecord {
accountType = GovernanceAccountType.TokenOwnerRecord
realm: PublicKey
governingTokenMint: PublicKey
governingTokenOwner: PublicKey
governingTokenDepositAmount: BN
unrelinquishedVotesCount: number
totalVotesCount: number
reserved: Uint8Array
governanceDelegate?: PublicKey
constructor(args: {
realm: PublicKey
governingTokenMint: PublicKey
governingTokenOwner: PublicKey
governingTokenDepositAmount: BN
unrelinquishedVotesCount: number
totalVotesCount: number
reserved: Uint8Array
}) {
this.realm = args.realm
this.governingTokenMint = args.governingTokenMint
this.governingTokenOwner = args.governingTokenOwner
this.governingTokenDepositAmount = args.governingTokenDepositAmount
this.unrelinquishedVotesCount = args.unrelinquishedVotesCount
this.totalVotesCount = args.totalVotesCount
this.reserved = args.reserved
}
}
export async function getTokenOwnerAddress(
programId: PublicKey,
realm: PublicKey,
governingTokenMint: PublicKey,
governingTokenOwner: PublicKey
) {
const [tokenOwnerRecordAddress] = await PublicKey.findProgramAddress(
[
Buffer.from(GOVERNANCE_PROGRAM_SEED),
realm.toBuffer(),
governingTokenMint.toBuffer(),
governingTokenOwner.toBuffer(),
],
programId
)
return tokenOwnerRecordAddress
}
export enum ProposalState {
Draft,
SigningOff,
Voting,
Succeeded,
Executing,
Completed,
Cancelled,
Defeated,
ExecutingWithErrors,
}
export class Proposal {
accountType = GovernanceAccountType.Proposal
governance: PublicKey
governingTokenMint: PublicKey
state: ProposalState
tokenOwnerRecord: PublicKey
signatoriesCount: number
signatoriesSignedOffCount: number
yesVotesCount: BN
noVotesCount: BN
instructionsExecutedCount: number
instructionsCount: number
instructionsNextIndex: number
draftAt: BN
signingOffAt: BN | null
votingAt: BN | null
votingAtSlot: BN | null
votingCompletedAt: BN | null
executingAt: BN | null
closedAt: BN | null
executionFlags: InstructionExecutionFlags
maxVoteWeight: BN | null
voteThresholdPercentage: VoteThresholdPercentage | null
name: string
descriptionLink: string
constructor(args: {
governance: PublicKey
governingTokenMint: PublicKey
state: ProposalState
tokenOwnerRecord: PublicKey
signatoriesCount: number
signatoriesSignedOffCount: number
descriptionLink: string
name: string
yesVotesCount: BN
noVotesCount: BN
draftAt: BN
signingOffAt: BN | null
votingAt: BN | null
votingAtSlot: BN | null
votingCompletedAt: BN | null
executingAt: BN | null
closedAt: BN | null
instructionsExecutedCount: number
instructionsCount: number
instructionsNextIndex: number
executionFlags: InstructionExecutionFlags
maxVoteWeight: BN | null
voteThresholdPercentage: VoteThresholdPercentage | null
}) {
this.governance = args.governance
this.governingTokenMint = args.governingTokenMint
this.state = args.state
this.tokenOwnerRecord = args.tokenOwnerRecord
this.signatoriesCount = args.signatoriesCount
this.signatoriesSignedOffCount = args.signatoriesSignedOffCount
this.descriptionLink = args.descriptionLink
this.name = args.name
this.yesVotesCount = args.yesVotesCount
this.noVotesCount = args.noVotesCount
this.draftAt = args.draftAt
this.signingOffAt = args.signingOffAt
this.votingAt = args.votingAt
this.votingAtSlot = args.votingAtSlot
this.votingCompletedAt = args.votingCompletedAt
this.executingAt = args.executingAt
this.closedAt = args.closedAt
this.instructionsExecutedCount = args.instructionsExecutedCount
this.instructionsCount = args.instructionsCount
this.instructionsNextIndex = args.instructionsNextIndex
this.executionFlags = args.executionFlags
this.maxVoteWeight = args.maxVoteWeight
this.voteThresholdPercentage = args.voteThresholdPercentage
}
/// Returns true if Proposal is in state when no voting can happen any longer
isVoteFinalized(): boolean {
switch (this.state) {
case ProposalState.Succeeded:
case ProposalState.Executing:
case ProposalState.Completed:
case ProposalState.Cancelled:
case ProposalState.Defeated:
case ProposalState.ExecutingWithErrors:
return true
case ProposalState.Draft:
case ProposalState.SigningOff:
case ProposalState.Voting:
return false
}
}
isFinalState(): boolean {
// 1) ExecutingWithErrors is not really a final state, it's undefined.
// However it usually indicates none recoverable execution error so we treat is as final for the ui purposes
// 2) Succeeded with no instructions is also treated as final since it can't transition any longer
// It really doesn't make any sense but until it's solved in the program we have to consider it as final in the ui
switch (this.state) {
case ProposalState.Completed:
case ProposalState.Cancelled:
case ProposalState.Defeated:
case ProposalState.ExecutingWithErrors:
return true
case ProposalState.Succeeded:
return this.instructionsCount === 0
case ProposalState.Executing:
case ProposalState.Draft:
case ProposalState.SigningOff:
case ProposalState.Voting:
return false
}
}
getStateTimestamp(): number {
switch (this.state) {
case ProposalState.Succeeded:
case ProposalState.Defeated:
return this.votingCompletedAt ? this.votingCompletedAt.toNumber() : 0
case ProposalState.Completed:
case ProposalState.Cancelled:
return this.closedAt ? this.closedAt.toNumber() : 0
case ProposalState.Executing:
case ProposalState.ExecutingWithErrors:
return this.executingAt ? this.executingAt.toNumber() : 0
case ProposalState.Draft:
return this.draftAt.toNumber()
case ProposalState.SigningOff:
return this.signingOffAt ? this.signingOffAt.toNumber() : 0
case ProposalState.Voting:
return this.votingAt ? this.votingAt.toNumber() : 0
}
}
getStateSortRank(): number {
// Always show proposals in voting state at the top
if (this.state === ProposalState.Voting) {
return 2
}
// Then show proposals in pending state and finalized at the end
return this.isFinalState() ? 0 : 1
}
/// Returns true if Proposal has not been voted on yet
isPreVotingState() {
return !this.votingAtSlot
}
}
export class SignatoryRecord {
accountType: GovernanceAccountType = GovernanceAccountType.SignatoryRecord
proposal: PublicKey
signatory: PublicKey
signedOff: boolean
constructor(args: {
proposal: PublicKey
signatory: PublicKey
signedOff: boolean
}) {
this.proposal = args.proposal
this.signatory = args.signatory
this.signedOff = !!args.signedOff
}
}
export async function getSignatoryRecordAddress(
programId: PublicKey,
proposal: PublicKey,
signatory: PublicKey
) {
const [signatoryRecordAddress] = await PublicKey.findProgramAddress(
[
Buffer.from(GOVERNANCE_PROGRAM_SEED),
proposal.toBuffer(),
signatory.toBuffer(),
],
programId
)
return signatoryRecordAddress
}
export class VoteWeight {
yes: BN
no: BN
constructor(args: { yes: BN; no: BN }) {
this.yes = args.yes
this.no = args.no
}
}
export class VoteRecord {
accountType = GovernanceAccountType.VoteRecord
proposal: PublicKey
governingTokenOwner: PublicKey
isRelinquished: boolean
voteWeight: VoteWeight
constructor(args: {
proposal: PublicKey
governingTokenOwner: PublicKey
isRelinquished: boolean
voteWeight: VoteWeight
}) {
this.proposal = args.proposal
this.governingTokenOwner = args.governingTokenOwner
this.isRelinquished = !!args.isRelinquished
this.voteWeight = args.voteWeight
}
}
export class AccountMetaData {
pubkey: PublicKey
isSigner: boolean
isWritable: boolean
constructor(args: {
pubkey: PublicKey
isSigner: boolean
isWritable: boolean
}) {
this.pubkey = args.pubkey
this.isSigner = !!args.isSigner
this.isWritable = !!args.isWritable
}
}
export class InstructionData {
programId: PublicKey
accounts: AccountMetaData[]
data: Uint8Array
constructor(args: {
programId: PublicKey
accounts: AccountMetaData[]
data: Uint8Array
}) {
this.programId = args.programId
this.accounts = args.accounts
this.data = args.data
}
}
export class ProposalInstruction {
accountType = GovernanceAccountType.ProposalInstruction
proposal: PublicKey
instructionIndex: number
holdUpTime: number
instruction: InstructionData
executedAt: BN | null
executionStatus: InstructionExecutionStatus
constructor(args: {
proposal: PublicKey
instructionIndex: number
holdUpTime: number
instruction: InstructionData
executedAt: BN | null
executionStatus: InstructionExecutionStatus
}) {
this.proposal = args.proposal
this.instructionIndex = args.instructionIndex
this.holdUpTime = args.holdUpTime
this.instruction = args.instruction
this.executedAt = args.executedAt
this.executionStatus = args.executionStatus
}
}