Create a Storage Account

This example demonstrates how to create tokens, establish shared storage accounts, and manage complex permission scenarios in Keeta. Building on the basic storage account concepts, this code shows a complete workflow involving token creation, liquidity provisioning, and delegated sending capabilities.

Step by Step

1

Initial Setup and Account Generation

Establish the foundation by generating a random seed and create three accounts: a liquidity provider (index 0), accountA (index 1), and accountB (index 2). Each account connects to the Keeta test network.

async function main() {
    const seed = KeetaNet.lib.Account.generateRandomSeed({ asString: true });
    console.log("seed =", seed);
    
    // Create liquidity provider account (token creator)
    const liquidityProviderAccount = KeetaNet.lib.Account.fromSeed(seed, 0);
    const liquidityProviderClient = KeetaNet.UserClient.fromNetwork("test", liquidityProviderAccount);
    
    // Create two user accounts for shared storage
    const accountA = KeetaNet.lib.Account.fromSeed(seed, 1);
    const accountB = KeetaNet.lib.Account.fromSeed(seed, 2);
    const userClientA = KeetaNet.UserClient.fromNetwork("test", accountA);
    const userClientB = KeetaNet.UserClient.fromNetwork("test", accountB);
    
    console.log("accountA.publicKey =", accountA.publicKeyString.toString());
    console.log("accountB.publicKey =", accountB.publicKeyString.toString());
}

2

Token Creation and Minting

This creates a new token account using the TOKEN algorithm, sets public access permissions, mints 10,000 tokens, and publishes the token to the blockchain. The liquidity provider becomes the initial holder of all tokens.

async function createToken(userClient: KeetaNet.UserClient) {
    const builder = userClient.initBuilder();

    // Create a new token account
    const pendingTokenAccount = builder.generateIdentifier(KeetaNet.lib.Account.AccountKeyAlgorithm.TOKEN)
    await builder.computeBlocks();
    const tokenAccount = pendingTokenAccount.account;
    console.log("tokenAccount.publicKey =", tokenAccount.publicKeyString.toString());

    // Set token permissions and metadata
    builder.setInfo({
        name: '',
        description: '',
        metadata: '',
        defaultPermission: new KeetaNet.lib.Permissions(['ACCESS']), // Public token
    }, { account: tokenAccount });

    // Mint 10,000 tokens
    builder.modifyTokenSupply(10_000n, { account: tokenAccount });
    builder.modifyTokenBalance(tokenAccount, 10_000n);

    // Publish to blockchain
    await userClient.publishBuilder(builder);
    console.log("Token account created and minted.\n");
    
    return tokenAccount;
}

// Call the function in main()
console.log("\nCreating liquidity provider account and token account...");
const tokenAccount = await createToken(liquidityProviderClient);
console.log("liquidityProviderClient.balances[] =", await liquidityProviderClient.allBalances());
3

Storage Account Creation with Permissions

This creates a storage account with default permissions allowing token holding and deposits. AccountB receives SEND_ON_BEHALF permission, enabling it to send tokens from the storage account without being the owner.

// Check initial storage accounts (should be empty)
console.log("\nChecking storage accounts before creation:");
console.log("accountA.storageAccounts[] =", (await userClientA.listACLsByPrincipal()).filter(acl => acl.entity.isStorage()));
console.log("accountB.storageAccounts[] =", (await userClientB.listACLsByPrincipal()).filter(acl => acl.entity.isStorage()));

// Create storage account
const builder = userClientA.initBuilder();
const pendingStorageAccount = builder.generateIdentifier(KeetaNet.lib.Account.AccountKeyAlgorithm.STORAGE);
await builder.computeBlocks();
const storageAccount = pendingStorageAccount.account;
console.log("storageAccount.publicKey =", storageAccount.publicKeyString.toString());

// Set default permissions
builder.setInfo({
    name: '',
    description: '',
    metadata: '',
    defaultPermission: new KeetaNet.lib.Permissions([
        'STORAGE_CAN_HOLD', // Allow holding any token
        'STORAGE_DEPOSIT',  // Allow anyone to deposit
    ])
}, { account: storageAccount });

// Grant SEND_ON_BEHALF permission to accountB
builder.updatePermissions(
    accountB,
    new KeetaNet.lib.Permissions(['SEND_ON_BEHALF']),
    undefined,
    undefined,
    { account: storageAccount }
);

