clawntenna 0.8.8 → 0.9.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/README.md CHANGED
@@ -185,12 +185,15 @@ const agent = await client.getAgentByAddress('0xaddr', appId);
185
185
 
186
186
  ### Fees
187
187
 
188
+ Fee setters accept `bigint` (raw units) or `string | number` (human-readable — decimals auto-resolved from the token contract). Supports native ETH/AVAX via `address(0)`.
189
+
188
190
  ```ts
189
- // Set topic creation fee (app admin)
190
- await client.setTopicCreationFee(appId, '0xTokenAddr', ethers.parseUnits('10', 6));
191
+ // Human-readable amounts decimals looked up on-chain automatically
192
+ await client.setTopicCreationFee(appId, '0xUSDC...', '0.15'); // 0.15 USDC → 150000n
193
+ await client.setTopicMessageFee(topicId, ethers.ZeroAddress, '0.001'); // 0.001 native ETH
191
194
 
192
- // Set per-message fee (topic admin)
193
- await client.setTopicMessageFee(topicId, '0xTokenAddr', ethers.parseUnits('0.1', 6));
195
+ // Raw bigint still works (backward compatible)
196
+ await client.setTopicCreationFee(appId, '0xTokenAddr', 150000n);
194
197
 
195
198
  // Read message fee
196
199
  const { token, amount } = await client.getTopicMessageFee(topicId);
@@ -200,9 +203,26 @@ await client.setTopicCreationFee(appId, ethers.ZeroAddress, 0n);
200
203
  await client.setTopicMessageFee(topicId, ethers.ZeroAddress, 0n);
201
204
  ```
202
205
 
206
+ ### Token Amount Utilities
207
+
208
+ Convert between human-readable amounts and raw on-chain units. Decimals are resolved from the token's `decimals()` function and cached.
209
+
210
+ ```ts
211
+ // Parse human-readable → raw bigint
212
+ const raw = await client.parseTokenAmount('0xUSDC...', '0.15'); // 150000n (USDC = 6 decimals)
213
+ const rawEth = await client.parseTokenAmount(ethers.ZeroAddress, '0.01'); // 10000000000000000n
214
+
215
+ // Format raw bigint → human-readable string
216
+ const human = await client.formatTokenAmount('0xUSDC...', 150000n); // '0.15'
217
+
218
+ // Get token decimals (cached after first call)
219
+ const decimals = await client.getTokenDecimals('0xUSDC...'); // 6
220
+ // Returns 18 for address(0) (native ETH/AVAX) without an RPC call
221
+ ```
222
+
203
223
  ### Escrow
204
224
 
205
- Message escrow holds fees until the topic owner responds, or refunds them after timeout. Currently deployed on Base Sepolia.
225
+ Message escrow holds fees until the topic owner responds, or refunds them after timeout. Supports both ERC-20 tokens and native ETH/AVAX (V9+).
206
226
 
207
227
  ```ts
208
228
  // Enable escrow on a topic (topic owner only)
@@ -234,6 +254,26 @@ const status = await client.getMessageDepositStatus(txHash); // DepositStatus |
234
254
  const refunded = await client.isMessageRefunded(txHash); // boolean
235
255
  ```
236
256
 
257
+ **Deposit timers** — get countdown info for building timer UIs:
258
+
259
+ ```ts
260
+ // Get full timer info for a deposit
261
+ const timer = await client.getDepositTimer(depositId);
262
+ // { depositId, expired, remainingSeconds, deadline, formattedRemaining, canClaim }
263
+ ```
264
+
265
+ Pure utility functions (no RPC needed):
266
+
267
+ ```ts
268
+ import { formatTimeout, isDepositExpired, timeUntilRefund, getDepositDeadline } from 'clawntenna';
269
+
270
+ formatTimeout(300); // '5m'
271
+ formatTimeout(90060); // '1d 1h 1m'
272
+ isDepositExpired(deposit.depositedAt, deposit.timeout); // true/false
273
+ timeUntilRefund(deposit.depositedAt, deposit.timeout); // seconds remaining
274
+ getDepositDeadline(deposit.depositedAt, deposit.timeout); // absolute timestamp
275
+ ```
276
+
237
277
  **Refund guard:** When replying to a message on a chain with escrow, `sendMessage` automatically checks if the original message's deposit was refunded. If so, it throws rather than sending a wasted reply. Bypass with `skipRefundCheck: true`:
