{"version":3,"sources":["../../src/protocol/fee-registry.ts"],"sourcesContent":["/**\n * Adapter for the on-chain FeeRegistry contract — the source of truth the\n * gateway re-reads on every /v1/escrow/pay to size the payment amount.\n *\n * The registry stores per-operation `{amount, asset, payee, enabled}`\n * records keyed on `keccak256(name)`. SDK consumers (builders sizing\n * payments) MUST read fees from this contract rather than hardcoding\n * them, or the signed `amount` won't match the gateway's expected total\n * and /v1/escrow/pay returns 400.\n *\n * Five operation kinds are wired up; each kind's name matches the\n * corresponding `payments.kind` column value on the gateway:\n *\n *   - 'grant_registration'   — one-time fee when a grant is first registered\n *                              (POST /v1/grants).\n *   - 'data_access'          — per-access surcharge on grant payments\n *                              (every accessRecord posted against a grant).\n *   - 'data_registration'    — one-time fee for registering a data point\n *                              (POST /v1/data → addDataWithSignature).\n *   - 'server_registration'  — one-time fee for registering a personal server\n *                              (POST /v1/servers).\n *   - 'builder_registration' — one-time fee for registering a builder\n *                              (POST /v1/builders; gateway-only, no on-chain\n *                              submission).\n *\n * Disabled or unregistered fees skip enforcement: the corresponding\n * operation settles WITHOUT requiring a payment from escrow. `getFee`\n * surfaces this via `enabled: false` rather than throwing — the gateway\n * itself treats a disabled fee as a steady state, not a misconfig.\n *\n * Deployers can override the on-chain operation strings via\n * `FEE_REGISTRY_<KIND>_OP` env vars on the gateway side; the matching\n * SDK escape hatch is the `opts.<kind>OpName` arguments.\n *\n * This module is signer/transport-agnostic — callers pass in their own\n * `PublicClient` for the contract reads. No caching here; long-running\n * service consumers should wrap with their own TTL. Mirrors\n * data-gateway/lib/fee-registry.ts byte-for-byte.\n *\n * @category Protocol\n */\nimport { parseAbi, type Address, type Hex, type PublicClient } from \"viem\";\nimport type { DataPortabilityGatewayConfig } from \"./eip712\";\n\nexport const FEE_REGISTRY_ABI = parseAbi([\n  \"struct Fee { uint256 amount; address asset; address payee; bool enabled; }\",\n  \"function fees(bytes32 operation) view returns (Fee)\",\n  \"function operationKey(string name) pure returns (bytes32)\",\n]);\n\nexport type FeeKind =\n  | \"grant_registration\"\n  | \"data_access\"\n  | \"data_registration\"\n  | \"server_registration\"\n  | \"builder_registration\";\n\n/**\n * Map from a user-facing opType (POST /v1/escrow/pay body field, matches\n * the gateway's `payments.op_type` column) to the FeeKind that gates its\n * one-time registration fee.\n *\n * Data access is a per-call surcharge on grants only — it's not a\n * registration fee for any op, so it lives outside this map.\n */\nexport const REGISTRATION_KIND_FOR_OP: Record<string, FeeKind> = {\n  grant: \"grant_registration\",\n  data: \"data_registration\",\n  server: \"server_registration\",\n  builder: \"builder_registration\",\n};\n\nexport interface FeeEntry {\n  amount: bigint;\n  // Asset the fee is denominated in. 0x0000…0000 = native VANA; anything\n  // else is an ERC-20 contract address.\n  asset: Address;\n  // The recipient the on-chain settle pass routes the fee to. Only\n  // meaningful when `enabled` — disabled fees never land as a SettleOp `to`.\n  payee: Address;\n  enabled: boolean;\n}\n\n/**\n * Compound fee schedule for one op type, mirroring the gateway's\n * lib/op-fees.ts `OpFee`. For ANY op type, `registrationFee` is the\n * one-time fee charged at registration. For `'grant'` only, `dataAccessFee`\n * is the per-access surcharge — for any other op type it's always 0n with\n * `dataAccessEnabled: false`.\n *\n * `xxxEnabled` reflects the on-chain `Fee.enabled` flag. When OFF, the\n * corresponding amount is 0 and the pay handler should NOT require\n * payment for that kind. When both are off (for a grant) or registration\n * is off (for any other op type), the entire payment flow is skipped —\n * the op settles directly via the no-payment path.\n */\nexport interface OpFee {\n  // Asset for whichever components are enabled. Falls back to native VANA\n  // (0x0) when both components are disabled. The pay handler enforces that\n  // the payer's `asset` matches.\n  asset: Address;\n  registrationFee: bigint;\n  dataAccessFee: bigint;\n  registrationEnabled: boolean;\n  dataAccessEnabled: boolean;\n  // Surfaced for the SDK's on-chain log-filter use case; the gateway's\n  // OpFee type doesn't include these but the SDK keeps them since callers\n  // sizing on-chain assertions need to know where the fee lands. Equal to\n  // the zero address when the corresponding kind is disabled.\n  registrationPayee: Address;\n  dataAccessPayee: Address;\n}\n\nexport interface FeeRegistryOptions {\n  grantRegistrationOpName?: string;\n  dataAccessOpName?: string;\n  dataRegistrationOpName?: string;\n  serverRegistrationOpName?: string;\n  builderRegistrationOpName?: string;\n}\n\nfunction operationNameFor(\n  kind: FeeKind,\n  opts: FeeRegistryOptions | undefined,\n): string {\n  switch (kind) {\n    case \"grant_registration\":\n      return opts?.grantRegistrationOpName ?? \"grant_registration\";\n    case \"data_access\":\n      return opts?.dataAccessOpName ?? \"data_access\";\n    case \"data_registration\":\n      return opts?.dataRegistrationOpName ?? \"data_registration\";\n    case \"server_registration\":\n      return opts?.serverRegistrationOpName ?? \"server_registration\";\n    case \"builder_registration\":\n      return opts?.builderRegistrationOpName ?? \"builder_registration\";\n  }\n}\n\nconst ZERO_ADDRESS = \"0x0000000000000000000000000000000000000000\" as Address;\n\n/**\n * Reads one fee kind from the FeeRegistry. Calls the contract's\n * `operationKey(name)` first to derive the bytes32 key — matches the\n * gateway's approach exactly (could compute locally via keccak256, but\n * going through the contract eliminates any chance of encoding drift).\n *\n * Returns `{enabled: false}` entries WITHOUT throwing — disabled is a\n * valid steady state on the gateway. The only validation is the\n * zero-payee check, and that only fires when the fee is enabled\n * (a disabled fee never lands as a SettleOp `to`).\n */\nexport async function getFee(\n  client: PublicClient,\n  config: DataPortabilityGatewayConfig,\n  kind: FeeKind,\n  opts?: FeeRegistryOptions,\n): Promise<FeeEntry> {\n  const address = config.contracts.feeRegistry as Address;\n  const opName = operationNameFor(kind, opts);\n\n  const opKey = (await client.readContract({\n    address,\n    abi: FEE_REGISTRY_ABI,\n    functionName: \"operationKey\",\n    args: [opName],\n  })) as Hex;\n\n  const fee = (await client.readContract({\n    address,\n    abi: FEE_REGISTRY_ABI,\n    functionName: \"fees\",\n    args: [opKey],\n  })) as FeeEntry;\n\n  if (fee.enabled && fee.payee === ZERO_ADDRESS) {\n    throw new Error(\n      `FeeRegistry: enabled operation \"${opName}\" has zero-address payee — contract pre-flight rejects payouts to 0x0`,\n    );\n  }\n\n  return fee;\n}\n\n/**\n * Convenience: combine the FeeRegistry reads for one op type into the\n * compound shape the pay handler validates against.\n *\n * For 'grant' opType the result includes both registration + data_access\n * components; for other op types data_access is always disabled with\n * amount=0. Disabled components contribute 0 to the signed total —\n * callers compute `amount = registrationFee + dataAccessFee` and the pay\n * handler accepts (or short-circuits with 'Payment not required' when\n * both are 0).\n *\n * Throws on asset mismatch ONLY when both components are enabled — a\n * disabled fee never lands as a SettleOp, so its asset is moot.\n */\nexport async function getOpFee(\n  client: PublicClient,\n  config: DataPortabilityGatewayConfig,\n  opType: string,\n  opts?: FeeRegistryOptions,\n): Promise<OpFee> {\n  const registrationKind = REGISTRATION_KIND_FOR_OP[opType];\n  if (!registrationKind) {\n    throw new Error(\n      `getOpFee: unknown opType \"${opType}\" — supported types are ${Object.keys(REGISTRATION_KIND_FOR_OP).join(\", \")}`,\n    );\n  }\n\n  const includeDataAccess = opType === \"grant\";\n  const [registration, dataAccess] = await Promise.all([\n    getFee(client, config, registrationKind, opts),\n    includeDataAccess\n      ? getFee(client, config, \"data_access\", opts)\n      : Promise.resolve<FeeEntry>({\n          amount: 0n,\n          asset: ZERO_ADDRESS,\n          payee: ZERO_ADDRESS,\n          enabled: false,\n        }),\n  ]);\n\n  if (\n    registration.enabled &&\n    dataAccess.enabled &&\n    registration.asset.toLowerCase() !== dataAccess.asset.toLowerCase()\n  ) {\n    throw new Error(\n      `FeeRegistry asset mismatch for \"${opType}\": registration=${registration.asset} vs data_access=${dataAccess.asset}. The gateway requires both kinds to settle in the same asset when both are enabled.`,\n    );\n  }\n\n  const asset = registration.enabled\n    ? registration.asset\n    : dataAccess.enabled\n      ? dataAccess.asset\n      : ZERO_ADDRESS;\n\n  return {\n    asset,\n    registrationFee: registration.enabled ? registration.amount : 0n,\n    dataAccessFee: dataAccess.enabled ? dataAccess.amount : 0n,\n    registrationEnabled: registration.enabled,\n    dataAccessEnabled: dataAccess.enabled,\n    registrationPayee: registration.enabled ? registration.payee : ZERO_ADDRESS,\n    dataAccessPayee: dataAccess.enabled ? dataAccess.payee : ZERO_ADDRESS,\n  };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyCA,kBAAoE;AAG7D,MAAM,uBAAmB,sBAAS;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAiBM,MAAM,2BAAoD;AAAA,EAC/D,OAAO;AAAA,EACP,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,SAAS;AACX;AAmDA,SAAS,iBACP,MACA,MACQ;AACR,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO,MAAM,2BAA2B;AAAA,IAC1C,KAAK;AACH,aAAO,MAAM,oBAAoB;AAAA,IACnC,KAAK;AACH,aAAO,MAAM,0BAA0B;AAAA,IACzC,KAAK;AACH,aAAO,MAAM,4BAA4B;AAAA,IAC3C,KAAK;AACH,aAAO,MAAM,6BAA6B;AAAA,EAC9C;AACF;AAEA,MAAM,eAAe;AAarB,eAAsB,OACpB,QACA,QACA,MACA,MACmB;AACnB,QAAM,UAAU,OAAO,UAAU;AACjC,QAAM,SAAS,iBAAiB,MAAM,IAAI;AAE1C,QAAM,QAAS,MAAM,OAAO,aAAa;AAAA,IACvC;AAAA,IACA,KAAK;AAAA,IACL,cAAc;AAAA,IACd,MAAM,CAAC,MAAM;AAAA,EACf,CAAC;AAED,QAAM,MAAO,MAAM,OAAO,aAAa;AAAA,IACrC;AAAA,IACA,KAAK;AAAA,IACL,cAAc;AAAA,IACd,MAAM,CAAC,KAAK;AAAA,EACd,CAAC;AAED,MAAI,IAAI,WAAW,IAAI,UAAU,cAAc;AAC7C,UAAM,IAAI;AAAA,MACR,mCAAmC,MAAM;AAAA,IAC3C;AAAA,EACF;AAEA,SAAO;AACT;AAgBA,eAAsB,SACpB,QACA,QACA,QACA,MACgB;AAChB,QAAM,mBAAmB,yBAAyB,MAAM;AACxD,MAAI,CAAC,kBAAkB;AACrB,UAAM,IAAI;AAAA,MACR,6BAA6B,MAAM,gCAA2B,OAAO,KAAK,wBAAwB,EAAE,KAAK,IAAI,CAAC;AAAA,IAChH;AAAA,EACF;AAEA,QAAM,oBAAoB,WAAW;AACrC,QAAM,CAAC,cAAc,UAAU,IAAI,MAAM,QAAQ,IAAI;AAAA,IACnD,OAAO,QAAQ,QAAQ,kBAAkB,IAAI;AAAA,IAC7C,oBACI,OAAO,QAAQ,QAAQ,eAAe,IAAI,IAC1C,QAAQ,QAAkB;AAAA,MACxB,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,OAAO;AAAA,MACP,SAAS;AAAA,IACX,CAAC;AAAA,EACP,CAAC;AAED,MACE,aAAa,WACb,WAAW,WACX,aAAa,MAAM,YAAY,MAAM,WAAW,MAAM,YAAY,GAClE;AACA,UAAM,IAAI;AAAA,MACR,mCAAmC,MAAM,mBAAmB,aAAa,KAAK,mBAAmB,WAAW,KAAK;AAAA,IACnH;AAAA,EACF;AAEA,QAAM,QAAQ,aAAa,UACvB,aAAa,QACb,WAAW,UACT,WAAW,QACX;AAEN,SAAO;AAAA,IACL;AAAA,IACA,iBAAiB,aAAa,UAAU,aAAa,SAAS;AAAA,IAC9D,eAAe,WAAW,UAAU,WAAW,SAAS;AAAA,IACxD,qBAAqB,aAAa;AAAA,IAClC,mBAAmB,WAAW;AAAA,IAC9B,mBAAmB,aAAa,UAAU,aAAa,QAAQ;AAAA,IAC/D,iBAAiB,WAAW,UAAU,WAAW,QAAQ;AAAA,EAC3D;AACF;","names":[]}