openclaw-algorand-plugin 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +112 -0
- package/index.ts +361 -0
- package/lib/mcp-servers.ts +14 -0
- package/lib/x402-fetch.ts +213 -0
- package/memory/algorand-plugin.md +82 -0
- package/openclaw.plugin.json +30 -0
- package/package.json +38 -0
- package/setup.ts +80 -0
- package/skills/algorand-development/SKILL.md +90 -0
- package/skills/algorand-development/references/build-smart-contracts-reference.md +79 -0
- package/skills/algorand-development/references/build-smart-contracts.md +52 -0
- package/skills/algorand-development/references/create-project-reference.md +86 -0
- package/skills/algorand-development/references/create-project.md +89 -0
- package/skills/algorand-development/references/implement-arc-standards-arc32-arc56.md +396 -0
- package/skills/algorand-development/references/implement-arc-standards-arc4.md +265 -0
- package/skills/algorand-development/references/implement-arc-standards.md +92 -0
- package/skills/algorand-development/references/search-algorand-examples-reference.md +119 -0
- package/skills/algorand-development/references/search-algorand-examples.md +89 -0
- package/skills/algorand-development/references/troubleshoot-errors-contract.md +373 -0
- package/skills/algorand-development/references/troubleshoot-errors-transaction.md +599 -0
- package/skills/algorand-development/references/troubleshoot-errors.md +105 -0
- package/skills/algorand-development/references/use-algokit-cli-reference.md +228 -0
- package/skills/algorand-development/references/use-algokit-cli.md +64 -0
- package/skills/algorand-interaction/SKILL.md +223 -0
- package/skills/algorand-interaction/references/algorand-mcp.md +743 -0
- package/skills/algorand-interaction/references/examples-algorand-mcp.md +647 -0
- package/skills/algorand-python/SKILL.md +95 -0
- package/skills/algorand-python/references/build-smart-contracts-decorators.md +413 -0
- package/skills/algorand-python/references/build-smart-contracts-reference.md +55 -0
- package/skills/algorand-python/references/build-smart-contracts-storage.md +452 -0
- package/skills/algorand-python/references/build-smart-contracts-transactions.md +445 -0
- package/skills/algorand-python/references/build-smart-contracts-types.md +438 -0
- package/skills/algorand-python/references/build-smart-contracts.md +82 -0
- package/skills/algorand-python/references/create-project-reference.md +55 -0
- package/skills/algorand-python/references/create-project.md +75 -0
- package/skills/algorand-python/references/implement-arc-standards-arc32-arc56.md +101 -0
- package/skills/algorand-python/references/implement-arc-standards-arc4.md +154 -0
- package/skills/algorand-python/references/implement-arc-standards.md +39 -0
- package/skills/algorand-python/references/troubleshoot-errors-contract.md +355 -0
- package/skills/algorand-python/references/troubleshoot-errors-transaction.md +430 -0
- package/skills/algorand-python/references/troubleshoot-errors.md +46 -0
- package/skills/algorand-python/references/use-algokit-utils-reference.md +350 -0
- package/skills/algorand-python/references/use-algokit-utils.md +76 -0
- package/skills/algorand-typescript/SKILL.md +131 -0
- package/skills/algorand-typescript/references/algorand-ts-migration-from-beta.md +448 -0
- package/skills/algorand-typescript/references/algorand-ts-migration-from-tealscript.md +487 -0
- package/skills/algorand-typescript/references/algorand-ts-migration.md +102 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-methods-and-abi.md +134 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-reference.md +58 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-storage.md +154 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-transactions.md +187 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-types-and-values.md +150 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax.md +84 -0
- package/skills/algorand-typescript/references/build-smart-contracts-reference.md +52 -0
- package/skills/algorand-typescript/references/build-smart-contracts.md +74 -0
- package/skills/algorand-typescript/references/call-smart-contracts-reference.md +237 -0
- package/skills/algorand-typescript/references/call-smart-contracts.md +183 -0
- package/skills/algorand-typescript/references/create-project-reference.md +53 -0
- package/skills/algorand-typescript/references/create-project.md +86 -0
- package/skills/algorand-typescript/references/deploy-react-frontend-examples.md +527 -0
- package/skills/algorand-typescript/references/deploy-react-frontend-reference.md +412 -0
- package/skills/algorand-typescript/references/deploy-react-frontend.md +239 -0
- package/skills/algorand-typescript/references/implement-arc-standards-arc32-arc56.md +73 -0
- package/skills/algorand-typescript/references/implement-arc-standards-arc4.md +126 -0
- package/skills/algorand-typescript/references/implement-arc-standards.md +44 -0
- package/skills/algorand-typescript/references/test-smart-contracts-examples.md +245 -0
- package/skills/algorand-typescript/references/test-smart-contracts-unit-tests.md +147 -0
- package/skills/algorand-typescript/references/test-smart-contracts.md +127 -0
- package/skills/algorand-typescript/references/troubleshoot-errors-contract.md +296 -0
- package/skills/algorand-typescript/references/troubleshoot-errors-transaction.md +438 -0
- package/skills/algorand-typescript/references/troubleshoot-errors.md +56 -0
- package/skills/algorand-typescript/references/use-algokit-utils-reference.md +342 -0
- package/skills/algorand-typescript/references/use-algokit-utils.md +74 -0
- package/skills/algorand-x402-python/SKILL.md +113 -0
- package/skills/algorand-x402-python/references/create-python-x402-client-examples.md +469 -0
- package/skills/algorand-x402-python/references/create-python-x402-client-reference.md +313 -0
- package/skills/algorand-x402-python/references/create-python-x402-client.md +207 -0
- package/skills/algorand-x402-python/references/create-python-x402-facilitator-examples.md +924 -0
- package/skills/algorand-x402-python/references/create-python-x402-facilitator-reference.md +629 -0
- package/skills/algorand-x402-python/references/create-python-x402-facilitator.md +408 -0
- package/skills/algorand-x402-python/references/create-python-x402-server-examples.md +703 -0
- package/skills/algorand-x402-python/references/create-python-x402-server-reference.md +303 -0
- package/skills/algorand-x402-python/references/create-python-x402-server.md +221 -0
- package/skills/algorand-x402-python/references/explain-algorand-x402-python-examples.md +605 -0
- package/skills/algorand-x402-python/references/explain-algorand-x402-python-reference.md +315 -0
- package/skills/algorand-x402-python/references/explain-algorand-x402-python.md +167 -0
- package/skills/algorand-x402-python/references/use-python-x402-core-avm-examples.md +554 -0
- package/skills/algorand-x402-python/references/use-python-x402-core-avm-reference.md +278 -0
- package/skills/algorand-x402-python/references/use-python-x402-core-avm.md +166 -0
- package/skills/algorand-x402-typescript/SKILL.md +129 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-client-examples.md +879 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-client-reference.md +371 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-client.md +236 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-facilitator-examples.md +875 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-facilitator-reference.md +461 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-facilitator.md +270 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-nextjs-examples.md +1181 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-nextjs-reference.md +360 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-nextjs.md +251 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-paywall-examples.md +870 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-paywall-reference.md +323 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-paywall.md +281 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-server-examples.md +1135 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-server-reference.md +382 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-server.md +216 -0
- package/skills/algorand-x402-typescript/references/explain-algorand-x402-typescript-examples.md +616 -0
- package/skills/algorand-x402-typescript/references/explain-algorand-x402-typescript-reference.md +323 -0
- package/skills/algorand-x402-typescript/references/explain-algorand-x402-typescript.md +232 -0
- package/skills/algorand-x402-typescript/references/use-typescript-x402-core-avm-examples.md +1417 -0
- package/skills/algorand-x402-typescript/references/use-typescript-x402-core-avm-reference.md +504 -0
- package/skills/algorand-x402-typescript/references/use-typescript-x402-core-avm.md +158 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
# Algorand Python Storage Patterns
|
|
2
|
+
|
|
3
|
+
Store data on-chain using GlobalState, LocalState, and Box storage in Algorand Python smart contracts.
|
|
4
|
+
|
|
5
|
+
## When to use this reference
|
|
6
|
+
|
|
7
|
+
Use this reference when:
|
|
8
|
+
|
|
9
|
+
- Choosing between storage types for your contract
|
|
10
|
+
- Implementing global or local state
|
|
11
|
+
- Working with Box storage for larger data
|
|
12
|
+
- Understanding MBR (Minimum Balance Requirement) costs
|
|
13
|
+
|
|
14
|
+
## Storage Types Overview
|
|
15
|
+
|
|
16
|
+
| Storage | Scope | Who Pays MBR | Max Size | Use Case |
|
|
17
|
+
|---------|-------|--------------|----------|----------|
|
|
18
|
+
| `GlobalState` | App-wide | App creator | 64 keys, 8KB total | Shared app data |
|
|
19
|
+
| `LocalState` | Per-user | User (on opt-in) | 16 keys, 2KB total | Per-user data with opt-in |
|
|
20
|
+
| `Box` | App-wide | App account | 32KB per box | Large single values |
|
|
21
|
+
| `BoxMap` | Per-key | App account | 32KB per box | Per-user data without opt-in |
|
|
22
|
+
| `BoxRef` | App-wide | App account | 32KB | Raw binary data, manual sizing |
|
|
23
|
+
|
|
24
|
+
## GlobalState
|
|
25
|
+
|
|
26
|
+
Store app-wide data accessible by all users. Two approaches available:
|
|
27
|
+
|
|
28
|
+
### Simple Assignment (Recommended for Basic Use)
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from algopy import ARC4Contract, UInt64, Bytes, arc4
|
|
32
|
+
|
|
33
|
+
class MyContract(ARC4Contract):
|
|
34
|
+
def __init__(self) -> None:
|
|
35
|
+
# Simple assignment - uses variable name as key
|
|
36
|
+
self.counter = UInt64(0)
|
|
37
|
+
self.name = Bytes(b"MyApp")
|
|
38
|
+
self.active = True
|
|
39
|
+
|
|
40
|
+
@arc4.abimethod
|
|
41
|
+
def increment(self) -> None:
|
|
42
|
+
self.counter += 1
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### GlobalState Wrapper (For Advanced Control)
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from algopy import ARC4Contract, GlobalState, UInt64, Bytes, arc4
|
|
49
|
+
|
|
50
|
+
class MyContract(ARC4Contract):
|
|
51
|
+
def __init__(self) -> None:
|
|
52
|
+
# With custom key and description (for ARC-32/56 app spec)
|
|
53
|
+
self.counter = GlobalState(
|
|
54
|
+
UInt64(0),
|
|
55
|
+
key="cnt",
|
|
56
|
+
description="Transaction counter"
|
|
57
|
+
)
|
|
58
|
+
# No default value - must set before reading
|
|
59
|
+
self.owner = GlobalState(Bytes)
|
|
60
|
+
|
|
61
|
+
@arc4.abimethod
|
|
62
|
+
def increment(self) -> UInt64:
|
|
63
|
+
self.counter.value += 1
|
|
64
|
+
return self.counter.value
|
|
65
|
+
|
|
66
|
+
@arc4.abimethod
|
|
67
|
+
def get_counter_safe(self) -> UInt64:
|
|
68
|
+
# Check if value exists
|
|
69
|
+
if self.counter:
|
|
70
|
+
return self.counter.value
|
|
71
|
+
return UInt64(0)
|
|
72
|
+
|
|
73
|
+
@arc4.abimethod
|
|
74
|
+
def get_with_default(self) -> Bytes:
|
|
75
|
+
# Get with fallback default
|
|
76
|
+
return self.owner.get(default=Bytes(b"unset"))
|
|
77
|
+
|
|
78
|
+
@arc4.abimethod
|
|
79
|
+
def get_maybe(self) -> tuple[UInt64, bool]:
|
|
80
|
+
# Returns (value, exists)
|
|
81
|
+
return self.counter.maybe()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### GlobalState Methods
|
|
85
|
+
|
|
86
|
+
| Method | Description |
|
|
87
|
+
|--------|-------------|
|
|
88
|
+
| `.value` | Get/set the value (errors if not set) |
|
|
89
|
+
| `.get(default)` | Get value or return default |
|
|
90
|
+
| `.maybe()` | Returns `(value, exists)` tuple |
|
|
91
|
+
| `bool(state)` | `True` if value is set |
|
|
92
|
+
| `.key` | Access raw storage key |
|
|
93
|
+
|
|
94
|
+
## LocalState
|
|
95
|
+
|
|
96
|
+
Store per-user data. Requires user to opt-in to the application first.
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from algopy import ARC4Contract, LocalState, UInt64, Bytes, Account, Txn, arc4
|
|
100
|
+
|
|
101
|
+
class MyContract(ARC4Contract):
|
|
102
|
+
def __init__(self) -> None:
|
|
103
|
+
self.user_balance = LocalState(UInt64, key="bal")
|
|
104
|
+
self.user_name = LocalState(Bytes)
|
|
105
|
+
|
|
106
|
+
@arc4.abimethod(allow_actions=["OptIn"])
|
|
107
|
+
def opt_in(self) -> None:
|
|
108
|
+
# Initialize local state on opt-in
|
|
109
|
+
self.user_balance[Txn.sender] = UInt64(0)
|
|
110
|
+
|
|
111
|
+
@arc4.abimethod
|
|
112
|
+
def deposit(self, amount: UInt64) -> None:
|
|
113
|
+
self.user_balance[Txn.sender] += amount
|
|
114
|
+
|
|
115
|
+
@arc4.abimethod
|
|
116
|
+
def get_balance(self, account: Account) -> UInt64:
|
|
117
|
+
# Direct access (fails if not opted in)
|
|
118
|
+
return self.user_balance[account]
|
|
119
|
+
|
|
120
|
+
@arc4.abimethod
|
|
121
|
+
def get_balance_safe(self, account: Account) -> UInt64:
|
|
122
|
+
# Check if account has local state
|
|
123
|
+
if account in self.user_balance:
|
|
124
|
+
return self.user_balance[account]
|
|
125
|
+
return UInt64(0)
|
|
126
|
+
|
|
127
|
+
@arc4.abimethod
|
|
128
|
+
def get_balance_default(self, account: Account) -> UInt64:
|
|
129
|
+
# Get with default
|
|
130
|
+
return self.user_balance.get(account, default=UInt64(0))
|
|
131
|
+
|
|
132
|
+
@arc4.abimethod
|
|
133
|
+
def get_balance_maybe(self, account: Account) -> tuple[UInt64, bool]:
|
|
134
|
+
# Returns (value, exists)
|
|
135
|
+
return self.user_balance.maybe(account)
|
|
136
|
+
|
|
137
|
+
@arc4.abimethod
|
|
138
|
+
def clear_balance(self, account: Account) -> None:
|
|
139
|
+
# Delete local state entry
|
|
140
|
+
del self.user_balance[account]
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### LocalState Methods
|
|
144
|
+
|
|
145
|
+
| Method | Description |
|
|
146
|
+
|--------|-------------|
|
|
147
|
+
| `state[account]` | Get/set value for account |
|
|
148
|
+
| `state.get(account, default)` | Get value or return default |
|
|
149
|
+
| `state.maybe(account)` | Returns `(value, exists)` tuple |
|
|
150
|
+
| `account in state` | `True` if account has value |
|
|
151
|
+
| `del state[account]` | Delete value for account |
|
|
152
|
+
| `.key` | Access raw storage key |
|
|
153
|
+
|
|
154
|
+
## Box Storage
|
|
155
|
+
|
|
156
|
+
Store larger data without user opt-in. App account pays MBR.
|
|
157
|
+
|
|
158
|
+
### Box (Single Value)
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from algopy import ARC4Contract, Box, UInt64, Bytes, arc4, String
|
|
162
|
+
|
|
163
|
+
class MyContract(ARC4Contract):
|
|
164
|
+
def __init__(self) -> None:
|
|
165
|
+
# Box with compile-time constant key
|
|
166
|
+
self.config = Box(UInt64, key=b"config")
|
|
167
|
+
self.data = Box(Bytes, key=b"data")
|
|
168
|
+
|
|
169
|
+
@arc4.abimethod
|
|
170
|
+
def set_config(self, value: UInt64) -> None:
|
|
171
|
+
# Automatically creates box if needed
|
|
172
|
+
self.config.value = value
|
|
173
|
+
|
|
174
|
+
@arc4.abimethod
|
|
175
|
+
def get_config(self) -> UInt64:
|
|
176
|
+
# Check existence first
|
|
177
|
+
if self.config:
|
|
178
|
+
return self.config.value
|
|
179
|
+
return UInt64(0)
|
|
180
|
+
|
|
181
|
+
@arc4.abimethod
|
|
182
|
+
def get_config_maybe(self) -> tuple[UInt64, bool]:
|
|
183
|
+
return self.config.maybe()
|
|
184
|
+
|
|
185
|
+
@arc4.abimethod
|
|
186
|
+
def delete_config(self) -> None:
|
|
187
|
+
# Delete box to reclaim MBR
|
|
188
|
+
del self.config.value
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Box with Dynamic Key
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from algopy import ARC4Contract, Box, UInt64, String, arc4
|
|
195
|
+
|
|
196
|
+
class MyContract(ARC4Contract):
|
|
197
|
+
@arc4.abimethod
|
|
198
|
+
def create_named_box(self, name: String) -> None:
|
|
199
|
+
# Create box with dynamic key inside method
|
|
200
|
+
box = Box(UInt64, key=name.bytes)
|
|
201
|
+
box.value = UInt64(100)
|
|
202
|
+
|
|
203
|
+
@arc4.abimethod
|
|
204
|
+
def read_named_box(self, name: String) -> UInt64:
|
|
205
|
+
box = Box(UInt64, key=name.bytes)
|
|
206
|
+
if box:
|
|
207
|
+
return box.value
|
|
208
|
+
return UInt64(0)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Box Methods
|
|
212
|
+
|
|
213
|
+
| Method | Description |
|
|
214
|
+
|--------|-------------|
|
|
215
|
+
| `.value` | Get/set the box value |
|
|
216
|
+
| `.get(default=...)` | Get value or return default |
|
|
217
|
+
| `.maybe()` | Returns `(value, exists)` tuple |
|
|
218
|
+
| `bool(box)` | `True` if box exists |
|
|
219
|
+
| `del box.value` | Delete the box |
|
|
220
|
+
| `.create(size=...)` | Create box with specific size |
|
|
221
|
+
| `.length` | Get box length in bytes |
|
|
222
|
+
| `.extract(start, length)` | Extract bytes from box |
|
|
223
|
+
| `.replace(start, value)` | Replace bytes in box |
|
|
224
|
+
| `.splice(start, stop, value)` | Splice bytes in box |
|
|
225
|
+
|
|
226
|
+
### BoxMap (Per-Key Storage)
|
|
227
|
+
|
|
228
|
+
Best for per-user data when you don't want to require opt-in.
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
from algopy import ARC4Contract, BoxMap, UInt64, String, Account, Txn, arc4
|
|
232
|
+
|
|
233
|
+
class MyContract(ARC4Contract):
|
|
234
|
+
def __init__(self) -> None:
|
|
235
|
+
# BoxMap with key type and value type
|
|
236
|
+
self.balances = BoxMap(Account, UInt64, key_prefix="bal_")
|
|
237
|
+
self.names = BoxMap(UInt64, String, key_prefix="name_")
|
|
238
|
+
|
|
239
|
+
@arc4.abimethod
|
|
240
|
+
def deposit(self, amount: UInt64) -> None:
|
|
241
|
+
sender = Txn.sender
|
|
242
|
+
if sender in self.balances:
|
|
243
|
+
self.balances[sender] += amount
|
|
244
|
+
else:
|
|
245
|
+
self.balances[sender] = amount
|
|
246
|
+
|
|
247
|
+
@arc4.abimethod
|
|
248
|
+
def get_balance(self, account: Account) -> UInt64:
|
|
249
|
+
if account in self.balances:
|
|
250
|
+
return self.balances[account]
|
|
251
|
+
return UInt64(0)
|
|
252
|
+
|
|
253
|
+
@arc4.abimethod
|
|
254
|
+
def get_balance_default(self, account: Account) -> UInt64:
|
|
255
|
+
return self.balances.get(account, default=UInt64(0))
|
|
256
|
+
|
|
257
|
+
@arc4.abimethod
|
|
258
|
+
def get_balance_maybe(self, account: Account) -> tuple[UInt64, bool]:
|
|
259
|
+
return self.balances.maybe(account)
|
|
260
|
+
|
|
261
|
+
@arc4.abimethod
|
|
262
|
+
def withdraw_all(self) -> UInt64:
|
|
263
|
+
sender = Txn.sender
|
|
264
|
+
balance = self.balances.get(sender, default=UInt64(0))
|
|
265
|
+
if balance > 0:
|
|
266
|
+
del self.balances[sender] # Delete to reclaim MBR
|
|
267
|
+
return balance
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### BoxMap Methods
|
|
271
|
+
|
|
272
|
+
| Method | Description |
|
|
273
|
+
|--------|-------------|
|
|
274
|
+
| `map[key]` | Get/set value for key |
|
|
275
|
+
| `map.get(key, default=...)` | Get value or return default |
|
|
276
|
+
| `map.maybe(key)` | Returns `(value, exists)` tuple |
|
|
277
|
+
| `key in map` | `True` if key exists |
|
|
278
|
+
| `del map[key]` | Delete the box |
|
|
279
|
+
| `.length(key)` | Get box length for key |
|
|
280
|
+
| `.key_prefix` | Access raw key prefix |
|
|
281
|
+
| `.box(key)` | Get Box proxy for key |
|
|
282
|
+
|
|
283
|
+
### BoxRef (Raw Binary Data)
|
|
284
|
+
|
|
285
|
+
For manual control over large binary data.
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
from algopy import ARC4Contract, BoxRef, Bytes, UInt64, Txn, Global, arc4
|
|
289
|
+
|
|
290
|
+
class MyContract(ARC4Contract):
|
|
291
|
+
def __init__(self) -> None:
|
|
292
|
+
self.blob = BoxRef(key=b"blob")
|
|
293
|
+
|
|
294
|
+
@arc4.abimethod
|
|
295
|
+
def create_blob(self, size: UInt64) -> bool:
|
|
296
|
+
# Manually create box with specific size
|
|
297
|
+
return self.blob.create(size=size)
|
|
298
|
+
|
|
299
|
+
@arc4.abimethod
|
|
300
|
+
def write_to_blob(self, offset: UInt64, data: Bytes) -> None:
|
|
301
|
+
self.blob.replace(offset, data)
|
|
302
|
+
|
|
303
|
+
@arc4.abimethod
|
|
304
|
+
def read_from_blob(self, offset: UInt64, length: UInt64) -> Bytes:
|
|
305
|
+
return self.blob.extract(offset, length)
|
|
306
|
+
|
|
307
|
+
@arc4.abimethod
|
|
308
|
+
def delete_blob(self) -> bool:
|
|
309
|
+
return self.blob.delete()
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Minimum Balance Requirement (MBR)
|
|
313
|
+
|
|
314
|
+
### MBR Formulas
|
|
315
|
+
|
|
316
|
+
**Global State:**
|
|
317
|
+
```
|
|
318
|
+
100,000 + (28,500 * NumUint) + (50,000 * NumByteSlice) microAlgos
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Local State (per opt-in):**
|
|
322
|
+
```
|
|
323
|
+
100,000 + (28,500 * NumUint) + (50,000 * NumByteSlice) microAlgos
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**Box Storage:**
|
|
327
|
+
```
|
|
328
|
+
2,500 + (400 * (key_length + value_length)) microAlgos per box
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Box MBR Examples
|
|
332
|
+
|
|
333
|
+
| Box Name | Size | MBR Cost |
|
|
334
|
+
|----------|------|----------|
|
|
335
|
+
| "a" (1 byte) | 100 bytes | 2,500 + 400×101 = 42,900 µAlgo |
|
|
336
|
+
| "user" (4 bytes) | 1024 bytes | 2,500 + 400×1028 = 413,700 µAlgo |
|
|
337
|
+
| 32-byte key | 8 bytes | 2,500 + 400×40 = 18,500 µAlgo |
|
|
338
|
+
|
|
339
|
+
### Funding App Account for Boxes (CRITICAL)
|
|
340
|
+
|
|
341
|
+
The app account must be funded BEFORE creating boxes.
|
|
342
|
+
|
|
343
|
+
```python
|
|
344
|
+
# In your deploy script or test setup:
|
|
345
|
+
from algokit_utils import AlgorandClient
|
|
346
|
+
|
|
347
|
+
algorand = AlgorandClient.from_environment()
|
|
348
|
+
deployer = algorand.account.from_environment("DEPLOYER")
|
|
349
|
+
|
|
350
|
+
# Deploy the contract
|
|
351
|
+
factory = algorand.client.get_typed_app_factory(MyAppFactory)
|
|
352
|
+
result = factory.deploy()
|
|
353
|
+
|
|
354
|
+
# Fund app account for box MBR
|
|
355
|
+
if result.operation_performed in ["create", "replace"]:
|
|
356
|
+
algorand.send.payment(
|
|
357
|
+
sender=deployer.address,
|
|
358
|
+
receiver=result.app_client.app_address,
|
|
359
|
+
amount=1_000_000 # 1 Algo for box storage
|
|
360
|
+
)
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Calculating MBR in Contract
|
|
364
|
+
|
|
365
|
+
```python
|
|
366
|
+
from algopy import ARC4Contract, UInt64, arc4
|
|
367
|
+
|
|
368
|
+
class MyContract(ARC4Contract):
|
|
369
|
+
@arc4.abimethod
|
|
370
|
+
def calculate_box_mbr(self, key_length: UInt64, value_length: UInt64) -> UInt64:
|
|
371
|
+
return UInt64(2500) + (key_length + value_length) * UInt64(400)
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
## Choosing Storage Type
|
|
375
|
+
|
|
376
|
+
| Scenario | Recommended | Reason |
|
|
377
|
+
|----------|-------------|--------|
|
|
378
|
+
| App configuration | `GlobalState` | Simple, always available |
|
|
379
|
+
| Per-user data, user pays | `LocalState` | User covers MBR on opt-in |
|
|
380
|
+
| Per-user data, app pays | `BoxMap` | No opt-in needed |
|
|
381
|
+
| Large single value | `Box` | Up to 32KB per box |
|
|
382
|
+
| Large binary blob | `BoxRef` | Manual size control |
|
|
383
|
+
| Dynamic key-value store | `BoxMap` | Flexible key types |
|
|
384
|
+
|
|
385
|
+
## Common Mistakes
|
|
386
|
+
|
|
387
|
+
### Forgetting to Fund App Account for Boxes
|
|
388
|
+
|
|
389
|
+
```python
|
|
390
|
+
# INCORRECT - Box creation fails if app not funded
|
|
391
|
+
@arc4.abimethod
|
|
392
|
+
def create_user(self, account: Account) -> None:
|
|
393
|
+
self.users[account] = UInt64(0) # Fails: insufficient MBR
|
|
394
|
+
|
|
395
|
+
# CORRECT - Fund app account before creating boxes
|
|
396
|
+
# (Done externally via payment transaction)
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Not Checking Box Existence
|
|
400
|
+
|
|
401
|
+
```python
|
|
402
|
+
# INCORRECT - Fails if box doesn't exist
|
|
403
|
+
@arc4.abimethod
|
|
404
|
+
def get_value(self) -> UInt64:
|
|
405
|
+
return self.my_box.value # Error if not set
|
|
406
|
+
|
|
407
|
+
# CORRECT - Check first or use get/maybe
|
|
408
|
+
@arc4.abimethod
|
|
409
|
+
def get_value_safe(self) -> UInt64:
|
|
410
|
+
if self.my_box:
|
|
411
|
+
return self.my_box.value
|
|
412
|
+
return UInt64(0)
|
|
413
|
+
|
|
414
|
+
# OR
|
|
415
|
+
@arc4.abimethod
|
|
416
|
+
def get_value_default(self) -> UInt64:
|
|
417
|
+
return self.my_box.get(default=UInt64(0))
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Not Deleting Boxes to Reclaim MBR
|
|
421
|
+
|
|
422
|
+
```python
|
|
423
|
+
# CORRECT - Delete boxes when no longer needed
|
|
424
|
+
@arc4.abimethod
|
|
425
|
+
def cleanup_user(self, account: Account) -> None:
|
|
426
|
+
if account in self.users:
|
|
427
|
+
del self.users[account] # Reclaims MBR
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### Using LocalState Without Opt-in Check
|
|
431
|
+
|
|
432
|
+
```python
|
|
433
|
+
# INCORRECT - Fails if user not opted in
|
|
434
|
+
@arc4.abimethod
|
|
435
|
+
def get_user_data(self, account: Account) -> UInt64:
|
|
436
|
+
return self.user_data[account] # Error if not opted in
|
|
437
|
+
|
|
438
|
+
# CORRECT - Check opt-in status
|
|
439
|
+
@arc4.abimethod
|
|
440
|
+
def get_user_data_safe(self, account: Account) -> UInt64:
|
|
441
|
+
if account in self.user_data:
|
|
442
|
+
return self.user_data[account]
|
|
443
|
+
return UInt64(0)
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
## References
|
|
447
|
+
|
|
448
|
+
- [Algorand Python Storage Documentation](https://dev.algorand.co/algokit/languages/python/lg-storage/)
|
|
449
|
+
- [Box Storage Concepts](https://dev.algorand.co/concepts/smart-contracts/storage/box/)
|
|
450
|
+
- [Global Storage Concepts](https://dev.algorand.co/concepts/smart-contracts/storage/global/)
|
|
451
|
+
- [Local Storage Concepts](https://dev.algorand.co/concepts/smart-contracts/storage/local/)
|
|
452
|
+
- [Protocol Parameters (MBR)](https://dev.algorand.co/concepts/protocol/protocol-parameters/)
|