UNPKG

@tribecahq/tribeca-sdk

Version:

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

448 lines (405 loc) 12.3 kB
import type { BN } from "@project-serum/anchor"; import { createMemoInstruction, TransactionEnvelope, } from "@saberhq/solana-contrib"; import { getOrCreateATA, TOKEN_PROGRAM_ID } from "@saberhq/token-utils"; import type { TransactionInstruction } from "@solana/web3.js"; import { PublicKey, SystemProgram, SYSVAR_INSTRUCTIONS_PUBKEY, } from "@solana/web3.js"; import invariant from "tiny-invariant"; import { TRIBECA_ADDRESSES } from "../../constants"; import type { EscrowData, LockedVoterProgram, LockerData, LockerParams, ProposalData, } from "../../programs"; import type { TribecaSDK } from "../../sdk"; import type { VoteSide } from "../../wrappers/govern/types"; import { GovernorWrapper } from "../govern/governor"; import { findWhitelistAddress } from "."; import { findEscrowAddress } from "./pda"; /** * Helper methods around a Locked Voter electorate. */ export class LockerWrapper { readonly program: LockedVoterProgram; readonly governor: GovernorWrapper; private _lockerData: LockerData | null = null; constructor( readonly sdk: TribecaSDK, readonly locker: PublicKey, readonly governorKey: PublicKey ) { this.program = sdk.programs.LockedVoter; this.governor = new GovernorWrapper(sdk, governorKey); } static async load( sdk: TribecaSDK, lockerKey: PublicKey, governorKey: PublicKey ): Promise<LockerWrapper> { const wrapper = new LockerWrapper(sdk, lockerKey, governorKey); await wrapper.data(); return wrapper; } /** * Fetches the data of the locker. * @returns */ async reload(): Promise<LockerData> { return this.program.account.locker.fetch(this.locker); } async fetchProposalData(proposalKey: PublicKey): Promise<ProposalData> { return await this.sdk.govern.program.account.proposal.fetch(proposalKey); } async fetchEscrow(escrowKey: PublicKey): Promise<EscrowData> { return await this.program.account.escrow.fetch(escrowKey); } async fetchEscrowByAuthority( authority: PublicKey = this.sdk.provider.wallet.publicKey ): Promise<EscrowData> { const [escrowKey] = await findEscrowAddress(this.locker, authority); return this.fetchEscrow(escrowKey); } /** * Fetches the data of the locker. * @returns */ async data(): Promise<LockerData> { if (!this._lockerData) { this._lockerData = await this.reload(); } return this._lockerData; } async getOrCreateEscrow( authority: PublicKey = this.sdk.provider.wallet.publicKey ): Promise<{ escrow: PublicKey; instruction: TransactionInstruction | null; }> { const [escrow] = await findEscrowAddress(this.locker, authority); const escrowData = await this.program.account.escrow.fetchNullable(escrow); if (escrowData) { return { escrow: escrow, instruction: null }; } else { return { escrow: escrow, instruction: await this.newEscrowIX(authority), }; } } /** * Creates the instruction to build a new Escrow. * @param authority * @returns */ async newEscrowIX( authority: PublicKey = this.sdk.provider.wallet.publicKey ): Promise<TransactionInstruction> { const [escrow, bump] = await findEscrowAddress(this.locker, authority); return this.program.instruction.newEscrow(bump, { accounts: { locker: this.locker, escrow, escrowOwner: authority, payer: this.sdk.provider.wallet.publicKey, systemProgram: SystemProgram.programId, }, }); } async activateProposal({ proposal, authority = this.sdk.provider.wallet.publicKey, }: { proposal: PublicKey; authority?: PublicKey; }): Promise<TransactionEnvelope> { const [escrow] = await findEscrowAddress(this.locker, authority); const ix = this.program.instruction.activateProposal({ accounts: { locker: this.locker, governor: this.governorKey, proposal, escrow, escrowOwner: authority, governProgram: TRIBECA_ADDRESSES.Govern, }, }); return new TransactionEnvelope(this.sdk.provider, [ix]); } async lockTokensV1({ amount, duration, authority = this.sdk.provider.wallet.publicKey, }: { amount: BN; duration: BN; authority?: PublicKey; }): Promise<TransactionEnvelope> { invariant(this.locker, "locker not set"); const { escrow, instruction: initEscrowIx } = await this.getOrCreateEscrow( authority ); const { govTokenAccount, govTokenVault, instructions } = await this._getOrCreateGovTokenATAsInternal(authority, escrow); if (initEscrowIx) { instructions.push(initEscrowIx); } const lockerData = await this.reload(); instructions.push( this.program.instruction.lock(amount, duration, { accounts: { locker: this.locker, escrow: escrow, escrowOwner: authority, escrowTokens: govTokenVault, sourceTokens: govTokenAccount, tokenProgram: TOKEN_PROGRAM_ID, }, remainingAccounts: lockerData.params.whitelistEnabled ? [ { pubkey: SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false, }, { pubkey: PublicKey.default, isSigner: false, isWritable: false, }, ] : [], }) ); return new TransactionEnvelope(this.sdk.provider, instructions); } async lockTokens({ amount, duration, authority = this.sdk.provider.wallet.publicKey, }: { amount: BN; duration: BN; authority?: PublicKey; }): Promise<TransactionEnvelope> { invariant(this.locker, "locker not set"); const { escrow, instruction: initEscrowIx } = await this.getOrCreateEscrow( authority ); const { govTokenAccount, govTokenVault, instructions } = await this._getOrCreateGovTokenATAsInternal(authority, escrow); if (initEscrowIx) { instructions.push(initEscrowIx); } const lockerData = await this.reload(); const lockAccounts = { locker: this.locker, escrow: escrow, escrowOwner: authority, escrowTokens: govTokenVault, sourceTokens: govTokenAccount, tokenProgram: TOKEN_PROGRAM_ID, }; if (lockerData.params.whitelistEnabled) { instructions.push( this.program.instruction.lockWithWhitelist(amount, duration, { accounts: { lock: lockAccounts, instructionsSysvar: SYSVAR_INSTRUCTIONS_PUBKEY, }, }) ); } else { instructions.push( this.program.instruction.lockPermissionless(amount, duration, { accounts: lockAccounts, }) ); } return new TransactionEnvelope(this.sdk.provider, instructions); } async exit({ authority = this.sdk.provider.wallet.publicKey, }: { authority?: PublicKey; }): Promise<TransactionEnvelope> { invariant(this.locker, "locker not set"); const [escrow] = await findEscrowAddress(this.locker, authority); const escrowData = await this.fetchEscrow(escrow); const { govTokenAccount, instructions } = await this._getOrCreateGovTokenATAsInternal(authority, escrow); instructions.push( this.program.instruction.exit({ accounts: { locker: this.locker, escrow, escrowOwner: authority, escrowTokens: escrowData.tokens, destinationTokens: govTokenAccount, payer: this.sdk.provider.wallet.publicKey, tokenProgram: TOKEN_PROGRAM_ID, }, }) ); return new TransactionEnvelope(this.sdk.provider, instructions); } async castVotes({ voteSide, proposal, authority = this.sdk.provider.wallet.publicKey, reason, }: { voteSide: VoteSide; proposal: PublicKey; authority?: PublicKey; reason?: string; }): Promise<TransactionEnvelope> { const ixs: TransactionInstruction[] = []; const { escrow, instruction: escrowIx } = await this.getOrCreateEscrow( authority ); if (escrowIx) { ixs.push(escrowIx); } const { voteKey, instruction: createVoteIX } = await this.governor.getOrCreateVote({ proposal, voter: authority, }); if (createVoteIX) { ixs.push(createVoteIX); } ixs.push( this.program.instruction.castVote(voteSide, { accounts: { locker: this.locker, escrow: escrow, voteDelegate: authority ?? this.sdk.provider.wallet.publicKey, proposal, vote: voteKey, governor: this.governorKey, governProgram: TRIBECA_ADDRESSES.Govern, }, }) ); if (reason?.length) { ixs.push(createMemoInstruction(reason, [authority])); } return new TransactionEnvelope(this.sdk.provider, ixs); } async setVoteDelegate( newDelegate: PublicKey, authority: PublicKey = this.sdk.provider.wallet.publicKey ): Promise<TransactionEnvelope> { const [escrow] = await findEscrowAddress(this.locker, authority); return new TransactionEnvelope(this.sdk.provider, [ this.program.instruction.setVoteDelegate(newDelegate, { accounts: { escrow, escrowOwner: authority, }, }), ]); } async createApproveProgramLockPrivilegeIx( programId: PublicKey, owner: PublicKey | null ): Promise<TransactionInstruction> { const [whitelistEntry, bump] = await findWhitelistAddress( this.locker, programId, owner ); const lockerData = await this.reload(); const governorData = await this.sdk.programs.Govern.account.governor.fetch( lockerData.governor ); return this.program.instruction.approveProgramLockPrivilege(bump, { accounts: { locker: this.locker, whitelistEntry, governor: lockerData.governor, smartWallet: governorData.smartWallet, executableId: programId, whitelistedOwner: owner ?? SystemProgram.programId, payer: this.sdk.provider.wallet.publicKey, systemProgram: SystemProgram.programId, }, }); } async createRevokeProgramLockPrivilegeIx( programId: PublicKey, owner: PublicKey | null ): Promise<TransactionInstruction> { const [whitelistEntry] = await findWhitelistAddress( this.locker, programId, owner ); const lockerData = await this.reload(); const governorData = await this.sdk.programs.Govern.account.governor.fetch( lockerData.governor ); return this.program.instruction.revokeProgramLockPrivilege({ accounts: { locker: this.locker, whitelistEntry, governor: lockerData.governor, smartWallet: governorData.smartWallet, payer: this.sdk.provider.wallet.publicKey, }, }); } async setLockerParamsIx(args: LockerParams): Promise<TransactionInstruction> { const lockerData = await this.reload(); const governorData = await this.sdk.programs.Govern.account.governor.fetch( lockerData.governor ); return this.program.instruction.setLockerParams(args, { accounts: { locker: this.locker, governor: lockerData.governor, smartWallet: governorData.smartWallet, }, }); } private async _getOrCreateGovTokenATAsInternal( authority: PublicKey, escrow: PublicKey ): Promise<{ govTokenAccount: PublicKey; govTokenVault: PublicKey; instructions: TransactionInstruction[]; }> { const { provider } = this.sdk; const lockerData = await this.data(); const { address: govTokenAccount, instruction: ix1 } = await getOrCreateATA( { provider, mint: lockerData.tokenMint, owner: authority, payer: authority, } ); const { address: govTokenVault, instruction: ix2 } = await getOrCreateATA({ provider, mint: lockerData.tokenMint, owner: escrow, payer: authority, }); return { govTokenAccount, govTokenVault, instructions: [ix1, ix2].filter( (ix): ix is TransactionInstruction => !!ix ), }; } }