@elisym/sdk 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -3,6 +3,7 @@
3
3
  var system = require('@solana-program/system');
4
4
  var kit = require('@solana/kit');
5
5
  var Decimal2 = require('decimal.js-light');
6
+ var zod = require('zod');
6
7
  var nostrTools = require('nostr-tools');
7
8
  var nip44 = require('nostr-tools/nip44');
8
9
 
@@ -215,7 +216,118 @@ function assertExpiry(createdAt, expirySecs) {
215
216
  }
216
217
  }
217
218
 
219
+ // src/payment/priorityFee.ts
220
+ var PRIORITY_FEE_FLOOR_MICROLAMPORTS = 1000n;
221
+ var DEFAULT_PERCENTILE = 75;
222
+ var DEFAULT_CACHE_TTL_MS = 1e4;
223
+ var cache2 = /* @__PURE__ */ new Map();
224
+ async function estimatePriorityFeeMicroLamports(rpc, options) {
225
+ const percentile = clampPercentile(options?.percentile ?? DEFAULT_PERCENTILE);
226
+ const ttl = options?.ttlMs ?? DEFAULT_CACHE_TTL_MS;
227
+ const accounts = options?.accounts ?? [];
228
+ const key = cacheKey(percentile, accounts);
229
+ const now = Date.now();
230
+ const cached = cache2.get(key);
231
+ if (cached && now < cached.expires) {
232
+ return cached.microLamports;
233
+ }
234
+ const samples = await rpc.getRecentPrioritizationFees(accounts).send();
235
+ const fee = pickPercentileFee(samples, percentile);
236
+ cache2.set(key, { microLamports: fee, expires: now + ttl });
237
+ return fee;
238
+ }
239
+ function clearPriorityFeeCache() {
240
+ cache2.clear();
241
+ }
242
+ function pickPercentileFee(samples, percentile) {
243
+ if (samples.length === 0) {
244
+ return PRIORITY_FEE_FLOOR_MICROLAMPORTS;
245
+ }
246
+ const sorted = samples.map((sample) => BigInt(sample.prioritizationFee)).sort(compareBigInt);
247
+ const clamped = clampPercentile(percentile);
248
+ const indexFloat = clamped / 100 * (sorted.length - 1) | 0;
249
+ const value = sorted[indexFloat];
250
+ return value > PRIORITY_FEE_FLOOR_MICROLAMPORTS ? value : PRIORITY_FEE_FLOOR_MICROLAMPORTS;
251
+ }
252
+ function clampPercentile(value) {
253
+ if (!Number.isFinite(value)) {
254
+ return DEFAULT_PERCENTILE;
255
+ }
256
+ if (value < 0) {
257
+ return 0;
258
+ }
259
+ if (value > 100) {
260
+ return 100;
261
+ }
262
+ return value;
263
+ }
264
+ function compareBigInt(left, right) {
265
+ if (left < right) {
266
+ return -1;
267
+ }
268
+ if (left > right) {
269
+ return 1;
270
+ }
271
+ return 0;
272
+ }
273
+ function cacheKey(percentile, accounts) {
274
+ if (accounts.length === 0) {
275
+ return `p:${percentile}`;
276
+ }
277
+ return `p:${percentile}:${[...accounts].sort().join(",")}`;
278
+ }
279
+ var MAX_DESCRIPTION_LENGTH = LIMITS.MAX_DESCRIPTION_LENGTH;
280
+ var MAX_SAFE_LAMPORTS = Number.MAX_SAFE_INTEGER;
281
+ var MAX_EXPIRY_SECS_SCHEMA = 86400;
282
+ var BASE58_RE = /^[1-9A-HJ-NP-Za-km-z]+$/;
283
+ var SOLANA_ADDRESS_LENGTH_RE = /^.{32,44}$/;
284
+ var lamportsSchema = zod.z.number().int().positive().max(MAX_SAFE_LAMPORTS, `amount must be <= ${MAX_SAFE_LAMPORTS}`);
285
+ var feeAmountSchema = zod.z.number().int().nonnegative().max(MAX_SAFE_LAMPORTS, `fee_amount must be <= ${MAX_SAFE_LAMPORTS}`);
286
+ var solanaAddressSchema = zod.z.string().regex(BASE58_RE, "must be base58").regex(SOLANA_ADDRESS_LENGTH_RE, "must be 32-44 base58 chars");
287
+ var PaymentRequestSchema = zod.z.object({
288
+ recipient: solanaAddressSchema,
289
+ amount: lamportsSchema,
290
+ reference: solanaAddressSchema,
291
+ description: zod.z.string().max(MAX_DESCRIPTION_LENGTH).optional(),
292
+ fee_address: solanaAddressSchema.optional(),
293
+ fee_amount: feeAmountSchema.optional(),
294
+ created_at: zod.z.number().int().positive(),
295
+ expiry_secs: zod.z.number().int().positive().max(MAX_EXPIRY_SECS_SCHEMA, `expiry_secs must be <= ${MAX_EXPIRY_SECS_SCHEMA}`)
296
+ });
297
+ function parsePaymentRequest(input, options) {
298
+ let parsed;
299
+ try {
300
+ parsed = JSON.parse(input);
301
+ } catch (e) {
302
+ return {
303
+ ok: false,
304
+ error: { code: "invalid_json", message: `Invalid payment request JSON: ${e}` }
305
+ };
306
+ }
307
+ const result = PaymentRequestSchema.safeParse(parsed);
308
+ if (!result.success) {
309
+ return {
310
+ ok: false,
311
+ error: { code: "schema", message: result.error.message }
312
+ };
313
+ }
314
+ if (options?.maxAmountLamports !== void 0) {
315
+ if (BigInt(result.data.amount) > options.maxAmountLamports) {
316
+ return {
317
+ ok: false,
318
+ error: {
319
+ code: "amount_exceeds_max",
320
+ message: `Payment amount ${result.data.amount} lamports exceeds approved max ${options.maxAmountLamports}.`
321
+ }
322
+ };
323
+ }
324
+ }
325
+ return { ok: true, data: result.data };
326
+ }
327
+
218
328
  // src/payment/solana.ts
