UNPKG

@river-build/web3

Version:

Dapps for our Space and Registry contracts

433 lines 16.2 kB
import { EntitlementModuleType, isRuleEntitlement, isRuleEntitlementV2, isUserEntitlement, } from '../ContractTypes'; import { IChannelShim } from './IChannelShim'; import { IRolesShim } from './IRolesShim'; import { ISpaceOwnerShim } from './ISpaceOwnerShim'; import { IEntitlementsShim } from './IEntitlementsShim'; import { IMulticallShim } from './IMulticallShim'; import { OwnableFacetShim } from './OwnableFacetShim'; import { TokenPausableFacetShim } from './TokenPausableFacetShim'; import { UNKNOWN_ERROR } from './BaseContractShim'; import { UserEntitlementShim } from './UserEntitlementShim'; import { isRoleIdInArray } from '../ContractHelpers'; import { toPermissions } from '../ConvertersRoles'; import { IMembershipShim } from './IMembershipShim'; import { NoopRuleData } from '../entitlement'; import { RuleEntitlementShim } from './RuleEntitlementShim'; import { RuleEntitlementV2Shim } from './RuleEntitlementV2Shim'; import { IBanningShim } from './IBanningShim'; import { ITippingShim } from './ITippingShim'; import { IERC721AQueryableShim } from './IERC721AQueryableShim'; import { IEntitlementDataQueryableShim } from './IEntitlementDataQueryableShim'; import { parseChannelMetadataJSON } from '../Utils'; import { IPrepayShim } from './IPrepayShim'; import { IERC721AShim } from './IERC721AShim'; import { IReviewShim } from './IReviewShim'; export class Space { address; addressToEntitlement = {}; spaceId; provider; channel; entitlements; multicall; ownable; pausable; roles; spaceOwner; membership; banning; erc721AQueryable; entitlementDataQueryable; prepay; erc721A; spaceOwnerErc721A; tipping; review; constructor(address, spaceId, config, provider) { this.address = address; this.spaceId = spaceId; this.provider = provider; // // If you add a new contract shim, make sure to add it in getAllShims() // this.channel = new IChannelShim(address, provider); this.entitlements = new IEntitlementsShim(address, provider); this.multicall = new IMulticallShim(address, provider); this.ownable = new OwnableFacetShim(address, provider); this.pausable = new TokenPausableFacetShim(address, provider); this.roles = new IRolesShim(address, provider); this.spaceOwner = new ISpaceOwnerShim(config.addresses.spaceOwner, provider); this.spaceOwnerErc721A = new IERC721AShim(config.addresses.spaceOwner, provider); this.membership = new IMembershipShim(address, provider); this.banning = new IBanningShim(address, provider); this.erc721AQueryable = new IERC721AQueryableShim(address, provider); this.entitlementDataQueryable = new IEntitlementDataQueryableShim(address, provider); this.prepay = new IPrepayShim(address, provider); this.erc721A = new IERC721AShim(address, provider); this.tipping = new ITippingShim(address, provider); this.review = new IReviewShim(address, provider); } getAllShims() { return [ this.channel, this.entitlements, this.multicall, this.ownable, this.pausable, this.roles, this.spaceOwner, this.spaceOwnerErc721A, this.membership, this.banning, this.erc721AQueryable, this.entitlementDataQueryable, this.prepay, this.erc721A, this.tipping, ]; } get Address() { return this.address; } get SpaceId() { return this.spaceId; } get Channels() { return this.channel; } get Multicall() { return this.multicall; } get Ownable() { return this.ownable; } get Pausable() { return this.pausable; } get Roles() { return this.roles; } get Entitlements() { return this.entitlements; } get SpaceOwner() { return this.spaceOwner; } get Membership() { return this.membership; } get Banning() { return this.banning; } get ERC721AQueryable() { return this.erc721AQueryable; } get EntitlementDataQueryable() { return this.entitlementDataQueryable; } get Prepay() { return this.prepay; } get ERC721A() { return this.erc721A; } get SpaceOwnerErc721A() { return this.spaceOwnerErc721A; } get Tipping() { return this.tipping; } get Review() { return this.review; } async totalTips({ currency }) { const [count, amount] = await Promise.all([ this.tipping.totalTipsByCurrency(currency), this.tipping.tipAmountByCurrency(currency), ]); return { count, amount, }; } getSpaceInfo() { return this.spaceOwner.read.getSpaceInfo(this.address); } async getRole(roleId) { // get all the entitlements for the space const entitlementShims = await this.getEntitlementShims(); // get the various pieces of details const [roleEntitlements, channels] = await Promise.all([ this.getRoleEntitlements(entitlementShims, roleId), this.getChannelsWithRole(roleId), ]); // assemble the result if (roleEntitlements === null) { return null; } return { id: roleEntitlements.roleId, name: roleEntitlements.name, permissions: roleEntitlements.permissions, channels, users: roleEntitlements.users, ruleData: roleEntitlements.ruleData, }; } async getChannel(channelNetworkId) { // get most of the channel details except the roles which // require a separate call to get each role's details const channelId = channelNetworkId.startsWith('0x') ? channelNetworkId : `0x${channelNetworkId}`; const channelInfo = await this.Channels.read.getChannel(channelId); const roles = await this.getChannelRoleEntitlements(channelInfo); const metadata = parseChannelMetadataJSON(channelInfo.metadata); return { spaceNetworkId: this.spaceId, channelNetworkId: channelNetworkId.replace('0x', ''), name: metadata.name, description: metadata.description, disabled: channelInfo.disabled, roles, }; } async getChannelMetadata(channelNetworkId) { const channelId = channelNetworkId.startsWith('0x') ? channelNetworkId : `0x${channelNetworkId}`; const channelInfo = await this.Channels.read.getChannel(channelId); const metadata = parseChannelMetadataJSON(channelInfo.metadata); return { name: metadata.name, channelNetworkId: channelInfo.id.replace('0x', ''), description: metadata.description, disabled: channelInfo.disabled, }; } async getChannels() { const channels = []; const getOutput = await this.Channels.read.getChannels(); for (const o of getOutput) { const metadata = parseChannelMetadataJSON(o.metadata); channels.push({ name: metadata.name, description: metadata.description, channelNetworkId: o.id.replace('0x', ''), disabled: o.disabled, }); } return channels; } async getChannelRoles(channelNetworkId) { const channelId = channelNetworkId.startsWith('0x') ? channelNetworkId : `0x${channelNetworkId}`; // get all the roleIds for the channel const channelInfo = await this.Channels.read.getChannel(channelId); // return the role info return this.getRolesInfo(channelInfo.roleIds); } async getPermissionsByRoleId(roleId) { const permissions = await this.Roles.read.getPermissionsByRoleId(roleId); return toPermissions(permissions); } async getChannelRoleEntitlements(channelInfo) { // get all the entitlements for the space const entitlementShims = await this.getEntitlementShims(); const getRoleEntitlementsAsync = []; for (const roleId of channelInfo.roleIds) { getRoleEntitlementsAsync.push(this.getRoleEntitlements(entitlementShims, roleId)); } // get all the role info const allRoleEntitlements = await Promise.all(getRoleEntitlementsAsync); return allRoleEntitlements.filter((r) => r !== null); } async findEntitlementByType(entitlementType) { const entitlements = await this.getEntitlementShims(); for (const entitlement of entitlements) { if (entitlement.moduleType === entitlementType) { return entitlement; } } return null; } parseError(error) { // try each of the contracts to see who can give the best error message const shims = this.getAllShims(); const first = shims[0]; const rest = shims.slice(1); let err = first.parseError(error); if (err?.name !== UNKNOWN_ERROR) { return err; } for (const contract of rest) { err = contract.parseError(error); if (err?.name !== UNKNOWN_ERROR) { return err; } } return err; } parseLog(log) { const shims = this.getAllShims(); for (const contract of shims) { try { return contract.parseLog(log); } catch (error) { // ignore, throw error if none match } } throw new Error('Failed to parse log: ' + JSON.stringify(log)); } async getEntitlementByAddress(address) { if (!this.addressToEntitlement[address]) { const entitlement = await this.entitlements.read.getEntitlement(address); switch (entitlement.moduleType) { case EntitlementModuleType.UserEntitlement: this.addressToEntitlement[address] = new UserEntitlementShim(address, this.provider); break; case EntitlementModuleType.RuleEntitlement: this.addressToEntitlement[address] = new RuleEntitlementShim(address, this.provider); break; case EntitlementModuleType.RuleEntitlementV2: this.addressToEntitlement[address] = new RuleEntitlementV2Shim(address, this.provider); break; default: throw new Error(`Unsupported entitlement module type: ${entitlement.moduleType}`); } } return this.addressToEntitlement[address]; } async getRoleInfo(roleId) { try { return await this.roles.read.getRoleById(roleId); } catch (e) { // any error means the role doesn't exist //console.error(e) return null; } } async getEntitlementShims() { // get all the entitlement addresses supported in the space const entitlementInfo = await this.entitlements.read.getEntitlements(); const getEntitlementShims = []; // with the addresses, get the entitlement shims for (const info of entitlementInfo) { getEntitlementShims.push(this.getEntitlementByAddress(info.moduleAddress)); } return Promise.all(getEntitlementShims); } async getEntitlementDetails(entitlementShims, roleId) { let users = []; let ruleData = undefined; let ruleDataV2 = undefined; let useRuleDataV1 = false; // with the shims, get the role details for each entitlement await Promise.all(entitlementShims.map(async (entitlement) => { if (isUserEntitlement(entitlement)) { users = (await entitlement.getRoleEntitlement(roleId)) ?? []; } else if (isRuleEntitlement(entitlement)) { ruleData = (await entitlement.getRoleEntitlement(roleId)) ?? undefined; useRuleDataV1 = true; } else if (isRuleEntitlementV2(entitlement)) { ruleDataV2 = (await entitlement.getRoleEntitlement(roleId)) ?? undefined; } })); if (useRuleDataV1) { return { users, ruleData: { kind: 'v1', rules: ruleData ?? NoopRuleData, }, }; } else { return { users, ruleData: { kind: 'v2', rules: ruleDataV2 ?? NoopRuleData, }, }; } } async getChannelsWithRole(roleId) { const channelMetadatas = new Map(); // get all the channels from the space const allChannels = await this.channel.read.getChannels(); // for each channel, check with each entitlement if the role is in the channel // add the channel to the list if it is not already added for (const c of allChannels) { if (!channelMetadatas.has(c.id) && isRoleIdInArray(c.roleIds, roleId)) { const metadata = parseChannelMetadataJSON(c.metadata); channelMetadatas.set(c.id, { channelNetworkId: c.id.replace('0x', ''), name: metadata.name, description: metadata.description, disabled: c.disabled, }); } } return Array.from(channelMetadatas.values()); } async getRolesInfo(roleIds) { // use a Set to ensure that we only get roles once const roles = new Set(); const getRoleStructsAsync = []; for (const roleId of roleIds) { // get the role info if we don't already have it if (!roles.has(roleId.toString())) { getRoleStructsAsync.push(this.roles.read.getRoleById(roleId)); } } // get all the role info return Promise.all(getRoleStructsAsync); } async getRoleEntitlements(entitlementShims, roleId) { const [roleInfo, entitlementDetails] = await Promise.all([ this.getRoleInfo(roleId), this.getEntitlementDetails(entitlementShims, roleId), ]); // assemble the result if (roleInfo === null) { return null; } return { roleId: roleInfo.id.toNumber(), name: roleInfo.name, permissions: toPermissions(roleInfo.permissions), users: entitlementDetails.users, ruleData: entitlementDetails.ruleData, }; } async getTokenIdsOfOwner(linkedWallets) { const tokenPromises = linkedWallets.map((wallet) => this.erc721AQueryable.read.tokensOfOwner(wallet)); const allTokenArrays = await Promise.all(tokenPromises); return allTokenArrays.flat().map((token) => token.toString()); } async getProtocolFee() { return (await this.membership.read.getProtocolFee()).toBigInt(); } /** * This function is potentially expensive and should be used with caution. * For example, a space with 1000 members will make 1000 + 1 calls to the blockchain. * @param untilTokenId - The token id to stop at, if not provided, will get all members * @returns An array of member addresses */ async __expensivelyGetMembers(untilTokenId) { if (untilTokenId === undefined) { untilTokenId = await this.erc721A.read.totalSupply(); } untilTokenId = Number(untilTokenId); const tokenIds = Array.from({ length: untilTokenId }, (_, i) => i); const promises = tokenIds.map((tokenId) => this.erc721A.read.ownerOf(tokenId)); return Promise.all(promises); } } //# sourceMappingURL=Space.js.map