|
| 1 | +--- |
| 2 | +title: Inner Transactions |
| 3 | +description: Overview of Inner Transactions in Algorand Smart Contracts. |
| 4 | +draft: true |
| 5 | +--- |
| 6 | + |
| 7 | +import { Tabs, TabItem } from '@astrojs/starlight/components'; |
| 8 | +import { Code } from '@astrojs/starlight/components'; |
| 9 | +import RemoteCode from '/src/components/RemoteCode.astro'; |
| 10 | + |
| 11 | +## What is Inner Transaction? |
| 12 | + |
| 13 | +When a smart contract is deployed to the Algorand blockchain, it is assigned a unique identifier called the App ID. Additionally, every smart contract has an associated unique Algorand account. |
| 14 | + |
| 15 | +We call these accounts _application accounts_, and their unique identifier is a 58-character long public key known as the _application address_. The account allows the smart contract to function as an escrow account, which can hold and manage assets (ASA) and send transactions just like any other Algorand account. |
| 16 | + |
| 17 | +The transactions sent by the smart contract (application) account are called _Inner Transactions_. |
| 18 | + |
| 19 | +## Inner Transaction Details |
| 20 | + |
| 21 | +Since application accounts are Algorand accounts, just like any other account, they need Algos to cover transaction fees when sending inner transactions. To fund the application account, any other account in the Algorand network can send Algos to the specified account. For funds to leave the smart contract, the following conditions must be met: |
| 22 | + |
| 23 | +- The logic within the contract must submit an inner transaction. |
| 24 | +- The smart contract’s logic must return true. |
| 25 | + |
| 26 | +A smart contract can issue up to 256 inner transactions with one application call. If any of these transactions fail, the smart contract call will also fail. |
| 27 | + |
| 28 | +Inner transactions support all the same transaction types as a regular account can make. |
| 29 | + |
| 30 | +- Payment |
| 31 | +- Key Registration |
| 32 | +- Asset Configuration |
| 33 | +- Asset Freeze |
| 34 | +- Asset Transfer |
| 35 | +- Application Call |
| 36 | +- State Proof |
| 37 | + |
| 38 | +You can also group multiple inner transactions and atomically execute them. Refer to the [code example](#grouped-inner-transactions) below for more details. |
| 39 | + |
| 40 | +Inner transactions are evaluated during AVM execution, allowing changes to be visible within the contract. For example, if the `balance` opcode is used before and after submitting a `pay` transaction, the balance change would be visible to the executing contract. |
| 41 | + |
| 42 | +Inner transactions also have access to the Sender field. It is not required to set this field as all inner transactions default the sender to the contract address. If another account is rekeyed to the smart contract address, setting the sender to the address that has been rekeyed allows the contract to spend from that account. The recipient of an inner transaction must be in the accounts array. Additionally, if the sender of an inner transaction is not the contract, the sender must also be in the accounts array. |
| 43 | + |
| 44 | +Clear state programs do not support creating inner transactions. However, clear state programs can be called by an inner transaction. |
| 45 | + |
| 46 | +## Paying Inner Transaction Fees |
| 47 | + |
| 48 | +By default, fees for Inner Transactions are paid by the application account (NOT the smart contract method caller) and are set automatically to the minimum transaction fee. |
| 49 | + |
| 50 | +However, for many smart contracts (apps), this presents an attack vector in which the application account could be drained through repeated calls to send Inner Transactions that incur fee costs. The recommended pattern is to hard-code Inner Transaction fees to zero. This forces the app call sender to cover those fees through increased fees on the outer transaction through fee pooling. Fee pooling enables the application call to a smart contract method to cover the fees for inner transactions or any other transaction within an atomic transaction group. |
| 51 | + |
| 52 | +To illustrate this concept, let's examine a payment Inner Transaction example that utilizes fee pooling to force the app call sender to cover Inner Transaction fees: |
| 53 | + |
| 54 | +## Payment |
| 55 | + |
| 56 | +<Tabs syncKey='lang'> |
| 57 | + <TabItem label='Python' icon='seti:python'> |
| 58 | + <RemoteCode |
| 59 | + src='https://raw.githubusercontent.com/algorand-devrel/new-devportal-code-examples/main/projects/devportal-code-examples/smart_contracts/inner_transactions/contract.py' |
| 60 | + snippet='PAYMENT' |
| 61 | + lang='py' |
| 62 | + title='Payment Inner Transaction' |
| 63 | + frame='none' |
| 64 | + /> |
| 65 | + </TabItem> |
| 66 | + <TabItem label='TypeScript' icon='seti:typescript'></TabItem> |
| 67 | +</Tabs> |
| 68 | + |
| 69 | +## Asset(ASA) Create |
| 70 | + |
| 71 | +Assets can be created by a smart contract. Use the following contract code to create an asset with an inner transaction. |
| 72 | + |
| 73 | +<Tabs syncKey='lang'> |
| 74 | + <TabItem label='Python' icon='seti:python'> |
| 75 | + <RemoteCode |
| 76 | + src='https://raw.githubusercontent.com/algorand-devrel/new-devportal-code-examples/main/projects/devportal-code-examples/smart_contracts/inner_transactions/contract.py' |
| 77 | + snippet='ASSET_CREATE' |
| 78 | + lang='py' |
| 79 | + title='Asset Create Inner Transaction' |
| 80 | + frame='none' |
| 81 | + /> |
| 82 | + </TabItem> |
| 83 | + <TabItem label='TypeScript' icon='seti:typescript'></TabItem> |
| 84 | +</Tabs> |
| 85 | + |
| 86 | +## Asset(ASA) Opt In |
| 87 | + |
| 88 | +If a smart contract wishes to transfer an asset it holds or needs to opt into an asset, this can be done with an asset transfer inner transaction. If the smart contract created the asset (ASA) via an inner transaction, it does not need to opt into the asset. |
| 89 | + |
| 90 | +<Tabs syncKey='lang'> |
| 91 | + <TabItem label='Python' icon='seti:python'> |
| 92 | + <RemoteCode |
| 93 | + src='https://raw.githubusercontent.com/algorand-devrel/new-devportal-code-examples/main/projects/devportal-code-examples/smart_contracts/inner_transactions/contract.py' |
| 94 | + snippet='ASSET_OPT_IN' |
| 95 | + lang='py' |
| 96 | + title='Asset Opt-In Inner Transaction' |
| 97 | + frame='none' |
| 98 | + /> |
| 99 | + </TabItem> |
| 100 | + <TabItem label='TypeScript' icon='seti:typescript'></TabItem> |
| 101 | +</Tabs> |
| 102 | + |
| 103 | +## Asset(ASA) Transfer |
| 104 | + |
| 105 | +If a smart contract is opted into the asset (ASA), it can transfer the asset with an asset transfer transaction. |
| 106 | + |
| 107 | +<Tabs syncKey='lang'> |
| 108 | + <TabItem label='Python' icon='seti:python'> |
| 109 | + <RemoteCode |
| 110 | + src='https://raw.githubusercontent.com/algorand-devrel/new-devportal-code-examples/main/projects/devportal-code-examples/smart_contracts/inner_transactions/contract.py' |
| 111 | + snippet='ASSET_TRANSFER' |
| 112 | + lang='py' |
| 113 | + title='Asset Transfer Inner Transaction' |
| 114 | + frame='none' |
| 115 | + /> |
| 116 | + </TabItem> |
| 117 | + <TabItem label='TypeScript' icon='seti:typescript'></TabItem> |
| 118 | +</Tabs> |
| 119 | + |
| 120 | +## Asset(ASA) Freeze |
| 121 | + |
| 122 | +A smart contract can freeze any asset, where the smart contract is set as the freeze address. |
| 123 | + |
| 124 | +<Tabs syncKey='lang'> |
| 125 | + <TabItem label='Python' icon='seti:python'> |
| 126 | + <RemoteCode |
| 127 | + src='https://raw.githubusercontent.com/algorand-devrel/new-devportal-code-examples/main/projects/devportal-code-examples/smart_contracts/inner_transactions/contract.py' |
| 128 | + snippet='ASSET_FREEZE' |
| 129 | + lang='py' |
| 130 | + title='Asset Freeze Inner Transaction' |
| 131 | + frame='none' |
| 132 | + /> |
| 133 | + </TabItem> |
| 134 | + <TabItem label='TypeScript' icon='seti:typescript'></TabItem> |
| 135 | +</Tabs> |
| 136 | + |
| 137 | +## Asset(ASA) Revoke |
| 138 | + |
| 139 | +A smart contract can revoke or clawback any asset where the smart contract address is specified as the asset clawback address. |
| 140 | + |
| 141 | +<Tabs syncKey='lang'> |
| 142 | + <TabItem label='Python' icon='seti:python'> |
| 143 | + <RemoteCode |
| 144 | + src='https://raw.githubusercontent.com/algorand-devrel/new-devportal-code-examples/main/projects/devportal-code-examples/smart_contracts/inner_transactions/contract.py' |
| 145 | + snippet='ASSET_REVOKE' |
| 146 | + lang='py' |
| 147 | + title='Asset Clawback Inner Transaction' |
| 148 | + frame='none' |
| 149 | + /> |
| 150 | + </TabItem> |
| 151 | + <TabItem label='TypeScript' icon='seti:typescript'></TabItem> |
| 152 | +</Tabs> |
| 153 | + |
| 154 | +## Asset(ASA) Configuration |
| 155 | + |
| 156 | +As with all assets, the mutable addresses can be changed using contract code similar to the code below. Note these these addresses cannot be changed once set to an empty value. |
| 157 | + |
| 158 | +<Tabs syncKey='lang'> |
| 159 | + <TabItem label='Python' icon='seti:python'> |
| 160 | + <RemoteCode |
| 161 | + src='https://raw.githubusercontent.com/algorand-devrel/new-devportal-code-examples/main/projects/devportal-code-examples/smart_contracts/inner_transactions/contract.py' |
| 162 | + snippet='ASSET_CONFIG' |
| 163 | + lang='py' |
| 164 | + title='Asset Config Inner Transaction' |
| 165 | + frame='none' |
| 166 | + /> |
| 167 | + </TabItem> |
| 168 | + <TabItem label='TypeScript' icon='seti:typescript'></TabItem> |
| 169 | +</Tabs> |
| 170 | + |
| 171 | +## Asset (ASA) Delete |
| 172 | + |
| 173 | +Assets managed by the contract can also be deleted. This can be done with the following contract code. Note that the entire supply of the asset must be returned to the contract account before deleting the asset. |
| 174 | + |
| 175 | +<Tabs syncKey='lang'> |
| 176 | + <TabItem label='Python' icon='seti:python'> |
| 177 | + <RemoteCode |
| 178 | + src='https://raw.githubusercontent.com/algorand-devrel/new-devportal-code-examples/main/projects/devportal-code-examples/smart_contracts/inner_transactions/contract.py' |
| 179 | + snippet='ASSET_DELETE' |
| 180 | + lang='py' |
| 181 | + title='Asset Delete Inner Transaction' |
| 182 | + frame='none' |
| 183 | + /> |
| 184 | + </TabItem> |
| 185 | + <TabItem label='TypeScript' icon='seti:typescript'></TabItem> |
| 186 | +</Tabs> |
| 187 | + |
| 188 | +## Grouped Inner Transactions |
| 189 | + |
| 190 | +A smart contract can make inner transactions consisting of multiple transactions grouped together atomically. The following example groups a payment transaction with a call to another smart contract. |
| 191 | + |
| 192 | +<Tabs syncKey='lang'> |
| 193 | + <TabItem label='Python' icon='seti:python'> |
| 194 | + <RemoteCode |
| 195 | + src='https://raw.githubusercontent.com/algorand-devrel/new-devportal-code-examples/main/projects/devportal-code-examples/smart_contracts/inner_transactions/contract.py' |
| 196 | + snippet='GROUPED_INNER_TXNS' |
| 197 | + lang='py' |
| 198 | + title='Multiple Inner Transaction Atomically' |
| 199 | + frame='none' |
| 200 | + /> |
| 201 | + </TabItem> |
| 202 | + <TabItem label='TypeScript' icon='seti:typescript'></TabItem> |
| 203 | +</Tabs> |
| 204 | + |
| 205 | +## Contract to Contract Calls |
| 206 | + |
| 207 | +A smart contract can also call another smart contract method with inner transactions. However there are some limitations when making contract to contract calls. |
| 208 | + |
| 209 | +- An application may not call itself, even indirectly. This is referred to as re-entrancy and is explicitly forbidden. |
| 210 | +- An application may only call into other applications up to a stack depth of 8. In other words, if app calls (->) look like 1->2->3->4->5->6->7->8, App 8 may not call another application. This would violate the stack depth limit. |
| 211 | +- An application may issue up to 256 inner transactions to increase its budget (max budget of 179.2k even for a group size of 1), but the max call budget is shared for all applications in the group. This means you can't have two app calls in the same group that both try to issue 256 inner app calls. |
| 212 | +- An application of AVM version 6 or above may not call contracts with a AVM version 3 or below. This limitation protects an older application from unexpected behavior introduced in newer AVM versions. |
| 213 | + |
| 214 | +A smart contract can call other smart contracts using any of the OnComplete types. This allows a smart contract to create, opt in, close out, clear state, delete, or just call (NoOp) other smart contracts. To call an existing smart contract the following contract code can be used. |
| 215 | + |
| 216 | +### NoOp Application call |
| 217 | + |
| 218 | +<Tabs syncKey='lang'> |
| 219 | + <TabItem label='Python' icon='seti:python'> |
| 220 | + <RemoteCode |
| 221 | + src='https://raw.githubusercontent.com/algorand-devrel/new-devportal-code-examples/main/projects/devportal-code-examples/smart_contracts/inner_transactions/contract.py' |
| 222 | + snippet='NOOP_APP_CALL' |
| 223 | + lang='py' |
| 224 | + title='NoOp App Call Inner Transaction' |
| 225 | + frame='none' |
| 226 | + /> |
| 227 | + </TabItem> |
| 228 | + <TabItem label='TypeScript' icon='seti:typescript'></TabItem> |
| 229 | +</Tabs> |
| 230 | + |
| 231 | +### Deploy smart contract via inner transaction |
| 232 | + |
| 233 | +<Tabs syncKey='lang'> |
| 234 | + <TabItem label='Python' icon='seti:python'> |
| 235 | + <RemoteCode |
| 236 | + src='https://raw.githubusercontent.com/algorand-devrel/new-devportal-code-examples/main/projects/devportal-code-examples/smart_contracts/inner_transactions/contract.py' |
| 237 | + snippet='DEPLOY_APP' |
| 238 | + lang='py' |
| 239 | + title='Deploy App With Inner Transaction' |
| 240 | + frame='none' |
| 241 | + /> |
| 242 | + </TabItem> |
| 243 | + <TabItem label='TypeScript' icon='seti:typescript'></TabItem> |
| 244 | +</Tabs> |
0 commit comments