diff --git a/1/products.json b/1/products.json index 97c0f05a..db91a293 100644 --- a/1/products.json +++ b/1/products.json @@ -314,11 +314,7 @@ "vaults": [], "deprecatedVaults": [ "0x716bF454066a84F39A2F78b5707e79a9d64f1225", - "0xf2f826c190D020A6D1EC422bF2269E63b8b315E0", - "0x19756be2473A9C3351A426568FF9630c9D67f300", - "0x57995a00bA1e3E17BCF08BA775BF225cA1f5F25e", - "0xd45a56F5b67eBA836295B4c95fd6EA48D1334938", - "0x49aEC2e6137615f0cf1CAE334B5f3211953144bE" + "0xf2f826c190D020A6D1EC422bF2269E63b8b315E0" ], "deprecationReason": "Deprecated due to low activity.", "notExplorable": true diff --git a/143/entities.json b/143/entities.json index 285ebb69..66233a70 100644 --- a/143/entities.json +++ b/143/entities.json @@ -44,7 +44,7 @@ "k3-capital": { "name": "K3 Capital", "logo": "k3.svg", - "description": "K3 Capital provide leading institutional asset and risk management in decentralized finance.", + "description": "K3 Capital is a third-party curator using Euler infrastructure to configure vaults across selected DeFi markets. The curator is responsible for asset selection, vault parameter configuration, and management of the vaults it configures.", "url": "https://k3.capital/", "addresses": { "0x060DB084bF41872861f175d83f3cb1B5566dfEA3": "K3 Capital Multisig" @@ -65,7 +65,7 @@ "hyperithm": { "name": "Hyperithm", "logo": "hyperithm.png", - "description": "Hyperithm maximizes yield by swiftly integrating unique collaterals, providing borrowers with high-return opportunities through active optimization while ensuring effective risk management.", + "description": "Hyperithm is a third-party curator using Euler infrastructure to configure markets involving selected collateral assets. The curator is responsible for asset selection, vault parameter configuration, and management of the vaults it configures.", "url": "https://www.hyperithm.com/", "addresses": { "0x75178137D3B4B9A0F771E0e149b00fB8167BA325": "Hyperithm MPC wallet", diff --git a/146/products.json b/146/products.json index 6771e398..a38785bb 100644 --- a/146/products.json +++ b/146/products.json @@ -2,8 +2,9 @@ "re7-labs-sonic": { "name": "Re7 Labs Cluster", "entity": "re7-labs", - "vaults": ["0xF3c631B979EB59d8333374baA7c58B5Aff5e24D2"], + "vaults": [], "deprecatedVaults": [ + "0xF3c631B979EB59d8333374baA7c58B5Aff5e24D2", "0xEeb63D2d370C5318ef174D366A41507F9380f093", "0x8537a336fC74EA742527E8300175A7b07a108c9E", "0x4c0AF5d6Bcb10B3C05FB5F3a846999a3d87534C7", @@ -12,7 +13,8 @@ "0xeEb1DC1Ca7ffC5b54aD1cc4c1088Db4E5657Cb6c", "0x2ad7546A19eb5D5eDb37EC88A4775995078d1CAd" ], - "notExplorable": true + "notExplorable": true, + "deprecationReason": "Deprecated due to low activity." }, "mev-capital-sonic": { "name": "MEV Capital Sonic Cluster", @@ -45,7 +47,8 @@ "0x2A377470DcF0e00F6DEd484DDf82df65F0Fb50af", "0xB6a53021E7A40ea20DF1e1991B0C89432073386E" ], - "notExplorable": true + "notExplorable": true, + "deprecationReason": "Deprecated due to low activity." }, "mev-capital-beets": { "name": "MEV Capital Beets Cluster", diff --git a/59144/entities.json b/59144/entities.json index 1be72a7f..82b18b93 100644 --- a/59144/entities.json +++ b/59144/entities.json @@ -79,7 +79,7 @@ "alphagrowth": { "name": "AlphaGrowth", "logo": "alphagrowth.svg", - "description": "AlphaGrowth curates lending markets and structured DeFi products on Euler V2.", + "description": "AlphaGrowth is a third-party curator using Euler infrastructure to configure lending markets and structured DeFi products. The curator is responsible for asset selection, vault parameter configuration, and management of the vaults it configures.", "url": "https://alphagrowth.io/", "addresses": { "0x4f894Bfc9481110278C356adE1473eBe2127Fd3C": "AlphaGrowth Multisig" diff --git a/8453/products.json b/8453/products.json index 7886e689..82f52180 100644 --- a/8453/products.json +++ b/8453/products.json @@ -180,9 +180,6 @@ "entity": "jpeg-trading", "url": "https://jpeg-trading.com/", "tags": ["suppress high utilisation warning"], - "vaults": [ - "0xFA493128d7D092A082571b92334574A0a529E799", - "0xcc639251bd612A0930990162c15227D1Ba996354" - ] + "vaults": ["0xcc639251bd612A0930990162c15227D1Ba996354"] } } diff --git a/999/entities.json b/999/entities.json index 03ceae5e..c298eef8 100644 --- a/999/entities.json +++ b/999/entities.json @@ -2,7 +2,7 @@ "clearstar": { "name": "Clearstar", "logo": "clearstar.svg", - "description": "Clearstar is a third-party curator using Euler infrastructure to configure vaults focused on decentralized markets.", + "description": "Clearstar is a third-party curator using Euler infrastructure to configure vaults across selected decentralized markets. The curator is responsible for asset selection, vault parameter configuration, and management of the vaults it configures.", "url": "https://www.clearstar.xyz/", "addresses": { "0x65A067b5955F11F6202F14C3b9Cd64830C4170fB": "Clearstar Governor", diff --git a/README.md b/README.md index 2a97e523..8a92c34b 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ Inside each network's directory are JSON files that correspond to the following Each entry in this object corresponds to a company, organisation, or other entity. Each key is an **entity ID**, which is a unique string per entity. It should be in "URL slug" format (lowercase ASCII letters or numbers, separated by `-` characters). +An entity ID identifies the same organisation on every chain. When an entity appears in several chain files, its `name`, `logo`, `description`, `url` and `social` must be identical in each of them; only `addresses` may differ per file. Verification rejects drift. + * `name`: Full, official name of the entity. (Required) * `logo`: The filename of a logo stored in the `logo/` directory. * `description`: Long-form description of the entity, displayed on the entity's page. @@ -29,7 +31,7 @@ Each entry in this object corresponds to a lending product, which is primarily a * `name`: Official name of the product. (Required) * `description`: Long-form description of the product, displayed on the product's page. -* `entity`: An entity ID that refers to the organisation responsible for governing and/or creating this vault, or a list of entity IDs if the vault is joint-managed. +* `entity`: An entity ID, or a list of entity IDs. The first (or only) entity is the organisation responsible for governing this product; any further entities are display-only co-brands (for example the issuer of the collateral a market is built on) and hold no governance role. Order matters, and every entry must be a distinct entity from `entities.json`. * `url`: Website where users can learn more about the product. * `logo`: The filename of a logo stored in the `logo/` directory. * `vaults`: An array of the vault addresses (in checksumed hex format) that comprise the product. A vault may only appear in one product. diff --git a/verify.js b/verify.js index 2c420eff..e35fd901 100644 --- a/verify.js +++ b/verify.js @@ -78,6 +78,10 @@ const ISO_ALPHA2_RE = /^[A-Za-z]{2}$/; // label wherever it is listed. Registrations accumulate across validateChain // calls so conflicts between chain files are caught too. const entityAddressRegistry = new Map(); +// Entity profiles are chain-agnostic too: the same entity ID must describe the +// same organisation (name, logo, description, url, social) in every chain file. +const ENTITY_PROFILE_FIELDS = ["name", "logo", "description", "url", "social"]; +const entityProfileRegistry = new Map(); for (const file of fs.readdirSync(".")) { if (!/^\d+$/.test(file)) continue; @@ -98,6 +102,7 @@ function validateChain(chainId) { const assets = loadJsonFileIfExists(`${chainId}/assets.json`) || []; registerEntityAddresses(chainId, entities); + registerEntityProfiles(chainId, entities); validateEarnVaults(`${chainId}/earn-vaults.json`, earnVaults); validateAssets(`${chainId}/assets.json`, assets); @@ -169,9 +174,7 @@ function validateChain(chainId) { ); } - for (const entity of getArray(product.entity)) { - if (!entities[entity]) throw Error(`products: no such entity ${entity}`); - } + validateProductEntities(product.entity, entities, productId); if (product.logo && !logos[product.logo]) throw Error(`products: logo not found: ${product.logo}`); @@ -449,6 +452,51 @@ function registerEntityAddresses(chainId, entities) { } } +/** + * Validates that an entity ID describes the same organisation in every chain + * file. Addresses are checked separately by registerEntityAddresses. + */ +function registerEntityProfiles(chainId, entities) { + for (const entityId of Object.keys(entities)) { + const entity = entities[entityId]; + const previous = entityProfileRegistry.get(entityId); + + if (!previous) { + entityProfileRegistry.set(entityId, { chainId, entity }); + continue; + } + + for (const field of ENTITY_PROFILE_FIELDS) { + const before = JSON.stringify(previous.entity[field] ?? null); + const after = JSON.stringify(entity[field] ?? null); + if (before !== after) + throw Error( + `entities: ${entityId}.${field} differs between chain ${previous.chainId} and chain ${chainId}; entity profiles must match across chains`, + ); + } + } +} + +/** + * The first entity governs the product; any further entities are display-only + * co-brands. Every entry must be a distinct, known entity ID. + */ +function validateProductEntities(entity, entities, productId) { + const ids = getArray(entity); + if (ids.length === 0) + throw Error( + `products: entity must name at least one entity ID: ${productId}`, + ); + const seen = new Set(); + for (const entityId of ids) { + if (typeof entityId !== "string" || !entities[entityId]) + throw Error(`products: no such entity ${entityId}: ${productId}`); + if (seen.has(entityId)) + throw Error(`products: entity lists ${entityId} twice: ${productId}`); + seen.add(entityId); + } +} + function loadJsonFile(file) { return JSON.parse(fs.readFileSync(file).toString()); }