Skip to content

Commit

Permalink
feat: add price feed contract call
Browse files Browse the repository at this point in the history
  • Loading branch information
Ansonhkg committed Dec 19, 2024
1 parent 96994c4 commit 2019031
Show file tree
Hide file tree
Showing 5 changed files with 284 additions and 53 deletions.
210 changes: 210 additions & 0 deletions packages/contracts-sdk/src/lib/contracts-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,121 @@ export class LitContracts {
this.connected = true;
};

/**
* Retrieves the PriceFeed contract instance based on the provided network, context, and RPC URL.
* If a context is provided, it determines if a contract resolver is used for bootstrapping contracts.
* If a resolver address is present in the context, it retrieves the PriceFeed contract from the contract resolver instance.
* Otherwise, it retrieves the PriceFeed contract using the contract address and ABI.
* Throws an error if required contract data is missing or if the PriceFeed contract cannot be obtained.
*
* @param network - The network key.
* @param context - The contract context or contract resolver context.
* @param rpcUrl - The RPC URL.
* @returns The PriceFeed contract instance.
* @throws Error if required contract data is missing or if the PriceFeed contract cannot be obtained.
*/
public static async getPriceFeedContract(
network: LIT_NETWORKS_KEYS,
context?: LitContractContext | LitContractResolverContext,
rpcUrl?: string
) {
let provider: ethers.providers.StaticJsonRpcProvider;

const _rpcUrl = rpcUrl || RPC_URL_BY_NETWORK[network];

if (context && 'provider' in context!) {
provider = context.provider;
} else {
provider = new ethers.providers.StaticJsonRpcProvider({
url: _rpcUrl,
skipFetchSetup: true,
});
}

if (!context) {
const contractData = await LitContracts._resolveContractContext(network);

const priceFeedContract = contractData.find(
(item: { name: string }) => item.name === 'PriceFeed'
);
const { address, abi } = priceFeedContract!;

// Validate the required data
if (!address || !abi) {
throw new InitError(
{
info: {
address,
abi,
network,
},
},
'❌ Required contract data is missing for PriceFeed'
);
}

return new ethers.Contract(address, abi, provider);
} else {
if (!context.resolverAddress) {
const priceFeedContract = (context as LitContractContext).PriceFeed;

if (!priceFeedContract.address) {
throw new InitError(
{
info: {
priceFeedContract,
context,
},
},
'❌ Could not get PriceFeed contract address from contract context'
);
}
return new ethers.Contract(
priceFeedContract.address,

// FIXME: NOTE!! PriceFeedData.abi is not used since we don't use the imported ABIs in this package.
// We should remove all imported ABIs and exclusively use NETWORK_CONTEXT_BY_NETWORK to retrieve ABIs for all other contracts.

// old convention: priceFeedContract.abi ?? PriceFeedData.abi

// new convention
priceFeedContract.abi,
provider
);
} else {
const contractContext = await LitContracts._getContractsFromResolver(
context as LitContractResolverContext,
provider,
['PriceFeed']
);

if (!contractContext.PriceFeed.address) {
throw new InitError(
{
info: {
contractContext,
context,
},
},
'❌ Could not get PriceFeed contract from contract resolver instance'
);
}

const priceFeedABI = NETWORK_CONTEXT_BY_NETWORK[network].data.find(
(data: any) => {
return data.name === 'PriceFeed';
}
);

return new ethers.Contract(
contractContext.PriceFeed.address,
contractContext.PriceFeed.abi ?? priceFeedABI?.contracts[0].ABI,
provider
);
}
}
}

