Skip to content

Conversation

@sc4l3r
Copy link

@sc4l3r sc4l3r commented Feb 5, 2026

Disclaimer: I'm a community developer and not a member of the official Keeta network development team.

This PR adds support for a new blockchain, the Keeta network, a new L1 blockchain which enables interaction across multiple blockchains by acting as a unifying layer for direct cross-chain transactions.

Implementation Note / Question:

Currently, the chain's SDK for the adapter is implemented without any additional NPM packages since I read that this is usually discouraged.
I recommend to include the official chain's SDK https://www.npmjs.com/package/@keetanetwork/keetanet-client which would take care of choosing the right representatives, make the API calls, retry failed requests, etc. to reduce the maintenance of the code in this repo.

If you agree on including the new package, I'm happy to adjust the PR for that.

Name (to be shown on DefiLlama):

Keeta

Twitter Link:

https://x.com/KeetaNetwork/

List of audit links if any:
Website Link:

https://keeta.com/

Logo (High resolution, will be shown with rounded borders):

Usually this one, e.g. on coinmarketcap, coingecko, etc.: https://avatars.githubusercontent.com/u/99282583

Others are available at https://keeta.com/mediakit

Current TVL:

Timestamp: 2026-02-04T14:00:00.000Z

  • 3,442,969.760363121958364072 KTA Explorer: ~ $960,244.27
  • 7,796.126542 USDC Explorer: ~ $7,793.01
  • 635.930263 EURC Explorer: ~ $750.40

In total about $968,787.67

Treasury Addresses (if the protocol has treasury)
Chain:

keeta

Coingecko ID (so your TVL can appear on Coingecko, leave empty if not listed):

keeta

Coinmarketcap ID (so your TVL can appear on Coinmarketcap, leave empty if not listed):

36066

Short Description (to be shown on DefiLlama):

Keeta Network is a high-performance Layer 1 blockchain built to meet the demands of the global financial system.

Token address and ticker if any:

Contract on Base: 0xc0634090F2Fe6c6d75e61Be2b949464aBB498973
Ticker: $KTA

