Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
238 changes: 238 additions & 0 deletions blockchain/contracts/scripts/calculateUSDPricing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
// Script to calculate fees in USD based on LITKEY token price
// Usage: HARDHAT_NETWORK=litMainnet npx ts-node --files scripts/calculateUSDPricing.ts

import hre from 'hardhat';

const { ethers } = hre;

// on Lit Chain Mainnet
const NAGA_PROD_PRICE_FEED_ADDRESS =
'0x88F5535Fa6dA5C225a3C06489fE4e3405b87608C';

Comment on lines +2 to +11
Copy link

Copilot AI Nov 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded contract address may cause issues on different networks. The script hardcodes the Naga production PriceFeed address for Lit Chain Mainnet, but doesn't validate that the current network matches. If run on a different network, the contract call will fail. Consider adding network validation or making the address configurable via environment variables or command-line arguments.

Suggested change
// Usage: HARDHAT_NETWORK=litMainnet npx ts-node --files scripts/calculateUSDPricing.ts
import hre from 'hardhat';
const { ethers } = hre;
// on Lit Chain Mainnet
const NAGA_PROD_PRICE_FEED_ADDRESS =
'0x88F5535Fa6dA5C225a3C06489fE4e3405b87608C';
// Usage: HARDHAT_NETWORK=litMainnet PRICE_FEED_ADDRESS=0xYourAddressHere npx ts-node --files scripts/calculateUSDPricing.ts
import hre from 'hardhat';
const { ethers, network } = hre;
// Naga PriceFeed address for Lit Chain Mainnet
const DEFAULT_LIT_MAINNET_PRICE_FEED_ADDRESS = '0x88F5535Fa6dA5C225a3C06489fE4e3405b87608C';
// Get address from env or fallback to default if on Lit Chain Mainnet
function getPriceFeedAddress(): string {
const envAddress = process.env.PRICE_FEED_ADDRESS;
if (envAddress && envAddress !== '') {
return envAddress;
}
// Validate network
if (network.name === 'litMainnet') {
return DEFAULT_LIT_MAINNET_PRICE_FEED_ADDRESS;
}
throw new Error(
`No PRICE_FEED_ADDRESS provided and not on Lit Chain Mainnet (network: ${network.name}). Please set PRICE_FEED_ADDRESS environment variable.`
);
}

Copilot uses AI. Check for mistakes.
// Product IDs from LibPriceFeedStorage.ProductId enum
enum ProductId {
PkpSign = 0,
EncSign = 1,
LitAction = 2,
SignSessionKey = 3,
}

// LitActionPriceComponent enum values
enum LitActionPriceComponent {
baseAmount = 0,
runtimeLength = 1,
memoryUsage = 2,
codeLength = 3,
responseLength = 4,
signatures = 5,
broadcasts = 6,
contractCalls = 7,
callDepth = 8,
decrypts = 9,
fetches = 10,
}

// NodePriceMeasurement enum values
enum NodePriceMeasurement {
perSecond = 0,
perMegabyte = 1,
perCount = 2,
}

const PRODUCT_NAMES = {
[ProductId.PkpSign]: 'PKP Sign',
[ProductId.EncSign]: 'Encrypted Sign',
[ProductId.LitAction]: 'Lit Action',
[ProductId.SignSessionKey]: 'Sign Session Key',
};

const LIT_ACTION_COMPONENT_NAMES = {
[LitActionPriceComponent.baseAmount]: 'Base Amount',
[LitActionPriceComponent.runtimeLength]: 'Runtime Length',
[LitActionPriceComponent.memoryUsage]: 'Memory Usage',
[LitActionPriceComponent.codeLength]: 'Code Length',
[LitActionPriceComponent.responseLength]: 'Response Length',
[LitActionPriceComponent.signatures]: 'Signatures',
[LitActionPriceComponent.broadcasts]: 'Broadcasts',
[LitActionPriceComponent.contractCalls]: 'Contract Calls',
[LitActionPriceComponent.callDepth]: 'Call Depth',
[LitActionPriceComponent.decrypts]: 'Decrypts',
[LitActionPriceComponent.fetches]: 'Fetches',
};

const MEASUREMENT_NAMES = {
[NodePriceMeasurement.perSecond]: '/second',
[NodePriceMeasurement.perMegabyte]: '/MB',
[NodePriceMeasurement.perCount]: '/count',
};

interface LitActionPriceConfig {
priceComponent: bigint;
priceMeasurement: bigint;
price: bigint;
}

/**
* Get LITKEY token price in USD from CoinGecko
*/
async function getLitKeyPrice(): Promise<number> {
try {
// Try to get LIT token price from CoinGecko
// Note: You may need to adjust the token ID if LITKEY is listed differently
const response = await fetch(
'https://api.coingecko.com/api/v3/simple/price?ids=lit-protocol&vs_currencies=usd'
);
Copy link

Copilot AI Nov 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing HTTP response status check. The fetch call should verify the response is successful before parsing JSON. If the API returns a 4xx or 5xx status, response.json() may fail or return an error object instead of the expected data structure. Add if (!response.ok) throw new Error(...) before parsing the JSON.

Suggested change
);
);
if (!response.ok) {
throw new Error(`Failed to fetch LITKEY price: ${response.status} ${response.statusText}`);
}

