#arbitrary tx execution in smart contract
1 messages · Page 1 of 1 (latest)
Hi @pure gate. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:
Smart Contract Wallet with Generic Transaction Group Submission
Based on the knowledge sources, here's an example of a smart contract wallet in Algorand TypeScript that includes a method for generic transaction group submission using itxnCompose (which supports dynamic/arbitrary groups) and gtxn for verifying incoming group transactions.
Note: For a dynamic/arbitrary mix of transactions,
itxnComposeis the right tool. For reading results from dynamically composed inner transactions, you useop.GITxnops. [Inner Transactions]
import {
Contract,
Global,
assert,
gtxn,
itxn,
itxnCompose,
Account,
Application,
Asset,
Uint64,
TransactionType,
} from '@algorandfoundation/algorand-typescript'
/**
* A simple smart contract wallet that can:
* 1. Execute a payment inner transaction
* 2. Execute an asset transfer inner transaction
* 3. Execute a generic group of inner transactions (app calls, payments, asset transfers)
*/
export class SmartContractWallet extends Contract {
/**
* Execute a single payment from the contract
*/
executePayment(receiver: Account, amount: uint64): void {
// Only the owner (creator) can trigger payments
assert(Txn.sender === Global.creatorAddress, 'Only owner can execute payments')
itxn
.payment({
receiver: receiver,
amount: amount,
fee: 0,
})
.submit()
}
/**
* Execute a single asset transfer from the contract
*/
executeAssetTransfer(receiver: Account, assetId: Asset, amount: uint64): void {
assert(Txn.sender === Global.creatorAddress, 'Only owner can execute asset transfers')
itxn
.assetTransfer({
xferAsset: assetId,
assetReceiver: receiver,
assetAmount: amount,
fee: 0,
})
.submit()
}
/**
- Execute a generic group of inner transactions atomically.
- Supports any mix of payment, asset transfer, and app call transactions.
- @param receivers - Array of receiver addresses (for payments/asset transfers)
- @param amounts - Array of amounts corresponding to each receiver
- @param assetIds - Array of asset IDs (use 0 for payment transactions)
- @param appId - Application to call (optional, pass 0 to skip app call)
- @param count - Total number of transactions in the group
*/
executeGenericGroup(
receivers: Address[],
amounts: uint64[],
assetIds: uint64[],
appId: Application,
count: uint64,
): void {
assert(Txn.sender === Global.creatorAddress, 'Only owner can execute transaction groups')
assert(count > Uint64(0), 'Must have at least one transaction')
// Build the group dynamically using itxnCompose
for (const i of urange(count)) {
const receiver = receivers[i]
const amount = amounts[i]
const assetId = assetIds[i]
if (assetId === Uint64(0)) {
// Payment transaction
const payFields = {
type: TransactionType.Payment,
receiver: receiver,
amount: amount,
fee: Uint64(0),
} satisfies PaymentComposeFields
if (i === Uint64(0)) {
itxnCompose.begin(payFields)
} else {
itxnCompose.next(payFields)
}
} else {
// Asset transfer transaction
const xferFields = {
type: TransactionType.AssetTransfer,
xferAsset: assetId,
assetReceiver: receiver,
assetAmount: amount,
fee: Uint64(0),
} satisfies AssetTransferComposeFields
if (i === Uint64(0)) {
itxnCompose.begin(xferFields)
} else {
itxnCompose.next(xferFields)
}
}
}
// Optionally append an app call at the end
if (appId.id !== Uint64(0)) {
itxnCompose.next(
itxn.applicationCall({
appId: appId.id,
fee: Uint64(0),
})
)
}
// Submit all staged transactions atomically
itxnCompose.submit()
}
}
## Key Concepts Used
| Concept | Purpose |
|---|---|
| `itxnCompose.begin()` | Stages the **first** transaction in a new inner group [[Inner Transactions](<https://dev.algorand.co/algokit/languages/typescript/lg-itxns/#itxn-compose-api>)] |
| `itxnCompose.next()` | Stages **subsequent** transactions in the group [[Inner Transactions](<https://dev.algorand.co/algokit/languages/typescript/lg-itxns/#itxn-compose-api>)] |
| `itxnCompose.submit()` | Dispatches all staged transactions atomically [[Inner Transactions](<https://dev.algorand.co/algokit/languages/typescript/lg-itxns/#itxn-compose-api>)] |
| `itxn.payment().submit()` | Submits a single payment inner transaction [[Grouped Inner Transactions](<https://dev.algorand.co/concepts/smart-contracts/inner-txn/#grouped-inner-transactions>)] |
| `itxn.assetTransfer().submit()` | Submits a single asset transfer inner transaction |
## Important Limitations
itxnComposedoes not provide strongly typed return values. To read results from any transaction in the group, useop.GITxnops (e.g.,op.GITxn.lastLog(1)). [Inner Transactions]- If the number of transactions is known at compile time, prefer
itxn.submitGroup(...)instead, as it gives typed return values. [Fixed-Size Groups] ItxnParamsobjects cannot be stored in arrays or application state. [Inner Transactions]