Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/typescript/algokit_utils/src/algorand-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
{
Expand Down
12 changes: 6 additions & 6 deletions packages/typescript/algokit_utils/src/clients/asset-manager.ts
Original file line number Diff line number Diff line change
@@ -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. */
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) => {
Expand Down
51 changes: 37 additions & 14 deletions packages/typescript/algokit_utils/src/transactions/composer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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 ?? {
Expand Down Expand Up @@ -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<PendingTransactionResponse>()
if (params?.maxRoundsToWaitForConfirmation) {
Expand All @@ -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,
}
}

Expand Down Expand Up @@ -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<ABIReturn>()
private parseAbiReturnValues(confirmations: PendingTransactionResponse[]): (ABIReturn | undefined)[] {
const abiReturns = new Array<ABIReturn | undefined>()

for (let i = 0; i < confirmations.length; i++) {
const confirmation = confirmations[i]
Expand All @@ -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)
}
}

Expand Down
16 changes: 8 additions & 8 deletions packages/typescript/algokit_utils/src/transactions/creator.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
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<T>(addTransactionGetter: (c: Composer) => (params: T) => void): (params: T) => Promise<Transaction> {
private _transaction<T>(addTransactionGetter: (c: TransactionComposer) => (params: T) => void): (params: T) => Promise<Transaction> {
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
return tx!
}
}

private _transactions<T>(addTransactionGetter: (c: Composer) => (params: T) => void): (params: T) => Promise<Transaction[]> {
private _transactions<T>(addTransactionGetter: (c: TransactionComposer) => (params: T) => void): (params: T) => Promise<Transaction[]> {
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)
Expand Down
75 changes: 28 additions & 47 deletions packages/typescript/algokit_utils/src/transactions/sender.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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'

Expand All @@ -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 & {
Expand All @@ -58,79 +55,63 @@ export type SendAppCreateMethodCallResult = Expand<
export class TransactionSender {
constructor(
private assetManager: AssetManager,
private newGroup: (composerConfig?: TransactionComposerConfig) => Composer,
private newComposer: (composerConfig?: TransactionComposerConfig) => TransactionComposer,
) {}

private async sendSingleTransaction<T>(
params: T,
addMethod: (composer: Composer, params: T) => void,
addMethod: (composer: TransactionComposer, params: T) => void,
sendParams?: SendParams,
): Promise<SendResult> {
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<T, R>(
params: T,
addMethod: (composer: Composer, params: T) => void,
addMethod: (composer: TransactionComposer, params: T) => void,
transformResult: (baseResult: SendResult) => R,
sendParams?: SendParams,
): Promise<R> {
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<T>(
params: T,
addMethod: (composer: Composer, params: T) => void,
addMethod: (composer: TransactionComposer, params: T) => void,
sendParams?: SendParams,
): Promise<SendAppCallMethodCallResult> {
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<T, R>(
params: T,
addMethod: (composer: Composer, params: T) => void,
addMethod: (composer: TransactionComposer, params: T) => void,
transformResult: (baseResult: SendAppCallMethodCallResult) => R,
sendParams?: SendParams,
): Promise<R> {
const baseResult = await this.sendMethodCall(params, addMethod, sendParams)
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
Expand Down Expand Up @@ -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')
}
Expand Down