-
Notifications
You must be signed in to change notification settings - Fork 6.9k
feat: add Keeta chain #17941
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add Keeta chain #17941
Conversation
📝 WalkthroughWalkthroughAdds 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
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)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
|
The adapter at projects/keeta exports TVL: |
There was a problem hiding this 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.
5bc7446 to
3a49e5d
Compare
|
The adapter at projects/keeta exports TVL: |
There was a problem hiding this 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.supplyexists or is a valid numeric string before passing it toBigInt()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 === 5andoperation.method === 0 / 1are 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); }
3a49e5d to
8ecb2c2
Compare
|
The adapter at projects/keeta exports TVL: |
There was a problem hiding this 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.
|
The TVL is reported as 0 right now. I assume that is because it's a new chain and the Running the test script locally with debug logs enabled yields the correct token balances: |
|
|
||
| async function tvl(api) { | ||
| for (const token of SUPPORTED_TOKENS) { | ||
| const supply = await getSupply(token, api.timestamp); |
There was a problem hiding this comment.
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).
| 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; | ||
| } |
There was a problem hiding this comment.
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.
|
I've added the Keeta token mappings in DefiLlama/defillama-server#11338 to the coins server which may resolve the 0 TVL issue. |
| keeta: { | ||
| [ADDRESSES.keeta.KTA]: { coingeckoId: 'keeta', decimals: 18 }, | ||
| [ADDRESSES.keeta.USDC]: { coingeckoId: 'usd-coin', decimals: 6 }, | ||
| [ADDRESSES.keeta.EURC]: { coingeckoId: 'euro-coin', decimals: 6 }, | ||
| } |
There was a problem hiding this comment.
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.
g1nt0ki
left a comment
There was a problem hiding this 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.
We track a bridged tvl for a chain, can you share bridge info on how the USDC & EUR are supplied to keeta?
|
@g1nt0ki thanks for the reply!
Understood, so only if they're part of a liquidity pool in a DEX for example, right?
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? |
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.000ZIn 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):
keetaCoinmarketcap ID (so your TVL can appear on Coinmarketcap, leave empty if not listed):
36066Short 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:
0xc0634090F2Fe6c6d75e61Be2b949464aBB498973Ticker: $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:
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