diff --git a/ON_CHAIN_UPDATES.md b/ON_CHAIN_UPDATES.md
deleted file mode 100644
index 7eca81dc..00000000
--- a/ON_CHAIN_UPDATES.md
+++ /dev/null
@@ -1,42 +0,0 @@
-# On-chain Updates
-
-## Introduction
-
-This document is used for logging the on-chain updates for products and product types. The updates are made by the Nexus Mutual team and are used to keep the SDK up-to-date with the latest on-chain data.
-
-## How to update this document
-
-To update this document, follow the steps below:
-
-1. Identify the section(s) you need to update based on whether the on-chain changes are at listing level or at product type level.
-2. In the corresponding section(s), add a new line starting with `YYYY-MM-DD`, where `YYYY-MM-DD` is the date of the update.
-3. Fill in the details of the update. e.g. `Updated Protocol Cover cover wording`, `Updated Pendle listing annex`, etc.
-4. Submit a PR with the changes.
-
-## Product Types Updates
-
-- 2025-03-26: Updated `Native Syndicate Cover` cover wording.
-
-## Listings Updates
-
-- 2025-04-23: Updated the Annex for `Fasanara`.
-- 2025-05-21: Updated `Fasanara` listing annex.
-- 2025-10-03: Updated `Dialectic Ellipse` listing annex
-- 2025-11-05: Updated `AUTOfinance` listing name and annex.
-- 2025-11-05: Updated `AUTOfinance autoUSD Vault` listing name and annex.
-- 2025-11-05: Updated `Blue Chip Euler v2 Vaults & Markets` listing name and annex.
-- 2025-11-05: Updated `Blue Chip Morpho Vaults & Markets` listing name and annex.
-- 2025-11-05: Updated `B-Tier Morpho Vaults & Markets` listing name and annex.
-- 2025-11-05: Updated `B-Tier Euler v2 Vaults & Markets` listing name and annex.
-- 2025-11-05: Updated `Elite Cover` listing annex.
-- 2025-11-05: Updated `Essential Cover` listing annex.
-- 2025-11-05: Updated `Entry Cover` listing annex.
-- 2025-11-05: Updated `Brava Advanced Cover` listing annex.
-- 2025-11-05: Updated `Brava Balanced Cover` listing annex.
-- 2025-11-05: Updated `Yearn v3` listing annex.
-- 2025-11-05: Updated `Beefy` listing annex.
-- 2025-12-09: Updated `Dialectic Ellipse` listing annex.
-- 2025-12-11: Updated `Defi Vault 2` listing annex.
-- 2026-01-26: Updated `K&R AAA` && `K&R AA` && `K&R A` && `K&R B` && `K&R C` && `K&R D` listings min price.
-- 2026-02-02: Updated `Kidnap & Ransom Cover`product type grace period
-- 2026-02-05: Updated `OpenCover Morpho Base Vaults` & `Noon DeFi Deployment 1` & `Noon DeFi Deployment 2` listing annex.
diff --git a/README.md b/README.md
index 9032bfbb..4a464d38 100644
--- a/README.md
+++ b/README.md
@@ -12,23 +12,31 @@ This package only exports CommonJS modules. You can import it like this:
```js
// Usage with ES6 modules
-import { products, productTypes } from '@nexusmutual/sdk';
+import { NexusSDK } from '@nexusmutual/sdk';
```
## Nexus Mutual contract addresses and abis
Source of truth for the latest mainnet addresses. Feeds into https://api.nexusmutual.io/sdk/.
-## Listed products and product types metadata
+## Product metadata
+
+Product and product type metadata is served by the Nexus Mutual API (default base URL `https://api.nexusmutual.io/v2`).
+Use the `ProductAPI` class to query products and product types instead of local JSON or logo assets.
+For logos, use the URL `https://api.nexusmutual.io/v2/logos/:productId`
-The `products` folder contains all protocols listed on Nexus Mutual.
+### Example
-If you're a protocol owner and want to update any details (i.e. logo, website, etc), please submit a PR.
-Logos should meet the following criteria:
+```typescript
+import { ProductAPI } from '@nexusmutual/sdk';
-- svg format, with 1:1 ratio
-- no fixed width or height
-- the image should reach the edge of the viewbox
+const productApi = new ProductAPI();
+
+const productTypes = await productApi.getAllProductTypes();
+const product = await productApi.getProductById(247);
+
+console.log(productTypes.length, product.name);
+```
## Development
@@ -250,7 +258,9 @@ Note: The following product types do not require IPFS content:
- sherlockBugBounty
- immunefiBugBounty
-For a complete list of products and product types, see [products.json](https://sdk.nexusmutual.io/data/products.json) and [product-types.json](https://sdk.nexusmutual.io/data/product-types.json).
+For a complete list of products and product types, use the API endpoints:
+`GET https://api.nexusmutual.io/v2/products` and `GET https://api.nexusmutual.io/v2/product-types`,
+or the `ProductAPI.getAllProducts()` and `ProductAPI.getAllProductTypes()` helpers.
### Validation Errors
diff --git a/package-lock.json b/package-lock.json
index b1838066..1b76eb23 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -45,8 +45,7 @@
"semver": "^7.4.0",
"ts-jest": "^29.1.1",
"tsup": "^7.1.0",
- "typescript": "^5.1.6",
- "viem": "^1.16.6"
+ "typescript": "^5.1.6"
},
"engines": {
"node": ">=11.14.0"
@@ -61,12 +60,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/@adraffy/ens-normalize": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz",
- "integrity": "sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==",
- "dev": true
- },
"node_modules/@ampproject/remapping": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
@@ -2681,30 +2674,6 @@
"integrity": "sha512-eRp7yuD8mxTPnmIyUbmjc+JjaW/fNDgjnXgaSFnbCChocMEmAINEZt/ynwvq0SeqBJnWaYxxGXKqZNHylyUVwQ==",
"license": "GPL-3.0"
},
- "node_modules/@noble/curves": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
- "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==",
- "dev": true,
- "dependencies": {
- "@noble/hashes": "1.3.2"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/@noble/hashes": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
- "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
- "dev": true,
- "engines": {
- "node": ">= 16"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- }
- },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -2740,42 +2709,6 @@
"node": ">= 8"
}
},
- "node_modules/@scure/base": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.6.tgz",
- "integrity": "sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==",
- "dev": true,
- "funding": {
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/@scure/bip32": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.2.tgz",
- "integrity": "sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA==",
- "dev": true,
- "dependencies": {
- "@noble/curves": "~1.2.0",
- "@noble/hashes": "~1.3.2",
- "@scure/base": "~1.1.2"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/@scure/bip39": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz",
- "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==",
- "dev": true,
- "dependencies": {
- "@noble/hashes": "~1.3.0",
- "@scure/base": "~1.1.0"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- }
- },
"node_modules/@sinclair/typebox": {
"version": "0.27.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
@@ -3206,30 +3139,6 @@
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
"dev": true
},
- "node_modules/abitype": {
- "version": "0.9.8",
- "resolved": "https://registry.npmjs.org/abitype/-/abitype-0.9.8.tgz",
- "integrity": "sha512-puLifILdm+8sjyss4S+fsUN09obiT1g2YW6CtcQF+QDzxR0euzgEB29MZujC6zMk2a6SVmtttq1fc6+YFA7WYQ==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/wagmi-dev"
- }
- ],
- "peerDependencies": {
- "typescript": ">=5.0.4",
- "zod": "^3 >=3.19.1"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- },
- "zod": {
- "optional": true
- }
- }
- },
"node_modules/acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
@@ -6879,21 +6788,6 @@
"node": ">=12"
}
},
- "node_modules/isows": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.3.tgz",
- "integrity": "sha512-2cKei4vlmg2cxEjm3wVSqn8pcoRF/LX/wpifuuNquFO4SQmPwarClT+SUCA2lt+l581tTeZIPIZuIDo2jWN1fg==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/wagmi-dev"
- }
- ],
- "peerDependencies": {
- "ws": "*"
- }
- },
"node_modules/istanbul-lib-coverage": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
@@ -9789,57 +9683,6 @@
"integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==",
"license": "MIT"
},
- "node_modules/viem": {
- "version": "1.21.4",
- "resolved": "https://registry.npmjs.org/viem/-/viem-1.21.4.tgz",
- "integrity": "sha512-BNVYdSaUjeS2zKQgPs+49e5JKocfo60Ib2yiXOWBT6LuVxY1I/6fFX3waEtpXvL1Xn4qu+BVitVtMh9lyThyhQ==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/wevm"
- }
- ],
- "dependencies": {
- "@adraffy/ens-normalize": "1.10.0",
- "@noble/curves": "1.2.0",
- "@noble/hashes": "1.3.2",
- "@scure/bip32": "1.3.2",
- "@scure/bip39": "1.2.1",
- "abitype": "0.9.8",
- "isows": "1.0.3",
- "ws": "8.13.0"
- },
- "peerDependencies": {
- "typescript": ">=5.0.4"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
- },
- "node_modules/viem/node_modules/ws": {
- "version": "8.13.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
- "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
- "dev": true,
- "engines": {
- "node": ">=10.0.0"
- },
- "peerDependencies": {
- "bufferutil": "^4.0.1",
- "utf-8-validate": ">=5.0.2"
- },
- "peerDependenciesMeta": {
- "bufferutil": {
- "optional": true
- },
- "utf-8-validate": {
- "optional": true
- }
- }
- },
"node_modules/walker": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
diff --git a/package.json b/package.json
index b1a0f37e..c8d50c56 100644
--- a/package.json
+++ b/package.json
@@ -76,7 +76,6 @@
"semver": "^7.4.0",
"ts-jest": "^29.1.1",
"tsup": "^7.1.0",
- "typescript": "^5.1.6",
- "viem": "^1.16.6"
+ "typescript": "^5.1.6"
}
}
diff --git a/public/index.html b/public/index.html
index 4a55ec19..33b2416e 100644
--- a/public/index.html
+++ b/public/index.html
@@ -24,8 +24,9 @@
diff --git a/scripts/build-logos.js b/scripts/build-logos.js
deleted file mode 100644
index 8c5dfd32..00000000
--- a/scripts/build-logos.js
+++ /dev/null
@@ -1,100 +0,0 @@
-const { appendFile, mkdir, readdir, readFile, writeFile, copyFile } = require('fs').promises;
-const path = require('path');
-
-const { loadConfig, optimize } = require('svgo');
-
-const { parseFilePath } = require('./utils');
-
-const GENERATED_OUTPUT_DIR = path.join(__dirname, '../generated');
-const LOGOS_OUTPUT_DIR = path.join(GENERATED_OUTPUT_DIR, '/logos');
-const SRC_DIR = path.join(__dirname, '../src/logos');
-
-const buildLogos = async () => {
- await mkdir(LOGOS_OUTPUT_DIR, { recursive: true });
-
- // Find all logos in the src/ folder
- const allFilePaths = await readFiles(SRC_DIR);
- const allFileNames = allFilePaths.map(p => path.basename(p));
- const svgFilePaths = allFilePaths.filter(path => path.endsWith('.svg'));
- const otherFilePaths = allFilePaths.filter(path => !path.endsWith('.svg'));
-
- // Load `./svgo.config.js`
- const config = await loadConfig();
-
- // Create a Promise for each svg transformation
- const transformations = svgFilePaths.map(async filePath => {
- const content = await readFile(filePath);
- const { filename } = parseFilePath(filePath);
-
- const { data } = optimize(content, config);
-
- return { name: filename, svg: data };
- });
-
- // Execute all the transformations in parallel
- const components = await Promise.all(transformations);
-
- // Write each component to output directory
- await Promise.all(
- components.map(async ({ name, svg }) => {
- await writeFile(path.join(LOGOS_OUTPUT_DIR, `${name}.svg`), svg);
- console.log(`Copy optimized ${name}.svg`);
- }),
- );
-
- // Copy over non-svg files for legacy support. These files should be replaced with svg's
- otherFilePaths.forEach(async filePath => {
- const { filename, extension } = parseFilePath(filePath);
- console.log(`Copy ${filename} for legacy support`);
- await copyFile(filePath, path.join(LOGOS_OUTPUT_DIR, `${filename}.${extension}`));
- });
-
- // Contains all file names including extension
- const allLogoFileNames = allFileNames.map(filePath => {
- const { filename, extension } = parseFilePath(filePath);
- return `${filename}.${extension}`;
- });
-
- // Contains all file names without extension
- const allLogoNames = allFileNames.map(filePath => {
- const { filename } = parseFilePath(filePath);
- return filename;
- });
-
- // Write `types.ts` file
- await appendFile(
- path.join(GENERATED_OUTPUT_DIR, 'types.ts'),
- `\
-export const allLogoFileNames = [
- ${allLogoFileNames.map(filename => `'${filename}'`).join(',\n ')}
-] as const;
-
-export type LogoFileName = (typeof allLogoFileNames)[number];
-
-export const allLogoNames = [
- ${allLogoNames.map(name => `'${name}'`).join(',\n ')}
-] as const;
-
-export type LogoName = (typeof allLogoNames)[number];
-`,
- );
-};
-
-async function readFiles(dir) {
- const dirents = await readdir(dir, { withFileTypes: true });
-
- const nestedPaths = await Promise.all(
- dirents.map(dirent => {
- const res = path.resolve(dir, dirent.name);
- return dirent.isDirectory() ? readFiles(res) : res;
- }),
- );
-
- const paths = nestedPaths.flat();
-
- return paths;
-}
-
-module.exports = {
- buildLogos,
-};
diff --git a/scripts/build-products.js b/scripts/build-products.js
deleted file mode 100644
index b48454e9..00000000
--- a/scripts/build-products.js
+++ /dev/null
@@ -1,187 +0,0 @@
-const fs = require('fs');
-const { readdir } = require('fs').promises;
-const path = require('path');
-
-const { CoverProducts, Cover, addresses } = require('@nexusmutual/deployments');
-const ethers = require('ethers');
-const fetch = require('node-fetch');
-
-const { parseProductCoverAssets, parseFilePath, getCoverAssetsSymbols, fetchEventsInBatches } = require('./utils');
-const { allPrivateProductsIds } = require(path.join(__dirname, '../src/constants/privateProducts.js'));
-const productMetadata = require('../data/legacy-product-metadata.json');
-
-const { PROVIDER_URL } = process.env;
-
-const ipfsURL = ipfsHash => `https://api.nexusmutual.io/ipfs/${ipfsHash}`;
-
-const fetchProductTypes = async coverProducts => {
- const productTypesCount = (await coverProducts.getProductTypeCount()).toNumber();
- const ids = Array.from({ length: productTypesCount }, (_, i) => i);
-
- const productTypes = await Promise.all(
- ids.map(async id => {
- const [productType, name, { ipfsHash }] = await Promise.all([
- coverProducts.getProductType(id),
- coverProducts.getProductTypeName(id),
- coverProducts.getLatestProductTypeMetadata(id),
- ]);
- const coverWordingURL = ipfsURL(ipfsHash);
- return {
- id,
- coverWordingURL,
- name: name.trim(),
- gracePeriod: productType.gracePeriod,
- claimMethod: productType.claimMethod,
- assessmentCooldownPeriod: productType.assessmentCooldownPeriod,
- payoutRedemptionPeriod: productType.payoutRedemptionPeriod,
- };
- }),
- );
-
- return productTypes;
-};
-
-const createLogoDict = async logosDir => {
- const dirents = await readdir(logosDir, { withFileTypes: true });
- const filenames = dirents.map(dirent => dirent.name);
-
- const map = filenames.reduce((acc, filename) => {
- const { id, filename: name, extension } = parseFilePath(filename);
-
- // Skip files that don't have an id. These files have no id in the filename and will never be
- // used in a product
- if (id) {
- acc[Number(id)] = `${name}.${extension}`;
- }
-
- return acc;
- }, {});
-
- return map;
-};
-
-const EVENTS_START_BLOCK = 7700000;
-
-const fetchProducts = async (coverContract, coverProducts, provider) => {
- const eventFilter = coverProducts.filters.ProductSet();
-
- const events = await fetchEventsInBatches(coverProducts, eventFilter, EVENTS_START_BLOCK, provider);
-
- const logos = await createLogoDict(path.join(__dirname, '../src/logos'));
-
- const sortedEvents = events
- // sort ascending by blockNumber to get the latest ipfs hash
- // (ascending rather than descending due to the reduce into productMetadata object below)
- .sort((a, b) => a.blockNumber - b.blockNumber);
-
- await Promise.all(
- sortedEvents.map(async event => {
- const id = event.args.id.toNumber();
- const { ipfsHash } = await coverProducts.getLatestProductMetadata(id);
- productMetadata[id] = { id, ipfsHash };
-
- // Only update the timestamp if it's not already set
- // This is to avoid overwriting the timestamp if the event was an update, not a creation (e.g. for ipfsMetadata)
- if (!productMetadata[id].timestamp) {
- const block = await provider.getBlock(event.blockNumber);
- productMetadata[id].timestamp = block.timestamp;
- }
- }),
- );
-
- const productsCount = await coverProducts.getProductCount();
- const ids = Array.from(Array(productsCount.toNumber()).keys());
- const batches = [];
-
- while (ids.length) {
- batches.push(ids.splice(0, 50));
- }
-
- const products = [];
- const coverAssetsMap = await getCoverAssetsSymbols(provider);
- const defaultMinPriceRatio = await coverContract.getDefaultMinPriceRatio();
- const defaultMinPrice = defaultMinPriceRatio.toNumber();
-
- for (const batch of batches) {
- const promises = batch.map(async id => {
- const { productType, isDeprecated, useFixedPrice, coverAssets, minPrice } = await coverProducts.getProduct(id);
- const name = await coverProducts.getProductName(id);
- console.log(`Processing #${id} (${name})`);
- const { ipfsHash, timestamp } = productMetadata[id];
- const metadata = ipfsHash === '' ? {} : await fetch(ipfsURL(ipfsHash)).then(res => res.json());
-
- if (logos[id] === undefined) {
- logos[id] = 'nexus-mutual.svg';
- }
-
- const isPrivate = allPrivateProductsIds.includes(id);
-
- let productMinPrice = minPrice || defaultMinPrice;
-
- if (productType === 2) {
- productMinPrice = 0;
- }
-
- return {
- id,
- name,
- productType,
- isDeprecated,
- useFixedPrice,
- logo: logos[id],
- metadata,
- coverAssets: parseProductCoverAssets(coverAssets, coverAssetsMap),
- isPrivate,
- timestamp,
- minPrice: productMinPrice,
- };
- });
-
- const batchProducts = await Promise.all(promises);
- products.push(...batchProducts);
- }
-
- return products;
-};
-
-const buildProducts = async () => {
- if (PROVIDER_URL === undefined) {
- console.log('PROVIDER_URL environment variable is not defined');
- process.exit(1);
- }
-
- const provider = new ethers.providers.JsonRpcProvider(PROVIDER_URL);
- const coverProducts = new ethers.Contract(addresses.CoverProducts, CoverProducts, provider);
-
- console.log('Generating product types...');
- const productTypesPath = path.join(__dirname, '../generated/product-types.json');
- const productTypes = await fetchProductTypes(coverProducts);
- fs.writeFileSync(productTypesPath, JSON.stringify(productTypes, null, 2));
-
- // Generate ProductTypes enum
- const generatedTypesPath = path.join(__dirname, '../generated/types.ts');
- const productTypeNamesCamelCased = productTypes
- .map(({ name }) => name)
- .map(name => name.replace(/ /g, ''))
- .map(name => name.replace(/ETH/g, 'Eth'))
- .map(name => name.replace(/&/g, 'And'))
- .map(name => `${name[0].toLowerCase()}${name.slice(1)}`)
- .map(name => (name.endsWith('Cover') ? name.slice(0, -5) : name));
- fs.appendFileSync(
- generatedTypesPath,
- `\nexport enum ProductTypes {\n${productTypeNamesCamelCased.map((name, i) => ` ${name} = ${i},`).join('\n')}\n}\n`,
- );
-
- const coverContract = new ethers.Contract(addresses.Cover, Cover, provider);
-
- console.log('Generating products...');
- const productsPath = path.join(__dirname, '../generated/products.json');
- const products = await fetchProducts(coverContract, coverProducts, provider);
- fs.writeFileSync(productsPath, JSON.stringify(products, null, 2));
-
- console.log('Done.');
-};
-
-module.exports = {
- buildProducts,
-};
diff --git a/scripts/build.js b/scripts/build.js
index e04c89ec..f250a36b 100644
--- a/scripts/build.js
+++ b/scripts/build.js
@@ -5,9 +5,6 @@ const path = require('node:path');
const { build } = require('tsup');
-const { buildLogos } = require('./build-logos');
-const { buildProducts } = require('./build-products');
-
const main = async () => {
// Clean directories
const dist = path.join(__dirname, '../dist');
@@ -27,10 +24,6 @@ const main = async () => {
const { version } = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf8'));
fs.writeFileSync(sdkVersionPath, JSON.stringify({ version }, null, 2));
- // Build assets
- await buildLogos();
- await buildProducts();
-
await build({
entry: ['src/index.ts'],
format: ['cjs', 'esm'],
@@ -42,19 +35,6 @@ const main = async () => {
publicDir: 'public',
});
- // Copy over all processed logo files to dist
- const logosOutDir = path.join(dist, 'logos');
- const logosSrcDir = path.join(__dirname, '../generated/logos');
- fs.mkdirSync(logosOutDir);
- const logoDirents = await fs.promises.readdir(logosSrcDir, { withFileTypes: true });
- for (const dirent of logoDirents) {
- if (dirent.isFile()) {
- const source = path.join(logosSrcDir, dirent.name);
- const dest = path.join(logosOutDir, dirent.name);
- fs.copyFileSync(source, dest);
- }
- }
-
// Copy addresses from @nexusmutual/deployments package
const addressesFile = path.join(__dirname, '../node_modules/@nexusmutual/deployments/dist/data/addresses.json');
const dataOutDir = path.join(dist, 'data');
@@ -74,12 +54,8 @@ const main = async () => {
}
}
- // Copy products.json and product-types.json from generated to dist
- const generatedDir = path.join(__dirname, '../generated');
- fs.copyFileSync(path.join(generatedDir, 'products.json'), path.join(dist, 'data/products.json'));
- fs.copyFileSync(path.join(generatedDir, 'product-types.json'), path.join(dist, 'data/product-types.json'));
-
// Copy version.json from generated to dist
+ const generatedDir = path.join(__dirname, '../generated');
fs.copyFileSync(path.join(generatedDir, 'version.json'), path.join(dist, 'data/version.json'));
};
diff --git a/src/constants/cover.ts b/src/constants/cover.ts
index 61c848fb..9f373be9 100644
--- a/src/constants/cover.ts
+++ b/src/constants/cover.ts
@@ -1,7 +1,3 @@
-import { Address, zeroAddress } from 'viem';
-
-import { ProductTypes } from '../../generated/types';
-
export enum CoverAsset {
ETH = 0,
DAI = 1,
@@ -24,85 +20,5 @@ export const MINIMUM_COVER_PERIOD = 28;
export const MAXIMUM_COVER_PERIOD = 365;
export const DEFAULT_SLIPPAGE = 10; // 0.1%
-// Commission ratios
-export const DEFAULT_COMMISSION_RATIO = 15_00; // 15%
-export const SHERLOCK_BUG_BOUNTY_COMMISSION_RATIO = 10_00; // 10%
-export const TRM_COMMISSION_RATIO = 10_00; // 10%
-export const FUND_PORTFOLIO_COMMISSION_RATIO = 10_00; // 10%
-export const DEFI_PASS_COMMISSION_RATIO = 10_00; // 10%
-export const GENERALIZED_FUND_PORTFOLIO_COMMISSION_RATIO = 10_00; // 10%
-export const CRYPTO_COVER_COMMISSION_RATIO = 10_00; // 10%
-export const NEXUS_MUTUAL_COVER_COMMISSION_RATIO = 10_00; // 10%
-export const LEVERAGED_LIQUIDATION_COMMISSION_RATIO = 10_00; // 10%
-export const NON_EVM_PROTOCOL_COMMISSION_RATIO = 10_00;
-export const KIDNAP_AND_RANSOM_COVER_COMMISSION_RATIO = 10_00;
-export const NO_COMMISSION = 0; // 0%
-
-// Commission destinations
-export const NO_COMMISSION_DESTINATION = zeroAddress as Address;
export const NEXUS_MUTUAL_DAO_TREASURY_ADDRESS = '0x8e53D04644E9ab0412a8c6bd228C84da7664cFE3';
-export const IMMUNEFI_ADDRESS = '0x9c2F47079eb7Def5dd01Dd7E1138583f82376bDc' as Address;
-export const SPEARBIT_CANTINA_ADDRESS = '0x3Dcb7CFbB431A11CAbb6f7F2296E2354f488Efc2' as Address;
-export const VAULT_ADDRESS = '0x5f2b6e70aa6a217e9ecd1ed7d0f8f38ce9a348a2';
-
-export const BUY_COVER_COMMISSION_RATIO_BY_PRODUCT_TYPE: Record = {
- [ProductTypes.ethSlashing]: DEFAULT_COMMISSION_RATIO,
- [ProductTypes.liquidCollectiveEthStaking]: DEFAULT_COMMISSION_RATIO,
- [ProductTypes.stakewiseEthStaking]: DEFAULT_COMMISSION_RATIO,
- [ProductTypes.sherlockQuotaShare]: DEFAULT_COMMISSION_RATIO,
- [ProductTypes.unoReQuotaShare]: DEFAULT_COMMISSION_RATIO,
- [ProductTypes.theRetailMutual]: TRM_COMMISSION_RATIO,
- [ProductTypes.singleProtocol]: NEXUS_MUTUAL_COVER_COMMISSION_RATIO,
- [ProductTypes.multiProtocol]: NEXUS_MUTUAL_COVER_COMMISSION_RATIO,
- [ProductTypes.custody]: DEFAULT_COMMISSION_RATIO,
- [ProductTypes.yieldToken]: DEFAULT_COMMISSION_RATIO,
- [ProductTypes.sherlockExcess]: DEFAULT_COMMISSION_RATIO,
- [ProductTypes.nativeProtocol]: NEXUS_MUTUAL_COVER_COMMISSION_RATIO,
- [ProductTypes.ethSlashingUmbrella]: DEFAULT_COMMISSION_RATIO,
- [ProductTypes.openCoverTransaction]: DEFAULT_COMMISSION_RATIO,
- [ProductTypes.fundPortfolio]: FUND_PORTFOLIO_COMMISSION_RATIO,
- [ProductTypes.sherlockBugBounty]: SHERLOCK_BUG_BOUNTY_COMMISSION_RATIO,
- [ProductTypes.deFiPass]: DEFI_PASS_COMMISSION_RATIO,
- [ProductTypes.followOn]: NO_COMMISSION,
- [ProductTypes.immunefiBugBounty]: DEFAULT_COMMISSION_RATIO,
- [ProductTypes.nexusMutual]: NEXUS_MUTUAL_COVER_COMMISSION_RATIO,
- [ProductTypes.generalizedFundPortfolio]: GENERALIZED_FUND_PORTFOLIO_COMMISSION_RATIO,
- [ProductTypes.crypto]: CRYPTO_COVER_COMMISSION_RATIO,
- [ProductTypes.nativeSyndicate]: NO_COMMISSION,
- [ProductTypes.spearbitCantina]: DEFAULT_COMMISSION_RATIO,
- [ProductTypes.leveragedLiquidation]: LEVERAGED_LIQUIDATION_COMMISSION_RATIO,
- [ProductTypes.nonEVMProtocol]: NON_EVM_PROTOCOL_COMMISSION_RATIO,
- [ProductTypes.kidnapAndRansom]: KIDNAP_AND_RANSOM_COVER_COMMISSION_RATIO,
- [ProductTypes.vault]: DEFAULT_COMMISSION_RATIO,
-};
-
-export const BUY_COVER_COMMISSION_DESTINATION_BY_PRODUCT_TYPE: Record = {
- [ProductTypes.ethSlashing]: NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
- [ProductTypes.liquidCollectiveEthStaking]: NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
- [ProductTypes.stakewiseEthStaking]: NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
- [ProductTypes.sherlockQuotaShare]: NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
- [ProductTypes.unoReQuotaShare]: NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
- [ProductTypes.theRetailMutual]: NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
- [ProductTypes.singleProtocol]: NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
- [ProductTypes.multiProtocol]: NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
- [ProductTypes.custody]: NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
- [ProductTypes.yieldToken]: NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
- [ProductTypes.sherlockExcess]: NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
- [ProductTypes.nativeProtocol]: NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
- [ProductTypes.ethSlashingUmbrella]: NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
- [ProductTypes.openCoverTransaction]: NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
- [ProductTypes.fundPortfolio]: NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
- [ProductTypes.sherlockBugBounty]: NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
- [ProductTypes.deFiPass]: NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
- [ProductTypes.followOn]: NO_COMMISSION_DESTINATION,
- [ProductTypes.immunefiBugBounty]: IMMUNEFI_ADDRESS,
- [ProductTypes.nexusMutual]: NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
- [ProductTypes.generalizedFundPortfolio]: NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
- [ProductTypes.crypto]: NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
- [ProductTypes.nativeSyndicate]: NO_COMMISSION_DESTINATION,
- [ProductTypes.spearbitCantina]: SPEARBIT_CANTINA_ADDRESS,
- [ProductTypes.leveragedLiquidation]: NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
- [ProductTypes.nonEVMProtocol]: NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
- [ProductTypes.kidnapAndRansom]: NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
- [ProductTypes.vault]: VAULT_ADDRESS,
-};
+export const NEXUS_MUTUAL_COVER_COMMISSION_RATIO = 1000; // 10%
diff --git a/src/constants/index.ts b/src/constants/index.ts
index 562b7175..59fbe1a7 100644
--- a/src/constants/index.ts
+++ b/src/constants/index.ts
@@ -1,5 +1,2 @@
-const { allPrivateProductsIds } = require('./privateProducts') as { allPrivateProductsIds: number[] };
-
-export { allPrivateProductsIds };
export * from './cover';
export * from './products';
diff --git a/src/constants/privateProducts.js b/src/constants/privateProducts.js
deleted file mode 100644
index 428ef444..00000000
--- a/src/constants/privateProducts.js
+++ /dev/null
@@ -1,109 +0,0 @@
-// List of all private products ids
-// For new private products, add the id here
-
-const allPrivateProductsIds = [
- 17, // Bundle: Gelt + mStable + Aave v2
- 43, // Liquid Collective
- 63, // Sherlock
- 65, // Stakewise 3rd party (3 ETH/validator)
- 66, // Stakewise operated (3 ETH/validator)
- 82, // EtherFi
- 83, // Squeeth by Opyn (Sherlock)
- 84, // Rage Trade (Sherlock)
- 85, // Sentiment (Sherlock)
- 86, // Lyra Newport (Sherlock)
- 87, // Perennial (Sherlock)
- 88, // LiquiFi (Sherlock)
- 89, // Lyra Avalon (Sherlock)
- 90, // Buffer Finance (Sherlock)
- 91, // Hook (Sherlock)
- 92, // Holyheld (Sherlock)
- 93, // Union Finance (Sherlock)"
- 94, // OpenQ (Sherlock)
- 99, // Chorus One
- 100, // Kiln
- 101, // Vertex (Native Protocol)
- 102, // The Retail Mutual
- 104, // Teller (Sherlock)
- 105, // Ajna (Sherlock)
- 107, // Vox Finance (UnoRe)
- 108, // MahaLend (UnoRe)
- 109, // SELF (UnoRe)
- 110, // Scallop (UnoRe)
- 111, // WeFi (UnoRe)
- 112, // ZkTsunami (UnoRe)
- 113, // Hats Protocol
- 151, // Arcadia (Sherlock)
- 159, // Liquid Collective
- 163, // Pocket Universe
- 164, // Request Finance
- 186, // Delta Prime (UnoRe)
- 195, // Dialectic Moonphase
- 196, // Dialectic Chronograph
- 220, // Flat Money (Sherlock)
- 227, // Base DeFi Pass
- 233, // Relative Finance
- 239, // Everstake
- 240, // Ensuro (deprecated)
- 250, // Fasanara
- 259, // Bittensor
- 260, // DeFi Covered
- 273, // L1 Advisors Cover
- 274, // Figment ETH Slashing
- 287, // Ensuro Yield (deprecated)
- 291, // OC Cover
- 292, // Usual Bug Bounty
- 323, // Colend Single Protocol Cover
- 334, // Noon DeFi Deployment
- 337, // Mellow Finance Bug Bounty Cover
- 340, // wstETH (Aave)
- 341, // weETH (Aave)
- 342, // rsETH (Aave)
- 353, // Dialectic Ellipse
- 358, // DeFi Vault 1
- 359, // DeFi Vault 2
- 360, // TermMax Markets
- 361, // Vesu
- 362, // Algoquant
- 363, // Solstice USX Depeg
- 366, // OpenCover Morpho Base Vaults
- 367, // K&R AAA
- 368, // K&R AA
- 369, // K&R A
- 370, // K&R B
- 371, // K&R C
- 372, // K&R D
- 373, // PT-cUSD-29JAN2026 (Morpho)
- 375, // Fasanara 2
- 376, // Noon DeFi Deployment 2
- 377, // IPOR TAU InfiniFi Vault
- 379, // PT Ethena sUSDE 5FEB2026 (Aave)
- 380, // PT Ethena USDe 5FEB2026 (Aave)
- 381, // PT Ethena sUSDE 15JAN2026 (Aave)
- 382, // PT Ethena USDe 15JAN2026 (Aave)
- 389, // Fasanara 2
- 390, // Noon DeFi Deployment 2
- 392, // Sablier
- 393, // OpenCover Smokehouse USDC
- 394, // Ekubo Starknet
- 395, // OpenCover Smokehouse USDT
- 396, // OpenCover Sentora PYUSD
- 397, // OpenCover Steakhouse H Yield I USDT
- 398, // OpenCover Steakhouse Prime I USDT
- 399, // OpenCover Steakhouse H Yield I AUSD
- 400, // OpenCover Steakhouse H Yield I USDC
- 401, // PT-stcUSD-23JUL2026 (Morpho)
- 402, // PT-cUSD-23JUL2026 (Morpho)
- 403, // TermMax
- 405, // OpenCover YoUSD
- 406, // Teller
- 409, // OpenCover WETH SuperVault
- 410, // OpenCover Base USDC SuperVault
- 411, // OpenCover WBTC SuperVault
- 412, // OpenCover Steakhouse HYI USDC Base
- 413, // OpenCover Steakhouse USDC
-];
-
-module.exports = {
- allPrivateProductsIds,
-};
diff --git a/src/index.ts b/src/index.ts
index f62b98c6..789ea975 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -3,22 +3,18 @@ import * as deployments from '@nexusmutual/deployments';
import * as constants from './constants';
import * as ipfs from './ipfs';
import { NexusSDK } from './nexus-sdk';
+import * as productApi from './product-api';
import * as quote from './quote';
import * as swap from './swap';
import * as types from './types';
-import productTypesData from '../generated/product-types.json';
-import productsData from '../generated/products.json';
-import * as generatedTypes from '../generated/types';
const nexusSdk = {
...deployments,
- products: productsData,
- productTypes: productTypesData,
- ...generatedTypes,
...swap,
...types,
...quote,
...ipfs,
+ ...productApi,
...constants,
NexusSDK,
};
@@ -26,12 +22,7 @@ const nexusSdk = {
// Re-export everything from the deployments package (e.g. `addresses` and `abis`)
export * from '@nexusmutual/deployments';
-// Export product data, so it will be included in the bundle
-export { default as products } from '../generated/products.json';
-export { default as productTypes } from '../generated/product-types.json';
-
-// Export generated logo types
-export * from '../generated/types';
+export * from './product-api';
export * from './swap';
diff --git a/src/product-api/ProductAPI.ts b/src/product-api/ProductAPI.ts
new file mode 100644
index 00000000..16e21278
--- /dev/null
+++ b/src/product-api/ProductAPI.ts
@@ -0,0 +1,51 @@
+import { NexusSDKBase } from '../nexus-sdk-base';
+import { Product, ProductType } from '../types/product';
+import { NexusSDKConfig } from '../types/sdk';
+
+export class ProductAPI extends NexusSDKBase {
+ /**
+ * Create a new ProductAPI instance
+ * @param config SDK configuration
+ */
+ constructor(config: NexusSDKConfig = {}) {
+ super(config);
+ }
+
+ /**
+ * Get product type details by ID
+ * @param productTypeId ID of the product type
+ * @returns Product type details
+ */
+ public async getProductTypeById(productTypeId: number): Promise {
+ const productTypeEndpoint = `/product-types/${productTypeId}?withAttributes=ipfsContentType`;
+ return this.sendRequest(productTypeEndpoint);
+ }
+
+ /**
+ * Get all product types
+ * @returns List of product types
+ */
+ public async getAllProductTypes(): Promise {
+ const productTypesEndpoint = '/product-types?withAttributes=ipfsContentType';
+ return this.sendRequest(productTypesEndpoint);
+ }
+
+ /**
+ * Get product details by ID
+ * @param productId ID of the product
+ * @returns Product details
+ */
+ public async getProductById(productId: number): Promise {
+ const productEndpoint = `/products/${productId}`;
+ return this.sendRequest(productEndpoint);
+ }
+
+ /**
+ * Get all products
+ * @returns List of products
+ */
+ public async getAllProducts(): Promise {
+ const productsEndpoint = '/products';
+ return this.sendRequest(productsEndpoint);
+ }
+}
diff --git a/src/product-api/index.ts b/src/product-api/index.ts
new file mode 100644
index 00000000..e9f560aa
--- /dev/null
+++ b/src/product-api/index.ts
@@ -0,0 +1 @@
+export * from './ProductAPI';
diff --git a/src/quote/Quote.ts b/src/quote/Quote.ts
index bb91efd1..aac94e0a 100644
--- a/src/quote/Quote.ts
+++ b/src/quote/Quote.ts
@@ -1,11 +1,6 @@
import { AxiosError, AxiosRequestConfig } from 'axios';
-import productTypes from '../../generated/product-types.json';
-import products from '../../generated/products.json';
-import { ProductTypes } from '../../generated/types';
import {
- BUY_COVER_COMMISSION_DESTINATION_BY_PRODUCT_TYPE,
- BUY_COVER_COMMISSION_RATIO_BY_PRODUCT_TYPE,
COMMISSION_DENOMINATOR,
CoverAsset,
DEFAULT_SLIPPAGE,
@@ -17,6 +12,7 @@ import {
} from '../constants';
import { Ipfs } from '../ipfs';
import { NexusSDKBase } from '../nexus-sdk-base';
+import { ProductAPI } from '../product-api/ProductAPI';
import {
CoverRouterProductCapacityResponse,
CoverRouterQuoteResponse,
@@ -27,23 +23,14 @@ import {
NexusSDKConfig,
QuoteParams,
IPFSTypeContentTuple,
- IPFS_CONTENT_TYPE_BY_PRODUCT_TYPE,
} from '../types';
-type ProductDTO = Omit<(typeof products)[number], 'productType'> & {
- productType: ProductTypes;
-};
-
-const productsMap: Record = products.reduce(
- (acc, product) => ({ ...acc, [product.id]: product }),
- {},
-);
-
/**
* Class for handling quote-related functionality
*/
export class Quote extends NexusSDKBase {
private ipfs: Ipfs;
+ private productAPI: ProductAPI;
/**
* Create a new Quote instance
@@ -53,6 +40,7 @@ export class Quote extends NexusSDKBase {
constructor(config: NexusSDKConfig = {}, ipfs?: Ipfs) {
super(config);
this.ipfs = ipfs || new Ipfs(config);
+ this.productAPI = new ProductAPI(config);
}
/**
@@ -151,26 +139,29 @@ export class Quote extends NexusSDKBase {
}
}
- const productType = productsMap[productId]?.productType;
- if (productType === undefined) {
+ const product = await this.productAPI.getProductById(productId);
+ const productTypeId = product?.productType;
+ if (productTypeId === undefined) {
return {
result: undefined,
error: { message: `Invalid product` },
};
}
- if (IPFS_CONTENT_TYPE_BY_PRODUCT_TYPE[productType] !== undefined && !ipfsCidOrContent) {
+ const productType = await this.productAPI.getProductTypeById(productTypeId);
+
+ if (productType.ipfsContentType !== undefined && !ipfsCidOrContent) {
return {
result: undefined,
error: {
message: `Missing IPFS content. \n
- ${productTypes[productType]?.name} requires ${IPFS_CONTENT_TYPE_BY_PRODUCT_TYPE[productType]} content type.`,
+ ${productType.name} requires ${productType.ipfsContentType} content type.`,
},
};
}
let ipfsData = ipfsCidOrContent as string;
- const contentType = IPFS_CONTENT_TYPE_BY_PRODUCT_TYPE[productType];
+ const contentType = productType.ipfsContentType;
// Handle uploading content to IPFS if provided as an object
if (!isString && contentType !== undefined && ipfsCidOrContent) {
@@ -207,12 +198,12 @@ export class Quote extends NexusSDKBase {
const maxPremiumInAsset = this.calculatePremiumWithCommissionAndSlippage(
BigInt(premium),
- commissionRatio || BUY_COVER_COMMISSION_RATIO_BY_PRODUCT_TYPE[productType],
+ commissionRatio || +productType.commissionRatio,
slippageValue,
);
const yearlyCostPerc = this.calculatePremiumWithCommissionAndSlippage(
BigInt(quote.annualPrice),
- commissionRatio || BUY_COVER_COMMISSION_RATIO_BY_PRODUCT_TYPE[productType],
+ commissionRatio || +productType.commissionRatio,
slippageValue,
);
@@ -236,9 +227,8 @@ export class Quote extends NexusSDKBase {
period: period * 60 * 60 * 24, // seconds
maxPremiumInAsset: maxPremiumInAsset.toString(),
paymentAsset: paymentAssetEnum,
- commissionRatio: commissionRatio || BUY_COVER_COMMISSION_RATIO_BY_PRODUCT_TYPE[productType],
- commissionDestination:
- commissionDestination || BUY_COVER_COMMISSION_DESTINATION_BY_PRODUCT_TYPE[productType],
+ commissionRatio: commissionRatio || +productType.commissionRatio,
+ commissionDestination: commissionDestination || productType.commissionDestination,
ipfsData,
},
poolAllocationRequests: quote.poolAllocationRequests,
diff --git a/src/quote/calculatePremiumWithCommissionAndSlippage.test.ts b/src/quote/calculatePremiumWithCommissionAndSlippage.test.ts
index f815d2a5..b7c3517d 100644
--- a/src/quote/calculatePremiumWithCommissionAndSlippage.test.ts
+++ b/src/quote/calculatePremiumWithCommissionAndSlippage.test.ts
@@ -1,4 +1,4 @@
-import { formatEther, parseEther } from 'viem';
+import { formatEther, parseEther } from 'ethers/lib/utils';
import { Quote } from './index';
import { COMMISSION_DENOMINATOR } from '../constants/cover';
@@ -6,7 +6,7 @@ import { COMMISSION_DENOMINATOR } from '../constants/cover';
const BUY_COVER_COMMISSION_RATIO = 1500;
describe('calculatePremiumWithCommissionAndSlippage', () => {
- let priceValue = parseEther('0');
+ let priceValue = parseEther('0').toBigInt();
const quoteApi = new Quote();
const priceWithCommissionFormula = (value: string) =>
@@ -18,11 +18,11 @@ describe('calculatePremiumWithCommissionAndSlippage', () => {
);
beforeEach(() => {
- priceValue = parseEther('0');
+ priceValue = parseEther('0').toBigInt();
});
it('should return 0 BigInt value for 0 price value', () => {
- const expectedValue = parseEther('0');
+ const expectedValue = parseEther('0').toBigInt();
expect(quoteApi.calculatePremiumWithCommissionAndSlippage(priceValue, BUY_COVER_COMMISSION_RATIO)).toStrictEqual(
expectedValue,
@@ -31,7 +31,7 @@ describe('calculatePremiumWithCommissionAndSlippage', () => {
it('should return correct BigInt values for 1 value price - integer value', () => {
const value = '1';
- priceValue = parseEther(value);
+ priceValue = parseEther(value).toBigInt();
const expectedValue = priceWithCommissionAndSlippageFormula(value);
@@ -43,67 +43,67 @@ describe('calculatePremiumWithCommissionAndSlippage', () => {
it('should return correct BigInt value for 0.1 value price - decimal value < 1', () => {
const value = '0.1';
- priceValue = parseEther(value);
+ priceValue = parseEther(value).toBigInt();
const expectedValue = priceWithCommissionAndSlippageFormula(value);
const actualValue = quoteApi.calculatePremiumWithCommissionAndSlippage(priceValue, BUY_COVER_COMMISSION_RATIO);
- const actualValueFormatted = formatEther(actualValue);
+ const actualValueFormatted = formatEther(BigInt(actualValue));
expect(parseFloat(actualValueFormatted)).toBeCloseTo(expectedValue, 4);
});
it('should return correct BigInt value for 1.23456789 value price - decimal value > 1', () => {
const value = '1.23456789';
- priceValue = parseEther(value);
+ priceValue = parseEther(value).toBigInt();
const expectedValue = priceWithCommissionAndSlippageFormula(value);
const actualValue = quoteApi.calculatePremiumWithCommissionAndSlippage(priceValue, BUY_COVER_COMMISSION_RATIO);
- const actualValueFormatted = formatEther(actualValue);
+ const actualValueFormatted = formatEther(BigInt(actualValue));
expect(parseFloat(actualValueFormatted)).toBeCloseTo(expectedValue, 4);
});
it('should return correct BigInt values for 1000 value price - integer value', () => {
const value = '1000';
- priceValue = parseEther(value);
+ priceValue = parseEther(value).toBigInt();
const expectedValue = priceWithCommissionAndSlippageFormula(value);
const actualValue = quoteApi.calculatePremiumWithCommissionAndSlippage(priceValue, BUY_COVER_COMMISSION_RATIO);
- const actualValueFormatted = formatEther(actualValue);
+ const actualValueFormatted = formatEther(BigInt(actualValue));
expect(parseFloat(actualValueFormatted)).toBeCloseTo(expectedValue, 4);
});
it('should return correct BigInt value for 0.1 value price as BigInt-like object - decimal value < 1', () => {
const value = '0.1';
- priceValue = parseEther(value);
+ priceValue = parseEther(value).toBigInt();
const expectedValue = priceWithCommissionAndSlippageFormula(value);
const actualValue = quoteApi.calculatePremiumWithCommissionAndSlippage(priceValue, BUY_COVER_COMMISSION_RATIO);
- const actualValueFormatted = formatEther(actualValue);
+ const actualValueFormatted = formatEther(BigInt(actualValue));
expect(parseFloat(actualValueFormatted)).toBeCloseTo(expectedValue, 4);
});
it('should return correct BigInt value for 1.23456789 value price as BigInt-like object - decimal value > 1', () => {
const value = '1.23456789';
- priceValue = parseEther(value);
+ priceValue = parseEther(value).toBigInt();
const expectedValue = priceWithCommissionAndSlippageFormula(value);
const actualValue = quoteApi.calculatePremiumWithCommissionAndSlippage(priceValue, BUY_COVER_COMMISSION_RATIO);
- const actualValueFormatted = formatEther(actualValue);
+ const actualValueFormatted = formatEther(BigInt(actualValue));
expect(parseFloat(actualValueFormatted)).toBeCloseTo(expectedValue, 4);
});
it('should return correct BigInt values for 1 value price and 1% slippage', () => {
const value = '1';
- priceValue = parseEther(value);
+ priceValue = parseEther(value).toBigInt();
const slippageValue = 100;
const expectedValue = priceWithCommissionAndSlippageFormula(value, slippageValue);
@@ -113,14 +113,14 @@ describe('calculatePremiumWithCommissionAndSlippage', () => {
BUY_COVER_COMMISSION_RATIO,
slippageValue,
);
- const actualValueFormatted = formatEther(actualValue);
+ const actualValueFormatted = formatEther(BigInt(actualValue));
expect(parseFloat(actualValueFormatted)).toBeCloseTo(expectedValue, 4);
});
it('should return correct BigInt values for 1 value price and 0.5% slippage', () => {
const value = '1';
- priceValue = parseEther(value);
+ priceValue = parseEther(value).toBigInt();
const slippageValue = 50;
const expectedValue = priceWithCommissionAndSlippageFormula(value, slippageValue);
@@ -130,7 +130,7 @@ describe('calculatePremiumWithCommissionAndSlippage', () => {
BUY_COVER_COMMISSION_RATIO,
slippageValue,
);
- const actualValueFormatted = formatEther(actualValue);
+ const actualValueFormatted = formatEther(BigInt(actualValue));
expect(parseFloat(actualValueFormatted)).toBeCloseTo(expectedValue, 4);
});
diff --git a/src/quote/getQuoteAndBuyCoverInputs.test.ts b/src/quote/getQuoteAndBuyCoverInputs.test.ts
index 99cc3974..3fe7bbc4 100644
--- a/src/quote/getQuoteAndBuyCoverInputs.test.ts
+++ b/src/quote/getQuoteAndBuyCoverInputs.test.ts
@@ -1,5 +1,5 @@
+import { parseEther } from 'ethers/lib/utils';
import mockAxios from 'jest-mock-axios';
-import { parseEther } from 'viem';
import {
CoverAsset,
@@ -9,7 +9,6 @@ import {
NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
SLIPPAGE_DENOMINATOR,
TARGET_PRICE_DENOMINATOR,
- NEXUS_MUTUAL_COVER_COMMISSION_RATIO,
} from '../constants/cover';
import { Quote } from '../quote';
import {
@@ -20,9 +19,37 @@ import {
CoverValidators,
DefiPassContent,
} from '../types';
+import { Product, ProductType } from '../types/product';
jest.mock('axios', () => mockAxios);
jest.setTimeout(10_000);
+const mockProduct: Product = {
+ id: 1,
+ productType: 1,
+ name: 'Test Product',
+ minPrice: '100',
+ coverAssets: [CoverAsset.ETH],
+ initialPriceRation: '100',
+ capacityReductionRatio: '0',
+ isDeprecated: false,
+ useFixedPrice: false,
+ metadata: '',
+ allowedPools: [],
+ logo: '',
+};
+
+const mockProductType: ProductType = {
+ id: 1,
+ name: 'Protocol',
+ metadata: '',
+ claimMethod: 0,
+ gracePeriod: '30',
+ assessmentCooldownPeriod: '0',
+ payoutRedemptionPeriod: '0',
+ commissionRatio: '500',
+ commissionDestination: NEXUS_MUTUAL_DAO_TREASURY_ADDRESS,
+};
+
const coverRouterCapacityResponse: CoverRouterProductCapacityResponse = {
productId: 150,
availableCapacity: [
@@ -79,12 +106,16 @@ describe('getQuoteAndBuyCoverInputs', () => {
});
it('uses DEFAULT_NEXUS_API_URL if no API URL is supplied', async () => {
- mockAxios.get.mockResolvedValue({ data: {} });
const productId = 1;
const amount = '100';
const period = 30;
const coverAsset = CoverAsset.ETH;
+ mockAxios.get.mockResolvedValueOnce({ data: mockProduct });
+ mockAxios.get.mockResolvedValueOnce({ data: mockProductType });
+ mockAxios.get.mockResolvedValueOnce({ data: coverRouterQuoteResponse });
+ mockAxios.get.mockResolvedValueOnce({ data: coverRouterCapacityResponse });
+
await quoteApi.getQuoteAndBuyCoverInputs({
productId,
amount,
@@ -93,14 +124,11 @@ describe('getQuoteAndBuyCoverInputs', () => {
buyerAddress,
});
- const defaultGetQuoteUrl = DEFAULT_NEXUS_API_URL + '/quote';
- expect(mockAxios.get).toHaveBeenCalledWith(defaultGetQuoteUrl, {
- params: { amount, coverAsset, period, productId, paymentAsset: coverAsset },
- });
+ const defaultGetProductUrl = DEFAULT_NEXUS_API_URL + '/products/1';
+ expect(mockAxios.get).toHaveBeenCalledWith(defaultGetProductUrl, {});
});
it('allows the consumer to override nexusApiUrl param', async () => {
- mockAxios.get.mockResolvedValue({ data: {} });
const url = 'http://hahahahahah';
const quoteApi = new Quote({ apiUrl: url });
const productId = 1;
@@ -108,6 +136,11 @@ describe('getQuoteAndBuyCoverInputs', () => {
const period = 30;
const coverAsset = CoverAsset.ETH;
+ mockAxios.get.mockResolvedValueOnce({ data: mockProduct });
+ mockAxios.get.mockResolvedValueOnce({ data: mockProductType });
+ mockAxios.get.mockResolvedValueOnce({ data: coverRouterQuoteResponse });
+ mockAxios.get.mockResolvedValueOnce({ data: coverRouterCapacityResponse });
+
await quoteApi.getQuoteAndBuyCoverInputs({
productId,
amount,
@@ -118,10 +151,8 @@ describe('getQuoteAndBuyCoverInputs', () => {
ipfsCidOrContent: '',
});
- const overrideGetQuoteUrl = url + '/quote';
- expect(mockAxios.get).toHaveBeenCalledWith(overrideGetQuoteUrl, {
- params: { amount, coverAsset, period, productId, paymentAsset: coverAsset },
- });
+ const overrideGetProductUrl = url + '/products/1';
+ expect(mockAxios.get).toHaveBeenCalledWith(overrideGetProductUrl, {});
});
const invalidProductIds = [-1, 'a', true, {}, [], null, undefined];
@@ -217,8 +248,10 @@ describe('getQuoteAndBuyCoverInputs', () => {
);
it('returns an error if ipfsData is not a valid IPFS content for the product type - ETH Slashing', async () => {
- mockAxios.get.mockResolvedValueOnce({ data: coverRouterQuoteResponse });
- mockAxios.get.mockResolvedValueOnce({ data: coverRouterCapacityResponse });
+ const ethSlashingProduct = { ...mockProduct, id: 82, productType: 5 };
+ const ethSlashingProductType = { ...mockProductType, id: 5, ipfsContentType: 'coverValidators' };
+ mockAxios.get.mockResolvedValueOnce({ data: ethSlashingProduct });
+ mockAxios.get.mockResolvedValueOnce({ data: ethSlashingProductType });
const invalidContent: CoverFreeText = { version: '1.0', freeText: 'test' };
const { error } = await quoteApi.getQuoteAndBuyCoverInputs({
@@ -238,6 +271,11 @@ describe('getQuoteAndBuyCoverInputs', () => {
});
it('returns an error if ipfsData content has an empty validators field for ETH Slashing product type', async () => {
+ const ethSlashingProduct = { ...mockProduct, id: 82, productType: 5 };
+ const ethSlashingProductType = { ...mockProductType, id: 5, ipfsContentType: 'coverValidators' };
+ mockAxios.get.mockResolvedValueOnce({ data: ethSlashingProduct });
+ mockAxios.get.mockResolvedValueOnce({ data: ethSlashingProductType });
+
const emptyValidators: CoverValidators = { version: '1.0', validators: [] };
const { error } = await quoteApi.getQuoteAndBuyCoverInputs({
...quoteParams,
@@ -258,6 +296,11 @@ describe('getQuoteAndBuyCoverInputs', () => {
});
it('returns an error if ipfsData is not a valid IPFS content for the product type - UnoRe Quota Share', async () => {
+ const quotaShareProduct = { ...mockProduct, id: 107, productType: 6 };
+ const quotaShareProductType = { ...mockProductType, id: 6, ipfsContentType: 'coverQuotaShare' };
+ mockAxios.get.mockResolvedValueOnce({ data: quotaShareProduct });
+ mockAxios.get.mockResolvedValueOnce({ data: quotaShareProductType });
+
const invalidContent: CoverFreeText = { version: '1.0', freeText: 'test' };
const { error } = await quoteApi.getQuoteAndBuyCoverInputs({
...quoteParams,
@@ -276,6 +319,11 @@ describe('getQuoteAndBuyCoverInputs', () => {
});
it('returns an error if ipfsData is not a valid IPFS content for the product type - Fund Portfolio', async () => {
+ const fundPortfolioProduct = { ...mockProduct, id: 195, productType: 7 };
+ const fundPortfolioProductType = { ...mockProductType, id: 7, ipfsContentType: 'coverAumCoverAmountPercentage' };
+ mockAxios.get.mockResolvedValueOnce({ data: fundPortfolioProduct });
+ mockAxios.get.mockResolvedValueOnce({ data: fundPortfolioProductType });
+
const invalidContent: CoverFreeText = { version: '1.0', freeText: 'test' };
const { error } = await quoteApi.getQuoteAndBuyCoverInputs({
...quoteParams,
@@ -294,6 +342,11 @@ describe('getQuoteAndBuyCoverInputs', () => {
});
it('returns an error if ipfsData is not a valid IPFS content for the product type - Nexus Mutual Cover', async () => {
+ const nexusMutualCoverProduct = { ...mockProduct, id: 247, productType: 8 };
+ const nexusMutualCoverProductType = { ...mockProductType, id: 8, ipfsContentType: 'coverWalletAddresses' };
+ mockAxios.get.mockResolvedValueOnce({ data: nexusMutualCoverProduct });
+ mockAxios.get.mockResolvedValueOnce({ data: nexusMutualCoverProductType });
+
const invalidContent: CoverFreeText = { version: '1.0', freeText: 'test' };
const { error } = await quoteApi.getQuoteAndBuyCoverInputs({
...quoteParams,
@@ -312,6 +365,11 @@ describe('getQuoteAndBuyCoverInputs', () => {
});
it('returns an error if ipfsData is not a valid IPFS content for Defi Pass', async () => {
+ const defiPassProduct = { ...mockProduct, id: 227, productType: 9 };
+ const defiPassProductType = { ...mockProductType, id: 9, ipfsContentType: 'defiPassContent' };
+ mockAxios.get.mockResolvedValueOnce({ data: defiPassProduct });
+ mockAxios.get.mockResolvedValueOnce({ data: defiPassProductType });
+
const emptyWalletsContent: DefiPassContent = { version: '1.0', wallets: [] };
const { error } = await quoteApi.getQuoteAndBuyCoverInputs({
...quoteParams,
@@ -332,6 +390,11 @@ describe('getQuoteAndBuyCoverInputs', () => {
});
it('returns an error if ipfsData is not a valid IPFS content for the Defi Pass - empty address', async () => {
+ const defiPassProduct = { ...mockProduct, id: 227, productType: 9 };
+ const defiPassProductType = { ...mockProductType, id: 9, ipfsContentType: 'defiPassContent' };
+ mockAxios.get.mockResolvedValueOnce({ data: defiPassProduct });
+ mockAxios.get.mockResolvedValueOnce({ data: defiPassProductType });
+
const emptyWalletString: DefiPassContent = { version: '1.0', walletAddress: '' };
const { error } = await quoteApi.getQuoteAndBuyCoverInputs({
...quoteParams,
@@ -351,6 +414,9 @@ describe('getQuoteAndBuyCoverInputs', () => {
it('allows the consumer to provide a valid IPFS CID', async () => {
const ipfsCid = 'QmYfSDbuQLqJ2MAG3ATRjUPVFQubAhAM5oiYuuu9Kfs8RY';
+ const nexusMutualCoverProduct = { ...mockProduct, id: 247, productType: 8 };
+ const nexusMutualCoverProductType = { ...mockProductType, id: 8, ipfsContentType: 'coverWalletAddresses' };
+
const coverRouterQuoteResponse: CoverRouterQuoteResponse = {
quote: {
totalCoverAmountInAsset: parseEther('1000').toString(),
@@ -367,6 +433,8 @@ describe('getQuoteAndBuyCoverInputs', () => {
},
capacities: [{ poolId: '147', capacity: [{ assetId: '1', amount: parseEther('1000').toString() }] }],
};
+ mockAxios.get.mockResolvedValueOnce({ data: nexusMutualCoverProduct });
+ mockAxios.get.mockResolvedValueOnce({ data: nexusMutualCoverProductType });
mockAxios.get.mockResolvedValueOnce({ data: coverRouterQuoteResponse });
mockAxios.get.mockResolvedValueOnce({ data: coverRouterCapacityResponse });
@@ -381,6 +449,12 @@ describe('getQuoteAndBuyCoverInputs', () => {
});
it('allows the consumer to provide a valid IPFS content', async () => {
+ const nexusMutualCoverProduct = { ...mockProduct, id: 247, productType: 8 };
+ const nexusMutualCoverProductType = { ...mockProductType, id: 8, ipfsContentType: 'coverWalletAddresses' };
+
+ mockAxios.get.mockResolvedValueOnce({ data: nexusMutualCoverProduct });
+ mockAxios.get.mockResolvedValueOnce({ data: nexusMutualCoverProductType });
+
mockAxios.post.mockResolvedValueOnce({
data: {
ipfsHash: 'QmYfSDbuQLqJ2MAG3ATRjUPVFQubAhAM5oiYuuu9Kfs8RY',
@@ -421,6 +495,9 @@ describe('getQuoteAndBuyCoverInputs', () => {
});
it('returns an object with displayInfo and buyCoverInput parameters', async () => {
+ mockAxios.get.mockResolvedValueOnce({ data: mockProduct });
+ mockAxios.get.mockResolvedValueOnce({ data: mockProductType });
+
const coverRouterQuoteResponse: CoverRouterQuoteResponse = {
quote: {
totalCoverAmountInAsset: parseEther('1000').toString(),
@@ -450,11 +527,11 @@ describe('getQuoteAndBuyCoverInputs', () => {
const { premiumInAsset, annualPrice } = coverRouterQuoteResponse.quote;
const expectedMaxPremiumInAsset = quoteApi.calculatePremiumWithCommissionAndSlippage(
BigInt(premiumInAsset),
- NEXUS_MUTUAL_COVER_COMMISSION_RATIO,
+ +mockProductType.commissionRatio,
);
const expectedYearlyCostPerc = quoteApi.calculatePremiumWithCommissionAndSlippage(
BigInt(annualPrice),
- NEXUS_MUTUAL_COVER_COMMISSION_RATIO,
+ +mockProductType.commissionRatio,
);
expect(error).toBeUndefined();
@@ -470,15 +547,17 @@ describe('getQuoteAndBuyCoverInputs', () => {
expect(result?.buyCoverInput.buyCoverParams.period).toBe(30 * 60 * 60 * 24);
expect(result?.buyCoverInput.buyCoverParams.maxPremiumInAsset).toBe(expectedMaxPremiumInAsset.toString());
expect(result?.buyCoverInput.buyCoverParams.paymentAsset).toBe(CoverAsset.ETH);
- expect(result?.buyCoverInput.buyCoverParams.commissionRatio).toBe(NEXUS_MUTUAL_COVER_COMMISSION_RATIO);
- expect(result?.buyCoverInput.buyCoverParams.commissionDestination).toBe(NEXUS_MUTUAL_DAO_TREASURY_ADDRESS);
+ expect(result?.buyCoverInput.buyCoverParams.commissionRatio).toBe(+mockProductType.commissionRatio);
+ expect(result?.buyCoverInput.buyCoverParams.commissionDestination).toBe(mockProductType.commissionDestination);
expect(result?.buyCoverInput.buyCoverParams.ipfsData).toBe('QmYfSDbuQLqJ2MAG3ATRjUPVFQubAhAM5oiYuuu9Kfs8RY');
- expect(mockAxios.get).toHaveBeenCalledTimes(2);
+ expect(mockAxios.get).toHaveBeenCalledTimes(4);
});
it('should handle "Not enough capacity for the cover amount" error correctly - ETH', async () => {
mockAxios.get.mockReset();
+ mockAxios.get.mockResolvedValueOnce({ data: mockProduct });
+ mockAxios.get.mockResolvedValueOnce({ data: mockProductType });
mockAxios.get.mockRejectedValueOnce({
isAxiosError: true,
response: {
@@ -496,6 +575,8 @@ describe('getQuoteAndBuyCoverInputs', () => {
});
it('should handle "Not enough capacity for the cover amount" error correctly - DAI', async () => {
+ mockAxios.get.mockResolvedValueOnce({ data: mockProduct });
+ mockAxios.get.mockResolvedValueOnce({ data: mockProductType });
mockAxios.get.mockRejectedValueOnce({
isAxiosError: true,
response: {
diff --git a/src/swap/calculateEthForExactNxm.test.ts b/src/swap/calculateEthForExactNxm.test.ts
index 9e123386..5f5537c1 100644
--- a/src/swap/calculateEthForExactNxm.test.ts
+++ b/src/swap/calculateEthForExactNxm.test.ts
@@ -1,5 +1,5 @@
import { BigNumber } from 'ethers';
-import { parseEther } from 'viem';
+import { parseEther } from 'ethers/lib/utils';
import { Reserves } from './reserves.type';
import { Swap } from './Swap';
@@ -21,7 +21,7 @@ describe('calculateEthForExactNxm', () => {
];
test.each(cases)('calculates nxm out for eth in correctly - %s', (_type, nxmOut, expectedEthIn) => {
- const nxmOutParsed = parseEther(nxmOut.toString());
+ const nxmOutParsed = parseEther(nxmOut.toString()).toBigInt();
const ethInCalculated = swapApi.calculateEthForExactNxm(nxmOutParsed, reserves);
expect(ethInCalculated.toString()).toBe(expectedEthIn.toString());
});
@@ -29,13 +29,13 @@ describe('calculateEthForExactNxm', () => {
// throws error for invalid nxmOut values
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const invalidCases: Array<[string, any, string]> = [
- ['large value = nxmA reserve', parseEther('142858.457219554100789497'), 'Not enough NXM in the pool'],
- ['large value over nxmA reserve', parseEther('150000'), 'Not enough NXM in the pool'],
- ['larger value over nxmA reserve', parseEther('1000000'), 'Not enough NXM in the pool'],
- ['zero value', parseEther('0'), 'NXM out value must be greater than 0'],
- ['unit negative value', parseEther('-1'), 'NXM out value must be greater than 0'],
- ['large negative value', parseEther('-1000000'), 'NXM out value must be greater than 0'],
- ['small negative value', parseEther('-0.000000000000000001'), 'NXM out value must be greater than 0'],
+ ['large value = nxmA reserve', parseEther('142858.457219554100789497').toBigInt(), 'Not enough NXM in the pool'],
+ ['large value over nxmA reserve', parseEther('150000').toBigInt(), 'Not enough NXM in the pool'],
+ ['larger value over nxmA reserve', parseEther('1000000').toBigInt(), 'Not enough NXM in the pool'],
+ ['zero value', parseEther('0').toBigInt(), 'NXM out value must be greater than 0'],
+ ['unit negative value', parseEther('-1').toBigInt(), 'NXM out value must be greater than 0'],
+ ['large negative value', parseEther('-1000000').toBigInt(), 'NXM out value must be greater than 0'],
+ ['small negative value', parseEther('-0.000000000000000001').toBigInt(), 'NXM out value must be greater than 0'],
['null value', null, 'NXM out value must be greater than 0'],
['undefined value', undefined, 'Cannot mix BigInt and other types, use explicit conversions'],
['string value', '1', 'Cannot mix BigInt and other types, use explicit conversions'],
diff --git a/src/swap/calculateExactEthForNxm.test.ts b/src/swap/calculateExactEthForNxm.test.ts
index 0f301861..2827e080 100644
--- a/src/swap/calculateExactEthForNxm.test.ts
+++ b/src/swap/calculateExactEthForNxm.test.ts
@@ -1,5 +1,5 @@
import { BigNumber } from 'ethers';
-import { parseEther } from 'viem';
+import { parseEther } from 'ethers/lib/utils';
import { Reserves } from './reserves.type';
import { Swap } from './Swap';
@@ -22,7 +22,7 @@ describe('calculateExactEthForNxm', () => {
];
test.each(cases)('calculates eth out for nxm in correctly - %s', (_type, nxmIn, expectedEthOut) => {
- const nxmInParsed = parseEther(nxmIn.toString());
+ const nxmInParsed = parseEther(nxmIn.toString()).toBigInt();
const ethOutCalculated = swapApi.calculateExactEthForNxm(nxmInParsed, reserves);
expect(ethOutCalculated.toString()).toBe(expectedEthOut.toString());
});
@@ -30,10 +30,10 @@ describe('calculateExactEthForNxm', () => {
// throws error for invalid nxmIn values
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const invalidCases: Array<[string, any, string]> = [
- ['zero value', parseEther('0'), 'NXM in value must be greater than 0'],
- ['unit negative value', parseEther('-1'), 'NXM in value must be greater than 0'],
- ['large negative value', parseEther('-1000000'), 'NXM in value must be greater than 0'],
- ['small negative value', parseEther('-0.000000000000000001'), 'NXM in value must be greater than 0'],
+ ['zero value', parseEther('0').toBigInt(), 'NXM in value must be greater than 0'],
+ ['unit negative value', parseEther('-1').toBigInt(), 'NXM in value must be greater than 0'],
+ ['large negative value', parseEther('-1000000').toBigInt(), 'NXM in value must be greater than 0'],
+ ['small negative value', parseEther('-0.000000000000000001').toBigInt(), 'NXM in value must be greater than 0'],
['null value', null, 'NXM in value must be greater than 0'],
['undefined value', undefined, 'Cannot mix BigInt and other types, use explicit conversions'],
['string value', '1', 'Cannot mix BigInt and other types, use explicit conversions'],
diff --git a/src/swap/calculateExactNxmForEth.test.ts b/src/swap/calculateExactNxmForEth.test.ts
index f8f6bd26..73dd6246 100644
--- a/src/swap/calculateExactNxmForEth.test.ts
+++ b/src/swap/calculateExactNxmForEth.test.ts
@@ -1,5 +1,5 @@
import { BigNumber } from 'ethers';
-import { parseEther } from 'viem';
+import { parseEther } from 'ethers/lib/utils';
import { Reserves } from './reserves.type';
import { Swap } from './Swap';
@@ -22,7 +22,7 @@ describe('calculateExactNxmForEth', () => {
];
test.each(cases)('calculates nxm out for eth in correctly - %s', (_type, ethIn, expectedNxmOut) => {
- const ethInParsed = parseEther(ethIn.toString());
+ const ethInParsed = parseEther(ethIn.toString()).toBigInt();
const nxmOutCalculated = swapApi.calculateExactNxmForEth(ethInParsed, reserves);
expect(nxmOutCalculated.toString()).toBe(expectedNxmOut.toString());
});
@@ -30,10 +30,10 @@ describe('calculateExactNxmForEth', () => {
// throws error for invalid nxmIn values
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const invalidCases: Array<[string, any, string]> = [
- ['zero value', parseEther('0'), 'ETH in value must be greater than 0'],
- ['unit negative value', parseEther('-1'), 'ETH in value must be greater than 0'],
- ['large negative value', parseEther('-1000000'), 'ETH in value must be greater than 0'],
- ['small negative value', parseEther('-0.000000000000000001'), 'ETH in value must be greater than 0'],
+ ['zero value', parseEther('0').toBigInt(), 'ETH in value must be greater than 0'],
+ ['unit negative value', parseEther('-1').toBigInt(), 'ETH in value must be greater than 0'],
+ ['large negative value', parseEther('-1000000').toBigInt(), 'ETH in value must be greater than 0'],
+ ['small negative value', parseEther('-0.000000000000000001').toBigInt(), 'ETH in value must be greater than 0'],
['null value', null, 'ETH in value must be greater than 0'],
['undefined value', undefined, 'Cannot mix BigInt and other types, use explicit conversions'],
['string value', '1', 'Cannot mix BigInt and other types, use explicit conversions'],
diff --git a/src/swap/calculateNxmForExactEth.test.ts b/src/swap/calculateNxmForExactEth.test.ts
index 3c9e2acf..39e00ae0 100644
--- a/src/swap/calculateNxmForExactEth.test.ts
+++ b/src/swap/calculateNxmForExactEth.test.ts
@@ -1,5 +1,5 @@
import { BigNumber } from 'ethers';
-import { parseEther } from 'viem';
+import { parseEther } from 'ethers/lib/utils';
import { Reserves } from './reserves.type';
import { Swap } from './Swap';
@@ -21,7 +21,7 @@ describe('calculateNxmForExactEth', () => {
];
test.each(cases)('calculates nxm out for eth in correctly - %s', (_type, ethOut, expectedNxmIn) => {
- const ethOutParsed = parseEther(ethOut.toString());
+ const ethOutParsed = parseEther(ethOut.toString()).toBigInt();
const nxmInCalculated = swapApi.calculateNxmForExactEth(ethOutParsed, reserves);
expect(nxmInCalculated.toString()).toBe(expectedNxmIn.toString());
});
@@ -29,13 +29,13 @@ describe('calculateNxmForExactEth', () => {
// throws error for invalid ethOut values
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const invalidCases: Array<[string, any, string]> = [
- ['large = ethReserve', parseEther('5000'), 'Not enough ETH in the pool'],
+ ['large = ethReserve', parseEther('5000').toBigInt(), 'Not enough ETH in the pool'],
['large value over ethReserve', parseEther('15000'), 'Not enough ETH in the pool'],
- ['larger value over ethReserve', parseEther('100000'), 'Not enough ETH in the pool'],
- ['zero value', parseEther('0'), 'ETH out value must be greater than 0'],
- ['unit negative value', parseEther('-1'), 'ETH out value must be greater than 0'],
- ['large negative value', parseEther('-1000000'), 'ETH out value must be greater than 0'],
- ['small negative value', parseEther('-0.000000000000000001'), 'ETH out value must be greater than 0'],
+ ['larger value over ethReserve', parseEther('100000').toBigInt(), 'Not enough ETH in the pool'],
+ ['zero value', parseEther('0').toBigInt(), 'ETH out value must be greater than 0'],
+ ['unit negative value', parseEther('-1').toBigInt(), 'ETH out value must be greater than 0'],
+ ['large negative value', parseEther('-1000000').toBigInt(), 'ETH out value must be greater than 0'],
+ ['small negative value', parseEther('-0.000000000000000001').toBigInt(), 'ETH out value must be greater than 0'],
['null value', null, 'ETH out value must be greater than 0'],
['undefined value', undefined, 'Cannot mix BigInt and other types, use explicit conversions'],
['string value', '1', 'Cannot mix BigInt and other types, use explicit conversions'],
diff --git a/src/swap/calculatePriceImpactA.test.ts b/src/swap/calculatePriceImpactA.test.ts
index 7c34352c..c70b9c4e 100644
--- a/src/swap/calculatePriceImpactA.test.ts
+++ b/src/swap/calculatePriceImpactA.test.ts
@@ -1,5 +1,5 @@
import { BigNumber } from 'ethers';
-import { parseEther } from 'viem';
+import { parseEther } from 'ethers/lib/utils';
import { Reserves } from './reserves.type';
import { Swap } from './Swap';
@@ -22,7 +22,7 @@ describe('calculatePriceImpactB', () => {
];
test.each(cases)('calculates price impact A - %s', (_type, ethIn, expectedPriceImpact) => {
- const ethInParsed = parseEther(ethIn.toString());
+ const ethInParsed = parseEther(ethIn.toString()).toBigInt();
const priceImpact = swapApi.calculatePriceImpactA(ethInParsed, reserves);
expect(priceImpact.toString()).toBe(expectedPriceImpact.toString());
});
@@ -30,10 +30,10 @@ describe('calculatePriceImpactB', () => {
// throws error for invalid nxmIn values
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const invalidCases: Array<[string, any, string]> = [
- ['zero value', parseEther('0'), 'ETH in value must be greater than 0'],
- ['unit negative value', parseEther('-1'), 'ETH in value must be greater than 0'],
- ['large negative value', parseEther('-1000000'), 'ETH in value must be greater than 0'],
- ['small negative value', parseEther('-0.000000000000000001'), 'ETH in value must be greater than 0'],
+ ['zero value', parseEther('0').toBigInt(), 'ETH in value must be greater than 0'],
+ ['unit negative value', parseEther('-1').toBigInt(), 'ETH in value must be greater than 0'],
+ ['large negative value', parseEther('-1000000').toBigInt(), 'ETH in value must be greater than 0'],
+ ['small negative value', parseEther('-0.000000000000000001').toBigInt(), 'ETH in value must be greater than 0'],
['null value', null, 'ETH in value must be greater than 0'],
['undefined value', undefined, 'Cannot mix BigInt and other types, use explicit conversions'],
['string value', '1', 'Cannot mix BigInt and other types, use explicit conversions'],
diff --git a/src/swap/calculatePriceImpactB.test.ts b/src/swap/calculatePriceImpactB.test.ts
index 6c7b4e4a..06e2db87 100644
--- a/src/swap/calculatePriceImpactB.test.ts
+++ b/src/swap/calculatePriceImpactB.test.ts
@@ -1,5 +1,5 @@
import { BigNumber } from 'ethers';
-import { parseEther } from 'viem';
+import { parseEther } from 'ethers/lib/utils';
import { Reserves } from './reserves.type';
import { Swap } from './Swap';
@@ -22,7 +22,7 @@ describe('calculatePriceImpactB', () => {
];
test.each(cases)('calculates price impact B - %s', (_type, nxmIn, expectedPriceImpact) => {
- const nxmInParsed = parseEther(nxmIn.toString());
+ const nxmInParsed = parseEther(nxmIn.toString()).toBigInt();
const priceImpact = swapApi.calculatePriceImpactB(nxmInParsed, reserves);
expect(priceImpact.toString()).toBe(expectedPriceImpact.toString());
});
@@ -30,12 +30,12 @@ describe('calculatePriceImpactB', () => {
// throws error for invalid nxmIn values
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const invalidCases: Array<[string, any, string]> = [
- ['small value = 1e-17', parseEther('0.00000000000000001'), 'Division by zero'],
- ['small value = 1e-18', parseEther('0.000000000000000001'), 'Division by zero'],
- ['zero value', parseEther('0'), 'NXM in value must be greater than 0'],
- ['unit negative value', parseEther('-1'), 'NXM in value must be greater than 0'],
- ['large negative value', parseEther('-1000000'), 'NXM in value must be greater than 0'],
- ['small negative value', parseEther('-0.000000000000000001'), 'NXM in value must be greater than 0'],
+ ['small value = 1e-17', parseEther('0.00000000000000001').toBigInt(), 'Division by zero'],
+ ['small value = 1e-18', parseEther('0.000000000000000001').toBigInt(), 'Division by zero'],
+ ['zero value', parseEther('0').toBigInt(), 'NXM in value must be greater than 0'],
+ ['unit negative value', parseEther('-1').toBigInt(), 'NXM in value must be greater than 0'],
+ ['large negative value', parseEther('-1000000').toBigInt(), 'NXM in value must be greater than 0'],
+ ['small negative value', parseEther('-0.000000000000000001').toBigInt(), 'NXM in value must be greater than 0'],
['null value', null, 'NXM in value must be greater than 0'],
['undefined value', undefined, 'Cannot mix BigInt and other types, use explicit conversions'],
['string value', '1', 'Cannot mix BigInt and other types, use explicit conversions'],
diff --git a/src/swap/calculateSpotPrice.test.ts b/src/swap/calculateSpotPrice.test.ts
index 738ef247..6343e2d3 100644
--- a/src/swap/calculateSpotPrice.test.ts
+++ b/src/swap/calculateSpotPrice.test.ts
@@ -1,4 +1,4 @@
-import { parseEther } from 'viem';
+import { parseEther } from 'ethers/lib/utils';
import { Reserves } from './reserves.type';
import { Swap } from './Swap';
@@ -6,15 +6,15 @@ import { Swap } from './Swap';
describe('calculateSpotPrice', () => {
const swapApi = new Swap();
const reserves: Reserves = {
- nxmA: parseEther('2'),
- nxmB: parseEther('5'),
- ethReserve: parseEther('10'),
+ nxmA: parseEther('2').toBigInt(),
+ nxmB: parseEther('5').toBigInt(),
+ ethReserve: parseEther('10').toBigInt(),
budget: BigInt('0'),
};
it('calculates spot price', () => {
const { spotPriceA, spotPriceB } = swapApi.calculateSpotPrice(reserves);
- expect(spotPriceA).toBe(parseEther('5'));
- expect(spotPriceB).toBe(parseEther('2'));
+ expect(spotPriceA).toBe(parseEther('5').toBigInt());
+ expect(spotPriceB).toBe(parseEther('2').toBigInt());
});
});
diff --git a/src/types/ipfs.ts b/src/types/ipfs.ts
index 30f89826..c69b7fe1 100644
--- a/src/types/ipfs.ts
+++ b/src/types/ipfs.ts
@@ -1,6 +1,5 @@
import { z } from 'zod';
-import { ProductTypes } from '../../generated/types';
import {
coverValidatorsSchema,
coverQuotaShareSchema,
@@ -95,70 +94,6 @@ export type IPFSTypeContentTuple =
| [type: ContentType.file, content: File]
| [type: ContentType.productAnnex, content: ProductAnnex];
-export const IPFS_CONTENT_TYPE_BY_PRODUCT_TYPE: Record = {
- [ProductTypes.ethSlashing]: ContentType.coverValidators,
- [ProductTypes.liquidCollectiveEthStaking]: ContentType.coverValidators,
- [ProductTypes.stakewiseEthStaking]: ContentType.coverValidators,
- [ProductTypes.sherlockQuotaShare]: ContentType.coverQuotaShare,
- [ProductTypes.unoReQuotaShare]: ContentType.coverQuotaShare,
- [ProductTypes.deFiPass]: ContentType.defiPassContent,
- [ProductTypes.nexusMutual]: ContentType.coverWalletAddresses,
- [ProductTypes.followOn]: ContentType.coverFreeText,
- [ProductTypes.fundPortfolio]: ContentType.coverAumCoverAmountPercentage,
- [ProductTypes.generalizedFundPortfolio]: ContentType.coverAumCoverAmountPercentage,
- // ---------------------------------------------------------
- [ProductTypes.singleProtocol]: undefined,
- [ProductTypes.custody]: undefined,
- [ProductTypes.yieldToken]: undefined,
- [ProductTypes.sherlockExcess]: undefined,
- [ProductTypes.nativeProtocol]: undefined,
- [ProductTypes.theRetailMutual]: undefined,
- [ProductTypes.multiProtocol]: undefined,
- [ProductTypes.ethSlashingUmbrella]: undefined,
- [ProductTypes.openCoverTransaction]: undefined,
- [ProductTypes.sherlockBugBounty]: undefined,
- [ProductTypes.immunefiBugBounty]: undefined,
- [ProductTypes.crypto]: undefined,
- [ProductTypes.nativeSyndicate]: undefined,
- [ProductTypes.spearbitCantina]: undefined,
- [ProductTypes.leveragedLiquidation]: undefined,
- [ProductTypes.nonEVMProtocol]: undefined,
- [ProductTypes.kidnapAndRansom]: undefined,
- [ProductTypes.vault]: undefined,
-};
-
-export interface IPFSContentForProductType {
- [ProductTypes.ethSlashing]: CoverValidators;
- [ProductTypes.liquidCollectiveEthStaking]: CoverValidators;
- [ProductTypes.stakewiseEthStaking]: CoverValidators;
- [ProductTypes.sherlockQuotaShare]: CoverQuotaShare;
- [ProductTypes.unoReQuotaShare]: CoverQuotaShare;
- [ProductTypes.deFiPass]: DefiPassContent;
- [ProductTypes.nexusMutual]: CoverWalletAddresses;
- [ProductTypes.followOn]: CoverFreeText;
- [ProductTypes.fundPortfolio]: CoverAumCoverAmountPercentage;
- [ProductTypes.generalizedFundPortfolio]: CoverAumCoverAmountPercentage;
- // ---------------------------------------------------------
- [ProductTypes.singleProtocol]: undefined;
- [ProductTypes.custody]: undefined;
- [ProductTypes.yieldToken]: undefined;
- [ProductTypes.sherlockExcess]: undefined;
- [ProductTypes.nativeProtocol]: undefined;
- [ProductTypes.theRetailMutual]: undefined;
- [ProductTypes.multiProtocol]: undefined;
- [ProductTypes.ethSlashingUmbrella]: undefined;
- [ProductTypes.openCoverTransaction]: undefined;
- [ProductTypes.sherlockBugBounty]: undefined;
- [ProductTypes.immunefiBugBounty]: undefined;
- [ProductTypes.crypto]: undefined;
- [ProductTypes.nativeSyndicate]: undefined;
- [ProductTypes.spearbitCantina]: undefined;
- [ProductTypes.leveragedLiquidation]: undefined;
- [ProductTypes.nonEVMProtocol]: undefined;
- [ProductTypes.kidnapAndRansom]: undefined;
- [ProductTypes.vault]: undefined;
-}
-
export type IPFSUploadServiceResponse = {
ipfsHash: string;
};
diff --git a/src/types/product.ts b/src/types/product.ts
new file mode 100644
index 00000000..492e6b7e
--- /dev/null
+++ b/src/types/product.ts
@@ -0,0 +1,31 @@
+import { Integer } from './data';
+import { ContentType } from './ipfs';
+import { CoverAsset } from '../constants';
+
+export type ProductType = {
+ id: Integer;
+ name: string;
+ metadata: string;
+ claimMethod: Integer;
+ gracePeriod: string;
+ assessmentCooldownPeriod: string;
+ payoutRedemptionPeriod: string;
+ commissionRatio: string;
+ commissionDestination: string;
+ ipfsContentType?: ContentType; // Optional field to specify required IPFS content type for the product
+};
+
+export type Product = {
+ id: Integer;
+ productType: ProductType['id'];
+ name: string;
+ minPrice: string;
+ coverAssets: CoverAsset[];
+ initialPriceRation: string;
+ capacityReductionRatio: string;
+ isDeprecated: boolean;
+ useFixedPrice: boolean;
+ metadata: string;
+ allowedPools: number[];
+ logo: string;
+};