@algorandfoundation/algokit-utils 9.1.1-beta.3 → 9.1.1-beta.5

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/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "**"
7
7
  ],
8
8
  "name": "@algorandfoundation/algokit-utils",
9
- "version": "9.1.1-beta.3",
9
+ "version": "9.1.1-beta.5",
10
10
  "private": false,
11
11
  "description": "A set of core Algorand utilities written in TypeScript and released via npm that make it easier to build solutions on Algorand.",
12
12
  "author": "Algorand Foundation",
@@ -7,6 +7,8 @@ import Indexer = algosdk.Indexer;
7
7
  */
8
8
  export declare class TransactionLogger {
9
9
  private _sentTransactionIds;
10
+ private _latestLastValidRound?;
11
+ private _pushTxn;
10
12
  /**
11
13
  * The list of transaction IDs that has been logged thus far.
12
14
  */
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var algosdk = require('algosdk');
4
+ var config = require('../config.js');
4
5
  var indexer = require('./indexer.js');
5
6
 
6
7
  var decodeSignedTransaction = algosdk.decodeSignedTransaction;
@@ -12,6 +13,13 @@ class TransactionLogger {
12
13
  constructor() {
13
14
  this._sentTransactionIds = [];
14
15
  }
16
+ _pushTxn(stxn) {
17
+ const decoded = decodeSignedTransaction(stxn);
18
+ if (decoded.txn.lastValid > (this._latestLastValidRound ?? BigInt(0))) {
19
+ this._latestLastValidRound = BigInt(decoded.txn.lastValid);
20
+ }
21
+ this._sentTransactionIds.push(decoded.txn.txID());
22
+ }
15
23
  /**
16
24
  * The list of transaction IDs that has been logged thus far.
17
25
  */
@@ -29,14 +37,10 @@ class TransactionLogger {
29
37
  */
30
38
  logRawTransaction(signedTransactions) {
31
39
  if (Array.isArray(signedTransactions)) {
32
- for (const stxn of signedTransactions) {
33
- const decoded = decodeSignedTransaction(stxn);
34
- this._sentTransactionIds.push(decoded.txn.txID());
35
- }
40
+ signedTransactions.forEach((stxn) => this._pushTxn(stxn));
36
41
  }
37
42
  else {
38
- const decoded = decodeSignedTransaction(signedTransactions);
39
- this._sentTransactionIds.push(decoded.txn.txID());
43
+ this._pushTxn(signedTransactions);
40
44
  }
41
45
  }
42
46
  /** Return a proxy that wraps the given Algodv2 with this transaction logger.
@@ -52,7 +56,22 @@ class TransactionLogger {
52
56
  if (this._sentTransactionIds.length === 0)
53
57
  return;
54
58
  const lastTxId = this._sentTransactionIds[this._sentTransactionIds.length - 1];
55
- await indexer.runWhenIndexerCaughtUp(() => indexer$1.lookupTransactionByID(lastTxId).do());
59
+ await indexer.runWhenIndexerCaughtUp(async () => {
60
+ try {
61
+ await indexer$1.lookupTransactionByID(lastTxId).do();
62
+ }
63
+ catch (e) {
64
+ // If the txid lookup failed, then try to look up the last valid round
65
+ // If that round exists, then we know indexer is caught up
66
+ if (this._latestLastValidRound) {
67
+ await indexer$1.lookupBlock(this._latestLastValidRound).do();
68
+ config.Config.getLogger().debug(`waitForIndexer has waited until the last valid round ${this._latestLastValidRound} was indexed, but did not find transaction ${lastTxId} in the indexer.`);
69
+ }
70
+ else {
71
+ throw e;
72
+ }
73
+ }
74
+ });
56
75
  }
57
76
  }
58
77
  class TransactionLoggingAlgodv2ProxyHandler {
@@ -1 +1 @@
1
- {"version":3,"file":"transaction-logger.js","sources":["../../src/testing/transaction-logger.ts"],"sourcesContent":["import algosdk from 'algosdk'\nimport { runWhenIndexerCaughtUp } from './indexer'\nimport Algodv2 = algosdk.Algodv2\nimport decodeSignedTransaction = algosdk.decodeSignedTransaction\nimport Indexer = algosdk.Indexer\n\n/**\n * Allows you to keep track of Algorand transaction IDs by wrapping an `Algodv2` in a proxy.\n * Useful for automated tests.\n */\nexport class TransactionLogger {\n private _sentTransactionIds: string[] = []\n\n /**\n * The list of transaction IDs that has been logged thus far.\n */\n get sentTransactionIds(): Readonly<string[]> {\n return this._sentTransactionIds\n }\n\n /**\n * Clear all logged IDs.\n */\n clear() {\n this._sentTransactionIds = []\n }\n\n /**\n * The method that captures raw transactions and stores the transaction IDs.\n */\n logRawTransaction(signedTransactions: Uint8Array | Uint8Array[]) {\n if (Array.isArray(signedTransactions)) {\n for (const stxn of signedTransactions) {\n const decoded = decodeSignedTransaction(stxn)\n this._sentTransactionIds.push(decoded.txn.txID())\n }\n } else {\n const decoded = decodeSignedTransaction(signedTransactions)\n this._sentTransactionIds.push(decoded.txn.txID())\n }\n }\n\n /** Return a proxy that wraps the given Algodv2 with this transaction logger.\n *\n * @param algod The `Algodv2` to wrap\n * @returns The wrapped `Algodv2`, any transactions sent using this algod instance will be logged by this transaction logger\n */\n capture(algod: Algodv2): Algodv2 {\n return new Proxy<Algodv2>(algod, new TransactionLoggingAlgodv2ProxyHandler(this))\n }\n\n /** Wait until all logged transactions IDs appear in the given `Indexer`. */\n async waitForIndexer(indexer: Indexer) {\n if (this._sentTransactionIds.length === 0) return\n const lastTxId = this._sentTransactionIds[this._sentTransactionIds.length - 1]\n await runWhenIndexerCaughtUp(() => indexer.lookupTransactionByID(lastTxId).do())\n }\n}\n\nclass TransactionLoggingAlgodv2ProxyHandler implements ProxyHandler<Algodv2> {\n private transactionLogger: TransactionLogger\n\n constructor(transactionLogger: TransactionLogger) {\n this.transactionLogger = transactionLogger\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n get(target: Algodv2, property: string | symbol, receiver: any) {\n if (property === 'sendRawTransaction') {\n return (stxOrStxs: Uint8Array | Uint8Array[]) => {\n this.transactionLogger.logRawTransaction(stxOrStxs)\n return target[property].call(receiver, stxOrStxs)\n }\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (target as any)[property]\n }\n}\n"],"names":["indexer","runWhenIndexerCaughtUp"],"mappings":";;;;;AAGA,IAAO,uBAAuB,GAAG,OAAO,CAAC,uBAAuB;AAGhE;;;AAGG;MACU,iBAAiB,CAAA;AAA9B,IAAA,WAAA,GAAA;QACU,IAAmB,CAAA,mBAAA,GAAa,EAAE;;AAE1C;;AAEG;AACH,IAAA,IAAI,kBAAkB,GAAA;QACpB,OAAO,IAAI,CAAC,mBAAmB;;AAGjC;;AAEG;IACH,KAAK,GAAA;AACH,QAAA,IAAI,CAAC,mBAAmB,GAAG,EAAE;;AAG/B;;AAEG;AACH,IAAA,iBAAiB,CAAC,kBAA6C,EAAA;AAC7D,QAAA,IAAI,KAAK,CAAC,OAAO,CAAC,kBAAkB,CAAC,EAAE;AACrC,YAAA,KAAK,MAAM,IAAI,IAAI,kBAAkB,EAAE;AACrC,gBAAA,MAAM,OAAO,GAAG,uBAAuB,CAAC,IAAI,CAAC;AAC7C,gBAAA,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;;;aAE9C;AACL,YAAA,MAAM,OAAO,GAAG,uBAAuB,CAAC,kBAAkB,CAAC;AAC3D,YAAA,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;;;AAIrD;;;;AAIG;AACH,IAAA,OAAO,CAAC,KAAc,EAAA;QACpB,OAAO,IAAI,KAAK,CAAU,KAAK,EAAE,IAAI,qCAAqC,CAAC,IAAI,CAAC,CAAC;;;IAInF,MAAM,cAAc,CAACA,SAAgB,EAAA;AACnC,QAAA,IAAI,IAAI,CAAC,mBAAmB,CAAC,MAAM,KAAK,CAAC;YAAE;AAC3C,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,mBAAmB,CAAC,MAAM,GAAG,CAAC,CAAC;AAC9E,QAAA,MAAMC,8BAAsB,CAAC,MAAMD,SAAO,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC;;AAEnF;AAED,MAAM,qCAAqC,CAAA;AAGzC,IAAA,WAAA,CAAY,iBAAoC,EAAA;AAC9C,QAAA,IAAI,CAAC,iBAAiB,GAAG,iBAAiB;;;AAI5C,IAAA,GAAG,CAAC,MAAe,EAAE,QAAyB,EAAE,QAAa,EAAA;AAC3D,QAAA,IAAI,QAAQ,KAAK,oBAAoB,EAAE;YACrC,OAAO,CAAC,SAAoC,KAAI;AAC9C,gBAAA,IAAI,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,SAAS,CAAC;gBACnD,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC;AACnD,aAAC;;;AAGH,QAAA,OAAQ,MAAc,CAAC,QAAQ,CAAC;;AAEnC;;;;"}
1
+ {"version":3,"file":"transaction-logger.js","sources":["../../src/testing/transaction-logger.ts"],"sourcesContent":["import algosdk from 'algosdk'\nimport { Config } from '../config'\nimport { runWhenIndexerCaughtUp } from './indexer'\nimport Algodv2 = algosdk.Algodv2\nimport decodeSignedTransaction = algosdk.decodeSignedTransaction\nimport Indexer = algosdk.Indexer\n\n/**\n * Allows you to keep track of Algorand transaction IDs by wrapping an `Algodv2` in a proxy.\n * Useful for automated tests.\n */\nexport class TransactionLogger {\n private _sentTransactionIds: string[] = []\n private _latestLastValidRound?: bigint\n\n private _pushTxn(stxn: Uint8Array) {\n const decoded = decodeSignedTransaction(stxn)\n if (decoded.txn.lastValid > (this._latestLastValidRound ?? BigInt(0))) {\n this._latestLastValidRound = BigInt(decoded.txn.lastValid)\n }\n this._sentTransactionIds.push(decoded.txn.txID())\n }\n\n /**\n * The list of transaction IDs that has been logged thus far.\n */\n get sentTransactionIds(): Readonly<string[]> {\n return this._sentTransactionIds\n }\n\n /**\n * Clear all logged IDs.\n */\n clear() {\n this._sentTransactionIds = []\n }\n\n /**\n * The method that captures raw transactions and stores the transaction IDs.\n */\n logRawTransaction(signedTransactions: Uint8Array | Uint8Array[]) {\n if (Array.isArray(signedTransactions)) {\n signedTransactions.forEach((stxn) => this._pushTxn(stxn))\n } else {\n this._pushTxn(signedTransactions)\n }\n }\n\n /** Return a proxy that wraps the given Algodv2 with this transaction logger.\n *\n * @param algod The `Algodv2` to wrap\n * @returns The wrapped `Algodv2`, any transactions sent using this algod instance will be logged by this transaction logger\n */\n capture(algod: Algodv2): Algodv2 {\n return new Proxy<Algodv2>(algod, new TransactionLoggingAlgodv2ProxyHandler(this))\n }\n\n /** Wait until all logged transactions IDs appear in the given `Indexer`. */\n async waitForIndexer(indexer: Indexer) {\n if (this._sentTransactionIds.length === 0) return\n const lastTxId = this._sentTransactionIds[this._sentTransactionIds.length - 1]\n await runWhenIndexerCaughtUp(async () => {\n try {\n await indexer.lookupTransactionByID(lastTxId).do()\n } catch (e) {\n // If the txid lookup failed, then try to look up the last valid round\n // If that round exists, then we know indexer is caught up\n if (this._latestLastValidRound) {\n await indexer.lookupBlock(this._latestLastValidRound).do()\n Config.getLogger().debug(\n `waitForIndexer has waited until the last valid round ${this._latestLastValidRound} was indexed, but did not find transaction ${lastTxId} in the indexer.`,\n )\n } else {\n throw e\n }\n }\n })\n }\n}\n\nclass TransactionLoggingAlgodv2ProxyHandler implements ProxyHandler<Algodv2> {\n private transactionLogger: TransactionLogger\n\n constructor(transactionLogger: TransactionLogger) {\n this.transactionLogger = transactionLogger\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n get(target: Algodv2, property: string | symbol, receiver: any) {\n if (property === 'sendRawTransaction') {\n return (stxOrStxs: Uint8Array | Uint8Array[]) => {\n this.transactionLogger.logRawTransaction(stxOrStxs)\n return target[property].call(receiver, stxOrStxs)\n }\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (target as any)[property]\n }\n}\n"],"names":["indexer","runWhenIndexerCaughtUp","Config"],"mappings":";;;;;;AAIA,IAAO,uBAAuB,GAAG,OAAO,CAAC,uBAAuB;AAGhE;;;AAGG;MACU,iBAAiB,CAAA;AAA9B,IAAA,WAAA,GAAA;QACU,IAAmB,CAAA,mBAAA,GAAa,EAAE;;AAGlC,IAAA,QAAQ,CAAC,IAAgB,EAAA;AAC/B,QAAA,MAAM,OAAO,GAAG,uBAAuB,CAAC,IAAI,CAAC;AAC7C,QAAA,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,IAAI,CAAC,qBAAqB,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE;YACrE,IAAI,CAAC,qBAAqB,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;;AAE5D,QAAA,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;;AAGnD;;AAEG;AACH,IAAA,IAAI,kBAAkB,GAAA;QACpB,OAAO,IAAI,CAAC,mBAAmB;;AAGjC;;AAEG;IACH,KAAK,GAAA;AACH,QAAA,IAAI,CAAC,mBAAmB,GAAG,EAAE;;AAG/B;;AAEG;AACH,IAAA,iBAAiB,CAAC,kBAA6C,EAAA;AAC7D,QAAA,IAAI,KAAK,CAAC,OAAO,CAAC,kBAAkB,CAAC,EAAE;AACrC,YAAA,kBAAkB,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;;aACpD;AACL,YAAA,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC;;;AAIrC;;;;AAIG;AACH,IAAA,OAAO,CAAC,KAAc,EAAA;QACpB,OAAO,IAAI,KAAK,CAAU,KAAK,EAAE,IAAI,qCAAqC,CAAC,IAAI,CAAC,CAAC;;;IAInF,MAAM,cAAc,CAACA,SAAgB,EAAA;AACnC,QAAA,IAAI,IAAI,CAAC,mBAAmB,CAAC,MAAM,KAAK,CAAC;YAAE;AAC3C,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,mBAAmB,CAAC,MAAM,GAAG,CAAC,CAAC;AAC9E,QAAA,MAAMC,8BAAsB,CAAC,YAAW;AACtC,YAAA,IAAI;gBACF,MAAMD,SAAO,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE;;YAClD,OAAO,CAAC,EAAE;;;AAGV,gBAAA,IAAI,IAAI,CAAC,qBAAqB,EAAE;oBAC9B,MAAMA,SAAO,CAAC,WAAW,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC,EAAE,EAAE;AAC1D,oBAAAE,aAAM,CAAC,SAAS,EAAE,CAAC,KAAK,CACtB,CAAA,qDAAA,EAAwD,IAAI,CAAC,qBAAqB,CAAA,2CAAA,EAA8C,QAAQ,CAAA,gBAAA,CAAkB,CAC3J;;qBACI;AACL,oBAAA,MAAM,CAAC;;;AAGb,SAAC,CAAC;;AAEL;AAED,MAAM,qCAAqC,CAAA;AAGzC,IAAA,WAAA,CAAY,iBAAoC,EAAA;AAC9C,QAAA,IAAI,CAAC,iBAAiB,GAAG,iBAAiB;;;AAI5C,IAAA,GAAG,CAAC,MAAe,EAAE,QAAyB,EAAE,QAAa,EAAA;AAC3D,QAAA,IAAI,QAAQ,KAAK,oBAAoB,EAAE;YACrC,OAAO,CAAC,SAAoC,KAAI;AAC9C,gBAAA,IAAI,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,SAAS,CAAC;gBACnD,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC;AACnD,aAAC;;;AAGH,QAAA,OAAQ,MAAc,CAAC,QAAQ,CAAC;;AAEnC;;;;"}
@@ -1,4 +1,5 @@
1
1
  import algosdk from 'algosdk';
2
+ import { Config } from '../config.mjs';
2
3
  import { runWhenIndexerCaughtUp } from './indexer.mjs';
3
4
 
4
5
  var decodeSignedTransaction = algosdk.decodeSignedTransaction;
@@ -10,6 +11,13 @@ class TransactionLogger {
10
11
  constructor() {
11
12
  this._sentTransactionIds = [];
12
13
  }
14
+ _pushTxn(stxn) {
15
+ const decoded = decodeSignedTransaction(stxn);
16
+ if (decoded.txn.lastValid > (this._latestLastValidRound ?? BigInt(0))) {
17
+ this._latestLastValidRound = BigInt(decoded.txn.lastValid);
18
+ }
19
+ this._sentTransactionIds.push(decoded.txn.txID());
20
+ }
13
21
  /**
14
22
  * The list of transaction IDs that has been logged thus far.
15
23
  */
@@ -27,14 +35,10 @@ class TransactionLogger {
27
35
  */
28
36
  logRawTransaction(signedTransactions) {
29
37
  if (Array.isArray(signedTransactions)) {
30
- for (const stxn of signedTransactions) {
31
- const decoded = decodeSignedTransaction(stxn);
32
- this._sentTransactionIds.push(decoded.txn.txID());
33
- }
38
+ signedTransactions.forEach((stxn) => this._pushTxn(stxn));
34
39
  }
35
40
  else {
36
- const decoded = decodeSignedTransaction(signedTransactions);
37
- this._sentTransactionIds.push(decoded.txn.txID());
41
+ this._pushTxn(signedTransactions);
38
42
  }
39
43
  }
40
44
  /** Return a proxy that wraps the given Algodv2 with this transaction logger.
@@ -50,7 +54,22 @@ class TransactionLogger {
50
54
  if (this._sentTransactionIds.length === 0)
51
55
  return;
52
56
  const lastTxId = this._sentTransactionIds[this._sentTransactionIds.length - 1];
53
- await runWhenIndexerCaughtUp(() => indexer.lookupTransactionByID(lastTxId).do());
57
+ await runWhenIndexerCaughtUp(async () => {
58
+ try {
59
+ await indexer.lookupTransactionByID(lastTxId).do();
60
+ }
61
+ catch (e) {
62
+ // If the txid lookup failed, then try to look up the last valid round
63
+ // If that round exists, then we know indexer is caught up
64
+ if (this._latestLastValidRound) {
65
+ await indexer.lookupBlock(this._latestLastValidRound).do();
66
+ Config.getLogger().debug(`waitForIndexer has waited until the last valid round ${this._latestLastValidRound} was indexed, but did not find transaction ${lastTxId} in the indexer.`);
67
+ }
68
+ else {
69
+ throw e;
70
+ }
71
+ }
72
+ });
54
73
  }
55
74
  }
56
75
  class TransactionLoggingAlgodv2ProxyHandler {
@@ -1 +1 @@
1
- {"version":3,"file":"transaction-logger.mjs","sources":["../../src/testing/transaction-logger.ts"],"sourcesContent":["import algosdk from 'algosdk'\nimport { runWhenIndexerCaughtUp } from './indexer'\nimport Algodv2 = algosdk.Algodv2\nimport decodeSignedTransaction = algosdk.decodeSignedTransaction\nimport Indexer = algosdk.Indexer\n\n/**\n * Allows you to keep track of Algorand transaction IDs by wrapping an `Algodv2` in a proxy.\n * Useful for automated tests.\n */\nexport class TransactionLogger {\n private _sentTransactionIds: string[] = []\n\n /**\n * The list of transaction IDs that has been logged thus far.\n */\n get sentTransactionIds(): Readonly<string[]> {\n return this._sentTransactionIds\n }\n\n /**\n * Clear all logged IDs.\n */\n clear() {\n this._sentTransactionIds = []\n }\n\n /**\n * The method that captures raw transactions and stores the transaction IDs.\n */\n logRawTransaction(signedTransactions: Uint8Array | Uint8Array[]) {\n if (Array.isArray(signedTransactions)) {\n for (const stxn of signedTransactions) {\n const decoded = decodeSignedTransaction(stxn)\n this._sentTransactionIds.push(decoded.txn.txID())\n }\n } else {\n const decoded = decodeSignedTransaction(signedTransactions)\n this._sentTransactionIds.push(decoded.txn.txID())\n }\n }\n\n /** Return a proxy that wraps the given Algodv2 with this transaction logger.\n *\n * @param algod The `Algodv2` to wrap\n * @returns The wrapped `Algodv2`, any transactions sent using this algod instance will be logged by this transaction logger\n */\n capture(algod: Algodv2): Algodv2 {\n return new Proxy<Algodv2>(algod, new TransactionLoggingAlgodv2ProxyHandler(this))\n }\n\n /** Wait until all logged transactions IDs appear in the given `Indexer`. */\n async waitForIndexer(indexer: Indexer) {\n if (this._sentTransactionIds.length === 0) return\n const lastTxId = this._sentTransactionIds[this._sentTransactionIds.length - 1]\n await runWhenIndexerCaughtUp(() => indexer.lookupTransactionByID(lastTxId).do())\n }\n}\n\nclass TransactionLoggingAlgodv2ProxyHandler implements ProxyHandler<Algodv2> {\n private transactionLogger: TransactionLogger\n\n constructor(transactionLogger: TransactionLogger) {\n this.transactionLogger = transactionLogger\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n get(target: Algodv2, property: string | symbol, receiver: any) {\n if (property === 'sendRawTransaction') {\n return (stxOrStxs: Uint8Array | Uint8Array[]) => {\n this.transactionLogger.logRawTransaction(stxOrStxs)\n return target[property].call(receiver, stxOrStxs)\n }\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (target as any)[property]\n }\n}\n"],"names":[],"mappings":";;;AAGA,IAAO,uBAAuB,GAAG,OAAO,CAAC,uBAAuB;AAGhE;;;AAGG;MACU,iBAAiB,CAAA;AAA9B,IAAA,WAAA,GAAA;QACU,IAAmB,CAAA,mBAAA,GAAa,EAAE;;AAE1C;;AAEG;AACH,IAAA,IAAI,kBAAkB,GAAA;QACpB,OAAO,IAAI,CAAC,mBAAmB;;AAGjC;;AAEG;IACH,KAAK,GAAA;AACH,QAAA,IAAI,CAAC,mBAAmB,GAAG,EAAE;;AAG/B;;AAEG;AACH,IAAA,iBAAiB,CAAC,kBAA6C,EAAA;AAC7D,QAAA,IAAI,KAAK,CAAC,OAAO,CAAC,kBAAkB,CAAC,EAAE;AACrC,YAAA,KAAK,MAAM,IAAI,IAAI,kBAAkB,EAAE;AACrC,gBAAA,MAAM,OAAO,GAAG,uBAAuB,CAAC,IAAI,CAAC;AAC7C,gBAAA,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;;;aAE9C;AACL,YAAA,MAAM,OAAO,GAAG,uBAAuB,CAAC,kBAAkB,CAAC;AAC3D,YAAA,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;;;AAIrD;;;;AAIG;AACH,IAAA,OAAO,CAAC,KAAc,EAAA;QACpB,OAAO,IAAI,KAAK,CAAU,KAAK,EAAE,IAAI,qCAAqC,CAAC,IAAI,CAAC,CAAC;;;IAInF,MAAM,cAAc,CAAC,OAAgB,EAAA;AACnC,QAAA,IAAI,IAAI,CAAC,mBAAmB,CAAC,MAAM,KAAK,CAAC;YAAE;AAC3C,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,mBAAmB,CAAC,MAAM,GAAG,CAAC,CAAC;AAC9E,QAAA,MAAM,sBAAsB,CAAC,MAAM,OAAO,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC;;AAEnF;AAED,MAAM,qCAAqC,CAAA;AAGzC,IAAA,WAAA,CAAY,iBAAoC,EAAA;AAC9C,QAAA,IAAI,CAAC,iBAAiB,GAAG,iBAAiB;;;AAI5C,IAAA,GAAG,CAAC,MAAe,EAAE,QAAyB,EAAE,QAAa,EAAA;AAC3D,QAAA,IAAI,QAAQ,KAAK,oBAAoB,EAAE;YACrC,OAAO,CAAC,SAAoC,KAAI;AAC9C,gBAAA,IAAI,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,SAAS,CAAC;gBACnD,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC;AACnD,aAAC;;;AAGH,QAAA,OAAQ,MAAc,CAAC,QAAQ,CAAC;;AAEnC;;;;"}
1
+ {"version":3,"file":"transaction-logger.mjs","sources":["../../src/testing/transaction-logger.ts"],"sourcesContent":["import algosdk from 'algosdk'\nimport { Config } from '../config'\nimport { runWhenIndexerCaughtUp } from './indexer'\nimport Algodv2 = algosdk.Algodv2\nimport decodeSignedTransaction = algosdk.decodeSignedTransaction\nimport Indexer = algosdk.Indexer\n\n/**\n * Allows you to keep track of Algorand transaction IDs by wrapping an `Algodv2` in a proxy.\n * Useful for automated tests.\n */\nexport class TransactionLogger {\n private _sentTransactionIds: string[] = []\n private _latestLastValidRound?: bigint\n\n private _pushTxn(stxn: Uint8Array) {\n const decoded = decodeSignedTransaction(stxn)\n if (decoded.txn.lastValid > (this._latestLastValidRound ?? BigInt(0))) {\n this._latestLastValidRound = BigInt(decoded.txn.lastValid)\n }\n this._sentTransactionIds.push(decoded.txn.txID())\n }\n\n /**\n * The list of transaction IDs that has been logged thus far.\n */\n get sentTransactionIds(): Readonly<string[]> {\n return this._sentTransactionIds\n }\n\n /**\n * Clear all logged IDs.\n */\n clear() {\n this._sentTransactionIds = []\n }\n\n /**\n * The method that captures raw transactions and stores the transaction IDs.\n */\n logRawTransaction(signedTransactions: Uint8Array | Uint8Array[]) {\n if (Array.isArray(signedTransactions)) {\n signedTransactions.forEach((stxn) => this._pushTxn(stxn))\n } else {\n this._pushTxn(signedTransactions)\n }\n }\n\n /** Return a proxy that wraps the given Algodv2 with this transaction logger.\n *\n * @param algod The `Algodv2` to wrap\n * @returns The wrapped `Algodv2`, any transactions sent using this algod instance will be logged by this transaction logger\n */\n capture(algod: Algodv2): Algodv2 {\n return new Proxy<Algodv2>(algod, new TransactionLoggingAlgodv2ProxyHandler(this))\n }\n\n /** Wait until all logged transactions IDs appear in the given `Indexer`. */\n async waitForIndexer(indexer: Indexer) {\n if (this._sentTransactionIds.length === 0) return\n const lastTxId = this._sentTransactionIds[this._sentTransactionIds.length - 1]\n await runWhenIndexerCaughtUp(async () => {\n try {\n await indexer.lookupTransactionByID(lastTxId).do()\n } catch (e) {\n // If the txid lookup failed, then try to look up the last valid round\n // If that round exists, then we know indexer is caught up\n if (this._latestLastValidRound) {\n await indexer.lookupBlock(this._latestLastValidRound).do()\n Config.getLogger().debug(\n `waitForIndexer has waited until the last valid round ${this._latestLastValidRound} was indexed, but did not find transaction ${lastTxId} in the indexer.`,\n )\n } else {\n throw e\n }\n }\n })\n }\n}\n\nclass TransactionLoggingAlgodv2ProxyHandler implements ProxyHandler<Algodv2> {\n private transactionLogger: TransactionLogger\n\n constructor(transactionLogger: TransactionLogger) {\n this.transactionLogger = transactionLogger\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n get(target: Algodv2, property: string | symbol, receiver: any) {\n if (property === 'sendRawTransaction') {\n return (stxOrStxs: Uint8Array | Uint8Array[]) => {\n this.transactionLogger.logRawTransaction(stxOrStxs)\n return target[property].call(receiver, stxOrStxs)\n }\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (target as any)[property]\n }\n}\n"],"names":[],"mappings":";;;;AAIA,IAAO,uBAAuB,GAAG,OAAO,CAAC,uBAAuB;AAGhE;;;AAGG;MACU,iBAAiB,CAAA;AAA9B,IAAA,WAAA,GAAA;QACU,IAAmB,CAAA,mBAAA,GAAa,EAAE;;AAGlC,IAAA,QAAQ,CAAC,IAAgB,EAAA;AAC/B,QAAA,MAAM,OAAO,GAAG,uBAAuB,CAAC,IAAI,CAAC;AAC7C,QAAA,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,IAAI,CAAC,qBAAqB,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE;YACrE,IAAI,CAAC,qBAAqB,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;;AAE5D,QAAA,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;;AAGnD;;AAEG;AACH,IAAA,IAAI,kBAAkB,GAAA;QACpB,OAAO,IAAI,CAAC,mBAAmB;;AAGjC;;AAEG;IACH,KAAK,GAAA;AACH,QAAA,IAAI,CAAC,mBAAmB,GAAG,EAAE;;AAG/B;;AAEG;AACH,IAAA,iBAAiB,CAAC,kBAA6C,EAAA;AAC7D,QAAA,IAAI,KAAK,CAAC,OAAO,CAAC,kBAAkB,CAAC,EAAE;AACrC,YAAA,kBAAkB,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;;aACpD;AACL,YAAA,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC;;;AAIrC;;;;AAIG;AACH,IAAA,OAAO,CAAC,KAAc,EAAA;QACpB,OAAO,IAAI,KAAK,CAAU,KAAK,EAAE,IAAI,qCAAqC,CAAC,IAAI,CAAC,CAAC;;;IAInF,MAAM,cAAc,CAAC,OAAgB,EAAA;AACnC,QAAA,IAAI,IAAI,CAAC,mBAAmB,CAAC,MAAM,KAAK,CAAC;YAAE;AAC3C,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,mBAAmB,CAAC,MAAM,GAAG,CAAC,CAAC;AAC9E,QAAA,MAAM,sBAAsB,CAAC,YAAW;AACtC,YAAA,IAAI;gBACF,MAAM,OAAO,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE;;YAClD,OAAO,CAAC,EAAE;;;AAGV,gBAAA,IAAI,IAAI,CAAC,qBAAqB,EAAE;oBAC9B,MAAM,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC,EAAE,EAAE;AAC1D,oBAAA,MAAM,CAAC,SAAS,EAAE,CAAC,KAAK,CACtB,CAAA,qDAAA,EAAwD,IAAI,CAAC,qBAAqB,CAAA,2CAAA,EAA8C,QAAQ,CAAA,gBAAA,CAAkB,CAC3J;;qBACI;AACL,oBAAA,MAAM,CAAC;;;AAGb,SAAC,CAAC;;AAEL;AAED,MAAM,qCAAqC,CAAA;AAGzC,IAAA,WAAA,CAAY,iBAAoC,EAAA;AAC9C,QAAA,IAAI,CAAC,iBAAiB,GAAG,iBAAiB;;;AAI5C,IAAA,GAAG,CAAC,MAAe,EAAE,QAAyB,EAAE,QAAa,EAAA;AAC3D,QAAA,IAAI,QAAQ,KAAK,oBAAoB,EAAE;YACrC,OAAO,CAAC,SAAoC,KAAI;AAC9C,gBAAA,IAAI,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,SAAS,CAAC;gBACnD,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC;AACnD,aAAC;;;AAGH,QAAA,OAAQ,MAAc,CAAC,QAAQ,CAAC;;AAEnC;;;;"}
@@ -1826,7 +1826,7 @@ export declare class ApplicationClient {
1826
1826
  deletable?: boolean | undefined;
1827
1827
  updatable?: boolean | undefined;
1828
1828
  return?: ABIReturn | undefined;
1829
- /** Configuration to resolve app by creator and name `getCreatorAppsByName` */
1829
+ /** The maximum opcode budget for a simulate call as per https://github.com/algorand/go-algorand/blob/807b29a91c371d225e12b9287c5d56e9b33c4e4c/ledger/simulation/trace.go#L104 */
1830
1830
  operationPerformed: "update" | "create";
1831
1831
  } | {
1832
1832
  compiledApproval: import("./app").CompiledTeal;
@@ -20,6 +20,8 @@ var getApplicationAddress = algosdk.getApplicationAddress;
20
20
  var Indexer = algosdk.Indexer;
21
21
  var OnApplicationComplete = algosdk.OnApplicationComplete;
22
22
  var SourceMap = algosdk.ProgramSourceMap;
23
+ /** The maximum opcode budget for a simulate call as per https://github.com/algorand/go-algorand/blob/807b29a91c371d225e12b9287c5d56e9b33c4e4c/ledger/simulation/trace.go#L104 */
24
+ const MAX_SIMULATE_OPCODE_BUDGET = 20000 * 16;
23
25
  /**
24
26
  * Determines deploy time control (UPDATABLE, DELETABLE) value by inspecting application specification
25
27
  * @param approval TEAL Approval program, not the base64 version found on the appSpec
@@ -915,13 +917,11 @@ class AppClient {
915
917
  ...params,
916
918
  };
917
919
  // Read-only calls do not require fees to be paid, as they are only simulated on the network.
918
- // Therefore there is no value in calculating the minimum fee needed for a successful app call with inner transactions.
919
- // As a a result we only need to send a single simulate call,
920
- // however to do this successfully we need to ensure fees for the transaction are fully covered using maxFee.
921
- if (params.coverAppCallInnerTransactionFees) {
922
- if (params.maxFee === undefined) {
923
- throw Error(`Please provide a maxFee for the transaction when coverAppCallInnerTransactionFees is enabled.`);
924
- }
920
+ // With maximum opcode budget provided, ensure_budget (and similar op-up utilities) won't need to create inner transactions,
921
+ // so fee coverage for op-up inner transactions does not need to be accounted for in readonly calls.
922
+ // If max_fee is provided, use it as static_fee, as there may still be inner transactions sent which need to be covered by the outermost transaction,
923
+ // even though ARC-22 specifies that readonly methods should not send inner transactions.
924
+ if (params.coverAppCallInnerTransactionFees && params.maxFee) {
925
925
  readonlyParams.staticFee = params.maxFee;
926
926
  readonlyParams.extraFee = undefined;
927
927
  }
@@ -933,6 +933,8 @@ class AppClient {
933
933
  allowUnnamedResources: params.populateAppCallResources ?? true,
934
934
  // Simulate calls for a readonly method shouldn't invoke signing
935
935
  skipSignatures: true,
936
+ // Simulate calls for a readonly method can use the max opcode budget
937
+ extraOpcodeBudget: MAX_SIMULATE_OPCODE_BUDGET,
936
938
  });
937
939
  return this.processMethodCallReturn({
938
940
  ...result,
@@ -944,6 +946,8 @@ class AppClient {
944
946
  }
945
947
  catch (e) {
946
948
  const error = e;
949
+ // For read-only calls with max opcode budget, fee issues should be rare
950
+ // but we can still provide helpful error message if they occur
947
951
  if (params.coverAppCallInnerTransactionFees && error && error.message && error.message.match(/fee too small/)) {
948
952
  throw Error(`Fees were too small. You may need to increase the transaction maxFee.`);
949
953
  }