# Introduction

Keeta Network is a high-performance layer-1 blockchain network designed to serve as a common-ground for all asset transfers. Cross-chain transactions can be completed seamless, providing a direct transfer between any assets from any network, instantly. Systems can easily connect to Keeta Network, allowing their assets to interact with the rest of the assets in Keeta's ecosystem.

In addition to interoperability, Keeta has also introduced unprecedented performance and utility. Settlement times of 400 milliseconds and a throughput of up to 10 million transactions per second place Keeta as the front-runner in efficiency. This performance and innovation, in addition to Keeta's native tokenization and built-in compliance protocols, makes Keeta Network the ideal centerpiece for the digital asset ecosystem.

<table data-view="cards"><thead><tr><th></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Start Developing</strong></td><td><a href="/files/UWmbtZHKrd8NQuDNSZTi">/files/UWmbtZHKrd8NQuDNSZTi</a></td><td><a href="/pages/tA3I8Z6ZvcT22FUDFWNY">/pages/tA3I8Z6ZvcT22FUDFWNY</a></td></tr><tr><td><strong>Create your first account</strong></td><td><a href="/files/9VTRBwKIodELDT2nSkJP">/files/9VTRBwKIodELDT2nSkJP</a></td><td><a href="/pages/UJRiqjk7iay5fs1BAXSi">/pages/UJRiqjk7iay5fs1BAXSi</a></td></tr><tr><td><strong>Send a transaction</strong></td><td><a href="/files/qB78aEdHITclKQ0g5rkL">/files/qB78aEdHITclKQ0g5rkL</a></td><td><a href="/pages/1n1A06zg1iLkDT0hdxP6">/pages/1n1A06zg1iLkDT0hdxP6</a></td></tr></tbody></table>

<figure><img src="/files/6uqc3VRpZQcrOLFPoIfg" alt=""><figcaption><p>Keeta Network connecting a variety of networks to create an interconnected digital ecosystem.</p></figcaption></figure>


# Start Developing

To interact with the Keeta Network, we’ve built the **KeetaNet SDK** — a JavaScript and TypeScript-compatible library that works in multiple environments, including **Node.js**, modern **web browsers**, and other JavaScript runtimes.

Whether you're building server-side applications or browser-based dApps, the SDK gives you everything you need to connect to the Keeta Network, manage accounts, send transactions, and more.

## Installation and Type Support

The SDK includes full TypeScript definitions, so you’ll get autocomplete and inline type checking in modern editors. You can install it from npm:

```bash
npm install @keetanetwork/keetanet-client
```

Type definitions are bundled with the package — no need to install anything extra.

## Using the SDK in NodeJS

In Node.js, you can import the KeetaNet SDK like this:

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

Once imported, the SDK exposes several modules and utilities. The most important starting point is the `UserClient`, which lets you connect to the network and perform operations like sending tokens or fetching blockchain data.

```typescript
const signer = KeetaNet.lib.Account.fromSeed(mySeed, 0);
const client = KeetaNet.UserClient.fromNetwork('test', signer);
```

## Using the SDK in the Browser

You can also use the KeetaNet SDK directly in a browser using a script tag. Here's a basic example to get started:

```html
<html>
  <head>
    <script src="https://static.test.keeta.com/keetanet-browser.js"></script>
  </head>
  <body>
    <script>
      // Generate a random seed
      const seed = KeetaNet.lib.Account.generateRandomSeed({ asString: true });

      // Create an account from the seed
      const account = KeetaNet.lib.Account.fromSeed(seed, 0);

      // Connect to the test network
      const client = KeetaNet.UserClient.fromNetwork('test', account);

      // Fetch and log some chain info
      client.chain().then(console.debug);
    </script>
  </body>
</html>

```

This is a good way to get started quickly without any build tools. Just keep in mind: **don’t store or expose real private keys or seeds in client-side code** in production apps.


# Create Your First Account

To start interacting with KeetaNet, you’ll first need to create an account. Accounts are based on cryptographic key pairs, and they’re derived from a secure seed.

This page shows you how to generate a seed, turn it into an account, and connect that account to the Keeta test network.

{% hint style="info" %}
Want to learn more about what an account is and how it works? Read: [What is an Account →](/components/accounts)
{% endhint %}

## Generate a Seed

The KeetaNet SDK includes utilities for generating secure random seeds. These seeds are used to create key pairs — think of them as the starting point for your identity on the network.

{% hint style="warning" %}
Your seed is sensitive information. Anyone with access to your seed can access your account.
{% endhint %}

Here’s how to generate one and use it:

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

async function main() {
  // Generate a secure random seed
  const seed = KeetaNet.lib.Account.generateRandomSeed({ asString: true });
  console.log("Generated seed:", seed);

  // Create an account using the generated seed
  const account = KeetaNet.lib.Account.fromSeed(seed, 0);

  // Connect to the Keeta test network with this account
  const userClient = KeetaNet.UserClient.fromNetwork("test", account);
  console.log("Public key:", account.publicKeyString.toString());
}

main().catch(console.error);
```

#### How it works

* `generateRandomSeed()` creates a strong, random seed.
* `fromSeed(seed, 0)` derives your first account (index `0`) from that seed.
* `UserClient.fromNetwork("test", account)` connects you to the testnet using your new account.
* You can use this client to send transactions, fetch chain data, or interact with the ledger.

## Receive a token

If someone wants to send tokens to your Keeta account, they’ll need your **public address**.

#### Option 1: Public Address from a Newly Created Account

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

// Generate a secure random seed
const seed = KeetaNet.lib.Account.generateRandomSeed({ asString: true });

// Create a new account from the seed (index 0)
const account = KeetaNet.lib.Account.fromSeed(seed, 0);

// Get the public address
const publicKey = account.publicKeyString.toString();
console.log("Your public Keeta address:", publicKey);
```

#### Option 2: Public Address from an Existing Account

If you already have an account, you can share your `publicKey` to receive funds. This is the address others will send tokens to.

```typescript
const existingSeed = "your existing seed string...";
const account = KeetaNet.lib.Account.fromSeed(existingSeed, 0);
const publicKey = account.publicKeyString.toString();
```

## Generate a QR code

You can turn the public address into a QR code so others can scan and send easily. This works great on mobile wallets or in-person payments.

To generate a QR code image:

```typescript
import { toDataURL } from "qrcode";

const qrCode = await toDataURL(accountPublicKey);
```

This gives you a **base64 image URL** (like `data:image/png;base64,...`) that you can embed in an `<img>` tag:

```typescript
<img src={qrCode} />
```

{% hint style="success" %}
You don’t need a specific library — any QR code tool that supports text-to-QR conversion will work. Just pass in the address string, and you're good to go.
{% endhint %}

Now you are ready to start sending your first transaction:

<table data-view="cards"><thead><tr><th></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Send a Transaction</strong></td><td><a href="/files/qB78aEdHITclKQ0g5rkL">/files/qB78aEdHITclKQ0g5rkL</a></td><td><a href="/pages/1n1A06zg1iLkDT0hdxP6">/pages/1n1A06zg1iLkDT0hdxP6</a></td></tr></tbody></table>


# Send a Transaction

Transactions are made up of one or more [operations](/components/blocks/operations). Operations describe the specific actions your account wants to perform on the ledger — for example, sending tokens, setting permissions, or interacting with storage.

Each operation is made up of **effects**, which are the actual changes applied to the [ledger](/components/ledger).

Take the `send` operation, for example:

* It **decreases** the balance of the sender
* It **increases** the balance of the recipient
* It **validates** that the sender's balance doesn't drop below zero

When you build a transaction, you're queuing up one or more of these operations to execute together. The KeetaNet SDK gives you tools to construct, compute, and publish these operations easily.

{% hint style="info" %}
You can think of a transaction as a container for operations, and operations as instructions to update the blockchain state.
{% endhint %}

