diff --git a/docs/code/interfaces/types_transaction.TransactionGroupToSend.md b/docs/code/interfaces/types_transaction.TransactionGroupToSend.md new file mode 100644 index 00000000..10b67316 --- /dev/null +++ b/docs/code/interfaces/types_transaction.TransactionGroupToSend.md @@ -0,0 +1,55 @@ +[@algorandfoundation/algokit-utils](../README.md) / [types/transaction](../modules/types_transaction.md) / TransactionGroupToSend + +# Interface: TransactionGroupToSend + +[types/transaction](../modules/types_transaction.md).TransactionGroupToSend + +A group of transactions to send together as an atomic group + +**`See`** + +https://developer.algorand.org/docs/get-details/atomic_transfers/ + +## Table of contents + +### Properties + +- [sendParams](types_transaction.TransactionGroupToSend.md#sendparams) +- [signer](types_transaction.TransactionGroupToSend.md#signer) +- [transactions](types_transaction.TransactionGroupToSend.md#transactions) + +## Properties + +### sendParams + +• `Optional` **sendParams**: `Omit`<`Omit`<[`SendTransactionParams`](types_transaction.SendTransactionParams.md), ``"maxFee"``\>, ``"skipSending"``\> + +Any parameters to control the semantics of the send to the network + +#### Defined in + +[src/types/transaction.ts:68](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transaction.ts#L68) + +___ + +### signer + +• `Optional` **signer**: [`SendTransactionFrom`](../modules/types_transaction.md#sendtransactionfrom) + +Optional signer to pass in, required if at least one transaction provided is just the transaction, ignored otherwise + +#### Defined in + +[src/types/transaction.ts:72](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transaction.ts#L72) + +___ + +### transactions + +• **transactions**: (`Transaction` \| [`TransactionToSign`](types_transaction.TransactionToSign.md))[] + +The list of transactions to send, which can either be a raw transaction (in which case signer is required) or the transaction with its signer + +#### Defined in + +[src/types/transaction.ts:70](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/transaction.ts#L70) diff --git a/docs/code/modules/index.md b/docs/code/modules/index.md index 0a53674e..46c20e60 100644 --- a/docs/code/modules/index.md +++ b/docs/code/modules/index.md @@ -147,7 +147,7 @@ the estimated rate. #### Defined in -[src/transaction.ts:267](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L267) +[src/transaction.ts:283](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L283) ___ @@ -266,7 +266,7 @@ the transaction note ready for inclusion in a transaction #### Defined in -[src/transaction.ts:21](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L21) +[src/transaction.ts:27](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L27) ___ @@ -967,7 +967,7 @@ The public address #### Defined in -[src/transaction.ts:42](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L42) +[src/transaction.ts:48](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L48) ___ @@ -992,7 +992,7 @@ The suggested transaction parameters #### Defined in -[src/transaction.ts:291](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L291) +[src/transaction.ts:307](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L307) ___ @@ -1329,9 +1329,7 @@ Signs and sends a group of [up to 16](https://developer.algorand.org/docs/get-de | Name | Type | Description | | :------ | :------ | :------ | -| `groupSend` | `Object` | The group details to send, with: * `transactions`: The array of transactions to send along with their signing account * `sendParams`: The parameters to dictate how the group is sent | -| `groupSend.sendParams?` | `Omit`<`Omit`<[`SendTransactionParams`](../interfaces/types_transaction.SendTransactionParams.md), ``"maxFee"``\>, ``"skipSending"``\> | - | -| `groupSend.transactions` | [`TransactionToSign`](../interfaces/types_transaction.TransactionToSign.md)[] | - | +| `groupSend` | [`TransactionGroupToSend`](../interfaces/types_transaction.TransactionGroupToSend.md) | The group details to send, with: * `transactions`: The array of transactions to send along with their signing account * `sendParams`: The parameters to dictate how the group is sent | | `algod` | `default` | An algod client | #### Returns @@ -1342,7 +1340,7 @@ An object with group transaction ID (`groupTransactionId`) and (if `skipWaiting` #### Defined in -[src/transaction.ts:115](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L115) +[src/transaction.ts:121](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L121) ___ @@ -1370,7 +1368,7 @@ An object with transaction (`transaction`) and (if `skipWaiting` is `false` or u #### Defined in -[src/transaction.ts:56](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L56) +[src/transaction.ts:62](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L62) ___ @@ -1506,4 +1504,4 @@ Pending transaction information #### Defined in -[src/transaction.ts:220](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L220) +[src/transaction.ts:236](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/transaction.ts#L236) diff --git a/docs/code/modules/types_transaction.md b/docs/code/modules/types_transaction.md index 6f9a1872..0b0ab446 100644 --- a/docs/code/modules/types_transaction.md +++ b/docs/code/modules/types_transaction.md @@ -9,6 +9,7 @@ - [ConfirmedTransactionResult](../interfaces/types_transaction.ConfirmedTransactionResult.md) - [SendTransactionParams](../interfaces/types_transaction.SendTransactionParams.md) - [SendTransactionResult](../interfaces/types_transaction.SendTransactionResult.md) +- [TransactionGroupToSend](../interfaces/types_transaction.TransactionGroupToSend.md) - [TransactionToSign](../interfaces/types_transaction.TransactionToSign.md) ### Type Aliases diff --git a/src/transaction.spec.ts b/src/transaction.spec.ts index 854af739..8a850ac0 100644 --- a/src/transaction.spec.ts +++ b/src/transaction.spec.ts @@ -133,6 +133,25 @@ describe('transaction', () => { expect(Buffer.from(confirmations[1].txn.txn.grp).toString('hex')).toBe(Buffer.from(txn2.group).toString('hex')) }) + test('Transaction group is sent with same signer', async () => { + const { algod, testAccount } = localnet.context + const txn1 = await getTestTransaction(1) + const txn2 = await getTestTransaction(2) + + const { confirmations } = await algokit.sendGroupOfTransactions({ transactions: [txn1, txn2], signer: testAccount }, algod) + + invariant(confirmations) + invariant(confirmations[0].txn.txn.grp) + invariant(confirmations[1].txn.txn.grp) + invariant(txn1.group) + invariant(txn2.group) + expect(confirmations.length).toBe(2) + expect(confirmations[0]['confirmed-round']).toBeGreaterThanOrEqual(txn1.firstRound) + expect(confirmations[1]['confirmed-round']).toBeGreaterThanOrEqual(txn2.firstRound) + expect(Buffer.from(confirmations[0].txn.txn.grp).toString('hex')).toBe(Buffer.from(txn1.group).toString('hex')) + expect(Buffer.from(confirmations[1].txn.txn.grp).toString('hex')).toBe(Buffer.from(txn2.group).toString('hex')) + }) + test('Transaction group is sent using transaction signers', async () => { const { algod, testAccount, generateAccount } = localnet.context const account2 = await generateAccount({ suppressLog: true, initialFunds: algokit.algos(10) }) diff --git a/src/transaction.ts b/src/transaction.ts index 78d56d9f..e76d63ad 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -4,7 +4,13 @@ import { Config } from './' import { TransactionSignerAccount } from './types/account' import { PendingTransactionResponse } from './types/algod' import { AlgoAmount } from './types/amount' -import { SendTransactionFrom, SendTransactionParams, SendTransactionResult, TransactionNote, TransactionToSign } from './types/transaction' +import { + SendTransactionFrom, + SendTransactionParams, + SendTransactionResult, + TransactionGroupToSend, + TransactionNote, +} from './types/transaction' /** Encodes a transaction note into a byte array ready to be included in an Algorand transaction. * @@ -112,13 +118,23 @@ const groupBy = (array: T[], predicate: (value: T, id: number, array: T[]) => * @param algod An algod client * @returns An object with group transaction ID (`groupTransactionId`) and (if `skipWaiting` is `false` or unset) confirmation (`confirmation`) */ -export const sendGroupOfTransactions = async function ( - groupSend: { transactions: TransactionToSign[]; sendParams?: Omit, 'skipSending'> }, - algod: Algodv2, -) { - const { transactions, sendParams } = groupSend +export const sendGroupOfTransactions = async function (groupSend: TransactionGroupToSend, algod: Algodv2) { + const { transactions, signer, sendParams } = groupSend + + const transactionsWithSigner = transactions.map((t) => { + if ('transaction' in t) return t + + if (!signer) { + throw new Error(`Attempt to send transaction ${t.txID()} as part of a group transaction, but no signer was provided.`) + } + + return { + transaction: t, + signer: signer, + } + }) const transactionsToSend = transactions.map((t) => { - return t.transaction + return 'transaction' in t ? t.transaction : t }) const group = algosdk.assignGroupID(transactionsToSend) @@ -129,9 +145,9 @@ export const sendGroupOfTransactions = async function ( // Sign transactions either using TransactionSigners, or not let signedTransactions: Uint8Array[] - if (transactions.find((t) => 'signer' in t.signer)) { + if (transactionsWithSigner.find((t) => 'signer' in t.signer)) { // Validate all or nothing for transaction signers - if (transactions.find((t) => !('signer' in t.signer))) { + if (transactionsWithSigner.find((t) => !('signer' in t.signer))) { throw new Error( "When issuing a group transaction the signers should either all be TransactionSignerAccount's or all not. " + 'Received at least one TransactionSignerAccount, but not all of them so failing the send.', @@ -140,7 +156,7 @@ export const sendGroupOfTransactions = async function ( // Group transaction signers by signer const groupedBySigner = groupBy( - transactions.map((t, i) => ({ + transactionsWithSigner.map((t, i) => ({ signer: t.signer as TransactionSignerAccount, id: i, })), @@ -165,7 +181,7 @@ export const sendGroupOfTransactions = async function ( signedTransactions = signed.sort((s1, s2) => s1.id - s2.id).map((s) => s.txn) } else { signedTransactions = group.map((groupedTransaction, id) => { - const signer = transactions[id].signer + const signer = transactionsWithSigner[id].signer return 'sk' in signer ? groupedTransaction.signTxn(signer.sk) : 'lsig' in signer @@ -179,7 +195,7 @@ export const sendGroupOfTransactions = async function ( Config.getLogger(sendParams?.suppressLog).debug( `Signer IDs (${groupId})`, - transactions.map((t) => getSenderAddress(t.signer)), + transactionsWithSigner.map((t) => getSenderAddress(t.signer)), ) Config.getLogger(sendParams?.suppressLog).debug( diff --git a/src/types/transaction.ts b/src/types/transaction.ts index 9e79db7f..d6c8501c 100644 --- a/src/types/transaction.ts +++ b/src/types/transaction.ts @@ -59,3 +59,15 @@ export interface TransactionToSign { /** The account to use to sign the transaction, either an account (with private key loaded) or a logic signature account */ signer: SendTransactionFrom } + +/** A group of transactions to send together as an atomic group + * @see https://developer.algorand.org/docs/get-details/atomic_transfers/ + */ +export interface TransactionGroupToSend { + /** Any parameters to control the semantics of the send to the network */ + sendParams?: Omit, 'skipSending'> + /** The list of transactions to send, which can either be a raw transaction (in which case signer is required) or the transaction with its signer */ + transactions: (TransactionToSign | Transaction)[] + /** Optional signer to pass in, required if at least one transaction provided is just the transaction, ignored otherwise */ + signer?: SendTransactionFrom +}