From 9b50cf9733354f6ba6426ae659f67051a1f731f4 Mon Sep 17 00:00:00 2001 From: Daniel Sanchez Date: Tue, 6 Apr 2021 17:32:33 +0200 Subject: [PATCH] Generate the list fetching info from manifest.json --- .eslintrc.js | 7 ++ README.md | 34 +++++- config/appsList.js | 132 +++++++++++++++++++++++ package.json | 9 +- public/gnosis-default.applist.json | 161 +++++++++++++++++++++++++---- src/apps.js | 151 --------------------------- src/buildList.js | 32 +++++- index.js => src/startDev.js | 11 +- src/utils/fetchAppInfo.js | 78 ++++++++++++++ src/utils/isAppManifestValid.js | 9 ++ src/write.js | 8 +- yarn.lock | 36 +++++++ 12 files changed, 480 insertions(+), 188 deletions(-) create mode 100644 .eslintrc.js create mode 100644 config/appsList.js delete mode 100644 src/apps.js rename index.js => src/startDev.js (59%) create mode 100644 src/utils/fetchAppInfo.js create mode 100644 src/utils/isAppManifestValid.js diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..cc4da78 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: [ + 'eslint:recommended', + 'prettier', // Add prettier rules to eslint + 'plugin:prettier/recommended', // Plugin to use prettier rules with eslint + ] +} diff --git a/README.md b/README.md index 96ee3b5..5719d09 100644 --- a/README.md +++ b/README.md @@ -1 +1,33 @@ -# safe-apps-list \ No newline at end of file +# Gnosis Safe default apps list + +This is a small project to generate and build the default apps list for Gnosis Safe + +## Getting started + +These instructions will get you to get a valid apps list to load in Gnosis Safe + +### Installing and running + +Install dependencies for the project: +``` +yarn install +``` + +To launch a local instance for dev purposes: +``` +yarn start +``` + +### Building + +Generate an instance of the list: +``` +yarn build +``` + +### Configuring + +To update the list configuration you have to modify the file at `config/appList.js`. To configure an app two parameters are mandatory: + + - `url`: An URL to fetch the resource. If it's using an IPFS link the following pattern should be used: `${process.env.REACT_APP_IPFS_GATEWAY}/QmUXF1yVGdqUfMbhNyfM3jpP6Bw66cYnKPoWq6iHkhd3Aw` + - `networks`: An array of supported networks. For convenience the enumerated object can be used to set it in a more friendly way. ex: [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY] \ No newline at end of file diff --git a/config/appsList.js b/config/appsList.js new file mode 100644 index 0000000..5ab171c --- /dev/null +++ b/config/appsList.js @@ -0,0 +1,132 @@ +const ETHEREUM_NETWORK = { + MAINNET: 1, + MORDEN :2, + ROPSTEN: 3, + RINKEBY: 4, + GOERLI: 5, + KOVAN: 42, + XDAI: 100, + ENERGY_WEB_CHAIN: 246, + VOLTA: 73799, + UNKNOWN: 0, + LOCAL: 4447, +} + +const safeAppsConfig = [ + // 1inch + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmUXF1yVGdqUfMbhNyfM3jpP6Bw66cYnKPoWq6iHkhd3Aw`, + networks: [ETHEREUM_NETWORK.MAINNET], + }, + // Aave + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmQ3w2ezp2zx3u2LYQHyuNzMrLDJFjyL1rjAFTjNMcQ4cK`, + networks: [ETHEREUM_NETWORK.MAINNET], + }, + // Aave v2 + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmVg7aXr5S8sT2iUdUwdkfTJNknmB7rcE3t92HiGoVsYDj`, + networks: [ETHEREUM_NETWORK.MAINNET], + }, + //Balancer Exchange + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmRb2VfPVYBrv6gi2zDywgVgTg3A19ZCRMqwL13Ez5f5AS`, + networks: [ETHEREUM_NETWORK.MAINNET], + }, + // Balancer Pool + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmVaxypk2FTyfcTS9oZKxmpQziPUTu2VRhhW7sso1mGysf`, + networks: [ETHEREUM_NETWORK.MAINNET], + }, + // CMM + // Point to a static server to allow app update without Safe deployment + { + url: `https://safe-cmm.gnosis.io`, + networks: [ETHEREUM_NETWORK.RINKEBY, ETHEREUM_NETWORK.XDAI], + }, + // Compound + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmX31xCdhFDmJzoVG33Y6kJtJ5Ujw8r5EJJBrsp8Fbjm7k`, + networks: [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY], + }, + // dHedge + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmaiemnumMaaK9wE1pbMfm3YSBUpcFNgDh3Bf6VZCZq57Q`, + networks: [ETHEREUM_NETWORK.MAINNET], + }, + // Idle + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmTvrLwJtyjG8QFHgvqdPhcV5DBMQ7oZceSU4uvPw9vQaj`, + networks: [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY], + }, + // Lido finance + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/Qmde8dsa9r8bB59CNGww6LRiaZABuKaJfuzvu94hFkatJC`, + networks: [ETHEREUM_NETWORK.MAINNET], + }, + // Mushrooms finance + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmT96aES2YA9BssByc6DVizQDkofmKRErs8gJyqWipjyS8`, + networks: [ETHEREUM_NETWORK.MAINNET], + }, + // Pooltogether + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmTa21pi77hiT1sLCGy5BeVwcyzExUSp2z7byxZukye8hr`, + networks: [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY], + }, + // Request + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmTBBaiDQyGa17DJ7DdviyHbc51fTVgf6Z5PW5w2YUTkgR`, + networks: [ETHEREUM_NETWORK.MAINNET], + }, + // Sablier + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/Qmb1Xpfu9mnX4A3trpoVeBZ9sTiNtEuRoFKEiaVXWntDxB`, + networks: [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY], + }, + // Synthetix + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmXLxxczMH4MBEYDeeN9zoiHDzVkeBmB5rBjA3UniPEFcA`, + networks: [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY], + }, + // OpenZeppelin + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmQovvfYYMUXjZfNbysQDUEXR8nr55iJRwcYgJQGJR7KEA`, + networks: [ + ETHEREUM_NETWORK.MAINNET, + ETHEREUM_NETWORK.RINKEBY, + //ETHEREUM_NETWORK.ENERGY_WEB_CHAIN, + //ETHEREUM_NETWORK.VOLTA, + // ETHEREUM_NETWORK.XDAI, + ], + }, + // TX-Builder + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmZBgEvjqi9Jg8xATr9uUgNUVmnfYiECNxZv9Taux7mzgV`, + networks: [ + ETHEREUM_NETWORK.MAINNET, + ETHEREUM_NETWORK.RINKEBY, + ETHEREUM_NETWORK.ENERGY_WEB_CHAIN, + ETHEREUM_NETWORK.VOLTA, + ETHEREUM_NETWORK.XDAI, + ], + }, + // Wallet-Connect + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmX9B982ZAaBzbm6yBoZUS3uLgcizYA6wW65RCXVRZkG6f`, + networks: [ + ETHEREUM_NETWORK.MAINNET, + ETHEREUM_NETWORK.RINKEBY, + ETHEREUM_NETWORK.ENERGY_WEB_CHAIN, + ETHEREUM_NETWORK.VOLTA, + ETHEREUM_NETWORK.XDAI, + ], + }, + // Yearn Vaults + { + url: `${process.env.REACT_APP_IPFS_GATEWAY}/Qme9HuPPhgCtgfj1CktvaDKhTesMueGCV2Kui1Sqna3Xs9`, + networks: [ETHEREUM_NETWORK.MAINNET], + }, +] + +module.exports = safeAppsConfig \ No newline at end of file diff --git a/package.json b/package.json index 0dfe448..c779f16 100644 --- a/package.json +++ b/package.json @@ -5,21 +5,20 @@ "main": "public/app-list.json", "scripts": { "build": "rimraf public && mkdir -p public && cross-env node src/write.js > public/gnosis-default.applist.json", - "start": "nodemon ./index.js", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "eslintConfig": { - "extends": "react-app" + "start": "nodemon src/startDev.js" }, "dependencies": { "express": "^4.17.1" }, "devDependencies": { + "axios": "^0.21.1", "cors": "^2.8.5", "cross-env": "^7.0.3", "dotenv": "^8.2.0", "eslint": "^7.23.0", + "eslint-plugin-prettier": "^3.3.1", "nodemon": "^2.0.7", + "prettier": "^2.2.1", "rimraf": "^3.0.2" } } diff --git a/public/gnosis-default.applist.json b/public/gnosis-default.applist.json index d975288..dbe5246 100644 --- a/public/gnosis-default.applist.json +++ b/public/gnosis-default.applist.json @@ -1,6 +1,6 @@ { "name": "Gnosis Safe default app list", - "timestamp": "2021-04-06T07:47:42.271Z", + "timestamp": "2021-04-06T15:20:49.324Z", "version": { "major": 0, "minor": 1, @@ -16,120 +16,231 @@ ], "apps": [ { + "id": "{\"url\":\"https://cloudflare-ipfs.com/ipfs/QmUXF1yVGdqUfMbhNyfM3jpP6Bw66cYnKPoWq6iHkhd3Aw\",\"name\":\"1\"}", "url": "https://cloudflare-ipfs.com/ipfs/QmUXF1yVGdqUfMbhNyfM3jpP6Bw66cYnKPoWq6iHkhd3Aw", - "disabled": false, + "name": "1inch.exchange", + "iconUrl": "https://cloudflare-ipfs.com/ipfs/QmUXF1yVGdqUfMbhNyfM3jpP6Bw66cYnKPoWq6iHkhd3Aw/assets/1inch.png", + "description": "DEX aggregator with the best prices on the market", + "iconPath": "assets/1inch.png", + "providedBy": { + "name": "1inch corporation", + "url": "https://1inch.exchange" + }, "networks": [ 1 ] }, { + "id": "{\"url\":\"https://cloudflare-ipfs.com/ipfs/QmQ3w2ezp2zx3u2LYQHyuNzMrLDJFjyL1rjAFTjNMcQ4cK\",\"name\":\"A\"}", "url": "https://cloudflare-ipfs.com/ipfs/QmQ3w2ezp2zx3u2LYQHyuNzMrLDJFjyL1rjAFTjNMcQ4cK", - "disabled": false, + "name": "Aave", + "iconUrl": "https://cloudflare-ipfs.com/ipfs/QmQ3w2ezp2zx3u2LYQHyuNzMrLDJFjyL1rjAFTjNMcQ4cK/aave.svg", + "description": "Lend and borrow straight from your Gnosis Safe", + "iconPath": "aave.svg", + "providedBy": { + "name": "Aave", + "url": "https://aave.com" + }, "networks": [ 1 ] }, { + "id": "{\"url\":\"https://cloudflare-ipfs.com/ipfs/QmVg7aXr5S8sT2iUdUwdkfTJNknmB7rcE3t92HiGoVsYDj\",\"name\":\"A\"}", + "url": "https://cloudflare-ipfs.com/ipfs/QmVg7aXr5S8sT2iUdUwdkfTJNknmB7rcE3t92HiGoVsYDj", + "name": "Aave V2", + "iconUrl": "https://cloudflare-ipfs.com/ipfs/QmVg7aXr5S8sT2iUdUwdkfTJNknmB7rcE3t92HiGoVsYDj/aave.svg", + "description": "Deposit, earn, borrow, and stake on Aave, straight from your Gnosis Safe", + "iconPath": "aave.svg", + "providedBy": { + "name": "Aave", + "url": "https://aave.com" + }, + "networks": [ + 1 + ] + }, + { + "id": "{\"url\":\"https://cloudflare-ipfs.com/ipfs/QmRb2VfPVYBrv6gi2zDywgVgTg3A19ZCRMqwL13Ez5f5AS\",\"name\":\"B\"}", "url": "https://cloudflare-ipfs.com/ipfs/QmRb2VfPVYBrv6gi2zDywgVgTg3A19ZCRMqwL13Ez5f5AS", - "disabled": false, + "name": "Balancer Exchange", + "iconUrl": "https://cloudflare-ipfs.com/ipfs/QmRb2VfPVYBrv6gi2zDywgVgTg3A19ZCRMqwL13Ez5f5AS/logo512.png", + "description": "Exchange tokens using the Balancer DEX", + "iconPath": "logo512.png", + "providedBy": { + "name": "Balancer Labs", + "url": "https://balancer.finance" + }, "networks": [ 1 ] }, { + "id": "{\"url\":\"https://cloudflare-ipfs.com/ipfs/QmVaxypk2FTyfcTS9oZKxmpQziPUTu2VRhhW7sso1mGysf\",\"name\":\"B\"}", "url": "https://cloudflare-ipfs.com/ipfs/QmVaxypk2FTyfcTS9oZKxmpQziPUTu2VRhhW7sso1mGysf", - "disabled": false, + "name": "Balancer Pool Management", + "iconUrl": "https://cloudflare-ipfs.com/ipfs/QmVaxypk2FTyfcTS9oZKxmpQziPUTu2VRhhW7sso1mGysf/logo512.png", + "description": "Manage liquidity on Balancer from your Gnosis Safe", + "iconPath": "logo512.png", + "providedBy": { + "name": "Balancer Labs", + "url": "https://balancer.finance" + }, "networks": [ 1 ] }, { + "id": "{\"url\":\"https://safe-cmm.gnosis.io\",\"name\":\"Gnosis Custom Market Maker\"}", "url": "https://safe-cmm.gnosis.io", - "disabled": false, + "name": "Gnosis Custom Market Maker", + "iconUrl": "https://safe-cmm.gnosis.io/img/appIcon.svg", + "description": "Allows you to deploy, withdraw and manage your custom market maker strategies", + "iconPath": "img/appIcon.svg", "networks": [ 4, 100 ] }, { + "id": "{\"url\":\"https://cloudflare-ipfs.com/ipfs/QmX31xCdhFDmJzoVG33Y6kJtJ5Ujw8r5EJJBrsp8Fbjm7k\",\"name\":\"C\"}", "url": "https://cloudflare-ipfs.com/ipfs/QmX31xCdhFDmJzoVG33Y6kJtJ5Ujw8r5EJJBrsp8Fbjm7k", - "disabled": false, + "name": "Compound", + "iconUrl": "https://cloudflare-ipfs.com/ipfs/QmX31xCdhFDmJzoVG33Y6kJtJ5Ujw8r5EJJBrsp8Fbjm7k/Compound.png", + "description": "Money markets on the Ethereum blockchain", + "iconPath": "Compound.png", "networks": [ 1, 4 ] }, { + "id": "{\"url\":\"https://cloudflare-ipfs.com/ipfs/QmaiemnumMaaK9wE1pbMfm3YSBUpcFNgDh3Bf6VZCZq57Q\",\"name\":\"d\"}", "url": "https://cloudflare-ipfs.com/ipfs/QmaiemnumMaaK9wE1pbMfm3YSBUpcFNgDh3Bf6VZCZq57Q", - "disabled": false, + "name": "dHEDGE", + "iconUrl": "https://cloudflare-ipfs.com/ipfs/QmaiemnumMaaK9wE1pbMfm3YSBUpcFNgDh3Bf6VZCZq57Q/logo.svg", + "description": "Decentralized asset management", + "iconPath": "logo.svg", "networks": [ 1 ] }, { + "id": "{\"url\":\"https://cloudflare-ipfs.com/ipfs/QmTvrLwJtyjG8QFHgvqdPhcV5DBMQ7oZceSU4uvPw9vQaj\",\"name\":\"I\"}", "url": "https://cloudflare-ipfs.com/ipfs/QmTvrLwJtyjG8QFHgvqdPhcV5DBMQ7oZceSU4uvPw9vQaj", - "disabled": false, + "name": "Idle v4", + "iconUrl": "https://cloudflare-ipfs.com/ipfs/QmTvrLwJtyjG8QFHgvqdPhcV5DBMQ7oZceSU4uvPw9vQaj/logo.svg", + "description": "Always the best yield, with no effort", + "iconPath": "logo.svg", "networks": [ 1, 4 ] }, { + "id": "{\"url\":\"https://cloudflare-ipfs.com/ipfs/Qmde8dsa9r8bB59CNGww6LRiaZABuKaJfuzvu94hFkatJC\",\"name\":\"L\"}", "url": "https://cloudflare-ipfs.com/ipfs/Qmde8dsa9r8bB59CNGww6LRiaZABuKaJfuzvu94hFkatJC", - "disabled": false, + "name": "Lido Staking", + "iconUrl": "https://cloudflare-ipfs.com/ipfs/Qmde8dsa9r8bB59CNGww6LRiaZABuKaJfuzvu94hFkatJC/logo.svg", + "description": "Lido is the liquid staking solution for Ethereum.", + "iconPath": "logo.svg", + "providedBy": { + "name": "Lido", + "url": "https://lido.fi" + }, "networks": [ 1 ] }, { + "id": "{\"url\":\"https://cloudflare-ipfs.com/ipfs/QmT96aES2YA9BssByc6DVizQDkofmKRErs8gJyqWipjyS8\",\"name\":\"M\"}", "url": "https://cloudflare-ipfs.com/ipfs/QmT96aES2YA9BssByc6DVizQDkofmKRErs8gJyqWipjyS8", - "disabled": false, + "name": "Mushrooms Finance", + "iconUrl": "https://cloudflare-ipfs.com/ipfs/QmT96aES2YA9BssByc6DVizQDkofmKRErs8gJyqWipjyS8/logo.png", + "description": "Mushrooms Finance is a yield aggregator with focus on seeking sustainable profit", + "iconPath": "logo.png", "networks": [ 1 ] }, { + "id": "{\"url\":\"https://cloudflare-ipfs.com/ipfs/QmTa21pi77hiT1sLCGy5BeVwcyzExUSp2z7byxZukye8hr\",\"name\":\"P\"}", "url": "https://cloudflare-ipfs.com/ipfs/QmTa21pi77hiT1sLCGy5BeVwcyzExUSp2z7byxZukye8hr", - "disabled": false, + "name": "PoolTogether", + "iconUrl": "https://cloudflare-ipfs.com/ipfs/QmTa21pi77hiT1sLCGy5BeVwcyzExUSp2z7byxZukye8hr/pool-together.png", + "description": "No-loss lotteries on your Gnosis Safe", + "iconPath": "pool-together.png", + "providedBy": { + "name": "Avo Labs", + "url": "https://avolabs.io/" + }, "networks": [ 1, 4 ] }, { + "id": "{\"url\":\"https://cloudflare-ipfs.com/ipfs/QmTBBaiDQyGa17DJ7DdviyHbc51fTVgf6Z5PW5w2YUTkgR\",\"name\":\"R\"}", "url": "https://cloudflare-ipfs.com/ipfs/QmTBBaiDQyGa17DJ7DdviyHbc51fTVgf6Z5PW5w2YUTkgR", - "disabled": false, + "name": "Request", + "iconUrl": "https://cloudflare-ipfs.com/ipfs/QmTBBaiDQyGa17DJ7DdviyHbc51fTVgf6Z5PW5w2YUTkgR/request_icon_green.svg", + "description": "Create, receive and pay crypto invoices straight from your Gnosis Safe", + "iconPath": "request_icon_green.svg", + "providedBy": { + "name": "Request", + "url": "https://request.network" + }, "networks": [ 1 ] }, { + "id": "{\"url\":\"https://cloudflare-ipfs.com/ipfs/Qmb1Xpfu9mnX4A3trpoVeBZ9sTiNtEuRoFKEiaVXWntDxB\",\"name\":\"S\"}", "url": "https://cloudflare-ipfs.com/ipfs/Qmb1Xpfu9mnX4A3trpoVeBZ9sTiNtEuRoFKEiaVXWntDxB", - "disabled": false, + "name": "Sablier", + "iconUrl": "https://cloudflare-ipfs.com/ipfs/Qmb1Xpfu9mnX4A3trpoVeBZ9sTiNtEuRoFKEiaVXWntDxB/logo.svg", + "description": "Safe App for interacting with the Sablier protocol", + "iconPath": "logo.svg", + "providedBy": { + "name": "Sablier", + "url": "https://sablier.finance" + }, "networks": [ 1, 4 ] }, { + "id": "{\"url\":\"https://cloudflare-ipfs.com/ipfs/QmXLxxczMH4MBEYDeeN9zoiHDzVkeBmB5rBjA3UniPEFcA\",\"name\":\"S\"}", "url": "https://cloudflare-ipfs.com/ipfs/QmXLxxczMH4MBEYDeeN9zoiHDzVkeBmB5rBjA3UniPEFcA", - "disabled": false, + "name": "Synthetix", + "iconUrl": "https://cloudflare-ipfs.com/ipfs/QmXLxxczMH4MBEYDeeN9zoiHDzVkeBmB5rBjA3UniPEFcA/Synthetix.png", + "description": "Trade synthetic assets on Ethereum", + "iconPath": "Synthetix.png", "networks": [ 1, 4 ] }, { + "id": "{\"url\":\"https://cloudflare-ipfs.com/ipfs/QmQovvfYYMUXjZfNbysQDUEXR8nr55iJRwcYgJQGJR7KEA\",\"name\":\"O\"}", "url": "https://cloudflare-ipfs.com/ipfs/QmQovvfYYMUXjZfNbysQDUEXR8nr55iJRwcYgJQGJR7KEA", - "disabled": false, + "name": "OpenZeppelin", + "iconUrl": "https://cloudflare-ipfs.com/ipfs/QmQovvfYYMUXjZfNbysQDUEXR8nr55iJRwcYgJQGJR7KEA/openzeppelin.png", + "description": "Securely manage and monitor your Ethereum project.", + "iconPath": "openzeppelin.png", "networks": [ 1, 4 ] }, { + "id": "{\"url\":\"https://cloudflare-ipfs.com/ipfs/QmZBgEvjqi9Jg8xATr9uUgNUVmnfYiECNxZv9Taux7mzgV\",\"name\":\"T\"}", "url": "https://cloudflare-ipfs.com/ipfs/QmZBgEvjqi9Jg8xATr9uUgNUVmnfYiECNxZv9Taux7mzgV", - "disabled": false, + "name": "Transaction Builder", + "iconUrl": "https://cloudflare-ipfs.com/ipfs/QmZBgEvjqi9Jg8xATr9uUgNUVmnfYiECNxZv9Taux7mzgV/tx-builder.png", + "description": "A Safe app to compose custom transactions", + "iconPath": "tx-builder.png", "networks": [ 1, 4, @@ -139,8 +250,12 @@ ] }, { + "id": "{\"url\":\"https://cloudflare-ipfs.com/ipfs/QmX9B982ZAaBzbm6yBoZUS3uLgcizYA6wW65RCXVRZkG6f\",\"name\":\"W\"}", "url": "https://cloudflare-ipfs.com/ipfs/QmX9B982ZAaBzbm6yBoZUS3uLgcizYA6wW65RCXVRZkG6f", - "disabled": false, + "name": "WalletConnect", + "iconUrl": "https://cloudflare-ipfs.com/ipfs/QmX9B982ZAaBzbm6yBoZUS3uLgcizYA6wW65RCXVRZkG6f/wallet-connect.svg", + "description": "Allows your Gnosis Safe Multisig to connect to dapps via WalletConnect.", + "iconPath": "wallet-connect.svg", "networks": [ 1, 4, @@ -150,8 +265,16 @@ ] }, { + "id": "{\"url\":\"https://cloudflare-ipfs.com/ipfs/Qme9HuPPhgCtgfj1CktvaDKhTesMueGCV2Kui1Sqna3Xs9\",\"name\":\"Y\"}", "url": "https://cloudflare-ipfs.com/ipfs/Qme9HuPPhgCtgfj1CktvaDKhTesMueGCV2Kui1Sqna3Xs9", - "disabled": false, + "name": "Yearn Vaults", + "iconUrl": "https://cloudflare-ipfs.com/ipfs/Qme9HuPPhgCtgfj1CktvaDKhTesMueGCV2Kui1Sqna3Xs9/logo.svg", + "description": "Safe App for interacting with Yearn Vaults", + "iconPath": "logo.svg", + "providedBy": { + "name": "Yearn Finance", + "url": "https://yearn.finance" + }, "networks": [ 1 ] diff --git a/src/apps.js b/src/apps.js deleted file mode 100644 index f23ae83..0000000 --- a/src/apps.js +++ /dev/null @@ -1,151 +0,0 @@ -const ETHEREUM_NETWORK = { - MAINNET: 1, - MORDEN :2, - ROPSTEN: 3, - RINKEBY: 4, - GOERLI: 5, - KOVAN: 42, - XDAI: 100, - ENERGY_WEB_CHAIN: 246, - VOLTA: 73799, - UNKNOWN: 0, - LOCAL: 4447, - } - -const safeAppsConfig = [ - // 1inch - { - url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmUXF1yVGdqUfMbhNyfM3jpP6Bw66cYnKPoWq6iHkhd3Aw`, - disabled: false, - networks: [ETHEREUM_NETWORK.MAINNET], - }, - // Aave - { - url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmQ3w2ezp2zx3u2LYQHyuNzMrLDJFjyL1rjAFTjNMcQ4cK`, - disabled: false, - networks: [ETHEREUM_NETWORK.MAINNET], - }, - //Balancer Exchange - { - url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmRb2VfPVYBrv6gi2zDywgVgTg3A19ZCRMqwL13Ez5f5AS`, - disabled: false, - networks: [ETHEREUM_NETWORK.MAINNET], - }, - // Balancer Pool - { - url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmVaxypk2FTyfcTS9oZKxmpQziPUTu2VRhhW7sso1mGysf`, - disabled: false, - networks: [ETHEREUM_NETWORK.MAINNET], - }, - // CMM - // Point to a static server to allow app update without Safe deployment - { - url: `https://safe-cmm.gnosis.io`, - disabled: false, - networks: [ETHEREUM_NETWORK.RINKEBY, ETHEREUM_NETWORK.XDAI], - }, - // Compound - { - url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmX31xCdhFDmJzoVG33Y6kJtJ5Ujw8r5EJJBrsp8Fbjm7k`, - disabled: false, - networks: [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY], - }, - // dHedge - { - url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmaiemnumMaaK9wE1pbMfm3YSBUpcFNgDh3Bf6VZCZq57Q`, - disabled: false, - networks: [ETHEREUM_NETWORK.MAINNET], - }, - // Idle - { - url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmTvrLwJtyjG8QFHgvqdPhcV5DBMQ7oZceSU4uvPw9vQaj`, - disabled: false, - networks: [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY], - }, - // Lido finance - { - url: `${process.env.REACT_APP_IPFS_GATEWAY}/Qmde8dsa9r8bB59CNGww6LRiaZABuKaJfuzvu94hFkatJC`, - disabled: false, - networks: [ETHEREUM_NETWORK.MAINNET], - }, - // Mushrooms finance - { - url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmT96aES2YA9BssByc6DVizQDkofmKRErs8gJyqWipjyS8`, - disabled: false, - networks: [ETHEREUM_NETWORK.MAINNET], - }, - // Pooltogether - { - url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmTa21pi77hiT1sLCGy5BeVwcyzExUSp2z7byxZukye8hr`, - disabled: false, - networks: [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY], - }, - // Request - { - url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmTBBaiDQyGa17DJ7DdviyHbc51fTVgf6Z5PW5w2YUTkgR`, - disabled: false, - networks: [ETHEREUM_NETWORK.MAINNET], - }, - // Sablier - { - url: `${process.env.REACT_APP_IPFS_GATEWAY}/Qmb1Xpfu9mnX4A3trpoVeBZ9sTiNtEuRoFKEiaVXWntDxB`, - disabled: false, - networks: [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY], - }, - // Synthetix - { - url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmXLxxczMH4MBEYDeeN9zoiHDzVkeBmB5rBjA3UniPEFcA`, - disabled: false, - networks: [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY], - }, - // OpenZeppelin - { - url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmQovvfYYMUXjZfNbysQDUEXR8nr55iJRwcYgJQGJR7KEA`, - disabled: false, - networks: [ - ETHEREUM_NETWORK.MAINNET, - ETHEREUM_NETWORK.RINKEBY, - //ETHEREUM_NETWORK.ENERGY_WEB_CHAIN, - //ETHEREUM_NETWORK.VOLTA, - // ETHEREUM_NETWORK.XDAI, - ], - }, - // TX-Builder - { - url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmZBgEvjqi9Jg8xATr9uUgNUVmnfYiECNxZv9Taux7mzgV`, - disabled: false, - networks: [ - ETHEREUM_NETWORK.MAINNET, - ETHEREUM_NETWORK.RINKEBY, - ETHEREUM_NETWORK.ENERGY_WEB_CHAIN, - ETHEREUM_NETWORK.VOLTA, - ETHEREUM_NETWORK.XDAI, - ], - }, - // Wallet-Connect - { - url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmX9B982ZAaBzbm6yBoZUS3uLgcizYA6wW65RCXVRZkG6f`, - disabled: false, - networks: [ - ETHEREUM_NETWORK.MAINNET, - ETHEREUM_NETWORK.RINKEBY, - ETHEREUM_NETWORK.ENERGY_WEB_CHAIN, - ETHEREUM_NETWORK.VOLTA, - ETHEREUM_NETWORK.XDAI, - ], - }, - // Yearn Vaults - { - url: `${process.env.REACT_APP_IPFS_GATEWAY}/Qme9HuPPhgCtgfj1CktvaDKhTesMueGCV2Kui1Sqna3Xs9`, - disabled: false, - networks: [ETHEREUM_NETWORK.MAINNET], - }, - ] - -module.exports = function apps() { - if (!process.env.REACT_APP_IPFS_GATEWAY) { - throw Error('REACT_APP_IPFS_GATEWAY should be defined') - } - - return safeAppsConfig -} \ No newline at end of file diff --git a/src/buildList.js b/src/buildList.js index 65df00a..8a3a73e 100644 --- a/src/buildList.js +++ b/src/buildList.js @@ -1,11 +1,31 @@ const { version } = require('../package.json') -const staticAppsList = require('./apps.js') -module.exports = function buildList () { +const safeAppsConfig = require('../config/appsList') +const fetchAppInfo = require('./utils/fetchAppInfo') + +const getAppsList = async () => { + if (!process.env.REACT_APP_IPFS_GATEWAY) { + throw Error('REACT_APP_IPFS_GATEWAY should be defined') + } + + const safeAppsWithManifestInfoPromises = safeAppsConfig.map(async app => { + const appInfo = fetchAppInfo(app) + return appInfo + }) + + const safeAppsWithManifestInfo = await Promise.all(safeAppsWithManifestInfoPromises) + + return safeAppsWithManifestInfo +} + + +const buildList = async () => { const parsed = version.split('.') + const appsList = await getAppsList() + return { - name: "Gnosis Safe default app list", + name: "Gnosis Safe default apps list", timestamp: new Date().toISOString(), version: { major: +parsed[0], @@ -14,6 +34,8 @@ module.exports = function buildList () { }, logoUri: 'logo-uri', keywords: ['gnosis', 'safe', 'default', 'app', 'list'], - apps: [...staticAppsList()] + apps: [...appsList] } -} \ No newline at end of file +} + +module.exports = buildList \ No newline at end of file diff --git a/index.js b/src/startDev.js similarity index 59% rename from index.js rename to src/startDev.js index 14e1292..b6f3ae4 100644 --- a/index.js +++ b/src/startDev.js @@ -1,15 +1,16 @@ -const express = require('express') const cors = require('cors') -const path = require('path') +const config = require('dotenv').config() +const express = require('express') -const appList = require('./src/app-list.json') +const buildList = require('./buildList') const app = express() const port = process.env.PORT || '8080' app.use(cors()) -app.get('/', (req, res) => { - res.json(appList) +app.get('/', async (req, res) => { + const list = await buildList() + res.json(list) }); diff --git a/src/utils/fetchAppInfo.js b/src/utils/fetchAppInfo.js new file mode 100644 index 0000000..cf31b24 --- /dev/null +++ b/src/utils/fetchAppInfo.js @@ -0,0 +1,78 @@ +const axios = require('axios') +const isAppManifestValid = require('./isAppManifestValid') + +const removeLastTrailingSlash = (url) => { + if (url.substr(-1) === '/') { + return url.substr(0, url.length - 1) + } + return url +} + +const getEmptySafeApp = () => { + return { + id: Math.random().toString(), + url: '', + name: 'unknown', + iconUrl: '', + description: '', + // fetchStatus: SAFE_APP_FETCH_STATUS.LOADING, + } +} + +module.exports = async ({ url: appUrl, networks }) => { + let res = { + ...getEmptySafeApp(), + // loadingStatus: SAFE_APP_FETCH_STATUS.ERROR, + } + + if (!appUrl?.length) { + return res + } + + res.url = appUrl.trim() + const noTrailingSlashUrl = removeLastTrailingSlash(res.url) + + try { + const appInfo = await axios.get(`${noTrailingSlashUrl}/manifest.json`, { timeout: 5_000 }) + + // verify imported app fulfil safe requirements + if (!appInfo?.data || !isAppManifestValid(appInfo.data)) { + throw Error('The app does not fulfil the structure required.') + } + + // the DB origin field has a limit of 100 characters + const originFieldSize = 100 + const jsonDataLength = 20 + const remainingSpace = originFieldSize - res.url.length - jsonDataLength + + const appInfoData = { + name: appInfo.data.name, + iconPath: appInfo.data.iconPath, + description: appInfo.data.description, + providedBy: appInfo.data.providedBy, + } + + res = { + ...res, + ...appInfoData, + networks, + id: JSON.stringify({ url: res.url, name: appInfo.data.name.substring(0, remainingSpace) }), + // loadingStatus: SAFE_APP_FETCH_STATUS.SUCCESS, + } + + if (appInfo.data.iconPath) { + try { + const iconInfo = await axios.get(`${noTrailingSlashUrl}/${appInfo.data.iconPath}`, { timeout: 1000 * 10 }) + if (/image\/\w/gm.test(iconInfo.headers['content-type'])) { + res.iconUrl = `${noTrailingSlashUrl}/${appInfo.data.iconPath}` + } + } catch (error) { + console.error(`It was not possible to fetch icon from app ${res.url}`) + } + } + return res + } catch (error) { + console.error(`It was not possible to fetch app from ${res.url}: ${error.message}`) + return res + } +} \ No newline at end of file diff --git a/src/utils/isAppManifestValid.js b/src/utils/isAppManifestValid.js new file mode 100644 index 0000000..a4b3c80 --- /dev/null +++ b/src/utils/isAppManifestValid.js @@ -0,0 +1,9 @@ +module.exports = (appInfo) => +// `appInfo` exists and `name` exists +!!appInfo?.name && +// if `name` exists is not 'unknown' +appInfo.name !== 'unknown' && +// `description` exists +!!appInfo.description && +// no `error` (or `error` undefined) +!appInfo.error \ No newline at end of file diff --git a/src/write.js b/src/write.js index b5e6784..3b5e2cd 100644 --- a/src/write.js +++ b/src/write.js @@ -1,3 +1,7 @@ const config = require('dotenv').config() -const buildList = require('./buildList') -console.log(JSON.stringify(buildList(), null, 2)) \ No newline at end of file +const buildList = require('./buildList.js'); + +(async () => { + const list = await buildList().then(list => JSON.stringify(list, null, 2)) + console.log(list) +})() diff --git a/yarn.lock b/yarn.lock index ede79f6..a8c0820 100644 --- a/yarn.lock +++ b/yarn.lock @@ -154,6 +154,13 @@ astral-regex@^2.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== +axios@^0.21.1: + version "0.21.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" + integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== + dependencies: + follow-redirects "^1.10.0" + balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -520,6 +527,13 @@ escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +eslint-plugin-prettier@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.3.1.tgz#7079cfa2497078905011e6f82e8dd8453d1371b7" + integrity sha512-Rq3jkcFY8RYeQLgk2cCwuc0P7SEFwDravPhsJZOQ5N4YI4DSg50NyqJ/9gdZHzQlHf8MvafSesbNJCcP/FF6pQ== + dependencies: + prettier-linter-helpers "^1.0.0" + eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" @@ -677,6 +691,11 @@ fast-deep-equal@^3.1.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-diff@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -727,6 +746,11 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469" integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA== +follow-redirects@^1.10.0: + version "1.13.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267" + integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA== + forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" @@ -1359,6 +1383,18 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5" + integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q== + progress@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"