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

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