Skip to content
Back to skills

601 Storage 1bfad015

ASecurity

| Storage | Scope | Who Pays MBR | Use Case | |---------|-------|--------------|----------| | `GlobalState` | App-wide | App account | Shared app data | | `LocalState` | Per-user | User (on opt-in) | Per-user data with opt-in | | `Box` | App-wide | App account | Large data, single key | | `BoxMap` | Per-key | App account | Per-user data without opt-in |

  • 9 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added June 1, 2026
businesstypescriptgotesting

Works with

  • cli

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned June 1, 2026

npx -y skills add tools-only/X-Skills --skill 601-storage_1bfad015 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 601 Storage 1bfad015?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 601 Storage 1bfad015
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-601-storage-1bfad015/badge)](https://www.skillsdirectory.com/skills/tools-only-601-storage-1bfad015)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
# Storage Patterns

## Storage Types Overview

| Storage | Scope | Who Pays MBR | Use Case |
|---------|-------|--------------|----------|
| `GlobalState` | App-wide | App account | Shared app data |
| `LocalState` | Per-user | User (on opt-in) | Per-user data with opt-in |
| `Box` | App-wide | App account | Large data, single key |
| `BoxMap` | Per-key | App account | Per-user data without opt-in |

## Storage Access Patterns

### GlobalState

```typescript
import { GlobalState, clone } from '@algorandfoundation/algorand-typescript'

export class MyContract extends Contract {
  appState = GlobalState<MyData>({ key: 'state' })

  public updateState(amount: uint64): void {
    const state = clone(this.appState.value)
    const updated = clone(state)
    updated.counter = updated.counter + amount
    this.appState.value = clone(updated)
  }
}
```

### LocalState (Requires Opt-in)

```typescript
import { LocalState, Txn, clone } from '@algorandfoundation/algorand-typescript'

export class MyContract extends Contract {
  userData = LocalState<UserData>({ key: 'user' })

  public optInToApplication(): void {
    const initial: UserData = { balance: Uint64(0) }
    this.userData(Txn.sender).value = clone(initial)
  }

  public getBalance(): uint64 {
    return clone(this.userData(Txn.sender).value).balance
  }
}
```

### BoxMap (No Opt-in Required)

```typescript
import { BoxMap, Account, clone } from '@algorandfoundation/algorand-typescript'

export class MyContract extends Contract {
  userBoxes = BoxMap<Account, UserData>({ keyPrefix: 'u' })

  public createUser(user: Account): void {
    const initial: UserData = { balance: Uint64(0) }
    this.userBoxes(user).value = clone(initial)
  }
}
```

## Box Storage MBR (CRITICAL)

Box storage increases the app account's Minimum Balance Requirement (MBR). The app account must be funded BEFORE boxes can be created.

### MBR Formula

```
(2500 per box) + (400 * (box size + key size)) microAlgos per box
```

### Funding Pattern in deploy-config.ts

```typescript
import { AlgorandClient } from '@algorandfoundation/algokit-utils'
import { MyAppFactory } from '../artifacts/my_app/MyAppClient'

export async function deploy() {
  const algorand = AlgorandClient.fromEnvironment()
  const deployer = await algorand.account.fromEnvironment('DEPLOYER')

  const factory = algorand.client.getTypedAppFactory(MyAppFactory, {
    defaultSender: deployer.addr,
  })

  const { appClient, result } = await factory.deploy({
    onUpdate: 'append',
    onSchemaBreak: 'append'
  })

  // MANDATORY: Fund app account if BoxMap is used
  if (['create', 'replace'].includes(result.operationPerformed)) {
    await algorand.send.payment({
      amount: (1).algo(),  // Fund app account for box MBR
      sender: deployer.addr,
      receiver: appClient.appAddress,
    })
  }
}
```

**Note**: `operationPerformed` values are `'create'`, `'replace'`, `'update'`, or `'append'`. Only fund on `'create'` or `'replace'` to avoid redundant funding.

### Testing with BoxMap

E2E tests using BoxMap require app account funding before first box operation:

```typescript
// Fund immediately after deployment in test setup
const { client } = await deploy(testAccount)

await localnet.algorand.send.payment({
  amount: (1).algo(),
  sender: testAccount,
  receiver: client.appAddress,
})

// Now box operations will work
await client.send.createUser({ args: [userAccount] })
```

## Choosing Storage Type

| Scenario | Recommended | Reason |
|----------|-------------|--------|
| Per-user data, user pays | `LocalState` | User covers MBR on opt-in |
| Per-user data, app pays | `BoxMap` | No opt-in needed, app funds MBR |
| Shared app config | `GlobalState` | Simple, always available |
| Large data (>128 bytes) | `Box` or `BoxMap` | No size limits like GlobalState |

## Class Properties

Cannot define class properties or constants. Only storage proxies allowed on contract classes:

```typescript
// CORRECT: Module-level constants
const MAX_ITEMS: uint64 = Uint64(100)

class MyContract extends Contract {
  items = GlobalState<ItemList>({ key: 'items' })  // OK: storage proxy

  public addItem(): void {
    // Use MAX_ITEMS here
  }
}

// INCORRECT: Class properties
class MyContract extends Contract {
  private readonly MAX_ITEMS: uint64 = Uint64(100)  // Compiler error
}
```

Files in this skill

  • README.md1.1 KB
  • skill.md4.2 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.