Category (full list at https://defillama.com/categories) *Please choose only one:

Bridge (somewhat, it's an L1 blockchain)

Oracle Provider(s): Specify the oracle(s) used (e.g., Chainlink, Band, API3, TWAP, etc.):
Implementation Details: Briefly describe how the oracle is integrated into your project:
Documentation/Proof: Provide links to documentation or any other resources that verify the oracle's usage:
forkedFrom (Does your project originate from another project):
methodology (what is being counted as tvl, how is tvl being calculated):

Assets move onto the Keeta network via so-called asset movement anchors. Right now, there are two anchors:

  • EVM / Base Anchor: Enables direct inbound / outbound movement of KTA, USDC, EURC, cbBTC between Base and Keeta. Tokens are locked on Base (contract) and then minted on Keeta on inbound movements. For outbound movements, assets are burned on Keeta and sent from the contract to the target address. (you can view the transactions on https://kee.tools/anchors/asset-movement/keeta ).
  • Bridge.xyz anchor: Enables additional rails such as deposits from banks, from Ethereum (USDC, USDT, PYUSD), and USDC from Arbitrum, Avalanche and Polygon. The conversion of these assets to KTA/USDC/EURC is handled by Bridge and assets move in- / outbound via the Base anchor.

The TVL is calculated as the supply of the token (such as KTA, USDC, EURC) on the network. The token's supply increases when tokens enter the network and decreases when tokens leave the network, e.g. via the Base anchor, and so it represents the tokens that are available (locked) on the chain.

Github org/user (Optional, if your code is open source, we can track activity):

https://github.com/KeetaNetwork/

Does this project have a referral program?

No

Summary by CodeRabbit

  • New Features
    • Added Keeta blockchain support with a TVL adapter calculating token supply-based TVL.
    • Integrated Keeta core assets: KTA, USDC, EURC for valuation and reporting.
    • Enabled historical supply lookup (supply at a given timestamp) for accurate past TVL.
    • Added configurable KEETA_RPC setting and token mappings for Keeta assets.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

📝 Walkthrough

Walkthrough

Adds Keeta chain support: new helper module computing token supply from on-chain TOKEN_ADMIN_SUPPLY ops, a TVL adapter using it, and related config updates (chain registration, core assets, env var, token mappings).

Changes

Cohort / File(s) Summary
Keeta Chain Implementation
projects/helper/chain/keeta.js
New module: selects/caches highest-weighted representative endpoint (refresh ~5min), RPC helpers, account/chain fetchers, supplyChangeAfter iterator, and exported getSupply(token, timestamp).
TVL Adapter
projects/keeta/index.js
New Keeta TVL adapter: loads tokens from coreAssets, calls getSupply per token with current timestamp, and reports via api.add; exports metadata and start date.
Chain Registration
projects/helper/chains.json
Adds "keeta" to the chains list.
Core Assets
projects/helper/coreAssets.json
Adds top-level keeta asset group with KTA, USDC, and EURC addresses.
Environment
projects/helper/env.js
Adds KEETA_RPC to DEFAULTS and includes it in ENV_KEYS.
Token Mapping
projects/helper/tokenMapping.js
Adds fixBalancesTokens.keeta entries mapping KTA/USDC/EURC to coingecko IDs and decimals.

Sequence Diagram(s)

sequenceDiagram
  participant Adapter as Keeta TVL Adapter
  participant Helper as keeta helper module
  participant RPC as Keeta Representative RPC

  Adapter->>Helper: getSupply(token, timestamp)
  Helper->>Helper: selectCachedRepresentative() / maybe refresh
  Helper->>RPC: GET /account/info and /account/head
  RPC-->>Helper: head, account info
  Helper->>RPC: fetch chain blocks (paginated)
  RPC-->>Helper: blocks
  Helper->>Helper: iterate blocks, compute supply delta
  Helper-->>Adapter: supply at timestamp
  Adapter->>API: api.add(token, supply)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • RohanNero
  • g1nt0ki

Poem

🐰 I hopped through blocks with nimble feet,

Traced KTA, USDC, EURC down each chain,
I cached a friendly RPC to keep the beat,
Now TVL hums softly across the plain,
A carrot for code — hop on, celebrate! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add Keeta chain' accurately and concisely summarizes the main change—adding support for a new blockchain network to the adapter.
Description check ✅ Passed The PR description comprehensively fills the template with protocol details, TVL information, methodology, implementation notes, and all required fields are completed with relevant information.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@llamabutler
Copy link

The adapter at projects/keeta exports TVL:

keeta                     0.00

total                    0.00 

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@projects/helper/chain/keeta.js`:
- Around line 44-63: In getRepresentativeEndpoint ensure you don't crash when
weightedReps is empty: initialize updateRepsPromise only once and only create
the periodic updater once (guard creation of setInterval with a new module-scope
flag/variable like repIntervalId), call updateReps() into updateRepsPromise and
await it, then check if weightedReps[0] exists before accessing rep.api; if
missing or updateReps returned empty, return the KEETA_RPC fallback. Use the
existing symbols getRepresentativeEndpoint, updateRepsPromise, updateReps,
weightedReps, KEETA_RPC, UPDATE_REPS_INTERVAL and setInterval to locate where to
add the guards and the single-interval guard.
- Around line 112-149: In supplyChangeAfter ensure blockDate is declared locally
instead of assigned implicitly: declare a local variable (e.g., const or let
blockDate) inside the loop before assigning new Date(block.date) so you don't
create a global and avoid race/strict-mode errors; update the assignment near
where blockDate is used for the comparison (inside the for (const { block } of
chain.blocks) loop) and keep all references to blockDate within that scope.

@llamabutler
Copy link

The adapter at projects/keeta exports TVL:

keeta                     0.00

total                    0.00 

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@projects/helper/chain/keeta.js`:
- Around line 118-162: The function supplyChangeAfter currently silently returns
0n when currentHeadBlock isn't found across pages; update supplyChangeAfter (and
the loop that calls getChain) to detect when the full chain has been paginated
without ever setting foundStart and, instead of returning a misleading 0n,
perform a bounded retry (e.g., 2–3 attempts with short backoff) calling getChain
from the initial start and then, if still not found, emit a clear warning/error
that includes currentHeadBlock and relevant pagination info (or throw an error)
so callers (getSupply) won't quietly use current supply; reference
supplyChangeAfter, getChain, currentHeadBlock, foundStart, and chain.nextKey
when locating where to add the retry/log/throw.

In `@projects/keeta/index.js`:
- Around line 10-15: The tvl function is passing BigInt values from getSupply
directly into api.add which expects a number or numeric string; update tvl
(iterating SUPPORTED_TOKENS) to convert each supply to a string (e.g., call
.toString() on the BigInt returned by getSupply) before calling api.add(token,
...). Ensure you reference the getSupply call inside tvl and replace
api.add(token, supply) with api.add(token, supplyAsString) so serialization no
longer fails.
🧹 Nitpick comments (2)
projects/helper/chain/keeta.js (2)

174-191: The approach is sound; verify API response shape matches expectations.

The two-step method (current supply minus post-timestamp delta) is a clever workaround for the lack of a historical supply API. However, there is no validation that info.supply exists or is a valid numeric string before passing it to BigInt() on line 190. If the API response shape changes or the token account doesn't have supply info, this will throw an unhandled error.

Consider adding a guard:

if (!info || info.supply == null) throw new Error(`No supply info for token ${token}`);

143-156: Magic numbers for operation type and method reduce readability.

operation.type === 5 and operation.method === 0 / 1 are unexplained. Consider extracting named constants to make the intent self-documenting:

Suggested improvement
+const OP_TOKEN_ADMIN_SUPPLY = 5;
+const METHOD_ADD = 0;
+const METHOD_SUBTRACT = 1;
+
 // ...
-        if (operation.type === 5) {
-          if (operation.method === 0) {
+        if (operation.type === OP_TOKEN_ADMIN_SUPPLY) {
+          if (operation.method === METHOD_ADD) {
             supplyChange += BigInt(operation.amount);
           }
-          if (operation.method === 1) {
+          if (operation.method === METHOD_SUBTRACT) {
             supplyChange -= BigInt(operation.amount);
           }

@llamabutler
Copy link

The adapter at projects/keeta exports TVL:

keeta                     0.00

total                    0.00 

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@projects/helper/env.js`:
- Line 48: The KEETA_RPC constant in projects/helper/env.js is set to an
incorrect URL ("https://rep1.main.network.api.keeta.com/api") causing 404s;
update the KEETA_RPC value to the correct base RPC endpoint (remove the
erroneous "/api" and fix the hostname part) and verify the new value responds to
requests like {KEETA_RPC}/node/ledger/representatives; ensure the KEETA_RPC
constant is the corrected, reachable URL per Keeta docs so the adapter can fetch
representative and account data.

@sc4l3r
Copy link
Author

sc4l3r commented Feb 6, 2026

The TVL is reported as 0 right now. I assume that is because it's a new chain and the keeta: tokens are not yet mapped in the defillama price API. If it's an issue in my code, let me know what I'm missing and I'll add that.

Running the test script locally with debug logs enabled yields the correct token balances:

┌─────────┬──────┬────────┬──────────────────────────┬─────────────────────────────────────────────────────────────────────────────┬───────────┐
│ (index) │ name │ symbol │ balance                  │ label                                                                       │ decimals  │
├─────────┼──────┼────────┼──────────────────────────┼─────────────────────────────────────────────────────────────────────────────┼───────────┤
│ 0       │ '-'  │ '-'    │ '3.5804958213694375e+24' │ 'keeta:keeta_anqdilpazdekdu4acw65fj7smltcp26wbrildkqtszqvverljpwpezmd44ssg' │ undefined │
│ 1       │ '-'  │ '-'    │ '7897496255'             │ 'keeta:keeta_amnkge74xitii5dsobstldatv3irmyimujfjotftx7plaaaseam4bntb7wnna' │ undefined │
│ 2       │ '-'  │ '-'    │ '852575892'              │ 'keeta:keeta_apblhar4ncp3ln62wrygsn73pt3houuvj7ic47aarnolpcu67oqn4xqcji3au' │ undefined │
└─────────┴──────┴────────┴──────────────────────────┴─────────────────────────────────────────────────────────────────────────────┴───────────┘


async function tvl(api) {
for (const token of SUPPORTED_TOKENS) {
const supply = await getSupply(token, api.timestamp);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To determine whether this is a request for historical data or for the latest data, what is the typical difference of the api.timestamp from it being created and this function being called?

For a query for the most recent data we could skip the supplyChangeAfter call in the getSupply function to speed it up (no need to fetch the token's chain if we're interested in the latest supply which we get from the account info already).

Comment on lines +4 to +106
const UPDATE_REPS_INTERVAL = 5 * 60 * 1000;

let weightedReps = [];
let updateRepsPromise = null;

/**
* Updates the known representatives and stores them sorted by their weight.
*/
async function updateReps() {
const api = weightedReps.length > 0 ? weightedReps[0].api : getEnv('KEETA_RPC');

const { representatives } = await http.get(`${api}/node/ledger/representatives`);

const reps = []
for (const rep of representatives) {
reps.push({
weight: BigInt(rep.weight),
api: rep.endpoints.api,
});
}

reps.sort((rep1, rep2) => {
if (rep2.weight > rep1.weight) {
return 1;
}
if (rep2.weight < rep1.weight) {
return -1;
}
return 0;
});

weightedReps = reps;
}

/**
* Gets the API endpoint of the currently heighest weighted representative.
* If the representatives are unknown, it initializes them and schedules a period refresh.
*
* @returns URL of the representative's API endpoint
*/
async function getRepresentativeEndpoint() {
if (weightedReps.length === 0 && !updateRepsPromise) {
updateRepsPromise = updateReps().catch(function () {
// Ignore any errors
});

// Update reps regularly
setInterval(() => {
updateRepsPromise = updateReps().catch(function () {
// Ignore any errors
});
}, UPDATE_REPS_INTERVAL);
}

// If an update is running, wait for it
if (updateRepsPromise) await updateRepsPromise;

// If fetching the reps fails initially and weightedReps is empty,
// fall back to the default representative API endpoint.
if (weightedReps.length === 0) {
return getEnv('KEETA_RPC');
}

// Return the API endpoint of the representative with the most weight
return weightedReps[0].api;
}

/**
* Fetch the account information for a given account including the current head block and supply.
*
* See https://static.network.keeta.com/docs/classes/KeetaNetSDK.Client.html#getaccountinfo
*
* @param {string} account - Address of the account to fetch the information for
* @returns The account information
*/
async function getAccountInfo(account) {
const api = await getRepresentativeEndpoint();

return await http.get(`${api}/node/ledger/account/${account}`);
}

/**
* Get the chain for a given account, which is the set of blocks the account has created.
*
* See https://static.network.keeta.com/docs/classes/KeetaNetSDK.Client.html#getchain
*
* @param {string} account - The account to get the chain for
* @param {string} startBlock - The block hash to start from -- this is used to paginate the request
* @returns The chain of blocks for the given account, in reverse order starting with the most recent block
*/
async function getChain(account, startBlock) {
const api = await getRepresentativeEndpoint();

let url = `${api}/node/ledger/account/${account}/chain`;

if (startBlock) {
url += '?start=' + startBlock;
}

const chain = await http.get(url);

return chain;
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of this code would be covered by the chain's official SDK https://www.npmjs.com/package/@keetanetwork/keetanet-client including automatic retries, API response validation etc and improve the maintainability of this adapter, essentially replacing it with:

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

// No need to specify a representative, the SDK takes care of that.
const client = KeetaNet.Client.fromNetwork('main')

// Get account information
const { currentHeadBlock, info } = await client.getAccountInfo(token);

// Get chain of token account
const chain = await client.getChain(token, { startBlock: start });

If you agree on adding the SDK as a dependency, I'll adjust the code accordingly.

@sc4l3r
Copy link
Author

sc4l3r commented Feb 6, 2026

I've added the Keeta token mappings in DefiLlama/defillama-server#11338 to the coins server which may resolve the 0 TVL issue.

Comment on lines +110 to +114
keeta: {
[ADDRESSES.keeta.KTA]: { coingeckoId: 'keeta', decimals: 18 },
[ADDRESSES.keeta.USDC]: { coingeckoId: 'usd-coin', decimals: 6 },
[ADDRESSES.keeta.EURC]: { coingeckoId: 'euro-coin', decimals: 6 },
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added these mappings since it was mentioned in the docs at https://docs.llama.fi/list-your-project/how-to-add-a-new-blockchain#id-3.-add-token-mappings-in-tokenmapping.js .

Is this actually necessary? I noticed that when running the update-token-mappings script from the coins server in the defillama-server repo that it would add these tokens automatically, but with their address as the symbol (add not KTA / USDC / EURC), which is not desired I think.

Copy link
Member

@g1nt0ki g1nt0ki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the PR but we treat only part where tokens locked in defi projects on a given chain as chain tvl.

https://docs.llama.fi/

We track a bridged tvl for a chain, can you share bridge info on how the USDC & EUR are supplied to keeta?

@sc4l3r
Copy link
Author

sc4l3r commented Feb 6, 2026

@g1nt0ki thanks for the reply!

thanks for the PR but we treat only part where tokens locked in defi projects on a given chain as chain tvl.

Understood, so only if they're part of a liquidity pool in a DEX for example, right?

We track a bridged tvl for a chain, can you share bridge info on how the USDC & EUR are supplied to keeta?

They move in- and out of the chain via a so-called "asset movement anchor". Right now, we have a direct anchor to Base. Tokens are locked in a contract on Base and then minted on Keeta on inbound movements. For outbound movements, assets are burned on Keeta and sent from the contract to the target address. (you can view the transactions on https://kee.tools/anchors/asset-movement/keeta ).

Does that answer the question?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants