# Single-Token Storage Account

This example shows how to create a storage account that can only hold one specific type of token. It demonstrates how to set permissions so that the storage account is restricted to receiving and holding only the designated token using the `STORAGE_CAN_HOLD` permission.

{% stepper %}
{% step %}

### Account Setup and Initial State <a href="#step-1-account-setup-and-initial-state" id="step-1-account-setup-and-initial-state"></a>

This establishes the foundation by generating a random seed and creating a single account that will own the storage account. The code checks for existing storage accounts (initially empty) and connects to the Keeta test network through a user client

```typescript
async function main() {
    const seed = KeetaNet.lib.Account.generateRandomSeed({ asString: true });
    console.log("seed =", seed);

    const account = KeetaNet.lib.Account.fromSeed(seed, 0);
    const userClient = KeetaNet.UserClient.fromNetwork("test", account);
    
    console.log("account.publicKey =", account.publicKeyString.toString());    
    console.log("account.storageAccounts[] =", (await userClient.listACLsByPrincipal()).filter(acl => acl.entity.isStorage()));
}
```

{% endstep %}

{% step %}

### Creating a Basic Storage Account <a href="#step-2-creating-a-basic-storage-account" id="step-2-creating-a-basic-storage-account"></a>

This creates a storage account with minimal permissions. The `STORAGE_DEPOSIT` permission allows anyone to deposit tokens into the storage account, but notably missing is `STORAGE_CAN_HOLD`, which means the account cannot actually hold any tokens yet.

```typescript
async function createStorageAccount(userClient: KeetaNet.UserClient) {
    const builder = userClient.initBuilder();
    const pendingStorageAccount = builder.generateIdentifier(KeetaNet.lib.Account.AccountKeyAlgorithm.STORAGE);
    await builder.computeBlocks();
    const storageAccount = pendingStorageAccount.account;

    builder.setInfo({
        name: '', 
        description: '',
        metadata: '',
        defaultPermission: new KeetaNet.lib.Permissions(['STORAGE_DEPOSIT'])
    }, { account: storageAccount });

    await userClient.publishBuilder(builder);
    return storageAccount;
}
```

{% endstep %}

{% step %}

### Examining Account State and Permissions <a href="#step-3-examining-account-state-and-permissions" id="step-3-examining-account-state-and-permissions"></a>

This examines the storage account's Access Control Lists (ACLs) and default permissions. The ACL shows all permission relationships involving the storage account, while the default permissions show what actions are allowed by default (currently only `STORAGE_DEPOSIT`).

```typescript
console.log("storageAccount.acls[] =", (await userClient.listACLsByEntity({ account: storageAccount })).map(acl => ({
    entity: acl.entity.publicKeyString.toString(),
    principal: acl.principal.publicKeyString.toString(),
    target: acl.target.publicKeyString.toString(),
    permissions: acl.permissions.base.flags,
})));

console.log("storageAccount.defaultPermission =", (await userClient.state({ account: storageAccount })).info.defaultPermission?.base.flags);
```

{% endstep %}

{% step %}

### Granting Token Holding Permission <a href="#step-4-granting-token-holding-permission" id="step-4-granting-token-holding-permission"></a>

This grants the storage account permission to hold the base token (Keeta - KTA). The `STORAGE_CAN_HOLD` permission is added specifically for the base token, allowing the storage account to receive and hold KTA tokens. The `ADD` method appends this permission to existing ones.

```typescript
const tokenAccount = userClient.baseToken;

await userClient.updatePermissions(
    tokenAccount,
    new KeetaNet.lib.Permissions(['STORAGE_CAN_HOLD']),
    undefined,
    KeetaNet.lib.Block.AdjustMethod.ADD,
    { account: storageAccount }
);
```

{% endstep %}

{% step %}

### Verifying Updated Permissions <a href="#step-5-verifying-updated-permissions" id="step-5-verifying-updated-permissions"></a>

This checks the updated ACL to confirm the new permission has been added. The storage account now has `STORAGE_CAN_HOLD` permission specifically for the base token, allowing it to receive KTA deposits.

