@anywalletkit/wallet-near 0.1.8 → 0.2.0

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/history.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  mapRpcTxStatus,
10
10
  nearExplorerTxUrl,
11
11
  parseJsonPreservingBigInts
12
- } from "./chunk-MX4CTCHL.js";
12
+ } from "./chunk-ZWMEZA5A.js";
13
13
  export {
14
14
  HistoryUnavailableError,
15
15
  createNearHistory,
package/dist/index.cjs CHANGED
@@ -47,6 +47,7 @@ __export(src_exports, {
47
47
  YOCTO: () => YOCTO,
48
48
  actionKey: () => actionKey,
49
49
  argsBytes: () => argsBytes,
50
+ assertWellFormed: () => assertWellFormed,
50
51
  base58ToBytes: () => base58ToBytes,
51
52
  bytesToBase58: () => bytesToBase58,
52
53
  createClient: () => createClient,
@@ -67,6 +68,7 @@ __export(src_exports, {
67
68
  implicitAccountId: () => implicitAccountId,
68
69
  isDangerous: () => isDangerous,
69
70
  isImplicitAccountId: () => isImplicitAccountId,
71
+ isPlainObject: () => isPlainObject,
70
72
  isValidAccountId: () => isValidAccountId,
71
73
  mapNearblocksGroup: () => mapNearblocksGroup,
72
74
  mapNearblocksTxn: () => mapNearblocksTxn,
@@ -194,7 +196,7 @@ var bytesToBase58 = (b) => import_base.base58.encode(b);
194
196
 
195
197
  // src/borsh.ts
196
198
  var utf8 = new TextEncoder();
197
- var utf8d = new TextDecoder();
199
+ var utf8d = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
198
200
  var BorshWriter = class {
199
201
  _buf = new Uint8Array(256);
200
202
  _len = 0;
@@ -296,7 +298,10 @@ var BorshReader = class {
296
298
  return utf8d.decode(this.vecU8());
297
299
  }
298
300
  option(read) {
299
- return this.u8() === 0 ? void 0 : read();
301
+ const tag = this.u8();
302
+ if (tag === 0) return void 0;
303
+ if (tag !== 1) throw new RangeError(`borsh: option tag ${tag}`);
304
+ return read();
300
305
  }
301
306
  done() {
302
307
  if (this.remaining !== 0) throw new RangeError(`borsh: ${this.remaining} trailing bytes`);
@@ -339,13 +344,21 @@ var ACTION_TAG = {
339
344
  useGlobalContract: 10
340
345
  };
341
346
  function actionKey(a) {
342
- const k = Object.keys(a)[0];
343
- if (!k || !(k in ACTION_TAG)) throw new RangeError(`unknown action ${k}`);
347
+ const keys = typeof a === "object" && a !== null ? Object.keys(a) : [];
348
+ if (keys.length !== 1) throw new RangeError(`an action must have exactly one key (has: ${keys.join(", ") || "none"})`);
349
+ const k = keys[0];
350
+ if (!Object.prototype.hasOwnProperty.call(ACTION_TAG, k)) throw new RangeError(`unknown action ${k}`);
344
351
  return k;
345
352
  }
353
+ function isPlainObject(v) {
354
+ if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
355
+ const proto = Object.getPrototypeOf(v);
356
+ return proto === Object.prototype || proto === null;
357
+ }
346
358
  function argsBytes(args) {
347
359
  if (args instanceof Uint8Array) return args;
348
360
  if (typeof args === "string") return utf8.encode(args);
361
+ if (!isPlainObject(args)) throw new TypeError("function-call args must be bytes, a string or a plain object");
349
362
  return utf8.encode(JSON.stringify(args));
350
363
  }
351
364
  function writeAccessKey(w, k) {
@@ -403,6 +416,8 @@ function writeAction(w, a) {
403
416
  return;
404
417
  case "functionCall": {
405
418
  const f = a.functionCall;
419
+ if (typeof f.methodName !== "string") throw new TypeError("functionCall.methodName must be a string");
420
+ if (f.gas !== void 0 && typeof f.gas !== "bigint" || f.deposit !== void 0 && typeof f.deposit !== "bigint") throw new TypeError("functionCall gas and deposit must be bigints");
406
421
  w.string(f.methodName).vecU8(argsBytes(f.args)).u64(f.gas ?? 30n * 10n ** 12n).u128(f.deposit ?? 0n);
407
422
  return;
408
423
  }
@@ -449,6 +464,11 @@ function writeAction(w, a) {
449
464
  }
450
465
  }
451
466
  }
467
+ function mode(r) {
468
+ const m = r.u8();
469
+ if (m !== 0 && m !== 1) throw new RangeError(`borsh: unknown global-contract mode ${m}`);
470
+ return m;
471
+ }
452
472
  function readAction(r) {
453
473
  const tag = r.u8();
454
474
  switch (tag) {
@@ -483,11 +503,11 @@ function readAction(r) {
483
503
  }
484
504
  case 9: {
485
505
  const code = r.vecU8();
486
- const m = r.u8();
506
+ const m = mode(r);
487
507
  return { deployGlobalContract: { code, deployMode: m === 0 ? "codeHash" : "accountId" } };
488
508
  }
489
509
  case 10: {
490
- const m = r.u8();
510
+ const m = mode(r);
491
511
  return { useGlobalContract: m === 0 ? { codeHash: r.bytes(32) } : { accountId: r.string() } };
492
512
  }
493
513
  default:
@@ -958,7 +978,8 @@ var WalletAccountReadOnlyNear = class _WalletAccountReadOnlyNear {
958
978
  }
959
979
  /** Fill `signerId` / `publicKey` / `nonce` / `blockHash` (the last two from the node when missing). */
960
980
  async buildTransaction(tx) {
961
- if (typeof tx === "string") return decodeAnyTransaction(fromBase64(tx)).transaction;
981
+ if (typeof tx === "string") return canonicalTransaction(tx);
982
+ assertWellFormed(tx);
962
983
  const signerId = tx.signerId ?? this.address;
963
984
  const publicKey = tx.publicKey ?? this.publicKey;
964
985
  let nonce = tx.nonce;
@@ -1072,6 +1093,7 @@ var WalletAccountReadOnlyNear = class _WalletAccountReadOnlyNear {
1072
1093
  if (tx.publicKey.keyType !== this.publicKey.keyType || !equalBytes(tx.publicKey.data, this.publicKey.data)) throw new UnsafeTransactionError("public-key-mismatch", "The transaction public key is not this account's key.");
1073
1094
  if (!tx.actions.length) throw new UnsafeTransactionError("no-actions", "The transaction has no actions.");
1074
1095
  if (!isValidAccountId(tx.receiverId)) throw new UnsafeTransactionError("invalid-account", `"${tx.receiverId}" is not a valid NEAR account id.`);
1096
+ assertWellFormed(tx);
1075
1097
  if (tx.blockHash.length !== 32) throw new UnsafeTransactionError("missing-block-hash", "The transaction has no 32-byte block hash.");
1076
1098
  if (tx.nonce <= 0n) throw new UnsafeTransactionError("missing-nonce", "The transaction nonce must be positive.");
1077
1099
  if (!this._config.allowDangerousActions) {
@@ -1127,6 +1149,64 @@ function parseSignature(signature) {
1127
1149
  }
1128
1150
  return base58ToBytes(s, 64);
1129
1151
  }
1152
+ var METHOD_NAME = /^[A-Za-z0-9_]{1,256}$/;
1153
+ function assertWellFormed(tx) {
1154
+ if (!Array.isArray(tx.actions)) throw new UnsafeTransactionError("malformed-action", "actions must be an array.");
1155
+ assertShape(tx.actions);
1156
+ assertNames(tx.actions);
1157
+ }
1158
+ function assertShape(actions, depth = 0) {
1159
+ for (const a of actions) {
1160
+ const keys = typeof a === "object" && a !== null ? Object.keys(a) : [];
1161
+ if (keys.length !== 1 || !Object.prototype.hasOwnProperty.call(ACTION_TAG, keys[0])) {
1162
+ throw new UnsafeTransactionError("malformed-action", `An action must have exactly one known key (has: ${keys.join(", ") || "none"}).`);
1163
+ }
1164
+ if ("functionCall" in a) {
1165
+ const f = a.functionCall;
1166
+ 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
+ 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
+ }
1169
+ if ("signedDelegate" in a && depth === 0) assertShape(a.signedDelegate.delegateAction.actions, depth + 1);
1170
+ }
1171
+ }
1172
+ function assertNames(actions, depth = 0) {
1173
+ const account = (id) => {
1174
+ if (typeof id !== "string" || !isValidAccountId(id)) throw new UnsafeTransactionError("invalid-account", `${JSON.stringify(id)} is not a valid NEAR account id.`);
1175
+ };
1176
+ const method = (name) => {
1177
+ if (typeof name !== "string" || !METHOD_NAME.test(name)) throw new UnsafeTransactionError("invalid-method", `${JSON.stringify(name)} is not a method name this wallet signs (ASCII letters, digits, '_').`);
1178
+ };
1179
+ for (const a of actions) {
1180
+ if ("functionCall" in a) method(a.functionCall.methodName);
1181
+ else if ("deleteAccount" in a) account(a.deleteAccount.beneficiaryId);
1182
+ else if ("addKey" in a && "functionCall" in a.addKey.accessKey.permission) {
1183
+ const p = a.addKey.accessKey.permission.functionCall;
1184
+ account(p.receiverId);
1185
+ for (const m of p.methodNames) method(m);
1186
+ } else if ("useGlobalContract" in a && "accountId" in a.useGlobalContract) account(a.useGlobalContract.accountId);
1187
+ else if ("signedDelegate" in a) {
1188
+ const d = a.signedDelegate.delegateAction;
1189
+ account(d.senderId);
1190
+ account(d.receiverId);
1191
+ if (depth > 0) throw new UnsafeTransactionError("unknown-action", "A delegated action inside a delegated action is not signed.");
1192
+ assertNames(d.actions, depth + 1);
1193
+ }
1194
+ }
1195
+ }
1196
+ function canonicalTransaction(b64) {
1197
+ const bytes = fromBase64(b64);
1198
+ let decoded;
1199
+ try {
1200
+ decoded = decodeAnyTransaction(bytes);
1201
+ } catch (e) {
1202
+ throw new UnsafeTransactionError("non-canonical", `Not a borsh NEAR transaction: ${e.message}`);
1203
+ }
1204
+ const again = decoded.signature ? encodeSignedTransaction({ transaction: decoded.transaction, signature: decoded.signature }) : encodeTransaction(decoded.transaction);
1205
+ if (!equalBytes(again, bytes)) throw new UnsafeTransactionError("non-canonical", "This borsh transaction does not re-encode to the same bytes, so what was reviewed may not be what gets signed.");
1206
+ if (!isValidAccountId(decoded.transaction.signerId) || !isValidAccountId(decoded.transaction.receiverId)) throw new UnsafeTransactionError("invalid-account", "The transaction names an invalid NEAR account id.");
1207
+ assertWellFormed(decoded.transaction);
1208
+ return decoded.transaction;
1209
+ }
1130
1210
  function equalBytes(a, b) {
1131
1211
  if (a.length !== b.length) return false;
1132
1212
  let diff = 0;
@@ -1173,7 +1253,7 @@ var WalletAccountNear = class _WalletAccountNear extends WalletAccountReadOnlyNe
1173
1253
  key() {
1174
1254
  if (this._disposed) throw new import_core4.SignerError("This account has been disposed.");
1175
1255
  }
1176
- async signBytes(bytes, context) {
1256
+ async #signBytes(bytes, context) {
1177
1257
  this.key();
1178
1258
  return this.signer.sign(bytes, { scheme: "ed25519", context });
1179
1259
  }
@@ -1188,16 +1268,17 @@ var WalletAccountNear = class _WalletAccountNear extends WalletAccountReadOnlyNe
1188
1268
  async sign(message) {
1189
1269
  const bytes = typeof message === "string" ? new TextEncoder().encode(message) : message;
1190
1270
  if (bytes.length === 32 || carriesKey(bytes, this.publicKey.data)) throw new MessageIsTransactionError();
1191
- return `ed25519:${bytesToBase58(await this.signBytes(bytes, "near-message"))}`;
1271
+ return `ed25519:${bytesToBase58(await this.#signBytes(bytes, "near-message"))}`;
1192
1272
  }
1193
1273
  /** NEP-413 off-chain message (what NEAR Intents and dApps ask wallets to sign). */
1194
1274
  async signNep413(payload) {
1195
- const sig = await this.signBytes(nep413Hash(payload), "near-nep413");
1275
+ const sig = await this.#signBytes(nep413Hash(payload), "near-nep413");
1196
1276
  return { signature: toBase64(sig), publicKey: publicKeyToString(this.publicKey), accountId: this.address };
1197
1277
  }
1198
1278
  /** Sign a fully built transaction. No checks — see `signTransaction`. */
1199
1279
  async signBuilt(tx) {
1200
- const sig = await this.signBytes(WalletAccountReadOnlyNear.hashOf(tx), "near-tx");
1280
+ assertWellFormed(tx);
1281
+ const sig = await this.#signBytes(WalletAccountReadOnlyNear.hashOf(tx), "near-tx");
1201
1282
  return { transaction: tx, signature: { keyType: "ed25519", data: sig } };
1202
1283
  }
1203
1284
  /** Build → hard checks → preflight → fee cap. Shared by sign / send / transfer. */