@elisym/sdk 0.5.0 → 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 +142 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +155 -9
- package/dist/index.d.ts +155 -9
- package/dist/index.js +139 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
code: "invalid_amount",
|
|
286
|
-
|
|
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
|
|
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
|
-
|
|
502
|
+
paymentInstructions,
|
|
387
503
|
m
|
|
388
504
|
)
|
|
389
505
|
);
|
|
@@ -2318,6 +2434,7 @@ exports.NostrPool = NostrPool;
|
|
|
2318
2434
|
exports.PROTOCOL_FEE_BPS = PROTOCOL_FEE_BPS;
|
|
2319
2435
|
exports.PROTOCOL_PROGRAM_ID_DEVNET = PROTOCOL_PROGRAM_ID_DEVNET;
|
|
2320
2436
|
exports.PROTOCOL_TREASURY = PROTOCOL_TREASURY;
|
|
2437
|
+
exports.PaymentRequestSchema = PaymentRequestSchema;
|
|
2321
2438
|
exports.PingService = PingService;
|
|
2322
2439
|
exports.RELAYS = RELAYS;
|
|
2323
2440
|
exports.SolanaPaymentStrategy = SolanaPaymentStrategy;
|
|
@@ -2325,8 +2442,10 @@ exports.assertExpiry = assertExpiry;
|
|
|
2325
2442
|
exports.assertLamports = assertLamports;
|
|
2326
2443
|
exports.buildPaymentInstructions = buildPaymentInstructions;
|
|
2327
2444
|
exports.calculateProtocolFee = calculateProtocolFee;
|
|
2445
|
+
exports.clearPriorityFeeCache = clearPriorityFeeCache;
|
|
2328
2446
|
exports.clearProtocolConfigCache = clearProtocolConfigCache;
|
|
2329
2447
|
exports.createPaymentRequestWithOnchainConfig = createPaymentRequestWithOnchainConfig;
|
|
2448
|
+
exports.estimatePriorityFeeMicroLamports = estimatePriorityFeeMicroLamports;
|
|
2330
2449
|
exports.formatSol = formatSol;
|
|
2331
2450
|
exports.getProtocolConfig = getProtocolConfig;
|
|
2332
2451
|
exports.getProtocolProgramId = getProtocolProgramId;
|
|
@@ -2334,6 +2453,8 @@ exports.jobRequestKind = jobRequestKind;
|
|
|
2334
2453
|
exports.jobResultKind = jobResultKind;
|
|
2335
2454
|
exports.nip44Decrypt = nip44Decrypt;
|
|
2336
2455
|
exports.nip44Encrypt = nip44Encrypt;
|
|
2456
|
+
exports.parsePaymentRequest = parsePaymentRequest;
|
|
2457
|
+
exports.pickPercentileFee = pickPercentileFee;
|
|
2337
2458
|
exports.timeAgo = timeAgo;
|
|
2338
2459
|
exports.toDTag = toDTag;
|
|
2339
2460
|
exports.truncateKey = truncateKey;
|