@elisym/sdk 0.3.2 → 0.4.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.cts CHANGED
@@ -1,41 +1,75 @@
1
+ import { Address, TransactionSigner, Rpc, SolanaRpcApi } from '@solana/kit';
1
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.cjs';
2
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.cjs';
3
4
  import { Filter, Event } from 'nostr-tools';
4
- import { Transaction } from '@solana/web3.js';
5
5
 
6
+ /**
7
+ * Protocol fee + treasury inputs for building a payment request.
8
+ *
9
+ * In Phase 2 the SDK no longer reads PROTOCOL_FEE_BPS / PROTOCOL_TREASURY
10
+ * from constants. Callers supply this config so they can either pass
11
+ * the bundled fallbacks or, in Phase 3, values fetched from the on-chain
12
+ * elisym-config program.
13
+ */
14
+ interface ProtocolConfigInput {
15
+ /** Protocol fee in basis points (300 = 3%). Must be a non-negative integer. */
16
+ feeBps: number;
17
+ /** Solana address of the protocol treasury. */
18
+ treasury: Address;
19
+ }
6
20
  /**
7
21
  * Pluggable payment strategy interface.
8
22
  * Implement this for each payment chain (Solana, Lightning, Cashu, EVM).
23
+ *
24
+ * The interface is intentionally generic about the on-chain transaction type
25
+ * (`unknown` for build/verify inputs) so future chains can plug in without
26
+ * pulling Solana types into shared code paths.
9
27
  */
10
28
  interface PaymentStrategy {
11
29
  readonly chain: string;
12
30
  /** Calculate protocol fee using basis-point math. */
13
- calculateFee(amount: number): number;
31
+ calculateFee(amount: number, config: ProtocolConfigInput): number;
14
32
  /** Create a payment request with auto-calculated protocol fee. */
15
- createPaymentRequest(recipientAddress: string, amount: number, expirySecs?: number): PaymentRequestData;
33
+ createPaymentRequest(recipientAddress: string, amount: number, config: ProtocolConfigInput, options?: {
34
+ expirySecs?: number;
35
+ }): PaymentRequestData;
16
36
  /**
17
37
  * Validate that a payment request has the correct recipient and protocol fee.
18
38
  * Returns a typed validation error if invalid, null if OK.
19
39
  */
20
- validatePaymentRequest(requestJson: string, expectedRecipient?: string): PaymentValidationError | null;
40
+ validatePaymentRequest(requestJson: string, config: ProtocolConfigInput, expectedRecipient?: string): PaymentValidationError | null;
21
41
  /**
22
- * Build an unsigned transaction from a payment request.
23
- * Returns chain-specific transaction type. The caller is responsible for signing and sending.
24
- * @example For Solana: `const tx = await strategy.buildTransaction(...) as Transaction;`
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()`).
25
45
  */
26
- buildTransaction(payerAddress: string, paymentRequest: PaymentRequestData): Promise<unknown>;
46
+ buildTransaction(paymentRequest: PaymentRequestData, payerSigner: TransactionSigner, rpc: Rpc<SolanaRpcApi>, config: ProtocolConfigInput): Promise<unknown>;
27
47
  /**
28
48
  * Verify a payment on-chain.
29
- * @param connection Chain-specific connection (e.g. Solana `Connection`).
30
49
  */
31
- verifyPayment(connection: unknown, paymentRequest: PaymentRequestData, options?: VerifyOptions): Promise<VerifyResult>;
50
+ verifyPayment(rpc: Rpc<SolanaRpcApi>, paymentRequest: PaymentRequestData, config: ProtocolConfigInput, options?: VerifyOptions): Promise<VerifyResult>;
32
51
  }
33
52
 
