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 +53 -6
- package/dist/cli/index.js +115 -7
- package/dist/index.cjs +146 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +104 -3
- package/dist/index.d.ts +104 -3
- package/dist/index.js +136 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
//
|
|
190
|
-
await client.setTopicCreationFee(appId, '
|
|
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
|
-
//
|
|
193
|
-
await client.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1599
|
+
isDepositExpired,
|
|
1600
|
+
isValidTimeout,
|
|
1601
|
+
keypairFromPrivateKey,
|
|
1602
|
+
timeUntilRefund
|
|
1462
1603
|
});
|
|
1463
1604
|
//# sourceMappingURL=index.cjs.map
|