Skip to content

Commit

Permalink
feat(transaction): Making grouped transaction definition nicer
Browse files Browse the repository at this point in the history
  • Loading branch information
robdmoore committed Mar 23, 2023
1 parent 49058bf commit 4dd1ae9
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 22 deletions.
55 changes: 55 additions & 0 deletions docs/code/interfaces/types_transaction.TransactionGroupToSend.md
Original file line number Diff line number Diff line change
@@ -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)
18 changes: 8 additions & 10 deletions docs/code/modules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

___

Expand Down Expand Up @@ -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)

___

Expand Down Expand Up @@ -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)

___

Expand All @@ -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)

___

Expand Down Expand Up @@ -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
Expand All @@ -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)

___

Expand Down Expand Up @@ -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)

___

Expand Down Expand Up @@ -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)
1 change: 1 addition & 0 deletions docs/code/modules/types_transaction.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions src/transaction.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) })
Expand Down
40 changes: 28 additions & 12 deletions src/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -112,13 +118,23 @@ const groupBy = <T>(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<Omit<SendTransactionParams, 'maxFee'>, '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)
Expand All @@ -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.',
Expand All @@ -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,
})),
Expand All @@ -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
Expand All @@ -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(
Expand Down
12 changes: 12 additions & 0 deletions src/types/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Omit<SendTransactionParams, 'maxFee'>, '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
}

0 comments on commit 4dd1ae9

Please sign in to comment.