@anywalletkit/wallet-near 0.3.1 → 0.4.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/index.d.cts CHANGED
@@ -626,7 +626,13 @@ declare class WalletAccountReadOnlyNear implements IWalletAccountReadOnly {
626
626
  static hashOf(tx: NearBuiltTransaction): Uint8Array;
627
627
  static txHash(tx: NearBuiltTransaction): string;
628
628
  }
629
- declare function receiptOf(hash: string, o: RawFinalOutcome): NearReceipt;
629
+ /**
630
+ * The receipt of a `tx` / `send_tx` outcome: status 0 when the transaction or any receipt failed,
631
+ * 1 when it executed (`SuccessValue` / `SuccessReceiptId`), `undefined` while it is not executed
632
+ * yet (no execution status, `NotStarted` / `Started`, or `final_execution_status` NONE / INCLUDED /
633
+ * INCLUDED_FINAL).
634
+ */
635
+ declare function receiptOf(hash: string, o: RawFinalOutcome): NearReceipt | undefined;
630
636
  declare function parseSignature(signature: string): Uint8Array;
631
637
  /**
632
638
  * An object transaction has exactly the shape the encoder writes, before it is built or signed (also
@@ -660,6 +666,15 @@ declare class WalletAccountNear extends WalletAccountReadOnlyNear implements IWa
660
666
  readonly signerKind: Signer['kind'];
661
667
  private _disposed;
662
668
  private _readOnly;
669
+ /**
670
+ * Highest nonce this account has broadcast (accepted, or maybe sent). The access key is read at
671
+ * `final`, which lags the block that holds our last transaction, so a second send right after
672
+ * the first would sign the same nonce and be refused. A filled-in nonce is never below it + 1;
673
+ * NEAR only needs nonces to increase, so a gap left by a transaction that never landed is harmless.
674
+ */
675
+ private _nonceFloor;
676
+ /** Tail of this account's send queue: prepare → sign → broadcast runs one send at a time. */
677
+ private _sendTail;
663
678
  constructor(signer: Signer, publicKey: Uint8Array, path: string, config?: NearConfig, accountId?: string);
664
679
  /** Wrap a raw derived key (takes ownership; zeroized on dispose). */
665
680
  static fromPrivateKey(privateKey: Uint8Array, publicKey: Uint8Array, path: string, config?: NearConfig, accountId?: string): WalletAccountNear;
@@ -679,7 +694,11 @@ declare class WalletAccountNear extends WalletAccountReadOnlyNear implements IWa
679
694
  sign(message: string | Uint8Array): Promise<string>;
680
695
  /** NEP-413 off-chain message (what NEAR Intents and dApps ask wallets to sign). */
681
696
  signNep413(payload: Nep413Payload): Promise<Nep413Signed>;
682
- /** Sign a fully built transaction. No checks — see `signTransaction`. */
697
+ /**
698
+ * Sign a fully built transaction. It gets the same hard checks as `signTransaction` (this
699
+ * account's signer and key, no dangerous action unless `allowDangerousActions`, gas limit) but
700
+ * no node round-trip: no preflight and no fee check.
701
+ */
683
702
  signBuilt(tx: NearBuiltTransaction): Promise<NearSignedTransaction>;
684
703
  /** Build → hard checks → preflight → fee cap. Shared by sign / send / transfer. */
685
704
  private prepare;
package/dist/index.d.ts CHANGED
@@ -626,7 +626,13 @@ declare class WalletAccountReadOnlyNear implements IWalletAccountReadOnly {
626
626
  static hashOf(tx: NearBuiltTransaction): Uint8Array;
627
627
  static txHash(tx: NearBuiltTransaction): string;
628
628
  }
629
- declare function receiptOf(hash: string, o: RawFinalOutcome): NearReceipt;
629
+ /**
630
+ * The receipt of a `tx` / `send_tx` outcome: status 0 when the transaction or any receipt failed,
631
+ * 1 when it executed (`SuccessValue` / `SuccessReceiptId`), `undefined` while it is not executed
632
+ * yet (no execution status, `NotStarted` / `Started`, or `final_execution_status` NONE / INCLUDED /
633
+ * INCLUDED_FINAL).
634
+ */
635
+ declare function receiptOf(hash: string, o: RawFinalOutcome): NearReceipt | undefined;
630
636
  declare function parseSignature(signature: string): Uint8Array;
