Skip to content

Commit 432ac93

Browse files
committed
feat(priceFeed):
1. include price feed info when we first connected, sort it from lowest to highest (default to true). 2. Added a new param `getNewPrices` if user want to fetch new prices from the contract. 3. created helper function to convert validator structs to urls
1 parent 345cd29 commit 432ac93

File tree

4 files changed

+229
-146
lines changed

4 files changed

+229
-146
lines changed

packages/contracts-sdk/src/lib/contracts-sdk.ts

Lines changed: 151 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* eslint-disable import/order */
2-
import { isBrowser, isNode } from '@lit-protocol/misc';
2+
import { isBrowser, isNode, log } from '@lit-protocol/misc';
33
import {
44
ContractName,
55
CreateCustomAuthMethodRequest,
@@ -620,18 +620,18 @@ export class LitContracts {
620620
};
621621

622622
/**
623-
* Retrieves the PriceFeed contract instance based on the provided network, context, and RPC URL.
624-
* If a context is provided, it determines if a contract resolver is used for bootstrapping contracts.
625-
* If a resolver address is present in the context, it retrieves the PriceFeed contract from the contract resolver instance.
626-
* Otherwise, it retrieves the PriceFeed contract using the contract address and ABI.
627-
* Throws an error if required contract data is missing or if the PriceFeed contract cannot be obtained.
628-
*
629-
* @param network - The network key.
630-
* @param context - The contract context or contract resolver context.
631-
* @param rpcUrl - The RPC URL.
632-
* @returns The PriceFeed contract instance.
633-
* @throws Error if required contract data is missing or if the PriceFeed contract cannot be obtained.
634-
*/
623+
* Retrieves the PriceFeed contract instance based on the provided network, context, and RPC URL.
624+
* If a context is provided, it determines if a contract resolver is used for bootstrapping contracts.
625+
* If a resolver address is present in the context, it retrieves the PriceFeed contract from the contract resolver instance.
626+
* Otherwise, it retrieves the PriceFeed contract using the contract address and ABI.
627+
* Throws an error if required contract data is missing or if the PriceFeed contract cannot be obtained.
628+
*
629+
* @param network - The network key.
630+
* @param context - The contract context or contract resolver context.
631+
* @param rpcUrl - The RPC URL.
632+
* @returns The PriceFeed contract instance.
633+
* @throws Error if required contract data is missing or if the PriceFeed contract cannot be obtained.
634+
*/
635635
public static async getPriceFeedContract(
636636
network: LIT_NETWORKS_KEYS,
637637
context?: LitContractContext | LitContractResolverContext,
@@ -938,7 +938,7 @@ export class LitContracts {
938938
const names = contractNames ?? LitContracts.contractNames;
939939

940940
const contractContext: LitContractContext = {} as LitContractContext;
941-
// Ah, Bluebird.props(), we miss you 🫗
941+
// Ah, Bluebird.props(), we miss you ����
942942
await Promise.all(
943943
names.map(async (contractName) => {
944944
const contracts = context?.contractContext;
@@ -1150,7 +1150,6 @@ export class LitContracts {
11501150
});
11511151

11521152
const networks = activeValidatorStructs.map((item: ValidatorStruct) => {
1153-
const centralisation = CENTRALISATION_BY_NETWORK[network];
11541153

11551154
// Convert the integer IP to a string format
11561155
const ip = intToIP(item.ip);
@@ -1175,6 +1174,62 @@ export class LitContracts {
11751174
return networks;
11761175
};
11771176

1177+
1178+
/**
1179+
* Generates an array of validator URLs based on the given validator structs and network configurations.
1180+
*
1181+
* @param {ValidatorStruct[]} activeValidatorStructs - Array of validator structures containing IP and port information.
1182+
* @param {string | undefined} nodeProtocol - Optional node protocol to override the default protocol selection logic.
1183+
* @param {string} litNetwork - The name of the network used to determine HTTP/HTTPS settings.
1184+
* @returns {string[]} Array of constructed validator URLs.
1185+
*
1186+
* @example
1187+
* // Example input
1188+
* const activeValidatorStructs = [
1189+
* { ip: 3232235777, port: 443 }, // IP: 192.168.1.1
1190+
* { ip: 3232235778, port: 80 }, // IP: 192.168.1.2
1191+
* ];
1192+
* const nodeProtocol = undefined;
1193+
* const litNetwork = "mainnet";
1194+
*
1195+
* // Example output
1196+
* const urls = generateValidatorURLs(activeValidatorStructs, nodeProtocol, litNetwork);
1197+
* console.log(urls);
1198+
* Output: [
1199+
* "https://192.168.1.1:443",
1200+
* "http://192.168.1.2:80"
1201+
* ]
1202+
*/
1203+
public static generateValidatorURLs({
1204+
activeValidatorStructs,
1205+
nodeProtocol,
1206+
litNetwork,
1207+
}: {
1208+
activeValidatorStructs: ValidatorStruct[];
1209+
nodeProtocol?: string;
1210+
litNetwork: LIT_NETWORK_VALUES;
1211+
}): string[] {
1212+
return activeValidatorStructs.map((item) => {
1213+
// Convert the integer IP to a string format
1214+
const ip = intToIP(item.ip);
1215+
const port = item.port;
1216+
1217+
// Determine the protocol to use based on conditions
1218+
const protocol =
1219+
nodeProtocol || // Use nodeProtocol if defined
1220+
(port === 443 ? HTTPS : HTTP_BY_NETWORK[litNetwork]) || // HTTPS for port 443 or network-specific HTTP
1221+
HTTP; // Fallback to HTTP
1222+
1223+
// Construct the URL
1224+
const url = `${protocol}${ip}:${port}`;
1225+
1226+
// Log the constructed URL for debugging
1227+
LitContracts.logger.debug("Validator's URL:", url);
1228+
1229+
return url;
1230+
});
1231+
}
1232+
11781233
/**
11791234
* Retrieves the connection information for a given network.
11801235
*
@@ -1193,17 +1248,31 @@ export class LitContracts {
11931248
networkContext,
11941249
rpcUrl,
11951250
nodeProtocol,
1251+
sortByPrice
11961252
}: {
11971253
litNetwork: LIT_NETWORKS_KEYS;
11981254
networkContext?: LitContractContext | LitContractResolverContext;
11991255
rpcUrl?: string;
12001256
nodeProtocol?: typeof HTTP | typeof HTTPS | null;
1257+
sortByPrice?: boolean;
12011258
}): Promise<{
12021259
stakingContract: ethers.Contract;
12031260
epochInfo: EpochInfo;
12041261
minNodeCount: number;
12051262
bootstrapUrls: string[];
1263+
priceByNetwork: Record<string, number>;
12061264
}> => {
1265+
1266+
// if it's true, we will sort the networks by price feed from lowest to highest
1267+
// if it's false, we will not sort the networks
1268+
let _sortByPrice = sortByPrice || true;
1269+
1270+
if (_sortByPrice) {
1271+
log('Sorting networks by price feed from lowest to highest');
1272+
} else {
1273+
log('Not sorting networks by price feed');
1274+
}
1275+
12071276
const stakingContract = await LitContracts.getStakingContract(
12081277
litNetwork,
12091278
networkContext,
@@ -1246,32 +1315,44 @@ export class LitContracts {
12461315
};
12471316
});
12481317

1249-
const networks = activeValidatorStructs.map((item: ValidatorStruct) => {
1250-
// Convert the integer IP to a string format
1251-
const ip = intToIP(item.ip);
1252-
const port = item.port;
1253-
1254-
// Determine the protocol to use based on various conditions
1255-
const protocol =
1256-
// If nodeProtocol is defined, use it
1257-
nodeProtocol ||
1258-
// If port is 443, use HTTPS, otherwise use network-specific HTTP
1259-
(port === 443 ? HTTPS : HTTP_BY_NETWORK[litNetwork]) ||
1260-
// Fallback to HTTP if no other conditions are met
1261-
HTTP;
1318+
const unsortedNetworks = LitContracts.generateValidatorURLs({
1319+
activeValidatorStructs,
1320+
litNetwork,
1321+
})
12621322

1263-
const url = `${protocol}${ip}:${port}`;
1323+
// networks are all the nodes we know, but we also want to sort it by price feed
1324+
const priceFeedInfo = await LitContracts.getPriceFeedInfo({
1325+
litNetwork,
1326+
networkContext,
1327+
rpcUrl,
1328+
nodeProtocol,
1329+
});
12641330

1265-
LitContracts.logger.debug("Validator's URL:", url);
1331+
// example of Network to Price Map: {
1332+
// 'http://xxx:7470': 100, <-- lowest price
1333+
// 'http://yyy:7471': 300, <-- highest price
1334+
// 'http://zzz:7472': 200 <-- middle price
1335+
// }
1336+
const PRICE_BY_NETWORK = priceFeedInfo.networkPrices.mapByAddress;
1337+
1338+
// sorted networks by prices (lowest to highest)
1339+
// [
1340+
// 'http://xxx:7470', <-- lowest price
1341+
// 'http://zzz:7472', <-- middle price
1342+
// 'http://yyy:7471' <-- highest price
1343+
// ]
1344+
const sortedNetworks = unsortedNetworks.sort((a, b) =>
1345+
PRICE_BY_NETWORK[a] - PRICE_BY_NETWORK[b]
1346+
);
12661347

1267-
return url;
1268-
});
1348+
const bootstrapUrls = _sortByPrice ? sortedNetworks : unsortedNetworks;
12691349

12701350
return {
12711351
stakingContract,
12721352
epochInfo: typedEpochInfo,
12731353
minNodeCount: minNodeCountInt,
1274-
bootstrapUrls: networks,
1354+
bootstrapUrls: bootstrapUrls,
1355+
priceByNetwork: PRICE_BY_NETWORK,
12751356
};
12761357
};
12771358

@@ -1280,79 +1361,66 @@ export class LitContracts {
12801361
networkContext,
12811362
rpcUrl,
12821363
nodeProtocol,
1364+
productIds, // Array of product IDs
12831365
}: {
1284-
litNetwork: LIT_NETWORKS_KEYS;
1366+
litNetwork: LIT_NETWORKS_KEYS,
12851367
networkContext?: LitContractContext | LitContractResolverContext;
12861368
rpcUrl?: string;
12871369
nodeProtocol?: typeof HTTP | typeof HTTPS | null;
1370+
productIds?: number[];
12881371
}) => {
1372+
1373+
if (!productIds || productIds.length === 0) {
1374+
log('No product IDs provided. Defaulting to 0');
1375+
1376+
// You should use all [0,1,2] because we fetch the price first to connect to the cheapest node. And after that the user calls the actual function
1377+
productIds = [0, 1, 2]
1378+
}
1379+
12891380
const priceFeedContract = await LitContracts.getPriceFeedContract(
12901381
litNetwork,
12911382
networkContext,
1292-
rpcUrl
1293-
);
1383+
rpcUrl,
1384+
)
12941385

1295-
const nodesForRequest = await priceFeedContract['getNodesForRequest']([0]);
1386+
const nodesForRequest = await priceFeedContract['getNodesForRequest'](productIds);
12961387

12971388
const epochId = nodesForRequest[0].toNumber();
12981389
const minNodeCount = nodesForRequest[1].toNumber();
12991390
const nodesAndPrices = nodesForRequest[2];
13001391

1301-
// const totalNodes = nodesAndPrices.length;
1302-
1303-
const activeValidatorStructs: ValidatorStruct[] = nodesAndPrices.map(
1304-
(item: any) => {
1305-
const activeUnkickedValidatorStruct = item.validator;
1306-
return {
1307-
ip: activeUnkickedValidatorStruct.ip,
1308-
ipv6: activeUnkickedValidatorStruct.ipv6,
1309-
port: activeUnkickedValidatorStruct.port,
1310-
nodeAddress: activeUnkickedValidatorStruct.nodeAddress,
1311-
reward: activeUnkickedValidatorStruct.reward,
1312-
seconderPubkey: activeUnkickedValidatorStruct.seconderPubkey,
1313-
receiverPubkey: activeUnkickedValidatorStruct.receiverPubkey,
1314-
};
1392+
const activeValidatorStructs: ValidatorStruct[] = nodesAndPrices.map((item: any) => {
1393+
return {
1394+
ip: item.validator.ip,
1395+
ipv6: item.validator.ipv6,
1396+
port: item.validator.port,
1397+
nodeAddress: item.validator.nodeAddress,
1398+
reward: item.validator.reward,
1399+
seconderPubkey: item.validator.seconderPubkey,
1400+
receiverPubkey: item.validator.receiverPubkey,
13151401
}
1316-
);
1317-
1318-
const networks = activeValidatorStructs.map((item: ValidatorStruct) => {
1319-
// Convert the integer IP to a string format
1320-
const ip = intToIP(item.ip);
1321-
const port = item.port;
1322-
1323-
// Determine the protocol to use based on various conditions
1324-
const protocol =
1325-
// If nodeProtocol is defined, use it
1326-
nodeProtocol ||
1327-
// If port is 443, use HTTPS, otherwise use network-specific HTTP
1328-
(port === 443 ? HTTPS : HTTP_BY_NETWORK[litNetwork]) ||
1329-
// Fallback to HTTP if no other conditions are met
1330-
HTTP;
1331-
1332-
const url = `${protocol}${ip}:${port}`;
1333-
1334-
LitContracts.logger.debug("Validator's URL:", url);
1335-
1336-
return url;
13371402
});
13381403

1339-
console.log('networks:', networks);
1404+
const networks = LitContracts.generateValidatorURLs({
1405+
activeValidatorStructs,
1406+
litNetwork,
1407+
})
1408+
1409+
console.log("networks:", networks);
13401410

13411411
const prices = nodesAndPrices.flatMap((item: any) => {
13421412
// Flatten the nested prices array and convert BigNumber to number
1343-
return item.prices.map((price: ethers.BigNumber) =>
1344-
parseFloat(price.toString())
1345-
);
1413+
return item.prices.map((price: ethers.BigNumber) => parseFloat(price.toString()));
13461414
});
13471415

1348-
console.log('Prices as numbers:', prices);
1416+
console.log("Prices as numbers:", prices);
13491417

1350-
const networkPriceMap = networks.reduce((acc: any, network, index) => {
1418+
const networkPriceMap: Record<string, number> = networks.reduce((acc: any, network, index) => {
13511419
acc[network] = prices[index];
13521420
return acc;
13531421
}, {});
13541422

1355-
console.log('Network to Price Map:', networkPriceMap);
1423+
console.log("Network to Price Map:", networkPriceMap);
13561424

13571425
const networkPriceObjArr = networks.map((network, index) => {
13581426
return {
@@ -1365,11 +1433,11 @@ export class LitContracts {
13651433
epochId,
13661434
minNodeCount,
13671435
networkPrices: {
1368-
arrObjects: networkPriceObjArr,
1369-
map: networkPriceMap,
1436+
arr: networkPriceObjArr,
1437+
mapByAddress: networkPriceMap
13701438
},
1371-
};
1372-
};
1439+
}
1440+
}
13731441

13741442
private static async _resolveContractContext(
13751443
network: LIT_NETWORK_VALUES

0 commit comments

Comments
 (0)