// Publish storage account
await userClientA.publishBuilder(builder);
console.log("Storage account created and permissions updated.");
4

Token Distribution

The liquidity provider distributes tokens: 1,000 tokens to the storage account for shared use and 5,000 tokens to accountA for individual use. This creates a realistic shared treasury scenario.

// Check balances before distribution
console.log("\nChecking balances before deposit:");
console.log("accountA.balances[] =", await userClientA.allBalances());
console.log("accountB.balances[] =", await userClientB.allBalances());
console.log("storageAccount.balances[] =", await userClientA.allBalances({ account: storageAccount }));

// Distribute tokens from liquidity provider
console.log("\nDepositing tokens from the liquidity provider...");
const builderSend = await liquidityProviderClient.initBuilder();
builderSend.send(storageAccount, 1_000n, tokenAccount);  // 1,000 to storage
builderSend.send(accountA, 5_000n, tokenAccount);        // 5,000 to accountA
await liquidityProviderClient.publishBuilder(builderSend);

// Check balances after distribution
console.log("\nChecking balances after deposit:");
console.log("accountA.balances[] =", await userClientA.allBalances());
console.log("accountB.balances[] =", await userClientB.allBalances());
console.log("storageAccount.balances[] =", await userClientA.allBalances({ account: storageAccount }));
5

Delegated Operations Using SEND_ON_BEHALF

AccountB uses its SEND_ON_BEHALF permission to send tokens from the storage account: 500 tokens to accountA and 300 tokens to itself. The { account: storageAccount } parameter specifies the source account for delegated operations.

// accountB sends tokens from storageAccount using SEND_ON_BEHALF permission
await userClientB.send(accountA, 500n, tokenAccount, undefined, { account: storageAccount });
await userClientB.send(accountB, 300n, tokenAccount, undefined, { account: storageAccount });

// Check final balances
console.log("\nChecking balances after accountB sends tokens from storageAccount:");
console.log("accountA.balances[] =", await userClientA.allBalances());
console.log("accountB.balances[] =", await userClientB.allBalances());
console.log("storageAccount.balances[] =", await userClientA.allBalances({ account: storageAccount }));

This workflow demonstrates how to create a complete token ecosystem with shared storage and delegated permissions, enabling team treasuries, shared wallets, and controlled token distribution systems.

Full Code Example

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