631
637
  /**
632
638
  * An object transaction has exactly the shape the encoder writes, before it is built or signed (also
@@ -660,6 +666,15 @@ declare class WalletAccountNear extends WalletAccountReadOnlyNear implements IWa
660
666
  readonly signerKind: Signer['kind'];
661
667
  private _disposed;
662
668
  private _readOnly;
669
+ /**
670
+ * Highest nonce this account has broadcast (accepted, or maybe sent). The access key is read at
671
+ * `final`, which lags the block that holds our last transaction, so a second send right after
672
+ * the first would sign the same nonce and be refused. A filled-in nonce is never below it + 1;
673
+ * NEAR only needs nonces to increase, so a gap left by a transaction that never landed is harmless.
674
+ */
675
+ private _nonceFloor;
676
+ /** Tail of this account's send queue: prepare → sign → broadcast runs one send at a time. */
677
+ private _sendTail;
663
678
  constructor(signer: Signer, publicKey: Uint8Array, path: string, config?: NearConfig, accountId?: string);
664
679
  /** Wrap a raw derived key (takes ownership; zeroized on dispose). */
665
680
  static fromPrivateKey(privateKey: Uint8Array, publicKey: Uint8Array, path: string, config?: NearConfig, accountId?: string): WalletAccountNear;
@@ -679,7 +694,11 @@ declare class WalletAccountNear extends WalletAccountReadOnlyNear implements IWa
679
694
  sign(message: string | Uint8Array): Promise<string>;
680
695
  /** NEP-413 off-chain message (what NEAR Intents and dApps ask wallets to sign). */
681
696
  signNep413(payload: Nep413Payload): Promise<Nep413Signed>;
682
- /** Sign a fully built transaction. No checks — see `signTransaction`. */
697
+ /**
698
+ * Sign a fully built transaction. It gets the same hard checks as `signTransaction` (this
699
+ * account's signer and key, no dangerous action unless `allowDangerousActions`, gas limit) but
700
+ * no node round-trip: no preflight and no fee check.
701
+ */
683
702
  signBuilt(tx: NearBuiltTransaction): Promise<NearSignedTransaction>;
684
703
  /** Build → hard checks → preflight → fee cap. Shared by sign / send / transfer. */
685
704
  private prepare;
package/dist/index.js CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  encodeCursor,
23
23
  failureOf,
24
24
  fromBase64,
25
- isDefinitive,
25
+ isDefinitiveBroadcast,
26
26
  mapNearblocksGroup,
27
27
  mapNearblocksTxn,
28
28
  mapRpcTxStatus,
@@ -30,7 +30,7 @@ import {
30
30
  outcomeFailed,
31
31
  parseJsonPreservingBigInts,
32
32
  toBase64
33
- } from "./chunk-ZWMEZA5A.js";
33
+ } from "./chunk-5UHDSFWA.js";
34
34
 
35
35
  // src/address.ts
36
36
  import { base58 } from "@scure/base";
