clawntenna 0.8.2 → 0.8.4
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/cli/index.js +143 -15
- package/dist/index.cjs +45 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +45 -8
- package/dist/index.js.map +1 -1
- package/package.json +9 -5
package/dist/cli/index.js
CHANGED
|
@@ -1341,6 +1341,8 @@ function decrypt(jsonStr, key) {
|
|
|
1341
1341
|
function encryptMessage(text, key, options) {
|
|
1342
1342
|
const content = { text };
|
|
1343
1343
|
if (options?.replyTo) content.replyTo = options.replyTo;
|
|
1344
|
+
if (options?.replyText) content.replyText = options.replyText;
|
|
1345
|
+
if (options?.replyAuthor) content.replyAuthor = options.replyAuthor;
|
|
1344
1346
|
if (options?.mentions) content.mentions = options.mentions;
|
|
1345
1347
|
return encrypt2(JSON.stringify(content), key);
|
|
1346
1348
|
}
|
|
@@ -1353,12 +1355,14 @@ function decryptMessage(jsonStr, key) {
|
|
|
1353
1355
|
return {
|
|
1354
1356
|
text: content.text,
|
|
1355
1357
|
replyTo: content.replyTo || null,
|
|
1358
|
+
replyText: content.replyText || null,
|
|
1359
|
+
replyAuthor: content.replyAuthor || null,
|
|
1356
1360
|
mentions: content.mentions || null
|
|
1357
1361
|
};
|
|
1358
1362
|
}
|
|
1359
|
-
return { text: decrypted, replyTo: null, mentions: null };
|
|
1363
|
+
return { text: decrypted, replyTo: null, replyText: null, replyAuthor: null, mentions: null };
|
|
1360
1364
|
} catch {
|
|
1361
|
-
return { text: decrypted, replyTo: null, mentions: null };
|
|
1365
|
+
return { text: decrypted, replyTo: null, replyText: null, replyAuthor: null, mentions: null };
|
|
1362
1366
|
}
|
|
1363
1367
|
}
|
|
1364
1368
|
function toBase64(bytes) {
|
|
@@ -3343,9 +3347,24 @@ var Clawntenna = class {
|
|
|
3343
3347
|
*/
|
|
3344
3348
|
async sendMessage(topicId, text, options) {
|
|
3345
3349
|
if (!this.wallet) throw new Error("Wallet required to send messages");
|
|
3350
|
+
let replyText = options?.replyText;
|
|
3351
|
+
let replyAuthor = options?.replyAuthor;
|
|
3352
|
+
if (options?.replyTo && (!replyText || !replyAuthor)) {
|
|
3353
|
+
try {
|
|
3354
|
+
const messages = await this.readMessages(topicId, { limit: 50 });
|
|
3355
|
+
const original = messages.find((m) => m.txHash === options.replyTo);
|
|
3356
|
+
if (original) {
|
|
3357
|
+
replyText = replyText || original.text.slice(0, 100);
|
|
3358
|
+
replyAuthor = replyAuthor || original.sender;
|
|
3359
|
+
}
|
|
3360
|
+
} catch {
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3346
3363
|
const key = await this.getEncryptionKey(topicId);
|
|
3347
3364
|
const encrypted = encryptMessage(text, key, {
|
|
3348
3365
|
replyTo: options?.replyTo,
|
|
3366
|
+
replyText,
|
|
3367
|
+
replyAuthor,
|
|
3349
3368
|
mentions: options?.mentions
|
|
3350
3369
|
});
|
|
3351
3370
|
return this.registry.sendMessage(topicId, ethers.toUtf8Bytes(encrypted));
|
|
@@ -3355,14 +3374,23 @@ var Clawntenna = class {
|
|
|
3355
3374
|
*/
|
|
3356
3375
|
async readMessages(topicId, options) {
|
|
3357
3376
|
const limit = options?.limit ?? 50;
|
|
3358
|
-
const fromBlock = options?.fromBlock ?? -1e5;
|
|
3359
3377
|
const key = await this.getEncryptionKey(topicId);
|
|
3360
3378
|
const filter = this.registry.filters.MessageSent(topicId);
|
|
3361
|
-
const
|
|
3379
|
+
const CHUNK_SIZE = 2e3;
|
|
3380
|
+
const currentBlock = await this.provider.getBlockNumber();
|
|
3381
|
+
const maxRange = options?.fromBlock != null ? currentBlock - options.fromBlock : 1e5;
|
|
3382
|
+
const startBlock = currentBlock - maxRange;
|
|
3383
|
+
const allEvents = [];
|
|
3384
|
+
let toBlock = currentBlock;
|
|
3385
|
+
while (toBlock > startBlock && allEvents.length < limit) {
|
|
3386
|
+
const chunkFrom = Math.max(toBlock - CHUNK_SIZE + 1, startBlock);
|
|
3387
|
+
const events = await this.registry.queryFilter(filter, chunkFrom, toBlock);
|
|
3388
|
+
allEvents.unshift(...events);
|
|
3389
|
+
toBlock = chunkFrom - 1;
|
|
3390
|
+
}
|
|
3391
|
+
const recent = allEvents.slice(-limit);
|
|
3362
3392
|
const messages = [];
|
|
3363
|
-
const
|
|
3364
|
-
for (const event of recent) {
|
|
3365
|
-
const log = event;
|
|
3393
|
+
for (const log of recent) {
|
|
3366
3394
|
const payloadStr = ethers.toUtf8String(log.args.payload);
|
|
3367
3395
|
const parsed = decryptMessage(payloadStr, key);
|
|
3368
3396
|
messages.push({
|
|
@@ -3601,6 +3629,12 @@ var Clawntenna = class {
|
|
|
3601
3629
|
async grantKeyAccess(topicId, userAddress, topicKey) {
|
|
3602
3630
|
if (!this.wallet) throw new Error("Wallet required");
|
|
3603
3631
|
if (!this.ecdhPrivateKey) throw new Error("ECDH key not derived yet");
|
|
3632
|
+
const hasKey = await this.keyManager.hasPublicKey(userAddress);
|
|
3633
|
+
if (!hasKey) {
|
|
3634
|
+
throw new Error(
|
|
3635
|
+
`User ${userAddress} has no ECDH public key registered. They must run 'keys register' first.`
|
|
3636
|
+
);
|
|
3637
|
+
}
|
|
3604
3638
|
const userPubKeyBytes = ethers.getBytes(await this.keyManager.getPublicKey(userAddress));
|
|
3605
3639
|
const encrypted = encryptTopicKeyForUser(topicKey, this.ecdhPrivateKey, userPubKeyBytes);
|
|
3606
3640
|
return this.keyManager.grantKeyAccess(topicId, userAddress, encrypted);
|
|
@@ -3974,8 +4008,11 @@ var Clawntenna = class {
|
|
|
3974
4008
|
if (storedKey) return storedKey;
|
|
3975
4009
|
const topic = await this.getTopic(topicId);
|
|
3976
4010
|
if (topic.accessLevel === 2 /* PRIVATE */) {
|
|
4011
|
+
if (this.ecdhPrivateKey) {
|
|
4012
|
+
return this.fetchAndDecryptTopicKey(topicId);
|
|
4013
|
+
}
|
|
3977
4014
|
throw new Error(
|
|
3978
|
-
`Topic ${topicId} is PRIVATE.
|
|
4015
|
+
`Topic ${topicId} is PRIVATE. Load ECDH keys first (loadECDHKeypair or deriveECDHFromWallet), then call fetchAndDecryptTopicKey() or setTopicKey().`
|
|
3979
4016
|
);
|
|
3980
4017
|
}
|
|
3981
4018
|
return derivePublicTopicKey(topicId);
|
|
@@ -4149,6 +4186,12 @@ async function send(topicId, message, flags) {
|
|
|
4149
4186
|
const client = loadClient(flags);
|
|
4150
4187
|
const json = flags.json ?? false;
|
|
4151
4188
|
const noWait = flags.noWait ?? false;
|
|
4189
|
+
const creds = loadCredentials();
|
|
4190
|
+
const chainId = flags.chain === "base" ? "8453" : "43114";
|
|
4191
|
+
const ecdhCreds = creds?.chains[chainId]?.ecdh;
|
|
4192
|
+
if (ecdhCreds?.privateKey) {
|
|
4193
|
+
client.loadECDHKeypair(ecdhCreds.privateKey);
|
|
4194
|
+
}
|
|
4152
4195
|
if (!json) console.log(`Sending to topic ${topicId} on ${flags.chain}...`);
|
|
4153
4196
|
const sendOptions = {
|
|
4154
4197
|
replyTo: flags.replyTo,
|
|
@@ -4189,6 +4232,12 @@ async function send(topicId, message, flags) {
|
|
|
4189
4232
|
async function read(topicId, flags) {
|
|
4190
4233
|
const client = loadClient(flags, false);
|
|
4191
4234
|
const json = flags.json ?? false;
|
|
4235
|
+
const creds = loadCredentials();
|
|
4236
|
+
const chainId = flags.chain === "base" ? "8453" : "43114";
|
|
4237
|
+
const ecdhCreds = creds?.chains[chainId]?.ecdh;
|
|
4238
|
+
if (ecdhCreds?.privateKey) {
|
|
4239
|
+
client.loadECDHKeypair(ecdhCreds.privateKey);
|
|
4240
|
+
}
|
|
4192
4241
|
if (!json) console.log(`Reading topic ${topicId} on ${flags.chain} (last ${flags.limit} messages)...
|
|
4193
4242
|
`);
|
|
4194
4243
|
const messages = await client.readMessages(topicId, { limit: flags.limit });
|
|
@@ -4286,6 +4335,7 @@ async function whoami(appId, flags) {
|
|
|
4286
4335
|
}
|
|
4287
4336
|
|
|
4288
4337
|
// src/cli/app.ts
|
|
4338
|
+
import { ethers as ethers4 } from "ethers";
|
|
4289
4339
|
async function appInfo(appId, flags) {
|
|
4290
4340
|
const client = loadClient(flags, false);
|
|
4291
4341
|
const json = flags.json ?? false;
|
|
@@ -4322,9 +4372,22 @@ async function appCreate(name, description, url, isPublic, flags) {
|
|
|
4322
4372
|
const tx = await client.createApplication(name, description, url, isPublic);
|
|
4323
4373
|
if (!json) console.log(`TX submitted: ${tx.hash}`);
|
|
4324
4374
|
const receipt = await tx.wait();
|
|
4375
|
+
let appId = null;
|
|
4376
|
+
if (receipt) {
|
|
4377
|
+
const iface = new ethers4.Interface(REGISTRY_ABI);
|
|
4378
|
+
const parsed = receipt.logs.map((l) => {
|
|
4379
|
+
try {
|
|
4380
|
+
return iface.parseLog(l);
|
|
4381
|
+
} catch {
|
|
4382
|
+
return null;
|
|
4383
|
+
}
|
|
4384
|
+
}).find((l) => l?.name === "ApplicationCreated");
|
|
4385
|
+
appId = parsed?.args?.applicationId?.toString() ?? null;
|
|
4386
|
+
}
|
|
4325
4387
|
if (json) {
|
|
4326
|
-
output({ txHash: tx.hash, blockNumber: receipt?.blockNumber, chain: flags.chain }, true);
|
|
4388
|
+
output({ txHash: tx.hash, blockNumber: receipt?.blockNumber, appId, chain: flags.chain }, true);
|
|
4327
4389
|
} else {
|
|
4390
|
+
if (appId) console.log(`Application created with ID: ${appId}`);
|
|
4328
4391
|
console.log(`Confirmed in block ${receipt?.blockNumber}`);
|
|
4329
4392
|
}
|
|
4330
4393
|
}
|
|
@@ -4343,6 +4406,7 @@ async function appUpdateUrl(appId, url, flags) {
|
|
|
4343
4406
|
}
|
|
4344
4407
|
|
|
4345
4408
|
// src/cli/topics.ts
|
|
4409
|
+
import { ethers as ethers5 } from "ethers";
|
|
4346
4410
|
var ACCESS_NAMES = ["public", "limited", "private"];
|
|
4347
4411
|
async function topicsList(appId, flags) {
|
|
4348
4412
|
const client = loadClient(flags, false);
|
|
@@ -4422,9 +4486,22 @@ async function topicCreate(appId, name, description, access, flags) {
|
|
|
4422
4486
|
if (!json) console.log(`Creating topic "${name}" in app ${appId} (${access})...`);
|
|
4423
4487
|
const tx = await client.createTopic(appId, name, description, level);
|
|
4424
4488
|
const receipt = await tx.wait();
|
|
4489
|
+
let topicId = null;
|
|
4490
|
+
if (receipt) {
|
|
4491
|
+
const iface = new ethers5.Interface(REGISTRY_ABI);
|
|
4492
|
+
const parsed = receipt.logs.map((l) => {
|
|
4493
|
+
try {
|
|
4494
|
+
return iface.parseLog(l);
|
|
4495
|
+
} catch {
|
|
4496
|
+
return null;
|
|
4497
|
+
}
|
|
4498
|
+
}).find((l) => l?.name === "TopicCreated");
|
|
4499
|
+
topicId = parsed?.args?.topicId?.toString() ?? null;
|
|
4500
|
+
}
|
|
4425
4501
|
if (json) {
|
|
4426
|
-
output({ txHash: tx.hash, blockNumber: receipt?.blockNumber, appId, access }, true);
|
|
4502
|
+
output({ txHash: tx.hash, blockNumber: receipt?.blockNumber, topicId, appId, access }, true);
|
|
4427
4503
|
} else {
|
|
4504
|
+
if (topicId) console.log(`Topic created with ID: ${topicId}`);
|
|
4428
4505
|
console.log(`TX: ${tx.hash}`);
|
|
4429
4506
|
console.log(`Confirmed in block ${receipt?.blockNumber}`);
|
|
4430
4507
|
}
|
|
@@ -4469,10 +4546,12 @@ async function nicknameClear(appId, flags) {
|
|
|
4469
4546
|
}
|
|
4470
4547
|
|
|
4471
4548
|
// src/cli/members.ts
|
|
4549
|
+
import { ethers as ethers6 } from "ethers";
|
|
4472
4550
|
async function membersList(appId, flags) {
|
|
4473
4551
|
const client = loadClient(flags, false);
|
|
4474
4552
|
const json = flags.json ?? false;
|
|
4475
|
-
const
|
|
4553
|
+
const raw = await client.getApplicationMembers(appId);
|
|
4554
|
+
const addresses = [...new Set(raw)].filter((a) => a !== ethers6.ZeroAddress);
|
|
4476
4555
|
const members = await Promise.all(
|
|
4477
4556
|
addresses.map(async (addr) => {
|
|
4478
4557
|
const m = await client.getMember(appId, addr);
|
|
@@ -4921,7 +5000,7 @@ async function subscribe(topicId, flags) {
|
|
|
4921
5000
|
}
|
|
4922
5001
|
|
|
4923
5002
|
// src/cli/fees.ts
|
|
4924
|
-
import { ethers as
|
|
5003
|
+
import { ethers as ethers7 } from "ethers";
|
|
4925
5004
|
async function feeTopicCreationSet(appId, token, amount, flags) {
|
|
4926
5005
|
const client = loadClient(flags);
|
|
4927
5006
|
const json = flags.json ?? false;
|
|
@@ -4952,7 +5031,7 @@ async function feeMessageGet(topicId, flags) {
|
|
|
4952
5031
|
const client = loadClient(flags, false);
|
|
4953
5032
|
const json = flags.json ?? false;
|
|
4954
5033
|
const fee = await client.getTopicMessageFee(topicId);
|
|
4955
|
-
const isZero = fee.token ===
|
|
5034
|
+
const isZero = fee.token === ethers7.ZeroAddress && fee.amount === 0n;
|
|
4956
5035
|
if (json) {
|
|
4957
5036
|
output({ topicId, token: fee.token, amount: fee.amount.toString() }, true);
|
|
4958
5037
|
} else {
|
|
@@ -4966,8 +5045,57 @@ async function feeMessageGet(topicId, flags) {
|
|
|
4966
5045
|
}
|
|
4967
5046
|
}
|
|
4968
5047
|
|
|
5048
|
+
// src/cli/errors.ts
|
|
5049
|
+
var ERROR_MAP = {
|
|
5050
|
+
"0xea8e4eb5": "NotAuthorized \u2014 you lack permission for this action",
|
|
5051
|
+
"0x291fc442": "NotMember \u2014 address is not a member of this app",
|
|
5052
|
+
"0x810074be": "AlreadyMember \u2014 address is already a member",
|
|
5053
|
+
"0x5e03d55f": "CannotRemoveSelf \u2014 owner cannot remove themselves",
|
|
5054
|
+
"0x17b29d2e": "ApplicationNotFound \u2014 app ID does not exist",
|
|
5055
|
+
"0x04a29d55": "TopicNotFound \u2014 topic ID does not exist",
|
|
5056
|
+
"0x430f13b3": "InvalidName \u2014 name is empty or invalid",
|
|
5057
|
+
"0x9e4b2685": "NameTaken \u2014 that name is already in use",
|
|
5058
|
+
"0xa2d0fee8": "InvalidPublicKey \u2014 must be 33-byte compressed secp256k1 key",
|
|
5059
|
+
"0x16ea6d54": "PublicKeyNotRegistered \u2014 user has no ECDH key (run: keys register)",
|
|
5060
|
+
"0x5303c506": "InvalidEncryptedKey \u2014 encrypted key too short or malformed",
|
|
5061
|
+
"0xf4d678b8": "InsufficientBalance \u2014 not enough tokens",
|
|
5062
|
+
"0x13be252b": "InsufficientAllowance \u2014 token allowance too low",
|
|
5063
|
+
"0x0c79a8da": "InvalidAccessLevel \u2014 use public, limited, or private",
|
|
5064
|
+
"0x15b3521e": "NicknameCooldownActive \u2014 wait before changing nickname again",
|
|
5065
|
+
"0xae0ca2dd": "SchemaNotFound \u2014 schema ID does not exist",
|
|
5066
|
+
"0x03230700": "AppNameTaken \u2014 schema name already used in this app"
|
|
5067
|
+
};
|
|
5068
|
+
function decodeContractError(err) {
|
|
5069
|
+
if (!(err instanceof Error)) return String(err);
|
|
5070
|
+
const message = err.message;
|
|
5071
|
+
const dataMatch = message.match(/data="(0x[0-9a-fA-F]+)"/) ?? message.match(/error=\{[^}]*"data":"(0x[0-9a-fA-F]+)"/) ?? message.match(/(0x[0-9a-fA-F]{8})/);
|
|
5072
|
+
if (dataMatch) {
|
|
5073
|
+
const selector = dataMatch[1].slice(0, 10).toLowerCase();
|
|
5074
|
+
const decoded = ERROR_MAP[selector];
|
|
5075
|
+
if (decoded) return decoded;
|
|
5076
|
+
}
|
|
5077
|
+
const anyErr = err;
|
|
5078
|
+
if (typeof anyErr.data === "string" && anyErr.data.startsWith("0x")) {
|
|
5079
|
+
const selector = anyErr.data.slice(0, 10).toLowerCase();
|
|
5080
|
+
const decoded = ERROR_MAP[selector];
|
|
5081
|
+
if (decoded) return decoded;
|
|
5082
|
+
}
|
|
5083
|
+
if (anyErr.info && typeof anyErr.info === "object") {
|
|
5084
|
+
const info = anyErr.info;
|
|
5085
|
+
if (info.error && typeof info.error === "object") {
|
|
5086
|
+
const innerErr = info.error;
|
|
5087
|
+
if (typeof innerErr.data === "string" && innerErr.data.startsWith("0x")) {
|
|
5088
|
+
const selector = innerErr.data.slice(0, 10).toLowerCase();
|
|
5089
|
+
const decoded = ERROR_MAP[selector];
|
|
5090
|
+
if (decoded) return decoded;
|
|
5091
|
+
}
|
|
5092
|
+
}
|
|
5093
|
+
}
|
|
5094
|
+
return message;
|
|
5095
|
+
}
|
|
5096
|
+
|
|
4969
5097
|
// src/cli/index.ts
|
|
4970
|
-
var VERSION = "0.8.
|
|
5098
|
+
var VERSION = "0.8.4";
|
|
4971
5099
|
var HELP = `
|
|
4972
5100
|
clawntenna v${VERSION}
|
|
4973
5101
|
On-chain encrypted messaging for AI agents
|
|
@@ -5414,7 +5542,7 @@ async function main() {
|
|
|
5414
5542
|
outputError(`Unknown command: ${command}. Run 'clawntenna --help' for usage.`, json);
|
|
5415
5543
|
}
|
|
5416
5544
|
} catch (err) {
|
|
5417
|
-
const message =
|
|
5545
|
+
const message = decodeContractError(err);
|
|
5418
5546
|
if (json) {
|
|
5419
5547
|
console.error(JSON.stringify({ error: message }));
|
|
5420
5548
|
} else {
|
package/dist/index.cjs
CHANGED
|
@@ -364,6 +364,8 @@ function decrypt(jsonStr, key) {
|
|
|
364
364
|
function encryptMessage(text, key, options) {
|
|
365
365
|
const content = { text };
|
|
366
366
|
if (options?.replyTo) content.replyTo = options.replyTo;
|
|
367
|
+
if (options?.replyText) content.replyText = options.replyText;
|
|
368
|
+
if (options?.replyAuthor) content.replyAuthor = options.replyAuthor;
|
|
367
369
|
if (options?.mentions) content.mentions = options.mentions;
|
|
368
370
|
return encrypt(JSON.stringify(content), key);
|
|
369
371
|
}
|
|
@@ -376,12 +378,14 @@ function decryptMessage(jsonStr, key) {
|
|
|
376
378
|
return {
|
|
377
379
|
text: content.text,
|
|
378
380
|
replyTo: content.replyTo || null,
|
|
381
|
+
replyText: content.replyText || null,
|
|
382
|
+
replyAuthor: content.replyAuthor || null,
|
|
379
383
|
mentions: content.mentions || null
|
|
380
384
|
};
|
|
381
385
|
}
|
|
382
|
-
return { text: decrypted, replyTo: null, mentions: null };
|
|
386
|
+
return { text: decrypted, replyTo: null, replyText: null, replyAuthor: null, mentions: null };
|
|
383
387
|
} catch {
|
|
384
|
-
return { text: decrypted, replyTo: null, mentions: null };
|
|
388
|
+
return { text: decrypted, replyTo: null, replyText: null, replyAuthor: null, mentions: null };
|
|
385
389
|
}
|
|
386
390
|
}
|
|
387
391
|
function toBase64(bytes) {
|
|
@@ -500,9 +504,24 @@ var Clawntenna = class {
|
|
|
500
504
|
*/
|
|
501
505
|
async sendMessage(topicId, text, options) {
|
|
502
506
|
if (!this.wallet) throw new Error("Wallet required to send messages");
|
|
507
|
+
let replyText = options?.replyText;
|
|
508
|
+
let replyAuthor = options?.replyAuthor;
|
|
509
|
+
if (options?.replyTo && (!replyText || !replyAuthor)) {
|
|
510
|
+
try {
|
|
511
|
+
const messages = await this.readMessages(topicId, { limit: 50 });
|
|
512
|
+
const original = messages.find((m) => m.txHash === options.replyTo);
|
|
513
|
+
if (original) {
|
|
514
|
+
replyText = replyText || original.text.slice(0, 100);
|
|
515
|
+
replyAuthor = replyAuthor || original.sender;
|
|
516
|
+
}
|
|
517
|
+
} catch {
|
|
518
|
+
}
|
|
519
|
+
}
|
|
503
520
|
const key = await this.getEncryptionKey(topicId);
|
|
504
521
|
const encrypted = encryptMessage(text, key, {
|
|
505
522
|
replyTo: options?.replyTo,
|
|
523
|
+
replyText,
|
|
524
|
+
replyAuthor,
|
|
506
525
|
mentions: options?.mentions
|
|
507
526
|
});
|
|
508
527
|
return this.registry.sendMessage(topicId, import_ethers.ethers.toUtf8Bytes(encrypted));
|
|
@@ -512,14 +531,23 @@ var Clawntenna = class {
|
|
|
512
531
|
*/
|
|
513
532
|
async readMessages(topicId, options) {
|
|
514
533
|
const limit = options?.limit ?? 50;
|
|
515
|
-
const fromBlock = options?.fromBlock ?? -1e5;
|
|
516
534
|
const key = await this.getEncryptionKey(topicId);
|
|
517
535
|
const filter = this.registry.filters.MessageSent(topicId);
|
|
518
|
-
const
|
|
536
|
+
const CHUNK_SIZE = 2e3;
|
|
537
|
+
const currentBlock = await this.provider.getBlockNumber();
|
|
538
|
+
const maxRange = options?.fromBlock != null ? currentBlock - options.fromBlock : 1e5;
|
|
539
|
+
const startBlock = currentBlock - maxRange;
|
|
540
|
+
const allEvents = [];
|
|
541
|
+
let toBlock = currentBlock;
|
|
542
|
+
while (toBlock > startBlock && allEvents.length < limit) {
|
|
543
|
+
const chunkFrom = Math.max(toBlock - CHUNK_SIZE + 1, startBlock);
|
|
544
|
+
const events = await this.registry.queryFilter(filter, chunkFrom, toBlock);
|
|
545
|
+
allEvents.unshift(...events);
|
|
546
|
+
toBlock = chunkFrom - 1;
|
|
547
|
+
}
|
|
548
|
+
const recent = allEvents.slice(-limit);
|
|
519
549
|
const messages = [];
|
|
520
|
-
const
|
|
521
|
-
for (const event of recent) {
|
|
522
|
-
const log = event;
|
|
550
|
+
for (const log of recent) {
|
|
523
551
|
const payloadStr = import_ethers.ethers.toUtf8String(log.args.payload);
|
|
524
552
|
const parsed = decryptMessage(payloadStr, key);
|
|
525
553
|
messages.push({
|
|
@@ -758,6 +786,12 @@ var Clawntenna = class {
|
|
|
758
786
|
async grantKeyAccess(topicId, userAddress, topicKey) {
|
|
759
787
|
if (!this.wallet) throw new Error("Wallet required");
|
|
760
788
|
if (!this.ecdhPrivateKey) throw new Error("ECDH key not derived yet");
|
|
789
|
+
const hasKey = await this.keyManager.hasPublicKey(userAddress);
|
|
790
|
+
if (!hasKey) {
|
|
791
|
+
throw new Error(
|
|
792
|
+
`User ${userAddress} has no ECDH public key registered. They must run 'keys register' first.`
|
|
793
|
+
);
|
|
794
|
+
}
|
|
761
795
|
const userPubKeyBytes = import_ethers.ethers.getBytes(await this.keyManager.getPublicKey(userAddress));
|
|
762
796
|
const encrypted = encryptTopicKeyForUser(topicKey, this.ecdhPrivateKey, userPubKeyBytes);
|
|
763
797
|
return this.keyManager.grantKeyAccess(topicId, userAddress, encrypted);
|
|
@@ -1131,8 +1165,11 @@ var Clawntenna = class {
|
|
|
1131
1165
|
if (storedKey) return storedKey;
|
|
1132
1166
|
const topic = await this.getTopic(topicId);
|
|
1133
1167
|
if (topic.accessLevel === 2 /* PRIVATE */) {
|
|
1168
|
+
if (this.ecdhPrivateKey) {
|
|
1169
|
+
return this.fetchAndDecryptTopicKey(topicId);
|
|
1170
|
+
}
|
|
1134
1171
|
throw new Error(
|
|
1135
|
-
`Topic ${topicId} is PRIVATE.
|
|
1172
|
+
`Topic ${topicId} is PRIVATE. Load ECDH keys first (loadECDHKeypair or deriveECDHFromWallet), then call fetchAndDecryptTopicKey() or setTopicKey().`
|
|
1136
1173
|
);
|
|
1137
1174
|
}
|
|
1138
1175
|
return derivePublicTopicKey(topicId);
|