```typescript
console.log("storageAccount.acls[] =", (await userClient.listACLsByEntity({ account: storageAccount })).map(acl => ({
    entity: acl.entity.publicKeyString.toString(),
    principal: acl.principal.publicKeyString.toString(),
    target: acl.target.publicKeyString.toString(),
    permissions: acl.permissions.base.flags,
})));
```

{% endstep %}
{% endstepper %}

## Complete Code Example

```typescript
import * as KeetaNet from "@keetanetwork/keetanet-client";

async function createStorageAccount(userClient: KeetaNet.UserClient) {
    // Initialize the user client builder
    const builder = userClient.initBuilder();

    // Create a new storage account
    const pendingStorageAccount = builder.generateIdentifier(KeetaNet.lib.Account.AccountKeyAlgorithm.STORAGE);

    // Compute the pending storage account
    await builder.computeBlocks();

    // Get the storage account
    const storageAccount = pendingStorageAccount.account;
    console.log("storageAccount.publicKey =", storageAccount.publicKeyString.toString());

    // Setting the storage account default permissions
    builder.setInfo(
        {
            name: '',
            description: '',
            metadata: '',
            defaultPermission: new KeetaNet.lib.Permissions([
                'STORAGE_DEPOSIT', // Allow everyone to deposit into the storage account
            ])
        },
        { account: storageAccount }
    );

    // Publish the builder to create the storage account
    await userClient.publishBuilder(builder);

    return storageAccount;
}

async function main() {
    const seed = KeetaNet.lib.Account.generateRandomSeed({ asString: true });
    console.log("seed =", seed);

    const account = KeetaNet.lib.Account.fromSeed(seed, 0);
    const userClient = KeetaNet.UserClient.fromNetwork("test", account);
    
    console.log("account.publicKey =", account.publicKeyString.toString());    
    console.log("account.storageAccounts[] =", (await userClient.listACLsByPrincipal()).filter(acl => acl.entity.isStorage()));

    /**
     * Create a new storage account with default permissions allowing deposits.
     * 
     * This will allow anyone to deposit into the storage account, but won't
     * allow the storage account to hold any tokens.
     */
    const storageAccount = await createStorageAccount(userClient);
    console.log("storageAccount.publicKey =", storageAccount.publicKeyString.toString());

    console.log("storageAccount.acls[] =", (await userClient.listACLsByEntity({ account: storageAccount })).map(acl => ({
        entity: acl.entity.publicKeyString.toString(),
        principal: acl.principal.publicKeyString.toString(),
        target: acl.target.publicKeyString.toString(),
        permissions: acl.permissions.base.flags,
    })));

    console.log("");
    
    console.log("storageAccount.defaultPermission =", (await userClient.state({ account: storageAccount })).info.defaultPermission?.base.flags);
    console.log("");

    // Keeta (KTA) base token account
    const tokenAccount = userClient.baseToken;

    /**
     * Add permission to allow the storage account to hold the base token (Keeta - KTA).
     */
    await userClient.updatePermissions(
        tokenAccount,
        new KeetaNet.lib.Permissions(['STORAGE_CAN_HOLD']),
        undefined,
        KeetaNet.lib.Block.AdjustMethod.ADD,
        { account: storageAccount }
    )

    console.log("storageAccount.acls[] =", (await userClient.listACLsByEntity({ account: storageAccount })).map(acl => ({
        entity: acl.entity.publicKeyString.toString(),
        principal: acl.principal.publicKeyString.toString(),
        target: acl.target.publicKeyString.toString(),
        permissions: acl.permissions.base.flags,
    })));

    /**
     * If you try to send tokens that are not the base token (KTA) to the storage account,
     * it will fail with an error:
     * "XX does not have required permissions to perform action on YY/undefined -- needs [STORAGE_CAN_HOLD, ACCESS]/[]"
     * 
     * But if you try to send the base token (KTA) to the storage account, it will succeed.
     */
}


main().then(() => {
    console.log("Done");
    process.exit(0);
}).catch((err) => {
    console.error("Error:", err);
    process.exit(1);
});
```
