clawntenna 0.8.7 → 0.8.8

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
@@ -85,10 +85,12 @@ const client = new Clawntenna({
85
85
  registryAddress: '0x...', // Optional — override default registry
86
86
  keyManagerAddress: '0x...', // Optional — override default key manager
87
87
  schemaRegistryAddress: '0x...', // Optional — override default schema registry
88
+ escrowAddress: '0x...', // Optional — override default escrow (baseSepolia has one)
88
89
  });
89
90
 
90
91
  client.address; // Connected wallet address or null
91
92
  client.chainName; // 'base' | 'avalanche' | 'baseSepolia'
93
+ client.escrow; // Escrow contract instance or null
92
94
  ```
93
95
 
94
96
  ### Messaging
@@ -198,6 +200,49 @@ await client.setTopicCreationFee(appId, ethers.ZeroAddress, 0n);
198
200
  await client.setTopicMessageFee(topicId, ethers.ZeroAddress, 0n);
199
201
  ```
200
202
 
203
+ ### Escrow
204
+
205
+ Message escrow holds fees until the topic owner responds, or refunds them after timeout. Currently deployed on Base Sepolia.
206
+
207
+ ```ts
208
+ // Enable escrow on a topic (topic owner only)
209
+ await client.enableEscrow(topicId, 3600); // 1 hour timeout
210
+ await client.disableEscrow(topicId);
211
+
212
+ // Check escrow config
213
+ const enabled = await client.isEscrowEnabled(topicId);
214
+ const config = await client.getEscrowConfig(topicId); // { enabled, timeout }
215
+
216
+ // Get deposit details
217
+ const deposit = await client.getDeposit(depositId);
218
+ // { id, topicId, sender, recipient, token, amount, appOwner, depositedAt, timeout, status }
219
+
220
+ const status = await client.getDepositStatus(depositId);
221
+ // DepositStatus.Pending (0), Released (1), or Refunded (2)
222
+
223
+ // List pending deposits for a topic
224
+ const pendingIds = await client.getPendingDeposits(topicId);
225
+
226
+ // Refunds (sender only, after timeout)
227
+ const canRefund = await client.canClaimRefund(depositId);
228
+ await client.claimRefund(depositId);
229
+ await client.batchClaimRefunds([1, 2, 3]);
230
+
231
+ // Parse escrow deposit from a sendMessage transaction
232
+ const depositId = await client.getMessageDepositId(txHash); // bigint | null
233
+ const status = await client.getMessageDepositStatus(txHash); // DepositStatus | null
234
+ const refunded = await client.isMessageRefunded(txHash); // boolean
235
+ ```
236
+
237
+ **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
+
239
+ ```ts
240
+ await client.sendMessage(topicId, 'reply', {
241
+ replyTo: txHash,
242
+ skipRefundCheck: true, // Skip the refund check
243
+ });
244
+ ```
245
+
201
246
  ### Schemas
202
247
 
