diff --git a/.env.master.example b/.env.master.example index 0e8dcd54..bcc7ff5b 100644 --- a/.env.master.example +++ b/.env.master.example @@ -279,6 +279,19 @@ RPC_URL_138_FIREBLOCKS= WS_URL_138_FIREBLOCKS= CHAIN_ID_138= +# --- Non-EVM operator binding (liquidity planner / handoff advisory) --- +# Public XRPL r-address (optional). Same as XRPL_ACCOUNT / XRPL_WALLET_ADDRESS / XRP_WALLET_ADDRESS. +XRPL_ACCOUNT= +XRPL_DEPLOYER_ADDRESS= +# Native Tron base58 address override if not using derived EVM→Tron mapping. +TRON_DEPLOYER_ADDRESS= +# Set to 1 after ops confirms derived Tron deployer is canonical (see config/non-evm-operator-binding.json). +TRON_CANONICAL_CONFIRMED= +# Solana public key override if not using SOLANA_KEYPAIR_PATH. +SOLANA_DEPLOYER_ADDRESS= +SOLANA_RPC_URL= +TRONGRID_API_KEY= + # --- Phoenix deploy API --- PORT= GITEA_TOKEN= diff --git a/config/non-evm-lane-requirements.json b/config/non-evm-lane-requirements.json index 2c03a724..d17e464c 100644 --- a/config/non-evm-lane-requirements.json +++ b/config/non-evm-lane-requirements.json @@ -1,6 +1,6 @@ { "schema": "non-evm-lane-requirements/v1", - "generatedAt": "2026-05-11T22:34:46.673427+00:00", + "generatedAt": "2026-05-12T07:06:40.872440+00:00", "status": "stubs_bound_repo_side", "lanes": [ { @@ -13,7 +13,7 @@ "rentReserveTarget", "venueMinimumLiquidity" ], - "minimumFundingTarget": "TBD", + "minimumFundingTarget": "minSolOperationalHint=0.05 (Operational gas+rent buffer hint only; operator confirms live venue targets.)", "claimBoundary": "Do not claim native Solana liquidity until SPL mints, rent/gas, and venue inventory are bound." }, { @@ -26,7 +26,7 @@ "energyBandwidthTarget", "trc20Inventory" ], - "minimumFundingTarget": "TBD", + "minimumFundingTarget": "minTrxOperationalHint=200 (TRX for bandwidth/energy visibility; operator confirms relay inventory.)", "claimBoundary": "Do not claim native Tron liquidity until the canonical wallet and TRC-20 inventory are confirmed." }, { @@ -40,7 +40,7 @@ "trustlineIssuerPolicy", "xrpReserveTarget" ], - "minimumFundingTarget": "TBD", + "minimumFundingTarget": "minXrpReserveHint=2 (Above typical base reserve; operator confirms trustline/issuer policy.)", "claimBoundary": "Do not claim XRPL corridor readiness until account reserve, tags, trustlines, and wXRP controller evidence are closed." }, { @@ -85,5 +85,6 @@ "claimBoundary": "Use HYPE only as a market-cap watch item until identifiers and custody path are verified." } ], + "operatorBindingConfig": "config/non-evm-operator-binding.json", "validationRule": "A lane is claimable only after canonicalWallet, asset IDs, native gas/reserve target, venue target, and evidence source are non-TBD." } diff --git a/config/non-evm-operator-binding.json b/config/non-evm-operator-binding.json new file mode 100644 index 00000000..ebd32216 --- /dev/null +++ b/config/non-evm-operator-binding.json @@ -0,0 +1,32 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "version": "1.0.0", + "description": "Operator-declared non-EVM binding for liquidity planner and advisory closure. Public XRPL r-address may be committed; do not put private seeds here. Flip tron.canonicalDeployerConfirmed only after ops confirms the EVM-derived Tron deployer is canonical.", + "documentation": "Env overrides still win (XRPL_ACCOUNT, TRON_DEPLOYER_ADDRESS, SOLANA_KEYPAIR_PATH). This file supplies defaults and minimum native hints for reports. See docs/03-deployment/CHAIN138_TO_SOLANA_GRU_TOKEN_DEPLOYMENT_LINEUP.md and config/solana-gru-bridge-lineup.json for SPL mint work.", + "xrpl": { + "canonicalAccount": "", + "destinationTagPolicy": "none" + }, + "tron": { + "addressOverride": "", + "canonicalDeployerConfirmed": false + }, + "minimumFundingTargets": { + "solana": { + "minSolOperationalHint": "0.05", + "note": "Operational gas+rent buffer hint only; operator confirms live venue targets." + }, + "tron": { + "minTrxOperationalHint": "200", + "note": "TRX for bandwidth/energy visibility; operator confirms relay inventory." + }, + "xrpl": { + "minXrpReserveHint": "2", + "note": "Above typical base reserve; operator confirms trustline/issuer policy." + } + }, + "otherNonEvmMajors": { + "status": "planning_bucket_only", + "note": "Per-network binding still requires custody + asset IDs per lane; not auto-closed by this file." + } +} diff --git a/config/solana-gru-bridge-lineup.json b/config/solana-gru-bridge-lineup.json index ec7cb6db..58e5160d 100644 --- a/config/solana-gru-bridge-lineup.json +++ b/config/solana-gru-bridge-lineup.json @@ -8,6 +8,7 @@ "canonicalCrosscheck": "docs/11-references/EXPLORER_TOKEN_LIST_CROSSCHECK.md", "tokenMapping": "config/token-mapping-multichain.json", "nonEvmFramework": "config/non-evm-bridge-framework.json", + "nonEvmOperatorBinding": "config/non-evm-operator-binding.json", "runbook": "docs/03-deployment/CHAIN138_TO_SOLANA_GRU_TOKEN_DEPLOYMENT_LINEUP.md" }, "chain138": { diff --git a/docs/04-configuration/CWUSDC_NON_MANUAL_PROVIDER_TASKS.md b/docs/04-configuration/CWUSDC_NON_MANUAL_PROVIDER_TASKS.md index 23147c5d..34ca05c5 100644 --- a/docs/04-configuration/CWUSDC_NON_MANUAL_PROVIDER_TASKS.md +++ b/docs/04-configuration/CWUSDC_NON_MANUAL_PROVIDER_TASKS.md @@ -17,7 +17,7 @@ Latest evidence source: [../../reports/status/cwusdc-provider-handoff-latest.md] | CoinGecko price API | External blocker | API responds but does not include the cWUSDC contract key (primary listing gate). | | DexScreener token APIs | Optional / usually empty pre-indexing | Same JSON: `summary.dexScreenerTokenApisIndexed`; not counted in `failedRequiredIds`. | | EVM liquidity-gap planner rows | No current rows | Latest planner summary shows `rows = 0`. | -| Non-EVM funding requirements | Open, operator-bound | Solana, Tron, XRPL, and other non-EVM requirements need wallet/asset/target binding before claims. | +| Non-EVM funding requirements | Open, operator-bound | Solana, Tron, XRPL, and other non-EVM requirements need wallet/asset/target binding before claims. Hints and env keys: `config/non-evm-operator-binding.json`, `.env.master.example` (XRPL / Tron / Solana block); planner reads binding via `plan-token-aggregation-liquidity-gap-funding.mjs`. | | Mainnet cWUSDC supply attestation | Refreshed | `reports/status/cwusdc-supply-circulating-attestation-latest.md`; circulating supply `10451316981.309788`. | | Global cUSDC/cWUSDC family proof | Refreshed | `reports/status/global-cusdc-cwusdc-family-supply-proof-latest.md`; `22 / 23` entries proved for aggregate. | | Etherscan Value dossier | Refreshed | `reports/status/cwusdc-etherscan-value-dossier-latest.md`; ready for external submission, but Etherscan USD Value not ready. | diff --git a/docs/04-configuration/GRU_TRANSPORT_LOADER_DESIGN_SPEC.md b/docs/04-configuration/GRU_TRANSPORT_LOADER_DESIGN_SPEC.md index 94365dd6..5d11a33d 100644 --- a/docs/04-configuration/GRU_TRANSPORT_LOADER_DESIGN_SPEC.md +++ b/docs/04-configuration/GRU_TRANSPORT_LOADER_DESIGN_SPEC.md @@ -159,6 +159,7 @@ Implementation is complete when, with pinned JSON and extended loader, **all** o ## 9. Related documents +- Non-EVM funding hints (planner / lane stubs, orthogonal to GRU loader): [config/non-evm-operator-binding.json](../../config/non-evm-operator-binding.json), `scripts/verify/build-non-evm-requirement-stubs.py`, `scripts/verify/plan-token-aggregation-liquidity-gap-funding.mjs`. - Provider / wallet matrix (narrative, not loader): [metamask/METAMASK_ASSET_PRICE_PROVIDER_SUBMISSION_MATRIX.md](metamask/METAMASK_ASSET_PRICE_PROVIDER_SUBMISSION_MATRIX.md) - MetaMask / GRU / DefiLlama master replay: [00-meta/METAMASK_GRU_DEFILLAMA_CHAIN138_MASTER_REFERENCE.md](00-meta/METAMASK_GRU_DEFILLAMA_CHAIN138_MASTER_REFERENCE.md) - Token mapping loader source: [`config/token-mapping-loader.cjs`](../../config/token-mapping-loader.cjs) diff --git a/scripts/validation/validate-config-files.sh b/scripts/validation/validate-config-files.sh index cc811f1b..fad81934 100755 --- a/scripts/validation/validate-config-files.sh +++ b/scripts/validation/validate-config-files.sh @@ -93,6 +93,24 @@ else fi fi fi + if [[ -f "$PROJECT_ROOT/config/non-evm-operator-binding.json" ]]; then + log_ok "Found: config/non-evm-operator-binding.json" + if command -v jq &>/dev/null; then + if jq -e ' + (.version | type == "string") + and (.xrpl | type == "object") + and (.tron | type == "object") + and (.minimumFundingTargets | type == "object") + ' "$PROJECT_ROOT/config/non-evm-operator-binding.json" &>/dev/null; then + log_ok "non-evm-operator-binding.json: structure valid" + else + log_err "non-evm-operator-binding.json: invalid structure" + ERRORS=$((ERRORS + 1)) + fi + fi + else + log_warn "Optional config/non-evm-operator-binding.json missing; skipping" + fi if [[ -f "$PROJECT_ROOT/config/public-routing-coverage-matrix.json" ]]; then log_ok "Found: config/public-routing-coverage-matrix.json" if command -v jq &>/dev/null; then diff --git a/scripts/verify/build-non-evm-requirement-stubs.py b/scripts/verify/build-non-evm-requirement-stubs.py index 612649de..8f6a83f2 100755 --- a/scripts/verify/build-non-evm-requirement-stubs.py +++ b/scripts/verify/build-non-evm-requirement-stubs.py @@ -13,6 +13,16 @@ ROOT = Path(__file__).resolve().parents[2] CONFIG_OUT = ROOT / "config/non-evm-lane-requirements.json" REPORT_JSON = ROOT / "reports/status/non-evm-lane-requirements-latest.json" REPORT_MD = ROOT / "reports/status/non-evm-lane-requirements-latest.md" +OPERATOR_BINDING = ROOT / "config/non-evm-operator-binding.json" + + +def load_operator_binding_hints() -> dict[str, Any]: + if not OPERATOR_BINDING.exists(): + return {} + try: + return json.loads(OPERATOR_BINDING.read_text()) + except (json.JSONDecodeError, OSError): + return {} LANES: list[dict[str, Any]] = [ { @@ -91,11 +101,36 @@ def table(headers: list[str], rows: list[list[Any]]) -> str: def main() -> int: generated_at = datetime.now(timezone.utc).isoformat() + bind = load_operator_binding_hints() + hints = bind.get("minimumFundingTargets") or {} + + def min_tgt(network: str) -> str: + m = hints.get(network) if hints else None + if isinstance(m, dict) and m: + parts = [f"{k}={v}" for k, v in m.items() if k != "note" and v] + note = m.get("note") + base = "; ".join(parts) if parts else "TBD" + return f"{base} ({note})" if note else base + return "TBD" + + lanes_out = [] + for lane in LANES: + row = dict(lane) + net = row["network"] + if net == "solana": + row["minimumFundingTarget"] = min_tgt("solana") + elif net == "tron": + row["minimumFundingTarget"] = min_tgt("tron") + elif net == "xrpl": + row["minimumFundingTarget"] = min_tgt("xrpl") + lanes_out.append(row) + payload = { "schema": "non-evm-lane-requirements/v1", "generatedAt": generated_at, "status": "stubs_bound_repo_side", - "lanes": LANES, + "lanes": lanes_out, + "operatorBindingConfig": str(OPERATOR_BINDING.relative_to(ROOT)), "validationRule": "A lane is claimable only after canonicalWallet, asset IDs, native gas/reserve target, venue target, and evidence source are non-TBD.", } for path in (CONFIG_OUT, REPORT_JSON): @@ -120,7 +155,7 @@ def main() -> int: lane["minimumFundingTarget"], lane["claimBoundary"], ] - for lane in LANES + for lane in lanes_out ], ), ] diff --git a/scripts/verify/plan-token-aggregation-liquidity-gap-funding.mjs b/scripts/verify/plan-token-aggregation-liquidity-gap-funding.mjs index 9e570eb8..cc00a6ec 100644 --- a/scripts/verify/plan-token-aggregation-liquidity-gap-funding.mjs +++ b/scripts/verify/plan-token-aggregation-liquidity-gap-funding.mjs @@ -18,6 +18,15 @@ const repoRoot = resolve(new URL("../..", import.meta.url).pathname); const readinessPath = resolve(repoRoot, "reports/status/token-aggregation-adoption-readiness-live-20260509.json"); const nonEvmHealthPath = resolve(repoRoot, "reports/status/non-evm-network-health-latest.json"); const nonEvmLaneStatusPath = resolve(repoRoot, "reports/status/non-evm-lane-status-latest.json"); +const operatorNonEvmBindingPath = resolve(repoRoot, "config/non-evm-operator-binding.json"); + +function readOperatorNonEvmBinding() { + try { + return JSON.parse(readFileSync(operatorNonEvmBindingPath, "utf8")); + } catch { + return {}; + } +} const jsonOut = resolve(repoRoot, "reports/status/token-aggregation-liquidity-gap-funding-plan-latest.json"); const mdOut = resolve(repoRoot, "reports/status/token-aggregation-liquidity-gap-funding-plan-latest.md"); const deployer = (process.env.DEPLOYER_ADDRESS || process.env.DEPLOYER || "0x4A666F96fC8764181194447A7dFdb7d471b301C8").trim(); @@ -298,7 +307,9 @@ function solanaWalletFromConfig() { return { address: "", source: "missing" }; } -function tronWalletFromConfig() { +function tronWalletFromConfig(binding) { + const override = binding?.tron?.addressOverride?.trim(); + if (override) return { address: override, source: "config_non_evm_operator_binding_tron_override" }; const explicit = readEnvValue("TRON_DEPLOYER_ADDRESS", "TRON_WALLET_ADDRESS", "TRON_PUBLIC_ADDRESS", "TRON_ACCOUNT_ADDRESS"); if (explicit) return { address: explicit, source: "env_tron_address" }; const ethAddress = deployer.replace(/^0x/i, ""); @@ -310,9 +321,26 @@ function tronWalletFromConfig() { return { address: "", source: "missing" }; } -function xrplWalletFromConfig() { +function xrplWalletFromConfig(binding) { const explicit = readEnvValue("XRPL_DEPLOYER_ADDRESS", "XRP_DEPLOYER_ADDRESS", "XRPL_WALLET_ADDRESS", "XRP_WALLET_ADDRESS", "XRPL_ACCOUNT"); - return explicit ? { address: explicit, source: "env_xrpl_address" } : { address: "", source: "missing" }; + if (explicit?.trim()) return { address: explicit.trim(), source: "env_xrpl_address" }; + const cfg = binding?.xrpl?.canonicalAccount?.trim(); + if (cfg) return { address: cfg, source: "config_non_evm_operator_binding" }; + return { address: "", source: "missing" }; +} + +function requiredFundingFor(network, binding) { + const m = binding?.minimumFundingTargets; + if (network === "Solana" && m?.solana?.minSolOperationalHint) { + return `${m.solana.minSolOperationalHint} SOL (repo hint; operator confirms venue targets)`; + } + if (network === "Tron" && m?.tron?.minTrxOperationalHint) { + return `${m.tron.minTrxOperationalHint} TRX (repo hint; operator confirms energy/inventory)`; + } + if (network === "XRPL" && m?.xrpl?.minXrpReserveHint) { + return `${m.xrpl.minXrpReserveHint} XRP (repo reserve hint; align trustlines/issuer policy)`; + } + return "TBD"; } async function solanaNativeBalance(address) { @@ -501,15 +529,17 @@ async function buildNonEvmFundingRequirements() { const health = readJsonIfExists(nonEvmHealthPath, null); const laneStatus = readJsonIfExists(nonEvmLaneStatusPath, null); const lanes = laneStatus?.lanes ?? {}; + const binding = readOperatorNonEvmBinding(); const solanaWallet = solanaWalletFromConfig(); - const tronWallet = tronWalletFromConfig(); - const xrplWallet = xrplWalletFromConfig(); + const tronWallet = tronWalletFromConfig(binding); + const xrplWallet = xrplWalletFromConfig(binding); + const tronCanon = binding?.tron?.canonicalDeployerConfirmed === true; const [solanaBalance, tronBalance, xrplBalance] = await Promise.all([ solanaNativeBalance(solanaWallet.address), tronNativeBalance(tronWallet.address), xrplNativeBalance(xrplWallet.address), ]); - return [ + const requirements = [ { network: "Solana", target: "mainnet-beta", @@ -520,7 +550,7 @@ async function buildNonEvmFundingRequirements() { currentBalanceRaw: solanaBalance.raw, nativeGasAsset: "SOL", bridgeOrWrappedAsset: lanes.solana?.destinationAsset?.symbol ?? "cWAUSDT", - requiredFunding: "TBD", + requiredFunding: requiredFundingFor("Solana", binding), status: solanaWallet.address ? "spl_mint_inventory_and_minimum_funding_targets_required" : "wallet_and_spl_mint_inventory_required", networkHealth: networkHealth(health, "Solana"), requirements: [ @@ -540,11 +570,20 @@ async function buildNonEvmFundingRequirements() { currentBalanceRaw: tronBalance.raw, nativeGasAsset: "TRX", bridgeOrWrappedAsset: "TronAdapter relay inventory", - requiredFunding: "TBD", - status: tronWallet.source === "derived_from_evm_deployer_address" ? "derived_tron_wallet_needs_operator_confirmation_and_asset_inventory" : "native_tron_wallet_and_asset_inventory_required", + requiredFunding: requiredFundingFor("Tron", binding), + status: + !tronWallet.address + ? "native_tron_wallet_and_asset_inventory_required" + : tronWallet.source === "derived_from_evm_deployer_address" && !tronCanon + ? "derived_tron_wallet_needs_operator_confirmation_and_asset_inventory" + : "native_tron_wallet_and_asset_inventory_required", networkHealth: networkHealth(health, "Tron"), requirements: [ - tronWallet.source === "derived_from_evm_deployer_address" ? "Confirm whether the EVM deployer-derived Tron address is the canonical native Tron deployer." : "Bind canonical Tron custody wallet address.", + tronCanon + ? "Operator confirmed EVM-derived Tron deployer as canonical (config/non-evm-operator-binding.json tron.canonicalDeployerConfirmed)." + : tronWallet.source === "derived_from_evm_deployer_address" + ? "Confirm whether the EVM deployer-derived Tron address is the canonical native Tron deployer (set tron.canonicalDeployerConfirmed when true)." + : "Bind canonical Tron custody wallet address.", "Check TRX energy/bandwidth funding and any native TRC-20 inventory needed for relay settlement.", "Promote or document native Tron-side contracts/assets before treating Tron as liquidity-ready.", "Close Chain 138 TronAdapter source/publication evidence separately from native Tron funding.", @@ -560,15 +599,18 @@ async function buildNonEvmFundingRequirements() { currentBalanceRaw: xrplBalance.raw, nativeGasAsset: "XRP", bridgeOrWrappedAsset: lanes.xrpl?.wrappedAsset?.address ? `wXRP ${lanes.xrpl.wrappedAsset.address}` : "wXRP", - requiredFunding: "TBD", + requiredFunding: requiredFundingFor("XRPL", binding), status: xrplWallet.address ? "xrpl_reserve_trustline_and_bridge_inventory_required" : "xrpl_wallet_reserve_and_bridge_inventory_required", networkHealth: networkHealth(health, "XRPL"), requirements: [ xrplWallet.address ? "Canonical XRPL account is bound for native XRP checks." : "Bind canonical XRPL account and optional destination tag policy.", + binding?.xrpl?.destinationTagPolicy && binding.xrpl.destinationTagPolicy !== "none" + ? `Destination tag policy (from config): ${binding.xrpl.destinationTagPolicy}.` + : null, "Check XRP reserve, transfer-fee cushion, and any trustline/issuer requirements.", "Check Chain 138 wXRP inventory and MintBurnController authorization readiness.", "Close Chain 138 XRPLAdapter/wXRP/MintBurnController source-publication evidence separately from XRPL funding.", - ], + ].filter(Boolean), }, { network: "Other non-EVM majors", @@ -590,8 +632,17 @@ async function buildNonEvmFundingRequirements() { ], }, ]; + return { + requirements, + bindingEcho: { + configPath: "config/non-evm-operator-binding.json", + xrplAccountBound: Boolean(xrplWallet.address), + tronCanonicalDeployerConfirmed: tronCanon, + tronWalletSource: tronWallet.source, + solanaWalletSource: solanaWallet.source, + }, + }; } - const readiness = JSON.parse(readFileSync(readinessPath, "utf8")); const details = readiness.blockerInventory?.liquidityMissingDetails ?? []; const rows = []; @@ -743,7 +794,8 @@ const counts = rows.reduce((acc, row) => { return acc; }, {}); -const nonEvmFundingRequirements = await buildNonEvmFundingRequirements(); +const nev = await buildNonEvmFundingRequirements(); +const nonEvmFundingRequirements = nev.requirements; const ethereumSourceInventory = await buildEthereumSourceInventory(); const payload = { @@ -778,6 +830,7 @@ const payload = { coffeeMoneyPlan, ethereumSourceInventory, nonEvmFundingRequirements, + operatorNonEvmBindingEcho: nev.bindingEcho, }; const md = [ @@ -896,7 +949,12 @@ const md = [ "", "## Non-EVM Funding Requirements", "", - "These networks are now part of funding scope. The planner resolves non-EVM deployer wallets where the repo can prove them, checks native gas balances where possible, and leaves funding amounts `TBD` until asset IDs and minimum venue targets are bound.", + "These networks are now part of funding scope. The planner resolves non-EVM deployer wallets where the repo can prove them, checks native gas balances where possible, and applies `minimumFundingTargets` hints from `config/non-evm-operator-binding.json` (operator must still confirm live venue targets).", + "", + table( + ["Field", "Value"], + Object.entries(payload.operatorNonEvmBindingEcho || {}).map(([k, v]) => [k, typeof v === "object" ? JSON.stringify(v) : String(v)]), + ), "", table( ["Network", "Target", "Wallet", "Source", "Native gas", "Current balance", "Required funding", "Status"],