For a full list of available operations, check out the our [static documentation section](https://static.test.keeta.com/docs/classes/KeetaNetSDK.Referenced.BlockOperation.html).

## Code Example: Sending 1 KTA

{% hint style="info" %}

#### When running this code, make sure that the account has at least 1 KTA in the account

{% endhint %}

```typescript
/**
 * Load the KeetaNet Client SDK
 */
const KeetaNet = require('@keetanetwork/keetanet-client');

/**
 * This is the fake seed for the demo account, replace with a valid one
 */
const DEMO_ACCOUNT_SEED = 'D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0';

async function main() {
	// Create a signer account from the demo seed (account index 0)
	const signer_account = KeetaNet.lib.Account.fromSeed(DEMO_ACCOUNT_SEED, 0);
	console.log('🔑 Signer account:', signer_account.publicKeyString.get());

	// Connect to the Keeta test network
	const client = KeetaNet.UserClient.fromNetwork('test', signer_account);

	// Start building a transaction
	const builder = client.initBuilder();

	// Define the recipient (the faucet address for testing)
	const faucet_account = KeetaNet.lib.Account.fromPublicKeyString(
		'keeta_aabszsbrqppriqddrkptq5awubshpq3cgsoi4rc624xm6phdt74vo5w7wipwtmi'
	);

	// Add a send operation to the builder: 1 KTA to the faucet address
	builder.send(faucet_account, 1n, client.baseToken);

	// Compute the transaction blocks (Keeta breaks operations into blocks)
	const computed = await client.computeBuilderBlocks(builder);
	console.log('🧱 Computed blocks:', computed.blocks);

	// Publish the transaction to the network
	const transaction = await client.publishBuilder(builder);
	console.log('✅ Transaction published:', transaction);
}

main().then(
	() => process.exit(0),
	(err) => {
		console.error('❌ Error:', err);
		process.exit(1);
	}
);

```

#### What's Happening Here?

* You **create a signer** using the account derived from your seed.
* You connect to the **Keeta testnet** using `UserClient`.
* You initialize a **transaction builder**, which lets you queue operations.
* You queue a `send` operation with 1 KTA as the amount.
* You compute the transaction blocks (how Keeta structures data before publishing).
* You publish the transaction to the network.


# Data Structure

Keeta's approach to blockchain technology centers around its use of a Directed Acyclic Graph (DAG) structure, representing a departure from traditional blockchain architectures.

In this system, transactions are linked in a multi-dimensional web rather than a single, linear chain, allowing for parallel processing that increases the network's throughput and scalability.

Unlike traditional blockchains, which struggle with scalability issues due to their linear nature and sequential transaction processing, Keeta's DAG system can handle a high volume of transactions simultaneously. This parallel processing eliminates the bottlenecks that plague traditional systems as network activity increases, enabling Keeta Network to scale efficiently as it grows.

<figure><img src="/files/2YBXQQe6CD7z1RDcgdvU" alt=""><figcaption><p>Keeta Network processes transactions in parallel, improving performance.</p></figcaption></figure>


# Consensus

At its core, the Keeta Network utilizes a Delegated Proof of Stake (dPoS) voting protocol, which allows for quick consensus while ensuring decentralization. Keeta's block validation process follows a structured five-step sequence that ensures security and efficiency.&#x20;

The process begins when a client initiates a vote on a new block(s), sending their request to multiple network representatives. These representatives respond with temporary votes, providing initial validation of the proposed blocks. The client then requests verification of all temporary votes, creating a cross-validation layer. Following this, representatives submit their permanent votes, confirming their final decision on block validity. In the final step, the client broadcasts both the validated blocks and their associated votes to the network, where they are permanently added to the blockchain.

<figure><img src="/files/FBSYG6UfG0vV0DNQl9aD" alt=""><figcaption><p>Each transaction must receive a sufficient number of votes before being broadcasted to the network.</p></figcaption></figure>


# Voting Power

Representatives' voting power in [dPoS](/architecture/consensus) is determined by the amount of Keeta tokens delegated to them. When token holders delegate their Keeta to a representative, they increase that representative's voting power in the consensus process.

Representatives with more delegated tokens have stronger voting power when deciding whether to add new blocks to the chain. This system ensures that those trusted with more of the network's tokens have greater influence in validation decisions, while still allowing smaller token holders to participate in governance by choosing which representatives to support through delegation. The mechanism creates a balance between network security and decentralized participation.

<figure><img src="/files/IgQMNcOV1GKclFBO6pmt" alt=""><figcaption><p>dPoS allows all participants to contribute to the voting process without requiring them to be representatives, and allows trustworthy parties to become representatives without personally holding substantial weight.</p></figcaption></figure>


# Votes

A vote is used by representatives as a means to communicate their intent to add a set of blocks to their ledger. Depending on the time-frame that an issued vote is valid for, it is either considered a temporary or permanent vote.

Both temporary and permanent votes on Keeta Network are encoded as X.509 certificates. X.509 was chosen because it is a widely-used and well-known standard, and contains a flexible data structure using ASN.1.

Each vote contains the following:

**An Issuer:** The [account](/components/accounts) that signed the vote

**Serial:** An arbitrary integer defined by the issuer which is generally incrementing. Two separate votes with the same issuer + serial will not be accepted by the network

**Blocks:** A set of hashes of [blocks](/components/blocks) that the issuer is vouching validity for

**Starting Time:** The timestamp when the vote was issued

**Ending/Expiry Time:** The timestamp of when the vote expires and should not be considered in quorum anymore

**Signature:** The issuers signature on the data included, proving that they were the one to issue the vote

A vote is considered valid to a representative in the [voting process](/architecture/consensus) if it is not expired, the serial has not been seen before, and the set of blocks is valid.


# Vote Stapling

A vote staple is the unit of distribution, creating a permanent voting context by combining multiple [blocks](/components/blocks) to all be voted on and published to the network at once. Nodes transmit vote staples over the network and clients publish vote staples. If one block in the staple is invalid, all blocks are rejected and must be sent back through the voting process without the invalid operation(s).

Keeping the blocks and [votes](/architecture/consensus/votes) together increases efficiency and relieves the network from locating multiple pieces of data on the same operation. A staple is constructed from at least one block, and at least one vote. All votes within a single staple must be for the same blocks in the same order as the staple was constructed with. Additionally, a staple cannot contain mixed temporary and permanent votes.

To maximize efficiency, the network uses compressed staples. The more similar the included blocks and votes contents are, the more the staple can be compressed.


# Ledger

The ledger records everything on the network, including [user identities](/features/identity-profiles), [account](/components/accounts) balances, operation history, and [votes](/architecture/consensus/votes). All operations are added to the ledger once they are confirmed by the necessary representatives through the consensus mechanism and added to the account’s blockchain. The ledger contains the effects of every operation that has been made since the first operation on the network and can be referred to by the [nodes](/components/nodes) for record-keeping.

The Keeta Network ledger maintains the state of the system and contains an up-to-date state for the following kinds of data:

**Voting Power:** As a result of balances being delegated to a representative using the “Set Representative” operation, that representative gains [voting power](/architecture/consensus/voting-power). Currently, voting power is the sum of all balances of the base token of the accounts which have delegated to that particular representative.

**Balances:** Within the ledger, balances are maintained on a per-token basis on each account and are maintained as an arbitrary large big integer.

**Tokens:** Tokens may be created with the “Create Identifier” operation and are stored within the Keeta Network ledger. Each token has the supply (total number of minted tokens) and outstanding balance of that token (tokens which have been issued to accounts) maintained as state within the ledger.

**Certificates:** [Certificates](/components/certificates) are a specific type of metadata within the Keeta Network ledger which can be used to identify the user associated with an account. The certificate for an account is validated to share the same public key as the account.

**Metadata:** As a result of the “Set Info” operation, some basic user information may be set on an address for informational purposes.

**Permissions:** Keeta Network has an extensive [permissions](/components/accounts/permissions) system, allowing for fine-grained control over accounts, tokens, and the network.

**Blocks:** In order to maintain ordering, [blocks](/components/blocks) are recorded in the ledger. Blocks contain all of the updates made to the ledger, as well as which accounts are being updated.

**Votes:** Issued by ledgers as a side-effect of the ledger contents, votes are endorsements by a particular representative to insert a given block or set of blocks into their ledger. Votes come in two kinds: permanent and temporary. Only permanent votes end up on the“main” ledger, but a representative must maintain all votes it issues and so unpublished votes (whether permanent or temporary) will be stored in the “side ledger.”

**Side Ledger:** The side ledger is a secondary area where unpublished votes are stored. All temporary votes will be unpublished, but the representative that issued them must keep track of them throughout the lifetime of the vote.

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Get Ledger History</strong></td><td><a href="/files/UWmbtZHKrd8NQuDNSZTi">/files/UWmbtZHKrd8NQuDNSZTi</a></td><td><a href="/pages/66mVFvRJZKC89pIrNIeg">/pages/66mVFvRJZKC89pIrNIeg</a></td></tr><tr><td><strong>Filter Ledger History</strong></td><td><a href="/files/UWmbtZHKrd8NQuDNSZTi">/files/UWmbtZHKrd8NQuDNSZTi</a></td><td><a href="/pages/QhqxZ3kkwd2kuZERoeCr">/pages/QhqxZ3kkwd2kuZERoeCr</a></td></tr></tbody></table>


# Get Ledger History

The ledger history is the record of how the ledger state came to be. It is represented by the set of vote staples which have been applied to the ledger.

This is expressed in two different ways in the KeetaNet SDK

* The [UserClient.history](https://static.test.keeta.com/docs/classes/KeetaNetSDK.UserClient.html#history) method which returns a list of [vote staples](https://static.test.keeta.com/docs/classes/KeetaNetSDK.Referenced.VoteStaple.html) which have affected the given account.
* The [UserClient.chain](https://static.test.keeta.com/docs/classes/KeetaNetSDK.UserClient.html#chain) method which returns a list of [blocks](https://static.test.keeta.com/docs/classes/KeetaNetSDK.Referenced.Block.html) which have been applied for a given account.

These two differ in that the history method returns all vote staples which affected an account, even if they were not issued by the account -- for example if a transfer was made to the account, the history method would return the vote staple which included the transfer, but the chain method would not because it was not issued by the account.

Additionally there is a method to [filter a list of vote staples](https://static.test.keeta.com/docs/classes/KeetaNetSDK.UserClient.html#filterstapleoperations-2) to a list of operations which are relevant to a specific account. This is useful because the list of operations in a vote staple may include changes that are uninteresting from an account perspective.

## History via predefined seed

The script above demonstrates how to connect to the KeetaNet testnet and retrieve the account history using a predefined demo seed.

```typescript
import * as KeetaNet from '@keetanetwork/keetanet-client';
import util from 'node:util';

// Demo seed — Replace with real seed 
const DEMO_SEED = 'D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0';

async function main() {
  // Create an account from the seed (index 0)
  const account = KeetaNet.lib.Account.fromSeed(DEMO_SEED, 0);

  // Connect to KeetaNet testnet using the account
  const client = KeetaNet.UserClient.fromNetwork('test', account);

  // Fetch the account history
  const history = await client.history();

  // Print the account history in readable format
  console.log(
    '📜 Account history:',
    util.inspect(KeetaNet.lib.Utils.Helper.debugPrintableObject(history), {
      depth: 10,
      colors: true
    })
  );
}

main().catch((err) => {
  console.error('❌ Error:', err);
  process.exit(1);
});

```

## History of a public account

To retrieve the transaction history using a public Keeta address, you can use the provided script that demonstrates connecting to the KeetaNet testnet. By substituting a valid KeetaNet public key string, the script fetches the account's transaction history.

```typescript
import * as KeetaNet from '@keetanetwork/keetanet-client';
import util from 'node:util';

async function main() {
  // Replace this with any valid KeetaNet public key string
  const publicKeyString = 'keeta_aabrrk5663nikbzhc3vr24nvwabyseqvlbf4ritnsoca2ujfurk6jqprplnm66y';

  // Create an account object from the public key string
  const account = await KeetaNet.lib.Account.fromPublicKeyString(publicKeyString);

  // Connect to the KeetaNet testnet
  const client = KeetaNet.UserClient.fromNetwork('test', null, { account });

  // Fetch the account's transaction history
  const history = await client.history();

  // Display the history in a readable format
  console.log(
    '📜 Account history for', publicKeyString,
    util.inspect(KeetaNet.lib.Utils.Helper.debugPrintableObject(history), {
      depth: 10,
      colors: true
    })
  );
}

main().catch((err) => {
  console.error('❌ Error:', err);
  process.exit(1);
});
```


# Blocks

Blocks on Keeta Network are the fundamental mechanism by which the [ledger](/components/ledger) is updated. Each block contains an ordered set of operations which are performed by a given [account](/components/accounts). Each block can hold multiple operations from the same account, and blocks can vary in size.

Structure Blocks are encoded in ASN.1 DER, which enables them to be extensible in the future. The blocks’ size is dynamic, so it can be as large as the representatives will accept.


# Create a Block

The block is digitally signed by the signer (if present, otherwise the account) and the signature is included in the block. The block is identified by its [hash](https://static.test.keeta.com/docs/classes/KeetaNetSDK.Referenced.Block.html#hash-1) which is the hash of the block not including the signature.

The KeetaNet SDK provides a block builder method which allows the user to create blocks in an incremental fashion. The block builder is created using the [BlockBuilder](https://static.test.keeta.com/docs/classes/KeetaNetSDK.Referenced.BlockBuilder.html) class, however in most cases a [UserClientBuilder](https://static.test.keeta.com/docs/classes/KeetaNetSDK.Referenced.UserClientBuilder.html) from a [UserClient](https://static.test.keeta.com/docs/classes/KeetaNetSDK.UserClient.html#initbuilder) should be used because it will handle things like getting the correct network and previous block hash.


# Operations

[Operations](https://static.test.keeta.com/docs/classes/KeetaNetSDK.Referenced.BlockOperation.html) describe the actions performed by an account on the ledger. They are fundamentally composed of effects, which are the specific changes or constraints performed on the ledger.

An example operation is a [Send](https://static.test.keeta.com/docs/classes/KeetaNetSDK.Referenced.BlockOperationSEND.html) operation which has the effects of decrementing the balance of the sender, incrementing the balance of the receiver, and validating that the sender's balance does not drop below zero.

The KeetaNet SDK provides [a number of operations](https://static.test.keeta.com/docs/classes/KeetaNetSDK.Referenced.BlockOperation.html) which are used to perform actions on the ledger.

<table data-view="cards"><thead><tr><th></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td><strong>Send</strong></td><td><a href="/pages/WEuAP6wNX8QREvhxRtjA">/pages/WEuAP6wNX8QREvhxRtjA</a></td><td><a href="/files/qB78aEdHITclKQ0g5rkL">/files/qB78aEdHITclKQ0g5rkL</a></td></tr><tr><td><strong>Receive</strong></td><td><a href="/pages/zmI3h4qqt4U1LbsYLSni">/pages/zmI3h4qqt4U1LbsYLSni</a></td><td><a href="/files/OtqggG648t3ZDJaxWMZk">/files/OtqggG648t3ZDJaxWMZk</a></td></tr><tr><td><strong>setInfo</strong></td><td><a href="/pages/E2LTFoLPiSVCqNkU0vQA">/pages/E2LTFoLPiSVCqNkU0vQA</a></td><td><a href="/files/3bS47oGl9oRc3mEJ9DI5">/files/3bS47oGl9oRc3mEJ9DI5</a></td></tr><tr><td><strong>modifyTokenBalance</strong></td><td><a href="/pages/b7rfeTRLfAlrRQV14vZj">/pages/b7rfeTRLfAlrRQV14vZj</a></td><td><a href="/files/iUHldTP5AcdGJly7DL3U">/files/iUHldTP5AcdGJly7DL3U</a></td></tr><tr><td><strong>modifyTokenSupply</strong></td><td><a href="/pages/DVA8ZlEQ0ogFh3n40YEZ">/pages/DVA8ZlEQ0ogFh3n40YEZ</a></td><td><a href="/files/GdKQMFHTfxLSRnAoTbO8">/files/GdKQMFHTfxLSRnAoTbO8</a></td></tr><tr><td><strong>generateIdentifier</strong></td><td><a href="/pages/0MKdD7DP8Mz949ibcmnF">/pages/0MKdD7DP8Mz949ibcmnF</a></td><td><a href="/files/WowOkgXT6HFJ7Y9O6bzR">/files/WowOkgXT6HFJ7Y9O6bzR</a></td></tr><tr><td><strong>updatePermissions</strong></td><td><a href="/pages/pvtUaYEJUvPC8GQ4Cqsr">/pages/pvtUaYEJUvPC8GQ4Cqsr</a></td><td><a href="/files/QoA3VjKvzqxxGh3UGc68">/files/QoA3VjKvzqxxGh3UGc68</a></td></tr></tbody></table>


# Send

The **Send** operation is used to transfer tokens from one account to another on the Keeta Network. It’s the most basic transaction you can perform.

Below is a minimal example that sends `1 KTA` to a target address using the KeetaNet SDK. Let’s walk through the minimal example for sending tokens using KeetaNet.

{% stepper %}
{% step %}

### Load the Keeta SDK and set up your seed

You import the SDK and use a demo seed to generate an account. In real usage, you’d store this seed securely (never hardcoded).

```typescript
const KeetaNet = require('@keetanetwork/keetanet-client');

const DEMO_ACCOUNT_SEED = 'D3M0D3M0...';
```

{% endstep %}

{% step %}

### Create your signer account

This generates an account object from the seed at index `0`. This account will be used to sign the transaction (i.e., it's the "sender").

```typescript
const sender = KeetaNet.lib.Account.fromSeed(DEMO_ACCOUNT_SEED, 0);
```

{% endstep %}

{% step %}

### Connect to the Keeta test network

This initializes a client session connected to the **testnet**, using the sender account to authenticate.

```typescript
const client = KeetaNet.UserClient.fromNetwork('test', sender);
```

{% endstep %}

{% step %}

### Define the recipient

You define who you're sending tokens to. In this example, it's a faucet address, but it could be any valid Keeta account.

```typescript
const recipient = KeetaNet.lib.Account.fromPublicKeyString('keeta_...');
```

{% endstep %}

{% step %}

### Initialize a transaction builder

This creates a **builder**, which is used to queue one or more operations (like `send`, `setRep`, etc.) that will be packaged into a transaction.

```typescript
const builder = client.initBuilder();
```

{% endstep %}

{% step %}

### Add the send operation

```typescript
builder.send(recipient, 1n, client.baseToken);
```

This is the core of the operation. Let’s break it down:

| Argument           | Meaning                                                     |
| ------------------ | ----------------------------------------------------------- |
| `recipient`        | The recipient account object (created earlier)              |
| `1n`               | The amount to send, in tokens (as a `BigInt`). `1n` = 1 KTA |
| `client.baseToken` | The token to send (usually Keeta's native token, KTA)       |

So this line is saying:

> “Send 1 KTA to the specified recipient.”
> {% endstep %}

{% step %}

### (Optional) Compute the blocks

This step lets you preview how the transaction will be constructed before sending it. It’s useful for debugging or simulation.

```typescript
await client.computeBuilderBlocks(builder);
```

{% endstep %}

{% step %}

### Publish the transaction

This sends your built transaction to the Keeta network, where it will be validated and added to the ledger.

```typescript
await client.publishBuilder(builder);
```

{% endstep %}
{% endstepper %}

If all goes well, you’ve just sent 1 KTA to the recipient. You can now build on this with more operations, like minting, delegating, or managing tokens.

## Complete Code Example

```typescript
const KeetaNet = require('@keetanetwork/keetanet-client');

// ⚠️ Demo seed, replace with working seed
const DEMO_ACCOUNT_SEED = 'D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0';

async function main() {
	const sender = KeetaNet.lib.Account.fromSeed(DEMO_ACCOUNT_SEED, 0);
	const client = KeetaNet.UserClient.fromNetwork('test', sender);

	const recipient = KeetaNet.lib.Account.fromPublicKeyString(
		'keeta_aabszsbrqppriqddrkptq5awubshpq3cgsoi4rc624xm6phdt74vo5w7wipwtmi'
	);

	const builder = client.initBuilder();
	builder.send(recipient, 1n, client.baseToken); // send 1 KTA

	await client.computeBuilderBlocks(builder); // optional but recommended
	await client.publishBuilder(builder);       // send it to the network

	console.log('✅ Sent 1 KTA');
}

main().catch(console.error);

```


# Receive

The `receive` operation is used to declare that your account expects to receive a certain token or asset from another account. It’s commonly used in swaps and other multi-party interactions where one party commits to sending something, and the other defines what they want in return.

This operator is added to a transaction using a `builder` and is usually paired with a corresponding `send` operation.

## When to Use

* Declaring part of a **swap** (e.g., “I send X, and expect to receive Y”)
* Defining expected incoming tokens or assets in a contract
* Building conditional transactions where value is exchanged

## How it works

When performing a swap, the `send()` operation defines what your account is giving, and the `receive()` operation defines what you expect in return.

You create a transaction using both operations, then **compute** the block — but **don’t publish it**. The resulting unsigned block is meant to be sent to the counterparty (e.g. via QR, API, or message).

They can then **verify, sign, and publish** the transaction on-chain. Once published with signatures, the swap is finalized.

```typescript
const signer = KeetaNet.lib.Account.fromSeed(DEMO_ACCOUNT_SEED, 0);
const client = KeetaNet.UserClient.fromNetwork('test', signer);

// Addresses
const recipient = KeetaNet.lib.Account.fromPublicKeyString('<recipient-address>');
const sendToken = KeetaNet.lib.Account.fromPublicKeyString('<abc-token-address>');
const receiveToken = KeetaNet.lib.Account.fromPublicKeyString('<xyz-token-address>');

// Amounts (assumes you’ve already fetched decimals and validated)
const sendAmount = Numeric.fromDecimalString("10.0", 2);     // 10 ABC
const receiveAmount = Numeric.fromDecimalString("5.0", 2);   // 5 XYZ

// Create transaction
const builder = client.initBuilder();

builder.send(recipient, sendAmount.valueOf(), sendToken);
builder.receive(recipient, receiveAmount.valueOf(), receiveToken, true);

// Compute the transaction block (not yet published)
const { blocks } = await client.computeBuilderBlocks(builder);

// This unsigned block can now be signed and published by the other party
const unsignedBytes = blocks[0].toBytes();

console.log("📦 Unsigned swap block ready for signature:", unsignedBytes);
```

## Signature Flow:

* You (Party A) compute the transaction with `send()` and `receive()`.
* You send the **unpublished block** to Party B (e.g., over API or QR code).
* Party B verifies it, signs it, and publishes the transaction to KeetaNet.
* Done — the atomic swap is complete!

{% hint style="warning" %}
You **must not publish** the block yourself if you're expecting a counterparty to sign it. The final publish should be done by the party who agrees to the trade — typically the one **receiving the assets**.
{% endhint %}


# setInfo

The `setInfo()` operation allows you to attach **custom metadata** and define **default permissions** for an account, such as a token, identifier, or other asset. It's most commonly used to describe assets (e.g., NFTs or tokens) or set defaults for how other users can interact with them.

This operation is part of the `builder` and is added to a transaction before publishing.

## When to Use

* Naming a token or identifier
* Describing its purpose or contents
* Embedded metadata, could be optionally signed to prove authenticity
* Defining default access permissions

Here's a simplified example used within a transaction builder. You can find a full implementation in the '[Tokenizing Real-World Assets](/guides/tokenizing-real-world-assets)' section of the guide.

```typescript
builder.setInfo({
  name: 'DEMORWA',
  description: 'Demo Token for Real World Asset',
  metadata: metadata_base64, // base64-encoded JSON with asset info + signature
  defaultPermission: new KeetaNet.lib.Permissions(['ACCESS'], [])
}, {
  account: token.account // the account you're attaching this info to
});
```

#### Explanation of Each Field

* **`name`** – A human-readable name for the asset/account (e.g. token symbol).
* **`description`** – A short description or summary of the asset.
* **`metadata`** – A base64-encoded string containing structured data. This can include things like signed JSON that links the on-chain token to a real-world asset. The data can also optionally be encrypted.
* **`defaultPermission`** – A permissions object that controls what other accounts are allowed to do with this entity. E.g. ACCESS permission gives anyone the ability to hold and transfer the asset to others.

## Why Use It?

`setInfo()` is especially useful for:

* Setting up NFTs or tokens that represent real-world assets
* Making assets easily discoverable or identifiable
* Defining behavior for asset holders without custom logic

{% hint style="warning" %}
Keep in mind that metadata is public and immutable once published as part of a transaction.
{% endhint %}


# modifyTokenSupply

The `modifyTokenSupply()` operation allows you to **mint** (increase) or **burn** (decrease) the total supply of a token on KeetaNet. This is a low-level supply management tool that modifies the global token supply stored in a given token account.

This operation is part of the transaction builder and must be **published** to take effect.

## When to Use

* Minting new tokens to increase supply (e.g. when issuing rewards or enabling inflation).
* Burning tokens to reduce supply (e.g. for deflationary models or manual corrections).
* Dynamically adjusting circulating supply in response to governance or economic rules.

## How It Works

{% stepper %}
{% step %}

### Get a Token Account

You need the token account you want to modify. This is usually derived from a known public key.

```typescript
const tokenAccount = Client.lib.Account.fromPublicKeyString(tokenPublicKey)
```

{% endstep %}

{% step %}

### Initialize a Builder

Start a transaction builder using `userClient.initBuilder()`.

```typescript
const builder = userClient.initBuilder()
```

{% endstep %}

{% step %}

### Call `modifyTokenSupply()`

Provide an amount and a token account:

* Positive values **mint** tokens.
* Negative values **burn** tokens.\
  The amount must be an integer or a BigNumber.

```typescript
const builder = userClient.initBuilder()
builder.modifyTokenSupply(1000n, { account: tokenAccount }) // Mint 1000 tokens
await userClient.publishBuilder(builder)

const builder = userClient.initBuilder()
builder.modifyTokenSupply(-500n, { account: tokenAccount }) // Burn 500 tokens
await userClient.publishBuilder(builder)
```

{% endstep %}

{% step %}

### Publish the Builder

Send the transaction to the network using `userClient.publishBuilder()`

```typescript
await userClient.publishBuilder(builder)
```

{% endstep %}
{% endstepper %}

## Full Example

```typescript
// Step 1: Load token account
const tokenAccount = Client.lib.Account.fromPublicKeyString(tokenPublicKey)
if (!tokenAccount.isToken()) {
  throw new Error("Invalid token public key")
}

// Step 2: Start builder
const builder = userClient.initBuilder()

// Step 3a: Burn 100 tokens
builder.modifyTokenSupply(-100n, { account: tokenAccount })
// Step 3b: Or mint 100 tokens
builder.modifyTokenSupply(100n, { account: tokenAccount })

// Step 4: Publish the transaction
await userClient.publishBuilder(builder)

```

## Method

```typescript
modifyTokenSupply(
  amount: BigNumber | number,
  options: {
    account: Account; // The token account whose supply you're modifying
  }
): void
```


# modifyTokenBalance

The `modifyTokenBalance()` function lets you **directly adjust a specific account’s balance for a token**, either by adding to or subtracting from it. This is done **from or to the token's "unallocated balance"**, not another user.

Think of it like minting or burning — but instead of affecting total supply, you're just updating who holds what.

## When to Use

* Mint tokens to an account **(from unallocated token balance)**
* Burn tokens from an account **(to unallocated token balance)**
* Adjust balance after supply changes
* Pre-fill or wipe account balances during setup/testing

## How It Works (Step-by-Step)

{% stepper %}
{% step %}

### Choose a Token

This is the token whose balance is being modified.

```typescript
const tokenAccount = Client.lib.Account.fromPublicKeyString("TOKEN_PUBLIC_KEY")
```

{% endstep %}

{% step %}

### Choose a Target Account

This is the account receiving or sending the tokens: `userAccount`
{% endstep %}

{% step %}

### Determine Amount

* Use a **positive amount** to credit (add tokens).
* Use a **negative amount** to debit (remove tokens).
* You can also **overwrite** the balance using `isSet: true`.

```typescript
// Add 1000 tokens
const amount = 1000n

// OR remove 500 tokens
const amount = -500n

// OR set balance exactly to 0
const amount = 0n
const isSet = true
```

{% endstep %}

{% step %}

### Call `modifyTokenBalance()`

Add the balance operation to the builder.

```typescript
builder.modifyTokenBalance(
  tokenAccount,
  amount,
  isSet ?? false, // optional: true if you want to overwrite balance
  { account: userAccount }
)
```

{% endstep %}

{% step %}

### Publish the Builder

Finalize the changes by sending them to the network.

```typescript
await userClient.publishBuilder(builder)
```

{% endstep %}
{% endstepper %}

## Method

```typescript
modifyTokenBalance(
  token: TokenOrPending,
  amount: bigint,
  isSet?: boolean,
  options?: { account: Account }
): void
```

* `amount`: Can be positive (add) or negative (remove)
* `isSet`: Default is false. If true, sets the exact balance. Otherwise, adds/subtracts from current balance.
* `account`: The target account to modify.


# updatePermissions

The ‎`updatePermissions()` operation lets you change what an account or identifier is allowed to do on the Keeta Network. Use it to grant, restrict, or remove access to specific token-related actions for a given account.

```typescript
userClient.updatePermissions(
  targetAccount,
  new Client.lib.Permissions([
    "ACCESS",
    "ADMIN",
    // ...add or remove permissions as needed
  ]),
  tokenAccount,
  Client.lib.Block.AdjustMethod.SET,      // Optional: SET (default), ADD, or SUBTRACT
  { account: tokenAccount }               // Optional: options
)
```

*targetAccount*: The account to update permissions for.

*permissions*: The new permissions to assign, as a ‎\`Permissions\` object.

*tokenAccount*: The token (public address) these permissions relate to.

*method* (optional): How to update permissions (‎\`SET\`, ‎\`ADD\`, or ‎\`SUBTRACT\`).

*options* (optional): Additional settings (e.g. which account is performing the update).

## When to Use

* Grant new permissions to an account for a specific token.
* Remove or revoke existing permissions from an account.
* Change roles or access after onboarding, role changes, or security reviews.
* Respond to organizational changes or update access control policies.
* Maintain secure and flexible token operations on the network.

## Permission Types

Specify any combination of permission strings as defined in the SDK. For the complete list and descriptions, refer to the [static documentation](https://static.test.keeta.com/docs/enums/KeetaNetSDK.Referenced.BaseFlag.html#permission_delegate_add).

## Requirements

To update permissions, the calling account must have the ‎`ACCESS` right on the token. Additionally, the account must either be an ‎`ADMIN` or have the ‎`PERMISSION_DELEGATE_ADD` permission.

## Code Example: How to Remove ACCESS Permission from an Account

{% stepper %}
{% step %}

### Identify the Target Account and Token

Decide which account should lose access (‎`accountToDenyAccess`) and the token (‎`tokenAccount`) this affects.

```typescript
// Example dummy public keys (replace with real ones in production)
const accountToDenyAccess = "kta_1dummyaccountpublickeyxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const tokenAccount = "kta_1dummytokenpublickeyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy";
```

{% endstep %}

{% step %}

### Prepare the Permissions Object

Create a ‎`Permissions` object with the permission you want to remove.

```typescript
const permissionsToRemove = new Client.lib.Permissions(["ACCESS"]);
```

{% endstep %}

{% step %}

### Call updatePermissions with the SUBSTRACT method

Use the ‎`SUBTRACT` method to remove the permission.&#x20;

```typescript
userClient.updatePermissions(
  accountToDenyAccess,
  permissionsToRemove,
  tokenAccount,
  Client.lib.Block.AdjustMethod.SUBTRACT,
  { account: tokenAccount }
);
```

{% hint style="info" %}
If you wanted to add a permission instead, use the ‎`Client.lib.Block.AdjustMethod.ADD` and to set it `Client.lib.Block.AdjustMethod.SET`.
{% endhint %}
{% endstep %}

{% step %}

### Confirm the Update

Wait for confirmation on the network to ensure the permission has been removed.

{% hint style="warning" %}
The account performing this operation must have ‎`ACCESS` and either ‎`ADMIN` or ‎`PERMISSION_DELEGATE_ADD` rights on the token.
{% endhint %}
{% endstep %}
{% endstepper %}


# generateIdentifier

The ‎`generateIdentifier()` operation creates a new identifier for an account or asset, such as a network, token, or storage entity. This function is typically used when initializing new assets or accounts that require a unique identifier on the Keeta Network.

This operation is part of the ‎\`builder\` and is added to a transaction before publishing.

## When to Use

* Creating a new token, network, or storage account
* Assigning a unique identifier to a new asset
* Initializing accounts before attaching metadata or permissions

## When to Use It?

* Ensuring each asset or account has a unique and valid identifier
* Streamlining the creation of new entities on the network

## Example; Creating a new token account with generateIdentifier and setting info

Here’s a simplified example using the transaction builder. See the ‘[Tokenizing Real-World Assets](/guides/tokenizing-real-world-assets)’ section for a full implementation,

```typescript
// Step 1: Generate a new token identifier with custom options
const pendingAccount = builder.generateIdentifier('TOKEN', {
  owner: user.account,           // Assign the initial owner of the token
  symbol: 'DEMO',                // Token symbol
  decimals: 2,                   // Number of decimal places
  metadata: {
    category: 'Real World Asset',
    createdBy: 'Demo Script'
  }
});

// Step 2: Prepare base64-encoded metadata (for demonstration)
const assetInfo = {
  assetType: 'Real World Asset',
  issuer: 'Demo Organization',
  issuedAt: new Date().toISOString()
};
const metadata_base64 = Buffer.from(JSON.stringify(assetInfo)).toString('base64');

// Step 3: Attach info and default permissions to the new token account
builder.setInfo({
  name: 'DEMORWA',
  description: 'Demo Token for Real World Asset',
  metadata: metadata_base64, // base64-encoded JSON with asset info + signature
  defaultPermission: newKeetaNet.lib.Permissions(['ACCESS'], [])
}, {
  account: pendingAccount.account // The account you're attaching this info to
});

```

* ‎type – The category of identifier to generate. Can be ‎\`NETWORK\`, ‎\`TOKEN\`, or ‎\`STORAGE\`.
* ‎options – (Optional) Additional parameters to customize the identifier generation.
* Returns – A ‎\`PendingAccount\<Algo>\` object representing the newly created identifier.


# Set Representative

Keeta uses a delegated proof-of-stake (dPoS) model where every account can choose a representative to vote on its behalf in network consensus. By assigning a representative, you delegate your voting power — which is based on your token balance — to that representative.

## Step-by-Step: Delegate to a Representative

{% stepper %}
{% step %}

### Convert the representative's address into an Account object

This takes the public address (as a string) and turns it into a proper `Account` object.\
The `assertAccount()` call ensures it’s valid and usable for delegation.

```typescript
const builder = userClient.initBuilder();
```

{% endstep %}

{% step %}

## Initialize a transaction builder

The builder is used to queue operations that will be packaged into a single transaction.

```typescript
builder.setRep(representativeAccount);
```

{% endstep %}

{% step %}

### Publish the transaction to the network

This finalizes the transaction and submits it to the Keeta network. Once confirmed, your tokens will be delegated and the representative will begin voting with your power.

```typescript
await userClient.publishBuilder(builder);
```

{% endstep %}
{% endstepper %}

Your account is now linked to a representative. This means:

* You’re participating in Keeta’s governance and consensus process.
* Your token balance contributes to your chosen representative’s voting power.

## Full Code Example

```typescript
// Convert the public key string into a valid Keeta account
const representativeAccount = Client.lib.Account
  .fromPublicKeyString(data.representativeAddress)
  .assertAccount(); // Ensure it's a proper account type

// Initialize a block builder
const builder = userClient.initBuilder();

// Add a "set representative" operation
builder.setRep(representativeAccount);

// Publish the operation to the network
await userClient.publishBuilder(builder);

```


# Nodes

A node is a piece of software that participates on Keeta Network. Nodes can be representatives that [vote](/architecture/consensus) on the network or participants that are only on the network to view the operations. Nodes that serve as representatives refer to their own copies of the [ledger](/components/ledger) to ensure that new operations are valid. Nodes are a key factor in assuring security and integrity within the network.

Nodes on Keeta Network are hosted in cloud environments like as Google Cloud, allowing them to scale efficiently with the network.&#x20;


# Ledger Pruning

Since some [nodes](/components/nodes) are intended to [vote](/architecture/consensus) on transactions, while others only view the transactions, the parts of the [ledger](/components/ledger) that are necessary to store can vary between nodes. To maximize efficiency within the network, ledger pruning allows the node host to store only the parts of the ledger that they determine are necessary to carry out their intended operations. In addition to increasing network efficiency, pruning saves nodes storage costs. There are four different types of nodes on Keeta Network, each with different pruning abilities.

**Historical Node:** Historical nodes do not participate in ledger pruning and maintain the entire history of the ledger dating back to the network’s genesis. These nodes require the most storage since they maintain large amounts of data. They can be called upon for any historical information on any account, token, or transaction that has ever taken place on the network.

**Current Node:** Current nodes only record and store the most recent blocks for each account. They maintain up-to-date information on the ledger’s most recent activity but not archive all historical data. Representatives must host at least a Current node but can host a Historical node if preferred.

**Service Node:** Service nodes hold data for a subset of [accounts](/components/accounts) relevant to the node's host. These nodes also maintain updated representative network addresses, [voting power](/architecture/consensus/voting-power), and public account identifiers, which reduces the client burden when interacting with the network.&#x20;

**Listen-only Node:** Listen-only nodes don’t record or store account activity—they listen in on network activity in real-time. They can take actions or measurements based on what is happening on the network at any given moment, but they don’t need to maintain storage for that activity since they are not recording any of it.


# Accounts

Accounts on Keeta Network refer to either the public key of a [key-pair](/components/key-pairs) or a deterministically generated account. Every account is given an address, which is a representation of their public key or some other uniquely identifiable information. Each account has a separate ordered blockchain within the [DAG](/architecture/data-structure) to store that account’s blocks. Keeta Network hosts a variety of account types.

**Keyed Accounts:** Keyed accounts are comprised of a private and public key pair. They can digitally sign a block and are generally the only kind of accounts on other blockchains. On Keeta Network, these are the only accounts that can sign [votes](/architecture/consensus/votes) or [blocks](/components/blocks).

**Generated Accounts:** Generated accounts are special-purpose addresses generated deterministically from publicly available data. Unlike keyed accounts, they do not have the ability to sign transactions but serve other specific roles within the network. Generated accounts have an address which is deterministically derived from some fixed input, either from an operation within a block or well-known information such as the network ID. Ownership permissions will automatically be granted to the creator over the generated account. To publish a block to a generated account’s chain, a keyed account with proper permissions must sign a block for the generated account.

**Network Accounts:** Each network has exactly one Network Account. This account is generated from the unique numeric representation of that network. Network accounts are used to assign network-wide permissions. For example, to create a new token on the network, the creator must have that permission assigned to them on the network account. The base token for the network is also generated from this address.

**Storage Accounts:** Storage accounts are a versatile account type which can hold balances and are generally meant to be used as holding accounts for funds. They may be jointly owned or controlled by multiple accounts by setting the appropriate access control list (ACL) entries.

**Token Accounts:** Tokens act as identifiers for different transferrable currencies on the network. Administrators and owners of these accounts have the ability to modify the total supply, modify an entity’s balance of the token, and grant/revoke a specific user’s ability to use the token.


# Permissions

Two types of permissions are used in Keeta Network: “base permissions” and “external permissions.” Base permissions are represented by a symbolic name that corresponds to a specific value. External permissions are not managed by Keeta Network and can hold arbitrary flags (offsets) managed by an external party.

**Encoding:** Both base and external permissions are encoded using a bit-field of offsets to leverage bitwise operations for combining and checking permissions in a compact, fast bit-based expression. Subsequent modifications are likewise very efficient. Within the network, bit-fields will always be represented as one of the following:

* An array of numbers representing the index of true offsets within the bit-field
* The integer output of the bitwise operations
* For the case of base flags, an array of the flag names

**Access Control List:** On Keeta Network, permissions are stored and represented alongside information that describes their use. An access control list (ACL) entry will contain the following fields:

* Principal: The address identifying the actor on the network accessing an entity
* Entity: The network address that is being acted upon by the principal, and the address whose chain in which the ACL modifications occur
* Target: An optional address narrowing the scope of a permission
* Permissions: The list of base and/or external permissions granted, both represented as bitfields.

**Permission Hierarchy:** Permissions on Keeta Network are always read from most to least specific, with the default if none are set being empty. They will always be read with this priority. Permissions do not inherit from level to level. If one is defined it will override the less specific entry.

1. ACL entry that matches the principal + entity + target exactly
2. ACL entry that matches the principal + entity exactly, but does not include a specific target
3. (If the entity is able to) The default permission set by the entity
4. If none of the above are found, the permissions are assumed to be empty.

For example, if a storage address grants the ability for one user “SEND ON BEHALF” with no target, that user will be able to send any token from the storage [account](/components/accounts). If the storage account then creates an ACL entry for the same user with a specific token as a target not including “SEND ON BEHALF" that user will still be able to send any token, just excluding the specific token in which permissions were removed from.

**Base Permissions:** Base permissions each have a symbolic name and use case defined by Keeta Network. Each symbolic name is tied to a specific offset to be used in the bit-field, and a specific use on the network.

**External Permissions:** External permissions are not managed by Keeta Network. They can hold arbitrary flags managed by an external party. External parties can set these to arbitrary values using the same bitfield format as their counterpart. Within the network each offset is not tied to a symbolic name, and such are always only represented as a bitfield. If an external party wants to represent these using symbolic names, there is a client-side method to do so.

**Ownership:** The “OWNER” base permission represents ownership of an address. This is only applicable to generated account identifiers, as the owner of other accounts is the holder of the private key. On identifier creation, the creator is automatically given this permission. After that point, there must always be exactly one address with the “OWNER” flag, if any modifications are made, they must be done within the same vote staple as to not have the end count not equal to one. Some examples where an account created a storage account and is trying to transfer ownership include:

* Invalid Ownership Modification
  * The owner of a storage account signs a block granting a different address the OWNER permission, leaving two owners
  * The owner of a storage account signs a block lowering their own permissions to ADMIN, leaving no owners
  * The owner of a storage account performs both the correct addition and removal, but performs the action in two vote staples, making each one invalid alone.
* Valid Ownership Modification
  * The owner of a storage account signs a block in which the new owner is granted the OWNER permission and the previous owner is re-assigned a bitfield not including OWNER.

**Delegation:** On Keeta Network,  the “PERMISSION DELEGATE ADD” and “PERMISSION DELEGATE REMOVE” flags both represent the ability for the principal to re-assign permissions to other addresses for the entity that these were granted from.  Both of these flags work in a similar way, but each representing a different method (“AdjustMethod”) that is being used in the “MODIFY PERMISSIONS” block operation. A principal with either of these flags is only able to add or remove a subset of the permissions that they have on the same entity, and is not able to re-grant either of the delegation flags unless they are an admin of the entity.


# Block Accounts

The Keeta Network uses an Access Control List (ACL) system where token owners can grant or revoke `ACCESS` permissions for individual accounts.&#x20;

This tutorial will guide you through creating a token, granting access to multiple accounts, blocking one specific account, and verifying that the block worked correctly.

## Prerequisites <a href="#prerequisites" id="prerequisites"></a>

Before you begin, ensure you have [Installed the KeetaNet SDK.](/introduction/start-developing)

{% stepper %}
{% step %}

### Set Up Your Accounts <a href="#step-1-set-up-your-accounts" id="step-1-set-up-your-accounts"></a>

We generate a random seed to create deterministic accounts for our tutorial. The `tokenOwner` account at index 0 will own and control the token throughout this process. `addressA` at index 1 will be the account we block later to demonstrate the blocking functionality. `addressB` at index 2 will keep access throughout the entire process to show selective blocking.&#x20;

Finally, we create a `UserClient` for the token owner which allows us to perform blockchain operations.

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

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

    // Create accounts
    const tokenOwner = KeetaNet.lib.Account.fromSeed(seed, 0);
    const addressA = KeetaNet.lib.Account.fromSeed(seed, 1);
    const addressB = KeetaNet.lib.Account.fromSeed(seed, 2);
    
    const tokenOwnerClient = KeetaNet.UserClient.fromNetwork("test", tokenOwner);

    console.log("Token Owner:", tokenOwner.publicKeyString.toString());
    console.log("Address A:", addressA.publicKeyString.toString()); 
    console.log("Address B:", addressB.publicKeyString.toString());
}
```

{% endstep %}

{% step %}

### Create Your Token <a href="#step-2-create-your-token" id="step-2-create-your-token"></a>

We use the builder pattern to create a new token account on the Keeta Network. The `generateIdentifier()` method creates a new TOKEN-type account with a unique identifier. After calling `computeBlocks()`, we can access the generated token account. The `setInfo()` method defines the token metadata and crucially sets the default `ACCESS` permission, which allows public interaction with the token.&#x20;

We then use `modifyTokenSupply()` to mint 10,000 tokens to the token account. Finally, `publishBuilder()` commits all these changes to the blockchain in a single transaction.

```typescript
console.log("\n=== Creating Token ===");
const builder = tokenOwnerClient.initBuilder();
const pendingToken = builder.generateIdentifier(KeetaNet.lib.Account.AccountKeyAlgorithm.TOKEN);
await builder.computeBlocks();
const tokenAccount = pendingToken.account;

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

builder.modifyTokenSupply(10000n, { account: tokenAccount });
await tokenOwnerClient.publishBuilder(builder);
console.log("✅ Token created with public ACCESS");
```

{% endstep %}

{% step %}

### Grant Access to Specific Addresses <a href="#step-3-grant-access-to-specific-addresses" id="step-3-grant-access-to-specific-addresses"></a>

The `updatePermissions()` method modifies access rights for specific accounts on our token. We use `AdjustMethod.SET` to grant the `ACCESS` permission to each address explicitly.&#x20;

This ensures both addresses have clear, documented access to interact with the token. The `{ account: tokenAccount }` parameter specifies which token we're modifying permissions for.&#x20;

Each call to `updatePermissions()` creates a separate transaction that grants access to the specified address.

```typescript
await tokenOwnerClient.updatePermissions(
    addressA,
    new KeetaNet.lib.Permissions(['ACCESS']),
    tokenAccount,
    KeetaNet.lib.Block.AdjustMethod.SET,
    { account: tokenAccount }
);

await tokenOwnerClient.updatePermissions(
    addressB,
    new KeetaNet.lib.Permissions(['ACCESS']),
    tokenAccount,
    KeetaNet.lib.Block.AdjustMethod.SET,
    { account: tokenAccount }
);
```

{% endstep %}

{% step %}

### Verify Initial Access <a href="#step-4-verify-initial-access" id="step-4-verify-initial-access"></a>

The `listACLsByEntity()` method retrieves all Access Control List entries for our token, showing us every account that has been granted specific permissions. We search through this list to find ACL entries matching each of our test addresses using the `comparePublicKey()` method for secure address comparison.&#x20;

The `permissions.has(['ACCESS'])` method checks whether the ACCESS permission is present in each account's permission set. At this point in the tutorial, both addresses should show `true`, indicating they have access to the token.

```typescript
console.log("\n=== Checking Initial Access ===");
const initialACLs = await tokenOwnerClient.listACLsByEntity({ account: tokenAccount });

const addressA_ACL = initialACLs.find(acl => acl.principal.comparePublicKey(addressA));
const addressB_ACL = initialACLs.find(acl => acl.principal.comparePublicKey(addressB));

const addressA_HasAccess = addressA_ACL && addressA_ACL.permissions.has(['ACCESS']);
const addressB_HasAccess = addressB_ACL && addressB_ACL.permissions.has(['ACCESS']);

console.log("Address A has access:", addressA_HasAccess);
console.log("Address B has access:", addressB_HasAccess);
```

{% endstep %}

{% step %}

### Block a Specific Address <a href="#step-5-block-a-specific-address" id="step-5-block-a-specific-address"></a>

We use the same `updatePermissions()` method as before, but this time with `AdjustMethod.SUBTRACT` instead of `SET`.&#x20;

The `SUBTRACT` method removes the specified permissions from the target account rather than granting them. In this case, we're removing the `ACCESS` permission from Address A, effectively blocking that account from interacting with our token. Only Address A is affected by this operation, while Address B retains all its existing permissions.&#x20;

The blocking takes effect immediately after the transaction is published to the network.

```typescript
console.log("\n=== Blocking Address A ===");
await tokenOwnerClient.updatePermissions(
    addressA,
    new KeetaNet.lib.Permissions(['ACCESS']),
    tokenAccount,
    KeetaNet.lib.Block.AdjustMethod.SUBTRACT,
    { account: tokenAccount }
);
console.log("✅ Address A blocked");
```

{% endstep %}

{% step %}

### Verify the Block Worked <a href="#step-6-verify-the-block-worked" id="step-6-verify-the-block-worked"></a>

We perform the same ACL lookup and permission check as we did in Step 4, but now we can see the results of our blocking operation.&#x20;

Address A should now show `false` when we check for ACCESS permission, indicating it has been successfully blocked.&#x20;

Address B should still show `true`, demonstrating that our blocking was selective and didn't affect other accounts.&#x20;

The summary section provides a clear confirmation that our selective blocking worked correctly by comparing the before and after states of both addresses.

```typescript
console.log("\n=== Verifying Block ===");
const finalACLs = await tokenOwnerClient.listACLsByEntity({ account: tokenAccount });

const final_addressA_ACL = finalACLs.find(acl => acl.principal.comparePublicKey(addressA));
const final_addressB_ACL = finalACLs.find(acl => acl.principal.comparePublicKey(addressB));

const final_addressA_HasAccess = final_addressA_ACL && final_addressA_ACL.permissions.has(['ACCESS']);
const final_addressB_HasAccess = final_addressB_ACL && final_addressB_ACL.permissions.has(['ACCESS']);

console.log("Address A has access after blocking:", final_addressA_HasAccess);
console.log("Address B has access after blocking:", final_addressB_HasAccess);

console.log("\n=== Summary ===");
console.log("Address A blocked successfully:", addressA_HasAccess && !final_addressA_HasAccess);
console.log("Address B still has access:", addressB_HasAccess && final_addressB_HasAccess);
```

{% endstep %}
{% endstepper %}

## Full Code Example

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

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

    // Create accounts
    const tokenOwner = KeetaNet.lib.Account.fromSeed(seed, 0);
    const addressA = KeetaNet.lib.Account.fromSeed(seed, 1);
    const addressB = KeetaNet.lib.Account.fromSeed(seed, 2);
    
    const tokenOwnerClient = KeetaNet.UserClient.fromNetwork("test", tokenOwner);

    console.log("Token Owner:", tokenOwner.publicKeyString.toString());
    console.log("Address A:", addressA.publicKeyString.toString()); 
    console.log("Address B:", addressB.publicKeyString.toString());

    // 1. Create token with public access
    console.log("\n=== Creating Token ===");
    const builder = tokenOwnerClient.initBuilder();
    const pendingToken = builder.generateIdentifier(KeetaNet.lib.Account.AccountKeyAlgorithm.TOKEN);
    await builder.computeBlocks();
    const tokenAccount = pendingToken.account;

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

    builder.modifyTokenSupply(10000n, { account: tokenAccount });
    await tokenOwnerClient.publishBuilder(builder);
    console.log("✅ Token created with public ACCESS");

    // 2. Grant explicit access to both addresses
    await tokenOwnerClient.updatePermissions(
        addressA,
        new KeetaNet.lib.Permissions(['ACCESS']),
        tokenAccount,
        KeetaNet.lib.Block.AdjustMethod.SET,
        { account: tokenAccount }
    );

    await tokenOwnerClient.updatePermissions(
        addressB,
        new KeetaNet.lib.Permissions(['ACCESS']),
        tokenAccount,
        KeetaNet.lib.Block.AdjustMethod.SET,
        { account: tokenAccount }
    );

    // 3. Verify both addresses have access
    console.log("\n=== Checking Initial Access ===");
    const initialACLs = await tokenOwnerClient.listACLsByEntity({ account: tokenAccount });
    
    const addressA_ACL = initialACLs.find(acl => acl.principal.comparePublicKey(addressA));
    const addressB_ACL = initialACLs.find(acl => acl.principal.comparePublicKey(addressB));
    
    const addressA_HasAccess = addressA_ACL && addressA_ACL.permissions.has(['ACCESS']);
    const addressB_HasAccess = addressB_ACL && addressB_ACL.permissions.has(['ACCESS']);
    
    console.log("Address A has access:", addressA_HasAccess);
    console.log("Address B has access:", addressB_HasAccess);

    // 4. Block Address A
    console.log("\n=== Blocking Address A ===");
    await tokenOwnerClient.updatePermissions(
        addressA,
        new KeetaNet.lib.Permissions(['ACCESS']),
        tokenAccount,
        KeetaNet.lib.Block.AdjustMethod.SUBTRACT,
        { account: tokenAccount }
    );
    console.log("✅ Address A blocked");

    // 5. Verify blocking worked - A is blocked, B still has access
    console.log("\n=== Verifying Block ===");
    const finalACLs = await tokenOwnerClient.listACLsByEntity({ account: tokenAccount });
    
    const final_addressA_ACL = finalACLs.find(acl => acl.principal.comparePublicKey(addressA));
    const final_addressB_ACL = finalACLs.find(acl => acl.principal.comparePublicKey(addressB));
    
    const final_addressA_HasAccess = final_addressA_ACL && final_addressA_ACL.permissions.has(['ACCESS']);
    const final_addressB_HasAccess = final_addressB_ACL && final_addressB_ACL.permissions.has(['ACCESS']);
    
    console.log("Address A has access after blocking:", final_addressA_HasAccess);
    console.log("Address B has access after blocking:", final_addressB_HasAccess);

    console.log("\n=== Summary ===");
    console.log("Address A blocked successfully:", addressA_HasAccess && !final_addressA_HasAccess);
    console.log("Address B still has access:", addressB_HasAccess && final_addressB_HasAccess);
}

main().catch(console.error);

```


# Storage Accounts

Storage accounts represent a versatile account type designed to hold balances and serve as custodial accounts for funds. These accounts can be shared among multiple users or entities and can undergo complete ownership changes. The defining feature of storage accounts is their ability to implement custom rules regarding the types of tokens they can receive and who is permitted to deposit into them.&#x20;

This is achieved through Keeta’s permission system, allowing for fine-grained control over account operations. Storage accounts are particularly useful for scenarios requiring joint fund management, escrow services, or segregated fund storage with specific access controls.

### Core Features

* **Advanced permissions:** Set fine-grained rules for who can view, deposit, or withdraw funds.
* **Multi-entity ownership:** Accounts can be shared across people, organizations, or automated systems.
* **Custom account logic:** Enable workflows such as joint approvals, time locks, escrow, or spending limits.
* **Flexible transferability:** Easily change or assign ownership when needed.

### How Users Can Leverage Storage Account <a href="#how-users-can-leverage-storage-accounts" id="how-users-can-leverage-storage-accounts"></a>

Keeta introduces an innovative approach to digital banking by allowing users to utilize a single key pair to open and manage multiple accounts across different banks. This process begins with the user generating a key pair on Keeta Network, consisting of both a public and private key. The public key serves as a secure identifier that can be shared with various financial institutions.

When a user wishes to open an account with a bank, they simply share their public key. The bank then uses this public key to generate and open a storage account on Keeta specifically for that user. This process can be repeated with multiple banks, all using the same public key. As a result, users can establish and access numerous accounts across various financial institutions without the need to generate and manage multiple sets of cryptographic keys.

This system offers significant advantages in terms of user convenience and security. It simplifies account management for individuals who maintain relationships with multiple banks or financial institutions, while still preserving the high level of security associated with cryptographic key pairs.

<figure><img src="/files/oImfIVq91YT1ZlnSi4Py" alt=""><figcaption><p>Flow of Creating a Joint Accounts on the Network</p></figcaption></figure>


# 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

{% stepper %}
{% step %}

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

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.

```typescript
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());
}


```

{% endstep %}

{% step %}

### Token Creation and Minting <a href="#step-2-token-creation-and-minting" id="step-2-token-creation-and-minting"></a>

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.

```typescript
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());

```

{% endstep %}

{% step %}

### Storage Account Creation with Permissions <a href="#step-3-storage-account-creation-with-permissions" id="step-3-storage-account-creation-with-permissions"></a>

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.

```typescript
// 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.");

```

{% endstep %}

{% step %}

### Token Distribution <a href="#step-4-token-distribution" id="step-4-token-distribution"></a>

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.

```typescript
// 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 }));

```

{% endstep %}

{% step %}

### 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.

```typescript
// 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 }));

```

{% endstep %}
{% endstepper %}

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

```typescript
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);
});

```


# 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);
});
```


# Key Pairs

In the world of digital transactions, a key pair is like your personal lock and key set. It consists of a public key that can be freely shared, and a private key that is kept secret. On the Keeta Network, when a user makes a transaction, they use their private key to 'sign' it, creating a unique stamp. Anyone can then use your public key to verify that you indeed created the unique stamp, similar to how a bank verifies your signature on a check, but far more secure and impossible to forge.

The Keeta network allows participants to manage their own key pairs, offering flexibility and security. The user has direct control over their transactions – no party can act on their behalf without their private key. Even if someone obtains your public key, they can't use it to make transactions or access your assets; only the private key can do that.

This key pair system brings several advantages to Keeta. It ensures transactions are secure and tamper-proof, providing an accurate record of what party made each transaction – crucial for financial operations. Whether an individual makes a single payment or a crypto exchange manages millions, Keeta's key pair system provides the security and flexibility needed.

<figure><img src="/files/55bix1AhE9phJDqXPzYG" alt=""><figcaption><p>A public and private key are both needed to make transactions on the network.</p></figcaption></figure>


# Storing Key Pairs

Keeta provides multiple options for key storage to cater to different security needs and use cases. For high-security applications, Hardware Security Modules (HSMs) can be used to store private keys, providing an additional layer of protection against unauthorized access. For more consumer-oriented applications, on-device storage solutions can be utilized.

For example, a secure financial app on iOS could store private keys within the Secure Enclave on the user's device, giving them immediate access to their accounts and transactions. This approach ensures that sensitive data remains protected by hardware-level security, while still allowing for quick and convenient user interactions with their financial information.


# Certificates

Certificates on the network can be attached to [accounts](/components/accounts) to serve as digital credentials that validate the identity, qualifications, and capabilities of participants within the system. These certificates are fundamental to establishing trust and enabling secure interactions between various parties on the network.

<figure><img src="/files/oreZY1IOOhCNHoSqOfZE" alt=""><figcaption></figcaption></figure>

### Scope & Flexibility

At their core, Keeta certificates are structured pieces of information that can represent a wide range of attributes. These may include Know Your Customer (KYC) verifications, business licenses, regulatory compliance certifications, and other important qualifications.

The flexibility of the certificate system allows for the representation of virtually any relevant attribute that may be required for network operations.

### Dynamic Nature

One of the key features of certificates on the Keeta Network is their dynamic nature. Unlike traditional static credentials, these certificates can be updated and modified over time through [Keeta's Dynamic Rule Engine](/features/native-tokenization/built-in-rules-engine). This adaptability ensures that the information remains current and relevant, reflecting any changes in a participant's status or qualifications.

### Compliance & Security

Certificates play a crucial role in the network's compliance and security framework. They not only validate the legitimacy of participants but also define their capabilities within the system.

For example, a certificate might specify transaction limits, access rights, or the types of operations a participant is authorized to perform. This certificate-based approach to identity and permissions management helps streamline processes, and reduce verification overhead.


# Get Certificates

A certificate is a mechanism for one entity (the issuer) to assert specific attributes about another entity (the subject). Each certificate contains:

* **Subject's Public Key** — The Keeta account identifier
* **Certified Attributes** — Identity data like full name, email, or address
* **Issuer's Digital Signature** — Cryptographic proof of authenticity

Keeta extends X.509 certificates to support both public and sensitive attributes. Sensitive attributes use encryption and cryptographic commitments to enable selective disclosure — subjects can prove specific values to third parties without exposing data to others

### Get Certificates via the SDK <a href="#getting-certificates" id="getting-certificates"></a>

To fetch certificates tied to any Keeta account address (read-only), create a client with a `null` signer and pass the target `account` in the options:

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

async function main() {
  // Target address
  const publicKeyString = 'keeta_aabg2lkwuy4gvzr44cniihdmwzinfuunqv4qgsuhbq7jpt4qms622tldjbdexwy';
 const account = await KeetaNet.lib.Account.fromPublicKeyString(publicKeyString);
  // Read-only client bound to target account
  const client = KeetaNet.UserClient.fromNetwork(
    'test',
    null,
    { account }
  );

  try {
    // Fetch and sort certificates by issuance date (newest first)
    const response = await client.getCertificates();
    const sorted = response.sort(
      (a, b) => b.certificate.notBefore.valueOf() - a.certificate.notBefore.valueOf()
    );

    console.log(`Found ${sorted.length} certificates for ${account}`);

    // Display basic info for each certificate
    sorted.forEach(({ certificate }) => {
      console.log('Issuer:', certificate.issuerDN);
      console.log('Subject:', certificate.subjectDN);
      console.log('Valid until:', certificate.notAfter.toISOString());
      console.log('—');
    });
  } finally {
    await client.destroy();
  }
}

main().catch(console.error);

```


# Digital Signatures

Keeta Network uses digital signatures for digitally signing [blocks](/components/blocks) and [votes](/architecture/consensus/votes), and cryptographic hashing for referencing blocks.

The network currently supports 3 different cryptographic algorithms for performing digital signatures, but is extensible to support additional algorithms, as well as deprecating algorithms in the future should the need arise.

The currently supported algorithms are:

* EcDSA with secp256k1
* EcDSA with secp256r1
* Ed25519


# Post Quantum Readiness

Keeta Network is extensible to support additional cryptographic algorithms and can be migrated to fully support post-quantum cryptography (PQC), including deprecating all algorithms which are not post-quantum cryptography.


# Data Integrity

Ensuring data integrity is fundamental to the operation and trustworthiness of any blockchain system. In the context of Keeta Network, where transactions are validated through a meticulous two-phase voting process, the preservation of untampered data becomes even more vital.

**Append-only Ledger:** Once a transaction has been validated and appended to the blockchain, it becomes an immutable record. This means that it cannot be altered or deleted without a consensus from a quorum of the network’s representatives. This feature ensures that historical data remains consistent.

**Cryptographic Hashing:** Keeta Network employs SHA3-256 hashing for its records. This cryptographic hashing ensures that no alteration can take place within any record without affecting this record.

**Chain Consistency:** Every block on Keeta Network's blockchain contains a reference to the previous block through its cryptographic hash. This chaining mechanism ensures that the blocks are in the correct sequence, and any attempt to modify a block would not only affect that block but also every subsequent block, making unauthorized changes easily detectable.

**Use of TLS for Non-repudiation:** Keeta Network relies on Transport Layer Security (TLS) not just for encryption but importantly for non-repudiation. By utilizing HTTPS for all communication, it is possible to ensure that requests are being directed and delivered to the desired representatives.

By implementing these mechanisms into its core, Keeta Network ensures the integrity of its data. This trustworthiness is foundational for the platform’s wide adoption and the overall security of the network.


# Protection From Common Attacks

Keeta has taken extensive measures to ensure that the network is built to be secure against common blockchain attacks.

**Sybil attack**

One of the predominant concerns in decentralized systems is the Sybil attack, where a single adversary controls multiple [nodes](/components/nodes) on the network, effectively trying to subvert the network’s functionality. Such an attack can disrupt honest nodes from achieving consensus or facilitate malicious activities.

To counteract this, Keeta Network employs the use of X.509 certificates for certifying endpoints for representatives. This certification process ensures that each representative on the network is authenticated and can be trusted. X.509 certificates provide a standardized way of verifying the identity of participants and tying them to a public key infrastructure (PKI), making it computationally expensive and logistically challenging for an attacker to create a significant number of Sybil nodes.

By relying on the trusted certification process, not only does Keeta Network dramatically reduce the potential for Sybil attacks, but it also establishes an added layer of trust among participants. This approach ensures that network nodes represent unique, authenticated entities, effectively fortifying the network’s resilience against such threats

**51% attack**

In blockchain system, a 51% attack refers to a situation where a single entity or coalition controls more than half of the computational power (for Proof of Work systems) or more than half of the authority (in Proof of Stake systems), enabling them to doublespend coins, prevent transaction confirmations, or halt the creation of new blocks. Such dominance poses a severe threat to the integrity and trustworthiness of the network.

Keeta Network implements a proactive voting system to mitigate this threat. If a single representative amasses more than 50% of the voting weight, the Keeta Network protocol automatically adjusts the normal threshold for voting. This adaptive mechanism ensures that at least onetwo other representatives are required to reach a consensus, regardless of how much voting weight the dominant representative holds.

This safeguard not only ensures that no single representative can unilaterally dictate the network’s decisions but also promotes a decentralized and democratic ethos within Keeta Network. Such a mechanism reinforces the network’s resilience against centralized threats, preserving the foundational principles of decentralization and security that Keeta Network upholds.

**Spam attack**

In the decentralized environment of blockchain systems, spam attacks often manifest as an influx of legitimate yet superfluous transactions. These transactions, while valid in their structure, are intentionally designed to flood the network, causing bottlenecks, delays, and inefficiencies.

Keeta Network employs a strategic approach to counter such spamming tactics. Firstly, the network’s design incorporates a two-phase voting process, which acts as an initial filter to mitigate the volume of these transactions. However, in situations where an actor is persistent in dispatching a large number of genuine but unnecessary transactions, representatives on Keeta Network have the agency to respond. Representatives can observe transaction patterns and, upon identifying an attempt to spam the network, have the discretion to adjust transaction fees.

By ratcheting up these fees in response to abnormal transactional activity, Keeta Network introduces a financial deterrent. This increased cost makes it prohibitively expensive for malicious entities to continue their spamming efforts. Furthermore, representatives can also choose to decline voting for these transactions entirely, effectively blocking them from being added to the blockchain. This dual-layered approach ensures that Keeta Network remains resilient against transactional spam, ensuring smooth operations and preserving network integrity.

**Denial of service attack**

A Denial of Service (DoS) attack aims to render a service unavailable by overwhelming it with traffic or exploiting specific vulnerabilities. In blockchain contexts, DoS attacks can severely hamper network operations, affecting all users connected to the network.

Keeta Network’s design incorporates preemptive measures against DoS attacks. The aforementioned twophase voting process not only helps against spam attacks but is also effective in mitigating the impact of DoS attacks. By utilizing HTTPS in the voting process, Keeta Network can employ existing DDoS and DoS prevention mechanisms to safeguard the network.

Additionally, representatives observing abnormal traffic or suspicious patterns indicative of a DoS attack can start imposing fees on suspected malicious actors. This proactive stance not only helps to minimize the impact of DoS attacks but also empowers the representatives to maintain the network’s integrity actively.

\ <br>


# Benchmarks and Performance Metrics

TPS Throughput Testing Results

| Cloud Provider | Ledger Database | Max TPS |
| -------------- | --------------- | ------- |
| AWS            | DynamoDB        | 2M      |
| AWS            | DynamoDB        | 3.5M    |
| GCP            | Spanner         | 13M     |
| GCP            | Spanner         | 11.2M   |

\
Future Testing Configurations

<table><thead><tr><th width="168.98046875">Representatives</th><th width="178.8984375">Other Nodes</th><th width="202.078125">Transactions per Block</th><th>Blocks per Staple</th></tr></thead><tbody><tr><td>5</td><td>0</td><td>1000</td><td>1</td></tr><tr><td>5</td><td>10</td><td>1 to 10</td><td>1 to 2</td></tr><tr><td>5</td><td>30</td><td>1</td><td>1</td></tr></tbody></table>


# Separating Nodes from Hardware

Keeta's architecture separates [nodes](/components/nodes) from servers, allowing multiple servers to support a single node. This design enables both vertical and horizontal scaling without downtime, maintaining consistent transaction throughput under heavy loads and avoiding common network bottlenecks.

Unlike traditional blockchain systems where nodes and servers are combined—requiring nodes to go offline for hardware upgrades—Keeta's approach permits hardware upgrades while keeping the network operational. This method delivers high performance without compromising decentralization or efficiency.

By decoupling nodes from the underlying hardware, Keeta ensures that scaling and upgrades can be performed without disrupting network availability. This flexible architecture sets Keeta apart from competitors, providing continuous operation at full capacity during expansion or maintenance activities.

<figure><img src="/files/DkULjHT0au6i7JgpexWA" alt=""><figcaption><p>By separating nodes from servers, Keeta Network delivers high performance without compromising decentralization or efficiency.</p></figcaption></figure>


# Eliminating Mempools

A key feature of Keeta Network is the elimination of memory pools, or "mempools." In traditional blockchain networks, mempools serve as waiting areas for transactions before they're added to a block. This can lead to delays and increased transaction fees during periods of high network activity. Keeta's architecture bypasses this issue entirely. By leveraging the [DAG structure](/architecture/data-structure) and advanced [consensus mechanism](/architecture/consensus), transactions can be quickly validated and incorporated into the network without the need for a mempool intermediary. The result is fast transaction processing and significantly reduced fees.

<figure><img src="/files/6JmIoJGYyIxpQcwcaUhU" alt=""><figcaption><p>Traditional blockchains utilize mempools as waiting areas for transactions.</p></figcaption></figure>


# Overview

Building a global financial application today means integrating with a different vendor for every rail you want to support. One provider for ACH, another one for SWIFT, another for Ethereum, another for FX. Each comes with its own API, its own compliance requirements, and its own integration overhead. Going global, in practice, means rebuilding your payment stack for every region you enter.

Anchors solve this at the protocol level. An Anchor is a standardized interface that connects any external payment rail, blockchain network or traditional, to Keeta. Once connected, that rail's native assets become freely tradeable with any other asset on the network. Moving USD via ACH, ETH via an Ethereum bridge, any other currency over SWIFT — it's all possible with Anchors.

The Anchor system takes care of [service discovery](/anchors/overview/anchor-resolver), and allows your [application](/anchors/overview/anchor-client) to easily compare [services](/anchors/overview/anchor-server) based on their speed and coverage. It also handles the onboarding to these services. This includes sharing verifiable KYC[^1] credentials, presenting Terms of Service agreements, or even establishing customer support channels. For providers, such as banks and bridges, it offers a way to integrate once and reach any compatible application across the Keeta ecosystem.

{% hint style="info" %}
Keeta operates a set of flagship Anchors that provide foundational services, including connectivity to [Base Chain](https://www.base.org/) and various fiat payment rails, with liquidity for these tokenized assets. But any participant can create and host an Anchor for any rail. Multiple providers can anchor the same rail simultaneously — your application and its users route between them based on fees, rules, and performance. There is no single point of failure, and no vendor lock-in.
{% endhint %}

Anchors come in various categories, each representing both a service your application may consume, and a function a provider may offer:

* [**Asset Movement Anchors**](/anchors/anchor-types/asset-movement) bring value onto and off of Keeta. These are the on/off ramps: commercial banks, stablecoin ramps, and blockchain bridges. When your users need to deposit funds from their bank account or withdraw to a crypto wallet, an Asset Movement Anchor is what makes that happen.
* [**FX Anchors**](/anchors/anchor-types/fx-foreign-exchange) handle conversion between asset classes. Backed by market makers, DeFi liquidity pools, and traditional FX desks, they let your app offer currency conversion across any pair, without exposing your users to counterparty risk.
* **KYC Anchors** issue verifiable identity certificates to users on Keeta. Certificate Authorities can be governments, KYC providers, banks, or cell phone carriers — any entity qualified to attest to a user's identity or credentials. Once issued, certificates travel with the user as a portable digital passport, letting any Anchor that requires compliance verify identity instantly without repeating onboarding.
* Various other supporting Anchors that offer the building blocks of a modern banking experience: **Username Anchors** abstract away from machine-oriented `keeta_` addresses, and offer a more human friendly addressing system. **Notification Anchors** faciliate push notifications, and remove the need for open websockets to be notified about network events. **Storage Anchors** provide a private data store, where applications may put user contacts, icons, or any other arbitrary files required by their operation.

***

Together, Anchors turn Keeta into the access layer for a truly global financial system. They make fragmented rails feel like one network for developers and users, and empower a new generation of banking applications built for an instant, borderless, programmable world.

[^1]: Including Know Your Business, and even Know Your Agent flows.


# Anchor Client

The **Anchor Client** is the developer-facing library for interacting with Anchor services. It abstracts the complexity of service discovery, authentication, request signing, and communication with anchors.

### **Purpose**

The Anchor Client provides:

* **Automatic service discovery** via the Anchor Resolver
* **Request authentication** using Keeta account signatures
* **Type-safe APIs** for each anchor service type
* **Error handling** with user-friendly error messages
* **Multi-provider support** to query multiple anchors simultaneously

### **Who Uses It**

Anchor Clients are used by:

* **Wallet developers** building user-facing applications
* **DApp developers** integrating fiat on/off ramps
* **Exchange developers** connecting to liquidity providers
* **Payment processors** routing transactions through optimal providers
* **Trading platforms** accessing FX services

The Anchor Client is analogous to the `keetanet-client` library – while `keetanet-client` lets you interact with Keeta, the Anchor Client lets you interact with anchor services that connect to traditional finance and other blockchain networks.

### **Key Features**

**Service-Specific Clients**: Each anchor service has its own specialized client:

* `KeetaAnchor.FX.Client` - Foreign exchange operations
* `KeetaAnchor.AssetMovement.Client` - Cross-chain and fiat transfers
* `KeetaAnchor.KYC.Client` - Identity verification services

**Standardized Server Interaction**: The client abstracts the complexity of working with anchor servers:

* Handles HTTP communication and request formatting
* Automatically signs requests with your Keeta account
* Validates and verifies signed responses from providers
* Converts between on-chain and off-chain data formats
* Provides consistent error handling across all anchor types
* Makes it easy to query and compare multiple providers

### **Example: Using the FX Client**

{% @github-files/github-code-block url="<https://github.com/KeetaNetwork/keetanet-examples/blob/main/src/anchor/fx-client.ts>" %}


# Anchor Server

The **Anchor Server** is the SDK that helps developers build and operate anchor services. It provides the server-side infrastructure to run an anchor that other users can discover and interact with.

### **Purpose**

The Anchor Server SDK provides:

* **HTTP server infrastructure** with built-in request routing
* **Request validation** and authentication
* **Signature verification** for client requests
* **Metadata publishing** for service discovery
* **Queue management** for async operations
* **Error handling** with standardized error responses

### **Who Uses It**

Anchor Servers are operated by:

* **Financial institutions** providing fiat on/off ramps
* **Liquidity providers** offering FX services
* **Bridge operators** enabling cross-chain transfers
* **KYC providers** offering identity verification
* **Payment processors** facilitating settlements

### **Advantages of the SDK**

Building an anchor from scratch requires handling many complex concerns:

* Secure authentication and signature verification
* Service metadata formatting and publishing
* Request/response validation
* Asynchronous operation management
* Error handling and status codes

The Anchor Server SDK handles all of this, letting you focus on your business logic. It provides:

1. **Standardized APIs**: All anchors implement the same interfaces, making integration easier for clients
2. **Security**: Built-in signature verification and authentication
3. **Reliability**: Queue management and retry logic for async operations
4. **Discoverability**: Automatic metadata publishing for the Resolver
5. **Compliance**: Structured patterns for KYC and regulatory requirements

### **Example: Running an FX Anchor Server**

{% @github-files/github-code-block url="<https://github.com/KeetaNetwork/keetanet-examples/blob/main/src/anchor/fx-server.ts>" %}


# Anchor Resolver

The **Anchor Resolver** is the discovery mechanism for Anchor services on Keeta. Think of it like DNS for the internet – just as DNS translates domain names into IP addresses, the Anchor Resolver translates service requirements into anchor endpoints.

### **How It Works**

When you need to find an Anchor service (like an FX provider or Asset Movement service), the Resolver:

1. **Queries the Keeta network** for accounts that provide the service you need
2. **Reads service metadata** from those accounts to understand their capabilities
3. **Matches your requirements** (like currency pairs, supported rails, geographic regions) to available providers
4. **Returns endpoint URLs** and configuration details for compatible services

The Resolver looks up metadata published on-chain by anchor operators. This metadata describes:

* What services they offer (FX, Banking, KYC, Asset Movement, etc.)
* Which currencies, tokens, or assets they support
* Required authentication methods
* API endpoint URLs
* Supported country codes and KYC providers

### **Why It Matters**

The Resolver enables **dynamic service discovery**. Instead of hardcoding anchor URLs in your application, you describe what you need, and the Resolver finds providers that match. This creates a decentralized marketplace where:

* New Anchors can join without client-side updates
* Clients automatically discover the best providers for their needs
* Service availability is stored on-chain
* Geographic and regulatory requirements can be matched

### **Example: Finding an FX Provider**

```typescript
import * as KeetaAnchor from '@keetanetwork/anchor';
import * as KeetaNet from '@keetanetwork/keetanet-client';

async function findFXProvider() {
  const networkAlias = 'test';
	const config = KeetaAnchor.KeetaNet.Client.Config.getDefaultConfig(networkAlias);
	const userClient = KeetaAnchor.KeetaNet.UserClient.fromNetwork(networkAlias, null);
	const networkAddress = userClient.networkAddress;

  // Create a resolver to discover services
	const resolver = new KeetaAnchor.lib.Resolver({
		root: networkAddress,
		client: userClient,
		trustedCAs: []
	});
  
  const USDToken = 'keeta_ap7wtjtyfc4yvt26jstau4n76uqnv4znjnz5pcgpnjsty5vjbxkvmk55yl4f6';
  const KTAToken = 'keeta_anyiff4v34alvumupagmdyosydeq24lc4def5mrpmmyhx3j6vj2uucckeqn52';
  // The resolver automatically finds FX services that can convert
  // between the specified currencies
  const fxServices = await resolver.lookup('fx', {
    inputCurrencyCode: USDToken,
    outputCurrencyCode: KTAToken
  });
  
	if (fxServices) {
		for (const serviceProvider of Object.keys(fxServices)) {
			const resolvedMetadata = await KeetaAnchor.lib.Resolver.Metadata.fullyResolveValuizable(fxServices[serviceProvider]);
			console.log(`Found FX Service: ${serviceProvider}`, util.inspect(resolvedMetadata, { depth: 10, colors: true }));
		}
		// Each service includes operations like getQuote, createExchange, etc.
	}
}
```


# Encrypted Containers

**Encrypted Containers** are a secure way to encrypt data and share it with specific Keeta accounts. They provide end-to-end encryption with built-in access control.

### **What Are They For**

Encrypted Containers solve a common problem in blockchain systems: how to share private data securely. Use cases include:

* **KYC documents**: Share identity verification documents with compliant anchors
* **Private transaction details**: Share sensitive payment information
* **Confidential certificates**: Distribute verifiable credentials
* **Secure messaging**: Send encrypted data between accounts

### **How Are They Constructed**

An Encrypted Container:

1. **Encrypts plaintext** for each authorized recipient using their public key
2. **Optionally signs** the container to prove authenticity
3. **Serializes to ASN.1** format for compact, standardized encoding

### **Advantages of the SDK**

The Encrypted Container implementation in the Anchor SDK:

* **Handles cryptographic complexity**: You don't need to understand ASN.1, key derivation, or cipher modes
* **Manages access control**: Automatically encrypts keys for each recipient
* **Provides verification**: Built-in signature support for authenticity
* **Optimizes size**: Automatic compression for large payloads
* **Ensures compatibility**: Standard encoding works across different implementations

### **Example: Creating and Sharing an Encrypted Container**

{% @github-files/github-code-block url="<https://github.com/KeetaNetwork/keetanet-examples/blob/main/src/anchor/encrypted-container.ts>" %}


# Anchor Types


# Asset Movement

Asset Movement Anchors are the on- and off-ramps between Keeta and the outside world. They cover the full range of providers that move value across networks: commercial banks, stablecoin ramps, blockchain bridges, and card networks. Once connected, any of these rails can be reached through the same interface, whether you're depositing fiat from a bank account, withdrawing to a crypto wallet, or bridging between chains. This page covers the two mechanics your application will use: [**managed transfers**](#managed-transfers) for one-off movement of value, and [**persistent addresses**](#persistent-addresses) for reusable destinations that forward incoming assets to Keeta automatically.

## Managed Transfers

Managed transfers are the primary way of moving value to and from Keeta. A client specifies the destination and amount, whether a bank account or a blockchain address. The selected provider registers the request and informs the client how to complete it. Once the provider receives the funds, it automatically completes the transfer on the destination side. Each managed transfer carries a full lifecycle, and its status can be monitored from request through settlement.

### How They Are Constructed

Each transaction [request](https://github.com/KeetaNetwork/anchor/blob/cc4af48060705ca57bfe31640ddf47fcedf3bb91/src/services/asset-movement/common.ts#L345-L389) comprises the following information:

* **Asset** being transferred (i.e. `$BTC` or `EUR`) — or a pair of assets if the transfer involves a conversion.
* **Value** — how much value is being moved. Always specified in the source asset's smallest unit (cents for `USD`, wei for `$ETH` , etc.)
* The **source** — where the value originates. A [**location**](https://github.com/KeetaNetwork/anchor/blob/cc4af48060705ca57bfe31640ddf47fcedf3bb91/src/services/asset-movement/lib/location.ts#L5-L102) identifying the rail or network (a blockchain, a banking system, a card network, etc.)
* The **destination** — where the value should go. A **location** and a **recipient**, which may be a wallet address, or bank account.

## Persistent Addresses

**Persistent Addresses** are reusable destination addresses that automatically forward received assets to your Keeta account. Think of them as permanent forwarding addresses for different networks and rails.

When moving assets from external networks (like Ethereum, Bitcoin, or bank accounts) to Keeta, you typically need a unique destination address. Persistent addresses:

* Remain constant across multiple transfers
* Automatically forward incoming assets to your Keeta account
* Work across different asset types and networks
* Can be shared with others for recurring payments

### **How They Are Constructed**

A persistent address consists of:

* **Template**: Defines the asset type, location (chain/bank), and rail
* **Address Instance**: The actual address generated from the template
* **Forwarding Rules**: Instructions for how to route received assets to Keeta

### Common Use Cases for Persistent Addresses

* **Receiving Salary** - Bank deposit could be made directly to a Keeta account
* **Cross-Chain DeFi** - Moving funds between blockchain networks

## Running an Asset Movement Anchor

If you already operate a service that moves value across networks, the Anchor SDK is mainly a translation layer between that service and the Keeta interface. A DeFi bridge can expose itself by forwarding EVM contract call instructions in the Anchor format. A stablecoin ramp can map its fiat deposit and issuance flow to managed transfer endpoints, and verify user compliance through certificates issued by KYC Anchors instead of re-collecting documents.


# Ethereum VM Anchors

Ethereum VM (EVM) based Asset Movement Anchors enable transfers between Keeta and EVM-compatible blockchain networks like Ethereum, Base etc.

### **What's Unique About EVM Anchors**

EVM Anchors have special characteristics:

1. **Smart Contract Integration**: Uses smart contracts for secure custody and forwarding
2. **Multi-Chain Support**: Works across all EVM-compatible chains using the same interface
3. **Gas Management**: Handles gas fees transparently
4. **ERC-20 Tokens**: Supports any ERC-20 token with contract address
5. **Native Asset Support**: Can handle ETH, USDC, EURC, etc.

#### **EVM-Specific Rails**

* **EVM\_SEND**: Standard token transfers (like sending ETH or ERC-20)
* **EVM\_CALL**: Smart contract calls for more complex operations

#### **Example: Moving USDC from Keeta to Base**

{% @github-files/github-code-block url="<https://github.com/KeetaNetwork/keetanet-examples/blob/main/src/anchor/asset-movement-evm-inbound.ts>" %}

#### **Example: Moving USDC from Base to Keeta using Persistent Addresses**

{% @github-files/github-code-block url="<https://github.com/KeetaNetwork/keetanet-examples/blob/main/src/anchor/asset-movement-persistent-address.ts>" %}


# Fiat Anchors

Fiat Asset Movement Anchors provide on-ramps and off-ramps between traditional fiat currency systems and Keeta. They connect bank accounts, wire transfers, ACH, and other payment rails to Keeta tokens.

### **Benefits of Fiat Asset Movement Anchors**

Fiat Asset Movement Anchors enable:

* **Fiat On-Ramps**: Deposit USD, EUR, etc. from your bank to receive Keeta tokens
* **Fiat Off-Ramps**: Withdraw Keeta tokens to receive fiat in your bank account
* **Traditional Payment Compatibility**: Accept ACH, wires, SEPA, PIX, etc.
* **Regulatory Compliance**: Anchors handle KYC/AML requirements
* **Multiple Currencies**: Support for various fiat currencies and regions

#### **Supported Rails**

Common banking rails:

* **ACH**: US bank transfers
* **ACH\_DEBIT**: Pull funds from bank account
* **WIRE**: Fast domestic wire transfers
* **WIRE\_RECEIVE**: Incoming wire transfers
* **WIRE\_INTL\_PUSH**: International wire transfers
* **SEPA\_PUSH**: European bank transfers
* **PIX\_PUSH**: Brazilian instant payments
* **CLABE\_PUSH**: Mexican bank transfers
* **SPEI\_PUSH**: Mexican electronic transfers

### KYC Requirements

Most Fiat Anchors require KYC (Know Your Customer) verification to comply with anti-money laundering (AML) regulations. The Keeta Anchor SDK provides a powerful integration between KYC Anchors and Fiat Asset Movement Anchors that enables **on-chain KYC flows** where identity verification can be performed once and shared securely across multiple service providers.

#### **How KYC and Fiat Anchors Work Together**

The integration works through a three-step process:

1. **Verification with KYC Anchor**: Users complete identity verification with a KYC provider, submitting required documents and information
2. **Certificate Issuance**: The KYC provider issues cryptographically signed certificates that prove verification status
3. **Secure Sharing**: These certificates are securely shared with Fiat Anchors using [encrypted containers](/anchors/overview/encrypted-containers), allowing the Fiat Anchor to verify compliance without re-collecting sensitive documents

This approach provides several key benefits:

* **Single Verification**: Users verify their identity once and reuse certificates across multiple Anchors
* **Privacy**: Personal documents remain with the KYC provider; Fiat Anchors only receive verification certificates
* **Compliance**: Fiat Anchors can cryptographically verify that proper KYC has been performed
* **Efficiency**: Eliminates redundant verification processes across different providers
* **On-Chain Provenance**: KYC status can be verified on-chain through certificate signatures


# Other Asset Movements


# FX (Foreign Exchange)

The FX Anchor service enables currency and token conversions on Keeta. It connects users who want to swap between different assets with liquidity providers who offer exchange services.

### Understanding FX Anchors

An FX Anchor operates like a decentralized exchange interface:

1. **Discovery**: Clients find FX providers using the Resolver
2. **Quote Request**: User requests a price for a conversion
3. **Quote Response**: Provider returns a signed quote with rate and fees
4. **Exchange**: User submits a block swapping tokens with the provider
5. **Settlement**: Provider completes the swap atomically on-chain

The SDK handles the quote signing, verification, and atomic swap construction, ensuring both parties are protected.

### Core Concepts

**Conversion Input**

Every FX operation starts with a `ConversionInput` specifying:

* **from**: The token you have
* **to**: The token you want
* **amount**: How much to convert
* **affinity**: Whether amount refers to 'from' (you have this much) or 'to' (you want this much)

**Quotes vs Estimates**

* **Estimate**: Non-binding price indication, useful for showing users expected rates
* **Quote**: Binding commitment from provider with signature, required for execution

**Floating vs Fixed Rate**

* **Fixed Rate (with quote)**: Price locked at quote time, guaranteed regardless of market movement
* **Floating Rate (without quote)**: Price determined at execution time, may be better or worse than estimate

### Basic Client FX Flow

{% @github-files/github-code-block url="<https://github.com/KeetaNetwork/keetanet-examples/blob/main/src/anchor/fx-client.ts>" %}


# KYC


# Storage


# Username


# Example: Cross-Border Payroll

Let’s break down how a U.S.-based company can use the Keeta anchor system to pay EU-based contractors in euros, leveraging both fiat and FX anchors. This illustrates instant, compliant, and cost-efficient global payroll without relying on legacy banking settlement delays.

**FLOW IMAGE COMES HERE**

## Key Benefits

* **Sub-second settlement:** No multi-day payment delays.
* **Unified compliance:** All regulatory requirements (KYC/AML, sanctions) are enforced at protocol level.
* **Cost-efficiency:** Eliminates intermediary fees and reduces FX markup.
* **Transparency:** Real-time on-chain audit trails for all steps.

This process allows a business to pay international employees or contractors with the speed and certainty of sending a domestic instant payment—combining traditional bank rails with the transparency and programmability of a fast, interoperable blockchain network.


# Example: On-Chain Credit Bureau


# Identity Profiles

All of the [certificates](/components/certificates) issued by Certificate Authorities (CAs) are brought together to collectively form a comprehensive digital profile for each user on the Keeta network. A user might have their identity verified by a government agency, their financial credentials certified by a bank, their professional qualifications validated by an industry expert, and their educational background confirmed by a university. This creates a rich, multi-faceted overview of the user's verified attributes, all linked to their unique public key on the network.

Such a system allows for efficient and secure sharing of verified information across different services and applications within the Keeta ecosystem, reducing repetitive verification processes and enhancing user privacy by allowing selective disclosure of relevant certificates as needed for specific interactions or transactions.

<figure><img src="/files/O324uW0jUjWzHSWgISmJ" alt=""><figcaption><p>User can utilize certificates from a variety of CAs to create a compehensive digital profile.</p></figcaption></figure>


# Utilizing Identity Profiles

When a user wants to open a new [account](/components/accounts) with an entity on Keeta Network that requires identity verification, they simply need to provide their[ public key](/components/key-pairs) and the relevant [certificates](/components/certificates). The business will then instantly validate the provided certificate(s) and open the new account. This allows the business to effectively verify the user's identity and credentials without the need for a lengthy application process.

This system creates a "digital passport", allowing users to carry their verified credentials with them across the Keeta ecosystem in a private and secure manner. It not only simplifies the account opening process but also puts users in control of their own data and privacy. The user only needs to trust one party with their information, which is the CA. Any party that receives the certificate does not recieve the actual personal information, but rather a confirmation that the personal information exists and is sufficient for the action on the network. Users' personal information is no longer being stored across a variety of data servers with unknown security.

Moreover, the dynamic nature of these certificates means that if a user's status changes (for example, they move to a new address or obtain a new qualification), this information can be updated in their certificates, ensuring that businesses always have access to the most current and accurate user data when creating new accounts or providing services.

<figure><img src="/files/KsuNwLa6QtRGUNxPK2i0" alt=""><figcaption><p>With approved certificates, users can effortlessly engage with a variety of entities, streamlining account creation and service access across multiple sectors.</p></figcaption></figure>


# Native Tokenization

Tokenization is a cornerstone feature of Keeta Network, offering a flexible and efficient way to represent value. In the Keeta ecosystem, tokens are not smart contract-based assets but are rather native to the network itself. Tokens can be created for a wide range of applications, from representing digital currencies to creating digital versions of real-world assets.

Unlike traditional blockchain platforms such as Ethereum, where tokens are typically created and managed through smart contracts, Keeta tokens exist as first-class citizens on the network. This fundamental difference in token architecture offers several advantages in terms of efficiency and flexibility.

**Streamlined Operations and Cost Efficiency**

The implementation of native tokenization eliminates the need for smart contract interactions for basic token operations. This design choice significantly reduces transaction complexity and associated costs. In contrast to Ethereum's tokenization model, where tokens are represented by smart contracts, Keeta's approach allows for more streamlined and cost-effective token operations.

**Versatility and Ease of Use**

Keeta's token system is designed for versatility and ease of use. Any approved network participant can create tokens, representing a wide range of assets. The system supports both fungible and non-fungible tokens, catering to diverse tokenization needs. Additionally, Keeta incorporates a dynamic rules engine that allows token creators to attach specific conditions or behaviors to their tokens, with these rules being automatically enforced by the network.


# Token Creation

The token creation process on the Keeta network begins with the token creator defining the key attributes of their token. These attributes typically include the token's name, symbol, total supply, and any specific rules or restrictions that will govern the token's behavior on the network. This initial step allows creators to customize their tokens according to their specific needs or use cases.

Once the token attributes are defined, the creator initiates a token creation on the Keeta network. If the creation is deemed valid, the network generates a unique identifier for the new token and records its creation on the blockchain. Following this, the network assigns the initial supply to the creator's account.

After the token is created and recorded on the blockchain, it becomes part of the Keeta ecosystem. The token creator can distribute, transfer, or manage the tokens according to their intended purpose. The network allows these newly created tokens to interact with other components of the ecosystem, such as exchanges, wallets, or other tokens, subject to the rules and restrictions set during the creation process.

It's worth noting that the Keeta network incorporates certain measures to maintain the integrity of the token creation process. These include safeguards to prevent the creation of fraudulent or duplicate tokens, as well as compliance checks to ensure that tokens meet any relevant regulatory requirements, if necessary. Additionally, the network's rules engine allows for the implementation of specific token behaviors, such as automatic burning, minting, or transfer restrictions. These features can be customized during the creation process or modified later, depending on the network's protocols and the specific requirements of the token.

<table data-view="cards"><thead><tr><th></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Mint Tokens</strong></td><td><a href="/files/6meU2HiUEgzPjVtHvG9F">/files/6meU2HiUEgzPjVtHvG9F</a></td><td><a href="/pages/ga1nZkSZjSlRlAFANBvZ">/pages/ga1nZkSZjSlRlAFANBvZ</a></td></tr><tr><td><strong>Burn Tokens</strong></td><td><a href="/files/HYoJKHWiKK2PMyzVX5gW">/files/HYoJKHWiKK2PMyzVX5gW</a></td><td><a href="/pages/Aq1JHZZkR1C3yIVRbPiJ">/pages/Aq1JHZZkR1C3yIVRbPiJ</a></td></tr><tr><td><strong>Set Permissions</strong></td><td><a href="/files/QoA3VjKvzqxxGh3UGc68">/files/QoA3VjKvzqxxGh3UGc68</a></td><td><a href="/pages/PZPZi39LMR5qG0bn7nFs">/pages/PZPZi39LMR5qG0bn7nFs</a></td></tr></tbody></table>


# Mint Tokens

This guide explains how to create and mint a token using the KeetaNet SDK. You'll learn how to build and publish a token account, set its supply, and adjust balances.

### Step-by-Step <a href="#step-by-step-minting-a-token" id="step-by-step-minting-a-token"></a>

{% stepper %}
{% step %}

### Initialize the Transaction Builder

Start by creating a transaction builder from your user client. This builder is used to queue up all operations before publishing to the network.

```typescript
const builder = userClient.initBuilder();
```

{% endstep %}

{% step %}

### Generate the Token Account

Use the builder to create a new token account:

```typescript
const pendingTokenAccount = builder.generateIdentifier(KeetaNet.lib.Account.AccountKeyAlgorithm.TOKEN);
await builder.computeBlocks();
const tokenAccount = pendingTokenAccount.account;
console.log("tokenAccount.publicKey =", tokenAccount.publicKeyString.toString());
```

{% endstep %}

{% step %}

### Set Token Information & Permissions

Configure the basic information and default permissions for your token:

```typescript
builder.setInfo(
    {
        name: '',                 // Token name
        description: '',          // Short description
        metadata: '',             // Arbitrary metadata
        defaultPermission: new KeetaNet.lib.Permissions([
            'ACCESS', // Public token
        ]),
    },
    { account: tokenAccount },
);
```

{% endstep %}

{% step %}

### Mint Token Supply

Define the total supply for the new token and mint tokens into the liquidity account:

```typescript
builder.modifyTokenSupply(10_000n, { account: tokenAccount }); // Set total supply
builder.send(account, 10_000n, tokenAccount, undefined, { account: tokenAccount });             // Distribute token amount to the liquidity account
```

{% endstep %}

{% step %}

### Publish the Operation

Send the queued operations to the network:

```typescript
await builder.publish();
console.log("Token account created and minted.");
```

{% endstep %}
{% endstepper %}

## Full Code Example

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

// Function to create and mint a new token
async function createToken(userClient: KeetaNet.UserClient) {
    const builder = userClient.initBuilder();

    // Generate the token account identifier
    const pendingTokenAccount = builder.generateIdentifier(KeetaNet.lib.Account.AccountKeyAlgorithm.TOKEN);
    await builder.computeBlocks();
    const tokenAccount = pendingTokenAccount.account;

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

    // Increase (mint) total token supply and distribute the tokens
    builder.modifyTokenSupply(10_000n, { account: tokenAccount });
    builder.send(account, 10_000n, tokenAccount, undefined, { account: tokenAccount });

    // Publish the transaction to the network
    await builder.publish();

    console.log("Token account created and minted.");
    return tokenAccount;
}

// Main function demonstrating token creation
async function main() {
    // Generate a random seed for account creation
    const seed = KeetaNet.lib.Account.generateRandomSeed({ asString: true });
    console.log("seed =", seed);

    // Create a liquidity provider account from the seed
    const liquidityProviderAccount = KeetaNet.lib.Account.fromSeed(seed, 0);

    // Instantiate a user client connected to the test network
    const liquidityProviderClient = KeetaNet.UserClient.fromNetwork("test", liquidityProviderAccount);

    // Create and mint the token
    const tokenAccount = await createToken(liquidityProviderClient);

    // Log the token identifier that was created
    console.log("Token Account =", tokenAccount.publicKeyString.get());

    // Log balances of the liquidity provider
    console.log("liquidityProviderClient.balances[] =", await liquidityProviderClient.allBalances());
}

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

```


# Burn Tokens

Below is a minimal, end-to-end example that mints a token and then burns part of its supply. After creating a token account and minting 10,000 units, a burn operation reduces both the holder’s balance and the token’s total supply by 2,500. Burns are staged with a builder and become final once published.

How it works:

* Create a token account, set basic info and default permission.
* Mint an initial supply to the token account.
* Start a new builder to burn: subtract the same amount from the account balance and the total supply.
* Publish to commit the burn on-chain.

This pattern keeps the ledger consistent: every burned unit is removed from circulation and reflected immediately in both balance and supply.import \* as KeetaNet from "@keetanetwork/keetanet-client";

```typescript

async function main() {
    // Generate random seed for account creation
    const seed = KeetaNet.lib.Account.generateRandomSeed({ asString: true });
    console.log("seed =", seed);

    // Create account and user client
    const account = KeetaNet.lib.Account.fromSeed(seed, 0);
    const userClient = KeetaNet.UserClient.fromNetwork("test", account);

    // Create and mint new token
    const builderMint = userClient.initBuilder();
    const pendingTokenAccount = builderMint.generateIdentifier(KeetaNet.lib.Account.AccountKeyAlgorithm.TOKEN);
    await builderMint.computeBlocks();
    const tokenAccount = pendingTokenAccount.account;

    builderMint.setInfo(
        {
            name: '',
            description: '',
            metadata: '',
            defaultPermission: new KeetaNet.lib.Permissions(['ACCESS']),
        },
        { account: tokenAccount }
    );
    builderMint.modifyTokenSupply(10_000n, { account: tokenAccount });

    await builderMint.publish();

    console.log("Token account created and minted.");
    console.log("Balances after minting:", await userClient.allBalances());

    // Burn some tokens
    const builderBurn = userClient.initBuilder();
    builderBurn.modifyTokenSupply(-2_500n, { account: tokenAccount });
    await builderBurn.publish();

    console.log("2,500 tokens burned from account", tokenAccount.publicKeyString.toString());
    console.log("Balances after burning:", await userClient.allBalances());
}

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

```


# Set Permissions

{% hint style="info" %}
Documentation is coming soon. The [SDK](https://static.test.keeta.com/docs/classes/KeetaNetSDK.Referenced.Permissions.html) is live and can be used today.
{% endhint %}


# Built-in Rules Engine

Keeta's blockchain includes a rules engine that allows token creators to attach specific rules to tokens when they are issued, or later in their life-cycle. These rules define how the token can be used, transferred, or managed on the network. For example, a rule might restrict transfers to certain users, set specific transaction dates, or require approval before transactions can occur.

<figure><img src="/files/1tX7h5XLRTTYa1nf02LK" alt=""><figcaption><p>Time-Locked Tokens Example</p></figcaption></figure>

<figure><img src="/files/LYOwBWIyzkcT7Rc62aft" alt=""><figcaption><p>Transaction Approval Requirements Example</p></figcaption></figure>

**Automatic Enforcement of Token Rules**

The network automatically enforces these attached rules. Whenever a transaction involving the token is initiated, the network checks the token's rules to ensure the transaction complies with them. If the transaction doesn't meet the specified conditions, the network will deny it. This automatic enforcement ensures that all token activities adhere to the intended guidelines without the need for manual oversight.

**Updating Token Rules After Issuance**

One of the key features of Keeta's rules engine is the ability to update the rules even after the token has been created and distributed. Token issuers can modify the rules to adapt to new regulations, changing business needs, or other factors. Once the rules are updated, the network applies the new conditions to all future transactions involving that token.


# Anchors

Keeta's Anchor system allows users to securely move their digital assets across multiple payment networks, blockchains or traditional banks without extensive fees. Any network can be connected to Keeta through the Anchor system, allowing that network's native assets to be traded freely with any other asset on Keeta. This feature brings a new level of interoperability to ecosystems that are otherwise fragmented.

Any foreign asset that is tokenized on Keeta is done so 1:1 and can be returned back to the native asset at any point. Upon sending the asset back to its native network, the original asset is released and the tokenized version on Keeta is burned.&#x20;

Traditional payment systems like the SWIFT and the Automated Clearing House (ACH) can also be connected to Keeta, creating a payment ecosystem with unprecedented global interoperability. Fiat currencies can be transferred with the same security and performance as digital assets, and the two can be interchanged seamlessly. Regulatory compliance protocols are built-in to the network natively, making the ecosystem feasible for central banks, commercial banks, and other highly-regulated financial entities.


# Creating an Anchor

Any participant on Keeta can create an anchor to any external system. The anchor hosts can set the rules for their own anchor and charge fees as desired. To allow for a competitive marketplace, there is no limit to how many participants can host the same anchor. For example, if five different banks host an anchor to the same domestic payment rail, network users can determine which bank's anchor they would like to utilize, based on the fees, rules, and performance of the anchor. The same dynamic is true with connected blockchain networks and their native digital assets.

Some anchors may choose to join the network as a public anchor, hoping to attract as many users as possible. Other anchors may choose to join the network as a private anchor for a specific group. Using the banks as an example again, one bank may host the domestic payment rail to any local citizen while another bank hosts the same domestic payment rail specifically for users that are certified members of their bank.


# Public Network

Keeta Network is available to any party, serving as a hub for innovation and collaboration. The team continues to built the ecosystem with additional tools and protocols to aid participants in taking asset transfers to a new level. With unprecedented performance, interoperability, and real-world applicability, Keeta has set itself up to become an industry leader over the existing public layer-1 blockchain networks.

Keeta's use of dPoS as a consensus mechanism provides decentralization by giving all token holders the ability to have an input on the consensus of the network. As the network's participation grows, it becomes more decentralized. Once a transaction is completed, it is broadcasted publicly to the network so any participant can read or audit and transaction. Any person or entity can join the network, and we encourage everyone to do so.


# Private Sub Network

Keeta has built the network with real-world applicability in mind. Some specific use cases require a level of privacy that a public network cannot provide. To accommodate this, Keeta Network can be launched in a private setting. Instead of the main network, this is known as a sub network.

Sub networks operate identically to the main network, but the transactions are private so they cannot be viewed by main network participants. These networks can be launched in a centralized or decentralized manner, leaving the distribution of power and overall accessibility to be determined by the sub network creator. Accounts from the main network can be transferred to any sub network, allowing users to seamlessly utilize the main network and any relevant sub network with their single universal key pair. Balances can also be transferred between networks, allowing sub network transactions to be reflected on the main network once the accounts are transferred back.


# Keeta Network's Advantage

This section may move or be deleted

Numbers don't lie. The following reasons are why Keeta Network has the potential to revolutionize blockchain technology:

**Scalability**: Keeta Network's enhanced dPoS mechanism provides efficient scalability, enabling it to accommodate the world's financial transactions seamlessly without sacrificing performance or security.

**Speed**: Keeta Network is the fastest blockchain in the world, supporting an astonishing 10 million transactions per second.

**Energy Consumption**: Many blockchains have struggled with extensive carbon footprints, but Keeta's architecture allows the network to operate at a mere 0.000056 CO2e/transaction.

**Low transaction cost**: By utilizing existing cloud infrastructure and providing options for transaction fees in various tokens, it can be more costeffective and cost-predictable.

**Security:** With digital identity features and fully consistent writes, Keeta Network ensures secure transactions.

**Flexibility:** Multi-token support and robust ability to be updated make Keeta Network adaptable for various applications.

**Global Governance:** Its decentralized nature allows for global participation while still adhering to local laws through token-level governance.

**Interoperability:** The ability to partition the network into distinct, interoperable subnets allows for greater flexibility and collaboration between different parties.

**Regulatory Compliance:** Built-in support for digital identity and sanctioning mechanisms make it easier to comply with existing laws and regulations.


# Resolving the Blockchain Trilemma

Keeta's approach addresses the long-standing blockchain trilemma - the challenge of achieving decentralization, scalability, and security simultaneously. Traditional blockchain networks sacrifice one of these aspects to enhance the others. However, Keeta's DAG structure, combined with its Delegated Proof of Stake (dPoS) consensus mechanism, allows for a harmonious balance of all three elements. The DAG structure provides scalability, while the dPoS system ensures decentralization and security through its validator selection process.


# Keeta vs. Ethereum

One of Keeta's key innovations is its built-in rule engine and tokenization system, eliminating the need for external smart contracts. While Ethereum has led in decentralized applications and smart contracts, it faces significant scalability challenges. To increase transaction throughput, Ethereum relies on layer-2 solutions like rollups, which introduce complexities and costs. These rollups batch transactions and later verify them on the main blockchain but have drawbacks like delayed finality and dependence on centralized sequencers, raising the risk of single points of failure.

Keeta avoids these issues by integrating scalability directly into its core architecture. Using a Directed Acyclic Graph (DAG) structure—a system that allows for multiple simultaneous transaction pathways—it achieves high throughput and near-instantaneous transaction validation without additional layers. Keeta's built-in mechanisms cover most of the use cases, rendering general-purpose smart contracts unnecessary.

Functionalities such as data access and identity management, often implemented through smart contracts on other platforms, are inherently built into Keeta. This ensures users can perform transactions and access services at minimal cost, regardless of network demand. The platform becomes more accessible to regulated institutions and everyday users, offering a smoother and more predictable experience compared to Ethereum, which continues to struggle with high fees despite multiple upgrades.

In essence, while Ethereum is a well-established platform with a vast ecosystem, its reliance on general-purpose smart contracts introduces complexity and inefficiency. Keeta offers superior scalability and cost efficiency by providing built-in solutions tailored to specific needs without the overhead of smart contracts. This makes it a more attractive option for financial institutions and developers seeking a future-proof solution.

<table data-full-width="true"><thead><tr><th>KEETA</th><th>ETHEREUM</th></tr></thead><tbody><tr><td>10,000,000 TPS</td><td>120 TPS</td></tr><tr><td>400 MS settlement times</td><td>12,000 MS settlement times</td></tr><tr><td>≈ $0.00005 transaction fee</td><td>≈ $3.46 transaction fee</td></tr><tr><td>Delegated Proof-of-Stake</td><td>Proof-of-Stake</td></tr><tr><td>Built-in rules engine</td><td>Custom Smart Contract Logic Required</td></tr></tbody></table>


# Keeta vs. Ripple

Ripple has long faced criticism over centralization concerns due to its consensus mechanism, which depends on a limited number of trusted validators—fewer than 35—to maintain the ledger. With Ripple Labs operating many of these validators and holding significant influence over the network, it forces a centralized structure, increasing the risk of manipulation and governance issues. In contrast, Keeta offers a completely decentralized version of the network. By employing Delegated Proof of Stake (dPoS) and a Directed Acyclic Graph (DAG) structure, Keeta allows for a broader, more decentralized set of validators to govern the network and enables the network participants to choose these validators. Keeta also provides the ability to launch subnets, which are private versions of the network that can be utilized for a variety of use cases, allowing operations to be completed outside of the main blockchain and in a centralized environment, but with the same performance.

Ripple's XRP Ledger lacks capabilities in tokenization, built-in rules, identity management, and flexibility for handling diverse use cases. Its functionalities are largely limited to payment-related operations, restricting its versatility. Although Ripple is developing more advanced programmability—such as Ethereum Virtual Machine (EVM)-compatible sidechains to enable smart contracts—these efforts are still in progress and may inherit issues similar to Ethereum, including scalability challenges and high transaction costs.

Keeta, on the other hand, eliminates the need for external smart contracts by incorporating a built-in rule engine and tokenization system that covers most of the real-world use cases. By avoiding the complexities of general-purpose smart contracts, Keeta inherently provides functionalities like data access, identity management, and customizable rules within the platform.

<table data-full-width="true"><thead><tr><th>KEETA</th><th>RIPPLE</th></tr></thead><tbody><tr><td>10,000,000 TPS</td><td>1,500 TPS</td></tr><tr><td>400 MS settlement times</td><td>3000 - 5000 MS settlement times</td></tr><tr><td>≈ $0.00005 transaction fee</td><td>≈ $0.0002 transaction fee</td></tr><tr><td>Delegated Proof-of-Stake</td><td>Ripple Consensus Algorithm</td></tr><tr><td>Native Token Implementation</td><td>Lacks Sophisticated Programmable Contract Capabilities</td></tr></tbody></table>


# Keeta vs. Solana

Solana is known for its high throughput, achieving tens of thousands of transactions per second (TPS), a major selling point for its ecosystem. However, this speed relies on vertically scaling nodes, requiring each to have substantial hardware resources. Nodes must invest in high-end hardware, but scalability limits remain. Upgrading hardware often necessitates shutting down nodes, leading to downtime and reduced reliability.

Keeta achieves high TPS through a flexible architecture that separates nodes from servers. By utilizing multiple servers per node, Keeta allows both vertical and horizontal scaling on demand without shutting down nodes. This design enables seamless hardware upgrades and scaling, ensuring consistent TPS performance under heavy load while maintaining decentralization and reducing bottlenecks.

Regulatory compliance is another area where Keeta surpasses Solana. While Solana lacks built-in mechanisms for Know Your Customer (KYC) and Anti-Money Laundering (AML) compliance — critical for financial institutions — Keeta integrates these protocols directly into its platform. This ensures that institutions can easily adhere to legal requirements, making Keeta more attractive to banks and other entities operating in regulated industries.

Keeta's support for sub-nets and private networks also gives it an edge over Solana. While Solana operates as a single blockchain, Keeta allows users to create customized sub-nets or private networks that operate independently yet leverage the main network's security and scalability. This is particularly valuable for enterprises requiring tailored solutions with specific privacy or operational needs, offering a level of customization that Solana does not provide.

<table data-full-width="true"><thead><tr><th>KEETA</th><th>SOLANA</th></tr></thead><tbody><tr><td>10,000,000 TPS</td><td>65,000 TPS</td></tr><tr><td>400 MS settlement times</td><td>400-600 MS settlement times</td></tr><tr><td>≈ $0.00005 transaction fee</td><td>≈ $0.00025 transaction fee</td></tr><tr><td>Delegated Proof-of-Stake</td><td>Proof of History &#x26; Proof of Stake</td></tr><tr><td>Native Token Implementation</td><td>SPL Tokens: Separate Standard for Creating/Managing tokens</td></tr></tbody></table>


# Overview (main vs test networks)

All examples use the `test` network so they can be easily run with faucet funds instead of real funds.  This main differences between `test` and `main` network interactions is which network alias, tokens and contract addresses should be used when interacting with the network and anchors.  The Anchor Metadata and Resolver handle identifying which Anchor's are available and what their endpoints are.  This makes it very easy to switch between `test` and `main`.

For the latest tokens and supported assets on the main network, refer to the Anchor Resolver Metadata.

{% embed url="<https://static.network.keeta.com/metadata/currencyMap>" %}

{% embed url="<https://static.network.keeta.com/metadata/services>" %}

Below is a summary of the chain/network ID's, token and contract addresses between the two environments.

## Network and Chain ID's

{% columns %}
{% column width="25%" %}
Chain

Keeta

Base

Ethereum

Arbitrum
{% endcolumn %}

{% column width="33.333333333333336%" %}
Test

1413829460

84532

11155111

421614
{% endcolumn %}

{% column width="41.66666666666665%" %}
Main

21378

8453

1

42161
{% endcolumn %}
{% endcolumns %}

For the most up to date tokens and contracts supported, use the [Anchor Metadata](/anchors/overview/anchor-resolver) to determine correct tokens and contract addresses.  Below is one example for USDC

## USDC Token on TEST

{% columns %}
{% column width="16.666666666666664%" %}
Chain

Keeta

Base

Ethereum

Arbitrum
{% endcolumn %}

{% column width="83.33333333333333%" %}
Test

`keeta_apna75yhhvnv4ei7ape55hndk4yepno7a7i2mhtiwahiygixjcnmvswxhnmnk`

`0x036CbD53842c5426634e7929541eC2318f3dCF7e`

`0x1c7D4B196Cb0C7B01d743fbc6116a902379C7238`

`0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d`
{% endcolumn %}
{% endcolumns %}

## USDC Token on MAIN

{% columns %}
{% column width="16.666666666666664%" %}
Chain

Keeta

Base

Ethereum

Arbitrum
{% endcolumn %}

{% column width="83.33333333333333%" %}
Test

`keeta_amnkge74xitii5dsobstldatv3irmyimujfjotftx7plaaaseam4bntb7wnna`

`0x833589fcd6edb6e08f4c7c32d4f71b54bda02913`

`0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48`

`0xaf88d065e77c8cC2239327C5EDb3A432268e5831`
{% endcolumn %}
{% endcolumns %}


# Tokenizing Real-World Assets

This guide shows how to create a **non-fungible token (NFT)** that represents a real-world asset — like a document, a product, or a right of ownership — on the Keeta Network.

{% stepper %}
{% step %}

### Prepare your accounts

You'll need two accounts:

* A **signer account** to send the transaction
* An **authority account** to sign the metadata (can be the same in testing)

```typescript
const signer = KeetaNet.lib.Account.fromSeed(DEMO_SEED, 0);
const authority = KeetaNet.lib.Account.fromSeed(DEMO_SEED, 1);
```

{% endstep %}

{% step %}

### Create metadata for your real-world asset

This includes:

* A unique ID for the asset (e.g. serial number, URL, or database ref)
* A digital signature proving the authority account approved it

```typescript
const assetIdBuffer = Buffer.from(ASSET_ID, 'utf-8');
const signatureBuffer = await authority.sign(assetIdBuffer);

const metadata = {
  asset_id: ASSET_ID,
  authority: authority.publicKeyString.get(),
  signature: signatureBuffer.toString('base64')
};

const metadataBase64 = Buffer.from(JSON.stringify(metadata)).toString('base64');
```

{% endstep %}

{% step %}

### Start a transaction builder

This prepares your transaction to include all operations in one publishable bundle.

```typescript
const builder = client.initBuilder();
builder.updateAccounts({
  signer,
  account: signer
});
```

{% endstep %}

{% step %}

### Create the token account

This is where the NFT will live. It’s a new account of type `TOKEN`.

```typescript
const token = builder.generateIdentifier(KeetaNet.lib.Account.AccountKeyAlgorithm.TOKEN);
await client.computeBuilderBlocks(builder); // seal block so token exists
```

{% endstep %}

{% step %}

### Make it non-fungible

Set the token’s supply to `1`. That means only one account can own this NFT at a time.

```typescript
builder.modifyTokenSupply(1n, { account: token.account });
```

{% endstep %}

{% step %}

### Attach metadata and permissions

Now link your signed metadata to the token, and set permissions so it can be held by any user.

```typescript
builder.setInfo({
  name: 'RWA-DEMO',
  description: 'Token representing a real-world asset',
  metadata: metadataBase64,
  defaultPermission: new KeetaNet.lib.Permissions(['ACCESS'], [])
}, {
  account: token.account
});
```

{% endstep %}

{% step %}

### Publish the transaction

Finalize and submit your transaction to the Keeta Network.

```typescript
await client.computeBuilderBlocks(builder);
await client.publishBuilder(builder);
```

{% endstep %}
{% endstepper %}

Your NFT now lives on-chain and holds signed, verifiable metadata that links it to a real-world asset.

To see its address:

```typescript
console.log('Token address:', token.account.publicKeyString.get());
```

## Full Code Example

```typescript
const KeetaNet = require('@keetanetwork/keetanet-client');

const DEMO_SEED = 'D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0D3M0';
const ASSET_ID = 'asset://unique-asset-id-001';

async function main() {
  // 1️⃣ Create two accounts from the seed:
  // - signer: sends the transaction
  // - authority: signs the metadata (can be same as signer for demo)
  const signer = KeetaNet.lib.Account.fromSeed(DEMO_SEED, 0);
  const authority = KeetaNet.lib.Account.fromSeed(DEMO_SEED, 1);

  // 2️⃣ Connect to the Keeta test network using the signer account
  const client = KeetaNet.UserClient.fromNetwork('test', signer);

  // 3️⃣ Create and sign the asset metadata
  const assetIdBuffer = Buffer.from(ASSET_ID, 'utf-8');
  const signatureBuffer = await authority.sign(assetIdBuffer);

  const metadata = {
    asset_id: ASSET_ID,
    authority: authority.publicKeyString.get(),
    signature: signatureBuffer.toString('base64')
  };

  const metadataBase64 = Buffer.from(JSON.stringify(metadata)).toString('base64');

  // 4️⃣ Start building a transaction
  const builder = client.initBuilder();
  builder.updateAccounts({
    signer,
    account: signer
  });

  // 5️⃣ Generate a new token account (this will be your NFT)
  const token = builder.generateIdentifier(KeetaNet.lib.Account.AccountKeyAlgorithm.TOKEN);

  // 6️⃣ Compute a block to seal the token creation (required before you can modify it)
  await client.computeBuilderBlocks(builder);

  // 7️⃣ Set the token supply to 1 — making it a non-fungible token
  builder.modifyTokenSupply(1n, {
    account: token.account
  });

  // 8️⃣ Attach the metadata and set default permissions (allow others to hold it)
  builder.setInfo({
    name: 'RWA-DEMO',
    description: 'Non-Fungible Token representing a real-world asset',
    metadata: metadataBase64,
    defaultPermission: new KeetaNet.lib.Permissions(['ACCESS'], [])
  }, {
    account: token.account
  });

  // 9️⃣ Compute and publish all blocks to the network
  await client.computeBuilderBlocks(builder);
  await client.publishBuilder(builder);

  // 🔚 Done — log the token's public address
  console.log('✅ RWA Token created at:', token.account.publicKeyString.get());
}

main().catch(console.error);
```


# Add KYC Certificate

This guide shows the steps needed to obtain and add a KYC certificate to an account. It uses the Footprint sandbox anchor for demonstration purposes and the Anchor Resolver to search for the provider.

{% stepper %}
{% step %}

### Prepare your account

Generate the account to add the certificate to

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
import * as KeetaAnchor from "@keetanetwork/anchor";
const userAccount = Account.fromSeed(seed, 0);
```

{% endcode %}
{% endtab %}

{% tab title="C#" %}

<pre class="language-csharp" data-expandable="true"><code class="lang-csharp"><strong>using KeetaNet.Anchor;
</strong>using Account userAccount = runtime.Accounts.FromSeed(seed, 0, "ecdsa_secp256k1");w
</code></pre>

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Create a UserClient

A UserClient is used to interact with Keeta

{% tabs %}
{% tab title="Typescript" %}

```typescript
await using userClient = KeetaAnchor.KeetaNet.UserClient.fromNetwork(
  network,
	userAccount
);
```

{% endtab %}

{% tab title="C#" %}

```csharp
using UserClient userClient = UserClient.FromNetwork(Network, userAccount);
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Create an Anchor KYC Client

Anchor clients use a UserClient and add Anchor specific interactions like KYC, FX and Asset Movement

{% tabs %}
{% tab title="Typescript" %}

```typescript
const kycClient = new KeetaAnchor.KYC.Client(userClient);
```

{% endtab %}

{% tab title="C#" %}

```csharp
using KycClient kycClient = runtime.CreateKycClient(
			TestnetEndpoints.NodeApi,
			userClient.NetworkAddress,
			userAccount);
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Get available providers

This will search the Anchor Registry for KYC Anchors that support the provided details (eg. US country code in this example)

{% tabs %}
{% tab title="Typescript" %}

```typescript
const providers = await kycClient.createVerification({
  countryCodes: ['US'],
	account: userAccount
});
```

{% endtab %}

{% tab title="C#" %}

```csharp
IReadOnlyList<KycProvider> providers = await kycClient.GetProviders(Countries, cancellationToken);
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Start the KYC Verification

We'll use the provider matching the "Footprint" id in this step. Sandbox has 2 providers, a basic demo provider as well as the Footprint provider. Then start the verification. The result of starting verification returns a webURL that can be used to complete the KYC Verification with the KYC Anchor.

{% tabs %}
{% tab title="Typescript" %}

```typescript
const provider = providers.find((p) => p.id === "Footprint");
const verification = await provider.startVerification();

console.log(verification.webURL);
```

{% endtab %}

{% tab title="C#" %}

```csharp
KycProvider? provider = providers.FirstOrDefault(candidate => candidate.Id == "Footprint");

VerificationOutcome created = await kycClient.StartVerification(provider, Countries, cancellationToken: cancellationToken);
if (created.Ready is null) {
  Verification verification = created.Ready;
  Console.WriteLine(verification.WebUrl);
}
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Get KYC Certificate from Provider

Once the KYC Verification is complete, we can obtain the KYC Certificate from the Anchor.

{% tabs %}
{% tab title="Typescript" %}

```typescript
const certificates = await verification.getCertificates();
```

{% endtab %}

{% tab title="C#" %}

```csharp
CertificatesOutcome results = await kycClient.GetCertificates(provider, verification.Id, cancellationToken);
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Add KYC Certificate to User Account

The KYC Anchor returns the leaf certificate and intermediates that can be used to construct the chain of authority. We can then add the certificates to the account. If the Anchor returns multiple certificates we can iterate through all of them.

{% tabs %}
{% tab title="Typescript" %}

```typescript
for (const [i, certGroup] of certificates.results.entries()) {
  // Re-wrap with the user's account as the subject key so sensitive
	// attributes (PII) can be decrypted
	const cert = new KeetaAnchor.lib.Certificates.Certificate(
		certGroup.certificate.toPEM(),
		{ subjectKey: userAccount }
	);

	// Construct the Certificate Bundle from Intermediates
	const intermediates = certGroup.intermediates
	  ? new KeetaAnchor.KeetaNet.lib.Utils.Certificate.CertificateBundle([...certGroup.intermediates])
		: null;
			
	// Attach the certificate to the user's account onchain
	await userClient.modifyCertificate(
	  KeetaAnchor.KeetaNet.lib.Block.AdjustMethod.ADD,
		cert,
		intermediates
	);
}
```

{% endtab %}

{% tab title="C#" %}

```csharp
foreach (IssuedCertificate issued in results.Ready.Results)
{
  index++;
	using KycCertificate certificate = runtime.KycCertificates.Parse(issued.Value);
	Console.WriteLine($"\n  Certificate {index}:");
	using CryptoCertificate baseCertificate = certificate.Base();
	Console.WriteLine($"    Subject: {baseCertificate.Subject}");
	Console.WriteLine($"    Valid: {certificate.IsValidAt(DateTimeOffset.UtcNow)}");

	if (issued.Intermediates.Count > 0)
	{
		Console.WriteLine($"    Intermediate certificates: {issued.Intermediates.Count}");
	}

	if (certificate.GetAttributeNames().Contains("fullName", StringComparer.Ordinal))
	{
		KycAttributeValue fullName = certificate.GetAttribute("fullName", userAccount);
		Console.WriteLine($"    Full name (decrypted): {fullName.AsText()}");
	}

	await userClient.ModifyCertificateAsync(
		runtime,
		issued.Value,
		issued.Intermediates,
		cancellationToken: cancellationToken).ConfigureAwait(false);
}
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Read Certificates from User Account

{% tabs %}
{% tab title="Typescript" %}

```typescript
const onChain = await userClient.client.getAllCertificates(userAccount);
console.log(`\nOn-chain certificates for this account: ${onChain.length}`);
```

{% endtab %}

{% tab title="C#" %}

```csharp
IReadOnlyList<IssuedCertificate> onChain = await nodeClient.GetAllCertificates(userAccount, cancellationToken);
Console.WriteLine($"\nOn-chain certificates for this account: {onChain.Count}");
```

{% endtab %}
{% endtabs %}
{% endstep %}
{% endstepper %}

## Full Code Example

{% tabs %}
{% tab title="Typescript" %}
{% @github-files/github-code-block url="<https://github.com/KeetaNetwork/keetanet-examples/blob/main/src/anchor/kyc-client.ts>" %}
{% endtab %}

{% tab title="C#" %}
{% @github-files/github-code-block url="<https://github.com/KeetaNetwork/keetanet-examples/blob/main/csharp/src/Anchor/KycClient.cs>" %}
{% endtab %}
{% endtabs %}


# Share KYC Attributes

This guide shows how to use the Anchor SDK to share KYC attributes with an Anchor that requires KYC using an existing on-chain KYC certificate.

{% stepper %}
{% step %}

### Identify Share KYC Is Required

When an Anchor requires KYC it will return an error of type `KeetaAssetMovementAnchorKYCShareNeededError`  and will include the required KYC attributes and which Keeta account they should be shared with.  Sharing KYC attributes uses encrypted containers that only grant the specific principals access to decrypt the container so it remains encrypted in transit.\
`KeetaAssetMovementAnchorKYCShareNeededError` members shown below.

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
    readonly shareWithPrincipals: Account[];
    readonly neededAttributes: string[] | undefined;
    readonly tosFlow: KeetaAssetMovementAnchorKYCExternalURLFlow | undefined;
    readonly acceptedIssuers: {
        name: string;
        value: string;
    }[][];
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Select KYC Certificate

Full example below shows more in depth details on identifying a KYC certificate on the users account, for example an Anchor may require a certificate from a specific issuer. At a high level it follows these steps to get the certificates for the account from the network and build the Certificate.

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
// Get account certificates
const records = await userClient.client.getAllCertificates(account);

// Build the intermediates from the first result
const intermediateSet = records[0].intermediates
	? new Set(records[0].intermediates.getCertificates())
	: undefined;

// Construct the certificate from the first result
const certificate = new KeetaAnchor.lib.Certificates.Certificate(records[0].certificate.toPEM(), {
	subjectKey: userAccount,
	store: {
		root: trustedRoots,
		intermediate: intermediateSet ?? new Set()
	}
});
```

{% endcode %}
{% endtab %}

{% tab title="C#" %}
{% code expandable="true" %}

```csharp
IReadOnlyList<OnChainCertificate> records = await nodeClient.GetAllCertificates(userAccount, cancellationToken);

const intermediateSet = record.intermediates
  ? new Set(record.intermediates.getCertificates())
	: undefined;

if (trustedRoots.size > 0) {
	const cert = new KeetaAnchor.lib.Certificates.Certificate(record.certificate.toPEM(), {
		subjectKey: account,
		store: {
			root: trustedRoots,
			intermediate: intermediateSet ?? new Set()
		}
	});
}
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Build the Sharable Certificate Attributes

Create the encrypted container to share certificate attributes, this selects only the attributes requested by the Anchor.

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
const sharable = await KeetaAnchor.lib.Certificates.SharableCertificateAttributes.fromCertificate(
  certificate,
  intermediateSet,
	neededAttributes
);
```

{% endcode %}
{% endtab %}

{% tab title="C#" %}
{% code expandable="true" %}

```csharp
SharableCertificateAttributes sharable = await runtime.Sharables.FromCertificate(
  certificate,
  userAccount,
  httpClient,
	intermediates,
	neededAttributes,
	cancellationToken);
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Grant Access for the Anchor

Granting access to the encrypted container allows the principal to decrypt the attributes.  This enables the sensitive attributes to be encrypted in transit and only viewable by the recipient.

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
for (const principal of shareWithPrincipals) {
  await sharable.grantAccess(principal);
}
```

{% endcode %}
{% endtab %}

{% tab title="C#" %}
{% code expandable="true" %}

```csharp
foreach (string principalAddress in shareWithPrincipals)
{
  using Account principal = runtime.Accounts.FromPublicKeyString(principalAddress);
  sharable.GrantAccess([principal]);
}
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Share the Attributes with the Anchor

Anchor providers that require KYC have an endpoint to share the attributes.  The Anchor Client SDK handles the request details.

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
await provider.shareKYCAttributes({
  account,
	attributes: sharable
});
```

{% endcode %}
{% endtab %}

{% tab title="C#" %}
{% code expandable="true" %}

```csharp
await assetMovementClient.ShareKycAttributesAndWait(
  provider,
	new AssetShareKycRequest(sharable.ToPem()),
	cancellationToken: cancellationToken);
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Handle Additional Onboarding Steps

An Anchor may require some additional onboarding steps.  Like adding it's own certificate to the account or granting permissions. These will be identified with an error of type `KeetaAssetMovementAnchorUserActionNeededError`

The `actionsNeeded` attribute of the error can be used to complete the actions and the UserClientBuilder can be used to construct a Keeta block to publish the actions.

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
const builder = userClient.initBuilder();
KeetaAssetMovementAnchorUserActionNeededError.addOperationsToBuilder(actionsNeeded, builder);
await userClient.publishBuilder(builder);
```

{% endcode %}
{% endtab %}

{% tab title="C#" %}
{% code expandable="true" %}

```csharp
// C#: translate the required actions into ledger operations and publish them in one block.
await UserActions.Execute(runtime, userClient, userActionNeeded, cancellationToken);
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}
{% endstepper %}

## Full Code Example

{% tabs %}
{% tab title="TypeScript" %}
{% @github-files/github-code-block url="<https://github.com/KeetaNetwork/keetanet-examples/blob/main/src/anchor/kyc-client-sharekyc.ts>" %}
{% endtab %}

{% tab title="C#" %}
{% @github-files/github-code-block url="<https://github.com/KeetaNetwork/keetanet-examples/blob/main/csharp/src/Anchor/KycClientShareKyc.cs>" %}
{% endtab %}
{% endtabs %}


# Fiat Deposit From USDC

This example shows how to deposit USDC from the Arbitrum Network, which is converted to USD on Keeta.  It assumes KYC has already been completed and shared with the Anchor.  See the two KYC guides [Add KYC Certificate](https://app.gitbook.com/o/qYWvPguljbNCiIZoKq7U/sites/site_VPB12/s/pitcpcamWc0BKEe28I1D/~/edit/~/changes/85/guides/add-kyc-certificate) and [Share KYC Attributes](https://app.gitbook.com/o/qYWvPguljbNCiIZoKq7U/sites/site_VPB12/s/pitcpcamWc0BKEe28I1D/~/edit/~/changes/85/guides/share-kyc-attributes) for more details on handling KYC.

To run the example on the main network, the following changes are needed.

* Change network from `test` to `main`
* Use the main network Keeta USD Token - `keeta_amnkge74xitii5dsobstldatv3irmyimujfjotftx7plaaaseam4bntb7wnna`
* Use the main Arbitrum Network USDC Contract - `0xaf88d065e77c8cC2239327C5EDb3A432268e5831`
* Use the main Arbitrum Chain ID - `42161`

{% stepper %}
{% step %}

### Setup Account and Anchor Client

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
const account = Account.fromSeed(seed.trim(), 0);
await using userClient = KeetaAnchor.KeetaNet.UserClient.fromNetwork('test', account);
const assetMovementClient = new KeetaAnchor.AssetMovement.Client(userClient);
```

{% endcode %}
{% endtab %}

{% tab title="C#" %}
{% code expandable="true" %}

```csharp
using Account userAccount = runtime.Accounts.FromSeed(seed, 0, "ecdsa_secp256k1");

using AssetMovementClient assetMovementClient = runtime.CreateAssetMovementClient(
			Constants.NodeApi,
			userClient.NetworkAddress,
			userAccount);

using NodeClient nodeClient = runtime.CreateNodeClient(Constants.NodeApi);
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Establish Source Location and Destination

In this example the source is the Arbitrum USDC contract and destination is the Keeta USD Token on the Test Network. This establishes the asset pair that will be used for the transfer.

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
const ARBITRUM_CHAIN_ID = 421614n;
const ARBITRUM_USDC_ASSET = 'evm:0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d';
const KEETA_USD_ASSET = Account.fromPublicKeyString('keeta_any4zllibya6fum3lsoimxmnmeo57nklxlh4c6d6xosfacarfaa3knkiprkmm');
const ASSET_PAIR = { from: ARBITRUM_USDC_ASSET, to: KEETA_USD_ASSET } as const;
```

{% endcode %}
{% endtab %}

{% tab title="C#" %}
{% code expandable="true" %}

```csharp
AssetOrPair assetPair = AssetOrPair.Pair(Constants.ArbitrumUsdcAsset, Constants.KeetaUsdAsset);
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Identify Provider with Anchor Resolver

Use the Asset Movement Anchor Client to identify providers that can handle the transfer.

{% tabs %}
{% tab title="TypeScript" %}

```typescript
// Find Asset Movement providers that support Arbitrum => Keeta USD
const providers = await assetMovementClient.getProvidersForTransfer({
	// USDC (Arbitrum) => USD (Keeta)
	asset: ASSET_PAIR,
	// Source: Arbitrum
	from: {
		type: 'chain',
		chain: {
			type: 'evm',
			chainId: ARBITRUM_CHAIN_ID
		}
	},
	// Destination: Keeta Network
	to: {
		type: 'chain',
		chain: {
			type: 'keeta',
			networkId: userClient.network
		}
	}
});
```

{% endtab %}

{% tab title="C#" %}
{% code expandable="true" %}

```csharp
IReadOnlyList<AssetProvider> providers = await assetMovementClient.GetProvidersForTransfer(
  new AssetProviderSearch(
    Asset: assetPair,
    From: Constants.ArbitrumSepoliaLocation,
		To: keetaDestination),
cancellationToken);
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Create Persistent Deposit Address

A persistent deposit address is an account on another cryptocurrency network that automatically transfers tokens deposited on the other network to equivalent Keeta tokens.

{% tabs %}
{% tab title="TypeScript" %}

```typescript
const persistentAddressResponse = await providers[0].createPersistentForwardingAddress({
	account: userAccount,
	asset: ASSET_PAIR,
	sourceLocation: {
		type: 'chain',
		chain: { type: 'evm', chainId: ARBITRUM_CHAIN_ID }
	},
	destinationLocation: {
		type: 'chain',
		chain: { type: 'keeta', networkId: userClient.network }
	},
	destinationAddress: userAccount.publicKeyString.get()
});

console.log(persistentAddressResponse.address);
```

{% endtab %}

{% tab title="C#" %}
{% code expandable="true" %}

```csharp
JsonElement persistentAddressResponse = await assetMovementClient.CreatePersistentForwardingAddress(
  providers[0],
  new AssetCreateAddressRequest(
  SourceLocation: Constants.ArbitrumSepoliaLocation,
  Asset: assetPair,
	DestinationLocation: keetaDestination,
	DestinationAddress: userAccount.Address),
cancellationToken);
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Deposit USDC Funds

On the TEST network, funds can be deposited to the address returned from the previous step using any Arbitrum wallet connected to the test (Sepolia) network.  Alternatively, Circle's faucet `https://faucet.circle.com/` can be used to deposit USDC using Arbitrum Sepolia.
{% endstep %}

{% step %}

### List Anchor Transactions

The Anchor will show transactions for a given account and can be used to identify when the new transaction has been completed.

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
const transactionResponse = await providers[0].listTransactions({
	account: userAccount,
	persistentAddresses: [{
		location: {
			type: 'chain',
				chain: {
					type: 'evm',
					chainId: ARBITRUM_CHAIN_ID
				}
			},
		persistentAddress: persistentAddressResponse.address.toString()
	}]
});
```

{% endcode %}
{% endtab %}

{% tab title="C#" %}
{% code expandable="true" %}

```csharp
AssetTransactionPage transactionResponse = await assetMovementClient.ListTransactions(
  provider,
  new AssetListTransactionsRequest(
    PersistentAddresses:
    [
		  new AssetPersistentAddressFilter(
  		  Constants.ArbitrumSepoliaLocation,
	  		persistentAddress),
		]),
	monitorToken);
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Check Keeta Account History

Another method to verify the transaction has completed is to check the account history on Keeta.

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
const accountHistory = await userClient.history();
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}
{% endstepper %}

## Full Code Example

{% tabs %}
{% tab title="TypeScript" %}
{% @github-files/github-code-block url="<https://github.com/KeetaNetwork/keetanet-examples/blob/main/src/anchor/asset-movement-fiat-deposit-from-crypto.ts>" %}
{% endtab %}

{% tab title="C#" %}
{% @github-files/github-code-block url="<https://github.com/KeetaNetwork/keetanet-examples/blob/main/csharp/src/Anchor/AssetMovementFiatDepositFromCrypto.cs>" %}
{% endtab %}
{% endtabs %}


# Fiat Deposit from Bank

This example shows how to obtain bank deposit information from an Anchor that supports inbound payments that are minted on Keeta.  It assumes KYC has already been completed and shared with the Anchor.  See the two KYC guides [Add KYC Certificate](https://app.gitbook.com/o/qYWvPguljbNCiIZoKq7U/sites/site_VPB12/s/pitcpcamWc0BKEe28I1D/~/edit/~/changes/85/guides/add-kyc-certificate) and [Share KYC Attributes](https://app.gitbook.com/o/qYWvPguljbNCiIZoKq7U/sites/site_VPB12/s/pitcpcamWc0BKEe28I1D/~/edit/~/changes/85/guides/share-kyc-attributes) for more details on handling KYC.

To run the example on the main network, the following changes are needed.

* Change network from `test` to `main`
* Use the main network Keeta USD Token - `keeta_amnkge74xitii5dsobstldatv3irmyimujfjotftx7plaaaseam4bntb7wnna`

{% stepper %}
{% step %}

### Setup Account and Anchor Client

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
const account = Account.fromSeed(seed.trim(), 0);
await using userClient = KeetaAnchor.KeetaNet.UserClient.fromNetwork('test', account);
const assetMovementClient = new KeetaAnchor.AssetMovement.Client(userClient);
```

{% endcode %}
{% endtab %}

{% tab title="C#" %}
{% code expandable="true" %}

```csharp
using Account userAccount = runtime.Accounts.FromSeed(seed, 0, "ecdsa_secp256k1");

UserClient userClient = UserClient.FromNetwork("test", userAccount);
using AssetMovementClient assetMovementClient = runtime.CreateAssetMovementClient(
  Constants.NodeApi,
	userClient.NetworkAddress,
	userAccount);
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Establish Source Location and Destination

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
const KEETA_USD_ASSET = Account.fromPublicKeyString('keeta_any4zllibya6fum3lsoimxmnmeo57nklxlh4c6d6xosfacarfaa3knkiprkmm');
const US_BANK_SOURCE = { type: 'bank-account', account: { type: 'us' }} as const;

const keetaDestination = {
	type: 'chain',
	chain: { type: 'keeta', networkId: userClient.network }
} as const;

const assetPair = { from: 'USD' as const, to: KEETA_USD_ASSET };
```

{% endcode %}
{% endtab %}

{% tab title="C#" %}
{% code expandable="true" %}

```csharp
string KeetaUsdAsset = "keeta_any4zllibya6fum3lsoimxmnmeo57nklxlh4c6d6xosfacarfaa3knkiprkmm";
string BankAccountUsLocation = "bank-account:us";
string keetaDestination = $"chain:keeta:{userClient.Network}";

AssetOrPair assetPair = AssetOrPair.Pair("USD", KeetaUsdAsset);
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Identify Provider with Anchor Resolver

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
const providers = await assetMovementClient.getProvidersForTransfer({
	asset: assetPair,
	from: US_BANK_SOURCE,
	to: keetaDestination
});
```

{% endcode %}
{% endtab %}

{% tab title="C#" %}
{% code expandable="true" %}

```csharp
IReadOnlyList<AssetProvider> providers = await assetMovementClient.GetProvidersForTransfer(
  new AssetProviderSearch(
    Asset: assetPair,
    From: BankAccountUsLocation,
    To: keetaDestination),
cancellationToken);
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Request Deposit Information

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
const depositInfo = await provider.createPersistentForwardingAddress({
	account,
	asset: assetPair,
	sourceLocation: US_BANK_SOURCE,
	destinationLocation: keetaDestination,
	destinationAddress: account.publicKeyString.get()
});
```

{% endcode %}
{% endtab %}

{% tab title="C#" %}
{% code expandable="true" %}

```csharp
JsonElement depositInfo = await assetMovementClient.CreatePersistentForwardingAddress(
  provider,
  request,
  cancellationToken);
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Make an ACH Payment and Check Balance

There are multiple ways to check if an account has changed.  Simplest is to query the account balance for a given token.

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
const accountUSDBalance = await userClient.balance(KEETA_USD_ASSET);
```

{% endcode %}
{% endtab %}

{% tab title="C#" %}
{% code expandable="true" %}

```csharp
using Account usdToken = runtime.Accounts.FromPublicKeyString(Constants.KeetaUsdAsset);
BigInteger currentBalance = await nodeClient.GetAccountBalance(userAccount, usdToken, cancellationToken);
```

{% endcode %}
{% endtab %}
{% endtabs %}

Alternatively, can poll the account history to identify changes affecting the account

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
const accountHistory = await userClient.history();
```

{% endcode %}
{% endtab %}
{% endtabs %}

Lastly, the UserClient has a change callback that can be implemented to connect via websocket and listen for changes.  It also uses a periodic polling fallback in case the websocket disconnects.

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
const userClientChangeListener = userClient.on('change', function(data) {
   console.log(data);
});
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}
{% endstepper %}

## Full Code Example

{% tabs %}
{% tab title="TypeScript" %}
{% @github-files/github-code-block url="<https://github.com/KeetaNetwork/keetanet-examples/blob/main/src/anchor/asset-movement-fiat-deposit-from-bank.ts>" %}
{% endtab %}

{% tab title="C#" %}
{% @github-files/github-code-block url="<https://github.com/KeetaNetwork/keetanet-examples/blob/main/csharp/src/Anchor/AssetMovementFiatDepositFromBank.cs>" %}
{% endtab %}
{% endtabs %}


# Fiat Withdraw to Bank

This example shows how to initiate a withdraw/payment request from an Anchor that supports outbound payments to a bank.  It assumes KYC has already been completed and shared with the Anchor and that the account holds USD that can be sent.  See the two KYC guides [Add KYC Certificate](https://app.gitbook.com/o/qYWvPguljbNCiIZoKq7U/sites/site_VPB12/s/pitcpcamWc0BKEe28I1D/~/edit/~/changes/85/guides/add-kyc-certificate) and [Share KYC Attributes](https://app.gitbook.com/o/qYWvPguljbNCiIZoKq7U/sites/site_VPB12/s/pitcpcamWc0BKEe28I1D/~/edit/~/changes/85/guides/share-kyc-attributes) for more details on handling KYC.

To run the example on the main network, the following changes are needed.

* Change network from `test` to `main`
* Use the main network Keeta USD Token - `keeta_amnkge74xitii5dsobstldatv3irmyimujfjotftx7plaaaseam4bntb7wnna`

{% stepper %}
{% step %}

### Setup Account and Anchor Client

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
const account = Account.fromSeed(seed.trim(), 0);
await using userClient = KeetaAnchor.KeetaNet.UserClient.fromNetwork('test', account);
const assetMovementClient = new KeetaAnchor.AssetMovement.Client(userClient);
```

{% endcode %}
{% endtab %}

{% tab title="C#" %}
{% code expandable="true" %}

```csharp
using Account userAccount = runtime.Accounts.FromSeed(seed, 0, "ecdsa_secp256k1");

UserClient userClient = UserClient.FromNetwork("test", userAccount);
using AssetMovementClient assetMovementClient = runtime.CreateAssetMovementClient(
  Constants.NodeApi,
	userClient.NetworkAddress,
	userAccount);
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Identify Providers for Transfer

The asset movement client can search the Anchor Resolver metadata to identify providers that can handle the source and destination of the transfer.

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
const providers = await assetMovementClient.getProvidersForTransfer({
  asset: {
    from: Account.fromPublicKeyString('keeta_any4zllibya6fum3lsoimxmnmeo57nklxlh4c6d6xosfacarfaa3knkiprkmm'),
		to: 'USD'
	},
	from: {
	  type: 'chain',
		chain: { type: 'keeta', networkId: userClient.network }
	},
	to: {
		type: 'bank-account',
		account: { type: 'us' }
	}
});
```

{% endcode %}
{% endtab %}

{% tab title="C#" %}
{% code expandable="true" %}

```csharp
using AssetMovementClient assetMovementClient = runtime.CreateAssetMovementClient(
    Constants.NodeApi,
    userClient.NetworkAddress,
    userAccount);

// asset: { from: KeetaUsdAsset, to: 'USD' }
AssetOrPair assetPair = AssetOrPair.Pair(Constants.KeetaUsdAsset, "USD");

// from: chain keeta (networkId)  ->  to: bank-account us
string keetaSource = $"chain:keeta:{userClient.Network}";
IReadOnlyList<AssetProvider> providers = await assetMovementClient.GetProvidersForTransfer(
    new AssetProviderSearch(
        Asset: assetPair,
        From: keetaSource,
        To: Constants.BankAccountUsLocation),
    cancellationToken);
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Construct the Bank Recipient

The Anchor SDK provides specific types for different bank deposits as `BankAccountAddressResolved`.  For US Bank accounts specifically it would use the type `UsBankAccountResolved`

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
const bankRecipient: UsBankAccountResolved = {
	type: 'bank-account',
	accountType: 'us',
	accountNumber: '99911330003085852',
	routingNumber '021000021',
	accountTypeDetail: 'checking',
	accountOwner: {
		type: 'individual',
		firstName: 'John',
		lastName: 'Doe'
	},
	accountAddress: {
		line1: '123 Main Street',
		line2: 'Apt. 1,
		city: 'White Plains',
		subdivision: 'NY',
		postalCode: '10601',
		country: 'US'
	}
}
```

{% endcode %}
{% endtab %}

{% tab title="C#" %}
{% code expandable="true" %}

```csharp
var bankRecipient = new
{
    type = "bank-account",
    accountType = "us",
    accountNumber = "99911330003085852",
    routingNumber = "021000021",
    bankName = "Chase",
    accountTypeDetail = "checking",
    accountOwner = new
    {
        type = "individual",
        firstName = "John",
        lastName = "Doe",
    },
    accountAddress = new
    {
        line1 = "123 Main Street",
        line2 = "Apt. 1",
        city = "White Plains",
        subdivision = "NY",
        postalCode = "10601",
        country = "US",
    },
};
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Initiate Transfer

Initiating a transfer with the anchor begins the process to submit the payment.  The Anchor will return a specific ID to use as part of the transfer and what account to send the payment too.  These are returned as "instructions" that can be used to construct the Keeta block on the network.

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
const transfer = await providers[0].initiateTransfer({
  account,
	asset: {
	  from: Account.fromPublicKeyString('keeta_any4zllibya6fum3lsoimxmnmeo57nklxlh4c6d6xosfacarfaa3knkiprkmm'),
		to: 'USD'
	},
	from: {
	  location: {
		  type: 'chain',
			chain: { type: 'keeta', networkId: userClient.network }
		}
	},
	to: {
		location: {
			type: 'bank-account',
				account: { type: 'us' }
			},
		recipient: bankRecipient
		},
	value: 200n // $2.00
});
```

{% endcode %}
{% endtab %}

{% tab title="C#" %}
{% code expandable="true" %}

```csharp
AssetTransfer transfer = await assetMovementClient.InitiateTransfer(
	provider,
	new AssetTransferRequest(
	assetPair,
	new AssetTransferSource(keetaSource),
	new AssetTransferDestination(Constants.BankAccountUsLocation, bankRecipient),
		amountToWithdraw.ToString(CultureInfo.InvariantCulture)),
cancellationToken);
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Send the Payment to the Anchor

Use the Keeta Client SDK to construct a transaction on Keeta that sends the funds to the Anchor to process the outbound payment.

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
const instruction = transfer.instructions[0];
const anchorAccount = Account.toAccount(instruction.sendToAddress);
const usdTokenAccount = Account.fromPublicKeyString('keeta_any4zllibya6fum3lsoimxmnmeo57nklxlh4c6d6xosfacarfaa3knkiprkmm').assertKeyType(Account.AccountKeyAlgorithm.TOKEN);

// Send the required funds to the anchor account with the provided external identifier instructions
const sendBlockResult = await userClient.send(
	anchorAccount,
	200n,
	usdTokenAccount,
	instruction.external
);

```

{% endcode %}
{% endtab %}

{% tab title="C#" %}
{% code expandable="true" %}

```csharp
await userClient.Send(runtime, sendTo, amountToWithdraw, usdToken, external, cancellationToken);
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Poll the Anchor for Transfer Status

Once the payment has been submitted, the transfer status can be checked periodically until the transfer is complete.

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
const transactionResult = await transfer.getTransferStatus();

console.log(transactionResult.transaction.status);
```

{% endcode %}
{% endtab %}

{% tab title="C#" %}
{% code expandable="true" %}

```csharp
AssetTransferStatus transactionResult = await transfer.GetTransferStatus(monitorToken);
string? status = transactionResult.Transaction.TryGetProperty("status", out JsonElement statusElement)
  ? statusElement.GetString()
  : null;

Console.WriteLine($"Status: {status ?? transactionResult.Transaction.GetRawText()}");
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}
{% endstepper %}

## Full Code Example

{% tabs %}
{% tab title="TypeScript" %}
{% @github-files/github-code-block url="<https://github.com/KeetaNetwork/keetanet-examples/blob/main/src/anchor/asset-movement-fiat-withdraw-to-bank.ts>" %}
{% endtab %}

{% tab title="C#" %}
{% @github-files/github-code-block url="<https://github.com/KeetaNetwork/keetanet-examples/blob/main/csharp/src/Anchor/AssetMovementFiatWithdrawToBank.cs>" %}
{% endtab %}
{% endtabs %}


# Fiat Conversions With Anchor Chaining

This example shows how to use the Anchor Chaining SDK to perform fiat conversions.  FX via an external fiat anchor may require additional steps than just a simple FX Anchor.  The Anchor Chaining library helps facilitate the steps required (eg. CAD to EUR may require CAD to USD then USD to EUR). \
This example assumes KYC has been completed and onboarded wtih a fiat anchor that supports USD and EUR and that the account has existing USD balance.&#x20;

To run the example on the main network, the following changes are needed.

* Change network from `test` to `main`
* Use the main network Keeta USD Token - `keeta_amnkge74xitii5dsobstldatv3irmyimujfjotftx7plaaaseam4bntb7wnna`
* Use the main network Keeta EUR Token - `keeta_anutgo4o3yp5tvc6wjt4vzsehjbn7t2wylpxmam4d4ojtdkjj2yca2qoinfcs`

{% stepper %}
{% step %}

### Setup Account and Anchor Client

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
const account = Account.fromSeed(seed.trim(), 0);
await using userClient = KeetaAnchor.KeetaNet.UserClient.fromNetwork('test', account);
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Initialize Anchor Chaining

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
	const anchorChaining = new AnchorChaining({
		client: userClient
	});
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Construct the Conversion Request

This example performs a conversion between USD and EUR for $2.00

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
	const keetaLocation = `chain:keeta:${userClient.network}` as const;
	const conversionRequest = {
		source: {
			asset: Account.fromPublicKeyString('keeta_any4zllibya6fum3lsoimxmnmeo57nklxlh4c6d6xosfacarfaa3knkiprkmm');,
			location: keetaLocation,
			value: 200n,
			rail: 'KEETA_SEND' as const
		},
		destination: {
			asset: Account.fromPublicKeyString('keeta_amqsghqea5mv2476c44ahgt7xbawms5z76d7ffbzirkp56t2hn4bahoqljqeg');,
			location: keetaLocation,
			recipient: account.publicKeyString.get(),
			rail: 'KEETA_SEND' as const
		}
	};
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Search Available Paths

Searching the available anchor paths uses the Anchor Resolver metadata to determine what anchor's currently support the desired request and what paths can be used to get from source to destination.

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
const paths = await anchorChaining.getPaths(conversionRequest);
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Compute Plans

Getting the plans involves contacting each Anchor required to perform the request and obtaining the instructions for each step of the plan.

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
const plans = await anchorChaining.getPlans(conversionRequest);
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Execute the Plan

The Anchor Chaining library automatically generates the necessary blocks required to send from the users account to the anchor to perform the swaps.

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
const conversion = await plan.execute();
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Confirm Balance Updates

Once the conversion is completed balance changes can be viewed directly from the network

{% tabs %}
{% tab title="TypeScript" %}
{% code expandable="true" %}

```ts
const allBalances = await userClient.allBalances()
```

{% endcode %}
{% endtab %}
{% endtabs %}
{% endstep %}
{% endstepper %}

## Full Code Example

{% tabs %}
{% tab title="TypeScript" %}
{% @github-files/github-code-block url="<https://github.com/KeetaNetwork/keetanet-examples/blob/main/src/anchor/asset-movement-fiat-fx.ts>" %}
{% endtab %}
{% endtabs %}


# Using x402 on Keeta

[x402](https://www.x402.org/) is an open, HTTP-native payments standard built around the **402 Payment Required** status code. It lets any HTTP endpoint charge for access — per request, per token, per API call — without accounts, API keys, or subscription billing. A server responds `402` with machine-readable payment instructions, the client signs a payment and retries the request, and the server serves the resource once payment is confirmed.

This guide shows how to accept and make x402 payments **on Keeta**: as a client paying for a protected resource, as a server charging for one, and how the Keeta x402 facilitator verifies and settles those payments.

{% hint style="info" %}
x402 on Keeta is implemented by the `exact` payment scheme, defined in the [x402 Keeta scheme specification](https://github.com/x402-foundation/x402/blob/main/specs/schemes/exact/scheme_exact_keeta.md) and shipped as the reference [`@x402/keeta`](https://www.npmjs.com/package/@x402/keeta) package. The examples below follow the [Keeta x402 example app](https://github.com/sc4l3r/keeta-x402) developed by a community member.
{% endhint %}

## How x402 works on Keeta

Keeta's payment flow follows the general x402 pattern, with one Keeta-specific detail: instead of publishing a transaction itself, the client only **signs a block** and hands it to the resource server. The facilitator publishes it with a fee block it creates and signs itself as a single vote staple, so that the network fees are covered by the facilitator free of charge.

You can try it out yourself using the interactive demo on the [third-party Keeta x402 facilitator](https://facilitator.x402.keeta.com/).

Review the [third-party service notice](#third-party-service-notice) before using it.

{% stepper %}
{% step %}

### Client requests a resource

A client makes a normal HTTP request to a paid endpoint. No payment is attached yet.
{% endstep %}

{% step %}

### Server responds with 402

The resource server replies `402 Payment Required` with `PaymentRequirements`: `scheme` (`exact`), `network`, `asset`, `amount`, and `payTo`:

```json
{
  "x402Version": 2,
  "error": "Payment required",
  "resource": {
    "url": "https://facilitator.x402.keeta.com/weather",
    "description": "Get current weather data (demo endpoint)",
    "mimeType": "application/json"
  },
  "accepts": [
    {
      "scheme": "exact",
      "network": "keeta:1413829460",
      "amount": "1000000",
      "asset": "keeta_anyiff4v34alvumupagmdyosydeq24lc4def5mrpmmyhx3j6vj2uucckeqn52",
      "payTo": "keeta_aab7jq4fx7fl24qk36qpowlyasu3jekv4gdadrz6lhnda3n2upnj2dga7rxjf5a",
      "maxTimeoutSeconds": 300,
      "extra": {}
    },
    {
      "scheme": "exact",
      "network": "keeta:1413829460",
      "amount": "1000",
      "asset": "keeta_apna75yhhvnv4ei7ape55hndk4yepno7a7i2mhtiwahiygixjcnmvswxhnmnk",
      "payTo": "keeta_aab7jq4fx7fl24qk36qpowlyasu3jekv4gdadrz6lhnda3n2upnj2dga7rxjf5a",
      "maxTimeoutSeconds": 300,
      "extra": {}
    }
  ]
}
```

{% endstep %}

{% step %}

### Client signs a block (but doesn't publish it)

The client builds a Keeta block containing a single `SEND` operation that satisfies the server's requirements, signs it with its Keeta account, and serializes it (ASN.1 DER, Base64-encoded) into the `PaymentPayload`. It retries the request with a `PAYMENT-SIGNATURE` header carrying that payload.

```json
{
  "x402Version": 2,
  "payload": {
    "block": "MIH8AgEAAgRURVNUBQAYEzIwMjYwNzAyMTIwMTQwLjY5OVoEIgADAm6dCFFcNSQH4W8GXVEtN6TOPnKi0E8H989NDaWNL2kFAAQgJgvfwf+ZA9wHkB1phDyzxBjbk4PvTHbbtFrAfOEKqN4wUKBOMEwEIgAD9MOFv8q9cgrfoPdZeASptJFV4YYBxz5Z2jBtuqPanQwCAw9CQAQhA3CCl5XfALrRlHgMweHSwMkNcWLgyF6yL2Mwe+0+qnVKBEAGXojraVbFTUlN001p6XXlHpGeEtTsJGuPbCGWgoMLzksWivjcoyk0ZT/kaPFQICd8wa/m9jQIucYCR3B2xvZz"
  },
  "resource": {
    "url": "https://facilitator.x402.keeta.com/weather",
    "description": "Get current weather data (demo endpoint)",
    "mimeType": "application/json"
  },
  "accepted": {
    "scheme": "exact",
    "network": "keeta:1413829460",
    "amount": "1000000",
    "asset": "keeta_anyiff4v34alvumupagmdyosydeq24lc4def5mrpmmyhx3j6vj2uucckeqn52",
    "payTo": "keeta_aab7jq4fx7fl24qk36qpowlyasu3jekv4gdadrz6lhnda3n2upnj2dga7rxjf5a",
    "maxTimeoutSeconds": 300,
    "extra": {}
  }
}
```

{% endstep %}

{% step %}

### Server verifies via the facilitator

The resource server forwards the payload to the facilitator's `POST /verify`, which decodes the block and checks the signature, the operation, the amount/asset/recipient, and that the signer is authorized to send on behalf of the paying account.
{% endstep %}

{% step %}

### Facilitator settles and sponsors the fee

On `POST /settle`, the facilitator creates and signs its own fee block, collects votes from the network's representatives, and publishes the client's block together with the fee block as a single vote staple, sponsoring the fee itself. It returns a `SettlementResponse` with the transaction hash to the resource server.

```json
{
  "success": true,
  "payer": "keeta_aabqe3u5bbivynjea7qw6bs5kewtpjgohzzkfucpa7346tinuwgs62mfdqpmlsy",
  "transaction": "E247FE98A3925CB6D5D7CB8C66CE6BDE0C69646326EC3C0F60919C29528C6E99",
  "network": "keeta:1413829460"
}
```

{% endstep %}

{% step %}

### Server returns the resource

The resource server returns `200 OK` with the requested content and a settlement receipt header.

```json
{
  "report": {
    "weather": "sunny",
    "temperature": 70
  }
}
```

{% endstep %}
{% endstepper %}

{% hint style="warning" %}
Because Keeta accounts are ordered, per-account chains, a client **can't submit multiple payments from the same account in parallel**. It has to wait for each request to settle before signing the next one. Queue requests on a single account, or spread payments across multiple accounts if you need concurrency.
{% endhint %}

## Prerequisites

* A Keeta account for the paying client, and a Keeta account to receive payments on the server side
* Node.js, with the KeetaNet client SDK and the x402 packages for Keeta:

```bash
npm install @keetanetwork/keetanet-client @x402/core @x402/keeta @x402/fetch @x402/express
```

* For testnet development, fund your client account from the [testnet faucet](https://faucet.test.keeta.com/)

## Using x402 as a client

This is the "buyer" side, so an app or agent that pays to access someone else's endpoint. The example below uses fetch for payments but you can use any client the [x402 reference implementation supports](https://docs.x402.org/getting-started/quickstart-for-buyers#3-make-paid-requests-automatically) like axios.

See the full example [here](https://github.com/sc4l3r/keeta-x402/blob/main/apps/server/src/client.ts).

```typescript
import * as KeetaNet from "@keetanetwork/keetanet-client";
import { x402HTTPClient } from "@x402/core/http";
import { x402Client } from "@x402/core/client";
import { wrapFetchWithPayment } from "@x402/fetch";
import { ExactKeetaScheme, KEETA_TESTNET_CAIP2, toClientKeetaSigner } from "@x402/keeta";

// Derive the paying account. It must hold a balance of whatever asset it will pay with.
const account = KeetaNet.lib.Account.fromSeed(
  await KeetaNet.lib.Account.seedFromPassphrase(process.env.CLIENT_PASSPHRASE),
  0,
);

// toClientKeetaSigner opens a UserClient under the hood, dispose it when done via await using.
await using clientKeetaSigner = toClientKeetaSigner(account);

const client = new x402Client();
client.register(KEETA_TESTNET_CAIP2, new ExactKeetaScheme(clientKeetaSigner));

// Wrap fetch so 402 responses are handled transparently.
const fetchWithPayment = wrapFetchWithPayment(fetch, client);
const httpClient = new x402HTTPClient(client);

const response = await fetchWithPayment("https://facilitator.x402.keeta.com/weather", { method: "GET" });
const result = await httpClient.processResponse(response);

console.log("Response:", result.body);

if (result.paymentStatus === "settled") {
  console.log("Payment settled:", result.header);
} else if (result.paymentStatus === "settle_failed") {
  console.error("Settlement failed:", result.header);
}
```

`wrapFetchWithPayment` does the whole round trip: it sends the initial request, reads the `402`, signs a block with `clientKeetaSigner`, and retries with the `PAYMENT-SIGNATURE` header attached. `x402HTTPClient.processResponse` then gives you the payment status.

## Using x402 as a server

This is the "seller" side, so charging for an API route. The example below uses Express.js for the HTTP server but you can use any NodeJS framework the [x402 reference implementation supports](https://docs.x402.org/getting-started/quickstart-for-sellers#2-add-payment-middleware) like Next.js, Hono, or Fastify.

See the full example [here](https://github.com/sc4l3r/keeta-x402/blob/main/apps/server/src/main.ts).

```typescript
import express from "express";
import { x402ResourceServer, HTTPFacilitatorClient } from "@x402/core/server";
import { paymentMiddleware } from "@x402/express";
import { KEETA_TESTNET_CAIP2, KTA_TESTNET_ADDRESS } from "@x402/keeta";
import { ExactKeetaScheme } from "@x402/keeta/exact/server";

const app = express();
const payTo = process.env.SERVER_ADDRESS!; // your Keeta receiving address

// Point at the Keeta facilitator. The server trusts it to verify and settle
// correctly, so only use facilitators you trust.
// The one below is operated by the community.
const facilitatorClient = new HTTPFacilitatorClient({
  url: "https://facilitator.x402.keeta.com",
});

const server = new x402ResourceServer(facilitatorClient);
server.register(KEETA_TESTNET_CAIP2, new ExactKeetaScheme());

app.use(
  paymentMiddleware(
    {
      "GET /weather": {
        accepts: [
          {
            scheme: "exact",
            // Default unit is USDC; the token address on Keeta is derived automatically.
            price: "0.01",
            network: KEETA_TESTNET_CAIP2,
            payTo,
          },
          {
            scheme: "exact",
            // Or accept any other Keeta token directly, by asset address + raw amount.
            price: {
              asset: KTA_TESTNET_ADDRESS,
              amount: "1000", // raw units, 0.000001 KTA at 9 decimals
            },
            network: KEETA_TESTNET_CAIP2,
            payTo,
          },
        ],
        description: "Get current weather data for any location",
        mimeType: "application/json",
      },
    },
    server,
  ),
);

app.get("/weather", (req, res) => {
  res.send({ report: { weather: "sunny", temperature: 70 } });
});

app.listen(4021, () => console.log("Server listening at http://localhost:4021"));
```

A request without payment gets a `402` back with the `PaymentRequirements` above; a request from a properly configured x402 client gets a `200` with the JSON payload and a settlement receipt header.

## Fee sponsoring on the Keeta facilitator

Every settlement on Keeta's `exact` scheme is **fee-sponsored by the facilitator**. The client signs only a payment block for the exact amount owed but it never needs to build or fund a separate fee transaction.

The facilitator cannot redirect funds: it can only append its own fee block alongside the block the client already signed, so it never gains the ability to alter where the client's payment goes.

## Facilitators

The following facilitators are known to be available for use.

* Third-party operated: [https://facilitator.x402.keeta.com](https://facilitator.x402.keeta.com/) using the open-source implementation on [GitHub](https://github.com/sc4l3r/keeta-x402/tree/main/apps/facilitator).

### Third-Party Service Notice

{% hint style="warning" %}
This service is developed, operated, and maintained by an independent third party as part of the Keeta Grant Program.

Although this service is accessible through a Keeta-owned subdomain, it is not operated by Keeta, Inc. The use of a Keeta subdomain does not constitute an endorsement of the service or its operator.

Keeta does not control or assume responsibility for the service's operation, availability, content, security, or compliance.

Your use of this service is at your own risk and is subject to the terms and privacy policy provided by the third-party operator, where applicable.
{% endhint %}

## Further reading

* [Keeta x402 example repo](https://github.com/sc4l3r/keeta-x402): full client, server, and facilitator example apps
* [x402 Keeta `exact` scheme specification](https://github.com/x402-foundation/x402/blob/main/specs/schemes/exact/scheme_exact_keeta.md): protocol sequence, payload formats, verification and settlement rules
* [`@x402/keeta` on npm](https://www.npmjs.com/package/@x402/keeta)
* [x402 buyer quickstart](https://docs.x402.org/getting-started/quickstart-for-buyers.md), [x402 seller quickstart](https://docs.x402.org/getting-started/quickstart-for-sellers.md)


# Deploying a Node

The preferred method for deploying a node is using the Pulumi deployment scripts, which will generate the necessary scalable GCP infrastructure to run a Representative Node. The configuration options can be used to control the components that Pulumi deploys.

{% stepper %}
{% step %}

### Install Prerequisites

1. Pulumi CLI  <https://www.pulumi.com/docs/get-started/install/>

Login is not required, and the deployment process can leverage multiple backends (eg, Google Cloud Bucket or Pulumi Hosted). If you choose to store the stack in a cloud environment, then log in to the preferred provider.

2. Google Cloud CLI <https://cloud.google.com/sdk/docs/install>
3. Node JS Version 20.18.0

If using macOS then the following are require prerequisites to compile from source and can be installed with `brew`

* jq, coreutils, python
  {% endstep %}

{% step %}

### Clone the Node Repository

```
git clone git@github.com:KeetaNetwork/node.git keeta-node
cd keeta-node/deployment
```

{% endstep %}

{% step %}

### Create Deployment Config

Use the example `config/example.json` to set the desired configuration parameters. Copy the example to your desired Pulumi stack name (eg, `config/test.json`) and update the configuration to fit your needs.
{% endstep %}

{% step %}

### Initialize Pulumi Stack

The deployment is designed to work with a KMS to manage encrypting secrets, such as the SEED used by the Representative for voting and other sensitive data. Here we initialize the Pulumi stack with a KMS.  `<name>` should match the name of the configuration file created in the previous step, and the GCPKMS URL should match a KMS created in your GCP Project.

```
pulumi stack init <name> --secrets-provider="gcpkms://projects/<p>/locations/<l>/keyRings/<r>/cryptoKeys/<k>"
```

{% endstep %}

{% step %}

### Define GCP Project for Pulumi

Define the GCP environment that Pulumi should deploy to. This will add the GCP Project to the Pulumi.\<name>.yaml file that was initialized in the previous step.

```
config --stack <name> set gcp:project
```

{% endstep %}

{% step %}

### Add Encrypted Representative SEED

Encrypted variables can be added to the Pulumi configuration as secrets, encrypted with the KMS provided earlier.  In this case, `KEETANET_LAMBDA_SEED` is the environment variable the node uses along with the index of the Representative from `Regions` in the configuration to compute the private key used for Voting by the Representative.  The SEED should be a 32-character hex string. For example, using openssl, a seed could be generated with `openssl rand -hex 32`

```
pulumi config --stack <name> set --secret keetanet-cloud-deploy:KEETANET_LAMBDA_SEED
```

{% endstep %}

{% step %}

### Deploy the Pulumi Stack

The following command will build the necessary binaries and deploy all of the infrastructure components. Pulumi will display the changes with a prompt to proceed or cancel.

```
make do-deploy
```

{% endstep %}
{% endstepper %}


# Official Links

Discord: [discord.com/invite/keeta](https://discord.com/invite/keeta)

X: [x.com/KeetaNetwork](https://x.com/KeetaNetwork)

Website: [keeta.com](https://keeta.com/)

Whitepaper: [keeta.com/keetanet-whitepaper-20250312.pdf](https://keeta.com/keetanet-whitepaper-20250312.pdf)

Product Manual: <https://keeta.com/product-manual.pdf>

SDK Documentation: [static.test.keeta.com/docs](https://static.test.keeta.com/docs/)

### Main Network

Network Wallet: [wallet.keeta.com](https://wallet.keeta.com/)

Network Block Explorer: [explorer.keeta.com](https://explorer.keeta.com/)

### Test Network

Test Network Wallet: [wallet.test.keeta.com](https://wallet.test.keeta.com)

Test Network Block Explorer: [explorer.test.keeta.com](https://explorer.test.keeta.com)

Test Network Faucet: [faucet.test.keeta.com](https://faucet.test.keeta.com)


# Tokenomics

<figure><img src="/files/kaAQLGCwBpF1VyupOSCz" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/Al8uW7BnQRshs673nEcz" alt=""><figcaption></figcaption></figure>


# Roadmap

<figure><img src="/files/w3ScdMAVr8MFfIdvWOJl" alt=""><figcaption></figcaption></figure>


# Articles

Explore our latest insights and developments related to the Keeta Network.

<table data-view="cards"><thead><tr><th></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-card-cover data-type="image">Cover image</th></tr></thead><tbody><tr><td>Cryptographically Verifiable Identity Sharing with Keeta</td><td><a href="/pages/IlIrZio1Rgy3Z72bKBGr">/pages/IlIrZio1Rgy3Z72bKBGr</a></td><td><a href="/files/rlk30YxoURrOg7CIAFK3">/files/rlk30YxoURrOg7CIAFK3</a></td></tr></tbody></table>


# Cryptographically Verifiable Identity Sharing with Keeta

In digital systems, establishing trust between parties often comes down to verifying identity attributes: names, emails, addresses, government IDs, etc. Sharing this data securely, selectively, and in a way that others can verify has long been a challenge—especially when both privacy and integrity are required.

Keeta provides a cryptographic framework for identity certification and selective disclosure using X.509 certificates, enhanced with commitment schemes and encrypted containers. This post outlines how it works.

### What Is a Certificate?

In cryptographic terms, a certificate is a signed data structure used to assert claims about a subject. One entity (the issuer) attests to attributes of another entity (the subject) by issuing a certificate containing:

* The subject’s public key
* A set of attributes
* The issuer’s digital signature over the certificate’s contents

Keeta builds on the standard X.509 certificate format, which is widely used in TLS, PKI systems, and identity infrastructure. X.509 supports an extensions mechanism that allows embedding additional, application-specific data within the certificate. Keeta uses this to encode structured identity attributes in a [consistent, extensible format](https://keeta.notion.site/Keeta-KYC-Certificate-Extensions-13e5da848e588042bdcef81fc40458b7).

### Sensitive Attributes and Commitment Schemes

In many cases, identity attributes are privacy-sensitive. Including them in plaintext inside a certificate would expose them to anyone with access to the certificate.

Keeta addresses this using a combination of encryption and cryptographic commitments. Specifically:

1. The issuer encrypts the attribute value (e.g., full name, address) and a random salt using the subject’s public key.
2. The issuer computes a commitment in the form of a cryptographic hash over:
   * the subject's account identifier (public key),
   * the attribute's cleartext value,
   * the salt.

This commitment is included in the certificate’s extension field, alongside the encrypted value and encrypted salt.This structure enables the following properties:

* **Binding**: The commitment ties the attribute to a specific subject and value.
* **Hiding**: The actual attribute value and salt remain encrypted.
* **Non-repudiation**: The certificate is signed by the issuer and includes the commitment.

Because cryptographic hashes are one-way functions, the commitment cannot be reversed to reveal the attribute value, but can be used to verify it when disclosed later.

### Selective Disclosure with Encrypted Containers

To share specific sensitive attributes with a third party, Keeta uses an "encrypted container." This is a payload encrypted to the public keys of one or more designated recipients.Here’s how the process works at a high level when a user shares attributes via the Keeta Wallet:

1. **Decryption**:
   1. The client decrypts each sensitive attribute and its associated salt.
2. **Proof Generation**:
   1. For each attribute, the client generates a proof consisting of:
      * The cleartext value
      * The salt
3. **Container Construction**:
   1. All selected proofs are encrypted into a container using the public keys of the intended recipients.
4. **Link Generation**:
   1. The client generates a link referencing:
      * The encrypted container
      * The source certificate
      * The subject's account (public key)
5. **Verification by Recipient**:
   1. The recipient decrypts the container, retrieves the proofs, and:
      * Recomputes the commitment hash for each attribute
      * Compares it against the commitment embedded in the certificate
      * Validates that the certificate was issued by a trusted authority

This mechanism enables verifiable selective disclosure. Only the recipient can access the attribute value, and they can independently verify that it matches what the issuer originally certified—without needing to trust the subject.

## Summary

Keeta provides a cryptographically robust model for identity certification and sharing:

* Attributes are embedded in X.509 certificates using standardized extensions.
* Sensitive attributes are encrypted and committed using cryptographic hash functions and salts.
* Commitment schemes ensure attribute integrity without exposing values.
* Encrypted containers enable secure, recipient-specific selective disclosure.
* All proofs are verifiable against the original issuer certificate without relying on out-of-band trust.

This architecture supports privacy-preserving identity verification at internet scale, with use cases ranging from fintech onboarding to decentralized identity frameworks.

For more technical documentation or to integrate Keeta into your platform, contact our team.


# Whitepaper

{% file src="/files/MpdT1WiwdvXl8tlEPxBf" %}


# Product manual

{% file src="/files/KhfIwbuyOHcaLtezntvq" %}


# Brand & Press

Download our official logo and wordmark assets here. Our brand colors are showcased throughout this page for reference. For media inquiries and press opportunities, please contact our communications: <press@keeta.com>.&#x20;

{% file src="/files/BffpSXufofl8L4TEe1bx" %}

{% file src="/files/BLmB8WEGqpeKIKXIxtZI" %}


