-
Notifications
You must be signed in to change notification settings - Fork 22
docs: next-release fee documentation (integration of #425, #439, #432, #436) #441
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
base: main
Are you sure you want to change the base?
Changes from all commits
54b25d5
39a2789
ee04eb5
a834315
30c6457
84ffd1b
5938da8
ee38c12
2536d2f
5f80f92
1c42d31
7e60d97
bce101d
bce252b
d84ca63
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,175 @@ | ||
| --- | ||
| description: "Developer NFT Rewards explains how GenLayer credits contract deployers with a share of transaction fees and epoch inflation, and how to read and claim those rewards with GenLayerJS." | ||
| --- | ||
|
|
||
| import { Callout } from "nextra-theme-docs"; | ||
|
|
||
| # Developer NFT Rewards | ||
|
|
||
| GenLayer rewards the people who build on it. The first time you deploy an Intelligent Contract, the network mints a **Developer NFT** to your deploying address. That NFT accrues rewards from the activity your contracts generate, and you claim those rewards on-chain whenever you like. | ||
|
|
||
| This page covers what the Developer NFT is, how it earns, and how to read and claim rewards with [GenLayerJS](/developers/decentralized-applications/genlayer-js). | ||
|
|
||
| <Callout type="info"> | ||
| The Developer NFT is minted automatically on your first contract deployment — there is no separate registration step. You get **one NFT per developer address**. | ||
| </Callout> | ||
|
|
||
| ## What the Developer NFT is | ||
|
|
||
| - It is minted to the **deployer** (the address that sends the deploy transaction) the first time that address deploys a contract. | ||
| - There is exactly **one NFT per developer address**. Deploying again from the same address does not mint a second NFT. | ||
| - The NFT is the account that accumulates and holds your claimable rewards. Only the NFT owner can claim. | ||
|
|
||
| Each NFT tracks: | ||
|
|
||
| | Field | Meaning | | ||
| |-------|---------| | ||
| | `nftId` | The on-chain id of your Developer NFT. | | ||
| | `developer` | The address that owns the NFT. | | ||
| | `claimableRewards` | Fee rewards accumulated so far and not yet claimed. | | ||
| | `lastClaimedEpoch` | The last epoch whose inflation rewards you have claimed. | | ||
| | `ghosts` | The contract ("ghost") addresses associated with this NFT. | | ||
|
|
||
| ## How rewards are earned | ||
|
|
||
| Developer rewards come from two independent sources. | ||
|
|
||
| ### 1. Fee share | ||
|
|
||
| Roughly **10% of the gross transaction fees** paid by transactions against your contract are credited to your NFT. This is the fee leg, and it is credited **when each transaction finalizes** — not at acceptance. | ||
|
|
||
| - Credited per transaction, as soon as that transaction reaches finalization. | ||
| - No epoch has to close first — fee rewards become claimable immediately. | ||
| - Rewards are attributed to the epoch in which the transaction was **created**, which matters for the inflation share below. | ||
|
|
||
| ### 2. Inflation share | ||
|
|
||
| Each epoch, GenLayer sets aside **10% of that epoch's inflation** into a pool for developers. That pool is split across Developer NFTs **pro-rata by the fees each NFT's contracts generated during the epoch**, with one important limit: | ||
|
|
||
| <Callout type="info"> | ||
| Your inflation reward for an epoch is **capped at 1× the fees your contracts earned that epoch**. If your contracts earned no fees in an epoch, you earn no inflation for that epoch — the unclaimed portion of the pool is burned. | ||
| </Callout> | ||
|
|
||
| In practice, on a low-traffic network the developer inflation pool is larger than the total fees generated, so each NFT typically receives an inflation reward roughly equal to its own fee earnings for the epoch (that is, up to a **2× effect**: your fee share plus a matching inflation share). | ||
|
|
||
| ### When rewards become claimable | ||
|
|
||
| | Reward source | Claimable when | | ||
| |---------------|----------------| | ||
| | Fee share | Immediately, at each transaction's finalization. | | ||
| | Inflation share | Once the epoch is **finalized** (a finalized epoch lags the current epoch by at least one step). | | ||
|
|
||
| The current, unfinalized epoch is never included in claimable inflation. | ||
|
|
||
| ## Reading and claiming with GenLayerJS | ||
|
|
||
| All of the following use the standard GenLayerJS client. The NFT contract address is resolved automatically from the network's on-chain address registry — you don't pass it yourself. | ||
|
|
||
| ```typescript | ||
| import { testnetBradbury } from "genlayer-js/chains"; | ||
| import { createClient, createAccount } from "genlayer-js"; | ||
|
|
||
| const account = createAccount(); // or createAccount(privateKey) | ||
| const client = createClient({ | ||
| chain: testnetBradbury, | ||
| account, | ||
| }); | ||
| ``` | ||
|
|
||
| ### Look up your Developer NFT | ||
|
|
||
| `getDeveloperNft` returns the full reward record for a developer address, or `null` if that address has never deployed a contract. | ||
|
|
||
| ```typescript | ||
| const nft = await client.getDeveloperNft({ developer: account.address }); | ||
|
|
||
| if (nft === null) { | ||
| console.log("This address has no Developer NFT yet — deploy a contract first."); | ||
| } else { | ||
| console.log("NFT id:", nft.nftId); | ||
| console.log("Unclaimed fee rewards:", nft.claimableRewards); | ||
| console.log("Last claimed epoch:", nft.lastClaimedEpoch); | ||
| console.log("Contracts (ghosts):", nft.ghosts); | ||
| } | ||
| ``` | ||
|
|
||
| The returned shape is: | ||
|
|
||
| ```typescript | ||
| interface DeveloperNft { | ||
| nftId: bigint; | ||
| developer: `0x${string}`; | ||
| claimableRewards: bigint; | ||
| lastClaimedEpoch: bigint; | ||
| ghosts: `0x${string}`[]; | ||
| } | ||
| ``` | ||
|
|
||
| ### Check claimable rewards | ||
|
|
||
| Fee rewards and inflation rewards are read separately. | ||
|
|
||
| ```typescript | ||
| // All-time accumulated fee rewards not yet claimed. | ||
| const feeRewards = await client.getClaimableRewardsFromFees({ | ||
| nftId: nft.nftId, | ||
| }); | ||
|
|
||
| // Inflation rewards over the next `numberOfEpochsToClaim` finalized epochs, | ||
| // starting from lastClaimedEpoch + 1. | ||
| const inflationRewards = await client.getClaimableRewardsFromInflation({ | ||
| nftId: nft.nftId, | ||
| numberOfEpochsToClaim: 50n, | ||
| }); | ||
|
|
||
| console.log("Claimable from fees:", feeRewards); // bigint (wei) | ||
| console.log("Claimable from inflation:", inflationRewards); // bigint (wei) | ||
| ``` | ||
|
|
||
| <Callout type="info"> | ||
| `getClaimableRewardsFromInflation` requires **two** arguments: the `nftId` and how many epochs to look ahead. It only counts epochs that are already finalized; the current epoch is excluded. | ||
| </Callout> | ||
|
|
||
| ### Claim rewards | ||
|
|
||
| There are two ways to claim. | ||
|
|
||
| **Claim everything** — sweeps all accumulated fee rewards plus inflation for every finalized epoch: | ||
|
|
||
| ```typescript | ||
| const txHash = await client.claimNftRewards({ | ||
| nftId: nft.nftId, | ||
| }); | ||
| ``` | ||
|
|
||
| **Claim a bounded number of epochs** — useful when many epochs have accumulated: | ||
|
|
||
| ```typescript | ||
| const txHash = await client.claimNftEpochs({ | ||
| nftId: nft.nftId, | ||
| numberOfEpochsToClaim: 50n, | ||
| }); | ||
| ``` | ||
|
|
||
| <Callout type="warning"> | ||
| A single claim processes **at most 50 epochs of inflation** at a time. If more than 50 finalized epochs have accumulated since your last claim, use `claimNftEpochs` (or call the claim repeatedly) to clear them in batches. The read view `getClaimableRewardsFromInflation` does **not** enforce this 50-epoch cap, so it can report more than a single claim will actually pay out — size your claim accordingly. | ||
|
Comment on lines
+137
to
+155
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Correct the “claim everything” description. The page says 🤖 Prompt for AI Agents |
||
| </Callout> | ||
|
|
||
| Both claim methods return the EVM transaction hash. Only the NFT owner can claim; claiming when there is nothing to claim reverts. | ||
|
|
||
| ## Gotchas | ||
|
|
||
| <Callout type="warning"> | ||
| **One NFT per developer.** With current behavior, only the **first contract you deploy** from an address accrues rewards for that NFT. Additional contracts deployed from the same address do not add to the NFT's earnings today. Deploy from a dedicated address if you want a contract's activity to be tracked on its own NFT. | ||
| </Callout> | ||
|
|
||
| - **Zero-fee epochs earn no inflation.** Because the inflation share is capped at 1× your fee earnings for the epoch, an epoch in which your contracts generated no fees pays no inflation — that share is burned, not carried forward. | ||
| - **Claims sweep all fee rewards at once.** Any claim (`claimNftRewards` or `claimNftEpochs`) empties your entire accumulated fee balance, even if you only meant to claim a few epochs of inflation. There is no partial fee claim. | ||
| - **Inflation lags fees.** Fee rewards are claimable the moment a transaction finalizes; inflation for an epoch only becomes claimable after that epoch is finalized, which is at least one step behind the current epoch. | ||
|
|
||
| ## Related | ||
|
|
||
| - [GenLayer JS](/developers/decentralized-applications/genlayer-js) — client setup and configuration. | ||
| - [Reading Data from Intelligent Contracts](/developers/decentralized-applications/reading-data) | ||
| - [Writing Data to Intelligent Contracts](/developers/decentralized-applications/writing-data) | ||
| - [Staking Contract Guide](/developers/staking-guide) — the sibling reward system for validators and delegators. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| # Fee Outcomes and Debugging | ||
|
|
||
| Applications should explain transaction outcomes in terms users or operators can act on. A submitted GenLayer transaction can be accepted by consensus, finalized later, rejected before execution or finish with a contract error. | ||
|
|
||
| ## Success rule | ||
|
|
||
| A transaction is successful only when both are true: | ||
|
|
||
| - the transaction status is `ACCEPTED` or `FINALIZED` | ||
| - the execution result is `FINISHED_WITH_RETURN` | ||
|
|
||
| Use `isSuccessful(tx)` from `genlayer-js` or the transaction-kit outcome surfaces instead of checking status alone. A transaction can be accepted by validators and still finish with a contract error. | ||
|
|
||
| ## User-facing outcomes | ||
|
|
||
| | Status and result | Meaning | Surface it as | | ||
| | --- | --- | --- | | ||
| | `ACCEPTED` / `FINALIZED` + `FINISHED_WITH_RETURN` | Execution succeeded. | Done. Unused fee budget refunds at finalization. | | ||
| | `UNDETERMINED` + any result | Validators could not reach a majority. | Treat as not executed, even if a leader result exists. | | ||
| | Rejected at submission, such as `MaxPriceExceeded` | A price cap or funding rule failed before execution. | Nothing charged. Re-estimate and retry. | | ||
| | `FINISHED_WITH_ERROR` | The contract reverted or errored. | Execution failed. Consensus fees may still be consumed. | | ||
|
|
||
| ## Gasless networks | ||
|
|
||
| Some Studio deployments run gasless, with fee accounting disabled and all prices zero. The transaction kit detects this from the estimate: | ||
|
|
||
| ```typescript | ||
| if (quote.gasless) { | ||
| // Show "No fees on this network" and submit without fee params. | ||
| } | ||
| ``` | ||
|
|
||
| The same app code can work on gasless Studio and fee-charging networks. Do not hard-code a network name to decide whether fees are enabled; use the estimate result. | ||
|
|
||
| ## Refunds | ||
|
|
||
| The initial deposit is a maximum budget, not the final cost. Unused budget is refunded when the transaction finalizes. Users should understand three numbers: | ||
|
|
||
| - total deposit: what must be available when signing | ||
| - spent fees: what the transaction actually consumed | ||
| - refund: the unused part returned after finalization | ||
|
|
||
| If an app tracks only until `decided`, it may not yet know the final refund. Use `finalized` tracking for screens or tools that need final fee settlement. | ||
|
|
||
| ## Explorer debugging | ||
|
|
||
| Open the transaction in GenLayer Explorer and inspect the Fees section. It should show: | ||
|
|
||
| - allocation versus consumed values | ||
| - locked price caps versus current prices | ||
| - message fee budget and consumption | ||
| - refunds by category | ||
| - settlement status | ||
|
|
||
| If a quote looks too large, check whether the transaction used a developer profile or network defaults. `PolicyQuote.source` is `developer` when a matching `fee-profile.json` entry was used and `network-default` otherwise. | ||
|
|
||
| If a transaction failed even though the user accepted the quote, check: | ||
|
|
||
| - whether a price cap was exceeded before execution | ||
| - whether the method hit an unmeasured expensive branch | ||
| - whether `totalMessageFees` was too low for emitted child transactions | ||
| - whether the contract itself returned `FINISHED_WITH_ERROR` | ||
| - whether the app tracked only status and ignored execution result |
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.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Keep the no-NFT guard active for subsequent examples.
getDeveloperNftcan returnnull, but the next snippet unconditionally accessesnft.nftId. A first-time developer following these examples will hit a runtime error. Return/throw after the null case or place the read and claim examples inside the non-null branch.🤖 Prompt for AI Agents