@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.d.ts CHANGED
@@ -1,8 +1,157 @@
1
1
  import { Address, TransactionSigner, Rpc, SolanaRpcApi } from '@solana/kit';
2
- import { P as PaymentRequestData, a as PaymentValidationError, V as VerifyOptions, b as VerifyResult, S as SubCloser, N as Network, A as Agent, E as ElisymIdentity, C as CapabilityCard, c as SubmitJobOptions, J as JobSubscriptionOptions, d as Job, e as PingResult, f as ElisymClientConfig, g as AgentConfig } from './types-CII4k_8d.js';
3
- export { h as Capability, I as Identity, i as JobStatus, j as JobUpdateCallbacks, L as LlmConfig, k as NetworkStats, l as PaymentAddress, m as PaymentInfo, n as PaymentValidationCode, W as WalletConfig } from './types-CII4k_8d.js';
4
2
  import { Filter, Event } from 'nostr-tools';
3
+ import { z } from 'zod';
4
+
5
+ declare class ElisymIdentity {
6
+ private _secretKey;
7
+ readonly publicKey: string;
8
+ readonly npub: string;
9
+ get secretKey(): Uint8Array;
10
+ private constructor();
11
+ static generate(): ElisymIdentity;
12
+ static fromSecretKey(sk: Uint8Array): ElisymIdentity;
13
+ toJSON(): {
14
+ publicKey: string;
15
+ npub: string;
16
+ };
17
+ /** Best-effort scrub of the secret key bytes in memory. */
18
+ scrub(): void;
19
+ static fromHex(hex: string): ElisymIdentity;
20
+ }
21
+
22
+ interface SubCloser {
23
+ close: (reason?: string) => void;
24
+ }
25
+ /** Capability card published to Nostr (NIP-89). */
26
+ interface CapabilityCard {
27
+ name: string;
28
+ description: string;
29
+ capabilities: string[];
30
+ payment?: PaymentInfo;
31
+ image?: string;
32
+ static?: boolean;
33
+ }
34
+ /** Payment info embedded in capability card (legacy format for on-network events). */
35
+ interface PaymentInfo {
36
+ chain: string;
37
+ network: string;
38
+ address: string;
39
+ /** Price in lamports (must be non-negative integer). */
40
+ job_price?: number;
41
+ }
42
+ /** Agent discovered from the network. */
43
+ interface Agent {
44
+ pubkey: string;
45
+ npub: string;
46
+ cards: CapabilityCard[];
47
+ eventId: string;
48
+ supportedKinds: number[];
49
+ lastSeen: number;
50
+ picture?: string;
51
+ name?: string;
52
+ about?: string;
53
+ }
54
+ type Network = 'mainnet' | 'devnet';
55
+ /**
56
+ * Job lifecycle status.
57
+ * Note: for broadcast jobs (no providerPubkey, no bid), fetchRecentJobs() keeps
58
+ * status as 'processing' even if a provider sent 'payment-required' feedback,
59
+ * because the customer hasn't committed to that provider yet. The real-time
60
+ * payment-required transition is handled by subscribeToJobUpdates().
61
+ */
62
+ type JobStatus = 'payment-required' | 'payment-completed' | 'processing' | 'error' | 'success' | 'partial' | 'unknown';
63
+ interface Job {
64
+ eventId: string;
65
+ customer: string;
66
+ agentPubkey?: string;
67
+ capability?: string;
68
+ bid?: number;
69
+ status: JobStatus;
70
+ result?: string;
71
+ resultEventId?: string;
72
+ amount?: number;
73
+ txHash?: string;
74
+ createdAt: number;
75
+ }
76
+ interface SubmitJobOptions {
77
+ /** Job input text. Sent unencrypted if providerPubkey is not set. */
78
+ input: string;
79
+ capability: string;
80
+ /** Target provider pubkey. If omitted, job is broadcast unencrypted and visible to all relays. */
81
+ providerPubkey?: string;
82
+ /** Kind offset (default 100 - kind 5100). */
83
+ kindOffset?: number;
84
+ }
85
+ interface JobUpdateCallbacks {
86
+ onFeedback?: (status: string, amount?: number, paymentRequest?: string, senderPubkey?: string) => void;
87
+ onResult?: (content: string, eventId: string) => void;
88
+ onError?: (error: string) => void;
89
+ }
90
+ interface JobSubscriptionOptions {
91
+ jobEventId: string;
92
+ providerPubkey?: string;
93
+ customerPublicKey: string;
94
+ callbacks: JobUpdateCallbacks;
95
+ timeoutMs?: number;
96
+ customerSecretKey?: Uint8Array;
97
+ kindOffsets?: number[];
98
+ sinceOverride?: number;
99
+ }
100
+ interface PingResult {
101
+ online: boolean;
102
+ /** The identity used for the ping session - reuse for job submission so pubkeys match. */
103
+ identity: ElisymIdentity | null;
104
+ }
105
+ interface PaymentRequestData {
106
+ recipient: string;
107
+ /** Total amount in lamports (must be positive integer). */
108
+ amount: number;
109
+ reference: string;
110
+ description?: string;
111
+ fee_address?: string;
112
+ fee_amount?: number;
113
+ /** Creation timestamp (Unix seconds). */
114
+ created_at: number;
115
+ /** Expiry duration in seconds. */
116
+ expiry_secs: number;
117
+ }
118
+ interface VerifyResult {
119
+ verified: boolean;
120
+ txSignature?: string;
121
+ error?: string;
122
+ }
123
+ interface VerifyOptions {
124
+ retries?: number;
125
+ intervalMs?: number;
126
+ txSignature?: string;
127
+ }
128
+ type PaymentValidationCode = 'invalid_json' | 'invalid_amount' | 'missing_recipient' | 'invalid_recipient_address' | 'missing_reference' | 'invalid_reference_address' | 'recipient_mismatch' | 'expired' | 'future_timestamp' | 'fee_address_mismatch' | 'fee_amount_mismatch' | 'missing_fee' | 'invalid_fee_params';
129
+ interface PaymentValidationError {
130
+ code: PaymentValidationCode;
131
+ message: string;
132
+ }
133
+ interface NetworkStats {
134
+ totalAgentCount: number;
135
+ agentCount: number;
136
+ jobCount: number;
137
+ totalLamports: number;
138
+ }
139
+ interface ElisymClientConfig {
140
+ relays?: string[];
141
+ }
5
142
 
