@algorandfoundation/algokit-utils 8.1.0-beta.1 → 8.1.0-beta.3
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/index.js +1 -0
- package/index.js.map +1 -1
- package/index.mjs +1 -1
- package/package.json +1 -1
- package/testing/fixtures/algorand-fixture.d.ts +19 -19
- package/testing/fixtures/algorand-fixture.js +7 -5
- package/testing/fixtures/algorand-fixture.js.map +1 -1
- package/testing/fixtures/algorand-fixture.mjs +7 -5
- package/testing/fixtures/algorand-fixture.mjs.map +1 -1
- package/testing/test-logger.js +1 -1
- package/testing/test-logger.js.map +1 -1
- package/testing/test-logger.mjs +1 -1
- package/testing/test-logger.mjs.map +1 -1
- package/testing/transaction-logger.js +4 -1
- package/testing/transaction-logger.js.map +1 -1
- package/testing/transaction-logger.mjs +4 -1
- package/testing/transaction-logger.mjs.map +1 -1
- package/transaction/transaction.d.ts +18 -3
- package/transaction/transaction.js +356 -215
- package/transaction/transaction.js.map +1 -1
- package/transaction/transaction.mjs +356 -216
- package/transaction/transaction.mjs.map +1 -1
- package/types/app-client.d.ts +5 -1
- package/types/app-deployer.js +1 -1
- package/types/app-deployer.js.map +1 -1
- package/types/app-deployer.mjs +1 -1
- package/types/app-deployer.mjs.map +1 -1
- package/types/composer.d.ts +5 -3
- package/types/composer.js +96 -24
- package/types/composer.js.map +1 -1
- package/types/composer.mjs +96 -24
- package/types/composer.mjs.map +1 -1
- package/types/testing.d.ts +46 -2
- package/types/transaction.d.ts +13 -0
|
@@ -191,14 +191,14 @@ const sendTransaction = async function (send, algod) {
|
|
|
191
191
|
return { transaction };
|
|
192
192
|
}
|
|
193
193
|
let txnToSend = transaction;
|
|
194
|
-
const
|
|
195
|
-
// Populate
|
|
194
|
+
const populateAppCallResources = sendParams?.populateAppCallResources ?? config.Config.populateAppCallResources;
|
|
195
|
+
// Populate resources if the transaction is an appcall and populateAppCallResources wasn't explicitly set to false
|
|
196
196
|
// NOTE: Temporary false by default until this algod bug is fixed: https://github.com/algorand/go-algorand/issues/5914
|
|
197
|
-
if (txnToSend.type === algosdk.TransactionType.appl &&
|
|
197
|
+
if (txnToSend.type === algosdk.TransactionType.appl && populateAppCallResources) {
|
|
198
198
|
const newAtc = new AtomicTransactionComposer();
|
|
199
199
|
newAtc.addTransaction({ txn: txnToSend, signer: getSenderTransactionSigner(from) });
|
|
200
|
-
const
|
|
201
|
-
txnToSend =
|
|
200
|
+
const atc = await prepareGroupForSending(newAtc, algod, { ...sendParams, populateAppCallResources });
|
|
201
|
+
txnToSend = atc.buildGroup()[0].txn;
|
|
202
202
|
}
|
|
203
203
|
const signedTransaction = await signTransaction(txnToSend, from);
|
|
204
204
|
await algod.sendRawTransaction(signedTransaction).do();
|
|
@@ -210,13 +210,19 @@ const sendTransaction = async function (send, algod) {
|
|
|
210
210
|
return { transaction: txnToSend, confirmation };
|
|
211
211
|
};
|
|
212
212
|
/**
|
|
213
|
-
* Get
|
|
213
|
+
* Get the execution info of a transaction group for the given ATC
|
|
214
|
+
* The function uses the simulate endpoint and depending on the sendParams can return the following:
|
|
215
|
+
* - The unnamed resources accessed by the group
|
|
216
|
+
* - The unnamed resources accessed by each transaction in the group
|
|
217
|
+
* - The required fee delta for each transaction in the group. A positive value indicates a fee deficit, a negative value indicates a surplus.
|
|
214
218
|
*
|
|
215
|
-
* @param algod The algod client to use for the simulation
|
|
216
219
|
* @param atc The ATC containing the txn group
|
|
217
|
-
* @
|
|
220
|
+
* @param algod The algod client to use for the simulation
|
|
221
|
+
* @param sendParams The send params for the transaction group
|
|
222
|
+
* @param additionalAtcContext Additional ATC context used to determine how best to alter transactions in the group
|
|
223
|
+
* @returns The execution info for the group
|
|
218
224
|
*/
|
|
219
|
-
async function
|
|
225
|
+
async function getGroupExecutionInfo(atc, algod, sendParams, additionalAtcContext) {
|
|
220
226
|
const simulateRequest = new algosdk.modelsv2.SimulateRequest({
|
|
221
227
|
txnGroups: [],
|
|
222
228
|
allowUnnamedResources: true,
|
|
@@ -225,28 +231,77 @@ async function getUnnamedAppCallResourcesAccessed(atc, algod) {
|
|
|
225
231
|
});
|
|
226
232
|
const nullSigner = algosdk.makeEmptyTransactionSigner();
|
|
227
233
|
const emptySignerAtc = atc.clone();
|
|
228
|
-
|
|
234
|
+
const appCallIndexesWithoutMaxFees = [];
|
|
235
|
+
emptySignerAtc['transactions'].forEach((t, i) => {
|
|
229
236
|
t.signer = nullSigner;
|
|
237
|
+
if (sendParams.coverAppCallInnerTransactionFees && t.txn.type === algosdk.TransactionType.appl) {
|
|
238
|
+
if (!additionalAtcContext?.suggestedParams) {
|
|
239
|
+
throw Error(`Please provide additionalAtcContext.suggestedParams when coverAppCallInnerTransactionFees is enabled`);
|
|
240
|
+
}
|
|
241
|
+
const maxFee = additionalAtcContext?.maxFees?.get(i)?.microAlgo;
|
|
242
|
+
if (maxFee === undefined) {
|
|
243
|
+
appCallIndexesWithoutMaxFees.push(i);
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
t.txn.fee = maxFee;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
230
249
|
});
|
|
250
|
+
if (sendParams.coverAppCallInnerTransactionFees && appCallIndexesWithoutMaxFees.length > 0) {
|
|
251
|
+
throw Error(`Please provide a maxFee for each app call transaction when coverAppCallInnerTransactionFees is enabled. Required for transaction ${appCallIndexesWithoutMaxFees.join(', ')}`);
|
|
252
|
+
}
|
|
253
|
+
const perByteTxnFee = BigInt(additionalAtcContext?.suggestedParams.fee ?? 0n);
|
|
254
|
+
const minTxnFee = BigInt(additionalAtcContext?.suggestedParams.minFee ?? 1000n);
|
|
231
255
|
const result = await emptySignerAtc.simulate(algod, simulateRequest);
|
|
232
256
|
const groupResponse = result.simulateResponse.txnGroups[0];
|
|
233
257
|
if (groupResponse.failureMessage) {
|
|
234
|
-
|
|
258
|
+
if (sendParams.coverAppCallInnerTransactionFees && groupResponse.failureMessage.match(/fee too small/)) {
|
|
259
|
+
throw Error(`Fees were too small to resolve execution info via simulate. You may need to increase an app call transaction maxFee.`);
|
|
260
|
+
}
|
|
261
|
+
throw Error(`Error resolving execution info via simulate in transaction ${groupResponse.failedAt}: ${groupResponse.failureMessage}`);
|
|
235
262
|
}
|
|
236
263
|
return {
|
|
237
|
-
|
|
238
|
-
txns: groupResponse.txnResults.map(
|
|
239
|
-
|
|
240
|
-
|
|
264
|
+
groupUnnamedResourcesAccessed: sendParams.populateAppCallResources ? groupResponse.unnamedResourcesAccessed : undefined,
|
|
265
|
+
txns: groupResponse.txnResults.map((txn, i) => {
|
|
266
|
+
const originalTxn = atc['transactions'][i].txn;
|
|
267
|
+
let requiredFeeDelta = 0n;
|
|
268
|
+
if (sendParams.coverAppCallInnerTransactionFees) {
|
|
269
|
+
// Min fee calc is lifted from algosdk https://github.com/algorand/js-algorand-sdk/blob/6973ff583b243ddb0632e91f4c0383021430a789/src/transaction.ts#L710
|
|
270
|
+
// 75 is the number of bytes added to a txn after signing it
|
|
271
|
+
const parentPerByteFee = perByteTxnFee * BigInt(originalTxn.toByte().length + 75);
|
|
272
|
+
const parentMinFee = parentPerByteFee < minTxnFee ? minTxnFee : parentPerByteFee;
|
|
273
|
+
const parentFeeDelta = parentMinFee - originalTxn.fee;
|
|
274
|
+
if (originalTxn.type === algosdk.TransactionType.appl) {
|
|
275
|
+
const calculateInnerFeeDelta = (itxns, acc = 0n) => {
|
|
276
|
+
// Surplus inner transaction fees do not pool up to the parent transaction.
|
|
277
|
+
// Additionally surplus inner transaction fees only pool from sibling transactions that are sent prior to a given inner transaction, hence why we iterate in reverse order.
|
|
278
|
+
return itxns.reverse().reduce((acc, itxn) => {
|
|
279
|
+
const currentFeeDelta = (itxn.innerTxns && itxn.innerTxns.length > 0 ? calculateInnerFeeDelta(itxn.innerTxns, acc) : acc) +
|
|
280
|
+
(minTxnFee - itxn.txn.txn.fee); // Inner transactions don't require per byte fees
|
|
281
|
+
return currentFeeDelta < 0n ? 0n : currentFeeDelta;
|
|
282
|
+
}, acc);
|
|
283
|
+
};
|
|
284
|
+
const innerFeeDelta = calculateInnerFeeDelta(txn.txnResult.innerTxns ?? []);
|
|
285
|
+
requiredFeeDelta = innerFeeDelta + parentFeeDelta;
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
requiredFeeDelta = parentFeeDelta;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
unnamedResourcesAccessed: sendParams.populateAppCallResources ? txn.unnamedResourcesAccessed : undefined,
|
|
293
|
+
requiredFeeDelta,
|
|
294
|
+
};
|
|
295
|
+
}),
|
|
241
296
|
};
|
|
242
297
|
}
|
|
243
298
|
/**
|
|
244
299
|
* Take an existing Atomic Transaction Composer and return a new one with the required
|
|
245
|
-
*
|
|
300
|
+
* app call resources populated into it
|
|
246
301
|
*
|
|
247
302
|
* @param algod The algod client to use for the simulation
|
|
248
303
|
* @param atc The ATC containing the txn group
|
|
249
|
-
* @returns A new ATC with the resources
|
|
304
|
+
* @returns A new ATC with the resources populated into the transactions
|
|
250
305
|
*
|
|
251
306
|
* @privateRemarks
|
|
252
307
|
*
|
|
@@ -258,229 +313,312 @@ async function getUnnamedAppCallResourcesAccessed(atc, algod) {
|
|
|
258
313
|
*
|
|
259
314
|
*/
|
|
260
315
|
async function populateAppCallResources(atc, algod) {
|
|
261
|
-
|
|
316
|
+
return await prepareGroupForSending(atc, algod, { populateAppCallResources: true });
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Take an existing Atomic Transaction Composer and return a new one with changes applied to the transactions
|
|
320
|
+
* based on the supplied sendParams to ensure the transaction group is ready for sending.
|
|
321
|
+
*
|
|
322
|
+
* @param algod The algod client to use for the simulation
|
|
323
|
+
* @param atc The ATC containing the txn group
|
|
324
|
+
* @param sendParams The send params for the transaction group
|
|
325
|
+
* @param additionalAtcContext Additional ATC context used to determine how best to change the transactions in the group
|
|
326
|
+
* @returns A new ATC with the changes applied
|
|
327
|
+
*
|
|
328
|
+
* @privateRemarks
|
|
329
|
+
* Parts of this function will eventually be implemented in algod. Namely:
|
|
330
|
+
* - Simulate will return information on how to populate reference arrays, see https://github.com/algorand/go-algorand/pull/6015
|
|
331
|
+
*/
|
|
332
|
+
async function prepareGroupForSending(atc, algod, sendParams, additionalAtcContext) {
|
|
333
|
+
const executionInfo = await getGroupExecutionInfo(atc, algod, sendParams, additionalAtcContext);
|
|
262
334
|
const group = atc.buildGroup();
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
335
|
+
const [_, additionalTransactionFees] = sendParams.coverAppCallInnerTransactionFees
|
|
336
|
+
? executionInfo.txns
|
|
337
|
+
.map((txn, i) => {
|
|
338
|
+
const groupIndex = i;
|
|
339
|
+
const txnInGroup = group[groupIndex].txn;
|
|
340
|
+
const maxFee = additionalAtcContext?.maxFees?.get(i)?.microAlgo;
|
|
341
|
+
const immutableFee = maxFee !== undefined && maxFee === txnInGroup.fee;
|
|
342
|
+
// Because we don't alter non app call transaction, they take priority
|
|
343
|
+
const priorityMultiplier = txn.requiredFeeDelta > 0n && (immutableFee || txnInGroup.type !== algosdk.TransactionType.appl) ? 1000n : 1n;
|
|
344
|
+
return {
|
|
345
|
+
...txn,
|
|
346
|
+
groupIndex,
|
|
347
|
+
// Measures the priority level of covering the transaction fee using the surplus group fees. The higher the number, the higher the priority.
|
|
348
|
+
surplusFeePriorityLevel: txn.requiredFeeDelta > 0n ? txn.requiredFeeDelta * priorityMultiplier : -1n,
|
|
349
|
+
};
|
|
350
|
+
})
|
|
351
|
+
.sort((a, b) => {
|
|
352
|
+
return a.surplusFeePriorityLevel > b.surplusFeePriorityLevel ? -1 : a.surplusFeePriorityLevel < b.surplusFeePriorityLevel ? 1 : 0;
|
|
353
|
+
})
|
|
354
|
+
.reduce((acc, { groupIndex, requiredFeeDelta }) => {
|
|
355
|
+
if (requiredFeeDelta > 0n) {
|
|
356
|
+
// There is a fee deficit on the transaction
|
|
357
|
+
let surplusGroupFees = acc[0];
|
|
358
|
+
const additionalTransactionFees = acc[1];
|
|
359
|
+
const additionalFeeDelta = requiredFeeDelta - surplusGroupFees;
|
|
360
|
+
if (additionalFeeDelta <= 0n) {
|
|
361
|
+
// The surplus group fees fully cover the required fee delta
|
|
362
|
+
surplusGroupFees = -additionalFeeDelta;
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
// The surplus group fees do not fully cover the required fee delta, use what is available
|
|
366
|
+
additionalTransactionFees.set(groupIndex, additionalFeeDelta);
|
|
367
|
+
surplusGroupFees = 0n;
|
|
368
|
+
}
|
|
369
|
+
return [surplusGroupFees, additionalTransactionFees];
|
|
370
|
+
}
|
|
371
|
+
return acc;
|
|
372
|
+
}, [
|
|
373
|
+
executionInfo.txns.reduce((acc, { requiredFeeDelta }) => {
|
|
374
|
+
if (requiredFeeDelta < 0n) {
|
|
375
|
+
return acc + -requiredFeeDelta;
|
|
376
|
+
}
|
|
377
|
+
return acc;
|
|
378
|
+
}, 0n),
|
|
379
|
+
new Map(),
|
|
380
|
+
])
|
|
381
|
+
: [0n, new Map()];
|
|
382
|
+
executionInfo.txns.forEach(({ unnamedResourcesAccessed: r }, i) => {
|
|
383
|
+
// Populate Transaction App Call Resources
|
|
384
|
+
if (sendParams.populateAppCallResources && r !== undefined && group[i].txn.type === algosdk.TransactionType.appl) {
|
|
385
|
+
if (r.boxes || r.extraBoxRefs)
|
|
386
|
+
throw Error('Unexpected boxes at the transaction level');
|
|
387
|
+
if (r.appLocals)
|
|
388
|
+
throw Error('Unexpected app local at the transaction level');
|
|
389
|
+
if (r.assetHoldings)
|
|
390
|
+
throw Error('Unexpected asset holding at the transaction level');
|
|
391
|
+
group[i].txn['applicationCall'] = {
|
|
392
|
+
...group[i].txn.applicationCall,
|
|
393
|
+
accounts: [...(group[i].txn?.applicationCall?.accounts ?? []), ...(r.accounts ?? [])],
|
|
394
|
+
foreignApps: [...(group[i].txn?.applicationCall?.foreignApps ?? []), ...(r.apps ?? [])],
|
|
395
|
+
foreignAssets: [...(group[i].txn?.applicationCall?.foreignAssets ?? []), ...(r.assets ?? [])],
|
|
396
|
+
boxes: [...(group[i].txn?.applicationCall?.boxes ?? []), ...(r.boxes ?? [])],
|
|
397
|
+
};
|
|
398
|
+
const accounts = group[i].txn.applicationCall?.accounts?.length ?? 0;
|
|
399
|
+
if (accounts > MAX_APP_CALL_ACCOUNT_REFERENCES)
|
|
400
|
+
throw Error(`Account reference limit of ${MAX_APP_CALL_ACCOUNT_REFERENCES} exceeded in transaction ${i}`);
|
|
401
|
+
const assets = group[i].txn.applicationCall?.foreignAssets?.length ?? 0;
|
|
402
|
+
const apps = group[i].txn.applicationCall?.foreignApps?.length ?? 0;
|
|
403
|
+
const boxes = group[i].txn.applicationCall?.boxes?.length ?? 0;
|
|
404
|
+
if (accounts + assets + apps + boxes > MAX_APP_CALL_FOREIGN_REFERENCES) {
|
|
405
|
+
throw Error(`Resource reference limit of ${MAX_APP_CALL_FOREIGN_REFERENCES} exceeded in transaction ${i}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// Cover App Call Inner Transaction Fees
|
|
409
|
+
if (sendParams.coverAppCallInnerTransactionFees) {
|
|
410
|
+
const additionalTransactionFee = additionalTransactionFees.get(i);
|
|
411
|
+
if (additionalTransactionFee !== undefined) {
|
|
412
|
+
if (group[i].txn.type !== algosdk.TransactionType.appl) {
|
|
413
|
+
throw Error(`An additional fee of ${additionalTransactionFee} µALGO is required for non app call transaction ${i}`);
|
|
414
|
+
}
|
|
415
|
+
const transactionFee = group[i].txn.fee + additionalTransactionFee;
|
|
416
|
+
const maxFee = additionalAtcContext?.maxFees?.get(i)?.microAlgo;
|
|
417
|
+
if (maxFee === undefined || transactionFee > maxFee) {
|
|
418
|
+
throw Error(`Calculated transaction fee ${transactionFee} µALGO is greater than max of ${maxFee ?? 'undefined'} for transaction ${i}`);
|
|
419
|
+
}
|
|
420
|
+
group[i].txn.fee = transactionFee;
|
|
421
|
+
}
|
|
287
422
|
}
|
|
288
423
|
});
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
const assets = t.txn.applicationCall?.foreignAssets?.length ?? 0;
|
|
295
|
-
const apps = t.txn.applicationCall?.foreignApps?.length ?? 0;
|
|
296
|
-
const boxes = t.txn.applicationCall?.boxes?.length ?? 0;
|
|
297
|
-
return accounts + assets + apps + boxes < MAX_APP_CALL_FOREIGN_REFERENCES;
|
|
298
|
-
};
|
|
299
|
-
// If this is a asset holding or app local, first try to find a transaction that already has the account available
|
|
300
|
-
if (type === 'assetHolding' || type === 'appLocal') {
|
|
301
|
-
const { account } = reference;
|
|
302
|
-
let txnIndex = txns.findIndex((t) => {
|
|
303
|
-
if (!isApplBelowLimit(t))
|
|
424
|
+
// Populate Group App Call Resources
|
|
425
|
+
if (sendParams.populateAppCallResources) {
|
|
426
|
+
const populateGroupResource = (txns, reference, type) => {
|
|
427
|
+
const isApplBelowLimit = (t) => {
|
|
428
|
+
if (t.txn.type !== algosdk.TransactionType.appl)
|
|
304
429
|
return false;
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
t.txn.applicationCall?.
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
430
|
+
const accounts = t.txn.applicationCall?.accounts?.length ?? 0;
|
|
431
|
+
const assets = t.txn.applicationCall?.foreignAssets?.length ?? 0;
|
|
432
|
+
const apps = t.txn.applicationCall?.foreignApps?.length ?? 0;
|
|
433
|
+
const boxes = t.txn.applicationCall?.boxes?.length ?? 0;
|
|
434
|
+
return accounts + assets + apps + boxes < MAX_APP_CALL_FOREIGN_REFERENCES;
|
|
435
|
+
};
|
|
436
|
+
// If this is a asset holding or app local, first try to find a transaction that already has the account available
|
|
437
|
+
if (type === 'assetHolding' || type === 'appLocal') {
|
|
438
|
+
const { account } = reference;
|
|
439
|
+
let txnIndex = txns.findIndex((t) => {
|
|
440
|
+
if (!isApplBelowLimit(t))
|
|
441
|
+
return false;
|
|
442
|
+
return (
|
|
443
|
+
// account is in the foreign accounts array
|
|
444
|
+
t.txn.applicationCall?.accounts?.map((a) => a.toString()).includes(account.toString()) ||
|
|
445
|
+
// account is available as an app account
|
|
446
|
+
t.txn.applicationCall?.foreignApps?.map((a) => algosdk.getApplicationAddress(a).toString()).includes(account.toString()) ||
|
|
447
|
+
// account is available since it's in one of the fields
|
|
448
|
+
Object.values(t.txn).some((f) => algosdk.stringifyJSON(f, (_, v) => (v instanceof algosdk.Address ? v.toString() : v))?.includes(account.toString())));
|
|
449
|
+
});
|
|
450
|
+
if (txnIndex > -1) {
|
|
451
|
+
if (type === 'assetHolding') {
|
|
452
|
+
const { asset } = reference;
|
|
453
|
+
txns[txnIndex].txn['applicationCall'] = {
|
|
454
|
+
...txns[txnIndex].txn.applicationCall,
|
|
455
|
+
foreignAssets: [...(txns[txnIndex].txn?.applicationCall?.foreignAssets ?? []), ...[asset]],
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
const { app } = reference;
|
|
460
|
+
txns[txnIndex].txn['applicationCall'] = {
|
|
461
|
+
...txns[txnIndex].txn.applicationCall,
|
|
462
|
+
foreignApps: [...(txns[txnIndex].txn?.applicationCall?.foreignApps ?? []), ...[app]],
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
// Now try to find a txn that already has that app or asset available
|
|
468
|
+
txnIndex = txns.findIndex((t) => {
|
|
469
|
+
if (!isApplBelowLimit(t))
|
|
470
|
+
return false;
|
|
471
|
+
// check if there is space in the accounts array
|
|
472
|
+
if ((t.txn.applicationCall?.accounts?.length ?? 0) >= MAX_APP_CALL_ACCOUNT_REFERENCES)
|
|
473
|
+
return false;
|
|
474
|
+
if (type === 'assetHolding') {
|
|
475
|
+
const { asset } = reference;
|
|
476
|
+
return t.txn.applicationCall?.foreignAssets?.includes(asset);
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
const { app } = reference;
|
|
480
|
+
return t.txn.applicationCall?.foreignApps?.includes(app) || t.txn.applicationCall?.appIndex === app;
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
if (txnIndex > -1) {
|
|
484
|
+
const { account } = reference;
|
|
316
485
|
txns[txnIndex].txn['applicationCall'] = {
|
|
317
486
|
...txns[txnIndex].txn.applicationCall,
|
|
318
|
-
|
|
487
|
+
accounts: [...(txns[txnIndex].txn?.applicationCall?.accounts ?? []), ...[account]],
|
|
319
488
|
};
|
|
489
|
+
return;
|
|
320
490
|
}
|
|
321
|
-
|
|
322
|
-
|
|
491
|
+
}
|
|
492
|
+
// If this is a box, first try to find a transaction that already has the app available
|
|
493
|
+
if (type === 'box') {
|
|
494
|
+
const { app, name } = reference;
|
|
495
|
+
const txnIndex = txns.findIndex((t) => {
|
|
496
|
+
if (!isApplBelowLimit(t))
|
|
497
|
+
return false;
|
|
498
|
+
// If the app is in the foreign array OR the app being called, then we know it's available
|
|
499
|
+
return t.txn.applicationCall?.foreignApps?.includes(app) || t.txn.applicationCall?.appIndex === app;
|
|
500
|
+
});
|
|
501
|
+
if (txnIndex > -1) {
|
|
323
502
|
txns[txnIndex].txn['applicationCall'] = {
|
|
324
503
|
...txns[txnIndex].txn.applicationCall,
|
|
325
|
-
|
|
504
|
+
boxes: [...(txns[txnIndex].txn?.applicationCall?.boxes ?? []), ...[{ appIndex: app, name }]],
|
|
326
505
|
};
|
|
506
|
+
return;
|
|
327
507
|
}
|
|
328
|
-
return;
|
|
329
508
|
}
|
|
330
|
-
//
|
|
331
|
-
txnIndex = txns.findIndex((t) => {
|
|
332
|
-
if (
|
|
333
|
-
return false;
|
|
334
|
-
// check if there is space in the accounts array
|
|
335
|
-
if ((t.txn.applicationCall?.accounts?.length ?? 0) >= MAX_APP_CALL_ACCOUNT_REFERENCES)
|
|
509
|
+
// Find the txn index to put the reference(s)
|
|
510
|
+
const txnIndex = txns.findIndex((t) => {
|
|
511
|
+
if (t.txn.type !== algosdk.TransactionType.appl)
|
|
336
512
|
return false;
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
return
|
|
513
|
+
const accounts = t.txn.applicationCall?.accounts?.length ?? 0;
|
|
514
|
+
if (type === 'account')
|
|
515
|
+
return accounts < MAX_APP_CALL_ACCOUNT_REFERENCES;
|
|
516
|
+
const assets = t.txn.applicationCall?.foreignAssets?.length ?? 0;
|
|
517
|
+
const apps = t.txn.applicationCall?.foreignApps?.length ?? 0;
|
|
518
|
+
const boxes = t.txn.applicationCall?.boxes?.length ?? 0;
|
|
519
|
+
// If we're adding local state or asset holding, we need space for the acocunt and the other reference
|
|
520
|
+
if (type === 'assetHolding' || type === 'appLocal') {
|
|
521
|
+
return accounts + assets + apps + boxes < MAX_APP_CALL_FOREIGN_REFERENCES - 1 && accounts < MAX_APP_CALL_ACCOUNT_REFERENCES;
|
|
340
522
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
return
|
|
523
|
+
// If we're adding a box, we need space for both the box ref and the app ref
|
|
524
|
+
if (type === 'box' && BigInt(reference.app) !== BigInt(0)) {
|
|
525
|
+
return accounts + assets + apps + boxes < MAX_APP_CALL_FOREIGN_REFERENCES - 1;
|
|
344
526
|
}
|
|
527
|
+
return accounts + assets + apps + boxes < MAX_APP_CALL_FOREIGN_REFERENCES;
|
|
345
528
|
});
|
|
346
|
-
if (txnIndex
|
|
347
|
-
|
|
529
|
+
if (txnIndex === -1) {
|
|
530
|
+
throw Error('No more transactions below reference limit. Add another app call to the group.');
|
|
531
|
+
}
|
|
532
|
+
if (type === 'account') {
|
|
348
533
|
txns[txnIndex].txn['applicationCall'] = {
|
|
349
534
|
...txns[txnIndex].txn.applicationCall,
|
|
350
|
-
accounts: [...(txns[txnIndex].txn?.applicationCall?.accounts ?? []), ...[
|
|
535
|
+
accounts: [...(txns[txnIndex].txn?.applicationCall?.accounts ?? []), ...[reference]],
|
|
351
536
|
};
|
|
352
|
-
return;
|
|
353
537
|
}
|
|
354
|
-
|
|
355
|
-
// If this is a box, first try to find a transaction that already has the app available
|
|
356
|
-
if (type === 'box') {
|
|
357
|
-
const { app, name } = reference;
|
|
358
|
-
const txnIndex = txns.findIndex((t) => {
|
|
359
|
-
if (!isApplBelowLimit(t))
|
|
360
|
-
return false;
|
|
361
|
-
// If the app is in the foreign array OR the app being called, then we know it's available
|
|
362
|
-
return t.txn.applicationCall?.foreignApps?.includes(app) || t.txn.applicationCall?.appIndex === app;
|
|
363
|
-
});
|
|
364
|
-
if (txnIndex > -1) {
|
|
538
|
+
else if (type === 'app') {
|
|
365
539
|
txns[txnIndex].txn['applicationCall'] = {
|
|
366
540
|
...txns[txnIndex].txn.applicationCall,
|
|
367
|
-
|
|
541
|
+
foreignApps: [
|
|
542
|
+
...(txns[txnIndex].txn?.applicationCall?.foreignApps ?? []),
|
|
543
|
+
...[typeof reference === 'bigint' ? reference : BigInt(reference)],
|
|
544
|
+
],
|
|
368
545
|
};
|
|
369
|
-
return;
|
|
370
546
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
if (type === 'assetHolding' || type === 'appLocal') {
|
|
384
|
-
return accounts + assets + apps + boxes < MAX_APP_CALL_FOREIGN_REFERENCES - 1 && accounts < MAX_APP_CALL_ACCOUNT_REFERENCES;
|
|
547
|
+
else if (type === 'box') {
|
|
548
|
+
const { app, name } = reference;
|
|
549
|
+
txns[txnIndex].txn['applicationCall'] = {
|
|
550
|
+
...txns[txnIndex].txn.applicationCall,
|
|
551
|
+
boxes: [...(txns[txnIndex].txn?.applicationCall?.boxes ?? []), ...[{ appIndex: app, name }]],
|
|
552
|
+
};
|
|
553
|
+
if (app.toString() !== '0') {
|
|
554
|
+
txns[txnIndex].txn['applicationCall'] = {
|
|
555
|
+
...txns[txnIndex].txn.applicationCall,
|
|
556
|
+
foreignApps: [...(txns[txnIndex].txn?.applicationCall?.foreignApps ?? []), ...[app]],
|
|
557
|
+
};
|
|
558
|
+
}
|
|
385
559
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
560
|
+
else if (type === 'assetHolding') {
|
|
561
|
+
const { asset, account } = reference;
|
|
562
|
+
txns[txnIndex].txn['applicationCall'] = {
|
|
563
|
+
...txns[txnIndex].txn.applicationCall,
|
|
564
|
+
foreignAssets: [...(txns[txnIndex].txn?.applicationCall?.foreignAssets ?? []), ...[asset]],
|
|
565
|
+
accounts: [...(txns[txnIndex].txn?.applicationCall?.accounts ?? []), ...[account]],
|
|
566
|
+
};
|
|
389
567
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
if (txnIndex === -1) {
|
|
393
|
-
throw Error('No more transactions below reference limit. Add another app call to the group.');
|
|
394
|
-
}
|
|
395
|
-
if (type === 'account') {
|
|
396
|
-
txns[txnIndex].txn['applicationCall'] = {
|
|
397
|
-
...txns[txnIndex].txn.applicationCall,
|
|
398
|
-
accounts: [...(txns[txnIndex].txn?.applicationCall?.accounts ?? []), ...[reference]],
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
else if (type === 'app') {
|
|
402
|
-
txns[txnIndex].txn['applicationCall'] = {
|
|
403
|
-
...txns[txnIndex].txn.applicationCall,
|
|
404
|
-
foreignApps: [
|
|
405
|
-
...(txns[txnIndex].txn?.applicationCall?.foreignApps ?? []),
|
|
406
|
-
...[typeof reference === 'bigint' ? reference : BigInt(reference)],
|
|
407
|
-
],
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
else if (type === 'box') {
|
|
411
|
-
const { app, name } = reference;
|
|
412
|
-
txns[txnIndex].txn['applicationCall'] = {
|
|
413
|
-
...txns[txnIndex].txn.applicationCall,
|
|
414
|
-
boxes: [...(txns[txnIndex].txn?.applicationCall?.boxes ?? []), ...[{ appIndex: app, name }]],
|
|
415
|
-
};
|
|
416
|
-
if (app.toString() !== '0') {
|
|
568
|
+
else if (type === 'appLocal') {
|
|
569
|
+
const { app, account } = reference;
|
|
417
570
|
txns[txnIndex].txn['applicationCall'] = {
|
|
418
571
|
...txns[txnIndex].txn.applicationCall,
|
|
419
572
|
foreignApps: [...(txns[txnIndex].txn?.applicationCall?.foreignApps ?? []), ...[app]],
|
|
573
|
+
accounts: [...(txns[txnIndex].txn?.applicationCall?.accounts ?? []), ...[account]],
|
|
420
574
|
};
|
|
421
575
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
g.
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
});
|
|
469
|
-
g.boxes?.forEach((b) => {
|
|
470
|
-
populateGroupResource(group, b, 'box');
|
|
471
|
-
// Remove apps as resource from the group if we're adding it here
|
|
472
|
-
g.apps = g.apps?.filter((app) => BigInt(app) !== BigInt(b.app));
|
|
473
|
-
});
|
|
474
|
-
g.assets?.forEach((a) => {
|
|
475
|
-
populateGroupResource(group, a, 'asset');
|
|
476
|
-
});
|
|
477
|
-
g.apps?.forEach((a) => {
|
|
478
|
-
populateGroupResource(group, a, 'app');
|
|
479
|
-
});
|
|
480
|
-
if (g.extraBoxRefs) {
|
|
481
|
-
for (let i = 0; i < g.extraBoxRefs; i += 1) {
|
|
482
|
-
const ref = new algosdk.modelsv2.BoxReference({ app: 0, name: new Uint8Array(0) });
|
|
483
|
-
populateGroupResource(group, ref, 'box');
|
|
576
|
+
else if (type === 'asset') {
|
|
577
|
+
txns[txnIndex].txn['applicationCall'] = {
|
|
578
|
+
...txns[txnIndex].txn.applicationCall,
|
|
579
|
+
foreignAssets: [
|
|
580
|
+
...(txns[txnIndex].txn?.applicationCall?.foreignAssets ?? []),
|
|
581
|
+
...[typeof reference === 'bigint' ? reference : BigInt(reference)],
|
|
582
|
+
],
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
const g = executionInfo.groupUnnamedResourcesAccessed;
|
|
587
|
+
if (g) {
|
|
588
|
+
// Do cross-reference resources first because they are the most restrictive in terms
|
|
589
|
+
// of which transactions can be used
|
|
590
|
+
g.appLocals?.forEach((a) => {
|
|
591
|
+
populateGroupResource(group, a, 'appLocal');
|
|
592
|
+
// Remove resources from the group if we're adding them here
|
|
593
|
+
g.accounts = g.accounts?.filter((acc) => acc !== a.account);
|
|
594
|
+
g.apps = g.apps?.filter((app) => BigInt(app) !== BigInt(a.app));
|
|
595
|
+
});
|
|
596
|
+
g.assetHoldings?.forEach((a) => {
|
|
597
|
+
populateGroupResource(group, a, 'assetHolding');
|
|
598
|
+
// Remove resources from the group if we're adding them here
|
|
599
|
+
g.accounts = g.accounts?.filter((acc) => acc !== a.account);
|
|
600
|
+
g.assets = g.assets?.filter((asset) => BigInt(asset) !== BigInt(a.asset));
|
|
601
|
+
});
|
|
602
|
+
// Do accounts next because the account limit is 4
|
|
603
|
+
g.accounts?.forEach((a) => {
|
|
604
|
+
populateGroupResource(group, a, 'account');
|
|
605
|
+
});
|
|
606
|
+
g.boxes?.forEach((b) => {
|
|
607
|
+
populateGroupResource(group, b, 'box');
|
|
608
|
+
// Remove apps as resource from the group if we're adding it here
|
|
609
|
+
g.apps = g.apps?.filter((app) => BigInt(app) !== BigInt(b.app));
|
|
610
|
+
});
|
|
611
|
+
g.assets?.forEach((a) => {
|
|
612
|
+
populateGroupResource(group, a, 'asset');
|
|
613
|
+
});
|
|
614
|
+
g.apps?.forEach((a) => {
|
|
615
|
+
populateGroupResource(group, a, 'app');
|
|
616
|
+
});
|
|
617
|
+
if (g.extraBoxRefs) {
|
|
618
|
+
for (let i = 0; i < g.extraBoxRefs; i += 1) {
|
|
619
|
+
const ref = new algosdk.modelsv2.BoxReference({ app: 0, name: new Uint8Array(0) });
|
|
620
|
+
populateGroupResource(group, ref, 'box');
|
|
621
|
+
}
|
|
484
622
|
}
|
|
485
623
|
}
|
|
486
624
|
}
|
|
@@ -499,15 +637,17 @@ async function populateAppCallResources(atc, algod) {
|
|
|
499
637
|
* @returns An object with transaction IDs, transactions, group transaction ID (`groupTransactionId`) if more than 1 transaction sent, and (if `skipWaiting` is `false` or unset) confirmation (`confirmation`)
|
|
500
638
|
*/
|
|
501
639
|
const sendAtomicTransactionComposer = async function (atcSend, algod) {
|
|
502
|
-
const { atc: givenAtc, sendParams, ...executeParams } = atcSend;
|
|
640
|
+
const { atc: givenAtc, sendParams, additionalAtcContext, ...executeParams } = atcSend;
|
|
503
641
|
let atc;
|
|
504
642
|
atc = givenAtc;
|
|
505
643
|
try {
|
|
506
644
|
const transactionsWithSigner = atc.buildGroup();
|
|
507
645
|
// If populateAppCallResources is true OR if populateAppCallResources is undefined and there are app calls, then populate resources
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
646
|
+
const populateAppCallResources = executeParams?.populateAppCallResources ?? sendParams?.populateAppCallResources ?? config.Config.populateAppCallResources;
|
|
647
|
+
const coverAppCallInnerTransactionFees = executeParams?.coverAppCallInnerTransactionFees;
|
|
648
|
+
if ((populateAppCallResources || coverAppCallInnerTransactionFees) &&
|
|
649
|
+
transactionsWithSigner.map((t) => t.txn.type).includes(algosdk.TransactionType.appl)) {
|
|
650
|
+
atc = await prepareGroupForSending(givenAtc, algod, { ...executeParams, populateAppCallResources, coverAppCallInnerTransactionFees }, additionalAtcContext);
|
|
511
651
|
}
|
|
512
652
|
const transactionsToSend = transactionsWithSigner.map((t) => {
|
|
513
653
|
return t.txn;
|
|
@@ -521,7 +661,7 @@ const sendAtomicTransactionComposer = async function (atcSend, algod) {
|
|
|
521
661
|
config.Config.getLogger(executeParams?.suppressLog ?? sendParams?.suppressLog).debug(`Transaction IDs (${groupId})`, transactionsToSend.map((t) => t.txID()));
|
|
522
662
|
}
|
|
523
663
|
if (config.Config.debug && config.Config.traceAll) {
|
|
524
|
-
//
|
|
664
|
+
// Emit the simulate response for use with AlgoKit AVM debugger
|
|
525
665
|
const simulateResponse = await performAtomicTransactionComposerSimulate.performAtomicTransactionComposerSimulate(atc, algod);
|
|
526
666
|
await config.Config.events.emitAsync(types_lifecycleEvents.EventType.TxnGroupSimulated, {
|
|
527
667
|
simulateResponse,
|
|
@@ -563,7 +703,7 @@ const sendAtomicTransactionComposer = async function (atcSend, algod) {
|
|
|
563
703
|
}
|
|
564
704
|
if (config.Config.debug && typeof e === 'object') {
|
|
565
705
|
err.traces = [];
|
|
566
|
-
config.Config.
|
|
706
|
+
config.Config.getLogger(executeParams?.suppressLog ?? sendParams?.suppressLog).error('Received error executing Atomic Transaction Composer and debug flag enabled; attempting simulation to get more information', err);
|
|
567
707
|
const simulate = await performAtomicTransactionComposerSimulate.performAtomicTransactionComposerSimulate(atc, algod);
|
|
568
708
|
if (config.Config.debug && !config.Config.traceAll) {
|
|
569
709
|
// Emit the event only if traceAll: false, as it should have already been emitted above
|
|
@@ -584,7 +724,7 @@ const sendAtomicTransactionComposer = async function (atcSend, algod) {
|
|
|
584
724
|
}
|
|
585
725
|
}
|
|
586
726
|
else {
|
|
587
|
-
config.Config.
|
|
727
|
+
config.Config.getLogger(executeParams?.suppressLog ?? sendParams?.suppressLog).error('Received error executing Atomic Transaction Composer, for more information enable the debug flag', err);
|
|
588
728
|
}
|
|
589
729
|
throw err;
|
|
590
730
|
}
|
|
@@ -812,6 +952,7 @@ exports.getSenderTransactionSigner = getSenderTransactionSigner;
|
|
|
812
952
|
exports.getTransactionParams = getTransactionParams;
|
|
813
953
|
exports.getTransactionWithSigner = getTransactionWithSigner;
|
|
814
954
|
exports.populateAppCallResources = populateAppCallResources;
|
|
955
|
+
exports.prepareGroupForSending = prepareGroupForSending;
|
|
815
956
|
exports.sendAtomicTransactionComposer = sendAtomicTransactionComposer;
|
|
816
957
|
exports.sendGroupOfTransactions = sendGroupOfTransactions;
|
|
817
958
|
exports.sendTransaction = sendTransaction;
|