Local Storage
Local state is associated with each account that opts into the application. Algorand smart contracts offer local storage, which enables accounts to maintain persistent key-value data. This data is accessible to authorized contracts and can be queried from external sources.
Manipulating Local State
Smart contracts can create, update, and delete values in local state. The number of values that can be written is limited by the initial configuration set during smart contract creation.
TEAL (Transaction Execution Approval Language) provides several opcodes for facilitating reading and writing to state including app_local_put
, app_local_get
and app_local_get_ex
. In addition to using TEAL, the local state values of a smart contract can be read externally using SDKs and the goal CLI. These reads are non-transactional queries that retrieve the current state of the contract.
Allocation
Local storage is allocated when an account opts into a smart contract by submitting an opt-in transaction. Each account can have between 0 and 16 key-value pairs in local storage, with a total of 2KB memory shared among them. The amount of local storage is determined during smart contract creation and cannot be edited later. The opted-in user account is responsible for funding the local storage by increasing their minimum balance requirement.
1 def __init__(self) -> None:2 ## Initialise local storages3 self.local_int = LocalState(UInt64) # Uint644 self.local_bytes = LocalState(Bytes) # Bytes5 self.local_bool = LocalState(bool) # Bool6 self.local_asset = LocalState(Asset) # Asset7 self.local_application = LocalState(Application) # Application8 self.local_account = LocalState(Account) # Account
Reading from Local State
Local storage values are stored in the account’s balance record. Any account that sends a transaction to the smart contract can have its local storage modified by the smart contract, as long as the account has opted into the smart contract. Local storage can be read by any application call that has the smart contract’s app ID in its foreign apps array and the account in its foreign accounts array. In addition to the transaction sender, a smart contract call can reference up to four additional accounts whose local storage can be manipulated for the current smart contract, as long as those accounts have opted into the contract.
These five accounts can have their storage values read for any smart contract on Algorand by specifying the application ID of the smart contract, if the additional contract is included in the transaction’s applications array. This is a read-only operation and does not allow one smart contract to modify the local state of another. The additionally referenced accounts can be changed per smart contract call (transaction). The key-value pairs in local storage can be read on-chain directly or off-chain using SDKs and the goal CLI. Local storage is editable only by the smart contract itself, but it can be deleted by either the smart contract or the user account (using a ClearState call).
TEAL provides opcodes to read local state values for the current smart contract.
The app_local_get
opcode retrieves values from the current contract’s local storage.
The app_local_get_ex
opcode returns two values on the stack: a boolean
indicating whether the value was found, and the actual value
if it exists.
The _ex opcodes allow reading local states from other accounts and smart contracts, as long as the account and contract are included in the accounts and applications arrays. Branching logic is typically used after calling the _ex opcodes to handle cases where the value is found or not found.
1 @arc4.abimethod2 def get_item_local_data(self, for_account: Account) -> UInt64:3 return self.local_int[for_account]4
5 # get function6 @arc4.abimethod7 def get_local_data_with_default_int(self, for_account: Account) -> UInt64:8 return self.local_int.get(for_account, default=UInt64(0)) # Uint649
10 # maybe function11 @arc4.abimethod12 def maybe_local_data(self, for_account: Account) -> tuple[UInt64, bool]:13 # used to get data or assert int14 result, exists = self.local_int.maybe(for_account) # Uint6415 if not exists:16 result = UInt64(0)17 return result, exists
1#pragma version 102
3get_item_local_data:4 proto 1 15 frame_dig -16 int 07 byte "local_int"8 app_local_get_ex9 assert10 retsub11
12get_local_data_with_default_int:13 proto 1 114 frame_dig -115 int 016 byte "local_int"17 app_local_get_ex18 int 019 cover 220 select21 retsub22
23maybe_local_data:24 proto 1 225 frame_dig -126 int 027 byte "local_int"28 app_local_get_ex29 dup30 uncover 231 swap32 bnz maybe_local_data_after_if_else@233 int 034 frame_bury 135
36maybe_local_data_after_if_else@2:37 frame_dig 138 frame_dig 039 uncover 340 uncover 341 retsub
Refer Sinppet Source to get local storage value for different data types
The local state values of a smart contract can also be read externally using cli. The below command reads are non-transactional queries that retrieve the current state of the contract.
Example command:
goal app read --app-id 1 --guess-format --local --from <ADDRESS>
This command will return the local state for the account specified by --from
.
Example output with 3 key-value pairs
1{2 "Creator": {3 "tb": "FRYCPGH25DHCYQGXEB54NJ6LHQG6I2TWMUV2P3UWUU7RWP7BQ2BMBBDPD4",4 "tt": 15 },6 "MyBytesKey": {7 "tb": "hello",8 "tt": 19 },10 "MyUintKey": {11 "tt": 2,12 "ui": 5013 }14}
Interpretation:
- The keys are
Creator
,MyBytesKey
, andMyUintKey
. - The
tt
field indicates the type of the value: 1 for byte slices (byte-array values), 2 for uint64 values. - When
tt=1
, the value is stored in thetb
field. The--guess-format
option automatically converts theCreator
value to an Algorand address with a checksum (instead of displaying the raw 32-byte public key). - When
tt=2
, the value is stored in theui
field.
Writing to Local State
To write to local state, use the app_local_put
opcode. An additional account parameter is provided to specify which account’s local storage should be modified.
1 @arc4.abimethod2 def set_local_int(self, for_account: Account, value: UInt64) -> None:3 self.local_int[for_account] = value # Uint64
1#pragma version 102
3set_local_int:4 proto 2 05 frame_dig -26 byte "local_int"7 frame_dig -18 app_local_put9 retsub
Refer Sinppet Source to set local storage value for different data types
Deletion of Local State
Deleting a smart contract does not affect its local storage. Accounts must clear out of the smart contract to recover their minimum balance. Every smart contract has an ApprovalProgram and a ClearStateProgram. An account holder can clear their local state for a contract at any time by executing a ClearState transaction, deleting their data and freeing up their locked minimum balance. An account can request to clear its local state using a closeout transaction or clear its local state for a specific contract using a clearstate transaction, which will always succeed, even after the contract is deleted.
1 @arc4.abimethod2 def delete_local_data(self, for_account: Account) -> None:3 del self.local_account[for_account] # Uint64
1#pragma version 102
3delete_local_data:4 proto 1 05 frame_dig -16 byte "local_account"7 app_local_del8 err
Refer Sinppet Source to delete local storage value for different data types
Summary of Local State Operations
For manipulating local storage data like reading, writing, deleting and checking if exists:
TEAL: Different opcodes can be used
Function | Description |
---|---|
app_local_get | Get local data for the current app |
app_local_get_ex | Get local data for other app |
app_local_put | Set local data to the current app |
app_local_del | Delete local data from the current app |
app_local_get_ex | Check if local data exists for the current app |
app_local_get_ex | Check if local data exists for the other app |
Different functions of LocalState class can be used. The detailed api reference can be found here
Function | Description |
---|---|
LocalState(type_) | Initialize a local state with the specified data type |
getitem(account) | Get data for the given account |
get(account, default) | Get data for the given account, or a default value if not found |
maybe(account) | Get data for the given account, and a boolean indicating if it exists |
setitem(account, value) | Set data for the given account |
delitem(account) | Delete data for the given account |
contains(account) | Check if data exists for the given account |