@anywalletkit/wallet-near 0.2.0 → 0.3.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/index.cjs CHANGED
@@ -45,6 +45,7 @@ __export(src_exports, {
45
45
  WalletAccountReadOnlyNear: () => WalletAccountReadOnlyNear,
46
46
  WalletManagerNear: () => WalletManagerNear,
47
47
  YOCTO: () => YOCTO,
48
+ accessKeyPermissionTag: () => accessKeyPermissionTag,
48
49
  actionKey: () => actionKey,
49
50
  argsBytes: () => argsBytes,
50
51
  assertWellFormed: () => assertWellFormed,
@@ -361,17 +362,34 @@ function argsBytes(args) {
361
362
  if (!isPlainObject(args)) throw new TypeError("function-call args must be bytes, a string or a plain object");
362
363
  return utf8.encode(JSON.stringify(args));
363
364
  }
365
+ var FUNCTION_CALL_PERMISSION_KEYS = /* @__PURE__ */ new Set(["allowance", "receiverId", "methodNames"]);
366
+ function accessKeyPermissionTag(permission) {
367
+ const keys = isPlainObject(permission) ? Object.keys(permission) : [];
368
+ if (keys.length !== 1) throw new TypeError(`an access-key permission must have exactly one variant, fullAccess or functionCall (has: ${keys.join(", ") || "none"})`);
369
+ const variant = permission[keys[0]];
370
+ if (keys[0] === "fullAccess") {
371
+ if (!isPlainObject(variant)) throw new TypeError("fullAccess must be a plain object");
372
+ return 1;
373
+ }
374
+ if (keys[0] === "functionCall") {
375
+ if (!isPlainObject(variant) || Object.keys(variant).some((k) => !FUNCTION_CALL_PERMISSION_KEYS.has(k))) throw new TypeError("functionCall must be a plain { receiverId, methodNames, allowance? } object");
376
+ if (typeof variant.receiverId !== "string") throw new TypeError("functionCall.receiverId must be a string");
377
+ if (!Array.isArray(variant.methodNames) || !variant.methodNames.every((m) => typeof m === "string")) throw new TypeError("functionCall.methodNames must be an array of strings");
378
+ if (variant.allowance !== void 0 && typeof variant.allowance !== "bigint") throw new TypeError("functionCall.allowance must be a bigint or absent");
379
+ return 0;
380
+ }
381
+ throw new TypeError(`unknown access-key permission ${JSON.stringify(keys[0])}`);
382
+ }
364
383
  function writeAccessKey(w, k) {
365
384
  w.u64(k.nonce);
366
- if ("functionCall" in k.permission) {
385
+ const tag = accessKeyPermissionTag(k.permission);
386
+ w.u8(tag);
387
+ if (tag === 0) {
367
388
  const p = k.permission.functionCall;
368
- w.u8(0);
369
389
  w.option(p.allowance, (a) => w.u128(a));
370
390
  w.string(p.receiverId);
371
391
  w.u32(p.methodNames.length);
372
392
  for (const m of p.methodNames) w.string(m);
373
- } else {
374
- w.u8(1);
375
393
  }
376
394
  }
377
395
  function readAccessKey(r) {
@@ -767,8 +785,15 @@ function describeTransaction(tx, ctx = {}) {
767
785
  if (DANGEROUS_ACTIONS.has(key)) {
768
786
  dangerous ??= KIND_OF_DANGEROUS[key];
769
787
  warnings.push({ code: "dangerous-action", message: `${key} changes the account, its keys or its code.` });
770
- if (key === "addKey" && "fullAccess" in a.addKey.accessKey.permission) {
771
- warnings.push({ code: "full-access-key", message: "Adds a FULL ACCESS key: whoever holds it owns the account." });
788
+ if (key === "addKey") {
789
+ let tag;
790
+ try {
791
+ tag = accessKeyPermissionTag(a.addKey?.accessKey?.permission);
792
+ } catch {
793
+ tag = void 0;
794
+ }
795
+ if (tag === 1) warnings.push({ code: "full-access-key", message: "Adds a FULL ACCESS key: whoever holds it owns the account." });
796
+ else if (tag === void 0) warnings.push({ code: "dangerous-action", message: "Malformed access-key permission: neither fullAccess nor functionCall. The wallet refuses to sign it." });
772
797
  }
773
798
  continue;
774
799
  }
@@ -1166,6 +1191,15 @@ function assertShape(actions, depth = 0) {
1166
1191
  if (!(f.args instanceof Uint8Array) && typeof f.args !== "string" && !isPlainObject(f.args)) throw new UnsafeTransactionError("malformed-action", "Function-call args must be bytes, a string or a plain object.");
1167
1192
  if (f.gas !== void 0 && typeof f.gas !== "bigint" || f.deposit !== void 0 && typeof f.deposit !== "bigint") throw new UnsafeTransactionError("malformed-action", "Function-call gas and deposit must be bigints.");
1168
1193
  }
1194
+ if ("addKey" in a) {
1195
+ const k = a.addKey;
1196
+ if (!isPlainObject(k) || !isPlainObject(k.accessKey)) throw new UnsafeTransactionError("malformed-action", "addKey must carry an accessKey object.");
1197
+ try {
1198
+ accessKeyPermissionTag(k.accessKey.permission);
1199
+ } catch (e) {
1200
+ throw new UnsafeTransactionError("malformed-action", `AddKey: ${e.message}.`);
1201
+ }
1202
+ }
1169
1203
  if ("signedDelegate" in a && depth === 0) assertShape(a.signedDelegate.delegateAction.actions, depth + 1);
1170
1204
  }
1171
1205
  }
@@ -1228,13 +1262,20 @@ var WalletAccountNear = class _WalletAccountNear extends WalletAccountReadOnlyNe
1228
1262
  needsNetwork = true;
1229
1263
  /** Derivation path, or `external` for an external signer. */
1230
1264
  path;
1231
- /** The signer that holds the key. */
1232
- signer;
1265
+ /**
1266
+ * The signer that holds the key, in a private field: not a property of the account, so no code
1267
+ * the account is handed to (a protocol module, a logger) can reach it and sign around the
1268
+ * account's checks (audit 2026-10-29 A1).
1269
+ */
1270
+ #signer;
1271
+ /** What holds the key: `seed`, `key` (one imported key) or `external`. */
1272
+ signerKind;
1233
1273
  _disposed = false;
1234
1274
  _readOnly;
1235
1275
  constructor(signer, publicKey, path, config = {}, accountId) {
1236
1276
  super(accountId ?? implicitAccountId(publicKey), config, ed25519Key(publicKey));
1237
- this.signer = (0, import_core4.requireCurve)(signer, "ed25519");
1277
+ this.#signer = (0, import_core4.requireCurve)(signer, "ed25519");
1278
+ this.signerKind = this.#signer.kind;
1238
1279
  this.path = path;
1239
1280
  }
1240
1281
  /** Wrap a raw derived key (takes ownership; zeroized on dispose). */
@@ -1248,14 +1289,14 @@ var WalletAccountNear = class _WalletAccountNear extends WalletAccountReadOnlyNe
1248
1289
  }
1249
1290
  /** The same key bound to a named account (`alice.near`) that lists it as a full-access key. */
1250
1291
  withAccountId(accountId) {
1251
- return new _WalletAccountNear(this.signer, this.publicKey.data, this.path, this._config, accountId);
1292
+ return new _WalletAccountNear(this.#signer, this.publicKey.data, this.path, this._config, accountId);
1252
1293
  }
1253
1294
  key() {
1254
1295
  if (this._disposed) throw new import_core4.SignerError("This account has been disposed.");
1255
1296
  }
1256
1297
  async #signBytes(bytes, context) {
1257
1298
  this.key();
1258
- return this.signer.sign(bytes, { scheme: "ed25519", context });
1299
+ return this.#signer.sign(bytes, { scheme: "ed25519", context });
1259
1300
  }
1260
1301
  /**
1261
1302
  * Raw ed25519 signature over the message bytes, as NEAR tooling prints it: "ed25519:<base58>".
@@ -1342,7 +1383,7 @@ var WalletAccountNear = class _WalletAccountNear extends WalletAccountReadOnlyNe
1342
1383
  /** Dispose the signer (zeroizes a local key). Idempotent. */
1343
1384
  dispose() {
1344
1385
  this._disposed = true;
1345
- this.signer.dispose();
1386
+ this.#signer.dispose();
1346
1387
  }
1347
1388
  };
1348
1389
  function decodeSigned(b64) {