@river-build/web3
Version:
Dapps for our Space and Registry contracts
433 lines • 16.2 kB
JavaScript
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