Skip to content

Commit a79e752

Browse files
authored
Merge pull request #925 from LIT-Protocol/feature/jss-118-naga-featbug-add-signsessionkey-product-index-3-for
feat(pricing): add `sign-session-key` pricing support and product-awa…
2 parents 3e63754 + c53626d commit a79e752

File tree

5 files changed

+95
-9
lines changed

5 files changed

+95
-9
lines changed

packages/auth/src/lib/AuthManager/authAdapters/getPkpAuthContextAdapter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,9 @@ export async function getPkpAuthContextAdapter(
147147
const nodeUrls = litClientCtx.getMaxPricesForNodeProduct({
148148
nodePrices: respondingNodePrices,
149149
userMaxPrice: litClientCtx.getUserMaxPrice({
150-
product: 'LIT_ACTION',
150+
product: 'SIGN_SESSION_KEY',
151151
}),
152-
productId: PRODUCT_IDS['LIT_ACTION'],
152+
productId: PRODUCT_IDS['SIGN_SESSION_KEY'],
153153
numRequiredNodes: threshold,
154154
});
155155

packages/networks/src/networks/vNaga/shared/managers/LitChainClient/apis/rawContractApis/pricing/getNodesForRequest.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@ import {
1515
* - DECRYPTION (0): Used for decryption operations
1616
* - SIGN (1): Used for signing operations
1717
* - LA (2): Used for Lit Actions execution
18+
* - SIGN_SESSION_KEY (3): Used for sign session key operations
1819
*/
1920
export const PRODUCT_IDS = {
2021
DECRYPTION: 0n, // For decryption operations
2122
SIGN: 1n, // For signing operations
2223
LIT_ACTION: 2n, // For Lit Actions execution
24+
SIGN_SESSION_KEY: 3n, // For sign session key operations
2325
} as const;
2426

2527
// Schema for the request
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { PRODUCT_IDS } from '@lit-protocol/constants';
2+
3+
import { getMaxPricesForNodeProduct } from './getMaxPricesForNodeProduct';
4+
5+
describe('getMaxPricesForNodeProduct', () => {
6+
it('uses the requested product column when ranking nodes', () => {
7+
const nodePrices = [
8+
{
9+
url: 'https://node-a',
10+
prices: [80n, 5n, 9n, 30n],
11+
},
12+
{
13+
url: 'https://node-b',
14+
prices: [70n, 4n, 8n, 10n],
15+
},
16+
{
17+
url: 'https://node-c',
18+
prices: [60n, 3n, 7n, 20n],
19+
},
20+
];
21+
22+
// Log the incoming order to show the decryption column is already sorted lowest-first.
23+
console.log(
24+
'incoming order',
25+
nodePrices.map(({ url, prices }) => ({
26+
url,
27+
decryptionPrice: prices[PRODUCT_IDS.DECRYPTION],
28+
signPrice: prices[PRODUCT_IDS.SIGN],
29+
litActionPrice: prices[PRODUCT_IDS.LIT_ACTION],
30+
signSessionKeyPrice: prices[PRODUCT_IDS.SIGN_SESSION_KEY],
31+
}))
32+
);
33+
34+
// Call the helper exactly like the SDK does: ask for SIGN_SESSION_KEY prices,
35+
// pass the raw price feed output, and cap the request at two nodes.
36+
const result = getMaxPricesForNodeProduct({
37+
nodePrices,
38+
userMaxPrice: 100n,
39+
productId: PRODUCT_IDS.SIGN_SESSION_KEY,
40+
numRequiredNodes: 2,
41+
});
42+
43+
console.log(
44+
'selected nodes',
45+
result.map(({ url, price }) => ({ url, price }))
46+
);
47+
48+
// After sorting the nodes by the session-key column, the helper should
49+
// return node-b (10) and node-c (20) even though the original array was
50+
// ordered by the decryption price column.
51+
expect(result).toHaveLength(2);
52+
expect(result[0].url).toBe('https://node-b');
53+
expect(result[1].url).toBe('https://node-c');
54+
55+
// Base prices are taken from the SIGN_SESSION_KEY column (10 and 20)
56+
// with the excess (100 - 30 = 70) split evenly.
57+
expect(result[0].price).toBe(10n + 35n);
58+
expect(result[1].price).toBe(20n + 35n);
59+
});
60+
});

packages/networks/src/networks/vNaga/shared/managers/pricing-manager/getMaxPricesForNodeProduct.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ export interface MaxPricesForNodes {
1515
* Ensures the total cost does not exceed userMaxPrice.
1616
* Operates in the order of lowest priced node to highest.
1717
*
18+
* Example:
19+
* - Selected nodes have SIGN_SESSION_KEY prices of 10 and 20.
20+
* - `userMaxPrice` is 100.
21+
* - Base total = 10 + 20 = 30.
22+
* - Excess = 100 - 30 = 70.
23+
* - Each node receives 70 / 2 = 35 extra budget, yielding 45 and 55.
24+
*
1825
* @param nodePrices - An object where keys are node addresses and values are arrays of prices for different action types.
1926
* @param userMaxPrice - The maximum price the user is willing to pay to execute the request.
2027
* @param productId - The ID of the product to determine which price to consider.
@@ -28,19 +35,33 @@ export function getMaxPricesForNodeProduct({
2835
productId,
2936
numRequiredNodes,
3037
}: MaxPricesForNodes): { url: string; price: bigint }[] {
38+
// Always evaluate pricing using the product-specific column so we truly pick
39+
// the cheapest validators for that product (the upstream feed is sorted by
40+
// prices[0]/decryption price only).
41+
const sortedNodes = [...nodePrices].sort((a, b) => {
42+
const priceA = a.prices[productId];
43+
const priceB = b.prices[productId];
44+
45+
if (priceA === priceB) {
46+
return 0;
47+
}
48+
49+
return priceA < priceB ? -1 : 1;
50+
});
51+
3152
// If we don't need all nodes to service the request, only use the cheapest `n` of them
3253
const nodesToConsider = numRequiredNodes
33-
? nodePrices.slice(0, numRequiredNodes)
34-
: nodePrices;
54+
? sortedNodes.slice(0, numRequiredNodes)
55+
: sortedNodes;
3556

57+
// Sum the unadjusted cost for the nodes we plan to use.
3658
let totalBaseCost = 0n;
37-
38-
// Calculate the base total cost without adjustments
3959
for (const { prices } of nodesToConsider) {
60+
// Example: base total accumulates 10 + 20 = 30 for the two cheapest nodes.
4061
totalBaseCost += prices[productId];
4162
}
4263

43-
// Verify that we have a high enough userMaxPrice to fulfill the request
64+
// Refuse to proceed if the caller's budget cannot even cover the base cost.
4465
if (totalBaseCost > userMaxPrice) {
4566
throw new MaxPriceTooLow(
4667
{
@@ -58,13 +79,16 @@ export function getMaxPricesForNodeProduct({
5879
* our request to fail if the price on some of the nodes is higher than we think it was, as long as it's not
5980
* drastically different than we expect it to be
6081
*/
82+
// Any remaining budget is spread across the participating nodes to
83+
// provide cushion for minor pricing fluctuations. Example: 100 - 30 = 70.
6184
const excessBalance = userMaxPrice - totalBaseCost;
6285

6386
// Map matching the keys from `nodePrices`, but w/ the per-node maxPrice computed based on `userMaxPrice`
6487
const maxPricesPerNode: { url: string; price: bigint }[] = [];
6588

6689
for (const { url, prices } of nodesToConsider) {
67-
// For now, we'll distribute the remaining balance equally across nodes
90+
// Distribute the remaining budget evenly across nodes to form the max price.
91+
// Example: each node receives 70 / 2 = 35, becoming 10+35 and 20+35.
6892
maxPricesPerNode.push({
6993
url,
7094
price: excessBalance

packages/networks/src/networks/vNaga/shared/managers/pricing-manager/schema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { PRODUCT_ID_VALUES } from '@lit-protocol/constants';
55

66
export const PricingContextSchema = z
77
.object({
8-
product: z.enum(['DECRYPTION', 'SIGN', 'LIT_ACTION']),
8+
product: z.enum(['DECRYPTION', 'SIGN', 'LIT_ACTION', 'SIGN_SESSION_KEY']),
99
userMaxPrice: z.bigint().optional(),
1010
nodePrices: z.array(
1111
z.object({ url: z.string(), prices: z.array(z.bigint()) })

0 commit comments

Comments
 (0)