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.
Account Setup and Initial State
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
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()));
}
Creating a Basic Storage Account
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.
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;
}
Examining Account State and Permissions
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
).
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);
Granting Token Holding Permission
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.
const tokenAccount = userClient.baseToken;
await userClient.updatePermissions(
tokenAccount,
new KeetaNet.lib.Permissions(['STORAGE_CAN_HOLD']),
undefined,
KeetaNet.lib.Block.AdjustMethod.ADD,
{ account: storageAccount }
);
Verifying Updated Permissions
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.
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,
})));
Complete Code Example
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);
});
Last updated