Copilot uses AI. Check for mistakes.
const data = await response.json();

if (data['lit-protocol'] && data['lit-protocol'].usd) {
return data['lit-protocol'].usd;
}

throw new Error('LIT price not found in CoinGecko response');
} catch (error) {
console.error('Error fetching LITKEY price from CoinGecko:', error);
console.log('Falling back to manual price input...');
// You can set a default price here or throw
throw new Error(
'Unable to fetch LITKEY price. Please check CoinGecko API or set manually.'
Comment on lines +96 to +97
Copy link

Copilot AI Nov 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message references "LITKEY price" but the function attempts to fetch "LIT price" from CoinGecko using the token ID 'lit-protocol'. This inconsistency could lead to confusion. Either update the error message to reflect that it's fetching LIT price (not LITKEY), or clarify the relationship between LIT and LITKEY tokens in the comments.

Copilot uses AI. Check for mistakes.
Comment on lines +78 to +97
Copy link

Copilot AI Nov 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The script relies on external API (CoinGecko) without rate limiting or caching. For a production script that may be run frequently, consider implementing rate limiting, caching the token price for a reasonable duration, or providing an option to manually specify the price to avoid API rate limits or availability issues.

Suggested change
async function getLitKeyPrice(): Promise<number> {
try {
// Try to get LIT token price from CoinGecko
// Note: You may need to adjust the token ID if LITKEY is listed differently
const response = await fetch(
'https://api.coingecko.com/api/v3/simple/price?ids=lit-protocol&vs_currencies=usd'
);
const data = await response.json();
if (data['lit-protocol'] && data['lit-protocol'].usd) {
return data['lit-protocol'].usd;
}
throw new Error('LIT price not found in CoinGecko response');
} catch (error) {
console.error('Error fetching LITKEY price from CoinGecko:', error);
console.log('Falling back to manual price input...');
// You can set a default price here or throw
throw new Error(
'Unable to fetch LITKEY price. Please check CoinGecko API or set manually.'
// In-memory cache for LITKEY price
let cachedLitKeyPrice: number | null = null;
let cachedLitKeyPriceTimestamp: number | null = null;
const CACHE_DURATION_MS = 5 * 60 * 1000; // 5 minutes
/**
* Get LITKEY token price in USD from CoinGecko, with caching and manual override
*/
async function getLitKeyPrice(): Promise<number> {
// 1. Check for manual override via environment variable
const manualPrice = process.env.LITKEY_PRICE_USD;
if (manualPrice !== undefined) {
const priceNum = Number(manualPrice);
if (!isNaN(priceNum) && priceNum > 0) {
return priceNum;
} else {
console.warn('Invalid LITKEY_PRICE_USD env var value, falling back to API.');
}
}
// 2. Check cache
const now = Date.now();
if (
cachedLitKeyPrice !== null &&
cachedLitKeyPriceTimestamp !== null &&
now - cachedLitKeyPriceTimestamp < CACHE_DURATION_MS
) {
return cachedLitKeyPrice;
}
// 3. Fetch from CoinGecko
try {
const response = await fetch(
'https://api.coingecko.com/api/v3/simple/price?ids=lit-protocol&vs_currencies=usd'
);
const data = await response.json();
if (data['lit-protocol'] && data['lit-protocol'].usd) {
cachedLitKeyPrice = data['lit-protocol'].usd;
cachedLitKeyPriceTimestamp = now;
return cachedLitKeyPrice;
}
throw new Error('LIT price not found in CoinGecko response');
} catch (error) {
console.error('Error fetching LITKEY price from CoinGecko:', error);
console.log('Falling back to manual price input...');
throw new Error(
'Unable to fetch LITKEY price. Please check CoinGecko API or set manually via LITKEY_PRICE_USD env var.'

Copilot uses AI. Check for mistakes.
);
}
}

/**
* Get PriceFeed contract address from networkContext.json or use default
*/
function getPriceFeedAddress(): string {
// Naga prod address
Comment on lines +103 to +106
Copy link

Copilot AI Nov 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getPriceFeedAddress function always returns the hardcoded mainnet address regardless of the actual network being used. This could be misleading since the function name and comment suggest it might retrieve the address from networkContext.json. Either implement the network-specific logic or rename the function to getMainnetPriceFeedAddress to be more explicit.

Suggested change
* Get PriceFeed contract address from networkContext.json or use default
*/
function getPriceFeedAddress(): string {
// Naga prod address
* Get the PriceFeed contract address for Lit Chain Mainnet.
*/
function getMainnetPriceFeedAddress(): string {
// Naga prod address (Lit Chain Mainnet)

Copilot uses AI. Check for mistakes.
return NAGA_PROD_PRICE_FEED_ADDRESS;
}

/**
* Convert wei to LITKEY tokens (18 decimals)
*/
function weiToTokens(wei: bigint): number {
return parseFloat(ethers.formatUnits(wei, 18));
}

/**
* Format price for display
*/
function formatPrice(priceInTokens: number, priceInUSD: number): string {
return `${priceInTokens.toFixed(6)} LITKEY ($${priceInUSD.toFixed(6)})`;
}

async function main() {
console.log('=== Calculating Fees in USD ===\n');

// Get network info
const network = await ethers.provider.getNetwork();
console.log(`Network: ${network.name} (Chain ID: ${network.chainId})\n`);

// Get LITKEY price in USD
console.log('Fetching LITKEY token price from CoinGecko...');
const litKeyPriceUSD = await getLitKeyPrice();
console.log(`LITKEY Price: $${litKeyPriceUSD.toFixed(4)} USD\n`);

// Get PriceFeed contract
const priceFeedAddress = getPriceFeedAddress();
console.log(`PriceFeed Contract Address: ${priceFeedAddress}\n`);

// Use PriceFeedDiamond which includes all facets via hardhat-diamond-abi plugin
const priceFeed = await ethers.getContractAt(
'PriceFeedDiamond',
priceFeedAddress
);
Comment on lines +141 to +144
Copy link

Copilot AI Nov 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling for contract interaction failures. If the contract doesn't exist at the specified address, is not deployed on the current network, or if there's an RPC error, the script will crash with an unhelpful error. Consider adding try-catch blocks around contract calls with meaningful error messages.

Copilot uses AI. Check for mistakes.

// Get all product IDs
const productIds = [
ProductId.PkpSign,
ProductId.EncSign,
ProductId.LitAction,
ProductId.SignSessionKey,
];

console.log('=== Network Base Prices ===');
const baseNetworkPrices = await priceFeed.baseNetworkPrices(productIds);
for (let i = 0; i < productIds.length; i++) {
const productId = productIds[i];
const priceInWei = baseNetworkPrices[i];
const priceInTokens = weiToTokens(priceInWei);
const priceInUSD = priceInTokens * litKeyPriceUSD;
console.log(
`${PRODUCT_NAMES[productId]}: ${formatPrice(priceInTokens, priceInUSD)}`
);
}

console.log('\n=== Network Max Prices ===');
const maxNetworkPrices = await priceFeed.maxNetworkPrices(productIds);
for (let i = 0; i < productIds.length; i++) {
const productId = productIds[i];
const priceInWei = maxNetworkPrices[i];
const priceInTokens = weiToTokens(priceInWei);
const priceInUSD = priceInTokens * litKeyPriceUSD;
console.log(
`${PRODUCT_NAMES[productId]}: ${formatPrice(priceInTokens, priceInUSD)}`
);
}

// Get prices at different usage percentages
console.log('\n=== Prices at Different Usage Percentages ===');
const usagePercentages = [0, 25, 50, 75, 100];
for (const usagePercent of usagePercentages) {
console.log(`\nUsage: ${usagePercent}%`);
const prices = await priceFeed.usagePercentToPrices(
usagePercent,
productIds
);
for (let i = 0; i < productIds.length; i++) {
const productId = productIds[i];
const priceInWei = prices[i];
const priceInTokens = weiToTokens(priceInWei);
const priceInUSD = priceInTokens * litKeyPriceUSD;
console.log(
` ${PRODUCT_NAMES[productId]}: ${formatPrice(
priceInTokens,
priceInUSD
)}`
);
}
}

// Get LitAction price configs
console.log('\n=== LitAction Price Components ===');
const litActionPriceConfigs: LitActionPriceConfig[] =
await priceFeed.getLitActionPriceConfigs();

for (const config of litActionPriceConfigs) {
// Convert bigint to number for enum casting
const priceComponentNum = Number(config.priceComponent);
const priceMeasurementNum = Number(config.priceMeasurement);

const componentName =
LIT_ACTION_COMPONENT_NAMES[
priceComponentNum as LitActionPriceComponent
] || `Component ${priceComponentNum}`;
const measurementName =
MEASUREMENT_NAMES[priceMeasurementNum as NodePriceMeasurement] || '';
const priceInTokens = weiToTokens(config.price);
const priceInUSD = priceInTokens * litKeyPriceUSD;
console.log(
`${componentName} ${measurementName}: ${formatPrice(
priceInTokens,
priceInUSD
)}`
);
}

console.log('\n=== Summary ===');
console.log(`LITKEY Token Price: $${litKeyPriceUSD.toFixed(4)} USD`);
console.log(`PriceFeed Contract: ${priceFeedAddress}`);
console.log(`Network: ${network.name} (Chain ID: ${network.chainId})`);
}

main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
Loading