UNPKG

@tribecahq/tribeca-sdk

Version:

The TypeScript SDK for Tribeca, an open standard and toolkit for launching DAOs on Solana.

231 lines (217 loc) 6.23 kB
import type { TransactionEnvelope } from "@saberhq/solana-contrib"; import type { TokenAmount } from "@saberhq/token-utils"; import { getATAAddress, getOrCreateATA, TOKEN_PROGRAM_ID, } from "@saberhq/token-utils"; import type { PublicKey, TransactionInstruction } from "@solana/web3.js"; import { SystemProgram } from "@solana/web3.js"; import BN from "bn.js"; import { TRIBECA_ADDRESSES } from "../../constants"; import type { EscrowData, LockerData } from "../../programs/lockedVoter"; import type { TribecaSDK } from "../../sdk"; import { findVoteAddress } from "../govern/pda"; import type { VoteSide } from "../govern/types"; export class VoteEscrow { private _lockerData: LockerData | null = null; private _escrowData: EscrowData | null = null; constructor( readonly sdk: TribecaSDK, readonly locker: PublicKey, readonly governorKey: PublicKey, readonly escrowKey: PublicKey, readonly owner: PublicKey ) {} get provider() { return this.sdk.provider; } get lockerProgram() { return this.sdk.programs.LockedVoter; } /** * Locker data. */ async lockerData() { if (!this._lockerData) { this._lockerData = await this.lockerProgram.account.locker.fetch( this.locker ); } return this._lockerData; } /** * Escrow data. */ async data() { if (!this._escrowData) { this._escrowData = await this.lockerProgram.account.escrow.fetch( this.escrowKey ); } return this._escrowData; } /** * Creates a function to calculate the voting power of this escrow. * @returns */ async makeCalculateVotingPower(): Promise<(timestampSeconds: number) => BN> { const escrowData = await this.data(); const lockerData = await this.lockerData(); return (timestampSeconds: number) => { if (escrowData.escrowStartedAt.eq(new BN(0))) { return new BN(0); } if ( timestampSeconds < escrowData.escrowStartedAt.toNumber() || timestampSeconds >= escrowData.escrowEndsAt.toNumber() ) { return new BN(0); } const secondsUntilLockupExpiry = escrowData.escrowEndsAt .sub(new BN(timestampSeconds)) .toNumber(); const relevantSecondsUntilLockupExpiry = Math.min( secondsUntilLockupExpiry, lockerData.params.maxStakeDuration.toNumber() ); const powerIfMaxLockup = escrowData.amount.mul( new BN(lockerData.params.maxStakeVoteMultiplier) ); return powerIfMaxLockup .mul(new BN(relevantSecondsUntilLockupExpiry)) .div(lockerData.params.maxStakeDuration); }; } /** * Calculates the voting power of this escrow. * @param time Optional time to calculate power for. * @returns */ async calculateVotingPower(time: Date = new Date()): Promise<BN> { return (await this.makeCalculateVotingPower())( Math.floor(time.getTime() / 1_000) ); } /** * Activates a proposal. * @returns */ activateProposal(proposal: PublicKey): TransactionEnvelope { return this.provider.newTX([ this.lockerProgram.instruction.activateProposal({ accounts: { locker: this.locker, governor: this.governorKey, proposal, escrow: this.escrowKey, escrowOwner: this.owner, governProgram: TRIBECA_ADDRESSES.Govern, }, }), ]); } /** * Casts a vote on a proposal. * @returns */ async castVote({ proposal, side, }: { proposal: PublicKey; side: VoteSide; }): Promise<TransactionEnvelope> { const [voteKey, voteBump] = await findVoteAddress(proposal, this.owner); const vote = await this.provider.getAccountInfo(voteKey); let createVoteIX: TransactionInstruction | null = null; if (!vote) { createVoteIX = this.sdk.programs.Govern.instruction.newVote( voteBump, this.owner, { accounts: { proposal, vote: voteKey, payer: this.provider.wallet.publicKey, systemProgram: SystemProgram.programId, }, } ); } return this.provider.newTX([ createVoteIX, this.lockerProgram.instruction.castVote(side, { accounts: { locker: this.locker, escrow: this.escrowKey, voteDelegate: this.owner, proposal, vote: voteKey, governor: this.governorKey, governProgram: TRIBECA_ADDRESSES.Govern, }, }), ]); } /** * Locks tokens into the escrow. * @param amount * @param durationSeconds The duration of the lock, in seconds * @param authority * @returns */ async lock( amount: TokenAmount, durationSeconds: number ): Promise<TransactionEnvelope> { const escrowData = await this.data(); const sourceTokens = await getATAAddress({ mint: amount.token.mintAccount, owner: escrowData.owner, }); return this.provider.newTX([ this.lockerProgram.instruction.lock( amount.toU64(), new BN(durationSeconds), { accounts: { locker: this.locker, escrow: this.escrowKey, escrowTokens: escrowData.tokens, escrowOwner: escrowData.owner, sourceTokens, tokenProgram: TOKEN_PROGRAM_ID, }, } ), ]); } /** * Exits the escrow. * @returns */ async exit(): Promise<TransactionEnvelope> { const lockerData = await this.lockerData(); const escrowData = await this.data(); const destinationTokens = await getOrCreateATA({ provider: this.provider, mint: lockerData.tokenMint, owner: escrowData.owner, }); return this.provider.newTX([ destinationTokens.instruction, this.lockerProgram.instruction.exit({ accounts: { locker: this.locker, escrow: this.escrowKey, escrowOwner: escrowData.owner, escrowTokens: escrowData.tokens, destinationTokens: destinationTokens.address, payer: this.provider.wallet.publicKey, tokenProgram: TOKEN_PROGRAM_ID, }, }), ]); } }