143
+ /**
144
+ * Pluggable signer used by `PaymentStrategy.buildTransaction`.
145
+ *
146
+ * Aliased to Solana Kit's `TransactionSigner` so callers can pass a hot
147
+ * `KeyPairSigner`, an external KMS-backed signer, a hardware-wallet adapter,
148
+ * or any other implementation that conforms to the Solana Kit signer contract
149
+ * (TransactionPartialSigner / TransactionSendingSigner / TransactionModifyingSigner).
150
+ *
151
+ * Exposing the alias here lets downstream packages depend on `@elisym/sdk`'s
152
+ * abstraction instead of importing Kit directly when wiring custom signers.
153
+ */
154
+ type Signer = TransactionSigner;
6
155
  /**
7
156
  * Protocol fee + treasury inputs for building a payment request.
8
157
  *
@@ -37,18 +186,54 @@ interface PaymentStrategy {
37
186
  * Validate that a payment request has the correct recipient and protocol fee.
38
187
  * Returns a typed validation error if invalid, null if OK.
39
188
  */
40
- validatePaymentRequest(requestJson: string, config: ProtocolConfigInput, expectedRecipient?: string): PaymentValidationError | null;
189
+ validatePaymentRequest(requestJson: string, config: ProtocolConfigInput, expectedRecipient?: string, options?: {
190
+ maxAmountLamports?: bigint;
191
+ }): PaymentValidationError | null;
41
192
  /**
42
- * Build and sign a transaction from a payment request using a TransactionSigner.
43
- * Returns a chain-specific signed transaction value. The caller is responsible
44
- * for sending it (e.g. via `rpc.sendTransaction(...).send()`).
193
+ * Build and sign a transaction from a payment request using a `Signer`.
194
+ *
195
+ * The `Signer` parameter is intentionally the abstract interface, not a
196
+ * concrete `KeyPairSigner`, so callers can plug in external signers
197
+ * (KMS, hardware wallet, ElizaOS approval Action) without holding the
198
+ * raw secret key in process memory.
199
+ *
200
+ * Returns a chain-specific signed transaction value. The caller is
201
+ * responsible for sending it (e.g. via `rpc.sendTransaction(...).send()`).
45
202
  */
46
- buildTransaction(paymentRequest: PaymentRequestData, payerSigner: TransactionSigner, rpc: Rpc<SolanaRpcApi>, config: ProtocolConfigInput): Promise<unknown>;
203
+ buildTransaction(paymentRequest: PaymentRequestData, payerSigner: Signer, rpc: Rpc<SolanaRpcApi>, config: ProtocolConfigInput, options?: BuildTransactionOptions): Promise<unknown>;
47
204
  /**
48
205
  * Verify a payment on-chain.
49
206
  */
50
207
  verifyPayment(rpc: Rpc<SolanaRpcApi>, paymentRequest: PaymentRequestData, config: ProtocolConfigInput, options?: VerifyOptions): Promise<VerifyResult>;