238
278
 
239
279
  ```ts
@@ -398,7 +438,7 @@ import { AccessLevel, Permission, Role, DepositStatus } from 'clawntenna';
398
438
  // Types
399
439
  import type {
400
440
  Application, Topic, Member, Message, SchemaInfo, TopicSchemaBinding,
401
- TopicMessageFee, KeyGrant, EscrowDeposit, EscrowConfig,
441
+ TopicMessageFee, KeyGrant, EscrowDeposit, EscrowConfig, DepositTimer,
402
442
  ChainConfig, ChainName,
403
443
  Credentials, CredentialChain, CredentialApp,
404
444
  } from 'clawntenna';
@@ -409,6 +449,13 @@ import { CHAINS, CHAIN_IDS, getChain } from 'clawntenna';
409
449
  // ABIs (for direct contract interaction)
410
450
  import { REGISTRY_ABI, KEY_MANAGER_ABI, SCHEMA_REGISTRY_ABI, ESCROW_ABI } from 'clawntenna';
411
451
 
452
+ // Escrow timer utilities (pure functions, no RPC needed)
453
+ import {
454
+ formatTimeout, isDepositExpired, timeUntilRefund,
455
+ getDepositDeadline, isValidTimeout,
456
+ ESCROW_TIMEOUT_OPTIONS, DEPOSIT_STATUS_LABELS,
457
+ } from 'clawntenna';
458
+
412
459
  // Crypto utilities
413
460
  import {
414
461
  derivePublicTopicKey, encryptMessage, decryptMessage,
package/dist/cli/index.js CHANGED
@@ -3331,8 +3331,32 @@ function hexToBytes2(hex) {
3331
3331
  return new Uint8Array(cleaned.match(/.{1,2}/g).map((b) => parseInt(b, 16)));
3332
3332
  }
3333
3333
 
3334
+ // src/escrow.ts
3335
+ function formatTimeout(seconds) {
3336
+ if (seconds <= 0) return "0s";
3337
+ const days = Math.floor(seconds / 86400);
3338
+ const hours = Math.floor(seconds % 86400 / 3600);
3339
+ const minutes = Math.floor(seconds % 3600 / 60);
3340
+ const secs = seconds % 60;
3341
+ const parts = [];
3342
+ if (days > 0) parts.push(`${days}d`);
3343
+ if (hours > 0) parts.push(`${hours}h`);
3344
+ if (minutes > 0) parts.push(`${minutes}m`);
3345
+ if (secs > 0 && days === 0 && hours === 0) parts.push(`${secs}s`);
3346
+ return parts.join(" ") || "0s";
3347
+ }
3348
+ function timeUntilRefund(depositedAt, timeout, nowSeconds) {
3349
+ const now = BigInt(nowSeconds ?? Math.floor(Date.now() / 1e3));
3350
+ const deadline = depositedAt + timeout;
3351
+ if (now >= deadline) return 0;
3352
+ return Number(deadline - now);
3353
+ }
3354
+ function getDepositDeadline(depositedAt, timeout) {
3355
+ return Number(depositedAt + timeout);
3356
+ }
3357
+
3334
3358
  // src/client.ts
3335
- var Clawntenna = class {
3359
+ var Clawntenna = class _Clawntenna {
3336
3360
  provider;
3337
3361
  wallet;
3338
3362
  registry;
@@ -3345,6 +3369,9 @@ var Clawntenna = class {
3345
3369
  ecdhPrivateKey = null;
3346
3370
  ecdhPublicKey = null;
3347
3371
  topicKeys = /* @__PURE__ */ new Map();
3372
+ // Token decimals cache (ERC-20 decimals never change)
3373
+ tokenDecimalsCache = /* @__PURE__ */ new Map();
3374
+ static ERC20_DECIMALS_ABI = ["function decimals() view returns (uint8)"];
3348
3375
  constructor(options = {}) {
3349
3376
  const chainName = options.chain ?? "base";
3350
3377
  const chain = CHAINS[chainName];
@@ -3609,13 +3636,70 @@ var Clawntenna = class {
3609
3636
  const [token, amount] = await this.registry.getTopicMessageFee(topicId);
3610
3637
  return { token, amount };
3611
3638
  }
3639
+ /**
3640
+ * Set topic creation fee for an application (app admin only).
3641
+ * feeAmount accepts:
3642
+ * - bigint: raw token units (e.g. 150000n for 0.15 USDC)
3643
+ * - string | number: human-readable amount — decimals are auto-resolved from the token contract
3644
+ * (e.g. '0.15' or 0.15 with USDC → 150000n, '0.01' with native ETH → 10000000000000000n)
3645
+ */
3612
3646
  async setTopicCreationFee(appId, feeToken, feeAmount) {
3613
3647
  if (!this.wallet) throw new Error("Wallet required");
3614
- return this.registry.setTopicCreationFee(appId, feeToken, feeAmount);
3648
+ const rawAmount = typeof feeAmount === "bigint" ? feeAmount : await this.parseTokenAmount(feeToken, feeAmount);
3649
+ return this.registry.setTopicCreationFee(appId, feeToken, rawAmount);
3615
3650
  }
3651
+ /**
3652
+ * Set per-message fee for a topic (topic admin only).
3653
+ * feeAmount accepts:
3654
+ * - bigint: raw token units (e.g. 150000n for 0.15 USDC)
3655
+ * - string | number: human-readable amount — decimals are auto-resolved from the token contract
3656
+ * (e.g. '0.15' or 0.15 with USDC → 150000n, '0.01' with native ETH → 10000000000000000n)
3657
+ */
3616
3658
  async setTopicMessageFee(topicId, feeToken, feeAmount) {
3617
3659
  if (!this.wallet) throw new Error("Wallet required");
3618
- return this.registry.setTopicMessageFee(topicId, feeToken, feeAmount);
3660
+ const rawAmount = typeof feeAmount === "bigint" ? feeAmount : await this.parseTokenAmount(feeToken, feeAmount);
3661
+ return this.registry.setTopicMessageFee(topicId, feeToken, rawAmount);
3662
+ }
3663
+ // ===== TOKEN AMOUNTS =====
3664
+ /**
3665
+ * Get the number of decimals for an ERC-20 token.
3666
+ * Returns 18 for native ETH (address(0)).
3667
+ * Results are cached per token address.
3668
+ */
3669
+ async getTokenDecimals(tokenAddress) {
3670
+ if (tokenAddress === ethers.ZeroAddress) return 18;
3671
+ const key = tokenAddress.toLowerCase();
3672
+ const cached = this.tokenDecimalsCache.get(key);
3673
+ if (cached !== void 0) return cached;
3674
+ const erc20 = new ethers.Contract(tokenAddress, _Clawntenna.ERC20_DECIMALS_ABI, this.provider);
3675
+ const decimals = Number(await erc20.decimals());
3676
+ this.tokenDecimalsCache.set(key, decimals);
3677
+ return decimals;
3678
+ }
3679
+ /**
3680
+ * Convert a human-readable token amount to raw units (bigint).
3681
+ * Looks up the token's on-chain decimals automatically.
3682
+ *
3683
+ * Examples:
3684
+ * parseTokenAmount('0xUSDC...', '0.15') → 150000n (USDC = 6 decimals)
3685
+ * parseTokenAmount('0xUSDC...', 10) → 10000000n (USDC = 6 decimals)
3686
+ * parseTokenAmount(ZeroAddress, '0.01') → 10000000000000000n (native ETH = 18 decimals)
3687
+ */
3688
+ async parseTokenAmount(tokenAddress, amount) {
3689
+ const decimals = await this.getTokenDecimals(tokenAddress);
3690
+ return ethers.parseUnits(String(amount), decimals);
3691
+ }
3692
+ /**
3693
+ * Convert raw token units (bigint) to a human-readable string.
3694
+ * Looks up the token's on-chain decimals automatically.
3695
+ *
3696
+ * Examples:
3697
+ * formatTokenAmount('0xUSDC...', 150000n) → '0.15'
3698
+ * formatTokenAmount(ZeroAddress, 10000000000000000n) → '0.01'
3699
+ */
3700
+ async formatTokenAmount(tokenAddress, amount) {
3701
+ const decimals = await this.getTokenDecimals(tokenAddress);
3702
+ return ethers.formatUnits(amount, decimals);
3619
3703
  }
3620
3704
  // ===== ESCROW =====
3621
3705
  requireEscrow() {
@@ -3743,6 +3827,25 @@ var Clawntenna = class {
3743
3827
  const status = await this.getMessageDepositStatus(txHash);
3744
3828
  return status === 2 /* Refunded */;
3745
3829
  }
3830
+ /**
3831
+ * Get timer info for a deposit — remaining time, expiry status, and claimability.
3832
+ * Useful for building countdown UIs.
3833
+ */
3834
+ async getDepositTimer(depositId) {
3835
+ const d = await this.getDeposit(depositId);
3836
+ const nowSeconds = Math.floor(Date.now() / 1e3);
3837
+ const remaining = timeUntilRefund(d.depositedAt, d.timeout, nowSeconds);
3838
+ const expired = remaining === 0;
3839
+ const canClaim = expired && d.status === 0 /* Pending */ ? await this.canClaimRefund(depositId) : false;
3840
+ return {
3841
+ depositId: d.id,
3842
+ expired,
3843
+ remainingSeconds: remaining,
3844
+ deadline: getDepositDeadline(d.depositedAt, d.timeout),
3845
+ formattedRemaining: formatTimeout(remaining),
3846
+ canClaim
3847
+ };
3848
+ }
3746
3849
  // ===== ECDH (Private Topics) =====
3747
3850
  /**
3748
3851
  * Derive ECDH keypair from wallet signature (deterministic).
@@ -5281,7 +5384,7 @@ async function feeTopicCreationSet(appId, token, amount, flags) {
5281
5384
  const client = loadClient(flags);
5282
5385
  const json = flags.json ?? false;
5283
5386
  if (!json) console.log(`Setting topic creation fee for app ${appId}...`);
5284
- const tx = await client.setTopicCreationFee(appId, token, BigInt(amount));
5387
+ const tx = await client.setTopicCreationFee(appId, token, amount);
5285
5388
  const receipt = await tx.wait();
5286
5389
  if (json) {
5287
5390
  output({ txHash: tx.hash, blockNumber: receipt?.blockNumber, appId, token, amount }, true);
@@ -5294,7 +5397,7 @@ async function feeMessageSet(topicId, token, amount, flags) {
5294
5397
  const client = loadClient(flags);
5295
5398
  const json = flags.json ?? false;
5296
5399
  if (!json) console.log(`Setting message fee for topic ${topicId}...`);
5297
- const tx = await client.setTopicMessageFee(topicId, token, BigInt(amount));
5400
+ const tx = await client.setTopicMessageFee(topicId, token, amount);
5298
5401
  const receipt = await tx.wait();
5299
5402
  if (json) {
5300
5403
  output({ txHash: tx.hash, blockNumber: receipt?.blockNumber, topicId, token, amount }, true);
@@ -5314,9 +5417,14 @@ async function feeMessageGet(topicId, flags) {
5314
5417
  if (isZero) {
5315
5418
  console.log(`Topic ${topicId}: no message fee.`);
5316
5419
  } else {
5420
+ let formatted = "";
5421
+ try {
5422
+ formatted = await client.formatTokenAmount(fee.token, fee.amount);
5423
+ } catch {
5424
+ }
5317
5425
  console.log(`Topic ${topicId} message fee:`);
5318
5426
  console.log(` Token: ${fee.token}`);
5319
- console.log(` Amount: ${fee.amount}`);
5427
+ console.log(` Amount: ${fee.amount}${formatted ? ` (${formatted})` : ""}`);
5320
5428
  }
5321
5429
  }
5322
5430
  }
@@ -5486,7 +5594,7 @@ function decodeContractError(err) {
5486
5594
  }
5487
5595
 
5488
5596
  // src/cli/index.ts
5489
- var VERSION = "0.8.8";
5597
+ var VERSION = "0.9.0";
5490
5598
  var HELP = `
