UNPKG

@river-build/web3

Version:

Dapps for our Space and Registry contracts

917 lines 34.7 kB
import { IRuleEntitlementAbi } from './v3/IRuleEntitlementShim'; import { IRuleEntitlementV2Abi } from './v3/IRuleEntitlementV2Shim'; import { dlogger } from '@river-build/dlog'; import { encodeAbiParameters, decodeAbiParameters, getAbiItem, } from 'viem'; import { ethers } from 'ethers'; import { MOCK_ADDRESS } from './Utils'; const log = dlogger('csb:entitlement'); const zeroAddress = ethers.constants.AddressZero; export var OperationType; (function (OperationType) { OperationType[OperationType["NONE"] = 0] = "NONE"; OperationType[OperationType["CHECK"] = 1] = "CHECK"; OperationType[OperationType["LOGICAL"] = 2] = "LOGICAL"; })(OperationType || (OperationType = {})); export var CheckOperationType; (function (CheckOperationType) { CheckOperationType[CheckOperationType["NONE"] = 0] = "NONE"; CheckOperationType[CheckOperationType["MOCK"] = 1] = "MOCK"; CheckOperationType[CheckOperationType["ERC20"] = 2] = "ERC20"; CheckOperationType[CheckOperationType["ERC721"] = 3] = "ERC721"; CheckOperationType[CheckOperationType["ERC1155"] = 4] = "ERC1155"; CheckOperationType[CheckOperationType["ISENTITLED"] = 5] = "ISENTITLED"; CheckOperationType[CheckOperationType["ETH_BALANCE"] = 6] = "ETH_BALANCE"; })(CheckOperationType || (CheckOperationType = {})); function checkOpString(operation) { switch (operation) { case CheckOperationType.NONE: return 'NONE'; case CheckOperationType.MOCK: return 'MOCK'; case CheckOperationType.ERC20: return 'ERC20'; case CheckOperationType.ERC721: return 'ERC721'; case CheckOperationType.ERC1155: return 'ERC1155'; case CheckOperationType.ISENTITLED: return 'ISENTITLED'; case CheckOperationType.ETH_BALANCE: return 'ETH_BALANCE'; default: return 'UNKNOWN'; } } // Enum for Operation oneof operation_clause export var LogicalOperationType; (function (LogicalOperationType) { LogicalOperationType[LogicalOperationType["NONE"] = 0] = "NONE"; LogicalOperationType[LogicalOperationType["AND"] = 1] = "AND"; LogicalOperationType[LogicalOperationType["OR"] = 2] = "OR"; })(LogicalOperationType || (LogicalOperationType = {})); export function isContractLogicalOperation(operation) { return operation.opType === OperationType.LOGICAL; } export const NoopOperation = { opType: OperationType.NONE, index: 0, }; export const NoopRuleData = { operations: [], checkOperations: [], logicalOperations: [], }; export const EncodedNoopRuleData = encodeRuleDataV2(NoopRuleData); function isCheckOperationV2(operation) { return operation.opType === OperationType.CHECK; } function isLogicalOperation(operation) { return operation.opType === OperationType.LOGICAL; } function isAndOperation(operation) { return operation.logicalType === LogicalOperationType.AND; } function isOrOperation(operation) { return operation.logicalType === LogicalOperationType.OR; } export function postOrderArrayToTree(operations) { const stack = []; operations.forEach((op) => { if (isLogicalOperation(op)) { if (stack.length < 2) { throw new Error('Invalid post-order array, missing operations'); } // Pop the two most recent operations from the stack const right = stack.pop(); const left = stack.pop(); // Ensure the operations exist if (!left || !right) { throw new Error('Invalid post-order array, missing operations'); } // Update the current logical operation's children if (isLogicalOperation(op)) { op.leftOperation = left; op.rightOperation = right; } } // Push the current operation back into the stack stack.push(op); }); // The last item in the stack is the root of the tree const root = stack.pop(); if (!root) { throw new Error('Invalid post-order array'); } return root; } const thresholdParamsAbi = { components: [ { name: 'threshold', type: 'uint256', }, ], name: 'thresholdParams', type: 'tuple', }; export function encodeThresholdParams(params) { if (params.threshold < 0n) { throw new Error(`Invalid threshold ${params.threshold}: must be greater than or equal to 0`); } return encodeAbiParameters([thresholdParamsAbi], [params]); } export function decodeThresholdParams(params) { return decodeAbiParameters([thresholdParamsAbi], params)[0]; } const erc1155ParamsAbi = { components: [ { name: 'threshold', type: 'uint256', }, { name: 'tokenId', type: 'uint256', }, ], name: 'erc1155Params', type: 'tuple', }; export function encodeERC1155Params(params) { if (params.threshold < 0n) { throw new Error(`Invalid threshold ${params.threshold}: must be greater than or equal to 0`); } if (params.tokenId < 0n) { throw new Error(`Invalid tokenId ${params.tokenId}: must be greater than or equal to 0`); } return encodeAbiParameters([erc1155ParamsAbi], [params]); } export function decodeERC1155Params(params) { return decodeAbiParameters([erc1155ParamsAbi], params)[0]; } export function encodeRuleData(ruleData) { const encodeRuleDataAbi = getAbiItem({ abi: IRuleEntitlementAbi, name: 'encodeRuleData', }); if (!encodeRuleDataAbi) { throw new Error('encodeRuleData ABI not found'); } // @ts-ignore return encodeAbiParameters(encodeRuleDataAbi.inputs, [ruleData]); } export function decodeRuleData(entitlementData) { const getRuleDataAbi = getAbiItem({ abi: IRuleEntitlementAbi, name: 'getRuleData', }); if (!getRuleDataAbi) { throw new Error('getRuleData ABI not found'); } const decoded = decodeAbiParameters(getRuleDataAbi.outputs, entitlementData); return decoded[0]; } export function encodeRuleDataV2(ruleData) { // If we encounter a no-op rule data, just encode as empty bytes. if (ruleData.operations.length === 0) { return '0x'; } const getRuleDataV2Abi = getAbiItem({ abi: IRuleEntitlementV2Abi, name: 'getRuleDataV2', }); if (!getRuleDataV2Abi) { throw new Error('encodeRuleDataV2 ABI not found'); } // @ts-ignore return encodeAbiParameters(getRuleDataV2Abi.outputs, [ruleData]); } export function decodeRuleDataV2(entitlementData) { if (entitlementData === '0x') { return { operations: [], checkOperations: [], logicalOperations: [], }; } const getRuleDataV2Abi = getAbiItem({ abi: IRuleEntitlementV2Abi, name: 'getRuleDataV2', }); if (!getRuleDataV2Abi) { throw new Error('encodeRuleDataV2 ABI not found'); } // @ts-ignore const decoded = decodeAbiParameters(getRuleDataV2Abi.outputs, entitlementData); return decoded[0]; } export function ruleDataToOperations(data) { const decodedOperations = []; const roData = data; roData.operations.forEach((operation) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison if (operation.opType === OperationType.CHECK) { const checkOperation = roData.checkOperations[operation.index]; decodedOperations.push({ opType: OperationType.CHECK, checkType: checkOperation.opType, chainId: checkOperation.chainId, contractAddress: checkOperation.contractAddress, params: checkOperation.params, }); } // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison else if (operation.opType === OperationType.LOGICAL) { const logicalOperation = roData.logicalOperations[operation.index]; decodedOperations.push({ opType: OperationType.LOGICAL, logicalType: logicalOperation.logOpType, leftOperation: decodedOperations[logicalOperation.leftOperationIndex], rightOperation: decodedOperations[logicalOperation.rightOperationIndex], }); // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison } else if (operation.opType === OperationType.NONE) { decodedOperations.push(NoopOperation); } else { throw new Error(`Unknown logical operation type ${operation.opType}`); } }); return decodedOperations; } export function postOrderTraversal(operation, data) { if (isLogicalOperation(operation)) { postOrderTraversal(operation.leftOperation, data); // Capture index of the most recently added operation, which is the // top of the left child's postorder tree const leftChildIndex = data.operations.length - 1; postOrderTraversal(operation.rightOperation, data); data.logicalOperations.push({ logOpType: operation.logicalType, leftOperationIndex: leftChildIndex, rightOperationIndex: data.operations.length - 1, // Index of right child root }); data.operations.push({ opType: OperationType.LOGICAL, index: data.logicalOperations.length - 1, }); } else if (isCheckOperationV2(operation)) { data.checkOperations.push({ opType: operation.checkType, chainId: operation.chainId, contractAddress: operation.contractAddress, params: operation.params, }); data.operations.push({ opType: OperationType.CHECK, index: data.checkOperations.length - 1, }); } else { throw new Error('Unrecognized operation type'); } } export function treeToRuleData(root) { const data = { operations: [], checkOperations: [], logicalOperations: [], }; postOrderTraversal(root, data); return data; } /** * Evaluates an AndOperation * If either of the operations are false, the entire operation is false, and the * other operation is aborted. Once both operations succeed, the entire operation * succeeds. * @param operation * @param controller * @returns true once both succeed, false if either fail */ async function evaluateAndOperation(controller, linkedWallets, xchainConfig, operation) { if (!operation?.leftOperation || !operation?.rightOperation) { controller.abort(); return zeroAddress; } const newController = new AbortController(); controller.signal.addEventListener('abort', () => { newController.abort(); }); const interuptFlag = {}; let tempInterupt; const interupted = new Promise((resolve) => { tempInterupt = resolve; }); const interupt = () => { if (tempInterupt) { tempInterupt(interuptFlag); } }; async function racer(operationEntry) { const result = await Promise.race([ evaluateTree(newController, linkedWallets, xchainConfig, operationEntry), interupted, ]); if (result === interuptFlag) { return zeroAddress; // interupted } else if (isValidAddress(result)) { return result; } else { controller.abort(); interupt(); return zeroAddress; } } const checks = await Promise.all([ racer(operation.leftOperation), racer(operation.rightOperation), ]); const result = checks.every((res) => isValidAddress(res)); if (!result) { return zeroAddress; } return checks[0]; } /** * Evaluates an OrOperation * If either of the operations are true, the entire operation is true * and the other operation is aborted. Once both operationd fail, the * entire operation fails. * @param operation * @param signal * @returns true once one succeeds, false if both fail */ async function evaluateOrOperation(controller, linkedWallets, xchainConfig, operation) { if (!operation?.leftOperation || !operation?.rightOperation) { controller.abort(); return zeroAddress; } const newController = new AbortController(); controller.signal.addEventListener('abort', () => { newController.abort(); }); const interuptFlag = {}; let tempInterupt; const interupted = new Promise((resolve) => { tempInterupt = resolve; }); const interupt = () => { if (tempInterupt) { tempInterupt(interuptFlag); } }; async function racer(operation) { const result = await Promise.race([ evaluateTree(newController, linkedWallets, xchainConfig, operation), interupted, ]); if (result === interuptFlag) { return zeroAddress; // interupted, the other must have returned true } else if (isValidAddress(result)) { // cancel the other operation newController.abort(); interupt(); return result; } else { return zeroAddress; } } const checks = await Promise.all([ racer(operation.leftOperation), racer(operation.rightOperation), ]); const result = checks.find((res) => isValidAddress(res)); return result ?? ethers.constants.AddressZero; } /** * Evaluates a CheckOperation * Mekes the smart contract call. Will be aborted if another branch invalidates * the need to make the check. * @param operation * @param signal * @returns */ async function evaluateCheckOperation(controller, linkedWallets, xchainConfig, operation) { if (!operation) { controller.abort(); return zeroAddress; } switch (operation.checkType) { case CheckOperationType.MOCK: { return evaluateMockOperation(operation, controller); } case CheckOperationType.NONE: throw new Error('Unknown check operation type'); default: } if (operation.checkType !== CheckOperationType.ETH_BALANCE && operation.chainId < 0n) { throw new Error(`Invalid chain id for check operation ${checkOpString(operation.checkType)}`); } if (operation.checkType !== CheckOperationType.ETH_BALANCE && operation.contractAddress === zeroAddress) { throw new Error(`Invalid contract address for check operation ${checkOpString(operation.checkType)}`); } if ([ CheckOperationType.ERC20, CheckOperationType.ERC721, CheckOperationType.ETH_BALANCE, ].includes(operation.checkType)) { const { threshold } = decodeThresholdParams(operation.params); if (threshold <= 0n) { throw new Error(`Invalid threshold for check operation ${checkOpString(operation.checkType)}`); } } else if (operation.checkType === CheckOperationType.ERC1155) { const { tokenId, threshold } = decodeERC1155Params(operation.params); if (tokenId < 0n) { throw new Error(`Invalid token id for check operation ${checkOpString(operation.checkType)}`); } if (threshold <= 0n) { throw new Error(`Invalid threshold for check operation ${checkOpString(operation.checkType)}`); } } switch (operation.checkType) { case CheckOperationType.ISENTITLED: { const provider = await findProviderFromChainId(xchainConfig, operation.chainId); if (!provider) { controller.abort(); return zeroAddress; } return evaluateCrossChainEntitlementOperation(operation, controller, provider, linkedWallets); } case CheckOperationType.ETH_BALANCE: { const etherChainProviders = await findEtherChainProviders(xchainConfig); if (!etherChainProviders.length) { controller.abort(); return zeroAddress; } return evaluateEthBalanceOperation(operation, controller, etherChainProviders, linkedWallets); } case CheckOperationType.ERC1155: { const provider = await findProviderFromChainId(xchainConfig, operation.chainId); if (!provider) { controller.abort(); return zeroAddress; } return evaluateERC1155Operation(operation, controller, provider, linkedWallets); } case CheckOperationType.ERC20: { const provider = await findProviderFromChainId(xchainConfig, operation.chainId); if (!provider) { controller.abort(); return zeroAddress; } return evaluateERC20Operation(operation, controller, provider, linkedWallets); } case CheckOperationType.ERC721: { const provider = await findProviderFromChainId(xchainConfig, operation.chainId); if (!provider) { controller.abort(); return zeroAddress; } return evaluateERC721Operation(operation, controller, provider, linkedWallets); } default: throw new Error('Unknown check operation type'); } } /** * * @param operations * @param linkedWallets * @param providers * @returns An entitled wallet or the zero address, indicating no entitlement */ export async function evaluateOperationsForEntitledWallet(operations, linkedWallets, xchainConfig) { if (operations.length === 0) { return zeroAddress; } const controller = new AbortController(); const root = postOrderArrayToTree(operations); const result = evaluateTree(controller, linkedWallets, xchainConfig, root); controller.abort(); return result; } export async function evaluateTree(controller, linkedWallets, xchainConfig, entry) { if (!entry) { controller.abort(); return zeroAddress; } const newController = new AbortController(); controller.signal.addEventListener('abort', () => { newController.abort(); }); if (isLogicalOperation(entry)) { if (isAndOperation(entry)) { return evaluateAndOperation(newController, linkedWallets, xchainConfig, entry); } else if (isOrOperation(entry)) { return evaluateOrOperation(newController, linkedWallets, xchainConfig, entry); } else { throw new Error('Unknown operation type'); } } else if (isCheckOperationV2(entry)) { return evaluateCheckOperation(newController, linkedWallets, xchainConfig, entry); } else { throw new Error('Unknown operation type'); } } // These two methods are used to create a rule data struct for an external token or NFT // checks for testing. export function createExternalTokenStruct(addresses, options) { if (addresses.length === 0) { return NoopRuleData; } const defaultChain = addresses.map((address) => ({ chainId: options?.checkOptions?.chainId ?? 1n, address: address, type: options?.checkOptions?.type ?? CheckOperationType.ERC20, params: encodeThresholdParams({ threshold: options?.checkOptions?.threshold ?? BigInt(1) }), })); return createOperationsTree(defaultChain, options?.logicalOp ?? LogicalOperationType.OR); } export function createExternalNFTStruct(addresses, options) { if (addresses.length === 0) { return NoopRuleData; } const defaultChain = addresses.map((address) => ({ // Anvil chain id chainId: options?.checkOptions?.chainId ?? 31337n, address: address, type: options?.checkOptions?.type ?? CheckOperationType.ERC721, params: encodeThresholdParams({ threshold: options?.checkOptions?.threshold ?? BigInt(1) }), })); return createOperationsTree(defaultChain, options?.logicalOp ?? LogicalOperationType.OR); } export class DecodedCheckOperationBuilder { decodedCheckOp = {}; setType(checkOpType) { this.decodedCheckOp.type = checkOpType; return this; } setChainId(chainId) { this.decodedCheckOp.chainId = chainId; return this; } setThreshold(threshold) { this.decodedCheckOp.threshold = threshold; return this; } setAddress(address) { this.decodedCheckOp.address = address; return this; } setTokenId(tokenId) { this.decodedCheckOp.tokenId = tokenId; return this; } setByteEncodedParams(params) { this.decodedCheckOp.byteEncodedParams = params; return this; } build() { if (this.decodedCheckOp.type === undefined) { throw new Error('DecodedCheckOperation requires a type'); } const opStr = checkOpString(this.decodedCheckOp.type); // For contract-related checks, assert set values for chain id and contract address switch (this.decodedCheckOp.type) { case CheckOperationType.ERC1155: case CheckOperationType.ERC20: case CheckOperationType.ERC721: case CheckOperationType.ISENTITLED: if (this.decodedCheckOp.chainId === undefined) { throw new Error(`DecodedCheckOperation of type ${opStr} requires a chainId`); } if (this.decodedCheckOp.address === undefined) { throw new Error(`DecodedCheckOperation of type ${opStr} requires an address`); } } // threshold check switch (this.decodedCheckOp.type) { case CheckOperationType.ERC1155: case CheckOperationType.ETH_BALANCE: case CheckOperationType.ERC20: case CheckOperationType.ERC721: if (this.decodedCheckOp.threshold === undefined) { throw new Error(`DecodedCheckOperation of type ${opStr} requires a threshold`); } } // tokenId check if (this.decodedCheckOp.type === CheckOperationType.ERC1155 && this.decodedCheckOp.tokenId === undefined) { throw new Error(`DecodedCheckOperation of type ${opStr} requires a tokenId`); } // byte-encoded params check if (this.decodedCheckOp.type === CheckOperationType.ISENTITLED && this.decodedCheckOp.byteEncodedParams === undefined) { throw new Error(`DecodedCheckOperation of type ${opStr} requires byteEncodedParams`); } switch (this.decodedCheckOp.type) { case CheckOperationType.ERC20: case CheckOperationType.ERC721: return { type: this.decodedCheckOp.type, chainId: this.decodedCheckOp.chainId, address: this.decodedCheckOp.address, threshold: this.decodedCheckOp.threshold, }; case CheckOperationType.ERC1155: return { type: CheckOperationType.ERC1155, chainId: this.decodedCheckOp.chainId, address: this.decodedCheckOp.address, threshold: this.decodedCheckOp.threshold, tokenId: this.decodedCheckOp.tokenId, }; case CheckOperationType.ETH_BALANCE: return { type: CheckOperationType.ETH_BALANCE, threshold: this.decodedCheckOp.threshold, }; case CheckOperationType.ISENTITLED: return { type: CheckOperationType.ISENTITLED, address: this.decodedCheckOp.address, chainId: this.decodedCheckOp.chainId, byteEncodedParams: this.decodedCheckOp.byteEncodedParams, }; default: throw new Error(`Check operation type ${opStr} unrecognized or not used in production`); } } } export function createOperationsTree(checkOp, logicalOp = LogicalOperationType.OR) { if (checkOp.length === 0) { return { operations: [NoopOperation], checkOperations: [], logicalOperations: [], }; } let operations = checkOp.map((op) => { let params; switch (op.type) { case CheckOperationType.ERC20: case CheckOperationType.ERC721: params = encodeThresholdParams({ threshold: op.threshold ?? BigInt(1) }); break; case CheckOperationType.ETH_BALANCE: params = encodeThresholdParams({ threshold: op.threshold ?? BigInt(0) }); break; case CheckOperationType.ERC1155: params = encodeERC1155Params({ threshold: op.threshold ?? BigInt(1), tokenId: op.tokenId ?? BigInt(0), }); break; case CheckOperationType.ISENTITLED: params = op.byteEncodedParams ?? `0x`; break; default: params = '0x'; } return { opType: OperationType.CHECK, checkType: op.type, chainId: op.chainId ?? 1n, contractAddress: op.address ?? zeroAddress, params, }; }); while (operations.length > 1) { const newOperations = []; for (let i = 0; i < operations.length; i += 2) { if (i + 1 < operations.length) { newOperations.push({ opType: OperationType.LOGICAL, logicalType: logicalOp, leftOperation: operations[i], rightOperation: operations[i + 1], }); } else { newOperations.push(operations[i]); // Odd one out, just push it to the next level } } operations = newOperations; } return treeToRuleData(operations[0]); } // Return a set of reified check operations from a rule data struct in order to easily evaluate // thresholds, convert check operations into token schemas, etc. export function createDecodedCheckOperationFromTree(entitlementData) { const operations = ruleDataToOperations(entitlementData); const checkOpSubsets = []; operations.forEach((operation) => { if (isCheckOperationV2(operation)) { const op = { address: operation.contractAddress, chainId: operation.chainId, type: operation.checkType, }; if (operation.checkType === CheckOperationType.ERC1155) { const { threshold, tokenId } = decodeERC1155Params(operation.params); checkOpSubsets.push({ ...op, threshold, tokenId, }); } else if (operation.checkType === CheckOperationType.ERC20 || operation.checkType === CheckOperationType.ERC721 || operation.checkType === CheckOperationType.ETH_BALANCE) { const { threshold } = decodeThresholdParams(operation.params); checkOpSubsets.push({ ...op, threshold, }); } else if (operation.checkType === CheckOperationType.ISENTITLED) { checkOpSubsets.push({ ...op, byteEncodedParams: operation.params, }); } } }); return checkOpSubsets; } async function evaluateMockOperation(operation, controller) { const result = operation.chainId === 1n; const { threshold } = decodeThresholdParams(operation.params); const delay = Number.parseInt(threshold.toString()); return await new Promise((resolve) => { controller.signal.onabort = () => { if (timeout) { clearTimeout(timeout); resolve(zeroAddress); } }; const timeout = setTimeout(() => { if (result) { resolve(MOCK_ADDRESS); } else { resolve(zeroAddress); } }, delay); }); } async function evaluateERC721Operation(operation, controller, provider, linkedWallets) { const { threshold } = decodeThresholdParams(operation.params); return evaluateContractBalanceAcrossWallets(operation.contractAddress, threshold, controller, provider, linkedWallets); } async function evaluateERC20Operation(operation, controller, provider, linkedWallets) { const { threshold } = decodeThresholdParams(operation.params); return evaluateContractBalanceAcrossWallets(operation.contractAddress, threshold, controller, provider, linkedWallets); } async function evaluateCrossChainEntitlementOperation(operation, controller, provider, linkedWallets) { const contract = new ethers.Contract(operation.contractAddress, ['function isEntitled(address[], bytes) view returns (bool)'], provider); return await Promise.any(linkedWallets.map(async (wallet) => { const isEntitled = await contract.callStatic.isEntitled([wallet], operation.params); if (isEntitled === true) { return wallet; } throw new Error('Not entitled'); })).catch(() => { controller.abort(); return zeroAddress; }); } async function evaluateERC1155Operation(operation, controller, provider, linkedWallets) { const contract = new ethers.Contract(operation.contractAddress, ['function balanceOf(address, uint256) view returns (uint)'], provider); const { threshold, tokenId } = decodeERC1155Params(operation.params); const walletBalances = await Promise.all(linkedWallets.map(async (wallet) => { try { const result = (await contract.callStatic.balanceOf(wallet, tokenId)); const resultAsBigNumber = ethers.BigNumber.from(result); return { wallet, balance: resultAsBigNumber, }; } catch (error) { return { wallet, balance: ethers.BigNumber.from(0), }; } })); const walletsWithAsset = walletBalances.filter((result) => result.balance.gt(0)); const accumulatedBalance = walletsWithAsset.reduce((acc, el) => acc.add(el.balance), ethers.BigNumber.from(0)); if (walletsWithAsset.length > 0 && accumulatedBalance.gte(threshold)) { return walletsWithAsset[0].wallet; } else { controller.abort(); return zeroAddress; } } async function getEthBalance(provider, wallet) { try { const balance = await provider.getBalance(wallet); return { wallet, balance, }; } catch (error) { return { wallet, balance: ethers.BigNumber.from(0), }; } } async function evaluateEthBalanceOperation(operation, controller, providers, linkedWallets) { const { threshold } = decodeThresholdParams(operation.params); const balancePromises = []; for (const wallet of linkedWallets) { for (const provider of providers) { balancePromises.push(getEthBalance(provider, wallet)); } } const walletBalances = await Promise.all(balancePromises); const walletsWithAsset = walletBalances.filter((balance) => balance.balance.gt(0)); const accumulatedBalance = walletsWithAsset.reduce((acc, el) => acc.add(el.balance), ethers.BigNumber.from(0)); if (walletsWithAsset.length > 0 && accumulatedBalance.gte(threshold)) { return walletsWithAsset[0].wallet; } else { controller.abort(); return zeroAddress; } } async function evaluateContractBalanceAcrossWallets(contractAddress, threshold, controller, provider, linkedWallets) { const contract = new ethers.Contract(contractAddress, ['function balanceOf(address) view returns (uint)'], provider); const walletBalances = await Promise.all(linkedWallets.map(async (wallet) => { try { const result = await contract.callStatic.balanceOf(wallet); const resultAsBigNumber = ethers.BigNumber.from(result); if (!ethers.BigNumber.isBigNumber(resultAsBigNumber)) { return { wallet, balance: ethers.BigNumber.from(0), }; } return { wallet, balance: resultAsBigNumber, }; } catch (error) { return { wallet, balance: ethers.BigNumber.from(0), }; } })); const walletsWithAsset = walletBalances.filter((balance) => balance.balance.gt(0)); const accumulatedBalance = walletsWithAsset.reduce((acc, el) => acc.add(el.balance), ethers.BigNumber.from(0)); if (walletsWithAsset.length > 0 && accumulatedBalance.gte(threshold)) { return walletsWithAsset[0].wallet; } else { controller.abort(); return zeroAddress; } } async function findProviderFromChainId(xchainConfig, chainId) { if (!(Number(chainId) in xchainConfig.supportedRpcUrls)) { return undefined; } const url = xchainConfig.supportedRpcUrls[Number(chainId)]; const provider = new ethers.providers.StaticJsonRpcProvider(url); await provider.ready; return provider; } async function findEtherChainProviders(xchainConfig) { const etherChainProviders = []; for (const chainId of xchainConfig.etherBasedChains) { if (!(Number(chainId) in xchainConfig.supportedRpcUrls)) { log.info(`(WARN) findEtherChainProviders: No supported RPC URL for chain id ${chainId}`); } else { const url = xchainConfig.supportedRpcUrls[Number(chainId)]; etherChainProviders.push(new ethers.providers.StaticJsonRpcProvider(url)); } } await Promise.all(etherChainProviders.map((p) => p.ready)); return etherChainProviders; } function isValidAddress(value) { return (typeof value === 'string' && ethers.utils.isAddress(value) && value !== ethers.constants.AddressZero); } //# sourceMappingURL=entitlement.js.map