Skip to content

Commit 2019031

Browse files
committed
feat: add price feed contract call
1 parent 96994c4 commit 2019031

File tree

5 files changed

+284
-53
lines changed

5 files changed

+284
-53
lines changed

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

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,121 @@ export class LitContracts {
619619
this.connected = true;
620620
};
621621

622+
/**
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+
*/
635+
public static async getPriceFeedContract(
636+
network: LIT_NETWORKS_KEYS,
637+
context?: LitContractContext | LitContractResolverContext,
638+
rpcUrl?: string
639+
) {
640+
let provider: ethers.providers.StaticJsonRpcProvider;
641+
642+
const _rpcUrl = rpcUrl || RPC_URL_BY_NETWORK[network];
643+
644+
if (context && 'provider' in context!) {
645+
provider = context.provider;
646+
} else {
647+
provider = new ethers.providers.StaticJsonRpcProvider({
648+
url: _rpcUrl,
649+
skipFetchSetup: true,
650+
});
651+
}
652+
653+
if (!context) {
654+
const contractData = await LitContracts._resolveContractContext(network);
655+
656+
const priceFeedContract = contractData.find(
657+
(item: { name: string }) => item.name === 'PriceFeed'
658+
);
659+
const { address, abi } = priceFeedContract!;
660+
661+
// Validate the required data
662+
if (!address || !abi) {
663+
throw new InitError(
664+
{
665+
info: {
666+
address,
667+
abi,
668+
network,
669+
},
670+
},
671+
'❌ Required contract data is missing for PriceFeed'
672+
);
673+
}
674+
675+
return new ethers.Contract(address, abi, provider);
676+
} else {
677+
if (!context.resolverAddress) {
678+
const priceFeedContract = (context as LitContractContext).PriceFeed;
679+
680+
if (!priceFeedContract.address) {
681+
throw new InitError(
682+
{
683+
info: {
684+
priceFeedContract,
685+
context,
686+
},
687+
},
688+
'❌ Could not get PriceFeed contract address from contract context'
689+
);
690+
}
691+
return new ethers.Contract(
692+
priceFeedContract.address,
693+
694+
// FIXME: NOTE!! PriceFeedData.abi is not used since we don't use the imported ABIs in this package.
695+
// We should remove all imported ABIs and exclusively use NETWORK_CONTEXT_BY_NETWORK to retrieve ABIs for all other contracts.
696+
697+
// old convention: priceFeedContract.abi ?? PriceFeedData.abi
698+
699+
// new convention
700+
priceFeedContract.abi,
701+
provider
702+
);
703+
} else {
704+
const contractContext = await LitContracts._getContractsFromResolver(
705+
context as LitContractResolverContext,
706+
provider,
707+
['PriceFeed']
708+
);
709+
710+
if (!contractContext.PriceFeed.address) {
711+
throw new InitError(
712+
{
713+
info: {
714+
contractContext,
715+
context,
716+
},
717+
},
718+
'❌ Could not get PriceFeed contract from contract resolver instance'
719+
);
720+
}
721+
722+
const priceFeedABI = NETWORK_CONTEXT_BY_NETWORK[network].data.find(
723+
(data: any) => {
724+
return data.name === 'PriceFeed';
725+
}
726+
);
727+
728+
return new ethers.Contract(
729+
contractContext.PriceFeed.address,
730+
contractContext.PriceFeed.abi ?? priceFeedABI?.contracts[0].ABI,
731+
provider
732+
);
733+
}
734+
}
735+
}
736+
622737
/**
623738
* Retrieves the Staking contract instance based on the provided network, context, and RPC URL.
624739
* If a context is provided, it determines if a contract resolver is used for bootstrapping contracts.
@@ -722,6 +837,7 @@ export class LitContracts {
722837
return data.name === 'Staking';
723838
}
724839
);
840+
725841
return new ethers.Contract(
726842
contractContext.Staking.address,
727843
contractContext.Staking.abi ?? stakingABI?.contracts[0].ABI,
@@ -944,6 +1060,7 @@ export class LitContracts {
9441060
}
9451061

9461062
/**
1063+
* FIXME: Remove this for Naga
9471064
* @deprecated - Use {@link getConnectionInfo } instead, which provides more information.
9481065
*/
9491066
public static getMinNodeCount = async (
@@ -973,6 +1090,7 @@ export class LitContracts {
9731090
};
9741091

9751092
/**
1093+
* FIXME: remove this for Naga
9761094
* @deprecated - Use {@link getConnectionInfo } instead, which provides more information.
9771095
*/
9781096
public static getValidators = async (
@@ -1157,6 +1275,98 @@ export class LitContracts {
11571275
};
11581276
};
11591277

1278+
public static getPriceFeedInfo = async ({
1279+
litNetwork,
1280+
networkContext,
1281+
rpcUrl,
1282+
nodeProtocol,
1283+
}: {
1284+
litNetwork: LIT_NETWORKS_KEYS,
1285+
networkContext?: LitContractContext | LitContractResolverContext;
1286+
rpcUrl?: string;
1287+
nodeProtocol?: typeof HTTP | typeof HTTPS | null;
1288+
}) => {
1289+
const priceFeedContract = await LitContracts.getPriceFeedContract(
1290+
litNetwork,
1291+
networkContext,
1292+
rpcUrl,
1293+
)
1294+
1295+
const nodesForRequest = await priceFeedContract['getNodesForRequest']([0]);
1296+
1297+
const epochId = nodesForRequest[0].toNumber();
1298+
const minNodeCount = nodesForRequest[1].toNumber();
1299+
const nodesAndPrices = nodesForRequest[2];
1300+
1301+
// const totalNodes = nodesAndPrices.length;
1302+
1303+
const activeValidatorStructs: ValidatorStruct[] = nodesAndPrices.map((item: any) => {
1304+
const activeUnkickedValidatorStruct = item.validator;
1305+
return {
1306+
ip: activeUnkickedValidatorStruct.ip,
1307+
ipv6: activeUnkickedValidatorStruct.ipv6,
1308+
port: activeUnkickedValidatorStruct.port,
1309+
nodeAddress: activeUnkickedValidatorStruct.nodeAddress,
1310+
reward: activeUnkickedValidatorStruct.reward,
1311+
seconderPubkey: activeUnkickedValidatorStruct.seconderPubkey,
1312+
receiverPubkey: activeUnkickedValidatorStruct.receiverPubkey,
1313+
}
1314+
});
1315+
1316+
const networks = activeValidatorStructs.map((item: ValidatorStruct) => {
1317+
// Convert the integer IP to a string format
1318+
const ip = intToIP(item.ip);
1319+
const port = item.port;
1320+
1321+
// Determine the protocol to use based on various conditions
1322+
const protocol =
1323+
// If nodeProtocol is defined, use it
1324+
nodeProtocol ||
1325+
// If port is 443, use HTTPS, otherwise use network-specific HTTP
1326+
(port === 443 ? HTTPS : HTTP_BY_NETWORK[litNetwork]) ||
1327+
// Fallback to HTTP if no other conditions are met
1328+
HTTP;
1329+
1330+
const url = `${protocol}${ip}:${port}`;
1331+
1332+
LitContracts.logger.debug("Validator's URL:", url);
1333+
1334+
return url;
1335+
});
1336+
1337+
console.log("networks:", networks);
1338+
1339+
const prices = nodesAndPrices.flatMap((item: any) => {
1340+
// Flatten the nested prices array and convert BigNumber to number
1341+
return item.prices.map((price: ethers.BigNumber) => parseFloat(price.toString()));
1342+
});
1343+
1344+
console.log("Prices as numbers:", prices);
1345+
1346+
const networkPriceMap = networks.reduce((acc: any, network, index) => {
1347+
acc[network] = prices[index];
1348+
return acc;
1349+
}, {});
1350+
1351+
console.log("Network to Price Map:", networkPriceMap);
1352+
1353+
const networkPriceObjArr = networks.map((network, index) => {
1354+
return {
1355+
network, // The key will be the network URL
1356+
price: prices[index], // The value will be the corresponding price
1357+
};
1358+
});
1359+
1360+
return {
1361+
epochId,
1362+
minNodeCount,
1363+
networkPrices: {
1364+
arrObjects: networkPriceObjArr,
1365+
map: networkPriceMap
1366+
},
1367+
}
1368+
}
1369+
11601370
private static async _resolveContractContext(
11611371
network: LIT_NETWORK_VALUES
11621372
// context?: LitContractContext | LitContractResolverContext

packages/core/src/lib/lit-core.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ export class LitCore {
404404
return {
405405
socketAddress: urlWithoutProtocol,
406406

407-
// FIXME: This is a placeholder value, we need to get the actual value from the contract
407+
// FIXME: This is a placeholder value. Brendon said: It's not used anymore in the nodes, but leaving it as we may need it in the future.
408408
value: 1,
409409
};
410410
});
@@ -699,11 +699,9 @@ export class LitCore {
699699
await Promise.race([
700700
new Promise((_resolve, reject) => {
701701
timeoutHandle = setTimeout(() => {
702-
const msg = `Error: Could not handshake with nodes after timeout of ${
703-
this.config.connectTimeout
704-
}ms. Could only connect to ${Object.keys(serverKeys).length} of ${
705-
this.config.bootstrapUrls.length
706-
} nodes. Please check your network connection and try again. Note that you can control this timeout with the connectTimeout config option which takes milliseconds.`;
702+
const msg = `Error: Could not handshake with nodes after timeout of ${this.config.connectTimeout
703+
}ms. Could only connect to ${Object.keys(serverKeys).length} of ${this.config.bootstrapUrls.length
704+
} nodes. Please check your network connection and try again. Note that you can control this timeout with the connectTimeout config option which takes milliseconds.`;
707705

708706
try {
709707
throw new InitError({}, msg);
@@ -1031,8 +1029,8 @@ export class LitCore {
10311029
this._epochCache.currentNumber &&
10321030
this._epochCache.startTime &&
10331031
Math.floor(Date.now() / 1000) <
1034-
this._epochCache.startTime +
1035-
Math.floor(EPOCH_PROPAGATION_DELAY / 1000) &&
1032+
this._epochCache.startTime +
1033+
Math.floor(EPOCH_PROPAGATION_DELAY / 1000) &&
10361034
this._epochCache.currentNumber >= 3 // FIXME: Why this check?
10371035
) {
10381036
return this._epochCache.currentNumber - 1;
@@ -1063,7 +1061,7 @@ export class LitCore {
10631061
data,
10641062
requestId,
10651063
}: // eslint-disable-next-line @typescript-eslint/no-explicit-any
1066-
SendNodeCommand): Promise<any> => {
1064+
SendNodeCommand): Promise<any> => {
10671065
// FIXME: Replace <any> usage with explicit, strongly typed handlers
10681066
data = { ...data, epoch: this.currentEpochNumber };
10691067

packages/lit-node-client-nodejs/src/lib/lit-node-client-nodejs.ts

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,11 @@ import type {
133133
Signature,
134134
SuccessNodePromises,
135135
} from '@lit-protocol/types';
136+
import { LitContracts } from '@lit-protocol/contracts-sdk';
136137

137138
export class LitNodeClientNodeJs
138139
extends LitCore
139-
implements LitClientSessionManager, ILitNodeClient
140-
{
140+
implements LitClientSessionManager, ILitNodeClient {
141141
defaultAuthCallback?: (authSigParams: AuthCallbackParams) => Promise<AuthSig>;
142142

143143
// ========== Constructor ==========
@@ -1164,8 +1164,8 @@ export class LitNodeClientNodeJs
11641164
// -- optional params
11651165
...(params.authMethods &&
11661166
params.authMethods.length > 0 && {
1167-
authMethods: params.authMethods,
1168-
}),
1167+
authMethods: params.authMethods,
1168+
}),
11691169

11701170
nodeSet,
11711171
};
@@ -1893,8 +1893,8 @@ export class LitNodeClientNodeJs
18931893
const sessionCapabilityObject = params.sessionCapabilityObject
18941894
? params.sessionCapabilityObject
18951895
: await this.generateSessionCapabilityObjectWithWildcards(
1896-
params.resourceAbilityRequests.map((r) => r.resource)
1897-
);
1896+
params.resourceAbilityRequests.map((r) => r.resource)
1897+
);
18981898
const expiration = params.expiration || LitNodeClientNodeJs.getExpiration();
18991899

19001900
// -- (TRY) to get the wallet signature
@@ -1976,12 +1976,24 @@ export class LitNodeClientNodeJs
19761976

19771977
const capabilities = params.capacityDelegationAuthSig
19781978
? [
1979-
...(params.capabilityAuthSigs ?? []),
1980-
params.capacityDelegationAuthSig,
1981-
authSig,
1982-
]
1979+
...(params.capabilityAuthSigs ?? []),
1980+
params.capacityDelegationAuthSig,
1981+
authSig,
1982+
]
19831983
: [...(params.capabilityAuthSigs ?? []), authSig];
19841984

1985+
// get max price from contract
1986+
// FIXME: https://github.com/LIT-Protocol/lit-assets/blob/36d4306912c6cc51de8a3ad261febf37eb84c94d/blockchain/contracts/contracts/lit-node/PriceFeed/PriceFeedFacet.sol#L134
1987+
const priceFeedInfo = await LitContracts.getPriceFeedInfo({
1988+
litNetwork: this.config.litNetwork,
1989+
networkContext: this.config.contractContext,
1990+
rpcUrl: this.config.rpcUrl,
1991+
});
1992+
1993+
// console.log("priceFeedInfo:", priceFeedInfo);
1994+
1995+
// process.exit();
1996+
19851997
// This is the template that will be combined with the node address as a single object, then signed by the session key
19861998
// so that the node can verify the session signature
19871999
const sessionSigningTemplate = {
@@ -1991,16 +2003,25 @@ export class LitNodeClientNodeJs
19912003
issuedAt: new Date().toISOString(),
19922004
expiration: sessionExpiration,
19932005

2006+
// fetch it from the contract, i don't want to spend more than 10 cents on signing
19942007
// FIXME: This is a dummy value for now
1995-
maxPrice: '0x1234567890abcdef1234567890abcdef12345678',
2008+
// maxPrice: '0x1234567890abcdef1234567890abcdef12345678',
19962009
};
19972010

19982011
const sessionSigs: SessionSigsMap = {};
19992012

20002013
this.connectedNodes.forEach((nodeAddress: string) => {
2014+
2015+
const maxPrice = priceFeedInfo.networkPrices.map[nodeAddress];
2016+
2017+
if (maxPrice <= 0) {
2018+
throw new Error(`Invalid maxPrice for node: ${nodeAddress}`);
2019+
}
2020+
20012021
const toSign: SessionSigningTemplate = {
20022022
...sessionSigningTemplate,
20032023
nodeAddress,
2024+
maxPrice: maxPrice.toString(),
20042025
};
20052026

20062027
const signedMessage = JSON.stringify(toSign);

0 commit comments

Comments
 (0)