diff --git a/.github/workflows/preview-vercel-shell.yml b/.github/workflows/preview-vercel-shell.yml index b88d3cf50..f74edfdf7 100644 --- a/.github/workflows/preview-vercel-shell.yml +++ b/.github/workflows/preview-vercel-shell.yml @@ -17,7 +17,7 @@ jobs: if: (github.event_name == 'pull_request' && !github.event.pull_request.draft) || (github.event_name == 'push' && github.ref == 'refs/heads/dev') env: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_SHELL_PROJECT_ID_NEXT }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_SHELL_PROJECT_ID }} NX_SKIP_NX_CACHE: true GIT_COMMIT_SHA: ${{ github.sha }} HUSKY: 0 diff --git a/.github/workflows/production-vercel-shell.yml b/.github/workflows/production-vercel-shell.yml index 01b1801ad..9b951b7de 100644 --- a/.github/workflows/production-vercel-shell.yml +++ b/.github/workflows/production-vercel-shell.yml @@ -13,7 +13,7 @@ jobs: - self-hosted env: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_SHELL_PROJECT_ID_NEXT }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_SHELL_PROJECT_ID }} NX_SKIP_NX_CACHE: true GIT_COMMIT_SHA: ${{ github.sha }} HUSKY: 0 diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml new file mode 100644 index 000000000..e5b4d85d4 --- /dev/null +++ b/.github/workflows/snyk.yml @@ -0,0 +1,79 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# A sample workflow which sets up Snyk to analyze the full Snyk platform (Snyk Open Source, Snyk Code, +# Snyk Container and Snyk Infrastructure as Code) +# The setup installs the Snyk CLI - for more details on the possible commands +# check https://docs.snyk.io/snyk-cli/cli-reference +# The results of Snyk Code are then uploaded to GitHub Security Code Scanning +# +# In order to use the Snyk Action you will need to have a Snyk API token. +# More details in https://github.com/snyk/actions#getting-your-snyk-token +# or you can signup for free at https://snyk.io/login +# +# For more examples, including how to limit scans to only high-severity issues +# and fail PR checks, see https://github.com/snyk/actions/ + +name: Snyk Security + +on: + push: + branches: ["dev", "prod"] + pull_request: + branches: ["dev"] + +permissions: + contents: read + +jobs: + snyk: + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Snyk CLI to check for security issues + # Snyk can be used to break the build when it detects security issues. + # In this case we want to upload the SAST issues to GitHub Code Scanning + uses: snyk/actions/setup@806182742461562b67788a64410098c9d9b96adb + + # For Snyk Open Source you must first set up the development environment for your application's dependencies + # For example for Node + #- uses: actions/setup-node@v4 + # with: + # node-version: 20 + + env: + # This is where you will need to introduce the Snyk API token created with your Snyk account + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + + # Runs Snyk Code (SAST) analysis and uploads result into GitHub. + # Use || true to not fail the pipeline + - name: Snyk Code test + run: snyk code test --sarif > snyk-code.sarif # || true + + # Runs Snyk Open Source (SCA) analysis and uploads result to Snyk. + - name: Snyk Open Source monitor + run: snyk monitor --all-projects + + # Runs Snyk Infrastructure as Code (IaC) analysis and uploads result to Snyk. + # Use || true to not fail the pipeline. + - name: Snyk IaC test and report + run: snyk iac test --report # || true + + # Build the docker image for testing + - name: Build a Docker image + run: docker build -t your/image-to-test . + # Runs Snyk Container (Container and SCA) analysis and uploads result to Snyk. + - name: Snyk Container monitor + run: snyk container monitor your/image-to-test --file=Dockerfile + + # Push the Snyk Code results into GitHub Code Scanning tab + - name: Upload result to GitHub Code Scanning + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: snyk-code.sarif diff --git a/apps/shell/next.config.mjs b/apps/shell/next.config.mjs index 13d713ff4..18fa2229f 100644 --- a/apps/shell/next.config.mjs +++ b/apps/shell/next.config.mjs @@ -39,6 +39,12 @@ const nextConfig = { }, }; +const COMMIT_SHA = + process.env['GIT_COMMIT_SHA'] ?? + process.env['NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA'] ?? + process.env['VERCEL_GIT_COMMIT_SHA']; + +/** @type {import('@sentry/nextjs').SentryBuildOptions} **/ const sentryWebpackPluginOptions = { // For all available options, see: // https://github.com/getsentry/sentry-webpack-plugin#options @@ -50,14 +56,23 @@ const sentryWebpackPluginOptions = { project: process.env.SENTRY_PROJECT, authToken: process.env.SENTRY_AUTH_TOKEN, + release: { + name: COMMIT_SHA ?? 'development', + deploy: { + env: process.env.VERCEL_ENV ?? 'development', + }, + }, + // For all available options, see: // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/ // Upload a larger set of source maps for prettier stack traces (increases build time) widenClientFileUpload: true, - // Transpiles SDK to be compatible with IE11 (increases bundle size) - transpileClientSDK: true, + // Automatically annotate React components to show their full name in breadcrumbs and session replay + reactComponentAnnotation: { + enabled: true, + }, // Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers. // This can increase your server load as well as your hosting bill. diff --git a/apps/shell/src/app/authz/page.tsx b/apps/shell/src/app/authz/page.tsx index 81379affe..35f1fc98f 100644 --- a/apps/shell/src/app/authz/page.tsx +++ b/apps/shell/src/app/authz/page.tsx @@ -4,7 +4,6 @@ import { QueryClient, } from '@tanstack/react-query'; import { headers } from 'next/headers'; -import { haqqMainnet } from 'wagmi/chains'; import { createCosmosService, getChainParams } from '@haqq/data-access-cosmos'; import { AuthzPage } from '@haqq/shell-authz'; import { @@ -12,11 +11,15 @@ import { indexerBalancesFetcher, parseWagmiCookies, } from '@haqq/shell-shared'; +import { supportedChainsIds } from '../../config/wagmi-config'; export default async function Authz() { const cookies = headers().get('cookie'); const { chainId, walletAddress } = parseWagmiCookies(cookies); - const chainIdToUse = chainId ?? haqqMainnet.id; + const chainIdToUse = + chainId && supportedChainsIds.includes(chainId) + ? chainId + : supportedChainsIds[0]; const { cosmosRestEndpoint } = getChainParams(chainIdToUse); const queryClient = new QueryClient(); const { getAuthzGranteeGrants, getAuthzGranterGrants } = diff --git a/apps/shell/src/app/dao/page.tsx b/apps/shell/src/app/dao/page.tsx index 47d0b1486..146e6f49c 100644 --- a/apps/shell/src/app/dao/page.tsx +++ b/apps/shell/src/app/dao/page.tsx @@ -4,7 +4,6 @@ import { QueryClient, } from '@tanstack/react-query'; import { headers } from 'next/headers'; -import { haqqMainnet } from 'wagmi/chains'; import { createCosmosService, getChainParams } from '@haqq/data-access-cosmos'; import { DaoPage } from '@haqq/shell-dao'; import { @@ -12,11 +11,15 @@ import { indexerBalancesFetcher, parseWagmiCookies, } from '@haqq/shell-shared'; +import { supportedChainsIds } from '../../config/wagmi-config'; export default async function Authz() { const cookies = headers().get('cookie'); const { chainId, walletAddress } = parseWagmiCookies(cookies); - const chainIdToUse = chainId ?? haqqMainnet.id; + const chainIdToUse = + chainId && supportedChainsIds.includes(chainId) + ? chainId + : supportedChainsIds[0]; const queryClient = new QueryClient(); const { cosmosRestEndpoint } = getChainParams(chainIdToUse); const { getErc20TokenPairs } = createCosmosService(cosmosRestEndpoint); diff --git a/apps/shell/src/app/faucet/page.tsx b/apps/shell/src/app/faucet/page.tsx index 18b4975f5..5d25bfd24 100644 --- a/apps/shell/src/app/faucet/page.tsx +++ b/apps/shell/src/app/faucet/page.tsx @@ -6,12 +6,13 @@ import { import dynamic from 'next/dynamic'; import { headers } from 'next/headers'; import { notFound } from 'next/navigation'; -import { haqqMainnet, haqqTestedge2 } from 'wagmi/chains'; +import { haqqTestedge2 } from 'wagmi/chains'; import { ethToHaqq, indexerBalancesFetcher, parseWagmiCookies, } from '@haqq/shell-shared'; +import { supportedChainsIds } from '../../config/wagmi-config'; import { env } from '../../env/client'; const AuthProvider = dynamic(async () => { @@ -26,8 +27,11 @@ const FaucetPage = dynamic(async () => { export default async function Faucet() { const cookies = headers().get('cookie'); - const { chainId: parsedChainId, walletAddress } = parseWagmiCookies(cookies); - const chainIdToUse = parsedChainId ?? haqqMainnet.id; + const { chainId, walletAddress } = parseWagmiCookies(cookies); + const chainIdToUse = + chainId && supportedChainsIds.includes(chainId) + ? chainId + : supportedChainsIds[0]; if (chainIdToUse !== haqqTestedge2.id) { return notFound(); diff --git a/apps/shell/src/app/governance/page.tsx b/apps/shell/src/app/governance/page.tsx index 1ad2eeca4..83d2445cb 100644 --- a/apps/shell/src/app/governance/page.tsx +++ b/apps/shell/src/app/governance/page.tsx @@ -5,10 +5,10 @@ import { dehydrate, } from '@tanstack/react-query'; import { headers } from 'next/headers'; -import { haqqMainnet } from 'wagmi/chains'; import { createCosmosService, getChainParams } from '@haqq/data-access-cosmos'; import { ProposalListPage } from '@haqq/shell-governance'; import { parseWagmiCookies } from '@haqq/shell-shared'; +import { supportedChainsIds } from '../../config/wagmi-config'; export const dynamic = 'force-dynamic'; export const fetchCache = 'force-no-store'; @@ -16,7 +16,10 @@ export const fetchCache = 'force-no-store'; export default async function ProposalList() { const cookies = headers().get('cookie'); const { chainId } = parseWagmiCookies(cookies); - const chainIdToUse = chainId ?? haqqMainnet.id; + const chainIdToUse = + chainId && supportedChainsIds.includes(chainId) + ? chainId + : supportedChainsIds[0]; const { cosmosRestEndpoint } = getChainParams(chainIdToUse); const { getProposals, getGovernanceParams, getProposalTally } = createCosmosService(cosmosRestEndpoint); diff --git a/apps/shell/src/app/governance/proposal/[id]/page.tsx b/apps/shell/src/app/governance/proposal/[id]/page.tsx index 83f8dc905..5aeeffc2b 100644 --- a/apps/shell/src/app/governance/proposal/[id]/page.tsx +++ b/apps/shell/src/app/governance/proposal/[id]/page.tsx @@ -5,10 +5,10 @@ import { } from '@tanstack/react-query'; import { headers } from 'next/headers'; import { notFound } from 'next/navigation'; -import { haqqMainnet } from 'wagmi/chains'; import { createCosmosService, getChainParams } from '@haqq/data-access-cosmos'; import { ProposalDetailsPage } from '@haqq/shell-governance'; import { parseWagmiCookies } from '@haqq/shell-shared'; +import { supportedChainsIds } from '../../../../config/wagmi-config'; export const dynamic = 'force-dynamic'; export const fetchCache = 'force-no-store'; @@ -26,7 +26,10 @@ export default async function ProposalDetails({ const cookies = headers().get('cookie'); const { chainId } = parseWagmiCookies(cookies); - const chainIdToUse = chainId ?? haqqMainnet.id; + const chainIdToUse = + chainId && supportedChainsIds.includes(chainId) + ? chainId + : supportedChainsIds[0]; const { cosmosRestEndpoint } = getChainParams(chainIdToUse); const { getProposalDetails, diff --git a/apps/shell/src/app/page.tsx b/apps/shell/src/app/page.tsx index 055735a90..aea7806a4 100644 --- a/apps/shell/src/app/page.tsx +++ b/apps/shell/src/app/page.tsx @@ -4,7 +4,6 @@ import { QueryClient, } from '@tanstack/react-query'; import { headers } from 'next/headers'; -import { haqqMainnet } from 'wagmi/chains'; import { createCosmosService, getChainParams } from '@haqq/data-access-cosmos'; import { MainPage } from '@haqq/shell-main'; import { @@ -13,12 +12,17 @@ import { indexerBalancesFetcher, parseWagmiCookies, } from '@haqq/shell-shared'; +import { supportedChainsIds } from '../config/wagmi-config'; export default async function IndexPage() { const headersList = headers(); const cookies = headersList.get('cookie'); const { chainId, walletAddress } = parseWagmiCookies(cookies); - const chainIdToUse = chainId ?? haqqMainnet.id; + const chainIdToUse = + chainId && supportedChainsIds.includes(chainId) + ? chainId + : supportedChainsIds[0]; + const { cosmosRestEndpoint } = getChainParams(chainIdToUse); const { getProposals, diff --git a/apps/shell/src/app/staking/page.tsx b/apps/shell/src/app/staking/page.tsx index 33a3f09ea..0376df74c 100644 --- a/apps/shell/src/app/staking/page.tsx +++ b/apps/shell/src/app/staking/page.tsx @@ -4,7 +4,6 @@ import { dehydrate, } from '@tanstack/react-query'; import { headers } from 'next/headers'; -import { haqqMainnet } from 'wagmi/chains'; import { createCosmosService, getChainParams } from '@haqq/data-access-cosmos'; import { ethToHaqq, @@ -12,6 +11,7 @@ import { parseWagmiCookies, } from '@haqq/shell-shared'; import { ValidatorListPage } from '@haqq/shell-staking'; +import { supportedChainsIds } from '../../config/wagmi-config'; export const dynamic = 'force-dynamic'; export const fetchCache = 'force-no-store'; @@ -20,7 +20,10 @@ export default async function ValidatorList() { const headersList = headers(); const cookies = headersList.get('cookie'); const { chainId, walletAddress } = parseWagmiCookies(cookies); - const chainIdToUse = chainId ?? haqqMainnet.id; + const chainIdToUse = + chainId && supportedChainsIds.includes(chainId) + ? chainId + : supportedChainsIds[0]; const { cosmosRestEndpoint } = getChainParams(chainIdToUse); const { getStakingParams, diff --git a/apps/shell/src/app/staking/validator/[address]/page.tsx b/apps/shell/src/app/staking/validator/[address]/page.tsx index f3df417e5..488c0057b 100644 --- a/apps/shell/src/app/staking/validator/[address]/page.tsx +++ b/apps/shell/src/app/staking/validator/[address]/page.tsx @@ -5,7 +5,6 @@ import { } from '@tanstack/react-query'; import { headers } from 'next/headers'; import { notFound } from 'next/navigation'; -import { haqqMainnet } from 'wagmi/chains'; import { createCosmosService, getChainParams } from '@haqq/data-access-cosmos'; import { ethToHaqq, @@ -13,6 +12,7 @@ import { parseWagmiCookies, } from '@haqq/shell-shared'; import { ValidatorDetailsPage } from '@haqq/shell-staking'; +import { supportedChainsIds } from '../../../../config/wagmi-config'; export default async function ValidatorDetails({ params: { address }, @@ -25,7 +25,11 @@ export default async function ValidatorDetails({ const cookies = headers().get('cookie'); const { chainId, walletAddress } = parseWagmiCookies(cookies); - const chainIdToUse = chainId ?? haqqMainnet.id; + const chainIdToUse = + chainId && supportedChainsIds.includes(chainId) + ? chainId + : supportedChainsIds[0]; + const { cosmosRestEndpoint } = getChainParams(chainIdToUse); const { getValidators, diff --git a/apps/shell/src/config/wagmi-config.ts b/apps/shell/src/config/wagmi-config.ts index 699c621e9..70b0a6148 100644 --- a/apps/shell/src/config/wagmi-config.ts +++ b/apps/shell/src/config/wagmi-config.ts @@ -1,3 +1,4 @@ +import { Transport } from 'viem'; import { createConfig, http, @@ -8,6 +9,18 @@ import { import { haqqMainnet, haqqTestedge2 } from 'wagmi/chains'; import { walletConnect } from 'wagmi/connectors'; +export const supportedChains = [haqqMainnet, haqqTestedge2] as const; +export const supportedChainsIds = supportedChains.map((chain): number => { + return chain.id; +}); +const supportedChainsTransports = supportedChains.reduce( + (acc, chain) => { + acc[chain.id] = http(); + return acc; + }, + {} as Record, +); + export function createWagmiConfig(walletConnectProjectId?: string) { const connectors: CreateConnectorFn[] = []; @@ -24,11 +37,8 @@ export function createWagmiConfig(walletConnectProjectId?: string) { } return createConfig({ - chains: [haqqMainnet, haqqTestedge2], - transports: { - [haqqMainnet.id]: http(), - [haqqTestedge2.id]: http(), - }, + chains: supportedChains, + transports: supportedChainsTransports, connectors, ssr: true, multiInjectedProviderDiscovery: true, diff --git a/apps/vesting/src/components/modals/AlertWithDetails/AlertWithDetails.tsx b/apps/vesting/src/components/modals/AlertWithDetails/AlertWithDetails.tsx index b88f7ccf9..0f20dae37 100644 --- a/apps/vesting/src/components/modals/AlertWithDetails/AlertWithDetails.tsx +++ b/apps/vesting/src/components/modals/AlertWithDetails/AlertWithDetails.tsx @@ -43,16 +43,15 @@ export function AlertWithDetails({ {details && (
- {details && ( -
- -
- )} +
+ +
+ {onClose && (