import { ethers } from 'ethers'; import Proposal from './Proposal'; import type { ProposalStatus } from './Proposal'; /** * The `Pod` object is the interface for fetching pod data. * * The Pod object should not be instantiated directly, use {@link getPod} instead. * * The following properties are on the object itself: * * ```js * const { * id, // Pod ID * safe, // Gnosis safe address, aka the Pod address * ensName, // E.g., orcanauts.pod.xyz * admin, // Address of pod admin * imageUrl, // Source of NFT image * imageNoTextUrl, // Source of NFT image without text (used for avatars) * } = await getPod(); * ``` * * Members, EOAs and member Pods can be fetched with the following functions: * * ```js * const pod = await getPod(podAddress); * // Fetches list of all members from the pod, as an array of Ethereum addresses. * // This includes any pods that may be members of the original pods. * const members = await pod.getMembers(); * * // Fetches any member EOAs (externally owned accounts). That is, any member that is not a smart contract or pod. * const memberEOAs = await pod.getMemberEOAs(); * * // Fetches Pod objects for any member pods. * const memberPods = await pod.getMemberPods(); * ``` * * You can also check if a user is a member, admin, or member of those pods with the following functions: * * ```js * const pod = await getPod(podAddress); * * const isMember = await pod.isMember(userAddress); * // Not an async function * const isAdmin = pod.isAdmin(userAddress); * * const isAdminPodMember = await pod.isAdminPodMember(userAddress); * * // Includes both pods and users as sub pod members. * const isSubPodMember = await pod.isSubPodMember(userAddress); * ``` */ export default class Pod { /** * Note this constructor should not be called directly. Use `getPod()` instead. * @param identifier Can be either podId or safe address */ constructor(identifier: string | number); controller: string; /** @property Pod ID */ id: number; /** @property Gnosis Safe address */ safe: string; /** @property Gnosis Safe address, duplicate of `safe` */ address: string; /** @property Current nonce, i.e., the nonce of the active proposal */ nonce: number; /** @property Number of votes required to pass a proposal */ threshold: number; /** @property ENS name */ ensName: string; /** @property Pod name * full ENS name if custom domain, otherwise ensLabel if pod URL */ podName: string; /** @property Admin address */ admin: string; /** @property Description of NFT */ description: string; /** @property Link to Pod NFT image */ imageUrl: string; /** @property Link to Pod NFT image with no text */ imageNoTextUrl: string; /** * @ignore * @property Array of members of pod. * Do not call this property directly, use `Pod.getMembers()` */ members?: string[]; /** * @ignore * @property Array of member EOAs * Do not call this property directly, use `Pod.getMemberEOAs()` */ memberEOAs?: string[]; /** * @ignore * @property Admin pod object */ adminPod?: Pod; /** * @ignore * @property Array of Pod objects for any member pods * Do not call this property directly, use `Pod.getMemberPods()` */ memberPods?: Pod[]; /** * @ignore * @property Array of Pod objects for any super pods * Do not call this property directly, use `Pod.getSuperPods()` */ superPods?: Pod[]; /** * Returns an array of Proposal objects in reverse chronological order. Defaults to returning 5, * which can be overridden by passing { limit: 10 } for example in the options. * * By default, the first Proposal will be the active proposal, if there is one, and then any executed proposals. * * Queued proposals can be fetched by passing { status: 'queued' } in the options. This will return queued * proposals, then the active proposal, then executed proposals (up to the requested limit). * * Executed proposals can be fetched by passing { status: 'passed' } or { status: 'rejected' } in the options. This will return * only executed proposals. * * @param options * @returns */ getProposals: (options?: { status?: ProposalStatus; limit?: number; }) => Promise; /** * Gets a specific proposal by either nonce or the safeTxHash. * @param identifier - Can be either the proposal id/nonce (preferred), or the safeTxHash */ getProposal: (identifier: number | string) => Promise; /** * Returns an array of this pod's super pods, i.e., pods that this pod is a member of */ getSuperPods: () => Promise; /** * Returns an array of all active super proposals, i.e., active proposals of any super pods */ getSuperProposals: () => Promise; /** * Returns of list of all member addresses. * Members include member pods and member EOAs */ getMembers: () => Promise; /** * @ignore * Populates the memberEOAs and memberPods fields. * The process for fetching either of these fields is the same. */ populateMembers(): Promise; /** * Returns list of all member EOAs, not including any smart contract/pod members. */ getMemberEOAs: () => Promise; /** * Returns Pod objects of all member pods. */ getMemberPods: () => Promise; /** * Calls a Pod method via a persona. * The persona object can be fetched by using `Pod.getPersonas(address)` * E.g., * ``` * callAsPersona( * pod.mintMember, * [newMember], * { type: 'admin', address: userAddress }, * signer, * ) * ``` * The sender must be a signer in the admin case, or the address of the sender. * The sender must be a member of the relevant pod. * * @param method * @param args * @param persona * @param sender * @returns */ callAsPersona: (method: any, args: Array, persona: { type: string; address: string; }, sender?: ethers.Signer | string) => Promise; /** * Fetches all personas for a given address related to this pod. * All personas return as an object indicating the type of the persona and the address of the persona. * For members and admins, the persona address is the user's address. * For admin pods and sub pods, the persona address is the pod address. * @param address */ getPersonas(address: string): Promise<{ type: string; address: string; }[]>; /** * Checks if user is a member of this pod * @param address */ isMember: (address: string) => Promise; /** * Checks if user is admin of this pod * @param address */ isAdmin: (address: string) => boolean; /** * Checks if given address is a member of the admin pod (if there is one) * Returns false if there is no admin pod. */ isAdminPodMember: (address: string) => Promise; /** * Checks if given address is a member of any subpods. * Returns false if the user is a member of **this** pod, but not any sub pods * * @param address */ isSubPodMember: (address: string) => Promise; /** * Returns all sub pods of this pod that an address is the member of. * @param address */ getSubPodsByMember: (address: string) => Promise; /** * Fetches an external pod (i.e., a sub or admin pod) and performs checks. * Mostly used in context of creating sub/super proposals. * @ignore * @param podIdentifier - Pod safe, ID, or the Pod object itself * @param relationship - 'admin' or 'sub' * @param signerAddress - Address of signer * @returns */ getExternalPod: (podIdentifier: string | number | Pod, relationship: string, signerAddress: string) => Promise; /** * Creates a proposal on the pod. * * If the proposal parameter is in the { data, to } format, this will create a proposal to execute * an arbitrary smart contract function on this pod. * * The `data` parameter is unsigned encoded function data. You can generate this function data a number of ways, * our recommended way would be through ethers.Interface.encodeFunctionData. The `to` parameter should be the smart contract * that the function being called belongs to. * * The pod object can also populate these transactions by using the supplied methods with no signer (i.e., `pod.mint()`) * * To create a proposal on the current pod, you can chain the method and the propose method like so: * * ```js * await pod.propose(await pod.mint(newMember), podMember); * ``` * * To create a proposal on the admin pod to mint to a managed pod, you can call the managed pod's method: * Note that the pod you call the functions on is important: it determines which pod you are attempting to mint to. * * ```js * // It is important that you call mint() from managedPod here * await adminPod.propose(await managedPod.mint(newMember), adminPodMember); * ``` * * In order to create a sub proposal of an existing proposal, you can pass the proposal object to the sub pod's propose method: * * ``` * const [superProposal] = await superPod.getProposals(); * // This creates a sub proposal to approve the super proposal. * await subPod.propose(superProposal, signer); * * // The propose function also returns a Propose object, so you can chain `propose` like so: * await subPod.propose( * await superPod.propose( * await superPod.mint(newMember), * superPodMember, * ), * subPodMember, * ); * ``` * * The sender parameter should be the address the proposal is being sent from. This should be a member * of the pod for creating a proposal, or a member of a sub pod to create sub proposals. * * @param proposal * @param sender - Address of sender * @param opts.nonce - Optional nonce. This will create a proposal with the given nonce. * @returns */ propose: (proposal: { data: string; to: string; } | Proposal | ethers.providers.TransactionResponse, sender: string, opts?: { nonce?: number; }) => Promise; /** * Mints member(s) to this pod, or returns an unsigned transaction to do so. * @param newMember - Can be a single address or an array of addresses * @param signer - If a signer is provided, then the tx will execute. Otherwise, an unsigned transaction will be returned. * @throws if signer is not admin */ mintMember: (newMember: string | string[], signer?: ethers.Signer) => Promise; /** * Burns member(s) from this pod, or returns an unsigned transaction to do so. * @param memberToBurn - Can be a single address or an array of addresses * @param signer - If a signer is provided, then the tx will execute. Otherwise, an unsigned transaction will be returned. * @throws If signer is not admin */ burnMember: (memberToBurn: string | string[], signer?: ethers.Signer) => Promise; /** * Mints and burns members simultaneously. */ batchMintAndBurn: (mintMembers: string[], burnMembers: string[], signer?: ethers.Signer) => Promise; /** * Transfers a membership. If a signer is provided, it will execute the transaction. Otherwise it will return the unsigned tx. * * @param toAddress - Address that will receive membership * @param fromAddress - Address that is giving up membership * @param signer - If a signer is provided, then the tx will execute. Otherwise, an unsigned transaction will be returned. * @throws If toAddress is already a member * @throws if fromAddress is not a member * @throws If provided signer is not the fromAddress */ transferMembership: (fromAddress: string, toAddress: string, signer?: ethers.Signer) => Promise; /** * Changes the threshold for the safe associated with this pod. * If a signer is provided, it will execute the transaction. Otherwise it will return the unsigned tx. * @param newThreshold - new threshold * @param signer - Signer of admin * @throws If signer is not admin * @returns */ changeThreshold: (newThreshold: number, signer?: ethers.Signer) => Promise; /** * Transfers admin role from signer's account to addressToTransferTo * If a signer is provided, it will execute the transaction. Otherwise it will return the unsigned tx. * @param addressToTransferTo - Address that will receive admin role * @param signer - Signer of admin * @throws If signer is not admin */ transferAdmin: (addressToTransferTo: string, signer?: ethers.Signer) => Promise; /** * Migrates the pod to the latest version. Signer must be the admin of pod. * If a signer is provided, it will execute the transaction. Otherwise it will return the unsigned tx. * @param signer - Signer of pod admin * @throws If signer is not pod admin */ migratePodToLatest: (signer?: ethers.Signer) => Promise; /** * */ isPodifyInProgress: () => Promise; /** * Ejects a safe from the Orca ecosystem. * This zeroes out all ENS + Controller data, removes the Orca module, and burns the pod's MemberTokens * If a signer is provided, it will execute the transaction. Otherwise it will return the unsigned tx. * * This function can also clean up data for safes that have already removed the Orca module, * but note that the reverse resolver must be zeroed out by the safe manually in this case. */ ejectSafe: (signer?: ethers.Signer) => Promise; /** * Creates a reject proposal at a given nonce, mostly used to un-stuck the transaction queue * @param nonce - nonce to create the reject transaction at * @param signer - Signer or address of pod member */ createRejectProposal: (nonce: number, signer: ethers.Signer | string) => Promise; }