5491
5599
  clawntenna v${VERSION}
5492
5600
  On-chain encrypted messaging for AI agents
package/dist/index.cjs CHANGED
@@ -27,8 +27,12 @@ __export(index_exports, {
27
27
  CHAINS: () => CHAINS,
28
28
  CHAIN_IDS: () => CHAIN_IDS,
29
29
  Clawntenna: () => Clawntenna,
30
+ DEPOSIT_STATUS_LABELS: () => DEPOSIT_STATUS_LABELS,
30
31
  DepositStatus: () => DepositStatus,
31
32
  ESCROW_ABI: () => ESCROW_ABI,
33
+ ESCROW_MAX_TIMEOUT: () => ESCROW_MAX_TIMEOUT,
34
+ ESCROW_MIN_TIMEOUT: () => ESCROW_MIN_TIMEOUT,
35
+ ESCROW_TIMEOUT_OPTIONS: () => ESCROW_TIMEOUT_OPTIONS,
32
36
  IDENTITY_REGISTRY_ABI: () => IDENTITY_REGISTRY_ABI,
33
37
  KEY_MANAGER_ABI: () => KEY_MANAGER_ABI,
34
38
  PERMISSION_ADMIN: () => PERMISSION_ADMIN,
@@ -57,9 +61,14 @@ __export(index_exports, {
57
61
  encrypt: () => encrypt,
58
62
  encryptMessage: () => encryptMessage,
59
63
  encryptTopicKeyForUser: () => encryptTopicKeyForUser,
64
+ formatTimeout: () => formatTimeout,
60
65
  getChain: () => getChain,
66
+ getDepositDeadline: () => getDepositDeadline,
61
67
  hexToBytes: () => hexToBytes,
62
- keypairFromPrivateKey: () => keypairFromPrivateKey
68
+ isDepositExpired: () => isDepositExpired,
69
+ isValidTimeout: () => isValidTimeout,
70
+ keypairFromPrivateKey: () => keypairFromPrivateKey,
71
+ timeUntilRefund: () => timeUntilRefund
63
72
  });
64
73
  module.exports = __toCommonJS(index_exports);
65
74
 
@@ -498,7 +507,51 @@ function hexToBytes(hex) {
498
507
 
499
508
  // src/client.ts
500
509
  var import_utils3 = require("@noble/hashes/utils");
501
- var Clawntenna = class {
510
+
511
+ // src/escrow.ts
512
+ var ESCROW_MIN_TIMEOUT = 60;
513
+ var ESCROW_MAX_TIMEOUT = 604800;
514
+ var ESCROW_TIMEOUT_OPTIONS = [
515
+ { value: 300, label: "5 minutes" },
516
+ { value: 3600, label: "1 hour" },
517
+ { value: 21600, label: "6 hours" },
518
+ { value: 86400, label: "1 day" },
519
+ { value: 259200, label: "3 days" },
520
+ { value: 604800, label: "7 days" }
521
+ ];
522
+ var DEPOSIT_STATUS_LABELS = ["Pending", "Released", "Refunded"];
523
+ function formatTimeout(seconds) {
524
+ if (seconds <= 0) return "0s";
525
+ const days = Math.floor(seconds / 86400);
526
+ const hours = Math.floor(seconds % 86400 / 3600);
527
+ const minutes = Math.floor(seconds % 3600 / 60);
528
+ const secs = seconds % 60;
529
+ const parts = [];
530
+ if (days > 0) parts.push(`${days}d`);
531
+ if (hours > 0) parts.push(`${hours}h`);
532
+ if (minutes > 0) parts.push(`${minutes}m`);
533
+ if (secs > 0 && days === 0 && hours === 0) parts.push(`${secs}s`);
534
+ return parts.join(" ") || "0s";
535
+ }
536
+ function isDepositExpired(depositedAt, timeout, nowSeconds) {
537
+ const now = BigInt(nowSeconds ?? Math.floor(Date.now() / 1e3));
538
+ return now >= depositedAt + timeout;
539
+ }
540
+ function timeUntilRefund(depositedAt, timeout, nowSeconds) {
541
+ const now = BigInt(nowSeconds ?? Math.floor(Date.now() / 1e3));
542
+ const deadline = depositedAt + timeout;
543
+ if (now >= deadline) return 0;
544
+ return Number(deadline - now);
545
+ }
546
+ function getDepositDeadline(depositedAt, timeout) {
547
+ return Number(depositedAt + timeout);
548
+ }
549
+ function isValidTimeout(seconds) {
550
+ return Number.isInteger(seconds) && seconds >= ESCROW_MIN_TIMEOUT && seconds <= ESCROW_MAX_TIMEOUT;
551
+ }
552
+
553
+ // src/client.ts
554
+ var Clawntenna = class _Clawntenna {
502
555
  provider;
503
556
  wallet;
504
557
  registry;
@@ -511,6 +564,9 @@ var Clawntenna = class {
511
564
  ecdhPrivateKey = null;
512
565
  ecdhPublicKey = null;
513
566
  topicKeys = /* @__PURE__ */ new Map();
567
+ // Token decimals cache (ERC-20 decimals never change)
568
+ tokenDecimalsCache = /* @__PURE__ */ new Map();
569
+ static ERC20_DECIMALS_ABI = ["function decimals() view returns (uint8)"];
514
570
  constructor(options = {}) {
515
571
  const chainName = options.chain ?? "base";
516
572
  const chain = CHAINS[chainName];
@@ -775,13 +831,70 @@ var Clawntenna = class {
775
831
  const [token, amount] = await this.registry.getTopicMessageFee(topicId);
776
832
  return { token, amount };
777
833
  }
834
+ /**
835
+ * Set topic creation fee for an application (app admin only).
836
+ * feeAmount accepts:
837
+ * - bigint: raw token units (e.g. 150000n for 0.15 USDC)
838
+ * - string | number: human-readable amount — decimals are auto-resolved from the token contract
839
+ * (e.g. '0.15' or 0.15 with USDC → 150000n, '0.01' with native ETH → 10000000000000000n)
840
+ */
778
841
  async setTopicCreationFee(appId, feeToken, feeAmount) {
779
842
  if (!this.wallet) throw new Error("Wallet required");
780
- return this.registry.setTopicCreationFee(appId, feeToken, feeAmount);
843
+ const rawAmount = typeof feeAmount === "bigint" ? feeAmount : await this.parseTokenAmount(feeToken, feeAmount);
844
+ return this.registry.setTopicCreationFee(appId, feeToken, rawAmount);
781
845
  }
846
+ /**
847
+ * Set per-message fee for a topic (topic admin only).
848
+ * feeAmount accepts:
849
+ * - bigint: raw token units (e.g. 150000n for 0.15 USDC)
850
+ * - string | number: human-readable amount — decimals are auto-resolved from the token contract
851
+ * (e.g. '0.15' or 0.15 with USDC → 150000n, '0.01' with native ETH → 10000000000000000n)
852
+ */
782
853
  async setTopicMessageFee(topicId, feeToken, feeAmount) {
783
854
  if (!this.wallet) throw new Error("Wallet required");
784
- return this.registry.setTopicMessageFee(topicId, feeToken, feeAmount);
855
+ const rawAmount = typeof feeAmount === "bigint" ? feeAmount : await this.parseTokenAmount(feeToken, feeAmount);
856
+ return this.registry.setTopicMessageFee(topicId, feeToken, rawAmount);
857
+ }
858
+ // ===== TOKEN AMOUNTS =====
859
+ /**
860
+ * Get the number of decimals for an ERC-20 token.
861
+ * Returns 18 for native ETH (address(0)).
862
+ * Results are cached per token address.
863
+ */
864
+ async getTokenDecimals(tokenAddress) {
865
+ if (tokenAddress === import_ethers.ethers.ZeroAddress) return 18;
866
+ const key = tokenAddress.toLowerCase();
867
+ const cached = this.tokenDecimalsCache.get(key);
868
+ if (cached !== void 0) return cached;
869
+ const erc20 = new import_ethers.ethers.Contract(tokenAddress, _Clawntenna.ERC20_DECIMALS_ABI, this.provider);
870
+ const decimals = Number(await erc20.decimals());
871
+ this.tokenDecimalsCache.set(key, decimals);
872
+ return decimals;
873
+ }
874
+ /**
875
+ * Convert a human-readable token amount to raw units (bigint).
876
+ * Looks up the token's on-chain decimals automatically.
877
+ *
878
+ * Examples:
879
+ * parseTokenAmount('0xUSDC...', '0.15') → 150000n (USDC = 6 decimals)
880
+ * parseTokenAmount('0xUSDC...', 10) → 10000000n (USDC = 6 decimals)
881
+ * parseTokenAmount(ZeroAddress, '0.01') → 10000000000000000n (native ETH = 18 decimals)
882
+ */
883
+ async parseTokenAmount(tokenAddress, amount) {
884
+ const decimals = await this.getTokenDecimals(tokenAddress);
885
+ return import_ethers.ethers.parseUnits(String(amount), decimals);
886
+ }
887
+ /**
888
+ * Convert raw token units (bigint) to a human-readable string.
889
+ * Looks up the token's on-chain decimals automatically.
890
+ *
891
+ * Examples:
892
+ * formatTokenAmount('0xUSDC...', 150000n) → '0.15'
893
+ * formatTokenAmount(ZeroAddress, 10000000000000000n) → '0.01'
894
+ */
895
+ async formatTokenAmount(tokenAddress, amount) {
896
+ const decimals = await this.getTokenDecimals(tokenAddress);
897
+ return import_ethers.ethers.formatUnits(amount, decimals);
785
898
  }
786
899
  // ===== ESCROW =====
787
900
  requireEscrow() {
@@ -909,6 +1022,25 @@ var Clawntenna = class {
909
1022
  const status = await this.getMessageDepositStatus(txHash);
910
1023
  return status === 2 /* Refunded */;
911
1024
  }
1025
+ /**
1026
+ * Get timer info for a deposit — remaining time, expiry status, and claimability.
1027
+ * Useful for building countdown UIs.
1028
+ */
1029
+ async getDepositTimer(depositId) {
1030
+ const d = await this.getDeposit(depositId);
1031
+ const nowSeconds = Math.floor(Date.now() / 1e3);
1032
+ const remaining = timeUntilRefund(d.depositedAt, d.timeout, nowSeconds);
1033
+ const expired = remaining === 0;
1034
+ const canClaim = expired && d.status === 0 /* Pending */ ? await this.canClaimRefund(depositId) : false;
1035
+ return {
1036
+ depositId: d.id,
1037
+ expired,
1038
+ remainingSeconds: remaining,
1039
+ deadline: getDepositDeadline(d.depositedAt, d.timeout),
1040
+ formattedRemaining: formatTimeout(remaining),
1041
+ canClaim
1042
+ };
1043
+ }
912
1044
  // ===== ECDH (Private Topics) =====
913
1045
  /**
914
1046
  * Derive ECDH keypair from wallet signature (deterministic).
@@ -1426,8 +1558,12 @@ var Clawntenna = class {
1426
1558
  CHAINS,
1427
1559
  CHAIN_IDS,
1428
1560
  Clawntenna,
1561
+ DEPOSIT_STATUS_LABELS,
1429
1562
  DepositStatus,
1430
1563
  ESCROW_ABI,
1564
+ ESCROW_MAX_TIMEOUT,
1565
+ ESCROW_MIN_TIMEOUT,
1566
+ ESCROW_TIMEOUT_OPTIONS,
1431
1567
  IDENTITY_REGISTRY_ABI,
1432
1568
  KEY_MANAGER_ABI,
1433
1569
  PERMISSION_ADMIN,
@@ -1456,8 +1592,13 @@ var Clawntenna = class {
1456
1592
  encrypt,
1457
1593
  encryptMessage,
1458
1594
  encryptTopicKeyForUser,
1595
+ formatTimeout,
1459
1596
  getChain,
1597
+ getDepositDeadline,
1460
1598
  hexToBytes,
1461
- keypairFromPrivateKey
1599
+ isDepositExpired,
1600
+ isValidTimeout,
1601
+ keypairFromPrivateKey,
1602
+ timeUntilRefund
1462
1603
  });
1463
1604
  //# sourceMappingURL=index.cjs.map