# arl:fee-delegate — Ammora SDK 0.3.8 Fee-only managed NFT delegation. Only the NFT owner may grant or revoke it. npm run arl:fee-delegate -- npm run arl:fee-delegate -- revoke [Read in Docs](https://docs.ammora.org/examples?command=arl%3Afee-delegate) · [Detailed guide](https://docs.ammora.org/guides/src-arl-readme) · [Local setup](https://docs.ammora.org/guides/example-setup) ## Context and execution GIWA Sepolia 91342. Public SDK 0.3.8, Math 0.3.0. Source revision: `a9b3fd798fa51495e8defec99a25de59e4d70672`. Install the SDK in your app, or prepare the example project using the local setup guide for its npm commands. Review the entry source and guide for required arguments, roles and deployment selection. `AMMORA_SEND=0` does not turn send-only commands into previews. Never put CLI keys in a browser or resend an uncertain transaction. Command: `npm run arl:fee-delegate -- ` (`tsx src/arl/fee-delegate.ts`). Omit `--` and arguments when the source accepts none. ## Entry file: `src/arl/fee-delegate.ts` ```typescript /** Fee-only managed NFT delegation. Only the NFT owner may grant or revoke it. * npm run arl:fee-delegate -- * npm run arl:fee-delegate -- revoke */ import { AmmConcentratedV3PositionDelegatePermission, DammV2Client, ammConcentratedPositionManagerV3Abi } from "@ammora-protocol/sdk"; import { zeroAddress } from "viem"; import { address, clientOptions, deployment, reader, signer } from "../shared/env.js"; import { main, requireContract, send } from "../shared/run.js"; main(async () => { const [poolArg, idArg, delegateArg, mode, ...extra] = process.argv.slice(2); const pool = address("ARL_POOL", poolArg); if (!idArg || !/^\d+$/.test(idArg) || !delegateArg || extra.length) throw new Error("usage: , or revoke"); const tokenId = BigInt(idArg); const revoke = delegateArg === "revoke"; if (revoke ? mode !== undefined : !["claim-fee", "claim-fee-to-owner"].includes(mode ?? "")) throw new Error("select claim-fee, claim-fee-to-owner, or revoke without a mode"); const delegate = revoke ? zeroAddress : address("LP_FEE_DELEGATE", delegateArg); if (!revoke && delegate === zeroAddress) throw new Error("use revoke to clear fee delegation"); const permission = revoke ? 0 : mode === "claim-fee" ? AmmConcentratedV3PositionDelegatePermission.ClaimFee : AmmConcentratedV3PositionDelegatePermission.ClaimFeeToOwner; const { wallet, account } = signer(); if (!revoke && delegate.toLowerCase() === account.address.toLowerCase()) throw new Error("fee delegate must differ from the owner"); const client = await reader(); const contracts = deployment().contracts.launch; await requireContract(client, "pool", pool); const sdk = new DammV2Client(client, pool, "concentrated", contracts.concentratedFactory, contracts.concentratedPositionManager, clientOptions()); // SDK authenticates the managed pool and requires the actual NFT owner. This // replaces the single delegate slot; it never ORs in unrelated permissions. const transaction = await sdk.planUpdateDelegatePermission(tokenId, delegate, permission, account.address); console.log(`position ${tokenId}: delegate ${delegate}, fee permission ${permission}`); const hash = await send("updateDelegatePermission", transaction, wallet, account, client); const receipt = await client.getTransactionReceipt({ hash }); const manager = contracts.concentratedPositionManager; const actualDelegate = await client.readContract({ address: manager, abi: ammConcentratedPositionManagerV3Abi, functionName: "positionDelegate", args: [tokenId], blockNumber: receipt.blockNumber }); const actualPermission = await client.readContract({ address: manager, abi: ammConcentratedPositionManagerV3Abi, functionName: "delegatePermission", args: [tokenId], blockNumber: receipt.blockNumber }); if (actualDelegate.toLowerCase() !== delegate.toLowerCase() || Number(actualPermission) !== permission) throw new Error("Confirmed delegation differs from requested state; reconcile the existing receipt before continuing"); console.log(`confirmed fee delegate ${actualDelegate}, permission ${actualPermission}`); }); ``` ## Supporting files Keep relative paths intact. Local imports are listed recursively; JSON templates are choices to review, not approved inputs. Runtime-selected files and wallet/environment setup still follow the detailed guide. - [src/shared/env.ts](https://docs.ammora.org/examples/source/src/shared/env.ts.txt) - [src/shared/input.ts](https://docs.ammora.org/examples/source/src/shared/input.ts.txt) - [src/shared/retry.ts](https://docs.ammora.org/examples/source/src/shared/retry.ts.txt) - [src/shared/rpc.ts](https://docs.ammora.org/examples/source/src/shared/rpc.ts.txt) - [src/shared/run.ts](https://docs.ammora.org/examples/source/src/shared/run.ts.txt) - [package.json](https://docs.ammora.org/examples/source/package.json.txt) - [package-lock.json](https://docs.ammora.org/examples/source/package-lock.json.txt) - [tsconfig.json](https://docs.ammora.org/examples/source/tsconfig.json.txt) - [.env.giwa.example](https://docs.ammora.org/examples/source/file.env.giwa.example.txt) - [.nvmrc](https://docs.ammora.org/examples/source/file.nvmrc.txt) ## AI implementation checklist Use the actual SDK exports and parameter types in the linked files. Start with reads and unsigned plans where supported. Use a connected wallet for browser signing. Preserve raw integer units, exact approvals, caller authority, deadlines and receipt reconciliation. Report local checks separately from live-chain verification.