@@ -862,8 +862,7 @@ var WalletAccountReadOnlyNear = class _WalletAccountReadOnlyNear {
862
862
  /** Status of one of this account's transactions (the `tx` RPC needs the sender). */
863
863
  async getTransactionReceipt(hash) {
864
864
  const o = await this.requireRpc("fetch transaction receipts").txStatus(hash, this.address);
865
- if (!o) return void 0;
866
- return receiptOf(hash, o);
865
+ return o ? receiptOf(hash, o) : void 0;
867
866
  }
868
867
  /** Verify an `ed25519:<base58>` (or bare base58 / hex) signature produced by `sign`. */
869
868
  async verify(message, signature) {
@@ -884,8 +883,10 @@ var WalletAccountReadOnlyNear = class _WalletAccountReadOnlyNear {
884
883
  };
885
884
  function receiptOf(hash, o) {
886
885
  const failure = outcomeFailed(o);
887
- const pending = o.final_execution_status !== void 0 && /NONE|INCLUDED$/.test(o.final_execution_status) && !failureOf(o.status) && o.status === void 0;
888
- return { hash, status: failure ? 0 : 1, ...failure ? { failure } : {}, raw: { ...o, ...pending ? { pending: true } : {} } };
886
+ if (failure) return { hash, status: 0, failure, raw: { ...o } };
887
+ if (o.final_execution_status !== void 0 && /^(?:NONE|INCLUDED|INCLUDED_FINAL)$/.test(o.final_execution_status)) return void 0;
888
+ const executed = typeof o.status === "object" && o.status !== null && ("SuccessValue" in o.status || "SuccessReceiptId" in o.status);
889
+ return executed ? { hash, status: 1, raw: { ...o } } : void 0;
889
890
  }
890
891
  function parseSignature(signature) {
891
892
  const s = signature.startsWith("ed25519:") ? signature.slice("ed25519:".length) : signature;
@@ -993,6 +994,15 @@ var WalletAccountNear = class _WalletAccountNear extends WalletAccountReadOnlyNe
993
994
  signerKind;
994
995
  _disposed = false;
995
996
  _readOnly;
997
+ /**
998
+ * Highest nonce this account has broadcast (accepted, or maybe sent). The access key is read at
999
+ * `final`, which lags the block that holds our last transaction, so a second send right after
1000
+ * the first would sign the same nonce and be refused. A filled-in nonce is never below it + 1;
1001
+ * NEAR only needs nonces to increase, so a gap left by a transaction that never landed is harmless.
1002
+ */
1003
+ _nonceFloor;
1004
+ /** Tail of this account's send queue: prepare → sign → broadcast runs one send at a time. */
1005
+ _sendTail = Promise.resolve();
996
1006
  constructor(signer, publicKey, path, config = {}, accountId) {
997
1007
  super(accountId ?? implicitAccountId(publicKey), config, ed25519Key(publicKey));
998
1008
  this.#signer = requireCurve(signer, "ed25519");
@@ -1037,15 +1047,44 @@ var WalletAccountNear = class _WalletAccountNear extends WalletAccountReadOnlyNe
1037
1047
  const sig = await this.#signBytes(nep413Hash(payload), "near-nep413");
1038
1048
  return { signature: toBase64(sig), publicKey: publicKeyToString(this.publicKey), accountId: this.address };
1039
1049
  }
1040
- /** Sign a fully built transaction. No checks — see `signTransaction`. */
1050
+ /**
1051
+ * Sign a fully built transaction. It gets the same hard checks as `signTransaction` (this
1052
+ * account's signer and key, no dangerous action unless `allowDangerousActions`, gas limit) but
1053
+ * no node round-trip: no preflight and no fee check.
1054
+ */
1041
1055
  async signBuilt(tx) {
1056
+ const own = this.#ownCopy(tx);
1057
+ this.assertSafe(own);
1058
+ return this.#signChecked(own);
1059
+ }
1060
+ /**
1061
+ * A plain copy of `tx`, decoded from its borsh bytes: the object the checks read is the one that
1062
+ * gets hashed and signed, whatever the caller does with its own object (a getter, or an array
1063
+ * changed while a node call is awaited).
1064
+ */
1065
+ #ownCopy(tx) {
1066
+ let copy;
1067
+ try {
1068
+ copy = decodeTransaction(encodeTransaction(tx));
1069
+ } catch (e) {
1070
+ this.assertSafe(tx);
1071
+ throw new UnsafeTransactionError("non-canonical", `Not an encodable NEAR transaction: ${e.message}`);
1072
+ }
1073
+ return copy;
1074
+ }
1075
+ /** Sign a transaction that has already been through `assertSafe`. */
1076
+ async #signChecked(tx) {
1042
1077
  assertWellFormed(tx);
1043
1078
  const sig = await this.#signBytes(WalletAccountReadOnlyNear.hashOf(tx), "near-tx");
1044
1079
  return { transaction: tx, signature: { keyType: "ed25519", data: sig } };
1045
1080
  }
1046
1081
  /** Build → hard checks → preflight → fee cap. Shared by sign / send / transfer. */
1047
1082
  async prepare(tx, maxFee, op) {
1048
- const built = await this.buildTransaction(tx);
1083
+ let built = await this.buildTransaction(tx);
1084
+ if (typeof tx !== "string" && tx.nonce === void 0 && this._nonceFloor !== void 0 && built.nonce <= this._nonceFloor) {
1085
+ built = { ...built, nonce: this._nonceFloor + 1n };
1086
+ }
1087
+ built = this.#ownCopy(built);
1049
1088
  this.assertSafe(built);
1050
1089
  const estimate = await this.estimate(built);
1051
1090
  this.assertSafe(built, estimate.fee);
@@ -1061,18 +1100,24 @@ var WalletAccountNear = class _WalletAccountNear extends WalletAccountReadOnlyNe
1061
1100
  this.key();
1062
1101
  this.requireRpc("sign transactions");
1063
1102
  const { built } = await this.prepare(tx, feeLimit(this._config.transactionMaxFee, options), "transaction");
1064
- return toBase64(encodeSignedTransaction(await this.signBuilt(built)));
1103
+ return toBase64(encodeSignedTransaction(await this.#signChecked(built)));
1065
1104
  }
1066
1105
  async broadcast(signed) {
1067
1106
  const hash = WalletAccountReadOnlyNear.txHash(signed.transaction);
1068
1107
  const rpc = this.requireRpc("broadcast");
1108
+ const used = () => {
1109
+ const n = signed.transaction.nonce;
1110
+ if (this._nonceFloor === void 0 || n > this._nonceFloor) this._nonceFloor = n;
1111
+ };
1069
1112
  let outcome;
1070
1113
  try {
1071
1114
  outcome = await rpc.sendTx(toBase64(encodeSignedTransaction(signed)));
1072
1115
  } catch (e) {
1073
- if (isDefinitive(e)) throw e;
1116
+ if (isDefinitiveBroadcast(e)) throw e;
1117
+ used();
1074
1118
  throw new BroadcastUnknownError(hash, e);
1075
1119
  }
1120
+ used();
1076
1121
  const failure = outcomeFailed(outcome);
1077
1122
  if (failure) throw new Error(`Transaction ${hash} failed on chain: ${failure}`);
1078
1123
  return outcome.transaction?.hash ?? hash;
@@ -1081,20 +1126,35 @@ var WalletAccountNear = class _WalletAccountNear extends WalletAccountReadOnlyNe
1081
1126
  * Sign and broadcast. Accepts a transaction, or base64 borsh of an unsigned /
1082
1127
  * already-signed transaction (a signed one is checked, not re-signed).
1083
1128
  */
1084
- async sendTransaction(tx, options = {}) {
1129
+ sendTransaction(tx, options = {}) {
1130
+ return this.#serial(() => this.#sendTransactionNow(tx, options));
1131
+ }
1132
+ async #sendTransactionNow(tx, options) {
1085
1133
  this.key();
1086
1134
  this.requireRpc("send transactions");
1087
1135
  const presigned = typeof tx === "string" ? decodeSigned(tx) : void 0;
1088
1136
  const { built, estimate } = await this.prepare(tx, feeLimit(this._config.transactionMaxFee, options), "transaction");
1089
- const signed = presigned ?? await this.signBuilt(built);
1137
+ const signed = presigned ?? await this.#signChecked(built);
1090
1138
  return { hash: await this.broadcast(signed), fee: estimate.fee, estimate };
1091
1139
  }
1092
1140
  /** Native NEAR (no `token`) or NEP-141 `ft_transfer` (with `storage_deposit` when the recipient needs it). */
1093
- async transfer(o, writeOptions = {}) {
1141
+ transfer(o, writeOptions = {}) {
1142
+ return this.#serial(() => this.#transferNow(o, writeOptions));
1143
+ }
1144
+ async #transferNow(o, writeOptions) {
1094
1145
  this.key();
1095
1146
  this.requireRpc("transfer tokens");
1096
1147
  const { built, estimate } = await this.prepare(await this.transferActions(o), feeLimit(this._config.transferMaxFee, writeOptions), "transfer");
1097
- return { hash: await this.broadcast(await this.signBuilt(built)), fee: estimate.fee, estimate };
1148
+ return { hash: await this.broadcast(await this.#signChecked(built)), fee: estimate.fee, estimate };
1149
+ }
1150
+ /**
1151
+ * One send at a time per account: two sends started together would read the same access-key
1152
+ * nonce before either is broadcast. A failed send does not block the next.
1153
+ */
1154
+ #serial(run) {
1155
+ const next = this._sendTail.then(run);
1156
+ this._sendTail = next.then(() => void 0, () => void 0);
1157
+ return next;
1098
1158
  }
1099
1159
  /** The same account as a {@link WalletAccountReadOnlyNear}. */
1100
1160
  toReadOnlyAccount() {