diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..15d85e73 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +NEXT_PUBLIC_HOST_URL='http://localhost:3000' +NEXT_PUBLIC_GOOGLE_ANALYTICS_TRACKING_ID= +NEXT_PUBLIC_GOOGLE_ANALYTICS_DOMAIN= \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..1514a3e1 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,28 @@ +module.exports = { + parser: '@typescript-eslint/parser', + extends: [ + 'plugin:@typescript-eslint/recommended', + 'plugin:security/recommended-legacy', + 'standard-with-typescript', + 'next/core-web-vitals' + ], + parserOptions: { + project: './tsconfig.json' + }, + ignorePatterns: ['.eslintrc.js', 'next.config.js', 'next-env.d.ts', 'out'], + rules: { + '@typescript-eslint/key-spacing': 0, + 'multiline-ternary': 0, + 'no-console': ['error', { allow: ['info', 'warn', 'error'] }], + 'max-len': [ + 2, + { + code: 100, + ignoreComments: true, + ignoreStrings: true, + ignoreTemplateLiterals: true, + ignoreUrls: true + } + ] + } +} diff --git a/.gitbook.yaml b/.gitbook.yaml deleted file mode 100644 index cc03e8f2..00000000 --- a/.gitbook.yaml +++ /dev/null @@ -1,11 +0,0 @@ -root: ./ - -redirects: - safe-core-protocol: safe-smart-account - safe-core-protocol/plugins: safe-smart-account/modules.md - safe-core-protocol/hooks: safe-smart-account/guards.md - safe-core-protocol/security: safe-smart-account/security/README.md - safe-core-protocol/security/bug-bounty-program: safe-smart-account/security/bug-bounty-program.md - safe-core-protocol/security/security-audits: safe-smart-account/security/security-audits.md - safe-core-protocol/signatures: safe-smart-account/signatures/README.md - safe-core-protocol/signatures/eip-1271: safe-smart-account/signatures/eip-1271.md diff --git a/.gitbook/assets/diagram-safe-ecosystem.png b/.gitbook/assets/diagram-safe-ecosystem.png deleted file mode 100644 index 90dbce99..00000000 Binary files a/.gitbook/assets/diagram-safe-ecosystem.png and /dev/null differ diff --git a/.gitbook/assets/diagram-safe-tools.png b/.gitbook/assets/diagram-safe-tools.png deleted file mode 100644 index 3fb6b4e8..00000000 Binary files a/.gitbook/assets/diagram-safe-tools.png and /dev/null differ diff --git a/.gitbook/assets/diagram-third-party-custody.png b/.gitbook/assets/diagram-third-party-custody.png deleted file mode 100644 index d5b52602..00000000 Binary files a/.gitbook/assets/diagram-third-party-custody.png and /dev/null differ diff --git a/.gitbook/assets/eoas-vs-smart-accounts.png b/.gitbook/assets/eoas-vs-smart-accounts.png deleted file mode 100644 index cd81e3cb..00000000 Binary files a/.gitbook/assets/eoas-vs-smart-accounts.png and /dev/null differ diff --git a/.gitbook/assets/safe-iceberg.png b/.gitbook/assets/safe-iceberg.png deleted file mode 100644 index 7f6b55e2..00000000 Binary files a/.gitbook/assets/safe-iceberg.png and /dev/null differ diff --git a/.gitbook/assets/table-compare-storage-solutions.png b/.gitbook/assets/table-compare-storage-solutions.png deleted file mode 100644 index c0b293c7..00000000 Binary files a/.gitbook/assets/table-compare-storage-solutions.png and /dev/null differ diff --git a/.github/scripts/generateSupportedNetworks.js b/.github/scripts/generateSupportedNetworks.js new file mode 100644 index 00000000..e45856dc --- /dev/null +++ b/.github/scripts/generateSupportedNetworks.js @@ -0,0 +1,156 @@ +// This script generates the supported networks page from the safe-deployments repo. +// It clones the repo, reads the JSON files, and generates the markdown files as well as a _meta.json file for nextra. + +const shell = require('shelljs') +const fs = require('fs') +const path = require('path') + +// Explore a given directory recursively and return all the file paths +const walkPath = dir => { + let results = [] + const list = fs.readdirSync(dir) + list.forEach(function (file) { + const filePath = path.join(dir, file) + const stat = fs.statSync(filePath) + if (stat?.isDirectory()) { + results = results.concat(walkPath(filePath)) + } else { + results.push(filePath) + } + }) + + return results +} + +// Reduce function to deduplicate an array +const deduplicate = (acc, curr) => { + if (acc.includes(curr)) { + return acc + } + + return [...acc, curr] +} + +const supportedNetworksPath = './pages/smart-account-supported-networks' + +const generateSupportedNetworks = async () => { + const deploymentRepoUrl = 'https://github.com/safe-global/safe-deployments/' + shell.exec(`git clone ${deploymentRepoUrl} ./deployments`) + shell.rm('-rf', supportedNetworksPath) + + const fetch = await import('node-fetch') + const paths = walkPath('deployments/src/assets').map(p => + p.replace('deployments/src/assets/', '') + ) + + const allNetworks = await fetch + .default('https://chainid.network/chains.json') + .then(res => res.json()) + + const contracts = paths.map(p => { + const file = fs.readFileSync(`deployments/src/assets/${p}`, 'utf8') + const json = JSON.parse(file) + + return Object.entries(json.networkAddresses).map(([chainId, address]) => ({ + name: p.split('/')[1].split('.')[0], + version: p.split('/')[0], + address, + chainId, + chainName: allNetworks.find(n => n.chainId === parseInt(chainId))?.name, + blockExplorerUrl: allNetworks.find(n => n.chainId === parseInt(chainId)) + ?.explorers?.[0]?.url + })) + }) + + const versions = contracts + .flat() + .map(c => c.version) + .reduce(deduplicate, []) + .reverse() + + shell.mkdir(supportedNetworksPath) + + versions.forEach(version => { + const _contracts = contracts.flat().filter(c => c.version === version) + + const networks = Object.entries( + _contracts.reduce((acc, curr) => { + const { chainId, chainName } = curr + if (acc[chainId]) { + return acc + } + + return { + ...acc, + [chainId]: chainName + } + }, {}) + ) + + const content = `# ${version} + +This page lists the addresses of all the Safe contracts \`${version}\` grouped by chain. + +## Networks +${networks + .map(([chainId, network]) => { + return ` +### ${network} + +This network's chain ID is ${chainId}. + +${_contracts + .filter(c => c.chainId === chainId) + .map( + c => + `- \`${c.name}.sol\`: ${ + c.blockExplorerUrl == null || + deprecatedBlockExplorers.includes(c.blockExplorerUrl) + ? c.address + : `[${c.address}](${c.blockExplorerUrl}/address/${c.address})` + }` + ) + .join('\n')} +` + }) + .join('\n')} + + ` + fs.writeFileSync(`${supportedNetworksPath}/${version}.md`, content) + }) + + // Generate _meta.json file to order versions in descending order + fs.writeFileSync( + `${supportedNetworksPath}/_meta.json`, + JSON.stringify( + versions.reduce((acc, curr) => ({ ...acc, [curr]: curr }), {}), + null, + 2 + ) + ) + + shell.rm('-rf', './deployments') +} + +generateSupportedNetworks() + +const deprecatedBlockExplorers = [ + 'https://blockexplorer.avax.boba.network', + 'https://blockexplorer.bobabeam.boba.network', + 'https://blockexplorer.rinkeby.boba.network', + 'https://evm.explorer.canto.io', + 'https://evm-testnet.venidiumexplorer.com', + 'https://evm.venidiumexplorer.com', + 'https://explorer.autobahn.network', + 'https://explorer.eurus.network', + 'https://explorer.tst.publicmint.io', + 'https://goerli.arbiscan.io', + 'https://kovan-optimistic.etherscan.io', + 'https://rabbit.analogscan.com', + 'https://rinkeby.etherscan.io', + 'https://ropsten.etherscan.io', + 'https://stardust-explorer.metis.io', + 'https://testnet.arbiscan.io', + 'https://testnet.torusscan.com', + 'https://testnetexplorer.eurus.network' +] diff --git a/.github/scripts/prepar_production_deployment.sh b/.github/scripts/prepar_production_deployment.sh new file mode 100644 index 00000000..1d15edc5 --- /dev/null +++ b/.github/scripts/prepar_production_deployment.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -ev + +# Only: +# - Tagged commits +# - Security env variables are available. +if [ -n "$VERSION_TAG" ] && [ -n "$PROD_DEPLOYMENT_HOOK_TOKEN" ] && [ -n "$PROD_DEPLOYMENT_HOOK_URL" ] +then + curl --silent --output /dev/null --write-out "%{http_code}" -X POST \ + -F token="$PROD_DEPLOYMENT_HOOK_TOKEN" \ + -F ref=master \ + -F "variables[TRIGGER_RELEASE_COMMIT_TAG]=$VERSION_TAG" \ + $PROD_DEPLOYMENT_HOOK_URL +else + echo "[ERROR] Production deployment could not be prepared" +fi diff --git a/.github/scripts/s3_upload.sh b/.github/scripts/s3_upload.sh new file mode 100644 index 00000000..fab6b81a --- /dev/null +++ b/.github/scripts/s3_upload.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -ev + +aws s3 sync ./out $BUCKET --delete + +# Upload all HTML files again but w/o an extention so that URLs like /welcome open the right page + +cd out + +for file in $(find . -name '*.html' | sed 's|^\./||'); do + aws s3 cp ${file%} $BUCKET/${file%.*} --content-type 'text/html' +done \ No newline at end of file diff --git a/styles/Microsoft/AMPM.yml b/.github/styles/Microsoft/AMPM.yml similarity index 100% rename from styles/Microsoft/AMPM.yml rename to .github/styles/Microsoft/AMPM.yml diff --git a/styles/Microsoft/Accessibility.yml b/.github/styles/Microsoft/Accessibility.yml similarity index 100% rename from styles/Microsoft/Accessibility.yml rename to .github/styles/Microsoft/Accessibility.yml diff --git a/styles/Microsoft/Acronyms.yml b/.github/styles/Microsoft/Acronyms.yml similarity index 100% rename from styles/Microsoft/Acronyms.yml rename to .github/styles/Microsoft/Acronyms.yml diff --git a/styles/Microsoft/Adverbs.yml b/.github/styles/Microsoft/Adverbs.yml similarity index 100% rename from styles/Microsoft/Adverbs.yml rename to .github/styles/Microsoft/Adverbs.yml diff --git a/styles/Microsoft/Auto.yml b/.github/styles/Microsoft/Auto.yml similarity index 100% rename from styles/Microsoft/Auto.yml rename to .github/styles/Microsoft/Auto.yml diff --git a/styles/Microsoft/Avoid.yml b/.github/styles/Microsoft/Avoid.yml similarity index 100% rename from styles/Microsoft/Avoid.yml rename to .github/styles/Microsoft/Avoid.yml diff --git a/styles/Microsoft/ComplexWords.yml b/.github/styles/Microsoft/ComplexWords.yml similarity index 100% rename from styles/Microsoft/ComplexWords.yml rename to .github/styles/Microsoft/ComplexWords.yml diff --git a/styles/Microsoft/Contractions.yml b/.github/styles/Microsoft/Contractions.yml similarity index 100% rename from styles/Microsoft/Contractions.yml rename to .github/styles/Microsoft/Contractions.yml diff --git a/styles/Microsoft/Dashes.yml b/.github/styles/Microsoft/Dashes.yml similarity index 100% rename from styles/Microsoft/Dashes.yml rename to .github/styles/Microsoft/Dashes.yml diff --git a/styles/Microsoft/DateFormat.yml b/.github/styles/Microsoft/DateFormat.yml similarity index 100% rename from styles/Microsoft/DateFormat.yml rename to .github/styles/Microsoft/DateFormat.yml diff --git a/styles/Microsoft/DateNumbers.yml b/.github/styles/Microsoft/DateNumbers.yml similarity index 100% rename from styles/Microsoft/DateNumbers.yml rename to .github/styles/Microsoft/DateNumbers.yml diff --git a/styles/Microsoft/DateOrder.yml b/.github/styles/Microsoft/DateOrder.yml similarity index 93% rename from styles/Microsoft/DateOrder.yml rename to .github/styles/Microsoft/DateOrder.yml index 12d69ba5..bd9e0e95 100644 --- a/styles/Microsoft/DateOrder.yml +++ b/.github/styles/Microsoft/DateOrder.yml @@ -2,7 +2,7 @@ extends: existence message: "Always spell out the name of the month." link: https://docs.microsoft.com/en-us/style-guide/numbers#numbers-in-dates ignorecase: true -level: error +level: warning nonword: true tokens: - '\b\d{1,2}/\d{1,2}/(?:\d{4}|\d{2})\b' diff --git a/styles/Microsoft/Ellipses.yml b/.github/styles/Microsoft/Ellipses.yml similarity index 100% rename from styles/Microsoft/Ellipses.yml rename to .github/styles/Microsoft/Ellipses.yml diff --git a/styles/Microsoft/FirstPerson.yml b/.github/styles/Microsoft/FirstPerson.yml similarity index 100% rename from styles/Microsoft/FirstPerson.yml rename to .github/styles/Microsoft/FirstPerson.yml diff --git a/styles/Microsoft/Foreign.yml b/.github/styles/Microsoft/Foreign.yml similarity index 100% rename from styles/Microsoft/Foreign.yml rename to .github/styles/Microsoft/Foreign.yml diff --git a/styles/Microsoft/Gender.yml b/.github/styles/Microsoft/Gender.yml similarity index 100% rename from styles/Microsoft/Gender.yml rename to .github/styles/Microsoft/Gender.yml diff --git a/styles/Microsoft/GenderBias.yml b/.github/styles/Microsoft/GenderBias.yml similarity index 100% rename from styles/Microsoft/GenderBias.yml rename to .github/styles/Microsoft/GenderBias.yml diff --git a/styles/Microsoft/GeneralURL.yml b/.github/styles/Microsoft/GeneralURL.yml similarity index 100% rename from styles/Microsoft/GeneralURL.yml rename to .github/styles/Microsoft/GeneralURL.yml diff --git a/styles/Microsoft/HeadingAcronyms.yml b/.github/styles/Microsoft/HeadingAcronyms.yml similarity index 100% rename from styles/Microsoft/HeadingAcronyms.yml rename to .github/styles/Microsoft/HeadingAcronyms.yml diff --git a/styles/Microsoft/HeadingColons.yml b/.github/styles/Microsoft/HeadingColons.yml similarity index 100% rename from styles/Microsoft/HeadingColons.yml rename to .github/styles/Microsoft/HeadingColons.yml diff --git a/styles/Microsoft/HeadingPunctuation.yml b/.github/styles/Microsoft/HeadingPunctuation.yml similarity index 100% rename from styles/Microsoft/HeadingPunctuation.yml rename to .github/styles/Microsoft/HeadingPunctuation.yml diff --git a/styles/Microsoft/Headings.yml b/.github/styles/Microsoft/Headings.yml similarity index 100% rename from styles/Microsoft/Headings.yml rename to .github/styles/Microsoft/Headings.yml diff --git a/styles/Microsoft/Hyphens.yml b/.github/styles/Microsoft/Hyphens.yml similarity index 100% rename from styles/Microsoft/Hyphens.yml rename to .github/styles/Microsoft/Hyphens.yml diff --git a/styles/Microsoft/Negative.yml b/.github/styles/Microsoft/Negative.yml similarity index 100% rename from styles/Microsoft/Negative.yml rename to .github/styles/Microsoft/Negative.yml diff --git a/styles/Microsoft/Ordinal.yml b/.github/styles/Microsoft/Ordinal.yml similarity index 100% rename from styles/Microsoft/Ordinal.yml rename to .github/styles/Microsoft/Ordinal.yml diff --git a/styles/Microsoft/OxfordComma.yml b/.github/styles/Microsoft/OxfordComma.yml similarity index 100% rename from styles/Microsoft/OxfordComma.yml rename to .github/styles/Microsoft/OxfordComma.yml diff --git a/styles/Microsoft/Passive.yml b/.github/styles/Microsoft/Passive.yml similarity index 100% rename from styles/Microsoft/Passive.yml rename to .github/styles/Microsoft/Passive.yml diff --git a/styles/Microsoft/Percentages.yml b/.github/styles/Microsoft/Percentages.yml similarity index 100% rename from styles/Microsoft/Percentages.yml rename to .github/styles/Microsoft/Percentages.yml diff --git a/styles/Microsoft/Quotes.yml b/.github/styles/Microsoft/Quotes.yml similarity index 100% rename from styles/Microsoft/Quotes.yml rename to .github/styles/Microsoft/Quotes.yml diff --git a/styles/Microsoft/RangeFormat.yml b/.github/styles/Microsoft/RangeFormat.yml similarity index 100% rename from styles/Microsoft/RangeFormat.yml rename to .github/styles/Microsoft/RangeFormat.yml diff --git a/styles/Microsoft/RangeTime.yml b/.github/styles/Microsoft/RangeTime.yml similarity index 100% rename from styles/Microsoft/RangeTime.yml rename to .github/styles/Microsoft/RangeTime.yml diff --git a/styles/Microsoft/Ranges.yml b/.github/styles/Microsoft/Ranges.yml similarity index 100% rename from styles/Microsoft/Ranges.yml rename to .github/styles/Microsoft/Ranges.yml diff --git a/styles/Microsoft/Semicolon.yml b/.github/styles/Microsoft/Semicolon.yml similarity index 100% rename from styles/Microsoft/Semicolon.yml rename to .github/styles/Microsoft/Semicolon.yml diff --git a/styles/Microsoft/SentenceLength.yml b/.github/styles/Microsoft/SentenceLength.yml similarity index 100% rename from styles/Microsoft/SentenceLength.yml rename to .github/styles/Microsoft/SentenceLength.yml diff --git a/styles/Microsoft/Spacing.yml b/.github/styles/Microsoft/Spacing.yml similarity index 100% rename from styles/Microsoft/Spacing.yml rename to .github/styles/Microsoft/Spacing.yml diff --git a/styles/Microsoft/Suspended.yml b/.github/styles/Microsoft/Suspended.yml similarity index 100% rename from styles/Microsoft/Suspended.yml rename to .github/styles/Microsoft/Suspended.yml diff --git a/styles/Microsoft/Terms.yml b/.github/styles/Microsoft/Terms.yml similarity index 100% rename from styles/Microsoft/Terms.yml rename to .github/styles/Microsoft/Terms.yml diff --git a/styles/Microsoft/URLFormat.yml b/.github/styles/Microsoft/URLFormat.yml similarity index 100% rename from styles/Microsoft/URLFormat.yml rename to .github/styles/Microsoft/URLFormat.yml diff --git a/styles/Microsoft/Units.yml b/.github/styles/Microsoft/Units.yml similarity index 100% rename from styles/Microsoft/Units.yml rename to .github/styles/Microsoft/Units.yml diff --git a/styles/Microsoft/Vocab.yml b/.github/styles/Microsoft/Vocab.yml similarity index 100% rename from styles/Microsoft/Vocab.yml rename to .github/styles/Microsoft/Vocab.yml diff --git a/styles/Microsoft/We.yml b/.github/styles/Microsoft/We.yml similarity index 100% rename from styles/Microsoft/We.yml rename to .github/styles/Microsoft/We.yml diff --git a/styles/Microsoft/Wordiness.yml b/.github/styles/Microsoft/Wordiness.yml similarity index 100% rename from styles/Microsoft/Wordiness.yml rename to .github/styles/Microsoft/Wordiness.yml diff --git a/styles/Microsoft/meta.json b/.github/styles/Microsoft/meta.json similarity index 100% rename from styles/Microsoft/meta.json rename to .github/styles/Microsoft/meta.json diff --git a/.github/styles/config/vocabularies/default/accept.txt b/.github/styles/config/vocabularies/default/accept.txt new file mode 100644 index 00000000..06dfe491 --- /dev/null +++ b/.github/styles/config/vocabularies/default/accept.txt @@ -0,0 +1,218 @@ +(?i)cra +(?i)eth +(?i)http +(?i)https +(?i)sdk +(?i)zkevm +(?i)zksync +(?i)wagmi +[Aa]urora +[Aa]rbitrum +[Bb]ackend +[Bb]inance +[Bb]lockchain +[Bb]lockchains +[Bb]undlers +[Cc]elo +[Cc]omposable +[Cc]onfig +[Cc]rypto +[Cc]ryptocurrencies +[Dd]app +[Dd]apps +[Dd]elist +[Ee]ndhint +[Ee]rigon +[Ee]thereum +[Gg]asless +[Gg]elato +[Gg]oerli +[Gg]nosis +[Ll]ogics +[Mm]ainnet +[Mm]iddleware +[Mm]ultisig +[Nn]extra +[Oo]ffchain +[Oo]nboarding +[Oo]nchain +[Pp]olygon +[Pp]luggable +[Rr]eact +[Rr]eauthenticate +[Rr]elayer +[Rr]elayers +[Ss]epolia +[Tt]estnet +[Vv]alidator +[Ww]hitepaper +A1 +AA +ABI +ABIs +API +API Kit +Acala +Alfajores +Andromeda +Apothem +Arianee +Arthera +Astar +Auth Kit +Autobahn +Avax +BNB +Blocknative +Blockscout +Boba +Bobabeam +C-Chain +C1 +CORS +Canto +Callout +Cascadia +Chainlist +Chiado +Client Gateway +CloudWalk +Crab +Cronos +Crossbell +DAO +Darwinia +Devnet +EIP +EOA +EOAs +ERC +EURe +EVM +EdgeEVM +Edgeware +EtherLite +Etherscan +Eurus +Evmos +Fantom +Foesenek +Fuji +Fuse +GC +Godwoken +Gunicorn +Haqq +Holesky +Horizen +Hypra +IPFS +Infura +IoTeX +KCC +Kanazawa +Karura +Kava +Klaytn +Kovan +Kroma +LUKSO +Lightlink +Linea +Mandala +Mantle +Meld +Metadium +Metamask +Metis +Milkomeda +Monerium +Moonbase +Moonbeam +Moonriver +Mordor +Nova +OAuth +OP +Onramp +Onramp Kit +PGN +Pimlico +Polis +Protocol Kit +PublicMint +README +REI +RPC +Relay Kit +Renan +Rethereum +Rinkeby +Rollux +Rootstock +Ropsten +Safe App +Safe Apps +Safe Apps SDK +Safe Client Gateway +Safe Config Service +Safe Guards +Safe Module +Safe Modules +Safe Smart Account +Safe Transaction Service +Safe{Core} Account Abstraction SDK +Safe{Core} Protocol +Safe{DAO} +Shiden +ShimmerEVM +Shyft +Solaris +Sourcify +Souza +Sparknet +Stardust +Supernet +Syscoin +TC9 +Tanenbaum +Telos +Tenet +ThunderCore +Transaction Service +U2U +UI Kit +Ultron +Velas +Venidium +Vite +Vue +WEMIX3.0 +WSGI +WebSocket +XDC +YouTube +ZetaChain +Zoltu +Zora +address_full_match +auth_code +boolean +bytestrings +checksummed +eSpace +execTransaction +mempool +monorepo +multichain +npm +onboarding +onchain +pluggable +trace_block +trace_filter +trace_transaction +undefined +v1 +saltNonce +messageHash diff --git a/styles/Vocab/default/reject.txt b/.github/styles/config/vocabularies/default/reject.txt similarity index 50% rename from styles/Vocab/default/reject.txt rename to .github/styles/config/vocabularies/default/reject.txt index 6efbea0e..b4544e2d 100644 --- a/styles/Vocab/default/reject.txt +++ b/.github/styles/config/vocabularies/default/reject.txt @@ -1,2 +1 @@ Github -github diff --git a/styles/write-good/Cliches.yml b/.github/styles/write-good/Cliches.yml similarity index 100% rename from styles/write-good/Cliches.yml rename to .github/styles/write-good/Cliches.yml diff --git a/styles/write-good/E-Prime.yml b/.github/styles/write-good/E-Prime.yml similarity index 100% rename from styles/write-good/E-Prime.yml rename to .github/styles/write-good/E-Prime.yml diff --git a/styles/write-good/Illusions.yml b/.github/styles/write-good/Illusions.yml similarity index 100% rename from styles/write-good/Illusions.yml rename to .github/styles/write-good/Illusions.yml diff --git a/styles/write-good/Passive.yml b/.github/styles/write-good/Passive.yml similarity index 100% rename from styles/write-good/Passive.yml rename to .github/styles/write-good/Passive.yml diff --git a/styles/write-good/README.md b/.github/styles/write-good/README.md similarity index 100% rename from styles/write-good/README.md rename to .github/styles/write-good/README.md diff --git a/styles/write-good/So.yml b/.github/styles/write-good/So.yml similarity index 100% rename from styles/write-good/So.yml rename to .github/styles/write-good/So.yml diff --git a/styles/write-good/ThereIs.yml b/.github/styles/write-good/ThereIs.yml similarity index 100% rename from styles/write-good/ThereIs.yml rename to .github/styles/write-good/ThereIs.yml diff --git a/styles/write-good/TooWordy.yml b/.github/styles/write-good/TooWordy.yml similarity index 100% rename from styles/write-good/TooWordy.yml rename to .github/styles/write-good/TooWordy.yml diff --git a/styles/write-good/Weasel.yml b/.github/styles/write-good/Weasel.yml similarity index 100% rename from styles/write-good/Weasel.yml rename to .github/styles/write-good/Weasel.yml diff --git a/styles/write-good/meta.json b/.github/styles/write-good/meta.json similarity index 100% rename from styles/write-good/meta.json rename to .github/styles/write-good/meta.json diff --git a/.github/workflows/deploy-release.yml b/.github/workflows/deploy-release.yml new file mode 100644 index 00000000..a7f99e5c --- /dev/null +++ b/.github/workflows/deploy-release.yml @@ -0,0 +1,52 @@ +name: Release + +on: + release: + types: [published] + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: write-all + + name: Deploy release + + steps: + - name: Cancel previous runs + uses: styfle/cancel-workflow-action@0.8.0 + with: + access_token: ${{ github.token }} + + - uses: actions/checkout@v3 + + - uses: pnpm/action-setup@v2 + with: + version: 8 + + - name: Install dependencies + shell: bash + run: pnpm install --frozen-lockfile + + - name: Build app + shell: bash + run: pnpm build + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.AWS_ROLE }} + aws-region: ${{ secrets.AWS_REGION }} + + # Deploy the main branch to production environment + - name: Deploy to the production S3 + if: github.ref == 'refs/heads/main' + env: + BUCKET: s3://${{ secrets.AWS_STAGING_BUCKET_NAME }}/releases/${{ github.event.release.tag_name }} + run: bash ./.github/scripts/s3_upload.sh + + # Script to prepare production deployments + - run: bash ./.github/scripts/prepare_production_deployment.sh + env: + PROD_DEPLOYMENT_HOOK_TOKEN: ${{ secrets.PROD_DEPLOYMENT_HOOK_TOKEN }} + PROD_DEPLOYMENT_HOOK_URL: ${{ secrets.PROD_DEPLOYMENT_HOOK_URL }} + VERSION_TAG: ${{ github.event.release.tag_name }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..bc91f080 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,46 @@ +name: Deploy to staging + +on: + push: + branches: + - development + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: write-all + + name: Deploy to staging + + steps: + - name: Cancel previous runs + uses: styfle/cancel-workflow-action@0.8.0 + with: + access_token: ${{ github.token }} + + - uses: actions/checkout@v3 + + - uses: pnpm/action-setup@v2 + with: + version: 8 + + - name: Install dependencies + shell: bash + run: pnpm install --frozen-lockfile + + - name: Build app + shell: bash + run: pnpm build + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.AWS_ROLE }} + aws-region: ${{ secrets.AWS_REGION }} + + # Deploy the development branch to staging + - name: Deploy to the staging S3 + if: github.ref == 'refs/heads/development' + env: + BUCKET: s3://${{ secrets.AWS_STAGING_BUCKET_NAME }}/current + run: bash ./.github/scripts/s3_upload.sh diff --git a/.github/workflows/generate-supported-networks.yml b/.github/workflows/generate-supported-networks.yml new file mode 100644 index 00000000..d4fcb9a8 --- /dev/null +++ b/.github/workflows/generate-supported-networks.yml @@ -0,0 +1,25 @@ +name: Weekly update for supported networks + +on: + schedule: + - cron: "0 9 * * 1" + workflow_dispatch: + +jobs: + generate-supported-networks: + runs-on: ubuntu-latest + steps: + - name: Use Node.js 20.x + uses: actions/setup-node@v2 + with: + node-version: '20.x' + - uses: pnpm/action-setup@v2 + with: + version: 8 + - name: Install dependencies + run: pnpm install + - name: Generate supported networks + run: pnpm generate-supported-networks + - uses: actions/checkout@v4 + - name: Create Pull Request + uses: peter-evans/create-pull-request@v5 diff --git a/.github/workflows/link-check.yml b/.github/workflows/link-check.yml new file mode 100644 index 00000000..09e3efb9 --- /dev/null +++ b/.github/workflows/link-check.yml @@ -0,0 +1,19 @@ +name: Link check for PRs + +on: + pull_request: + branches: + - main + +jobs: + markdown-link-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: gaurav-nelson/github-action-markdown-link-check@v1 + with: + use-quiet-mode: 'yes' + use-verbose-mode: 'yes' + folder-path: './' + base-branch: 'main' + check-modified-files-only: 'yes' diff --git a/.github/workflows/report-readability.yml b/.github/workflows/report-readability.yml new file mode 100644 index 00000000..32d84a68 --- /dev/null +++ b/.github/workflows/report-readability.yml @@ -0,0 +1,17 @@ +name: Report readability + +on: pull_request + +jobs: + report-readability: + name: Report readability + runs-on: ubuntu-latest + steps: + - name: Checkout repo with history + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: Rebilly/lexi@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + glob: '**/*.md' diff --git a/.github/workflows/style-check.yml b/.github/workflows/style-check.yml new file mode 100644 index 00000000..6541d5f5 --- /dev/null +++ b/.github/workflows/style-check.yml @@ -0,0 +1,17 @@ +name: Style check +on: pull_request + +jobs: + vale-docs: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Vale Linter + uses: errata-ai/vale-action@reviewdog + with: + vale_flags: "--glob=*.md --minAlertLevel=error" + fail_on_error: true diff --git a/.github/workflows/weekly-link-check.yml b/.github/workflows/weekly-link-check.yml new file mode 100644 index 00000000..e3fbb8c7 --- /dev/null +++ b/.github/workflows/weekly-link-check.yml @@ -0,0 +1,28 @@ +name: Weekly link check + +on: + schedule: + - cron: "0 9 * * 1" + workflow_dispatch: + +jobs: + markdown-link-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: gaurav-nelson/github-action-markdown-link-check@v1 + with: + use-quiet-mode: 'yes' + use-verbose-mode: 'yes' + folder-path: './' + base-branch: 'main' + - name: Create Issue + if: ${{ failure() }} + uses: dacbd/create-issue-action@main + with: + token: ${{ github.token }} + title: Link Checker Report + labels: report, automated issue + body: | + Broken links found: [Failed Run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) + Workflow: `${{ github.workflow }}` diff --git a/.gitignore b/.gitignore index e43b0f98..f0b4c3c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,8 @@ .DS_Store +node_modules/ +out/ +dist/ +.next +.eslintcache +.env +tsconfig.tsbuildinfo \ No newline at end of file diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 00000000..42b62e5e --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npm run prepush diff --git a/.vale.ini b/.vale.ini index 2bb3c5cf..73318776 100644 --- a/.vale.ini +++ b/.vale.ini @@ -1,8 +1,10 @@ -StylesPath = styles +StylesPath = .github/styles MinAlertLevel = suggestion Vocab = default Packages = Microsoft, write-good -[*] -BasedOnStyles = Vale, Microsoft, write-good +[formats] +mdx = md +[*.{md,mdx}] +BasedOnStyles = Vale, Microsoft, write-good diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..db692225 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "numso.prettier-standard-vscode", + "chrischinchilla.vale-vscode", + "unifiedjs.vscode-mdx", + "znck.grammarly" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..1ae0f5b6 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "grammarly.files.include": ["**/*.md", "**/*.mdx"], + "vale.valeCLI.config": "${workspaceFolder}/.vale.ini" +} \ No newline at end of file diff --git a/LICENSE b/LICENSE.md similarity index 100% rename from LICENSE rename to LICENSE.md diff --git a/README.md b/README.md index 277382cd..38aa412c 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,80 @@ ---- -description: Everything you need to know to start building on Safe. ---- +# Safe Documentation -# Safe +[![License](https://img.shields.io/github/license/safe-global/safe-docs)](https://github.com/safe-global/safe-docs/blob/main/LICENSE.md) +![GitHub package.json version (branch)](https://img.shields.io/github/package-json/v/safe-global/safe-docs) -[Safe](https://safe.global) brings digital ownership of accounts to everyone by building universal and open contract standards for the custody of digital assets, data, and identity. +This repository hosts [Safe](https://safe.global) documentation. -Safe is at the forefront of modular smart account infrastructure, paving the way for developers to create various applications and wallets. +The documentation is built with [Nextra](https://nextra.site) and is live at [docs.safe.global](https://docs.safe.global). -Safe{Core} consists of three components: +## Installation -
+Install the dependencies using [pnpm](https://pnpm.io): -## Safe{Core} Protocol +``` +pnpm install +``` -The [Safe{Core} Protocol](safe-core-protocol/README.md) is an open, modular framework to make smart accounts secure, portable, and composable. +## Development -This section in the documentation has information and relevant links about the Safe{Core} Protocol and the different elements involved. +Git hooks are set up to run tests and linting checks before every `git push`. These hooks can be executed locally by running the following command: -## Safe{Core} AA SDK +``` +pnpm prepush +``` -The [Safe{Core} Account Abstraction SDK](safe-core-sdk/README.md) is a set of developer kits that help integrate Safe with different external service providers. The SDK offers developers the ability to abstract the complexity that comes with operating a smart contract account. +All links in the documentation are checked for validity on every pull request. These checks can be executed locally by running the following command: -This section in the documentation has relevant information and tutorials on the SDK kits and how to integrate them. +``` +pnpm linkcheck +``` -## Safe{Core} API +## Execution -The [Safe{Core} API](safe-core-api/available-services.md) makes reference to all the Safe infrastructure needed to power interfaces with all Safe account related information. This includes the Safe Transaction Service, Safe Events Service, etc. +The project can be run with a server that's executed in development and production mode. -This section in the documentation has information related to the services architecture. +### Development mode -## Reference +Run the server in development mode using the following command: -The [Reference](reference/safe-core-sdk/auth-kit/README.md) section in this documentation has technical details describing the different tools available at Safe in detail. +``` +pnpm dev +``` + +### Production mode + +Build the project: + +``` +pnpm build +``` + +Run the server in production mode using the following command: + +``` +pnpm start +``` + +## Testing + +Create an environment file in the root of the project and copy the content from the `.env.example` file using the following command: + +``` +cp .env.example .env +``` + +Remember to update the environment variables once the `.env` file is created. + +Run the tests using the following command: + +``` +pnpm test +``` + +## License + +This project is licensed under the [MIT License](./LICENSE.md). + +## Contributing + +Contributions are more than welcome! Please open an issue or create a pull request by following our [contributions guidelines](./CONTRIBUTING.md). diff --git a/SUMMARY.md b/SUMMARY.md deleted file mode 100644 index 06f61f72..00000000 --- a/SUMMARY.md +++ /dev/null @@ -1,86 +0,0 @@ -# Table of contents - - - -## Getting started - -* [What is Safe?](README.md) - -## Safe Smart Account - -* [Overview](safe-smart-account/README.md) - -* [Safe Modules](safe-smart-account/modules.md) - -* [Safe Guards](safe-smart-account/guards.md) - -* [Security](safe-smart-account/security/README.md) - * [Bug Bounty](safe-smart-account/security/bug-bounty-program.md) - * [Audits](safe-smart-account/security/security-audits.md) - -* [Signatures](safe-smart-account/signatures/README.md) - * [EIP-1271 Off-Chain signatures](safe-smart-account/signatures/eip-1271.md) - -## Safe{Core} Protocol - -* [Overview](safe-core-protocol/README.md) - -## Safe{Core} AA SDK - -* [Overview](safe-core-sdk/README.md) - -* [Auth Kit](safe-core-sdk/auth-kit/README.md) - * [Web3Auth](safe-core-sdk/auth-kit/web3auth.md) - -* [Protocol Kit](safe-core-sdk/protocol-kit/README.md) - -* [OnRamp Kit](safe-core-sdk/onramp-kit/README.md) - * [Stripe](safe-core-sdk/onramp-kit/stripe.md) - * [Monerium](safe-core-sdk/onramp-kit/monerium.md) - -* [Relay Kit](safe-core-sdk/relay-kit/README.md) - * [Gelato Relay](safe-core-sdk/relay-kit/gelato.md) - -* [API Kit](safe-core-sdk/api-kit/README.md) - -* [Safe Apps SDK](safe-core-sdk/safe-apps/README.md) - * [Overview](safe-core-sdk/safe-apps/overview.md) - * [Get started](safe-core-sdk/safe-apps/get-started.md) - * [Release](safe-core-sdk/safe-apps/release.md) - * [Safe App example](safe-core-sdk/safe-apps/example-safe-app.md) - -## Safe{Core} API - -* [Available Services](safe-core-api/available-services.md) - -* [Service Architecture](safe-core-api/service-architecture.md) - -* [RPC Requirements](safe-core-api/rpc-requirements.md) - -## Reference - -* [Auth Kit](reference/safe-core-sdk/auth-kit/README.md) - * [AuthKitBasePack](reference/safe-core-sdk/auth-kit/AuthKitBasePack.md) - * [Web3AuthModalPack](reference/safe-core-sdk/auth-kit/Web3AuthModalPack.md) - -* [Protocol Kit](reference/safe-core-sdk/protocol-kit/README.md) - -* [OnRamp Kit](reference/safe-core-sdk/onramp-kit/README.md) - * [OnRampKitBasePack](reference/safe-core-sdk/onramp-kit/OnRampKitBasePack.md) - * [StripePack](reference/safe-core-sdk/onramp-kit/StripePack.md) - * [MoneriumPack](reference/safe-core-sdk/onramp-kit/MoneriumPack.md) - -* [API Kit](reference/safe-core-sdk/api-kit/README.md) - -*** - -* [Contact us](contact-us.md) diff --git a/.gitbook/assets/add-custom-app.png b/assets/add-custom-app.png similarity index 100% rename from .gitbook/assets/add-custom-app.png rename to assets/add-custom-app.png diff --git a/.gitbook/assets/core-brands.png b/assets/core-brands.png similarity index 100% rename from .gitbook/assets/core-brands.png rename to assets/core-brands.png diff --git a/.gitbook/assets/diagram-safe-apps.png b/assets/diagram-safe-apps.png similarity index 100% rename from .gitbook/assets/diagram-safe-apps.png rename to assets/diagram-safe-apps.png diff --git a/.gitbook/assets/diagram-safe-core-protocol.png b/assets/diagram-safe-core-protocol.png similarity index 100% rename from .gitbook/assets/diagram-safe-core-protocol.png rename to assets/diagram-safe-core-protocol.png diff --git a/.gitbook/assets/diagram-safe-core-sdk.png b/assets/diagram-safe-core-sdk.png similarity index 100% rename from .gitbook/assets/diagram-safe-core-sdk.png rename to assets/diagram-safe-core-sdk.png diff --git a/.gitbook/assets/diagram-safe-guards.png b/assets/diagram-safe-guards.png similarity index 100% rename from .gitbook/assets/diagram-safe-guards.png rename to assets/diagram-safe-guards.png diff --git a/.gitbook/assets/diagram-safe-modules.png b/assets/diagram-safe-modules.png similarity index 100% rename from .gitbook/assets/diagram-safe-modules.png rename to assets/diagram-safe-modules.png diff --git a/.gitbook/assets/diagram-services-requests.png b/assets/diagram-services-requests.png similarity index 100% rename from .gitbook/assets/diagram-services-requests.png rename to assets/diagram-services-requests.png diff --git a/.gitbook/assets/diagram-services.png b/assets/diagram-services.png similarity index 100% rename from .gitbook/assets/diagram-services.png rename to assets/diagram-services.png diff --git a/.gitbook/assets/side-bar-menu.png b/assets/side-bar-menu.png similarity index 100% rename from .gitbook/assets/side-bar-menu.png rename to assets/side-bar-menu.png diff --git a/assets/svg/arrow-back.svg b/assets/svg/arrow-back.svg new file mode 100644 index 00000000..f78d5a67 --- /dev/null +++ b/assets/svg/arrow-back.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/chevron-down.svg b/assets/svg/chevron-down.svg new file mode 100644 index 00000000..c2115090 --- /dev/null +++ b/assets/svg/chevron-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/close.svg b/assets/svg/close.svg new file mode 100644 index 00000000..de90750a --- /dev/null +++ b/assets/svg/close.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/cross.svg b/assets/svg/cross.svg new file mode 100644 index 00000000..8bc89962 --- /dev/null +++ b/assets/svg/cross.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/discord-icon.svg b/assets/svg/discord-icon.svg new file mode 100644 index 00000000..d78504a8 --- /dev/null +++ b/assets/svg/discord-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/discourse-icon.svg b/assets/svg/discourse-icon.svg new file mode 100644 index 00000000..d4c0484a --- /dev/null +++ b/assets/svg/discourse-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/feedback-bad.svg b/assets/svg/feedback-bad.svg new file mode 100644 index 00000000..17c6dcdc --- /dev/null +++ b/assets/svg/feedback-bad.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/svg/feedback-good.svg b/assets/svg/feedback-good.svg new file mode 100644 index 00000000..880070d6 --- /dev/null +++ b/assets/svg/feedback-good.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/svg/feedback-ok.svg b/assets/svg/feedback-ok.svg new file mode 100644 index 00000000..a26f41c1 --- /dev/null +++ b/assets/svg/feedback-ok.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/svg/filter.svg b/assets/svg/filter.svg new file mode 100644 index 00000000..281a4317 --- /dev/null +++ b/assets/svg/filter.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/github-icon.svg b/assets/svg/github-icon.svg new file mode 100644 index 00000000..b01d2ca4 --- /dev/null +++ b/assets/svg/github-icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/svg/mirror-icon.svg b/assets/svg/mirror-icon.svg new file mode 100644 index 00000000..be495ed2 --- /dev/null +++ b/assets/svg/mirror-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/safe-logo-white.svg b/assets/svg/safe-logo-white.svg new file mode 100644 index 00000000..f60bac59 --- /dev/null +++ b/assets/svg/safe-logo-white.svg @@ -0,0 +1,18 @@ + + + + + + + + + \ No newline at end of file diff --git a/assets/svg/safe-logo.svg b/assets/svg/safe-logo.svg new file mode 100644 index 00000000..c06c469d --- /dev/null +++ b/assets/svg/safe-logo.svg @@ -0,0 +1,18 @@ + + + + + + + + + \ No newline at end of file diff --git a/assets/svg/search.svg b/assets/svg/search.svg new file mode 100644 index 00000000..81e4c9a2 --- /dev/null +++ b/assets/svg/search.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/x-icon.svg b/assets/svg/x-icon.svg new file mode 100644 index 00000000..b5700350 --- /dev/null +++ b/assets/svg/x-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/youtube-icon.svg b/assets/svg/youtube-icon.svg new file mode 100644 index 00000000..3a1dc4bf --- /dev/null +++ b/assets/svg/youtube-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/.gitbook/assets/transaction_service_architecture.png b/assets/transaction_service_architecture.png similarity index 100% rename from .gitbook/assets/transaction_service_architecture.png rename to assets/transaction_service_architecture.png diff --git a/safe-smart-account/security/README.md b/components/Feedback/Feedback.module.css similarity index 100% rename from safe-smart-account/security/README.md rename to components/Feedback/Feedback.module.css diff --git a/components/Feedback/index.tsx b/components/Feedback/index.tsx new file mode 100644 index 00000000..d06287ec --- /dev/null +++ b/components/Feedback/index.tsx @@ -0,0 +1,77 @@ +// import { signal } from '@preact/signals' +import Typography from '@mui/material/Typography' +import Button from '@mui/material/Button' +import Tooltip from '@mui/material/Tooltip' +import Grid from '@mui/material/Grid' + +import FeedbackBad from '../../assets/svg/feedback-bad.svg' +import FeedbackOk from '../../assets/svg/feedback-ok.svg' +import FeedbackGood from '../../assets/svg/feedback-good.svg' + +// const feedbackState = signal(JSON.parse(window.localStorage.getItem('feedback') ?? '{}')) + +const Feedback: React.FC = () => ( + + + + Was this page helpful? + + + {feedbackButtons.map(button => ( + + ))} + + + +) + +export default Feedback + +const feedbackButtons = [ + { + title: 'Poor content', + background: '#ff918f', + Icon: + }, + { + title: 'OK content', + background: '#b95e04', + Icon: + }, + { + title: 'Good content', + background: '#008847', + Icon: + } +] + +const FeedbackButton: React.FC<{ + title: string + background: string + Icon: JSX.Element +}> = ({ title, background, Icon }) => ( + + + +) diff --git a/components/Footer/Footer.module.css b/components/Footer/Footer.module.css new file mode 100644 index 00000000..94ecf948 --- /dev/null +++ b/components/Footer/Footer.module.css @@ -0,0 +1,67 @@ +.wrapper { + margin-top: 80px; + } + + .list { + padding: 0; + list-style: none; + display: flex; + flex-direction: column; + gap: 16px; + } + + .listItem { + font-size: 16px; + line-height: 20px; + color: var(--mui-palette-primary-light); + } + + .listItem a, + .subListItem a { + display: block; + } + + .listItem:hover a, + .subListItem:hover a { + color: white; + } + + .subList { + list-style: none; + padding: 0; + display: flex; + flex-wrap: wrap; + gap: 16px; + } + + .subListItem { + font-size: 16px; + line-height: 24px; + color: var(--mui-palette-primary-light); + } + + .socials { + display: flex; + flex-wrap: wrap; + gap: 24px; + } + + .socials svg { + width: 28px; + height: 28px; + } + + .logo { + width: 150px; + height: auto; + } + + @media (min-width: 600px) { + .subList { + gap: 32px; + } + + .subListItem { + line-height: 56px; + } + } \ No newline at end of file diff --git a/components/Footer/index.tsx b/components/Footer/index.tsx new file mode 100644 index 00000000..57217a41 --- /dev/null +++ b/components/Footer/index.tsx @@ -0,0 +1,293 @@ +import { ButtonBase, Container, Divider, Grid, Typography } from '@mui/material' +import Link from 'next/link' +import type { ComponentType, SyntheticEvent } from 'react' +import DiscordIcon from '../../assets/svg/discord-icon.svg' +import DiscourseIcon from '../../assets/svg/discourse-icon.svg' +import GithubIcon from '../../assets/svg/github-icon.svg' +import MirrorIcon from '../../assets/svg/mirror-icon.svg' +import Logo from '../../assets/svg/safe-logo-white.svg' +import XIcon from '../../assets/svg/x-icon.svg' +import YoutubeIcon from '../../assets/svg/youtube-icon.svg' +import css from './Footer.module.css' + +const SAFE_LINK = 'https://safe.global' + +// Safe +const CORE_LINK = 'https://core.safe.global' + +// Community +const GOVERNANCE_LINK = 'https://safe.global/governance' // Do not use: https://governance.safe.global +const ECOSYSTEM_LINK = 'https://ecosystem.safe.global' +const GRANTS_LINK = 'https://grants.safe.global' +const SAFECON_LINK = 'https://conf.safe.global' +const DUNE_LINK = 'https://dune.com/safe' + +// Resources +const HELP_LINK = 'https://help.safe.global' +const CAREERS_LINK = 'https://safe.global/careers' +const BRAND_LINK = 'https://press.safe.global' +const STACKEXCHANGE_LINK = 'https://ethereum.stackexchange.com/questions/tagged/safe-core' +const EXPERIMENTAL_LINK = 'https://github.com/5afe' + +// Sub-Footer +const TERMS_LINK = 'https://safe.global/terms' +const PRIVACY_LINK = 'https://safe.global/privacy' +const LICENSES_LINK = 'https://app.safe.global/licenses' +const COOKIE_LINK = 'https://safe.global/cookie' +const COOKIE_PREFERENCES_LINK = '#cookies' +const IMPRINT_LINK = 'https://safe.global/imprint' + +// Socials +const X_LINK = 'https://x.com/safe' +const FORUM_LINK = 'https://forum.safe.global' +const DISCORD_LINK = 'https://chat.safe.global' +const YOUTUBE_LINK = 'https://www.youtube.com/@safeglobal' +const MIRROR_LINK = 'https://safe.mirror.xyz' +const GITHUB_LINK = 'https://github.com/safe-global' + +interface FooterLink { + label: string + href: string + target: string + rel: string +} + +const safeItems: FooterLink[] = [ + { + label: 'Safe{Core}', + href: CORE_LINK, + target: '_blank', + rel: 'noreferrer' + } +] + +const communityItems: FooterLink[] = [ + { + label: 'Governance', + href: GOVERNANCE_LINK, + target: '_blank', + rel: 'noreferrer' + }, + { + label: 'Ecosystem', + href: ECOSYSTEM_LINK, + target: '_blank', + rel: 'noreferrer' + }, + { + label: 'Grants', + href: GRANTS_LINK, + target: '_blank', + rel: 'noreferrer' + }, + { + label: 'Safe{Con}', + href: SAFECON_LINK, + target: '_blank', + rel: 'noreferrer' + }, + { + label: 'Safe Analytics', + href: DUNE_LINK, + target: '_blank', + rel: 'noreferrer' + } +] + +const resourcesItems: FooterLink[] = [ + { + label: 'Help Center', + href: HELP_LINK, + target: '_blank', + rel: 'noreferrer' + }, + { + label: 'Careers', + href: CAREERS_LINK, + target: '_blank', + rel: 'noreferrer' + }, + { + label: 'Brand Kit', + href: BRAND_LINK, + target: '_blank', + rel: 'noreferrer' + }, + { + label: 'Developer Support', + href: STACKEXCHANGE_LINK, + target: '_blank', + rel: 'noreferrer' + }, + { + label: 'Experimental Tools', + href: EXPERIMENTAL_LINK, + target: '_blank', + rel: 'noreferrer' + } + +] + +const subFooterItems: FooterLink[] = [ + { + label: 'Terms', + href: TERMS_LINK, + target: '_blank', + rel: 'noreferrer' + }, + { + label: 'Privacy', + href: PRIVACY_LINK, + target: '_blank', + rel: 'noreferrer' + }, + { + label: 'Licenses', + href: LICENSES_LINK, + target: '_blank', + rel: 'noreferrer' + }, + { + label: 'Cookie Policy', + href: COOKIE_LINK, + target: '_blank', + rel: 'noreferrer' + }, + { + label: 'Preferences', + href: COOKIE_PREFERENCES_LINK, + target: '_blank', + rel: 'noreferrer' + }, + { + label: 'Imprint', + href: IMPRINT_LINK, + target: '_blank', + rel: 'noreferrer' + } +] + +const LinksColumn: React.FC<{ title: string, items: FooterLink[] }> = ({ + title, + items +}) => ( + + + {title} + +
    + {items.map(item => ( +
  • + + {item.label} + +
  • + ))} +
+
+) + +const Socials: React.FC = () => ( + +
+ {createFooterButton('X page', X_LINK, XIcon as React.FC)} + {createFooterButton( + 'Discourse forum', + FORUM_LINK, + DiscourseIcon as React.FC + )} + {createFooterButton( + 'Discord server', + DISCORD_LINK, + DiscordIcon as React.FC + )} + {createFooterButton( + 'Youtube channel', + YOUTUBE_LINK, + YoutubeIcon as React.FC + )} + {createFooterButton('Mirror blog', MIRROR_LINK, MirrorIcon as React.FC)} + {createFooterButton( + 'Github organization', + GITHUB_LINK, + GithubIcon as React.FC + )} +
+
+) + +const SubFooter: React.FC = () => { + // const { openBanner } = useCookieBannerContext() + + const showBanner = (e: SyntheticEvent): void => { + // Prevent opening the hash link + e.preventDefault() + // openBanner() + } + + return ( + + +
    + {subFooterItems.map(item => { + const isCookiePreferencesLink = false // item.href === COOKIE_PREFERENCES_LINK + return ( +
  • + + {item.label} + +
  • + ) + })} +
