Feat/por cre workflows - #2
Conversation
- mFONE method-1: combine Fasanara email NAV + 1token on-chain AUM - Remove offchain-onchain fallback path and onchainAssets config entirely - Fix HTTP call limit (skip fetchSupplyDetails for fundManager tokens) - Fix optional Pinata group ID (try/catch instead of hard fail) - Fix trailing comma in dev JSON configs - Update publicKeySource to midas.app/.well-known/save-keys.json - Update README: new overcol formulas, secrets table, token config fields
- Remove hasOffchainData flag — detected dynamically from fund_manager_claim - Move vlayerEndpoint to global config level (not per-token) - Simplify token entries in verification config to name only - Update per-token templates to match current schema - Trim overcollateralization section in README
There was a problem hiding this comment.
Code Review
This pull request implements the Midas Proof of Reserves (PoR) workflows for attestation and verification within the Chainlink CRE framework. The changes include logic for on-chain oracle price retrieval, Vlayer TLS Notary verification for fund manager emails, and overcollateralization checks using 1token reports. Feedback highlights a logic error in the report-fetching loop that skips fallback timestamps if a threshold check fails, as well as risks associated with fragile regex parsing for email data and the sanitization of malformed API responses to zero. It is also recommended to make the hardcoded attestation expiration configurable to better accommodate different token risk profiles.
| for (const ts of [timestamps.exact, timestamps.before]) { | ||
| let report: OneTokenReportData | null = null | ||
| try { | ||
| report = fetchOneTokenReport(runtime, ts, tokenConfig.oneTokenApi) | ||
| } catch (e) { | ||
| runtime.log(`1token "${ts}" error: ${e instanceof Error ? e.message : String(e)}`) | ||
| continue | ||
| } | ||
| if (!report || typeof report.equity?.total !== 'number') { | ||
| runtime.log(`1token "${ts}" no data — trying next`) | ||
| continue | ||
| } | ||
| if (!oneTokenRawReport) { | ||
| oneTokenRawReport = report | ||
| oneTokenTimestamp = ts | ||
| } | ||
| const passed = evaluateReport(report, ts) | ||
| if (passed) { | ||
| oneTokenReport = report | ||
| } else { | ||
| runtime.log('1token ratio below threshold — falling back to Method 2') | ||
| } | ||
| break | ||
| } |
There was a problem hiding this comment.
The loop tries to fetch the 1token report for the exact timestamp and then falls back to before. However, if the exact fetch succeeds but the evaluateReport check fails (ratio below threshold), it immediately falls back to Method 2 without trying the before timestamp. If the intent is to find any valid report within the window that passes the threshold, the logic should continue to the next timestamp if passed is false.
|
|
||
| const parseAmount = (line: string): number | null => { | ||
| // Match a number like 58,489,684.03 at the end of the line (before optional USD/currency) | ||
| const match = line.match(/([\d,]+\.?\d*)\s*(?:USD)?[\s]*$/) |
There was a problem hiding this comment.
The regular expression used to parse amounts from the email body is somewhat fragile. It assumes the amount is at the end of the line and optionally followed by 'USD'. If the email format changes slightly (e.g., trailing spaces, different currency indicators, or thousands separators other than commas), this parser will fail. Consider a more robust parsing strategy or ensuring the source data is strictly formatted.
|
|
||
| // Return only numeric fields to avoid null values crashing the CRE WASM serializer | ||
| const sanitize = (obj: Record<string, unknown>): Record<string, number> => | ||
| Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, typeof v === 'number' ? v : 0])) |
There was a problem hiding this comment.
The sanitize function replaces any non-numeric value with 0. While this prevents the CRE WASM serializer from crashing on null/undefined values, it can lead to incorrect calculations if a required field is missing or malformed in the 1token API response. It would be safer to validate the presence of critical fields before sanitization.
References
- Enforce defensive programming: ensure appropriate null/nil/None checks or other language-idiomatic guards exist before object property accesses.
| issuer: { identity: attesterPublicKey, name: 'Midas' }, | ||
| publicKeySource: 'https://midas.app/.well-known/save-keys.json', | ||
| createdAt: now.toISOString(), | ||
| expiresAt: new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString(), |
Add CRE attestation and verification workflows for mFONE and mHyperBTC
Implements Chainlink CRE-based Proof of Reserves for Midas tokens using the SAVE framework.
Attestation workflow triggers on NewClaim events, fetches ops data from IPFS, reads oracle price on-chain, verifies the fund manager email via Vlayer TLS Notary (mFONE only), runs an overcollateralization check using 1token AUM + Fasanara email NAV, builds a signed SAVE attestation, and pushes it on-chain.
Verification workflow triggers on AttestationSet events, fetches the attestation from IPFS, verifies all claims via @save/core, and pushes the verification result on-chain.
Supported tokens: mFONE (Fasanara email + 1token on-chain) and mHyperBTC (1token pv_base in BTC).
Includes per-token config templates for third-party verifiers (LlamaRisk etc.) to deploy their own independent verification workflows.