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