+
+ + + ©{new Date().getFullYear()} Safe Ecosystem Foundation + + +
+ ) +} + +const createFooterButton = ( + label: string, + href: string, + IconComponent: ComponentType +): JSX.Element => { + const buttonBaseAttributes = { + disableRipple: true, + target: '_blank', + rel: 'noreferrer' + } + return ( + + + + ) +} + +const Footer: React.FC = () => ( + + + + + + + + + + + + + + + +) + +export default Footer diff --git a/components/ResourceHub/Card.tsx b/components/ResourceHub/Card.tsx new file mode 100644 index 00000000..a8f024a3 --- /dev/null +++ b/components/ResourceHub/Card.tsx @@ -0,0 +1,80 @@ +import { Typography, Chip, Box } from '@mui/material' + +import css from './styles.module.css' +// import { ECOSYSTEM_DATA_URL } from '@/config/constants' +import { type KnowledgeResource } from './Resources' +import YouTubeEmbed from '../YouTube' +// import clsx from 'clsx' + +export const ProjectCard = (resource: KnowledgeResource): JSX.Element => { + const CardContent = ( +
+ {resource.type === 'Video' && ( + + )} + + + {resource.name} + + + {resource.type === 'Blog post' && ( + + {resource.abstract} + + )} + +
+ + {resource.tags.map(tag => ( + + ))} +
+
+ ) + + return ( + + + {CardContent} + + + ) +} diff --git a/components/ResourceHub/Resources.tsx b/components/ResourceHub/Resources.tsx new file mode 100644 index 00000000..c831ce75 --- /dev/null +++ b/components/ResourceHub/Resources.tsx @@ -0,0 +1,497 @@ +import { + Divider, + Grid, + Typography, + Chip, + TextField, + InputAdornment, + Button, + Dialog, + AppBar, + Toolbar, + IconButton, + Box, + Link, + Container +} from '@mui/material' +import type { Dispatch, ReactElement, SetStateAction } from 'react' +import type { GridProps } from '@mui/material' +import { Fragment, useMemo, useState } from 'react' +import { useRouter } from 'next/router' +import NextLink from 'next/link' +import type { NextRouter } from 'next/router' + +import SearchIcon from '../../assets/svg/search.svg' +import CrossIcon from '../../assets/svg/cross.svg' +import CloseIcon from '../../assets/svg/close.svg' +import FilterIcon from '../../assets/svg/filter.svg' +import ArrowBackIcon from '../../assets/svg/arrow-back.svg' +import { useResourceSearch } from './useResourceSearch' +import { SidebarAccordion } from './SidebarAccordion' +import { ProjectCard } from './Card' +import companyResources from './company-resources.json' +import communityResources from './community-resources.json' +import css from './styles.module.css' + +const resources = [ + ...communityResources.map(r => ({ ...r, origin: 'Community' })), + ...companyResources.map(r => ({ ...r, origin: 'Safe Team' })) +] + +export interface KnowledgeResource { + url: string + name: string + type: string + origin: string + abstract?: string + tags: string[] +} + +const getUniqueStrings = (entries: string[]): string[] => { + const uniqueEntries = new Set(entries) + return Array.from(uniqueEntries).sort() +} + +const isMatch = (all: string[], selected: string[]): boolean => { + // No selection means no filter applied + if (selected.length === 0) { + return true + } + + return selected.some(item => { + return all.includes(item) + }) +} + +const isStrictMatch = (all: string[], selected: string[]): boolean => { + // No selection means no filter applied + if (selected.length === 0) { + return true + } + + return selected.every(item => { + return all.includes(item) + }) +} + +export const _getFilteredResources = ({ + resources, + selectedTypes, + selectedSources, + selectedTags +}: { + resources: KnowledgeResource[] + selectedTypes: string[] + selectedSources: string[] + selectedTags: string[] +}): KnowledgeResource[] => + resources.filter( + resource => + isMatch([resource.type], selectedTypes) && + isMatch([resource.origin], selectedSources) && + isStrictMatch(resource.tags, selectedTags) + ) + +const SpecificTypeFilter = ({ + category, + onClick +}: { + category: KnowledgeResource['type'] + onClick: (category: KnowledgeResource['type']) => void +}): JSX.Element => { + return ( + + ) +} + +const EMPTY_FILTER: string[] = [] + +const GRID_SPACING: GridProps['spacing'] = { + xs: 2, + md: '30px' +} + +const PAGE_LENGTH = 12 + +const PAGE_QUERY_PARAM = 'page' + +const getPage = (query: NextRouter['query']): number => { + const page = Array.isArray(query[PAGE_QUERY_PARAM]) + ? query[PAGE_QUERY_PARAM][0] + : query[PAGE_QUERY_PARAM] + + return parseInt(page ?? '1') +} + +export const Resources = (): ReactElement => { + const [query, setQuery] = useState('') + const [isFilterDrawerOpen, setIsFilterDrawerOpen] = useState(false) + + const [selectedTypes, setSelectedTypes] = useState(EMPTY_FILTER) + const [selectedSources, setSelectedSources] = useState(EMPTY_FILTER) + const [selectedTags, setSelectedTags] = useState(EMPTY_FILTER) + + const router = useRouter() + const page = getPage(router.query) + + // Types + const allTypes = resources.flatMap(resource => resource.type) + const uniqueTypes = getUniqueStrings(allTypes) + + // Sources + const allSources = resources.flatMap(resource => resource.origin) + const uniqueSources = getUniqueStrings(allSources) + + // Tags + const allTags = resources.flatMap(resource => resource.tags) + const uniqueTags = getUniqueStrings(allTags) + + const onResetSearch = (): void => { + setQuery('') + } + + const onResetFilters = (): void => { + setSelectedTypes(EMPTY_FILTER) + setSelectedSources(EMPTY_FILTER) + setSelectedTags(EMPTY_FILTER) + } + + const onSelect = + (setState: Dispatch>) => + (property: string, checked: boolean) => { + setState(prev => { + if (checked) { + return prev.concat(property) + } else { + return prev.filter(item => item !== property) + } + }) + } + + const onSelectType = onSelect(setSelectedTypes) + const onSelectSource = onSelect(setSelectedSources) + const onSelectTag = onSelect(setSelectedTags) + + const toggleSpecificTag = (tag: string): void => { + onSelectTag(tag, !selectedTags.includes(tag)) + } + + const noFilters = useMemo(() => { + return ( + selectedTypes.length === 0 && + selectedSources.length === 0 && + selectedTags.length === 0 + ) + }, [selectedTypes, selectedSources, selectedTags]) + + // Type filtered results + const filteredResources = useMemo(() => { + if (noFilters) { + return resources + } + + return _getFilteredResources({ + resources, + selectedTypes, + selectedSources, + selectedTags + }) + }, [noFilters, selectedTypes, selectedSources, selectedTags]) + + // Search results + const searchResults = useResourceSearch(filteredResources, query) + + // Paginated filtered/search-based results + const visibleResults = searchResults.slice(0, PAGE_LENGTH * page) + + const shouldShowMoreButton = visibleResults.length < searchResults.length + + const sidebar = ( + <> + + + + + + + ) + + return ( + + + + + Resource Hub + + + + + ), + endAdornment: + query.length !== 0 ? ( + + + + + + ) : undefined + }} + value={query} + sx={{ border: 'none', width: '80%' }} + onChange={e => { + setQuery(e.target.value) + }} + fullWidth + /> + + + Example: + {' '} + {uniqueTags.slice(0, 3).map((primaryTag, idx, { length }) => { + return ( + + + {idx !== length - 1 && ', '} + + ) + })} + + + + + + + + {searchResults.length}{' '} + + result{searchResults.length === 1 ? '' : 's'} + + + {!noFilters && ( + + Reset all + + )} + + + + + {selectedTypes.map(category => ( + { + onSelectType(category, false) + }} + sx={{ + borderRadius: '4px', + height: '23px', + fontSize: '14px', + cursor: 'pointer' + }} + deleteIcon={} + /> + ))} + + {selectedSources.map(integration => ( + { + onSelectSource(integration, false) + }} + deleteIcon={} + sx={{ + borderRadius: '4px', + height: '23px', + fontSize: '14px', + cursor: 'pointer' + }} + /> + ))} + + {selectedTags.map(tag => ( + { + onSelectTag(tag, false) + }} + deleteIcon={} + sx={{ + borderRadius: '4px', + height: '23px', + fontSize: '14px', + cursor: 'pointer' + }} + /> + ))} + + + + {sidebar} + + + + {visibleResults.length > 0 ? ( + + {visibleResults.map((resource, idx) => ( + + + + ))} + {shouldShowMoreButton && ( + + + + )} + + + Listings are not endorsements and are only for informational + purposes. + + + + ) : ( + + + + No results found for {query ?? 'selected filters'} + + + Try searching something else + + + )} + + + + + + + { + setIsFilterDrawerOpen(false) + }} + className={css.backButton} + disableRipple + > + + + + Filter + + + +
+ {sidebar} + + + + +
+
+
+ ) +} + +export default Resources diff --git a/components/ResourceHub/SidebarAccordion.tsx b/components/ResourceHub/SidebarAccordion.tsx new file mode 100644 index 00000000..7946aa57 --- /dev/null +++ b/components/ResourceHub/SidebarAccordion.tsx @@ -0,0 +1,62 @@ +import { + Typography, + Accordion, + AccordionSummary, + AccordionDetails, + Checkbox, + List, + ListItem, + FormControlLabel +} from '@mui/material' + +import ChevronDownIcon from '../../assets/svg/chevron-down.svg' + +import css from './styles.module.css' +import React from 'react' + +export const SidebarAccordion: React.FC<{ + title: string + items: string[] + selectedItems: string[] + onChange: (item: string, checked: boolean) => void +}> = ({ title, items, selectedItems, onChange }) => { + return ( + + }> + + {title} + + + + + + {items.map(item => ( + + { + onChange(item, checked) + }} + checked={selectedItems.includes(item)} + edge='end' + /> + } + componentsProps={{ typography: { variant: 'body2' } }} + className={css.label} + /> + + ))} + + + + ) +} diff --git a/components/ResourceHub/community-resources.json b/components/ResourceHub/community-resources.json new file mode 100644 index 00000000..12838a6a --- /dev/null +++ b/components/ResourceHub/community-resources.json @@ -0,0 +1,151 @@ +[ + { + "name": "Gnosis Safe 🛠 Starting with the Safe Core SDK", + "url": "https://www.youtube.com/watch?v=t2LzhAFBxkI", + "type": "Video", + "tags": [ + "Introduction", + "Safe Core SDK" + ] + }, + { + "name": "How To Deploy a Smart Contract From a Web3 Multi-Sig Wallet", + "url": "https://www.youtube.com/watch?v=zn6omKzm3BI", + "type": "Video", + "tags": [ + "Introduction", + "Protocol" + ] + }, + { + "name": "Safe transaction service installation guide", + "url": "https://www.youtube.com/watch?v=FUytj_xStDI", + "type": "Video", + "tags": [ + "Introduction", + "Safe Transaction Service" + ] + }, + { + "name": "How do you add a custom network to the Safe UI?", + "url": "https://www.youtube.com/watch?v=E3v6p87bsYg", + "type": "Video", + "tags": [ + "Introduction", + "Safe Wallet" + ] + }, + { + "name": "Safe Wallet Tutorial | Multisig Wallet for DeFi", + "url": "https://www.youtube.com/watch?v=GHyxe32Z814", + "type": "Video", + "tags": [ + "Tutorial", + "Safe Wallet" + ] + }, + { + "name": "Safe 🛠 Build a Group Wallet to Buy things with Frens using Safe {Core} Account Abstraction SDK", + "url": "https://www.youtube.com/watch?v=czGf5YgWs7M", + "type": "Video", + "tags": [ + "Tutorial", + "Safe Core SDK" + ] + }, + { + "name": "How to Create a MultiSig Wallet Using Gnosis SAFE", + "url": "https://www.youtube.com/watch?v=JoFYldw6hVE", + "type": "Video", + "tags": [ + "Tutorial", + "Safe Wallet" + ] + }, + { + "name": "Step-by-Step Guide to SAFE: The Ultimate Multisig Wallet for DeFi", + "url": "https://www.youtube.com/watch?v=3edvkNc7Es0", + "type": "Video", + "tags": [ + "Tutorial", + "Safe Wallet" + ] + }, + { + "name": "SAFE Smart Contract Multi-Sig Storage 🔒 (Better than Hardware Wallet!⭐️) Step-by-Step Setup Guide ✅", + "url": "https://www.youtube.com/watch?v=EPa9I3LDplA", + "type": "Video", + "tags": [ + "Tutorial", + "Safe Wallet" + ] + }, + { + "name": "Safe MultiSig Contract Deep Dive", + "url": "https://www.youtube.com/watch?v=_2ZJ5HBEfUk", + "type": "Video", + "tags": [ + "Deep Dive", + "Protocol" + ] + }, + { + "name": "Ethereum: differences between createTransaction and createTransactionBatch (@safe-global/protocol...", + "url": "https://www.youtube.com/watch?v=fbZBVpBgq_4", + "type": "Video", + "tags": [ + "Deep Dive", + "Protocol" + ] + }, + { + "name": "Ethereum: Can a SAFE be deployed using a paymaster / gasless transaction?", + "url": "https://www.youtube.com/watch?v=VL2EkErsWaM", + "type": "Video", + "tags": [ + "Tutorial", + "Protocol", + "Gasless Transactions" + ] + }, + { + "name": "Ethereum: How to deploy a Safe on Hardhat", + "url": "https://www.youtube.com/watch?v=GFkNdPAzEwI", + "type": "Video", + "tags": [ + "Tutorial", + "Protocol", + "Hardhat" + ] + }, + { + "name": "Safe Study (Japanese)", + "url": "https://zenn.dev/kozayupapa/articles/877ca3c93fc4a9", + "type": "Blog post", + "abstract": "ERC-4337(AccountAbstraction) 等のライブラリを提供しているSafeについて理解を深めるため、下記のドキュメントを要約していきたいとおもいます。", + "tags": [ + "Tutorial", + "Protocol" + ] + }, + { + "name": "How to Multi-Distribute ERC-20 tokens via Safe Core SDK", + "url": "https://mirror.xyz/0xa1AC2cC82249A44892802a99CA84c4ed1072B29C/lL8AYV_b4VzTbojuZEprrxD7-RTTap2IMIS8qIObfl8", + "type": "Blog post", + "abstract": "As part of the Atem.green project I’m helping to build, we want to incentivize early contributors by handing out (pre-launch) ERC-20 tokens. We use Coordinape to determine the impact every contributor has in a given month. From that we can calculate how many (pre-launch) token each contributor earned. Now the question is: How do we distribute these tokens?", + "tags": [ + "Tutorial", + "Safe Core SDK" + ] + }, + { + "name": "Build a treasury wallet with multisignature Safe", + "url": "https://blog.logrocket.com/build-treasury-wallet-multisignature-gnosis-safe/", + "type": "Blog post", + "abstract": "Imagine you and your friends are building an NFT marketplace. You are the CEO and your friend works as a Solidity engineer who writes the smart contract. The NFT marketplace becomes popular, and your revenue builds from the market fee of every NFT sale transaction. You store your profit inside a smart contract, and boast to the media about your company that has enough money to buy a private island. Then, the Solidity engineer disappears and withdraws all the funds from the treasury. You watch in horror.", + "tags": [ + "Tutorial", + "Safe Core SDK" + ] + } +] diff --git a/components/ResourceHub/company-resources.json b/components/ResourceHub/company-resources.json new file mode 100644 index 00000000..eefbdb02 --- /dev/null +++ b/components/ResourceHub/company-resources.json @@ -0,0 +1,310 @@ +[ + { + "name": "Gnosis Safe 🛠 Safe modding 101: Create your own Safe module", + "url": "https://www.youtube.com/watch?v=nmDYc9PlAic", + "type": "Video", + "tags": [ + "Introduction", + "Modules" + ] + }, + { + "name": "Gnosis Safe Safe modding 101: Create your own Safe module", + "url": "https://www.youtube.com/watch?v=QdOfuxxXVBA", + "type": "Video", + "tags": [ + "Introduction", + "Modules" + ] + }, + { + "name": "Managing modules and transaction guards - Germán Martínez | Safe Core SDK | Devfolio", + "url": "https://www.youtube.com/watch?v=Y4PwNkU8OVM", + "type": "Video", + "tags": [ + "Deep Dive", + "Modules", + "Guards" + ] + }, + { + "name": "Safe Core SDK 🛠 Managing modules and transaction guards - Germán Martínez", + "url": "https://www.youtube.com/watch?v=ppwE9GXC5MA", + "type": "Video", + "tags": [ + "Deep Dive", + "Modules", + "Guards" + ] + }, + { + "name": "Safe 🛠 Building a Plugin with the Safe{Core} Protocol", + "url": "https://www.youtube.com/watch?v=EzAL1A5N_6k", + "type": "Video", + "tags": [ + "Protocol", + "Plugins" + ] + }, + { + "name": "Safe 🛠 Developing on Safe{Core} Protocol", + "url": "https://www.youtube.com/watch?v=6LpRecrNqFk", + "type": "Video", + "tags": [ + "Protocol" + ] + }, + { + "name": "Safe 🛠️ Building with the Safe{Core} SDK - Manuel Gellfart", + "url": "https://www.youtube.com/watch?v=seQNBoUnwEI", + "type": "Video", + "tags": [ + "Safe Core SDK" + ] + }, + { + "name": "Web3 Native (Aug 24, 2023) - Navigation the Future of Account Abstraction with Lukas Schor", + "url": "https://soundcloud.com/longhashventures_podcast/navigating-the-future-of-account-abstraction-with-safe-web3-native-podcast-safe-lukas-schor", + "type": "Podcast", + "tags": [ + "Perspectives", + "Account Abstraction" + ] + }, + { + "name": "Defi Drip, EP. 6 (Aug 22, 2023) - Safe and Account Abstraction with Johannes Moormann", + "url": "https://www.youtube.com/watch?v=G50rE0129zI", + "type": "Podcast", + "tags": [ + "Perspectives", + "Account Abstraction" + ] + }, + { + "name": "On the brink, EP. 440 (July 26, 2023) - Account Abstraction Adoption and Barriers with Richard Meissner", + "url": "https://onthebrink-podcast.com/safe/", + "type": "Podcast", + "tags": [ + "Perspectives", + "Account Abstraction" + ] + }, + { + "name": "Get the most secure web3 account in <30 seconds", + "url": "https://safe.mirror.xyz/pyf0meKAyooY1v8GB7z6Ik7o4wPHGTyCOcUf--UU-IA", + "type": "Blog post", + "abstract": "Introducing self-custody for everyone. From today, you can secure your crypto fast and free with Safe{Wallet} on Gnosis Chain using just your Google Account.", + "tags": [ + "Tutorial", + "Account Abstraction" + ] + }, + { + "name": "The new AI agent economy will run on Smart Accounts.", + "url": "https://safe.mirror.xyz/V965PykKzlE1PCuWxBjsCJR12WscLcnMxuvR9E9bP-Y", + "type": "Blog post", + "abstract": "Web3’s first billion users may not only be humans, but AI agents.", + "tags": [ + "Perspectives", + "Account Abstraction" + ] + }, + { + "name": "Enhancing Blockchain Security with ERC-7512: A Standard for representing smart contract audits onchain", + "url": "https://safe.mirror.xyz/Li4Mb4teTEmosE6dAsnJ_iz3aMKOV_4lDU84W4TSfc0", + "type": "Blog post", + "abstract": "In a significant stride towards fortifying blockchain security, we at Safe, along with top security experts have introduced ERC-7512, a standard for onchain audit report representations.", + "tags": [ + "Protocol", + "Security" + ] + }, + { + "name": "Introducing Safe{Core} Protocol: Solving the wicked problem of smart accounts", + "url": "https://safe.mirror.xyz/KiklifJINUpklBzf-usK_54EC86AfSeX5wH89bNxglM", + "type": "Blog post", + "abstract": "Today, we are introducing a whitepaper for a modular, open-source Safe{Core} Protocol aiming to advance the smart account transition.", + "tags": [ + "Introduction", + "Protocol", + "Account Abstraction" + ] + }, + { + "name": "Safe Modular Smart Account Architecture – Explained", + "url": "https://safe.mirror.xyz/t76RZPgEKdRmWNIbEzi75onWPeZrBrwbLRejuj-iPpQ", + "type": "Blog post", + "abstract": "Safe is at the forefront of modular Smart Account infrastructure, paving the way for developers to create a diverse range of applications and wallets.", + "tags": [ + "Deep Dive", + "Account Abstraction", + "Protocol" + ] + }, + { + "name": "Launching Monerium on Safe{Core}: Connecting Safes to Euro IBAN accounts", + "url": "https://safe.mirror.xyz/4pgiJAEQ2Jt0ij9Ezc8FSOSiRSfVY4Im8FZ0LuICx-8", + "type": "Blog post", + "abstract": "Today, the Safe{Core} Account Abstraction SDK added a shiny new tool to its toolbox.", + "tags": [ + "Introduction", + "Mass Adoption" + ] + }, + { + "name": "Redefine security with new Safe{Wallet} transaction risk scanner", + "url": "https://safe.mirror.xyz/rInLWZwD_sf7enjoFerj6FIzCYmVMGrrV8Nhg4THdwI", + "type": "Blog post", + "abstract": "We’re excited to bring you news of an important enhancement to your transactional security.", + "tags": [ + "Security", + "Safe Wallet" + ] + }, + { + "name": "Safe Smart Accounts & Diamond Proxies", + "url": "https://safe.mirror.xyz/P83_rVQuUQJAM-SnMpWvsHlN8oLnCeSncD1txyMDqpE", + "type": "Blog post", + "abstract": "Safe is a modular Smart Account protocol that uses Account Abstraction to build a wide range of wallets and other solutions through a shared plugin interface.", + "tags": [ + "Deep Dive", + "Protocol", + "Account Abstraction" + ] + }, + { + "name": "Account Abstraction in a Multichain Landscape - Part 1: Addresses", + "url": "https://safe.mirror.xyz/4GcGAOFno-suTCjBewiYH4k4yXPDdIukC5woO5Bjc4w", + "type": "Blog post", + "abstract": "This is the first article in a series of posts exploring account abstraction in a multichain landscape.", + "tags": [ + "Deep Dive", + "Account Abstraction" + ] + }, + { + "name": "Launching Safe{Core} Account Abstraction Stack with Stripe, Gelato and Web3Auth", + "url": "https://safe.mirror.xyz/FLvQQ5J9qXks0izRl73oC6LiFLofbwFNorwzaEj_xL8", + "type": "Blog post", + "abstract": "Safe is launching Safe{Core}, a modular AA stack with Stripe, Gelato, and Web3Auth as launch partners.", + "tags": [ + "Introduction", + "Account Abstraction", + "Mass Adoption" + ] + }, + { + "name": "Take Back Ownership Manifesto", + "url": "https://safe.mirror.xyz/tO26nZ4CruKCS8d49pyModJhzHw1TTm2QcMqx2lYaXQ", + "type": "Blog post", + "abstract": "Not your keys, not your coins.", + "tags": [ + "Perspectives", + "Account Abstraction" + ] + }, + { + "name": "Safe Community Call #1 with Castle NFT Platform", + "url": "https://www.youtube.com/watch?v=PCtjRs0BpHE&list=PL0knnt70iEZoqz14dWu27utp5IAtf3UDn&index=14", + "type": "Video", + "tags": [ + "Community Call" + ] + }, + { + "name": "Safe Community Call #2 with Onchain Den", + "url": "https://www.youtube.com/watch?v=o18qA1bvNXI", + "type": "Video", + "tags": [ + "Community Call" + ] + }, + { + "name": "Safe Community Call #3 announcing Safe Grants and Liminal updates", + "url": "https://www.youtube.com/watch?v=nzT9HAb9lVw", + "type": "Video", + "tags": [ + "Community Call" + ] + }, + { + "name": "Safe Community Call #4 with NinDAO", + "url": "https://www.youtube.com/watch?v=eosjuOocH1Y", + "type": "Video", + "tags": [ + "Community Call" + ] + }, + { + "name": "Safe Community Call #5 with Richard Meissner", + "url": "https://www.youtube.com/watch?v=9XUXkfwDixA", + "type": "Video", + "tags": [ + "Community Call" + ] + }, + { + "name": "Safe Community Call #6 with Tribes Co-Wallet", + "url": "https://www.youtube.com/watch?v=j44PnR-aGL8", + "type": "Video", + "tags": [ + "Community Call" + ] + }, + { + "name": "Safe Community Call #7 announcing Safe Core and Account Abstraction Hackathon", + "url": "https://www.youtube.com/watch?v=XOkVwtJOHSI", + "type": "Video", + "tags": [ + "Community Call" + ] + }, + { + "name": "Safe Community Call #8 with Podarchy Explorer demo from Metropolis", + "url": "https://www.youtube.com/watch?v=l6jKLPp-ZbQ", + "type": "Video", + "tags": [ + "Community Call" + ] + }, + { + "name": "Safe Community Call #9 with Brahma Fi Console", + "url": "https://www.youtube.com/watch?v=vt0F0MnIB9I", + "type": "Video", + "tags": [ + "Community Call" + ] + }, + { + "name": "Safe Community Call #10 with Redefine and Coinshift", + "url": "https://www.youtube.com/watch?v=-M0xBi3lH1s", + "type": "Video", + "tags": [ + "Community Call" + ] + }, + { + "name": "Safe Community Call #11 on Safe{Core} Protocol, Safe{Con}, Safe Grants Program and more", + "url": "https://www.youtube.com/watch?v=8_5H0Y1THEo", + "type": "Video", + "tags": [ + "Community Call" + ] + }, + { + "name": "Community Call #12: Grants Council and Safe DAO updates, ZK integrations & DAAta and AI Hackathon", + "url": "https://www.youtube.com/watch?v=_WbnF_qyjeQ", + "type": "Video", + "tags": [ + "Community Call" + ] + }, + { + "name": "Safe Community Call #13", + "url": "https://www.youtube.com/watch?v=5UaCdug2LPA", + "type": "Video", + "tags": [ + "Community Call" + ] + } +] diff --git a/components/ResourceHub/index.tsx b/components/ResourceHub/index.tsx new file mode 100644 index 00000000..901df11a --- /dev/null +++ b/components/ResourceHub/index.tsx @@ -0,0 +1,5 @@ +import dynamic from 'next/dynamic' + +const Projects = dynamic(async () => await import('./Resources')) + +export default Projects diff --git a/components/ResourceHub/styles.module.css b/components/ResourceHub/styles.module.css new file mode 100644 index 00000000..c8febfed --- /dev/null +++ b/components/ResourceHub/styles.module.css @@ -0,0 +1,203 @@ +.wrapper { + margin-top: 0 !important; +} + +.cardWrapper { + position: relative; +} + +.searchField :global .MuiInputBase-root { + background: white; +} + +/* Project card */ +.card { + background: var(--mui-palette-border-background); + box-shadow: inset 0 0 0 1px var(--mui-palette-border-light); + border-radius: 6px; + padding: 24px; + display: flex; + flex-direction: column; + align-items: flex-start; + position: relative; + transition: 0.3s; +} + +.outline:hover { + box-shadow: inset 0 0 0 1px var(--mui-palette-primary-main); +} + +.image { + width: 48px; + height: 48px; + margin-bottom: 16px; +} + +.image > img { + object-fit: contain; +} + +.socials { + position: absolute; + top: 24px; + right: 24px; + display: flex; + gap: 8px; + z-index: 1; +} + +.categories { + display: flex; + gap: 8px; + flex-wrap: wrap; + margin-top: 16px; + overflow-y: auto; + scrollbar-width: none; /* Firefox */ +} + +.categories::-webkit-scrollbar { + display: none; +} + +.chip { + border-radius: 4px; + height: 23px; + font-size: 14px; + cursor: pointer; +} + +.reset { + text-decoration: none; + color: var(--mui-palette-text-primary); + cursor: pointer; +} + +.reset:hover { + color: var(--mui-palette-primary-light); +} + +.sidebar { + display: none; +} + +.accordion { + background-color: transparent; + margin: 0 !important; +} + +/* Remove double borders between Accordions */ +.accordion + .accordion { + clip-path: inset(0px -1px -1px -1px); +} + +.accordion :global .Mui-expanded { + min-height: unset; +} + +.accordion :global .MuiAccordionSummary-expandIconWrapper { + margin-right: 2px; +} + +.accordion :global .MuiAccordionSummary-content { + margin: 16px 0 !important; +} + +.accordion :global .MuiAccordionDetails-root { + padding-top: 0; +} + +.list { + padding-top: 0; + padding-bottom: 0; +} + +.label { + display: flex; + margin: 0; + width: 100%; + justify-content: space-between; +} + +.chipContainer { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.description { + color: var(--mui-palette-primary-light); + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 5; + -webkit-box-orient: vertical; + flex-shrink: 0; +} + +.filterButton { + border: 1px solid var(--mui-palette-border-light); + color: var(--mui-palette-text-primary); + display: flex; + align-items: center; + padding: 0px 16px; + gap: 16px; + height: 48px; + font-size: 16px; +} + +.baseButton { + border: 0; + background: 0; + color: inherit; + font-size: 18px; + padding: 0; + margin: 0; + cursor: pointer; +} + +/* Mobile filter */ +.appBar { + border-bottom: 1px solid var(--mui-palette-border-light); + font-size: 20px; + position: sticky; + background-color: var(--mui-palette-background-main); + color: var(--mui-palette-text-primary); +} + +.backButton { + width: 64px; +} + +.backButton:hover { + background-color: unset; +} + +.filterWrapper { + padding: 60px 16px 20px; + background-color: var(--mui-palette-background-main); + display: flex; + flex-direction: column; + flex-grow: 1; +} + +.filterWrapper :global .MuiPaper-root { + box-shadow: unset; +} + +.filterWrapper :global .MuiPaper-root:not(:last-child) { + border-bottom: 1px solid var(--mui-palette-border-light); +} + +@media (min-width: 600px) { + .filterButton { + display: none; + } + + .sidebar { + display: block; + } +} + +.searchField input:focus-visible { + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; +} diff --git a/components/ResourceHub/useResourceSearch.ts b/components/ResourceHub/useResourceSearch.ts new file mode 100644 index 00000000..119352b2 --- /dev/null +++ b/components/ResourceHub/useResourceSearch.ts @@ -0,0 +1,41 @@ +import { useMemo } from 'react' +import Fuse from 'fuse.js' +import { type KnowledgeResource } from './Resources' + +const useResourceSearch = ( + resources: KnowledgeResource[], + query: string +): KnowledgeResource[] => { + const fuse = useMemo( + () => + new Fuse(resources, { + keys: [ + { + name: 'name', + weight: 0.99 + }, + { + name: 'abstract', + weight: 0.5 + }, + { + name: 'tags', + weight: 0.5 + } + ], + threshold: 0.3, + findAllMatches: true + }), + [resources] + ) + + return useMemo(() => { + if (query.length === 0) { + return resources + } + + return fuse.search(query).map(result => result.item) + }, [fuse, resources, query]) +} + +export { useResourceSearch } diff --git a/components/YouTube/Youtube.module.css b/components/YouTube/Youtube.module.css new file mode 100644 index 00000000..eb0d107c --- /dev/null +++ b/components/YouTube/Youtube.module.css @@ -0,0 +1,15 @@ +.video-responsive { + overflow: hidden; + padding-bottom: 56.25%; + position: relative; + height: 0; + width: 100%; +} + +.video-responsive iframe { + left: 0; + top: 0; + height: 100%; + width: 100%; + position: absolute; +} diff --git a/components/YouTube/index.tsx b/components/YouTube/index.tsx new file mode 100644 index 00000000..48af89a4 --- /dev/null +++ b/components/YouTube/index.tsx @@ -0,0 +1,14 @@ +const YouTubeEmbed: React.FC<{ embedId: string }> = ({ embedId }) => ( +
+