clawntenna 0.11.4 → 0.12.1

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 CHANGED
@@ -130,7 +130,22 @@ var REGISTRY_ABI = [
130
130
  "event AgentIdentityCleared(uint256 indexed applicationId, address indexed user)",
131
131
  // Data export
132
132
  "function exportMemberData(uint256 appId, address user) view returns (bytes)",
133
- "function exportApplicationData(uint256 appId) view returns (bytes)"
133
+ "function exportApplicationData(uint256 appId) view returns (bytes)",
134
+ // V11 — Typed view helpers
135
+ "function getTopicOwner(uint256 topicId) view returns (address)",
136
+ "function getTopicApplicationId(uint256 topicId) view returns (uint256)",
137
+ "function getApplicationOwner(uint256 appId) view returns (address)",
138
+ "function isTopicAdmin(uint256 topicId, address user) view returns (bool)",
139
+ "function isAppAdmin(uint256 appId, address user) view returns (bool)",
140
+ "function pendingApplicationOwner(uint256) view returns (address)",
141
+ // V11 — Two-step application ownership transfer
142
+ "function transferApplicationOwnership(uint256 appId, address newOwner)",
143
+ "function acceptApplicationOwnership(uint256 appId)",
144
+ "function cancelApplicationOwnershipTransfer(uint256 appId)",
145
+ // V11 — Events
146
+ "event ApplicationOwnershipTransferStarted(uint256 indexed appId, address indexed currentOwner, address indexed newOwner)",
147
+ "event ApplicationOwnershipTransferred(uint256 indexed appId, address indexed previousOwner, address indexed newOwner)",
148
+ "event ApplicationOwnershipTransferCancelled(uint256 indexed appId, address indexed owner)"
134
149
  ];
