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