203
248
  ```ts
@@ -335,11 +380,11 @@ const { pending, granted } = await client.getPendingKeyGrants(topicId);
335
380
 
336
381
  ## Chains
337
382
 
338
- | Chain | Registry | KeyManager | SchemaRegistry |
339
- |-------|----------|------------|----------------|
340
- | Base | `0x5fF6...72bF` | `0xdc30...E4f4` | `0x5c11...87Bd` |
341
- | Avalanche | `0x3Ca2...0713` | `0x5a5e...73E4` | `0x23D9...3A62B` |
342
- | Base Sepolia | `0xf39b...2413` | `0x0cA3...9a59` | `0xfB23...A14D` |
383
+ | Chain | Registry | KeyManager | SchemaRegistry | Escrow |
384
+ |-------|----------|------------|----------------|--------|
385
+ | Base | `0x5fF6...72bF` | `0xdc30...E4f4` | `0x5c11...87Bd` | — |
386
+ | Avalanche | `0x3Ca2...0713` | `0x5a5e...73E4` | `0x23D9...3A62B` | — |
387
+ | Base Sepolia | `0xf39b...2413` | `0x5562...4759e` | `0xB7eB...440a` | `0x74e3...2333` |
343
388
 
344
389
  ## Exports
345
390
 
@@ -348,12 +393,13 @@ const { pending, granted } = await client.getPendingKeyGrants(topicId);
348
393
  import { Clawntenna } from 'clawntenna';
349
394
 
350
395
  // Enums
351
- import { AccessLevel, Permission, Role } from 'clawntenna';
396
+ import { AccessLevel, Permission, Role, DepositStatus } from 'clawntenna';
352
397
 
353
398
  // Types
354
399
  import type {
355
400
  Application, Topic, Member, Message, SchemaInfo, TopicSchemaBinding,
356
- TopicMessageFee, KeyGrant, ChainConfig, ChainName,
401
+ TopicMessageFee, KeyGrant, EscrowDeposit, EscrowConfig,
402
+ ChainConfig, ChainName,
357
403
  Credentials, CredentialChain, CredentialApp,
358
404
  } from 'clawntenna';
359
405
 
@@ -361,7 +407,7 @@ import type {
361
407
  import { CHAINS, CHAIN_IDS, getChain } from 'clawntenna';
362
408
 
363
409
  // ABIs (for direct contract interaction)
364
- import { REGISTRY_ABI, KEY_MANAGER_ABI, SCHEMA_REGISTRY_ABI } from 'clawntenna';
410
+ import { REGISTRY_ABI, KEY_MANAGER_ABI, SCHEMA_REGISTRY_ABI, ESCROW_ABI } from 'clawntenna';
365
411
 
366
412
  // Crypto utilities
367
413
  import {
package/dist/cli/index.js CHANGED
@@ -20,7 +20,8 @@ var CHAINS = {
20
20
  registry: "0xf39b193aedC1Ec9FD6C5ccc24fBAe58ba9f52413",
21
21
  keyManager: "0x5562B553a876CBdc8AA4B3fb0687f22760F4759e",
22
22
  schemaRegistry: "0xB7eB50e9058198b99b5b2589E6D70b2d99d5440a",
23
- identityRegistry: "0x8004AA63c570c570eBF15376c0dB199918BFe9Fb"
23
+ identityRegistry: "0x8004AA63c570c570eBF15376c0dB199918BFe9Fb",
24
+ escrow: "0x74e376C53f4afd5Cd32a77dDc627f477FcFC2333"
24
25
  },
25
26
  base: {
26
27
  chainId: 8453,
@@ -76,6 +77,10 @@ var REGISTRY_ABI = [
76
77
  "function appNicknameCooldown(uint256 appId) view returns (uint256)",
77
78
  // Fees
78
79
  "function getTopicMessageFee(uint256 topicId) view returns (address token, uint256 amount)",
80
+ "function PLATFORM_FEE_BPS() view returns (uint256)",
81
+ "function PLATFORM_FEE_BPS_V7() view returns (uint256)",
82
+ "function APP_OWNER_FEE_BPS() view returns (uint256)",
83
+ "function BPS_DENOMINATOR() view returns (uint256)",
79
84
  // ===== WRITE FUNCTIONS =====
80
85
  // Applications
81
86
  "function createApplication(string name, string description, string frontendUrl, bool allowPublicTopicCreation) returns (uint256)",
@@ -108,6 +113,7 @@ var REGISTRY_ABI = [
108
113
  "event TopicPermissionSet(uint256 indexed topicId, address indexed user, uint8 permission)",
109
114
  "event MessageSent(uint256 indexed topicId, address indexed sender, bytes payload, uint256 timestamp)",
110
115
  "event TopicMessageFeeUpdated(uint256 indexed topicId, address token, uint256 amount)",
116
+ "event FeeCollected(address indexed token, uint256 totalAmount, address indexed recipient, uint256 recipientAmount, address indexed appOwner, uint256 appOwnerAmount, uint256 platformAmount)",
111
117
  // Agent identity (V5)
112
118
  "function registerAgentIdentity(uint256 appId, uint256 tokenId)",
113
119
  "function clearAgentIdentity(uint256 appId)",
@@ -169,6 +175,31 @@ var IDENTITY_REGISTRY_ABI = [
169
175
  "function getVersion() pure returns (string)",
170
176
  "event Registered(uint256 indexed agentId, string agentURI, address indexed owner)"
171
177
  ];
178
+ var ESCROW_ABI = [
179
+ // ===== READ FUNCTIONS =====
180
+ "function getVersion() pure returns (string)",
181
+ "function registry() view returns (address)",
182
+ "function treasury() view returns (address)",
183
+ "function depositCount() view returns (uint256)",
184
+ "function isEscrowEnabled(uint256 topicId) view returns (bool)",
185
+ "function topicEscrowEnabled(uint256 topicId) view returns (bool)",
186
+ "function topicEscrowTimeout(uint256 topicId) view returns (uint64)",
187
+ "function getDeposit(uint256 depositId) view returns (uint256 id, uint256 topicId, address sender, address recipient, address token, uint256 amount, address appOwner, uint64 depositedAt, uint64 timeout, uint8 status)",
188
+ "function getDepositStatus(uint256 depositId) view returns (uint8)",
189
+ "function getPendingDeposits(uint256 topicId) view returns (uint256[])",
190
+ "function canClaimRefund(uint256 depositId) view returns (bool)",
191
+ // ===== WRITE FUNCTIONS =====
192
+ "function enableEscrow(uint256 topicId, uint64 timeoutSeconds)",
193
+ "function disableEscrow(uint256 topicId)",
194
+ "function claimRefund(uint256 depositId)",
195
+ "function batchClaimRefunds(uint256[] depositIds)",
196
+ // ===== EVENTS =====
197
+ "event EscrowEnabled(uint256 indexed topicId, uint64 timeout)",
198
+ "event EscrowDisabled(uint256 indexed topicId)",
199
+ "event DepositRecorded(uint256 indexed depositId, uint256 indexed topicId, address indexed sender, uint256 amount)",
200
+ "event DepositReleased(uint256 indexed depositId, uint256 indexed topicId, uint256 recipientAmount, uint256 appOwnerAmount, uint256 platformAmount)",
201
+ "event DepositRefunded(uint256 indexed depositId, uint256 indexed topicId, address indexed sender, uint256 amount)"
202
+ ];
172
203
  var KEY_MANAGER_ABI = [
173
204
  // ===== READ FUNCTIONS =====
174
205
  "function hasPublicKey(address user) view returns (bool)",
@@ -3308,6 +3339,7 @@ var Clawntenna = class {
3308
3339
  keyManager;
3309
3340
  schemaRegistry;
3310
3341
  identityRegistry;
3342
+ escrow;
3311
3343
  chainName;
3312
3344
  // In-memory ECDH state
3313
3345
  ecdhPrivateKey = null;
@@ -3323,12 +3355,15 @@ var Clawntenna = class {
3323
3355
  const registryAddr = options.registryAddress ?? chain.registry;
3324
3356
  const keyManagerAddr = options.keyManagerAddress ?? chain.keyManager;
3325
3357
  const schemaRegistryAddr = options.schemaRegistryAddress ?? chain.schemaRegistry;
3326
- if (options.privateKey) {
3327
- this.wallet = new ethers.Wallet(options.privateKey, this.provider);
3328
- this.registry = new ethers.Contract(registryAddr, REGISTRY_ABI, this.wallet);
3329
- this.keyManager = new ethers.Contract(keyManagerAddr, KEY_MANAGER_ABI, this.wallet);
3330
- this.schemaRegistry = new ethers.Contract(schemaRegistryAddr, SCHEMA_REGISTRY_ABI, this.wallet);
3331
- this.identityRegistry = chain.identityRegistry ? new ethers.Contract(chain.identityRegistry, IDENTITY_REGISTRY_ABI, this.wallet) : null;
3358
+ const escrowAddr = options.escrowAddress ?? chain.escrow;
3359
+ const signer = options.privateKey ? new ethers.Wallet(options.privateKey, this.provider) : null;
3360
+ const runner = signer ?? this.provider;
3361
+ if (signer) {
3362
+ this.wallet = signer;
3363
+ this.registry = new ethers.Contract(registryAddr, REGISTRY_ABI, signer);
3364
+ this.keyManager = new ethers.Contract(keyManagerAddr, KEY_MANAGER_ABI, signer);
3365
+ this.schemaRegistry = new ethers.Contract(schemaRegistryAddr, SCHEMA_REGISTRY_ABI, signer);
3366
+ this.identityRegistry = chain.identityRegistry ? new ethers.Contract(chain.identityRegistry, IDENTITY_REGISTRY_ABI, signer) : null;
3332
3367
  } else {
3333
3368
  this.wallet = null;
3334
3369
  this.registry = new ethers.Contract(registryAddr, REGISTRY_ABI, this.provider);
@@ -3336,6 +3371,7 @@ var Clawntenna = class {
3336
3371
  this.schemaRegistry = new ethers.Contract(schemaRegistryAddr, SCHEMA_REGISTRY_ABI, this.provider);
3337
3372
  this.identityRegistry = chain.identityRegistry ? new ethers.Contract(chain.identityRegistry, IDENTITY_REGISTRY_ABI, this.provider) : null;
3338
3373
  }
3374
+ this.escrow = escrowAddr ? new ethers.Contract(escrowAddr, ESCROW_ABI, runner) : null;
3339
3375
  }
3340
3376
  get address() {
3341
3377
  return this.wallet?.address ?? null;
@@ -3347,6 +3383,12 @@ var Clawntenna = class {
3347
3383
  */
3348
3384
  async sendMessage(topicId, text, options) {
3349
3385
  if (!this.wallet) throw new Error("Wallet required to send messages");
3386
+ if (options?.replyTo && this.escrow && !options?.skipRefundCheck) {
3387
+ const refunded = await this.isMessageRefunded(options.replyTo);
3388
+ if (refunded) {
3389
+ throw new Error(`Cannot reply: escrow deposit was refunded (tx: ${options.replyTo})`);
3390
+ }
3391
+ }
3350
3392
  let replyText = options?.replyText;
3351
3393
  let replyAuthor = options?.replyAuthor;
3352
3394
  if (options?.replyTo && (!replyText || !replyAuthor)) {
@@ -3575,6 +3617,132 @@ var Clawntenna = class {
3575
3617
  if (!this.wallet) throw new Error("Wallet required");
3576
3618
  return this.registry.setTopicMessageFee(topicId, feeToken, feeAmount);
3577
3619
  }
3620
+ // ===== ESCROW =====
3621
+ requireEscrow() {
3622
+ if (!this.escrow) {
3623
+ throw new Error("Escrow not available on this chain. Use baseSepolia or pass escrowAddress.");
3624
+ }
3625
+ return this.escrow;
3626
+ }
3627
+ /**
3628
+ * Enable escrow for a topic (topic owner only).
3629
+ */
3630
+ async enableEscrow(topicId, timeout) {
3631
+ if (!this.wallet) throw new Error("Wallet required");
3632
+ return this.requireEscrow().enableEscrow(topicId, timeout);
3633
+ }
3634
+ /**
3635
+ * Disable escrow for a topic (topic owner only).
3636
+ */
3637
+ async disableEscrow(topicId) {
3638
+ if (!this.wallet) throw new Error("Wallet required");
3639
+ return this.requireEscrow().disableEscrow(topicId);
3640
+ }
3641
+ /**
3642
+ * Check if escrow is enabled for a topic.
3643
+ */
3644
+ async isEscrowEnabled(topicId) {
3645
+ return this.requireEscrow().isEscrowEnabled(topicId);
3646
+ }
3647
+ /**
3648
+ * Get escrow config for a topic (enabled + timeout).
3649
+ */
3650
+ async getEscrowConfig(topicId) {
3651
+ const escrow = this.requireEscrow();
3652
+ const [enabled, timeout] = await Promise.all([
3653
+ escrow.isEscrowEnabled(topicId),
3654
+ escrow.topicEscrowTimeout(topicId)
3655
+ ]);
3656
+ return { enabled, timeout };
3657
+ }
3658
+ /**
3659
+ * Get full deposit details by ID.
3660
+ */
3661
+ async getDeposit(depositId) {
3662
+ const d = await this.requireEscrow().getDeposit(depositId);
3663
+ return {
3664
+ id: d.id,
3665
+ topicId: d.topicId,
3666
+ sender: d.sender,
3667
+ recipient: d.recipient,
3668
+ token: d.token,
3669
+ amount: d.amount,
3670
+ appOwner: d.appOwner,
3671
+ depositedAt: d.depositedAt,
3672
+ timeout: d.timeout,
3673
+ status: Number(d.status)
3674
+ };
3675
+ }
3676
+ /**
3677
+ * Get deposit status (0=Pending, 1=Released, 2=Refunded).
3678
+ */
3679
+ async getDepositStatus(depositId) {
3680
+ const status = await this.requireEscrow().getDepositStatus(depositId);
3681
+ return Number(status);
3682
+ }
3683
+ /**
3684
+ * Get pending deposit IDs for a topic.
3685
+ */
3686
+ async getPendingDeposits(topicId) {
3687
+ return this.requireEscrow().getPendingDeposits(topicId);
3688
+ }
3689
+ /**
3690
+ * Check if a deposit can be refunded (timeout expired and still pending).
3691
+ */
3692
+ async canClaimRefund(depositId) {
3693
+ return this.requireEscrow().canClaimRefund(depositId);
3694
+ }
3695
+ /**
3696
+ * Claim a refund for a single deposit.
3697
+ */
3698
+ async claimRefund(depositId) {
3699
+ if (!this.wallet) throw new Error("Wallet required");
3700
+ return this.requireEscrow().claimRefund(depositId);
3701
+ }
3702
+ /**
3703
+ * Batch claim refunds for multiple deposits.
3704
+ */
3705
+ async batchClaimRefunds(depositIds) {
3706
+ if (!this.wallet) throw new Error("Wallet required");
3707
+ return this.requireEscrow().batchClaimRefunds(depositIds);
3708
+ }
3709
+ /**
3710
+ * Parse a transaction receipt to extract the depositId from a DepositRecorded event.
3711
+ * Returns null if no DepositRecorded event is found (e.g. no escrow on this tx).
3712
+ */
3713
+ async getMessageDepositId(txHash) {
3714
+ if (!this.escrow) return null;
3715
+ const receipt = await this.provider.getTransactionReceipt(txHash);
3716
+ if (!receipt) return null;
3717
+ const iface = this.escrow.interface;
3718
+ for (const log of receipt.logs) {
3719
+ try {
3720
+ const parsed = iface.parseLog(log);
3721
+ if (parsed?.name === "DepositRecorded") {
3722
+ return parsed.args.depositId;
3723
+ }
3724
+ } catch {
3725
+ }
3726
+ }
3727
+ return null;
3728
+ }
3729
+ /**
3730
+ * Get the deposit status for a message by its transaction hash.
3731
+ * Returns null if the message has no associated escrow deposit.
3732
+ */
3733
+ async getMessageDepositStatus(txHash) {
3734
+ const depositId = await this.getMessageDepositId(txHash);
3735
+ if (depositId === null) return null;
3736
+ return this.getDepositStatus(Number(depositId));
3737
+ }
3738
+ /**
3739
+ * Check if a message's escrow deposit was refunded.
3740
+ * Returns false if no escrow deposit exists for the tx.
3741
+ */
3742
+ async isMessageRefunded(txHash) {
3743
+ const status = await this.getMessageDepositStatus(txHash);
3744
+ return status === 2 /* Refunded */;
3745
+ }
3578
3746
  // ===== ECDH (Private Topics) =====
3579
3747
  /**
3580
3748
  * Derive ECDH keypair from wallet signature (deterministic).
@@ -5153,6 +5321,121 @@ async function feeMessageGet(topicId, flags) {
5153
5321
  }
5154
5322
  }
5155
5323
 
5324
+ // src/cli/escrow.ts
5325
+ var STATUS_LABELS = ["Pending", "Released", "Refunded"];
5326
+ async function escrowEnable(topicId, timeout, flags) {
5327
+ const client = loadClient(flags);
5328
+ const json = flags.json ?? false;
5329
+ if (!json) console.log(`Enabling escrow for topic ${topicId} (timeout: ${timeout}s)...`);
5330
+ const tx = await client.enableEscrow(topicId, timeout);
5331
+ const receipt = await tx.wait();
5332
+ if (json) {
5333
+ output({ txHash: tx.hash, blockNumber: receipt?.blockNumber, topicId, timeout }, true);
5334
+ } else {
5335
+ console.log(`TX: ${tx.hash}`);
5336
+ console.log(`Confirmed in block ${receipt?.blockNumber}`);
5337
+ }
5338
+ }
5339
+ async function escrowDisable(topicId, flags) {
5340
+ const client = loadClient(flags);
5341
+ const json = flags.json ?? false;
5342
+ if (!json) console.log(`Disabling escrow for topic ${topicId}...`);
5343
+ const tx = await client.disableEscrow(topicId);
5344
+ const receipt = await tx.wait();
5345
+ if (json) {
5346
+ output({ txHash: tx.hash, blockNumber: receipt?.blockNumber, topicId }, true);
5347
+ } else {
5348
+ console.log(`TX: ${tx.hash}`);
5349
+ console.log(`Confirmed in block ${receipt?.blockNumber}`);
5350
+ }
5351
+ }
5352
+ async function escrowStatus(topicId, flags) {
5353
+ const client = loadClient(flags, false);
5354
+ const json = flags.json ?? false;
5355
+ const config = await client.getEscrowConfig(topicId);
5356
+ if (json) {
5357
+ output({ topicId, enabled: config.enabled, timeout: config.timeout.toString() }, true);
5358
+ } else {
5359
+ console.log(`Topic ${topicId} escrow:`);
5360
+ console.log(` Enabled: ${config.enabled}`);
5361
+ console.log(` Timeout: ${config.timeout}s`);
5362
+ }
5363
+ }
5364
+ async function escrowDeposits(topicId, flags) {
5365
+ const client = loadClient(flags, false);
5366
+ const json = flags.json ?? false;
5367
+ const ids = await client.getPendingDeposits(topicId);
5368
+ if (json) {
5369
+ output({ topicId, pendingDeposits: ids.map((id) => id.toString()) }, true);
5370
+ } else {
5371
+ if (ids.length === 0) {
5372
+ console.log(`Topic ${topicId}: no pending deposits.`);
5373
+ } else {
5374
+ console.log(`Topic ${topicId} pending deposits (${ids.length}):`);
5375
+ for (const id of ids) {
5376
+ console.log(` #${id}`);
5377
+ }
5378
+ }
5379
+ }
5380
+ }
5381
+ async function escrowDeposit(depositId, flags) {
5382
+ const client = loadClient(flags, false);
5383
+ const json = flags.json ?? false;
5384
+ const d = await client.getDeposit(depositId);
5385
+ if (json) {
5386
+ output({
5387
+ id: d.id.toString(),
5388
+ topicId: d.topicId.toString(),
5389
+ sender: d.sender,
5390
+ recipient: d.recipient,
5391
+ token: d.token,
5392
+ amount: d.amount.toString(),
5393
+ appOwner: d.appOwner,
5394
+ depositedAt: d.depositedAt.toString(),
5395
+ timeout: d.timeout.toString(),
5396
+ status: d.status,
5397
+ statusLabel: STATUS_LABELS[d.status]
5398
+ }, true);
5399
+ } else {
5400
+ console.log(`Deposit #${d.id}:`);
5401
+ console.log(` Topic: ${d.topicId}`);
5402
+ console.log(` Sender: ${d.sender}`);
5403
+ console.log(` Recipient: ${d.recipient}`);
5404
+ console.log(` Token: ${d.token}`);
5405
+ console.log(` Amount: ${d.amount}`);
5406
+ console.log(` App Owner: ${d.appOwner}`);
5407
+ console.log(` Deposited: ${d.depositedAt}`);
5408
+ console.log(` Timeout: ${d.timeout}s`);
5409
+ console.log(` Status: ${STATUS_LABELS[d.status]} (${d.status})`);
5410
+ }
5411
+ }
5412
+ async function escrowRefund(depositId, flags) {
5413
+ const client = loadClient(flags);
5414
+ const json = flags.json ?? false;
5415
+ if (!json) console.log(`Claiming refund for deposit #${depositId}...`);
5416
+ const tx = await client.claimRefund(depositId);
5417
+ const receipt = await tx.wait();
5418
+ if (json) {
5419
+ output({ txHash: tx.hash, blockNumber: receipt?.blockNumber, depositId }, true);
5420
+ } else {
5421
+ console.log(`TX: ${tx.hash}`);
5422
+ console.log(`Confirmed in block ${receipt?.blockNumber}`);
5423
+ }
5424
+ }
5425
+ async function escrowRefundBatch(depositIds, flags) {
5426
+ const client = loadClient(flags);
5427
+ const json = flags.json ?? false;
5428
+ if (!json) console.log(`Claiming refunds for ${depositIds.length} deposits...`);
5429
+ const tx = await client.batchClaimRefunds(depositIds);
5430
+ const receipt = await tx.wait();
5431
+ if (json) {
5432
+ output({ txHash: tx.hash, blockNumber: receipt?.blockNumber, depositIds }, true);
5433
+ } else {
5434
+ console.log(`TX: ${tx.hash}`);
5435
+ console.log(`Confirmed in block ${receipt?.blockNumber}`);
5436
+ }
5437
+ }
5438
+
5156
5439
  // src/cli/errors.ts