329
+ var DEFAULT_COMPUTE_UNIT_LIMIT = 2e5;
330
+ var DEFAULT_PRIORITY_FEE_PERCENTILE = 75;
219
331
  var REFERENCE_BYTE_LENGTH = 32;
220
332
  function isValidSolanaAddress(value) {
221
333
  return kit.isAddress(value);
@@ -272,32 +384,27 @@ var SolanaPaymentStrategy = class {
272
384
  expiry_secs: expirySecs
273
385
  };
274
386
  }
275
- validatePaymentRequest(requestJson, config, expectedRecipient) {
387
+ validatePaymentRequest(requestJson, config, expectedRecipient, options) {
276
388
  assertConfig(config);
277
- let data;
278
- try {
279
- data = JSON.parse(requestJson);
280
- } catch (e) {
281
- return { code: "invalid_json", message: `Invalid payment request JSON: ${e}` };
282
- }
283
- if (typeof data.amount !== "number" || !Number.isInteger(data.amount) || data.amount <= 0) {
284
- return {
285
- code: "invalid_amount",
286
- message: `Invalid amount in payment request: ${data.amount}`
287
- };
288
- }
289
- if (typeof data.recipient !== "string" || !data.recipient) {
290
- return { code: "missing_recipient", message: "Missing recipient in payment request" };
389
+ const parsed = parsePaymentRequest(requestJson, {
390
+ maxAmountLamports: options?.maxAmountLamports
391
+ });
392
+ if (!parsed.ok) {
393
+ if (parsed.error.code === "invalid_json") {
394
+ return { code: "invalid_json", message: parsed.error.message };
395
+ }
396
+ if (parsed.error.code === "amount_exceeds_max") {
397
+ return { code: "invalid_amount", message: parsed.error.message };
398
+ }
399
+ return { code: "invalid_amount", message: parsed.error.message };
291
400
  }
401
+ const data = parsed.data;
292
402
  if (!isValidSolanaAddress(data.recipient)) {
293
403
  return {
294
404
  code: "invalid_recipient_address",
295
405
  message: `Invalid Solana address for recipient: ${data.recipient}`
296
406
  };
297
407
  }
298
- if (typeof data.reference !== "string" || !data.reference) {
299
- return { code: "missing_reference", message: "Missing reference in payment request" };
300
- }
301
408
  if (!isValidSolanaAddress(data.reference)) {
302
409
  return {
303
410
  code: "invalid_reference_address",
@@ -357,7 +464,7 @@ var SolanaPaymentStrategy = class {
357
464
  * read-only, non-signer account so providers can detect the payment via
358
465
  * `getSignaturesForAddress(reference)`.
359
466
  */
360
- async buildTransaction(paymentRequest, payerSigner, rpc, config) {
467
+ async buildTransaction(paymentRequest, payerSigner, rpc, config, options) {
361
468
  assertConfig(config);
362
469
  assertLamports(paymentRequest.amount, "payment amount");
363
470
  if (paymentRequest.amount === 0) {
@@ -376,14 +483,23 @@ var SolanaPaymentStrategy = class {
376
483
  `Invalid fee address: expected ${treasury}, got ${paymentRequest.fee_address}. Cannot build transaction with redirected fees.`
377
484
  );
378
485
  }
379
- const instructions = buildPaymentInstructions(paymentRequest, payerSigner);
486
+ const computeUnitLimit = options?.computeUnitLimit ?? DEFAULT_COMPUTE_UNIT_LIMIT;
487
+ if (!Number.isInteger(computeUnitLimit) || computeUnitLimit <= 0) {
488
+ throw new Error(`Invalid computeUnitLimit: ${computeUnitLimit}. Must be a positive integer.`);
489
+ }
490
+ const paymentInstructions = buildPaymentInstructions(paymentRequest, payerSigner);
491
+ const priorityFeeMicroLamports = options?.priorityFeeMicroLamports ?? await estimatePriorityFeeMicroLamports(rpc, {
492
+ percentile: options?.priorityFeePercentile ?? DEFAULT_PRIORITY_FEE_PERCENTILE
493
+ });
380
494
  const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
381
495
  const message = kit.pipe(
382
496
  kit.createTransactionMessage({ version: 0 }),
383
497
  (m) => kit.setTransactionMessageFeePayerSigner(payerSigner, m),
384
498
  (m) => kit.setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m),
499
+ (m) => kit.setTransactionMessageComputeUnitLimit(computeUnitLimit, m),
500
+ (m) => kit.setTransactionMessageComputeUnitPrice(priorityFeeMicroLamports, m),
385
501
  (m) => kit.appendTransactionMessageInstructions(
386
- instructions,
502
+ paymentInstructions,
387
503
  m
388
504
  )
389
505
  );
@@ -2295,9 +2411,6 @@ function validateAgentName(name) {
2295
2411
  throw new Error("Agent name must be 1-64 characters, alphanumeric, underscore, or hyphen.");
2296
2412
  }
2297
2413
  }
2298
- function serializeConfig(config) {
2299
- return JSON.stringify(config, null, 2) + "\n";
2300
- }
2301
2414
 
2302
2415
  exports.BoundedSet = BoundedSet;
2303
2416
  exports.DEFAULTS = DEFAULTS;
@@ -2321,6 +2434,7 @@ exports.NostrPool = NostrPool;
2321
2434
  exports.PROTOCOL_FEE_BPS = PROTOCOL_FEE_BPS;
2322
2435
  exports.PROTOCOL_PROGRAM_ID_DEVNET = PROTOCOL_PROGRAM_ID_DEVNET;
2323
2436
  exports.PROTOCOL_TREASURY = PROTOCOL_TREASURY;
2437
+ exports.PaymentRequestSchema = PaymentRequestSchema;
2324
2438
  exports.PingService = PingService;
2325
2439
  exports.RELAYS = RELAYS;
2326
2440
  exports.SolanaPaymentStrategy = SolanaPaymentStrategy;
@@ -2328,8 +2442,10 @@ exports.assertExpiry = assertExpiry;
2328
2442
  exports.assertLamports = assertLamports;
2329
2443
  exports.buildPaymentInstructions = buildPaymentInstructions;
2330
2444
  exports.calculateProtocolFee = calculateProtocolFee;
2445
+ exports.clearPriorityFeeCache = clearPriorityFeeCache;
2331
2446
  exports.clearProtocolConfigCache = clearProtocolConfigCache;
2332
2447
  exports.createPaymentRequestWithOnchainConfig = createPaymentRequestWithOnchainConfig;
2448
+ exports.estimatePriorityFeeMicroLamports = estimatePriorityFeeMicroLamports;
2333
2449
  exports.formatSol = formatSol;
2334
2450
  exports.getProtocolConfig = getProtocolConfig;
2335
2451
  exports.getProtocolProgramId = getProtocolProgramId;
@@ -2337,7 +2453,8 @@ exports.jobRequestKind = jobRequestKind;
2337
2453
  exports.jobResultKind = jobResultKind;
2338
2454
  exports.nip44Decrypt = nip44Decrypt;
2339
2455
  exports.nip44Encrypt = nip44Encrypt;
2340
- exports.serializeConfig = serializeConfig;
2456
+ exports.parsePaymentRequest = parsePaymentRequest;
2457
+ exports.pickPercentileFee = pickPercentileFee;
2341
2458
  exports.timeAgo = timeAgo;
2342
2459
  exports.toDTag = toDTag;
2343
2460
  exports.truncateKey = truncateKey;