@elisym/sdk 0.22.0 → 0.24.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
@@ -93,10 +93,35 @@ var DEFAULTS = {
93
93
  RESULT_RETRY_COUNT: 3,
94
94
  RESULT_RETRY_BASE_MS: 1e3,
95
95
  QUERY_MAX_CONCURRENCY: 6,
96
- VERIFY_SIGNATURE_LIMIT: 25
96
+ VERIFY_SIGNATURE_LIMIT: 25,
97
+ // Default ceiling for a single iroh file transfer (seed/fetch). A tunable
98
+ // default, not a protocol constant - the transfer is resumable and its own
99
+ // budget, decoupled from the result-wait window.
100
+ IROH_FETCH_TIMEOUT_MS: 3e5
97
101
  };
98
102
  var LIMITS = {
99
103
  MAX_INPUT_LENGTH: 1e5,
104
+ // NIP-44 v2 hard cap on encrypted plaintext: the pad() length prefix is a u16,
105
+ // so the plaintext can be at most 65_535 BYTES (not chars). Encrypting anything
106
+ // larger throws `invalid plaintext size` inside nip44Encrypt. This is the binding
107
+ // limit for TARGETED (encrypted) jobs - lower than every relay's NIP-11 cap.
108
+ NIP44_MAX_PLAINTEXT_BYTES: 65535,
109
+ // Spill threshold for encrypted content: above this many UTF-8 bytes, callers
110
+ // route the text through iroh out-of-band instead of inlining it. A non-spilled
111
+ // job is encrypted RAW (no envelope - see marketplace.submitJobRequest), so a
112
+ // 60_000-byte input is a 60_000-byte plaintext; the ~5.5KB gap under the hard cap
113
+ // is plain slack, and NIP44_MAX_PLAINTEXT_BYTES is the SDK backstop.
114
+ MAX_ENCRYPTED_INLINE_BYTES: 6e4,
115
+ // Ceiling above which a text/* attachment is NOT materialized back into a string
116
+ // (re-inlined into SkillInput.data) - the consumer gets a filePath / explicit
117
+ // fetch instead. Also bounds the in-memory git-diff buffer (memory-DoS guard).
118
+ MAX_REINLINE_TEXT_BYTES: 4194304,
119
+ // 4 MiB
120
+ // Hard safety cap on a single file transferred via iroh, enforced on the
121
+ // actual streamed bytes (never the sender-declared `size`). A tunable default;
122
+ // providers may lower it per deployment.
123
+ MAX_FILE_SIZE: 1073741824,
124
+ // 1 GiB
100
125
  MAX_TIMEOUT_SECS: 600,
101
126
  // Upper bound for execution budgets (`max_execution_secs` / `execution_timeout_secs`).
102
127
  // Distinct from MAX_TIMEOUT_SECS (the result-wait cap): execution budgets may be
@@ -114,6 +139,10 @@ var LIMITS = {
114
139
  MAX_POLICY_SUMMARY_LENGTH: 280,
115
140
  MAX_POLICY_VERSION_LENGTH: 32
116
141
  };
142
+ var UTF8_ENCODER = new TextEncoder();
143
+ function utf8ByteLength(value) {
144
+ return UTF8_ENCODER.encode(value).length;
145
+ }
117
146
  function getConfigDecoder() {
118
147
  return kit.getStructDecoder([
119
148
  ["discriminator", kit.fixDecoderSize(kit.getBytesDecoder(), 8)],
@@ -1237,6 +1266,12 @@ function parseCapabilityEvent(event, network) {
1237
1266
  if (card.payment && (typeof card.payment.chain !== "string" || typeof card.payment.network !== "string" || typeof card.payment.address !== "string")) {
1238
1267
  return null;
1239
1268
  }
1269
+ if (card.payment && (card.payment.token !== void 0 && typeof card.payment.token !== "string" || card.payment.symbol !== void 0 && typeof card.payment.symbol !== "string" || card.payment.mint !== void 0 && typeof card.payment.mint !== "string")) {
1270
+ return null;
1271
+ }
1272
+ if (card.inputMime !== void 0 && (typeof card.inputMime !== "string" || card.inputMime.length > 255) || card.outputMime !== void 0 && (typeof card.outputMime !== "string" || card.outputMime.length > 255)) {
1273
+ return null;
1274
+ }
1240
1275
  if (card.payment?.job_price !== null && card.payment?.job_price !== void 0 && (!Number.isInteger(card.payment.job_price) || card.payment.job_price < 0)) {
1241
1276
  return null;
1242
1277
  }
@@ -1497,6 +1532,7 @@ var DiscoveryService = class {
1497
1532
  return agents;
1498
1533
  }
1499
1534
  const deliveredJobsByProvider = /* @__PURE__ */ new Map();
1535
+ const customerByJob = /* @__PURE__ */ new Map();
1500
1536
  for (const ev of resultEvents) {
1501
1537
  if (!nostrTools.verifyEvent(ev)) {
1502
1538
  continue;
@@ -1516,8 +1552,13 @@ var DiscoveryService = class {
1516
1552
  deliveredJobsByProvider.set(ev.pubkey, delivered);
1517
1553
  }
1518
1554
  delivered.add(jobEventId);
1555
+ const customerPubkey = ev.tags.find((tag) => tag[0] === "p")?.[1];
1556
+ if (customerPubkey) {
1557
+ customerByJob.set(jobEventId, customerPubkey);
1558
+ }
1519
1559
  }
1520
1560
  }
1561
+ const countedRatings = /* @__PURE__ */ new Set();
1521
1562
  for (const ev of feedbackEvents) {
1522
1563
  if (!nostrTools.verifyEvent(ev)) {
1523
1564
  continue;
@@ -1533,19 +1574,25 @@ var DiscoveryService = class {
1533
1574
  if (ev.created_at > agent.lastSeen) {
1534
1575
  agent.lastSeen = ev.created_at;
1535
1576
  }
1577
+ const jobEventId = ev.tags.find((tag) => tag[0] === "e")?.[1];
1578
+ const hasDeliveredResult = jobEventId !== void 0 && deliveredJobsByProvider.get(targetPubkey)?.has(jobEventId) === true;
1579
+ const jobCustomer = jobEventId !== void 0 ? customerByJob.get(jobEventId) : void 0;
1580
+ const authoredByCustomer = jobCustomer !== void 0 && ev.pubkey === jobCustomer;
1536
1581
  const rating = ev.tags.find((tag) => tag[0] === "rating")?.[1];
1537
- if (rating === "1" || rating === "0") {
1538
- agent.totalRatingCount = (agent.totalRatingCount ?? 0) + 1;
1539
- if (rating === "1") {
1540
- agent.positiveCount = (agent.positiveCount ?? 0) + 1;
1582
+ if ((rating === "1" || rating === "0") && hasDeliveredResult && authoredByCustomer) {
1583
+ const ratingKey = `${ev.pubkey}:${jobEventId}`;
1584
+ if (!countedRatings.has(ratingKey)) {
1585
+ countedRatings.add(ratingKey);
1586
+ agent.totalRatingCount = (agent.totalRatingCount ?? 0) + 1;
1587
+ if (rating === "1") {
1588
+ agent.positiveCount = (agent.positiveCount ?? 0) + 1;
1589
+ }
1541
1590
  }
1542
1591
  }
1543
1592
  const status = ev.tags.find((tag) => tag[0] === "status")?.[1];
1544
1593
  const txTag = ev.tags.find((tag) => tag[0] === "tx");
1545
1594
  const txSignature = txTag?.[1];
1546
- const jobEventId = ev.tags.find((tag) => tag[0] === "e")?.[1];
1547
- const hasDeliveredResult = jobEventId !== void 0 && deliveredJobsByProvider.get(targetPubkey)?.has(jobEventId) === true;
1548
- if (status === "payment-completed" && typeof txSignature === "string" && txSignature && hasDeliveredResult) {
1595
+ if (status === "payment-completed" && typeof txSignature === "string" && txSignature && hasDeliveredResult && authoredByCustomer) {
1549
1596
  if (!agent.lastPaidJobAt || ev.created_at > agent.lastPaidJobAt) {
1550
1597
  agent.lastPaidJobAt = ev.created_at;
1551
1598
  agent.lastPaidJobTx = txSignature;
@@ -1801,6 +1848,67 @@ function nip44Decrypt(ciphertext, receiverSk, senderPubkey) {
1801
1848
  const conversationKey = nip44__namespace.v2.utils.getConversationKey(receiverSk, senderPubkey);
1802
1849
  return nip44__namespace.v2.decrypt(ciphertext, conversationKey);
1803
1850
  }
1851
+ var ENVELOPE_VERSION = "elisym-job/1";
1852
+ var ENVELOPE_NAMESPACE_PREFIX = "elisym-job/";
1853
+ var MAX_TICKET_LENGTH = 4096;
1854
+ var FileTransportSchema = zod.z.discriminatedUnion("kind", [
1855
+ zod.z.object({
1856
+ kind: zod.z.literal("iroh"),
1857
+ /** Opaque iroh `BlobTicket` string. Parsed into a real ticket only at fetch time. */
1858
+ ticket: zod.z.string().min(1).max(MAX_TICKET_LENGTH)
1859
+ })
1860
+ ]);
1861
+ var FileAttachmentSchema = zod.z.object({
1862
+ /** Display name only. Never used to derive a filesystem path (callers sanitize). */
1863
+ name: zod.z.string().min(1).max(255),
1864
+ /** Declared size in bytes (display/hint only; enforcement is on actual streamed bytes). */
1865
+ size: zod.z.number().int().nonnegative(),
1866
+ mime: zod.z.string().min(1).max(255),
1867
+ /** Ordered by sender preference; at least one. */
1868
+ transports: zod.z.array(FileTransportSchema).min(1),
1869
+ /** Optional provider hint (unix seconds) for when seeding may stop. */
1870
+ seedingExpiresAt: zod.z.number().int().nonnegative().optional()
1871
+ });
1872
+ var JobPayloadEnvelopeSchema = zod.z.object({
1873
+ v: zod.z.literal(ENVELOPE_VERSION),
1874
+ text: zod.z.string().optional(),
1875
+ attachment: FileAttachmentSchema.optional()
1876
+ });
1877
+ function encodeJobPayload(payload) {
1878
+ const envelope = { v: ENVELOPE_VERSION };
1879
+ if (payload.text !== void 0) {
1880
+ envelope.text = payload.text;
1881
+ }
1882
+ if (payload.attachment !== void 0) {
1883
+ envelope.attachment = payload.attachment;
1884
+ }
1885
+ return JSON.stringify(envelope);
1886
+ }
1887
+ function decodeJobPayload(content) {
1888
+ if (content.length > LIMITS.MAX_INPUT_LENGTH) {
1889
+ return { text: content };
1890
+ }
1891
+ let parsed;
1892
+ try {
1893
+ parsed = JSON.parse(content);
1894
+ } catch {
1895
+ return { text: content };
1896
+ }
1897
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1898
+ return { text: content };
1899
+ }
1900
+ const version = parsed.v;
1901
+ if (typeof version !== "string" || !version.startsWith(ENVELOPE_NAMESPACE_PREFIX)) {
1902
+ return { text: content };
1903
+ }
1904
+ const result = JobPayloadEnvelopeSchema.safeParse(parsed);
1905
+ if (!result.success) {
1906
+ throw new Error(
1907
+ `Invalid elisym job payload (v=${JSON.stringify(version)}): ${result.error.message}`
1908
+ );
1909
+ }
1910
+ return { text: result.data.text, attachment: result.data.attachment };
1911
+ }
1804
1912
 
1805
1913
  // src/services/marketplace.ts
1806
1914
  function isEncrypted(event) {
@@ -1833,7 +1941,8 @@ var MarketplaceService = class {
1833
1941
  }
1834
1942
  /** Submit a job request with NIP-44 encrypted input. Returns the event ID. */
1835
1943
  async submitJobRequest(identity, options) {
1836
- if (!options.input) {
1944
+ const hasAttachment = options.attachment !== void 0;
1945
+ if (!options.input && !hasAttachment) {
1837
1946
  throw new Error("Job input must not be empty.");
1838
1947
  }
1839
1948
  if (options.input.length > LIMITS.MAX_INPUT_LENGTH) {
@@ -1847,7 +1956,15 @@ var MarketplaceService = class {
1847
1956
  if (options.providerPubkey && !/^[0-9a-f]{64}$/.test(options.providerPubkey)) {
1848
1957
  throw new Error("Invalid provider pubkey: expected 64 hex characters.");
1849
1958
  }
1850
- const plaintext = options.input;
1959
+ const plaintext = hasAttachment ? encodeJobPayload({ text: options.input || void 0, attachment: options.attachment }) : options.input;
1960
+ if (options.providerPubkey) {
1961
+ const plaintextBytes = utf8ByteLength(plaintext);
1962
+ if (plaintextBytes > LIMITS.NIP44_MAX_PLAINTEXT_BYTES) {
1963
+ throw new Error(
1964
+ `Encrypted job input too large: ${plaintextBytes} bytes (max ${LIMITS.NIP44_MAX_PLAINTEXT_BYTES}). Send large input as a file/iroh attachment instead.`
1965
+ );
1966
+ }
1967
+ }
1851
1968
  const encrypted = options.providerPubkey ? nip44Encrypt(plaintext, identity.secretKey, options.providerPubkey) : plaintext;
1852
1969
  const tags = [
1853
1970
  ["i", options.providerPubkey ? "encrypted" : "text", "text"],
@@ -1941,9 +2058,15 @@ var MarketplaceService = class {
1941
2058
  if (content === null) {
1942
2059
  return;
1943
2060
  }
2061
+ let decoded;
2062
+ try {
2063
+ decoded = decodeJobPayload(content);
2064
+ } catch {
2065
+ return;
2066
+ }
1944
2067
  resultDelivered = true;
1945
2068
  try {
1946
- cb.onResult?.(content, ev.id);
2069
+ cb.onResult?.(decoded.text ?? "", ev.id, decoded.attachment);
1947
2070
  } catch {
1948
2071
  } finally {
1949
2072
  done();
@@ -2107,8 +2230,9 @@ var MarketplaceService = class {
2107
2230
  );
2108
2231
  }
2109
2232
  /** Submit a job result with NIP-44 encrypted content. Result kind is derived from the request kind. */
2110
- async submitJobResult(identity, requestEvent, content, amount) {
2111
- if (!content) {
2233
+ async submitJobResult(identity, requestEvent, content, amount, attachment) {
2234
+ const hasAttachment = attachment !== void 0;
2235
+ if (!content && !hasAttachment) {
2112
2236
  throw new Error("Job result content must not be empty.");
2113
2237
  }
2114
2238
  if (!Number.isInteger(requestEvent.kind)) {
@@ -2121,7 +2245,16 @@ var MarketplaceService = class {
2121
2245
  );
2122
2246
  }
2123
2247
  const shouldEncrypt = isEncrypted(requestEvent);
2124
- const resultContent = shouldEncrypt ? nip44Encrypt(content, identity.secretKey, requestEvent.pubkey) : content;
2248
+ const payload = hasAttachment ? encodeJobPayload({ text: content || void 0, attachment }) : content;
2249
+ if (shouldEncrypt) {
2250
+ const payloadBytes = utf8ByteLength(payload);
2251
+ if (payloadBytes > LIMITS.NIP44_MAX_PLAINTEXT_BYTES) {
2252
+ throw new Error(
2253
+ `Encrypted job result too large: ${payloadBytes} bytes (max ${LIMITS.NIP44_MAX_PLAINTEXT_BYTES}). Deliver large results as a file/iroh attachment instead.`
2254
+ );
2255
+ }
2256
+ }
2257
+ const resultContent = shouldEncrypt ? nip44Encrypt(payload, identity.secretKey, requestEvent.pubkey) : payload;
2125
2258
  const resultKind = KIND_JOB_RESULT_BASE + offset;
2126
2259
  const tags = [
2127
2260
  ["e", requestEvent.id],
@@ -2153,11 +2286,11 @@ var MarketplaceService = class {
2153
2286
  * With maxAttempts=3: try, ~1s, try, ~2s, try, throw.
2154
2287
  * Jitter: 0.5x-1.0x of calculated delay.
2155
2288
  */
2156
- async submitJobResultWithRetry(identity, requestEvent, content, amount, maxAttempts = DEFAULTS.RESULT_RETRY_COUNT, baseDelayMs = DEFAULTS.RESULT_RETRY_BASE_MS) {
2289
+ async submitJobResultWithRetry(identity, requestEvent, content, amount, maxAttempts = DEFAULTS.RESULT_RETRY_COUNT, baseDelayMs = DEFAULTS.RESULT_RETRY_BASE_MS, attachment) {
2157
2290
  const attempts = Math.max(1, maxAttempts);
2158
2291
  for (let attempt = 0; attempt < attempts; attempt++) {
2159
2292
  try {
2160
- return await this.submitJobResult(identity, requestEvent, content, amount);
2293
+ return await this.submitJobResult(identity, requestEvent, content, amount, attachment);
2161
2294
  } catch (e) {
2162
2295
  if (attempt >= attempts - 1) {
2163
2296
  throw e;
@@ -3429,35 +3562,43 @@ function formatNetworkBaseline(estimate) {
3429
3562
  return `Estimated network gas: ${total} SOL (${parts.join(" + ")}).`;
3430
3563
  }
3431
3564
  var NEGATIVE_CACHE_TTL_MS = 6e4;
3565
+ var MAX_CACHE_ENTRIES = 5e3;
3432
3566
  var verifyCache = /* @__PURE__ */ new Map();
3433
3567
  function clearQuickVerifyCache() {
3434
3568
  verifyCache.clear();
3435
3569
  }
3436
3570
  async function verifyJobPaymentQuick(rpc, txSignature, expectedRecipient) {
3437
3571
  if (!txSignature) {
3438
- return { verified: false, txSignature: "", reason: "invalid_input" };
3572
+ return { receivedFunds: false, txSignature: "", reason: "invalid_input" };
3439
3573
  }
3440
3574
  if (!expectedRecipient || !kit.isAddress(expectedRecipient)) {
3441
- return { verified: false, txSignature, reason: "invalid_input" };
3575
+ return { receivedFunds: false, txSignature, reason: "invalid_input" };
3442
3576
  }
3443
3577
  const cacheKey2 = `${txSignature}:${expectedRecipient}`;
3444
3578
  const cached = verifyCache.get(cacheKey2);
3445
3579
  if (cached) {
3446
- if (cached.result.verified) {
3580
+ if (cached.result.receivedFunds) {
3447
3581
  return cached.result;
3448
3582
  }
3449
3583
  if (Date.now() - cached.cachedAt < NEGATIVE_CACHE_TTL_MS) {
3450
3584
  return cached.result;
3451
3585
  }
3586
+ verifyCache.delete(cacheKey2);
3452
3587
  }
3453
3588
  const result = await doVerifyOnce(rpc, txSignature, expectedRecipient);
3589
+ if (verifyCache.size >= MAX_CACHE_ENTRIES) {
3590
+ const oldest = verifyCache.keys().next().value;
3591
+ if (oldest !== void 0) {
3592
+ verifyCache.delete(oldest);
3593
+ }
3594
+ }
3454
3595
  verifyCache.set(cacheKey2, { result, cachedAt: Date.now() });
3455
3596
  return result;
3456
3597
  }
3457
3598
  async function doVerifyOnce(rpc, txSignature, expectedRecipient) {
3458
3599
  const sigStr = txSignature;
3459
3600
  if (!rpc || typeof rpc.getTransaction !== "function") {
3460
- return { verified: false, txSignature: sigStr, reason: "rpc_error" };
3601
+ return { receivedFunds: false, txSignature: sigStr, reason: "rpc_error" };
3461
3602
  }
3462
3603
  let tx;
3463
3604
  try {
@@ -3467,13 +3608,13 @@ async function doVerifyOnce(rpc, txSignature, expectedRecipient) {
3467
3608
  maxSupportedTransactionVersion: 0
3468
3609
  }).send();
3469
3610
  } catch {
3470
- return { verified: false, txSignature: sigStr, reason: "rpc_error" };
3611
+ return { receivedFunds: false, txSignature: sigStr, reason: "rpc_error" };
3471
3612
  }
3472
3613
  if (!tx) {
3473
- return { verified: false, txSignature: sigStr, reason: "not_found" };
3614
+ return { receivedFunds: false, txSignature: sigStr, reason: "not_found" };
3474
3615
  }
3475
3616
  if (!tx.meta || tx.meta.err) {
3476
- return { verified: false, txSignature: sigStr, reason: "tx_failed" };
3617
+ return { receivedFunds: false, txSignature: sigStr, reason: "tx_failed" };
3477
3618
  }
3478
3619
  const accountKeys = tx.transaction.message.accountKeys;
3479
3620
  const recipientStr = expectedRecipient;
@@ -3487,7 +3628,7 @@ async function doVerifyOnce(rpc, txSignature, expectedRecipient) {
3487
3628
  if (pre !== void 0 && post !== void 0) {
3488
3629
  const delta = BigInt(post) - BigInt(pre);
3489
3630
  if (delta > 0n) {
3490
- return { verified: true, txSignature: sigStr };
3631
+ return { receivedFunds: true, txSignature: sigStr };
3491
3632
  }
3492
3633
  }
3493
3634
  }
@@ -3505,11 +3646,11 @@ async function doVerifyOnce(rpc, txSignature, expectedRecipient) {
3505
3646
  const preAmount = pre ? BigInt(pre.uiTokenAmount.amount) : 0n;
3506
3647
  const postAmount = BigInt(post.uiTokenAmount.amount);
3507
3648
  if (postAmount > preAmount) {
3508
- return { verified: true, txSignature: sigStr };
3649
+ return { receivedFunds: true, txSignature: sigStr };
3509
3650
  }
3510
3651
  }
3511
3652
  }
3512
- return { verified: false, txSignature: sigStr, reason: "recipient_mismatch" };
3653
+ return { receivedFunds: false, txSignature: sigStr, reason: "recipient_mismatch" };
3513
3654
  }
3514
3655
  var DEFAULT_LIMIT = 1e3;
3515
3656
  var NATIVE_KEY = "native";
@@ -3604,7 +3745,12 @@ var SessionSpendLimitEntrySchema = zod.z.object({
3604
3745
  chain: zod.z.enum(["solana"]),
3605
3746
  token: zod.z.string().min(1).max(16).regex(/^[a-z0-9]+$/, "token must be lowercase alphanumeric"),
3606
3747
  mint: zod.z.string().min(1).max(64).optional(),
3607
- amount: zod.z.number().positive().finite()
3748
+ // Stored as a string to preserve the operator's exact decimal text (avoids
3749
+ // Number round-tripping to scientific notation). Legacy configs persisted a
3750
+ // number; accept both and normalize to a positive-decimal string.
3751
+ amount: zod.z.union([zod.z.string(), zod.z.number()]).transform((value) => typeof value === "number" ? String(value) : value.trim()).refine((value) => /^\d+(?:\.\d+)?$/.test(value) && /[1-9]/.test(value), {
3752
+ message: 'amount must be a positive decimal (e.g. "0.5", "1")'
3753
+ })
3608
3754
  }).strict();
3609
3755
  var GlobalConfigSchema = zod.z.object({
3610
3756
  session_spend_limits: zod.z.array(SessionSpendLimitEntrySchema).max(16).optional()
@@ -3810,6 +3956,7 @@ exports.DEFAULT_KIND_OFFSET = DEFAULT_KIND_OFFSET;
3810
3956
  exports.DEFAULT_REDACT_PATHS = DEFAULT_REDACT_PATHS;
3811
3957
  exports.DiscoveryService = DiscoveryService;
3812
3958
  exports.ELISYM_PROTOCOL_TAG = ELISYM_PROTOCOL_TAG;
3959
+ exports.ENVELOPE_VERSION = ENVELOPE_VERSION;
3813
3960
  exports.ElisymClient = ElisymClient;
3814
3961
  exports.ElisymIdentity = ElisymIdentity;
3815
3962
  exports.GlobalConfigSchema = GlobalConfigSchema;
@@ -3858,6 +4005,8 @@ exports.compareAgentsByRank = compareAgentsByRank;
3858
4005
  exports.computeRankKey = computeRankKey;
3859
4006
  exports.createPaymentRequestWithOnchainConfig = createPaymentRequestWithOnchainConfig;
3860
4007
  exports.createSlidingWindowLimiter = createSlidingWindowLimiter;
4008
+ exports.decodeJobPayload = decodeJobPayload;
4009
+ exports.encodeJobPayload = encodeJobPayload;
3861
4010
  exports.estimateNetworkBaseline = estimateNetworkBaseline;
3862
4011
  exports.estimatePriorityFeeMicroLamports = estimatePriorityFeeMicroLamports;
3863
4012
  exports.estimateSolFeeLamports = estimateSolFeeLamports;
@@ -3881,6 +4030,7 @@ exports.resolveKnownAsset = resolveKnownAsset;
3881
4030
  exports.timeAgo = timeAgo;
3882
4031
  exports.toDTag = toDTag;
3883
4032
  exports.truncateKey = truncateKey;
4033
+ exports.utf8ByteLength = utf8ByteLength;
3884
4034
  exports.validateAgentName = validateAgentName;
3885
4035
  exports.validateExpiry = validateExpiry;
3886
4036
  exports.verifyJobPaymentQuick = verifyJobPaymentQuick;