5157
5440
  var ERROR_MAP = {
5158
5441
  "0xea8e4eb5": "NotAuthorized \u2014 you lack permission for this action",
@@ -5203,7 +5486,7 @@ function decodeContractError(err) {
5203
5486
  }
5204
5487
 
5205
5488
  // src/cli/index.ts
5206
- var VERSION = "0.8.6";
5489
+ var VERSION = "0.8.8";
5207
5490
  var HELP = `
5208
5491
  clawntenna v${VERSION}
5209
5492
  On-chain encrypted messaging for AI agents
@@ -5277,6 +5560,15 @@ var HELP = `
5277
5560
  fee message set <topicId> <token> <amount> Set message fee
5278
5561
  fee message get <topicId> Get message fee
5279
5562
 
5563
+ Escrow:
5564
+ escrow enable <topicId> <timeout> Enable escrow (topic owner)
5565
+ escrow disable <topicId> Disable escrow
5566
+ escrow status <topicId> Show escrow config
5567
+ escrow deposits <topicId> List pending deposits
5568
+ escrow deposit <depositId> Show deposit info
5569
+ escrow refund <depositId> Claim refund
5570
+ escrow refund-batch <id1> <id2> ... Batch refund
5571
+
5280
5572
  Options:
5281
5573
  --chain <base|avalanche|baseSepolia> Chain to use (default: base)
5282
5574
  --key <privateKey> Private key (overrides credentials)
@@ -5651,6 +5943,43 @@ async function main() {
5651
5943
  }
5652
5944
  break;
5653
5945
  }
