Files
radicle-explorer/src/base/orgs/Org.ts
T
2021-09-16 14:32:03 +02:00

292 lines
7.5 KiB
TypeScript

import * as ethers from 'ethers';
import type { TransactionResponse } from '@ethersproject/providers';
import type { ContractReceipt } from '@ethersproject/contracts';
import { OperationType } from "@gnosis.pm/safe-core-sdk-types";
import { Profile, ProfileType } from '@app/profile';
import { assert } from '@app/error';
import * as utils from '@app/utils';
import type { Config } from '@app/config';
import type { Project } from '@app/project';
const GetProjects = `
query GetProjects($org: ID!) {
projects(where: { org: $org }) {
anchor {
objectId
multihash
timestamp
}
}
}
`;
const GetOrgs = `
query GetOrgs {
orgs {
id
owner
}
}
`;
const GetOrgsByOwner = `
query GetOrgsByOwner($owners: [String!]!) {
orgs(where: { owner_in: $owners }) {
id
owner
creator
timestamp
}
}
`;
export class Org {
address: string;
owner: string;
constructor(address: string, owner: string) {
assert(ethers.utils.isAddress(address), "address must be valid");
this.address = address.toLowerCase(); // Don't store address checksum.
this.owner = owner;
}
async setName(name: string, config: Config): Promise<TransactionResponse> {
assert(config.signer);
const org = new ethers.Contract(
this.address,
config.abi.org,
config.signer
);
return org.setName(name, config.provider.network.ensAddress,
{ gasLimit: 200_000 });
}
async setNameMultisig(name: string, config: Config): Promise<void> {
assert(config.signer);
assert(config.safe.client);
const safeAddress = ethers.utils.getAddress(this.owner);
const orgAddress = ethers.utils.getAddress(this.address);
const org = new ethers.Contract(
this.address,
config.abi.org,
config.signer
);
const unsignedTx = await org.populateTransaction.setName(
name,
config.provider.network.ensAddress,
);
const txData = unsignedTx.data;
if (! txData) {
throw new Error("Org::setNameMultisig: Could not generate transaction for `setName` call");
}
const safeTx = {
to: orgAddress,
value: ethers.constants.Zero.toString(),
data: txData,
operation: OperationType.Call,
};
await utils.proposeSafeTransaction(safeTx, safeAddress, config);
}
async setOwner(address: string, config: Config): Promise<TransactionResponse> {
assert(config.signer);
const org = new ethers.Contract(
this.address,
config.abi.org,
config.signer
);
return org.setOwner(address);
}
async getMembers(config: Config): Promise<Array<string>> {
const safe = await utils.getSafe(this.owner, config);
if (safe) {
return safe.owners;
}
return [];
}
async isMember(address: string, config: Config): Promise<boolean> {
const members = await this.getMembers(config);
return members.includes(ethers.utils.getAddress(address));
}
async getProjects(config: Config): Promise<Array<Project>> {
const result = await utils.querySubgraph(
config.orgs.subgraph, GetProjects, { org: this.address }
);
const projects: Project[] = [];
for (const p of result.projects) {
try {
const proj: Project = {
id: utils.formatRadicleId(ethers.utils.arrayify(p.anchor.objectId)),
anchor: {
stateHash: utils.formatProjectHash(
ethers.utils.arrayify(p.anchor.multihash),
)
},
};
projects.push(proj);
} catch (e) {
console.error(e);
}
}
return projects;
}
async getProfile(profileType: ProfileType, config: Config): Promise<Profile> {
return Org.getProfile(this.address, profileType, config);
}
static async getProjectProfile(addr: string, config: Config): Promise<Profile> {
return Org.getProfile(addr, ProfileType.Project, config);
}
static async getAnchor(orgAddr: string, urn: string, config: Config): Promise<string | null> {
const org = new ethers.Contract(
orgAddr,
config.abi.org,
config.provider
);
const unpadded = utils.decodeRadicleId(urn);
const id = ethers.utils.zeroPad(unpadded, 32);
try {
const [,hash] = await org.anchors(id);
const anchor = utils.formatProjectHash(ethers.utils.arrayify(hash));
return anchor;
} catch (e) {
console.error(e);
return null;
}
}
static async getAll(config: Config): Promise<Array<Org>> {
const result = await utils.querySubgraph(config.orgs.subgraph, GetOrgs, {});
const orgs: Org[] = [];
for (const o of result.orgs) {
try {
orgs.push(new Org(o.id, o.owner));
} catch (e) {
console.error(e);
}
}
return orgs;
}
static fromReceipt(receipt: ContractReceipt): Org | null {
const event = receipt.events?.find(e => e.event === 'OrgCreated');
if (event && event.args) {
const address = event.args[0];
const owner = event.args[1];
return new Org(address, owner);
}
return null;
}
static async get(
address: string,
config: Config,
): Promise<Org | null> {
const org = new ethers.Contract(
address,
config.abi.org,
config.provider
);
try {
const owner = await org.owner();
const resolved = await org.resolvedAddress;
return new Org(resolved, owner);
} catch (e) {
console.error(e);
return null;
}
}
// Return the org profile if there is one, otherwise try to get the profile
// of its owner.
static async getProfile(address: string, profileType: ProfileType, config: Config): Promise<Profile> {
const profile = await Profile.get(address, profileType, config);
if (profile.ens) { // Orgs only use ENS for profile information.
return profile;
}
const org = await Org.get(address, config);
if (org) {
const ownerProfile = await Profile.get(org.owner, profileType, config);
if (ownerProfile.ens || ownerProfile.idx) {
return ownerProfile;
}
}
return profile;
}
// Return only Orgs that have a specific user as owner
static async getOrgsByOwner(owner: string, config: Config): Promise<Org[]> {
const orgsResult = await utils.querySubgraph(config.orgs.subgraph, GetOrgsByOwner, { owners: [owner] });
return orgsResult.orgs.map((o: { id: string; owner: string }) => {
return new Org(o.id, o.owner);
});
}
static async getOrgsByMember(memberAddr: string, config: Config): Promise<Org[]> {
const wallets = await utils.getOwnerSafes(memberAddr, config);
const owners = wallets?.concat([memberAddr]);
const orgsResult = await utils.querySubgraph(config.orgs.subgraph, GetOrgsByOwner, { owners });
return orgsResult.orgs.map((o: { id: string; owner: string }) => {
return new Org(o.id, o.owner);
});
}
static async createMultiSig(
owners: [string],
threshold: number,
config: Config,
): Promise<TransactionResponse> {
assert(config.signer);
const orgFactory = new ethers.Contract(
config.orgFactory.address,
config.abi.orgFactory,
config.signer
);
return orgFactory['createOrg(address[],uint256)'](owners, threshold, {
gasLimit: config.gasLimits.createOrg
});
}
static async create(
owner: string,
config: Config,
): Promise<TransactionResponse> {
assert(config.signer);
const orgFactory = new ethers.Contract(
config.orgFactory.address,
config.abi.orgFactory,
config.signer
);
return orgFactory['createOrg(address)'](owner, {
gasLimit: config.gasLimits.createOrg
});
}
}