/**
* Retrieves the Staking contract instance based on the provided network, context, and RPC URL.
* If a context is provided, it determines if a contract resolver is used for bootstrapping contracts.
Expand Down Expand Up @@ -722,6 +837,7 @@ export class LitContracts {
return data.name === 'Staking';
}
);

return new ethers.Contract(
contractContext.Staking.address,
contractContext.Staking.abi ?? stakingABI?.contracts[0].ABI,
Expand Down Expand Up @@ -944,6 +1060,7 @@ export class LitContracts {
}

/**
* FIXME: Remove this for Naga
* @deprecated - Use {@link getConnectionInfo } instead, which provides more information.
*/
public static getMinNodeCount = async (
Expand Down Expand Up @@ -973,6 +1090,7 @@ export class LitContracts {
};

/**
* FIXME: remove this for Naga
* @deprecated - Use {@link getConnectionInfo } instead, which provides more information.
*/
public static getValidators = async (
Expand Down Expand Up @@ -1157,6 +1275,98 @@ export class LitContracts {
};
};

public static getPriceFeedInfo = async ({
litNetwork,
networkContext,
rpcUrl,
nodeProtocol,
}: {
litNetwork: LIT_NETWORKS_KEYS,
networkContext?: LitContractContext | LitContractResolverContext;
rpcUrl?: string;
nodeProtocol?: typeof HTTP | typeof HTTPS | null;
}) => {
const priceFeedContract = await LitContracts.getPriceFeedContract(
litNetwork,
networkContext,
rpcUrl,
)

const nodesForRequest = await priceFeedContract['getNodesForRequest']([0]);

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

// const totalNodes = nodesAndPrices.length;

const activeValidatorStructs: ValidatorStruct[] = nodesAndPrices.map((item: any) => {
const activeUnkickedValidatorStruct = item.validator;
return {
ip: activeUnkickedValidatorStruct.ip,
ipv6: activeUnkickedValidatorStruct.ipv6,
port: activeUnkickedValidatorStruct.port,
nodeAddress: activeUnkickedValidatorStruct.nodeAddress,
reward: activeUnkickedValidatorStruct.reward,
seconderPubkey: activeUnkickedValidatorStruct.seconderPubkey,
receiverPubkey: activeUnkickedValidatorStruct.receiverPubkey,
}
});

const networks = activeValidatorStructs.map((item: ValidatorStruct) => {
// Convert the integer IP to a string format
const ip = intToIP(item.ip);
const port = item.port;

// Determine the protocol to use based on various conditions
const protocol =
// If nodeProtocol is defined, use it
nodeProtocol ||
// If port is 443, use HTTPS, otherwise use network-specific HTTP
(port === 443 ? HTTPS : HTTP_BY_NETWORK[litNetwork]) ||
// Fallback to HTTP if no other conditions are met
HTTP;

const url = `${protocol}${ip}:${port}`;

LitContracts.logger.debug("Validator's URL:", url);

return url;
});

console.log("networks:", networks);

const prices = nodesAndPrices.flatMap((item: any) => {
// Flatten the nested prices array and convert BigNumber to number
return item.prices.map((price: ethers.BigNumber) => parseFloat(price.toString()));
});

console.log("Prices as numbers:", prices);

const networkPriceMap = networks.reduce((acc: any, network, index) => {
acc[network] = prices[index];
return acc;
}, {});

console.log("Network to Price Map:", networkPriceMap);

const networkPriceObjArr = networks.map((network, index) => {
return {
network, // The key will be the network URL
price: prices[index], // The value will be the corresponding price
};
});

return {
epochId,
minNodeCount,
networkPrices: {
arrObjects: networkPriceObjArr,
map: networkPriceMap
},
}
}

private static async _resolveContractContext(
network: LIT_NETWORK_VALUES
// context?: LitContractContext | LitContractResolverContext
Expand Down
16 changes: 7 additions & 9 deletions packages/core/src/lib/lit-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ export class LitCore {
return {
socketAddress: urlWithoutProtocol,

// FIXME: This is a placeholder value, we need to get the actual value from the contract
// 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.
value: 1,
};
});
Expand Down Expand Up @@ -699,11 +699,9 @@ export class LitCore {
await Promise.race([
new Promise((_resolve, reject) => {
timeoutHandle = setTimeout(() => {
const msg = `Error: Could not handshake with nodes after timeout of ${
this.config.connectTimeout
}ms. Could only connect to ${Object.keys(serverKeys).length} of ${
this.config.bootstrapUrls.length
} nodes. Please check your network connection and try again. Note that you can control this timeout with the connectTimeout config option which takes milliseconds.`;
const msg = `Error: Could not handshake with nodes after timeout of ${this.config.connectTimeout
}ms. Could only connect to ${Object.keys(serverKeys).length} of ${this.config.bootstrapUrls.length
} nodes. Please check your network connection and try again. Note that you can control this timeout with the connectTimeout config option which takes milliseconds.`;

try {
throw new InitError({}, msg);
Expand Down Expand Up @@ -1031,8 +1029,8 @@ export class LitCore {
this._epochCache.currentNumber &&
this._epochCache.startTime &&
Math.floor(Date.now() / 1000) <
this._epochCache.startTime +
Math.floor(EPOCH_PROPAGATION_DELAY / 1000) &&
this._epochCache.startTime +
Math.floor(EPOCH_PROPAGATION_DELAY / 1000) &&
this._epochCache.currentNumber >= 3 // FIXME: Why this check?
) {
return this._epochCache.currentNumber - 1;
Expand Down Expand Up @@ -1063,7 +1061,7 @@ export class LitCore {
data,
requestId,
}: // eslint-disable-next-line @typescript-eslint/no-explicit-any
SendNodeCommand): Promise<any> => {
SendNodeCommand): Promise<any> => {
// FIXME: Replace <any> usage with explicit, strongly typed handlers
data = { ...data, epoch: this.currentEpochNumber };

Expand Down
43 changes: 32 additions & 11 deletions packages/lit-node-client-nodejs/src/lib/lit-node-client-nodejs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,11 @@ import type {
Signature,
SuccessNodePromises,
} from '@lit-protocol/types';
import { LitContracts } from '@lit-protocol/contracts-sdk';

export class LitNodeClientNodeJs
extends LitCore
implements LitClientSessionManager, ILitNodeClient
{
implements LitClientSessionManager, ILitNodeClient {
defaultAuthCallback?: (authSigParams: AuthCallbackParams) => Promise<AuthSig>;

// ========== Constructor ==========
Expand Down Expand Up @@ -1164,8 +1164,8 @@ export class LitNodeClientNodeJs
// -- optional params
...(params.authMethods &&
params.authMethods.length > 0 && {
authMethods: params.authMethods,
}),
authMethods: params.authMethods,
}),

nodeSet,
};
Expand Down Expand Up @@ -1893,8 +1893,8 @@ export class LitNodeClientNodeJs
const sessionCapabilityObject = params.sessionCapabilityObject
? params.sessionCapabilityObject
: await this.generateSessionCapabilityObjectWithWildcards(
params.resourceAbilityRequests.map((r) => r.resource)
);
params.resourceAbilityRequests.map((r) => r.resource)
);
const expiration = params.expiration || LitNodeClientNodeJs.getExpiration();

// -- (TRY) to get the wallet signature
Expand Down Expand Up @@ -1976,12 +1976,24 @@ export class LitNodeClientNodeJs

const capabilities = params.capacityDelegationAuthSig
? [
...(params.capabilityAuthSigs ?? []),
params.capacityDelegationAuthSig,
authSig,
]
...(params.capabilityAuthSigs ?? []),
params.capacityDelegationAuthSig,
authSig,
]
: [...(params.capabilityAuthSigs ?? []), authSig];

// get max price from contract
// FIXME: https://github.com/LIT-Protocol/lit-assets/blob/36d4306912c6cc51de8a3ad261febf37eb84c94d/blockchain/contracts/contracts/lit-node/PriceFeed/PriceFeedFacet.sol#L134
const priceFeedInfo = await LitContracts.getPriceFeedInfo({
litNetwork: this.config.litNetwork,
networkContext: this.config.contractContext,
rpcUrl: this.config.rpcUrl,
});

// console.log("priceFeedInfo:", priceFeedInfo);

// process.exit();

// This is the template that will be combined with the node address as a single object, then signed by the session key
// so that the node can verify the session signature
const sessionSigningTemplate = {
Expand All @@ -1991,16 +2003,25 @@ export class LitNodeClientNodeJs
issuedAt: new Date().toISOString(),
expiration: sessionExpiration,

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

const sessionSigs: SessionSigsMap = {};

this.connectedNodes.forEach((nodeAddress: string) => {

const maxPrice = priceFeedInfo.networkPrices.map[nodeAddress];

if (maxPrice <= 0) {
throw new Error(`Invalid maxPrice for node: ${nodeAddress}`);
}

const toSign: SessionSigningTemplate = {
...sessionSigningTemplate,
nodeAddress,
maxPrice: maxPrice.toString(),
};

const signedMessage = JSON.stringify(toSign);
Expand Down
Loading

0 comments on commit 2019031

Please sign in to comment.