51
208
  }
209
+ /**
210
+ * Optional knobs for `PaymentStrategy.buildTransaction`.
211
+ *
212
+ * Defaults are chosen for typical Solana mainnet conditions; override these
213
+ * when the caller knows peak fees are elevated, when running against a
214
+ * private cluster with no priority-fee samples, or when bundling multiple
215
+ * payment instructions.
216
+ */
217
+ interface BuildTransactionOptions {
218
+ /**
219
+ * Compute-unit limit attached to the transaction. Defaults to 200 000 -
220
+ * comfortable headroom for two SystemProgram transfers + a few extra ops.
221
+ */
222
+ computeUnitLimit?: number;
223
+ /**
224
+ * Per-CU priority-fee override in microLamports. When omitted, the
225
+ * strategy queries `getRecentPrioritizationFees`, sorts by percentile, and
226
+ * uses that value (cached for 10s). Pass an explicit value to skip the
227
+ * RPC call or override the percentile heuristic during traffic spikes.
228
+ */
229
+ priorityFeeMicroLamports?: bigint;
230
+ /**
231
+ * Percentile of the recent priority-fee distribution to charge when
232
+ * `priorityFeeMicroLamports` is not supplied. 50 = median, 75 = upper
233
+ * quartile (default), 90 = aggressive.
234
+ */
235
+ priorityFeePercentile?: number;
236
+ }
52
237
 
