├── src ├── vars.scss ├── assets │ ├── fonts │ │ ├── iconfont.eot │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ ├── iconfont.woff2 │ │ ├── Calibre-Medium-Custom.woff2 │ │ ├── Calibre-Semibold-Custom.woff2 │ │ └── space-mono-v11-latin-700.woff2 │ └── icons │ │ ├── telegram.svg │ │ ├── voted.svg │ │ ├── signature.svg │ │ ├── youtube.svg │ │ ├── github.svg │ │ ├── twitter.svg │ │ ├── lenster.svg │ │ └── coingecko.svg ├── plugins │ ├── safeSnap │ │ ├── logo.png │ │ ├── plugin.json │ │ ├── Create.vue │ │ ├── utils │ │ │ ├── validator.ts │ │ │ ├── realityETH.ts │ │ │ ├── safe.ts │ │ │ ├── coins.ts │ │ │ └── multiSend.ts │ │ ├── components │ │ │ ├── Input │ │ │ │ ├── Address.vue │ │ │ │ └── Amount.vue │ │ │ └── Tooltip.vue │ │ └── Proposal.vue │ ├── progress │ │ ├── index.ts │ │ ├── plugin.json │ │ └── ProposalSidebar.vue │ ├── hal │ │ ├── plugin.json │ │ ├── ProposalSidebar.vue │ │ └── components │ │ │ └── CustomBlock.vue │ ├── poap │ │ ├── plugin.json │ │ └── ProposalSidebar.vue │ ├── commentBox │ │ ├── plugin.json │ │ └── Proposal.vue │ ├── gnosis │ │ ├── plugin.json │ │ ├── ProposalSidebar.vue │ │ └── Create.vue │ ├── quorum │ │ └── plugin.json │ └── projectGalaxy │ │ ├── plugin.json │ │ └── ProposalSidebar.vue ├── components │ ├── IconDiscord.vue │ ├── BaseIndicator.vue │ ├── ContainerParallelInput.vue │ ├── FooterTitle.vue │ ├── FooterLinks.vue │ ├── SetupButtonBack.vue │ ├── FooterSocialsItem.vue │ ├── ProposalsItemBody.vue │ ├── AboutMembersListItem.vue │ ├── BasePill.vue │ ├── ButtonSidebar.vue │ ├── BaseLoading.vue │ ├── BaseContainer.vue │ ├── ButtonBack.vue │ ├── ProfileAboutBiography.vue │ ├── ButtonShare.vue │ ├── SidebarSpacesSkeleton.vue │ ├── LabelProposalVoted.vue │ ├── LabelInput.vue │ ├── SetupButtonNext.vue │ ├── AboutSubheader.vue │ ├── FooterLinksItem.vue │ ├── IconInformationTooltip.vue │ ├── ProfileActivityList.vue │ ├── LoadingPage.vue │ ├── BaseButtonIcon.vue │ ├── BaseIcon.vue │ ├── ProposalsItemTitle.vue │ ├── BaseButton.test.js │ ├── LoadingRow.vue │ ├── BaseNoResults.vue │ ├── BlockSpacesListButtonMore.vue │ ├── ButtonCard.vue │ ├── ButtonTheme.vue │ ├── BaseCounter.vue │ ├── ProfileSidebarHeaderSkeleton.vue │ ├── SidebarUnreadIndicator.vue │ ├── StrategiesBlockWarning.vue │ ├── ProfileName.vue │ ├── SpaceProposalResultsShutter.vue │ ├── BaseSidebarNavigationItem.vue │ ├── IconSocial.vue │ ├── TextAutolinker.vue │ ├── BaseBadge.vue │ ├── BaseMessage.vue │ ├── BaseMessageBlock.vue │ ├── BlockSpacesListSkeleton.vue │ ├── InputEmail.vue │ ├── ProfileSidebarNavigation.vue │ ├── SpaceProposalsNoProposals.vue │ ├── BaseSkinItem.vue │ ├── ExploreSkeletonLoading.vue │ ├── ProfileSidebarHeader.vue │ ├── InputNumber.vue │ ├── SetupMessageHelp.vue │ ├── ModalNotice.vue │ ├── SetupProfile.vue │ ├── SetupButtonCreate.vue │ ├── ModalConfirmAction.vue │ ├── InputRadio.vue │ ├── BaseValidationItem.vue │ ├── InputCheckbox.vue │ ├── AvatarToken.vue │ ├── SetupStrategyAdvanced.vue │ ├── InputUrl.vue │ ├── SpaceSidebar.vue │ ├── AvatarOverlayEdit.vue │ ├── SetupIntro.vue │ ├── ModalSpaces.vue │ ├── SpaceProposalPlugins.vue │ ├── AvatarSpace.vue │ ├── ProposalsItemActive.vue │ ├── SpaceCreateVotingDateEnd.vue │ ├── InputNewsletter.vue │ ├── InputSelectVoteValidation.vue │ ├── SpaceProposalPluginsSidebar.vue │ ├── TextareaJson.vue │ ├── BaseStrategyItem.vue │ ├── AvatarUser.vue │ ├── BaseLink.vue │ ├── SpaceProposalResultsProgressBar.vue │ ├── SpaceCreatePlugins.vue │ ├── BlockSpacesListItem.vue │ ├── SettingsAuthorsBlock.vue │ ├── MessageWarningGnosisNetwork.vue │ ├── SettingsTreasuriesBlockItem.vue │ ├── TextareaArray.vue │ ├── InputSelect.vue │ ├── SpaceProposalResultsQuorumPlugin.vue │ ├── BaseFlashNotification.vue │ ├── IconVerifiedSpace.vue │ ├── SpaceCreateVotingDateStart.vue │ ├── BaseModalSelectItem.vue │ ├── SpaceProposalResultsQuorum.vue │ ├── IndicatorAssetsChange.vue │ ├── SettingsStrategiesBlockItem.vue │ ├── ProfileAddressCopy.vue │ ├── TheLayout.vue │ ├── InputString.vue │ ├── SpaceProposalVoteSingleChoice.vue │ ├── InputSocial.vue │ ├── ListboxMultipleCategories.vue │ ├── SettingsAdminsBlock.vue │ ├── ButtonPlayground.vue │ ├── SpaceSidebarSkeleton.vue │ ├── LabelProposalState.vue │ ├── Ui │ │ ├── Select.vue │ │ ├── CollapsibleText.vue │ │ ├── CollapsibleContent.vue │ │ └── Collapsible.vue │ ├── MenuLanguages.vue │ ├── BaseNetworkItem.vue │ ├── ComboboxNetwork.vue │ ├── InputSelectVoteType.vue │ ├── SpaceProposalVotesListItemChoice.vue │ ├── ModalVotingType.vue │ ├── SetupExtras.vue │ ├── InputUploadAvatar.vue │ ├── ModalVotingPrivacy.vue │ ├── SpaceProposalVoteApproval.vue │ ├── SpaceProposalResultsList.vue │ ├── SpaceWarningFlagged.vue │ ├── InputDate.vue │ ├── InputSelectPrivacy.vue │ ├── TheHeader.vue │ ├── FooterSocials.vue │ ├── TreasuryAssetsList.vue │ ├── BaseUser.vue │ ├── SettingsLinkBlock.vue │ ├── SpaceProposalsMenuFilter.vue │ ├── ModalSpacesListItem.vue │ ├── ProfileSidebar.vue │ ├── ProfileActivityListItem.vue │ ├── InputUploadImage.vue │ ├── BaseButton.vue │ ├── BasePluginItem.vue │ ├── ModalReceipt.vue │ ├── ModalDelegate.vue │ ├── TheNavbar.vue │ ├── ModalTerms.vue │ ├── SettingsDomainBlock.vue │ ├── ModalVoteMessagePassport.vue │ ├── BasePopover.vue │ └── SpaceProposalResultsError.vue ├── composables │ ├── useModal.ts │ ├── useTxStatus.ts │ ├── useInfiniteLoader.ts │ ├── useScrollMonitor.ts │ ├── useCopy.ts │ ├── useTerms.ts │ ├── useSafe.ts │ ├── useUsername.ts │ ├── useFlashNotification.ts │ ├── useCategories.ts │ ├── useI18n.ts │ ├── useApolloQuery.ts │ ├── useGnosis.ts │ ├── useSkinsFilter.ts │ └── useValidationsFilter.ts ├── helpers │ ├── categories.json │ ├── abi.ts │ ├── clientEIP712.ts │ ├── clientGnosisSafe.ts │ ├── beams.ts │ ├── b64.ts │ ├── ens.ts │ ├── apollo.ts │ ├── auth.ts │ ├── utils.test.js │ ├── delegation.ts │ ├── shutter.ts │ ├── connectors.json │ ├── profile.ts │ └── covalent.ts ├── env.d.ts ├── locales │ └── languages.json ├── views │ ├── SpaceTreasury.vue │ └── ProfileView.vue └── main.ts ├── .nvmrc ├── .eslintignore ├── .browserslistrc ├── .husky ├── pre-push ├── pre-commit ├── post-checkout └── post-merge ├── babel.config.js ├── public ├── service-worker.js ├── avatar.png ├── favicon.png ├── about │ ├── main.png │ ├── big_planet.png │ └── small_planet.png ├── stickers │ ├── hooray.png │ └── just_signed.png ├── .well-known │ └── assetlinks.json ├── manifest.json └── icon.svg ├── crowdin.yml ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml ├── stale.yml └── workflows │ ├── nodejs.yml │ ├── deploy.yml │ └── update-snapshot-packages.yml ├── postcss.config.js ├── .prettierrc.js ├── .gitmodules ├── .gitpod.yml ├── vitest.ts ├── cypress.config.ts ├── .gitignore ├── cypress └── e2e │ └── createProposal.cy.js ├── .env ├── index.html ├── tsconfig.json ├── .eslintrc.js ├── LICENSE └── vite.config.ts /src/vars.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v15.14.0 -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | src/plugins/* -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@vue/cli-plugin-babel/preset'] 3 | }; 4 | -------------------------------------------------------------------------------- /public/service-worker.js: -------------------------------------------------------------------------------- 1 | importScripts('https://js.pusher.com/beams/service-worker.js'); 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged -------------------------------------------------------------------------------- /public/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraldinabeutnagel/snapshot/HEAD/public/avatar.png -------------------------------------------------------------------------------- /.husky/post-checkout: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn run init-submodules 5 | -------------------------------------------------------------------------------- /.husky/post-merge: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn run init-submodules 5 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraldinabeutnagel/snapshot/HEAD/public/favicon.png -------------------------------------------------------------------------------- /public/about/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraldinabeutnagel/snapshot/HEAD/public/about/main.png -------------------------------------------------------------------------------- /crowdin.yml: -------------------------------------------------------------------------------- 1 | files: 2 | - source: /src/locales/default.json 3 | translation: /src/locales/%locale%.json 4 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes # 2 | 3 | Changes in this PR: 4 | 5 | How to test and review this PR? 6 | -------------------------------------------------------------------------------- /public/about/big_planet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraldinabeutnagel/snapshot/HEAD/public/about/big_planet.png -------------------------------------------------------------------------------- /public/stickers/hooray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraldinabeutnagel/snapshot/HEAD/public/stickers/hooray.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /public/about/small_planet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraldinabeutnagel/snapshot/HEAD/public/about/small_planet.png -------------------------------------------------------------------------------- /src/assets/fonts/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraldinabeutnagel/snapshot/HEAD/src/assets/fonts/iconfont.eot -------------------------------------------------------------------------------- /src/assets/fonts/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraldinabeutnagel/snapshot/HEAD/src/assets/fonts/iconfont.ttf -------------------------------------------------------------------------------- /src/assets/fonts/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraldinabeutnagel/snapshot/HEAD/src/assets/fonts/iconfont.woff -------------------------------------------------------------------------------- /src/plugins/safeSnap/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraldinabeutnagel/snapshot/HEAD/src/plugins/safeSnap/logo.png -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | trailingComma: 'none', 4 | arrowParens: 'avoid' 5 | }; 6 | -------------------------------------------------------------------------------- /public/stickers/just_signed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraldinabeutnagel/snapshot/HEAD/public/stickers/just_signed.png -------------------------------------------------------------------------------- /src/assets/fonts/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraldinabeutnagel/snapshot/HEAD/src/assets/fonts/iconfont.woff2 -------------------------------------------------------------------------------- /src/components/IconDiscord.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "snapshot-spaces"] 2 | path = snapshot-spaces 3 | url = https://github.com/snapshot-labs/snapshot-spaces 4 | -------------------------------------------------------------------------------- /src/components/BaseIndicator.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/fonts/Calibre-Medium-Custom.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraldinabeutnagel/snapshot/HEAD/src/assets/fonts/Calibre-Medium-Custom.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/Calibre-Semibold-Custom.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraldinabeutnagel/snapshot/HEAD/src/assets/fonts/Calibre-Semibold-Custom.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/space-mono-v11-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geraldinabeutnagel/snapshot/HEAD/src/assets/fonts/space-mono-v11-latin-700.woff2 -------------------------------------------------------------------------------- /src/plugins/progress/index.ts: -------------------------------------------------------------------------------- 1 | export default class Plugin { 2 | public author = 'nick'; 3 | public version = '0.1.0'; 4 | public name = 'progess'; 5 | } 6 | -------------------------------------------------------------------------------- /src/components/ContainerParallelInput.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/FooterTitle.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/FooterLinks.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/composables/useModal.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue'; 2 | 3 | const modalAccountOpen = ref(false); 4 | 5 | export function useModal() { 6 | return { modalAccountOpen }; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/SetupButtonBack.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ $t('back') }} 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/helpers/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | "protocol", 3 | "social", 4 | "investment", 5 | "grant", 6 | "service", 7 | "media", 8 | "creator", 9 | "collector" 10 | ] 11 | -------------------------------------------------------------------------------- /src/components/FooterSocialsItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/ProposalsItemBody.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/helpers/abi.ts: -------------------------------------------------------------------------------- 1 | export const ERC20ABI = [ 2 | 'function name() public view returns (string)', 3 | 'function decimals() view returns (uint32)', 4 | 'function symbol() view returns (string)' 5 | ]; 6 | -------------------------------------------------------------------------------- /src/components/AboutMembersListItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | tasks: 2 | - name: Setup 3 | init: | 4 | yarn install --frozen-lockfile --silent --network-timeout 100000 5 | command: yarn dev 6 | 7 | ports: 8 | - port: 3000 9 | onOpen: open-preview 10 | -------------------------------------------------------------------------------- /src/helpers/clientEIP712.ts: -------------------------------------------------------------------------------- 1 | import Client from '@snapshot-labs/snapshot.js/src/sign'; 2 | 3 | const hubUrl = import.meta.env.VITE_HUB_URL || 'https://testnet.snapshot.org'; 4 | const client = new Client(hubUrl); 5 | 6 | export default client; 7 | -------------------------------------------------------------------------------- /src/components/BasePill.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /vitest.ts: -------------------------------------------------------------------------------- 1 | import createFetchMock from 'vitest-fetch-mock'; 2 | import { vi } from 'vitest'; 3 | 4 | const fetchMock = createFetchMock(vi); 5 | 6 | // sets globalThis.fetch and globalThis.fetchMock to our mocked version 7 | fetchMock.enableMocks(); 8 | -------------------------------------------------------------------------------- /src/components/ButtonSidebar.vue: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/helpers/clientGnosisSafe.ts: -------------------------------------------------------------------------------- 1 | import Client from '@snapshot-labs/snapshot.js/src/sign'; 2 | 3 | const relayerUrl = 4 | import.meta.env.VITE_RELAYER_URL || 'https://testnet.snapshot.org'; 5 | const client = new Client(relayerUrl); 6 | 7 | export default client; 8 | -------------------------------------------------------------------------------- /src/plugins/hal/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "HAL", 3 | "version": "1.0.0", 4 | "author": "hal.xyz", 5 | "website": "https://github.com/snapshot-labs/snapshot/tree/develop/src/plugins/hal", 6 | "icon": "ipfs://QmUjiGPfzTUT6KZJe2iDNtca9XnuVw9iXwQUB2ncCLSoMK" 7 | } 8 | -------------------------------------------------------------------------------- /src/components/BaseLoading.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/BaseContainer.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/components/ButtonBack.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | {{ $t('back') }} 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/components/ProfileAboutBiography.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/plugins/poap/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Poap Module", 3 | "version": "1.0.0", 4 | "author": "Poap-xyz", 5 | "website": "https://github.com/snapshot-labs/snapshot/tree/develop/src/plugins/poap", 6 | "icon": "ipfs://QmSH2PsJUSpHUwS9XAoqLKRvF7qibrsqmBTdmM9Mmyv5fb" 7 | } 8 | -------------------------------------------------------------------------------- /src/components/ButtonShare.vue: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | {{ $t('share') }} 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/plugins/hal/ProposalSidebar.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/plugins/progress/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Progress", 3 | "version": "0.1.0", 4 | "author": "Deadeye07", 5 | "website": "https://github.com/Deadeye07/snapshot-develop/tree/master/src/plugins/progress", 6 | "icon": "ipfs://Qmd1Zpmscf5HBvh5ouu6AUcDTst28CKYyrpfGnPCqd73gZ" 7 | } 8 | -------------------------------------------------------------------------------- /src/plugins/commentBox/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Comment Box", 3 | "version": "0.0.1", 4 | "author": "spiritbro1", 5 | "website": "https://github.com/snapshot-labs/snapshot/tree/develop/src/plugins/commentBox", 6 | "icon": "ipfs://QmWpLpFpeQ3iH69uCeXXAfLu56YaL9mNESMGfeJ5XSpkhY" 7 | } 8 | -------------------------------------------------------------------------------- /src/plugins/gnosis/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Gnosis Impact", 3 | "version": "0.0.1", 4 | "author": "davidalbela", 5 | "website": "https://...", 6 | "icon": "ipfs://QmPhmL1jPjaYKeKeEzyetAueNHCsGgrUVqSJasiTrcPWvx", 7 | "defaults": { 8 | "space": {}, 9 | "proposal": {} 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/components/SidebarSpacesSkeleton.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/components/LabelProposalVoted.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | {{ $t('voted') }} 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/components/LabelInput.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | interface ImportMetaEnv { 2 | readonly VITE_APP_TITLE: string; 3 | readonly VITE_HUB_URL: string; 4 | readonly VITE_RELAYER_URL: string; 5 | readonly VITE_SCORES_URL: string; 6 | readonly VITE_IPFS_GATEWAY: string; 7 | readonly VITE_DEFAULT_NETWORK: string; 8 | readonly VITE_PUSHER_BEAMS_INSTANCE_ID: string; 9 | } 10 | -------------------------------------------------------------------------------- /cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'cypress'; 2 | 3 | export default defineConfig({ 4 | viewportHeight: 900, 5 | viewportWidth: 1280, 6 | screenshotOnRunFailure: false, 7 | video: false, 8 | defaultCommandTimeout: 10000, 9 | e2e: { 10 | baseUrl: 'http://localhost:8081', 11 | supportFile: false 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /src/components/SetupButtonNext.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | {{ $t(text) }} 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/helpers/beams.ts: -------------------------------------------------------------------------------- 1 | import * as PusherPushNotifications from '@pusher/push-notifications-web'; 2 | 3 | let beams: any; 4 | 5 | try { 6 | beams = new PusherPushNotifications.Client({ 7 | instanceId: (import.meta.env.VITE_PUSHER_BEAMS_INSTANCE_ID as string) ?? '' 8 | }); 9 | } catch (e) { 10 | console.log(e); 11 | } 12 | 13 | export { beams }; 14 | -------------------------------------------------------------------------------- /src/components/AboutSubheader.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/FooterLinksItem.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/components/IconInformationTooltip.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | allow: 8 | # Allow updates for snapshot.js only 9 | - dependency-name: "@snapshot-labs/snapshot.js" 10 | - package-ecosystem: 'gitsubmodule' 11 | directory: '/' 12 | schedule: 13 | interval: 'daily' 14 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | daysUntilStale: 60 2 | daysUntilClose: 7 3 | exemptLabels: 4 | - pinned 5 | - enhancement 6 | - bug 7 | staleLabel: stale 8 | markComment: > 9 | This issue has been automatically marked as stale because it has not had 10 | recent activity. It will be closed if no further activity occurs. Thank you 11 | for your contributions. 12 | closeComment: false 13 | -------------------------------------------------------------------------------- /src/components/ProfileActivityList.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | {{ 10 | title.toUpperCase() 11 | }} 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/components/LoadingPage.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/plugins/quorum/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Quorum", 3 | "version": "0.1.0", 4 | "author": "lbeder", 5 | "website": "https://github.com/snapshot-labs/snapshot/tree/develop/src/plugins/quorum", 6 | "icon": "ipfs://Qmbyq2emXpjv1oFFJnhS3jL8aXZAqnya7zmNtKYXEs8jXa", 7 | "defaults": { 8 | "space": { 9 | "strategy": "static", 10 | "total": 1234 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | .yalc 5 | components.d.ts 6 | 7 | # local env files 8 | .env.local 9 | .env.*.local 10 | 11 | # Log files 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | pnpm-debug.log* 16 | 17 | # Editor directories and files 18 | .idea 19 | .vscode 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | # Vitest 27 | 28 | /coverage -------------------------------------------------------------------------------- /src/components/BaseButtonIcon.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/plugins/commentBox/Proposal.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 17 | 18 | -------------------------------------------------------------------------------- /src/components/BaseIcon.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 20 | 21 | -------------------------------------------------------------------------------- /public/.well-known/assetlinks.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "relation": [ 4 | "delegate_permission/common.handle_all_urls" 5 | ], 6 | "target": { 7 | "namespace": "android_app", 8 | "package_name": "org.snapshot", 9 | "sha256_cert_fingerprints": [ 10 | "44:5B:B1:EE:DF:5C:2A:90:7D:7A:10:DF:18:67:68:54:8E:8E:62:C1:DF:84:06:F7:8D:8E:AD:67:2E:B2:5C:E5" 11 | ] 12 | } 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /src/composables/useTxStatus.ts: -------------------------------------------------------------------------------- 1 | import { ref, watch, nextTick } from 'vue'; 2 | 3 | const pendingCount = ref(0); 4 | 5 | export function useTxStatus() { 6 | watch(pendingCount, () => { 7 | if (pendingCount.value < 0) { 8 | pendingCount.value = 0; 9 | } 10 | }); 11 | return { pendingCount }; 12 | } 13 | 14 | export function watchTxStatus(cb) { 15 | watch(pendingCount, () => nextTick(cb)); 16 | } 17 | -------------------------------------------------------------------------------- /src/locales/languages.json: -------------------------------------------------------------------------------- 1 | { 2 | "en-US": { 3 | "name": "English" 4 | }, 5 | "zh-CN": { 6 | "name": "Chinese", 7 | "nativeName": "简体中文" 8 | }, 9 | "ja-JP": { 10 | "name": "Japanese", 11 | "nativeName": "日本語" 12 | }, 13 | "ko-KR": { 14 | "name": "Korean", 15 | "nativeName": "한국어" 16 | }, 17 | "fr-FR": { 18 | "name": "French", 19 | "nativeName": "Français" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components/ProposalsItemTitle.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | {{ proposal.title }} 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/components/BaseButton.test.js: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import BaseButton from './BaseButton.vue'; 4 | 5 | describe('Button.vue', () => { 6 | it('should slot text', () => { 7 | const wrapper = mount(BaseButton, { 8 | slots: { 9 | default: 'Hello world' 10 | } 11 | }); 12 | 13 | expect(wrapper.text()).toContain('Hello world'); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/components/LoadingRow.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/composables/useInfiniteLoader.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue'; 2 | 3 | export function useInfiniteLoader(loadBy = 6) { 4 | const loadingMore = ref(false); 5 | const stopLoadingMore = ref(false); 6 | 7 | async function loadMore(loadFn) { 8 | if (!stopLoadingMore.value) { 9 | loadingMore.value = true; 10 | await loadFn(); 11 | loadingMore.value = false; 12 | } 13 | } 14 | 15 | return { loadBy, loadingMore, stopLoadingMore, loadMore }; 16 | } 17 | -------------------------------------------------------------------------------- /src/assets/icons/telegram.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cypress/e2e/createProposal.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | /** 4 | * work in progress 5 | */ 6 | describe('createProposal', () => { 7 | beforeEach(() => { 8 | cy.visit('/#/fabien.eth'); 9 | }); 10 | 11 | it('shows warning and connect button when not connected', () => { 12 | cy.get('[data-testid="create-proposal-button"]').click(); 13 | cy.get('[data-testid="create-proposal-connect-wallet-button"]').should( 14 | 'exist' 15 | ); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/components/BaseNoResults.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | {{ text }} 13 | 14 | {{ text }} 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/components/BlockSpacesListButtonMore.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | {{ $t('seeAll') }} 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/ButtonCard.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 11 | {{ title }} 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/components/ButtonTheme.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/composables/useScrollMonitor.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, onBeforeUnmount, ref } from 'vue'; 2 | import scrollMonitor from 'scrollmonitor'; 3 | 4 | export function useScrollMonitor(fn) { 5 | let elementWatcher; 6 | 7 | const endElement: any = ref(null); 8 | 9 | onMounted(() => { 10 | elementWatcher = scrollMonitor.create(endElement.value); 11 | elementWatcher.enterViewport(fn); 12 | }); 13 | 14 | onBeforeUnmount(() => elementWatcher.destroy()); 15 | 16 | return { endElement }; 17 | } 18 | -------------------------------------------------------------------------------- /src/plugins/projectGalaxy/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Galxe", 3 | "version": "0.0.1", 4 | "author": "Galxe", 5 | "description": "Galxe OAT", 6 | "website": "https://github.com/snapshot-labs/snapshot/tree/develop/src/plugins/projectGalaxy", 7 | "icon": "https://ipfs.io/ipfs/bafkreibjxoeegwucimd4hjfn3xmxrmkes3je5gwa723gaqosirzgewutfq", 8 | "defaults": { 9 | "space": { 10 | "oats": { 11 | "": "/campaign/" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/components/BaseCounter.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 17 | 18 | -------------------------------------------------------------------------------- /src/components/ProfileSidebarHeaderSkeleton.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/helpers/b64.ts: -------------------------------------------------------------------------------- 1 | /// URL-safe Base64 encoding and decoding. 2 | 3 | const B64U_LOOKUP = { 4 | '/': '_', 5 | _: '/', 6 | '+': '-', 7 | '-': '+', 8 | '=': '.', 9 | '.': '=' 10 | }; 11 | 12 | export const encode = str => 13 | btoa(str).replace(/(\+|\/|=)/g, m => B64U_LOOKUP[m]); 14 | 15 | export const decode = str => 16 | atob(str.replace(/(-|_|\.)/g, m => B64U_LOOKUP[m])); 17 | 18 | export const encodeJson = json => encode(JSON.stringify(json)); 19 | export const decodeJson = str => JSON.parse(decode(str)); 20 | -------------------------------------------------------------------------------- /src/components/SidebarUnreadIndicator.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 16 | 17 | -------------------------------------------------------------------------------- /src/components/StrategiesBlockWarning.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | {{ error.message }} 11 | 12 | {{ $t('learnMore') }} 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/composables/useCopy.ts: -------------------------------------------------------------------------------- 1 | import { useI18n } from '@/composables/useI18n'; 2 | import { useClipboard } from '@vueuse/core'; 3 | import { useFlashNotification } from '@/composables/useFlashNotification'; 4 | 5 | export function useCopy() { 6 | const { t } = useI18n(); 7 | const { copy, copied } = useClipboard(); 8 | const { notify } = useFlashNotification(); 9 | 10 | function copyToClipboard(text) { 11 | copy(text); 12 | if (copied) notify(t('notify.copied')); 13 | } 14 | 15 | return { copyToClipboard }; 16 | } 17 | -------------------------------------------------------------------------------- /src/plugins/progress/ProposalSidebar.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 22 | 23 | -------------------------------------------------------------------------------- /src/components/ProfileName.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 20 | {{ profile?.name || profile?.ens || shorten(address) }} 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/components/SpaceProposalResultsShutter.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ $t('poweredBy') }} 4 | 5 | 6 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | node-version: [14.x] 12 | 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: Use Node.js ${{ matrix.node-version }} 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | - name: yarn install, build, lint 20 | run: | 21 | yarn 22 | yarn run build 23 | yarn run lint 24 | -------------------------------------------------------------------------------- /src/components/BaseSidebarNavigationItem.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/components/IconSocial.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | VITE_HUB_URL=https://testnet.snapshot.org 2 | VITE_RELAYER_URL=https://testnet.snapshot.org 3 | VITE_SCORES_URL=https://score.snapshot.org 4 | VITE_IPFS_GATEWAY=snapshot.mypinata.cloud 5 | VITE_DEFAULT_NETWORK=1 6 | VITE_PUSHER_BEAMS_INSTANCE_ID=2e080021-d495-456d-b2cf-84f9fd718442 7 | VITE_SHUTTER_EON_PUBKEY=0x0e6493bbb4ee8b19aa9b70367685049ff01dc9382c46aed83f8bc07d2a5ba3e6030bd83b942c1fd3dff5b79bef3b40bf6b666e51e7f0be14ed62daaffad47435265f5c9403b1a801921981f7d8659a9bd91fe92fb1cf9afdb16178a532adfaf51a237103874bb03afafe9cab2118dae1be5f08a0a28bf488c1581e9db4bc23ca 8 | VITE_ENV=develop 9 | -------------------------------------------------------------------------------- /src/plugins/poap/ProposalSidebar.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 24 | 25 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | Snapshot 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/components/TextAutolinker.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/plugins/gnosis/ProposalSidebar.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 15 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/components/BaseBadge.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 18 | {{ $t('isCore') }} 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/components/BaseMessage.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/components/BaseMessageBlock.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/composables/useTerms.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue'; 2 | import { lsSet, lsGet } from '@/helpers/utils'; 3 | 4 | export function useTerms(spaceKey) { 5 | const modalTermsOpen = ref(false); 6 | const acceptedSpaces = ref(JSON.parse(lsGet('acceptedTerms', '[]'))); 7 | const termsAccepted = ref(acceptedSpaces.value.includes(spaceKey)); 8 | 9 | function acceptTerms() { 10 | acceptedSpaces.value.push(spaceKey); 11 | lsSet('acceptedTerms', JSON.stringify(acceptedSpaces.value)); 12 | termsAccepted.value = true; 13 | } 14 | 15 | return { modalTermsOpen, termsAccepted, acceptTerms }; 16 | } 17 | -------------------------------------------------------------------------------- /src/components/BlockSpacesListSkeleton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/plugins/projectGalaxy/ProposalSidebar.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 24 | 25 | -------------------------------------------------------------------------------- /src/components/InputEmail.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | target: 7 | type: choice 8 | description: Target 9 | options: 10 | - stable 11 | 12 | jobs: 13 | deploy: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | # Checks-out repo on develop branch 18 | - uses: actions/checkout@v3 19 | with: 20 | ref: 'develop' 21 | # Overwrite target 22 | - run: | 23 | git checkout -b ${{ github.event.inputs.target }} 24 | git push --set-upstream origin ${{ github.event.inputs.target }} --force 25 | -------------------------------------------------------------------------------- /src/assets/icons/voted.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/helpers/ens.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApolloClient, 3 | createHttpLink, 4 | InMemoryCache 5 | } from '@apollo/client/core'; 6 | 7 | const uri = 8 | import.meta.env.VITE_DEFAULT_NETWORK === '5' 9 | ? 'https://api.thegraph.com/subgraphs/name/ensdomains/ensgoerli' 10 | : 'https://api.thegraph.com/subgraphs/name/ensdomains/ens'; 11 | 12 | const httpLink = createHttpLink({ uri }); 13 | 14 | export const ensApolloClient = new ApolloClient({ 15 | link: httpLink, 16 | cache: new InMemoryCache({ 17 | addTypename: false 18 | }), 19 | defaultOptions: { 20 | query: { 21 | fetchPolicy: 'no-cache' 22 | } 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /src/components/ProfileSidebarNavigation.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ $t('profile.activity.header') }} 6 | 7 | 8 | 9 | 10 | {{ $t('profile.about.header') }} 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/components/SpaceProposalsNoProposals.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | {{ $t('noResultsFound') }} 13 | 14 | 17 | 18 | {{ $t('proposals.createProposal') }} 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/plugins/safeSnap/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Gnosis SafeSnap", 3 | "version": "1.0.0", 4 | "author": "Gnosis", 5 | "website": "https://safe.gnosis.io", 6 | "icon": "ipfs://QmQjZaheMTLRrk22mrGCkGk2LNoNqqYMR8Bxtwa8mBHyc9", 7 | "defaults": { 8 | "space": { 9 | "safes": [ 10 | { 11 | "network": "CHAIN_ID", 12 | "realityAddress": "0xSWITCH_WITH_REALITY_MODULE_ADDRESS" 13 | } 14 | ] 15 | }, 16 | "proposal": { 17 | "safes": [ 18 | { 19 | "network": "CHAIN_ID", 20 | "realityAddress": "0xSWITCH_WITH_REALITY_MODULE_ADDRESS" 21 | } 22 | ] 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Snapshot", 3 | "name": "Snapshot", 4 | "description": "Where decisions get made", 5 | "iconPath": "icon.svg", 6 | "icons": [ 7 | { 8 | "src": "favicon.ico", 9 | "sizes": "64x64 32x32 24x24 16x16", 10 | "type": "image/x-icon" 11 | }, 12 | { 13 | "src": "avatar.png", 14 | "type": "image/png", 15 | "sizes": "192x192" 16 | }, 17 | { 18 | "src": "avatar.png", 19 | "type": "image/png", 20 | "sizes": "512x512" 21 | } 22 | ], 23 | "start_url": ".", 24 | "display": "standalone", 25 | "theme_color": "#000000", 26 | "background_color": "#ffffff" 27 | } 28 | -------------------------------------------------------------------------------- /src/components/BaseSkinItem.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | {{ skin }} 17 | 18 | {{ $tc('inSpaces', [formatCompactNumber(skinsSpacesCount[skin] ?? 0)]) }} 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/components/ExploreSkeletonLoading.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 17 | 18 | 19 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/composables/useSafe.ts: -------------------------------------------------------------------------------- 1 | import { computed, reactive } from 'vue'; 2 | 3 | interface ExecutionStatus { 4 | batchError: 5 | | undefined 6 | | { 7 | num: number; 8 | message: string; 9 | }; 10 | } 11 | 12 | const state = reactive({ 13 | batchError: undefined 14 | }); 15 | 16 | export function useSafe() { 17 | function setBatchError(num: number, message: string) { 18 | state.batchError = { num, message }; 19 | } 20 | 21 | function clearBatchError() { 22 | state.batchError = undefined; 23 | } 24 | 25 | return { 26 | setBatchError, 27 | clearBatchError, 28 | safesnap: computed(() => state) 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /src/components/ProfileSidebarHeader.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/components/InputNumber.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 25 | 26 | -------------------------------------------------------------------------------- /src/components/SetupMessageHelp.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | documentation 15 | 16 | 17 | Discord 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/components/ModalNotice.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | {{ title }} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 22 | {{ $t('continue') }} 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/components/SetupProfile.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/components/SetupButtonCreate.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 23 | {{ $t('createButton') }} 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/components/ModalConfirmAction.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | {{ $t('confirmAction') }} 14 | 15 | 16 | 17 | 18 | 19 | 24 | {{ $t('confirm') }} 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/plugins/safeSnap/Create.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 27 | 28 | -------------------------------------------------------------------------------- /src/assets/icons/signature.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/helpers/apollo.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | import { 3 | ApolloClient, 4 | createHttpLink, 5 | InMemoryCache 6 | } from '@apollo/client/core'; 7 | 8 | // HTTP connection to the API 9 | const httpLink = createHttpLink({ 10 | // You should use an absolute URL here 11 | uri: `${import.meta.env.VITE_HUB_URL}/graphql` 12 | }); 13 | 14 | // Create the apollo client 15 | export const apolloClient = new ApolloClient({ 16 | link: httpLink, 17 | cache: new InMemoryCache({ 18 | addTypename: false 19 | }), 20 | defaultOptions: { 21 | query: { 22 | fetchPolicy: 'no-cache' 23 | } 24 | }, 25 | typeDefs: gql` 26 | enum OrderDirection { 27 | asc 28 | desc 29 | } 30 | ` 31 | }); 32 | -------------------------------------------------------------------------------- /src/components/InputRadio.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 22 | 23 | {{ label }} 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/assets/icons/youtube.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/BaseValidationItem.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {{ 19 | $tc('inSpaces', [ 20 | formatCompactNumber(validationsSpacesCount?.[validation] ?? 0) 21 | ]) 22 | }} 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/components/InputCheckbox.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 22 | 23 | {{ label }} 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/components/AvatarToken.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 31 | 32 | -------------------------------------------------------------------------------- /src/components/SetupStrategyAdvanced.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 20 | 21 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/components/InputUrl.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | emit('update:modelValue', input)" 22 | > 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/components/SpaceSidebar.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/components/AvatarOverlayEdit.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 16 | 20 | {{ avatar ? $t('edit') : $t('upload') }} 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/components/SetupIntro.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | {{ $t('header.description') }} 15 | 16 | 17 | 18 | 19 | 20 | 25 | {{ $t('getStarted') }} 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/components/ModalSpaces.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | {{ $t('spaces') }} 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/components/SpaceProposalPlugins.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 23 | 29 | 30 | -------------------------------------------------------------------------------- /src/composables/useUsername.ts: -------------------------------------------------------------------------------- 1 | import { computed, Ref } from 'vue'; 2 | import { shorten } from '@/helpers/utils'; 3 | import { Profile } from '@/helpers/interfaces'; 4 | 5 | import { useWeb3 } from '@/composables'; 6 | 7 | export function useUsername( 8 | address: Ref, 9 | profile?: Ref 10 | ) { 11 | const { web3Account } = useWeb3(); 12 | 13 | const username = computed(() => { 14 | if ( 15 | web3Account && 16 | address.value.toLowerCase() === web3Account.value.toLowerCase() 17 | ) { 18 | return 'You'; 19 | } 20 | 21 | if (profile?.value?.name) return profile.value.name; 22 | if (profile?.value?.ens) return profile.value.ens; 23 | return shorten(address.value); 24 | }); 25 | 26 | return { 27 | username 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /src/components/AvatarSpace.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 32 | 33 | -------------------------------------------------------------------------------- /src/components/ProposalsItemActive.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | {{ 17 | getRelativeProposalPeriod(proposal.state, proposal.start, proposal.end) 18 | }} 19 | 20 | - 21 | {{ 22 | formatPercentNumber( 23 | Number(formatCompactNumber(proposal.scores_total / proposal.quorum)) 24 | ) 25 | }} 26 | {{ $t('quorumReached') }} 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/components/SpaceCreateVotingDateEnd.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 23 | 32 | 33 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "jsx": "preserve", 8 | "sourceMap": true, 9 | "resolveJsonModule": true, 10 | "esModuleInterop": true, 11 | "lib": ["esnext", "dom"], 12 | "importHelpers": true, 13 | "allowSyntheticDefaultImports": true, 14 | "allowJs": true, 15 | "baseUrl": ".", 16 | "types": ["vite/client", "unplugin-icons/types/vue"], 17 | "paths": { 18 | "@/*": ["src/*"] 19 | }, 20 | "noImplicitAny": false, 21 | "skipLibCheck": true, 22 | "outDir": "dist" 23 | }, 24 | "include": [ 25 | "src/**/*.ts", 26 | "src/**/*.d.ts", 27 | "src/**/*.tsx", 28 | "src/**/*.vue", 29 | "components.d.ts" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /src/components/InputNewsletter.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 20 | 21 | 22 | 23 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/components/InputSelectVoteValidation.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 22 | 23 | 24 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/assets/icons/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/SpaceProposalPluginsSidebar.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 29 | 35 | 36 | -------------------------------------------------------------------------------- /src/components/TextareaJson.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 28 | 32 | 33 | -------------------------------------------------------------------------------- /src/composables/useFlashNotification.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue'; 2 | 3 | interface Notification { 4 | id: number; 5 | message: string; 6 | type: string; 7 | remove(): any; 8 | } 9 | 10 | const items = ref([]); 11 | 12 | export function useFlashNotification() { 13 | function notify(payload: any, duration = 4000) { 14 | const item: Notification = { 15 | id: Math.floor(Date.now() * Math.random()), 16 | message: Array.isArray(payload) ? payload[1] : payload, 17 | type: Array.isArray(payload) ? payload[0] : 'green', 18 | remove() { 19 | items.value.splice( 20 | items.value.findIndex(i => i.id === this.id), 21 | 1 22 | ); 23 | } 24 | }; 25 | 26 | items.value.push(item); 27 | setTimeout(() => item.remove(), duration); 28 | } 29 | 30 | return { notify, items }; 31 | } 32 | -------------------------------------------------------------------------------- /src/components/BaseStrategyItem.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | {{ strategy.id }} 17 | 18 | v{{ strategy.version }} 19 | 20 | 21 | 22 | {{ strategy.author }} 23 | 24 | 25 | {{ $tc('inSpaces', [formatCompactNumber(strategy.spacesCount)]) }} 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/components/AvatarUser.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 33 | 34 | -------------------------------------------------------------------------------- /src/components/BaseLink.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 21 | 22 | 26 | 27 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/components/SpaceProposalResultsProgressBar.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/components/SpaceCreatePlugins.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 33 | 34 | -------------------------------------------------------------------------------- /src/components/BlockSpacesListItem.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | {{ space?.name }} 24 | 25 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/views/SpaceTreasury.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/components/SettingsAuthorsBlock.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 16 | 17 | 18 | 19 | {{ getValidation('members').message }} 21 | 22 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/components/MessageWarningGnosisNetwork.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | {{ 22 | $t('settings.gnosisWrongNetwork.base', { 23 | network: networks?.[networkKey]?.name, 24 | action: $t(`settings.gnosisWrongNetwork.${action}`) 25 | }) 26 | }} 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/components/SettingsTreasuriesBlockItem.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 18 | 21 | 22 | {{ treasury.name }} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/helpers/auth.ts: -------------------------------------------------------------------------------- 1 | import injected from '@snapshot-labs/lock/connectors/injected'; 2 | import walletconnect from '@snapshot-labs/lock/connectors/walletconnect'; 3 | import portis from '@snapshot-labs/lock/connectors/portis'; 4 | import connectors from '@/helpers/connectors.json'; 5 | import walletlink from '@snapshot-labs/lock/connectors/walletlink'; 6 | import gnosis from '@snapshot-labs/lock/connectors/gnosis'; 7 | import stargazer from '@snapshot-labs/lock/connectors/stargazer'; 8 | 9 | const options: any = { connectors: [] }; 10 | const lockConnectors = { 11 | injected, 12 | walletconnect, 13 | walletlink, 14 | portis, 15 | stargazer, 16 | gnosis 17 | }; 18 | 19 | Object.entries(connectors).forEach((connector: any) => { 20 | options.connectors.push({ 21 | key: connector[0], 22 | connector: lockConnectors[connector[0]], 23 | options: connector[1].options 24 | }); 25 | }); 26 | 27 | export default options; 28 | -------------------------------------------------------------------------------- /src/components/TextareaArray.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | 25 | {{ title || '' }} 26 | 27 | 34 | 35 | -------------------------------------------------------------------------------- /src/components/InputSelect.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | {{ title }} 16 | 22 | 23 | {{ modelValue }} 24 | 25 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/components/SpaceProposalResultsQuorumPlugin.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 24 | {{ $t('settings.quorum.label') }} 25 | 26 | 27 | {{ formatCompactNumber(totalQuorumScore) }} / 28 | {{ formatCompactNumber(totalVotingPower) }} 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/components/BaseFlashNotification.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 11 | 12 | 13 | 18 | 19 | 20 | 21 | {{ item.message }} 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/components/IconVerifiedSpace.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | 29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/components/SpaceCreateVotingDateStart.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 28 | 37 | 38 | -------------------------------------------------------------------------------- /src/plugins/gnosis/Create.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/components/BaseModalSelectItem.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 17 | 18 | 19 | 20 | 24 | {{ tag }} 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/components/SpaceProposalResultsQuorum.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | {{ $t('settings.quorum.label') }} 21 | 22 | {{ formatCompactNumber(totalQuorumScore) }} 23 | / 24 | {{ formatCompactNumber(proposal?.quorum || space.voting?.quorum || 0) }} 25 | 26 | 27 | 28 | 32 | 33 | -------------------------------------------------------------------------------- /src/composables/useCategories.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue'; 2 | import categories from '@/helpers/categories.json'; 3 | import { useSpaces } from '@/composables/useSpaces'; 4 | 5 | export function useCategories() { 6 | const { orderedSpaces } = useSpaces(); 7 | 8 | // count spaces per category 9 | const spacesPerCategory = computed(() => { 10 | const spaces = orderedSpaces.value.reduce((counters, space) => { 11 | if (!space.private) { 12 | space.categories?.forEach((c: any) => counters[c]++); 13 | return counters; 14 | } 15 | }, Object.fromEntries(categories.map(c => [c, 0]))); 16 | return spaces; 17 | }); 18 | 19 | const categoriesOrderedBySpaceCount = computed(() => { 20 | return categories.sort( 21 | (a, b) => spacesPerCategory.value[b] - spacesPerCategory.value[a] 22 | ); 23 | }); 24 | 25 | return { 26 | categories, 27 | spacesPerCategory, 28 | categoriesOrderedBySpaceCount 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /src/components/IndicatorAssetsChange.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 20 | 21 | {{ 22 | `${quote.quote_24h > quote.quote ? '' : '+'}${formatPercentNumber( 23 | (quote.quote - quote.quote_24h) / quote.quote_24h 24 | )}` 25 | }} 26 | 27 | 28 | {{ `($${formatNumber(quote.quote - quote.quote_24h)})` }} 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/components/SettingsStrategiesBlockItem.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 18 | 21 | 22 | {{ strategy.name }} 23 | ${{ strategy.params.symbol }} 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /.github/workflows/update-snapshot-packages.yml: -------------------------------------------------------------------------------- 1 | name: Update Snapshot packages 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: 0 10 * * 1 7 | 8 | jobs: 9 | update-dep: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-node@v1 14 | with: 15 | node-version: '14.x' 16 | - name: Update snapshot.js 17 | run: | 18 | yarn upgrade @snapshot-labs/snapshot.js --latest 19 | - name: Update lock.js 20 | run: | 21 | yarn upgrade @snapshot-labs/lock --latest 22 | - name: Create Pull Request 23 | uses: peter-evans/create-pull-request@v3 24 | with: 25 | commit-message: Update Snapshot packages 26 | title: Update Snapshot packages 27 | body: | 28 | - Updates from snapshot.js or lock packages 29 | 30 | Auto-generated by Github Actions 31 | branch: update-snapshot-packages 32 | -------------------------------------------------------------------------------- /src/components/ProfileAddressCopy.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 23 | 24 | {{ profile.ens }} 25 | 26 | 27 | 31 | {{ shorten(userAddress) }} 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/helpers/utils.test.js: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { calcFromSeconds, calcToSeconds } from './utils'; 3 | 4 | describe('calcFromSeconds', () => { 5 | it('should return 3 for seconds from 10800 to 14399 and unit h', () => { 6 | expect(calcFromSeconds(60 * 60 * 3, 'h')).toBe(3); 7 | expect(calcFromSeconds(60 * 60 * 4 - 1, 'h')).toBe(3); 8 | }); 9 | 10 | it('should return 3 for seconds from 259200 to 345599 and unit d', () => { 11 | expect(calcFromSeconds(60 * 60 * 24 * 3, 'd')).toBe(3); 12 | expect(calcFromSeconds(60 * 60 * 24 * 4 - 1, 'd')).toBe(3); 13 | }); 14 | 15 | it('should return 3600 for 1 hour and 5400 for 1.5 hours', () => { 16 | expect(calcToSeconds(1, 'h')).toBe(3600); 17 | expect(calcToSeconds(1.5, 'h')).toBe(5400); 18 | }); 19 | 20 | it('should return 86400 for 1 day and 129600 for 1.5 days', () => { 21 | expect(calcToSeconds(1, 'd')).toBe(86400); 22 | expect(calcToSeconds(1.5, 'd')).toBe(129600); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/components/TheLayout.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 16 | 17 | 18 | 19 | 24 | 25 | 26 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/composables/useI18n.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue'; 2 | import { lsGet, lsSet } from '@/helpers/utils'; 3 | import i18n, { 4 | defaultLocale, 5 | setI18nLanguage, 6 | loadLocaleMessages 7 | } from '@/helpers/i18n'; 8 | 9 | const currentLocale = ref(lsGet('locale', defaultLocale)); 10 | 11 | export function useI18n() { 12 | const { t, d, tc } = i18n.global; 13 | 14 | async function setLocale(locale) { 15 | currentLocale.value = locale; 16 | lsSet('locale', locale); 17 | await loadLocaleMessages(i18n, locale); 18 | setI18nLanguage(i18n, locale); 19 | } 20 | 21 | async function loadLocale() { 22 | await loadLocaleMessages(i18n, currentLocale.value); 23 | setI18nLanguage(i18n, currentLocale.value); 24 | } 25 | 26 | function setPageTitle(message, params: any = {}) { 27 | document.title = t(message, params); 28 | } 29 | 30 | return { 31 | t, 32 | d, 33 | tc, 34 | setLocale, 35 | loadLocale, 36 | currentLocale, 37 | setPageTitle 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /src/components/InputString.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 28 | 37 | 38 | -------------------------------------------------------------------------------- /src/components/SpaceProposalVoteSingleChoice.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 30 | 31 | {{ shorten(choice, 32) }} 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { Buffer } from 'buffer'; 2 | (window as any).global = window; 3 | (window as any).Buffer = Buffer; 4 | import { createApp, h, provide } from 'vue'; 5 | import { LockPlugin } from '@snapshot-labs/lock/plugins/vue3'; 6 | import options from '@/helpers/auth'; 7 | import '../snapshot-spaces/skins'; 8 | import App from '@/App.vue'; 9 | import router from '@/router'; 10 | import i18n from '@/helpers/i18n'; 11 | import '@/helpers/auth'; 12 | import '@/style.scss'; 13 | import { apolloClient } from '@/helpers/apollo'; 14 | import { DefaultApolloClient } from '@vue/apollo-composable'; 15 | import VueTippy from 'vue-tippy'; 16 | 17 | const app = createApp({ 18 | setup() { 19 | provide(DefaultApolloClient, apolloClient); 20 | }, 21 | render: () => h(App) 22 | }) 23 | .use(i18n) 24 | .use(router) 25 | .use(LockPlugin, options) 26 | .use(VueTippy, { 27 | defaultProps: { delay: [400, null] }, 28 | directive: 'tippy' // => v-tippy 29 | }); 30 | 31 | app.mount('#app'); 32 | 33 | export default app; 34 | -------------------------------------------------------------------------------- /src/assets/icons/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/components/InputSocial.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | emit('update:modelValue', value)" 23 | > 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/components/ListboxMultipleCategories.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 27 | 39 | 40 | -------------------------------------------------------------------------------- /src/helpers/delegation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SNAPSHOT_SUBGRAPH_URL, 3 | subgraphRequest 4 | } from '@snapshot-labs/snapshot.js/src/utils'; 5 | 6 | export const contractAddress = '0x469788fE6E9E9681C6ebF3bF78e7Fd26Fc015446'; 7 | 8 | export async function getDelegates(network: string, address: string) { 9 | const params = { 10 | delegations: { 11 | __args: { 12 | where: { 13 | delegator: address.toLowerCase() 14 | }, 15 | first: 1000 16 | }, 17 | space: true, 18 | delegate: true 19 | } 20 | }; 21 | return await subgraphRequest(SNAPSHOT_SUBGRAPH_URL[network], params); 22 | } 23 | 24 | export async function getDelegators(network: string, address: string) { 25 | const params = { 26 | delegations: { 27 | __args: { 28 | where: { 29 | delegate: address.toLowerCase() 30 | }, 31 | first: 1000 32 | }, 33 | delegator: true, 34 | space: true 35 | } 36 | }; 37 | return await subgraphRequest(SNAPSHOT_SUBGRAPH_URL[network], params); 38 | } 39 | -------------------------------------------------------------------------------- /src/components/SettingsAdminsBlock.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 23 | 24 | 25 | 26 | {{ getValidation('admins').message }} 28 | 29 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/components/ButtonPlayground.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 39 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/components/SpaceSidebarSkeleton.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 14 | 17 | 18 | 19 | 20 | 21 | {{ $t('join') }} 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/components/LabelProposalState.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 29 | 30 | 31 | 49 | -------------------------------------------------------------------------------- /src/components/Ui/Select.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 27 | 28 | 29 | 30 | 31 | 32 | 40 | -------------------------------------------------------------------------------- /src/plugins/safeSnap/utils/validator.ts: -------------------------------------------------------------------------------- 1 | import { mustBeEthereumAddress, isArrayParameter } from '../index'; 2 | 3 | export const isAddress = (type: string): boolean => 4 | type.indexOf('address') === 0; 5 | export const isBoolean = (type: string): boolean => type.indexOf('bool') === 0; 6 | export const isString = (type: string): boolean => type.indexOf('string') === 0; 7 | export const isUint = (type: string): boolean => type.indexOf('uint') === 0; 8 | export const isInt = (type: string): boolean => type.indexOf('int') === 0; 9 | export const isByte = (type: string): boolean => type.indexOf('byte') === 0; 10 | 11 | export const isStringArray = (text: string): boolean => { 12 | try { 13 | const values = JSON.parse(text); 14 | return Array.isArray(values); 15 | } catch (e) { 16 | return false; 17 | } 18 | }; 19 | 20 | export const isParameterValue = (type: string, value: string) => { 21 | if (type === 'address') { 22 | return mustBeEthereumAddress(value); 23 | } else if (isArrayParameter(type)) { 24 | return isStringArray(value); 25 | } 26 | return !!value; 27 | }; 28 | -------------------------------------------------------------------------------- /src/components/MenuLanguages.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | {{ 32 | languages[$i18n.locale]?.nativeName ?? languages[$i18n.locale]?.name 33 | }} 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: [ 7 | 'plugin:cypress/recommended', 8 | 'plugin:vue/vue3-recommended', 9 | 'eslint:recommended', 10 | '@vue/eslint-config-typescript/recommended', 11 | '@vue/eslint-config-prettier' 12 | ], 13 | ignorePatterns: ['/node_modules/**/*.*'], 14 | parserOptions: { 15 | ecmaVersion: 2020 16 | }, 17 | rules: { 18 | 'no-console': 'off', 19 | 'prefer-template': 'error', 20 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 21 | '@typescript-eslint/no-explicit-any': 'off', 22 | '@typescript-eslint/ban-ts-ignore': 'off', 23 | '@typescript-eslint/camelcase': 'off', 24 | '@typescript-eslint/no-undef': 'off', 25 | '@typescript-eslint/no-var-requires': 'off', 26 | '@typescript-eslint/no-use-before-define': ['error', { functions: false }], 27 | 'vue/script-setup-uses-vars': 'error' 28 | }, 29 | globals: { 30 | $ref: 'readonly', 31 | defineProps: 'readonly', 32 | defineEmits: 'readonly', 33 | withDefaults: 'readonly' 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Snapshot Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /public/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | icon 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/composables/useApolloQuery.ts: -------------------------------------------------------------------------------- 1 | import { computed, ref } from 'vue'; 2 | import cloneDeep from 'lodash/cloneDeep'; 3 | import { apolloClient } from '@/helpers/apollo'; 4 | import { ensApolloClient } from '@/helpers/ens'; 5 | 6 | export function useApolloQuery() { 7 | const loading = ref(false); 8 | 9 | async function apolloQuery(options, path = '') { 10 | try { 11 | loading.value = true; 12 | const response = await apolloClient.query(options); 13 | loading.value = false; 14 | 15 | return cloneDeep(!path ? response.data : response.data[path]); 16 | } catch (error) { 17 | loading.value = false; 18 | console.log(error); 19 | } 20 | } 21 | 22 | async function ensApolloQuery(options) { 23 | try { 24 | loading.value = true; 25 | const response = await ensApolloClient.query(options); 26 | loading.value = false; 27 | 28 | return response.data; 29 | } catch (error) { 30 | loading.value = false; 31 | console.log(error); 32 | } 33 | } 34 | 35 | return { 36 | apolloQuery, 37 | ensApolloQuery, 38 | queryLoading: computed(() => loading.value) 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /src/helpers/shutter.ts: -------------------------------------------------------------------------------- 1 | import { randomBytes } from '@ethersproject/random'; 2 | import { BigNumber } from '@ethersproject/bignumber'; 3 | import { arrayify, hexlify } from '@ethersproject/bytes'; 4 | import { toUtf8Bytes, formatBytes32String } from '@ethersproject/strings'; 5 | import shutterWasm from '@shutter-network/shutter-crypto/dist/shutter-crypto.wasm?url'; 6 | import { init, encrypt } from '@shutter-network/shutter-crypto'; 7 | 8 | export default async function encryptChoice( 9 | choice: string, 10 | id: string 11 | ): Promise { 12 | await init(shutterWasm); 13 | 14 | const bytesChoice = toUtf8Bytes(choice); 15 | const message = arrayify(bytesChoice); 16 | const eonPublicKey = arrayify(import.meta.env.VITE_SHUTTER_EON_PUBKEY); 17 | 18 | const is32ByteString = id.substring(0, 2) === '0x'; 19 | const proposalId = arrayify(is32ByteString ? id : formatBytes32String(id)); 20 | 21 | const sigma = arrayify(BigNumber.from(randomBytes(32))); 22 | 23 | const encryptedMessage = await encrypt( 24 | message, 25 | eonPublicKey, 26 | proposalId, 27 | sigma 28 | ); 29 | 30 | return hexlify(encryptedMessage) ?? null; 31 | } 32 | -------------------------------------------------------------------------------- /src/plugins/hal/components/CustomBlock.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 27 | 28 | 29 | {{ $t('hal.text') }} 30 | 31 | Be notified 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/components/BaseNetworkItem.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | {{ 29 | $tc('inSpaces', [ 30 | formatCompactNumber(networksSpacesCount?.[network.key] ?? 0) 31 | ]) 32 | }} 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/components/ComboboxNetwork.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 27 | emit('select', value.id)" 33 | @search="value => (query = value)" 34 | > 35 | 36 | 37 | {{ item.name }} 38 | 39 | 40 | #{{ item.id }} 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/assets/icons/lenster.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/InputSelectVoteType.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 37 | 38 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/components/SpaceProposalVotesListItemChoice.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 21 | 22 | 29 | 30 | 37 | {{ format(proposal, vote.choice) }} 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/views/ProfileView.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 26 | 27 | 28 | 32 | 33 | 34 | 35 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/plugins/safeSnap/components/Input/Address.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 36 | 43 | {{ label }} 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/components/ModalVotingType.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 27 | 28 | {{ $t('voting.selectVoting') }} 29 | 30 | 31 | 32 | 36 | 37 | 38 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/composables/useGnosis.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue'; 2 | import { useWeb3 } from '@/composables'; 3 | import { getInstance } from '@snapshot-labs/lock/plugins/vue3'; 4 | import { ExtendedSpace } from '@/helpers/interfaces'; 5 | 6 | const defaultNetwork = import.meta.env.VITE_DEFAULT_NETWORK; 7 | 8 | export function useGnosis(space?: ExtendedSpace) { 9 | const { web3 } = useWeb3(); 10 | 11 | const auth = getInstance(); 12 | const connectorName = computed(() => auth.provider.value?.connectorName); 13 | 14 | const isGnosisSafe = computed( 15 | () => 16 | web3.value?.walletConnectType === 'Gnosis Safe Multisig' || 17 | connectorName.value === 'gnosis' 18 | ); 19 | 20 | const networkKey = computed(() => web3.value.network.key); 21 | 22 | const spaceNetworkKey = computed(() => space?.network); 23 | 24 | const isGnosisAndNotDefaultNetwork = computed(() => { 25 | return isGnosisSafe.value && networkKey.value !== defaultNetwork; 26 | }); 27 | 28 | const isGnosisAndNotSpaceNetwork = computed(() => { 29 | return isGnosisSafe.value && networkKey.value !== spaceNetworkKey.value; 30 | }); 31 | 32 | return { 33 | isGnosisSafe, 34 | isGnosisAndNotDefaultNetwork, 35 | isGnosisAndNotSpaceNetwork 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /src/plugins/safeSnap/utils/realityETH.ts: -------------------------------------------------------------------------------- 1 | import { StaticJsonRpcProvider } from '@ethersproject/providers'; 2 | import { BigNumber } from '@ethersproject/bignumber'; 3 | import { multicall } from '@snapshot-labs/snapshot.js/src/utils'; 4 | import { ORACLE_ABI } from '../constants'; 5 | 6 | export const retrieveInfoFromOracle = async ( 7 | provider: StaticJsonRpcProvider, 8 | network: string, 9 | oracleAddress: string, 10 | questionId: string | undefined 11 | ): Promise<{ 12 | currentBond: BigNumber | undefined; 13 | isApproved: boolean; 14 | endTime: number | undefined; 15 | }> => { 16 | if (questionId) { 17 | const result = await multicall(network, provider, ORACLE_ABI, [ 18 | [oracleAddress, 'getFinalizeTS', [questionId]], 19 | [oracleAddress, 'getBond', [questionId]], 20 | [oracleAddress, 'getBestAnswer', [questionId]] 21 | ]); 22 | 23 | const currentBond = BigNumber.from(result[1][0]); 24 | const answer = BigNumber.from(result[2][0]); 25 | 26 | return { 27 | currentBond, 28 | isApproved: answer.eq(BigNumber.from(1)), 29 | endTime: BigNumber.from(result[0][0]).toNumber() 30 | }; 31 | } 32 | return { 33 | currentBond: undefined, 34 | isApproved: false, 35 | endTime: undefined 36 | }; 37 | }; 38 | -------------------------------------------------------------------------------- /src/components/SetupExtras.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {{ $t('setup.validationTitle') }} 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/plugins/safeSnap/components/Tooltip.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Multisend address 21 | 25 | {{ shorten(multiSendAddress) }} 26 | 27 | 28 | Reality module address 29 | 33 | {{ shorten(realityAddress) }} 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/components/InputUploadAvatar.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 30 | 31 | 36 | 37 | 45 | 46 | -------------------------------------------------------------------------------- /src/components/ModalVotingPrivacy.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 22 | {{ $t('privacy.title') }} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 33 | 34 | 35 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/components/SpaceProposalVoteApproval.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 38 | 39 | {{ shorten(choice, 32) }} 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/plugins/safeSnap/utils/safe.ts: -------------------------------------------------------------------------------- 1 | import { GNOSIS_SAFE_TRANSACTION_API_URLS } from '../constants'; 2 | import { TokenAsset } from '@/helpers/interfaces'; 3 | import memoize from 'lodash/memoize'; 4 | 5 | async function callGnosisSafeTransactionApi(network: string, url: string) { 6 | const apiUrl = GNOSIS_SAFE_TRANSACTION_API_URLS[network]; 7 | const response = await fetch(apiUrl + url); 8 | return response.json(); 9 | } 10 | 11 | export const getGnosisSafeBalances = memoize( 12 | (network, safeAddress) => { 13 | const endpointPath = `/safes/${safeAddress}/balances/`; 14 | return callGnosisSafeTransactionApi(network, endpointPath); 15 | }, 16 | (safeAddress, network) => `${safeAddress}_${network}` 17 | ); 18 | 19 | export const getGnosisSafeCollectibles = memoize( 20 | (network, safeAddress) => { 21 | const endpointPath = `/safes/${safeAddress}/collectibles/`; 22 | return callGnosisSafeTransactionApi(network, endpointPath); 23 | }, 24 | (safeAddress, network) => `${safeAddress}_${network}` 25 | ); 26 | 27 | export const getGnosisSafeToken = memoize( 28 | async (network, tokenAddress): Promise => { 29 | const endpointPath = `/tokens/${tokenAddress}`; 30 | return callGnosisSafeTransactionApi(network, endpointPath); 31 | }, 32 | (tokenAddress, network) => `${tokenAddress}_${network}` 33 | ); 34 | -------------------------------------------------------------------------------- /src/components/SpaceProposalResultsList.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 33 | 34 | 43 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/components/SpaceWarningFlagged.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 37 | 38 | 39 | {{ $t('warningSpace') }} 40 | {{ 41 | $t('learnMore') 42 | }} 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/components/InputDate.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | {{ title }} 22 | 28 | 29 | {{ dateString }} 30 | 31 | 34 | 35 | 36 | 37 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/components/Ui/CollapsibleText.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 25 | 26 | 31 | 32 | 33 | 34 | 45 | {{ text }} 46 | 47 | 48 | 49 | 50 | 57 | -------------------------------------------------------------------------------- /src/assets/icons/coingecko.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | -------------------------------------------------------------------------------- /src/components/InputSelectPrivacy.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 42 | 43 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/components/TheHeader.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | {{ $t('header.title') }} 15 | 16 | 17 | {{ $t('header.description') }} 18 | 19 | 22 | 27 | {{ $t('createASpace') }} 28 | 29 | 33 | {{ $t('learnMore') }} 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/components/FooterSocials.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/composables/useSkinsFilter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Orders skins by spaces count and returns a list of skins 3 | * filtered by the search string (case insensitive). 4 | */ 5 | 6 | import { ref } from 'vue'; 7 | import { useApolloQuery } from '@/composables/useApolloQuery'; 8 | import { SKINS_COUNT_QUERY } from '@/helpers/queries'; 9 | import skins from '@/../snapshot-spaces/skins'; 10 | 11 | const skinsSpacesCount: any = ref(null); 12 | 13 | export function useSkinsFilter() { 14 | const loading = ref(false); 15 | 16 | const filterSkins = (q = '') => 17 | Object.keys(skins) 18 | .filter(s => s.toLowerCase().includes(q.toLowerCase())) 19 | .sort( 20 | (a, b) => 21 | (skinsSpacesCount.value[b] ?? 0) - (skinsSpacesCount.value[a] ?? 0) 22 | ); 23 | 24 | const { apolloQuery } = useApolloQuery(); 25 | 26 | async function getSkinsSpacesCount() { 27 | if (skinsSpacesCount.value) return; 28 | loading.value = true; 29 | const res = await apolloQuery( 30 | { 31 | query: SKINS_COUNT_QUERY 32 | }, 33 | 'skins' 34 | ); 35 | skinsSpacesCount.value = res.reduce( 36 | (obj: any, item: any) => ({ ...obj, [item.id]: item.spacesCount }), 37 | {} 38 | ); 39 | 40 | loading.value = false; 41 | } 42 | 43 | return { 44 | filterSkins, 45 | getSkinsSpacesCount, 46 | skinsSpacesCount, 47 | loadingSkins: loading 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /src/plugins/safeSnap/Proposal.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 37 | 48 | 49 | -------------------------------------------------------------------------------- /src/components/TreasuryAssetsList.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {{ wallet.name }} 29 | 30 | 37 | 38 | 43 | 44 | 45 | 46 | {{ $t('treasury.assets.empty') }} 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/components/BaseUser.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 23 | 29 | 38 | 39 | 40 | 41 | {{ username }} 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/components/SettingsLinkBlock.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 24 | 32 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/components/SpaceProposalsMenuFilter.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 52 | 53 | -------------------------------------------------------------------------------- /src/components/Ui/CollapsibleContent.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | {{ title }} 20 | 21 | 22 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 63 | -------------------------------------------------------------------------------- /src/components/ModalSpacesListItem.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {{ space.name }} 26 | 27 | 32 | 33 | 34 | {{ 35 | $tc('members', space.followers, { 36 | count: formatCompactNumber(space.followers) 37 | }) 38 | }} 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/components/ProfileSidebar.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 22 | 23 | 24 | 29 | 30 | 31 | 35 | 40 | 41 | 42 | {{ $t('profile.buttonEdit') }} 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/components/ProfileActivityListItem.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | {{ 29 | $t('profile.activity.votedFor', { 30 | choice: activity.vote?.choice 31 | ? `"${activity.vote?.choice}"` 32 | : '' 33 | }) 34 | }} 35 | 36 | 37 | {{ activity.title }} 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/components/InputUploadImage.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 34 | 35 | 36 | 41 | 42 | 43 | 44 | 45 | 59 | -------------------------------------------------------------------------------- /src/components/BaseButton.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 64 | -------------------------------------------------------------------------------- /src/components/BasePluginItem.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | v{{ plugin.version }} 20 | 21 | 22 | 23 | 28 | 29 | {{ plugin.author }} 30 | 31 | {{ 32 | $tc('inSpaces', [ 33 | formatCompactNumber(pluginsSpacesCount?.[plugin.key] ?? 0) 34 | ]) 35 | }} 36 | 37 | 38 | 42 | {{ $t('learnMore') }} 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/components/ModalReceipt.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | {{ $t('receipt') }} 16 | 17 | 18 | 19 | 20 | 21 | 22 | #{{ authorIpfsHash.slice(0, 7) }} 23 | 24 | 25 | 26 | 27 | 28 | #{{ relayerIpfsHash.slice(0, 7) }} 29 | 30 | 31 | 32 | 37 | 38 | {{ $t('verifyOnSignatorio') }} 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/composables/useValidationsFilter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Get list of validations and order them by popularity 3 | * Filter list of validations by a search string 4 | */ 5 | 6 | import { ref } from 'vue'; 7 | import { useApolloQuery } from '@/composables/useApolloQuery'; 8 | import { VALIDATIONS_COUNT_QUERY } from '@/helpers/queries'; 9 | import validations from '@snapshot-labs/snapshot.js/src/validations'; 10 | 11 | const validationsSpacesCount: any = ref(null); 12 | 13 | export function useValidationsFilter() { 14 | const loading = ref(false); 15 | 16 | const filterValidations = (q = ''): string[] => 17 | Object.keys(validations) 18 | .filter(v => JSON.stringify(v).toLowerCase().includes(q.toLowerCase())) 19 | .sort( 20 | (a, b) => 21 | (validationsSpacesCount.value?.[b] ?? 0) - 22 | (validationsSpacesCount.value?.[a] ?? 0) 23 | ); 24 | 25 | const { apolloQuery } = useApolloQuery(); 26 | 27 | async function getValidationsSpacesCount() { 28 | if (validationsSpacesCount.value) return; 29 | loading.value = true; 30 | const res = await apolloQuery( 31 | { 32 | query: VALIDATIONS_COUNT_QUERY 33 | }, 34 | 'validations' 35 | ); 36 | validationsSpacesCount.value = res.reduce( 37 | (obj: any, item: any) => ({ ...obj, [item.id]: item.spacesCount }), 38 | {} 39 | ); 40 | loading.value = false; 41 | } 42 | 43 | return { 44 | filterValidations, 45 | getValidationsSpacesCount, 46 | validationsSpacesCount, 47 | loadingValidations: loading 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /src/helpers/connectors.json: -------------------------------------------------------------------------------- 1 | { 2 | "injected": { 3 | "id": "injected", 4 | "name": "MetaMask" 5 | }, 6 | "walletconnect": { 7 | "id": "walletconnect", 8 | "name": "WalletConnect", 9 | "network": "1", 10 | "options": { 11 | "rpc": { 12 | "1": "https://rpc.snapshot.org/1", 13 | "4": "https://rpc.snapshot.org/4", 14 | "42": "https://rpc.snapshot.org/42", 15 | "10": "https://rpc.snapshot.org/10" 16 | } 17 | }, 18 | "icon": "ipfs://QmZRVqHpgRemw13aoovP2EaQdVtjzXRaQGQZsCLXWaNn9x" 19 | }, 20 | "walletlink": { 21 | "id": "walletlink", 22 | "name": "Coinbase Wallet", 23 | "network": "1", 24 | "options": { 25 | "appName": "Snapshot", 26 | "darkMode": false, 27 | "chainId": 1, 28 | "ethJsonrpcUrl": "https://rpc.snapshot.org/1" 29 | }, 30 | "icon": "ipfs://QmbJKEaeMz6qR3DmJSTxtYtrZeQPptVfnnYK72QBsvAw5q" 31 | }, 32 | "portis": { 33 | "id": "portis", 34 | "name": "Portis", 35 | "network": "1", 36 | "options": { 37 | "dappId": "3eb93706-c71d-456b-b4eb-322ea27f7d48", 38 | "network": "mainnet" 39 | }, 40 | "icon": "ipfs://QmNuLXa47xSrDNKRfpPNhoFTuoztvtWCcwGnPpT5MXJWMb" 41 | }, 42 | "stargazer": { 43 | "id": "stargazer", 44 | "name": "Stargazer", 45 | "icon": "ipfs://bafkreiapdizo36f3yeg7g6l46f7ahbbkyo4otufnfyqri6louysr3grpzy" 46 | }, 47 | "gnosis": { 48 | "id": "gnosis", 49 | "name": "Gnosis Safe", 50 | "icon": "ipfs://QmfJUHZLtRvadM7fvEJUWWxhS869KXXCMxPCr7TUqkwvUc", 51 | "hidden": true 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import path from 'path'; 3 | import { defineConfig } from 'vite'; 4 | import vue from '@vitejs/plugin-vue'; 5 | import ViteComponents from 'unplugin-vue-components/vite'; 6 | import visualizer from 'rollup-plugin-visualizer'; 7 | import Icons from 'unplugin-icons/vite'; 8 | import IconsResolver from 'unplugin-icons/resolver'; 9 | import { FileSystemIconLoader } from 'unplugin-icons/loaders'; 10 | 11 | export default defineConfig({ 12 | define: { 13 | 'process.env': process.env 14 | }, 15 | plugins: [ 16 | vue({ reactivityTransform: true }), 17 | ViteComponents({ 18 | directoryAsNamespace: true, 19 | resolvers: [ 20 | IconsResolver({ 21 | customCollections: ['s'], 22 | alias: { 23 | ho: 'heroicons-outline' 24 | } 25 | }) 26 | ] 27 | }), 28 | visualizer({ 29 | filename: './dist/stats.html', 30 | template: 'sunburst', 31 | gzipSize: true 32 | }), 33 | Icons({ 34 | compiler: 'vue3', 35 | customCollections: { 36 | // key as the collection name 37 | s: FileSystemIconLoader('./src/assets/icons', svg => 38 | svg.replace(/^ { 22 | return new Promise((resolove, reject) => { 23 | ensReverseRecordRequest(addresses) 24 | .then(reverseRecords => { 25 | const validNames = reverseRecords.map(n => 26 | namehash.normalize(n) === n ? n : '' 27 | ); 28 | const ensNames = Object.fromEntries( 29 | addresses.map((address, index) => { 30 | return [address, validNames[index]]; 31 | }) 32 | ); 33 | 34 | resolove(ensNames); 35 | }) 36 | .catch(error => { 37 | reject(error); 38 | }); 39 | }); 40 | } 41 | 42 | export async function getEnsAddress( 43 | addresses: string[] 44 | ): Promise<{ [k: string]: string } | null> { 45 | addresses = addresses.slice(0, 250); 46 | try { 47 | return await lookupAddresses(addresses); 48 | } catch (e) { 49 | console.log(e); 50 | return null; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/components/ModalDelegate.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | 25 | 26 | 27 | {{ $t('profile.about.delegate') }} 28 | 29 | 30 | 31 | 36 | {{ $t('delegate.to') }} 37 | 38 | 39 | {{ $t('space') }} 40 | 41 | 42 | 43 | 49 | {{ $t('confirm') }} 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/plugins/safeSnap/utils/coins.ts: -------------------------------------------------------------------------------- 1 | import { TokenAsset } from '@/helpers/interfaces'; 2 | 3 | export const ETHEREUM_COIN: TokenAsset = { 4 | name: 'Ether', 5 | decimals: 18, 6 | symbol: 'ETH', 7 | logoUri: 8 | 'https://safe-transaction-assets.gnosis-safe.io/chains/1/currency_logo.png', 9 | address: 'main' 10 | }; 11 | export const MATIC_COIN: TokenAsset = { 12 | name: 'MATIC', 13 | decimals: 18, 14 | symbol: 'MATIC', 15 | address: 'main', 16 | logoUri: 17 | 'https://safe-transaction-assets.gnosis-safe.io/chains/137/currency_logo.png' 18 | }; 19 | const EWC_COIN: TokenAsset = { 20 | name: 'Energy Web Token', 21 | symbol: 'EWT', 22 | address: 'main', 23 | decimals: 18, 24 | logoUri: 25 | 'https://safe-transaction-assets.gnosis-safe.io/chains/246/currency_logo.png' 26 | }; 27 | const XDAI_COIN: TokenAsset = { 28 | name: 'XDAI', 29 | symbol: 'XDAI', 30 | address: 'main', 31 | decimals: 18, 32 | logoUri: 33 | 'https://safe-transaction-assets.gnosis-safe.io/chains/100/currency_logo.png' 34 | }; 35 | const BNB_COIN: TokenAsset = { 36 | name: 'BNB', 37 | symbol: 'BNB', 38 | address: 'main', 39 | decimals: 18, 40 | logoUri: 41 | 'https://safe-transaction-assets.gnosis-safe.io/chains/56/currency_logo.png' 42 | }; 43 | 44 | export function getNativeAsset(network) { 45 | switch (parseInt(network)) { 46 | case 137: 47 | case 80001: 48 | return MATIC_COIN; 49 | case 100: 50 | return XDAI_COIN; 51 | case 246: 52 | return EWC_COIN; 53 | case 56: 54 | return BNB_COIN; 55 | } 56 | 57 | return ETHEREUM_COIN; 58 | } 59 | -------------------------------------------------------------------------------- /src/components/TheNavbar.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 15 | {{ $t('demoSite') }} 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 29 | snapshot 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 46 | 47 | {{ $tc('delegate.pendingTransaction', pendingCount) }} 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/plugins/safeSnap/components/Input/Amount.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 57 | 64 | {{ label }} 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/components/ModalTerms.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 24 | {{ $t('settings.terms.label') }} 25 | 26 | 27 | 28 | {{ 29 | $tc('modalTerms.mustAgreeTo', { 30 | action, 31 | spaceName: space.name || 'spaces' 32 | }) 33 | }} 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | {{ $t('cancel') }} 44 | 45 | 46 | 47 | 48 | {{ $t('agree') }} 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/helpers/covalent.ts: -------------------------------------------------------------------------------- 1 | import snapshot from '@snapshot-labs/snapshot.js'; 2 | 3 | const API_URL = 'https://api.covalenthq.com/v1'; 4 | const API_KEY = 'ckey_2d082caf47f04a46947f4f212a8'; 5 | export const ETHER_CONTRACT = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'; 6 | 7 | export async function getTokenBalances( 8 | address: string, 9 | chainId: string 10 | ): Promise { 11 | const tokenBalanceUrl = `${API_URL}/${chainId}/address/${address}/balances_v2/?quote-currency=USD&format=JSON&nft=false&no-nft-fetch=true&key=${API_KEY}`; 12 | const tokenBalances = await snapshot.utils.getJSON(tokenBalanceUrl); 13 | 14 | const validTokenBalances = tokenBalances.data.items.filter( 15 | item => 16 | item.contract_name && 17 | item.contract_ticker_symbol && 18 | item.logo_url && 19 | item.quote 20 | ); 21 | 22 | // If there is an ether item, move it to the top of the list 23 | const etherItem = validTokenBalances.find( 24 | item => item.contract_address === ETHER_CONTRACT 25 | ); 26 | if (etherItem) { 27 | const index = validTokenBalances.findIndex( 28 | item => item.contract_address === ETHER_CONTRACT 29 | ); 30 | validTokenBalances.splice(index, 1); 31 | validTokenBalances.unshift(etherItem); 32 | } 33 | 34 | return validTokenBalances; 35 | } 36 | 37 | export async function getTokenPrices( 38 | contract: string, 39 | chainId: string 40 | ): Promise { 41 | const tokenPricesUrl = `${API_URL}/pricing/historical_by_addresses_v2/${chainId}/USD/${contract}/?quote-currency=USD&format=JSON&key=${API_KEY}`; 42 | return await snapshot.utils.getJSON(tokenPricesUrl); 43 | } 44 | -------------------------------------------------------------------------------- /src/plugins/safeSnap/utils/multiSend.ts: -------------------------------------------------------------------------------- 1 | import { pack } from '@ethersproject/solidity'; 2 | import { Interface } from '@ethersproject/abi'; 3 | import { hexDataLength } from '@ethersproject/bytes'; 4 | 5 | import { SafeTransaction } from '@/helpers/interfaces'; 6 | import { MULTI_SEND_ABI, MULTI_SEND_VERSIONS } from '../constants'; 7 | 8 | export enum MULTI_SEND_VERSION { 9 | V1_3_0 = '1.3.0', 10 | V1_2_0 = '1.2.0', 11 | V1_1_1 = '1.1.1' 12 | } 13 | 14 | export function getMultiSend( 15 | network: number | string, 16 | version: MULTI_SEND_VERSION = MULTI_SEND_VERSION.V1_3_0 17 | ) { 18 | return MULTI_SEND_VERSIONS[version][network.toString()]; 19 | } 20 | 21 | export function encodeTransactions(transactions: SafeTransaction[]) { 22 | const values = transactions.map(tx => [ 23 | tx.operation, 24 | tx.to, 25 | tx.value, 26 | hexDataLength(tx.data || '0x'), 27 | tx.data || '0x' 28 | ]); 29 | 30 | const types = transactions.map(() => [ 31 | 'uint8', 32 | 'address', 33 | 'uint256', 34 | 'uint256', 35 | 'bytes' 36 | ]); 37 | 38 | return pack(types.flat(1), values.flat(1)); 39 | } 40 | 41 | export function createMultiSendTx( 42 | txs: SafeTransaction[], 43 | nonce: number, 44 | multiSendAddress: string 45 | ) { 46 | const multiSendContract = new Interface(MULTI_SEND_ABI); 47 | const transactionsEncoded = encodeTransactions(txs); 48 | const data = multiSendContract.encodeFunctionData('multiSend', [ 49 | transactionsEncoded 50 | ]); 51 | return { 52 | to: multiSendAddress, 53 | operation: '1', 54 | value: '0', 55 | nonce: nonce.toString(), 56 | data 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /src/components/SettingsDomainBlock.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {{ $t('learnMore') }} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 36 | 37 | 42 | 43 | 44 | 45 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/components/ModalVoteMessagePassport.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | {{ 14 | $t('validation.passport-gated.invalidVoterMessage', { 15 | amount: proposal.validation.params.operator === 'AND' ? 'all' : 'one', 16 | stamps: proposal.validation.params.stamps.join(', ') 17 | }) 18 | }} 19 | 20 | 21 | 22 | Gitcoin Passport 24 | 25 | 26 | 52 | 53 | -------------------------------------------------------------------------------- /src/components/BasePopover.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | 34 | 35 | 36 | 37 | {{ label }} 38 | 39 | 40 | 41 | 42 | 43 | 46 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/components/SpaceProposalResultsError.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 38 | 39 | {{ $t('resultsCalculating') }} 40 | 41 | 42 | {{ t('resultsError') }} 43 | 44 | 45 | 46 | {{ t('retry') }} 47 | 48 | 54 | 55 | {{ t('getHelp') }} 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/components/Ui/Collapsible.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | {{ number }} 18 | 19 | 24 | {{ title }} 25 | 26 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 68 | --------------------------------------------------------------------------------
5 | 6 |
17 | {{ $t('header.description') }} 18 |
46 | {{ $t('treasury.assets.empty') }} 47 |