135
150
  var SCHEMA_REGISTRY_ABI = [
136
151
  // ===== READ FUNCTIONS =====
@@ -3599,25 +3614,10 @@ var Clawntenna = class _Clawntenna {
3599
3614
  }
3600
3615
  /**
3601
3616
  * Check if the current signer is exempt from message fees on a topic.
3602
- * Mirrors contract logic: topic owner, app owner, ROLE_ADMIN, PERMISSION_ADMIN.
3617
+ * Uses the on-chain isTopicAdmin helper which checks: topic owner, app owner, ROLE_ADMIN, PERMISSION_ADMIN.
3603
3618
  */
3604
3619
  async _isFeeExempt(topicId) {
3605
- const addr = this._address.toLowerCase();
3606
- const topic = await this.getTopic(topicId);
3607
- if (topic.owner.toLowerCase() === addr) return true;
3608
- const app = await this.getApplication(Number(topic.applicationId));
3609
- if (app.owner.toLowerCase() === addr) return true;
3610
- try {
3611
- const member = await this.getMember(Number(topic.applicationId), this._address);
3612
- if ((member.roles & 8 /* ADMIN */) !== 0) return true;
3613
- } catch {
3614
- }
3615
- try {
3616
- const perm = await this.getTopicPermission(topicId, this._address);
3617
- if (perm === 4 /* ADMIN */) return true;
3618
- } catch {
3619
- }
3620
- return false;
3620
+ return this.registry.isTopicAdmin(topicId, this._address);
3621
3621
  }
3622
3622
  /**
3623
3623
  * Read and decrypt recent messages from a topic.
@@ -3809,6 +3809,39 @@ var Clawntenna = class _Clawntenna {
3809
3809
  async canWrite(topicId, address) {
3810
3810
  return this.registry.canWriteToTopic(topicId, address);
3811
3811
  }
3812
+ // ===== VIEW HELPERS (V11) =====
3813
+ async getTopicOwner(topicId) {
3814
+ return this.registry.getTopicOwner(topicId);
3815
+ }
3816
+ async getTopicApplicationId(topicId) {
3817
+ const id = await this.registry.getTopicApplicationId(topicId);
3818
+ return Number(id);
3819
+ }
3820
+ async getApplicationOwner(appId) {
3821
+ return this.registry.getApplicationOwner(appId);
3822
+ }
3823
+ async isTopicAdmin(topicId, user) {
3824
+ return this.registry.isTopicAdmin(topicId, user);
3825
+ }
3826
+ async isAppAdmin(appId, user) {
3827
+ return this.registry.isAppAdmin(appId, user);
3828
+ }
3829
+ // ===== APPLICATION OWNERSHIP TRANSFER (V11) =====
3830
+ async transferApplicationOwnership(appId, newOwner) {
3831
+ this.requireSigner();
3832
+ return this.registry.transferApplicationOwnership(appId, newOwner);
3833
+ }
3834
+ async acceptApplicationOwnership(appId) {
3835
+ this.requireSigner();
3836
+ return this.registry.acceptApplicationOwnership(appId);
3837
+ }
3838
+ async cancelApplicationOwnershipTransfer(appId) {
3839
+ this.requireSigner();
3840
+ return this.registry.cancelApplicationOwnershipTransfer(appId);
3841
+ }
3842
+ async getPendingApplicationOwner(appId) {
3843
+ return this.registry.pendingApplicationOwner(appId);
3844
+ }
3812
3845
  // ===== APPLICATIONS =====
3813
3846
  async createApplication(name, description, frontendUrl, allowPublicTopicCreation) {
3814
3847
  this.requireSigner();
@@ -5217,6 +5250,63 @@ async function appUpdateUrl(appId, url, flags) {
5217
5250
  console.log(`Confirmed in block ${receipt?.blockNumber}`);
5218
5251
  }
5219
5252
  }
5253
+ async function appTransferOwnership(appId, newOwner, flags) {
5254
+ const client = loadClient(flags);
5255
+ const json = flags.json ?? false;
5256
+ if (!json) console.log(`Starting ownership transfer for app ${appId} to ${newOwner}...`);
5257
+ const tx = await client.transferApplicationOwnership(appId, newOwner);
5258
+ const receipt = await tx.wait();
5259
+ if (json) {
5260
+ output({ txHash: tx.hash, blockNumber: receipt?.blockNumber, appId, newOwner }, true);
5261
+ } else {
5262
+ console.log(`TX: ${tx.hash}`);
5263
+ console.log(`Confirmed in block ${receipt?.blockNumber}`);
5264
+ console.log(`Pending owner set to ${newOwner}. They must call 'app accept-ownership ${appId}' to complete.`);
5265
+ }
5266
+ }
5267
+ async function appAcceptOwnership(appId, flags) {
5268
+ const client = loadClient(flags);
5269
+ const json = flags.json ?? false;
5270
+ if (!json) console.log(`Accepting ownership of app ${appId}...`);
5271
+ const tx = await client.acceptApplicationOwnership(appId);
5272
+ const receipt = await tx.wait();
5273
+ if (json) {
5274
+ output({ txHash: tx.hash, blockNumber: receipt?.blockNumber, appId }, true);
5275
+ } else {
5276
+ console.log(`TX: ${tx.hash}`);
5277
+ console.log(`Confirmed in block ${receipt?.blockNumber}`);
5278
+ console.log(`You are now the owner of app ${appId}.`);
5279
+ }
5280
+ }
5281
+ async function appCancelTransfer(appId, flags) {
5282
+ const client = loadClient(flags);
5283
+ const json = flags.json ?? false;
5284
+ if (!json) console.log(`Cancelling ownership transfer for app ${appId}...`);
5285
+ const tx = await client.cancelApplicationOwnershipTransfer(appId);
5286
+ const receipt = await tx.wait();
5287
+ if (json) {
5288
+ output({ txHash: tx.hash, blockNumber: receipt?.blockNumber, appId }, true);
5289
+ } else {
5290
+ console.log(`TX: ${tx.hash}`);
5291
+ console.log(`Confirmed in block ${receipt?.blockNumber}`);
5292
+ console.log(`Ownership transfer cancelled.`);
5293
+ }
5294
+ }
5295
+ async function appPendingOwner(appId, flags) {
5296
+ const client = loadClient(flags, false);
5297
+ const json = flags.json ?? false;
5298
+ const pending = await client.getPendingApplicationOwner(appId);
5299
+ const hasPending = pending !== ethers4.ZeroAddress;
5300
+ if (json) {
5301
+ output({ appId, pendingOwner: hasPending ? pending : null }, true);
5302
+ } else {
5303
+ if (hasPending) {
5304
+ console.log(`Pending owner for app ${appId}: ${pending}`);
5305
+ } else {
5306
+ console.log(`No pending ownership transfer for app ${appId}.`);
5307
+ }
5308
+ }
5309
+ }
5220
5310
 
5221
5311
  // src/cli/topics.ts
5222
5312
  import { ethers as ethers5 } from "ethers";
@@ -6158,6 +6248,117 @@ function formatAgo(epochSeconds) {
6158
6248
  return `${Math.floor(diff / 86400)}d ago`;
6159
6249
  }
6160
6250
 
6251
+ // src/cli/skill.ts
6252
+ import { readFileSync as readFileSync2 } from "fs";
6253
+ import { join as join2, dirname } from "path";
6254
+ import { fileURLToPath } from "url";
6255
+ var __dirname = dirname(fileURLToPath(import.meta.url));
6256
+ var PKG_ROOT = join2(__dirname, "..", "..");
6257
+ function showSkill(json) {
6258
+ const content = readFileSync2(join2(PKG_ROOT, "skill.md"), "utf-8");
6259
+ if (json) {
6260
+ console.log(JSON.stringify({ content }));
6261
+ } else {
6262
+ console.log(content);
6263
+ }
6264
+ }
6265
+ function showHeartbeat(json) {
6266
+ const content = readFileSync2(join2(PKG_ROOT, "heartbeat.md"), "utf-8");
6267
+ if (json) {
6268
+ console.log(JSON.stringify({ content }));
6269
+ } else {
6270
+ console.log(content);
6271
+ }
6272
+ }
6273
+ function showSkillJson() {
6274
+ const content = readFileSync2(join2(PKG_ROOT, "skill.json"), "utf-8");
6275
+ console.log(content);
6276
+ }
6277
+
6278
+ // src/cli/state.ts
6279
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
6280
+ import { join as join3 } from "path";
6281
+ var STATE_PATH = join3(CONFIG_DIR, "state.json");
6282
+ function stateInit(json) {
6283
+ if (existsSync2(STATE_PATH)) {
6284
+ output(
6285
+ json ? { status: "exists", path: STATE_PATH } : `State file already exists: ${STATE_PATH}`,
6286
+ json
6287
+ );
6288
+ return;
6289
+ }
6290
+ const creds = loadCredentials();
6291
+ const address = creds?.wallet?.address ?? "";
6292
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6293
+ const state = {
6294
+ version: 2,
6295
+ agent: {
6296
+ address,
6297
+ startedAt: now,
6298
+ lastScanAt: now,
6299
+ mode: "active",
6300
+ skillVersion: "0.12.1",
6301
+ lastSkillCheck: now
6302
+ },
6303
+ chains: {
6304
+ base: {
6305
+ lastScanAt: null,
6306
+ gasBalance: "0",
6307
+ gasCheckedAt: null,
6308
+ apps: {}
6309
+ },
6310
+ avalanche: {
6311
+ lastScanAt: null,
6312
+ gasBalance: "0",
6313
+ gasCheckedAt: null,
6314
+ apps: {}
6315
+ }
6316
+ },
6317
+ escrow: {
6318
+ base: {
6319
+ watching: {},
6320
+ history: [],
6321
+ stats: {
6322
+ totalEarned: "0",
6323
+ totalRefunded: "0",
6324
+ depositsResponded: 0,
6325
+ depositsReleased: 0,
6326
+ depositsRefunded: 0,
6327
+ depositsExpired: 0
6328
+ }
6329
+ },
6330
+ avalanche: {
6331
+ watching: {},
6332
+ history: [],
6333
+ stats: {
6334
+ totalEarned: "0",
6335
+ totalRefunded: "0",
6336
+ depositsResponded: 0,
6337
+ depositsReleased: 0,
6338
+ depositsRefunded: 0,
6339
+ depositsExpired: 0
6340
+ }
6341
+ }
6342
+ },
6343
+ people: {},
6344
+ messages: {
6345
+ sent: [],
6346
+ repliedTo: []
6347
+ },
6348
+ rateLimits: {
6349
+ windowStart: now,
6350
+ messagesInWindow: 0,
6351
+ perTopic: {}
6352
+ }
6353
+ };
6354
+ mkdirSync2(CONFIG_DIR, { recursive: true, mode: 448 });
6355
+ writeFileSync2(STATE_PATH, JSON.stringify(state, null, 2) + "\n", { mode: 384 });
6356
+ output(
6357
+ json ? { status: "created", path: STATE_PATH, address } : `State file created: ${STATE_PATH}`,
6358
+ json
6359
+ );
6360
+ }
6361
+
6161
6362
  // src/cli/errors.ts
6162
6363
  var ERROR_MAP = {
6163
6364
  "0xea8e4eb5": "NotAuthorized \u2014 you lack permission for this action",
@@ -6208,7 +6409,7 @@ function decodeContractError(err) {
6208
6409
  }
6209
6410
 
6210
6411
  // src/cli/index.ts
6211
- var VERSION = "0.11.4";
6412
+ var VERSION = "0.12.1";
6212
6413
  var HELP = `
6213
6414
  clawntenna v${VERSION}
6214
6415
  On-chain encrypted messaging for AI agents
@@ -6230,6 +6431,10 @@ var HELP = `
6230
6431
  app info <appId> Get application details
6231
6432
  app create "<name>" "<desc>" [--url] [--public] Create an application
6232
6433
  app update-url <appId> "<url>" Update frontend URL
6434
+ app transfer-ownership <appId> <newOwner> Start two-step ownership transfer
6435
+ app accept-ownership <appId> Accept pending ownership transfer
6436
+ app cancel-transfer <appId> Cancel pending ownership transfer
6437
+ app pending-owner <appId> Show pending ownership transfer
6233
6438
 
6234
6439
  Topics:
6235
6440
  topics <appId> List all topics in an app
@@ -6296,6 +6501,14 @@ var HELP = `
6296
6501
  escrow refund <depositId> Claim refund
6297
6502
  escrow refund-batch <id1> <id2> ... Batch refund
6298
6503
 
6504
+ Skill Files:
6505
+ skill Print skill.md (full protocol reference)
6506
+ heartbeat Print heartbeat.md (engagement loop)
6507
+ skill-json Print skill.json (metadata)
6508
+
6509
+ State:
6510
+ state init Initialize ~/.config/clawntenna/state.json
6511
+
6299
6512
  Options:
6300
6513
  --chain <base|avalanche|baseSepolia> Chain to use (default: base)
6301
6514
  --key <privateKey> Private key (overrides credentials)
@@ -6412,8 +6625,25 @@ async function main() {
6412
6625
  const url = args[2];
6413
6626
  if (isNaN(appId) || !url) outputError('Usage: clawntenna app update-url <appId> "<url>"', json);
6414
6627
  await appUpdateUrl(appId, url, cf);
6628
+ } else if (sub === "transfer-ownership") {
6629
+ const appId = parseInt(args[1], 10);
6630
+ const newOwner = args[2];
6631
+ if (isNaN(appId) || !newOwner) outputError("Usage: clawntenna app transfer-ownership <appId> <newOwner>", json);
6632
+ await appTransferOwnership(appId, newOwner, cf);
6633
+ } else if (sub === "accept-ownership") {
6634
+ const appId = parseInt(args[1], 10);
6635
+ if (isNaN(appId)) outputError("Usage: clawntenna app accept-ownership <appId>", json);
6636
+ await appAcceptOwnership(appId, cf);
6637
+ } else if (sub === "cancel-transfer") {
6638
+ const appId = parseInt(args[1], 10);
6639
+ if (isNaN(appId)) outputError("Usage: clawntenna app cancel-transfer <appId>", json);
6640
+ await appCancelTransfer(appId, cf);
6641
+ } else if (sub === "pending-owner") {
6642
+ const appId = parseInt(args[1], 10);
6643
+ if (isNaN(appId)) outputError("Usage: clawntenna app pending-owner <appId>", json);
6644
+ await appPendingOwner(appId, cf);
6415
6645
  } else {
6416
- outputError(`Unknown app subcommand: ${sub}. Use: info, create, update-url`, json);
6646
+ outputError(`Unknown app subcommand: ${sub}. Use: info, create, update-url, transfer-ownership, accept-ownership, cancel-transfer, pending-owner`, json);
6417
6647
  }
6418
6648
  break;
6419
6649
  }
@@ -6731,6 +6961,26 @@ async function main() {
6731
6961
  }
6732
6962
  break;
6733
6963
  }
6964
+ // --- Skill Files ---
6965
+ case "skill":
6966
+ showSkill(json);
6967
+ break;
6968
+ case "heartbeat":
6969
+ showHeartbeat(json);
6970
+ break;
6971
+ case "skill-json":
6972
+ showSkillJson();
6973
+ break;
6974
+ // --- State ---
6975
+ case "state": {
6976
+ const sub = args[0];
6977
+ if (sub === "init") {
6978
+ stateInit(json);
6979
+ } else {
6980
+ outputError(`Unknown state subcommand: ${sub}. Use: init`, json);
6981
+ }
6982
+ break;
6983
+ }
6734
6984
  default:
6735
6985
  outputError(`Unknown command: ${command}. Run 'clawntenna --help' for usage.`, json);
6736
6986
  }
package/dist/index.cjs CHANGED
@@ -216,7 +216,22 @@ var REGISTRY_ABI = [
216
216
  "event AgentIdentityCleared(uint256 indexed applicationId, address indexed user)",
217
217
  // Data export
218
218
  "function exportMemberData(uint256 appId, address user) view returns (bytes)",
219
- "function exportApplicationData(uint256 appId) view returns (bytes)"
219
+ "function exportApplicationData(uint256 appId) view returns (bytes)",
220
+ // V11 — Typed view helpers
221
+ "function getTopicOwner(uint256 topicId) view returns (address)",
222
+ "function getTopicApplicationId(uint256 topicId) view returns (uint256)",
223
+ "function getApplicationOwner(uint256 appId) view returns (address)",
224
+ "function isTopicAdmin(uint256 topicId, address user) view returns (bool)",
225
+ "function isAppAdmin(uint256 appId, address user) view returns (bool)",
226
+ "function pendingApplicationOwner(uint256) view returns (address)",
227
+ // V11 — Two-step application ownership transfer
228
+ "function transferApplicationOwnership(uint256 appId, address newOwner)",
229
+ "function acceptApplicationOwnership(uint256 appId)",
230
+ "function cancelApplicationOwnershipTransfer(uint256 appId)",
231
+ // V11 — Events
232
+ "event ApplicationOwnershipTransferStarted(uint256 indexed appId, address indexed currentOwner, address indexed newOwner)",
233
+ "event ApplicationOwnershipTransferred(uint256 indexed appId, address indexed previousOwner, address indexed newOwner)",
234
+ "event ApplicationOwnershipTransferCancelled(uint256 indexed appId, address indexed owner)"
220
235
  ];
221
236
  var SCHEMA_REGISTRY_ABI = [
222
237
  // ===== READ FUNCTIONS =====
@@ -349,21 +364,21 @@ var AccessLevel = /* @__PURE__ */ ((AccessLevel2) => {
349
364
  AccessLevel2[AccessLevel2["PRIVATE"] = 2] = "PRIVATE";
350
365
  return AccessLevel2;
351
366
  })(AccessLevel || {});
352
- var Permission = /* @__PURE__ */ ((Permission2) => {
353
- Permission2[Permission2["NONE"] = 0] = "NONE";
354
- Permission2[Permission2["READ"] = 1] = "READ";
355
- Permission2[Permission2["WRITE"] = 2] = "WRITE";
356
- Permission2[Permission2["READ_WRITE"] = 3] = "READ_WRITE";
357
- Permission2[Permission2["ADMIN"] = 4] = "ADMIN";
358
- return Permission2;
367
+ var Permission = /* @__PURE__ */ ((Permission3) => {
368
+ Permission3[Permission3["NONE"] = 0] = "NONE";
369
+ Permission3[Permission3["READ"] = 1] = "READ";
370
+ Permission3[Permission3["WRITE"] = 2] = "WRITE";
371
+ Permission3[Permission3["READ_WRITE"] = 3] = "READ_WRITE";
372
+ Permission3[Permission3["ADMIN"] = 4] = "ADMIN";
373
+ return Permission3;
359
374
  })(Permission || {});
360
- var Role = /* @__PURE__ */ ((Role2) => {
361
- Role2[Role2["MEMBER"] = 1] = "MEMBER";
362
- Role2[Role2["SUPPORT_MANAGER"] = 2] = "SUPPORT_MANAGER";
363
- Role2[Role2["TOPIC_MANAGER"] = 4] = "TOPIC_MANAGER";
364
- Role2[Role2["ADMIN"] = 8] = "ADMIN";
365
- Role2[Role2["OWNER_DELEGATE"] = 16] = "OWNER_DELEGATE";
366
- return Role2;
375
+ var Role = /* @__PURE__ */ ((Role3) => {
376
+ Role3[Role3["MEMBER"] = 1] = "MEMBER";
377
+ Role3[Role3["SUPPORT_MANAGER"] = 2] = "SUPPORT_MANAGER";
378
+ Role3[Role3["TOPIC_MANAGER"] = 4] = "TOPIC_MANAGER";
379
+ Role3[Role3["ADMIN"] = 8] = "ADMIN";
380
+ Role3[Role3["OWNER_DELEGATE"] = 16] = "OWNER_DELEGATE";
381
+ return Role3;
367
382
  })(Role || {});
368
383
  var DepositStatus = /* @__PURE__ */ ((DepositStatus2) => {
369
384
  DepositStatus2[DepositStatus2["Pending"] = 0] = "Pending";
@@ -799,25 +814,10 @@ var Clawntenna = class _Clawntenna {
799
814
  }
800
815
  /**
801
816
  * Check if the current signer is exempt from message fees on a topic.
802
- * Mirrors contract logic: topic owner, app owner, ROLE_ADMIN, PERMISSION_ADMIN.
817
+ * Uses the on-chain isTopicAdmin helper which checks: topic owner, app owner, ROLE_ADMIN, PERMISSION_ADMIN.
803
818
  */
804
819
  async _isFeeExempt(topicId) {
805
- const addr = this._address.toLowerCase();
806
- const topic = await this.getTopic(topicId);
807
- if (topic.owner.toLowerCase() === addr) return true;
808
- const app = await this.getApplication(Number(topic.applicationId));
809
- if (app.owner.toLowerCase() === addr) return true;
810
- try {
811
- const member = await this.getMember(Number(topic.applicationId), this._address);
812
- if ((member.roles & 8 /* ADMIN */) !== 0) return true;
813
- } catch {
814
- }
815
- try {
816
- const perm = await this.getTopicPermission(topicId, this._address);
817
- if (perm === 4 /* ADMIN */) return true;
818
- } catch {
819
- }
820
- return false;
820
+ return this.registry.isTopicAdmin(topicId, this._address);
821
821
  }
822
822
  /**
823
823
  * Read and decrypt recent messages from a topic.
@@ -1009,6 +1009,39 @@ var Clawntenna = class _Clawntenna {
1009
1009
  async canWrite(topicId, address) {
1010
1010
  return this.registry.canWriteToTopic(topicId, address);
1011
1011
  }
1012
+ // ===== VIEW HELPERS (V11) =====
1013
+ async getTopicOwner(topicId) {
1014
+ return this.registry.getTopicOwner(topicId);
1015
+ }
1016
+ async getTopicApplicationId(topicId) {
1017
+ const id = await this.registry.getTopicApplicationId(topicId);
1018
+ return Number(id);
1019
+ }
1020
+ async getApplicationOwner(appId) {
1021
+ return this.registry.getApplicationOwner(appId);
1022
+ }
1023
+ async isTopicAdmin(topicId, user) {
1024
+ return this.registry.isTopicAdmin(topicId, user);
1025
+ }
1026
+ async isAppAdmin(appId, user) {
1027
+ return this.registry.isAppAdmin(appId, user);
1028
+ }
1029
+ // ===== APPLICATION OWNERSHIP TRANSFER (V11) =====
1030
+ async transferApplicationOwnership(appId, newOwner) {
1031
+ this.requireSigner();
1032
+ return this.registry.transferApplicationOwnership(appId, newOwner);
1033
+ }
1034
+ async acceptApplicationOwnership(appId) {
1035
+ this.requireSigner();
1036
+ return this.registry.acceptApplicationOwnership(appId);
1037
+ }
1038
+ async cancelApplicationOwnershipTransfer(appId) {
1039
+ this.requireSigner();
1040
+ return this.registry.cancelApplicationOwnershipTransfer(appId);
1041
+ }
1042
+ async getPendingApplicationOwner(appId) {
1043
+ return this.registry.pendingApplicationOwner(appId);
1044
+ }
1012
1045
  // ===== APPLICATIONS =====
1013
1046
  async createApplication(name, description, frontendUrl, allowPublicTopicCreation) {
1014
1047
  this.requireSigner();