clawntenna 0.8.3 → 0.8.5
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 +135 -18
- package/dist/index.cjs +25 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +25 -7
- package/dist/index.js.map +1 -1
- package/package.json +9 -5
package/dist/cli/index.js
CHANGED
|
@@ -20,7 +20,7 @@ var CHAINS = {
|
|
|
20
20
|
registry: "0xf39b193aedC1Ec9FD6C5ccc24fBAe58ba9f52413",
|
|
21
21
|
keyManager: "0x5562B553a876CBdc8AA4B3fb0687f22760F4759e",
|
|
22
22
|
schemaRegistry: "0xB7eB50e9058198b99b5b2589E6D70b2d99d5440a",
|
|
23
|
-
identityRegistry: "
|
|
23
|
+
identityRegistry: "0x8004AA63c570c570eBF15376c0dB199918BFe9Fb"
|
|
24
24
|
},
|
|
25
25
|
base: {
|
|
26
26
|
chainId: 8453,
|
|
@@ -3374,14 +3374,23 @@ var Clawntenna = class {
|
|
|
3374
3374
|
*/
|
|
3375
3375
|
async readMessages(topicId, options) {
|
|
3376
3376
|
const limit = options?.limit ?? 50;
|
|
3377
|
-
const fromBlock = options?.fromBlock ?? -1e5;
|
|
3378
3377
|
const key = await this.getEncryptionKey(topicId);
|
|
3379
3378
|
const filter = this.registry.filters.MessageSent(topicId);
|
|
3380
|
-
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);
|
|
3381
3392
|
const messages = [];
|
|
3382
|
-
const
|
|
3383
|
-
for (const event of recent) {
|
|
3384
|
-
const log = event;
|
|
3393
|
+
for (const log of recent) {
|
|
3385
3394
|
const payloadStr = ethers.toUtf8String(log.args.payload);
|
|
3386
3395
|
const parsed = decryptMessage(payloadStr, key);
|
|
3387
3396
|
messages.push({
|
|
@@ -3620,6 +3629,12 @@ var Clawntenna = class {
|
|
|
3620
3629
|
async grantKeyAccess(topicId, userAddress, topicKey) {
|
|
3621
3630
|
if (!this.wallet) throw new Error("Wallet required");
|
|
3622
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
|
+
}
|
|
3623
3638
|
const userPubKeyBytes = ethers.getBytes(await this.keyManager.getPublicKey(userAddress));
|
|
3624
3639
|
const encrypted = encryptTopicKeyForUser(topicKey, this.ecdhPrivateKey, userPubKeyBytes);
|
|
3625
3640
|
return this.keyManager.grantKeyAccess(topicId, userAddress, encrypted);
|
|
@@ -3993,8 +4008,11 @@ var Clawntenna = class {
|
|
|
3993
4008
|
if (storedKey) return storedKey;
|
|
3994
4009
|
const topic = await this.getTopic(topicId);
|
|
3995
4010
|
if (topic.accessLevel === 2 /* PRIVATE */) {
|
|
4011
|
+
if (this.ecdhPrivateKey) {
|
|
4012
|
+
return this.fetchAndDecryptTopicKey(topicId);
|
|
4013
|
+
}
|
|
3996
4014
|
throw new Error(
|
|
3997
|
-
`Topic ${topicId} is PRIVATE.
|
|
4015
|
+
`Topic ${topicId} is PRIVATE. Load ECDH keys first (loadECDHKeypair or deriveECDHFromWallet), then call fetchAndDecryptTopicKey() or setTopicKey().`
|
|
3998
4016
|
);
|
|
3999
4017
|
}
|
|
4000
4018
|
return derivePublicTopicKey(topicId);
|
|
@@ -4033,6 +4051,14 @@ function outputError(message, json) {
|
|
|
4033
4051
|
}
|
|
4034
4052
|
process.exit(1);
|
|
4035
4053
|
}
|
|
4054
|
+
function chainIdForCredentials(chain) {
|
|
4055
|
+
const map = {
|
|
4056
|
+
base: "8453",
|
|
4057
|
+
baseSepolia: "84532",
|
|
4058
|
+
avalanche: "43114"
|
|
4059
|
+
};
|
|
4060
|
+
return map[chain] ?? "8453";
|
|
4061
|
+
}
|
|
4036
4062
|
function bigintReplacer(_key, value) {
|
|
4037
4063
|
return typeof value === "bigint" ? value.toString() : value;
|
|
4038
4064
|
}
|
|
@@ -4168,6 +4194,12 @@ async function send(topicId, message, flags) {
|
|
|
4168
4194
|
const client = loadClient(flags);
|
|
4169
4195
|
const json = flags.json ?? false;
|
|
4170
4196
|
const noWait = flags.noWait ?? false;
|
|
4197
|
+
const creds = loadCredentials();
|
|
4198
|
+
const chainId = chainIdForCredentials(flags.chain);
|
|
4199
|
+
const ecdhCreds = creds?.chains[chainId]?.ecdh;
|
|
4200
|
+
if (ecdhCreds?.privateKey) {
|
|
4201
|
+
client.loadECDHKeypair(ecdhCreds.privateKey);
|
|
4202
|
+
}
|
|
4171
4203
|
if (!json) console.log(`Sending to topic ${topicId} on ${flags.chain}...`);
|
|
4172
4204
|
const sendOptions = {
|
|
4173
4205
|
replyTo: flags.replyTo,
|
|
@@ -4208,6 +4240,12 @@ async function send(topicId, message, flags) {
|
|
|
4208
4240
|
async function read(topicId, flags) {
|
|
4209
4241
|
const client = loadClient(flags, false);
|
|
4210
4242
|
const json = flags.json ?? false;
|
|
4243
|
+
const creds = loadCredentials();
|
|
4244
|
+
const chainId = chainIdForCredentials(flags.chain);
|
|
4245
|
+
const ecdhCreds = creds?.chains[chainId]?.ecdh;
|
|
4246
|
+
if (ecdhCreds?.privateKey) {
|
|
4247
|
+
client.loadECDHKeypair(ecdhCreds.privateKey);
|
|
4248
|
+
}
|
|
4211
4249
|
if (!json) console.log(`Reading topic ${topicId} on ${flags.chain} (last ${flags.limit} messages)...
|
|
4212
4250
|
`);
|
|
4213
4251
|
const messages = await client.readMessages(topicId, { limit: flags.limit });
|
|
@@ -4284,7 +4322,7 @@ async function whoami(appId, flags) {
|
|
|
4284
4322
|
}
|
|
4285
4323
|
const creds = loadCredentials();
|
|
4286
4324
|
if (creds) {
|
|
4287
|
-
const chainId = flags.chain
|
|
4325
|
+
const chainId = chainIdForCredentials(flags.chain);
|
|
4288
4326
|
const chainCreds = creds.chains[chainId];
|
|
4289
4327
|
result.ecdhRegistered = chainCreds?.ecdh?.registered ?? false;
|
|
4290
4328
|
}
|
|
@@ -4305,6 +4343,7 @@ async function whoami(appId, flags) {
|
|
|
4305
4343
|
}
|
|
4306
4344
|
|
|
4307
4345
|
// src/cli/app.ts
|
|
4346
|
+
import { ethers as ethers4 } from "ethers";
|
|
4308
4347
|
async function appInfo(appId, flags) {
|
|
4309
4348
|
const client = loadClient(flags, false);
|
|
4310
4349
|
const json = flags.json ?? false;
|
|
@@ -4341,9 +4380,22 @@ async function appCreate(name, description, url, isPublic, flags) {
|
|
|
4341
4380
|
const tx = await client.createApplication(name, description, url, isPublic);
|
|
4342
4381
|
if (!json) console.log(`TX submitted: ${tx.hash}`);
|
|
4343
4382
|
const receipt = await tx.wait();
|
|
4383
|
+
let appId = null;
|
|
4384
|
+
if (receipt) {
|
|
4385
|
+
const iface = new ethers4.Interface(REGISTRY_ABI);
|
|
4386
|
+
const parsed = receipt.logs.map((l) => {
|
|
4387
|
+
try {
|
|
4388
|
+
return iface.parseLog(l);
|
|
4389
|
+
} catch {
|
|
4390
|
+
return null;
|
|
4391
|
+
}
|
|
4392
|
+
}).find((l) => l?.name === "ApplicationCreated");
|
|
4393
|
+
appId = parsed?.args?.applicationId?.toString() ?? null;
|
|
4394
|
+
}
|
|
4344
4395
|
if (json) {
|
|
4345
|
-
output({ txHash: tx.hash, blockNumber: receipt?.blockNumber, chain: flags.chain }, true);
|
|
4396
|
+
output({ txHash: tx.hash, blockNumber: receipt?.blockNumber, appId, chain: flags.chain }, true);
|
|
4346
4397
|
} else {
|
|
4398
|
+
if (appId) console.log(`Application created with ID: ${appId}`);
|
|
4347
4399
|
console.log(`Confirmed in block ${receipt?.blockNumber}`);
|
|
4348
4400
|
}
|
|
4349
4401
|
}
|
|
@@ -4362,6 +4414,7 @@ async function appUpdateUrl(appId, url, flags) {
|
|
|
4362
4414
|
}
|
|
4363
4415
|
|
|
4364
4416
|
// src/cli/topics.ts
|
|
4417
|
+
import { ethers as ethers5 } from "ethers";
|
|
4365
4418
|
var ACCESS_NAMES = ["public", "limited", "private"];
|
|
4366
4419
|
async function topicsList(appId, flags) {
|
|
4367
4420
|
const client = loadClient(flags, false);
|
|
@@ -4441,9 +4494,22 @@ async function topicCreate(appId, name, description, access, flags) {
|
|
|
4441
4494
|
if (!json) console.log(`Creating topic "${name}" in app ${appId} (${access})...`);
|
|
4442
4495
|
const tx = await client.createTopic(appId, name, description, level);
|
|
4443
4496
|
const receipt = await tx.wait();
|
|
4497
|
+
let topicId = null;
|
|
4498
|
+
if (receipt) {
|
|
4499
|
+
const iface = new ethers5.Interface(REGISTRY_ABI);
|
|
4500
|
+
const parsed = receipt.logs.map((l) => {
|
|
4501
|
+
try {
|
|
4502
|
+
return iface.parseLog(l);
|
|
4503
|
+
} catch {
|
|
4504
|
+
return null;
|
|
4505
|
+
}
|
|
4506
|
+
}).find((l) => l?.name === "TopicCreated");
|
|
4507
|
+
topicId = parsed?.args?.topicId?.toString() ?? null;
|
|
4508
|
+
}
|
|
4444
4509
|
if (json) {
|
|
4445
|
-
output({ txHash: tx.hash, blockNumber: receipt?.blockNumber, appId, access }, true);
|
|
4510
|
+
output({ txHash: tx.hash, blockNumber: receipt?.blockNumber, topicId, appId, access }, true);
|
|
4446
4511
|
} else {
|
|
4512
|
+
if (topicId) console.log(`Topic created with ID: ${topicId}`);
|
|
4447
4513
|
console.log(`TX: ${tx.hash}`);
|
|
4448
4514
|
console.log(`Confirmed in block ${receipt?.blockNumber}`);
|
|
4449
4515
|
}
|
|
@@ -4488,10 +4554,12 @@ async function nicknameClear(appId, flags) {
|
|
|
4488
4554
|
}
|
|
4489
4555
|
|
|
4490
4556
|
// src/cli/members.ts
|
|
4557
|
+
import { ethers as ethers6 } from "ethers";
|
|
4491
4558
|
async function membersList(appId, flags) {
|
|
4492
4559
|
const client = loadClient(flags, false);
|
|
4493
4560
|
const json = flags.json ?? false;
|
|
4494
|
-
const
|
|
4561
|
+
const raw = await client.getApplicationMembers(appId);
|
|
4562
|
+
const addresses = [...new Set(raw)].filter((a) => a !== ethers6.ZeroAddress);
|
|
4495
4563
|
const members = await Promise.all(
|
|
4496
4564
|
addresses.map(async (addr) => {
|
|
4497
4565
|
const m = await client.getMember(appId, addr);
|
|
@@ -4796,7 +4864,7 @@ async function keysRegister(flags) {
|
|
|
4796
4864
|
const client = loadClient(flags);
|
|
4797
4865
|
const json = flags.json ?? false;
|
|
4798
4866
|
const creds = loadCredentials();
|
|
4799
|
-
const chainId = flags.chain
|
|
4867
|
+
const chainId = chainIdForCredentials(flags.chain);
|
|
4800
4868
|
const ecdhCreds = creds?.chains[chainId]?.ecdh;
|
|
4801
4869
|
if (ecdhCreds?.privateKey) {
|
|
4802
4870
|
client.loadECDHKeypair(ecdhCreds.privateKey);
|
|
@@ -4828,7 +4896,7 @@ async function keysGrant(topicId, address, flags) {
|
|
|
4828
4896
|
const client = loadClient(flags);
|
|
4829
4897
|
const json = flags.json ?? false;
|
|
4830
4898
|
const creds = loadCredentials();
|
|
4831
|
-
const chainId = flags.chain
|
|
4899
|
+
const chainId = chainIdForCredentials(flags.chain);
|
|
4832
4900
|
const ecdhCreds = creds?.chains[chainId]?.ecdh;
|
|
4833
4901
|
if (ecdhCreds?.privateKey) {
|
|
4834
4902
|
client.loadECDHKeypair(ecdhCreds.privateKey);
|
|
@@ -4940,7 +5008,7 @@ async function subscribe(topicId, flags) {
|
|
|
4940
5008
|
}
|
|
4941
5009
|
|
|
4942
5010
|
// src/cli/fees.ts
|
|
4943
|
-
import { ethers as
|
|
5011
|
+
import { ethers as ethers7 } from "ethers";
|
|
4944
5012
|
async function feeTopicCreationSet(appId, token, amount, flags) {
|
|
4945
5013
|
const client = loadClient(flags);
|
|
4946
5014
|
const json = flags.json ?? false;
|
|
@@ -4971,7 +5039,7 @@ async function feeMessageGet(topicId, flags) {
|
|
|
4971
5039
|
const client = loadClient(flags, false);
|
|
4972
5040
|
const json = flags.json ?? false;
|
|
4973
5041
|
const fee = await client.getTopicMessageFee(topicId);
|
|
4974
|
-
const isZero = fee.token ===
|
|
5042
|
+
const isZero = fee.token === ethers7.ZeroAddress && fee.amount === 0n;
|
|
4975
5043
|
if (json) {
|
|
4976
5044
|
output({ topicId, token: fee.token, amount: fee.amount.toString() }, true);
|
|
4977
5045
|
} else {
|
|
@@ -4985,8 +5053,57 @@ async function feeMessageGet(topicId, flags) {
|
|
|
4985
5053
|
}
|
|
4986
5054
|
}
|
|
4987
5055
|
|
|
5056
|
+
// src/cli/errors.ts
|
|
5057
|
+
var ERROR_MAP = {
|
|
5058
|
+
"0xea8e4eb5": "NotAuthorized \u2014 you lack permission for this action",
|
|
5059
|
+
"0x291fc442": "NotMember \u2014 address is not a member of this app",
|
|
5060
|
+
"0x810074be": "AlreadyMember \u2014 address is already a member",
|
|
5061
|
+
"0x5e03d55f": "CannotRemoveSelf \u2014 owner cannot remove themselves",
|
|
5062
|
+
"0x17b29d2e": "ApplicationNotFound \u2014 app ID does not exist",
|
|
5063
|
+
"0x04a29d55": "TopicNotFound \u2014 topic ID does not exist",
|
|
5064
|
+
"0x430f13b3": "InvalidName \u2014 name is empty or invalid",
|
|
5065
|
+
"0x9e4b2685": "NameTaken \u2014 that name is already in use",
|
|
5066
|
+
"0xa2d0fee8": "InvalidPublicKey \u2014 must be 33-byte compressed secp256k1 key",
|
|
5067
|
+
"0x16ea6d54": "PublicKeyNotRegistered \u2014 user has no ECDH key (run: keys register)",
|
|
5068
|
+
"0x5303c506": "InvalidEncryptedKey \u2014 encrypted key too short or malformed",
|
|
5069
|
+
"0xf4d678b8": "InsufficientBalance \u2014 not enough tokens",
|
|
5070
|
+
"0x13be252b": "InsufficientAllowance \u2014 token allowance too low",
|
|
5071
|
+
"0x0c79a8da": "InvalidAccessLevel \u2014 use public, limited, or private",
|
|
5072
|
+
"0x15b3521e": "NicknameCooldownActive \u2014 wait before changing nickname again",
|
|
5073
|
+
"0xae0ca2dd": "SchemaNotFound \u2014 schema ID does not exist",
|
|
5074
|
+
"0x03230700": "AppNameTaken \u2014 schema name already used in this app"
|
|
5075
|
+
};
|
|
5076
|
+
function decodeContractError(err) {
|
|
5077
|
+
if (!(err instanceof Error)) return String(err);
|
|
5078
|
+
const message = err.message;
|
|
5079
|
+
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})/);
|
|
5080
|
+
if (dataMatch) {
|
|
5081
|
+
const selector = dataMatch[1].slice(0, 10).toLowerCase();
|
|
5082
|
+
const decoded = ERROR_MAP[selector];
|
|
5083
|
+
if (decoded) return decoded;
|
|
5084
|
+
}
|
|
5085
|
+
const anyErr = err;
|
|
5086
|
+
if (typeof anyErr.data === "string" && anyErr.data.startsWith("0x")) {
|
|
5087
|
+
const selector = anyErr.data.slice(0, 10).toLowerCase();
|
|
5088
|
+
const decoded = ERROR_MAP[selector];
|
|
5089
|
+
if (decoded) return decoded;
|
|
5090
|
+
}
|
|
5091
|
+
if (anyErr.info && typeof anyErr.info === "object") {
|
|
5092
|
+
const info = anyErr.info;
|
|
5093
|
+
if (info.error && typeof info.error === "object") {
|
|
5094
|
+
const innerErr = info.error;
|
|
5095
|
+
if (typeof innerErr.data === "string" && innerErr.data.startsWith("0x")) {
|
|
5096
|
+
const selector = innerErr.data.slice(0, 10).toLowerCase();
|
|
5097
|
+
const decoded = ERROR_MAP[selector];
|
|
5098
|
+
if (decoded) return decoded;
|
|
5099
|
+
}
|
|
5100
|
+
}
|
|
5101
|
+
}
|
|
5102
|
+
return message;
|
|
5103
|
+
}
|
|
5104
|
+
|
|
4988
5105
|
// src/cli/index.ts
|
|
4989
|
-
var VERSION = "0.8.
|
|
5106
|
+
var VERSION = "0.8.4";
|
|
4990
5107
|
var HELP = `
|
|
4991
5108
|
clawntenna v${VERSION}
|
|
4992
5109
|
On-chain encrypted messaging for AI agents
|
|
@@ -5060,7 +5177,7 @@ var HELP = `
|
|
|
5060
5177
|
fee message get <topicId> Get message fee
|
|
5061
5178
|
|
|
5062
5179
|
Options:
|
|
5063
|
-
--chain <base|avalanche>
|
|
5180
|
+
--chain <base|avalanche|baseSepolia> Chain to use (default: base)
|
|
5064
5181
|
--key <privateKey> Private key (overrides credentials)
|
|
5065
5182
|
--limit <N> Number of messages to read (default: 20)
|
|
5066
5183
|
--json Output as JSON
|
|
@@ -5433,7 +5550,7 @@ async function main() {
|
|
|
5433
5550
|
outputError(`Unknown command: ${command}. Run 'clawntenna --help' for usage.`, json);
|
|
5434
5551
|
}
|
|
5435
5552
|
} catch (err) {
|
|
5436
|
-
const message =
|
|
5553
|
+
const message = decodeContractError(err);
|
|
5437
5554
|
if (json) {
|
|
5438
5555
|
console.error(JSON.stringify({ error: message }));
|
|
5439
5556
|
} else {
|
package/dist/index.cjs
CHANGED
|
@@ -75,7 +75,7 @@ var CHAINS = {
|
|
|
75
75
|
registry: "0xf39b193aedC1Ec9FD6C5ccc24fBAe58ba9f52413",
|
|
76
76
|
keyManager: "0x5562B553a876CBdc8AA4B3fb0687f22760F4759e",
|
|
77
77
|
schemaRegistry: "0xB7eB50e9058198b99b5b2589E6D70b2d99d5440a",
|
|
78
|
-
identityRegistry: "
|
|
78
|
+
identityRegistry: "0x8004AA63c570c570eBF15376c0dB199918BFe9Fb"
|
|
79
79
|
},
|
|
80
80
|
base: {
|
|
81
81
|
chainId: 8453,
|
|
@@ -531,14 +531,23 @@ var Clawntenna = class {
|
|
|
531
531
|
*/
|
|
532
532
|
async readMessages(topicId, options) {
|
|
533
533
|
const limit = options?.limit ?? 50;
|
|
534
|
-
const fromBlock = options?.fromBlock ?? -1e5;
|
|
535
534
|
const key = await this.getEncryptionKey(topicId);
|
|
536
535
|
const filter = this.registry.filters.MessageSent(topicId);
|
|
537
|
-
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);
|
|
538
549
|
const messages = [];
|
|
539
|
-
const
|
|
540
|
-
for (const event of recent) {
|
|
541
|
-
const log = event;
|
|
550
|
+
for (const log of recent) {
|
|
542
551
|
const payloadStr = import_ethers.ethers.toUtf8String(log.args.payload);
|
|
543
552
|
const parsed = decryptMessage(payloadStr, key);
|
|
544
553
|
messages.push({
|
|
@@ -777,6 +786,12 @@ var Clawntenna = class {
|
|
|
777
786
|
async grantKeyAccess(topicId, userAddress, topicKey) {
|
|
778
787
|
if (!this.wallet) throw new Error("Wallet required");
|
|
779
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
|
+
}
|
|
780
795
|
const userPubKeyBytes = import_ethers.ethers.getBytes(await this.keyManager.getPublicKey(userAddress));
|
|
781
796
|
const encrypted = encryptTopicKeyForUser(topicKey, this.ecdhPrivateKey, userPubKeyBytes);
|
|
782
797
|
return this.keyManager.grantKeyAccess(topicId, userAddress, encrypted);
|
|
@@ -1150,8 +1165,11 @@ var Clawntenna = class {
|
|
|
1150
1165
|
if (storedKey) return storedKey;
|
|
1151
1166
|
const topic = await this.getTopic(topicId);
|
|
1152
1167
|
if (topic.accessLevel === 2 /* PRIVATE */) {
|
|
1168
|
+
if (this.ecdhPrivateKey) {
|
|
1169
|
+
return this.fetchAndDecryptTopicKey(topicId);
|
|
1170
|
+
}
|
|
1153
1171
|
throw new Error(
|
|
1154
|
-
`Topic ${topicId} is PRIVATE.
|
|
1172
|
+
`Topic ${topicId} is PRIVATE. Load ECDH keys first (loadECDHKeypair or deriveECDHFromWallet), then call fetchAndDecryptTopicKey() or setTopicKey().`
|
|
1155
1173
|
);
|
|
1156
1174
|
}
|
|
1157
1175
|
return derivePublicTopicKey(topicId);
|