feat(non-evm): operator binding config, planner hints, and validation
Some checks failed
Deploy to Phoenix / deploy (push) Has been skipped
Deploy to Phoenix / deploy-atomic-swap-dapp (push) Has been skipped
Deploy to Phoenix / cloudflare (push) Has been skipped
Deploy to Phoenix / validate (push) Failing after 3s

- Add config/non-evm-operator-binding.json (public hints only; no secrets).
- Extend .env.master.example with XRPL/Tron/Solana/TRONGRID overrides.
- Wire solana-gru-bridge-lineup refs; refresh non-evm lane stubs from binding.
- Teach liquidity-gap planner to read binding; validate JSON in validate-config-files.sh.
- Document handoff in CWUSDC_NON_MANUAL_PROVIDER_TASKS; cross-link GRU spec.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
defiQUG
2026-05-12 00:08:09 -07:00
parent 320e1410ea
commit 09e8c08023
9 changed files with 181 additions and 22 deletions

View File

@@ -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=

View File

@@ -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."
}

View File

@@ -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."
}
}

View File

@@ -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": {

View File

@@ -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. |

View File

@@ -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)

View File

@@ -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

View File

@@ -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
],
),
]

View File

@@ -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"],