53
238
  declare class NostrPool {
54
239
  private pool;
@@ -288,7 +473,9 @@ declare class SolanaPaymentStrategy implements PaymentStrategy {
288
473
  createPaymentRequest(recipientAddress: string, amount: number, config: ProtocolConfigInput, options?: {
289
474
  expirySecs?: number;
290
475
  }): PaymentRequestData;
291
- validatePaymentRequest(requestJson: string, config: ProtocolConfigInput, expectedRecipient?: string): PaymentValidationError | null;
476
+ validatePaymentRequest(requestJson: string, config: ProtocolConfigInput, expectedRecipient?: string, options?: {
477
+ maxAmountLamports?: bigint;
478
+ }): PaymentValidationError | null;
292
479
  /**
293
480
  * Build, sign, and return a transaction for the supplied payment request.
294
481
  * The caller is responsible for sending it (e.g. via `rpc.sendTransaction`).
@@ -297,7 +484,7 @@ declare class SolanaPaymentStrategy implements PaymentStrategy {
297
484
  * read-only, non-signer account so providers can detect the payment via
298
485
  * `getSignaturesForAddress(reference)`.
299
486
  */
300
- buildTransaction(paymentRequest: PaymentRequestData, payerSigner: TransactionSigner, rpc: Rpc<SolanaRpcApi>, config: ProtocolConfigInput): Promise<Readonly<unknown>>;
487
+ buildTransaction(paymentRequest: PaymentRequestData, payerSigner: Signer, rpc: Rpc<SolanaRpcApi>, config: ProtocolConfigInput, options?: BuildTransactionOptions): Promise<Readonly<unknown>>;
301
488
  verifyPayment(rpc: Rpc<SolanaRpcApi>, paymentRequest: PaymentRequestData, config: ProtocolConfigInput, options?: VerifyOptions): Promise<VerifyResult>;
302
489
  private _verifyBySignature;
303
490
  private _verifyByReference;
@@ -312,7 +499,7 @@ declare class SolanaPaymentStrategy implements PaymentStrategy {
312
499
  * Caller is responsible for validating `paymentRequest` upstream;
313
500
  * `buildTransaction` already does that before invoking this helper.
314
501
  */
315
- declare function buildPaymentInstructions(paymentRequest: PaymentRequestData, payerSigner: TransactionSigner): readonly unknown[];
502
+ declare function buildPaymentInstructions(paymentRequest: PaymentRequestData, payerSigner: Signer): readonly unknown[];
316
503
  /**
317
504
  * Convenience wrapper: fetch the on-chain protocol config first, then build a
318
505
  * payment request using its current fee/treasury values.
@@ -342,6 +529,101 @@ declare function validateExpiry(createdAt: number, expirySecs: number): string |
342
529
  /** Assert that payment request timestamps are valid and not expired. Throws on failure. */
343
530
  declare function assertExpiry(createdAt: number, expirySecs: number): void;
344
531
 
532
+ interface EstimatePriorityFeeOptions {
533
+ /**
534
+ * Percentile of the recent prioritization-fee distribution to charge.
535
+ * 50 = median, 75 = upper quartile, 90 = aggressive. Defaults to 75.
536
+ */
537
+ percentile?: number;
538
+ /**
539
+ * Cache window in milliseconds. Subsequent calls within this window with the
540
+ * same accounts will return the cached value. Defaults to 10s.
541
+ */
542
+ ttlMs?: number;
543
+ /**
544
+ * Optional account list passed to `getRecentPrioritizationFees` so the node
545
+ * returns fees observed on writes touching these accounts. Empty = global.
546
+ */
547
+ accounts?: readonly Address[];
548
+ }
549
+ /**
550
+ * Estimate a per-compute-unit priority fee from recent blocks, in
551
+ * microLamports (1 microLamport = 0.000001 Lamports).
552
+ *
553
+ * Falls back to a 1000 microLamport floor when the RPC returns no samples
554
+ * (typical on private clusters or under maintenance). Negative percentiles
555
+ * are clamped to the median.
556
+ *
557
+ * Cached per accounts-key for `ttlMs` (default 10s) using the same
558
+ * in-process cache pattern as `getProtocolConfig`.
559
+ */
560
+ declare function estimatePriorityFeeMicroLamports(rpc: Rpc<SolanaRpcApi>, options?: EstimatePriorityFeeOptions): Promise<bigint>;
561
+ declare function clearPriorityFeeCache(): void;
562
+ interface RecentPrioritizationFeeLike {
563
+ prioritizationFee: bigint | number;
564
+ slot?: bigint | number;
565
+ }
566
+ declare function pickPercentileFee(samples: readonly RecentPrioritizationFeeLike[], percentile: number): bigint;
567
+
568
+ /**
569
+ * Wire-shape for a NIP-90 payment_request blob, as parsed via JSON.parse.
570
+ *
571
+ * Stricter than the loose TypeScript interface: rejects negative amounts,
572
+ * floats, NaN/Infinity, mistyped recipient/reference, and any expiry
573
+ * outside `[1, LIMITS.MAX_TIMEOUT_SECS]`. The strategy applies semantic
574
+ * checks (recipient match, fee amount, expiry-vs-now) on top of this.
575
+ */
576
+ declare const PaymentRequestSchema: z.ZodObject<{
577
+ recipient: z.ZodString;
578
+ amount: z.ZodNumber;
579
+ reference: z.ZodString;
580
+ description: z.ZodOptional<z.ZodString>;
581
+ fee_address: z.ZodOptional<z.ZodString>;
582
+ fee_amount: z.ZodOptional<z.ZodNumber>;
583
+ created_at: z.ZodNumber;
584
+ expiry_secs: z.ZodNumber;
585
+ }, "strip", z.ZodTypeAny, {
586
+ recipient: string;
587
+ amount: number;
588
+ reference: string;
589
+ created_at: number;
590
+ expiry_secs: number;
591
+ description?: string | undefined;
592
+ fee_address?: string | undefined;
593
+ fee_amount?: number | undefined;
594
+ }, {
595
+ recipient: string;
596
+ amount: number;
597
+ reference: string;
598
+ created_at: number;
599
+ expiry_secs: number;
600
+ description?: string | undefined;
601
+ fee_address?: string | undefined;
602
+ fee_amount?: number | undefined;
603
+ }>;
604
+ type ParsedPaymentRequest = z.infer<typeof PaymentRequestSchema>;
605
+ interface ParseOptions {
606
+ /** Optional max amount cap (lamports). Rejects requests that exceed it. */
607
+ maxAmountLamports?: bigint;
608
+ }
609
+ interface ParseError {
610
+ code: 'invalid_json' | 'schema' | 'amount_exceeds_max';
611
+ message: string;
612
+ }
613
+ type ParseResult = {
614
+ ok: true;
615
+ data: ParsedPaymentRequest;
616
+ } | {
617
+ ok: false;
618
+ error: ParseError;
619
+ };
620
+ /**
621
+ * Parse a JSON-encoded payment request through the Zod schema, optionally
622
+ * enforcing a `maxAmountLamports` ceiling supplied by the caller (e.g. the
623
+ * customer's per-job spending cap).
624
+ */
625
+ declare function parsePaymentRequest(input: string, options?: ParseOptions): ParseResult;
626
+
345
627
  /**
346
628
  * Snapshot of the on-chain elisym-config program state.
347
629
  *
@@ -386,14 +668,10 @@ declare function timeAgo(unix: number): string;
386
668
  declare function truncateKey(hex: string, chars?: number): string;
387
669
 
388
670
  /**
389
- * Shared agent config - validation and serialization.
671
+ * Agent-name validation shared between CLI and MCP.
390
672
  * Browser-safe - no Node.js imports.
391
- * parseConfig lives in config-node.ts (exported from @elisym/sdk/node).
392
673
  */
393
-
394
674
  declare function validateAgentName(name: string): void;
395
- /** Serialize an AgentConfig to JSON string. */
396
- declare function serializeConfig(config: AgentConfig): string;
397
675
 
398
676
  /** A Set that evicts the oldest entries when it exceeds maxSize. Uses a ring buffer for O(1) eviction. */
399
677
  declare class BoundedSet<T> {
@@ -482,4 +760,4 @@ declare const LIMITS: {
482
760
  readonly MAX_CAPABILITY_LENGTH: 64;
483
761
  };
484
762
 
485
- export { Agent, AgentConfig, BoundedSet, CapabilityCard, DEFAULTS, DEFAULT_KIND_OFFSET, DiscoveryService, ElisymClient, ElisymClientConfig, type ElisymClientFullConfig, ElisymIdentity, type GetProtocolConfigOptions, Job, JobSubscriptionOptions, KIND_APP_HANDLER, KIND_JOB_FEEDBACK, KIND_JOB_REQUEST, KIND_JOB_REQUEST_BASE, KIND_JOB_RESULT, KIND_JOB_RESULT_BASE, KIND_PING, KIND_PONG, LAMPORTS_PER_SOL, LIMITS, MarketplaceService, MediaService, Network, NostrPool, PROTOCOL_FEE_BPS, PROTOCOL_PROGRAM_ID_DEVNET, PROTOCOL_TREASURY, PaymentRequestData, type PaymentStrategy, PaymentValidationError, PingResult, PingService, type ProtocolCluster, type ProtocolConfig, type ProtocolConfigInput, RELAYS, SolanaPaymentStrategy, SubCloser, SubmitJobOptions, VerifyOptions, VerifyResult, assertExpiry, assertLamports, buildPaymentInstructions, calculateProtocolFee, clearProtocolConfigCache, createPaymentRequestWithOnchainConfig, formatSol, getProtocolConfig, getProtocolProgramId, jobRequestKind, jobResultKind, nip44Decrypt, nip44Encrypt, serializeConfig, timeAgo, toDTag, truncateKey, validateAgentName, validateExpiry };
763
+ export { type Agent, BoundedSet, type BuildTransactionOptions, type CapabilityCard, DEFAULTS, DEFAULT_KIND_OFFSET, DiscoveryService, ElisymClient, type ElisymClientConfig, type ElisymClientFullConfig, ElisymIdentity, type EstimatePriorityFeeOptions, type GetProtocolConfigOptions, type Job, type JobStatus, type JobSubscriptionOptions, type JobUpdateCallbacks, KIND_APP_HANDLER, KIND_JOB_FEEDBACK, KIND_JOB_REQUEST, KIND_JOB_REQUEST_BASE, KIND_JOB_RESULT, KIND_JOB_RESULT_BASE, KIND_PING, KIND_PONG, LAMPORTS_PER_SOL, LIMITS, MarketplaceService, MediaService, type Network, type NetworkStats, NostrPool, PROTOCOL_FEE_BPS, PROTOCOL_PROGRAM_ID_DEVNET, PROTOCOL_TREASURY, type ParseOptions, type ParseResult, type ParsedPaymentRequest, type PaymentInfo, type PaymentRequestData, PaymentRequestSchema, type PaymentStrategy, type PaymentValidationCode, type PaymentValidationError, type PingResult, PingService, type ProtocolCluster, type ProtocolConfig, type ProtocolConfigInput, RELAYS, type Signer, SolanaPaymentStrategy, type SubCloser, type SubmitJobOptions, type VerifyOptions, type VerifyResult, assertExpiry, assertLamports, buildPaymentInstructions, calculateProtocolFee, clearPriorityFeeCache, clearProtocolConfigCache, createPaymentRequestWithOnchainConfig, estimatePriorityFeeMicroLamports, formatSol, getProtocolConfig, getProtocolProgramId, jobRequestKind, jobResultKind, nip44Decrypt, nip44Encrypt, parsePaymentRequest, pickPercentileFee, timeAgo, toDTag, truncateKey, validateAgentName, validateExpiry };
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { getTransferSolInstruction } from '@solana-program/system';
2
- import { pipe, createTransactionMessage, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstructions, signTransactionMessageWithSigners, address, AccountRole, getProgramDerivedAddress, assertAccountExists, isAddress, getAddressDecoder, fetchEncodedAccount, decodeAccount, getStructDecoder, fixDecoderSize, getBytesDecoder, getU8Decoder, getOptionDecoder, getU16Decoder, getBooleanDecoder, getI64Decoder } from '@solana/kit';
2
+ import { pipe, createTransactionMessage, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, setTransactionMessageComputeUnitLimit, setTransactionMessageComputeUnitPrice, appendTransactionMessageInstructions, signTransactionMessageWithSigners, address, AccountRole, getProgramDerivedAddress, assertAccountExists, isAddress, getAddressDecoder, fetchEncodedAccount, decodeAccount, getStructDecoder, fixDecoderSize, getBytesDecoder, getU8Decoder, getOptionDecoder, getU16Decoder, getBooleanDecoder, getI64Decoder } from '@solana/kit';
3
3
  import Decimal2 from 'decimal.js-light';
4
+ import { z } from 'zod';
4
5
  import { verifyEvent, finalizeEvent, getPublicKey, nip19, generateSecretKey, SimplePool } from 'nostr-tools';
5
6
  import * as nip44 from 'nostr-tools/nip44';
6
7
 
@@ -190,7 +191,118 @@ function assertExpiry(createdAt, expirySecs) {
190
191
  }
191
192
  }
192
193
 
194
+ // src/payment/priorityFee.ts
195
+ var PRIORITY_FEE_FLOOR_MICROLAMPORTS = 1000n;
196
+ var DEFAULT_PERCENTILE = 75;
197
+ var DEFAULT_CACHE_TTL_MS = 1e4;
198
+ var cache2 = /* @__PURE__ */ new Map();
199
+ async function estimatePriorityFeeMicroLamports(rpc, options) {
200
+ const percentile = clampPercentile(options?.percentile ?? DEFAULT_PERCENTILE);
201
+ const ttl = options?.ttlMs ?? DEFAULT_CACHE_TTL_MS;
202
+ const accounts = options?.accounts ?? [];
203
+ const key = cacheKey(percentile, accounts);
204
+ const now = Date.now();
205
+ const cached = cache2.get(key);
206
+ if (cached && now < cached.expires) {
207
+ return cached.microLamports;
208
+ }
209
+ const samples = await rpc.getRecentPrioritizationFees(accounts).send();
210
+ const fee = pickPercentileFee(samples, percentile);
211
+ cache2.set(key, { microLamports: fee, expires: now + ttl });
212
+ return fee;
213
+ }
214
+ function clearPriorityFeeCache() {
215
+ cache2.clear();
216
+ }
217
+ function pickPercentileFee(samples, percentile) {
218
+ if (samples.length === 0) {
219
+ return PRIORITY_FEE_FLOOR_MICROLAMPORTS;
220
+ }
221
+ const sorted = samples.map((sample) => BigInt(sample.prioritizationFee)).sort(compareBigInt);
222
+ const clamped = clampPercentile(percentile);
223
+ const indexFloat = clamped / 100 * (sorted.length - 1) | 0;
224
+ const value = sorted[indexFloat];
225
+ return value > PRIORITY_FEE_FLOOR_MICROLAMPORTS ? value : PRIORITY_FEE_FLOOR_MICROLAMPORTS;
226
+ }
227
+ function clampPercentile(value) {
228
+ if (!Number.isFinite(value)) {
229
+ return DEFAULT_PERCENTILE;
230
+ }
231
+ if (value < 0) {
232
+ return 0;
233
+ }
234
+ if (value > 100) {
235
+ return 100;
236
+ }
237
+ return value;
238
+ }
239
+ function compareBigInt(left, right) {
240
+ if (left < right) {
241
+ return -1;
242
+ }
243
+ if (left > right) {
244
+ return 1;
245
+ }
246
+ return 0;
247
+ }
248
+ function cacheKey(percentile, accounts) {
249
+ if (accounts.length === 0) {
250
+ return `p:${percentile}`;
251
+ }
252
+ return `p:${percentile}:${[...accounts].sort().join(",")}`;
253
+ }
254
+ var MAX_DESCRIPTION_LENGTH = LIMITS.MAX_DESCRIPTION_LENGTH;
255
+ var MAX_SAFE_LAMPORTS = Number.MAX_SAFE_INTEGER;
256
+ var MAX_EXPIRY_SECS_SCHEMA = 86400;
257
+ var BASE58_RE = /^[1-9A-HJ-NP-Za-km-z]+$/;
258
+ var SOLANA_ADDRESS_LENGTH_RE = /^.{32,44}$/;
259
+ var lamportsSchema = z.number().int().positive().max(MAX_SAFE_LAMPORTS, `amount must be <= ${MAX_SAFE_LAMPORTS}`);
260
+ var feeAmountSchema = z.number().int().nonnegative().max(MAX_SAFE_LAMPORTS, `fee_amount must be <= ${MAX_SAFE_LAMPORTS}`);
261
+ var solanaAddressSchema = z.string().regex(BASE58_RE, "must be base58").regex(SOLANA_ADDRESS_LENGTH_RE, "must be 32-44 base58 chars");
262
+ var PaymentRequestSchema = z.object({
263
+ recipient: solanaAddressSchema,
264
+ amount: lamportsSchema,
265
+ reference: solanaAddressSchema,
266
+ description: z.string().max(MAX_DESCRIPTION_LENGTH).optional(),
267
+ fee_address: solanaAddressSchema.optional(),
268
+ fee_amount: feeAmountSchema.optional(),
269
+ created_at: z.number().int().positive(),
270
+ expiry_secs: z.number().int().positive().max(MAX_EXPIRY_SECS_SCHEMA, `expiry_secs must be <= ${MAX_EXPIRY_SECS_SCHEMA}`)
271
+ });
272
+ function parsePaymentRequest(input, options) {
273
+ let parsed;
274
+ try {
275
+ parsed = JSON.parse(input);
276
+ } catch (e) {
277
+ return {
278
+ ok: false,
279
+ error: { code: "invalid_json", message: `Invalid payment request JSON: ${e}` }
280
+ };
281
+ }
282
+ const result = PaymentRequestSchema.safeParse(parsed);
283
+ if (!result.success) {
284
+ return {
285
+ ok: false,
286
+ error: { code: "schema", message: result.error.message }
287
+ };
288
+ }
289
+ if (options?.maxAmountLamports !== void 0) {
290
+ if (BigInt(result.data.amount) > options.maxAmountLamports) {
291
+ return {
292
+ ok: false,
293
+ error: {
294
+ code: "amount_exceeds_max",
295
+ message: `Payment amount ${result.data.amount} lamports exceeds approved max ${options.maxAmountLamports}.`
296
+ }
297
+ };
298
+ }
299
+ }
300
+ return { ok: true, data: result.data };
301
+ }
302
+
193
303
  // src/payment/solana.ts
304
+ var DEFAULT_COMPUTE_UNIT_LIMIT = 2e5;
305
+ var DEFAULT_PRIORITY_FEE_PERCENTILE = 75;
194
306
  var REFERENCE_BYTE_LENGTH = 32;
195
307
  function isValidSolanaAddress(value) {
196
308
  return isAddress(value);
@@ -247,32 +359,27 @@ var SolanaPaymentStrategy = class {
247
359
  expiry_secs: expirySecs
248
360
  };
249
361
  }
250
- validatePaymentRequest(requestJson, config, expectedRecipient) {
362
+ validatePaymentRequest(requestJson, config, expectedRecipient, options) {
251
363
  assertConfig(config);
252
- let data;
253
- try {
254
- data = JSON.parse(requestJson);
255
- } catch (e) {
256
- return { code: "invalid_json", message: `Invalid payment request JSON: ${e}` };
257
- }
258
- if (typeof data.amount !== "number" || !Number.isInteger(data.amount) || data.amount <= 0) {
259
- return {
260
- code: "invalid_amount",
261
- message: `Invalid amount in payment request: ${data.amount}`
262
- };
263
- }
264
- if (typeof data.recipient !== "string" || !data.recipient) {
265
- return { code: "missing_recipient", message: "Missing recipient in payment request" };
364
+ const parsed = parsePaymentRequest(requestJson, {
365
+ maxAmountLamports: options?.maxAmountLamports
366
+ });
367
+ if (!parsed.ok) {
368
+ if (parsed.error.code === "invalid_json") {
369
+ return { code: "invalid_json", message: parsed.error.message };
370
+ }
371
+ if (parsed.error.code === "amount_exceeds_max") {
372
+ return { code: "invalid_amount", message: parsed.error.message };
373
+ }
374
+ return { code: "invalid_amount", message: parsed.error.message };
266
375
  }
376
+ const data = parsed.data;
267
377
  if (!isValidSolanaAddress(data.recipient)) {
268
378
  return {
269
379
  code: "invalid_recipient_address",
270
380
  message: `Invalid Solana address for recipient: ${data.recipient}`
271
381
  };
272
382
  }
273
- if (typeof data.reference !== "string" || !data.reference) {
274
- return { code: "missing_reference", message: "Missing reference in payment request" };
275
- }
276
383
  if (!isValidSolanaAddress(data.reference)) {
277
384
  return {
278
385
  code: "invalid_reference_address",
@@ -332,7 +439,7 @@ var SolanaPaymentStrategy = class {
332
439
  * read-only, non-signer account so providers can detect the payment via
333
440
  * `getSignaturesForAddress(reference)`.
334
441
  */
335
- async buildTransaction(paymentRequest, payerSigner, rpc, config) {
442
+ async buildTransaction(paymentRequest, payerSigner, rpc, config, options) {
336
443
  assertConfig(config);
337
444
  assertLamports(paymentRequest.amount, "payment amount");
338
445
  if (paymentRequest.amount === 0) {
@@ -351,14 +458,23 @@ var SolanaPaymentStrategy = class {
351
458
  `Invalid fee address: expected ${treasury}, got ${paymentRequest.fee_address}. Cannot build transaction with redirected fees.`
352
459
  );
353
460
  }
354
- const instructions = buildPaymentInstructions(paymentRequest, payerSigner);
461
+ const computeUnitLimit = options?.computeUnitLimit ?? DEFAULT_COMPUTE_UNIT_LIMIT;
462
+ if (!Number.isInteger(computeUnitLimit) || computeUnitLimit <= 0) {
463
+ throw new Error(`Invalid computeUnitLimit: ${computeUnitLimit}. Must be a positive integer.`);
464
+ }
465
+ const paymentInstructions = buildPaymentInstructions(paymentRequest, payerSigner);
466
+ const priorityFeeMicroLamports = options?.priorityFeeMicroLamports ?? await estimatePriorityFeeMicroLamports(rpc, {
467
+ percentile: options?.priorityFeePercentile ?? DEFAULT_PRIORITY_FEE_PERCENTILE
468
+ });
355
469
  const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
356
470
  const message = pipe(
357
471
  createTransactionMessage({ version: 0 }),
358
472
  (m) => setTransactionMessageFeePayerSigner(payerSigner, m),
359
473
  (m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m),
474
+ (m) => setTransactionMessageComputeUnitLimit(computeUnitLimit, m),
475
+ (m) => setTransactionMessageComputeUnitPrice(priorityFeeMicroLamports, m),
360
476
  (m) => appendTransactionMessageInstructions(
361
- instructions,
477
+ paymentInstructions,
362
478
  m
363
479
  )
364
480
  );
@@ -2270,10 +2386,7 @@ function validateAgentName(name) {
2270
2386
  throw new Error("Agent name must be 1-64 characters, alphanumeric, underscore, or hyphen.");
2271
2387
  }
2272
2388
  }
2273
- function serializeConfig(config) {
2274
- return JSON.stringify(config, null, 2) + "\n";
2275
- }
2276
2389
 
2277
- export { BoundedSet, DEFAULTS, DEFAULT_KIND_OFFSET, DiscoveryService, ElisymClient, ElisymIdentity, KIND_APP_HANDLER, KIND_JOB_FEEDBACK, KIND_JOB_REQUEST, KIND_JOB_REQUEST_BASE, KIND_JOB_RESULT, KIND_JOB_RESULT_BASE, KIND_PING, KIND_PONG, LAMPORTS_PER_SOL, LIMITS, MarketplaceService, MediaService, NostrPool, PROTOCOL_FEE_BPS, PROTOCOL_PROGRAM_ID_DEVNET, PROTOCOL_TREASURY, PingService, RELAYS, SolanaPaymentStrategy, assertExpiry, assertLamports, buildPaymentInstructions, calculateProtocolFee, clearProtocolConfigCache, createPaymentRequestWithOnchainConfig, formatSol, getProtocolConfig, getProtocolProgramId, jobRequestKind, jobResultKind, nip44Decrypt, nip44Encrypt, serializeConfig, timeAgo, toDTag, truncateKey, validateAgentName, validateExpiry };
2390
+ export { BoundedSet, DEFAULTS, DEFAULT_KIND_OFFSET, DiscoveryService, ElisymClient, ElisymIdentity, KIND_APP_HANDLER, KIND_JOB_FEEDBACK, KIND_JOB_REQUEST, KIND_JOB_REQUEST_BASE, KIND_JOB_RESULT, KIND_JOB_RESULT_BASE, KIND_PING, KIND_PONG, LAMPORTS_PER_SOL, LIMITS, MarketplaceService, MediaService, NostrPool, PROTOCOL_FEE_BPS, PROTOCOL_PROGRAM_ID_DEVNET, PROTOCOL_TREASURY, PaymentRequestSchema, PingService, RELAYS, SolanaPaymentStrategy, assertExpiry, assertLamports, buildPaymentInstructions, calculateProtocolFee, clearPriorityFeeCache, clearProtocolConfigCache, createPaymentRequestWithOnchainConfig, estimatePriorityFeeMicroLamports, formatSol, getProtocolConfig, getProtocolProgramId, jobRequestKind, jobResultKind, nip44Decrypt, nip44Encrypt, parsePaymentRequest, pickPercentileFee, timeAgo, toDTag, truncateKey, validateAgentName, validateExpiry };
2278
2391
  //# sourceMappingURL=index.js.map
2279
2392
  //# sourceMappingURL=index.js.map