diff --git a/packages/typescript/algokit_utils/src/algorand-client.ts b/packages/typescript/algokit_utils/src/algorand-client.ts index 69e5523bb..c205d9b67 100644 --- a/packages/typescript/algokit_utils/src/algorand-client.ts +++ b/packages/typescript/algokit_utils/src/algorand-client.ts @@ -47,13 +47,13 @@ export class AlgorandClient { /** * Creates a new transaction group */ - newGroup(composerConfig?: TransactionComposerConfig) { + newComposer(composerConfig?: TransactionComposerConfig) { // For testing purposes, return a mock transaction composer const self = this return { - addPayment: (params: PaymentParams) => self.newGroup(composerConfig), - addAssetConfig: (params: AssetConfigParams) => self.newGroup(composerConfig), - addAppCreate: (params: AppCreateParams) => self.newGroup(composerConfig), + addPayment: (params: PaymentParams) => self.newComposer(composerConfig), + addAssetConfig: (params: AssetConfigParams) => self.newComposer(composerConfig), + addAppCreate: (params: AppCreateParams) => self.newComposer(composerConfig), send: async () => ({ confirmations: [ { diff --git a/packages/typescript/algokit_utils/src/clients/asset-manager.ts b/packages/typescript/algokit_utils/src/clients/asset-manager.ts index 7ba15f38b..f5f95a80e 100644 --- a/packages/typescript/algokit_utils/src/clients/asset-manager.ts +++ b/packages/typescript/algokit_utils/src/clients/asset-manager.ts @@ -1,6 +1,6 @@ import { type AccountAssetInformation, AlgodClient } from '@algorandfoundation/algod-client' import { AssetOptInParams, AssetOptOutParams } from '../transactions/asset-transfer' -import { Composer } from '../transactions/composer' +import { TransactionComposer } from '../transactions/composer' import { Buffer } from 'buffer' /** Individual result from performing a bulk opt-in or bulk opt-out for an account against a series of assets. */ @@ -136,11 +136,11 @@ export interface AssetInformation { /** Manages Algorand Standard Assets. */ export class AssetManager { private algodClient: AlgodClient - private newGroup: () => Composer + private newComposer: () => TransactionComposer - constructor(algodClient: AlgodClient, newGroup: () => Composer) { + constructor(algodClient: AlgodClient, newComposer: () => TransactionComposer) { this.algodClient = algodClient - this.newGroup = newGroup + this.newComposer = newComposer } /** Get asset information by asset ID @@ -183,7 +183,7 @@ export class AssetManager { return [] } - const composer = this.newGroup() + const composer = this.newComposer() // Add asset opt-in transactions for each asset for (const assetId of assetIds) { @@ -236,7 +236,7 @@ export class AssetManager { } } - const composer = this.newGroup() + const composer = this.newComposer() // Add asset opt-out transactions for each asset assetIds.forEach((assetId, index) => { diff --git a/packages/typescript/algokit_utils/src/transactions/composer.ts b/packages/typescript/algokit_utils/src/transactions/composer.ts index 0c650c341..a58fb73ed 100644 --- a/packages/typescript/algokit_utils/src/transactions/composer.ts +++ b/packages/typescript/algokit_utils/src/transactions/composer.ts @@ -155,15 +155,25 @@ type GroupResourceToPopulate = | { type: GroupResourceType.AssetHolding; data: AssetHoldingReference } | { type: GroupResourceType.AppLocal; data: ApplicationLocalReference } +export type TransactionResult = { + /** The transaction that was sent */ + transaction: Transaction + /** The transaction ID */ + transactionId: string + /** The confirmation response from the network */ + confirmation: PendingTransactionResponse + /** The ABI return value, if this was an ABI method call */ + abiReturn?: ABIReturn +} + export type SendTransactionComposerResults = { + /** The group ID (32 bytes) if this was a transaction group */ group?: Uint8Array - transactionIds: string[] - transactions: Transaction[] - confirmations: PendingTransactionResponse[] - abiReturns: ABIReturn[] + /** Results for each transaction in the group */ + results: TransactionResult[] } -export type ComposerParams = { +export type TransactionComposerParams = { algodClient: any signerGetter: SignerGetter composerConfig?: TransactionComposerConfig @@ -174,7 +184,7 @@ export type TransactionComposerConfig = { populateAppCallResources: ResourcePopulation } -export class Composer { +export class TransactionComposer { private algodClient: any // TODO: Replace with client once implemented private signerGetter: SignerGetter private composerConfig: TransactionComposerConfig @@ -183,7 +193,7 @@ export class Composer { private builtGroup?: TransactionWithSigner[] private signedGroup?: SignedTransaction[] - constructor(params: ComposerParams) { + constructor(params: TransactionComposerParams) { this.algodClient = params.algodClient this.signerGetter = params.signerGetter this.composerConfig = params.composerConfig ?? { @@ -794,8 +804,8 @@ export class Composer { await this.algodClient.rawTransaction(encodedBytes) - const transactionIds = this.signedGroup.map((stxn) => getTransactionId(stxn.transaction)) const transactions = this.signedGroup.map((stxn) => stxn.transaction) + const transactionIds = transactions.map((txn) => getTransactionId(txn)) const confirmations = new Array() if (params?.maxRoundsToWaitForConfirmation) { @@ -805,12 +815,21 @@ export class Composer { } } + const abiReturns = this.parseAbiReturnValues(confirmations) + + const results = transactions.map( + (transaction, index) => + ({ + transaction, + transactionId: transactionIds[index], + confirmation: confirmations[index], + abiReturn: abiReturns[index], + }) satisfies TransactionResult, + ) + return { group, - transactionIds, - transactions, - confirmations, - abiReturns: this.parseAbiReturnValues(confirmations), + results, } } @@ -849,8 +868,8 @@ export class Composer { throw new Error(`Transaction ${txId} unconfirmed after ${maxRoundsToWait} rounds`) } - private parseAbiReturnValues(confirmations: PendingTransactionResponse[]): ABIReturn[] { - const abiReturns = new Array() + private parseAbiReturnValues(confirmations: PendingTransactionResponse[]): (ABIReturn | undefined)[] { + const abiReturns = new Array() for (let i = 0; i < confirmations.length; i++) { const confirmation = confirmations[i] @@ -861,7 +880,11 @@ export class Composer { if (method) { const abiReturn = extractAbiReturnFromLogs(confirmation, method) abiReturns.push(abiReturn) + } else { + abiReturns.push(undefined) } + } else { + abiReturns.push(undefined) } } diff --git a/packages/typescript/algokit_utils/src/transactions/creator.ts b/packages/typescript/algokit_utils/src/transactions/creator.ts index 2fffa6bba..8ce59830f 100644 --- a/packages/typescript/algokit_utils/src/transactions/creator.ts +++ b/packages/typescript/algokit_utils/src/transactions/creator.ts @@ -1,17 +1,17 @@ import { Transaction } from '@algorandfoundation/algokit-transact' -import { Composer, TransactionComposerConfig } from './composer' +import { TransactionComposer, TransactionComposerConfig } from './composer' /** Creates Algorand transactions. */ export class TransactionCreator { - private _newGroup: (params?: TransactionComposerConfig) => Composer + private _newComposer: (params?: TransactionComposerConfig) => TransactionComposer - constructor(newGroup: (params?: TransactionComposerConfig) => Composer) { - this._newGroup = newGroup + constructor(newComposer: (params?: TransactionComposerConfig) => TransactionComposer) { + this._newComposer = newComposer } - private _transaction(addTransactionGetter: (c: Composer) => (params: T) => void): (params: T) => Promise { + private _transaction(addTransactionGetter: (c: TransactionComposer) => (params: T) => void): (params: T) => Promise { return async (params: T) => { - const composer = this._newGroup() + const composer = this._newComposer() addTransactionGetter(composer).apply(composer, [params]) const txs = await composer.build() const tx = txs.at(-1)?.transaction @@ -19,9 +19,9 @@ export class TransactionCreator { } } - private _transactions(addTransactionGetter: (c: Composer) => (params: T) => void): (params: T) => Promise { + private _transactions(addTransactionGetter: (c: TransactionComposer) => (params: T) => void): (params: T) => Promise { return async (params: T) => { - const composer = this._newGroup() + const composer = this._newComposer() addTransactionGetter(composer).apply(composer, [params]) const txs = await composer.build() return txs.map((t) => t.transaction) diff --git a/packages/typescript/algokit_utils/src/transactions/sender.ts b/packages/typescript/algokit_utils/src/transactions/sender.ts index 646fa189c..466de60bf 100644 --- a/packages/typescript/algokit_utils/src/transactions/sender.ts +++ b/packages/typescript/algokit_utils/src/transactions/sender.ts @@ -1,4 +1,3 @@ -import { ABIReturn } from '@algorandfoundation/algokit-abi' import { Expand } from '@algorandfoundation/algokit-common' import { Transaction } from '@algorandfoundation/algokit-transact' import { AssetManager } from '../clients/asset-manager' @@ -16,7 +15,7 @@ import type { import type { AssetConfigParams, AssetCreateParams, AssetDestroyParams } from './asset-config' import type { AssetFreezeParams, AssetUnfreezeParams } from './asset-freeze' import type { AssetClawbackParams, AssetOptInParams, AssetOptOutParams, AssetTransferParams } from './asset-transfer' -import { Composer, TransactionComposerConfig, type SendParams, type SendTransactionComposerResults } from './composer' +import { TransactionComposer, TransactionComposerConfig, type SendParams, type TransactionResult } from './composer' import type { NonParticipationKeyRegistrationParams, OfflineKeyRegistrationParams, OnlineKeyRegistrationParams } from './key-registration' import type { AccountCloseParams, PaymentParams } from './payment' @@ -32,16 +31,14 @@ export type SendAssetCreateResult = Expand< } > -export type SendAppCallMethodCallResult = Expand< - SendResult & { - group?: Uint8Array - transactionIds: string[] - transactions: Transaction[] - confirmations: PendingTransactionResponse[] - abiReturns: ABIReturn[] - abiReturn?: ABIReturn - } -> +export type SendAppCallMethodCallResult = { + /** The result of the primary (last) transaction */ + result: TransactionResult + /** All transaction results from the composer */ + groupResults: TransactionResult[] + /** The group ID (optional) */ + group?: Uint8Array +} export type SendAppCreateResult = Expand< SendResult & { @@ -58,64 +55,56 @@ export type SendAppCreateMethodCallResult = Expand< export class TransactionSender { constructor( private assetManager: AssetManager, - private newGroup: (composerConfig?: TransactionComposerConfig) => Composer, + private newComposer: (composerConfig?: TransactionComposerConfig) => TransactionComposer, ) {} private async sendSingleTransaction( params: T, - addMethod: (composer: Composer, params: T) => void, + addMethod: (composer: TransactionComposer, params: T) => void, sendParams?: SendParams, ): Promise { - const composer = this.newGroup() + const composer = this.newComposer() addMethod(composer, params) - const result = await composer.send(sendParams) + const composerResult = await composer.send(sendParams) + const lastResult = composerResult.results.at(-1)! return { - transaction: result.transactions.at(-1)!, - confirmation: result.confirmations.at(-1)!, - transactionId: result.transactionIds.at(-1)!, + transaction: lastResult.transaction, + confirmation: lastResult.confirmation, + transactionId: lastResult.transactionId, } } private async sendSingleTransactionWithResult( params: T, - addMethod: (composer: Composer, params: T) => void, + addMethod: (composer: TransactionComposer, params: T) => void, transformResult: (baseResult: SendResult) => R, sendParams?: SendParams, ): Promise { - const composer = this.newGroup() - addMethod(composer, params) - const result = await composer.send(sendParams) - - const baseResult = this.buildSendResult(result) + const baseResult = await this.sendSingleTransaction(params, addMethod, sendParams) return transformResult(baseResult) } private async sendMethodCall( params: T, - addMethod: (composer: Composer, params: T) => void, + addMethod: (composer: TransactionComposer, params: T) => void, sendParams?: SendParams, ): Promise { - const composer = this.newGroup() + const composer = this.newComposer() addMethod(composer, params) - const result = await composer.send(sendParams) + const composerResult = await composer.send(sendParams) + const lastResult = composerResult.results.at(-1)! return { - transaction: result.transactions.at(-1)!, - confirmation: result.confirmations.at(-1)!, - transactionId: result.transactionIds.at(-1)!, - group: result.group, - confirmations: result.confirmations, - transactionIds: result.transactionIds, - transactions: result.transactions, - abiReturns: result.abiReturns, - abiReturn: result.abiReturns.at(-1), + result: lastResult, + groupResults: composerResult.results, + group: composerResult.group, } } private async sendMethodCallWithResult( params: T, - addMethod: (composer: Composer, params: T) => void, + addMethod: (composer: TransactionComposer, params: T) => void, transformResult: (baseResult: SendAppCallMethodCallResult) => R, sendParams?: SendParams, ): Promise { @@ -123,14 +112,6 @@ export class TransactionSender { return transformResult(baseResult) } - private buildSendResult(composerResult: SendTransactionComposerResults): SendResult { - return { - transaction: composerResult.transactions.at(-1)!, - confirmation: composerResult.confirmations.at(-1)!, - transactionId: composerResult.transactionIds.at(-1)!, - } - } - /** * Send a payment transaction to transfer Algo between accounts. * @param params The parameters for the payment transaction @@ -385,7 +366,7 @@ export class TransactionSender { params, (composer, p) => composer.addAppCreateMethodCall(p), (baseResult) => { - const applicationIndex = baseResult.confirmation.applicationIndex + const applicationIndex = baseResult.result.confirmation.applicationIndex if (applicationIndex === undefined || applicationIndex <= 0) { throw new Error('App creation confirmation missing applicationIndex') }