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
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());
}
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());
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.");
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 }));
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