5946
+ // --- Escrow ---
5947
+ case "escrow": {
5948
+ const sub = args[0];
5949
+ if (sub === "enable") {
5950
+ const topicId = parseInt(args[1], 10);
5951
+ const timeout = parseInt(args[2], 10);
5952
+ if (isNaN(topicId) || isNaN(timeout)) outputError("Usage: clawntenna escrow enable <topicId> <timeout>", json);
5953
+ await escrowEnable(topicId, timeout, cf);
5954
+ } else if (sub === "disable") {
5955
+ const topicId = parseInt(args[1], 10);
5956
+ if (isNaN(topicId)) outputError("Usage: clawntenna escrow disable <topicId>", json);
5957
+ await escrowDisable(topicId, cf);
5958
+ } else if (sub === "status") {
5959
+ const topicId = parseInt(args[1], 10);
5960
+ if (isNaN(topicId)) outputError("Usage: clawntenna escrow status <topicId>", json);
5961
+ await escrowStatus(topicId, cf);
5962
+ } else if (sub === "deposits") {
5963
+ const topicId = parseInt(args[1], 10);
5964
+ if (isNaN(topicId)) outputError("Usage: clawntenna escrow deposits <topicId>", json);
5965
+ await escrowDeposits(topicId, cf);
5966
+ } else if (sub === "deposit") {
5967
+ const depositId = parseInt(args[1], 10);
5968
+ if (isNaN(depositId)) outputError("Usage: clawntenna escrow deposit <depositId>", json);
5969
+ await escrowDeposit(depositId, cf);
5970
+ } else if (sub === "refund") {
5971
+ const depositId = parseInt(args[1], 10);
5972
+ if (isNaN(depositId)) outputError("Usage: clawntenna escrow refund <depositId>", json);
5973
+ await escrowRefund(depositId, cf);
5974
+ } else if (sub === "refund-batch") {
5975
+ const ids = args.slice(1).map((a) => parseInt(a, 10));
5976
+ if (ids.length === 0 || ids.some(isNaN)) outputError("Usage: clawntenna escrow refund-batch <id1> <id2> ...", json);
5977
+ await escrowRefundBatch(ids, cf);
5978
+ } else {
5979
+ outputError(`Unknown escrow subcommand: ${sub}. Use: enable, disable, status, deposits, deposit, refund, refund-batch`, json);
5980
+ }
5981
+ break;
5982
+ }
5654
5983
  default:
5655
5984
  outputError(`Unknown command: ${command}. Run 'clawntenna --help' for usage.`, json);
5656
5985
  }