signer-noncemanager.ts 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. import { defineProperties } from "../utils/index.js";
  2. import { AbstractSigner } from "./abstract-signer.js";
  3. import type { TypedDataDomain, TypedDataField } from "../hash/index.js";
  4. import type {
  5. BlockTag, Provider, TransactionRequest, TransactionResponse
  6. } from "./provider.js";
  7. import type { Signer } from "./signer.js";
  8. /**
  9. * A **NonceManager** wraps another [[Signer]] and automatically manages
  10. * the nonce, ensuring serialized and sequential nonces are used during
  11. * transaction.
  12. */
  13. export class NonceManager extends AbstractSigner {
  14. /**
  15. * The Signer being managed.
  16. */
  17. signer!: Signer;
  18. #noncePromise: null | Promise<number>;
  19. #delta: number;
  20. /**
  21. * Creates a new **NonceManager** to manage %%signer%%.
  22. */
  23. constructor(signer: Signer) {
  24. super(signer.provider);
  25. defineProperties<NonceManager>(this, { signer });
  26. this.#noncePromise = null;
  27. this.#delta = 0;
  28. }
  29. async getAddress(): Promise<string> {
  30. return this.signer.getAddress();
  31. }
  32. connect(provider: null | Provider): NonceManager {
  33. return new NonceManager(this.signer.connect(provider));
  34. }
  35. async getNonce(blockTag?: BlockTag): Promise<number> {
  36. if (blockTag === "pending") {
  37. if (this.#noncePromise == null) {
  38. this.#noncePromise = super.getNonce("pending");
  39. }
  40. const delta = this.#delta;
  41. return (await this.#noncePromise) + delta;
  42. }
  43. return super.getNonce(blockTag);
  44. }
  45. /**
  46. * Manually increment the nonce. This may be useful when managng
  47. * offline transactions.
  48. */
  49. increment(): void {
  50. this.#delta++;
  51. }
  52. /**
  53. * Resets the nonce, causing the **NonceManager** to reload the current
  54. * nonce from the blockchain on the next transaction.
  55. */
  56. reset(): void {
  57. this.#delta = 0;
  58. this.#noncePromise = null;
  59. }
  60. async sendTransaction(tx: TransactionRequest): Promise<TransactionResponse> {
  61. const noncePromise = this.getNonce("pending");
  62. this.increment();
  63. tx = await this.signer.populateTransaction(tx);
  64. tx.nonce = await noncePromise;
  65. // @TODO: Maybe handle interesting/recoverable errors?
  66. // Like don't increment if the tx was certainly not sent
  67. return await this.signer.sendTransaction(tx);
  68. }
  69. signTransaction(tx: TransactionRequest): Promise<string> {
  70. return this.signer.signTransaction(tx);
  71. }
  72. signMessage(message: string | Uint8Array): Promise<string> {
  73. return this.signer.signMessage(message);
  74. }
  75. signTypedData(domain: TypedDataDomain, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): Promise<string> {
  76. return this.signer.signTypedData(domain, types, value);
  77. }
  78. }