34
53
  declare class NostrPool {
35
54
  private pool;
36
55
  private relays;
37
56
  private activeSubscriptions;
57
+ private resetListeners;
38
58
  constructor(relays?: string[]);
59
+ /**
60
+ * Register a callback to run after `reset()` completes (new SimplePool in place).
61
+ * Services that cache pool-derived state (e.g. ping results) must clear it here,
62
+ * otherwise stale values survive the reconnect. Returns an unsubscribe function.
63
+ *
64
+ * Contract:
65
+ * - Listeners are invoked **synchronously** at the end of `reset()`, in
66
+ * registration order. Do not rely on async work inside a listener having
67
+ * completed before `reset()` returns.
68
+ * - Listener exceptions are caught and swallowed so that one faulty listener
69
+ * cannot prevent the others from running (or abort the reset itself).
70
+ * If a listener needs to surface errors, it must do so out-of-band.
71
+ */
72
+ onReset(listener: () => void): () => void;
39
73
  /** Query relays synchronously. Returns `[]` on timeout (no error thrown). */
40
74
  querySync(filter: Filter): Promise<Event[]>;
41
75
  queryBatched(filter: Omit<Filter, 'authors'>, keys: string[], batchSize?: number, maxConcurrency?: number): Promise<Event[]>;
@@ -189,6 +223,20 @@ declare class MediaService {
189
223
  * this instance - recreating the service generates a new keypair.
190
224
  *
191
225
  * Requires `globalThis.crypto` (Node 20+, Bun, browsers).
226
+ *
227
+ * Lifetime / cleanup:
228
+ * - The constructor registers a listener on `pool.onReset()` and never
229
+ * unsubscribes. This is intentional: PingService is expected to share its
230
+ * lifetime with the NostrPool it is bound to (both live for the lifetime
231
+ * of an ElisymClient). If you ever construct a PingService that outlives
232
+ * its pool - or vice versa - add an explicit `dispose()` that calls the
233
+ * unsubscribe function returned by `pool.onReset()` to avoid leaking a
234
+ * reference to this instance through the pool's listener set.
235
+ * - `clearCache()` drops cached online results but does NOT cancel in-flight
236
+ * pings in `pendingPings`. An in-flight ping started before a pool reset
237
+ * may return `online: false` even if the new pool is healthy; the next
238
+ * ping attempt will go through the fresh subscription and resolve
239
+ * correctly.
192
240
  */
193
241
  declare class PingService {
194
242
  private pool;
@@ -197,6 +245,9 @@ declare class PingService {
197
245
  private pingCache;
198
246
  private pendingPings;
199
247
  constructor(pool: NostrPool);
248
+ /** Drop cached online results. In-flight pings are left alone - they'll
249
+ * resolve via their own timeouts and remove themselves from `pendingPings`. */
250
+ clearCache(): void;
200
251
  /**
201
252
  * Ping an agent via ephemeral Nostr events (kind 20200/20201).
202
253
  * Uses a persistent session identity to avoid relay rate-limiting.
@@ -233,33 +284,98 @@ declare class ElisymClient {
233
284
 
234
285
  declare class SolanaPaymentStrategy implements PaymentStrategy {
235
286
  readonly chain = "solana";
236
- calculateFee(amount: number): number;
237
- createPaymentRequest(recipientAddress: string, amount: number, expirySecs?: number): PaymentRequestData;
238
- validatePaymentRequest(requestJson: string, expectedRecipient?: string): PaymentValidationError | null;
287
+ calculateFee(amount: number, config: ProtocolConfigInput): number;
288
+ createPaymentRequest(recipientAddress: string, amount: number, config: ProtocolConfigInput, options?: {
289
+ expirySecs?: number;
290
+ }): PaymentRequestData;
291
+ validatePaymentRequest(requestJson: string, config: ProtocolConfigInput, expectedRecipient?: string): PaymentValidationError | null;
239
292
  /**
240
- * Build an unsigned transaction from a payment request.
241
- * The caller must set `recentBlockhash` and `feePayer` on the
242
- * returned Transaction before signing and sending.
293
+ * Build, sign, and return a transaction for the supplied payment request.
294
+ * The caller is responsible for sending it (e.g. via `rpc.sendTransaction`).
295
+ *
296
+ * The provider transfer instruction includes the payment reference as a
297
+ * read-only, non-signer account so providers can detect the payment via
298
+ * `getSignaturesForAddress(reference)`.
243
299
  */
244
- buildTransaction(payerAddress: string, paymentRequest: PaymentRequestData): Promise<Transaction>;
245
- verifyPayment(connection: unknown, paymentRequest: PaymentRequestData, options?: VerifyOptions): Promise<VerifyResult>;
300
+ buildTransaction(paymentRequest: PaymentRequestData, payerSigner: TransactionSigner, rpc: Rpc<SolanaRpcApi>, config: ProtocolConfigInput): Promise<Readonly<unknown>>;
301
+ verifyPayment(rpc: Rpc<SolanaRpcApi>, paymentRequest: PaymentRequestData, config: ProtocolConfigInput, options?: VerifyOptions): Promise<VerifyResult>;
246
302
  private _verifyBySignature;
247
303
  private _verifyByReference;
248
304
  }
305
+ /**
306
+ * Build the System program transfer instructions for a payment request.
307
+ *
308
+ * Returns the provider-amount transfer (with the payment reference attached
309
+ * as a read-only, non-signer account) and, if present, the protocol-fee
310
+ * transfer. Exposed so callers and tests can inspect amounts before signing.
311
+ *
312
+ * Caller is responsible for validating `paymentRequest` upstream;
313
+ * `buildTransaction` already does that before invoking this helper.
314
+ */
315
+ declare function buildPaymentInstructions(paymentRequest: PaymentRequestData, payerSigner: TransactionSigner): readonly unknown[];
316
+ /**
317
+ * Convenience wrapper: fetch the on-chain protocol config first, then build a
318
+ * payment request using its current fee/treasury values.
319
+ *
320
+ * Suitable for callers that want to "do the right thing" without managing the
321
+ * config cache or the SolanaPaymentStrategy instance themselves. Uses the same
322
+ * cache as `getProtocolConfig`, so back-to-back calls within the TTL only hit
323
+ * RPC once.
324
+ */
325
+ declare function createPaymentRequestWithOnchainConfig(rpc: Rpc<SolanaRpcApi>, programId: Address, recipient: string, amount: number, options?: {
326
+ expirySecs?: number;
327
+ }): Promise<PaymentRequestData>;
249
328
 
250
329
  /** Assert that a value is a non-negative integer (lamports). */
251
330
  declare function assertLamports(value: number, field: string): void;
252
331
  /**
253
- * Calculate protocol fee using Decimal basis-point math (no floats).
254
- * Returns ceil(amount * PROTOCOL_FEE_BPS / 10000).
255
- * Safe for amounts up to Number.MAX_SAFE_INTEGER - Decimal handles intermediate values.
332
+ * Calculate the protocol fee using basis-point math (no floats).
333
+ * Returns ceil(amount * feeBps / 10000).
334
+ *
335
+ * The caller passes the current fee (in basis points). Phase 2 of the
336
+ * Solana Kit migration removes the implicit dependency on PROTOCOL_FEE_BPS
337
+ * so callers can supply on-chain or test values.
256
338
  */
257
- declare function calculateProtocolFee(amount: number): number;
339
+ declare function calculateProtocolFee(amount: number, feeBps: number): number;
258
340
  /** Validate payment request timestamps. Returns error message or null if valid. */
259
341
  declare function validateExpiry(createdAt: number, expirySecs: number): string | null;
260
342
  /** Assert that payment request timestamps are valid and not expired. Throws on failure. */
261
343
  declare function assertExpiry(createdAt: number, expirySecs: number): void;
262
344
 
345
+ /**
346
+ * Snapshot of the on-chain elisym-config program state.
347
+ *
348
+ * `source` reflects how this snapshot was obtained:
349
+ * - `onchain`: fresh fetch via RPC.
350
+ * - `cache`: served from in-memory cache (still within TTL or stale-while-error).
351
+ *
352
+ * If RPC fails and no cached value exists, `getProtocolConfig` throws instead of
353
+ * returning stale hardcoded defaults - callers must handle the error explicitly.
354
+ */
355
+ interface ProtocolConfig {
356
+ programId: Address;
357
+ feeBps: number;
358
+ treasury: Address;
359
+ admin: Address;
360
+ pendingAdmin: Address | null;
361
+ paused: boolean;
362
+ version: number;
363
+ source: 'onchain' | 'cache';
364
+ }
365
+ declare function clearProtocolConfigCache(): void;
366
+ interface GetProtocolConfigOptions {
367
+ ttlMs?: number;
368
+ forceRefresh?: boolean;
369
+ }
370
+ /**
371
+ * Fetch the protocol config from the on-chain `elisym-config` program.
372
+ *
373
+ * Caches per-program-id with a TTL (default 60s). On RPC error, returns the
374
+ * last known good snapshot from cache. If nothing is cached, throws - callers
375
+ * must handle the error (e.g. refuse the payment, show a warning).
376
+ */
377
+ declare function getProtocolConfig(rpc: Rpc<SolanaRpcApi>, programId: Address, options?: GetProtocolConfigOptions): Promise<ProtocolConfig>;
378
+
263
379
  /** Encrypt plaintext using NIP-44 v2 (sender secret key + recipient public key). */
264
380
  declare function nip44Encrypt(plaintext: string, senderSk: Uint8Array, recipientPubkey: string): string;
265
381
  /** Decrypt ciphertext using NIP-44 v2 (receiver secret key + sender public key). */
@@ -309,10 +425,34 @@ declare function jobResultKind(offset: number): number;
309
425
  declare const KIND_PING = 20200;
310
426
  declare const KIND_PONG = 20201;
311
427
  declare const LAMPORTS_PER_SOL = 1000000000;
312
- /** Protocol fee in basis points (300 = 3%). */
428
+ /**
429
+ * @deprecated Fallback only - use `getProtocolConfig(rpc, programId)` for live values.
430
+ *
431
+ * Protocol fee in basis points (300 = 3%). Bundled as a default for offline use
432
+ * and for first-call before the on-chain config has been fetched. The on-chain
433
+ * `elisym-config` program is the source of truth.
434
+ */
313
435
  declare const PROTOCOL_FEE_BPS = 300;
314
- /** Solana address of the protocol treasury. */
315
- declare const PROTOCOL_TREASURY = "GY7vnWMkKpftU4nQ16C2ATkj1JwrQpHhknkaBUn67VTy";
436
+ /**
437
+ * @deprecated Fallback only - use `getProtocolConfig(rpc, programId)` for live values.
438
+ *
439
+ * Solana address of the protocol treasury. Bundled fallback; the on-chain
440
+ * `elisym-config` program is the source of truth and may rotate this address.
441
+ */
442
+ declare const PROTOCOL_TREASURY: Address;
443
+ /**
444
+ * Solana program ID for the elisym protocol config (devnet deployment).
445
+ *
446
+ * The Anchor program at this address is the source of truth for fee bps,
447
+ * treasury address, and admin rotation state. Read via `getProtocolConfig`.
448
+ */
449
+ declare const PROTOCOL_PROGRAM_ID_DEVNET: Address;
450
+ type ProtocolCluster = 'devnet' | 'mainnet' | 'localnet';
451
+ /**
452
+ * Resolve the elisym-config program ID for a given Solana cluster.
453
+ * Mainnet is intentionally unsupported until the program ships there.
454
+ */
455
+ declare function getProtocolProgramId(cluster: ProtocolCluster): Address;
316
456
  /** Default values for timeouts, retries, and batch sizes. */
317
457
  declare const DEFAULTS: {
318
458
  readonly SUBSCRIPTION_TIMEOUT_MS: 120000;
@@ -342,4 +482,4 @@ declare const LIMITS: {
342
482
  readonly MAX_CAPABILITY_LENGTH: 64;
343
483
  };
344
484
 
345
- export { Agent, AgentConfig, BoundedSet, CapabilityCard, DEFAULTS, DEFAULT_KIND_OFFSET, DiscoveryService, ElisymClient, ElisymClientConfig, type ElisymClientFullConfig, ElisymIdentity, 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_TREASURY, PaymentRequestData, type PaymentStrategy, PaymentValidationError, PingResult, PingService, RELAYS, SolanaPaymentStrategy, SubCloser, SubmitJobOptions, VerifyOptions, VerifyResult, assertExpiry, assertLamports, calculateProtocolFee, formatSol, jobRequestKind, jobResultKind, nip44Decrypt, nip44Encrypt, serializeConfig, timeAgo, toDTag, truncateKey, validateAgentName, validateExpiry };
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 };
package/dist/index.d.ts CHANGED
@@ -1,41 +1,75 @@
1
+ import { Address, TransactionSigner, Rpc, SolanaRpcApi } from '@solana/kit';
1
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';
2
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';
3
4
  import { Filter, Event } from 'nostr-tools';
4
- import { Transaction } from '@solana/web3.js';
5
5
 
6
+ /**
7
+ * Protocol fee + treasury inputs for building a payment request.
8
+ *
9
+ * In Phase 2 the SDK no longer reads PROTOCOL_FEE_BPS / PROTOCOL_TREASURY
10
+ * from constants. Callers supply this config so they can either pass
11
+ * the bundled fallbacks or, in Phase 3, values fetched from the on-chain
12
+ * elisym-config program.
13
+ */
14
+ interface ProtocolConfigInput {
15
+ /** Protocol fee in basis points (300 = 3%). Must be a non-negative integer. */
16
+ feeBps: number;
17
+ /** Solana address of the protocol treasury. */
18
+ treasury: Address;
19
+ }
6
20
  /**
7
21
  * Pluggable payment strategy interface.
8
22
  * Implement this for each payment chain (Solana, Lightning, Cashu, EVM).
23
+ *
24
+ * The interface is intentionally generic about the on-chain transaction type
25
+ * (`unknown` for build/verify inputs) so future chains can plug in without
26
+ * pulling Solana types into shared code paths.
9
27
  */
10
28
  interface PaymentStrategy {
11
29
  readonly chain: string;
12
30
  /** Calculate protocol fee using basis-point math. */
13
- calculateFee(amount: number): number;
31
+ calculateFee(amount: number, config: ProtocolConfigInput): number;
14
32
  /** Create a payment request with auto-calculated protocol fee. */
15
- createPaymentRequest(recipientAddress: string, amount: number, expirySecs?: number): PaymentRequestData;
33
+ createPaymentRequest(recipientAddress: string, amount: number, config: ProtocolConfigInput, options?: {
34
+ expirySecs?: number;
35
+ }): PaymentRequestData;
16
36
  /**
17
37
  * Validate that a payment request has the correct recipient and protocol fee.
18
38
  * Returns a typed validation error if invalid, null if OK.
19
39
  */
20
- validatePaymentRequest(requestJson: string, expectedRecipient?: string): PaymentValidationError | null;
40
+ validatePaymentRequest(requestJson: string, config: ProtocolConfigInput, expectedRecipient?: string): PaymentValidationError | null;
21
41
  /**
22
- * Build an unsigned transaction from a payment request.
23
- * Returns chain-specific transaction type. The caller is responsible for signing and sending.
24
- * @example For Solana: `const tx = await strategy.buildTransaction(...) as Transaction;`
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()`).
25
45
  */
26
- buildTransaction(payerAddress: string, paymentRequest: PaymentRequestData): Promise<unknown>;
46
+ buildTransaction(paymentRequest: PaymentRequestData, payerSigner: TransactionSigner, rpc: Rpc<SolanaRpcApi>, config: ProtocolConfigInput): Promise<unknown>;
27
47
  /**
28
48
  * Verify a payment on-chain.
29
- * @param connection Chain-specific connection (e.g. Solana `Connection`).
30
49
  */
31
- verifyPayment(connection: unknown, paymentRequest: PaymentRequestData, options?: VerifyOptions): Promise<VerifyResult>;
50
+ verifyPayment(rpc: Rpc<SolanaRpcApi>, paymentRequest: PaymentRequestData, config: ProtocolConfigInput, options?: VerifyOptions): Promise<VerifyResult>;
32
51
  }
33
52
 
34
53
  declare class NostrPool {
35
54
  private pool;
36
55
  private relays;
37
56
  private activeSubscriptions;
57
+ private resetListeners;
38
58
  constructor(relays?: string[]);
59
+ /**
60
+ * Register a callback to run after `reset()` completes (new SimplePool in place).
61
+ * Services that cache pool-derived state (e.g. ping results) must clear it here,
62
+ * otherwise stale values survive the reconnect. Returns an unsubscribe function.
63
+ *
64
+ * Contract:
65
+ * - Listeners are invoked **synchronously** at the end of `reset()`, in
66
+ * registration order. Do not rely on async work inside a listener having
67
+ * completed before `reset()` returns.
68
+ * - Listener exceptions are caught and swallowed so that one faulty listener
69
+ * cannot prevent the others from running (or abort the reset itself).
70
+ * If a listener needs to surface errors, it must do so out-of-band.
71
+ */
72
+ onReset(listener: () => void): () => void;
39
73
  /** Query relays synchronously. Returns `[]` on timeout (no error thrown). */
40
74
  querySync(filter: Filter): Promise<Event[]>;
41
75
  queryBatched(filter: Omit<Filter, 'authors'>, keys: string[], batchSize?: number, maxConcurrency?: number): Promise<Event[]>;
@@ -189,6 +223,20 @@ declare class MediaService {
189
223
  * this instance - recreating the service generates a new keypair.
190
224
  *
191
225
  * Requires `globalThis.crypto` (Node 20+, Bun, browsers).
226
+ *
227
+ * Lifetime / cleanup:
228
+ * - The constructor registers a listener on `pool.onReset()` and never
229
+ * unsubscribes. This is intentional: PingService is expected to share its
230
+ * lifetime with the NostrPool it is bound to (both live for the lifetime
231
+ * of an ElisymClient). If you ever construct a PingService that outlives
232
+ * its pool - or vice versa - add an explicit `dispose()` that calls the
233
+ * unsubscribe function returned by `pool.onReset()` to avoid leaking a
234
+ * reference to this instance through the pool's listener set.
235
+ * - `clearCache()` drops cached online results but does NOT cancel in-flight
236
+ * pings in `pendingPings`. An in-flight ping started before a pool reset
237
+ * may return `online: false` even if the new pool is healthy; the next
238
+ * ping attempt will go through the fresh subscription and resolve
239
+ * correctly.
192
240
  */
193
241
  declare class PingService {
194
242
  private pool;
@@ -197,6 +245,9 @@ declare class PingService {
197
245
  private pingCache;
198
246
  private pendingPings;
199
247
  constructor(pool: NostrPool);
248
+ /** Drop cached online results. In-flight pings are left alone - they'll
249
+ * resolve via their own timeouts and remove themselves from `pendingPings`. */
250
+ clearCache(): void;
200
251
  /**
201
252
  * Ping an agent via ephemeral Nostr events (kind 20200/20201).
202
253
  * Uses a persistent session identity to avoid relay rate-limiting.
@@ -233,33 +284,98 @@ declare class ElisymClient {
233
284
 
234
285
  declare class SolanaPaymentStrategy implements PaymentStrategy {
235
286
  readonly chain = "solana";
236
- calculateFee(amount: number): number;
237
- createPaymentRequest(recipientAddress: string, amount: number, expirySecs?: number): PaymentRequestData;
238
- validatePaymentRequest(requestJson: string, expectedRecipient?: string): PaymentValidationError | null;
287
+ calculateFee(amount: number, config: ProtocolConfigInput): number;
288
+ createPaymentRequest(recipientAddress: string, amount: number, config: ProtocolConfigInput, options?: {
289
+ expirySecs?: number;
290
+ }): PaymentRequestData;
291
+ validatePaymentRequest(requestJson: string, config: ProtocolConfigInput, expectedRecipient?: string): PaymentValidationError | null;
239
292
  /**
240
- * Build an unsigned transaction from a payment request.
241
- * The caller must set `recentBlockhash` and `feePayer` on the
242
- * returned Transaction before signing and sending.
293
+ * Build, sign, and return a transaction for the supplied payment request.
294
+ * The caller is responsible for sending it (e.g. via `rpc.sendTransaction`).
295
+ *
296
+ * The provider transfer instruction includes the payment reference as a
297
+ * read-only, non-signer account so providers can detect the payment via
298
+ * `getSignaturesForAddress(reference)`.
243
299
  */
244
- buildTransaction(payerAddress: string, paymentRequest: PaymentRequestData): Promise<Transaction>;
245
- verifyPayment(connection: unknown, paymentRequest: PaymentRequestData, options?: VerifyOptions): Promise<VerifyResult>;
300
+ buildTransaction(paymentRequest: PaymentRequestData, payerSigner: TransactionSigner, rpc: Rpc<SolanaRpcApi>, config: ProtocolConfigInput): Promise<Readonly<unknown>>;
301
+ verifyPayment(rpc: Rpc<SolanaRpcApi>, paymentRequest: PaymentRequestData, config: ProtocolConfigInput, options?: VerifyOptions): Promise<VerifyResult>;
246
302
  private _verifyBySignature;
247
303
  private _verifyByReference;
248
304
  }
305
+ /**
306
+ * Build the System program transfer instructions for a payment request.
307
+ *
308
+ * Returns the provider-amount transfer (with the payment reference attached
309
+ * as a read-only, non-signer account) and, if present, the protocol-fee
310
+ * transfer. Exposed so callers and tests can inspect amounts before signing.
311
+ *
312
+ * Caller is responsible for validating `paymentRequest` upstream;
313
+ * `buildTransaction` already does that before invoking this helper.
314
+ */
315
+ declare function buildPaymentInstructions(paymentRequest: PaymentRequestData, payerSigner: TransactionSigner): readonly unknown[];
316
+ /**
317
+ * Convenience wrapper: fetch the on-chain protocol config first, then build a
318
+ * payment request using its current fee/treasury values.
319
+ *
320
+ * Suitable for callers that want to "do the right thing" without managing the
321
+ * config cache or the SolanaPaymentStrategy instance themselves. Uses the same
322
+ * cache as `getProtocolConfig`, so back-to-back calls within the TTL only hit
323
+ * RPC once.
324
+ */
325
+ declare function createPaymentRequestWithOnchainConfig(rpc: Rpc<SolanaRpcApi>, programId: Address, recipient: string, amount: number, options?: {
326
+ expirySecs?: number;
327
+ }): Promise<PaymentRequestData>;
249
328
 
250
329
  /** Assert that a value is a non-negative integer (lamports). */
251
330
  declare function assertLamports(value: number, field: string): void;
252
331
  /**
253
- * Calculate protocol fee using Decimal basis-point math (no floats).
254
- * Returns ceil(amount * PROTOCOL_FEE_BPS / 10000).
255
- * Safe for amounts up to Number.MAX_SAFE_INTEGER - Decimal handles intermediate values.
332
+ * Calculate the protocol fee using basis-point math (no floats).
333
+ * Returns ceil(amount * feeBps / 10000).
334
+ *
335
+ * The caller passes the current fee (in basis points). Phase 2 of the
336
+ * Solana Kit migration removes the implicit dependency on PROTOCOL_FEE_BPS
337
+ * so callers can supply on-chain or test values.
256
338
  */
257
- declare function calculateProtocolFee(amount: number): number;
339
+ declare function calculateProtocolFee(amount: number, feeBps: number): number;
258
340
  /** Validate payment request timestamps. Returns error message or null if valid. */
259
341
  declare function validateExpiry(createdAt: number, expirySecs: number): string | null;
260
342
  /** Assert that payment request timestamps are valid and not expired. Throws on failure. */
261
343
  declare function assertExpiry(createdAt: number, expirySecs: number): void;
262
344
 
345
+ /**
346
+ * Snapshot of the on-chain elisym-config program state.
347
+ *
348
+ * `source` reflects how this snapshot was obtained:
349
+ * - `onchain`: fresh fetch via RPC.
350
+ * - `cache`: served from in-memory cache (still within TTL or stale-while-error).
351
+ *
352
+ * If RPC fails and no cached value exists, `getProtocolConfig` throws instead of
353
+ * returning stale hardcoded defaults - callers must handle the error explicitly.
354
+ */
355
+ interface ProtocolConfig {
356
+ programId: Address;
357
+ feeBps: number;
358
+ treasury: Address;
359
+ admin: Address;
360
+ pendingAdmin: Address | null;
361
+ paused: boolean;
362
+ version: number;
363
+ source: 'onchain' | 'cache';
364
+ }
365
+ declare function clearProtocolConfigCache(): void;
366
+ interface GetProtocolConfigOptions {
367
+ ttlMs?: number;
368
+ forceRefresh?: boolean;
369
+ }
370
+ /**
371
+ * Fetch the protocol config from the on-chain `elisym-config` program.
372
+ *
373
+ * Caches per-program-id with a TTL (default 60s). On RPC error, returns the
374
+ * last known good snapshot from cache. If nothing is cached, throws - callers
375
+ * must handle the error (e.g. refuse the payment, show a warning).
376
+ */
377
+ declare function getProtocolConfig(rpc: Rpc<SolanaRpcApi>, programId: Address, options?: GetProtocolConfigOptions): Promise<ProtocolConfig>;
378
+
263
379
  /** Encrypt plaintext using NIP-44 v2 (sender secret key + recipient public key). */
264
380
  declare function nip44Encrypt(plaintext: string, senderSk: Uint8Array, recipientPubkey: string): string;
265
381
  /** Decrypt ciphertext using NIP-44 v2 (receiver secret key + sender public key). */
@@ -309,10 +425,34 @@ declare function jobResultKind(offset: number): number;
309
425
  declare const KIND_PING = 20200;
310
426
  declare const KIND_PONG = 20201;
311
427
  declare const LAMPORTS_PER_SOL = 1000000000;
312
- /** Protocol fee in basis points (300 = 3%). */
428
+ /**
429
+ * @deprecated Fallback only - use `getProtocolConfig(rpc, programId)` for live values.
430
+ *
431
+ * Protocol fee in basis points (300 = 3%). Bundled as a default for offline use
432
+ * and for first-call before the on-chain config has been fetched. The on-chain
433
+ * `elisym-config` program is the source of truth.
434
+ */
313
435
  declare const PROTOCOL_FEE_BPS = 300;
314
- /** Solana address of the protocol treasury. */
315
- declare const PROTOCOL_TREASURY = "GY7vnWMkKpftU4nQ16C2ATkj1JwrQpHhknkaBUn67VTy";
436
+ /**
437
+ * @deprecated Fallback only - use `getProtocolConfig(rpc, programId)` for live values.
438
+ *
439
+ * Solana address of the protocol treasury. Bundled fallback; the on-chain
440
+ * `elisym-config` program is the source of truth and may rotate this address.
441
+ */
442
+ declare const PROTOCOL_TREASURY: Address;
443
+ /**
444
+ * Solana program ID for the elisym protocol config (devnet deployment).
445
+ *
446
+ * The Anchor program at this address is the source of truth for fee bps,
447
+ * treasury address, and admin rotation state. Read via `getProtocolConfig`.
448
+ */
449
+ declare const PROTOCOL_PROGRAM_ID_DEVNET: Address;
450
+ type ProtocolCluster = 'devnet' | 'mainnet' | 'localnet';
451
+ /**
452
+ * Resolve the elisym-config program ID for a given Solana cluster.
453
+ * Mainnet is intentionally unsupported until the program ships there.
454
+ */
455
+ declare function getProtocolProgramId(cluster: ProtocolCluster): Address;
316
456
  /** Default values for timeouts, retries, and batch sizes. */
317
457
  declare const DEFAULTS: {
318
458
  readonly SUBSCRIPTION_TIMEOUT_MS: 120000;
@@ -342,4 +482,4 @@ declare const LIMITS: {
342
482
  readonly MAX_CAPABILITY_LENGTH: 64;
343
483
  };
344
484
 
345
- export { Agent, AgentConfig, BoundedSet, CapabilityCard, DEFAULTS, DEFAULT_KIND_OFFSET, DiscoveryService, ElisymClient, ElisymClientConfig, type ElisymClientFullConfig, ElisymIdentity, 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_TREASURY, PaymentRequestData, type PaymentStrategy, PaymentValidationError, PingResult, PingService, RELAYS, SolanaPaymentStrategy, SubCloser, SubmitJobOptions, VerifyOptions, VerifyResult, assertExpiry, assertLamports, calculateProtocolFee, formatSol, jobRequestKind, jobResultKind, nip44Decrypt, nip44Encrypt, serializeConfig, timeAgo, toDTag, truncateKey, validateAgentName, validateExpiry };
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 };