@anywalletkit/wallet-near 0.1.6 → 0.1.8

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
@@ -30,6 +30,7 @@ __export(src_exports, {
30
30
  HistoryUnavailableError: () => HistoryUnavailableError,
31
31
  IMPLICIT_ACCOUNT_MIN_DEPOSIT: () => IMPLICIT_ACCOUNT_MIN_DEPOSIT,
32
32
  MAX_GAS: () => MAX_GAS,
33
+ MessageIsTransactionError: () => MessageIsTransactionError,
33
34
  NEAR_BASE_PATH: () => NEAR_BASE_PATH,
34
35
  NEP413_PREFIX: () => NEP413_PREFIX,
35
36
  NearRpc: () => NearRpc,
@@ -102,6 +103,7 @@ var STORAGE_DEPOSIT_GAS = 10n * TGAS;
102
103
  var IMPLICIT_ACCOUNT_MIN_DEPOSIT = 2n * 10n ** 21n;
103
104
 
104
105
  // src/errors.ts
106
+ var import_core = require("@anywalletkit/core");
105
107
  var UnsafeTransactionError = class extends Error {
106
108
  code;
107
109
  constructor(code, message) {
@@ -110,6 +112,13 @@ var UnsafeTransactionError = class extends Error {
110
112
  this.code = code;
111
113
  }
112
114
  };
115
+ var MessageIsTransactionError = class extends import_core.SignerError {
116
+ code = "message-is-transaction";
117
+ constructor() {
118
+ super("This message has the shape of a transaction (a 32-byte NEAR hash, or bytes naming this key as a Solana signer) and is not signed; use signNep413 for off-chain messages, or signTransaction so it can be reviewed.");
119
+ this.name = "MessageIsTransactionError";
120
+ }
121
+ };
113
122
  var SimulationError = class extends Error {
114
123
  reason;
115
124
  constructor(reason) {
@@ -535,7 +544,7 @@ function decodeAnyTransaction(bytes) {
535
544
  }
536
545
 
537
546
  // src/rpc.ts
538
- var import_core = require("@anywalletkit/core");
547
+ var import_core2 = require("@anywalletkit/core");
539
548
  function toBase64(bytes) {
540
549
  if (typeof btoa === "function") {
541
550
  let s = "";
@@ -575,9 +584,9 @@ function httpTransport(url, headers, timeoutMs) {
575
584
  ...signal ? { signal } : {}
576
585
  });
577
586
  } catch (e) {
578
- throw new import_core.NetworkError(`${method}: ${signal?.aborted ? `no answer within ${timeoutMs} ms` : "request failed"}`, { provider: url, cause: e });
587
+ throw new import_core2.NetworkError(`${method}: ${signal?.aborted ? `no answer within ${timeoutMs} ms` : "request failed"}`, { provider: url, cause: e });
579
588
  }
580
- if (!res.ok) throw new import_core.NetworkError(`${method}: HTTP ${res.status}`, { provider: url });
589
+ if (!res.ok) throw new import_core2.NetworkError(`${method}: HTTP ${res.status}`, { provider: url });
581
590
  const json = await res.json();
582
591
  if (json.error) throw new NearRpcError(method, json.error);
583
592
  return json.result;
@@ -605,7 +614,7 @@ function createClient(config) {
605
614
  lastError = e;
606
615
  }
607
616
  }
608
- throw new import_core.NetworkError(`All NEAR providers failed for ${method}`, { cause: lastError });
617
+ throw new import_core2.NetworkError(`All NEAR providers failed for ${method}`, { cause: lastError });
609
618
  };
610
619
  }
611
620
  var UNKNOWN = /UNKNOWN_ACCOUNT|UNKNOWN_ACCESS_KEY|UNKNOWN_TRANSACTION|does not exist/i;
@@ -648,12 +657,12 @@ var NearRpc = class {
648
657
  }
649
658
  async latestBlockHash() {
650
659
  const b = await this.fetch("block", { finality: "final" });
651
- if (!b.header?.hash) throw new import_core.NetworkError("block: no header hash in the response");
660
+ if (!b.header?.hash) throw new import_core2.NetworkError("block: no header hash in the response");
652
661
  return b.header.hash;
653
662
  }
654
663
  async gasPrice() {
655
664
  const r = await this.fetch("gas_price", [null]);
656
- if (!r.gas_price) throw new import_core.NetworkError("gas_price: no price in the response");
665
+ if (!r.gas_price) throw new import_core2.NetworkError("gas_price: no price in the response");
657
666
  return BigInt(r.gas_price);
658
667
  }
659
668
  async sendTx(signedTxBase64, waitUntil = "EXECUTED_OPTIMISTIC") {
@@ -823,7 +832,7 @@ function nep413Hash(p) {
823
832
  }
824
833
 
825
834
  // src/read-only-account.ts
826
- var import_core2 = require("@anywalletkit/core");
835
+ var import_core3 = require("@anywalletkit/core");
827
836
  var import_ed25519 = require("@noble/curves/ed25519.js");
828
837
  var import_sha22 = require("@noble/hashes/sha2.js");
829
838
  var ACTION_RECEIPT_GAS = 2n * 108059500000n;
@@ -865,7 +874,7 @@ var WalletAccountReadOnlyNear = class _WalletAccountReadOnlyNear {
865
874
  return publicKeyToString(this.publicKey);
866
875
  }
867
876
  requireRpc(what) {
868
- if (!this._rpc) throw new import_core2.NetworkError(`The wallet must be connected to a provider to ${what}.`);
877
+ if (!this._rpc) throw new import_core3.NetworkError(`The wallet must be connected to a provider to ${what}.`);
869
878
  return this._rpc;
870
879
  }
871
880
  /** The account id. */
@@ -1126,7 +1135,14 @@ function equalBytes(a, b) {
1126
1135
  }
1127
1136
 
1128
1137
  // src/account.ts
1129
- var import_core3 = require("@anywalletkit/core");
1138
+ var import_core4 = require("@anywalletkit/core");
1139
+ var carriesKey = (bytes, key) => {
1140
+ outer: for (let i = 0; i + key.length <= bytes.length; i++) {
1141
+ for (let j = 0; j < key.length; j++) if (bytes[i + j] !== key[j]) continue outer;
1142
+ return true;
1143
+ }
1144
+ return false;
1145
+ };
1130
1146
  var WalletAccountNear = class _WalletAccountNear extends WalletAccountReadOnlyNear {
1131
1147
  /** nonce / block hash come from the node, so building needs the network. */
1132
1148
  needsNetwork = true;
@@ -1138,12 +1154,12 @@ var WalletAccountNear = class _WalletAccountNear extends WalletAccountReadOnlyNe
1138
1154
  _readOnly;
1139
1155
  constructor(signer, publicKey, path, config = {}, accountId) {
1140
1156
  super(accountId ?? implicitAccountId(publicKey), config, ed25519Key(publicKey));
1141
- this.signer = (0, import_core3.requireCurve)(signer, "ed25519");
1157
+ this.signer = (0, import_core4.requireCurve)(signer, "ed25519");
1142
1158
  this.path = path;
1143
1159
  }
1144
1160
  /** Wrap a raw derived key (takes ownership; zeroized on dispose). */
1145
1161
  static fromPrivateKey(privateKey, publicKey, path, config = {}, accountId) {
1146
- return new _WalletAccountNear(import_core3.LocalSigner.fromPrivateKey("ed25519", privateKey, publicKey, path), publicKey, path, config, accountId);
1162
+ return new _WalletAccountNear(import_core4.LocalSigner.fromPrivateKey("ed25519", privateKey, publicKey, path), publicKey, path, config, accountId);
1147
1163
  }
1148
1164
  /** Account index for the standard m/44'/397'/i' path. */
1149
1165
  get index() {
@@ -1155,15 +1171,23 @@ var WalletAccountNear = class _WalletAccountNear extends WalletAccountReadOnlyNe
1155
1171
  return new _WalletAccountNear(this.signer, this.publicKey.data, this.path, this._config, accountId);
1156
1172
  }
1157
1173
  key() {
1158
- if (this._disposed) throw new import_core3.SignerError("This account has been disposed.");
1174
+ if (this._disposed) throw new import_core4.SignerError("This account has been disposed.");
1159
1175
  }
1160
1176
  async signBytes(bytes, context) {
1161
1177
  this.key();
1162
1178
  return this.signer.sign(bytes, { scheme: "ed25519", context });
1163
1179
  }
1164
- /** Raw ed25519 signature over the message bytes, as NEAR tooling prints it: "ed25519:<base58>". */
1180
+ /**
1181
+ * Raw ed25519 signature over the message bytes, as NEAR tooling prints it: "ed25519:<base58>".
1182
+ * A 32-byte message — string or bytes — is refused with {@link MessageIsTransactionError}: a NEAR
1183
+ * transaction is signed over its 32-byte sha256, so those bytes could be one. Bytes that carry
1184
+ * this account's public key are refused as well: one ed25519 key (an external signer) may also be
1185
+ * a Solana wallet, and a Solana transaction names its signer's key. Sites asking a wallet to sign
1186
+ * a message use {@link signNep413}.
1187
+ */
1165
1188
  async sign(message) {
1166
1189
  const bytes = typeof message === "string" ? new TextEncoder().encode(message) : message;
1190
+ if (bytes.length === 32 || carriesKey(bytes, this.publicKey.data)) throw new MessageIsTransactionError();
1167
1191
  return `ed25519:${bytesToBase58(await this.signBytes(bytes, "near-message"))}`;
1168
1192
  }
1169
1193
  /** NEP-413 off-chain message (what NEAR Intents and dApps ask wallets to sign). */
@@ -1193,7 +1217,7 @@ var WalletAccountNear = class _WalletAccountNear extends WalletAccountReadOnlyNe
1193
1217
  async signTransaction(tx, options = {}) {
1194
1218
  this.key();
1195
1219
  this.requireRpc("sign transactions");
1196
- const { built } = await this.prepare(tx, (0, import_core3.feeLimit)(this._config.transactionMaxFee, options), "transaction");
1220
+ const { built } = await this.prepare(tx, (0, import_core4.feeLimit)(this._config.transactionMaxFee, options), "transaction");
1197
1221
  return toBase64(encodeSignedTransaction(await this.signBuilt(built)));
1198
1222
  }
1199
1223
  async broadcast(signed) {
@@ -1218,7 +1242,7 @@ var WalletAccountNear = class _WalletAccountNear extends WalletAccountReadOnlyNe
1218
1242
  this.key();
1219
1243
  this.requireRpc("send transactions");
1220
1244
  const presigned = typeof tx === "string" ? decodeSigned(tx) : void 0;
1221
- const { built, estimate } = await this.prepare(tx, (0, import_core3.feeLimit)(this._config.transactionMaxFee, options), "transaction");
1245
+ const { built, estimate } = await this.prepare(tx, (0, import_core4.feeLimit)(this._config.transactionMaxFee, options), "transaction");
1222
1246
  const signed = presigned ?? await this.signBuilt(built);
1223
1247
  return { hash: await this.broadcast(signed), fee: estimate.fee, estimate };
1224
1248
  }
@@ -1226,7 +1250,7 @@ var WalletAccountNear = class _WalletAccountNear extends WalletAccountReadOnlyNe
1226
1250
  async transfer(o, writeOptions = {}) {
1227
1251
  this.key();
1228
1252
  this.requireRpc("transfer tokens");
1229
- const { built, estimate } = await this.prepare(await this.transferActions(o), (0, import_core3.feeLimit)(this._config.transferMaxFee, writeOptions), "transfer");
1253
+ const { built, estimate } = await this.prepare(await this.transferActions(o), (0, import_core4.feeLimit)(this._config.transferMaxFee, writeOptions), "transfer");
1230
1254
  return { hash: await this.broadcast(await this.signBuilt(built)), fee: estimate.fee, estimate };
1231
1255
  }
1232
1256
  /** The same account as a {@link WalletAccountReadOnlyNear}. */
@@ -1246,9 +1270,9 @@ function decodeSigned(b64) {
1246
1270
  }
1247
1271
 
1248
1272
  // src/manager.ts
1249
- var import_core4 = require("@anywalletkit/core");
1273
+ var import_core5 = require("@anywalletkit/core");
1250
1274
  var NEAR_BASE_PATH = "m/44'/397'";
1251
- var WalletManagerNear = class extends import_core4.WalletManager {
1275
+ var WalletManagerNear = class extends import_core5.WalletManager {
1252
1276
  constructor(seedOrSigner, config = {}) {
1253
1277
  super(seedOrSigner, config);
1254
1278
  }
@@ -1263,7 +1287,7 @@ var WalletManagerNear = class extends import_core4.WalletManager {
1263
1287
  const external = this.externalSigner();
1264
1288
  if (external) return this.fromSigner(external, full);
1265
1289
  return this.cachedAccount(full, () => {
1266
- const signer = import_core4.LocalSigner.leafFromSeed(this.seedBytes(), "ed25519", full);
1290
+ const signer = import_core5.LocalSigner.leafFromSeed(this.seedBytes(), "ed25519", full);
1267
1291
  return new WalletAccountNear(signer, signer.publicKey, full, this._config);
1268
1292
  });
1269
1293
  }
@@ -1276,14 +1300,14 @@ var WalletManagerNear = class extends import_core4.WalletManager {
1276
1300
  this.requireSigner("ed25519");
1277
1301
  let rel;
1278
1302
  let key = "external";
1279
- if ((0, import_core4.isSeedSigner)(signer)) {
1303
+ if ((0, import_core5.isSeedSigner)(signer)) {
1280
1304
  if (path === signer.path) key = path;
1281
1305
  else if (path.startsWith(`${signer.path}/`)) {
1282
1306
  rel = path.slice(signer.path.length + 1);
1283
1307
  key = path;
1284
- } else throw new import_core4.SignerError(`The injected signer sits at ${signer.path}; it cannot provide ${path}.`);
1308
+ } else throw new import_core5.SignerError(`The injected signer sits at ${signer.path}; it cannot provide ${path}.`);
1285
1309
  } else if (path !== `${NEAR_BASE_PATH}/0'` && path !== "external") {
1286
- throw new import_core4.SignerError(`An external signer has one account; ${path} is not derivable from it.`);
1310
+ throw new import_core5.SignerError(`An external signer has one account; ${path} is not derivable from it.`);
1287
1311
  }
1288
1312
  return this.signerAccount(key, signer, rel, (leaf, publicKey) => new WalletAccountNear(leaf, publicKey, key, this._config));
1289
1313
  }
@@ -1294,14 +1318,14 @@ var WalletManagerNear = class extends import_core4.WalletManager {
1294
1318
  /** NEAR has one protocol gas price (`gas_price` RPC, yoctoNEAR per gas); it can rise ~1%/block, so fast = ×1.1. */
1295
1319
  async getFeeRates() {
1296
1320
  const client = createClient(this._config);
1297
- if (!client) throw new import_core4.NetworkError("The wallet must be connected to a provider to read fee rates.");
1321
+ if (!client) throw new import_core5.NetworkError("The wallet must be connected to a provider to read fee rates.");
1298
1322
  const price = await new NearRpc(client).gasPrice();
1299
1323
  return { slow: price, normal: price, fast: price * 11n / 10n };
1300
1324
  }
1301
1325
  };
1302
1326
 
1303
1327
  // src/history.ts
1304
- var import_core5 = require("@anywalletkit/core");
1328
+ var import_core6 = require("@anywalletkit/core");
1305
1329
  var HistoryUnavailableError = class extends Error {
1306
1330
  chain;
1307
1331
  reason;
@@ -1355,7 +1379,8 @@ function decodeCursor(s) {
1355
1379
  const c = parsed;
1356
1380
  const ok = (v) => v === null || typeof v === "string";
1357
1381
  if (!c || c.v !== 1 || !ok(c.tx) || !ok(c.ft)) throw new Error("@anywalletkit/wallet-near: malformed history cursor");
1358
- return { v: 1, tx: c.tx, ft: c.ft };
1382
+ if (c.skip !== void 0 && (!Array.isArray(c.skip) || c.skip.length > 4 || !c.skip.every((h) => typeof h === "string"))) throw new Error("@anywalletkit/wallet-near: malformed history cursor");
1383
+ return { v: 1, tx: c.tx, ft: c.ft, ...c.skip?.length ? { skip: c.skip } : {} };
1359
1384
  }
1360
1385
  var TokenBucket = class {
1361
1386
  constructor(rate, burst = Math.max(2, Math.ceil(rate))) {
@@ -1448,7 +1473,7 @@ function big(v, approx) {
1448
1473
  return void 0;
1449
1474
  }
1450
1475
  var nsToSeconds = (ts) => {
1451
- if (typeof ts === "string" && /^\d+$/.test(ts)) return (0, import_core5.bigintToNumber)(BigInt(ts) / 1000000000n);
1476
+ if (typeof ts === "string" && /^\d+$/.test(ts)) return (0, import_core6.bigintToNumber)(BigInt(ts) / 1000000000n);
1452
1477
  if (typeof ts === "number") return Math.floor(ts / 1e9);
1453
1478
  return 0;
1454
1479
  };
@@ -1722,30 +1747,57 @@ function createNearHistory(options = {}) {
1722
1747
  const limit = Math.min(MAX_LIMIT, Math.max(1, Math.floor(query.limit ?? DEFAULT_LIMIT)));
1723
1748
  const cursor = query.cursor ? decodeCursor(query.cursor) : { v: 1, tx: "", ft: "" };
1724
1749
  const acct = encodeURIComponent(address);
1725
- const perPage = Math.min(MAX_LIMIT, limit * 2);
1726
- const txRows = cursor.tx === null ? [] : (await get(`/v1/account/${acct}/txns?per_page=${perPage}&order=desc${cursor.tx ? `&cursor=${encodeURIComponent(cursor.tx)}` : ""}`))?.txns ?? [];
1727
- const ftRows = cursor.ft === null ? [] : (await get(`/v1/account/${acct}/ft-txns?per_page=${limit}&order=desc${cursor.ft ? `&cursor=${encodeURIComponent(cursor.ft)}` : ""}`))?.txns ?? [];
1750
+ const perPage = Math.min(MAX_LIMIT - 1, limit * 2);
1751
+ const ftPer = Math.min(MAX_LIMIT - 1, limit);
1752
+ const txFetched = cursor.tx === null ? [] : (await get(`/v1/account/${acct}/txns?per_page=${perPage + 1}&order=desc${cursor.tx ? `&cursor=${encodeURIComponent(cursor.tx)}` : ""}`))?.txns ?? [];
1753
+ const ftFetched = cursor.ft === null ? [] : (await get(`/v1/account/${acct}/ft-txns?per_page=${ftPer + 1}&order=desc${cursor.ft ? `&cursor=${encodeURIComponent(cursor.ft)}` : ""}`))?.txns ?? [];
1754
+ const skip = new Set(cursor.skip ?? []);
1755
+ const leading = (rows) => {
1756
+ let n = 0;
1757
+ while (n < rows.length && skip.has(rows[n].transaction_hash)) n++;
1758
+ return n;
1759
+ };
1760
+ const txSkipped = leading(txFetched);
1761
+ const ftSkipped = leading(ftFetched);
1762
+ const txAll = txFetched.slice(txSkipped);
1763
+ const ftAll = ftFetched.slice(ftSkipped);
1764
+ const txRows = txAll.slice(0, perPage);
1765
+ const ftRows = ftAll.slice(0, ftPer);
1766
+ const txMore = txFetched.length > perPage;
1767
+ const ftMore = ftFetched.length > ftPer;
1728
1768
  const key = (r) => BigInt(typeof r.block_timestamp === "string" ? r.block_timestamp : Math.trunc(r.block_timestamp));
1729
1769
  const all = [...txRows.map((r) => ({ k: key(r), h: r.transaction_hash })), ...ftRows.map((r) => ({ k: key(r), h: r.transaction_hash }))];
1730
1770
  all.sort((a, b) => a.k > b.k ? -1 : a.k < b.k ? 1 : 0);
1771
+ const cut = /* @__PURE__ */ new Set();
1772
+ const goesOnPast = (rows, all2, per, more2) => {
1773
+ const last = rows[rows.length - 1];
1774
+ if (!more2 || !last) return;
1775
+ const next = all2[per];
1776
+ if (next === void 0 || next.transaction_hash === last.transaction_hash) cut.add(last.transaction_hash);
1777
+ };
1778
+ goesOnPast(txRows, txAll, perPage, txMore);
1779
+ goesOnPast(ftRows, ftAll, ftPer, ftMore);
1780
+ const partial = all.length > 0 && all.every((e) => cut.has(e.h)) ? [...cut] : [];
1781
+ if (partial.length) cut.clear();
1731
1782
  const chosen = /* @__PURE__ */ new Set();
1732
1783
  let boundary;
1733
1784
  for (const e of all) {
1734
- if (chosen.has(e.h)) continue;
1785
+ if (chosen.has(e.h) || cut.has(e.h)) continue;
1735
1786
  if (chosen.size >= limit && boundary !== void 0 && e.k < boundary) break;
1736
1787
  chosen.add(e.h);
1737
1788
  if (chosen.size === limit) boundary = e.k;
1738
1789
  }
1739
- const consume = (rows, asked, wasAt, pos) => {
1790
+ const consume = (fetched, skipped, rows, more2, wasAt, pos) => {
1740
1791
  if (wasAt === null) return { used: [], next: null };
1741
1792
  let n = 0;
1742
1793
  while (n < rows.length && chosen.has(rows[n].transaction_hash)) n++;
1743
1794
  const used = rows.slice(0, n);
1744
- if (rows.length < asked && n === rows.length) return { used, next: null };
1745
- return { used, next: n > 0 ? pos(used[n - 1]) : wasAt };
1795
+ if (!more2 && n === rows.length) return { used, next: null };
1796
+ const last = n > 0 ? used[n - 1] : skipped > 0 ? fetched[skipped - 1] : void 0;
1797
+ return { used, next: last ? pos(last) : wasAt };
1746
1798
  };
1747
- const txC = consume(txRows, perPage, cursor.tx, (r) => r.id);
1748
- const ftC = consume(ftRows, limit, cursor.ft, (r) => r.event_index);
1799
+ const txC = consume(txFetched, txSkipped, txRows, txMore, cursor.tx, (r) => r.id);
1800
+ const ftC = consume(ftFetched, ftSkipped, ftRows, ftMore, cursor.ft, (r) => r.event_index);
1749
1801
  const nextTx = txC.next, nextFt = ftC.next;
1750
1802
  const groups = /* @__PURE__ */ new Map();
1751
1803
  for (const h of chosen) groups.set(h, { receipts: [], ft: [] });
@@ -1760,7 +1812,9 @@ function createNearHistory(options = {}) {
1760
1812
  items = items.filter((t) => t.time >= since);
1761
1813
  }
1762
1814
  const page = { items };
1763
- if (more) page.cursor = encodeCursor({ v: 1, tx: nextTx, ft: nextFt });
1815
+ const goesOn = (fetched, skipped, rows) => rows.length === 0 && skipped > 0 && skipped === fetched.length ? [fetched[skipped - 1].transaction_hash] : [];
1816
+ const nextSkip = [.../* @__PURE__ */ new Set([...partial, ...goesOn(txFetched, txSkipped, txRows), ...goesOn(ftFetched, ftSkipped, ftRows)])];
1817
+ if (more) page.cursor = encodeCursor({ v: 1, tx: nextTx, ft: nextFt, ...nextSkip.length ? { skip: nextSkip } : {} });
1764
1818
  return page;
1765
1819
  },
1766
1820
  async getTransaction(hash, address) {