// Token creation function
async function createToken(userClient: KeetaNet.UserClient) {
    const builder = userClient.initBuilder();

    // Create a new token account
    const pendingTokenAccount = builder.generateIdentifier(KeetaNet.lib.Account.AccountKeyAlgorithm.TOKEN)
    await builder.computeBlocks();
    const tokenAccount = pendingTokenAccount.account;
    console.log("tokenAccount.publicKey =", tokenAccount.publicKeyString.toString());

    // Setting the token account default permissions
    builder.setInfo(
        {
            name: '',
            description: '',
            metadata: '',
            defaultPermission: new KeetaNet.lib.Permissions([
                'ACCESS', // Public token
            ]),
        },
        { account: tokenAccount },
    )

    // Minting the token
    builder.modifyTokenSupply(10_000n, { account: tokenAccount });
    builder.modifyTokenBalance(tokenAccount, 10_000n)

    // Publish the blocks
    await userClient.publishBuilder(builder);
    console.log("Token account created and minted.\n");

    return tokenAccount;
}

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

    /**
     * Creating liquidity provider account and token account
     */
    console.log("\nCreating liquidity provider account and token account...");
    const liquidityProviderAccount = KeetaNet.lib.Account.fromSeed(seed, 0);
    const liquidityProviderClient = KeetaNet.UserClient.fromNetwork("test", liquidityProviderAccount);
    const tokenAccount = await createToken(liquidityProviderClient);
    console.log("liquidityProviderClient.balances[] =", await liquidityProviderClient.allBalances());

    /**
     * Creating two user accounts (accountA and accountB)
     * to demonstrate shared storage account creation.
     */
    const accountA = KeetaNet.lib.Account.fromSeed(seed, 1);
    const accountB = KeetaNet.lib.Account.fromSeed(seed, 2);
    const userClientA = KeetaNet.UserClient.fromNetwork("test", accountA);
    const userClientB = KeetaNet.UserClient.fromNetwork("test", accountB);

    console.log("\nGetting accounts:");
    console.log("accountA.publicKey =", accountA.publicKeyString.toString());
    console.log("accountB.publicKey =", accountB.publicKeyString.toString());

    /**
     * Checking owned storage accounts
     */
    console.log("\nChecking storage accounts before creation:");
    console.log("accountA.storageAccounts[] =", (await userClientA.listACLsByPrincipal()).filter(acl => acl.entity.isStorage()));
    console.log("accountB.storageAccounts[] =", (await userClientB.listACLsByPrincipal()).filter(acl => acl.entity.isStorage()));

    /**
     * Creating a storage account
     */
    // Initialize the user client builder
    const builder = userClientA.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_CAN_HOLD', // Allow the storage account to hold any token
            'STORAGE_DEPOSIT', // Allow everyone to deposit into the storage account
        ])
    }, { account: storageAccount });

    // Until here, only `accountA` has access to the storageAccount, his permission is "OWNER".

    /**
     * Adding permission for `accountB` on the `storageAccount`
     * 
     * Here we can set "ADMIN" or "SEND_ON_BEHALF" permissions for `accountB`.
     * "ADMIN" would allow `accountB` to manage the storage account, while
     * "SEND_ON_BEHALF" would allow `accountB` to send tokens from the storage account
     */
    builder.updatePermissions(
        accountB,
        new KeetaNet.lib.Permissions(['SEND_ON_BEHALF']), 
        undefined,
        undefined,
        { account: storageAccount }
    );

    // Publish the blocks
    await userClientA.publishBuilder(builder);
    console.log("Storage account created and permissions updated.");
    
    /**
     * Checking owned storage accounts
     */
    console.log("\nChecking storage accounts after creation:");
    console.log("accountA.storageAccounts[] =", (await userClientA.listACLsByPrincipal()).filter(acl => acl.entity.isStorage()).map(acl => acl.entity.publicKeyString.toString()));
    console.log("accountB.storageAccounts[] =", (await userClientB.listACLsByPrincipal()).filter(acl => acl.entity.isStorage()).map(acl => acl.entity.publicKeyString.toString()));
    
    /**
     * Checking balances before deposit
     */
    console.log("\nChecking balances before deposit:");
    console.log("accountA.balances[] =", await userClientA.allBalances());
    console.log("accountB.balances[] =", await userClientB.allBalances());
    console.log("accountA.storageAccount.balances[] =", await userClientA.allBalances({ account: storageAccount }));
    console.log("accountB.storageAccount.balances[] =", await userClientB.allBalances({ account: storageAccount }));
    
    /**
     * Depositing tokens from the liquidity provider
     * LP -> SEND 1_000 -> storageAccount
     * LP -> SEND 5_000 -> accountA
     */
    console.log("\nDepositing tokens from the liquidity provider...");
    const builderSend = await liquidityProviderClient.initBuilder();
    builderSend.send(storageAccount, 1_000n, tokenAccount);
    builderSend.send(accountA, 5_000n, tokenAccount);
    await liquidityProviderClient.publishBuilder(builderSend);

    /**
     * Checking balances after deposit
     */
    console.log("\nChecking balances after deposit:");
    console.log("accountA.balances[] =", await userClientA.allBalances());
    console.log("accountB.balances[] =", await userClientB.allBalances());
    console.log("accountA.storageAccount.balances[] =", await userClientA.allBalances({ account: storageAccount }));
    console.log("accountB.storageAccount.balances[] =", await userClientB.allBalances({ account: storageAccount }));

    /**
     * Depositing tokens from the storage account
     * accountB using storageAccount -> SEND 500 -> accountA
     * accountB using storageAccount -> SEND 300 -> accountB
     */
    await userClientB.send(accountA, 500n, tokenAccount, undefined, { account: storageAccount });
    await userClientB.send(accountB, 300n, tokenAccount, undefined, { account: storageAccount });
    console.log("\nChecking balances after accountB sends 500 tokens from storageAccount to accountA:");
    console.log("accountA.balances[] =", await userClientA.allBalances());
    console.log("accountB.balances[] =", await userClientB.allBalances());
    console.log("accountA.storageAccount.balances[] =", await userClientA.allBalances({ account: storageAccount }));
    console.log("accountB.storageAccount.balances[] =", await userClientB.allBalances({ account: storageAccount }));
}

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

Last updated